diff --git a/README.md b/README.md index 13f5c77403f..f9d49d1407a 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,10 @@ -[![CI Status](https://github.com/se-edu/addressbook-level3/workflows/Java%20CI/badge.svg)](https://github.com/se-edu/addressbook-level3/actions) +# TuitionConnect + +[![CI Status](https://github.com/se-edu/addressbook-level3/workflows/Java%20CI/badge.svg)](https://github.com/AY2324S1-CS2103T-F10-4/tp/actions) ![Ui](docs/images/Ui.png) +### Description +TuitionConnect is a powerful **CLI** application designed to streamline the management of your tutoring business for **private tutors**. TuitionConnect helps to effortlessly *manage students*, *schedules* and *progress tracking* while ensuring *financial organization*. -* 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. +### Credits +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. diff --git a/build.gradle b/build.gradle index a2951cc709e..983434d96e3 100644 --- a/build.gradle +++ b/build.gradle @@ -66,7 +66,11 @@ dependencies { } shadowJar { - archiveFileName = 'addressbook.jar' + archiveFileName = 'tuitionconnect.jar' +} + +run { + enableAssertions = true } defaultTasks 'clean', 'test' diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml index eb761a9b9a7..ad5afc6f59a 100644 --- a/config/checkstyle/checkstyle.xml +++ b/config/checkstyle/checkstyle.xml @@ -16,7 +16,7 @@ - + diff --git a/docs/AboutUs.md b/docs/AboutUs.md index 1c9514e966a..d145e004652 100644 --- a/docs/AboutUs.md +++ b/docs/AboutUs.md @@ -9,51 +9,49 @@ You can reach us at the email `seer[at]comp.nus.edu.sg` ## Project team -### John Doe +### Lang Heran - + -[[homepage](http://www.comp.nus.edu.sg/~damithch)] -[[github](https://github.com/johndoe)] -[[portfolio](team/johndoe.md)] +[[github](http://github.com/heran9)] [[portfolio](team/heran9.md)] -* Role: Project Advisor - -### Jane Doe +* Role: Developer +* Responsibilities: Feature development, Documentation, Testing - +### Lim Yih Fei + -[[github](http://github.com/johndoe)] -[[portfolio](team/johndoe.md)] +[[github](http://github.com/yihfei)] +[[portfolio](team/yihfei.md)] -* Role: Team Lead -* Responsibilities: UI +* Role: Developer +* Responsibilities: Quality Assurance, UI -### Johnny Doe +### Lam Jin Heng Braydon - + -[[github](http://github.com/johndoe)] [[portfolio](team/johndoe.md)] +[[github](https://github.com/lambraydon)] [[portfolio](team/lambraydon.md)] * Role: Developer -* Responsibilities: Data +* Responsibilities: Feature Development, Quality Assurance, UI -### Jean Doe +### Armando Jovan Kusuma - + -[[github](http://github.com/johndoe)] -[[portfolio](team/johndoe.md)] +[[github](https://github.com/jovkusuma)] +[[portfolio](team/jovkusuma.md)] * Role: Developer -* Responsibilities: Dev Ops + Threading +* Responsibilities: Feature Development, Quality Assurance, Testing -### James Doe +### Winston Leonard Prayonggo - + -[[github](http://github.com/johndoe)] -[[portfolio](team/johndoe.md)] +[[github](http://github.com/WinstonLeonard)] +[[portfolio](team/winstonleonard.md)] * Role: Developer -* Responsibilities: UI +* Responsibilities: Refactoring code, Feature Development, Testing diff --git a/docs/DeveloperGuide.md b/docs/DeveloperGuide.md index 8a861859bfd..52efb001a91 100644 --- a/docs/DeveloperGuide.md +++ b/docs/DeveloperGuide.md @@ -2,14 +2,72 @@ layout: page title: Developer Guide --- -* Table of Contents -{:toc} +## Table of Contents + + * [**Acknowledgements**](#acknowledgements) + * [**Setting up, getting started**](#setting-up-getting-started) + * [**Design**](#design) + * [Architecture](#architecture) + * [UI component](#ui-component) + * [Logic component](#logic-component) + * [Model component](#model-component) + * [Storage component](#storage-component) + * [Common classes](#common-classes) + * [**Implementation**](#implementation) + * [Add feature](#add-feature) + * [Design considerations:](#design-considerations) + * [List feature](#list-feature) + * [Design considerations:](#design-considerations-1) + * [Find feature](#find-feature) + * [Design considerations:](#design-considerations-2) + * [Edit feature](#edit-feature) + * [Design considerations:](#design-considerations-3) + * [Find Free Time feature](#find-free-time-feature) + * [Design Considerations](#design-considerations-4) + * [Calculate monthly revenue](#calculate-monthly-revenue) + * [Design Considerations](#design-considerations-5) + * [Undo/redo feature](#undoredo-feature) + * [Design considerations:](#design-considerations-6) + * [Mark paid/unpaid features](#mark-paidunpaid-features) + * [Design considerations:](#design-considerations-7) + * [**Documentation, logging, testing, configuration, dev-ops**](#documentation-logging-testing-configuration-dev-ops) + * [**Appendix: Requirements**](#appendix-requirements) + * [Product scope](#product-scope) + * [User stories](#user-stories) + * [Use cases](#use-cases) + * [Non-Functional Requirements](#non-functional-requirements) + * [Glossary](#glossary) + * [**Appendix: Instructions for manual testing**](#appendix-instructions-for-manual-testing) + * [Launch and shutdown](#launch-and-shutdown) + * [List tutee(s)](#list-tutees) + * [Adding a tutee](#adding-a-tutee) + * [Deleting a tutee](#deleting-a-tutee) + * [Finding a tutee](#finding-a-tutee) + * [Editing a tutee](#editing-a-tutee) + * [Find free time](#find-free-time) + * [Marking a tutee as paid](#marking-a-tutee-as-paid) + * [Marking a tutee as not paid](#marking-a-tutee-as-not-paid) + * [Listing all unpaid tutees](#listing-all-unpaid-tutees) + * [Undo command](#undo-command) + * [Redo command](#redo-command) + * [Manually editing data file](#manually-editing-data-file) + * [**Planned Enhancements**](#planned-enhancements) + * [Batch Processing for Paid Command](#batch-processing-for-paid-command) + * [Scheduled Unpaid Marking](#scheduled-unpaid-marking) + * [Find using multiple keywords](#find-using-multiple-keywords) + * [Maximum PayRate](#maximum-payrate) + * [Prevent Commands meant to Modify Tutee Data from Not Changing the Data](#prevent-commands-meant-to-modify-tutee-data-from-not-changing-the-data) + * [Enable Group Lessons](#enable-group-lessons) + * [Enhance Edit Feature](#enhance-edit-feature) + + -------------------------------------------------------------------------------------------------------------------- ## **Acknowledgements** -* {list here sources of all reused/adapted ideas, code, documentation, and third-party libraries -- include links to the original source as well} +* This project is adapted from **[AddressBook 3(AB3)](https://github.com/nus-cs2103-AY2324S1/tp)** +* Undo and Redo features are adapted from proposed implementations from **[AddressBook 3(AB3)](https://github.com/nus-cs2103-AY2324S1/tp)** -------------------------------------------------------------------------------------------------------------------- @@ -36,7 +94,7 @@ Given below is a quick overview of main components and how they interact with ea **Main components of the architecture** -**`Main`** (consisting of classes [`Main`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/Main.java) and [`MainApp`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/MainApp.java)) is in charge of the app launch and shut down. +**`Main`** (consisting of classes [`Main`](https://github.com/AY2324S1-CS2103T-F10-4/tp/blob/master/src/main/java/seedu/address/Main.java) and [`MainApp`](https://github.com/AY2324S1-CS2103T-F10-4/tp/blob/master/src/main/java/seedu/address/MainApp.java)) is in charge of the app launch and shut down. * At app launch, it initializes the other components in the correct sequence, and connects them up with each other. * At shut down, it shuts down the other components and invokes cleanup methods where necessary. @@ -68,13 +126,13 @@ The sections below give more details of each component. ### UI component -The **API** of this component is specified in [`Ui.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/ui/Ui.java) +The **API** of this component is specified in [`Ui.java`](https://github.com/AY2324S1-CS2103T-F10-4/tp/blob/master/src/main/java/seedu/address/ui/Ui.java) ![Structure of the UI Component](images/UiClassDiagram.png) -The UI consists of a `MainWindow` that is made up of parts e.g.`CommandBox`, `ResultDisplay`, `PersonListPanel`, `StatusBarFooter` etc. All these, including the `MainWindow`, inherit from the abstract `UiPart` class which captures the commonalities between classes that represent parts of the visible GUI. +The UI consists of a `MainWindow` that is made up of parts e.g.`CommandBox`, `ResultDisplay`, `PersonListPanel`, `ScheduleListPanel`, `StatusBarFooter` etc. All these, including the `MainWindow`, inherit from the abstract `UiPart` class which captures the commonalities between classes that represent parts of the visible GUI. -The `UI` component uses the JavaFx UI framework. The layout of these UI parts are defined in matching `.fxml` files that are in the `src/main/resources/view` folder. For example, the layout of the [`MainWindow`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/ui/MainWindow.java) is specified in [`MainWindow.fxml`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/resources/view/MainWindow.fxml) +The `UI` component uses the JavaFx UI framework. The layout of these UI parts are defined in matching `.fxml` files that are in the `src/main/resources/view` folder. For example, the layout of the [`MainWindow`](https://github.com/AY2324S1-CS2103T-F10-4/tp/blob/master/src/main/java/seedu/address/ui/MainWindow.java) is specified in [`MainWindow.fxml`](https://github.com/AY2324S1-CS2103T-F10-4/tp/blob/master/src/main/resources/view/MainWindow.fxml) The `UI` component, @@ -85,7 +143,7 @@ The `UI` component, ### Logic component -**API** : [`Logic.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/logic/Logic.java) +**API** : [`Logic.java`](https://github.com/AY2324S1-CS2103T-F10-4/tp/blob/master/src/main/java/seedu/address/logic/Logic.java) Here's a (partial) class diagram of the `Logic` component: @@ -114,33 +172,27 @@ How the parsing works: * All `XYZCommandParser` classes (e.g., `AddCommandParser`, `DeleteCommandParser`, ...) inherit from the `Parser` interface so that they can be treated similarly where possible e.g, during testing. ### Model component -**API** : [`Model.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/model/Model.java) +**API** : [`Model.java`](https://github.com/AY2324S1-CS2103T-F10-4/tp/blob/master/src/main/java/seedu/address/model/Model.java) The `Model` component, -* stores the address book data i.e., all `Person` objects (which are contained in a `UniquePersonList` object). +* stores the tutee 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) -
: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.
- - - -
- ### Storage component -**API** : [`Storage.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/storage/Storage.java) +**API** : [`Storage.java`](https://github.com/AY2324S1-CS2103T-F10-4/tp/blob/master/src/main/java/seedu/address/storage/Storage.java) The `Storage` component, -* can save both address book data and user preference data in JSON format, and read them back into corresponding objects. +* can save both Tuition Connect 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`) @@ -154,37 +206,236 @@ Classes used by multiple components are in the `seedu.addressbook.commons` packa This section describes some noteworthy details on how certain features are implemented. -### \[Proposed\] Undo/redo feature +### Add feature +The `AddCommand` extends the `Command` class. While mostly similar to `delete` illustrated above, the command contains +checks to prevent any duplicate `Person` object (i.e. same name and phone number) as well as clashes in schedules. +If it passes these checks, the person is added into the system. + +`AddCommand` takes in the following fields: +* **Name (Compulsory field)**: String composed of character between A-Z and a-z. +* **Phone number (Compulsory field)**: Any number at least 3 digits long. +* **Email (Compulsory field)** String with restrictions in characters (XXXXXXXX@emaildomain) +* **Address (Compulsory field)**: String without restriction in characters. +* **Subject (Compulsory field)**: String without restriction in characters. +* **Day (Compulsory field)**: String with restrictions in characters, non-case sensitive (Mon/Monday/Tue/Tuesday/Wed/Wednesday/Thu/Thursday/Fri/Friday/Sat/Saturday/Sun/Sunday). +* **Begin (Compulsory field)**: String with restrictions (HHMM). +* **End (Compulsory field)**: String with restrictions (HHMM). +* **PayRate (Compulsory field)**: String with restrictions in characters, only numbers allowed (no negative numbers) + +The following sequence diagram shows how the add command works. + +![AddSequenceDiagram](images/AddSequenceDiagram.png) + +The following activity diagram summarizes what happens when a user executes a new command: + +![AddActivityDiagram](images/AddActivityDiagram.png) + +#### Design considerations: + +**Aspect: How add executes:** + +* **Alternative 1 (current choice):** All fields must be included in a single command input. + * Pros: Easy to implement. + * Cons: Command input may be too long and less user-friendly. + +* **Alternative 2**: Allow for optional parameters with default values, with the tutee's name and phone being the compulsory ones. + * Pros: More user-friendly, command will not be too lengthy. + * Cons: Harder to implement. + +### List feature + +There are three commands that deal with listing tutees: + +1. `ListCommand` - Shows the current list of all tutees in the list +2. `ListByDayCommand` - Shows the current list of tutees who have lessons on a specified day +3. `ListUnPaidCommand` - Shows the current list of tutees who have not paid + +The `ListCommand` extends the `Command` class. Both the `ListByDayCommand` and the `ListUnPaidCommand` extend the `ListCommand` class. All three commands override `Command#execute`. +The `ListCommandParser` is responsible for returning the appropriate `ListCommand` based on the command format. + + + +The `ListByDayCommand` is initialised with a `DayPredicate` and updates the `FilteredPersonList` to only display Persons whose `Day` field matches the specified input. + +The following sequence diagram shows how the list by day command works. + +![ListByDaySequenceDiagram](images/ListByDaySequenceDiagram.png) + +The `ListUnPaidCommand` follows a similar implementation to `ListByDayCommand`. It is initialised with a `PaidPredicate` instead and updates +the `FilteredPersonList` to only display Persons whose `isPaid` field is false. + +#### Design considerations: + +**Aspect: How to implement `ListByDayCommand` and `ListUnPaidCommand`:** + +* **Alternative 1 (current choice):** Extend the `ListCommand` class. + * Pros: Greater use of OOP. + * Cons: Harder to implement. + +* **Alternative 2:** Individual command class without extending `ListCommand`. + * Pros: Easier to implement. + * Cons: Less abstraction. + +### Find feature +The `FindCommand` extends the `Command` class. It allows the user to find for tutees by specifying their names and/or +subject using their prefixes. Both parameters are optional, but at least one of them must be specified for the `find` +command to work properly. + +`NameContainsKeywordsPredicate` is a class which takes a list of strings as input, and is used to test whether the input +matches any of the names inside the tutee list. + +`SubjectContainsKeywordsPredicate` is a class which takes a list of string as input, and is used to test whether the input +matches any of the subjects inside the tutee list. -#### Proposed Implementation +As for `NameSubjectPredicate`, it takes in two parameters `NameContainsKeywordsPredicate` and +`SubjectContainsKeywordsPredicate` as to accommodate for both input of fields n/ and sb/. -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: +However, since the method `updateFilteredPersonList` can only take one parameter, the merging of both +`NameContainsKeywordsPredicate` and `SubjectContainsKeywordsPredicate` into `NameSubjectPredicate` is the implementation +we decided to go with. -* `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. +`FindCommand` takes in the following fields: +* **Name (Optional field)**: String composed of character between A-Z and a-z. +* **Subject (Optional field)**: String without restriction in characters. + +The following sequence diagram shows how the edit command works. +![FindSequenceDiagram](images/FindSequenceDiagram.png) + +#### Design considerations: + +**Aspect: How find executes:** + +* **Alternative 1 (current choice):** Find tutees based on inputs for prefixes n/ and sb/. + * Pros: More sophisticated as it can search for subjects rather than only for name. + * Cons: Less user-friendly for beginners as it requires an extra step of inputting prefixes. +* **Alternative 2:** Find tutees based on their name. + * Pros: More user-friendly as the command format would only be `find [name]`. + * Cons: Users cannot search for tutees by subject. + +### Edit feature +The `EditCommand` extends the `Command` class. It allows the user to edit fields of the tutee by specifying the index +of the tutee. The command contains checks to prevent any duplicate `Person` object (i.e. same name and phone number) +as well as clashes in schedules. If it passes these checks, the person is edited successfully. + +`EditCommand` takes in the following fields: +* **Index (Compulsory Field)**: Numbers between 1 to the number of people inside the list. +* **Name (Optional field)**: String composed of character between A-Z and a-z. +* **Phone number (Optional field)**: Any number at least 3 digits long. +* **Email (Optional field)** String with restrictions in characters (XXXXXXXX@emaildomain) +* **Address (Optional field)**: String without restriction in characters. +* **Subject (Optional field)**: String without restriction in characters. +* **Day (Optional field)**: String with restrictions in characters, non-case sensitive (Mon/Monday/Tue/Tuesday/Wed/Wednesday/Thu/Thursday/Fri/Friday/Sat/Saturday/Sun/Sunday). +* **Begin (Optional field)**: String with restrictions (HHMM). +* **End (Optional field)**: String with restrictions (HHMM). +* **PayRate (Optional field)**: String with restrictions in characters, only numbers allowed (no negative numbers) + +The following sequence diagram shows how the edit command works. +![EditSequenceDiagram](images/EditSequenceDiagram.png) + +The following activity diagram summarizes what happens when a user executes an edit command: +![EditActivityDiagram](images/EditActivityDiagram.png) + +#### Design considerations: + +**Aspect: How edit executes:** + +* **Alternative 1 (current choice):** User specify which fields to edit by their prefixes. + * Pros: User can edit the fields that require changes by specifying their prefix. + * Cons: Command input may be too long and less user-friendly. +* **Alternative 2:** Users cannot edit tutees that are already added, and can only do delete and re-adding + of tutees whenever changes are necessary. + * Pros: Less prone to bugs, and is simpler for developers to implement. + * Cons: Not user-friendly and takes multiple steps for the user. + +### Find Free Time feature + +The `freeTime` Command extends the `Command` class. + +`freeTime` takes in the following fields: +* **Day (Compulsory field)**: String with restrictions in characters, non-case sensitive (Mon/MondayTue/Tuesday/Wed/Wednesday/Thu/Thursday/Fri/Friday/Sat/Saturday/Sun/Sunday). +* **Duration (Compulsory field)**: Positive Integer to represent duration in **minutes**. +* **Begin (Compulsory field)**: String with restrictions (HHMM). +* **End (Compulsory field)**: String with restrictions (HHMM). + +It displays a list of timeslots where the user is free on that _Day_, starting from _Begin_ to _End_. The timeslots listed down +must also be greater than the duration provided. + +The following sequence diagram shows how the `freeTime` command works. +![FreeTimeSequenceDiagram](images/FreeTimeSequenceDiagram.png) + +Variables inside the sequence diagram: +* toFind, Interval: Both are instances of the `Interval` Class which encapsulates the `Day`, `Duration`, `Begin`, `End` fields +* results: A list of strings where the user is busy on the specified given `Interval` class. +* timeslots: A list of timeslots after parsing the list of strings. +* availableTime: A list of timeslots where the user is free. + +The following activity diagram summarizes what happens when a user executes a `freeTime` command: +![FreeTimeActivityDiagram](images/FreeTimeActivityDiagram.png) + +#### Design Considerations +**Aspect: How `freeTime` executes:** + +* **Alternative 1 (current choice):** The command first finds timeslots when the user is busy on that _Day_ by looking at the tutees' schedules inside the + `UniquePersonList`. The TimeSlot Class finds free time based on the list of + timeslots when the user is busy, and then returns a list of timeslots where the user is free. (Each timeslot is between _Begin_ and _End_, + and is at least _Duration_ long) + * Pros: Command is short and simple to use. + * Cons: During the first round of user-testing, some new users were confused on how to use the command. + + +### Calculate monthly revenue + + +The `RevenueCommand` extends the `command class`. The command first gets a list containing all tutees. +The total monthly revenue can be calculated now by iterating through the list and calling `Person#getMonthlyFee`.
+ +The total monthly revenue is calculated as such:
+*Total Monthly Revenue* = Sum of every tutee's `monthlyFee` + +The following sequence diagram shows how the `RevenueCommand` works: +![RevenueSequenceDiagram.png](images/RevenueSequenceDiagram.png) + +#### Design Considerations +**Aspect: How `monthlyFee` is calculated:** + +* **Alternative 1 (current choice):** Calculate `monthlyFee` only when executing `RevenueCommand`. + * Pros: Up-to-date revenue figure as `PayRate` value and number of lessons monthly may change over time. + * Cons: Potentially more method calls to generate same value. + +* **Alternative 2:** Calculate `monthlyFee` when instantiating `Person` and include it as a field in `Person`. + * Pros: Readily accessible `monthlyFee` value. + * Cons: Have to implement logic to update `monthlyFee` when `PayRate` value and number of lessons monthly changes. + + +### Undo/redo feature + +The undo/redo mechanism is facilitated by `VersionedAddressBook`. It extends `AddressBook` with an undo/redo history, stored internally as an `addressBookStateList` and `currentStatePointer`. Additionally, it implements the following operations: + +* `VersionedAddressBook#commit()` — Saves the current tutee list state in its history. +* `VersionedAddressBook#undo()` — Restores the previous tutee list state from its history. +* `VersionedAddressBook#redo()` — Restores a previously undone tutee list state from its history. These operations are exposed in the `Model` interface as `Model#commitAddressBook()`, `Model#undoAddressBook()` and `Model#redoAddressBook()` respectively. Given below is an example usage scenario and how the undo/redo mechanism behaves at each step. -Step 1. The user launches the application for the first time. The `VersionedAddressBook` will be initialized with the initial address book state, and the `currentStatePointer` pointing to that single address book state. +Step 1. The user launches the application for the first time. The `VersionedAddressBook` will be initialized with the initial tutee list state, and the `currentStatePointer` pointing to that single tutee list state. ![UndoRedoState0](images/UndoRedoState0.png) -Step 2. The user executes `delete 5` command to delete the 5th person in the address book. The `delete` command calls `Model#commitAddressBook()`, causing the modified state of the address book after the `delete 5` command executes to be saved in the `addressBookStateList`, and the `currentStatePointer` is shifted to the newly inserted address book state. +Step 2. The user executes `delete 5` command to delete the 5th person in the tutee list. The `delete` command calls `Model#commitAddressBook()`, causing the modified state of the tutee list after the `delete 5` command executes to be saved in the `addressBookStateList`, and the `currentStatePointer` is shifted to the newly inserted tutee list state. ![UndoRedoState1](images/UndoRedoState1.png) -Step 3. The user executes `add n/David …​` to add a new person. The `add` command also calls `Model#commitAddressBook()`, causing another modified address book state to be saved into the `addressBookStateList`. +Step 3. The user executes `add n/David …​` to add a new person. The `add` command also calls `Model#commitAddressBook()`, causing another modified tutee list state to be saved into the `addressBookStateList`. ![UndoRedoState2](images/UndoRedoState2.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`. +
:information_source: **Note:** If a command fails its execution, it will not call `Model#commitAddressBook()`, so the tutee list state will not be saved into the `addressBookStateList`.
-Step 4. The user now decides that adding the person was a mistake, and decides to undo that action by executing the `undo` command. The `undo` command will call `Model#undoAddressBook()`, which will shift the `currentStatePointer` once to the left, pointing it to the previous address book state, and restores the address book to that state. +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 tutee list state, and restores the tutee list to that state. ![UndoRedoState3](images/UndoRedoState3.png) @@ -201,17 +452,17 @@ The following sequence diagram shows how the undo operation works:
-The `redo` command does the opposite — it calls `Model#redoAddressBook()`, which shifts the `currentStatePointer` once to the right, pointing to the previously undone state, and restores the address book to that state. +The `redo` command does the opposite — it calls `Model#redoAddressBook()`, which shifts the `currentStatePointer` once to the right, pointing to the previously undone state, and restores the tutee list to that state. -
:information_source: **Note:** If the `currentStatePointer` is at index `addressBookStateList.size() - 1`, pointing to the latest address book state, then there are no undone AddressBook states to restore. The `redo` command uses `Model#canRedoAddressBook()` to check if this is the case. If so, it will return an error to the user rather than attempting to perform the redo. +
:information_source: **Note:** If the `currentStatePointer` is at index `addressBookStateList.size() - 1`, pointing to the latest tutee list state, then there are no undone AddressBook states to restore. The `redo` command uses `Model#canRedoAddressBook()` to check if this is the case. If so, it will return an error to the user rather than attempting to perform the redo.
-Step 5. The user then decides to execute the command `list`. Commands that do not modify the address book, such as `list`, will usually not call `Model#commitAddressBook()`, `Model#undoAddressBook()` or `Model#redoAddressBook()`. Thus, the `addressBookStateList` remains unchanged. +Step 5. The user then decides to execute the command `list`. Commands that do not modify the tutee list, such as `list`, will usually not call `Model#commitAddressBook()`, `Model#undoAddressBook()` or `Model#redoAddressBook()`. Thus, the `addressBookStateList` remains unchanged. ![UndoRedoState4](images/UndoRedoState4.png) -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. +Step 6. The user executes `clear`, which calls `Model#commitAddressBook()`. Since the `currentStatePointer` is not pointing at the end of the `addressBookStateList`, all tutee list states after the `currentStatePointer` will be purged. Reason: It no longer makes sense to redo the `add n/David …​` command. This is the behavior that most modern desktop applications follow. ![UndoRedoState5](images/UndoRedoState5.png) @@ -221,22 +472,57 @@ The following activity diagram summarizes what happens when a user executes a ne #### Design considerations: -**Aspect: How undo & redo executes:** +**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. +* **Alternative 1 (current choice):** Saves the entire tutee list. + * Pros: Easy to implement. + * Cons: May have performance issues in terms of memory usage. * **Alternative 2:** Individual command knows how to undo/redo by itself. - * Pros: Will use less memory (e.g. for `delete`, just save the person being deleted). - * Cons: We must ensure that the implementation of each individual command are correct. + * Pros: Will use less memory (e.g. for `delete`, just save the person being deleted). + * Cons: We must ensure that the implementation of each individual command are correct. -_{more aspects and alternatives to be added}_ +### Mark paid/unpaid features +The proposed mark paid/check paid mechanism can check whether the person has paid or not by implementing a new boolean field 'paid' in the person object, it implements the following operations: -### \[Proposed\] Data archiving +* `paid [index]` — Mark the person at the index as paid. +* `unpaid [index]` — Mark the person at the index as not paid. +* `list unpaid` — List all the persons who haven't paid in the list. +* `unpaidAll` — Reset all students' payment status to not paid. + +The following sequence diagram shows how paid command works: + +![PaidSequenceDiagram.png](images/PaidSequenceDiagram.png) + +The unpaid command works similar to the paid command. + +The following sequence diagram shows how unpaidAll command works: + +![UnpaidAllSequenceDiagram.png](images/UnpaidAllSequenceDiagram.png) + +#### Design considerations: + +**Aspect: The choice of paid data type:** + +* **Alternative 1 (current choice):** Use simple boolean value. + * Pros: Easy to implement, fits the requirement: two status (paid, not paid). + * Cons: Different from all other fields in the person class, hard to maintain. + +* **Alternative 2:** Create a new paid class. + * Pros: Fits the other fields in the class. + * Cons: Hard to implement, waste of source (such as code storage, might affect the efficiency of the code). + +**Aspect: How to implement mark paid features:** + +* **Alternative 1 (current choice):** Create a new person, set everything else the same as before, and set paid as true. + * Pros: Since we created a new person, the command works individually and not depends on the other commands. + * Cons: Hard to implement. + +* **Alternative 2:** Use the edit command to edit the paid status of the person. + * Pros: Easy to implement. + * Cons: The paid command will rely on the edit commands, which violates the principle to reduce correlation between classes. -_{Explain here how the data archiving feature will be implemented}_ -------------------------------------------------------------------------------------------------------------------- @@ -255,74 +541,304 @@ _{Explain here how the data archiving feature will be implemented}_ ### Product scope -**Target user profile**: +**Target user profile**: Private tutors not affiliated to any tuition organisations -* has a need to manage a significant number of contacts +* has a need to manage multiple tutees +* has a need for managing personal tutoring schedule * prefer desktop apps over other types * can type fast * prefers typing to mouse interactions * is reasonably comfortable using CLI apps -**Value proposition**: manage contacts faster than a typical mouse/GUI driven app +**Value proposition**: It is tedious for tutors to keep track of multiple students and this is done conventionally through calendar applications. Simplify tutoring business with TuitionConnect. Effortlessly manage students, schedules and progress tracking while ensuring financial organization in an all in one product at a faster rate than non CLI applications. ### 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…​ | +|----------|---------|---------------------------------------------------------|-----------------------------------------------------------------| +| `* * *` | tutor | view a list of all tutees | see whoever are my tutees that I teach | +| `* * *` | tutor | view the specific details of a single tutee | see the different informations tailored to the tutee | +| `* * *` | tutor | add a new tutee | keep track of my tutees that I teach | +| `* * *` | tutor | find a tutee | search for a specific tutee that I teach | +| `* * *` | tutor | edit their details | account for changes in their information e.g. change in address | +| `* *` | tutor | remove tutees from the list | keep track of tutees that I have stopped teaching | +| `* *` | tutor | mark students that have already paid | keep track of students' payment statuses | +| `* *` | tutor | check all students who haven't paid | easily remind students who haven't paid | +| `* *` | tutor | undo and redo commands I made in the application | easily revert any mistakes | +| `* *` | tutor | calculate my total monthly revenue | better financially plan for my tutoring business | +| `* *` | tutor | view a list tutees whose lessons fall on a specific day | be reminded if I have any classes on that particular day | +| `* *` | tutor | find timeslots when I am free | prevent schedle clashes when I add or edit a tutee's schedule | + ### 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 `TuitionConnect` and the **Actor** is the `user`, unless specified otherwise) + +**Use case: UC01 - List all tutees** + +**MSS** + +1. User requests to list all tutees. +2. System shows all tutees. +3. System displays the success message. + + Use case ends. + +**Extensions** + +- 2a. The list of tutees is empty. + - 2a1. System informs the user that the list is empty. -**Use case: Delete a person** + Use case ends. +
+
+ +**Use case: UC02 - List tutees whose lessons are on a specified day** **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 all tutees whose lessons are on Monday. +2. System shows all tutees whose lessons are on Monday. Use case ends. **Extensions** -* 2a. The list is empty. +- 2a. The list of tutees is empty. + - 2a1. System informs the user that the list is empty. Use case ends. +
+
+ +**Use case: UC03 - Add a tutee** + +**MSS** + +1. User requests to add a tutee. +2. System adds a tutee. +3. System displays the success message. + + Use case ends. + +**Extensions** + +- 1a. User inputs incomplete tutee data.
+ - 1a1. System informs user of the incomplete tutee data. + + Use case resumes at 1. + +- 1b. User inputs name and phone number that already exists in the tutee list. + - 1b1. System informs user of duplicate tutees. + + Use case resumes at 1. + +- 1c. User inputs a clash in schedule. + - 1c1. System informs user of the clash in schedules. + + Use case resumes at 1. -* 3a. The given index is invalid. +- 1d. User inputs begin time which is greater than the end time. + - 1d1. System informs that begin time must be smaller than the end time. + + Use case resumes at 1. + +
+
+ +**Use case: UC04 - Delete a tutee** + +**MSS** + +1. User views the list of tutees. +2. User requests to delete a tutee. +3. System deletes the tutee. +4. System displays the success message. + + Use case ends. + +**Extensions** + +- 2a. The tutee that the user is trying to delete does not exist in the list. + - 2a1. System informs that user does not exist. +
+
+ +**Use case: UC05 - Edit a tutee** + +**MSS** + +1. User views the list of tutees. +2. User requests to edit a tutee. +3. System edits the tutee. +4. System displays the success message. + + Use case ends. + +**Extensions** + +- 2a. The schedule of the edited tutee clashes with an existing schedule. + - 2a1. System informs that there is a clash in schedules. + + Use case resumes at 2. + +- 2b. The edited begin time is after than the original end time. + - 2b1. System informs that begin time must be smaller than the end time. + + Use case resumes at 2. + +- 2c. The edited end time is before the original begin time. + - System informs that begin time must be smaller than the end time. + + Use case resumes at 2. + +- 2d. The edited begin time is after the edited begin time. + - 2d1. System informs that begin time must be smaller than the end time. + + Use case resumes at 2. +
+
+ +**Use case: UC06 - Find a tutee** + +**MSS** + +1. User requests to find a tutee. +2. System finds the tutee. +3. System displays the success message. + +**Extensions** - * 3a1. AddressBook shows an error message. +- 2a. The user inputs more than one word for name field. + - 2a1. System informs that name can only take one word. - Use case resumes at step 2. + Use case resumes at 2. -*{More to be added}* +- 2b. The user inputs more than one word for subject field. + - 2b1. System informs that subject can only take one word. + + Use case resumes at 2. +
+
+ +**Use case: UC07 - Mark a tutee as paid** + +**MSS** + +1. User views the list of tutees. +2. User requests mark the specific tutee as paid. +3. System marks the tutee as paid. +4. System displays the success message. + + Use case ends. + +**Extensions** + +- 2a. The tutee that the user is trying to mark as paid does not exist in the list. + - 2a1. System informs that user does not exist. +
+
+ +**Use case: UC08 - Reset all tutees in the list to not paid** + +**MSS** + +1. User views the list of tutees. +2. User requests mark all the tutees in the current list as not paid. +3. System marks all the tutee in the list as not paid. +4. System displays the success message. + + Use case ends. +
+
+ +**Use case: UC09 - Undo a command** + +**MSS** +1. User requests to undo. +2. System updates the tutee data to the previous state. +3. System informs user that the command is undone. + + Use case ends. + +**Extensions** +* 1a. No command to be undone. + * 1a1. System informs user that there is nothing to undo. + + Use case ends. +
+
+ +**Use case: UC10 - Redo a command** + +**MSS** +1. User requests to redo. +2. System updates the tutee data to the next state. +3. System informs user that the command is redone. + + Use case ends. + +**Extensions** +* 1a. No command to be redone. + * 1a1. System informs user that there is nothing to redo. + + Use case ends. +
+
+ +**Use case: UC11 - Finding free time** + +**MSS** +1. User requests to find free time +2. System shows the list of available free time + +**Extensions** +- 2a. The user does not have any free slots available. +<<<<<<< HEAD + - 2a1. System informs user that there is no available timeslots. +
+
+ +**Use case: UC12 - Get monthly revenue** + +**MSS** + +1. User requests for monthly revenue. +2. System displays the monthly revenue figure. + + 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. -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}* +2. The software should be platform independent (i.e. work on the Windows, Linux, and OS-X platforms). +3. Should be able to hold up to 1000 tutees without a noticeable sluggishness in performance for typical usage. +4. A user with above average typing speed for regular English text (i.e. not code, not system admin commands) should be able to accomplish most of the tasks faster using commands than using the mouse. +5. The product is a single user product (i.e. The data file created by one user cannot be accessed by another user during regular operations) +6. The data should be stored locally and should be in a human editable text file. +7. The product should not use a DBMS to store data. +8. The software should work without requiring an installer. +9. The software should not depend on a remote server. +10. The GUI should work well (i.e., should not cause any resolution-related inconveniences to the user) for, standard screen resolutions 1920x1080 and higher, and, for screen scales 100% and 125%. +11. the GUI should be usable (i.e., all functions can be used even if the user experience is not optimal) for, resolutions 1280x720 and higher, and, for screen scales 150%. +12. The product should be packaged into a single JAR file. ### 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 (Operating System): Windows, Linux, Unix, OS-X +* CLI: Command Line Interface, receives commands from user in the form of lines of text +* GUI: Graphical User Interface, a system of interactive user components for computer software +* Command: An instruction for the application to execute +* Timeslot: An interval of time from HH:MM to HH:MM +* Prefix: An abbreviation for the name of the parameter. Prefix should be entered before the actual parameter in a command and always ends with a slash (/). +* MSS: Main success scenario +
-------------------------------------------------------------------------------------------------------------------- ## **Appendix: Instructions for manual testing** @@ -338,40 +854,319 @@ testers are expected to do more *exploratory* testing. 1. Initial launch - 1. Download the jar file and copy into an empty folder + 1. Download the jar file and copy into an empty folder - 1. Double-click the jar file Expected: Shows the GUI with a set of sample contacts. The window size may not be optimum. + 1. Double-click the jar file Expected: Shows the GUI with a set of sample contacts. The window size may not be optimum. 1. Saving window preferences - 1. Resize the window to an optimum size. Move the window to a different location. Close the window. + 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.
+ 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 …​ }_ +### List tutee(s) + +1. Listing tutee(s) + + 1. Prerequisites: At least one tutee in the tutee list. + + 2. Test case: `list`
+ Expected: Shows the full list of tutees in the tutee list. + + 3. Test case: `list mon`
+ Expected: Shows tutees whose lessons fall on Monday in the tutee list. + + 4. Test case: `list unpaid`
+ Expected: Shows tutees who have yet to pay for their lessons in the tutee list. + +### Adding a tutee + +1. Adding a tutee into the list. + 1. Prerequisites: None + 2. Test case: `add n/Betsy Crowe p/92939402 e/betsycrowe@example.com a/Newgate Prison sb/Secondary 3 Physics d/mon b/1900 end/1930 pr/35.00`
+ Expected: The tutee is added into the bottom of the list. Details of the added tutee is shown in the status message. + + +2. Adding a duplicate tutee into the list + 1. Prerequisites: Completing the first test case for [Adding a tutee](#adding-a-tutee) + 2. Test case: `add n/Betsy Crowe p/92939402 e/betsycrowe@example.com a/Newgate Prison sb/Secondary 3 Physics d/mon b/1900 end/1930 pr/35.00`
+ Expected: The error message _This tutee already exists_ should be displayed in the status message. + + +3. Adding a tutee that has clashing schedules. + 1. Prerequisites: Completing the first test case for [Adding a tutee](#adding-a-tutee) + 2. Test case: `add n/Jason Antonius p/12345678 e/test@gmail.com a/PGPR Residences sb/CS1101S d/mon b/1900 end/1930 pr/20`
+ Expected: The error message _This date and time clashes with an existing schedule_ should be displayed in the status message. + +### Deleting a tutee + +1. Deleting a tutee while all tutees are being shown + 1. Prerequisites: List all tutees using the `list` command. Multiple tutees in the list. + 2. Test case: `delete 1`
+ Expected: First tutee is deleted from the list. Details of the deleted tutee shown in the status message. Timestamp in the status bar is updated. + + +### Finding a tutee + +1. Finding a tutee by their name + 1. Prerequisites: Have the default tutee data list. + 2. Test case: `find n/Betsy`
+ Expected: Tutees that contain the name predicate listed. + + +2. Finding a tutee by their subject + 1. Prerequisites: Have the default tutee data list. + 2. Test case: `find sb/Maths`
+ Expected: Tutees that contain the subject predicate listed. + + +3. Finding a tutee by their name and subject + 1. Prerequisites: Have the default tutee data list. + 2. Test case: `find n/Betsy sb/Maths`
+ Expected: Tutees that contain both the name predicate and subject predicate listed. + + +4. Finding a tutee by their multiple word names + 1. Prerequisites: Have the default tutee data list. + 2. Test case: `find n/Betsy Crower`
+ Expected: The error message _Name can only take one word._ + + +5. Finding a tutee by their multiple word subject + 1. Prerequisites: Have the default tutee data list. + 2. Test case: `find sb/English Language`
+ Expected: The error message _Subject can only take one word._ + + +### Editing a tutee + +1. Editing a tutee while all tutees are being shown + 1. Prerequisites: Have the default tutee data and list all tutees using the `list` command. + + 2. Test case: `edit 2 n/Betsy Crower a/Betsy street, block 110, #03-02`
+ Expected: Tutee is successfully edited, and the details of the edited tutee is shown in the status message. + + + +2. Editing a tutee that causes duplicate tutees + + 1. Prerequisites: Have the default tutee data and list all tutees using the `list` command. + + 2. Test case: `edit 2 n/Alex Yeoh p/87438807`
+ Expected: The error message _This tutee already exists_ should be displayed in the status message. + + + +3. Editing a tutee that causes clashing schedules. + + 1. Prerequisites: Have the default tutee data and list all tutees using the `list` command. + + 2. Test case: `edit 2 d/Mon b/2000 end/2200`
+ Expected: The error message _This date clashes with an existing schedule_ should be displayed in the status message. + +### Find free time + +1. Finding free time that results in no available timeslots + 1. Prerequisites: Have the default tutee data. + + 2. Test case: `freeTime d/Mon dur/30 b/2000 end/2100`
+ Expected: The result
+ _Here is your list of free time:_
+ _There are no available timeslots._
+ should be displayed in the status message. + + + +2. Finding free time that results in available timeslots + 1. Prerequisites: Have the default tutee data. + + 2. Test case: `freeTime d/Mon dur/30 b/1930 end/2130`
+ Expected: The result
+ + _Here is your list of free time:_
+ _Free from 19:30 - 20:00_
+ _Free from 21:00 - 21:30_
+ should be displayed in the status message. + + +### Marking a tutee as paid + +1. Marking a tutee as paid while all tutees are being shown + + 1. Prerequisites: List all tutees using the `list` command. Multiple tutees in the list. + + 1. Test case: `paid 2`
+ Expected: Second tutee is from the list is marked as paid. The message of marking person paid success will be shown. Timestamp in the status bar is updated. + + 1. Test case: `delete 0`
+ Expected: No tutee is marked as paid. Error details shown in the status message. Status bar remains the same. + + 1. Other incorrect paid commands to try: `paid`, `paid x`, `...` (where x is larger than the list size)
+ Expected: Similar to previous. + +### Marking a tutee as not paid + +1. Marking a tutee as not paid while all tutees are being shown + + 1. Prerequisites: List all tutees using the `list` command. Multiple tutees in the list. + + 2. Test case: `unpaid 3`
+ Expected: Second tutee is from the list is marked as not paid. The message of marking person not paid success will be shown. Timestamp in the status bar is updated. + + 3. Test case: `unpaid 0`
+ Expected: No tutee is marked as not paid. Error details shown in the status message. Status bar remains the same. + + 4. Other incorrect unpaid commands to try: `unpaid`, `unpaid x`, `...` (where x is larger than the list size)
+ Expected: Similar to previous. + +### Listing all unpaid tutees + +1. All tutees who haven't paid will be shown + 1. There are tutees in the list. + + 2. Test case: `list unpaid`
+ Expected: All tutees who haven't paid will be shown. The message of how many tutees are unpaid will be shown. Timestamp in the status bar is updated. + +### Undo command + +1. Undo previous commands that can modify the data of tutees. + 1. Prerequisites: At least one tutee is present in the tutee list. Execute any command that modify tutee data. In this instruction, `clear` is used. + 2. Test case: `undo`
+ Expected: Restore all tutees that were cleared. A message informing the user that the command is successfully undone is displayed. + + +2. Undo when there are no previous commands that modify the data of tutees. + 1. Prerequisites: Launch the application. Ensure no commands that modify the tutee data is executed. + 2. Test case: `undo`
+ Expected: No command is undone. Error details shown in the status message. + +### Redo command + +1. Redo a command when there is a undo command executed previously. + 1. At least one tutee is present in the tutee list. Execute any command that modify tutee data. In this instruction, `clear` is used followed by `undo`. + 2. Test case: `redo`
+ Expected: Clear the tutee list again. + + +2. Redo a command when there is no undo command executed previously to redo. + 1. Prerequisites: Ensure no `undo` command is executed after launching the application. + 2. Test case: `redo`
+ Expected: No command is redone. Error details shown in the status message. + +### Manually editing data file + +
+**:information_source: Info:**
+This section assumes that you are an advanced user and understand some basic computing terminologies +
+ +The default save file is called `"tuitionconnect.json"`. + +Below is an example of a valid save file format: +``` +{ + "persons" : [ { + "name" : "Bernice Yu", + "phone" : "99272758", + "email" : "berniceyu@example.com", + "address" : "Blk 30 Lorong 3 Serangoon Gardens, #07-18", + "subject" : "Physics", + "day" : "TUESDAY", + "begin" : "1000", + "end" : "1100", + "paid" : false, + "payRate" : "25.00" + }, { + "name" : "Charlotte Oliveiro", + "phone" : "93210283", + "email" : "charlotte@example.com", + "address" : "Blk 11 Ang Mo Kio Street 74, #11-04", + "subject" : "Chemistry", + "day" : "WEDNESDAY", + "begin" : "1200", + "end" : "1300", + "paid" : false, + "payRate" : "30.00" + } ] +} +``` + +| Parameter | Description | Requirement / Remarks | +|---------------|-------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------| +| **`name`** | Name of tutee | [Alphanumeric](#glossary) and may contain spaces | +| **`date`** | Date of the upcoming application task | In **dd-mm-yyyy** format | +| **`phone`** | Contact number of tutee | Any number at least 3 digits long | +| **`email`** | Email address of tutee | In **XXXXXXXX@emaildomain** format
Example: `johndoee@gmail.com` | +| **`address`** | Address of the tutee | [Alphanumeric](#glossary) and may contain spaces | +| **`subject`** | Subject of the tutee | [Alphanumeric](#glossary) and may contain spaces | +| **`day`** | Day of weekly recurring lesson of the tutee | Full name of day or first three letters of the full name
**Non-case sensitive**
Example: `Mon`/`Monday`/`monday` | +| **`begin`** | Begin time of a tutee's weekly recurring lesson | In **HHMM** format | +| **`end`** | End time of a tutee's weekly recurring lesson | In **HHMM** format | +| **`paid`** | Indicates if the tutee paid | boolean value for whether tutee has paid | +| **`payrate`** | dollars per hour you make teaching this tutee | Numbers up to two decimal places only
Numbers must be **non-negative** | + + +## **Planned Enhancements** + +### Batch Processing for Paid Command + +Reason: This enhancement allows users to mark multiple persons as paid in a single command, improving efficiency. + +Idea: Modify the paid command parser to accept a list of person identifiers (e.g., "paid 1, 2, 3"), and update the command execution logic to iterate through the list and mark each person as paid. + +### Scheduled Unpaid Marking + +Reason: Introduce a scheduling feature within the unpaid command to set future unpaid statuses for individuals. This would be beneficial for scenarios where payments should automatically lapse after a set period. + +Idea: Add a scheduling mechanism within the command execution to mark individuals as unpaid after a specified future date or duration. + +### Find using multiple keywords + +Reason: To create a more sophisticated find feature for the best results. This enhancement allows users to get more specific results tailored to their criteria. + +Idea: Modify the `NameContainsKeywordsPredicate` and `SubjectContainsKeywordsPredicate` to accept multiple word inputs (e.g. "find n/Alex Yeoh sb/Maths Chemistry). + +### Maximum PayRate + +Reason: `PayRate` that are extremely high may not be displayed properly by GUI and are unlikely to be realistic PayRates per hour anyway. + +Idea: Modify the VALIDATION_REGEX of `PayRate` such that it only accepts values up to 9999.99. + +### Prevent Commands meant to Modify Tutee Data from Not Changing the Data -### Deleting a person +Reason: `clear`,`edit`, `paid`, `delete`,`unpaidAll` are commands that deal with modifying tutee data. If the tutee's `isPaid` status is true, +the system permits the user to execute the `paid` command even though this will not change the `isPaid` status of the tutee. If these commands +do not change the tutee data in any way but still allowed to be executed, when the user executes `undo`, there will be no changes since there are now duplicate states of the `VersionedAddressBook` +is saved. The system should inform the user that this command will not modify any data and prevent the command from executing. -1. Deleting a person while all persons are being shown +Idea: Create a `Model#isSameData()` to compare whether the state of the tutee data before and after the command execution will be the same. If +`Model#isSameData()` returns true, a `CommandException` should be thrown and the system should inform the user that this command will not modify any data. - 1. Prerequisites: List all persons using the `list` command. Multiple persons in the list. +### Enable Group Lessons - 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. +Reason: Current `Lesson` implementation prevents any lessons clashes, that is no two `Person` objects can have lessons that fall in same timeslots. +However, this is based on the assumption that lessons are carried out on a one-on-one basis. Given the possibility of group lessons, having such a feature +would allow for more flexibility in the application. - 1. Test case: `delete 0`
- Expected: No person is deleted. Error details shown in the status message. Status bar remains the same. +Idea: Create a `GroupTag` field for `Lesson` class which contain an ID for each `Lesson` object if they are group lessons. When checking if two +lesson clashes, allow for lesson clash if `Lesson` objects have the same IDs which means they are the same group lesson. - 1. Other incorrect delete commands to try: `delete`, `delete x`, `...` (where x is larger than the list size)
- Expected: Similar to previous. +### Enhance Edit Feature +Reason: Currently, the edit feature might result in a bug if the NAME and (DAY/BEGIN/END) fields are edited at the same time. +For example, if tutee index 1 has the name John and has a lesson on Monday 20:00 - 21:00, trying to do `edit 1 n/Doe end/2030` +will result in an error (throwing the message that this date clashes with an existing schedule). This is because it considers the +pre-edited tutee as a schedule clash. -1. _{ more test cases …​ }_ +Idea: Have an additional check with the index. If the edited person has the same index as the pre-edited person, then the +system should allow the edit to happen. -### Saving data +### Put more restrictions in phone number and email +Reason: Users might be able to put in an infinitely-long phone number and email which may cut off the UI -1. Dealing with missing/corrupted data files +Idea: Add the restriction in the parsers. - 1. _{explain how to simulate a missing/corrupted file, and the expected behavior}_ +### UI Enhancements when minimizing the window size +Reason: When the window is minimized, both the schedule list panel and the tutee list panel might be cut off. -1. _{ more test cases …​ }_ +Idea: Set a minimum width for the Main Window to ensure the UI is not cut off. diff --git a/docs/UserGuide.md b/docs/UserGuide.md index 57437026c7b..659242b6574 100644 --- a/docs/UserGuide.md +++ b/docs/UserGuide.md @@ -3,178 +3,501 @@ 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. +# Welcome to TuitionConnect's User Guide! +:rocket: Introducing **TuitionConnect**: Revolutionising your Private Tutoring Business! :rocket: + +Fed up with setting up numerous unorganised spreadsheets on Microsoft Excel or Google Sheets to handle the administrative side of your private tutoring business? + +Or feeling overwhelmed with the ugly-looking chaos of your Google Calendar as your number of teaching schedules increase? + +Say goodbye to all this mess with the help of **TuitionConnect**: the ultimate desktop app designed to streamline the administrative and financial tasks of your private tutoring business! + +This user guide will teach you how to install **TuitionConnect** from scratch, as well as providing information about the interesting features of **TuitionConnect**. + +
+ +## Table of Contents + + +* [Welcome to TuitionConnect's User Guide!](#welcome-to-tuitionconnects-user-guide) + * [Table of Contents](#table-of-contents) + * [Introduction](#introduction) + * [Using this guide](#using-this-guide) + * [Symbols and Syntax](#symbols-and-syntax) + * [Layout](#layout) + * [Quick start](#quick-start) + * [Command Format](#command-format) + * [Parameters Requirement](#parameters-requirement) + * [Features](#features) + * [Viewing help : `help`](#viewing-help--help) + * [Adding a tutee : `add`](#adding-a-tutee--add) + * [Listing tutees : `list`](#listing-tutees--list) + * [Finding a tutee : `find`](#finding-a-tutee--find) + * [Editing a tutee : `edit`](#editing-a-tutee--edit) + * [Deleting a tutee: `delete`](#deleting-a-tutee-delete) + * [Clearing all entries : `clear`](#clearing-all-entries--clear) + * [Marking a tutee as paid : `paid`](#marking-a-tutee-as-paid--paid) + * [Marking a tutee as unpaid : `unpaid`](#marking-a-tutee-as-unpaid--unpaid) + * [Mark all tutee as unpaid: `unpaidAll`](#mark-all-tutee-as-unpaid-unpaidall) + * [Finding Free Time : `freeTime`](#finding-free-time--freetime) + * [Undo previous command : `undo`](#undo-previous-command--undo) + * [Redo previous undone command : `redo`](#redo-previous-undone-command--redo) + * [Calculating monthly revenue: `rev`](#calculating-monthly-revenue-rev) + * [Exiting the program : `exit`](#exiting-the-program--exit) + * [FAQ](#faq) + * [Known issues](#known-issues) + * [Command summary](#command-summary) + * [Glossary](#glossary) + * [Alphanumeric](#alphanumeric) + * [CLI](#cli) + * [Command](#command) + * [GUI](#gui) + * [Index](#index) + * [JAR file](#jar-file) + * [Java](#java) + * [JSON file](#json-file) + * [Parameter](#parameter) + -* Table of Contents -{:toc} -------------------------------------------------------------------------------------------------------------------- +## Introduction +TuitionConnect is a **desktop app** built for private tutors and private tutoring businesses to simplify the process of +administration and finance management, optimized for use via a **Command Line Interface** (CLI) while +still having the benefits of a Graphical User Interface (GUI). + +If you love to type, then **TuitionConnect** is the app for you! It helps you to track tutee-specific details, teaching-schedule management, +and other financial and administrative tasks faster than your old and conventional apps like Google Calendar or Microsoft Excel. + +Even if you are not a huge fan of typing, panic not! **TuitionConnect** was designed with you in mind! With this comprehensive User Guide, +paired up with simple and beginner-friendly features, anyone can learn how to use **TuitionConnect** in no time! + +:sparkles: **Key Features:** +1. **Effortless Administration:** Manage your tutoring schedules, student details, and lesson plans in one central hub. No more endless scrolling through messy spreadsheets or confusing calendar views! +2. **Finance Made Easy**: Keep track of payments, and monitor your earnings effortlessly. +3. **Calendar Sanity**: Prevent clashes in your schedules, and find slots where you are available! + +:bulb: **Make the Switch Today!** +Transform your tutoring experience with **TuitionConnect!** Jump straight to the [Quick Start Section](#quick-start) and experience **TuitionConnect** now! + +[Back to top ↑](#welcome-to-tuitionconnects-user-guide) + +
+ +## Using this guide +If you're feeling a bit lost, worry not! +This user guide is to assist you seamlessly incorporate this application into your private tutoring business operations. + +For first-time users, we understand how it feels to open up an application without proper instructions. Thus, we have +carefully crafted a [Quick Start](#quick-start) section in this guide to provide you with the knowledge it takes to +start using TuitionConnect for your business. + +The [Layout](#layout) will also help you understand the different components of TuitionConnect's GUI. + +Eager to learn more about what our application can do? Head over to the [Command Format](#command-format) section to +learn more about the general formats of the commands and getting yourself prepared before +delving into the [Features](#features) section where you are in for the ride of your life! The Features section contains +the in-depth explanation for each command's format and use cases. + +At last, we have also included a [Command Summary](#command-summary) section for when you become proficient at using +TuitionConnect to refer quickly to any commands that you may need! + +Still unsure about the more technical terms used in this guide? Fret not, refer to the [glossary](#glossary) +to better understand all the technical jargons! + +[Back to top ↑](#welcome-to-tuitionconnects-user-guide) + +
+ +## Symbols and Syntax + +Throughout this User Guide, you might run into the following symbols and syntax. + +| Symbol/Syntax | Meaning | +|-------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------| +| :information_source: **Notes** | Information that you need to pay attention to. | +| :bulb: **Tip** | Information that you may find helpful. | +| :exclamation: **Caution** | Information that you need to know before executing a [command](#command). | +| `Highlighted text block` | [Commands](#command) or [parameters](#parameter) that you can enter into our application, or text that is directly displayed in our application. | +| [Hyperlinked text in blue](#symbols-and-syntax) | When it is pressed, it should lead you to another section in the document or to an external link. | + +[Back to top ↑](#welcome-to-tuitionconnects-user-guide) + +
+ +## Layout +The image below describes TuitionConnect's layout with some description for each component. +![Layout](images/Layout.png) + +[Back to top ↑](#welcome-to-tuitionconnects-user-guide) + +
## Quick start 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). +2. Download the latest `TuitionConnect.jar` from [here](https://github.com/AY2324S1-CS2103T-F10-4/tp/releases). -1. Copy the file to the folder you want to use as the _home folder_ for your AddressBook. +3. Copy the file to the folder you want to use as the _home folder_ for your TuitionConnect. -1. Open a command terminal, `cd` into the folder you put the jar file in, and use the `java -jar addressbook.jar` command to run the application.
- A GUI similar to the below should appear in a few seconds. Note how the app contains some sample data.
+4. Open a command terminal, `cd` into the folder you put the jar file in, and use the `java -jar TuitionConnect.jar` command to run the application.
+ A GUI similar to the below should appear in a few seconds. The left list contains information about your tutees. The right list displays your teaching schedule for the next 7 days. Note how the app contains some sample data.
+
![Ui](images/Ui.png) -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.
+5. Type the command in the command box and press Enter to execute it. e.g. typing **`help`** and pressing Enter will open the help window.
Some example commands you can try: - * `list` : Lists all contacts. - - * `add n/John Doe p/98765432 e/johnd@example.com a/John street, block 123, #01-01` : Adds a contact named `John Doe` to the Address Book. + * `list` : Lists all tutees. - * `delete 3` : Deletes the 3rd contact shown in the current list. + * `add n/John Doe p/98765432 e/johnny@example.com a/John street, block 123, #01-01 sb/Primary 4 Math d/wed b/1500 end/1600 pr/20.00` : Adds a tutee named `John Doe` to the list. - * `clear` : Deletes all contacts. + * `delete 3` : Deletes the 3rd tutee shown in the current list. - * `exit` : Exits the app. +6. Refer to the [Features](#features) below for details of each command. -1. Refer to the [Features](#features) below for details of each command. - --------------------------------------------------------------------------------------------------------------------- - -## Features + [Back to top ↑](#welcome-to-tuitionconnects-user-guide) -
+
-**:information_source: Notes about the command format:**
+## Command Format * Words in `UPPER_CASE` are the parameters to be supplied by the user.
e.g. in `add n/NAME`, `NAME` is a parameter which can be used as `add n/John Doe`. * Items in square brackets are optional.
- e.g `n/NAME [t/TAG]` can be used as `n/John Doe t/friend` or as `n/John Doe`. - -* Items with `…`​ after them can be used multiple times including zero times.
- e.g. `[t/TAG]…​` can be used as ` ` (i.e. 0 times), `t/friend`, `t/friend t/family` etc. + e.g `list [DAY]` can be used as `list` or as `list Mon`. * Parameters can be in any order.
- e.g. if the command specifies `n/NAME p/PHONE_NUMBER`, `p/PHONE_NUMBER n/NAME` is also acceptable. + e.g. if the command specifies `n/NAME sb/SUBJECT`, `sb/SUBJECT n/NAME ` is also valid. + +* Extraneous parameters added after commands that do not take in parameters (such as `help`, `list`, `exit`, `undo`, `redo` and `clear`) will be ignored.
+ e.g. if the command typed is `undo 123`, it will be interpreted as `undo`. + +[Back to top ↑](#welcome-to-tuitionconnects-user-guide) + +
-* 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`. +## Parameters Requirement +Here are the [parameter](#glossary) requirements of commonly used parameters by [commands](#glossary) in the [**Features**](#features) section below. -* If you are using a PDF version of this document, be careful when copying and pasting commands that span multiple lines as space characters surrounding line-breaks may be omitted when copied over to the application. -
+| Parameter | Description | Requirement / Remarks | +|----------------|-------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| **`NAME`** | Name of tutee | [Alphanumeric](#glossary) and may contain spaces | +| **`DATE`** | Date of the upcoming application task | In **dd-mm-yyyy** format | +| **`PHONE`** | Contact number of tutee | Any number at least 3 digits long | +| **`EMAIL`** | Email address of tutee | In **XXXXXXXX@emaildomain** format
Example: `johndoee@gmail.com` | +| **`ADDRESS`** | Address of the tutee | [Alphanumeric](#glossary) and may contain spaces | +| **`SUBJECT`** | Subject of the tutee | [Alphanumeric](#glossary) and may contain spaces | +| **`DAY`** | Day of weekly recurring lesson of the tutee | Full name of day or first three letters of the full name
**Non-case sensitive**
Example: `Mon`/`Monday`/`monday` | +| **`BEGIN`** | Begin time of a tutee's weekly recurring lesson | In **HHMM** format | +| **`END`** | End time of a tutee's weekly recurring lesson | In **HHMM** format | +| **`PAYRATE`** | dollars per hour you make teaching this tutee | Numbers up to two decimal places only
Numbers must be **non-negative** | +| **`INDEX`** | The index number of the tutee shown in the tutee list panel | Used in [`delete`](#deleting-a-tutee-delete) [`edit`](#editing-a-tutee--edit) [`unpaid`](#marking-a-tutee-as-unpaid--unpaid) and [`paid`](#marking-a-tutee-as-paid--paid) commands
Must be a **positive number**
Example: (1,2,3,...)
| +| **`DURATION`** | The duration of a time slot in **minutes** | Used in [`freeTime`](#finding-free-time--freetime) Must be a **positive integer**
Example: (60,120,...)
| + +
+ +[Back to top ↑](#welcome-to-tuitionconnects-user-guide) + +
+ +## Features ### Viewing help : `help` -Shows a message explaning how to access the help page. +**Description**: Redirects you to the user guide if you are ever lost. + +**Format**: `help` + +**Sample Execution**: `help` + +![Help after](images/HelpAfter.png) + +[Back to top ↑](#welcome-to-tuitionconnects-user-guide) + +### Adding a tutee : `add` + +**Description**: Adds new tutees you are tutoring into the list. + +**Format**: `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS sb/SUBJECT d/DAY b/BEGIN e/END pr/PAYRATE` + +:exclamation: Things that can cause the `add` command to fail: +1. Inserting invalid inputs into the add command. + - :bulb: Check the [Parameter Requirements](#parameters-requirement) for valid parameter inputs. +2. Adding a tutee that will result in duplicate tutees. + - :information_source: Two tutees are considered duplicates if they have the same name and phone number +3. Adding a tutee that will result in clashing schedules. + - :bulb: Use the [`freeTime` command](#finding-free-time--freetime) to list down timings when you are available and prevent schedule clashses. + +**Examples**: +* `add n/John Doe p/98765432 e/johnny@example.com a/John street, block 123, #01-01 sb/Primary 4 Math d/wed b/1500 end/1600 pr/20.00` +* `add n/Betsy Crowe p/92939402 e/betsycrowe@example.com a/Newgate Prison sb/Secondary 3 Physics d/mon b/1900 end/1930 pr/35.00` + +**Sample Execution**: `add n/Betsy Crowe p/92939402 e/betsycrowe@example.com a/Newgate Prison sb/Secondary 3 Physics d/mon b/1900 end/1930 pr/35.00` + +![Add after](images/Add%20after.png) + +[Back to top ↑](#welcome-to-tuitionconnects-user-guide) + +### Listing tutees : `list` + +**Description**: View the tutees that you are currently teaching. + +**Format**: `list [DAY]/[unpaid]` + +:information_source: The `DAY` and `unpaid` parameters are optional. + * Without stating a specified `DAY` or `unpaid`, `list` will display all of your tutees + * When `DAY` is specified, only tutees whose lessons matches the specified `DAY` will be displayed + * When `unpaid` is included, only tutees who have yet to pay for their lessons will be displayed + * `list` can only accept up to one parameter in a single command -![help message](images/helpMessage.png) -Format: `help` +:exclamation: Things that can cause the `list` command to fail: +1. If the `DAY` parameter does not adhere to the specified format, the system will treat this as an invalid command. + - :bulb: Check the [Parameter Requirements](#parameters-requirement) for valid parameter inputs. +2. Adding both `DAY` and `unpaid` parameters. -### Adding a person: `add` +**Examples**: +* `list` +* `list monday` +* `list unpaid` -Adds a person to the address book. +**Sample Execution**: `list monday` -Format: `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]…​` +![list by day command](images/ListByDayCommand.png) -
:bulb: **Tip:** -A person can have any number of tags (including 0) -
+[Back to top ↑](#welcome-to-tuitionconnects-user-guide) -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` +### Finding a tutee : `find` -### Listing all persons : `list` +**Description** : Find tutees quickly in your current list. -Shows a list of all persons in the address book. +**Format**: `find [n/NAME] [sb/SUBJECT]` -Format: `list` +:information_source: `find` requires at least one of the two fields to be able to find for tutees and is non-case sensitive. -### Editing a person : `edit` +:information_source: As long as the tutee's `NAME` and/or `SUBJECT` contains the given keywords, the tutee will be displayed. + * Example: `find n/AL` returns a tutee named "ALICE" -Edits an existing person in the address book. +:information_source: Both n/ and sb/ prefixes take one word as input each. -Format: `edit INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [t/TAG]…​` +:exclamation: Things that can cause `find` command to fail: +1. Inserting invalid inputs into the find command. + - :bulb: Check the [Parameter Requirements](#parameters-requirement) for valid parameter inputs. +2. Inserting multiple word inputs for any of the two fields. + - :exclamation: As mentioned above, both prefixes can only take one word each as input. -* Edits the person at the specified `INDEX`. The index refers to the index number shown in the displayed person list. The index **must be a positive integer** 1, 2, 3, …​ -* At least one of the optional fields must be provided. -* Existing values will be updated to the input values. -* When editing tags, the existing tags of the person will be removed i.e adding of tags is not cumulative. -* You can remove all the person’s tags by typing `t/` without - specifying any tags after it. +**Examples**: +* `find n/Alex` +* `find sb/Maths` +* `find n/Alex sb/Maths` -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. +**Sample Execution**: `find n/Alex sb/Maths` -### Locating persons by name: `find` +![findAfter](images/findAfter.png) -Finds persons whose names contain any of the given keywords. +[Back to top ↑](#welcome-to-tuitionconnects-user-guide) -Format: `find KEYWORD [MORE_KEYWORDS]` +### Editing a tutee : `edit` -* 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` +**Description**: Edit a tutee in your current list. -Examples: -* `find John` returns `john` and `John Doe` -* `find alex david` returns `Alex Yeoh`, `David Li`
- ![result for 'find alex david'](images/findAlexDavidResult.png) +**Format**: `edit INDEX [n/NAME] [p/PHONE_NUMBER] [e/EMAIL] [a/ADDRESS] [s/SUBJECTS] [d/DAY] [b/BEGIN] [end/END] [pr/PAYRATE`] -### Deleting a person : `delete` +:information_source: `edit` requires at least one of the fields to be present to be able to edit. -Deletes the specified person from the address book. +:exclamation: Things that can cause `edit` command to fail: +1. Inserting invalid inputs into the `edit` command. + - :bulb: Check the [Parameter Requirements](#parameters-requirement) for valid parameter inputs. +2. Editing a tutee that will result in duplicate tutees. + - :information_source: Two tutees are considered duplicates if they have the same name and phone number. +3. Editing a tutee that will result in clashing schedules. + - :bulb: Use the [`freeTime` command](#finding-free-time--freetime) to list down timings when you are available and prevent schedule clashes. -Format: `delete INDEX` -* Deletes the person at the specified `INDEX`. -* The index refers to the index number shown in the displayed person list. -* The index **must be a positive integer** 1, 2, 3, …​ +**Examples**: +* `edit 1 p/91234567 d/Sun` +* `edit 2 n/Betsy Crower a/Betsy street, block 110, #03-02` -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. +**Sample Execution**: `edit 2 n/Betsy Crower a/Betsy street, block 110, #03-02` + +![editAfter](images/editAfter.png) + +[Back to top ↑](#welcome-to-tuitionconnects-user-guide) + +### Deleting a tutee: `delete` + +**Description**: Deletes the specific tutee from your list. + +**Format**: `delete INDEX` + +:exclamation: Things that can cause `delete` command to fail: +1. Inserting invalid `INDEX` into the `delete` command. + - :information_source: `INDEX` should not be smaller than 1 and larger than + the number of tutees in the list. + +**Examples**: +* `list` followed by `delete 2` deletes the 2nd person in the list. + +**Sample Execution**: `delete 2` + +![deleteAfter](images/deleteAfter.png) + +[Back to top ↑](#welcome-to-tuitionconnects-user-guide) ### Clearing all entries : `clear` -Clears all entries from the address book. +**Description**: Clears all entries from your list to start from scratch. -Format: `clear` +**Format**: `clear` -### Exiting the program : `exit` +**Sample Execution**: `clear` + +![ClearCommand.png](images/ClearCommand.png) + +[Back to top ↑](#welcome-to-tuitionconnects-user-guide) + +### Marking a tutee as paid : `paid` + +**Description**: Mark a specific tutee as paid in your list. + +**Format**: `paid INDEX` + +:exclamation: Things that can cause `paid` command to fail: +1. Inserting invalid `INDEX` into the `paid` command. + - :information_source: `INDEX` should not be smaller than 1 and larger than + the number of tutees in the list. -Exits the program. +* **`INDEX`**: Numbers between 1 to the number of people inside the list. -Format: `exit` +**Examples**: +* `list` followed by `paid 1` marks the first person as paid in the list. -### Saving the data +**Sample Execution**: `paid 1` -AddressBook data are saved in the hard disk automatically after any command that changes the data. There is no need to save manually. +![paidexample](images/paidexample.png) -### Editing the data file +[Back to top ↑](#welcome-to-tuitionconnects-user-guide) -AddressBook data are saved automatically as a JSON file `[JAR file location]/data/addressbook.json`. Advanced users are welcome to update data directly by editing that data file. +### Marking a tutee as unpaid : `unpaid` -
:exclamation: **Caution:** -If your changes to the data file makes its format invalid, AddressBook will discard all data and start with an empty data file at the next run. Hence, it is recommended to take a backup of the file before editing it. -
+**Description**: Mark a specific tutee as not paid in your list. -### Archiving data files `[coming in v2.0]` +**Format**: `unpaid INDEX` -_Details coming soon ..._ +:exclamation: Things that can cause `unpaid` command to fail: +1. Inserting invalid `INDEX` into the `unpaid` command. + - :information_source: `INDEX` should not be smaller than 1 and larger than + the number of tutees in the list. +**Examples**: +* `list` followed by `unpaid 2` marks the 2nd person as not paid in the list. + +**Sample Execution**: `unpaid 2` + +![unpaidexample](images/unpaidexample.png) + +[Back to top ↑](#welcome-to-tuitionconnects-user-guide) + +### Mark all tutees in the list as unpaid: `unpaidAll` + +**Description** : Mark all tutees in your current displayed list as not paid. + +**Format**: `unpaidAll` + +**Sample Execution**: `unpaidAll` + +![unpaidAllexample](images/unpaidAllexample.png) + +[Back to top ↑](#welcome-to-tuitionconnects-user-guide) + +### Finding Free Time : `freeTime` + +**Description**: Finds timeslots when you are free in your schedule. + +**Format**: `freeTime d/DAY dur/DURATION b/BEGIN end/END` + +:bulb: Tip on how to use this command: I want to know when I'm free on `DAY` between `BEGIN` and `END` for at least `DURATION` minutes long. + +**Example:** + - `freeTime d/Sat dur/120 b/0800 end/2200` + - :information_source: the command above will find timeslots when the user is free on `Sat` between `0800` and `2200`, where each timeslot is at least `120` minutes long. + +**Sample Execution**: `freeTime d/Sat dur/120 b/0800 end/2200` + +![freeTime after](images/freeTime%20after.png) + +[Back to top ↑](#welcome-to-tuitionconnects-user-guide) + +### Undo previous command : `undo` + +**Description**: Undo the most recent command if you made a mistake. + +**Format**: `undo` + +:information_source: You can only undo `add`,`clear`,`delete`,`edit`,`redo`,`paid`,`unpaid` and `unpaidAll` commands. + +**Sample Execution**: `clear` followed by `undo` +1. `clear` deletes all tutee in the tutee list + +![clearCommand](images/ClearCommand.png) + +2. `undo` restores all cleared tutees + +![undoCommand](images/undoCommand.png) + +[Back to top ↑](#welcome-to-tuitionconnects-user-guide) + +### Redo previous undone command : `redo` + +**Description**: Redo the most recent command that was undone if you changed your mind. + +**Format**: `redo` + +[Back to top ↑](#welcome-to-tuitionconnects-user-guide) + +### Calculating monthly revenue: `rev` + +**Description**: Calculate the total monthly revenue from all your tutees. + +**Format**: `rev` + +:information_source: `rev` command is time-sensitive (i.e. If the lesson occurrences varies between months, the value adjusts accordingly) + +**Sample Execution:** `rev` + +![Revenue Command Success](images/RevenueCommandSuccess.png) + +[Back to top ↑](#welcome-to-tuitionconnects-user-guide) + +### Exiting the program : `exit` + +**Description**: Exits the program when you are done. + +**Format**: `exit` + +:information_source: The application window closes automatically after you type the command `exit` + +[Back to top ↑](#welcome-to-tuitionconnects-user-guide) + +
-------------------------------------------------------------------------------------------------------------------- ## 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. +**Q**: How do I transfer my existing data to another machine?
+**A**: Overwrite the empty `tuitionconnect.json` file in the machine by deleting it and replacing it with the `tuitionconnect.json` that contains the data + +**Q**: Where is my data stored?
+**A**: Your data is stored locally in a file named `tuitionconnect.json` + +**Q**: Is there any way I can give feedback if I face any issues?
+**A**: You can submit your issues [here](https://github.com/AY2324S1-CS2103T-F10-4/tp/issues) + +[Back to top ↑](#welcome-to-tuitionconnects-user-guide) -------------------------------------------------------------------------------------------------------------------- @@ -182,16 +505,80 @@ _Details coming soon ..._ 1. **When using multiple screens**, if you move the application to a secondary screen, and later switch to using only the primary screen, the GUI will open off-screen. The remedy is to delete the `preferences.json` file created by the application before running the application again. + +
+ -------------------------------------------------------------------------------------------------------------------- ## 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` +| Action | Format, Examples | +|-----------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| **help** | `help` | +| **add** | `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS s/SUBJECT d/DAY b/BEGIN end/END pr/PAYRATE`
e.g., `add n/John Doe p/98765432 e/johnny@example.com a/John street, block 123, #01-01 sb/Primary 4 Math d/wed b/1500 end/1600 pr/20.00` | +| **delete** | `delete INDEX`
e.g., `delete 3` | +| **clear** | `clear` | +| **edit** | `edit INDEX n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS s/SUBJECTS d/DAY b/BEGIN end/END pr/PAYRATE`
e.g.,`edit p/91234567 d/Sun` | +| **list** | `list` | +| **find** | `find n/[NAME] sb/[SUBJECT]`
e.g., `find n/Alex sb/Math`, `find n/Alex`, `find sb/Maths` | +| **list by day** | `list DAY`
e.g., `list Monday` | +| **paid** | `paid INDEX`
e.g., `paid 1` | +| **unpaid** | `unpaid INDEX`
e.g., `unpaid 1` | +| **list unpaid** | `list unpaid` | +| **unpaidAll** | `unpaidAll` | +| **freeTime** | `d/DAY dur/DURATION b/BEGIN end/END` | +| **undo** | `undo` | +| **redo** | `redo` | +| **rev** | `rev` | +| **exit** | `exit` | + +[Back to top ↑](#welcome-to-tuitionconnects-user-guide) + +## Glossary + +### Alphanumeric + +Characters that are either a number or a letter. + +### CLI + +`CLI` is short for `Command Line Interface`, which is a text-based user interface where a program connects to the user. +Users interact with a system or application by the use of text commands, where the system will respond accordingly +depending on the command specified. + +### Command + +The specific instruction for the application to carry out. + +### GUI + +`GUI` is short for `Graphical User Interface`, which is a user interface that incorporates interactive visual +components _(such as icons, etc)_. Users interact with a system or application by clicking on these components, where +the system will respond accordingly to the user's actions by updating the user interface. + +### Index + +`Index` is the position of an item inside the list. If the `item` is at index 1, then we say that it is the first +item on the list. + +### JAR file + +`JAR` is short for `Java ARchive`, which is a file format that is used to package the files to run TuitionConnect. +Not only that, `JAR` files are also executables, meaning that it can be run by simply double-clicking the `JAR` file. + +### Java +`Java` is the programming language used to develop TuitionConnect. +A version of `Java` which is `Java 11` or higher is a requisite to run TuitionConnect on your devices. + +### JSON file + +`JSON` is short for JavaScript Object Notation, mainly utilized for storing data of users in TuitionConnect. +These type of files are lightweight and text-based, making it suitable for both humans to read and write, +as well as machines to parse and generate. + +### Parameter + +`Parameter`s are the inputs that a user can provide for a particular part of a command. As an example, for the field +`n/NAME` of add command, `NAME` can be substituted with the input that you want to specify (such as n/John or n/Alice). + +[Back to top ↑](#welcome-to-tuitionconnects-user-guide) diff --git a/docs/_config.yml b/docs/_config.yml index 6bd245d8f4e..35b99db9214 100644 --- a/docs/_config.yml +++ b/docs/_config.yml @@ -1,4 +1,4 @@ -title: "AB-3" +title: "TuitionConnect" theme: minima header_pages: @@ -8,7 +8,7 @@ header_pages: markdown: kramdown -repository: "se-edu/addressbook-level3" +repository: "AY2324S1-CS2103T-F10-4/tp" github_icon: "images/github-icon.png" plugins: diff --git a/docs/_sass/minima/_base.scss b/docs/_sass/minima/_base.scss index 0d3f6e80ced..b096993d4ec 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: "TuitionConnect"; font-size: 32px; } } diff --git a/docs/diagrams/AddActivityDiagram.puml b/docs/diagrams/AddActivityDiagram.puml new file mode 100644 index 00000000000..8dc3abe7119 --- /dev/null +++ b/docs/diagrams/AddActivityDiagram.puml @@ -0,0 +1,17 @@ +@startuml +skinparam conditionStyle diamond + +start +:User executes Add command; +note :Valid Input Format: \n"add n/NAME p/PHONE e/EMAIL \na/ADDRESS sb/SUBJECT d/DAY \nb/START end/END pr/PAYRATE" +:Checks for duplicate tutees and clashing schedules; +if () then ([Adding this tutee will not \nresult in duplicate tutees or \nclashing schedules]\n) + :Add tutee to tutee list; + +else ([Else]\n) + :Display error message; +endif + +stop + +@enduml diff --git a/docs/diagrams/AddSequenceDiagram.puml b/docs/diagrams/AddSequenceDiagram.puml new file mode 100644 index 00000000000..13b5ff2218a --- /dev/null +++ b/docs/diagrams/AddSequenceDiagram.puml @@ -0,0 +1,90 @@ +@startuml +!include style.puml +skinparam ArrowFontStyle plain + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR +participant ":AddCommandParser" as AddCommandParser LOGIC_COLOR +participant "a:AddCommand" as AddCommand 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("add n/John Doe p/98765432 \ne/johnd@example.com a/311, Clementi Ave 2, #02-25 \nsb/Maths d/Mon b/1200 end/1300 pr/20.00") +activate LogicManager + +LogicManager -> AddressBookParser : parseCommand("add n/John Doe p/98765432 \ne/johnd@example.com a/311, Clementi Ave 2, #02-25 \nsb/Maths d/Mon b/1200 end/1300 pr/20.00") +activate AddressBookParser + +create AddCommandParser +AddressBookParser -> AddCommandParser +activate AddCommandParser + +AddCommandParser --> AddressBookParser +deactivate AddCommandParser + +AddressBookParser -> AddCommandParser : parse("n/John Doe p/98765432 \ne/johnd@example.com a/311, Clementi Ave 2, #02-25 \nsb/Maths d/Mon b/1200 end/1300 pr/20.00") +activate AddCommandParser + +create AddCommand +AddCommandParser -> AddCommand +activate AddCommand + +AddCommand --> AddCommandParser : a +deactivate AddCommand + +AddCommandParser --> AddressBookParser : a +deactivate AddCommandParser +'Hidden arrow to position the destroy marker below the end of the activation bar. +AddCommandParser -[hidden]-> AddressBookParser +destroy AddCommandParser + +AddressBookParser --> LogicManager : a +deactivate AddressBookParser + +LogicManager -> AddCommand : execute() +activate AddCommand + +AddCommand -> Model : purgeAddressBook() +activate Model + +Model --> AddCommand +deactivate Model + +AddCommand -> Model : addPerson(toAdd) +activate Model + +note right +toAdd: +Person object containing +all the information parsed +from command input +end note + +Model --> AddCommand +deactivate Model + +AddCommand -> Model : commitAddressBook() +activate Model + +Model --> AddCommand +deactivate Model + + +create CommandResult +AddCommand -> CommandResult +activate CommandResult + +CommandResult --> AddCommand +deactivate CommandResult + +AddCommand --> LogicManager : result +deactivate AddCommand + +[<--LogicManager +deactivate LogicManager +@enduml diff --git a/docs/diagrams/DeleteSequenceDiagram.puml b/docs/diagrams/DeleteSequenceDiagram.puml index 40ea6c9dc4c..40541cf8a19 100644 --- a/docs/diagrams/DeleteSequenceDiagram.puml +++ b/docs/diagrams/DeleteSequenceDiagram.puml @@ -49,12 +49,25 @@ deactivate AddressBookParser LogicManager -> DeleteCommand : execute() activate DeleteCommand +DeleteCommand -> Model : purgeAddressBook() +activate Model + +Model --> DeleteCommand +deactivate Model + DeleteCommand -> Model : deletePerson(1) activate Model Model --> DeleteCommand deactivate Model +DeleteCommand -> Model : commitAddressBook() +activate Model + +Model --> DeleteCommand +deactivate Model + + create CommandResult DeleteCommand -> CommandResult activate CommandResult diff --git a/docs/diagrams/EditActivityDiagram.puml b/docs/diagrams/EditActivityDiagram.puml new file mode 100644 index 00000000000..7336a7238da --- /dev/null +++ b/docs/diagrams/EditActivityDiagram.puml @@ -0,0 +1,17 @@ +@startuml +skinparam conditionStyle diamond + +start +:User executes Edit command; +note :Example of Valid Input Format: \n"edit n/NAME p/PHONE e/EMAIL \na/ADDRESS sb/SUBJECT d/DAY \nb/START end/END pr/PAYRATE" +:Checks for duplicate tutees and clashing schedules; +if () then ([Tutee index\nnot in list\nor has clashing \nschedules with \nother tutees\nor edited fields\nwill result in\nduplicate tutees]\n) + :Display Error Message; + +else ([Else]\n) + :Edits the tutee; +endif + +stop + +@enduml diff --git a/docs/diagrams/EditSequenceDiagram.puml b/docs/diagrams/EditSequenceDiagram.puml new file mode 100644 index 00000000000..102907e6c72 --- /dev/null +++ b/docs/diagrams/EditSequenceDiagram.puml @@ -0,0 +1,141 @@ +@startuml +!include style.puml +skinparam noteTextAlignment left + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR +participant "parser:EditCommandParser" as EditCommandParser LOGIC_COLOR +participant "descriptor:EditPersonDescriptor" as EditPersonDescriptor LOGIC_COLOR +participant "command:EditCommand" as EditCommand LOGIC_COLOR +participant "result:CommandResult" as CommandResult LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant "model:Model" as Model MODEL_COLOR + +end box +[-> LogicManager : execute("edit 1 n/Alex sb/Maths") +activate LogicManager + +LogicManager -> AddressBookParser : parseCommand("edit 1 n/Alex sb/Maths") +activate AddressBookParser + +create EditCommandParser +AddressBookParser -> EditCommandParser +activate EditCommandParser + +EditCommandParser --> AddressBookParser : parser +deactivate EditCommandParser + +AddressBookParser -> EditCommandParser : parse(" 1 n/Alex sb/Maths") +activate EditCommandParser + +'note right +'//(omitted to reduce diagram complexity)// +'**parse** method argument is processed as follows: +'* separated into prefixes ("n/","p/", etc) using the ArgumentTokenizer class, +'* have accompanying values (John Doe, Banana, 5, etc) +'encapsulated in relevant data classes (Name, Phone, etc) +'* passed as arguments for Order instantiation +'end note + +create EditPersonDescriptor +EditCommandParser -> EditPersonDescriptor +activate EditPersonDescriptor + +EditPersonDescriptor --> EditCommandParser : descriptor +deactivate EditPersonDescriptor + +create EditCommand +EditCommandParser -> EditCommand : EditCommand(index, editPersonDescriptor); +activate EditCommand + +note left +index: +the index of person to be edited + +editPersonDescriptor: +a person object where its field are the edited values +end note + +EditCommand --> EditCommandParser : command +deactivate EditCommand + +EditCommandParser --> AddressBookParser : command +deactivate EditCommandParser + +AddressBookParser --> LogicManager : command +deactivate AddressBookParser + +destroy EditCommandParser + +LogicManager -> EditCommand : execute(model) +activate EditCommand + +EditCommand -> Model : getSortedPersonList() +activate Model + +Model --> EditCommand : lastShownList +deactivate Model + +EditCommand -> EditCommand: createEditedPerson(personToEdit, editPersonDescriptor) +activate EditCommand + +note right +personToEdit: +the person to be edited + +editPersonDescriptor +a person object where its field are the edited values +end note + +EditCommand --> EditCommand: editedPerson +deactivate EditCommand + +EditCommand -> Model : purgeAddressBook() +activate Model +deactivate Model + +EditCommand -> Model : setPerson(personToEdit, editedPerson, isEditingSchedule) +activate Model +deactivate Model + +note right +personToEdit: +the person to be edited + +editedPerson: +the editedPerson + +isEditingSchedule: +boolean to check if day,begin, and end fields are being edited +end note + +EditCommand -> Model : updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS) +activate Model +deactivate Model + +note right +PREDICATE_SHOW_ALL_PERSONS: +Predicate to show all persons +end note + +EditCommand -> Model : commitAddressBook(); +activate Model +deactivate Model + +create CommandResult +EditCommand -> CommandResult +activate CommandResult + +CommandResult --> EditCommand +deactivate CommandResult + +EditCommand --> LogicManager : result +deactivate EditCommand +EditCommand -[hidden]-> LogicManager : result + +[<--LogicManager +deactivate LogicManager +@enduml diff --git a/docs/diagrams/FindSequenceDiagram.puml b/docs/diagrams/FindSequenceDiagram.puml new file mode 100644 index 00000000000..5d403915add --- /dev/null +++ b/docs/diagrams/FindSequenceDiagram.puml @@ -0,0 +1,104 @@ +@startuml +!include style.puml +skinparam noteTextAlignment left + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR +participant "parser:FindCommandParser" as FindCommandParser LOGIC_COLOR +participant "predicate:NameContainsKeywordPredicate" as NameContainsKeywordPredicate LOGIC_COLOR +participant "subject:SubjectContainsKeywordPredicate" as SubjectContainsKeywordPredicate LOGIC_COLOR +participant "nameSubject:NameSubjectPredicate" as NameSubjectPredicate LOGIC_COLOR +participant "command:FindCommand" as FindCommand LOGIC_COLOR +participant "result:CommandResult" as CommandResult LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant "model:Model" as Model MODEL_COLOR + +end box +[-> LogicManager : execute("find n/Alex sb/Maths") +activate LogicManager + +LogicManager -> AddressBookParser : parseCommand("find n/Alex sb/Maths") +activate AddressBookParser + +create FindCommandParser +AddressBookParser -> FindCommandParser +activate FindCommandParser + +FindCommandParser --> AddressBookParser : parser +deactivate FindCommandParser + +AddressBookParser -> FindCommandParser : parse(" n/Alex sb/Maths") +activate FindCommandParser + +create NameContainsKeywordPredicate +FindCommandParser -> NameContainsKeywordPredicate +activate NameContainsKeywordPredicate + +NameContainsKeywordPredicate --> FindCommandParser : predicate +deactivate NameContainsKeywordPredicate + +create SubjectContainsKeywordPredicate +FindCommandParser -> SubjectContainsKeywordPredicate +activate SubjectContainsKeywordPredicate + +SubjectContainsKeywordPredicate --> FindCommandParser : subject +deactivate SubjectContainsKeywordPredicate + +create FindCommand +FindCommandParser --> FindCommand : FindCommand(predicate, subject) +activate FindCommand + +note right +predicate: +the predicate for the prefix name n/ + +subject: +the predicate for the prefix subject sb/ +end note + +FindCommand --> FindCommandParser : command +deactivate FindCommand + +FindCommandParser --> AddressBookParser : command +deactivate FindCommandParser + +AddressBookParser --> LogicManager : command +deactivate AddressBookParser + +destroy FindCommandParser + +LogicManager -> FindCommand : execute(model) +activate FindCommand + +create NameSubjectPredicate +FindCommand -> NameSubjectPredicate +activate NameSubjectPredicate + +NameSubjectPredicate --> FindCommand : nameSubject +deactivate NameSubjectPredicate + + +FindCommand -> Model : updateFilteredPersonList(nameSubject) +activate Model +deactivate Model + +note left +nameSubject: +the predicate of both inputs for keywords n/ and sb/ +end note + +create CommandResult +FindCommand -> CommandResult +activate CommandResult + +CommandResult --> FindCommand +deactivate CommandResult + +FindCommand --> LogicManager: result + +[<--LogicManager +deactivate LogicManager +@enduml diff --git a/docs/diagrams/FreeTimeActivityDiagram.puml b/docs/diagrams/FreeTimeActivityDiagram.puml new file mode 100644 index 00000000000..a9436436666 --- /dev/null +++ b/docs/diagrams/FreeTimeActivityDiagram.puml @@ -0,0 +1,19 @@ +@startuml +skinparam conditionStyle diamond + +start +:User executes freeTime command; +note :Valid Input Format: \n"freeTime d/DAY dur/DURATION \nb/START end/END" +:Finds the timeslots where the user is busy on DAY; +:Static method from TimeSlot class returns a list of timeslots\nwhere the user is free based on the timeslots where the user is busy; +:These list of timeslots are filtered so that each timeslot is at least\n DURATION long, and between START and END; +if () then ([List of available timeslots is empty]\n) + :Displays the message:\nThere are no available timeslots; + +else ([Else]\n) + :Displays all the timeslots\ninside the list; +endif + +stop + +@enduml diff --git a/docs/diagrams/FreeTimeSequenceDiagram.puml b/docs/diagrams/FreeTimeSequenceDiagram.puml new file mode 100644 index 00000000000..72ee54d273d --- /dev/null +++ b/docs/diagrams/FreeTimeSequenceDiagram.puml @@ -0,0 +1,83 @@ +@startuml +!include style.puml + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR +participant ":FreeTimeCommandParser" as FreeTimeCommandParser LOGIC_COLOR +participant ":FreeTimeCommand" as FreeTimeCommand LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant ":Model" as Model MODEL_COLOR +participant ":AddressBook" as AddressBook MODEL_COLOR +participant ":UniquePersonList" as UniquePersonList MODEL_COLOR +participant "<> \nTimeSlot" as TimeSlot MODEL_COLOR +end box +[-> LogicManager : execute("freeTime d/Sat dur/120 b/0700 end/1200") +activate LogicManager + +LogicManager -> AddressBookParser : parseCommand("freeTime d/Sat dur/120 b/0700 end/1200") +activate AddressBookParser + +create FreeTimeCommandParser +AddressBookParser -> FreeTimeCommandParser +activate FreeTimeCommandParser + +FreeTimeCommandParser --> AddressBookParser +deactivate FreeTimeCommandParser + +AddressBookParser -> FreeTimeCommandParser : parse("d/Sat dur/120 b/0700 end/1200") +activate FreeTimeCommandParser + +create FreeTimeCommand +FreeTimeCommandParser -> FreeTimeCommand +activate FreeTimeCommand +FreeTimeCommand --> FreeTimeCommandParser +deactivate FreeTimeCommand + +FreeTimeCommandParser --> AddressBookParser +deactivate FreeTimeCommandParser + +FreeTimeCommandParser -[hidden]-> AddressBookParser +destroy FreeTimeCommandParser + +AddressBookParser --> LogicManager +deactivate AddressBookParser + +LogicManager -> FreeTimeCommand : execute() +activate FreeTimeCommand + +FreeTimeCommand -> Model : findInterval(toFind) +activate Model + +'ADD MORE HERE +Model -> AddressBook : findInterval(interval) +activate AddressBook +AddressBook -> UniquePersonList : findInterval(interval) +activate UniquePersonList +UniquePersonList --> AddressBook +deactivate UniquePersonList +AddressBook --> Model +deactivate AddressBook + +Model --> FreeTimeCommand +deactivate Model + +FreeTimeCommand -> TimeSlot : parseIntervals(results) +activate TimeSlot +TimeSlot --> FreeTimeCommand : timeslots +deactivate TimeSlot + +FreeTimeCommand -> TimeSlot : findAvailableTime(timeslots, toFind) +activate TimeSlot +TimeSlot --> FreeTimeCommand : availableTime +deactivate TimeSlot + +FreeTimeCommand --> LogicManager: result +deactivate FreeTimeCommand + +FreeTimeCommand -[hidden]-> LogicManager: result + +[<--LogicManager +@enduml diff --git a/docs/diagrams/ListByDaySequenceDiagram.puml b/docs/diagrams/ListByDaySequenceDiagram.puml new file mode 100644 index 00000000000..d511925aa6b --- /dev/null +++ b/docs/diagrams/ListByDaySequenceDiagram.puml @@ -0,0 +1,78 @@ +@startuml +!include style.puml +skinparam ArrowFontStyle plain + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR +participant ":ListCommandParser" as ListCommandParser LOGIC_COLOR +participant "listByDay:ListByDayCommand" as ListByDayCommand 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("list Mon") +activate LogicManager + +LogicManager -> AddressBookParser : parseCommand("list Mon") +activate AddressBookParser + +create ListCommandParser +AddressBookParser -> ListCommandParser +activate ListCommandParser + +ListCommandParser --> AddressBookParser +deactivate ListCommandParser + +AddressBookParser -> ListCommandParser : parse("list Mon") +activate ListCommandParser + +create ListByDayCommand +ListCommandParser -> ListByDayCommand +activate ListByDayCommand + +ListByDayCommand --> ListCommandParser : listByDay +deactivate ListByDayCommand + +ListCommandParser --> AddressBookParser : listByDay +deactivate ListCommandParser +'Hidden arrow to position the destroy marker below the end of the activation bar. +ListCommandParser -[hidden]-> AddressBookParser +destroy ListCommandParser + +AddressBookParser --> LogicManager : listByDay +deactivate AddressBookParser + +LogicManager -> ListByDayCommand : execute() +activate ListByDayCommand + +ListByDayCommand -> Model : updateFilteredPersonList(DAY_PREDICATE) + +note right +DAY_PREDICATE: +Predicate to show tutees +whose lessons are +on the specified day +end note + +activate Model + +Model --> ListByDayCommand +deactivate Model + +create CommandResult +ListByDayCommand -> CommandResult +activate CommandResult + +CommandResult --> ListByDayCommand +deactivate CommandResult + +ListByDayCommand --> LogicManager : result +deactivate ListByDayCommand + +[<--LogicManager +deactivate LogicManager +@enduml diff --git a/docs/diagrams/ModelClassDiagram.puml b/docs/diagrams/ModelClassDiagram.puml index 0de5673070d..bd9c0ad12dc 100644 --- a/docs/diagrams/ModelClassDiagram.puml +++ b/docs/diagrams/ModelClassDiagram.puml @@ -13,12 +13,19 @@ Class ModelManager Class UserPrefs Class UniquePersonList +Class Interval Class Person Class Address Class Email Class Name Class Phone -Class Tag +Class Lesson +Class PayRate +Class Day +Class Begin +Class End +Class Subject + Class I #FFFFFF } @@ -37,11 +44,18 @@ UserPrefs .up.|> ReadOnlyUserPrefs AddressBook *--> "1" UniquePersonList UniquePersonList --> "~* all" Person +UniquePersonList ..> Interval Person *--> Name Person *--> Phone Person *--> Email Person *--> Address -Person *--> "*" Tag +Person *--> Subject +Person *--> PayRate +Person *--> Lesson +Lesson ..> Day +Lesson ..> Begin +Lesson ..> End + Person -[hidden]up--> I UniquePersonList -[hidden]right-> I diff --git a/docs/diagrams/PaidSequenceDiagram.puml b/docs/diagrams/PaidSequenceDiagram.puml new file mode 100644 index 00000000000..0abaf55a352 --- /dev/null +++ b/docs/diagrams/PaidSequenceDiagram.puml @@ -0,0 +1,83 @@ +@startuml +!include style.puml + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR +participant ":PaidCommandParser" as PaidCommandParser LOGIC_COLOR +participant "d:PaidCommand" as PaidCommand 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("paid 1") +activate LogicManager + +LogicManager -> AddressBookParser : parseCommand("paid 1") +activate AddressBookParser + +create PaidCommandParser +AddressBookParser -> PaidCommandParser +activate PaidCommandParser + +PaidCommandParser --> AddressBookParser +deactivate PaidCommandParser + +AddressBookParser -> PaidCommandParser : parse("1") +activate PaidCommandParser + +create PaidCommand +PaidCommandParser -> PaidCommand +activate PaidCommand + +PaidCommand --> PaidCommandParser : p +deactivate PaidCommand + +PaidCommandParser --> AddressBookParser : p +deactivate PaidCommandParser +'Hidden arrow to position the destroy marker below the end of the activation bar. +PaidCommandParser -[hidden]-> AddressBookParser +destroy PaidCommandParser + +AddressBookParser --> LogicManager : p +deactivate AddressBookParser + +LogicManager -> PaidCommand : execute() +activate PaidCommand + +PaidCommand -> Model : purgeAddressBook() +activate Model + +Model --> PaidCommand +deactivate Model + +PaidCommand -> Model : markPersonPaid(1) +activate Model + +Model --> PaidCommand +deactivate Model + +PaidCommand -> Model : commitAddressBook() +activate Model + +Model --> PaidCommand +deactivate Model + +create CommandResult +PaidCommand -> CommandResult +activate CommandResult + +CommandResult --> PaidCommand +deactivate CommandResult + +PaidCommand --> LogicManager : result +deactivate PaidCommand + +PaidCommand -[hidden]-> LogicManager +destroy PaidCommand +[<--LogicManager +deactivate LogicManager +@enduml diff --git a/docs/diagrams/RevenueSequenceDiagram.puml b/docs/diagrams/RevenueSequenceDiagram.puml new file mode 100644 index 00000000000..45016a7bf20 --- /dev/null +++ b/docs/diagrams/RevenueSequenceDiagram.puml @@ -0,0 +1,70 @@ +@startuml +!include style.puml +skinparam ArrowFontStyle plain + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR +participant "r:RevenueCommand" as RevenueCommand LOGIC_COLOR +participant ":CommandResult" as CommandResult LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant ":Model" as Model MODEL_COLOR +participant ":Person" as Person MODEL_COLOR +participant ":PayRate" as PayRate MODEL_COLOR +participant ":Lesson" as Lesson MODEL_COLOR +end box + +[-> LogicManager : execute("rev") +activate LogicManager + +LogicManager -> AddressBookParser : parseCommand("rev") +activate AddressBookParser +AddressBookParser -> RevenueCommand +activate RevenueCommand + +RevenueCommand --> AddressBookParser: r +deactivate RevenueCommand + + +AddressBookParser --> LogicManager : r +deactivate AddressBookParser + +LogicManager -> RevenueCommand : execute() +activate RevenueCommand + +RevenueCommand -> Model : getUnfilteredPersonList() +activate Model + +Model --> RevenueCommand +deactivate Model + +loop number of tutees +RevenueCommand -> Person : getMonthlyFee() +activate Person +Person -> PayRate : getValue() +activate PayRate +PayRate --> Person +deactivate PayRate +Person -> Lesson : getMonthlyHours() +activate Lesson +Lesson --> Person +deactivate Lesson +Person --> RevenueCommand : monthlyFee +deactivate Person +end + +create CommandResult +RevenueCommand -> CommandResult +activate CommandResult + +CommandResult --> RevenueCommand +deactivate CommandResult + +RevenueCommand --> LogicManager : result +deactivate RevenueCommand + +[<--LogicManager +deactivate LogicManager +@enduml diff --git a/docs/diagrams/StorageClassDiagram.puml b/docs/diagrams/StorageClassDiagram.puml index a821e06458c..eadc48ec8ba 100644 --- a/docs/diagrams/StorageClassDiagram.puml +++ b/docs/diagrams/StorageClassDiagram.puml @@ -19,7 +19,6 @@ Class "<>\nAddressBookStorage" as AddressBookStorage Class JsonAddressBookStorage Class JsonSerializableAddressBook Class JsonAdaptedPerson -Class JsonAdaptedTag } } @@ -38,6 +37,5 @@ JsonUserPrefsStorage .up.|> UserPrefsStorage JsonAddressBookStorage .up.|> AddressBookStorage JsonAddressBookStorage ..> JsonSerializableAddressBook JsonSerializableAddressBook --> "*" JsonAdaptedPerson -JsonAdaptedPerson --> "*" JsonAdaptedTag @enduml diff --git a/docs/diagrams/UiClassDiagram.puml b/docs/diagrams/UiClassDiagram.puml index 95473d5aa19..5df40221068 100644 --- a/docs/diagrams/UiClassDiagram.puml +++ b/docs/diagrams/UiClassDiagram.puml @@ -15,6 +15,8 @@ Class PersonListPanel Class PersonCard Class StatusBarFooter Class CommandBox +Class ScheduleListPanel +Class ScheduleCard } package Model <> { @@ -35,8 +37,10 @@ MainWindow *-down-> "1" ResultDisplay MainWindow *-down-> "1" PersonListPanel MainWindow *-down-> "1" StatusBarFooter MainWindow --> "0..1" HelpWindow +MainWindow --> "1" ScheduleListPanel -PersonListPanel -down-> "*" PersonCard +PersonListPanel -down--> "*" PersonCard +ScheduleListPanel -down-> "*" ScheduleCard MainWindow -left-|> UiPart @@ -47,7 +51,10 @@ PersonCard --|> UiPart StatusBarFooter --|> UiPart HelpWindow --|> UiPart + + PersonCard ..> Model +ScheduleCard ..> Model UiManager -right-> Logic MainWindow -left-> Logic diff --git a/docs/diagrams/UndoSequenceDiagram.puml b/docs/diagrams/UndoSequenceDiagram.puml index 87ff3e9237e..caa904f7ea8 100644 --- a/docs/diagrams/UndoSequenceDiagram.puml +++ b/docs/diagrams/UndoSequenceDiagram.puml @@ -38,6 +38,9 @@ Model -> VersionedAddressBook : undo() activate VersionedAddressBook VersionedAddressBook -> VersionedAddressBook :resetData(ReadOnlyAddressBook) +activate VersionedAddressBook +VersionedAddressBook --> VersionedAddressBook : +deactivate VersionedAddressBook VersionedAddressBook --> Model : deactivate VersionedAddressBook diff --git a/docs/diagrams/UnpaidAllSequenceDiagram.puml b/docs/diagrams/UnpaidAllSequenceDiagram.puml new file mode 100644 index 00000000000..c67bd05d104 --- /dev/null +++ b/docs/diagrams/UnpaidAllSequenceDiagram.puml @@ -0,0 +1,91 @@ +@startuml +!include style.puml + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR +participant ":UnPaidAllCommandParser" as UnPaidAllCommandParser LOGIC_COLOR +participant "d:UnPaidAllCommand" as UnPaidAllCommand 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("unpaidAll") +activate LogicManager + +LogicManager -> AddressBookParser : parseCommand("unpaidAll") +activate AddressBookParser + +create UnPaidAllCommandParser +AddressBookParser -> UnPaidAllCommandParser +activate UnPaidAllCommandParser + +UnPaidAllCommandParser --> AddressBookParser +deactivate UnPaidAllCommandParser + +AddressBookParser -> UnPaidAllCommandParser : parse("unpaidAll") +activate UnPaidAllCommandParser + +create UnPaidAllCommand +UnPaidAllCommandParser -> UnPaidAllCommand +activate UnPaidAllCommand + +UnPaidAllCommand --> UnPaidAllCommandParser : p +deactivate UnPaidAllCommand + +UnPaidAllCommandParser --> AddressBookParser : p +deactivate UnPaidAllCommandParser + +'Hidden arrow to position the destroy marker below the end of the activation bar. +UnPaidAllCommandParser -[hidden]-> AddressBookParser +destroy UnPaidAllCommandParser + +AddressBookParser --> LogicManager : p +deactivate AddressBookParser + +LogicManager -> UnPaidAllCommand : execute() +activate UnPaidAllCommand + +UnPaidAllCommand -> Model : purgeAddressBook() +activate Model + +Model --> UnPaidAllCommand +deactivate Model + +loop number of tutees + UnPaidAllCommand -> Model : markPersonUnPaid(index) + activate Model + Model --> UnPaidAllCommand + deactivate Model +end loop + +UnPaidAllCommand -> Model : updateFilteredPersonList() +activate Model + +Model --> UnPaidAllCommand +deactivate Model + +UnPaidAllCommand -> Model : commitAddressBook() +activate Model + +Model --> UnPaidAllCommand +deactivate Model + +create CommandResult +UnPaidAllCommand -> CommandResult +activate CommandResult + +CommandResult --> UnPaidAllCommand +deactivate CommandResult + +UnPaidAllCommand -[hidden]-> LogicManager +deactivate UnPaidAllCommand + +UnPaidAllCommand --> LogicManager : result +destroy UnPaidAllCommand +[<--LogicManager +deactivate LogicManager +@enduml diff --git a/docs/images/Add after.png b/docs/images/Add after.png new file mode 100644 index 00000000000..de176678a1b Binary files /dev/null and b/docs/images/Add after.png differ diff --git a/docs/images/Add before and after.png b/docs/images/Add before and after.png new file mode 100644 index 00000000000..fdf49250ced Binary files /dev/null and b/docs/images/Add before and after.png differ diff --git a/docs/images/AddActivityDiagram.png b/docs/images/AddActivityDiagram.png new file mode 100644 index 00000000000..a6af5ea2b41 Binary files /dev/null and b/docs/images/AddActivityDiagram.png differ diff --git a/docs/images/AddSequenceDiagram.png b/docs/images/AddSequenceDiagram.png new file mode 100644 index 00000000000..dfc9e42accc Binary files /dev/null and b/docs/images/AddSequenceDiagram.png differ diff --git a/docs/images/ClearCommand.png b/docs/images/ClearCommand.png new file mode 100644 index 00000000000..f2650848a77 Binary files /dev/null and b/docs/images/ClearCommand.png differ diff --git a/docs/images/DeleteSequenceDiagram.png b/docs/images/DeleteSequenceDiagram.png index e186f7ba096..55e6c86459b 100644 Binary files a/docs/images/DeleteSequenceDiagram.png and b/docs/images/DeleteSequenceDiagram.png differ diff --git a/docs/images/EditActivityDiagram.png b/docs/images/EditActivityDiagram.png new file mode 100644 index 00000000000..5bc0ad845ab Binary files /dev/null and b/docs/images/EditActivityDiagram.png differ diff --git a/docs/images/EditSequenceDiagram.png b/docs/images/EditSequenceDiagram.png new file mode 100644 index 00000000000..4d0ed2b8379 Binary files /dev/null and b/docs/images/EditSequenceDiagram.png differ diff --git a/docs/images/FindSequenceDiagram.png b/docs/images/FindSequenceDiagram.png new file mode 100644 index 00000000000..198fdb1e328 Binary files /dev/null and b/docs/images/FindSequenceDiagram.png differ diff --git a/docs/images/FreeTimeActivityDiagram.png b/docs/images/FreeTimeActivityDiagram.png new file mode 100644 index 00000000000..6b232830d3b Binary files /dev/null and b/docs/images/FreeTimeActivityDiagram.png differ diff --git a/docs/images/FreeTimeSequenceDiagram.png b/docs/images/FreeTimeSequenceDiagram.png new file mode 100644 index 00000000000..89dcdc8c8f6 Binary files /dev/null and b/docs/images/FreeTimeSequenceDiagram.png differ diff --git a/docs/images/HelpAfter.png b/docs/images/HelpAfter.png new file mode 100644 index 00000000000..cc751ce596c Binary files /dev/null and b/docs/images/HelpAfter.png differ diff --git a/docs/images/Layout.png b/docs/images/Layout.png new file mode 100644 index 00000000000..f483df3baaf Binary files /dev/null and b/docs/images/Layout.png differ diff --git a/docs/images/ListByDayCommand.png b/docs/images/ListByDayCommand.png new file mode 100644 index 00000000000..bfd13e30757 Binary files /dev/null and b/docs/images/ListByDayCommand.png differ diff --git a/docs/images/ListByDaySequenceDiagram.png b/docs/images/ListByDaySequenceDiagram.png new file mode 100644 index 00000000000..09db02f157d Binary files /dev/null and b/docs/images/ListByDaySequenceDiagram.png differ diff --git a/docs/images/ModelClassDiagram.png b/docs/images/ModelClassDiagram.png index a19fb1b4ac8..5bc55e02fea 100644 Binary files a/docs/images/ModelClassDiagram.png and b/docs/images/ModelClassDiagram.png differ diff --git a/docs/images/PaidSequenceDiagram.png b/docs/images/PaidSequenceDiagram.png new file mode 100644 index 00000000000..bef961ac020 Binary files /dev/null and b/docs/images/PaidSequenceDiagram.png differ diff --git a/docs/images/RevenueCommandSuccess.png b/docs/images/RevenueCommandSuccess.png new file mode 100644 index 00000000000..6c0ed138178 Binary files /dev/null and b/docs/images/RevenueCommandSuccess.png differ diff --git a/docs/images/RevenueSequenceDiagram.png b/docs/images/RevenueSequenceDiagram.png new file mode 100644 index 00000000000..f446f4598b1 Binary files /dev/null and b/docs/images/RevenueSequenceDiagram.png differ diff --git a/docs/images/StorageClassDiagram.png b/docs/images/StorageClassDiagram.png index 18fa4d0d51f..2ab0b2ab4ab 100644 Binary files a/docs/images/StorageClassDiagram.png and b/docs/images/StorageClassDiagram.png differ diff --git a/docs/images/Ui.png b/docs/images/Ui.png index 5bd77847aa2..6cd3bc210ac 100644 Binary files a/docs/images/Ui.png and b/docs/images/Ui.png differ diff --git a/docs/images/UiClassDiagram.png b/docs/images/UiClassDiagram.png index 11f06d68671..cf7d5fed275 100644 Binary files a/docs/images/UiClassDiagram.png and b/docs/images/UiClassDiagram.png differ diff --git a/docs/images/UndoSequenceDiagram.png b/docs/images/UndoSequenceDiagram.png index c7a7e637266..d7e737548fb 100644 Binary files a/docs/images/UndoSequenceDiagram.png and b/docs/images/UndoSequenceDiagram.png differ diff --git a/docs/images/UnpaidAllSequenceDiagram.png b/docs/images/UnpaidAllSequenceDiagram.png new file mode 100644 index 00000000000..450fa7fb9ad Binary files /dev/null and b/docs/images/UnpaidAllSequenceDiagram.png differ diff --git a/docs/images/deleteAfter.png b/docs/images/deleteAfter.png new file mode 100644 index 00000000000..766aa8cec75 Binary files /dev/null and b/docs/images/deleteAfter.png differ diff --git a/docs/images/editAfter.png b/docs/images/editAfter.png new file mode 100644 index 00000000000..28a13bdeecb Binary files /dev/null and b/docs/images/editAfter.png differ diff --git a/docs/images/findAfter.png b/docs/images/findAfter.png new file mode 100644 index 00000000000..35ab3f35fbf Binary files /dev/null and b/docs/images/findAfter.png differ diff --git a/docs/images/freeTime after.png b/docs/images/freeTime after.png new file mode 100644 index 00000000000..56f8cc3b4c2 Binary files /dev/null and b/docs/images/freeTime after.png differ diff --git a/docs/images/freeTime before and after.png b/docs/images/freeTime before and after.png new file mode 100644 index 00000000000..6486ea2263e Binary files /dev/null and b/docs/images/freeTime before and after.png differ diff --git a/docs/images/heran9.png b/docs/images/heran9.png new file mode 100644 index 00000000000..1007a25b933 Binary files /dev/null and b/docs/images/heran9.png differ diff --git a/docs/images/jovkusuma.png b/docs/images/jovkusuma.png new file mode 100644 index 00000000000..ce8e021f00f Binary files /dev/null and b/docs/images/jovkusuma.png differ diff --git a/docs/images/lambraydon.png b/docs/images/lambraydon.png new file mode 100644 index 00000000000..fa14d5e403d Binary files /dev/null and b/docs/images/lambraydon.png differ diff --git a/docs/images/listunpaidexample.png b/docs/images/listunpaidexample.png new file mode 100644 index 00000000000..165de416d5d Binary files /dev/null and b/docs/images/listunpaidexample.png differ diff --git a/docs/images/paidexample.png b/docs/images/paidexample.png new file mode 100644 index 00000000000..74c10a04336 Binary files /dev/null and b/docs/images/paidexample.png differ diff --git a/docs/images/undoCommand.png b/docs/images/undoCommand.png new file mode 100644 index 00000000000..f771909c37a Binary files /dev/null and b/docs/images/undoCommand.png differ diff --git a/docs/images/unpaidAllexample.png b/docs/images/unpaidAllexample.png new file mode 100644 index 00000000000..ecdbc69f951 Binary files /dev/null and b/docs/images/unpaidAllexample.png differ diff --git a/docs/images/unpaidexample.png b/docs/images/unpaidexample.png new file mode 100644 index 00000000000..59e9941f8ed Binary files /dev/null and b/docs/images/unpaidexample.png differ diff --git a/docs/images/winstonleonard.png b/docs/images/winstonleonard.png new file mode 100644 index 00000000000..efffe449fdc Binary files /dev/null and b/docs/images/winstonleonard.png differ diff --git a/docs/images/yihfei.png b/docs/images/yihfei.png new file mode 100644 index 00000000000..b946612bc97 Binary files /dev/null and b/docs/images/yihfei.png differ diff --git a/docs/index.md b/docs/index.md index 7601dbaad0d..0b680bf19e5 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,6 +1,6 @@ --- layout: page -title: AddressBook Level-3 +title: TuitionConnect --- [![CI Status](https://github.com/se-edu/addressbook-level3/workflows/Java%20CI/badge.svg)](https://github.com/se-edu/addressbook-level3/actions) @@ -8,10 +8,10 @@ title: AddressBook Level-3 ![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). +**TuitionConnect is a desktop application designed to streamline the administrative and financial tasks of your tuition business.** While it has a GUI, most of the user interactions happen using a CLI (Command Line Interface). -* If you are interested in using AddressBook, head over to the [_Quick Start_ section of the **User Guide**](UserGuide.html#quick-start). -* If you are interested about developing AddressBook, the [**Developer Guide**](DeveloperGuide.html) is a good place to start. +* If you are interested in using TuitionConnect, head over to the [_Quick Start_ section of the **User Guide**](UserGuide.html#quick-start). +* If you are interested about developing TuitionConnect, the [**Developer Guide**](DeveloperGuide.html) is a good place to start. **Acknowledgements** diff --git a/docs/team/heran9.md b/docs/team/heran9.md new file mode 100644 index 00000000000..3abc476e6e7 --- /dev/null +++ b/docs/team/heran9.md @@ -0,0 +1,46 @@ +--- +layout: page +title: Lang Heran's Project Portfolio Page +--- + +### Project: TuitionConnect + +TuitionConnect is a **desktop app for simplifying the process of administration and finance management for private tutors, 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, you can maximise tracking tutee-specific details, teaching-schedule management, and finance management. + +Given below are my contributions to the project. + +* **New Feature**: Add the ability to mark the student as paid/unpaid. + * What it does: Allow the user to mark their student has paid for their lessons or not paid for their lessons. + * Justification: This feature helps the users (private tutors) to track their students' payment statues. + * Highlights: The implementation of this feature set up the footstone for future financial management features. The implementation of this feature is not easy since it requires changes to other methods. + * Credits: The implementation of this feature learned from the work of DeleteCommand inside our implementation. + +* **New Feature**: Add the ability to reset all students in the current list as unpaid. + * What it does: Allow the user to reset all student shown in the list as unpaid for their lessons. + * Justification: This feature helps the users (private tutors) to reset all students' payment statues at the beginning of the month when there is new turn to get tuition fee. + * Highlights: This feature is a follow-up feature of paid/unpaid features, which allows the users to easily reset students payment statuses. + +* **New Feature**: Add the ability to list all unpaid students. + * What it does: Allow the user check all unpaid students in a list. + * Justification: This feature helps the users (private tutors) to easily see which students haven't paid. + * Highlights: This feature is a combination feature of list and paid features, combine the both functionalities of these two features,it comes up with a new and powerful feature. + +* **Code contributed**: + * [RepoSense link](https://nus-cs2103-ay2324s1.github.io/tp-dashboard/?search=heran9&breakdown=true) + +* **Enhancements to existing features**: + * Enhancements to the list command. Add the ability to list all unpaid students. + * What it does: Allow the user check all unpaid students in a list. + * Justification: This feature helps the users (private tutors) to easily see which students haven't paid. + * Highlights: This feature is a combination feature of list and paid features, combine the both functionalities of these two features,it comes up with a new and powerful feature. + +* **Documentation**: + * User Guide: + * Added documentation for the features `paid`, `unpaid`, `unpaidAll`, `list unpaid` + * Help to determine the overall format and structure of UG. + * Developer Guide: + * Added implementation details of the `paid`, `unpaid`, `list unpaid` features. + * Added Activity Diagrams for `paid` and `unpaidAll` features. + +* **Review/Mentoring contributions**: + * Reviewed and offered suggestions for team member's pull requests. diff --git a/docs/team/jovkusuma.md b/docs/team/jovkusuma.md new file mode 100644 index 00000000000..d1896fad89b --- /dev/null +++ b/docs/team/jovkusuma.md @@ -0,0 +1,65 @@ +--- +layout: page +title: Armando Jovan Kusuma's Project Portfolio Page +--- + +### Project: TuitionConnect +TuitionConnect is a **desktop app** built for tutors and tutoring businesses to simplify the process of +administration and finance management, optimized for use via a **Command Line Interface** (CLI) while +still having the benefits of a Graphical User Interface (GUI). + + +**Code contributed:** Click here for [RepoSense Link](https://nus-cs2103-ay2324s1.github.io/tp-dashboard/?search=jovkusuma&sort=groupTitle&sortWithin=title&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=true&checkedFileTypes=docs~functional-code~test-code&since=2023-09-22&tabOpen=true&tabType=authorship&tabAuthor=jovkusuma&tabRepo=AY2324S1-CS2103T-F10-4%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code&authorshipIsBinaryFileTypeChecked=false&authorshipIsIgnoredFilesChecked=false). + + +**Enhancements implemented:** + +* Refactored the `add` and `edit` command. + * Remove tags as parameter for both `add` and `edit` command. + * (Pull Requests [#90](https://github.com/AY2324S1-CS2103T-F10-4/tp/pull/90)) + +* Enhance the `find` command. + * Allow users to find tutees using partial keywords. + * Allow users to find by name and/or subject to find for tutees. + * (Pull Requests [#68](https://github.com/AY2324S1-CS2103T-F10-4/tp/pull/68), [#127](https://github.com/AY2324S1-CS2103T-F10-4/tp/pull/127)) + +* Enforce that `begin` time should strictly be before `end` time. + * Restrict users to only input `begin` times which are before `end` times. + * (Pull Requests [#111](https://github.com/AY2324S1-CS2103T-F10-4/tp/pull/111)) + +* Write and fixing test cases: + * Fixed the test cases for `StringUtilTest`, `AddressBookParserTest`, `FindCommandTest`, `FindCommandParserTest`, `NameContainsKeywordsPredicateTest` + to support refactored `find` command (Pull Requests [#68](https://github.com/AY2324S1-CS2103T-F10-4/tp/pull/68), [#127](https://github.com/AY2324S1-CS2103T-F10-4/tp/pull/127)). + * Fixed the test cases for `AddCommandTest`, `EditCommandTest` to support the enforcement of begin time less than end time (Pull Requests [#111](https://github.com/AY2324S1-CS2103T-F10-4/tp/pull/111)). + * Wrote test cases for `SubjectContainsKeywordsPredicateTest`, `NameSubjectPredicateTest` + to test the `find` Command (Pull Requests [#127](https://github.com/AY2324S1-CS2103T-F10-4/tp/pull/127), [#199](https://github.com/AY2324S1-CS2103T-F10-4/tp/pull/199)). + +**Contributions to the UG:** + +* Added `Using this guide` and `Glossary` sections (Pull Requests [#209](https://github.com/AY2324S1-CS2103T-F10-4/tp/pull/209)). +* Wrote features and descriptions for `help`, `find`, `edit` and `delete` (Pull Requests [#235](https://github.com/AY2324S1-CS2103T-F10-4/tp/pull/235)). + + +**Contributions to the DG:** + +* Added use cases for `add`, `edit` and `find` commands (Pull Requests [#102](https://github.com/AY2324S1-CS2103T-F10-4/tp/pull/102), [#210](https://github.com/AY2324S1-CS2103T-F10-4/tp/pull/210)). +* Added description and sequence diagram for `find` and `edit` commands (Pull Requests [#197](https://github.com/AY2324S1-CS2103T-F10-4/tp/pull/197)). +* Wrote manual test cases for `find` command (Pull requests [#234](https://github.com/AY2324S1-CS2103T-F10-4/tp/pull/234)). +* Wrote planned enhancements (Pull Requests [#210](https://github.com/AY2324S1-CS2103T-F10-4/tp/pull/210)). + + +**Contributions to team-based tasks**: + + +* Updating config.yml (Pull Requests [#53](https://github.com/AY2324S1-CS2103T-F10-4/tp/pull/53)). + + +**Review/Mentoring contributions:** + +* Reviewed a total of 31 PRs. +* Reviewed and offered suggestions for team member's pull requests. + + +**Contributions beyond the team project:** + +* Assisted other teams and reported 9 bugs during the [Practical Exam Dry Run](https://github.com/jovkusuma/ped/issues). diff --git a/docs/team/lambraydon.md b/docs/team/lambraydon.md new file mode 100644 index 00000000000..eb70ca3655b --- /dev/null +++ b/docs/team/lambraydon.md @@ -0,0 +1,58 @@ +--- +layout: page +title: Lam Jin Heng Braydon's Project Portfolio Page +--- + +### Project: TuitionConnect + +TuitionConnect is a desktop app built for tutors and tutoring businesses to simplify the process of administration and finance management, optimized for use via a Command Line Interface (CLI) while still having the benefits of a Graphical User Interface (GUI). + +Given below are my contributions to the project. + +* **New Feature**: `ListByDayCommand` + * What it does: Shows the current list of tutees who have lessons on a specified day + * Justification: This feature allows private tutors to be reminded if they have any classes on that particular day with a single command + * Highlights: This feature extends the `ListCommand` class and uses the same command syntax `list` followed by an optional `[DAY]` parameter. If the user does not specify the `[DAY]` parameter, it will be parsed as a `ListCommand` and shows all tutees in the list. If the `[DAY]` + parameter is included, it will be parsed as a `ListByDayCommand` + +* **New Feature**: `Undo` + * What it does: Undo commands that alter the data of tutees such as `delete`, `add` and `edit` + * Justification: This feature allows private tutors to efficiently undo commands if they made a mistake. + * Highlights: This feature saves the state of the `VersionedAddressBook` after every command that alters the data of tutees into temporary memory which will be cleared everytime the application is closed + +* **New Feature**: `Redo` + * What it does: Redo undo commands that alter the data of tutees such as `delete`, `add` and `edit` + * Justification: This feature allows private tutors to efficiently redo commands if they made a mistake. + +* **New Feature**: Created a `ScheduleList` that is displayed on the UI as a `ScheduleListPanel` which allows tutor to view upcoming lessons. + * What it does: Sorts a private tutor's lessons according the day and time so in an `ObservableList`. + * Justification: This feature allows private tutors to easily glance their schedule for the week. + * Highlights: This features requires a `Lesson` Class that encapsulates the `Day`, `Begin` and `End` fields in an `add` command. `Lesson` objects are than sorted using a `LessonComparator` + +* **Code contributed**: + * [Reposense Link](https://nus-cs2103-ay2324s1.github.io/tp-dashboard/?search=lambraydon&sort=groupTitle&sortWithin=title&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=true&checkedFileTypes=docs~functional-code~test-code&since=2023-09-22&tabOpen=true&zFR=false&tabType=authorship&tabAuthor=lambraydon&tabRepo=AY2324S1-CS2103T-F10-4%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code&authorshipIsBinaryFileTypeChecked=false&authorshipIsIgnoredFilesChecked=false) + +* **Project management**: + * Set up codecov for team repo + * Created team organisation on github + * Created initial UI mockup in v1.2 + +* **Enhancements to existing features**: + * Refactored code, added `Day` parameter (Pull requests [\#70](https://github.com/AY2324S1-CS2103T-F10-4/tp/pull/70)) + * Updated GUI by modifying UI of list tiles (Pull requests [\#117](https://github.com/AY2324S1-CS2103T-F10-4/tp/pull/117)) + * Refactored `Person#isSamePerson` such that a person is considered a duplicate if they have the `Name` and `Phone` (Pull requests [\#126](https://github.com/AY2324S1-CS2103T-F10-4/tp/pull/126)) + +* **Documentation**: + * User Guide: + * Added and modified documentation for the features `list` , `undo` and `redo` features (Pull requests [\#217](https://github.com/AY2324S1-CS2103T-F10-4/tp/pull/217)) + * Added parameters requirement section + * Developer Guide: + * Added implementation details and sequence diagrams of the `list`, `undo`, `redo` and `add` feature. (Pull requests [\#188](https://github.com/AY2324S1-CS2103T-F10-4/tp/pull/188), [\#187](https://github.com/AY2324S1-CS2103T-F10-4/tp/pull/187), [\#201](https://github.com/AY2324S1-CS2103T-F10-4/tp/pull/201)) + * Added planned enhancements (Pull requests [\#226](https://github.com/AY2324S1-CS2103T-F10-4/tp/pull/226)) + * Added manual test cases for `undo`, `redo` and `list`(Pull requests [\#226](https://github.com/AY2324S1-CS2103T-F10-4/tp/pull/226)) + +* **Community**: + * Assisted other teams in finding bugs during the Practical Exam Dry Run + +* **Tools**: + * codeCov diff --git a/docs/team/winstonleonard.md b/docs/team/winstonleonard.md new file mode 100644 index 00000000000..880ab89ebdc --- /dev/null +++ b/docs/team/winstonleonard.md @@ -0,0 +1,74 @@ +--- +layout: page +title: Winston Leonard Prayonggo's Project Portfolio Page +--- + +### Project: TuitionConnect +TuitionConnect is a **desktop app** built for tutors and tutoring businesses to simplify the process of +administration and finance management, optimized for use via a **Command Line Interface** (CLI) while +still having the benefits of a Graphical User Interface (GUI). + + +**Code contributed:** Click here for [RepoSense Link](https://nus-cs2103-ay2324s1.github.io/tp-dashboard/?search=winstonleonard&sort=groupTitle&sortWithin=title&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=true&checkedFileTypes=docs~functional-code~test-code&since=2023-09-22&tabOpen=true&tabType=authorship&tabAuthor=WinstonLeonard&tabRepo=AY2324S1-CS2103T-F10-4%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code&authorshipIsBinaryFileTypeChecked=false&authorshipIsIgnoredFilesChecked=false) + + +**Enhancements implemented:** + + + * Refactored the `add` and `edit` command. + * Allowed tutees to take in `Subject`, `Day`, `Begin`, `End` fields. + * (Pull Requests [#72](https://github.com/AY2324S1-CS2103T-F10-4/tp/pull/72)) + + + * Added clashing schedules mechanism + * Prevents the `add` and `edit` command from adding or editing tutees that will result in clashing schedules. + * (Pull Requests [#93](https://github.com/AY2324S1-CS2103T-F10-4/tp/pull/93)) + + + * Implemented the `freeTime` command + * Command that finds free time in the user's schedule. + * (Pull Requests [#121](https://github.com/AY2324S1-CS2103T-F10-4/tp/pull/121)) + + + * Write and fixing test cases: + * Fixed the test cases for `AddCommandTest`, `EditCommandTest`, `AddCommandParserTest`, `EditCommandParserTest`, `AddressBookParserTest`. `PersonBuilder`, `PersonUtil`, `TypicalPerson` + to support refactored `add` and `edit` command. (Pull Requests [#73](https://github.com/AY2324S1-CS2103T-F10-4/tp/pull/73)) + * Wrote test cases for `DurationTest`, `IntervalTest`, `IntervalBeginTest`, `IntervalEndTest`, `IntervalDayTest`, `TimeSlotTest`, `FreeTimeCommandTest`, `FreeTimeCommandParserTest` + to test the `freeTime` Command. (Pull Requests [#121](https://github.com/AY2324S1-CS2103T-F10-4/tp/pull/121)) + * Wrote test cases for clashing schedules for `add` and `edit` command (Pull Requests [#198](https://github.com/AY2324S1-CS2103T-F10-4/tp/pull/198)) + + +**Contributions to the UG:** + + + * Added Welcome Section, Introduction Section, Symbol Syntax Table. (Pull Requests [#207](https://github.com/AY2324S1-CS2103T-F10-4/tp/pull/207)) + * Added the Layout Section (Pull Requests [#216](https://github.com/AY2324S1-CS2103T-F10-4/tp/pull/216)) + * Wrote descriptions for `add` and `freeTime` (Pull Requests [#221](https://github.com/AY2324S1-CS2103T-F10-4/tp/pull/221)) + + +**Contributions to the DG:** + + +* Modified Model Class Diagram. (Pull Requests [#192](https://github.com/AY2324S1-CS2103T-F10-4/tp/pull/192)) +* Added description and Sequence Diagram for `freeTime` command (Pull Requests [#192](https://github.com/AY2324S1-CS2103T-F10-4/tp/pull/192)) +* Added Activity Diagrams for `freeTime` and `edit` command (Pull Requests [#211](https://github.com/AY2324S1-CS2103T-F10-4/tp/pull/211)) +* Wrote manual test cases for `add`, `freeTime`, `edit`, and also added a glossary. (Pull requests [#244](https://github.com/AY2324S1-CS2103T-F10-4/tp/pull/224)) +* Wrote planned enhancements (Pull Requests [#232](https://github.com/AY2324S1-CS2103T-F10-4/tp/pull/232)) + + +**Contributions to team-based tasks**: + + +* Enabling assertions (Pull Requests [#104](https://github.com/AY2324S1-CS2103T-F10-4/tp/pull/104)) + + +**Review/Mentoring contributions:** + + +* Reviewed and offered suggestions for team member's pull requests. + + +**Contributions beyond the team project:** + + +* Assisted other teams in finding bugs during the [Practical Exam Dry Run](https://github.com/WinstonLeonard/ped/issues) diff --git a/docs/team/yihfei.md b/docs/team/yihfei.md new file mode 100644 index 00000000000..3bf10e92678 --- /dev/null +++ b/docs/team/yihfei.md @@ -0,0 +1,58 @@ +--- +layout: page +title: Lim Yih Fei's Project Portfolio Page +--- +### Project: TuitionConnect +TuitionConnect is a **desktop app** built for tutors and tutoring businesses to simplify the process of +administration and finance management, optimized for use via a **Command Line Interface** (CLI) while +still having the benefits of a Graphical User Interface (GUI). + +Given below are my contributions to the project. + +* **New Feature**: Added the ability to calculate total monthly revenue. (Pull requests +[\#88](https://github.com/AY2324S1-CS2103T-F10-4/tp/pull/88), +[\#112](https://github.com/AY2324S1-CS2103T-F10-4/tp/pull/112), +[\#114](https://github.com/AY2324S1-CS2103T-F10-4/tp/pull/114)) + * What it does: allows the user to see a monthly revenue figure. + * Justification: This feature allows private tutors to financially plan better. + * Highlights: This feature accounts for different number of days in a month for calculation of revenue. + +* **New Feature**: Added a `ScheduleListPanel` to see a summarised view of upcoming lessons. (Pull requests +[\#71](https://github.com/AY2324S1-CS2103T-F10-4/tp/pull/71), +[\#115](https://github.com/AY2324S1-CS2103T-F10-4/tp/pull/115)) + * What it does: allows the user to quickly see a list of lesson cards that is sorted sequentially from the current day. + * Justification: This feature allows private tutors to easily plan for their schedules. + * Highlights: This features requires an enhancement of lesson comparator to sort the lesson cards. + +* **Code contributed**: [RepoSense link](https://nus-cs2103-ay2324s1.github.io/tp-dashboard/?search=yihfei&sort=groupTitle&sortWithin=title&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=true&checkedFileTypes=docs~functional-code~test-code&since=2023-09-22&tabOpen=true&zFR=false&tabType=authorship&tabAuthor=yihfei&tabRepo=AY2324S1-CS2103T-F10-4%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code&authorshipIsBinaryFileTypeChecked=false&authorshipIsIgnoredFilesChecked=false) + +* **Project management**: + * Managed releases for jar files (trial v1.3 and v1.3 for Mock PE) + * Maintained the team collaboration google docs (e.g. documenting features for v1.3) + * Renamed the product (Pull request [\#118](https://github.com/AY2324S1-CS2103T-F10-4/tp/pull/118)) + * Created the icon for product (Pull request [\#118](https://github.com/AY2324S1-CS2103T-F10-4/tp/pull/118)) + * Drafted the mock UI + +* **Enhancements to existing features**: + * Enhanced the GUI (Implement coloured tags for paid indicator, tags for subject, rounded corners for result display and search bar) (Pull requests +[\#118](https://github.com/AY2324S1-CS2103T-F10-4/tp/pull/118) +[\#124](https://github.com/AY2324S1-CS2103T-F10-4/tp/pull/124)) + * Wrote additional tests for lesson class and RevenueCommand related features (Pull requests +[\#112](https://github.com/AY2324S1-CS2103T-F10-4/tp/pull/112) +[\#222](https://github.com/AY2324S1-CS2103T-F10-4/tp/pull/222)) + +* **Documentation**: + * User Guide and Developer Guide: + * Added and modified documentation for the features `add` , `edit` and `rev` features (Pull requests +[\#131](https://github.com/AY2324S1-CS2103T-F10-4/tp/pull/131), +[\#189](https://github.com/AY2324S1-CS2103T-F10-4/tp/pull/189), +[\#194](https://github.com/AY2324S1-CS2103T-F10-4/tp/pull/194), +[\#233](https://github.com/AY2324S1-CS2103T-F10-4/tp/pull/233)) + * Updated GUI pictures + * Added implementation details and sequence diagrams of the `add` and `rev` features (Pull requests +[\#189](https://github.com/AY2324S1-CS2103T-F10-4/tp/pull/189), +[\#194](https://github.com/AY2324S1-CS2103T-F10-4/tp/pull/194)) + +* **Community**: + * Participated in PE Dry run + diff --git a/hello.txt b/hello.txt new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/main/java/seedu/address/MainApp.java b/src/main/java/seedu/address/MainApp.java index 3d6bd06d5af..76381ef72ec 100644 --- a/src/main/java/seedu/address/MainApp.java +++ b/src/main/java/seedu/address/MainApp.java @@ -35,8 +35,9 @@ * Runs the application. */ public class MainApp extends Application { + // initializes Ui, logic and storage for GUI - public static final Version VERSION = new Version(0, 2, 2, true); + public static final Version VERSION = new Version(1, 3, 1, true); private static final Logger logger = LogsCenter.getLogger(MainApp.class); diff --git a/src/main/java/seedu/address/commons/util/StringUtil.java b/src/main/java/seedu/address/commons/util/StringUtil.java index 61cc8c9a1cb..60039f3821a 100644 --- a/src/main/java/seedu/address/commons/util/StringUtil.java +++ b/src/main/java/seedu/address/commons/util/StringUtil.java @@ -18,7 +18,7 @@ public class StringUtil { *
examples:
      *       containsWordIgnoreCase("ABc def", "abc") == true
      *       containsWordIgnoreCase("ABc def", "DEF") == true
-     *       containsWordIgnoreCase("ABc def", "AB") == false //not a full word match
+     *       containsWordIgnoreCase("ABc def", "AB") == true //Partial name search
      *       
* @param sentence cannot be null * @param word cannot be null, cannot be empty, must be a single word @@ -27,15 +27,15 @@ public static boolean containsWordIgnoreCase(String sentence, String word) { requireNonNull(sentence); requireNonNull(word); - String preppedWord = word.trim(); + String preppedWord = word.toLowerCase().trim(); checkArgument(!preppedWord.isEmpty(), "Word parameter cannot be empty"); checkArgument(preppedWord.split("\\s+").length == 1, "Word parameter should be a single word"); - String preppedSentence = sentence; + String preppedSentence = sentence.toLowerCase(); String[] wordsInPreppedSentence = preppedSentence.split("\\s+"); return Arrays.stream(wordsInPreppedSentence) - .anyMatch(preppedWord::equalsIgnoreCase); + .anyMatch(x -> x.contains(preppedWord)); } /** diff --git a/src/main/java/seedu/address/logic/Logic.java b/src/main/java/seedu/address/logic/Logic.java index 92cd8fa605a..92222f119a1 100644 --- a/src/main/java/seedu/address/logic/Logic.java +++ b/src/main/java/seedu/address/logic/Logic.java @@ -33,6 +33,9 @@ public interface Logic { /** Returns an unmodifiable view of the filtered list of persons */ ObservableList getFilteredPersonList(); + /** Returns an unmodifiable view of the sorted list of persons by dateTime */ + ObservableList getScheduleList(); + /** * Returns the user prefs' address book file path. */ diff --git a/src/main/java/seedu/address/logic/LogicManager.java b/src/main/java/seedu/address/logic/LogicManager.java index 5aa3b91c7d0..3db1d3a94a6 100644 --- a/src/main/java/seedu/address/logic/LogicManager.java +++ b/src/main/java/seedu/address/logic/LogicManager.java @@ -71,6 +71,11 @@ public ObservableList getFilteredPersonList() { return model.getFilteredPersonList(); } + @Override + public ObservableList getScheduleList() { + return model.getScheduleList(); + } + @Override public Path getAddressBookFilePath() { return model.getAddressBookFilePath(); diff --git a/src/main/java/seedu/address/logic/Messages.java b/src/main/java/seedu/address/logic/Messages.java index ecd32c31b53..a9f772d8d6a 100644 --- a/src/main/java/seedu/address/logic/Messages.java +++ b/src/main/java/seedu/address/logic/Messages.java @@ -14,10 +14,15 @@ public class Messages { public static final String MESSAGE_UNKNOWN_COMMAND = "Unknown command"; public static final String MESSAGE_INVALID_COMMAND_FORMAT = "Invalid command format! \n%1$s"; - public static final String MESSAGE_INVALID_PERSON_DISPLAYED_INDEX = "The person index provided is invalid"; - public static final String MESSAGE_PERSONS_LISTED_OVERVIEW = "%1$d persons listed!"; + public static final String MESSAGE_BEGIN_AFTER_END = "Begin time must be before End time."; + public static final String MESSAGE_INVALID_TUTEE_DISPLAYED_INDEX = "The tutee index provided is invalid"; + public static final String MESSAGE_TUTEES_LISTED_OVERVIEW = "%1$d tutees listed!"; public static final String MESSAGE_DUPLICATE_FIELDS = "Multiple values specified for the following single-valued field(s): "; + public static final String MESSAGE_CANNOT_UNDO = + "Nothing to undo!"; + public static final String MESSAGE_CANNOT_REDO = + "Nothing to redo!"; /** * Returns an error message indicating the duplicate prefixes. @@ -41,10 +46,14 @@ public static String format(Person person) { .append(person.getPhone()) .append("; Email: ") .append(person.getEmail()) + .append("; IsPaid: ") + .append(person.getPaid()) .append("; Address: ") .append(person.getAddress()) - .append("; Tags: "); - person.getTags().forEach(builder::append); + .append("; Day: ") + .append(person.getDay()) + .append("; PayRate: ") + .append(person.getPayRate()); return builder.toString(); } diff --git a/src/main/java/seedu/address/logic/commands/AddCommand.java b/src/main/java/seedu/address/logic/commands/AddCommand.java index 5d7185a9680..c628eb16abc 100644 --- a/src/main/java/seedu/address/logic/commands/AddCommand.java +++ b/src/main/java/seedu/address/logic/commands/AddCommand.java @@ -1,16 +1,25 @@ package seedu.address.logic.commands; import static java.util.Objects.requireNonNull; +import static seedu.address.logic.Messages.MESSAGE_BEGIN_AFTER_END; import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_BEGIN; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DAY; import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; +import static seedu.address.logic.parser.CliSyntax.PREFIX_END; import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PAYRATE; import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; -import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; +import static seedu.address.logic.parser.CliSyntax.PREFIX_SUBJECT; +import java.util.logging.Logger; + +import seedu.address.commons.core.LogsCenter; import seedu.address.commons.util.ToStringBuilder; import seedu.address.logic.Messages; import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.model.Model; +import seedu.address.model.person.Lesson; import seedu.address.model.person.Person; /** @@ -26,20 +35,32 @@ public class AddCommand extends Command { + PREFIX_PHONE + "PHONE " + PREFIX_EMAIL + "EMAIL " + PREFIX_ADDRESS + "ADDRESS " - + "[" + PREFIX_TAG + "TAG]...\n" - + "Example: " + COMMAND_WORD + " " + + PREFIX_SUBJECT + "SUBJECT " + + PREFIX_DAY + "DAY " + + PREFIX_BEGIN + "START " + + PREFIX_END + "END " + + PREFIX_PAYRATE + "PAYRATE" + + "\nExample: " + COMMAND_WORD + " " + PREFIX_NAME + "John Doe " + PREFIX_PHONE + "98765432 " + PREFIX_EMAIL + "johnd@example.com " + PREFIX_ADDRESS + "311, Clementi Ave 2, #02-25 " - + PREFIX_TAG + "friends " - + PREFIX_TAG + "owesMoney"; + + PREFIX_SUBJECT + "Maths " + + PREFIX_DAY + "Mon " + + PREFIX_BEGIN + "1200 " + + PREFIX_END + "1300 " + + PREFIX_PAYRATE + "20.00"; public static final String MESSAGE_SUCCESS = "New person added: %1$s"; - public static final String MESSAGE_DUPLICATE_PERSON = "This person already exists in the address book"; + + public static final String MESSAGE_DUPLICATE_PERSON = "This tutee already exists"; + + public static final String MESSAGE_DUPLICATE_DATE = "This date and time clashes with an existing schedule"; private final Person toAdd; + private final Logger logger = LogsCenter.getLogger(getClass()); + /** * Creates an AddCommand to add the specified {@code Person} */ @@ -52,11 +73,25 @@ public AddCommand(Person person) { public CommandResult execute(Model model) throws CommandException { requireNonNull(model); + if (!Lesson.isValid(toAdd.getBegin(), toAdd.getEnd())) { + logger.info("[AddCommand.execute()]: Begin after End"); + throw new CommandException(String.format(MESSAGE_BEGIN_AFTER_END, AddCommand.MESSAGE_USAGE)); + } + if (model.hasPerson(toAdd)) { + logger.info("[AddCommand.execute()]: Duplicate Person"); throw new CommandException(MESSAGE_DUPLICATE_PERSON); } + if (model.hasDate(toAdd)) { + logger.info("[AddCommand.execute()]: Clashing Schedules"); + throw new CommandException(MESSAGE_DUPLICATE_DATE); + } + + model.purgeAddressBook(); model.addPerson(toAdd); + model.commitAddressBook(); + return new CommandResult(String.format(MESSAGE_SUCCESS, Messages.format(toAdd))); } diff --git a/src/main/java/seedu/address/logic/commands/ClearCommand.java b/src/main/java/seedu/address/logic/commands/ClearCommand.java index 9c86b1fa6e4..4cea8ecdf36 100644 --- a/src/main/java/seedu/address/logic/commands/ClearCommand.java +++ b/src/main/java/seedu/address/logic/commands/ClearCommand.java @@ -11,13 +11,17 @@ 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 = "Tutee data has been cleared!"; @Override public CommandResult execute(Model model) { requireNonNull(model); + + model.purgeAddressBook(); model.setAddressBook(new AddressBook()); + model.commitAddressBook(); + return new CommandResult(MESSAGE_SUCCESS); } } diff --git a/src/main/java/seedu/address/logic/commands/DeleteCommand.java b/src/main/java/seedu/address/logic/commands/DeleteCommand.java index 1135ac19b74..547fbd28b63 100644 --- a/src/main/java/seedu/address/logic/commands/DeleteCommand.java +++ b/src/main/java/seedu/address/logic/commands/DeleteCommand.java @@ -37,11 +37,15 @@ public CommandResult execute(Model model) throws CommandException { List lastShownList = model.getFilteredPersonList(); if (targetIndex.getZeroBased() >= lastShownList.size()) { - throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + throw new CommandException(Messages.MESSAGE_INVALID_TUTEE_DISPLAYED_INDEX); } Person personToDelete = lastShownList.get(targetIndex.getZeroBased()); + + model.purgeAddressBook(); model.deletePerson(personToDelete); + model.commitAddressBook(); + return new CommandResult(String.format(MESSAGE_DELETE_PERSON_SUCCESS, Messages.format(personToDelete))); } diff --git a/src/main/java/seedu/address/logic/commands/EditCommand.java b/src/main/java/seedu/address/logic/commands/EditCommand.java index 4b581c7331e..3dca6faa746 100644 --- a/src/main/java/seedu/address/logic/commands/EditCommand.java +++ b/src/main/java/seedu/address/logic/commands/EditCommand.java @@ -1,20 +1,25 @@ package seedu.address.logic.commands; import static java.util.Objects.requireNonNull; +import static seedu.address.logic.Messages.MESSAGE_BEGIN_AFTER_END; import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_BEGIN; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DAY; import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; +import static seedu.address.logic.parser.CliSyntax.PREFIX_END; import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PAID; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PAYRATE; import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; -import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; +import static seedu.address.logic.parser.CliSyntax.PREFIX_SUBJECT; import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; -import java.util.Collections; -import java.util.HashSet; import java.util.List; import java.util.Objects; import java.util.Optional; -import java.util.Set; +import java.util.logging.Logger; +import seedu.address.commons.core.LogsCenter; import seedu.address.commons.core.index.Index; import seedu.address.commons.util.CollectionUtil; import seedu.address.commons.util.ToStringBuilder; @@ -22,11 +27,16 @@ import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.model.Model; import seedu.address.model.person.Address; +import seedu.address.model.person.Begin; +import seedu.address.model.person.Day; import seedu.address.model.person.Email; +import seedu.address.model.person.End; +import seedu.address.model.person.Lesson; import seedu.address.model.person.Name; +import seedu.address.model.person.PayRate; import seedu.address.model.person.Person; import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; +import seedu.address.model.person.Subject; /** * Edits the details of an existing person in the address book. @@ -43,17 +53,25 @@ public class EditCommand extends Command { + "[" + PREFIX_PHONE + "PHONE] " + "[" + PREFIX_EMAIL + "EMAIL] " + "[" + PREFIX_ADDRESS + "ADDRESS] " - + "[" + PREFIX_TAG + "TAG]...\n" + + "[" + PREFIX_SUBJECT + "SUBJECT] " + + "[" + PREFIX_DAY + "DAY] " + + "[" + PREFIX_BEGIN + "BEGIN] " + + "[" + PREFIX_END + "END] " + + "[" + PREFIX_PAID + "PAID]...\n" + + "[" + PREFIX_PAYRATE + "PAYRATE]...\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."; + public static final String MESSAGE_DUPLICATE_PERSON = "This tutee already exists"; + public static final String MESSAGE_DUPLICATE_DATE = "This date clashes with an existing schedule"; private final Index index; private final EditPersonDescriptor editPersonDescriptor; + private final Logger logger = LogsCenter.getLogger(getClass()); + /** * @param index of the person in the filtered person list to edit @@ -73,18 +91,41 @@ public CommandResult execute(Model model) throws CommandException { List lastShownList = model.getFilteredPersonList(); if (index.getZeroBased() >= lastShownList.size()) { - throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + throw new CommandException(Messages.MESSAGE_INVALID_TUTEE_DISPLAYED_INDEX); } Person personToEdit = lastShownList.get(index.getZeroBased()); Person editedPerson = createEditedPerson(personToEdit, editPersonDescriptor); + if (!Lesson.isValid(editedPerson.getBegin(), editedPerson.getEnd())) { + throw new CommandException(String.format(MESSAGE_BEGIN_AFTER_END, AddCommand.MESSAGE_USAGE)); + } + + if (editedPerson.getBegin() == null) { + if (!Lesson.isValid(personToEdit.getBegin(), editedPerson.getEnd())) { + throw new CommandException(String.format(MESSAGE_BEGIN_AFTER_END, AddCommand.MESSAGE_USAGE)); + } + } + + if (editedPerson.getEnd() == null) { + if (!Lesson.isValid(editedPerson.getBegin(), personToEdit.getEnd())) { + throw new CommandException(String.format(MESSAGE_BEGIN_AFTER_END, AddCommand.MESSAGE_USAGE)); + } + } + if (!personToEdit.isSamePerson(editedPerson) && model.hasPerson(editedPerson)) { + logger.info("[EditCommand.execute()]: Editing would result in duplicate persons"); throw new CommandException(MESSAGE_DUPLICATE_PERSON); + } else if (editPersonDescriptor.getEditSchedule() && model.hasDate(editedPerson)) { + logger.info("[EditCommand.execute()]: Editing would result in clashing schedules"); + throw new CommandException(MESSAGE_DUPLICATE_DATE); } - model.setPerson(personToEdit, editedPerson); + model.setPerson(personToEdit, editedPerson, editPersonDescriptor.getEditSchedule()); + model.purgeAddressBook(); model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + model.commitAddressBook(); + return new CommandResult(String.format(MESSAGE_EDIT_PERSON_SUCCESS, Messages.format(editedPerson))); } @@ -99,9 +140,15 @@ private static Person createEditedPerson(Person personToEdit, EditPersonDescript 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); + Subject updatedSubject = editPersonDescriptor.getSubject().orElse(personToEdit.getSubject()); + Day updatedDay = editPersonDescriptor.getDay().orElse(personToEdit.getDay()); + Begin updatedBegin = editPersonDescriptor.getBegin().orElse(personToEdit.getBegin()); + End updatedEnd = editPersonDescriptor.getEnd().orElse(personToEdit.getEnd()); + Boolean updatedPaid = editPersonDescriptor.getPaid().orElse(personToEdit.getPaid()); + PayRate updatedPayRate = editPersonDescriptor.getPayRate().orElse(personToEdit.getPayRate()); + + return new Person(updatedName, updatedPhone, updatedEmail, updatedAddress, updatedSubject, updatedDay, + updatedBegin, updatedEnd, updatedPaid, updatedPayRate); } @Override @@ -137,7 +184,13 @@ public static class EditPersonDescriptor { private Phone phone; private Email email; private Address address; - private Set tags; + private Subject subject; + private Day day; + private Begin begin; + private End end; + private Boolean paid; + private PayRate payRate; + private Boolean editSchedule = false; public EditPersonDescriptor() {} @@ -150,14 +203,20 @@ public EditPersonDescriptor(EditPersonDescriptor toCopy) { setPhone(toCopy.phone); setEmail(toCopy.email); setAddress(toCopy.address); - setTags(toCopy.tags); + setSubject(toCopy.subject); + setDay(toCopy.day); + setBegin(toCopy.begin); + setEnd(toCopy.end); + setPaid(toCopy.paid); + setPayRate(toCopy.payRate); + setEditSchedule(toCopy.editSchedule); } /** * Returns true if at least one field is edited. */ public boolean isAnyFieldEdited() { - return CollectionUtil.isAnyNonNull(name, phone, email, address, tags); + return CollectionUtil.isAnyNonNull(name, phone, email, address, subject, day, begin, end, paid, payRate); } public void setName(Name name) { @@ -192,21 +251,59 @@ 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; + public void setSubject(Subject subject) { + this.subject = subject; } - /** - * 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(); + public Optional getSubject() { + return Optional.ofNullable(subject); + } + + public void setDay(Day day) { + this.day = day; + } + + public Optional getDay() { + return Optional.ofNullable(day); + } + + public void setBegin(Begin begin) { + this.begin = begin; + } + + public Optional getBegin() { + return Optional.ofNullable(begin); + } + + public void setEnd(End end) { + this.end = end; + } + + public Optional getEnd() { + return Optional.ofNullable(end); + } + + public void setPaid(Boolean paid) { + this.paid = paid; + } + + public Optional getPaid() { + return Optional.ofNullable(paid); + } + + public void setPayRate(PayRate payRate) { + this.payRate = payRate; + } + + public Optional getPayRate() { + return Optional.ofNullable(payRate); + } + public void setEditSchedule(Boolean setTrue) { + this.editSchedule = setTrue; + } + + public Boolean getEditSchedule() { + return this.editSchedule; } @Override @@ -225,7 +322,13 @@ public boolean equals(Object other) { && Objects.equals(phone, otherEditPersonDescriptor.phone) && Objects.equals(email, otherEditPersonDescriptor.email) && Objects.equals(address, otherEditPersonDescriptor.address) - && Objects.equals(tags, otherEditPersonDescriptor.tags); + && Objects.equals(subject, otherEditPersonDescriptor.subject) + && Objects.equals(day, otherEditPersonDescriptor.day) + && Objects.equals(begin, otherEditPersonDescriptor.begin) + && Objects.equals(end, otherEditPersonDescriptor.end) + && Objects.equals(paid, otherEditPersonDescriptor.paid) + && Objects.equals(payRate, otherEditPersonDescriptor.payRate); + } @Override @@ -235,7 +338,12 @@ public String toString() { .add("phone", phone) .add("email", email) .add("address", address) - .add("tags", tags) + .add("day", day) + .add("begin", begin) + .add("end", end) + .add("paid", paid) + .add("payrate", payRate) + .add("editSchedule", editSchedule) .toString(); } } diff --git a/src/main/java/seedu/address/logic/commands/FindCommand.java b/src/main/java/seedu/address/logic/commands/FindCommand.java index 72b9eddd3a7..dc60ba54cb7 100644 --- a/src/main/java/seedu/address/logic/commands/FindCommand.java +++ b/src/main/java/seedu/address/logic/commands/FindCommand.java @@ -1,39 +1,66 @@ package seedu.address.logic.commands; import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_SUBJECT; +import java.util.logging.Level; +import java.util.logging.Logger; + +import seedu.address.commons.core.LogsCenter; import seedu.address.commons.util.ToStringBuilder; import seedu.address.logic.Messages; import seedu.address.model.Model; import seedu.address.model.person.NameContainsKeywordsPredicate; +import seedu.address.model.person.NameSubjectPredicate; +import seedu.address.model.person.SubjectContainsKeywordsPredicate; /** * Finds and lists all persons in address book whose name contains any of the argument keywords. - * Keyword matching is case insensitive. + * 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"; + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Find persons with names or subjects " + + "matching the specified keywords (case-insensitive).\n" + + "Parameters: " + PREFIX_NAME + "[NAME_KEYWORD] " + PREFIX_SUBJECT + "[SUBJECT_KEYWORD]\n" + + "Examples: \n" + + "1. " + COMMAND_WORD + " " + PREFIX_NAME + "Alice " + PREFIX_SUBJECT + "Maths \n" + + "2. " + COMMAND_WORD + " " + PREFIX_NAME + "Alice \n" + + "3. " + COMMAND_WORD + " " + PREFIX_SUBJECT + "Maths"; + + private static final Logger logger = LogsCenter.getLogger(FindCommand.class); private final NameContainsKeywordsPredicate predicate; + private final SubjectContainsKeywordsPredicate subject; - public FindCommand(NameContainsKeywordsPredicate predicate) { + /** + * Constructore for the findCommand class + * + * @param predicate the keyword to be searched which starts with the prefix n/ + * @param subject the keyword to be searched which starts with the prefix sb/ + */ + public FindCommand(NameContainsKeywordsPredicate predicate, SubjectContainsKeywordsPredicate subject) { + requireAllNonNull(predicate, subject); this.predicate = predicate; + this.subject = subject; } @Override public CommandResult execute(Model model) { requireNonNull(model); - model.updateFilteredPersonList(predicate); + NameSubjectPredicate nameSubject = new NameSubjectPredicate(predicate, subject); + model.updateFilteredPersonList(nameSubject); + + logger.log(Level.INFO, "FindCommand executed with predicate: " + predicate + "and subject:" + subject); + return new CommandResult( - String.format(Messages.MESSAGE_PERSONS_LISTED_OVERVIEW, model.getFilteredPersonList().size())); + String.format(Messages.MESSAGE_TUTEES_LISTED_OVERVIEW, + model.getFilteredPersonList().size())); } - @Override public boolean equals(Object other) { if (other == this) { diff --git a/src/main/java/seedu/address/logic/commands/FreeTimeCommand.java b/src/main/java/seedu/address/logic/commands/FreeTimeCommand.java new file mode 100644 index 00000000000..f8e542140dc --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/FreeTimeCommand.java @@ -0,0 +1,75 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_BEGIN; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DAY; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DURATION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_END; + +import java.text.ParseException; +import java.util.List; + +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.interval.Interval; +import seedu.address.model.interval.TimeSlot; + +/** + * FreeTimeCommand + */ +public class FreeTimeCommand extends Command { + + public static final String COMMAND_WORD = "freeTime"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": finds a free time. " + + "Parameters: " + + PREFIX_DAY + "DAY " + + PREFIX_DURATION + "MINUTES " + + PREFIX_BEGIN + "BEGIN " + + PREFIX_END + "END " + + "Example: " + COMMAND_WORD + " " + + PREFIX_DAY + "Monday " + + PREFIX_DURATION + "60 " + + PREFIX_BEGIN + "0800 " + + PREFIX_END + "2200 "; + + public static final String MESSAGE_SUCCESS = "Here is your list of free time:\n%s"; + public static final String MESSAGE_ERROR = "An error occurred when executing the freeTime command."; + private final Interval toFind; + + /** + * Creates an FreeTimeCommand to check for the specified {@code Interval} + */ + public FreeTimeCommand(Interval interval) { + requireNonNull(interval); + toFind = interval; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + List result = model.findInterval(toFind); + try { + List timeslots = TimeSlot.parseIntervals(result); + List availableTime = TimeSlot.findAvailableTime(timeslots, toFind); + return new CommandResult(String.format(MESSAGE_SUCCESS, TimeSlot.printResults(availableTime))); + } catch (ParseException e) { + throw new CommandException(MESSAGE_ERROR); + } + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof FreeTimeCommand)) { + return false; + } + + FreeTimeCommand otherFindCommand = (FreeTimeCommand) other; + return toFind.equals(otherFindCommand.toFind); + } +} diff --git a/src/main/java/seedu/address/logic/commands/IsPaidCommand.java b/src/main/java/seedu/address/logic/commands/IsPaidCommand.java new file mode 100644 index 00000000000..82c651f1902 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/IsPaidCommand.java @@ -0,0 +1,68 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import java.util.List; + +import seedu.address.commons.core.index.Index; +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.logic.Messages; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.person.Person; + +/** + * Check if the tutee is paid. + * (Make use of the template of Delete Command and did some modification) + */ +public class IsPaidCommand extends Command { + public static final String COMMAND_WORD = "ispaid"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + "Example: " + COMMAND_WORD + " 1"; + + public static final String MESSAGE_CHECK_PERSON_PAID = "Check Whether Person Paid, Paid: %1$s"; + + private final Index targetIndex; + + public IsPaidCommand(Index targetIndex) { + this.targetIndex = targetIndex; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + List lastShownList = model.getFilteredPersonList(); + + if (targetIndex.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_TUTEE_DISPLAYED_INDEX); + } + + Person personToGetPaid = lastShownList.get(targetIndex.getZeroBased()); + model.getPersonPaid(personToGetPaid); + return new CommandResult(String.format(MESSAGE_CHECK_PERSON_PAID, personToGetPaid.getPaid())); + + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof IsPaidCommand)) { + return false; + } + + IsPaidCommand otherPaidCommands = (IsPaidCommand) other; + return targetIndex.equals(otherPaidCommands.targetIndex); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("targetIndex", targetIndex) + .toString(); + } +} diff --git a/src/main/java/seedu/address/logic/commands/ListByDayCommand.java b/src/main/java/seedu/address/logic/commands/ListByDayCommand.java new file mode 100644 index 00000000000..e9ae2ee0674 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/ListByDayCommand.java @@ -0,0 +1,41 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import seedu.address.logic.Messages; +import seedu.address.model.Model; +import seedu.address.model.person.DayPredicate; + +/** + * Lists all persons in the address book to the user by day. + */ +public class ListByDayCommand extends ListCommand { + private final DayPredicate predicate; + + public ListByDayCommand(DayPredicate predicate) { + this.predicate = predicate; + } + + @Override + public CommandResult execute(Model model) { + requireNonNull(model); + model.updateFilteredPersonList(predicate); + return new CommandResult( + String.format(Messages.MESSAGE_TUTEES_LISTED_OVERVIEW, model.getFilteredPersonList().size())); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof ListByDayCommand)) { + return false; + } + + ListByDayCommand otherListByDayCommand = (ListByDayCommand) other; + return predicate.equals(otherListByDayCommand.predicate); + } +} diff --git a/src/main/java/seedu/address/logic/commands/ListCommand.java b/src/main/java/seedu/address/logic/commands/ListCommand.java index 84be6ad2596..89f33857939 100644 --- a/src/main/java/seedu/address/logic/commands/ListCommand.java +++ b/src/main/java/seedu/address/logic/commands/ListCommand.java @@ -5,15 +5,18 @@ 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 tutees"; - public static final String MESSAGE_SUCCESS = "Listed all persons"; - + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Lists all tutees or list tutees filtered by day.\n " + + "Parameters: DAY (OPTIONAL).\n" + + "Example: " + COMMAND_WORD + " Monday"; @Override public CommandResult execute(Model model) { diff --git a/src/main/java/seedu/address/logic/commands/ListUnPaidCommand.java b/src/main/java/seedu/address/logic/commands/ListUnPaidCommand.java new file mode 100644 index 00000000000..db8befc7778 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/ListUnPaidCommand.java @@ -0,0 +1,49 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.logic.Messages; +import seedu.address.model.Model; +import seedu.address.model.person.PaidPredicate; + +/** + * Lists all unpaid persons. + */ +public class ListUnPaidCommand extends ListCommand { + private final PaidPredicate predicate; + + public ListUnPaidCommand(PaidPredicate predicate) { + this.predicate = predicate; + } + + @Override + public CommandResult execute(Model model) { + requireNonNull(model); + model.updateFilteredPersonList(predicate); + return new CommandResult( + String.format(Messages.MESSAGE_TUTEES_LISTED_OVERVIEW, model.getFilteredPersonList().size())); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof ListUnPaidCommand)) { + return false; + } + + ListUnPaidCommand otherListUnPaidCommand = (ListUnPaidCommand) other; + return predicate.equals(otherListUnPaidCommand.predicate); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("predicate", predicate) + .toString(); + } +} diff --git a/src/main/java/seedu/address/logic/commands/PaidCommand.java b/src/main/java/seedu/address/logic/commands/PaidCommand.java new file mode 100644 index 00000000000..a1d4b57c72b --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/PaidCommand.java @@ -0,0 +1,71 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import java.util.List; + +import seedu.address.commons.core.index.Index; +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.logic.Messages; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.person.Person; + +/** + * Mark the specific tutee as paid. + * (Make use of the template of Delete Command and did some modification) + */ +public class PaidCommand extends Command { + public static final String COMMAND_WORD = "paid"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + "Example: " + COMMAND_WORD + " 1"; + + public static final String MESSAGE_MARK_PERSON_PAID_SUCCESS = "MARK PERSON PAID SUCCESS"; + + private final Index targetIndex; + + public PaidCommand(Index targetIndex) { + this.targetIndex = targetIndex; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + List lastShownList = model.getFilteredPersonList(); + + if (targetIndex.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_TUTEE_DISPLAYED_INDEX); + } + + Person personToMarkPaid = lastShownList.get(targetIndex.getZeroBased()); + + model.purgeAddressBook(); + model.markPersonPaid(personToMarkPaid); + model.commitAddressBook(); + + return new CommandResult(String.format(MESSAGE_MARK_PERSON_PAID_SUCCESS, personToMarkPaid.getPaid())); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof PaidCommand)) { + return false; + } + + PaidCommand otherPaidCommands = (PaidCommand) other; + return targetIndex.equals(otherPaidCommands.targetIndex); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("targetIndex", targetIndex) + .toString(); + } +} diff --git a/src/main/java/seedu/address/logic/commands/RedoCommand.java b/src/main/java/seedu/address/logic/commands/RedoCommand.java new file mode 100644 index 00000000000..9703c1ce5d3 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/RedoCommand.java @@ -0,0 +1,28 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import seedu.address.logic.Messages; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; + +/** + * Redo the Address Book to the previous undoned saved version + */ +public class RedoCommand extends Command { + public static final String COMMAND_WORD = "redo"; + public static final String MESSAGE_SUCCESS = "Successfully redo previous command"; + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + if (!model.canRedoAddressBook()) { + throw new CommandException(Messages.MESSAGE_CANNOT_REDO); + } + + model.redoAddressBook(); + + return new CommandResult(MESSAGE_SUCCESS); + } +} diff --git a/src/main/java/seedu/address/logic/commands/RevenueCommand.java b/src/main/java/seedu/address/logic/commands/RevenueCommand.java new file mode 100644 index 00000000000..539babb9e64 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/RevenueCommand.java @@ -0,0 +1,39 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import java.util.List; + +import seedu.address.model.Model; +import seedu.address.model.person.Person; + +/** + * Calculates the total monthly revenue from all tutees. + */ +public class RevenueCommand extends Command { + + public static final String COMMAND_WORD = "rev"; + + public static final String MESSAGE_SUCCESS = "Successfully calculated" + "\nTotal monthly revenue: $"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Calculates total revenue " + + "earned from all tutees in a month.\n " + "Example: " + COMMAND_WORD; + + @Override + public CommandResult execute(Model model) { + requireNonNull(model); + List fullList = model.getUnfilteredPersonList(); + + double totalRevenue = 0; + // for every person, find revenue, add them tgt + for (Person tutee : fullList) { + // get revenue per lesson + double individualMonthlyFee = tutee.getMonthlyFee(); + assert individualMonthlyFee >= 0 : "monthly fee cannot be negative"; + totalRevenue += individualMonthlyFee; + } + + String formattedTotalRevenue = String.format("%.2f", totalRevenue); + return new CommandResult(MESSAGE_SUCCESS + formattedTotalRevenue); + } +} diff --git a/src/main/java/seedu/address/logic/commands/UnPaidAllCommand.java b/src/main/java/seedu/address/logic/commands/UnPaidAllCommand.java new file mode 100644 index 00000000000..91f68f5aec4 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/UnPaidAllCommand.java @@ -0,0 +1,38 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; + +import java.util.List; + +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.person.Person; + +/** + * Mark All tutees in the current list as unpaid. + */ +public class UnPaidAllCommand extends Command { + public static final String COMMAND_WORD = "unpaidAll"; + + public static final String MESSAGE_USAGE = COMMAND_WORD; + + public static final String MESSAGE_MARK_ALL_PERSON_UNPAID_SUCCESS = "RESET ALL PERSON TO UNPAID SUCCESS"; + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + List lastShownList = model.getFilteredPersonList(); + + model.purgeAddressBook(); + + for (Person person : lastShownList) { + model.markPersonUnPaid(person); + } + + model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + model.commitAddressBook(); + + return new CommandResult(MESSAGE_MARK_ALL_PERSON_UNPAID_SUCCESS); + } +} diff --git a/src/main/java/seedu/address/logic/commands/UnPaidCommand.java b/src/main/java/seedu/address/logic/commands/UnPaidCommand.java new file mode 100644 index 00000000000..3aa336974a5 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/UnPaidCommand.java @@ -0,0 +1,71 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import java.util.List; + +import seedu.address.commons.core.index.Index; +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.logic.Messages; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.person.Person; + +/** + * Mark the tutee as unpaid. + * (Make use of the template of Delete Command and did some modification) + */ +public class UnPaidCommand extends Command { + public static final String COMMAND_WORD = "unpaid"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + "Example: " + COMMAND_WORD + " 1"; + + public static final String MESSAGE_MARK_PERSON_UNPAID_SUCCESS = "MARK PERSON UNPAID SUCCESS"; + + private final Index targetIndex; + + public UnPaidCommand(Index targetIndex) { + this.targetIndex = targetIndex; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + List lastShownList = model.getFilteredPersonList(); + + if (targetIndex.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_TUTEE_DISPLAYED_INDEX); + } + + Person personToMarkUnPaid = lastShownList.get(targetIndex.getZeroBased()); + + model.purgeAddressBook(); + model.markPersonUnPaid(personToMarkUnPaid); + model.commitAddressBook(); + + return new CommandResult(String.format(MESSAGE_MARK_PERSON_UNPAID_SUCCESS, (personToMarkUnPaid.getPaid()))); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof UnPaidCommand)) { + return false; + } + + UnPaidCommand otherUnPaidCommands = (UnPaidCommand) other; + return targetIndex.equals(otherUnPaidCommands.targetIndex); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("targetIndex", targetIndex) + .toString(); + } +} diff --git a/src/main/java/seedu/address/logic/commands/UndoCommand.java b/src/main/java/seedu/address/logic/commands/UndoCommand.java new file mode 100644 index 00000000000..65e06e1c91d --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/UndoCommand.java @@ -0,0 +1,28 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import seedu.address.logic.Messages; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; + +/** + * Undo the Address Book to the previous saved version + */ +public class UndoCommand extends Command { + public static final String COMMAND_WORD = "undo"; + public static final String MESSAGE_SUCCESS = "Successfully undo previous command"; + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + if (!model.canUndoAddressBook()) { + throw new CommandException(Messages.MESSAGE_CANNOT_UNDO); + } + + model.undoAddressBook(); + + 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 index 4ff1a97ed77..91a98eb7569 100644 --- a/src/main/java/seedu/address/logic/parser/AddCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/AddCommandParser.java @@ -2,23 +2,29 @@ import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_BEGIN; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DAY; import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; +import static seedu.address.logic.parser.CliSyntax.PREFIX_END; import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PAYRATE; import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; -import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; +import static seedu.address.logic.parser.CliSyntax.PREFIX_SUBJECT; -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.Begin; +import seedu.address.model.person.Day; import seedu.address.model.person.Email; +import seedu.address.model.person.End; import seedu.address.model.person.Name; +import seedu.address.model.person.PayRate; import seedu.address.model.person.Person; import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; - +import seedu.address.model.person.Subject; /** * Parses input arguments and creates a new AddCommand object */ @@ -31,21 +37,29 @@ public class AddCommandParser implements Parser { */ public AddCommand parse(String args) throws ParseException { ArgumentMultimap argMultimap = - ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, PREFIX_TAG); + ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, + PREFIX_SUBJECT, PREFIX_DAY, PREFIX_BEGIN, PREFIX_END, PREFIX_PAYRATE); - if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_ADDRESS, PREFIX_PHONE, PREFIX_EMAIL) + if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_ADDRESS, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_SUBJECT, + PREFIX_DAY, PREFIX_BEGIN, PREFIX_END, PREFIX_PAYRATE) || !argMultimap.getPreamble().isEmpty()) { throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE)); } - argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS); + argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, + PREFIX_SUBJECT, PREFIX_DAY, PREFIX_BEGIN, PREFIX_END, PREFIX_PAYRATE); 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)); + Subject subject = ParserUtil.parseSubject(argMultimap.getValue(PREFIX_SUBJECT).get()); + Day day = ParserUtil.parseDay(argMultimap.getValue(PREFIX_DAY).get()); + Begin begin = ParserUtil.parseBegin(argMultimap.getValue(PREFIX_BEGIN).get()); + End end = ParserUtil.parseEnd(argMultimap.getValue(PREFIX_END).get()); + PayRate payRate = ParserUtil.parsePayRate(argMultimap.getValue(PREFIX_PAYRATE).get()); + boolean paid = false; - Person person = new Person(name, phone, email, address, tagList); + Person person = new Person(name, phone, email, address, subject, day, begin, end, paid, payRate); return new AddCommand(person); } diff --git a/src/main/java/seedu/address/logic/parser/AddressBookParser.java b/src/main/java/seedu/address/logic/parser/AddressBookParser.java index 3149ee07e0b..9798995e4dc 100644 --- a/src/main/java/seedu/address/logic/parser/AddressBookParser.java +++ b/src/main/java/seedu/address/logic/parser/AddressBookParser.java @@ -15,8 +15,16 @@ import seedu.address.logic.commands.EditCommand; import seedu.address.logic.commands.ExitCommand; import seedu.address.logic.commands.FindCommand; +import seedu.address.logic.commands.FreeTimeCommand; import seedu.address.logic.commands.HelpCommand; +import seedu.address.logic.commands.IsPaidCommand; import seedu.address.logic.commands.ListCommand; +import seedu.address.logic.commands.PaidCommand; +import seedu.address.logic.commands.RedoCommand; +import seedu.address.logic.commands.RevenueCommand; +import seedu.address.logic.commands.UnPaidAllCommand; +import seedu.address.logic.commands.UnPaidCommand; +import seedu.address.logic.commands.UndoCommand; import seedu.address.logic.parser.exceptions.ParseException; /** @@ -69,7 +77,7 @@ public Command parseCommand(String userInput) throws ParseException { return new FindCommandParser().parse(arguments); case ListCommand.COMMAND_WORD: - return new ListCommand(); + return new ListCommandParser().parse(arguments); case ExitCommand.COMMAND_WORD: return new ExitCommand(); @@ -77,10 +85,33 @@ public Command parseCommand(String userInput) throws ParseException { case HelpCommand.COMMAND_WORD: return new HelpCommand(); + case PaidCommand.COMMAND_WORD: + return new PaidCommandParser().parse(arguments); + + case IsPaidCommand.COMMAND_WORD: + return new IsPaidCommandParser().parse(arguments); + + case UndoCommand.COMMAND_WORD: + return new UndoCommand(); + + case RedoCommand.COMMAND_WORD: + return new RedoCommand(); + + case RevenueCommand.COMMAND_WORD: + return new RevenueCommand(); + + case UnPaidCommand.COMMAND_WORD: + return new UnPaidCommandParser().parse(arguments); + + case UnPaidAllCommand.COMMAND_WORD: + return new UnPaidAllCommand(); + + case FreeTimeCommand.COMMAND_WORD: + return new FreeTimeCommandParser().parse(arguments); + default: logger.finer("This user input caused a ParseException: " + userInput); throw new ParseException(MESSAGE_UNKNOWN_COMMAND); } } - } diff --git a/src/main/java/seedu/address/logic/parser/CliSyntax.java b/src/main/java/seedu/address/logic/parser/CliSyntax.java index 75b1a9bf119..fe02973ee97 100644 --- a/src/main/java/seedu/address/logic/parser/CliSyntax.java +++ b/src/main/java/seedu/address/logic/parser/CliSyntax.java @@ -10,6 +10,12 @@ public class CliSyntax { public static final Prefix PREFIX_PHONE = new Prefix("p/"); public static final Prefix PREFIX_EMAIL = new Prefix("e/"); public static final Prefix PREFIX_ADDRESS = new Prefix("a/"); - public static final Prefix PREFIX_TAG = new Prefix("t/"); + public static final Prefix PREFIX_SUBJECT = new Prefix("sb/"); + public static final Prefix PREFIX_DAY = new Prefix("d/"); + public static final Prefix PREFIX_BEGIN = new Prefix("b/"); + public static final Prefix PREFIX_END = new Prefix("end/"); + public static final Prefix PREFIX_PAID = new Prefix("p/"); + public static final Prefix PREFIX_PAYRATE = new Prefix("pr/"); + public static final Prefix PREFIX_DURATION = new Prefix("dur/"); } diff --git a/src/main/java/seedu/address/logic/parser/EditCommandParser.java b/src/main/java/seedu/address/logic/parser/EditCommandParser.java index 46b3309a78b..1a9e7d6950f 100644 --- a/src/main/java/seedu/address/logic/parser/EditCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/EditCommandParser.java @@ -3,21 +3,20 @@ import static java.util.Objects.requireNonNull; import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_BEGIN; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DAY; import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; +import static seedu.address.logic.parser.CliSyntax.PREFIX_END; import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PAYRATE; 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 static seedu.address.logic.parser.CliSyntax.PREFIX_SUBJECT; 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 @@ -32,7 +31,8 @@ public class EditCommandParser implements Parser { public EditCommand parse(String args) throws ParseException { requireNonNull(args); ArgumentMultimap argMultimap = - ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, PREFIX_TAG); + ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, + PREFIX_SUBJECT, PREFIX_DAY, PREFIX_BEGIN, PREFIX_END, PREFIX_PAYRATE); Index index; @@ -42,7 +42,8 @@ public EditCommand parse(String args) throws ParseException { throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditCommand.MESSAGE_USAGE), pe); } - argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS); + argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, + PREFIX_SUBJECT, PREFIX_DAY, PREFIX_BEGIN, PREFIX_END, PREFIX_PAYRATE); EditPersonDescriptor editPersonDescriptor = new EditPersonDescriptor(); @@ -58,28 +59,34 @@ public EditCommand parse(String args) throws ParseException { 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); + if (argMultimap.getValue(PREFIX_SUBJECT).isPresent()) { + editPersonDescriptor.setSubject(ParserUtil.parseSubject(argMultimap.getValue(PREFIX_SUBJECT).get())); } - return new EditCommand(index, editPersonDescriptor); - } + if (argMultimap.getValue(PREFIX_DAY).isPresent()) { + editPersonDescriptor.setDay(ParserUtil.parseDay(argMultimap.getValue(PREFIX_DAY).get())); + editPersonDescriptor.setEditSchedule(true); + } - /** - * 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 (argMultimap.getValue(PREFIX_BEGIN).isPresent()) { + editPersonDescriptor.setBegin(ParserUtil.parseBegin(argMultimap.getValue(PREFIX_BEGIN).get())); + editPersonDescriptor.setEditSchedule(true); + } - if (tags.isEmpty()) { - return Optional.empty(); + if (argMultimap.getValue(PREFIX_END).isPresent()) { + editPersonDescriptor.setEnd(ParserUtil.parseEnd(argMultimap.getValue(PREFIX_END).get())); + editPersonDescriptor.setEditSchedule(true); + } + + if (argMultimap.getValue(PREFIX_PAYRATE).isPresent()) { + editPersonDescriptor.setPayRate(ParserUtil.parsePayRate(argMultimap.getValue(PREFIX_PAYRATE).get())); } - Collection tagSet = tags.size() == 1 && tags.contains("") ? Collections.emptySet() : tags; - return Optional.of(ParserUtil.parseTags(tagSet)); - } + if (!editPersonDescriptor.isAnyFieldEdited()) { + throw new ParseException(EditCommand.MESSAGE_NOT_EDITED); + } + + return new EditCommand(index, editPersonDescriptor); + } } diff --git a/src/main/java/seedu/address/logic/parser/FindCommandParser.java b/src/main/java/seedu/address/logic/parser/FindCommandParser.java index 2867bde857b..194dc398e79 100644 --- a/src/main/java/seedu/address/logic/parser/FindCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/FindCommandParser.java @@ -1,33 +1,79 @@ package seedu.address.logic.parser; import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_SUBJECT; -import java.util.Arrays; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; +import seedu.address.commons.core.LogsCenter; import seedu.address.logic.commands.FindCommand; import seedu.address.logic.parser.exceptions.ParseException; import seedu.address.model.person.NameContainsKeywordsPredicate; +import seedu.address.model.person.SubjectContainsKeywordsPredicate; + /** * Parses input arguments and creates a new FindCommand object */ public class FindCommandParser implements Parser { + private static final Logger logger = LogsCenter.getLogger(FindCommandParser.class); + /** * 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()) { + logger.log(Level.INFO, "Parsing FindCommand with arguments" + args); + ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_SUBJECT); + argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_NAME, PREFIX_SUBJECT); + + if (!argMultimap.getPreamble().isEmpty() && argMultimap.getValue(PREFIX_NAME).isPresent()) { + logger.log(Level.WARNING, "Invalid input format: Both preamble and name specified."); + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE)); + } + if (!argMultimap.getPreamble().isEmpty() && argMultimap.getValue(PREFIX_SUBJECT).isPresent()) { + logger.log(Level.WARNING, "No name or subject specified in FindCommand."); throw new ParseException( String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE)); } - String[] nameKeywords = trimmedArgs.split("\\s+"); + logger.log(Level.INFO, "Successfully parsed FindCommand."); + if (argMultimap.getValue(PREFIX_NAME).isPresent()) { + assert ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get()) != null + : "Parsed name cannot be null"; + String nameKeywords = argMultimap.getValue(PREFIX_NAME).get(); + if (nameKeywords.split("\\s+").length > 1) { + throw new ParseException("Name can only take one word."); + } + ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get()); + } - return new FindCommand(new NameContainsKeywordsPredicate(Arrays.asList(nameKeywords))); - } + if (argMultimap.getValue(PREFIX_SUBJECT).isPresent()) { + assert ParserUtil.parseSubject(argMultimap.getValue(PREFIX_SUBJECT).get()) != null + : "Parsed subject cannot be null"; + String subjectKeywords = argMultimap.getValue(PREFIX_SUBJECT).get(); + if (subjectKeywords.split("\\s+").length > 1) { + throw new ParseException("Subject can only take one word."); + } + ParserUtil.parseSubject(argMultimap.getValue(PREFIX_SUBJECT).get()); + } + + List nameKeywords = argMultimap.getAllValues(PREFIX_NAME); + List subjectKeywords = argMultimap.getAllValues(PREFIX_SUBJECT); + if (nameKeywords.isEmpty() && subjectKeywords.isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE)); + } + + return new FindCommand( + new NameContainsKeywordsPredicate(nameKeywords), + new SubjectContainsKeywordsPredicate(subjectKeywords)); + } } diff --git a/src/main/java/seedu/address/logic/parser/FreeTimeCommandParser.java b/src/main/java/seedu/address/logic/parser/FreeTimeCommandParser.java new file mode 100644 index 00000000000..9adeb598d4c --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/FreeTimeCommandParser.java @@ -0,0 +1,59 @@ +package seedu.address.logic.parser; + +import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_BEGIN; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DAY; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DURATION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_END; + +import java.util.stream.Stream; + +import seedu.address.logic.commands.FreeTimeCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.interval.Duration; +import seedu.address.model.interval.Interval; +import seedu.address.model.interval.IntervalBegin; +import seedu.address.model.interval.IntervalDay; +import seedu.address.model.interval.IntervalEnd; + +/** + * Parses input arguments and creates a new FreeTimeCommand object + */ +public class FreeTimeCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the FreeTimeCommand + * and returns an FreeTimeCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public FreeTimeCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_DAY, PREFIX_DURATION, PREFIX_BEGIN, PREFIX_END); + + if (!arePrefixesPresent(argMultimap, PREFIX_DAY, PREFIX_DURATION, PREFIX_BEGIN, PREFIX_END) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, FreeTimeCommand.MESSAGE_USAGE)); + } + + argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_DAY, PREFIX_DURATION, PREFIX_BEGIN, PREFIX_END); + + IntervalDay day = ParserUtil.parseIntervalDay(argMultimap.getValue(PREFIX_DAY).get()); + Duration duration = ParserUtil.parseDuration(argMultimap.getValue(PREFIX_DURATION).get()); + IntervalBegin begin = ParserUtil.parseIntervalBegin(argMultimap.getValue(PREFIX_BEGIN).get()); + IntervalEnd end = ParserUtil.parseIntervalEnd(argMultimap.getValue(PREFIX_END).get()); + + + Interval interval = new Interval(day, duration, begin, end); + + return new FreeTimeCommand(interval); + } + + /** + * 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/IsPaidCommandParser.java b/src/main/java/seedu/address/logic/parser/IsPaidCommandParser.java new file mode 100644 index 00000000000..349c36040f2 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/IsPaidCommandParser.java @@ -0,0 +1,30 @@ +package seedu.address.logic.parser; + +import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.IsPaidCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a new IsPaidCommand object + * (Make use of the template of DeleteCommandParser and did some modifications) + */ +public class IsPaidCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the IsPaidCommand + * and returns a IsPaidCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public IsPaidCommand parse(String args) throws ParseException { + try { + Index index = ParserUtil.parseIndex(args); + return new IsPaidCommand(index); + } catch (ParseException pe) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, IsPaidCommand.MESSAGE_USAGE), pe); + } + } + +} diff --git a/src/main/java/seedu/address/logic/parser/ListCommandParser.java b/src/main/java/seedu/address/logic/parser/ListCommandParser.java new file mode 100644 index 00000000000..8b1a1339344 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/ListCommandParser.java @@ -0,0 +1,48 @@ +package seedu.address.logic.parser; + +import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import java.util.logging.Logger; + +import seedu.address.commons.core.LogsCenter; +import seedu.address.logic.commands.ListByDayCommand; +import seedu.address.logic.commands.ListCommand; +import seedu.address.logic.commands.ListUnPaidCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.person.Day; +import seedu.address.model.person.DayPredicate; +import seedu.address.model.person.PaidPredicate; + +/** + * Parses input arguments and creates a new ListCommand object + */ +public class ListCommandParser implements Parser { + private final Logger logger = LogsCenter.getLogger(getClass()); + /** + * 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 ListCommand parse(String args) throws ParseException { + String trimmedArgs = args.trim(); + if (trimmedArgs.isEmpty()) { + return new ListCommand(); + } + + if (trimmedArgs.startsWith("unpaid")) { + return new ListUnPaidCommand(new PaidPredicate(true)); + } + + Day day; + + try { + day = new Day(trimmedArgs); + } catch (IllegalArgumentException e) { + logger.info("[ListCommandParser.parse()]: Invalid extraneous parameter"); + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, ListByDayCommand.MESSAGE_USAGE)); + } + + return new ListByDayCommand(new DayPredicate(day)); + } +} diff --git a/src/main/java/seedu/address/logic/parser/PaidCommandParser.java b/src/main/java/seedu/address/logic/parser/PaidCommandParser.java new file mode 100644 index 00000000000..65b574875be --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/PaidCommandParser.java @@ -0,0 +1,34 @@ +package seedu.address.logic.parser; + +import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import java.util.logging.Logger; + +import seedu.address.commons.core.LogsCenter; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.PaidCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a new PaidCommand object + * (Make use of the template of DeleteCommandParser and did some modifications) + */ +public class PaidCommandParser implements Parser { + private final Logger logger = LogsCenter.getLogger(getClass()); + /** + * Parses the given {@code String} of arguments in the context of the PaidCommand + * and returns a PaidCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public PaidCommand parse(String args) throws ParseException { + try { + Index index = ParserUtil.parseIndex(args); + return new PaidCommand(index); + } catch (ParseException pe) { + logger.info("[PaidCommandParser.parse()]: Invalid extraneous parameter"); + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, PaidCommand.MESSAGE_USAGE), pe); + } + } + +} diff --git a/src/main/java/seedu/address/logic/parser/ParserUtil.java b/src/main/java/seedu/address/logic/parser/ParserUtil.java index b117acb9c55..2d7dd41d194 100644 --- a/src/main/java/seedu/address/logic/parser/ParserUtil.java +++ b/src/main/java/seedu/address/logic/parser/ParserUtil.java @@ -2,19 +2,22 @@ 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.interval.Duration; +import seedu.address.model.interval.IntervalBegin; +import seedu.address.model.interval.IntervalDay; +import seedu.address.model.interval.IntervalEnd; import seedu.address.model.person.Address; +import seedu.address.model.person.Begin; +import seedu.address.model.person.Day; import seedu.address.model.person.Email; +import seedu.address.model.person.End; import seedu.address.model.person.Name; +import seedu.address.model.person.PayRate; import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; - +import seedu.address.model.person.Subject; /** * Contains utility methods used for parsing strings in the various *Parser classes. */ @@ -95,30 +98,149 @@ public static Email parseEmail(String email) throws ParseException { return new Email(trimmedEmail); } + + /** + * Parses a {@code String day} into an {@code Day}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code day} is invalid. + */ + public static Day parseDay(String day) throws ParseException { + requireNonNull(day); + String trimmedDay = day.trim(); + Day initialisedDay; + + try { + initialisedDay = new Day(trimmedDay); + } catch (IllegalArgumentException e) { + throw new ParseException(Day.MESSAGE_CONSTRAINTS); + } + + return initialisedDay; + } + + /** + * Parses a {@code String subject} into an {@code Subject}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code subject} is invalid. + */ + public static Subject parseSubject(String subject) throws ParseException { + requireNonNull(subject); + String trimmedSubject = subject.trim(); + if (!Subject.isValidSubject(trimmedSubject)) { + throw new ParseException(Subject.MESSAGE_CONSTRAINTS); + } + return new Subject(trimmedSubject); + } + + /** + * Parses a {@code String begin} into an {@code Begin}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code begin} is invalid. + */ + public static Begin parseBegin(String begin) throws ParseException { + requireNonNull(begin); + String trimmedBegin = begin.trim(); + if (!Begin.isValidBegin(trimmedBegin)) { + throw new ParseException(Begin.MESSAGE_CONSTRAINTS); + } + return new Begin(trimmedBegin); + } + + /** + * Parses a {@code String end} into an {@code End}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code end} is invalid. + */ + public static End parseEnd(String end) throws ParseException { + requireNonNull(end); + String trimmedEnd = end.trim(); + if (!End.isValidEnd(trimmedEnd)) { + throw new ParseException(End.MESSAGE_CONSTRAINTS); + } + return new End(trimmedEnd); + } + /** - * Parses a {@code String tag} into a {@code Tag}. + * Parses a {@code String payRate} into an {@code PayRate}. * Leading and trailing whitespaces will be trimmed. * - * @throws ParseException if the given {@code tag} is invalid. + * @throws ParseException if the given {@code end} is invalid. */ - public static Tag parseTag(String tag) throws ParseException { - requireNonNull(tag); - String trimmedTag = tag.trim(); - if (!Tag.isValidTagName(trimmedTag)) { - throw new ParseException(Tag.MESSAGE_CONSTRAINTS); + public static PayRate parsePayRate(String payRate) throws ParseException { + requireNonNull(payRate); + String trimmedPayRate = payRate.trim(); + if (!PayRate.isValidPayRate(payRate)) { + throw new ParseException(PayRate.MESSAGE_CONSTRAINTS); } - return new Tag(trimmedTag); + return new PayRate(trimmedPayRate); } /** - * Parses {@code Collection tags} into a {@code Set}. + * Parses a {@code String day} into an {@code IntervalDay}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code day} is invalid. + */ + public static IntervalDay parseIntervalDay(String day) throws ParseException { + requireNonNull(day); + String trimmedDay = day.trim(); + IntervalDay initialisedDay; + + try { + initialisedDay = new IntervalDay(trimmedDay); + } catch (IllegalArgumentException e) { + throw new ParseException(IntervalDay.MESSAGE_CONSTRAINTS); + } + + return initialisedDay; + } + + /** + * Parses a {@code String begin} into an {@code IntervalBegin}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code begin} is invalid. + */ + public static IntervalBegin parseIntervalBegin(String begin) throws ParseException { + requireNonNull(begin); + String trimmedBegin = begin.trim(); + if (!IntervalBegin.isValidBegin(trimmedBegin)) { + throw new ParseException(IntervalBegin.MESSAGE_CONSTRAINTS); + } + return new IntervalBegin(trimmedBegin); + } + + /** + * Parses a {@code String end} into an {@code IntervalEnd}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code end} is invalid. + */ + public static IntervalEnd parseIntervalEnd(String end) throws ParseException { + requireNonNull(end); + String trimmedEnd = end.trim(); + if (!IntervalEnd.isValidEnd(trimmedEnd)) { + throw new ParseException(IntervalEnd.MESSAGE_CONSTRAINTS); + } + return new IntervalEnd(trimmedEnd); + } + + /** + * Parses a {@code String duration} into an {@code Duration}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code end} is invalid. */ - public static Set parseTags(Collection tags) throws ParseException { - requireNonNull(tags); - final Set tagSet = new HashSet<>(); - for (String tagName : tags) { - tagSet.add(parseTag(tagName)); + public static Duration parseDuration(String duration) throws ParseException { + requireNonNull(duration); + String trimmedDuration = duration.trim(); + if (!Duration.isValidDuration(trimmedDuration)) { + throw new ParseException(Duration.MESSAGE_CONSTRAINTS); } - return tagSet; + return new Duration(trimmedDuration); } } diff --git a/src/main/java/seedu/address/logic/parser/UnPaidCommandParser.java b/src/main/java/seedu/address/logic/parser/UnPaidCommandParser.java new file mode 100644 index 00000000000..d73a4a6f2f5 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/UnPaidCommandParser.java @@ -0,0 +1,33 @@ +package seedu.address.logic.parser; + +import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import java.util.logging.Logger; + +import seedu.address.commons.core.LogsCenter; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.UnPaidCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a new UnPaidCommand object + * (Make use of the template of DeleteCommandParser and did some modifications) + */ +public class UnPaidCommandParser implements Parser { + private final Logger logger = LogsCenter.getLogger(getClass()); + /** + * Parses the given {@code String} of arguments in the context of the UnPaidCommand + * and returns a UnPaidCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public UnPaidCommand parse(String args) throws ParseException { + try { + Index index = ParserUtil.parseIndex(args); + return new UnPaidCommand(index); + } catch (ParseException pe) { + logger.info("[UnPaidCommandParser.parse()]: Invalid extraneous parameter"); + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, UnPaidCommand.MESSAGE_USAGE), pe); + } + } +} diff --git a/src/main/java/seedu/address/model/AddressBook.java b/src/main/java/seedu/address/model/AddressBook.java index 73397161e84..f2098244e03 100644 --- a/src/main/java/seedu/address/model/AddressBook.java +++ b/src/main/java/seedu/address/model/AddressBook.java @@ -6,6 +6,7 @@ import javafx.collections.ObservableList; import seedu.address.commons.util.ToStringBuilder; +import seedu.address.model.interval.Interval; import seedu.address.model.person.Person; import seedu.address.model.person.UniquePersonList; @@ -67,6 +68,26 @@ public boolean hasPerson(Person person) { return persons.contains(person); } + /** + * Returns true if a person with the same schedule as {@code person} exists in the address book. + * @param person + * @return boolean for whether a person has a clashing schedule + */ + public boolean hasDate(Person person) { + requireNonNull(person); + return persons.checkSameDate(person); + } + + /** + * Finds the list of timings which have the same day as the Interval from the address book + * @param interval + * @return list of timings + */ + public List findInterval(Interval interval) { + requireNonNull(interval); + return persons.findInterval(interval); + } + /** * Adds a person to the address book. * The person must not already exist in the address book. @@ -80,10 +101,34 @@ public void addPerson(Person p) { * {@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) { + public void setPerson(Person target, Person editedPerson, boolean isEditingSchedule) { requireNonNull(editedPerson); - persons.setPerson(target, editedPerson); + persons.setPerson(target, editedPerson, isEditingSchedule); + } + + /** + * Set the person as paid in the address book. + * The person must exist in the address book. + */ + public void setPaid(Person target) { + persons.setPaid(target); + } + + /** + * Set the person as not paid in the address book. + * The person must exist in the address book. + */ + public void setUnPaid(Person target) { + persons.setUnPaid(target); + } + + /** + * Get the person's paid status. + * The person must exist in the address book. + */ + public boolean getPaid(Person target) { + return persons.getPaid(target); } /** diff --git a/src/main/java/seedu/address/model/Model.java b/src/main/java/seedu/address/model/Model.java index d54df471c1f..91ea6869383 100644 --- a/src/main/java/seedu/address/model/Model.java +++ b/src/main/java/seedu/address/model/Model.java @@ -1,10 +1,12 @@ package seedu.address.model; import java.nio.file.Path; +import java.util.List; import java.util.function.Predicate; import javafx.collections.ObservableList; import seedu.address.commons.core.GuiSettings; +import seedu.address.model.interval.Interval; import seedu.address.model.person.Person; /** @@ -14,6 +16,8 @@ public interface Model { /** {@code Predicate} that always evaluate to true */ Predicate PREDICATE_SHOW_ALL_PERSONS = unused -> true; + Predicate PREDICATE_SHOW_ALL_UNPAID_PERSONS = paid -> false; + /** * Replaces user prefs data with the data in {@code userPrefs}. */ @@ -57,12 +61,22 @@ public interface Model { */ boolean hasPerson(Person person); + boolean hasDate(Person person); + List findInterval(Interval interval); + /** * Deletes the given person. * The person must exist in the address book. */ void deletePerson(Person target); + void markPersonPaid(Person target); + + void markPersonUnPaid(Person target); + + boolean getPersonPaid(Person target); + + /** * Adds the given person. * {@code person} must not already exist in the address book. @@ -74,14 +88,52 @@ public interface Model { * {@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); + void setPerson(Person target, Person editedPerson, boolean isEditingSchedule); /** Returns an unmodifiable view of the filtered person list */ ObservableList getFilteredPersonList(); + /** Returns an unmodifiable view of the schedule list */ + ObservableList getScheduleList(); + + /** Returns an unmodifiable view of the unfiltered person list */ + ObservableList getUnfilteredPersonList(); + /** * 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); + + /** + * Saves the current addressbook {@code VersionedAddressBook} state in its history + */ + void commitAddressBook(); + + /** + * Restore the previous addressbook {@code VersionedAddressBook} state from its history + */ + void undoAddressBook(); + + /** + * Restore a previously undone addressbook {@code VersionedAddressBook} state from its history + */ + void redoAddressBook(); + + /** + * Remove states in the {@code VersionedAddressBook} that are no longer valid + */ + void purgeAddressBook(); + + /** + * Checks whether the addressbook has any saved states that can be restored + * @return a boolean to indicate whether an undo operation is possible + */ + boolean canUndoAddressBook(); + + /** + * Checks whether the addressbook has any saved undone states that can be restored + * @return a boolean to indicate whether an undo operation is possible + */ + boolean canRedoAddressBook(); } diff --git a/src/main/java/seedu/address/model/ModelManager.java b/src/main/java/seedu/address/model/ModelManager.java index 57bc563fde6..8914c083377 100644 --- a/src/main/java/seedu/address/model/ModelManager.java +++ b/src/main/java/seedu/address/model/ModelManager.java @@ -4,13 +4,17 @@ import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; import java.nio.file.Path; +import java.util.List; import java.util.function.Predicate; import java.util.logging.Logger; import javafx.collections.ObservableList; import javafx.collections.transformation.FilteredList; +import javafx.collections.transformation.SortedList; import seedu.address.commons.core.GuiSettings; import seedu.address.commons.core.LogsCenter; +import seedu.address.model.interval.Interval; +import seedu.address.model.person.LessonComparator; import seedu.address.model.person.Person; /** @@ -18,10 +22,12 @@ */ public class ModelManager implements Model { private static final Logger logger = LogsCenter.getLogger(ModelManager.class); - - private final AddressBook addressBook; + private final VersionedAddressBook addressBook; private final UserPrefs userPrefs; private final FilteredList filteredPersons; + private final SortedList scheduleList; + + private final ObservableList unfilteredPersons; /** * Initializes a ModelManager with the given addressBook and userPrefs. @@ -31,13 +37,15 @@ public ModelManager(ReadOnlyAddressBook addressBook, ReadOnlyUserPrefs userPrefs logger.fine("Initializing with address book: " + addressBook + " and user prefs " + userPrefs); - this.addressBook = new AddressBook(addressBook); + this.addressBook = new VersionedAddressBook(addressBook); this.userPrefs = new UserPrefs(userPrefs); + unfilteredPersons = new FilteredList<>(this.addressBook.getPersonList()); filteredPersons = new FilteredList<>(this.addressBook.getPersonList()); + scheduleList = new SortedList<>(this.addressBook.getPersonList(), new LessonComparator()); } public ModelManager() { - this(new AddressBook(), new UserPrefs()); + this(new VersionedAddressBook(), new UserPrefs()); } //=========== UserPrefs ================================================================================== @@ -93,11 +101,38 @@ public boolean hasPerson(Person person) { return addressBook.hasPerson(person); } + @Override + public boolean hasDate(Person person) { + requireNonNull(person); + return addressBook.hasDate(person); + } + + @Override + public List findInterval(Interval interval) { + requireNonNull(interval); + return addressBook.findInterval(interval); + } + @Override public void deletePerson(Person target) { addressBook.removePerson(target); } + @Override + public void markPersonPaid(Person target) { + addressBook.setPaid(target); + } + + @Override + public void markPersonUnPaid(Person target) { + addressBook.setUnPaid(target); + } + + @Override + public boolean getPersonPaid(Person target) { + return addressBook.getPaid(target); + } + @Override public void addPerson(Person person) { addressBook.addPerson(person); @@ -105,12 +140,45 @@ public void addPerson(Person person) { } @Override - public void setPerson(Person target, Person editedPerson) { - requireAllNonNull(target, editedPerson); + public void setPerson(Person target, Person editedPerson, boolean isEditingSchedule) { + requireAllNonNull(target, editedPerson, isEditingSchedule); + + addressBook.setPerson(target, editedPerson, isEditingSchedule); + } + + @Override + public void commitAddressBook() { + addressBook.commit(); + } + + @Override + public void undoAddressBook() { + addressBook.undo(); + } + + @Override + public void redoAddressBook() { + addressBook.redo(); + } + + @Override + public boolean canUndoAddressBook() { + return addressBook.canUndo(); + } + + @Override + public boolean canRedoAddressBook() { + return addressBook.canRedo(); + } - addressBook.setPerson(target, editedPerson); + @Override + public void purgeAddressBook() { + addressBook.purge(); } + + + //=========== Filtered Person List Accessors ============================================================= /** @@ -128,6 +196,28 @@ public void updateFilteredPersonList(Predicate predicate) { filteredPersons.setPredicate(predicate); } + //=========== Schedule List Accessors ============================================================= + + /** + * Returns an unmodifiable view of the list of {@code Person} sorted by date backed by the internal list of + * {@code versionedAddressBook} + */ + @Override + public ObservableList getScheduleList() { + return scheduleList; + } + + //=========== Full List Accessors ================================================================== + + /** + * Returns an unmodifiable unfiltered view of the list of {@code Person} backed by the internal list of + * {@code versionedAddressBook} + */ + @Override + public ObservableList getUnfilteredPersonList() { + return unfilteredPersons; + } + @Override public boolean equals(Object other) { if (other == this) { @@ -144,5 +234,4 @@ public boolean equals(Object other) { && userPrefs.equals(otherModelManager.userPrefs) && filteredPersons.equals(otherModelManager.filteredPersons); } - } diff --git a/src/main/java/seedu/address/model/UserPrefs.java b/src/main/java/seedu/address/model/UserPrefs.java index 6be655fb4c7..13456ba8615 100644 --- a/src/main/java/seedu/address/model/UserPrefs.java +++ b/src/main/java/seedu/address/model/UserPrefs.java @@ -14,7 +14,7 @@ public class UserPrefs implements ReadOnlyUserPrefs { private GuiSettings guiSettings = new GuiSettings(); - private Path addressBookFilePath = Paths.get("data" , "addressbook.json"); + private Path addressBookFilePath = Paths.get("data" , "tuitionconnect.json"); /** * Creates a {@code UserPrefs} with default values. diff --git a/src/main/java/seedu/address/model/VersionedAddressBook.java b/src/main/java/seedu/address/model/VersionedAddressBook.java new file mode 100644 index 00000000000..622f93b1b9c --- /dev/null +++ b/src/main/java/seedu/address/model/VersionedAddressBook.java @@ -0,0 +1,106 @@ +package seedu.address.model; + +import java.util.ArrayList; +import java.util.logging.Logger; + +import seedu.address.commons.core.LogsCenter; + +/** + * Represents an AddressBook with an undo/redo history + */ +public class VersionedAddressBook extends AddressBook { + + private final ArrayList addressBookStateList; + private int currentStatePointer; + private final Logger logger = LogsCenter.getLogger(getClass()); + + /** + * Creates a Versioned AddressBook + */ + public VersionedAddressBook() { + super(); + addressBookStateList = new ArrayList<>(); + + // Save a copy of initial state into state list + AddressBook initState = new AddressBook(this); + addressBookStateList.add(initState); + + // Initialise pointer to start of list + currentStatePointer = 0; + + logger.info("Versioned Address Book is initialised"); + } + + /** + * Creates a Versioned AddressBook using the Persons in the {@code toBeCopied} + */ + public VersionedAddressBook(ReadOnlyAddressBook toBeCopied) { + super(toBeCopied); + addressBookStateList = new ArrayList<>(); + + // Save a copy of initial state into state list + AddressBook initState = new AddressBook(this); + addressBookStateList.add(initState); + + // Initialise pointer to start of list + currentStatePointer = 0; + } + + /** + * Gets {@code AddressBook} that the pointer is pointing to + * @return current address book + */ + private AddressBook getCurrentAddressBook() { + return addressBookStateList.get(currentStatePointer); + } + + // Commits a copy of current address book so that the saved state is immutable + + /** + * Commits a defensive copy of current {@code AddressBook} to {@code addressBookStateList} + * so that the saved state is immutable + */ + public void commit() { + // Save a copy of the state into the state list + AddressBook copy = new AddressBook(this); + addressBookStateList.add(copy); + + currentStatePointer++; + + logger.info("[VersionedAddressBook.commit()]: Versioned Address Book commit to memory"); + } + + /** + * Shifts {@code currentStatePointer} to the left to restore previous version of the addressbook + */ + public void undo() { + currentStatePointer--; + this.resetData(getCurrentAddressBook()); + } + + /** + * Shifts {@code currentStatePointer} to the right to restore undoned version of the addressbook + */ + public void redo() { + currentStatePointer++; + this.resetData(getCurrentAddressBook()); + } + + public boolean canUndo() { + return currentStatePointer > 0; + } + + public boolean canRedo() { + return addressBookStateList.size() > 1 && currentStatePointer < addressBookStateList.size() - 1; + } + + /** + * Remove redundant {@code AddressBook} states from addressBookStateList + */ + public void purge() { + // Remove version data after the current state + if (addressBookStateList.size() > currentStatePointer + 1) { + addressBookStateList.subList(currentStatePointer + 1, addressBookStateList.size()).clear(); + } + } +} diff --git a/src/main/java/seedu/address/model/interval/Duration.java b/src/main/java/seedu/address/model/interval/Duration.java new file mode 100644 index 00000000000..f2d29c18a77 --- /dev/null +++ b/src/main/java/seedu/address/model/interval/Duration.java @@ -0,0 +1,73 @@ +package seedu.address.model.interval; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +/** + * Represents a duration in an interval + */ + +public class Duration { + public static final String MESSAGE_CONSTRAINTS = "The duration must be a positive integer"; + + public final String value; + + + /** + * Constructs a {@code Begin}. + * + * @param duration A valid duration + */ + public Duration(String duration) { + requireNonNull(duration); + checkArgument(isValidDuration(duration), MESSAGE_CONSTRAINTS); + value = duration; + } + + /** + * Checks if it is a valid duration + * @param test String to be checked + * @return boolean on whether the value is a valid duration + */ + public static boolean isValidDuration(String test) { + try { + int number = Integer.parseInt(test); + return number > 0; + } catch (NumberFormatException e) { + return false; + } + } + + /** + * @return defensive copy of Duration + */ + + public Duration copy() { + return new Duration(this.value); + } + + public int toInt() { + return Integer.parseInt(value); + } + + @Override + public String toString() { + return value; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof Duration)) { + return false; + } + + Duration otherDuration = (Duration) other; + return value.equals(otherDuration.value); + } + +} diff --git a/src/main/java/seedu/address/model/interval/Interval.java b/src/main/java/seedu/address/model/interval/Interval.java new file mode 100644 index 00000000000..5fe5749ed84 --- /dev/null +++ b/src/main/java/seedu/address/model/interval/Interval.java @@ -0,0 +1,84 @@ +package seedu.address.model.interval; + +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; + +import java.util.Objects; + +import seedu.address.commons.util.ToStringBuilder; + + +/** + * Represents an Interval in the address book. + */ +public class Interval { + + private final IntervalDay intervalDay; + private final IntervalBegin intervalBegin; + private final IntervalEnd intervalEnd; + private final Duration duration; + + /** + * Constructor for interval class + * @param intervalDay + * @param duration + * @param intervalBegin + * @param intervalEnd + */ + public Interval(IntervalDay intervalDay, Duration duration, IntervalBegin intervalBegin, IntervalEnd intervalEnd) { + requireAllNonNull(intervalDay, intervalBegin, intervalDay, duration); + this.intervalDay = intervalDay; + this.duration = duration; + this.intervalBegin = intervalBegin; + this.intervalEnd = intervalEnd; + } + + public IntervalDay getIntervalDay() { + return intervalDay.copy(); + } + + public Duration getDuration() { + return duration.copy(); + } + + public IntervalBegin getIntervalBegin() { + return intervalBegin.copy(); + } + + public IntervalEnd getIntervalEnd() { + return intervalEnd.copy(); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof Interval)) { + return false; + } + + Interval otherInterval = (Interval) other; + return intervalDay.equals(otherInterval.intervalDay) + && duration.equals(otherInterval.duration) + && intervalBegin.equals(otherInterval.intervalBegin) + && intervalEnd.equals(otherInterval.intervalEnd); + } + + @Override + public int hashCode() { + // use this method for custom fields hashing instead of implementing your own + return Objects.hash(intervalDay, duration, intervalBegin, intervalEnd); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("intervalDay", intervalDay) + .add("duration", duration) + .add("intervalBegin", intervalBegin) + .add("intervalEnd", intervalEnd) + .toString(); + } +} diff --git a/src/main/java/seedu/address/model/interval/IntervalBegin.java b/src/main/java/seedu/address/model/interval/IntervalBegin.java new file mode 100644 index 00000000000..b9ac3dc0abd --- /dev/null +++ b/src/main/java/seedu/address/model/interval/IntervalBegin.java @@ -0,0 +1,52 @@ +package seedu.address.model.interval; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; + +import seedu.address.model.person.Begin; + +/** + * Represents a Interval's Begin in the address book. + * Guarantees: immutable; is valid as declared in {@link #isValidBegin(String)} + */ + +public class IntervalBegin extends Begin { + + /** + * Constructs a {@code Begin}. + * + * @param begin A valid phone number. + */ + public IntervalBegin(String begin) { + super(begin); + } + + public Date getTimes() throws ParseException { + assert isValidBegin(value); + SimpleDateFormat dateFormat = new SimpleDateFormat("HHmm"); + return dateFormat.parse(value); + } + + /** + * @return a defensive copy of IntervalBegin + */ + public IntervalBegin copy() { + return new IntervalBegin(this.value); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof seedu.address.model.interval.IntervalBegin)) { + return false; + } + + seedu.address.model.interval.IntervalBegin otherBegin = (seedu.address.model.interval.IntervalBegin) other; + return value.equals(otherBegin.value); + } +} diff --git a/src/main/java/seedu/address/model/interval/IntervalDay.java b/src/main/java/seedu/address/model/interval/IntervalDay.java new file mode 100644 index 00000000000..5f0527cd097 --- /dev/null +++ b/src/main/java/seedu/address/model/interval/IntervalDay.java @@ -0,0 +1,80 @@ +package seedu.address.model.interval; + +import seedu.address.model.person.Day; + +/** + * Represents the day in the interval + * Guarantees: immutable; is valid as declared in {@link #isValidDay(String)} + */ +public class IntervalDay extends Day { + + /** + * Constructs a {@code Day}. + * + * @param day A valid day. + */ + public IntervalDay(String day) { + super(day); + } + + /** + * Parses the day input + * @param input string of Day + * @return parses the day into the complete day name + */ + public String parseDay(String input) { + String day = input.toLowerCase(); + String result = ""; + switch (day) { + case "mon": + case "monday": + result = "Mon"; + break; + case "tue": + case "tuesday": + result = "Tue"; + break; + case "wed": + case "wednesday": + result = "Wed"; + break; + case "thu": + case "thursday": + result = "Thu"; + break; + case "fri": + case "friday": + result = "Fri"; + break; + case "sat": + case "saturday": + result = "Sat"; + break; + case "sun": + case "sunday": + result = "Sun"; + break; + default: + throw new IllegalArgumentException(MESSAGE_CONSTRAINTS); + } + return result; + } + + public static boolean isValidDay(String test) { + return test.matches(VALIDATION_REGEX); + } + + /** + * @return a defensive copy of IntervalDay + */ + public IntervalDay copy() { + return new IntervalDay(this.stringValue); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof seedu.address.model.interval.IntervalDay // instanceof handles nulls + && value.equals(((seedu.address.model.interval.IntervalDay) other).value)); // state check + } +} diff --git a/src/main/java/seedu/address/model/interval/IntervalEnd.java b/src/main/java/seedu/address/model/interval/IntervalEnd.java new file mode 100644 index 00000000000..f91becb0e83 --- /dev/null +++ b/src/main/java/seedu/address/model/interval/IntervalEnd.java @@ -0,0 +1,53 @@ +package seedu.address.model.interval; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; + +import seedu.address.model.person.End; + +/** + * Represents an Interval's End in the address book. + * Guarantees: immutable; is valid as declared in {@link #isValidEnd(String)} + */ + +public class IntervalEnd extends End { + + /** + * Constructs a {@code End}. + * + * @param end A valid phone number. + */ + public IntervalEnd(String end) { + super(end); + } + + public Date getTimes() throws ParseException { + assert isValidEnd(value); + SimpleDateFormat dateFormat = new SimpleDateFormat("HHmm"); + return dateFormat.parse(value); + } + + /** + * @return defensive copy of IntervalEnd + */ + public IntervalEnd copy() { + return new IntervalEnd(this.value); + } + + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof seedu.address.model.interval.IntervalEnd)) { + return false; + } + + seedu.address.model.interval.IntervalEnd otherEnd = (seedu.address.model.interval.IntervalEnd) other; + return value.equals(otherEnd.value); + } +} diff --git a/src/main/java/seedu/address/model/interval/TimeSlot.java b/src/main/java/seedu/address/model/interval/TimeSlot.java new file mode 100644 index 00000000000..43511272620 --- /dev/null +++ b/src/main/java/seedu/address/model/interval/TimeSlot.java @@ -0,0 +1,149 @@ +package seedu.address.model.interval; + +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.Date; +import java.util.List; +import java.util.logging.Logger; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +import seedu.address.commons.core.LogsCenter; + +/** + * TimeSlot Class + */ +public class TimeSlot { + private static final Logger logger = LogsCenter.getLogger(TimeSlot.class); + private Date start; + private Date end; + + /** + * Constructor for TimeSlot + * @param start a Date object for start + * @param end a Date object for end + */ + public TimeSlot(Date start, Date end) { + requireAllNonNull(start, end); + this.start = start; + this.end = end; + } + + /** + * @return the duration of the timeslot in minutes + */ + public long getDurationMinutes() { + return (end.getTime() - start.getTime()) / (60 * 1000); + } + + /** + * Parses a list of String into a list of TimeSlots. + * @param timeSlots the list of strings to be parsed + * @return parsed timeslots + * @throws ParseException + */ + public static List parseIntervals(List timeSlots) throws ParseException { + List timeSlotObjects = new ArrayList<>(); + SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm"); + Pattern timePattern = Pattern.compile("\\d{2}:\\d{2}"); + for (String timeSlot : timeSlots) { + assert timeSlot.charAt(6) == '-'; + String[] times = timeSlot.split(" - "); + assert timePattern.matcher(times[0]).matches(); + assert timePattern.matcher(times[1]).matches(); + Date start = dateFormat.parse(times[0]); + Date end = dateFormat.parse(times[1]); + timeSlotObjects.add(new TimeSlot(start, end)); + } + return timeSlotObjects; + } + + /** + * Finds the available time within the given list of timeslots and interval + * @param timeslots + * @param interval + * @return a list of available timeslots + * @throws ParseException + */ + public static List findAvailableTime(List timeslots, Interval interval) throws ParseException { + timeslots.sort(Comparator.comparing(timeslot -> timeslot.getStart())); + + List availableTime = new ArrayList<>(); + Date lastEnd = interval.getIntervalBegin().getTimes(); + for (TimeSlot timeslot : timeslots) { + if (timeslot.getStart().after(interval.getIntervalEnd().getTimes())) { + availableTime.add(new TimeSlot(lastEnd, interval.getIntervalEnd().getTimes())); + } else if (timeslot.getStart().after(lastEnd)) { + availableTime.add(new TimeSlot(lastEnd, timeslot.getStart())); + } + if (lastEnd.before(timeslot.getEnd())) { + lastEnd = timeslot.getEnd(); + } + } + if (lastEnd.before(interval.getIntervalEnd().getTimes())) { + availableTime.add(new TimeSlot(lastEnd, interval.getIntervalEnd().getTimes())); + } + List validTimeSlots = availableTime.stream() + .filter(timeslot -> timeslot.getDurationMinutes() >= interval.getDuration().toInt()) + .collect(Collectors.toList()); + + return validTimeSlots; + } + + /** + * Prints out the results + * @param timeslots + * @return String of results + */ + public static String printResults(List timeslots) { + if (timeslots.size() == 0) { + logger.info("[TimeSlot.printResults]: No timeslots are found"); + return "There are no available timeslots."; + } + + String result = ""; + + for (TimeSlot timeslot : timeslots) { + logger.info("[TimeSlot.printResults]: Timeslots found"); + SimpleDateFormat timeFormat = new SimpleDateFormat("HH:mm"); + String startTime = timeFormat.format(timeslot.getStart()); + String endTime = timeFormat.format(timeslot.getEnd()); + result = result + "Free from " + startTime + " - " + endTime + "\n"; + } + return result; + } + + /** + * @return start + */ + public Date getStart() { + return start; + } + + /** + * @return end + */ + public Date getEnd() { + return end; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof TimeSlot)) { + return false; + } + + TimeSlot otherTimeslot = (TimeSlot) other; + return start.equals(otherTimeslot.getStart()) + && end.equals(otherTimeslot.getEnd()); + } +} diff --git a/src/main/java/seedu/address/model/person/Address.java b/src/main/java/seedu/address/model/person/Address.java index 469a2cc9a1e..df970ddad07 100644 --- a/src/main/java/seedu/address/model/person/Address.java +++ b/src/main/java/seedu/address/model/person/Address.java @@ -37,6 +37,14 @@ public static boolean isValidAddress(String test) { return test.matches(VALIDATION_REGEX); } + /** + * @return a defensive copy of address + */ + + public Address copy() { + return new Address(this.value); + } + @Override public String toString() { return value; diff --git a/src/main/java/seedu/address/model/person/Begin.java b/src/main/java/seedu/address/model/person/Begin.java new file mode 100644 index 00000000000..5ae2c949ffa --- /dev/null +++ b/src/main/java/seedu/address/model/person/Begin.java @@ -0,0 +1,67 @@ +package seedu.address.model.person; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +import java.time.LocalTime; +import java.time.format.DateTimeFormatter; + +/** + * Represents a Person's Begin in the address book. + * Guarantees: immutable; is valid as declared in {@link #isValidBegin(String)} + */ + +public class Begin { + + public static final String MESSAGE_CONSTRAINTS = "Begin has a format of HHMM"; + public static final String VALIDATION_REGEX = "^(0[0-9]|1[0-9]|2[0-3])[0-5][0-9]$"; + public final String value; + + /** + * Constructs a {@code Begin}. + * + * @param begin A valid phone number. + */ + public Begin(String begin) { + requireNonNull(begin); + checkArgument(isValidBegin(begin), MESSAGE_CONSTRAINTS); + value = begin; + } + + public static boolean isValidBegin(String test) { + return test.matches(VALIDATION_REGEX); + } + + private LocalTime parse(String test) { + assert isValidBegin(test); + + String pattern = "HHmm"; + DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern); + + return LocalTime.parse(test, formatter); + } + + public LocalTime getTime() { + return parse(value); + } + + @Override + public String toString() { + return value; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof Begin)) { + return false; + } + + Begin otherBegin = (Begin) other; + return value.equals(otherBegin.value); + } +} diff --git a/src/main/java/seedu/address/model/person/Day.java b/src/main/java/seedu/address/model/person/Day.java new file mode 100644 index 00000000000..8f46b26f27d --- /dev/null +++ b/src/main/java/seedu/address/model/person/Day.java @@ -0,0 +1,84 @@ +package seedu.address.model.person; + +import static java.util.Objects.requireNonNull; + +import java.time.DayOfWeek; +import java.time.format.TextStyle; +import java.util.Locale; + +/** + * Represents the day when the Tutee has tuition every week. + * Guarantees: immutable; is valid as declared in {@link #isValidDay(String)} + */ +public class Day { + public static final String MESSAGE_CONSTRAINTS = + "Days should be written using their full names or their first three letters, and it should not be blank"; + + public static final String VALIDATION_REGEX = + "(?i)^(Mon|Monday|Tue|Tuesday|Wed|Wednesday|Thu|Thursday|Fri|Friday|Sat|Saturday|Sun|Sunday)$"; + + public final DayOfWeek value; + public final String stringValue; + + /** + * Constructs a {@code Day}. + * + * @param day A valid day. + */ + public Day(String day) { + requireNonNull(day); + value = parse(day); + stringValue = value.getDisplayName(TextStyle.SHORT, Locale.ENGLISH); + } + + /** + * Parses an input as a DayOfWeek object. + * + * @param test Command input + * @return DayOfWeek object + */ + public static DayOfWeek parse(String test) { + String lowerCaseTest = test.toLowerCase(); + switch (lowerCaseTest) { + case "mon": + case "monday": + return DayOfWeek.MONDAY; + case "tue": + case "tuesday": + return DayOfWeek.TUESDAY; + case "wed": + case "wednesday": + return DayOfWeek.WEDNESDAY; + case "thu": + case "thursday": + return DayOfWeek.THURSDAY; + case "fri": + case "friday": + return DayOfWeek.FRIDAY; + case "sat": + case "saturday": + return DayOfWeek.SATURDAY; + case "sun": + case "sunday": + return DayOfWeek.SUNDAY; + default: + throw new IllegalArgumentException(MESSAGE_CONSTRAINTS); + } + } + + public static boolean isValidDay(String test) { + return test.matches(VALIDATION_REGEX); + } + + @Override + public String toString() { + return stringValue; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Day // instanceof handles nulls + && value.equals(((Day) other).value)); // state check + } +} diff --git a/src/main/java/seedu/address/model/person/DayPredicate.java b/src/main/java/seedu/address/model/person/DayPredicate.java new file mode 100644 index 00000000000..72f50b049b8 --- /dev/null +++ b/src/main/java/seedu/address/model/person/DayPredicate.java @@ -0,0 +1,44 @@ +package seedu.address.model.person; + +import static java.util.Objects.requireNonNull; + +import java.util.function.Predicate; + +import seedu.address.commons.util.ToStringBuilder; + +/** + * Tests that a {@code Person}'s {@code Day} matches the day given. + */ +public class DayPredicate implements Predicate { + private final Day day; + + public DayPredicate(Day day) { + this.day = day; + } + + @Override + public boolean test(Person person) { + requireNonNull(person); + return person.getDay().equals(this.day); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof DayPredicate)) { + return false; + } + + DayPredicate otherDayPredicate = (DayPredicate) other; + return day.equals(otherDayPredicate.day); + } + + @Override + public String toString() { + return new ToStringBuilder(this).add("day", day).toString(); + } +} diff --git a/src/main/java/seedu/address/model/person/Email.java b/src/main/java/seedu/address/model/person/Email.java index c62e512bc29..4dfbcb8d062 100644 --- a/src/main/java/seedu/address/model/person/Email.java +++ b/src/main/java/seedu/address/model/person/Email.java @@ -51,6 +51,13 @@ public static boolean isValidEmail(String test) { return test.matches(VALIDATION_REGEX); } + /** + * @return a defensive copy of email. + */ + public Email copy() { + return new Email(this.value); + } + @Override public String toString() { return value; diff --git a/src/main/java/seedu/address/model/person/End.java b/src/main/java/seedu/address/model/person/End.java new file mode 100644 index 00000000000..043cbf08581 --- /dev/null +++ b/src/main/java/seedu/address/model/person/End.java @@ -0,0 +1,67 @@ +package seedu.address.model.person; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +import java.time.LocalTime; +import java.time.format.DateTimeFormatter; + +/** + * Represents a Person's End in the address book. + * Guarantees: immutable; is valid as declared in {@link #isValidEnd(String)} + */ + +public class End { + + public static final String MESSAGE_CONSTRAINTS = "That is not a valid time format. End has a format of HHMM"; + public static final String VALIDATION_REGEX = "^(0[0-9]|1[0-9]|2[0-3])[0-5][0-9]$"; + public final String value; + + /** + * Constructs a {@code End}. + * + * @param end A valid phone number. + */ + public End(String end) { + requireNonNull(end); + checkArgument(isValidEnd(end), MESSAGE_CONSTRAINTS); + value = end; + } + + public static boolean isValidEnd(String test) { + return test.matches(VALIDATION_REGEX); + } + + private LocalTime parse(String test) { + assert isValidEnd(test); + + String pattern = "HHmm"; + DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern); + + return LocalTime.parse(test, formatter); + } + + public LocalTime getTime() { + return parse(value); + } + + @Override + public String toString() { + return value; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof End)) { + return false; + } + + End otherEnd = (End) other; + return value.equals(otherEnd.value); + } +} diff --git a/src/main/java/seedu/address/model/person/Lesson.java b/src/main/java/seedu/address/model/person/Lesson.java new file mode 100644 index 00000000000..89d4f075183 --- /dev/null +++ b/src/main/java/seedu/address/model/person/Lesson.java @@ -0,0 +1,150 @@ +package seedu.address.model.person; + +import static java.util.Objects.requireNonNull; + +import java.time.DayOfWeek; +import java.time.Duration; +import java.time.LocalDate; +import java.time.LocalTime; +import java.time.temporal.TemporalAdjusters; +import java.util.logging.Logger; + +import seedu.address.commons.core.LogsCenter; + +/** + * Represents the day and time when the Tutee has tuition every week. + * Guarantees: immutable; is valid as declared in + */ +public class Lesson { + public final DayOfWeek day; + + public final LocalTime begin; + + public final LocalTime end; + + private final Logger logger = LogsCenter.getLogger(getClass()); + /** + * Constructs a {@code DayTime}. + * + * @param day A valid day. + * @param begin A valid begin. + * @param end A valid end. + */ + public Lesson(Day day, Begin begin, End end) { + requireNonNull(day); + requireNonNull(begin); + requireNonNull(end); + + assert(begin.getTime().isBefore(end.getTime())) : "begin time must be before end time"; + + this.day = day.value; + this.begin = begin.getTime(); + this.end = end.getTime(); + } + + /** + * Constructor for defensive coding + * @param original + */ + public Lesson(Lesson original) { + requireNonNull(original); + this.day = original.day; + this.begin = original.begin; + this.end = original.end; + } + + public static boolean isValid(Begin begin, End end) { + return begin.getTime().compareTo(end.getTime()) < 0; + } + + public String getTimeSlot() { + return begin.toString() + " - " + end.toString(); + } + + public Lesson copy() { + return new Lesson(this); + } + + @Override + public String toString() { + return day.toString() + ", " + begin.toString() + " - " + end.toString(); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof Lesson)) { + return false; + } + + Lesson otherLesson = (Lesson) other; + return day.equals(otherLesson.day) + && begin.equals(otherLesson.begin) + && end.equals(otherLesson.end); + } + + /** + * Calculates the number of occurrences of a specific day of the week + * within a given month and year. + * + * @param year The year (4 digits) for which to calculate the occurrences. + * @param month The month (1-12) for which to calculate the occurrences. + * @return The number of occurrences of the specified day in the month. + */ + public int getNumOfDaysInMonth(int year, int month) { + requireNonNull(day); + assert (1000 <= year) && (year <= 9999) : "Year must be exactly 4 digits"; + assert (1 <= month) && (month <= 12) : "Month must be from January to December"; + + LocalDate firstDayOfMonth = LocalDate.of(year, month, 1); + LocalDate lastDayOfMonth = firstDayOfMonth.with(TemporalAdjusters.lastDayOfMonth()); + LocalDate temp = firstDayOfMonth; + + int count = 0; + while (!temp.isAfter(lastDayOfMonth)) { + if (temp.getDayOfWeek() == day) { + count++; + temp = temp.plusDays(7); + } else { + temp = temp.plusDays(1); + } + } + assert count > 0 : "number of days should be more than 0"; + return count; + } + + /** + * Calculates the lesson duration in hours. + * + * @return duration in terms of hours. + */ + public double calculateLessonDuration() { + try { + Duration duration = Duration.between(begin, end); + long minutes = duration.toMinutes(); + double hours = (double) minutes / 60; // Convert minutes to a fraction of hours + assert hours > 0.0 : "hours should be positive"; + return hours; + } catch (ArithmeticException e) { + logger.info("[Lesson.calculateLessonDuration()]: Duration capacity exceeded"); + return 1.0; + } + } + + /** + * Calculates the monthly lesson duration in hours. + * + * @return monthly duration in terms of hours. + */ + public double getMonthlyHours() { + double hours = calculateLessonDuration() * getNumOfDaysInMonth(LocalDate.now() + .getYear(), LocalDate.now().getMonthValue()); + assert hours > 0.0 : "hours should be positive"; + return hours; + } + +} diff --git a/src/main/java/seedu/address/model/person/LessonComparator.java b/src/main/java/seedu/address/model/person/LessonComparator.java new file mode 100644 index 00000000000..2d9db993f3b --- /dev/null +++ b/src/main/java/seedu/address/model/person/LessonComparator.java @@ -0,0 +1,50 @@ +package seedu.address.model.person; + +import java.time.DayOfWeek; +import java.time.LocalDate; +import java.util.Comparator; + + +/** + * Compares a {@code Person}'s {@code Lesson} against another {@code Lesson}. + */ +public class LessonComparator implements Comparator { + /** + * Compares two Person objects based on the lesson they are currently attending. + * + * @param person1 the first Person object to be compared + * @param person2 the second Person object to be compared + * @return a negative integer, zero, or a positive integer as the first + * Person's lesson is less than, equal to, or greater than the second Person's lesson. + */ + public int compare(Person person1, Person person2) { + Lesson lesson1 = person1.getLesson(); + Lesson lesson2 = person2.getLesson(); + + DayOfWeek currentDay = LocalDate.now().getDayOfWeek(); + + // Compare the days cyclically + // if lesson1 is earlier in the week than lesson 2, dayComparison return negative + int dayComparison = compareDaysCyclically(lesson1.day, currentDay) + - compareDaysCyclically(lesson2.day, currentDay); + + if (dayComparison != 0) { + return dayComparison; + } + // If days are the same, compare by start time + return lesson1.begin.compareTo(lesson2.begin); + } + + + /** + * Returns the days from day 2 to day 1 in a cyclical manner + * + * @param day1 + * @param day2 + * @return days from day2 to day1 + */ + public int compareDaysCyclically(DayOfWeek day1, DayOfWeek day2) { + // returns days required to get from day 2 to day 1 in a cyclical manner + return (day1.getValue() - day2.getValue() + DayOfWeek.values().length) % DayOfWeek.values().length; + } +} diff --git a/src/main/java/seedu/address/model/person/Name.java b/src/main/java/seedu/address/model/person/Name.java index 173f15b9b00..12a2296fb50 100644 --- a/src/main/java/seedu/address/model/person/Name.java +++ b/src/main/java/seedu/address/model/person/Name.java @@ -38,6 +38,13 @@ public static boolean isValidName(String test) { return test.matches(VALIDATION_REGEX); } + /** + * @return a defensive copy of Name + */ + public Name copy() { + return new Name(this.fullName); + } + @Override public String toString() { diff --git a/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java index 62d19be2977..b43da2359e9 100644 --- a/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java +++ b/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java @@ -18,7 +18,7 @@ public NameContainsKeywordsPredicate(List keywords) { @Override public boolean test(Person person) { - return keywords.stream() + return keywords.isEmpty() || keywords.stream() .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(person.getName().fullName, keyword)); } diff --git a/src/main/java/seedu/address/model/person/NameSubjectPredicate.java b/src/main/java/seedu/address/model/person/NameSubjectPredicate.java new file mode 100644 index 00000000000..ebb0c8c3e8e --- /dev/null +++ b/src/main/java/seedu/address/model/person/NameSubjectPredicate.java @@ -0,0 +1,48 @@ +package seedu.address.model.person; + +import java.util.function.Predicate; + +import seedu.address.commons.util.ToStringBuilder; + +/** + * Tests that a {@code Person}'s {@code Name} matches any of the keywords given. + */ +public class NameSubjectPredicate implements Predicate { + private final NameContainsKeywordsPredicate name; + private final SubjectContainsKeywordsPredicate subject; + + /** + * Constructor for the NameSubjectPredicate class + * @param name the keyword starting with the prefix n/ + * @param subject the keyword starting with the prefix sb/ + */ + public NameSubjectPredicate(NameContainsKeywordsPredicate name, SubjectContainsKeywordsPredicate subject) { + this.name = name; + this.subject = subject; + } + + @Override + public boolean test(Person person) { + return name.test(person) && subject.test(person); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof NameSubjectPredicate)) { + return false; + } + + NameSubjectPredicate otherNameSubjectPredicate = (NameSubjectPredicate) other; + return name.equals(otherNameSubjectPredicate.name) && subject.equals(otherNameSubjectPredicate.subject); + } + + @Override + public String toString() { + return new ToStringBuilder(this).add("name", name).add("subject", subject).toString(); + } +} diff --git a/src/main/java/seedu/address/model/person/PaidPredicate.java b/src/main/java/seedu/address/model/person/PaidPredicate.java new file mode 100644 index 00000000000..9f801909920 --- /dev/null +++ b/src/main/java/seedu/address/model/person/PaidPredicate.java @@ -0,0 +1,33 @@ +package seedu.address.model.person; + +import java.util.function.Predicate; + +/** + * Tests whether a person is unpaid. + */ +public class PaidPredicate implements Predicate { + + private final boolean paid; + public PaidPredicate(boolean paid) { + this.paid = paid; + } + @Override + public boolean test(Person person) { + return !person.getPaid(); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof PaidPredicate)) { + return false; + } + + PaidPredicate otherPaidPredicate = (PaidPredicate) other; + return this.paid == otherPaidPredicate.paid; + } +} diff --git a/src/main/java/seedu/address/model/person/PayRate.java b/src/main/java/seedu/address/model/person/PayRate.java new file mode 100644 index 00000000000..64de38c4fba --- /dev/null +++ b/src/main/java/seedu/address/model/person/PayRate.java @@ -0,0 +1,71 @@ +package seedu.address.model.person; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + + +/** + * Represents a Person's Pay Rate in the address book. + * Guarantees: immutable; is valid as declared in {@link #isValidPayRate(String)} + */ +public class PayRate { + + public static final String MESSAGE_CONSTRAINTS = "PayRate can be either integers" + + "or decimals of up to 2 decimal places. It cannot be negative"; + + /* + * The first character of the address must not be a whitespace, + * otherwise " " (a blank string) becomes a valid input. + * Only accepts positive numbers of up to 2 dp. + */ + public static final String VALIDATION_REGEX = "^\\s*\\d+(\\.\\d{1,2})?\\s*$"; + + public final double value; + + /** + * Constructs an {@code PayRate}. + * + * @param payRate A valid pay rate per hour. + */ + public PayRate(String payRate) { + requireNonNull(payRate); + checkArgument(isValidPayRate(payRate), MESSAGE_CONSTRAINTS); + value = Double.parseDouble(payRate); + assert value >= 0.0 : "payrate cannot be negative"; + } + + public static boolean isValidPayRate(String test) { + return test.matches(VALIDATION_REGEX); + } + + @Override + public String toString() { + return String.format("%.2f", value); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof PayRate)) { + return false; + } + + PayRate otherPayRate = (PayRate) other; + return value == otherPayRate.value; + } + + @Override + public int hashCode() { + Double payRate = value; + return payRate.hashCode(); + } + + public double getValue() { + return value; + } + +} diff --git a/src/main/java/seedu/address/model/person/Person.java b/src/main/java/seedu/address/model/person/Person.java index abe8c46b535..c85d798b181 100644 --- a/src/main/java/seedu/address/model/person/Person.java +++ b/src/main/java/seedu/address/model/person/Person.java @@ -2,14 +2,15 @@ import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; -import java.util.Collections; -import java.util.HashSet; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.time.format.DateTimeFormatter; +import java.util.Date; import java.util.Objects; -import java.util.Set; +import java.util.logging.Logger; +import seedu.address.commons.core.LogsCenter; import seedu.address.commons.util.ToStringBuilder; -import seedu.address.model.tag.Tag; - /** * Represents a Person in the address book. * Guarantees: details are present and not null, field values are validated, immutable. @@ -23,46 +24,159 @@ public class Person { // Data fields private final Address address; - private final Set tags = new HashSet<>(); + private final Subject subject; + private final Lesson lesson; + private boolean paid; + private PayRate payRate; + private Date beginTime; + private Date endTime; + + private final Logger logger = LogsCenter.getLogger(getClass()); /** * 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, Subject subject, Day day, + Begin begin, End end, boolean paid, PayRate payRate) { + requireAllNonNull(name, phone, email, address, subject, day, begin, end, paid, payRate); this.name = name; this.phone = phone; this.email = email; this.address = address; - this.tags.addAll(tags); + this.subject = subject; + this.paid = paid; + this.payRate = payRate; + this.lesson = new Lesson(day, begin, end); + + try { + this.beginTime = convertTime(begin.toString()); + this.endTime = convertTime(end.toString()); + } catch (ParseException e) { + logger.info("[Person constructor]: Error parsing begin and end."); + throw new RuntimeException("Error parsing begin and end."); + } + } + /** + * @return a defensive copy of name + */ public Name getName() { - return name; + return name.copy(); } + /** + * @return a defensive copy of phone + */ public Phone getPhone() { - return phone; + return phone.copy(); } + /** + * @return a defensive copy of email + */ public Email getEmail() { - return email; + return email.copy(); } + /** + * @return a defensive copy of address + */ public Address getAddress() { - return address; + return address.copy(); + } + + /** + * @return a defensive copy of subject + */ + public Subject getSubject() { + return subject.copy(); + } + + /** + * Return defensive copy of day + * @return day + */ + public Day getDay() { + String day = lesson.day.toString(); + return new Day(day); + } + + /** + * Return defensive copy of Begin + * @return begin + */ + public Begin getBegin() { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HHmm"); + String begin = lesson.begin.format(formatter); + return new Begin(begin); + } + + /** + * Return defensive copy of end + * @return end + */ + public End getEnd() { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HHmm"); + String end = lesson.end.format(formatter); + return new End(end); + } + + /** + * @return a defensive copy of lesson + */ + public Lesson getLesson() { + return lesson.copy(); + } + + /** + * @return a defensive copy of beginTime + */ + public Date getBeginTime() { + return (Date) beginTime.clone(); + } + + /** + * @return a defensive copy of endTime + */ + public Date getEndTime() { + return (Date) endTime.clone(); } /** * Returns an immutable tag set, which throws {@code UnsupportedOperationException} * if modification is attempted. */ - public Set getTags() { - return Collections.unmodifiableSet(tags); + public boolean getPaid() { + return this.paid; + } + + public void setPaid() { + this.paid = true; + } + + public void setUnPaid() { + this.paid = false; + } + + public PayRate getPayRate() { + return payRate; + } + + /** + * converts a string time into a date object + * @param time + * @return date object of the time + * @throws ParseException + */ + public Date convertTime(String time) throws ParseException { + SimpleDateFormat format = new SimpleDateFormat("HHmm"); + return format.parse(time); } /** - * Returns true if both persons have the same name. + * Returns true if both persons have the same name and phone number. * This defines a weaker notion of equality between two persons. */ public boolean isSamePerson(Person otherPerson) { @@ -71,7 +185,24 @@ public boolean isSamePerson(Person otherPerson) { } return otherPerson != null - && otherPerson.getName().equals(getName()); + && otherPerson.getName().equals(getName()) + && otherPerson.getPhone().equals(getPhone()); + } + + /** + * checks for clashing schedules + * @param otherPerson other person to be checked + * @return boolean for whether schedules clash + */ + public boolean isSameDate(Person otherPerson) { + if (otherPerson == this) { + return true; + } + + return otherPerson != null + && otherPerson.getDay().equals(getDay()) + && getBeginTime().before(otherPerson.getEndTime()) + && otherPerson.getBeginTime().before(getEndTime()); } /** @@ -94,13 +225,15 @@ public boolean equals(Object other) { && phone.equals(otherPerson.phone) && email.equals(otherPerson.email) && address.equals(otherPerson.address) - && tags.equals(otherPerson.tags); + && subject.equals(otherPerson.subject) + && lesson.equals(otherPerson.lesson) + && payRate.equals(otherPerson.payRate); } @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, subject, lesson, paid, payRate); } @Override @@ -110,8 +243,16 @@ public String toString() { .add("phone", phone) .add("email", email) .add("address", address) - .add("tags", tags) + .add("subject", subject) + .add("lesson", lesson) + .add("paid", paid) + .add("payrate", payRate) .toString(); } + public double getMonthlyFee() { + double monthlyFee = lesson.getMonthlyHours() * payRate.getValue(); + assert monthlyFee >= 0 : "monthly revenue should not be negative"; + return monthlyFee; + } } diff --git a/src/main/java/seedu/address/model/person/Phone.java b/src/main/java/seedu/address/model/person/Phone.java index d733f63d739..492aa2dc161 100644 --- a/src/main/java/seedu/address/model/person/Phone.java +++ b/src/main/java/seedu/address/model/person/Phone.java @@ -33,6 +33,13 @@ public static boolean isValidPhone(String test) { return test.matches(VALIDATION_REGEX); } + /** + * @return a defensive copy of phone. + */ + public Phone copy() { + return new Phone(this.value); + } + @Override public String toString() { return value; diff --git a/src/main/java/seedu/address/model/person/Subject.java b/src/main/java/seedu/address/model/person/Subject.java new file mode 100644 index 00000000000..ce1bcf987f7 --- /dev/null +++ b/src/main/java/seedu/address/model/person/Subject.java @@ -0,0 +1,73 @@ +package seedu.address.model.person; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +/** + * Represents a Person's Subject in the address book. + * Guarantees: immutable; is valid as declared in {@link #isValidSubject(String)} (String)} + */ +public class Subject { + + public static final String MESSAGE_CONSTRAINTS = "Subject can take any values, and it should not be blank"; + + /* + * The first character of the subject must not be a whitespace, + * otherwise " " (a blank string) becomes a valid input. + */ + public static final String VALIDATION_REGEX = "[^\\s].*"; + + public final String value; + + /** + * Constructs an {@code Subject}. + * + * @param subject A valid subject. + */ + public Subject(String subject) { + requireNonNull(subject); + checkArgument(isValidSubject(subject), MESSAGE_CONSTRAINTS); + value = subject; + } + + /** + * Returns true if a given string is a valid email. + */ + public static boolean isValidSubject(String test) { + return test.matches(VALIDATION_REGEX); + } + + /** + * @return a defensive copy of subject + */ + + public Subject copy() { + return new Subject(this.value); + } + + @Override + public String toString() { + return value; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof Subject)) { + return false; + } + + Subject otherSubject = (Subject) other; + return value.equals(otherSubject.value); + } + + @Override + public int hashCode() { + return value.hashCode(); + } + +} diff --git a/src/main/java/seedu/address/model/person/SubjectContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/person/SubjectContainsKeywordsPredicate.java new file mode 100644 index 00000000000..cfb72b5e04d --- /dev/null +++ b/src/main/java/seedu/address/model/person/SubjectContainsKeywordsPredicate.java @@ -0,0 +1,45 @@ +package seedu.address.model.person; + +import java.util.List; +import java.util.function.Predicate; + +import seedu.address.commons.util.StringUtil; +import seedu.address.commons.util.ToStringBuilder; + +/** + * Tests that a {@code Person}'s {@code Subject} matches any of the keywords given. + */ +public class SubjectContainsKeywordsPredicate implements Predicate { + private final List keywords; + + public SubjectContainsKeywordsPredicate(List keywords) { + this.keywords = keywords; + } + + @Override + public boolean test(Person person) { + return keywords.isEmpty() || keywords.stream() + .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(person.getSubject().value, keyword)); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof SubjectContainsKeywordsPredicate)) { + return false; + } + + SubjectContainsKeywordsPredicate otherSubjectContainsKeywordsPredicate = + (SubjectContainsKeywordsPredicate) other; + return keywords.equals(otherSubjectContainsKeywordsPredicate.keywords); + } + + @Override + public String toString() { + return new ToStringBuilder(this).add("keywords", keywords).toString(); + } +} diff --git a/src/main/java/seedu/address/model/person/UniquePersonList.java b/src/main/java/seedu/address/model/person/UniquePersonList.java index cc0a68d79f9..9d918539210 100644 --- a/src/main/java/seedu/address/model/person/UniquePersonList.java +++ b/src/main/java/seedu/address/model/person/UniquePersonList.java @@ -5,9 +5,12 @@ import java.util.Iterator; import java.util.List; +import java.util.stream.Collectors; import javafx.collections.FXCollections; import javafx.collections.ObservableList; +import seedu.address.model.interval.Interval; +import seedu.address.model.person.exceptions.ClashingScheduleException; import seedu.address.model.person.exceptions.DuplicatePersonException; import seedu.address.model.person.exceptions.PersonNotFoundException; @@ -36,6 +39,28 @@ public boolean contains(Person toCheck) { return internalList.stream().anyMatch(toCheck::isSamePerson); } + /** + * Returns true if the list contains a perosn that has a clashing schedule + */ + public boolean checkSameDate(Person toCheck) { + requireNonNull(toCheck); + return internalList.stream() + .filter(person -> !person.isSamePerson(toCheck)) + .anyMatch(toCheck::isSameDate); + } + + /** + * Finds the list of timings which have the same day as the Interval from the address book + * @param interval + * @return list of timings + */ + public List findInterval(Interval interval) { + requireNonNull(interval); + return internalList.stream() + .filter(person -> person.getDay().toString().equals(interval.getIntervalDay().toString())) + .map(person -> person.getLesson().getTimeSlot()).collect(Collectors.toList()); + } + /** * Adds a person to the list. * The person must not already exist in the list. @@ -45,6 +70,9 @@ public void add(Person toAdd) { if (contains(toAdd)) { throw new DuplicatePersonException(); } + if (checkSameDate(toAdd)) { + throw new ClashingScheduleException(); + } internalList.add(toAdd); } @@ -53,7 +81,7 @@ public void add(Person toAdd) { * {@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) { + public void setPerson(Person target, Person editedPerson, boolean isEditingSchedule) { requireAllNonNull(target, editedPerson); int index = internalList.indexOf(target); @@ -65,9 +93,50 @@ public void setPerson(Person target, Person editedPerson) { throw new DuplicatePersonException(); } + if (isEditingSchedule && checkSameDate(editedPerson)) { + throw new ClashingScheduleException(); + } + internalList.set(index, editedPerson); } + /** + * Set the person as paid in the list. + * The person must exist in the list. + */ + public void setPaid(Person toPaid) { + requireNonNull(toPaid); + + int index = internalList.indexOf(toPaid); + Person p = new Person(toPaid.getName(), toPaid.getPhone(), toPaid.getEmail(), toPaid.getAddress(), + toPaid.getSubject(), toPaid.getDay(), toPaid.getBegin(), + toPaid.getEnd(), true, toPaid.getPayRate()); + if (index != -1) { + internalList.set(index, p); + } + } + + public boolean getPaid(Person toGet) { + int index = internalList.indexOf(toGet); + return internalList.get(index).getPaid(); + } + + /** + * Set the person as not paid in the list. + * The person must exist in the list. + */ + public void setUnPaid(Person toUnPaid) { + requireNonNull(toUnPaid); + + int index = internalList.indexOf(toUnPaid); + Person p = new Person(toUnPaid.getName(), toUnPaid.getPhone(), toUnPaid.getEmail(), toUnPaid.getAddress(), + toUnPaid.getSubject(), toUnPaid.getDay(), toUnPaid.getBegin(), + toUnPaid.getEnd(), false, toUnPaid.getPayRate()); + if (index != -1) { + internalList.set(index, p); + } + } + /** * Removes the equivalent person from the list. * The person must exist in the list. diff --git a/src/main/java/seedu/address/model/person/exceptions/ClashingScheduleException.java b/src/main/java/seedu/address/model/person/exceptions/ClashingScheduleException.java new file mode 100644 index 00000000000..1b75eed2e58 --- /dev/null +++ b/src/main/java/seedu/address/model/person/exceptions/ClashingScheduleException.java @@ -0,0 +1,10 @@ +package seedu.address.model.person.exceptions; + +/** + * Signals that the operation will result in clashing schedules + */ +public class ClashingScheduleException extends RuntimeException { + public ClashingScheduleException() { + super("Operation would result in clashing schedules"); + } +} diff --git a/src/main/java/seedu/address/model/tag/Tag.java b/src/main/java/seedu/address/model/tag/Tag.java deleted file mode 100644 index f1a0d4e233b..00000000000 --- a/src/main/java/seedu/address/model/tag/Tag.java +++ /dev/null @@ -1,62 +0,0 @@ -package seedu.address.model.tag; - -import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.AppUtil.checkArgument; - -/** - * Represents a Tag in the address book. - * Guarantees: immutable; name is valid as declared in {@link #isValidTagName(String)} - */ -public class Tag { - - public static final String MESSAGE_CONSTRAINTS = "Tags names should be alphanumeric"; - public static final String VALIDATION_REGEX = "\\p{Alnum}+"; - - public final String tagName; - - /** - * Constructs a {@code Tag}. - * - * @param tagName A valid tag name. - */ - public Tag(String tagName) { - requireNonNull(tagName); - checkArgument(isValidTagName(tagName), MESSAGE_CONSTRAINTS); - this.tagName = tagName; - } - - /** - * Returns true if a given string is a valid tag name. - */ - public static boolean isValidTagName(String test) { - return test.matches(VALIDATION_REGEX); - } - - @Override - public boolean equals(Object other) { - if (other == this) { - return true; - } - - // instanceof handles nulls - if (!(other instanceof Tag)) { - return false; - } - - Tag otherTag = (Tag) other; - return tagName.equals(otherTag.tagName); - } - - @Override - public int hashCode() { - return tagName.hashCode(); - } - - /** - * Format state as text for viewing. - */ - public String toString() { - return '[' + tagName + ']'; - } - -} diff --git a/src/main/java/seedu/address/model/util/SampleDataUtil.java b/src/main/java/seedu/address/model/util/SampleDataUtil.java index 1806da4facf..cf9867e9af1 100644 --- a/src/main/java/seedu/address/model/util/SampleDataUtil.java +++ b/src/main/java/seedu/address/model/util/SampleDataUtil.java @@ -1,18 +1,17 @@ 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.Begin; +import seedu.address.model.person.Day; import seedu.address.model.person.Email; +import seedu.address.model.person.End; import seedu.address.model.person.Name; +import seedu.address.model.person.PayRate; import seedu.address.model.person.Person; import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; - +import seedu.address.model.person.Subject; /** * Contains utility methods for populating {@code AddressBook} with sample data. */ @@ -20,23 +19,24 @@ public class SampleDataUtil { public static Person[] getSamplePersons() { return new Person[] { new Person(new Name("Alex Yeoh"), new Phone("87438807"), new Email("alexyeoh@example.com"), - new Address("Blk 30 Geylang Street 29, #06-40"), - getTagSet("friends")), + new Address("Blk 30 Geylang Street 29, #06-40"), new Subject("Maths"), new Day("Mon"), + new Begin("2000"), new End("2100"), false, new PayRate("20")), 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 Address("Blk 30 Lorong 3 Serangoon Gardens, #07-18"), new Subject("Physics"), new Day("Tue"), + new Begin("1000"), new End("1100"), false, new PayRate("25.00")), + 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 Address("Blk 11 Ang Mo Kio Street 74, #11-04"), new Subject("Chemistry"), new Day("Wed"), + new Begin("1200"), new End("1300"), false, new PayRate("30.00")), 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 Address("Blk 436 Serangoon Gardens Street 26, #16-43"), new Subject("Biology"), new Day("Thu"), + new Begin("1700"), new End("1800"), false, new PayRate("40.00")), 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 Address("Blk 47 Tampines Street 20, #17-35"), new Subject("English"), new Day("Fri"), + new Begin("0900"), new End("1000"), false, new PayRate("50.00")), new Person(new Name("Roy Balakrishnan"), new Phone("92624417"), new Email("royb@example.com"), - new Address("Blk 45 Aljunied Street 85, #11-31"), - getTagSet("colleagues")) + new Address("Blk 45 Aljunied Street 85, #11-31"), new Subject("Chinese"), new Day("Sat"), + new Begin("0800"), new End("0900"), false, new PayRate("66.00")) }; } @@ -47,14 +47,4 @@ public static ReadOnlyAddressBook getSampleAddressBook() { } 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/JsonAdaptedPerson.java b/src/main/java/seedu/address/storage/JsonAdaptedPerson.java index bd1ca0f56c8..4224a762700 100644 --- a/src/main/java/seedu/address/storage/JsonAdaptedPerson.java +++ b/src/main/java/seedu/address/storage/JsonAdaptedPerson.java @@ -1,22 +1,19 @@ package seedu.address.storage; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import java.util.stream.Collectors; - import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; import seedu.address.commons.exceptions.IllegalValueException; import seedu.address.model.person.Address; +import seedu.address.model.person.Begin; +import seedu.address.model.person.Day; import seedu.address.model.person.Email; +import seedu.address.model.person.End; import seedu.address.model.person.Name; +import seedu.address.model.person.PayRate; import seedu.address.model.person.Person; import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; - +import seedu.address.model.person.Subject; /** * Jackson-friendly version of {@link Person}. */ @@ -28,7 +25,13 @@ class JsonAdaptedPerson { private final String phone; private final String email; private final String address; - private final List tags = new ArrayList<>(); + private final String subject; + private final String day; + private final String begin; + private final String end; + private final boolean paid; + + private final String payRate; /** * Constructs a {@code JsonAdaptedPerson} with the given person details. @@ -36,14 +39,19 @@ class JsonAdaptedPerson { @JsonCreator public JsonAdaptedPerson(@JsonProperty("name") String name, @JsonProperty("phone") String phone, @JsonProperty("email") String email, @JsonProperty("address") String address, - @JsonProperty("tags") List tags) { + @JsonProperty("subject") String subject, @JsonProperty("day") String day, + @JsonProperty("begin") String begin, @JsonProperty("end") String end, + @JsonProperty("paid") Boolean paid, @JsonProperty("payrate") String payRate) { this.name = name; this.phone = phone; this.email = email; this.address = address; - if (tags != null) { - this.tags.addAll(tags); - } + this.subject = subject; + this.day = day; + this.begin = begin; + this.end = end; + this.paid = paid; + this.payRate = payRate; } /** @@ -54,9 +62,12 @@ public JsonAdaptedPerson(Person source) { phone = source.getPhone().value; email = source.getEmail().value; address = source.getAddress().value; - tags.addAll(source.getTags().stream() - .map(JsonAdaptedTag::new) - .collect(Collectors.toList())); + subject = source.getSubject().value; + day = source.getDay().value.toString(); + begin = source.getBegin().value; + end = source.getEnd().value; + paid = source.getPaid(); + payRate = source.getPayRate().toString(); } /** @@ -65,11 +76,6 @@ public JsonAdaptedPerson(Person source) { * @throws IllegalValueException if there were any data constraints violated in the adapted person. */ public Person toModelType() throws IllegalValueException { - final List personTags = new ArrayList<>(); - for (JsonAdaptedTag tag : tags) { - personTags.add(tag.toModelType()); - } - if (name == null) { throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Name.class.getSimpleName())); } @@ -102,8 +108,48 @@ public Person toModelType() throws IllegalValueException { } final Address modelAddress = new Address(address); - final Set modelTags = new HashSet<>(personTags); - return new Person(modelName, modelPhone, modelEmail, modelAddress, modelTags); + if (subject == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Subject.class.getSimpleName())); + } + if (!Subject.isValidSubject(subject)) { + throw new IllegalValueException(Subject.MESSAGE_CONSTRAINTS); + } + final Subject modelSubject = new Subject(subject); + + if (day == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Day.class.getSimpleName())); + } + if (!Day.isValidDay(day)) { + throw new IllegalValueException(Day.MESSAGE_CONSTRAINTS); + } + final Day modelDay = new Day(day); + + if (begin == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Begin.class.getSimpleName())); + } + if (!Begin.isValidBegin(begin)) { + throw new IllegalValueException(Begin.MESSAGE_CONSTRAINTS); + } + final Begin modelBegin = new Begin(begin); + + if (end == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, End.class.getSimpleName())); + } + if (!End.isValidEnd(end)) { + throw new IllegalValueException(End.MESSAGE_CONSTRAINTS); + } + final End modelEnd = new End(end); + + if (payRate == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, PayRate.class.getSimpleName())); + } + if (!PayRate.isValidPayRate(payRate)) { + throw new IllegalValueException(PayRate.MESSAGE_CONSTRAINTS); + } + + final PayRate modelPayRate = new PayRate(payRate); + return new Person(modelName, modelPhone, modelEmail, modelAddress, modelSubject, modelDay, + modelBegin, modelEnd, paid, modelPayRate); } } diff --git a/src/main/java/seedu/address/storage/JsonAdaptedTag.java b/src/main/java/seedu/address/storage/JsonAdaptedTag.java deleted file mode 100644 index 0df22bdb754..00000000000 --- a/src/main/java/seedu/address/storage/JsonAdaptedTag.java +++ /dev/null @@ -1,48 +0,0 @@ -package seedu.address.storage; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonValue; - -import seedu.address.commons.exceptions.IllegalValueException; -import seedu.address.model.tag.Tag; - -/** - * Jackson-friendly version of {@link Tag}. - */ -class JsonAdaptedTag { - - private final String tagName; - - /** - * Constructs a {@code JsonAdaptedTag} with the given {@code tagName}. - */ - @JsonCreator - public JsonAdaptedTag(String tagName) { - this.tagName = tagName; - } - - /** - * Converts a given {@code Tag} into this class for Jackson use. - */ - public JsonAdaptedTag(Tag source) { - tagName = source.tagName; - } - - @JsonValue - public String getTagName() { - return tagName; - } - - /** - * Converts this Jackson-friendly adapted tag object into the model's {@code Tag} object. - * - * @throws IllegalValueException if there were any data constraints violated in the adapted tag. - */ - public Tag toModelType() throws IllegalValueException { - if (!Tag.isValidTagName(tagName)) { - throw new IllegalValueException(Tag.MESSAGE_CONSTRAINTS); - } - return new Tag(tagName); - } - -} diff --git a/src/main/java/seedu/address/ui/CommandBox.java b/src/main/java/seedu/address/ui/CommandBox.java index 9e75478664b..6a61ad45ebf 100644 --- a/src/main/java/seedu/address/ui/CommandBox.java +++ b/src/main/java/seedu/address/ui/CommandBox.java @@ -38,7 +38,7 @@ public CommandBox(CommandExecutor commandExecutor) { private void handleCommandEntered() { String commandText = commandTextField.getText(); if (commandText.equals("")) { - return; + setStyleToIndicateCommandFailure(); } try { diff --git a/src/main/java/seedu/address/ui/HelpWindow.java b/src/main/java/seedu/address/ui/HelpWindow.java index 3f16b2fcf26..8ee33be0aab 100644 --- a/src/main/java/seedu/address/ui/HelpWindow.java +++ b/src/main/java/seedu/address/ui/HelpWindow.java @@ -15,7 +15,7 @@ */ public class HelpWindow extends UiPart { - public static final String USERGUIDE_URL = "https://se-education.org/addressbook-level3/UserGuide.html"; + public static final String USERGUIDE_URL = "https://ay2324s1-cs2103t-f10-4.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/address/ui/MainWindow.java index 79e74ef37c0..984a9890e29 100644 --- a/src/main/java/seedu/address/ui/MainWindow.java +++ b/src/main/java/seedu/address/ui/MainWindow.java @@ -34,6 +34,7 @@ public class MainWindow extends UiPart { private PersonListPanel personListPanel; private ResultDisplay resultDisplay; private HelpWindow helpWindow; + private ScheduleListPanel scheduleListPanel; @FXML private StackPane commandBoxPlaceholder; @@ -50,6 +51,9 @@ public class MainWindow extends UiPart { @FXML private StackPane statusbarPlaceholder; + @FXML + private StackPane scheduleListPanelPlaceholder; + /** * Creates a {@code MainWindow} with the given {@code Stage} and {@code Logic}. */ @@ -121,6 +125,10 @@ void fillInnerParts() { CommandBox commandBox = new CommandBox(this::executeCommand); commandBoxPlaceholder.getChildren().add(commandBox.getRoot()); + + scheduleListPanel = new ScheduleListPanel(logic.getScheduleList()); + scheduleListPanelPlaceholder.getChildren().add(scheduleListPanel.getRoot()); + } /** diff --git a/src/main/java/seedu/address/ui/PersonCard.java b/src/main/java/seedu/address/ui/PersonCard.java index 094c42cda82..e67a9cfc40b 100644 --- a/src/main/java/seedu/address/ui/PersonCard.java +++ b/src/main/java/seedu/address/ui/PersonCard.java @@ -1,7 +1,5 @@ package seedu.address.ui; -import java.util.Comparator; - import javafx.fxml.FXML; import javafx.scene.control.Label; import javafx.scene.layout.FlowPane; @@ -38,6 +36,16 @@ public class PersonCard extends UiPart { private Label address; @FXML private Label email; + @FXML + private Label subject; + + @FXML + private Label lesson; + @FXML + private Label paid; + @FXML + private Label payRate; + @FXML private FlowPane tags; @@ -52,8 +60,19 @@ public PersonCard(Person person, int displayedIndex) { phone.setText(person.getPhone().value); address.setText(person.getAddress().value); email.setText(person.getEmail().value); - person.getTags().stream() - .sorted(Comparator.comparing(tag -> tag.tagName)) - .forEach(tag -> tags.getChildren().add(new Label(tag.tagName))); + subject.setText(person.getSubject().value); + lesson.setText(person.getLesson().toString()); + payRate.setText("Rate: " + person.getPayRate().toString() + "/h"); + + if (person.getPaid()) { + paid.setText("paid"); + paid.setStyle("-fx-background-color: green; -fx-text-fill: white;"); + } else { + paid.setText("not paid"); + paid.setStyle("-fx-background-color: red; -fx-text-fill: white;"); + + } + tags.getChildren().add(paid); + tags.getChildren().add(subject); } } diff --git a/src/main/java/seedu/address/ui/ScheduleCard.java b/src/main/java/seedu/address/ui/ScheduleCard.java new file mode 100644 index 00000000000..d165d0e41f2 --- /dev/null +++ b/src/main/java/seedu/address/ui/ScheduleCard.java @@ -0,0 +1,49 @@ +package seedu.address.ui; + +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.layout.FlowPane; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Region; +import seedu.address.model.person.Person; + + +/** + * An UI component that displays schedule of a {@code Person}. + */ +public class ScheduleCard extends UiPart { + + private static final String FXML = "ScheduleCard.fxml"; + + public final Person person; + @FXML + private HBox cardPane; + + @FXML + private Label name; + + @FXML + private Label address; + + @FXML + private Label lesson; + + @FXML + private Label subject; + + @FXML + private FlowPane tags; + + /** + * Creates a {@code PersonCode} with the given {@code Person} and index to display. + */ + public ScheduleCard(Person person) { + super(FXML); + this.person = person; + name.setText(person.getName().fullName); + address.setText(person.getAddress().value); + lesson.setText(person.getLesson().toString()); + subject.setText(person.getSubject().toString()); + tags.getChildren().add(subject); + } +} diff --git a/src/main/java/seedu/address/ui/ScheduleListPanel.java b/src/main/java/seedu/address/ui/ScheduleListPanel.java new file mode 100644 index 00000000000..f8c380e9446 --- /dev/null +++ b/src/main/java/seedu/address/ui/ScheduleListPanel.java @@ -0,0 +1,53 @@ +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' schedules. + */ +public class ScheduleListPanel extends UiPart { + + private static final String FXML = "ScheduleListPanel.fxml"; + + @FXML + private ListView scheduleListView; + + private final Logger logger = LogsCenter.getLogger(ScheduleListPanel.class); + + /** + * Creates a {@code ScheduleListPanel} with the given {@code ObservableList}. + */ + public ScheduleListPanel(ObservableList personList) { + super(FXML); + scheduleListView.setItems(personList); + scheduleListView.setCellFactory(listView -> new ScheduleListViewCell()); + } + + /** + * Custom {@code ListCell} that displays the graphics of a {@code Person} using a {@code PersonCard}. + */ + class ScheduleListViewCell 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 ScheduleCard(person).getRoot()); + } + } + } + + +} diff --git a/src/main/java/seedu/address/ui/UiManager.java b/src/main/java/seedu/address/ui/UiManager.java index fdf024138bc..b7c0f17b5db 100644 --- a/src/main/java/seedu/address/ui/UiManager.java +++ b/src/main/java/seedu/address/ui/UiManager.java @@ -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/tuition_connect.png"; private Logic logic; private MainWindow mainWindow; @@ -39,6 +39,8 @@ public void start(Stage primaryStage) { //Set the application icon. primaryStage.getIcons().add(getImage(ICON_APPLICATION)); + + try { mainWindow = new MainWindow(primaryStage, logic); mainWindow.show(); //This should be called before creating other UI parts diff --git a/src/main/resources/images/PaidSequenceDigram.png b/src/main/resources/images/PaidSequenceDigram.png new file mode 100644 index 00000000000..50195b79d41 Binary files /dev/null and b/src/main/resources/images/PaidSequenceDigram.png differ diff --git a/src/main/resources/images/UnpaidAllSequenceDigram.png b/src/main/resources/images/UnpaidAllSequenceDigram.png new file mode 100644 index 00000000000..575fe997bed Binary files /dev/null and b/src/main/resources/images/UnpaidAllSequenceDigram.png differ diff --git a/src/main/resources/images/address_book_32.png b/src/main/resources/images/address_book_32.png deleted file mode 100644 index 29810cf1fd9..00000000000 Binary files a/src/main/resources/images/address_book_32.png and /dev/null differ diff --git a/src/main/resources/images/tuition_connect.png b/src/main/resources/images/tuition_connect.png new file mode 100644 index 00000000000..6cae74b6489 Binary files /dev/null and b/src/main/resources/images/tuition_connect.png differ diff --git a/src/main/resources/view/DarkTheme.css b/src/main/resources/view/DarkTheme.css index 36e6b001cd8..07f94e5093f 100644 --- a/src/main/resources/view/DarkTheme.css +++ b/src/main/resources/view/DarkTheme.css @@ -89,22 +89,22 @@ .list-view { -fx-background-insets: 0; - -fx-padding: 0; + -fx-padding: 10; -fx-background-color: derive(#1d1d1d, 20%); } .list-cell { -fx-label-padding: 0 0 0 0; - -fx-graphic-text-gap : 0; - -fx-padding: 0 0 0 0; + -fx-graphic-text-gap : 10; + -fx-padding: 0; } .list-cell:filled:even { - -fx-background-color: #3c3e3f; + -fx-background-color: derive(#1d1d1d, 20%); } .list-cell:filled:odd { - -fx-background-color: #515658; + -fx-background-color: derive(#1d1d1d, 20%); } .list-cell:filled:selected { @@ -122,7 +122,7 @@ .cell_big_label { -fx-font-family: "Segoe UI Semibold"; - -fx-font-size: 16px; + -fx-font-size: 20px; -fx-text-fill: #010504; } @@ -131,6 +131,11 @@ -fx-font-size: 13px; -fx-text-fill: #010504; } +.custom-vbox { + -fx-background-color: grey; + -fx-background-radius: 10; + -fx-padding: 10; +} .stack-pane { -fx-background-color: derive(#1d1d1d, 20%); @@ -147,10 +152,16 @@ } .result-display { - -fx-background-color: transparent; + -fx-background-insets: 0; + -fx-border-color: #383838 #383838 #383838 #383838; + -fx-border-insets: 0; -fx-font-family: "Segoe UI Light"; -fx-font-size: 13pt; -fx-text-fill: white; + -fx-background-color: derive(#1d1d1d, 30%); + -fx-background-radius: 10; + -fx-padding: 10; + -fx-border-radius: 10 10 10 10; } .result-display .label { @@ -318,14 +329,16 @@ } #commandTextField { - -fx-background-color: transparent #383838 transparent #383838; -fx-background-insets: 0; - -fx-border-color: #383838 #383838 #ffffff #383838; + -fx-border-color: #383838 #383838 #383838 #383838; -fx-border-insets: 0; - -fx-border-width: 1; -fx-font-family: "Segoe UI Light"; -fx-font-size: 13pt; -fx-text-fill: white; + -fx-background-color: derive(#1d1d1d, 30%); + -fx-background-radius: 10; + -fx-padding: 10; + -fx-border-radius: 10 10 10 10; } #filterField, #personListPanel, #personWebpage { @@ -333,7 +346,7 @@ } #resultDisplay .content { - -fx-background-color: transparent, #383838, transparent, #383838; + -fx-background-color: derive(#1d1d1d, 30%); -fx-background-radius: 0; } diff --git a/src/main/resources/view/MainWindow.fxml b/src/main/resources/view/MainWindow.fxml index 7778f666a0a..1b2a932c5be 100644 --- a/src/main/resources/view/MainWindow.fxml +++ b/src/main/resources/view/MainWindow.fxml @@ -10,11 +10,12 @@ + + title="TuitionConnect" minWidth="450" minHeight="600" onCloseRequest="#handleExit"> - + @@ -50,7 +51,10 @@ - + + + + diff --git a/src/main/resources/view/PersonListCard.fxml b/src/main/resources/view/PersonListCard.fxml index f5e812e25e6..990f2a4ba38 100644 --- a/src/main/resources/view/PersonListCard.fxml +++ b/src/main/resources/view/PersonListCard.fxml @@ -10,11 +10,14 @@ + + + - + @@ -27,10 +30,17 @@ - + +