diff --git a/README.md b/README.md index 13f5c77403f..1dc23f5f76d 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,27 @@ -[![CI Status](https://github.com/se-edu/addressbook-level3/workflows/Java%20CI/badge.svg)](https://github.com/se-edu/addressbook-level3/actions) +[![CI Status](https://github.com/ay2122s1-cs2103t-t12-4/tp/workflows/Java%20CI/badge.svg)](https://github.com/AY2122S1-CS2103T-T12-4/tp/actions) + +

+ +

Welcome to TutAssistor!

+

![Ui](docs/images/Ui.png) -* This is **a sample project for Software Engineering (SE) students**.
- Example usages: - * as a starting point of a course project (as opposed to writing everything from scratch) - * as a case study -* The project simulates an ongoing software project for a desktop application (called _AddressBook_) used for managing contact details. - * It is **written in OOP fashion**. It provides a **reasonably well-written** code base **bigger** (around 6 KLoC) than what students usually write in beginner-level SE modules, without being overwhelmingly big. - * It comes with a **reasonable level of user and developer documentation**. -* It is named `AddressBook Level 3` (`AB3` for short) because it was initially created as a part of a series of `AddressBook` projects (`Level 1`, `Level 2`, `Level 3` ...). -* For the detailed documentation of this project, see the **[Address Book Product Website](https://se-education.org/addressbook-level3)**. -* This project is a **part of the se-education.org** initiative. If you would like to contribute code to this project, see [se-education.org](https://se-education.org#https://se-education.org/#contributing) for more info. +### Description +TutAssistor is a tool for private tutors to keep track of their students and classes. With TutAssistor, you can manage many different classes and students, and facilitate deconfliction of time slots. + +### Features +* Add and edit student contact information. +* Create classes with fixed timeslots and student limit to prevent overbooking. +* Add Tags to students and classes for easier searching. +* Add and edit Notes for students and classes to keep track of miscellaneous information. +* Search and filter students by timeslot, subject, day, etc. +* Add/view/change time slots for students. +* Be informed of whether or not time slots are fully booked. + +### Project information +* This project was made as part of a student project for NUS CS2103T AY21/22 S1. +* For the detailed documentation of this project, see the **[TutAssistor Website](https://ay2122s1-cs2103t-t12-4.github.io/tp/)**. + +### Acknowledgements +* This project is based on the AddressBook-Level3 project created by the [SE-EDU initiative](https://se-education.org). diff --git a/build.gradle b/build.gradle index be2d2905dde..4cdde3e08a2 100644 --- a/build.gradle +++ b/build.gradle @@ -56,6 +56,9 @@ dependencies { implementation group: 'org.openjfx', name: 'javafx-graphics', version: javaFxVersion, classifier: 'win' implementation group: 'org.openjfx', name: 'javafx-graphics', version: javaFxVersion, classifier: 'mac' implementation group: 'org.openjfx', name: 'javafx-graphics', version: javaFxVersion, classifier: 'linux' + implementation group: 'org.openjfx', name: 'javafx-swing', version: javaFxVersion, classifier: 'win' + implementation group: 'org.openjfx', name: 'javafx-swing', version: javaFxVersion, classifier: 'mac' + implementation group: 'org.openjfx', name: 'javafx-swing', version: javaFxVersion, classifier: 'linux' implementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.7.0' implementation group: 'com.fasterxml.jackson.datatype', name: 'jackson-datatype-jsr310', version: '2.7.4' @@ -65,6 +68,10 @@ dependencies { testRuntimeOnly group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: jUnitVersion } +run { + enableAssertions = true +} + shadowJar { archiveName = 'addressbook.jar' } diff --git a/docs/AboutUs.md b/docs/AboutUs.md index 1c9514e966a..6448e87fda2 100644 --- a/docs/AboutUs.md +++ b/docs/AboutUs.md @@ -3,57 +3,61 @@ layout: page title: About Us --- -We are a team based in the [School of Computing, National University of Singapore](http://www.comp.nus.edu.sg). +We are a team based in the [School of Computing, National University of Singapore](http://www.comp.nus.edu.sg). This project team was formed for the module CS2103T in AY21/22 Sem 1. + -You can reach us at the email `seer[at]comp.nus.edu.sg` ## Project team -### John Doe +### Timothy Ng - + -[[homepage](http://www.comp.nus.edu.sg/~damithch)] -[[github](https://github.com/johndoe)] -[[portfolio](team/johndoe.md)] -* Role: Project Advisor +[[github](https://github.com/timiditi)] +[[portfolio](team/timiditi.md)] -### Jane Doe +* Role: Team Lead +* Responsibilities: Documentation - +### Mohamed Ammar Zumana Haseen -[[github](http://github.com/johndoe)] -[[portfolio](team/johndoe.md)] + -* Role: Team Lead -* Responsibilities: UI +[[github](http://github.com/amzhy)] +[[portfolio](team/amzhy.md)] -### Johnny Doe +* Role: Developer +* Responsibilities: Ensures the testing of the project is done properly on time - +### Deng Huaiyu -[[github](http://github.com/johndoe)] [[portfolio](team/johndoe.md)] + -* Role: Developer -* Responsibilities: Data +[[github](https://github.com/NoraYUuu)] +[[portfolio](team/norayuuu.md)] -### Jean Doe +* Role: Code Quality +* Responsibilities: Looks after code quality and ensures adherence to coding standards. - +### Adam Ho -[[github](http://github.com/johndoe)] -[[portfolio](team/johndoe.md)] + -* Role: Developer -* Responsibilities: Dev Ops + Threading +[[github](http://github.com/adam-ky)] +[[portfolio](team/adam-ky.md)] +* Role: Time Keeper +* Responsibilities: In charge of defining, assigning, and tracking project tasks. Ensure project deliverables are done on time. + + + +### Feng Zhunyi -### James Doe + - +[[github](http://github.com/Leofeng10)] +[[portfolio](team/leofeng10.md)] -[[github](http://github.com/johndoe)] -[[portfolio](team/johndoe.md)] * Role: Developer -* Responsibilities: UI +* Responsibilities: Integration diff --git a/docs/DeveloperGuide.md b/docs/DeveloperGuide.md index 46eae8ee565..48d3fec7ba0 100644 --- a/docs/DeveloperGuide.md +++ b/docs/DeveloperGuide.md @@ -9,7 +9,10 @@ title: Developer Guide ## **Acknowledgements** -* {list here sources of all reused/adapted ideas, code, documentation, and third-party libraries -- include links to the original source as well} +* This software is built upon [SE-EDU's AddressBook Level-3](https://se-education.org/addressbook-level3/) project. +* The CLI History Navigation feature was inspired by [YaleChen299's ip](https://github.com/yalechen299/ip) for CS2103T, + though its implementation in this project is new. +* Implementation of opening User Guide in user's browser feature referenced from [samyipsh's tP](https://github.com/samyipsh/tp) for CS2103T. -------------------------------------------------------------------------------------------------------------------- @@ -21,14 +24,10 @@ Refer to the guide [_Setting up and getting started_](SettingUp.md). ## **Design** -
- -:bulb: **Tip:** The `.puml` files used to create diagrams in this document can be found in the [diagrams](https://github.com/se-edu/addressbook-level3/tree/master/docs/diagrams/) folder. Refer to the [_PlantUML Tutorial_ at se-edu/guides](https://se-education.org/guides/tutorials/plantUml.html) to learn how to create and edit diagrams. -
- ### Architecture - - +

+ +

The ***Architecture Diagram*** given above explains the high-level design of the App. @@ -54,26 +53,30 @@ The rest of the App consists of four components. The *Sequence Diagram* below shows how the components interact with each other for the scenario where the user issues the command `delete 1`. - - +

+ +

Each of the four main components (also shown in the diagram above), * defines its *API* in an `interface` with the same name as the Component. -* implements its functionality using a concrete `{Component Name}Manager` class (which follows the corresponding API `interface` mentioned in the previous point. +* implements its functionality using a concrete `{Component Name}Manager` class (which follows the corresponding API `interface` mentioned in the previous point.) For example, the `Logic` component defines its API in the `Logic.java` interface and implements its functionality using the `LogicManager.java` class which follows the `Logic` interface. Other components interact with a given component through its interface rather than the concrete class (reason: to prevent outside component's being coupled to the implementation of a component), as illustrated in the (partial) class diagram below. - - +

+ +

The sections below give more details of each component. ### UI component The **API** of this component is specified in [`Ui.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/ui/Ui.java) -![Structure of the UI Component](images/UiClassDiagram.png) +

+ +

-The UI consists of a `MainWindow` that is made up of parts e.g.`CommandBox`, `ResultDisplay`, `PersonListPanel`, `StatusBarFooter` etc. All these, including the `MainWindow`, inherit from the abstract `UiPart` class which captures the commonalities between classes that represent parts of the visible GUI. +The UI consists of a `MainWindow` that is made up of parts e.g.`CommandBox`, `ResultDisplay`, `StudentListPanel`, `StatusBarFooter` etc. All these, including the `MainWindow`, inherit from the abstract `UiPart` class which captures the commonalities between classes that represent parts of the visible GUI. The `UI` component uses the JavaFx UI framework. The layout of these UI parts are defined in matching `.fxml` files that are in the `src/main/resources/view` folder. For example, the layout of the [`MainWindow`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/ui/MainWindow.java) is specified in [`MainWindow.fxml`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/resources/view/MainWindow.fxml) @@ -82,7 +85,7 @@ The `UI` component, * executes user commands using the `Logic` component. * listens for changes to `Model` data so that the UI can be updated with the modified data. * keeps a reference to the `Logic` component, because the `UI` relies on the `Logic` to execute commands. -* depends on some classes in the `Model` component, as it displays `Person` object residing in the `Model`. +* depends on some classes in the `Model` component, as it displays `Student` object residing in the `Model`. ### Logic component @@ -90,12 +93,14 @@ The `UI` component, Here's a (partial) class diagram of the `Logic` component: - +

+ +

How the `Logic` component works: 1. When `Logic` is called upon to execute a command, it uses the `AddressBookParser` class to parse the user command. 1. This results in a `Command` object (more precisely, an object of one of its subclasses e.g., `AddCommand`) which is executed by the `LogicManager`. -1. The command can communicate with the `Model` when it is executed (e.g. to add a person). +1. The command can communicate with the `Model` when it is executed (e.g. to add a student). 1. The result of the command execution is encapsulated as a `CommandResult` object which is returned back from `Logic`. The Sequence Diagram below illustrates the interactions within the `Logic` component for the `execute("delete 1")` API call. @@ -107,38 +112,39 @@ The Sequence Diagram below illustrates the interactions within the `Logic` compo Here are the other classes in `Logic` (omitted from the class diagram above) that are used for parsing a user command: - +

+ +

How the parsing works: * When called upon to parse a user command, the `AddressBookParser` class creates an `XYZCommandParser` (`XYZ` is a placeholder for the specific command name e.g., `AddCommandParser`) which uses the other classes shown above to parse the user command and create a `XYZCommand` object (e.g., `AddCommand`) which the `AddressBookParser` returns back as a `Command` object. * All `XYZCommandParser` classes (e.g., `AddCommandParser`, `DeleteCommandParser`, ...) inherit from the `Parser` interface so that they can be treated similarly where possible e.g, during testing. ### Model component -**API** : [`Model.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/model/Model.java) - +**API** : [`Model.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/model/Model.java) +

+ +

The `Model` component, -* stores the address book data i.e., all `Person` objects (which are contained in a `UniquePersonList` object). -* stores the currently 'selected' `Person` objects (e.g., results of a search query) as a separate _filtered_ list which is exposed to outsiders as an unmodifiable `ObservableList` that can be 'observed' e.g. the UI can be bound to this list so that the UI automatically updates when the data in the list change. +* stores the address book data i.e., all `Student` objects (which are contained in a `UniqueStudentList` object). +* stores the currently 'selected' `Student` objects (e.g., results of a search query) as a separate _filtered_ list which is exposed to outsiders as an unmodifiable `ObservableList` that can be 'observed' e.g. the UI can be bound to this list so that the UI automatically updates when the data in the list change. * stores a `UserPref` object that represents the user’s preferences. This is exposed to the outside as a `ReadOnlyUserPref` objects. * does not depend on any of the other three components (as the `Model` represents data entities of the domain, they should make sense on their own without depending on other components) -
:information_source: **Note:** An alternative (arguably, a more OOP) model is given below. It has a `Tag` list in the `AddressBook`, which `Person` references. This allows `AddressBook` to only require one `Tag` object per unique tag, instead of each `Person` needing their own `Tag` objects.
- - - +
:information_source: **Note:** An alternative (arguably, a more OOP) model is given below. It has a `Tag` list in the `AddressBook`, which `Student` references. This allows `AddressBook` to only require one `Tag` object per unique tag, instead of each `Student` needing their own `Tag` objects. +
- -### Storage component +##### Storage component **API** : [`Storage.java`](https://github.com/se-edu/addressbook-level3/tree/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. * inherits from both `AddressBookStorage` and `UserPrefStorage`, which means it can be treated as either one (if only the functionality of only one is needed). @@ -151,12 +157,399 @@ Classes used by multiple components are in the `seedu.addressbook.commons` packa -------------------------------------------------------------------------------------------------------------------- ## **Implementation** - This section describes some noteworthy details on how certain features are implemented. -### \[Proposed\] Undo/redo feature -#### Proposed Implementation +### [Developed] Time conflict management + +The time conflict management mechanism is facilitated by `TimeSlot`. It is encapsulated in `Tuition` package which +defines time slot format, checks time slot format and manage time conflict. It implements the following operations: +* `Timeslot#checkClassConflict()` - Compares two time slots to detect time conflict +* `Timeslot#parseString()` - Parses the String to check the format of the String and creates a new `Timeslot` if the format is correct + +Given below is an example usage scenario and how the time conflict management mechanism behaves at each step. + +Step1: The user enters `addclass` command. The `Timeslot` will check the correctness of the format of the +time slot entered by user through `Timeslot#parseString()`. If it is incorrect, a `ParseException` will be thrown, and the +exception will be handled by throwing `IllegalValueException`. Then user will receive a guidance to correct the time slot format. + +Step2: After confirmed the time slot format is correct, `TuitionClass` will check whether there is a conflict in +time slot exists. If there is a conflict, a `CommandException` will be thrown. + +Step3: If time slot follows the format and no conflict exist, a new `TuitionClass` will be created + +The interactions between the components during the usage scenario is show in the *Sequence Diagram* below.
+![Ui](images/TimeConflictManagementSequence.png) + + +### [Developed] Display of Timetable + +The construction of the read-only timetable is facilitated by the `Timetable` class in the `Tuition` package. +The `Timetable` receives an updated `tuitionClassList` from `UniqueTuitionList`, and then processes the `TimeSlot` occupied by each `TuitionClass` to construct a timetable. +It generates a timetable with the help of the `TimetableInfoPage` class using the following operations: + +* `Timetable#parseTime()` - Extracts `TimeSlot` occupied by each `TuitionClass`. Determines the size of the timetable using the earliest starting time and latest ending time of the tuition class list. +* `TimetableInfoPage#setTableTime(start, end)` - Constructs a timetable given the starting and ending time. +* `Timetable#insertSlot()` - Inserts all tuition classes into the timetable. +* `TimetableInfoPage#addLesson(lesson, columnStartInsert, rowStartInsert, columnSpan, rowSpan)` - Inserts a lesson to the corresponding rows and columns of the timetable. + +
:information_source:**Note:** +The above operations in the `Timetable` class is responsible for processing data of the `tuitionClassList` and determining the details of timetable. +And operations in the `TimetableInfoPage` class interact with `UI` to construct the timetable according to details provided by the `Timetable` class. +
+ +Given below is an example usage scenario of how the timetable is generated. + +Step1: The user executes `timetable` command to view a read-only timetable. + +Step2: The `TimetableCommand` class checks whether the `mostRecentTuitionClasses` maintained in `UniqueTuitionList` is empty. + +Step2.1: If there is not any tuition class, a `CommandResult` will be returned to alert the user that no class has been found. + +Step2.2: Otherwise, the `mostRecentTuitionClasses` in `UniqueTuitionList` is passed to the `Timetable` class as a `tuitionClassList`. + +Step3: `Timetable` will proceed to parse the `TimeSlot` in all tuition classes using `Timetable#parseTime()`. +After comparing the time when each `TuitionClass` takes place, the time range and thus the size of the timetable to be produced can be decided. +The intended size of timetable is then passed to the `TimetableInfoPage` class by calling `TimetableInfoPage#setTableTime(start, end)`. + +Step4: After the construction of the timetable, each `TuitionClass` is inserted into the timetable by calling `TimetableInfoPage#addLesson`. + +The following *Sequence Diagram* illustrates how `Timetable` interacts with `TimetableInfoPage` as explained by `step 3` and `step 4`: + +

+ +

+ +Step5: The complete timetable is displayed to user through the `UI` component. + +The following *Activity Diagram* summarizes what happens when a user executes a "timetable" command:
+ +

+ +

+ +#### Design considerations + +**Aspect: Size of timetable:** + +* **Alternative 1 (current choice):** Change the size of timetable according to lessons added. + * Pros: Easier for users to view lessons scheduled. + * Cons: May produce a timetable at a slightly slower speed and is more prone to bugs. + +* **Alternative 2:** Fix the size of timetable. + * Pros: Easy to implement. + * Cons: User experience would be compromised as the timetable will include many empty slots. + +### [Developed] Adding Remarks With Editor +Users can add, edit, or remove remarks for students or tuition classes, which is facilitated by `RemarkEditor`. The `UIManager` displays a dialog box with a text area for users to type in the description of the remark. Additionally, the `RemarkEditor` supports the following operations: + +* `RemarkEditor#setRemark(name, remark)` — Sets the name and remark of the identified student or tuition class. +* `RemarkEditor#getRemark()`— Retrieves the description input by the user as a `Remark`. + +The interactions between the components during the execution of the `RemarkCommand` is show in the *Sequence Diagram* below. The interactions and execution paths for `RemarkClassCommand` are the same.
+![Ui](images/RemarkCommandSequenceDiagram.png) + +Given below is an example usage scenario of how a user can interact with the `RemarkEditor` dialog. + +Step 1. The user executes `remark 1` to edit the remark of the 1st student. A dialog box pops up for the user to edit the remark description. + +Step 2.1. The user clicks `Ok` and the remark for the student is automatically updated to the new description. + +Step 2.2 The user may choose to click `Cancel` or exit the dialog box and the remark will remain unchanged. + +### [Developed] CLI input history navigation +Users are able to navigate through their previously entered inputs from the Command Box in the UI, +using the `↑`up and `↓`down arrow keys. +This is facilitated by the `InputHistory` class. + +#### Current Implementation + +The `InputHistory` class consists of an `ArrayList` and an `int` which serves as a pointer/position marker for navigation through the list. + +The `CommandBox` object in UI has an `InputHistory` object when initialized. + +When Users enter a non-empty String into the Command Box and press the `Enter` key, the text entered will be added to its `InputHistory`. +When interacting with the Command Box, Users can press the `↑`up or `↓`down keys on their keyboard. +This event is detected and handled by `CommandBox` and previous inputs can be navigated. This is facilitated by the following methods: + +* `CommandBox#handleButtonPressed(KeyEvent)` - Handles events of Users pressing arrow keys. +* `InputHistory#add(String)` - Adds a given String to a list if the String is not equal to the last String added to the list. + Resets the navigation position to the end of the list. +* `InputHistory#getPreviousInput()` - Returns the String stored just before the current pointer position in the list. + * If list is empty, returns an empty String. + * If the pointer is already at the start of the list, returns first String in the list. +* `InputHistory#getNextInput()` - Returns the String stored just after the current pointer position in the list. + * If the pointer is already at the end of the list, returns an empty String. + +The activity diagram below shows the work flow of how input history is navigated. + +![activity diagram](images/NavigateInputHistoryActivityDiagram.png) + +Given below is an example usage scenario of how CLI history may be used. + +#### Steps + +Step 1: When the User launches TutAssistor, `CommandBox` creates a new `InputHistory` with an empty list. + +Step 2: The User types `addclass n/Chemistry l/2 ts/Mon 12:00-13:00` into the Command Box and presses the `Enter` key. + +Step 3: As the input is not an empty String, `CommandBox` passes the given User input to `InputHistory` as a String via the `InputHistory#add(String)` method. + +Step 4: Since `InputHistory`'s `ArrayList` is empty, the input is not equal to the last added String. +`InputHistory#add(String)` adds the input to the `ArrayList` and sets the pointer to the end of the list. + +Step 5: The User presses the `↑`up key on their keyboard. + +Step 6: The key press event is detected by `CommandBox#handleButtonPressed(KeyEvent)`. `InputHistory#getPreviousInput()` is called. +As the input history list is not empty, and the current pointer position is not at the start of the list, `"addclass n/Chemistry l/2 ts/Mon 12:00-13:00"` is returned. + +Step 7: The text in the Command Box UI is set to `addclass n/Chemistry l/2 ts/Mon 12:00-13:00`. + +#### Acknowledgement +This feature was inspired by a similar feature in [YaleChen299's ip](https://github.com/yalechen299/ip) for CS2103T, +though its implementation in this project is new. + +### [Developed] Editing a Student +Users can edit a student by editing the following fields: `Name`, `Phone`, `Email` and `Address`. +This is implemented using the `EditCommand`, `EditStudentDescriptor` and `EditCommandParser` classes. + +#### Current Implementation +The `EditCommand` receives an index that indicates the student to be edited and an +editable `EditStudentDescriptor` class which consists of the updated fields of the student. +The student is then updated with the help of the following methods: + +* `Student#sameStudent(Student)` - Checks if the name of the student has been changed. +* `ModelManager#hasStudent(Student)` - Checks if the updated name already exists in the database. +* `Student#equals(Student)` - Checks if any of the four fields have been changed. +* `TuitionClass#updateStudent(Student)` - Updates the student's name in the class. +* `ModelManager#setStudent(Student)` - Updates the student's details in database. + +
+Given below is an example usage scenario of how an `edit` command is executed. + +#### Steps +Step 1: The user enters `edit 1 n/Tom p/98989898` command. + +Step 2: The `EditCommandParser` parses the student index to ensure that it is valid. + +Step 3: An `EditStudentDescriptor` object is constructed. +The `EditCommandParser` parses the arguments for the `n/` and `p/` prefixes to ensure that the arguments are valid. +If the arguments are valid, the `EditStudentDescriptor` object is then updated with the relevant values - namely +the edited values for `Phone` and `Name` as well as the existing values for the `Email` and `Address` fields. + +Step 4: An `EditCommand` object is then constructed with the student index and the `EditStudentDescriptor` object. + +Step 5: The `EditCommand` object checks if any of the fields have been updated using `Student#equals(Student)`. If none of the four fields are updated a `CommandException` is thrown to alert the user that the details are up-to-date. + +Step 6: Otherwise, it proceeds to check if the student name has been changed using `Student#sameStudent(Student)`. If the name has been changed, it ensures that the name does not exist in the database using `ModelManager#hasStudent(Student)`. + +Step 7: If the updated name is valid, the name of the student is updated in all the tuition classes he/she is enrolled in using `TuitionClass#updateStudent(Student)`. + +Step 8: Finally, the student is updated in the database using `ModelManager#setStudent(Student)`. + +### [Developed] Editing Tuition Classes +Users can edit tuition classes by editing the following fields: limit, name and timeslot of a class. +This is implemented using the `EditClassCommand`, `EditClassDescriptor` and `EditClassCommandParser` classes. + +#### Current Implementation +Similar to `EditCommand`, the `EditClassCommand` receives an index that indicates the class to be edited and an editable `EditClassDescriptor` class which contains the updated fields of the class. + +The tuition class is then updated with the help of the following methods: + +* `TuitionClass#sameClassDetails()` - Checks if any field of the tuition class has been updated. +* `Timeslot#checkTimetableConflicts` - Checks if the updated time slot has been taken or overlaps with another class's timeslot. +* `ModelManager#setTuition(TuitionClass, TuitionClass)` - Updates the tuition class. + +Given below is an example usage scenario of how an `editclass` command is executed. +#### Steps +Step 1: The user enters `editclass 1 l/5 ts/Mon 10:00-11:00` command. + +Step 2: The `EditClassCommand` class will first check if any field of the tuition class has been updated using `TuitionClass#sameClassDetails()`. +If there are no changes, a `CommandException` will be thrown to alert the user that the class details are up-to-date. +Otherwise, it proceeds to verify that the updated `limit` is at least equal to the current number of students. + +Step 3: Upon ensuring that the limit is valid, it checks for potential conflicts by comparing the updated `timeslot` +against those of other classes using the `Timeslot#checkTimetableConflicts` method. +If there are conflicts, a `CommandException` will be thrown to the user to alert the class that the slot has been taken. + +Step 4: If there are no conflicts, the class tag of the students enrolled in the class which shows the `ClassName` and`Timeslot` of the updated class. + +Step 5: Finally, it replaces the existing class with the updated class in the database using `ModelManager#setTuition(TuitionClass, TuitionClass)`. + +#### Activity Diagram + +The user flow is shown in the *Activity Diagram* below.
+

+ +

+ +### [Developed] Deleting Students +This feature allows students to be deleted using the `deletestudent` or `del` command. +This is facilitated by the `DeleteStudentCommand` and `DeleteStudentCommandParser` classes. + +#### Current Implementation +The `DeleteStudentCommandParser` parses the input from user to ensure that the student indices are valid. Then, +students are removed from the database with the help of the following methods: + +* `ModelManager#getStudent(Student)` - Retrieves the `Student` indicated by its index from `UniqueStudentList`. +* `TuitionClass#removeStudent(Student)` - Removes the student's name from the `StudentList` of the class. +* `ModelManager#getStudent(Student)` - Deletes the `Student` indicated by its index from `UniqueStudentList`. + +Given below is an example usage of how a `DeleteStudentCommand` is executed. + +#### Steps +Step 1: The user enters `deletestudent 1 2` command. + +Step 2: The `DeleteStudentCommandParser` will parse the student indices to ensure that they are valid. +Additionally, the parser removes any duplicates among the student indices and sorts the indices in descending order. + +
:information_source: **Note:** +Removing duplicate indices ensures that only one student is deleted at a particular index. +Sorting ensures that the students at the indices are deleted in the correct order - which is important as the student list uses an internal +`ObservableList` that tracks the changes to the list while students are being deleted. +
+ +A `DeleteStudentCommand` object with the student indices as arguments is constructed. + +Step 3: The `DeleteStudentCommand` is executed. Student indices - 1 and 2, are used to retrieve the students by calling the `ModelManager#getStudent(Index)` method. +If the student exists, then the student is deleted by calling `ModelManager#getStudent(Student)`. + +Step 4:`CommandResult` is returned informing the user of the students that have been deleted successfully. + +### [Developed] Deleting Tuition Classes +This feature allows tuition classes to be deleted using the `deleteclass` or `delc` command. +This is facilitated by the `DeleteClassCommand` and `DeleteClassCommandParser` classes. + +#### Current Implementation +The `DeleteClassCommandParser` parses the input from user to ensure that the class indices are valid. Then, +classes are removed from the database with the help of the following methods: + +* `ModelManager#getTuitionClass(Index)` - Retrieves the `TuitionClass` indicated by its index from `UniqueTuitionList`. +* `Student#removeClass(TuitionClass)` - Removes the class identified by an internal id and the class tag from the enrolled students. +* `ModelManager#deleteTuition(TuitionClass)` - Removes the tuition class from `UniqueTuitionList`. + +Given below is an example usage of how a `DeleteClassCommand` is executed. + +#### Steps +Step 1: The user enters `deleteclass 1` command. + +Step 2: The `DeleteClassCommandParser` will parse the class index to ensure that it is valid. A `DeleteClassCommand` object with the class index as arguments is constructed. + +Step 3: The `DeleteClassCommand` is executed. Class index - 1, is used to retrieve the class by calling the `ModelManager#getTuitionClass(Index)` method. + +Step 4: If the tuition class exists, `Student#removeClass(TuitionClass)` is called to remove the Tuition class using its id from the all the students. +It also removes the class tag for all the enrolled students by using the `ClassName` and unique `TimeSlot` of the tuition class. + +Step 5: `ModelManager#deleteTuition(TuitionClass)` to delete the tuition class and update the list of tuition classes accordingly. + +Step 6: `CommandResult` is returned informing the user of the classes that have been deleted successfully. + +#### Activity Diagram + +The user flow is shown in the *Activity Diagram* below.
+

+ +

+ +### [Developed] Removing Students from Tuition Classes +This feature allows students enrolled in existing classes to be removed using student indices and a class index using the `remove` command. +This is facilitated by the `RemoveStudentCommand` and `RemoveStudentCommandParser` classes. + +#### Current Implementation +The `RemoveStudentCommandParser` parses the input from user to ensure that the indices are valid. Then, +students are removed from the respective tuition class with the help of the following methods: + +* `TuitionClass#containsStudent(Student)` - Checks if the student is enrolled in the class. +* `TuitionClass#removeStudent(Student)` - Removes an existing student from a class. +* `RemoveStudentCommand#updateInvalidStudents(Student)` - Updates the list of invalid students who do not exist in the class. + +Given below is an example usage of how a `RemoveStudentCommand` is executed. + +#### Sequence Diagram + +The following sequence diagram shows the interactions between the components when the `RemoveStudentCommand` is executed.
+![Ui](images/RmStudentSequenceDiagram.png) + +#### Steps +Step 1: The user enters `remove si/1 4 tc/1` command. + +Step 2: The `RemoveStudentCommandParser` will parse the student indices and class index to ensure that they are valid. +Additionally, it removes any duplicates among the student indices. A `RemoveStudentCommand` object with the student indices and class index as arguments is constructed. + +Step 3: The `RemoveStudentCommand` is executed. Student indices - 1 and 2, are used to retrieve the unique student names using the `UniqueStudentList`. + +Step 4: `TuitionClass#containsStudent(Student)` is called to confirm that the student exists in the class. + +Step 5: If the student exists, the student is removed from the class using TuitionClass#removeStudent(Student). The tuition class is also removed the particular student using +`Student#removeClass(TuitionClass)`. Otherwise, the names of the invalid students are tracked using `RemoveStudentCommand#updateInvalidStudents(Student)` + +Step 6: `CommandResult` is returned informing the user of both the students that have been removed successfully and the students who could not be removed as they do not exist in the tuition class. + + +#### Design considerations + +**Aspect: Whether to allow users to remove single or multiple students at once:** + +* **Alternative 1 (current choice):** Allows multiple students to be removed. + * Pros: More efficient for users to remove students from tuition classes. + * Cons: More bug-prone due to parsing errors or duplicate indices. This is later resolved by providing more specific instructions in user guide and detailed command feedback in TutAssistor. + +* **Alternative 2:** Allows only one student to be removed. + * Pros: Easy to implement and reduced possibility of parsing errors. + * Cons: Users who wish to mass-update class enrollment will find it inefficient. + +### [Developed] Adding Students to Existing Tuition Classes + +Users can add students to existing tuition classes using student index or name. +This is facilitated by the `AddToClassCommand` and `AddToClassCommandParser` classes. + +The `AddToClassCommandParser` parses the input from user and decides whether student indices or student names are used. +Students are then added to the respective tuition class with the help of the following operations in `AddToClassCommand`: + +* `AddToClassCommand#categorizeStudents()` - Categorizes students into four types, namely students that are added successfully, students with invalid names, students with valid names but not added due to class size limit, and students already enrolled in the class. +* `AddToClassCommand#updateModel()` - Updates the capacity of the corresponding tuition class and updates the class tag of students enrolled. + +Given below is an example usage scenario and how an `addtoclass` command is executed. + +The interactions between the components during the usage scenario is shown in the *Sequence Diagram* below.
+ +

+ +

+ +Step 1: The user enters `atc si/2 4 tc/1` command to add the second and fourth students to the first tuition class. + +Step 2: The `AddToClassCommandParser` will check and confirm that student indices are used. An `AddToClassCommand` object with student indices as parameter is constructed. + +Step 3: The `AddToClassCommand` is executed. Student indices, namely 2 and 4, are converted to student names using the `UniqueStudentList`. + +
:information_source: **Note:** +Student indices that are not found in the `UniqueStudentList` would be regarded as invalid indices. +Valid students who are not added due to tuition class size limit or who have been enrolled in the same class previously are identified using the `AddToClassCommand#categorizeStudents()` method. +
+ +Step 4: Newly enrolled students are added to the tuition class. `AddToClassCommand#updateModel()` is called to change the capacity of the class. It also updates the class tag of the students enrolled to show the `ClassName` and +`Timeslot` of the class. + +The following *Activity Diagram* summarizes what happens when a user executes an `addtoclass` command: + +

+ +

+ +#### Design considerations + +**Aspect: Whether to allow users to add students using names:** + +* **Alternative 1 (current choice):** Allows both student indices and student names. + * Pros: More flexible for users to add students to tuition classes. + * Cons: More bug-prone and more difficult for users to learn the commands. This is later resolved by providing more specific instructions in user guide and detailed command feedback in TutAssistor. + +* **Alternative 2:** Allows only student indices. + * Pros: Easy to implement and avoid confusions to users as command is easy to learn. + * Cons: Users with a lot of students will need to scroll down to find student indices. + +### \[Proposed\] Undo/redo feature 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: @@ -169,24 +562,30 @@ These operations are exposed in the `Model` interface as `Model#commitAddressBoo 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 2. The user executes `delete 5` command to delete the 5th student 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. -![UndoRedoState0](images/UndoRedoState0.png) +

+ +

-Step 2. The user executes `delete 5` command to delete the 5th person in the address book. The `delete` command calls `Model#commitAddressBook()`, causing the modified state of the address book after the `delete 5` command executes to be saved in the `addressBookStateList`, and the `currentStatePointer` is shifted to the newly inserted address book state. +Step 3. The user executes `add n/David …​` to add a new student. The `add` command also calls `Model#commitAddressBook()`, causing another modified address book state to be saved into the `addressBookStateList`. -![UndoRedoState1](images/UndoRedoState1.png) - -Step 3. The user executes `add n/David …​` to add a new person. The `add` command also calls `Model#commitAddressBook()`, causing another modified address book state to be saved into the `addressBookStateList`. - -![UndoRedoState2](images/UndoRedoState2.png) +

+ +

:information_source: **Note:** If a command fails its execution, it will not call `Model#commitAddressBook()`, so the address book state will not be saved into the `addressBookStateList`.
-Step 4. The user now decides that adding the person was a mistake, and decides to undo that action by executing the `undo` command. The `undo` command will call `Model#undoAddressBook()`, which will shift the `currentStatePointer` once to the left, pointing it to the previous address book state, and restores the address book to that state. +Step 4. The user now decides that adding the student was a mistake, and decides to undo that action by executing the `undo` command. The `undo` command will call `Model#undoAddressBook()`, which will shift the `currentStatePointer` once to the left, pointing it to the previous address book state, and restores the address book to that state. -![UndoRedoState3](images/UndoRedoState3.png) +

+ +

:information_source: **Note:** If the `currentStatePointer` is at index 0, pointing to the initial AddressBook state, then there are no previous AddressBook states to restore. The `undo` command uses `Model#canUndoAddressBook()` to check if this is the case. If so, it will return an error to the user rather than attempting to perform the undo. @@ -195,7 +594,9 @@ than attempting to perform the undo. The following sequence diagram shows how the undo operation works: -![UndoSequenceDiagram](images/UndoSequenceDiagram.png) +

+ +

:information_source: **Note:** The lifeline for `UndoCommand` should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram. @@ -209,36 +610,37 @@ The `redo` command does the opposite — it calls `Model#redoAddressBook()`, Step 5. The user then decides to execute the command `list`. Commands that do not modify the address book, such as `list`, will usually not call `Model#commitAddressBook()`, `Model#undoAddressBook()` or `Model#redoAddressBook()`. Thus, the `addressBookStateList` remains unchanged. -![UndoRedoState4](images/UndoRedoState4.png) +

+ +

Step 6. The user executes `clear`, which calls `Model#commitAddressBook()`. Since the `currentStatePointer` is not pointing at the end of the `addressBookStateList`, all address book states after the `currentStatePointer` will be purged. Reason: It no longer makes sense to redo the `add n/David …​` command. This is the behavior that most modern desktop applications follow. -![UndoRedoState5](images/UndoRedoState5.png) +

+ +

The following activity diagram summarizes what happens when a user executes a new command: +

+

-#### Design considerations: +#### Design considerations **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. + * 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 student being deleted). + * Cons: We must ensure that the implementation of each individual command are correct. _{more aspects and alternatives to be added}_ -### \[Proposed\] Data archiving - -_{Explain here how the data archiving feature will be implemented}_ - - -------------------------------------------------------------------------------------------------------------------- ## **Documentation, logging, testing, configuration, dev-ops** @@ -257,66 +659,204 @@ _{Explain here how the data archiving feature will be implemented}_ **Target user profile**: -* has a need to manage a significant number of contacts +* has a need to manage a significant number of students and classes * 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**: manage students and classes faster than a typical mouse/GUI driven app, and facilitate deconflicting of time slots ### User stories Priorities: High (must have) - `* * *`, Medium (nice to have) - `* *`, Low (unlikely to have) - `*` -| Priority | As a …​ | I want to …​ | So that I can…​ | +| 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 | +| `* * *` | Tutor | save the data I put | refer back to the same data at a later time | +| `* * *` | Tutor | create a new student | keep track of relevant details about that student | +| `* * *` | Tutor | exit the program | log off from the app | +| `* * *` | Tutor | set maximum number of students per class | avoid overbooking a class | +| `* * *` | Tutor | add time slots for classes | schedule extra classes for weaker students | +| `* * *` | Tutor | delete a student | | +| `* * *` | Tutor with many students | view the classes scheduled in a timeslot | avoid double-booking a timeslot | +| `* * *` | Tutor | add new information about an existing student | | +| `* * *` | Tutor | delete a class | | +| `* * *` | Tutor | add a student to a class | | +| `* *` | Tutor | filter students based on class | see which students will be attending a class | +| `* *` | Tutor | create notes for each class | track the homework assigned to students | +| `* *` | Tutor | view all classes that are scheduled on a day | prepare the relevant materials early | +| `* *` | Tutor | create tags for each student | track which students have not paid tuition fees | +| `* *` | New user | clear all data | start using the program after testing it out | +| `* *` | New user | see a list of commands | learn what I can do with the software | +| `* *` | Tutor | search for students by name | follow-up with students who are absent | +| `* *` | Tutor | bind and edit class fee to a student | remember students’ different fees | +| `* *` | Tutor | edit a student | keep my information about students up to date | +| `* *` | Unfamiliar/returning user | check the syntax for specific commands | be reminded of how to use commands | +| `*` | Forgetful tutor | set a reminder for when I open the app | not forget to prepare for the lessons | +| `*` | Organised tutor | customise the view of the class schedules | see the relevant classes | +| `*` | Tutor | export weekly schedule | refer to the notes even on my phone | -*{More to be added}* ### Use cases -(For all use cases below, the **System** is the `AddressBook` and the **Actor** is the `user`, unless specified otherwise) +(For all use cases below, the **System** is `TutAssistor` and the **Actor** is the `tutor`, unless specified otherwise) + +**Use case: UC01 - Add Student/Tuition class** + +**MSS** + +1. Tutor chooses to add a new student/tuition class and keys in information about his student/tuition class. +2. TutAssistor adds the student/tuition class and displays successful information. + +Use case ends. + +**Extensions** + +* 1a. TutAssistor detects the wrong format in the user input. + * 1a1. TutAssistor reminds the tutor the right format. + * 1a2. Tutor enters a new command. + + Steps 1a1-1a2 are repeated until the Tutor keys in information in the correct format by TutAssistor. -**Use case: Delete a person** + Use case resumes from step 2. + +**Use case: UC02 - Delete Student/Tuition Class** **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. Tutor chooses to delete an existing Student/Tuition Class. +2. TutAssistor deletes the Student/Tuition Class and displays successful information. + +Use case ends. + +**Extensions** + +* 1a. TutAssistor detects the wrong format in the user input. + * 1a1. TutAssistor reminds the tutor the right format. + * 1a2. Tutor enters a new command. + + Steps 1a1-1a2 are repeated until the Tutor keys in information in the correct format by TutAssistor. + + Use case resumes from step 2. + +* 1b. TutAssistor does not detect existing Student/Tuition Class with names indicated by the user. + * 1b1. TutAssistor alerts that the Student/Class does not exist. + * 1b2. Tutor keys in new command. + + Steps 1b1-1b2 are repeated until the Tutor keys in the existing student/class. + + Use case resumes from step 2. + +**Use Case: UC03 - Add student to a tuition class** + +**MSS** + +1. Tutor decides to add a particular student to a class. +2. Tutor keys in command to add an existing student to class. +3. TutAssistor adds student to the class successfully + +Use case ends. + +**Extensions** + +* 3a. TutAssistor detects an error when the number of students exceeds the limit. + * 3a1. TutAssistor displays error. Use case ends. +**Use Case: UC04 - Edit Student/Tuition class** + +**MSS** + +1. Tutor chooses to edit Student/Tuition class information and keys in the information. +2. TutAssistor updates Student/Tuition class information and displays successful information. + +Use case ends. + +**Extensions** + +* 1a. TutAssistor detects the wrong format in the user input. + * 1a1. TutAssistor reminds the tutor the right format. + * 1a2. Tutor enters a new command. + + Steps 1a1-1a2 are repeated until the Tutor keys in information in the correct format by TutAssistor. + + Use case resumes from step 2. + +**Use Case: UC05 - Search Student/Tuition class** + +**MSS** + +1. Tutor chooses to search Student/Tuition class information and keys in the information. +2. TutAssistor displays Student/Tuition class information and displays successful information. + +Use case ends. + +**Use Case: UC06 - View tuition classes** + +**MSS** + +1. Tutor keys in command to view the list of tuition classes for the week. +2. TutAssistor displays a list of tuition classes for the week. + +Use case ends. + **Extensions** -* 2a. The list is empty. +* 1a. Tutor keys in an unrecognised command. + * 1a1. TutAssistor displays an error message. + * 1a2. Tutor keys in a new command. + + Steps 1a1-1a2 are repeated until the Tutor keys in a command recognised by TutAssistor. + + Use case resumes from step 2. + + +**Use Case: UC07 - Edit student in a tuition class** + +**MSS** + +1. Tutor decides to edit a student’s details in a tuition class. +2. TutAssistor edits the student’s details and displays successful information. + +Use case ends. + +**Use case: UC08 - Remove student from tuition class** + +**MSS** + +1. Tutor chooses to remove a student from a tuition class and keys in the command. +2. TutAssistor removes the student from the tuition class and displays the successful information. + +Use case ends. - Use case ends. +**Use case: UC09 - View student** + +**MSS** -* 3a. The given index is invalid. +1. Tutor keys in command to view a particular student’s details. +2. TutAssistor displays the student’s details successfully. - * 3a1. AddressBook shows an error message. +Use case ends. + +**Use case: UC10 - Exit** + +**MSS** - Use case resumes at step 2. +1. Tutor chooses to exit the TutAssistor. +2. TutAssistor exits. -*{More to be added}* +Use case ends. ### Non-Functional Requirements 1. Should work on any _mainstream OS_ as long as it has Java `11` or above installed. -2. Should be able to hold up to 1000 persons without a noticeable sluggishness in performance for typical usage. +2. Should be able to hold up to 2000 tutees 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. +4. The response to any command should become visible within 5 seconds. -*{More to be added}* ### Glossary @@ -347,31 +887,101 @@ testers are expected to do more *exploratory* testing. 1. Resize the window to an optimum size. Move the window to a different location. Close the window. 1. Re-launch the app by double-clicking the jar file.
- Expected: The most recent window size and location is retained. + Expected: The most recent window size and location is retained. -1. _{ more test cases …​ }_ +1. Exiting the program -### Deleting a person + 1. Close the window or click on **File** > **Exit** in the top left corner.
+ Expected: The user logs off the programme. -1. Deleting a person while all persons are being shown +### Deleting a student - 1. Prerequisites: List all persons using the `list` command. Multiple persons in the list. +1. Deleting a student while all students are being shown + + 1. Prerequisites: List all students using the `list` command. Multiple students in the list. 1. Test case: `delete 1`
- Expected: First contact is deleted from the list. Details of the deleted contact shown in the status message. Timestamp in the status bar is updated. + Expected: First student is deleted from the list. Details of the deleted student shown in the status message. Timestamp in the status bar is updated. 1. Test case: `delete 0`
- Expected: No person is deleted. Error details shown in the status message. Status bar remains the same. + Expected: No student is deleted. Error details shown in the status message. Status bar remains the same. 1. Other incorrect delete commands to try: `delete`, `delete x`, `...` (where x is larger than the list size)
Expected: Similar to previous. -1. _{ more test cases …​ }_ +### Editing remarks + +1. Editing the remarks of a student + + 1. Prerequisites: List all students using the `list` command. At least one student in the list. + + 1. Test case: `remark 1`, input `Hello World!`, then click **Ok**.
+ Expected: The remarks of first student is changed to `Hello World!`. Details of the edited student shown in the status message. Timestamp in the status bar is updated. + + 1. Test case: `remark 1`, input `Hello World!`, then click **Cancel** or close the window.
+ Expected: The remarks of first student remains as the previous input`. Details of the edited student shown in the status message. Timestamp in the status bar is updated. + + 1. Test case: `remark 0`
+ Expected: Remark Editor window does not open. Error details shown in the status message. Status bar remains the same. + + 1. Other incorrect delete commands to try: `remark`, `remark x`, `...` (where x is larger than the list size)
+ Expected: Similar to previous. + +1. Removing remarks of a student -### Saving data + 1. Prerequisites: List all students using the `list` command. At least one student in the list. -1. Dealing with missing/corrupted data files + 1. Test case: `remark 1`, remove all input in the text area of the Remark Editor, then click **Ok**.
+ Expected: The remarks of first student is removed. Details of the edited student shown in the status message. Timestamp in the status bar is updated. - 1. _{explain how to simulate a missing/corrupted file, and the expected behavior}_ +### Viewing help + +1. Viewing help for TutAssistor + + 1. Enter help and press enter.
+ Expected: A help message is displayed in a separate help window. It also contains a **Open User Guide** button which opens up the TutAssistor user guide. + + 1. After opening the help window, click on the **Open User Guide** button.
+ Expected: The TutAssistor user guide opens in your browser. + +### Saving data -1. _{ more test cases …​ }_ +The data file that stores student and tuition class objects can be found in `data/addressbook.json`. + +1. Dealing with corrupted data files + + 1. Prerequisites: the data file contains at least one student object, and directly modify a student's email to `12345678`. + + 1. Launch TutAssistor.
+ Expected: The terminal shows the following error: + + ``` + INFO: Illegal values found in data\addressbook.json: Emails should be of the + format local-part@domain and adhere to the following constraints: + 1. The local-part should only contain alphanumeric characters and these + special characters, excluding the parentheses, (+_.-). The local-part may not + start or end with any special characters. + + 1. This is followed by a '@' and then a domain name. The domain name is made + up of domain labels separated by periods. + The domain name must: + - end with a domain label at least 2 characters long + - have each domain label start and end with alphanumeric characters + - have each domain label consist of alphanumeric characters, separated + only by hyphens, if any. + ``` + + Both student and tuition list panels should be empty. The corrupted data file will be replaced when new student or tuition class objects are added to TutAssistor. + + 1. Delete the corrupted file, then launch TutAssistor.
+ Expected: All previous data are replaced with the sample data. + +1. Dealing with missing data files + + 1. Delete the data file and launch TutAssistor.
+ Expected: TutAssistor is populated with sample data. + +1. Transferring data files across different platforms. + + 1. Copy the entire content from one data file to another.
+ Expected: the TutAssistor on both platforms will contain the same set of data. diff --git a/docs/UserGuide.md b/docs/UserGuide.md index 3716f3ca8a4..d8da0ea0300 100644 --- a/docs/UserGuide.md +++ b/docs/UserGuide.md @@ -3,43 +3,184 @@ 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. - -* Table of Contents -{:toc} +

+ +

+ +Welcome to the _TutAssistor User Guide_! Simply choose a topic below to find answers, learn about the features, and manage tutoring better. + +
+ +# Table of Contents +1. [Introduction](#1-introduction) + + 1.1 [What is TutAssistor?](#11-what-is-tutassistor) + + 1.2 [What can TutAssistor do for you?](#12-what-can-tutassistor-do-for-you) + + 1.3 [How to use this guide?](#13-how-to-use-this-guide) + +2. [Get started](#2-get-started) + + 2.1 [Installation guide](#21-installation-guide) + + 2.2 [Try it yourself!](#22-try-it-yourself) + +3.
Features
+ 3.1 Add student/class

+ 3.2 View student/class

+ 3.3 Edit student/class

+ 3.4 Delete student/class

+ 3.5 Add/Remove student from class

+ 3.6 Add remark

+ 3.7 Find students/classes

+ 3.8 List students/classes

+ 3.9 Sort classes

+ 3.10 View timetable

+ 3.11 View today's classes

+ 3.12 View help

+ 3.13 Navigate Input History

+ 3.14 Clear data

+ 3.15 Exit the program

+ 3.16 Track payment `coming in v2.0`

+ +4.
Additional Command Format Information
+ 4.1 Name

+ 4.2 Phone Number

+ 4.3 Email

+ 4.4 Timeslot

+ 4.5 Index

+ +5. [FAQ](#5-faq) + +6. [Command Summary](#6-command-summary) -------------------------------------------------------------------------------------------------------------------- +
+ +## 1 Introduction + +### 1.1 What is TutAssistor? +Welcome to the user guide of TutAssistor! + +Are you a private tutor struggling to keep track of all your classes and students? Do you spend +countless hours on administrative duties such as scheduling classes and updating numerous students' records? + +If the above situation sounds familiar to you, fret not, **TutAssistor** is here to save the day! + +**TutAssistor** is a ***desktop app intended for private tutors like yourself to +manage their students and classes, and it is optimized for use via a Command Line Interface (CLI)***. + +**TutAssistor** uses Command Line Interface (CLI); this means that you operate the application by typing commands into a Command Box. +If you are a fast typer, you can operate the application more easily and faster than +Graphical User Interface (GUI) applications; GUI applications allow users to interact +with the application via graphical icons such as buttons. + +You do not have to worry at all even if you are new to CLI applications as +this user guide will take you through step by step on how various +features of the **TutAssistor** can be utilised, all directed towards +providing the best possible experience to the user. -## Quick start +### 1.2 What can TutAssistor do for you? -1. Ensure you have Java `11` or above installed in your Computer. +TutAssistor provides an all-in-one platform for you to manage information +regarding your students and lessons stress-free and efficiently. In the latest version, we offer you the ability to: -1. Download the latest `addressbook.jar` from [here](https://github.com/se-edu/addressbook-level3/releases). +* Track student details +* Keep student details up-to-date +* Resolve conflicting tuition timeslots +* Efficiently create notes for each class/student +* View scheduled lessons in a timetable -1. Copy the file to the folder you want to use as the _home folder_ for your AddressBook. +### 1.3 How to use this guide? -1. Double-click the file to start the app. The GUI similar to the below should appear in a few seconds. Note how the app contains some sample data.
- ![Ui](images/Ui.png) +#### Step-by-step guide for each feature -1. Type the command in the command box and press Enter to execute it. e.g. typing **`help`** and pressing Enter will open the help window.
- Some example commands you can try: +The features in this guide are formatted with the following conventions: - * **`list`** : Lists all contacts. +* **Command Keyword** - Each feature is executed by a command keyword. The shortcut for each command is shown together with its full command keyword separated by the | symbol. You can use the command shortcuts to reduce typing. +* **Command Format** - Each command is accompanied by a set of information that you provide. Refer to the [Features](#3-features) below for details of each command. +* **Examples** - Possible usage of each features are provided. You may follow these examples when familiarising with the app. +* **Screenshots** - A visualisation of the expected outcome is provided for some features. - * **`add`**`n/John Doe p/98765432 e/johnd@example.com a/John street, block 123, #01-01` : Adds a contact named `John Doe` to the Address Book. - * **`delete`**`3` : Deletes the 3rd contact shown in the current list. +#### General symbols and syntax used in this guide - * **`clear`** : Deletes all contacts. +For additional information accompanying each segment, look for the following symbols: - * **`exit`** : Exits the app. +Syntax | What it means +-------|-------- +  :information_source: | Precedes information that are useful to remember. +  :bulb: | Precedes information that serve as tips for a feature. +  :warning: | Precedes information as an important warning. -1. Refer to the [Features](#features) below for details of each command. + +Jump right in to the next section: [2 Get Started](#2-get-started) to get you started! -------------------------------------------------------------------------------------------------------------------- +
+ +## 2 Get Started + +### 2.1 Installation guide + +#### For Windows +1. Ensure you have Java 11 or above installed in your Computer. You may install it [here](https://www.oracle.com/java/technologies/downloads/). +2. Download the latest TutAssistor release from [our github site](https://github.com/AY2122S1-CS2103T-T12-4/tp/releases). +3. Double click the downloaded TutAssistor.jar file to launch TutAssistor. + +#### For Mac +1. Ensure you have Java 11 or above installed in your Computer. You may install it [here](https://www.oracle.com/java/technologies/downloads/). +2. Download the latest TutAssistor release from [our github site](https://github.com/AY2122S1-CS2103T-T12-4/tp/releases). +3. Open Terminal. +4. Change the active directory to the location of TutAssistor.jar. + + For example, + + ``` + cd Downloads + ``` +4. Run the following command. + + ``` + java -jar TutAssistor.jar + ``` + +After launching the app, the GUI similar to the one shown below should appear in a few seconds. Note how the app contains some sample data. +![Ui](images/annotated_UI.png) +

Figure 1: GUI of TutAssistor

+ +Component | What it does +-------|-------- +Menu Bar | Exits TutAssistor or views help. +Command Box | Key in commands +Result Display Pane | Displays feedback of commands. +TuitionList Panel | Displays all tuition classes. +StudentList Panel | Displays all students. +Main Display Pane | Displays today's lessons, tuition class's or student's details, or a timetable. -## Features +### 2.2 Try it yourself! + +Once you have installed the latest TutAssistor, why not give it a try? + +Type the command in the command box and press Enter to execute it. e.g. typing help and pressing **Enter** will open the help window. + +Some example commands you can try: + +* `student 1`: Views the first student shown in the student list. +* `add n/John Doe p/98765432 e/johnd@example.com a/John street, block 123, #01-01`: Adds a student named `John Doe` to the TutAssistor. +* `addtoclass si/3 tc/1`: Adds the 3rd student in the student list to the 1st class in the tuition class list. +* `deleteclass 2`: Deletes the 2nd class shown in the tuition class list. +* `clear`: Deletes all students and classes. Note that this action is irreversible. +* `exit`: Exits the app. + +Now that you are more familiar with our app, make it yours! Head over to section [3 Features](#3-features) to learn more about the features TutAssistor can offer you! + +-------------------------------------------------------------------------------------------------------------------- +
+ +## 3 Features
**:information_source: Notes about the command format:**
@@ -48,10 +189,10 @@ AddressBook Level 3 (AB3) is a **desktop app for managing contacts, optimized fo e.g. in `add n/NAME`, `NAME` is a parameter which can be used as `add n/John Doe`. * Items in square brackets are optional.
- e.g `n/NAME [t/TAG]` can be used as `n/John Doe t/friend` or as `n/John Doe`. + e.g `n/NAME [r/REMARK]` can be used as `n/John Doe r/student` 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. +* Items with `…`​ after them can be used zero or more times.
+ e.g. `si/STUDENT_INDEX [STUDENT_INDEX]…​` can be used as ` ` (i.e. 0 times), `si/1`, `si/1 2 3` etc. * Parameters can be in any order.
e.g. if the command specifies `n/NAME p/PHONE_NUMBER`, `p/PHONE_NUMBER n/NAME` is also acceptable. @@ -61,132 +202,543 @@ AddressBook Level 3 (AB3) is a **desktop app for managing contacts, optimized fo * 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`. - + +Refer to the [Additional Command Format Information](#4-additional-command-format-information) under section 4 to learn the requirements of the various parameters used. +
-### Viewing help : `help` +### 3.1 Add student/tuition class +#### Adding a student: `add` | `a` +Adds a student with the specified information such as name, phone number, etc. +> [Read more on the requirements of the various parameters used.](#4-additional-command-format-information) -Shows a message explaning how to access the help page. +Format: `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [r/REMARK]` -![help message](images/helpMessage.png) +Examples: -Format: `help` +``` +add n/John Doe p/98765432 e/johnd@example.com a/John street, block 123, #01-01 +``` +``` +a n/Tom Ng p/97865342 e/tomng@eg.com a/221B Baker St r/Can only attend on even weeks +``` +
+:information_source: Note: `James Yeoh` and james   yeoh are considered the same person, namely `Name` is case-insensitive and the differences in whitespaces would not be considered. +
-### Adding a person: `add` +#### Adding a tuition class: `addclass` | `ac` +Adds a tuition class with a set limit of students at a specified [timeslot](#44-timeslot). TutAssistor will notify you if there are any conflicting timeslots. -Adds a person to the address book. +Format: `addclass n/NAME l/LIMIT ts/TIMESLOT [s/NAME,NAME,NAME...] [r/REMARK]` -Format: `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]…​` +* It is optional to add students into the class when creating a tuition class. +* The [timeslot](#44-timeslot) should follow the format "Ddd HH:mm-HH:mm". +* The limit of a class should not exceed 1000.
:bulb: **Tip:** -A person can have any number of tags (including 0) +There should not be a space after each comma when listing multiple students.
Examples: -* `add n/John Doe p/98765432 e/johnd@example.com a/John street, block 123, #01-01` -* `add n/Betsy Crowe t/friend e/betsycrowe@example.com a/Newgate Prison p/1234567 t/criminal` +``` +addclass n/Chemistry l/16 ts/Thu 15:00-17:00 s/Bernice Yu,Richard Ng +``` +``` +ac n/Math l/8 ts/Mon 11:00-14:00 r/Quiz on final lesson +``` + +### 3.2 View student/tuition class +Displays the details the specified student or class in the information page. + +#### 3.2.1 Viewing a student: `student` | `vs` +Format: `student INDEX` + +Example: +``` +student 2 +``` +or +``` +vs 2 +``` + +

+ view student
+ Figure 2: Displaying a student's details in the information page +

+ +#### 3.2.2 Viewing a tuition class: `class` | `vc` +Format: `class INDEX` + +Example: +``` +class 3 +``` +or +``` +vc 3 +``` +

+ view class
+ Figure 3: Displaying details of a class in the information page +

+ +### 3.3 Edit student/tuition class +Edits an existing student or tuition class. + +
+:information_source: Note: At least one parameter must be provided. +
-### Listing all persons : `list` +#### Editing a student: `edit` | `e` +Format: `edit INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS]` -Shows a list of all persons in the address book. +Examples: +``` +edit 1 n/Jason Tan a/221b Baker Street +``` +or +``` +e 1 n/Jason Tan a/221b Baker Street +``` + +
+:information_source: Note: The edited name must be unique and should not be the same name of another existing student. +
-Format: `list` -### Editing a person : `edit` +#### Editing a class: `editclass` | `ec` +Format: `editclass INDEX [n/NAME] [l/LIMIT] [ts/TIMESLOT]` -Edits an existing person in the address book. +Examples: +``` +editclass 1 n/Math ts/Wed 10:00-12:00 +``` +or + +``` +ec 1 n/Math ts/Wed 10:00-12:00 +``` +
+
+:information_source: Note: The edited timeslot cannot conflict with an en existing timeslot of another class. +
+
+An example output of using the `editclass` command is shown below: -Format: `edit INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [t/TAG]…​` +

+ Edit class
+ Figure 4: Example of executing `editclass 1 n/Math ts/Wed 10:00-12:00` command +

-* Edits the person at the specified `INDEX`. The index refers to the index number shown in the displayed person list. The index **must be a positive integer** 1, 2, 3, …​ -* At least one of the optional fields must be provided. -* Existing values will be updated to the input values. -* When editing tags, the existing tags of the person will be removed i.e adding of tags is not cumulative. -* You can remove all the person’s tags by typing `t/` without - specifying any tags after it. -Examples: -* `edit 1 p/91234567 e/johndoe@example.com` Edits the phone number and email address of the 1st person to be `91234567` and `johndoe@example.com` respectively. -* `edit 2 n/Betsy Crower t/` Edits the name of the 2nd person to be `Betsy Crower` and clears all existing tags. +### 3.4 Delete student/tuition class +Deletes a student or a tuition class from TutAssistor. + +* At least one student or class index must be provided. + +* Provide multiple indices to delete multiple students or classes at once. -### Locating persons by name: `find` +
+:warning: THIS COMMAND IS IRREVERSIBLE. IT WILL DELETE ALL DATA OF THE IDENTIFIED STUDENT OR CLASS. +
-Finds persons whose names contain any of the given keywords. +#### Deleting students: `delete` | `del` -Format: `find KEYWORD [MORE_KEYWORDS]` +Format: `delete STUDENT_INDEX [STUDENT_INDEX]...` -* 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` +Example: +``` +delete 1 2 +``` +or -Examples: -* `find John` returns `john` and `John Doe` -* `find alex david` returns `Alex Yeoh`, `David Li`
- ![result for 'find alex david'](images/findAlexDavidResult.png) +``` +del 1 2 +``` +An example output of using the `delete` command is shown below: + +

+ Delete student
+ Figure 5: Example of executing `delete 1 2` command +

+ +#### Deleting tuition classes: `deleteclass` | `delc` + +Format: `deleteclass CLASS_INDEX [CLASS_INDEX]...` + +Example: +``` +deleteclass 1 2 +``` +or + +``` +delc 1 2 +``` + +### 3.5 Add/Remove student from class + +Moves a student to/from a class. + +#### Adding existing students to a class: `addtoclass` | `atc` + +Adds one or more existing students to an existing class. + +
+ +:information_source: Note: When adding multiple students at once:
+ + * use commas(`,`) to separate names, with no spaces after each comma. + + * use spaces to separate indices. + +
+ +Format: + +`addtoclass si/STUDENT_INDEX [STUDENT_INDEX]... tc/CLASS_INDEX` + +or -### Deleting a person : `delete` +`addtoclass s/NAME[,NAME,NAME...] tc/CLASS_INDEX` -Deletes the specified person from the address book. +Examples: +``` +addtoclass si/1 tc/1 +``` +``` +atc si/1 2 3 4 tc/1 +``` +``` +addtoclass s/James,Felicia tc/2 +``` +``` +atc s/James tc/3 +``` + +#### Removing existing students from a class: `remove` | `rm` -Format: `delete INDEX` +Removes an existing student from a tuition class. -* Deletes the person at the specified `INDEX`. -* The index refers to the index number shown in the displayed person list. -* The index **must be a positive integer** 1, 2, 3, …​ +Format: `remove si/STUDENT_INDEX [STUDENT_INDEX]... tc/CLASS_INDEX` Examples: -* `list` followed by `delete 2` deletes the 2nd person in the address book. -* `find Betsy` followed by `delete 1` deletes the 1st person in the results of the `find` command. +``` +remove si/1 tc/1 +``` +``` +rm si/1 2 3 4 tc/2 +``` + +### 3.6 Add remark to student/tuition class +Upon entering the command, a pop-up window with a text box is displayed for you to edit remarks. + +
:bulb: **Tip:** +Users can add, remove, or edit remarks through the editor window. +
+ +

+ Remark editor
+ Figure 6: Editing remarks with editor window +

+ +
+:warning: For MacOS users, the remarks editor may open as a new tab instead of a window, which can cause a UI bug where the buttons are not displayed correctly. To open as a new window instead, go to System preferences > General > Prefer tabs and choose never. +
+ +#### Adding remark to a student: `remark` | `re` + +Adds a remark for a student. + +Format: `remark STUDENT_INDEX` -### Clearing all entries : `clear` +Example: +``` +remark 2 +``` +or +``` +re 2 +``` -Clears all entries from the address book. +#### Adding remark to a tuition class: `remarkclass` | `rec` + +Adds a remark for a tuition class. + +Format: `remarkclass CLASS_INDEX` + +Example: +``` +remarkclass 2 +``` +or +``` +rec 1 +``` + +
+:information_source: Note: When creating a new student or tuition class with the `add` command, you can use the optional `r/REMARK` parameter to add remarks directly. +
+ +### 3.7 Find student/tuition class +Filters the list based on the given keywords. The keywords are case-insensitive. +
+:information_source: Note: Keywords will be matched entirely. +Example: A command `findclass phys` will not filter physics classes. +
+ +#### Finding students by name: `find` | `f` +Displays a list of students whose name matches the given keywords. + +Format: `find KEYWORD [KEYWORD]...` + +Example: +``` +find alice tan +``` +or +``` +f alice tan +``` +TutAssistor will display a list of all students with `alice` or `tan` in their name. + +#### Finding classes by name: `findclass` | `fc` + +Displays a list of classes whose name matches the given keywords. + +Format: `findclass KEYWORD [KEYWORD]...` + +Example: +``` +findclass physics chemistry +``` +or +``` +fc physics chemistry +``` +TutAssistor will display a list of all classes with `physics` or `chemistry` in their name. + +The filtered list should look similar to the example shown below: + +

+ Find student
+ Figure 7: Example of executing `find alice tan` command +

+ +### 3.8 List all students/tuition classes +Shows the full list of students or classes. + +#### Listing all students: `list` | `l` + +Displays a list of all students. + +Format: `list` + +#### Listing all classes: `listclass` | `lc` + +Displays list of all classes. + +Format: `listclass` + +### 3.9 Sort tuition classes: `sort` | `s` + +Sorts tuition class list according to time or alphabetical order. +* After the user executes the `sort` command, the list will continue to remain sorted even after adding or editing classes. +* The list will not be automatically sorted upon restarting TutAssistor, i.e., the user will have to execute the `sort` command again. + +The possible usages of `sort` are given below: +* `sort` sorts by time +* `sort o/asc` sorts by ascending alphabetical order +* `sort o/desc` sorts by descending alphabetical order +* `sort o/time` sorts by time + +
+ +:information_source: Note:
+ +* For alphabetical order, the sorting is case-insensitive. For example, `chem` is considered the same as `Chem`. + +* For time order, the sorting assumes Gregorian calendar format. i.e. Sunday is considered the start of a week and + Saturday is considered the end of a week. + +
+ +An example output is shown below: + +

+ Sort by ascending order
+ Figure 7: Example of executing `sort o/asc` command +

+ +### 3.10 View timetable: `timetable` | `tt` +Shows classes scheduled in this week in a timetable. + +Format: +``` +timetable +``` +
+ +:information_source: Note:
+ + If a lesson is shorter than one hour, the timetable may not display its details properly, such as using smaller font size or omitting the details completely due to limited space in the time block. + + For example, in Figure 8 below, the class on Monday, 15:00-15:15 is only 15 minutes and thus not displayed with details. +
+ +

+ Timetable
+ Figure 8: A timetable view of all tuition classes in a week +

+ +### 3.11 View today's classes: `today` | `td` +Displays an overview of all classes happening today.
+Also, a reminder of classes happening today will be displayed +when the TutAssistor is reopened. + +

+ Today view
+ Figure 9: The view of all classes scheduled today in the Main Display Pane +

+ +### 3.12 View help: `help` | `h` + +Shows a command summary, as well as a link to the user guide.
+ +Format: `help` + +

+ Help window
+ Figure 10: The help window +

+ +### 3.13 Navigate input history + +When typing in the command box, use the **up** and **down** arrow keys to access and navigate through previously entered inputs. + +### 3.14 Clear data: `clear` +Clears all current student and tuition class data. Format: `clear` -### Exiting the program : `exit` +
+:warning: THIS COMMAND IS IRREVERSIBLE. IT WILL DELETE ALL EXISTING STUDENT AND CLASS DATA. +
+ +### 3.15 Exit the app: `exit` Exits the program. Format: `exit` -### Saving the data +### 3.16 Track payment `coming in v2.0` +_Details coming soon..._ -AddressBook data are saved in the hard disk automatically after any command that changes the data. There is no need to save manually. +-------------------------------------------------------------------------------------------------------------------- +
-### Editing the data file +## 4 Additional Command Format Information -AddressBook data are saved as a JSON file `[JAR file location]/data/addressbook.json`. Advanced users are welcome to update data directly by editing that data file. +The requirements of the various parameters used are explained below. Head back to [Features](#3-features) to refer to general notes on the command format. -
: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. -
+### 4.1 Name + +Student name is unique, i.e., there cannot be 2 or more students registered with the same name. TutAssistor ignores +letter case and trivial whitespaces when it checks for duplicate names. + +Note that if you successfully add a student, the name will appear exactly as what you typed, including trivial whitespaces. + +For example, when adding a new student, given an existing student named `John Doe`, + +- Valid: `John Doe the Great` +- Invalid: `john doe`, John   Doe + +Names for tuition classes may be reused. + +For example, given an existing class named `Biology`, you may add a new class also named `Biology`. -### Archiving data files `[coming in v2.0]` +### 4.2 Phone Number +Phone number should only contain digits, and should be at least 3 digits long. -_Details coming soon ..._ +### 4.3 Email +Email should be of the format `local-part@domain` and adhere to the following constraints: +1. The `local-part` should only contain alphanumeric characters and these special characters, `+` `_` `.` `-`. The `local-part` may not start or end with any special characters. +2. This is followed by a `@` and then a `domain` name. The domain name is made up of domain labels separated by periods. + The domain name must: + - end with a domain label at least 2 characters long + - have each domain label start and end with alphanumeric characters + - have each domain label consist of alphanumeric characters, separated only by hyphens, if any + +### 4.4 Timeslot +Timeslot for classes follows the format: +``` +Ddd HH:mm-HH:mm +``` +where +- `Ddd` is the day of the week, **abbreviated to the first three letters, with only the first letter capitalised**. + + Examples: + - Correct: `Mon`, `Tue`, `Wed`, `Thu`, `Fri`, `Sat`, `Sun` + - Incorrect: `mon`, `tuesday`, `WED`, `Thurs`, `Friday`, etc +- `HH:mm` is the time with 2 digits for the hour and 2 digits for the minute. The first time must be earlier in the day than the second time. + + Examples: + - Correct: `09:00-14:30` + - Incorrect: `9:00-14:00`, `9am-2pm`, `9-2`, `09:00-08:00` + +### 4.5 Index +Index for a student or tuition class must be a positive integer, starting from `1`. It must not exceed the size of the list. + +For example, if the list consists of `5` students/tuition classes: +- Correct: `1`, `2`, `3`, `4`, `5` +- Incorrect: `0`, `1.5`, `a`, `6`, etc. -------------------------------------------------------------------------------------------------------------------- +
-## FAQ +## 5 FAQ **Q**: How do I transfer my data to another Computer?
-**A**: Install the app in the other computer and overwrite the empty data file it creates with the file that contains the data of your previous AddressBook home folder. +**A**: Install the app in the other computer and overwrite the empty data file it creates with the file that contains the data of your previous TutAssistor home folder.
+ +**Q**: Do I lose all the details of classes and students when I close the application?
+**A**: All of your data is stored locally in a file under the data folder and will be loaded upon the next entry, +hence you do not lose any data.
+ +**Q**: What should I do if I am unsure of the command formats?
+**A**: Please type `help` to learn the command formats.
-------------------------------------------------------------------------------------------------------------------- +
+ +## 6 Command Summary + +Head back up to section [3 Features](#3-features) to refer to notes about the command format. + +Action | Format | Shortcut +-------|--------|--------- +[***Add Student***](#31-add-studenttuition-class) | `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [r/REMARK]` | `a` +[***Add Class***](#31-add-studenttuition-class) |`addclass n/NAME l/LIMIT ts/TIMESLOT [s/NAME,...] [r/REMARK]` | `ac` +[***View Student***](#32-view-studenttuition-class) | `student INDEX` | `vs` +[***View Class***](#32-view-studenttuition-class) | `class INDEX` | `vc` +[***Edit Student***](#33-edit-studenttuition-class) | `edit INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS]` | `e` +[***Edit Class***](#33-edit-studenttuition-class) | `editclass INDEX [n/NAME] [l/LIMIT] [ts/TIMESLOT]` | `ec` +[***Delete Student***](#34-delete-studenttuition-class) | `delete STUDENT_INDEX [STUDENT_INDEX]...` | `del` +[***Delete Class***](#34-delete-studenttuition-class) | `deleteclass CLASS_INDEX [CLASS_INDEX]...` | `delc` +[***Add Student to Class***](#35-addremove-student-from-class) | `addtoclass si/STUDENT_INDEX [STUDENT_INDEX]... tc/CLASS_INDEX`
or
`addtoclass s/NAME[,NAME...] tc/CLASS_INDEX` | `atc` +[***Remove Students from Class***](#35-addremove-student-from-class) | `remove si/STUDENT_INDEX [STUDENT_INDEX]... tc/CLASS_INDEX` | `rm` +[***Add Remarks to Student***](#36-add-remark-to-studenttuition-class) | `remark STUDENT_INDEX` | `re` +[***Add Remarks to Class***](#36-add-remark-to-studenttuition-class) | `remarkclass CLASS_INDEX` | `rec` +[***Find Student by Name***](#37-find-studenttuition-class) | `find KEYWORD [KEYWORD]...` | `f` +[***Find Class by Name***](#37-find-studenttuition-class) | `findclass KEYWORD [KEYWORD]...` | `fc` +[***List all Students***](#38-list-all-studentstuition-classes) | `list` | `l` +[***List all Classes***](#38-list-all-studentstuition-classes) | `listclass` | `lc` +[***Sort Tuition Class***](#39-sort-tuition-classes-sort--s) | `sort [o/ORDER]` | `s` +[***View Timetable***](#310-view-timetable-timetable--tt) | `timetable` | `tt` +[***View Today's Classes***](#311-view-todays-classes-today--td) | `today` | `td` +[***Help***](#312-view-help-help--h) | `help` | `h` +[***Clear all data***](#314-clear-data-clear) | `clear` | - +[***Exit***](#315-exit-the-app-exit) | `exit` | - -## 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` diff --git a/docs/_config.yml b/docs/_config.yml index 6bd245d8f4e..1d9e9d84cbc 100644 --- a/docs/_config.yml +++ b/docs/_config.yml @@ -1,4 +1,4 @@ -title: "AB-3" +title: "TutAssistor" theme: minima header_pages: @@ -8,7 +8,7 @@ header_pages: markdown: kramdown -repository: "se-edu/addressbook-level3" +repository: "AY2122S1-CS2103T-T12-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..ff73937479a 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: "TutAssistor"; font-size: 32px; } } diff --git a/docs/diagrams/AddToClassActivityDiagram.puml b/docs/diagrams/AddToClassActivityDiagram.puml new file mode 100644 index 00000000000..2cc46e6784e --- /dev/null +++ b/docs/diagrams/AddToClassActivityDiagram.puml @@ -0,0 +1,25 @@ +@startuml + +title AddToClassCommand + +start +:User executes addtoclass command; +:Parse command; + +if () then ([valid command]) + if () then ([use student indices]) + :Convert each index to student name; + else ([use student names]) + endif + :Determine valid students to be enrolled; + :Add valid students to respective + tuition class; + :Update tags of enrolled students + and the tuition class capacity; + :Update tuition list and student list + shown to user; +else ([else]) +:Alert invalid command message; +endif +stop +@enduml diff --git a/docs/diagrams/AddToClassSequenceDiagram.puml b/docs/diagrams/AddToClassSequenceDiagram.puml new file mode 100644 index 00000000000..758b9a595e9 --- /dev/null +++ b/docs/diagrams/AddToClassSequenceDiagram.puml @@ -0,0 +1,93 @@ +@startuml +!include style.puml + +title AddToClassCommand + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR_T3 +participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR_T2 +participant ":AddToClassCommandParser" as AddToClassCommandParser LOGIC_COLOR_T3 +participant "atc:AddToClassCommand" as AddToClassCommand LOGIC_COLOR_T2 +participant ":CommandResult" as CommandResult LOGIC_COLOR_T3 +end box + +box Model MODEL_COLOR_T1 +participant ":Model" as Model MODEL_COLOR +end box + +[-> LogicManager : execute\n("atc si/2 4 tc/1") +activate LogicManager LOGIC_COLOR_T3 + +LogicManager -[LOGIC_COLOR_T3]> AddressBookParser : parseCommand\n("atc si/2 4 tc/1") +activate AddressBookParser LOGIC_COLOR_T2 + +create AddToClassCommandParser +AddressBookParser -[LOGIC_COLOR_T2]> AddToClassCommandParser +activate AddToClassCommandParser LOGIC_COLOR_T3 + +AddToClassCommandParser -[LOGIC_COLOR_T3]-> AddressBookParser +deactivate AddToClassCommandParser + +AddressBookParser -[LOGIC_COLOR_T2]> AddToClassCommandParser : parse("si/2 4 tc/1") +activate AddToClassCommandParser LOGIC_COLOR_T3 + +create AddToClassCommand +AddToClassCommandParser -[LOGIC_COLOR_T3]> AddToClassCommand +activate AddToClassCommand LOGIC_COLOR_T2 + +AddToClassCommand -[LOGIC_COLOR_T2]-> AddToClassCommandParser : atc +deactivate AddToClassCommand + +AddToClassCommandParser -[LOGIC_COLOR_T3]-> AddressBookParser : atc +deactivate AddToClassCommandParser +AddToClassCommandParser -[hidden]-> AddressBookParser +destroy AddToClassCommandParser + +AddressBookParser -[LOGIC_COLOR_T2]-> LogicManager : atc +deactivate AddressBookParser + +LogicManager -[LOGIC_COLOR_T3]> AddToClassCommand : execute() +activate AddToClassCommand LOGIC_COLOR_T2 + +AddToClassCommand -[LOGIC_COLOR_T2]> Model : getTuitionClass(1) +activate Model MODEL_COLOR + +Model --> AddToClassCommand : tuition class +deactivate Model + +loop for each student index in input +AddToClassCommand -[LOGIC_COLOR_T2]> Model : getStudent(index) +activate Model MODEL_COLOR +Model --> AddToClassCommand : student +deactivate Model +end + +AddToClassCommand -[LOGIC_COLOR_T2]> AddToClassCommand : categorizeStudents() +activate AddToClassCommand LOGIC_COLOR_T3 +AddToClassCommand -[LOGIC_COLOR_T4]-> AddToClassCommand +deactivate AddToClassCommand + +loop for each valid student +AddToClassCommand -[LOGIC_COLOR_T2]> Model : addClass(tuition class) +activate Model MODEL_COLOR +Model --> AddToClassCommand +deactivate Model +AddToClassCommand -[LOGIC_COLOR_T2]> Model : updateModel() +activate Model MODEL_COLOR +Model --> AddToClassCommand +deactivate Model +end + +create CommandResult +AddToClassCommand -[LOGIC_COLOR_T2]> CommandResult +activate CommandResult LOGIC_COLOR_T3 + +CommandResult -[LOGIC_COLOR_T3]-> AddToClassCommand +deactivate CommandResult + +AddToClassCommand -[LOGIC_COLOR_T2]-> LogicManager : result +deactivate AddToClassCommand + +[<-[LOGIC_COLOR_T2]-LogicManager +deactivate LogicManager +@enduml diff --git a/docs/diagrams/ArchitectureSequenceDiagram.puml b/docs/diagrams/ArchitectureSequenceDiagram.puml index ef81d18c337..d43a4bc258c 100644 --- a/docs/diagrams/ArchitectureSequenceDiagram.puml +++ b/docs/diagrams/ArchitectureSequenceDiagram.puml @@ -13,7 +13,7 @@ activate ui UI_COLOR ui -[UI_COLOR]> logic : execute("delete 1") activate logic LOGIC_COLOR -logic -[LOGIC_COLOR]> model : deletePerson(p) +logic -[LOGIC_COLOR]> model : deleteStudent(p) activate model MODEL_COLOR model -[MODEL_COLOR]-> logic diff --git a/docs/diagrams/DeleteActivityDiagram.puml b/docs/diagrams/DeleteActivityDiagram.puml new file mode 100644 index 00000000000..b80bb417495 --- /dev/null +++ b/docs/diagrams/DeleteActivityDiagram.puml @@ -0,0 +1,20 @@ +@startuml + +title DeleteClassCommand + +:start; +:User executes "deleteclass 1"; + +if () then ([valid tuition class?]) + + :Removes the class and tags + from all the students in the class; + :Deletes the class in database; + :Updates the tuition list; + +else ([else]) + :Alert user that the class does not exist; + endif + +stop +@enduml diff --git a/docs/diagrams/DeleteSequenceDiagram.puml b/docs/diagrams/DeleteSequenceDiagram.puml index 1dc2311b245..577458fe4ee 100644 --- a/docs/diagrams/DeleteSequenceDiagram.puml +++ b/docs/diagrams/DeleteSequenceDiagram.puml @@ -48,7 +48,7 @@ deactivate AddressBookParser LogicManager -> DeleteCommand : execute() activate DeleteCommand -DeleteCommand -> Model : deletePerson(1) +DeleteCommand -> Model : deleteStudent(1) activate Model Model --> DeleteCommand diff --git a/docs/diagrams/EditClassActivityDiagram.puml b/docs/diagrams/EditClassActivityDiagram.puml new file mode 100644 index 00000000000..69e375dfb2f --- /dev/null +++ b/docs/diagrams/EditClassActivityDiagram.puml @@ -0,0 +1,25 @@ +@startuml + +title EditClassCommand + +:start; +:User executes "editclass 1 l/5 ts/Mon 10:00-11:00"; + +if () then ([valid limit]) + + if () then ([no timetable conflicts + due to new timeslot]) + :Update class tags of enrolled students; + :Update limit and timing of the class; + :Update tuition and student lists + shown to the user; + else ([else]) + :Alert user of timetable conflict; + endif + else ([else]) + :Alert user of the required + minimum capacity of the class; + endif + +stop +@enduml diff --git a/docs/diagrams/NavigateInputHistoryActivity.puml b/docs/diagrams/NavigateInputHistoryActivity.puml new file mode 100644 index 00000000000..e87ba4e0a87 --- /dev/null +++ b/docs/diagrams/NavigateInputHistoryActivity.puml @@ -0,0 +1,32 @@ +@startuml +'https://plantuml.com/activity-diagram-beta + +title Navigate Input History + +start +:User presses `↑`"up" key; + +if () then ([else]) + if() then ([Already at start of input history]) + :Command Box text set as first input in history; + else ([else]) + :Command Box text set as previous input; + endif +else ([Input history is empty]) + :Command box text set as empty String; + +endif + +stop + +start +:User presses `↓`"down" key; +if () then ([Already at the end of input history]) + :Command box text set as empty String; +else([else]) + :Command Box text set as next input; +endif + +stop + +@enduml diff --git a/docs/diagrams/RemarkCommandSequenceDiagram.puml b/docs/diagrams/RemarkCommandSequenceDiagram.puml new file mode 100644 index 00000000000..8667ce8479d --- /dev/null +++ b/docs/diagrams/RemarkCommandSequenceDiagram.puml @@ -0,0 +1,55 @@ +@startuml +!include style.puml + +title RemarkCommand + +Actor User as user USER_COLOR +box Ui UI_COLOR_T1 +participant ":RemarkEditor" as editor UI_COLOR_T2 +participant ":UI" as ui UI_COLOR +end box + +box Logic LOGIC_COLOR_T1 +participant ":Logic" as logic LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +Participant ":Model" as model MODEL_COLOR +end box + +user -[USER_COLOR]> ui : "remark 1" +activate ui UI_COLOR + +ui -[UI_COLOR_T3]> logic : execute() +activate logic LOGIC_COLOR + +logic -[LOGIC_COLOR]> model : get student's \n name and remark +activate model MODEL_COLOR + +model -[MODEL_COLOR]-> logic +deactivate model + +logic -[LOGIC_COLOR]> ui : showRemarkEditor() +activate ui UI_COLOR_T2 + +ui -[UI_COLOR_T2]> editor : load Remark Editor +activate editor UI_COLOR_T2 + +user -[USER_COLOR]>editor : User edits remark \n and clicks "Ok" +editor -[UI_COLOR_T2]-> ui : edited remark +deactivate editor + +ui -[UI_COLOR_T3]-> logic +deactivate ui + +logic -[LOGIC_COLOR]> model : update student +activate model MODEL_COLOR +model -[MODEL_COLOR]-> logic +deactivate model + +logic --[LOGIC_COLOR]> ui +deactivate logic + +ui--[UI_COLOR]> user +deactivate ui +@enduml diff --git a/docs/diagrams/RemoveStudentDiagram.puml b/docs/diagrams/RemoveStudentDiagram.puml new file mode 100644 index 00000000000..33a44aad266 --- /dev/null +++ b/docs/diagrams/RemoveStudentDiagram.puml @@ -0,0 +1,90 @@ +@startuml +!include style.puml + +title RemoveStudentCommand + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR_T3 +participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR_T2 +participant ":RemoveStudentCommandParser" as RemoveStudentCommandParser LOGIC_COLOR_T3 +participant "removeCmd:RemoveStudentCommand" as RemoveStudentCommand LOGIC_COLOR_T2 +participant ":CommandResult" as CommandResult LOGIC_COLOR_T3 +end box + +box Model MODEL_COLOR_T1 +participant ":Model" as Model MODEL_COLOR +end box + +[-> LogicManager : execute\n("remove si/1 2 tc/3") +activate LogicManager LOGIC_COLOR_T3 + +LogicManager -[LOGIC_COLOR_T3]> AddressBookParser : parseCommand\n("remove si/1 2 tc/3") +activate AddressBookParser LOGIC_COLOR_T2 + +create RemoveStudentCommandParser +AddressBookParser -[LOGIC_COLOR_T2]> RemoveStudentCommandParser +activate RemoveStudentCommandParser LOGIC_COLOR_T3 + +RemoveStudentCommandParser -[LOGIC_COLOR_T3]-> AddressBookParser +deactivate RemoveStudentCommandParser + +AddressBookParser -[LOGIC_COLOR_T2]> RemoveStudentCommandParser : parse("si/1 2 tc/3") +activate RemoveStudentCommandParser LOGIC_COLOR_T3 + +create RemoveStudentCommand +RemoveStudentCommandParser -[LOGIC_COLOR_T3]> RemoveStudentCommand +activate RemoveStudentCommand LOGIC_COLOR_T2 + +RemoveStudentCommand -[LOGIC_COLOR_T2]-> RemoveStudentCommandParser : removeCmd +deactivate RemoveStudentCommand + +RemoveStudentCommandParser -[LOGIC_COLOR_T3]-> AddressBookParser : removeCmd +deactivate RemoveStudentCommandParser +RemoveStudentCommandParser -[hidden]-> AddressBookParser +destroy RemoveStudentCommandParser + +AddressBookParser -[LOGIC_COLOR_T2]-> LogicManager : removeCmd +deactivate AddressBookParser + +LogicManager -[LOGIC_COLOR_T3]> RemoveStudentCommand : execute() +activate RemoveStudentCommand LOGIC_COLOR_T2 + +RemoveStudentCommand -[LOGIC_COLOR_T2]> Model : getTuitionClass(3) +activate Model MODEL_COLOR + +Model --> RemoveStudentCommand : tuition class +deactivate Model + +group + loop for each student index + RemoveStudentCommand -[LOGIC_COLOR_T2]> Model : getStudent(index) + activate Model MODEL_COLOR + Model --> RemoveStudentCommand : student + deactivate Model + + alt student exists in class + RemoveStudentCommand -[LOGIC_COLOR_T2]> Model : removeStudent(student) + activate Model MODEL_COLOR + Model --> RemoveStudentCommand : updatedClass + deactivate Model + else invalid student + RemoveStudentCommand -[LOGIC_COLOR_T2]> RemoveStudentCommand : updateInvalidStudents(student) + activate RemoveStudentCommand LOGIC_COLOR_T3 + RemoveStudentCommand -[LOGIC_COLOR_T4]-> RemoveStudentCommand + deactivate RemoveStudentCommand + end +end + +create CommandResult +RemoveStudentCommand -[LOGIC_COLOR_T2]> CommandResult +activate CommandResult LOGIC_COLOR_T3 + +CommandResult -[LOGIC_COLOR_T3]-> RemoveStudentCommand +deactivate CommandResult + +RemoveStudentCommand -[LOGIC_COLOR_T2]-> LogicManager : result +deactivate RemoveStudentCommand + +[<-[LOGIC_COLOR_T2]-LogicManager +deactivate LogicManager +@enduml diff --git a/docs/diagrams/TimeConflictManagement.puml b/docs/diagrams/TimeConflictManagement.puml new file mode 100644 index 00000000000..d65560c320f --- /dev/null +++ b/docs/diagrams/TimeConflictManagement.puml @@ -0,0 +1,80 @@ +@startuml +!include style.puml + +title AddClassCommand + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR_T3 +participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR_T2 +participant ":AddClassCommandParser" as AddClassCommandParser LOGIC_COLOR_T3 +participant "ac:AddClassCommand" as AddClassCommand LOGIC_COLOR_T2 +participant ":CommandResult" as CommandResult LOGIC_COLOR_T3 +participant ":Timeslot" as Timeslot LOGIC_COLOR_T4 +end box + +box Model MODEL_COLOR_T1 +participant ":Model" as Model MODEL_COLOR +end box + +[-> LogicManager : execute\n("ac n/Math l/8 \nts/Mon 11:00-14:00") +activate LogicManager LOGIC_COLOR_T3 + +LogicManager -[LOGIC_COLOR_T3]> AddressBookParser : parseCommand\n("ac n/Math l/8 \nts/Mon 11:00-14:00") +activate AddressBookParser LOGIC_COLOR_T2 + +create AddClassCommandParser +AddressBookParser -[LOGIC_COLOR_T2]> AddClassCommandParser +activate AddClassCommandParser LOGIC_COLOR_T3 + +AddClassCommandParser -[LOGIC_COLOR_T3]-> AddressBookParser +deactivate AddClassCommandParser + +AddressBookParser -[LOGIC_COLOR_T2]> AddClassCommandParser : parse\n("n/Math l/8 \nts/Mon 11:00-14:00") +activate AddClassCommandParser LOGIC_COLOR_T3 + +create AddClassCommand +AddClassCommandParser -[LOGIC_COLOR_T3]> AddClassCommand +activate AddClassCommand LOGIC_COLOR_T2 + +AddClassCommand -[LOGIC_COLOR_T2]-> AddClassCommandParser : ac +deactivate AddClassCommand + +AddClassCommandParser -[LOGIC_COLOR_T3]-> AddressBookParser : ac +deactivate AddClassCommandParser +AddClassCommandParser -[hidden]-> AddressBookParser +destroy AddClassCommandParser + +AddressBookParser -[LOGIC_COLOR_T2]-> LogicManager : ac +deactivate AddressBookParser + +LogicManager -[LOGIC_COLOR_T3]> AddClassCommand : execute() +activate AddClassCommand LOGIC_COLOR_T2 + +AddClassCommand -[LOGIC_COLOR_T2]> Model : getFilteredTuitionList() +activate Model MODEL_COLOR + +Model --> AddClassCommand : Tuition list +deactivate Model + +create Timeslot +AddClassCommand -[LOGIC_COLOR_T2]> Timeslot +activate Timeslot LOGIC_COLOR_T4 + +Timeslot --> Timeslot: checkTimetable\nConflicts(classList) + +Timeslot -[LOGIC_COLOR_T3]-> AddClassCommand +deactivate Timeslot + +create CommandResult +AddClassCommand -[LOGIC_COLOR_T2]> CommandResult +activate CommandResult LOGIC_COLOR_T3 + +CommandResult -[LOGIC_COLOR_T3]-> AddClassCommand +deactivate CommandResult + +AddClassCommand -[LOGIC_COLOR_T2]-> LogicManager : result +deactivate AddClassCommand + +[<-[LOGIC_COLOR_T2]-LogicManager +deactivate LogicManager +@enduml diff --git a/docs/diagrams/TimetableCommandActivityDiagram.puml b/docs/diagrams/TimetableCommandActivityDiagram.puml new file mode 100644 index 00000000000..c57d787669d --- /dev/null +++ b/docs/diagrams/TimetableCommandActivityDiagram.puml @@ -0,0 +1,20 @@ +@startuml + +title TimetableCommand + +start +:User executes "timetable"; + +if () then ([non-empty tuition class list]) + :Get updated tuition class list; + :Determine size of timetable; + :Insert tuition class slots to + the timetable; + :Display timetable to user; +else ([else]) + :Alert user that no tuition + class has been found; + +endif +stop +@enduml diff --git a/docs/diagrams/TimetableCommandSequenceDiagram.puml b/docs/diagrams/TimetableCommandSequenceDiagram.puml new file mode 100644 index 00000000000..cb23e9c3551 --- /dev/null +++ b/docs/diagrams/TimetableCommandSequenceDiagram.puml @@ -0,0 +1,68 @@ +@startuml +!include style.puml +skinparam BoxPadding 30 + +title TimetableCommand + +box Ui UI_COLOR_T1 +participant ":UI" as ui UI_COLOR +participant ":TimetableInfoPage" as TimetableInfoPage UI_COLOR_T2 +end box + +box Model MODEL_COLOR_T1 +participant ":Timetable" as Timetable MODEL_COLOR +end box + +[-[USER_COLOR]> ui : executeUiAction() +activate ui UI_COLOR + +ui -[UI_COLOR]> ui : showTimetable() +activate ui UI_COLOR_T4 +create TimetableInfoPage +ui -[UI_COLOR_T4]> TimetableInfoPage +activate TimetableInfoPage UI_COLOR_T2 + +create Timetable +TimetableInfoPage -[UI_COLOR_T2]> Timetable +activate Timetable MODEL_COLOR +Timetable --> TimetableInfoPage +deactivate Timetable + +TimetableInfoPage -[UI_COLOR_T2]> Timetable: showTimetable() +activate Timetable MODEL_COLOR +Timetable -> Timetable: parseTime() +activate Timetable MODEL_COLOR_T4 +Timetable --> Timetable: +deactivate Timetable +Timetable -> TimetableInfoPage: setTimetable(start, end) +activate TimetableInfoPage UI_COLOR_T3 +TimetableInfoPage -[UI_COLOR_T3]-> Timetable +deactivate TimetableInfoPage + +Timetable -> Timetable: insertSlot() +activate Timetable MODEL_COLOR_T4 +loop for each tuition class +Timetable -> TimetableInfoPage : addLesson() +activate TimetableInfoPage UI_COLOR_T3 +TimetableInfoPage -[UI_COLOR_T3]-> Timetable +deactivate TimetableInfoPage +end + +Timetable --> Timetable +deactivate Timetable +Timetable --> TimetableInfoPage +deactivate Timetable +Timetable -[hidden]-> TimetableInfoPage +destroy Timetable + +TimetableInfoPage -[UI_COLOR_T2]-> ui +deactivate TimetableInfoPage +TimetableInfoPage -[hidden]-> ui +destroy TimetableInfoPage +ui -[UI_COLOR_T4]-> ui +deactivate ui + +[<-[UI_COLOR]-ui +deactivate ui + +@enduml diff --git a/docs/images/AddToClassActivityDiagram.png b/docs/images/AddToClassActivityDiagram.png new file mode 100644 index 00000000000..b0e5bba7869 Binary files /dev/null and b/docs/images/AddToClassActivityDiagram.png differ diff --git a/docs/images/AddToClassSequenceDiagram.png b/docs/images/AddToClassSequenceDiagram.png new file mode 100644 index 00000000000..da58a3ea597 Binary files /dev/null and b/docs/images/AddToClassSequenceDiagram.png differ diff --git a/docs/images/ArchitectureSequenceDiagram.png b/docs/images/ArchitectureSequenceDiagram.png index 2f1346869d0..3b49f5bd354 100644 Binary files a/docs/images/ArchitectureSequenceDiagram.png and b/docs/images/ArchitectureSequenceDiagram.png differ diff --git a/docs/images/DeleteActivityDiagram.png b/docs/images/DeleteActivityDiagram.png new file mode 100644 index 00000000000..caf0ff388b9 Binary files /dev/null and b/docs/images/DeleteActivityDiagram.png differ diff --git a/docs/images/DeleteSequenceDiagram.png b/docs/images/DeleteSequenceDiagram.png index fa327b39618..6fe4eff7fb2 100644 Binary files a/docs/images/DeleteSequenceDiagram.png and b/docs/images/DeleteSequenceDiagram.png differ diff --git a/docs/images/DeleteStudentUG.png b/docs/images/DeleteStudentUG.png new file mode 100644 index 00000000000..c5998e5d1ea Binary files /dev/null and b/docs/images/DeleteStudentUG.png differ diff --git a/docs/images/EditClassActivityDiagram.png b/docs/images/EditClassActivityDiagram.png new file mode 100644 index 00000000000..77bc0117c01 Binary files /dev/null and b/docs/images/EditClassActivityDiagram.png differ diff --git a/docs/images/EditUserGuide.png b/docs/images/EditUserGuide.png new file mode 100644 index 00000000000..2b43d4b4cfc Binary files /dev/null and b/docs/images/EditUserGuide.png differ diff --git a/docs/images/NavigateInputHistoryActivityDiagram.png b/docs/images/NavigateInputHistoryActivityDiagram.png new file mode 100644 index 00000000000..7ad5a527f06 Binary files /dev/null and b/docs/images/NavigateInputHistoryActivityDiagram.png differ diff --git a/docs/images/RemarkCommandSequenceDiagram.png b/docs/images/RemarkCommandSequenceDiagram.png new file mode 100644 index 00000000000..e54737d2030 Binary files /dev/null and b/docs/images/RemarkCommandSequenceDiagram.png differ diff --git a/docs/images/RmStudentSequenceDiagram.png b/docs/images/RmStudentSequenceDiagram.png new file mode 100644 index 00000000000..b4237b797ca Binary files /dev/null and b/docs/images/RmStudentSequenceDiagram.png differ diff --git a/docs/images/TimeConflictManagementSequence.png b/docs/images/TimeConflictManagementSequence.png new file mode 100644 index 00000000000..59ddd1770c6 Binary files /dev/null and b/docs/images/TimeConflictManagementSequence.png differ diff --git a/docs/images/TimetableCommandActivityDiagram.png b/docs/images/TimetableCommandActivityDiagram.png new file mode 100644 index 00000000000..82aa9e14d57 Binary files /dev/null and b/docs/images/TimetableCommandActivityDiagram.png differ diff --git a/docs/images/TimetableCommandSequenceDiagram.png b/docs/images/TimetableCommandSequenceDiagram.png new file mode 100644 index 00000000000..35d821d1d41 Binary files /dev/null and b/docs/images/TimetableCommandSequenceDiagram.png differ diff --git a/docs/images/Ui.png b/docs/images/Ui.png index 91488fd1a0f..0ed22bc9254 100644 Binary files a/docs/images/Ui.png and b/docs/images/Ui.png differ diff --git a/docs/images/adam-ky.png b/docs/images/adam-ky.png new file mode 100644 index 00000000000..764c0ef852c Binary files /dev/null and b/docs/images/adam-ky.png differ diff --git a/docs/images/annotated_UI.png b/docs/images/annotated_UI.png new file mode 100644 index 00000000000..ce2def5ec33 Binary files /dev/null and b/docs/images/annotated_UI.png differ diff --git a/docs/images/class_view.png b/docs/images/class_view.png new file mode 100644 index 00000000000..3b8b120a40c Binary files /dev/null and b/docs/images/class_view.png differ diff --git a/docs/images/find_screenshot.png b/docs/images/find_screenshot.png new file mode 100644 index 00000000000..e8de3e416a3 Binary files /dev/null and b/docs/images/find_screenshot.png differ diff --git a/docs/images/helpWindow.png b/docs/images/helpWindow.png new file mode 100644 index 00000000000..03c554a7cd6 Binary files /dev/null and b/docs/images/helpWindow.png differ diff --git a/docs/images/leofeng10.png b/docs/images/leofeng10.png new file mode 100644 index 00000000000..b6b9434ee50 Binary files /dev/null and b/docs/images/leofeng10.png differ diff --git a/docs/images/norayuuu.png b/docs/images/norayuuu.png new file mode 100644 index 00000000000..f4501598962 Binary files /dev/null and b/docs/images/norayuuu.png differ diff --git a/docs/images/remarkEditor.png b/docs/images/remarkEditor.png new file mode 100644 index 00000000000..3b63ef692a8 Binary files /dev/null and b/docs/images/remarkEditor.png differ diff --git a/docs/images/remark_editor_class.png b/docs/images/remark_editor_class.png new file mode 100644 index 00000000000..7073d14d9a7 Binary files /dev/null and b/docs/images/remark_editor_class.png differ diff --git a/docs/images/remark_editor_student.png b/docs/images/remark_editor_student.png new file mode 100644 index 00000000000..717c5bf6558 Binary files /dev/null and b/docs/images/remark_editor_student.png differ diff --git a/docs/images/sort.png b/docs/images/sort.png new file mode 100644 index 00000000000..09923022a14 Binary files /dev/null and b/docs/images/sort.png differ diff --git a/docs/images/student_view.png b/docs/images/student_view.png new file mode 100644 index 00000000000..16d910aa2ae Binary files /dev/null and b/docs/images/student_view.png differ diff --git a/docs/images/ta_logo_cropped_white_bg.png b/docs/images/ta_logo_cropped_white_bg.png new file mode 100644 index 00000000000..0561de49330 Binary files /dev/null and b/docs/images/ta_logo_cropped_white_bg.png differ diff --git a/docs/images/ta_logo_fdfdfd_cropped.png b/docs/images/ta_logo_fdfdfd_cropped.png new file mode 100644 index 00000000000..3abfe53b173 Binary files /dev/null and b/docs/images/ta_logo_fdfdfd_cropped.png differ diff --git a/docs/images/time_table.png b/docs/images/time_table.png new file mode 100644 index 00000000000..af64fe9d696 Binary files /dev/null and b/docs/images/time_table.png differ diff --git a/docs/images/timiditi.png b/docs/images/timiditi.png new file mode 100644 index 00000000000..06cc01628f2 Binary files /dev/null and b/docs/images/timiditi.png differ diff --git a/docs/images/today_view.png b/docs/images/today_view.png new file mode 100644 index 00000000000..df6db89cd2a Binary files /dev/null and b/docs/images/today_view.png differ diff --git a/docs/images/ui_ug.png b/docs/images/ui_ug.png new file mode 100644 index 00000000000..eee4b49c996 Binary files /dev/null and b/docs/images/ui_ug.png differ diff --git a/docs/index.md b/docs/index.md index 7601dbaad0d..30bd84e6a83 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,17 +1,17 @@ --- layout: page -title: AddressBook Level-3 +title: TutAssistor --- -[![CI Status](https://github.com/se-edu/addressbook-level3/workflows/Java%20CI/badge.svg)](https://github.com/se-edu/addressbook-level3/actions) -[![codecov](https://codecov.io/gh/se-edu/addressbook-level3/branch/master/graph/badge.svg)](https://codecov.io/gh/se-edu/addressbook-level3) +[![CI Status](https://github.com/AY2122S1-CS2103T-T12-4/tp/workflows/Java%20CI/badge.svg)](https://github.com/AY2122S1-CS2103T-T12-4/tp/actions) +[![codecov](https://codecov.io/gh/AY2122S1-CS2103T-T12-4/tp/branch/master/graph/badge.svg?token=EDWM7KCEX4)](https://codecov.io/gh/AY2122S1-CS2103T-T12-4/tp) ![Ui](images/Ui.png) -**AddressBook is a desktop application for managing your contact details.** While it has a GUI, most of the user interactions happen using a CLI (Command Line Interface). +**TutAssistor is a desktop application for private tutors to manage tuition class timeslots.** 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 TutAssistor, head over to the [**User Guide**](UserGuide.html#quick-start). +* If you are interested about developing TutAssistor the [**Developer Guide**](DeveloperGuide.html) is a good place to start. **Acknowledgements** diff --git a/docs/team/adam-ky.md b/docs/team/adam-ky.md new file mode 100644 index 00000000000..43299d7ded9 --- /dev/null +++ b/docs/team/adam-ky.md @@ -0,0 +1,58 @@ +--- +layout: page +title: Adam's Project Portfolio Page +--- + +#### Project: TutAssistor + +TutAssistor is a desktop application for private tutors to manage tuition class timeslots. While it has a GUI, most of the user interactions happen using a CLI (Command Line Interface). The user interacts with it using a CLI, and it has a GUI created with JavaFX. It has about 20 kLoC. + +Given below are my contributions to the project. You may refer to my [RepoSense link](https://nus-cs2103-ay2122s1.github.io/tp-dashboard/?search=&sort=groupTitle&sortWithin=title&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=true&checkedFileTypes=docs~functional-code~test-code~other&since=2021-09-17&tabOpen=true&tabType=authorship&tabAuthor=adam-ky&tabRepo=AY2122S1-CS2103T-T12-4%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code~other&authorshipIsBinaryFileTypeChecked=false) to view my code contributions. + +#### New Features Implemented + +1. Designed the ability to edit remarks with [Remark Editor](https://github.com/AY2122S1-CS2103T-T12-4/tp/blob/master/docs/images/remarkEditor.png) [\#99](https://github.com/AY2122S1-CS2103T-T12-4/tp/pull/99), [\#101](https://github.com/AY2122S1-CS2103T-T12-4/tp/pull/101) + + * **What it does**: Allows users to edit or remove remarks from a student or tuition class. + * **Justification**: Users were not allowed to edit remarks in the original [AB3's implementation for `remark`](https://nus-cs2103-ay2122s1.github.io/tp/tutorials/AddRemark.html). In fact, adding remarks will override the previous remarks. However, the remark feature is essential for TutAssistor users who will be using it frequently to keep track of student and class details. This feature helps users to edit remarks freely and conveniently. + * **Highlights**: The automated GUI testing is not implemented in the latest version, so manual testing is required. Details for manual testing can be found [here](https://ay2122s1-cs2103t-t12-4.github.io/tp/DeveloperGuide.html#editing-remarks). + +2. Implemented the `remarkclass` feature [\#44](https://github.com/AY2122S1-CS2103T-T12-4/tp/pull/44) + + * **What it does**: allows users to optionally add remarks when creating tuition classes. + * **Justification**: users will use the remark feature extensively to keep track of details such as fee payments and homework. As such, it is important to implement the ability to add remarks to tuition classes. + * **Highlights**: the remarks for class can be edited with the Remark Editor mentioned above as well. + * **Credits**: this implementation was referenced from [AB3's Add Remark tutorial](https://nus-cs2103-ay2122s1.github.io/tp/tutorials/AddRemark.html). + +3. Added the ability to open the TutAssistor user guide from the Help window + + * **What it does**: allows the user to click the "**Open User Guide**" button to open the user guide in the user's browser automatically. + * **Justification**: in the AB3's implementation for the help window, the user has to click on the "**Copy URL**" button to retrieve the user guide link. It is tedious for users to manually copy the url link and open the user guide in their browser. It is more convenient to redirect the users to the user guide in their browser automatically when they click the button. + * **Credits**: this implementation was referenced from [samyipsh’s tP](https://github.com/samyipsh/tp) for CS2103T. + +#### Enhancements to existing features: + * Improved overall look for GUI, such as icons and labels [\#56](https://github.com/AY2122S1-CS2103T-T12-4/tp/pull/56) + * Updated [Help Window](https://github.com/AY2122S1-CS2103T-T12-4/tp/blob/master/docs/images/helpWindow.png) to include command summary and ability to open user guide [\#79](https://github.com/AY2122S1-CS2103T-T12-4/tp/pull/79), [\#120](https://github.com/AY2122S1-CS2103T-T12-4/tp/pull/120/files) + * Created `TuitionClassBuilder` and `TypicalClasses` as utility classes for testing `TuitionClass`-related commands [\#86](https://github.com/AY2122S1-CS2103T-T12-4/tp/pull/86) + +#### Documentation: + +**User Guide**: + * Ensured consistent formatting throughout the whole UG, including the table of contents, navigational links, and size of screenshots [\#113](https://github.com/AY2122S1-CS2103T-T12-4/tp/pull/113/files), [\#129](https://github.com/AY2122S1-CS2103T-T12-4/tp/pull/129/files) + * Designed the [TutAssistor logo](https://github.com/AY2122S1-CS2103T-T12-4/tp/blob/master/docs/images/ta_logo_cropped_white_bg.png) [\#133](https://github.com/AY2122S1-CS2103T-T12-4/tp/pull/133/files) + * Added relevant screenshots, including the UI mock up, Help Window, and Student Info Page [\#121](https://github.com/AY2122S1-CS2103T-T12-4/tp/pull/121), [\#28](https://github.com/AY2122S1-CS2103T-T12-4/tp/pull/28), [\#129](https://github.com/AY2122S1-CS2103T-T12-4/tp/pull/129) + * Documented `remark` and `remarkclass` features [\#54](https://github.com/AY2122S1-CS2103T-T12-4/tp/pull/54/files) + +**Developer Guide**: + * Documented implementation details of the `remark` feature [\#113](https://github.com/AY2122S1-CS2103T-T12-4/tp/pull/113/files), [\#140](https://github.com/AY2122S1-CS2103T-T12-4/tp/pull/140) + * Created [sequence diagram](https://github.com/AY2122S1-CS2103T-T12-4/tp/blob/master/docs/images/RemarkCommandSequenceDiagram.png) for `remark` implementation [\#122](https://github.com/AY2122S1-CS2103T-T12-4/tp/pull/122) + * Prescribed manual testing instructions for `exit`, `remark`, and `help` features [\#204](https://github.com/AY2122S1-CS2103T-T12-4/tp/pull/204) + +#### Project management: + * Managed and assigned 1.2 issues to track project tasks + * Facilitated team direction for week 7 tP requirements + +#### Community: + * Reported [10 bugs](https://github.com/adam-ky/ped/issues) for team T10-3 during the mock Practical Exam (PE) + * PRs reviewed (with non-trivial review comments): [\#95](https://github.com/AY2122S1-CS2103T-T12-4/tp/pull/95), [\#111](https://github.com/AY2122S1-CS2103T-T12-4/tp/pull/111), [\#136](https://github.com/AY2122S1-CS2103T-T12-4/tp/pull/136) + diff --git a/docs/team/amzhy.md b/docs/team/amzhy.md new file mode 100644 index 00000000000..5fb81ec63ad --- /dev/null +++ b/docs/team/amzhy.md @@ -0,0 +1,78 @@ +--- +layout: page +title: Ammar's Project Portfolio Page +--- + +### Project: TutAssistor + +TutAssistor is a desktop application for private tutors to manage tuition class timeslots. While it has a GUI, most of the user interactions happen using a CLI (Command Line Interface). The user interacts with it using a CLI, and it has a GUI created with JavaFX. +It has about 20 kLoC. + +Given below are my contributions to the project. + +### Summary of Contributions + +Access my [RepoSense link](https://nus-cs2103-ay2122s1.github.io/tp-dashboard/?search=amzhy&sort=groupTitle&sortWithin=title&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=true&checkedFileTypes=docs~functional-code~test-code~other&since=2021-09-17&tabOpen=true&tabType=authorship&zFR=false&tabAuthor=amzhy&tabRepo=AY2122S1-CS2103T-T12-4%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code&authorshipIsBinaryFileTypeChecked=false) to view the code that I have contributed. + +#### Enhancements implemented +* **Create edit features**: Designed and created `EditCommand`, `EditClassCommand` and their related `Parser` classes. (Pull request [\#125](https://github.com/AY2122S1-CS2103T-T12-4/tp/pull/125)) + * What it does: Allows user to edit details of a particular student or tuition class in **TutAssistor**. + * Justification: This improves the user experience as users do not have to delete and recreate entire students or classes, in order to change a few details. + * Highlights: + * Changing the `Timeslot` of a class has to be considered carefully, as it cannot overlap with any timeslot of existing classes. + +* **Create delete features**: Designed and created `DeleteCommand`, `DeleteClassCommand` and their corresponding `Parser` classes. (Pull requests [\#46](https://github.com/AY2122S1-CS2103T-T12-4/tp/pull/46)) + * What it does: Allows user to delete a student or tuition classes in **TutAssistor**. + * Justification: This enables the user to remove entire records pertaining to multiple students or tuition classes. + * Highlights: + * Duplicate indices in user inputs have to be removed to ensure that only one student/class is deleted at a particular index. + * The order of deletion has to be considered carefully by sorting the indices before deletion, as the +list of students/classes adapts while students/classes are being deleted respectively. + +* **Implement `RemoveStudent` feature**: Implemented `RemoveStudentCommand` and `RemoveStudentCommandParser`. (Pull request [\#58](https://github.com/AY2122S1-CS2103T-T12-4/tp/pull/58)) + * What it does: Allows user to remove existing students from a tuition class in **TutAssistor**. + * Justification: This allows the user to update class enrollment promptly whenever students drop out of a tuition class. + * Highlights: + * Efforts are taken to ensure that students who are not enrolled in the class are not removed and this is conveyed to users. + +* **Refactor Timeslot class**: Refactor Timeslot class to use Java `LocalTime` and `Date` for class timings instead of`String`. (Pull request [\#125](https://github.com/AY2122S1-CS2103T-T12-4/tp/pull/125)) + * What it does: Parses and saves the timing of any tuition class as `Date` and `LocalTime` to compare timeslots across classes. + * Justification: Previously, the time slot was a `String` which created scheduling conflicts between classes and classes to be created at invalid timings. (E.g classes could span across days). Following the refactoring of the `Timeslot`, classes were created only at valid timings and the timetable conflicts were resolved more easily +using the existing Java API. + * Highlights: + * This is a major refactor as the `Timeslot` is crucial for the logic of `Timetable` and general management of all the tuition classes. + +* **Refactor some commands to use multiple indices**: Refactor `DeleteCommand`, `DeleteClassCommand` and `RemoveStudentCommand` to use multiple student or class indices as arguments. (Pull request [\#65](https://github.com/AY2122S1-CS2103T-T12-4/tp/pull/65)) + * What it does: Allows user to delete multiple students or tuition classes, as well as remove multiple students from a class. + * Justification: Improve efficiency so that the user is able to delete multiple students or classes at once instead of +repeating similar commands multiple times. + +* **Testing**: Create tests for the `EditClassCommandParser`, `EditCommandParser`, `DeleteCommandParser`, `DeleteClassCommandParser` and `RemoveStudentCommandParser` as well as their related classes to increase coverage. (Pull Request [\#216](https://github.com/AY2122S1-CS2103T-T12-4/tp/pull/216), [\#230](https://github.com/AY2122S1-CS2103T-T12-4/tp/pull/230)) + +#### Contributions to the User Guide +* Added to the FAQ and Introduction, formatted figures and enabled quicklinks for the command summary. (Pull Request [\#139](https://github.com/AY2122S1-CS2103T-T12-4/tp/pull/139)) +* Added implementation details for `EditClassCommand`, `EditCommand`, `DeleteClassCommand`. (Pull Request [\#217](https://github.com/AY2122S1-CS2103T-T12-4/tp/pull/217)) +* Fix some documentation bugs from PE-D. (Pull Request [\#187](https://github.com/AY2122S1-CS2103T-T12-4/tp/pull/187)) +* Added screenshots for the `DeleteCommand` and `EditClassCommand`. (Pull Request [\#217](https://github.com/AY2122S1-CS2103T-T12-4/tp/pull/217), [\#233](https://github.com/AY2122S1-CS2103T-T12-4/tp/pull/233)) + +
+ +#### Contributions to the Developer Guide +* Added the implementation details of `EditClassCommand`. (Pull Request [\#135](https://github.com/AY2122S1-CS2103T-T12-4/tp/pull/135)) +* Added UML diagrams for `RemoveStudent` and`DeleteClassCommand`. features (Pull Request [\#209](https://github.com/AY2122S1-CS2103T-T12-4/tp/pull/209)) +* Added implementation details of `EditCommand` and `DeleteCommand`. (Pull Request [\#217](https://github.com/AY2122S1-CS2103T-T12-4/tp/pull/217)) +* Added use cases for TutAssistor. + + +#### Contributions to the team-based tasks +* Proposed the idea for TutAssistor. +* Proposed the feature to enable command shortcuts for long commands. +* Proposed the idea of input history navigation feature to improve the design of TutAssistor to model a CLI application. +* Enabled status checks and approvals for all PRs to prevent CI failures for the team's repository. + +#### Review/mentoring contributions +* PRs reviewed: [\#198](https://github.com/AY2122S1-CS2103T-T12-4/tp/pull/198) + +#### Contributions beyond the project team +* Reported [20 bugs](https://github.com/amzhy/ped/issues) in group F13-2's tP during mock PE. + diff --git a/docs/team/leofeng10.md b/docs/team/leofeng10.md new file mode 100644 index 00000000000..fa81aef0104 --- /dev/null +++ b/docs/team/leofeng10.md @@ -0,0 +1,70 @@ +--- +layout: page +title: Feng Zhunyi's Project Portfolio Page +--- + +### Project: TutAssistor + +TutAssistor is a desktop application for private tutors to manage tuition class timeslots. While it has a GUI, most of the user interactions happen using a CLI (Command Line Interface). The user interacts with it using a CLI, and it has a GUI created with JavaFX. +It has about 20 kLoC. + +Given below are my contributions to the project. +Access my [RepoSense link](https://nus-cs2103-ay2122s1.github.io/tp-dashboard/?search=leofeng&sort=groupTitle&sortWithin=title&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=true&checkedFileTypes=docs~functional-code~test-code~other&since=2021-09-17&tabOpen=true&tabType=authorship&zFR=false&tabAuthor=amzhy&tabRepo=AY2122S1-CS2103T-T12-4%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code&authorshipIsBinaryFileTypeChecked=false) to view the code that I have contributed. + +* Enhancements implemented + * **Create Tuition Model**: Designed and created `ClassLimit`, `ClassName`, `Timeslot`, `TuitionClass`, `UniqueTuitionList`. (Pull request [\#10](https://github.com/AY2122S1-CS2103T-T12-4/tp/pull/10/commits/8cdb3def342ca3d832e6668b2928ec162eff4792)) + * What it does: Allows user to edit any details of a student or tuition class with the exception of remarks in **TutAssistor** + * Justification: This is the most basic model for our app as our app aiming to manage tutors classes. Thus we need to have a + model for those classes and encapsulates classes' attributes in the model. + + * **Create add tuition features**: Designed and created `AddClassCommand` and its corresponding `Parser` classes. (Pull requests [\#14](https://github.com/AY2122S1-CS2103T-T12-4/tp/pull/14/commits/6a86752ef9acf8854b2a18330402d6887498b7a5)) + * What it does: Allows user to create a tuition class in **TutAssistor** + * Justification: This allows user to create a tuition class in order to record the class his has. + + * **Create view today tuition classes features**: Designed and created `ViewTodayTuitionCommand` and its corresponding `Parser` classes, and modified + `MainWindow`, `LogicManager` and relative files. (Pull requests [\#103](https://github.com/AY2122S1-CS2103T-T12-4/tp/pull/103)) + * What it does: Allows user to check today tuition classes in **TutAssistor** when he opens the app and when he wants + * Justification: This allows user to better arrange their time after knowing what classes they have today. + + * **Create AddRemark features**: Designed and created `AddRemarkCommand` and its corresponding `Parser` classes. (Pull requests [\#3](https://github.com/AY2122S1-CS2103T-T12-4/tp/pull/3)) + * What it does: Allows user to add remark to students or tuition classes in **TutAssistor** + * Justification: Notes are often need for a tutor to keep some records for students and classes. + + * **Create `Tuition` UI**: Designed and created `TuitionCard`, `TuitionListPanel`, and modified `MainApp`, `MainWindow` (Pull requests [\#10](https://github.com/AY2122S1-CS2103T-T12-4/tp/pull/10/commits/74ba546fabb33fd65299a005b4e321116ab52716)) + * What it does: Allows user to check tuition classes information in **TutAssistor** + * Justification: To check tuition information, a UI is needed to display hte information. + + * **Manage Timeslot conflict**: Designed and created the format of timeslot, and the algorithm for conflict detection.(Pull requests [\#41](https://github.com/AY2122S1-CS2103T-T12-4/tp/pull/41)) + * What it does: Allows user to add tuition classes in **TutAssistor** without time conflict + * Justification: **TutAssistor** allows users to manage their tuition classes, the our app needs to ensure that users + cannot add different tuition classes in the same time slot. + + * **Create store `Tuition` features**: Designed and created `JsonAdaptedTuition`, and modified + `AddressBookStorage`, `JsonAddressBookStorage`, `JsonSerializableAddressBook` to create, read tuition class . (Pull requests [\#10](https://github.com/AY2122S1-CS2103T-T12-4/tp/pull/10)) + * What it does: All user to store a tuition class into the json file in **TutAssistor** + * Justification: As users need to check their tuiton classes they have created, the class information need to be stored. We choose to store + the data into json file because it is easy to export and import. + + * **Testing**: Create tests for the `ClassLimit`, `Timeslot`, `ClassName`, `UniqueTuitionList` to increase coverage. (Pull request [\#83](https://github.com/AY2122S1-CS2103T-T12-4/tp/pull/83)) + + +* Contributions to the User Guide + * Added documentation for the features `addclass` and `today` (Pull Request [\#109](https://github.com/AY2122S1-CS2103T-T12-4/tp/pull/109)) + +* Contributions to the Developer Guide + * Added the implementation details of time conflict management (Pull Request [\#108](https://github.com/AY2122S1-CS2103T-T12-4/tp/pull/108)) + * Added the UML diagram for time conflict management (Pull Request [\#203](https://github.com/AY2122S1-CS2103T-T12-4/tp/pull/203)) + * Added Use cases for **TutAssistor**. + +* Contributions to the team-based tasks + * Proposed the attributes of a tuition class + * Proposed the UI to display Tuition classes and students + * Proposed the format of time slot, and the way to detect time conflict + * Proposed using hashcode as the unique ID for a tuition class for easier referencing + +* Review/mentoring contributions + * PRs reviewed: [\#42](https://github.com/AY2122S1-CS2103T-T12-4/tp/pull/42) + +* Contributions beyond the project team + * Reported [4 issues](https://github.com/Leofeng10/ped/issues) in group W13-1's tP during mock PE. + diff --git a/docs/team/norayuuu.md b/docs/team/norayuuu.md new file mode 100644 index 00000000000..1b2c01a7c81 --- /dev/null +++ b/docs/team/norayuuu.md @@ -0,0 +1,60 @@ +--- +layout: page +title: Deng Huaiyu's Project Portfolio Page +--- + +### Project: TutAssistor + +TutAssistor is a desktop application for private tutors to manage tuition class timeslots. While it has a GUI, most of the user interactions happen using a CLI (Command Line Interface). The user interacts with it using a CLI, and it has a GUI created with JavaFX. +It has about 20 kLoC. + +Given below are my contributions to the project. + +* **New Feature**: Designed and implemented the timetable feature. [\#104](https://github.com/AY2122S1-CS2103T-T12-4/tp/pull/104) + * What it does: In GUI, allows the user to view all scheduled lessons in one week, with the name and time range of each lesson specified in the timetable. + * Justification: This feature helps users to better visualize the lessons scheduled and quickly check lessons arranged at a specific time. + * Highlights: The size of timetable is designed to vary with the time of lessons arranged. The GUI implementation is thus challenging as the size and position of cells inserted and font size for each cell need to be computed with care. +* **New Feature**: Added the ability to add students to existing tuition class. [\#35](https://github.com/AY2122S1-CS2103T-T12-4/tp/pull/35), [#51](https://github.com/AY2122S1-CS2103T-T12-4/tp/pull/51), [\#75](https://github.com/AY2122S1-CS2103T-T12-4/tp/pull/75) + * What it does: Allows the user to add existing students to existing tuition class use one or more student names or student indices. + * Justification: Allows users to flexibly move students into tuition classes + * Highlights: Many situations need to be considered to show correct message to user, namely non-existing students, students already enrolled in class, valid students not enrolled due to class size limit, and valid students enrolled. [\#67](https://github.com/AY2122S1-CS2103T-T12-4/tp/pull/67) +* **New Feature**: Added the ability to add students when adding a new tuition class. [\#35](https://github.com/AY2122S1-CS2103T-T12-4/tp/pull/35) + * What it does: Allows the user to add students when creating a new tuition class. + * Justification: Adding students to a new tuition class directly is more convenient. +* **New Feature**: Added the ability to sort the tuition list. [\#80](https://github.com/AY2122S1-CS2103T-T12-4/tp/pull/80) + * What it does: Allows the user sort the tuition list by time and alphabetically order. + * Justification: Users can now view tuition classes in their preferred order. +* **Enhancement**: Redesigned `Student` and `TuitionClass` data structure. [\#35](https://github.com/AY2122S1-CS2103T-T12-4/tp/pull/35), [#51](https://github.com/AY2122S1-CS2103T-T12-4/tp/pull/51) + * What it does: Stored tuition classes that each student attend using tuition class ID, and store students in each class using student names. + * Justification: Previously students and tuition classes were saved as objects under each other. This takes extra space for storage and increases running time. + * Highlights: Major modification is done for `addclass` and `addtoclass` features as well as `JsonAdaptedTuition` and `JsonAdaptedStudent` files for storage. +* **Other Enhancements**: + * Enabled adding tags automatically when students are enrolled in a tuition class and disallowed users to add tags manually. [\#37](https://github.com/AY2122S1-CS2103T-T12-4/tp/pull/37), [\#114](https://github.com/AY2122S1-CS2103T-T12-4/tp/pull/114) + * Prevents same student with names differ only in number of white spaces and letter cases to be added [\#186](https://github.com/AY2122S1-CS2103T-T12-4/tp/pull/186) +* **Testing**: Wrote unit tests for `AddToClassCommand`, `StudentList`, `Timetable`, `SortCommand`, `SortCommandParser`, `TimetableCommand`, `TimetableParser`, and `UniqueTuitionList` classes. Increases code coverage for TutAssistor by 11.94% ([\#142](https://github.com/AY2122S1-CS2103T-T12-4/tp/pull/142), [\#145](https://github.com/AY2122S1-CS2103T-T12-4/tp/pull/145), [\#202](https://github.com/AY2122S1-CS2103T-T12-4/tp/pull/202)) +* **Code contributed**: [RepoSense link](https://nus-cs2103-ay2122s1.github.io/tp-dashboard/?search=T12&sort=groupTitle&sortWithin=title&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=true&checkedFileTypes=docs~functional-code~test-code~other&since=2021-09-17&tabOpen=true&tabType=authorship&tabAuthor=NoraYUuu&tabRepo=AY2122S1-CS2103T-T12-4%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code&authorshipIsBinaryFileTypeChecked=false). + +* **Documentation**: + * User Guide: + * Added documentation for the features `addtoclass`([\#55](https://github.com/AY2122S1-CS2103T-T12-4/tp/pull/55), [\#75](https://github.com/AY2122S1-CS2103T-T12-4/tp/pull/75)), `sort` ([\#81](https://github.com/AY2122S1-CS2103T-T12-4/tp/pull/81), [\#184](https://github.com/AY2122S1-CS2103T-T12-4/tp/pull/184)), `timetable` ([\#104](https://github.com/AY2122S1-CS2103T-T12-4/tp/pull/104), [\#106](https://github.com/AY2122S1-CS2103T-T12-4/tp/pull/106)) and modified `addclass` ([\#75](https://github.com/AY2122S1-CS2103T-T12-4/tp/pull/75)). + * Added dropdown list to table of content. [\#207](https://github.com/AY2122S1-CS2103T-T12-4/tp/pull/207) + * Added annotation for UI and explanation for various components of UI. [\#206](https://github.com/AY2122S1-CS2103T-T12-4/tp/pull/206) + * Developer Guide: + * Added implementation details of the `addtoclass` feature. [\#137](https://github.com/AY2122S1-CS2103T-T12-4/tp/pull/137) + * Added implementation details of the `timetable` feature. [\#112](https://github.com/AY2122S1-CS2103T-T12-4/tp/pull/112) + * Added UML diagrams for `addtoclass` and `timetable` features. [\#200](https://github.com/AY2122S1-CS2103T-T12-4/tp/pull/200) + * Added use cases for TutAssistor. +* **Contributions to the team-based tasks** + * Set up the GitHub team org and GitHub team repo. + * Added [codecov](https://github.com/AY2122S1-CS2103T-T12-4/tp/commit/24f790cc130773b81b1a77e603cbe4d81bdec3d2) and updated CI badge to TutAssistor. [\#208](https://github.com/AY2122S1-CS2103T-T12-4/tp/pull/208) + * Proposed and enhanced data structure: modified the relationship between students and tuition classes. + * Modified documentation to match the coding standard. [\#112](https://github.com/AY2122S1-CS2103T-T12-4/tp/pull/112) + * Updated user guide Table of Content and added annotation for screenshot. [#206](https://github.com/AY2122S1-CS2103T-T12-4/tp/pull/206) + * Updated sample data of TutAssistor. [\#55](https://github.com/AY2122S1-CS2103T-T12-4/tp/pull/55), [\#57](https://github.com/AY2122S1-CS2103T-T12-4/tp/pull/57) +* **Review/mentoring contributions** + * Helped teammate debug non-refreshing GUI. [\#35](https://github.com/AY2122S1-CS2103T-T12-4/tp/pull/35) + * PRs reviewed (with non-trivial review comments): [\#103](https://github.com/AY2122S1-CS2103T-T12-4/tp/pull/103). + * Helped teammate to install PlantUML on Mac. +* **Community** + * Contributed to forum discussions. (example: [1](https://github.com/nus-cs2103-AY2122S1/forum/issues/157)) + * Reported [10 bugs](https://github.com/NoraYUuu/ped/issues) in group W14-2's tP during mock PE. diff --git a/docs/team/timiditi.md b/docs/team/timiditi.md new file mode 100644 index 00000000000..4c2d52a6f96 --- /dev/null +++ b/docs/team/timiditi.md @@ -0,0 +1,65 @@ +--- +layout: page +title: Timothy's Project Portfolio Page +--- + +### Project: TutAssistor + +TutAssistor is a desktop application for private tutors to manage tuition class timeslots. +While it has a GUI, most of the user interactions happen using a CLI (Command Line Interface). +The user interacts with it using a CLI, and it has a GUI created with JavaFX. +It has about 20 kLoC. + +Given below are my contributions to the project. + +#### Code contributed +* Access my +[Reposense link](https://nus-cs2103-ay2122s1.github.io/tp-dashboard/?search=timiditi&sort=groupTitle&sortWithin=title&since=2021-09-17&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=false) +to view the code I have contributed. + +#### Enhancements implemented + +* **New feature:** Dedicated multi-use information display panel (Pull request [#42](https://github.com/AY2122S1-CS2103T-T12-4/tp/pull/42)) + * **What it does:** Adds a dedicated panel in GUI to display information. + * **Justification:** + * **Highlights:** The implementation of this GUI element allowed for future expansions of features that involve + users pulling up detailed information on command. + + Features added later in the project that utilized the display panel include the + [View Student/Class Commands](https://ay2122s1-cs2103t-t12-4.github.io/tp/UserGuide.html#32-view-studenttuition-class), + [View Timetable Command](https://ay2122s1-cs2103t-t12-4.github.io/tp/UserGuide.html#310-view-timetable-timetable--tt) + and [View Today's Classes Command](https://ay2122s1-cs2103t-t12-4.github.io/tp/UserGuide.html#311-view-todays-classes-today--td). +* **New Feature:** View student/class command ([#42](https://github.com/AY2122S1-CS2103T-T12-4/tp/pull/42)) + * **What it does:** Allows users to view detailed information on students and tuition classes in info panel via the `student` and `class` commands. + * **Justification:** Full information about a student or class can be long, especially if a user has added remarks to them. + By enabling users to view detailed information about students and tuition classes on command, + this feature allows less information about students and tuition to be displayed on the student and tuition class lists displayed by default on the GUI. + Hence, users to have a less cluttered overview of their student and class lists, only accessing more detailed information when desired. + Furthermore, this allows more students and classes to be displayed in their respective lists at a time. +* **New Feature:** Command input history navigation ([#111](https://github.com/AY2122S1-CS2103T-T12-4/tp/pull/111)) + * **What it does:** Allows users to access and navigate through previously entered inputs using the `↑` `↓` arrow keys. + * **Justification:** TutAssistor uses CLI, in which users may want to access previously entered inputs to redo commands with some modifications, without having to retype an entire long command. + * **Acknowledgement:** This feature was inspired by a similar feature in [YaleChen299's ip](https://github.com/yalechen299/ip) for CS2103T, + though its implementation in this project is new. +* **Enhancement:** Command shortcuts ([#111](https://github.com/AY2122S1-CS2103T-T12-4/tp/pull/111)) + * **What it does:** Adds shorter alternatives to command keywords. + * **Justification:** While the full command keywords such as `addtoclass` or `timetable` may be more intuitive to understand, + shortcuts such as `atc` and `tt` can greatly speed up the workflow of users more familiar with the software and CLI. + +#### Contributions to team-based tasks +* Managed scheduling and agendas for weekly team meetings. +* Refactored `Person` related classes in codebase to `Student` classes for clarity. ([#123](https://github.com/AY2122S1-CS2103T-T12-4/tp/pull/123)) +* Managed all [software releases](https://github.com/AY2122S1-CS2103T-T12-4/tp/releases). + +#### Documentation +* **User Guide:** + * Added guide for `find` and `list` commands. ([#87](https://github.com/AY2122S1-CS2103T-T12-4/tp/pull/87)) + * Added information on command shortcuts. ([#111](https://github.com/AY2122S1-CS2103T-T12-4/tp/pull/111)) + * Added command format information guide. ([#117](https://github.com/AY2122S1-CS2103T-T12-4/tp/pull/117)) + * Adjusted overall grammar, formatting and clarity. ([#117](https://github.com/AY2122S1-CS2103T-T12-4/tp/pull/117)) +* **Developer Guide:** + * Added user stories and product uses cases. ([commit 341b3ef](https://github.com/AY2122S1-CS2103T-T12-4/tp/commit/341b3ef390b8fc7286c2f0bce3ac0a9662886ab6#diff-1a95edf069a4136e9cb71bee758b0dc86996f6051f0d438ec2c424557de7160b)) + * Added implementation details for CLI History Navigation. ([#224](https://github.com/AY2122S1-CS2103T-T12-4/tp/pull/224)) + +#### Contributions beyond project team +* Reported [7 bugs and issues](https://github.com/timiditi/ped/issues/) with non-trivial comments for other team's tP. diff --git a/src/main/java/seedu/address/MainApp.java b/src/main/java/seedu/address/MainApp.java index 4133aaa0151..fdc0f7464df 100644 --- a/src/main/java/seedu/address/MainApp.java +++ b/src/main/java/seedu/address/MainApp.java @@ -36,7 +36,7 @@ */ public class MainApp extends Application { - public static final Version VERSION = new Version(0, 2, 0, true); + public static final Version VERSION = new Version(1, 4, 0, false); private static final Logger logger = LogsCenter.getLogger(MainApp.class); @@ -65,6 +65,7 @@ public void init() throws Exception { logic = new LogicManager(model, storage); + ui = new UiManager(logic); } diff --git a/src/main/java/seedu/address/commons/core/Browser.java b/src/main/java/seedu/address/commons/core/Browser.java new file mode 100644 index 00000000000..180ac20c6c1 --- /dev/null +++ b/src/main/java/seedu/address/commons/core/Browser.java @@ -0,0 +1,62 @@ +package seedu.address.commons.core; + +import java.awt.Desktop; +import java.awt.GraphicsEnvironment; +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.logging.Logger; + +// @@author samyipsh-reused +// This feature to open user guide in the user's browser is borrowed from AY2122S1-CS2103T-T10-3. + +/** + * Represents the user's browser to open url links. + */ +public class Browser { + private static Logger logger = LogsCenter.getLogger(Browser.class); + + /** + * Opens URL inside user's Desktop Browser. + * Assume running platform supports desktop. Use {@link #isDisplayAndBrowseCompatible()} to check. + * No validity checks of valid url. + * + * @param url + */ + public static void openUrl(String url) { + if (!isDisplayAndBrowseCompatible()) { + return; + } + + Desktop d = Desktop.getDesktop(); + + logger.info("Opening URL in browser: " + url + "..."); + + try { + d.browse(new URI(url)); + } catch (URISyntaxException | IOException e) { + logger.severe("Unable to open URL in user browser" + e.getMessage()); + e.printStackTrace(); + } + } + + /** + * Checks if platform running code supports Desktop and Desktop.Action.BROWSE. + * Helper function used to verify platform compatibility before calls to {@link #openUrl(String)} + */ + public static boolean isDisplayAndBrowseCompatible() { + if (GraphicsEnvironment.isHeadless()) { + logger.warning("Platform does not support display.\n GraphicsEnviornment.isHeadless()"); + return false; + } + + Desktop d = Desktop.getDesktop(); + if (!d.isSupported(Desktop.Action.BROWSE)) { + logger.warning("Platform does not support browsing in desktop"); + return false; + } + + return true; + } +} +// @@author diff --git a/src/main/java/seedu/address/commons/core/Messages.java b/src/main/java/seedu/address/commons/core/Messages.java index 1deb3a1e469..8fce8f018b6 100644 --- a/src/main/java/seedu/address/commons/core/Messages.java +++ b/src/main/java/seedu/address/commons/core/Messages.java @@ -5,9 +5,20 @@ */ public class Messages { - public static final String MESSAGE_UNKNOWN_COMMAND = "Unknown command"; + public static final String MESSAGE_UNKNOWN_COMMAND = "Unknown command. Type 'help' for more information!"; public static final String MESSAGE_INVALID_COMMAND_FORMAT = "Invalid command format! \n%1$s"; - public static final String MESSAGE_INVALID_PERSON_DISPLAYED_INDEX = "The person index provided is invalid"; - public static final String MESSAGE_PERSONS_LISTED_OVERVIEW = "%1$d persons listed!"; + public static final String MESSAGE_INVALID_STUDENT_DISPLAYED_INDEX = "The student index provided is invalid."; + public static final String MESSAGE_INVALID_INDICES = "The indices provided are invalid. Indices should be " + + "positive and formatted with one whitespace between any two indices."; + public static final String MESSAGE_STUDENTS_LISTED_OVERVIEW = "%1$d students listed!"; + public static final String MESSAGE_CLASSES_LISTED_OVERVIEW = "%1$d classes listed!"; + public static final String MESSAGE_INVALID_CLASS_DISPLAYED_INDEX = "The class index provided is invalid."; + public static final String MESSAGE_STUDENT_NOT_FOUND = "This student is not found."; + public static final String MESSAGE_CLASS_NOT_FOUND = "This tuition class is not found."; + public static final String MESSAGE_STUDENT_NOT_IN_CLASS = "Student is not enrolled in this class."; + public static final String MESSAGE_NOT_EDITED = "Minimum one field to edit must be provided."; + public static final String MESSAGE_TIMESLOT_FORMAT = "The format for time slot should be Ddd HH:mm-HH:mm \n" + + "The day is case-sensitive, with the first letter capitalized and the time follows 24-hour format.\n" + + "Example: Mon 11:00-14:00"; } diff --git a/src/main/java/seedu/address/commons/util/FileUtil.java b/src/main/java/seedu/address/commons/util/FileUtil.java index b1e2767cdd9..a48d19bd2b4 100644 --- a/src/main/java/seedu/address/commons/util/FileUtil.java +++ b/src/main/java/seedu/address/commons/util/FileUtil.java @@ -5,6 +5,9 @@ import java.nio.file.InvalidPathException; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.logging.Logger; + +import seedu.address.commons.core.LogsCenter; /** * Writes and reads files @@ -12,13 +15,20 @@ public class FileUtil { private static final String CHARSET = "UTF-8"; + private static final Logger logger = LogsCenter.getLogger(FileUtil.class); + /** + * Returns boolean true if file exists. + * + * @param file The path of the file. + * @return True if the file exists at the path, false otherwise. + */ public static boolean isFileExists(Path file) { return Files.exists(file) && Files.isRegularFile(file); } /** - * Returns true if {@code path} can be converted into a {@code Path} via {@link Paths#get(String)}, + * Returns true if {@code path} can be converted into a {@code Path} via , * otherwise returns false. * @param path A string representing the file path. Cannot be null. */ @@ -69,6 +79,7 @@ public static void createParentDirsOfFile(Path file) throws IOException { * Assumes file exists */ public static String readFromFile(Path file) throws IOException { + logger.info(new String(Files.readAllBytes(file), CHARSET)); return new String(Files.readAllBytes(file), CHARSET); } diff --git a/src/main/java/seedu/address/commons/util/JsonUtil.java b/src/main/java/seedu/address/commons/util/JsonUtil.java index 8ef609f055d..8693506840a 100644 --- a/src/main/java/seedu/address/commons/util/JsonUtil.java +++ b/src/main/java/seedu/address/commons/util/JsonUtil.java @@ -90,6 +90,8 @@ public static void saveJsonFile(T jsonFile, Path filePath) throws IOExceptio serializeObjectToJsonFile(filePath, jsonFile); } + //addclass n/cs2101 l/10 c/2 ts/2pm s/ + //add n/John Doe p/98765432 e/johnd@example.com a/311, Clementi Ave 2, #02-25 t/friends t/owesMoney /** * Converts a given string representation of a JSON data to instance of a class diff --git a/src/main/java/seedu/address/logic/Logic.java b/src/main/java/seedu/address/logic/Logic.java index 92cd8fa605a..3cf3679fcf0 100644 --- a/src/main/java/seedu/address/logic/Logic.java +++ b/src/main/java/seedu/address/logic/Logic.java @@ -8,7 +8,8 @@ import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.logic.parser.exceptions.ParseException; import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.person.Person; +import seedu.address.model.student.Student; +import seedu.address.model.tuition.TuitionClass; /** * API of the Logic component @@ -30,8 +31,11 @@ public interface Logic { */ ReadOnlyAddressBook getAddressBook(); - /** Returns an unmodifiable view of the filtered list of persons */ - ObservableList getFilteredPersonList(); + /** Returns an unmodifiable view of the filtered list of students */ + ObservableList getFilteredStudentList(); + + /** Returns an unmodifiable view of the filtered list of tuition classes */ + ObservableList getFilteredTuitionList(); /** * Returns the user prefs' address book file path. @@ -47,4 +51,7 @@ public interface Logic { * Set the user prefs' GUI settings. */ void setGuiSettings(GuiSettings guiSettings); + + /** Returns an unmodifiable view of the filtered list of today tuition classes */ + ObservableList getTodayTuitionList(); } diff --git a/src/main/java/seedu/address/logic/LogicManager.java b/src/main/java/seedu/address/logic/LogicManager.java index 9d9c6d15bdc..421f23405d0 100644 --- a/src/main/java/seedu/address/logic/LogicManager.java +++ b/src/main/java/seedu/address/logic/LogicManager.java @@ -14,7 +14,8 @@ import seedu.address.logic.parser.exceptions.ParseException; import seedu.address.model.Model; import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.person.Person; +import seedu.address.model.student.Student; +import seedu.address.model.tuition.TuitionClass; import seedu.address.storage.Storage; /** @@ -60,8 +61,8 @@ public ReadOnlyAddressBook getAddressBook() { } @Override - public ObservableList getFilteredPersonList() { - return model.getFilteredPersonList(); + public ObservableList getFilteredStudentList() { + return model.getFilteredStudentList(); } @Override @@ -78,4 +79,18 @@ public GuiSettings getGuiSettings() { public void setGuiSettings(GuiSettings guiSettings) { model.setGuiSettings(guiSettings); } + + @Override + public ObservableList getFilteredTuitionList() { + return model.getFilteredTuitionList(); + } + + @Override + public ObservableList getTodayTuitionList() { + return model.getTodayTuitionList(); + } } + + + + diff --git a/src/main/java/seedu/address/logic/commands/AddClassCommand.java b/src/main/java/seedu/address/logic/commands/AddClassCommand.java new file mode 100644 index 00000000000..ad0e891d094 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/AddClassCommand.java @@ -0,0 +1,104 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_LIMIT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_REMARK; +import static seedu.address.logic.parser.CliSyntax.PREFIX_STUDENT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TIMESLOT; + +import java.util.ArrayList; +import java.util.List; + +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.student.Student; +import seedu.address.model.tag.Tag; +import seedu.address.model.tuition.StudentList; +import seedu.address.model.tuition.Timeslot; +import seedu.address.model.tuition.TuitionClass; + +public class AddClassCommand extends Command { + public static final String COMMAND_WORD = "addclass"; + public static final String SHORTCUT = "ac"; + + public static final String MESSAGE_SUCCESS = "New tuition class added: %1$s"; + public static final String MESSAGE_TIMESLOT_CONFLICT = "This time slot has already been taken"; + public static final String MESSAGE_STUDENT_NOT_FOUND = "The following students are not found: "; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Adds tuition class given name, limit, timeslot, and student \n" + + "Parameters: " + + PREFIX_NAME + "NAME " + + PREFIX_LIMIT + "LIMIT " + + PREFIX_TIMESLOT + "TIMESLOT " + + "[" + PREFIX_STUDENT + "NAME,...] " + + "[" + PREFIX_REMARK + "REMARK]\n" + + "Example: " + COMMAND_WORD + " n/Physics l/10 ts/Mon 11:00-14:00 s/Alex Yeoh,Bernice Yu"; + + private static final String MESSAGE_CLASS_LIMIT_EXCEEDED = "The following students are not " + + "added due to class limit: "; + + private TuitionClass toAdd; + + /** + * Creates an AddClassCommand to add the specified {@code TuitionClass} + */ + public AddClassCommand(TuitionClass tuitionClass) { + requireNonNull(tuitionClass); + toAdd = tuitionClass; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + Timeslot timeslot = toAdd.getTimeslot(); + List classList = model.getFilteredTuitionList(); + if (classList != null && timeslot.checkTimetableConflicts(classList)) { + throw new CommandException(MESSAGE_TIMESLOT_CONFLICT); + } + + //Set students in tuition class + ArrayList[] students = getStudents(model, toAdd.getStudentList().getStudents()); + toAdd.changeStudents(students[0]); + + //Update model with new tuition class + model.addTuition(toAdd); + model.updateFilteredTuitionList(Model.PREDICATE_SHOW_ALL_TUITIONS); + addClassToStudents(toAdd, students[2], model); + String message = this.getMessage(students[1], students[3]); + return new CommandResult(String.format(MESSAGE_SUCCESS, toAdd) + "\n" + message); + } + + + private void addClassToStudents(TuitionClass tuitionClass, ArrayList validStudentsAsPerson, Model model) { + for (Student person: validStudentsAsPerson) { + Student studentToChange = person; + person.addClass(tuitionClass); + person.addTag(new Tag(String.format("%s | %s", tuitionClass.getName().getName(), + tuitionClass.getTimeslot()))); + model.setStudent(studentToChange, person); + } + } + + + private ArrayList[] getStudents(Model model, ArrayList nowStudents) { + toAdd.changeStudents(new ArrayList<>()); + ArrayList[] students = AddToClassCommand.categorizeStudents(new StudentList(nowStudents), model, toAdd); + ArrayList[] returnValue = new ArrayList[]{students[2], students[1], students[0], students[3]}; + return returnValue; + } + private String getMessage(ArrayList invalidStudents, ArrayList notAdded) { + String message = ""; + boolean hasInvalidStudent = invalidStudents.size() >= 1; + boolean hasNotAddedStudent = notAdded.size() >= 1; + if (hasInvalidStudent) { + message += MESSAGE_STUDENT_NOT_FOUND + invalidStudents; + } + if (hasNotAddedStudent) { + message += "\n" + MESSAGE_CLASS_LIMIT_EXCEEDED + notAdded; + } + return message; + } +} + diff --git a/src/main/java/seedu/address/logic/commands/AddCommand.java b/src/main/java/seedu/address/logic/commands/AddCommand.java index 71656d7c5c8..149b0030e6d 100644 --- a/src/main/java/seedu/address/logic/commands/AddCommand.java +++ b/src/main/java/seedu/address/logic/commands/AddCommand.java @@ -5,56 +5,56 @@ import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; -import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; +import static seedu.address.logic.parser.CliSyntax.PREFIX_REMARK; import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.model.Model; -import seedu.address.model.person.Person; +import seedu.address.model.student.Student; /** - * Adds a person to the address book. + * Adds a student to the address book. */ public class AddCommand extends Command { public static final String COMMAND_WORD = "add"; + public static final String SHORTCUT = "a"; - public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a person to the address book. " + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a student to TutAssistor.\n" + "Parameters: " + PREFIX_NAME + "NAME " + PREFIX_PHONE + "PHONE " + PREFIX_EMAIL + "EMAIL " + PREFIX_ADDRESS + "ADDRESS " - + "[" + PREFIX_TAG + "TAG]...\n" + + "[" + PREFIX_REMARK + "REMARK]...\n" + "Example: " + COMMAND_WORD + " " + PREFIX_NAME + "John Doe " + PREFIX_PHONE + "98765432 " + PREFIX_EMAIL + "johnd@example.com " + PREFIX_ADDRESS + "311, Clementi Ave 2, #02-25 " - + PREFIX_TAG + "friends " - + PREFIX_TAG + "owesMoney"; + + PREFIX_REMARK + "Weak in geometry."; - public static final String MESSAGE_SUCCESS = "New person added: %1$s"; - public static final String MESSAGE_DUPLICATE_PERSON = "This person already exists in the address book"; + public static final String MESSAGE_SUCCESS = "New student added: %1$s"; + public static final String MESSAGE_DUPLICATE_STUDENT = "This student already exists in TutAssistor"; - private final Person toAdd; + private final Student toAdd; /** - * Creates an AddCommand to add the specified {@code Person} + * Creates an AddCommand to add the specified {@code Student} */ - public AddCommand(Person person) { - requireNonNull(person); - toAdd = person; + public AddCommand(Student student) { + requireNonNull(student); + toAdd = student; } @Override public CommandResult execute(Model model) throws CommandException { requireNonNull(model); - if (model.hasPerson(toAdd)) { - throw new CommandException(MESSAGE_DUPLICATE_PERSON); + if (model.hasStudent(toAdd)) { + throw new CommandException(MESSAGE_DUPLICATE_STUDENT); } - model.addPerson(toAdd); + model.addStudent(toAdd); return new CommandResult(String.format(MESSAGE_SUCCESS, toAdd)); } diff --git a/src/main/java/seedu/address/logic/commands/AddToClassCommand.java b/src/main/java/seedu/address/logic/commands/AddToClassCommand.java new file mode 100644 index 00000000000..74c5cac3a0d --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/AddToClassCommand.java @@ -0,0 +1,320 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_STUDENT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_STUDENT_INDEX; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TUITION_CLASS; + +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Logger; + +import seedu.address.commons.core.LogsCenter; +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.student.Name; +import seedu.address.model.student.Student; +import seedu.address.model.tag.Tag; +import seedu.address.model.tuition.StudentList; +import seedu.address.model.tuition.TuitionClass; + +/** + * Adds students to existing tuition class. + */ +public class AddToClassCommand extends Command { + public static final String COMMAND_WORD = "addtoclass"; + public static final String SHORTCUT = "atc"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Add students to existing class. " + + "\n" + + "Parameters: " + + PREFIX_STUDENT_INDEX + "STUDENT_INDEX [STUDENT_INDEX]..." + + PREFIX_TUITION_CLASS + "CLASS_INDEX or\n" + + PREFIX_STUDENT + "NAME[,NAME...] " + + PREFIX_TUITION_CLASS + "CLASS_INDEX" + + "\n" + "Example1: " + COMMAND_WORD + " " + PREFIX_STUDENT_INDEX + "1 2 " + PREFIX_TUITION_CLASS + "2" + + "\n" + "Example2: " + COMMAND_WORD + " " + PREFIX_STUDENT + "Felicia,James " + PREFIX_TUITION_CLASS + "3"; + public static final String MESSAGE_SUCCESS = "New student %1$s added to class."; + private static final String MESSAGE_STUDENT_EXISTS = "Student %1$s is already in the class"; + private static final String MESSAGE_CLASS_IS_FULL = "The following students are not " + + "added due to class limit: "; + private static final String MESSAGE_LIMIT_EXCEEDED = "These students cannot be added due to class limit. "; + private static final String MESSAGE_STUDENT_NOT_FOUND = "The following students are not " + + "found in the address book: "; + private static final String MESSAGE_NO_STUDENT_ADDED = "No student has been added."; + private static final Logger logger = LogsCenter.getLogger(AddToClassCommand.class); + private List studentIndices; + private Index classIndex; + private StudentList studentList; + private boolean isUsingIndex; + private List unfoundIndices = new ArrayList<>(); + + /** + * Constructor for AddToClass command using student index. + * @param studentIndices index of student to be added. + * @param classIndex index of class to be added to. + */ + public AddToClassCommand(List studentIndices, Index classIndex) { + this.classIndex = classIndex; + this.studentIndices = studentIndices; + this.isUsingIndex = true; + } + + /** + * Constructor for AddToClass command using student name. + * @param students index of student to be added. + * @param classIndex index of class to be added to. + */ + public AddToClassCommand(StudentList students, Index classIndex) { + this.classIndex = classIndex; + this.studentList = students; + this.isUsingIndex = false; + } + + /** + * Adds an existing student to an existing class. + * @param model {@code Model} which the command should operate on. + * @return a CommandResult to be shown to users. + * @throws CommandException if the student or class is not found, or the student is in the class already. + */ + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + //Check that the tuition class exists and is not full + TuitionClass tuitionClass = model.getTuitionClass(classIndex); + if (tuitionClass == null) { + throw new CommandException(Messages.MESSAGE_CLASS_NOT_FOUND); + } + boolean isClassFull = tuitionClass.isFull(); + if (isClassFull) { + throw new CommandException(MESSAGE_LIMIT_EXCEEDED); + } + + //Check whether the command is using index or names + if (!isUsingIndex) { + logger.info("Names are used to add students."); + return this.executeStudentName(model, tuitionClass); + } + logger.info("Indices are used to add students."); + return this.executeStudentIndex(model, tuitionClass); + } + + private void updateModel(Model model, TuitionClass tuitionClass, + TuitionClass modifiedClass, Student studentToAdd, Student studentToChange) { + model.setStudent(studentToChange, studentToAdd); + model.setTuition(tuitionClass, modifiedClass); + } + + /** + * Categorizes students into not found, already enrolled, added, and not added due to class size limit. + * @param studentList students to be added. + * @param model model containing all students and tuition classes. + * @param tuitionClass the tuition class students are added to. + * @return an array of arraylists containing students being divided to not found, already enrolled, added, + * and not added due to class size limit. + */ + public static ArrayList[] categorizeStudents(StudentList studentList, Model model, TuitionClass tuitionClass) { + ArrayList invalidStudentNames = new ArrayList<>(); + ArrayList newStudents = new ArrayList<>(); + ArrayList notAdded = new ArrayList<>(); + ArrayList validStudentNames = new ArrayList<>(); + ArrayList existingStudents = new ArrayList<>(); + validStudentNames.addAll(tuitionClass.getStudentList().getStudents()); + int limit = tuitionClass.getLimit().getLimit(); + for (String studentName: studentList.getStudents()) { + Student student = new Student(new Name(studentName)); + if (!model.hasStudent(student)) { + addInvalidStudentName(studentName, invalidStudentNames); + continue; + } + if (validStudentNames.contains(studentName)) { + addExistingStudentName(studentName, existingStudents, newStudents, model, student); + continue; + } + if (limit <= validStudentNames.size()) { + addToNotAdded(notAdded, studentName); + continue; + } + if (!newStudents.contains(model.getSameNameStudent(student))) { + addNewStudent(studentName, newStudents, model, validStudentNames, student); + } + } + ArrayList[] returnValue = new ArrayList[]{newStudents, invalidStudentNames, + validStudentNames, notAdded, existingStudents}; + return returnValue; + } + + private static void addInvalidStudentName(String studentName, ArrayList invalidStudentNames) { + if (!invalidStudentNames.contains(studentName)) { + invalidStudentNames.add(studentName); + } + } + + private static void addExistingStudentName(String studentName, ArrayList existingStudents, + ArrayList newStudents, Model model, Student student) { + if (!existingStudents.contains(studentName) + && !newStudents.contains(model.getSameNameStudent(student))) { + existingStudents.add(studentName); + } + } + + private static void addToNotAdded(ArrayList notAdded, String studentName) { + if (!notAdded.contains(studentName)) { + notAdded.add(studentName); + } + } + + private static void addNewStudent(String studentName, ArrayList newStudents, Model model, + ArrayList validStudentNames, Student student) { + newStudents.add(model.getSameNameStudent(student)); + validStudentNames.add(studentName); + } + + /** + * Executes the command if students are added using their names. + * @param model model containing all students and tuition classes. + * @param tuitionClass the tuition class students are added to. + * @return a CommandResult of the AddToClass command. + */ + private CommandResult executeStudentName(Model model, TuitionClass tuitionClass) { + ArrayList[] students = this.categorizeStudents(studentList, model, tuitionClass); + ArrayList newStudents = students[0]; + + if (newStudents.size() == 0) { + return new CommandResult(getMessage(students)); + } + TuitionClass modifiedClass = null; + for (Student student: newStudents) { + modifiedClass = model.addToClass(tuitionClass, student); + } + if (modifiedClass == null) { + return new CommandResult(getMessage(students)); + } + + String logStudentName = updateStudent(newStudents, tuitionClass, modifiedClass, model); + logger.info("Add students [" + logStudentName + "] to class [" + tuitionClass.getName() + "]"); + return new CommandResult(getMessage(students)); + } + + /** + * Updates information of new students enrolled in the tuition class. + * @param newStudents students to be updated. + * @param tuitionClass the original tuition class. + * @param modifiedClass the updated tuition class. + * @param model the model containing all students and tuition classes. + * @return log information. + */ + private String updateStudent(ArrayList newStudents, TuitionClass tuitionClass, + TuitionClass modifiedClass, Model model) { + String logStudentName = ""; + assert newStudents != null : "Students to be added should not be null."; + for (Student student : newStudents) { + Student studentToAdd = student; + Student studentToChange = student; + logStudentName += student.getNameString(); + if (!student.equals(newStudents.get(newStudents.size() - 1))) { + logStudentName += ", "; + } + studentToAdd.addClass(modifiedClass); + studentToAdd.addTag(new Tag(String.format("%s | %s", modifiedClass.getName().getName(), + modifiedClass.getTimeslot()))); + updateModel(model, tuitionClass, modifiedClass, studentToAdd, studentToChange); + } + return logStudentName; + } + + /** + * Executes the command if students are added using their indexes. + * @param model model containing all students and tuition classes + * @param tuitionClass the tuition class students are added to + * @return a CommandResult of the AddToClass command + */ + private CommandResult executeStudentIndex(Model model, TuitionClass tuitionClass) { + ArrayList studentNames = new ArrayList<>(); + for (Index index: studentIndices) { + Student student = model.getStudent(index); + if (student == null) { + this.unfoundIndices.add("Index " + index.getOneBased() + " "); + } else { + studentNames.add(student.getName().toString()); + } + } + this.studentList = new StudentList(studentNames); + return executeStudentName(model, tuitionClass); + } + + /** + * Produces the message to show to user. + * @param students an array of arraylists containing students being divided to not found, already enrolled, added, + * and not added due to class size limit + * @return the message as a String + */ + private String getMessage(ArrayList[] students) { + ArrayList invalidStudentNames = students[1]; + ArrayList newStudents = students[0]; + ArrayList notAdded = students[3]; + boolean limitExceeded = notAdded.size() > 0; + boolean hasInvalidNames = invalidStudentNames.size() > 0; + boolean noStudentAdded = newStudents.size() == 0; + boolean studentExists = students[4].size() > 0; + String message = ""; + ArrayList studentsAdded = getNewlyAddedStudentNames(newStudents); + if (noStudentAdded) { + message += MESSAGE_NO_STUDENT_ADDED + "\n"; + } else { + message += String.format(MESSAGE_SUCCESS, studentsAdded) + "\n"; + } + if (studentExists) { + message += String.format(MESSAGE_STUDENT_EXISTS, students[4]) + "\n"; + } + if (limitExceeded) { + message += MESSAGE_CLASS_IS_FULL + notAdded + "\n"; + } + if (hasInvalidNames) { + message += MESSAGE_STUDENT_NOT_FOUND + invalidStudentNames; + } + if (unfoundIndices.size() > 0) { + message += MESSAGE_STUDENT_NOT_FOUND + unfoundIndices; + } + return message; + } + + private ArrayList getNewlyAddedStudentNames(ArrayList newStudents) { + ArrayList studentsAdded = new ArrayList<>(); + for (Student student : newStudents) { + studentsAdded.add(student.getName().toString()); + } + return studentsAdded; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || !getClass().equals(o.getClass())) { + return false; + } + AddToClassCommand that = (AddToClassCommand) o; + + if (isUsingIndex != that.isUsingIndex) { + return false; + } + if (isUsingIndex) { + if (!studentIndices.equals(that.studentIndices)) { + return false; + } + } else { + if (!studentList.equals(that.studentList)) { + return false; + } + } + if (!classIndex.equals(that.classIndex)) { + return false; + } + return true; + } +} diff --git a/src/main/java/seedu/address/logic/commands/CommandResult.java b/src/main/java/seedu/address/logic/commands/CommandResult.java index 92f900b7916..1875a9a24f4 100644 --- a/src/main/java/seedu/address/logic/commands/CommandResult.java +++ b/src/main/java/seedu/address/logic/commands/CommandResult.java @@ -11,19 +11,29 @@ public class CommandResult { private final String feedbackToUser; - /** Help information should be shown to the user. */ - private final boolean showHelp; + public enum UiAction { + SHOW_HELP, + SHOW_TUITION_PAGE, + SHOW_STUDENT_PAGE, + SHOW_TODAY_TUITIONS_PAGE, + SHOW_TIMETABLE, + SET_TUITIONS_DEFAULT, + SET_STUDENTS_DEFAULT, + SET_TUITIONS_FILTERED, + SET_STUDENTS_FILTERED, + EXIT, + NONE + } - /** The application should exit. */ - private final boolean exit; + /** Action to be taken by UI component*/ + private final UiAction uiAction; /** * Constructs a {@code CommandResult} with the specified fields. */ - public CommandResult(String feedbackToUser, boolean showHelp, boolean exit) { + public CommandResult(String feedbackToUser, UiAction uiAction) { this.feedbackToUser = requireNonNull(feedbackToUser); - this.showHelp = showHelp; - this.exit = exit; + this.uiAction = requireNonNull(uiAction); } /** @@ -31,19 +41,15 @@ public CommandResult(String feedbackToUser, boolean showHelp, boolean exit) { * and other fields set to their default value. */ public CommandResult(String feedbackToUser) { - this(feedbackToUser, false, false); + this(feedbackToUser, UiAction.NONE); } public String getFeedbackToUser() { return feedbackToUser; } - public boolean isShowHelp() { - return showHelp; - } - - public boolean isExit() { - return exit; + public UiAction getUiAction() { + return this.uiAction; } @Override @@ -59,13 +65,12 @@ public boolean equals(Object other) { CommandResult otherCommandResult = (CommandResult) other; return feedbackToUser.equals(otherCommandResult.feedbackToUser) - && showHelp == otherCommandResult.showHelp - && exit == otherCommandResult.exit; + && this.uiAction == otherCommandResult.uiAction; } @Override public int hashCode() { - return Objects.hash(feedbackToUser, showHelp, exit); + return Objects.hash(feedbackToUser, uiAction); } } diff --git a/src/main/java/seedu/address/logic/commands/DeleteClassCommand.java b/src/main/java/seedu/address/logic/commands/DeleteClassCommand.java new file mode 100644 index 00000000000..67d4b561b53 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/DeleteClassCommand.java @@ -0,0 +1,100 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import java.util.ArrayList; +import java.util.List; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.student.Name; +import seedu.address.model.student.Student; +import seedu.address.model.tuition.TuitionClass; + +/** + * Deletes tuition classes from TutAssistor. + */ +public class DeleteClassCommand extends Command { + public static final String COMMAND_WORD = "deleteclass"; + public static final String SHORTCUT = "delc"; + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Deletes existing classes.\n" + + "Parameters: CLASS_INDEX [CLASS_INDEX]... (must be a positive integer)\n" + + "Example: " + COMMAND_WORD + " 1 2"; + public static final String MESSAGE_DELETE_CLASSES_SUCCESS = "Deleted Classes: %1$s.\n"; + public static final String MESSAGE_DELETE_CLASSES_FAILURE = "Classes at index : %1$s are not found."; + private List classIndices = new ArrayList<>(); + private List removed = new ArrayList(); + private List invalidClasses = new ArrayList<>(); + + /** + * Constructor for the DeleteClass command. + * + * @param classIndices The list of class indexes to be deleted. + */ + public DeleteClassCommand(List classIndices) { + this.classIndices = classIndices; + this.removed = new ArrayList<>(); + this.invalidClasses = new ArrayList<>(); + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + model.updateFilteredTuitionList(Model.PREDICATE_SHOW_ALL_TUITIONS); + for (Index currIndex : classIndices) { + TuitionClass classToDelete = model.getTuitionClass(currIndex); + if (classToDelete == null) { + invalidClasses.add(currIndex.getOneBased()); + continue; + } + removeClassFromEnrolledStudents(model, classToDelete); + removed.add(classToDelete.getName().name + "|" + classToDelete.getTimeslot()); + model.deleteTuition(classToDelete); + } + if (!invalidClasses.isEmpty() && removed.isEmpty()) { + throw new CommandException(String.format(MESSAGE_DELETE_CLASSES_FAILURE, invalidClasses)); + } + return new CommandResult(getMessage(removed, invalidClasses)); + } + + private static void removeClassFromEnrolledStudents(Model model, TuitionClass classToDelete) { + for (String name : classToDelete.getStudentList().getStudents()) { + if (name != null && model.getSameNameStudent(new Student(new Name(name))) != null) { + Student student = model.getSameNameStudent(new Student(new Name(name))); + Student updatedStudent = student.removeClass(classToDelete); + model.setStudent(student, updatedStudent); + } + } + } + + private String getMessage(List removed, List invalidClasses) { + String message = ""; + if (!removed.isEmpty()) { + message += String.format(MESSAGE_DELETE_CLASSES_SUCCESS, removed); + } + if (!invalidClasses.isEmpty()) { + message += String.format(MESSAGE_DELETE_CLASSES_FAILURE, invalidClasses); + } + return message; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + if (!(other instanceof DeleteClassCommand)) { + return false; + } + DeleteClassCommand e = (DeleteClassCommand) other; + List otherIndex = e.classIndices; + if (classIndices.size() != otherIndex.size()) { + return false; + } + for (int i = 0; i < classIndices.size(); i++) { + return classIndices.get(i).equals(otherIndex.get(i)); + } + return false; + } +} diff --git a/src/main/java/seedu/address/logic/commands/DeleteCommand.java b/src/main/java/seedu/address/logic/commands/DeleteCommand.java index 02fd256acba..e700fdf95ba 100644 --- a/src/main/java/seedu/address/logic/commands/DeleteCommand.java +++ b/src/main/java/seedu/address/logic/commands/DeleteCommand.java @@ -2,52 +2,90 @@ import static java.util.Objects.requireNonNull; +import java.util.ArrayList; import java.util.List; -import seedu.address.commons.core.Messages; import seedu.address.commons.core.index.Index; import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.model.Model; -import seedu.address.model.person.Person; +import seedu.address.model.student.Student; +import seedu.address.model.tuition.TuitionClass; /** - * Deletes a person identified using it's displayed index from the address book. + * Deletes students from TutAssistor. */ public class DeleteCommand extends Command { public static final String COMMAND_WORD = "delete"; - + public static final String SHORTCUT = "del"; public static final String MESSAGE_USAGE = COMMAND_WORD - + ": Deletes the person identified by the index number used in the displayed person list.\n" - + "Parameters: INDEX (must be a positive integer)\n" - + "Example: " + COMMAND_WORD + " 1"; - - public static final String MESSAGE_DELETE_PERSON_SUCCESS = "Deleted Person: %1$s"; + + ": Deletes the students identified by the index numbers used in the Students list.\n" + + "Parameters: STUDENT_INDEX [STUDENT INDEX]... (must be a positive integer)\n" + + "Example: " + COMMAND_WORD + " 1 2"; - private final Index targetIndex; + public static final String MESSAGE_DELETE_STUDENTS_SUCCESS = "Deleted Students: %1$s.\n"; + public static final String MESSAGE_DELETE_STUDENTS_FAILURE = "Students at index: %1$s are not found."; + private List classIndices = new ArrayList<>(); + private List removed = new ArrayList<>(); + private List invalidStudents = new ArrayList<>(); - public DeleteCommand(Index targetIndex) { - this.targetIndex = targetIndex; + /** + * Constructor for DeleteCommand using a list of student indexes. + * + * @param studentIndices List of student indexes. + */ + public DeleteCommand(List studentIndices) { + this.classIndices = studentIndices; + this.removed = new ArrayList<>(); + this.invalidStudents = new ArrayList<>(); } @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_PERSON_DISPLAYED_INDEX); + model.updateFilteredStudentList(Model.PREDICATE_SHOW_ALL_STUDENTS); + for (Index studentIndex: classIndices) { + Student studentToDelete = model.getStudent(studentIndex); + if (studentToDelete == null) { + invalidStudents.add(studentIndex.getOneBased()); + continue; + } + for (Integer tuitionClassId : studentToDelete.getClassesArray()) { + TuitionClass tuitionClass = model.getClassById(tuitionClassId); + if (tuitionClass != null) { + TuitionClass updatedClass = tuitionClass.removeStudent(studentToDelete); + model.setTuition(tuitionClass, updatedClass); + } + } + removed.add(studentToDelete.getName().fullName); + model.deleteStudent(studentToDelete); } - - Person personToDelete = lastShownList.get(targetIndex.getZeroBased()); - model.deletePerson(personToDelete); - return new CommandResult(String.format(MESSAGE_DELETE_PERSON_SUCCESS, personToDelete)); + if (!invalidStudents.isEmpty() && removed.isEmpty()) { + throw new CommandException(String.format(MESSAGE_DELETE_STUDENTS_FAILURE, invalidStudents)); + } + String feedback = (!removed.isEmpty() + ? String.format(MESSAGE_DELETE_STUDENTS_SUCCESS, removed) : "") + + (!invalidStudents.isEmpty() + ? String.format(MESSAGE_DELETE_STUDENTS_FAILURE, invalidStudents) : ""); + return new CommandResult(feedback); } @Override public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof DeleteCommand // instanceof handles nulls - && targetIndex.equals(((DeleteCommand) other).targetIndex)); // state check + if (other == this) { + return true; + } + if (!(other instanceof DeleteCommand)) { + return false; + } + DeleteCommand e = (DeleteCommand) other; + List otherIndex = e.classIndices; + if (classIndices.size() != otherIndex.size()) { + return false; + } + for (int i = 0; i < classIndices.size(); i++) { + return classIndices.get(i).equals(otherIndex.get(i)); + } + return false; } } diff --git a/src/main/java/seedu/address/logic/commands/EditClassCommand.java b/src/main/java/seedu/address/logic/commands/EditClassCommand.java new file mode 100644 index 00000000000..d24be7ba438 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/EditClassCommand.java @@ -0,0 +1,205 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_LIMIT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TIMESLOT; + +import java.util.List; +import java.util.Optional; +import java.util.logging.Logger; +import java.util.stream.Collectors; + +import seedu.address.commons.core.LogsCenter; +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.commons.util.CollectionUtil; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.student.Name; +import seedu.address.model.student.Student; +import seedu.address.model.tuition.ClassLimit; +import seedu.address.model.tuition.ClassName; +import seedu.address.model.tuition.Timeslot; +import seedu.address.model.tuition.TuitionClass; + +public class EditClassCommand extends Command { + public static final String COMMAND_WORD = "editclass"; + public static final String SHORTCUT = "ec"; + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Edits details of the class identified by the class index. " + + "At least one parameter must be specified.\n" + + "Parameters: CLASS_INDEX " + + "[" + PREFIX_NAME + "NAME] " + + "[" + PREFIX_TIMESLOT + "TIMESLOT] " + + "[" + PREFIX_LIMIT + "LIMIT]\n" + + "Example: " + COMMAND_WORD + " 1 " + + PREFIX_NAME + "Physics " + + PREFIX_TIMESLOT + "Mon 11:00-11:30 " + + PREFIX_LIMIT + "20"; + + public static final String MESSAGE_EDIT_CLASS_SUCCESS = "Edited Class: %1$s"; + public static final String MESSAGE_NO_CLASS_CHANGES = "Class details are already up to date."; + public static final String MESSAGE_INVALID_CLASS_LIMIT = "Minimum limit of class is %1$s."; + public static final String MESSAGE_INVALID_CLASS_SLOT = "This timeslot is already taken. Check timetable!"; + private static final Logger logger = LogsCenter.getLogger(EditClassCommand.class); + private final Index index; + private final EditClassDescriptor editClassDescriptor; + + /** + * Constructor for EditClassCommand using class index and an EditClassDescriptor class. + * + * @param index Class index that indicates the class to be edited. + * @param editClass Details of the class to be edited. + */ + public EditClassCommand(Index index, EditClassDescriptor editClass) { + requireNonNull(index); + requireNonNull(editClass); + this.index = index; + this.editClassDescriptor = editClass; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + model.updateFilteredTuitionList(Model.PREDICATE_SHOW_ALL_TUITIONS); + TuitionClass classToEdit = model.getTuitionClass(index); + if (classToEdit == null) { + throw new CommandException(Messages.MESSAGE_INVALID_CLASS_DISPLAYED_INDEX); + } + TuitionClass editedClass = createEditedClass(classToEdit, editClassDescriptor); + if (classToEdit.sameClassDetails(editedClass)) { + throw new CommandException(MESSAGE_NO_CLASS_CHANGES); + } + //check minimum limit of class = current number of students + if (editedClass.getLimit().getLimit() < classToEdit.getStudentCount()) { + throw new CommandException(String.format(MESSAGE_INVALID_CLASS_LIMIT, classToEdit.getStudentCount())); + } + //check if updated timeslot is taken + List otherClasses = model.getFilteredTuitionList() + .stream().filter(x -> x.getId() != editedClass.getId()).collect(Collectors.toList()); + if (!editedClass.getTimeslot().equals(classToEdit.getTimeslot()) + && editedClass.getTimeslot().checkTimetableConflicts(otherClasses)) { + throw new CommandException(MESSAGE_INVALID_CLASS_SLOT); + } + if (!editedClass.getTimeslot().equals(classToEdit.getTimeslot()) + || !editedClass.getName().equals(classToEdit.getName())) { + updateStudentsInClass(classToEdit, editedClass, model); + } + model.setTuition(classToEdit, editedClass); + logger.info(String.format("Updated tuition class details from %s to %s", classToEdit, editedClass)); + return new CommandResult(String.format(MESSAGE_EDIT_CLASS_SUCCESS, editedClass)); + } + + private static void updateStudentsInClass(TuitionClass classToEdit, TuitionClass editedClass, Model model) { + for (String name : classToEdit.getStudentList().getStudents()) { + Student student = model.getSameNameStudent(new Student(new Name(name))); + if (student != null) { + Student updatedStudent = student.updateTag(classToEdit, editedClass); + model.setStudent(student, updatedStudent); + } + } + } + + private static TuitionClass createEditedClass(TuitionClass classToEdit, EditClassDescriptor editClassDescriptor) { + assert classToEdit != null; + + ClassName updatedName = editClassDescriptor.getName().orElse(classToEdit.getName()); + ClassLimit updatedLimit = editClassDescriptor.getLimit().orElse(classToEdit.getLimit()); + Timeslot updatedSlot = editClassDescriptor.getTimeslot().orElse(classToEdit.getTimeslot()); + + return new TuitionClass(updatedName, updatedLimit, updatedSlot, + classToEdit.getStudentList(), classToEdit.getRemark(), classToEdit.getId()); + } + + /** + * Stores the details to edit the person with. Each non-empty field value will replace the + * corresponding field value of the person. + */ + public static class EditClassDescriptor { + private ClassName name; + private ClassLimit limit; + private Timeslot timeslot; + + public EditClassDescriptor() {} + + /** + * Copy constructor. + * A defensive copy of {@code tags} is used internally. + */ + public EditClassDescriptor(EditClassDescriptor toCopy) { + setClassName(toCopy.name); + setLimit(toCopy.limit); + setTimeslot(toCopy.timeslot); + } + + /** + * Returns true if at least one field is edited. + */ + public boolean isAnyFieldEdited() { + return CollectionUtil.isAnyNonNull(name, limit, timeslot); + } + + public void setClassName(ClassName name) { + this.name = name; + } + + public Optional getName() { + return Optional.ofNullable(name); + } + + public void setLimit(ClassLimit limit) { + this.limit = limit; + } + + public Optional getLimit() { + return Optional.ofNullable(limit); + } + + public void setTimeslot(Timeslot timeslot) { + this.timeslot = timeslot; + } + + public Optional getTimeslot() { + return Optional.ofNullable(timeslot); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof EditClassCommand.EditClassDescriptor)) { + return false; + } + + // state check + EditClassDescriptor e = (EditClassDescriptor) other; + + return getName().equals(e.getName()) + && getLimit().equals(e.getLimit()) + && getTimeslot().equals(e.getTimeslot()); + } + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + // instanceof handles nulls + if (!(other instanceof EditClassCommand)) { + return false; + } + // state check + EditClassCommand e = (EditClassCommand) other; + return index.equals(e.index) + && editClassDescriptor.equals(e.editClassDescriptor); + } +} + + diff --git a/src/main/java/seedu/address/logic/commands/EditCommand.java b/src/main/java/seedu/address/logic/commands/EditCommand.java index 7e36114902f..4ac78d46829 100644 --- a/src/main/java/seedu/address/logic/commands/EditCommand.java +++ b/src/main/java/seedu/address/logic/commands/EditCommand.java @@ -5,151 +5,146 @@ import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; -import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; -import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; -import java.util.Collections; -import java.util.HashSet; import java.util.List; import java.util.Optional; -import java.util.Set; +import java.util.logging.Logger; +import java.util.stream.Collectors; +import seedu.address.commons.core.LogsCenter; import seedu.address.commons.core.Messages; import seedu.address.commons.core.index.Index; import seedu.address.commons.util.CollectionUtil; import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.model.Model; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Person; -import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; +import seedu.address.model.student.Address; +import seedu.address.model.student.Email; +import seedu.address.model.student.Name; +import seedu.address.model.student.Phone; +import seedu.address.model.student.Student; +import seedu.address.model.tuition.TuitionClass; /** - * Edits the details of an existing person in the address book. + * Edits the details of an existing student in TutAssistor. */ public class EditCommand extends Command { public static final String COMMAND_WORD = "edit"; - - public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits the details of the person identified " - + "by the index number used in the displayed person list. " - + "Existing values will be overwritten by the input values.\n" - + "Parameters: INDEX (must be a positive integer) " + public static final String SHORTCUT = "e"; + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Edits details of the student identified by the index number. " + + "At least one parameter must be specified.\n" + + "Parameters: STUDENT_INDEX " + "[" + PREFIX_NAME + "NAME] " + "[" + PREFIX_PHONE + "PHONE] " + "[" + PREFIX_EMAIL + "EMAIL] " - + "[" + PREFIX_ADDRESS + "ADDRESS] " - + "[" + PREFIX_TAG + "TAG]...\n" + + "[" + PREFIX_ADDRESS + "ADDRESS]\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_EDIT_STUDENT_SUCCESS = "Edited Student: %1$s"; + public static final String MESSAGE_NO_STUDENT_CHANGES = "Student details are already up to date."; + public static final String MESSAGE_DUPLICATE_STUDENT = "This student already exists in the address book."; + private static final Logger logger = LogsCenter.getLogger(EditCommand.class); private final Index index; - private final EditPersonDescriptor editPersonDescriptor; + private final EditStudentDescriptor editStudentDescriptor; /** - * @param index of the person in the filtered person list to edit - * @param editPersonDescriptor details to edit the person with + * Constructor for EditCommand using student index and EditStudentDescriptor student. + * + * @param index Student index that indicates the student to be edited. + * @param editStudentDescriptor Details of the student to be edited. */ - public EditCommand(Index index, EditPersonDescriptor editPersonDescriptor) { + public EditCommand(Index index, EditStudentDescriptor editStudentDescriptor) { requireNonNull(index); - requireNonNull(editPersonDescriptor); - + requireNonNull(editStudentDescriptor); this.index = index; - this.editPersonDescriptor = new EditPersonDescriptor(editPersonDescriptor); + this.editStudentDescriptor = new EditStudentDescriptor(editStudentDescriptor); } @Override public CommandResult execute(Model model) throws CommandException { requireNonNull(model); - List lastShownList = model.getFilteredPersonList(); - - if (index.getZeroBased() >= lastShownList.size()) { - throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + model.updateFilteredStudentList(Model.PREDICATE_SHOW_ALL_STUDENTS); + Student studentToEdit = model.getStudent(index); + if (studentToEdit == null) { + throw new CommandException(Messages.MESSAGE_INVALID_STUDENT_DISPLAYED_INDEX); + } + Student editedStudent = createEditedStudent(studentToEdit, editStudentDescriptor); + //stronger check for all fields - name address phone email + if (studentToEdit.equals(editedStudent)) { + throw new CommandException(MESSAGE_NO_STUDENT_CHANGES); } - Person personToEdit = lastShownList.get(index.getZeroBased()); - Person editedPerson = createEditedPerson(personToEdit, editPersonDescriptor); - - if (!personToEdit.isSamePerson(editedPerson) && model.hasPerson(editedPerson)) { - throw new CommandException(MESSAGE_DUPLICATE_PERSON); + //checks if name is unchanged or name is changed to that of a student who exists in database + if (studentToEdit.isSameStudent(editedStudent) || model.hasStudent(editedStudent)) { + throw new CommandException(MESSAGE_DUPLICATE_STUDENT); + } + //if the name is changed, the student list of all classes the student is enrolled in has to be updated + if (!studentToEdit.isSameStudent(editedStudent)) { + updateStudentInTuitionClass(model, editedStudent, studentToEdit); } + logger.info(String.format("Edited student: Details changed from %s to %s", studentToEdit, editedStudent)); + model.setStudent(studentToEdit, editedStudent); + return new CommandResult(String.format(MESSAGE_EDIT_STUDENT_SUCCESS, editedStudent)); + } - model.setPerson(personToEdit, editedPerson); - model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); - return new CommandResult(String.format(MESSAGE_EDIT_PERSON_SUCCESS, editedPerson)); + private static void updateStudentInTuitionClass(Model model, Student editedStudent, Student studentToEdit) { + List enrolledClasses = editedStudent + .getClasses().getClasses().stream().map(model::getClassById).collect(Collectors.toList()); + for (TuitionClass tuitionClass: enrolledClasses) { + if (tuitionClass != null) { + tuitionClass.updateStudent(studentToEdit, editedStudent); + logger.info(String.format("student name in class %s to %s", studentToEdit, editedStudent)); + } + } } /** - * Creates and returns a {@code Person} with the details of {@code personToEdit} - * edited with {@code editPersonDescriptor}. + * Creates and returns a {@code Student} with the details of {@code studentToEdit} + * edited with {@code editStudentDescriptor}. */ - private static Person createEditedPerson(Person personToEdit, EditPersonDescriptor editPersonDescriptor) { - assert personToEdit != null; + private static Student createEditedStudent(Student studentToEdit, EditStudentDescriptor editStudentDescriptor) { + assert studentToEdit != null; - Name updatedName = editPersonDescriptor.getName().orElse(personToEdit.getName()); - Phone updatedPhone = editPersonDescriptor.getPhone().orElse(personToEdit.getPhone()); - Email updatedEmail = editPersonDescriptor.getEmail().orElse(personToEdit.getEmail()); - Address updatedAddress = editPersonDescriptor.getAddress().orElse(personToEdit.getAddress()); - Set updatedTags = editPersonDescriptor.getTags().orElse(personToEdit.getTags()); + Name updatedName = editStudentDescriptor.getName().orElse(studentToEdit.getName()); + Phone updatedPhone = editStudentDescriptor.getPhone().orElse(studentToEdit.getPhone()); + Email updatedEmail = editStudentDescriptor.getEmail().orElse(studentToEdit.getEmail()); + Address updatedAddress = editStudentDescriptor.getAddress().orElse(studentToEdit.getAddress()); - return new Person(updatedName, updatedPhone, updatedEmail, updatedAddress, updatedTags); - } - - @Override - public boolean equals(Object other) { - // short circuit if same object - if (other == this) { - return true; - } - - // instanceof handles nulls - if (!(other instanceof EditCommand)) { - return false; - } - - // state check - EditCommand e = (EditCommand) other; - return index.equals(e.index) - && editPersonDescriptor.equals(e.editPersonDescriptor); + return new Student(updatedName, updatedPhone, updatedEmail, updatedAddress, + studentToEdit.getRemark(), studentToEdit.getTags(), studentToEdit.getClasses()); } /** - * Stores the details to edit the person with. Each non-empty field value will replace the - * corresponding field value of the person. + * Stores the details to edit the student with. Each non-empty field value will replace the + * corresponding field value of the student. */ - public static class EditPersonDescriptor { + public static class EditStudentDescriptor { private Name name; private Phone phone; private Email email; private Address address; - private Set tags; - public EditPersonDescriptor() {} + public EditStudentDescriptor() {} /** * Copy constructor. * A defensive copy of {@code tags} is used internally. */ - public EditPersonDescriptor(EditPersonDescriptor toCopy) { + public EditStudentDescriptor(EditStudentDescriptor toCopy) { setName(toCopy.name); setPhone(toCopy.phone); setEmail(toCopy.email); setAddress(toCopy.address); - setTags(toCopy.tags); } /** * Returns true if at least one field is edited. */ public boolean isAnyFieldEdited() { - return CollectionUtil.isAnyNonNull(name, phone, email, address, tags); + return CollectionUtil.isAnyNonNull(name, phone, email, address); } public void setName(Name name) { @@ -184,22 +179,6 @@ public Optional
getAddress() { return Optional.ofNullable(address); } - /** - * Sets {@code tags} to this object's {@code tags}. - * A defensive copy of {@code tags} is used internally. - */ - public void setTags(Set tags) { - this.tags = (tags != null) ? new HashSet<>(tags) : null; - } - - /** - * Returns an unmodifiable tag set, which throws {@code UnsupportedOperationException} - * if modification is attempted. - * Returns {@code Optional#empty()} if {@code tags} is null. - */ - public Optional> getTags() { - return (tags != null) ? Optional.of(Collections.unmodifiableSet(tags)) : Optional.empty(); - } @Override public boolean equals(Object other) { @@ -209,18 +188,33 @@ public boolean equals(Object other) { } // instanceof handles nulls - if (!(other instanceof EditPersonDescriptor)) { + if (!(other instanceof EditStudentDescriptor)) { return false; } // state check - EditPersonDescriptor e = (EditPersonDescriptor) other; + EditStudentDescriptor e = (EditStudentDescriptor) other; return getName().equals(e.getName()) && getPhone().equals(e.getPhone()) && getEmail().equals(e.getEmail()) - && getAddress().equals(e.getAddress()) - && getTags().equals(e.getTags()); + && getAddress().equals(e.getAddress()); } } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + // instanceof handles nulls + if (!(other instanceof EditCommand)) { + return false; + } + // state check + EditCommand e = (EditCommand) other; + return index.equals(e.index) + && editStudentDescriptor.equals(e.editStudentDescriptor); + } } diff --git a/src/main/java/seedu/address/logic/commands/ExitCommand.java b/src/main/java/seedu/address/logic/commands/ExitCommand.java index 3dd85a8ba90..bdc5d15c8f4 100644 --- a/src/main/java/seedu/address/logic/commands/ExitCommand.java +++ b/src/main/java/seedu/address/logic/commands/ExitCommand.java @@ -13,7 +13,7 @@ public class ExitCommand extends Command { @Override public CommandResult execute(Model model) { - return new CommandResult(MESSAGE_EXIT_ACKNOWLEDGEMENT, false, true); + return new CommandResult(MESSAGE_EXIT_ACKNOWLEDGEMENT, CommandResult.UiAction.EXIT); } } diff --git a/src/main/java/seedu/address/logic/commands/FindClassCommand.java b/src/main/java/seedu/address/logic/commands/FindClassCommand.java new file mode 100644 index 00000000000..4a684e5b7ea --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/FindClassCommand.java @@ -0,0 +1,46 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import seedu.address.commons.core.Messages; +import seedu.address.model.Model; +import seedu.address.model.NameContainsKeywordsPredicate; + +/** + * Finds and lists all classes in address book whose name contains any of the argument keywords. + * Keyword matching is case insensitive. + */ +public class FindClassCommand extends Command { + + public static final String COMMAND_WORD = "findclass"; + public static final String SHORTCUT = "fc"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all classes 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 + "english math science"; + + private final NameContainsKeywordsPredicate predicate; + + public FindClassCommand(NameContainsKeywordsPredicate predicate) { + this.predicate = predicate; + } + + @Override + public CommandResult execute(Model model) { + requireNonNull(model); + model.updateFilteredTuitionList(predicate); + return new CommandResult( + String.format(Messages.MESSAGE_CLASSES_LISTED_OVERVIEW, model.getFilteredTuitionList().size()), + CommandResult.UiAction.SET_TUITIONS_FILTERED); + + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof FindClassCommand // instanceof handles nulls + && predicate.equals(((FindClassCommand) other).predicate)); // state check + } + +} diff --git a/src/main/java/seedu/address/logic/commands/FindCommand.java b/src/main/java/seedu/address/logic/commands/FindCommand.java index d6b19b0a0de..ff5525dd9fc 100644 --- a/src/main/java/seedu/address/logic/commands/FindCommand.java +++ b/src/main/java/seedu/address/logic/commands/FindCommand.java @@ -4,17 +4,18 @@ import seedu.address.commons.core.Messages; import seedu.address.model.Model; -import seedu.address.model.person.NameContainsKeywordsPredicate; +import seedu.address.model.NameContainsKeywordsPredicate; /** - * Finds and lists all persons in address book whose name contains any of the argument keywords. + * Finds and lists all students in address book whose name contains any of the argument keywords. * Keyword matching is case insensitive. */ public class FindCommand extends Command { public static final String COMMAND_WORD = "find"; + public static final String SHORTCUT = "f"; - public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all persons whose names contain any of " + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all students whose names contain any of " + "the specified keywords (case-insensitive) and displays them as a list with index numbers.\n" + "Parameters: KEYWORD [MORE_KEYWORDS]...\n" + "Example: " + COMMAND_WORD + " alice bob charlie"; @@ -28,9 +29,10 @@ public FindCommand(NameContainsKeywordsPredicate predicate) { @Override public CommandResult execute(Model model) { requireNonNull(model); - model.updateFilteredPersonList(predicate); + model.updateFilteredStudentList(predicate); return new CommandResult( - String.format(Messages.MESSAGE_PERSONS_LISTED_OVERVIEW, model.getFilteredPersonList().size())); + String.format(Messages.MESSAGE_STUDENTS_LISTED_OVERVIEW, model.getFilteredStudentList().size()), + CommandResult.UiAction.SET_STUDENTS_FILTERED); } @Override diff --git a/src/main/java/seedu/address/logic/commands/HelpCommand.java b/src/main/java/seedu/address/logic/commands/HelpCommand.java index bf824f91bd0..8f67ef7e0d0 100644 --- a/src/main/java/seedu/address/logic/commands/HelpCommand.java +++ b/src/main/java/seedu/address/logic/commands/HelpCommand.java @@ -8,6 +8,7 @@ public class HelpCommand extends Command { public static final String COMMAND_WORD = "help"; + public static final String SHORTCUT = "h"; public static final String MESSAGE_USAGE = COMMAND_WORD + ": Shows program usage instructions.\n" + "Example: " + COMMAND_WORD; @@ -16,6 +17,6 @@ public class HelpCommand extends Command { @Override public CommandResult execute(Model model) { - return new CommandResult(SHOWING_HELP_MESSAGE, true, false); + return new CommandResult(SHOWING_HELP_MESSAGE, CommandResult.UiAction.SHOW_HELP); } } diff --git a/src/main/java/seedu/address/logic/commands/ListClassCommand.java b/src/main/java/seedu/address/logic/commands/ListClassCommand.java new file mode 100644 index 00000000000..983cb0842f1 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/ListClassCommand.java @@ -0,0 +1,24 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_TUITIONS; + +import seedu.address.model.Model; + +/** + * Lists all classes in the address book to the user. + */ +public class ListClassCommand extends Command { + public static final String COMMAND_WORD = "listclass"; + public static final String SHORTCUT = "lc"; + + public static final String MESSAGE_SUCCESS = "Listing all classes"; + + @Override + public CommandResult execute(Model model) { + requireNonNull(model); + model.updateFilteredTuitionList(PREDICATE_SHOW_ALL_TUITIONS); + return new CommandResult(MESSAGE_SUCCESS, CommandResult.UiAction.SET_TUITIONS_DEFAULT); + } + +} diff --git a/src/main/java/seedu/address/logic/commands/ListCommand.java b/src/main/java/seedu/address/logic/commands/ListCommand.java index 84be6ad2596..5dbf1145a09 100644 --- a/src/main/java/seedu/address/logic/commands/ListCommand.java +++ b/src/main/java/seedu/address/logic/commands/ListCommand.java @@ -1,24 +1,25 @@ package seedu.address.logic.commands; import static java.util.Objects.requireNonNull; -import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_STUDENTS; import seedu.address.model.Model; /** - * Lists all persons in the address book to the user. + * Lists all students in the address book to the user. */ public class ListCommand extends Command { public static final String COMMAND_WORD = "list"; + public static final String SHORTCUT = "l"; - public static final String MESSAGE_SUCCESS = "Listed all persons"; + public static final String MESSAGE_SUCCESS = "Listing all students"; @Override public CommandResult execute(Model model) { requireNonNull(model); - model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); - return new CommandResult(MESSAGE_SUCCESS); + model.updateFilteredStudentList(PREDICATE_SHOW_ALL_STUDENTS); + return new CommandResult(MESSAGE_SUCCESS, CommandResult.UiAction.SET_STUDENTS_DEFAULT); } } diff --git a/src/main/java/seedu/address/logic/commands/RemarkClassCommand.java b/src/main/java/seedu/address/logic/commands/RemarkClassCommand.java new file mode 100644 index 00000000000..651f06a7cd6 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/RemarkClassCommand.java @@ -0,0 +1,101 @@ +package seedu.address.logic.commands; + +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_TUITIONS; + +import java.util.List; +import java.util.logging.Logger; + +import seedu.address.commons.core.LogsCenter; +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.student.Remark; +import seedu.address.model.tuition.TuitionClass; +import seedu.address.ui.UiManager; + +/** + * Changes the remark of an existing tuition class in the TutAssistor. + */ +public class RemarkClassCommand extends Command { + + public static final String COMMAND_WORD = "remarkclass"; + public static final String SHORTCUT = "rec"; + + public static final String MESSAGE_UPDATE_REMARK_SUCCESS = "Remark updated for Tuition Class: %1$s"; + public static final String MESSAGE_DELETE_REMARK_SUCCESS = "Removed remark from Tuition Class: %1$s"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits the remark of the tuition class identified " + + "by the index number used in the last tuition classes listing.\n" + + "Parameters: CLASS_INDEX (must be a positive integer)\n" + + "Example: " + COMMAND_WORD + " 1 "; + + private static final Logger logger = LogsCenter.getLogger(RemarkClassCommand.class); + + private final Index index; + + /** + * @param index of the tuition class in the filtered tuition class list to edit the remark + */ + public RemarkClassCommand(Index index) { + requireAllNonNull(index); + + this.index = index; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + model.updateFilteredTuitionList(PREDICATE_SHOW_ALL_TUITIONS); + List lastShownList = model.getFilteredTuitionList(); + + if (index.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_CLASS_DISPLAYED_INDEX); + } + + TuitionClass classToEdit = lastShownList.get(index.getZeroBased()); + + String name = String.valueOf(classToEdit.getName()); + String remarkToEdit = String.valueOf(classToEdit.getRemark()); + Remark newRemark = UiManager.showRemarkEditor(name, remarkToEdit); + + TuitionClass editedClass = new TuitionClass(classToEdit.getName(), classToEdit.getLimit(), + classToEdit.getTimeslot(), classToEdit.getStudentList(), newRemark, classToEdit.getId()); + + logger.info("Remarks updated from: [" + remarkToEdit + "] to [" + newRemark.toString() + "]"); + + model.setTuition(classToEdit, editedClass); + model.updateFilteredTuitionList(PREDICATE_SHOW_ALL_TUITIONS); + + return new CommandResult(generateSuccessMessage(editedClass)); + } + + /** + * Generates the message for the command execution outcome. + * @param classToEdit The tuition class with the new remark. + * @return A message that informs the user whether the remark is updated successfully or deleted. + */ + private String generateSuccessMessage(TuitionClass classToEdit) { + String remark = String.valueOf(classToEdit.getRemark()); + String message = !remark.isEmpty() ? MESSAGE_UPDATE_REMARK_SUCCESS : MESSAGE_DELETE_REMARK_SUCCESS; + return String.format(message, classToEdit); + } + + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof RemarkClassCommand)) { + return false; + } + + // state check + RemarkClassCommand e = (RemarkClassCommand) other; + return index.equals(e.index); + } +} diff --git a/src/main/java/seedu/address/logic/commands/RemarkCommand.java b/src/main/java/seedu/address/logic/commands/RemarkCommand.java new file mode 100644 index 00000000000..88dd079ea75 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/RemarkCommand.java @@ -0,0 +1,103 @@ +package seedu.address.logic.commands; + +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_STUDENTS; + +import java.util.List; +import java.util.logging.Logger; + +import seedu.address.commons.core.LogsCenter; +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.student.Remark; +import seedu.address.model.student.Student; +import seedu.address.ui.UiManager; + +/** + * Changes the remark of an existing student in the TutAssistor. + */ +public class RemarkCommand extends Command { + + public static final String COMMAND_WORD = "remark"; + public static final String SHORTCUT = "re"; + + public static final String MESSAGE_UPDATE_REMARK_SUCCESS = "Remark updated for Student: %1$s"; + public static final String MESSAGE_DELETE_REMARK_SUCCESS = "Removed remark from Student: %1$s"; + + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits the remark of the student identified " + + "by the index number used in the last student listing.\n" + + "Parameters: STUDENT_INDEX (must be a positive integer)\n" + + "Example: " + COMMAND_WORD + " 1 "; + + private static final Logger logger = LogsCenter.getLogger(RemarkCommand.class); + + private final Index index; + + /** + * @param index of the student in the filtered student list to edit the remark + * @param remark of the student to be updated to + */ + public RemarkCommand(Index index, Remark remark) { + requireAllNonNull(index, remark); + + this.index = index; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + List lastShownList = model.getFilteredStudentList(); + + if (index.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_STUDENT_DISPLAYED_INDEX); + } + + Student studentToEdit = lastShownList.get(index.getZeroBased()); + + String name = String.valueOf(studentToEdit.getName()); + String remarkToEdit = String.valueOf(studentToEdit.getRemark()); + Remark newRemark = UiManager.showRemarkEditor(name, remarkToEdit); + + Student editedStudent = new Student( + studentToEdit.getName(), studentToEdit.getPhone(), studentToEdit.getEmail(), + studentToEdit.getAddress(), newRemark, studentToEdit.getTags(), studentToEdit.getClasses()); + + logger.info("Remark Editor closed."); + + model.setStudent(studentToEdit, editedStudent); + model.updateFilteredStudentList(PREDICATE_SHOW_ALL_STUDENTS); + + return new CommandResult(generateSuccessMessage(editedStudent)); + } + + /** + * Generates the message for the command execution outcome. + * @param studentToEdit The student with the new remark. + * @return A message that informs the user whether the remark is updated successfully or deleted. + */ + private String generateSuccessMessage(Student studentToEdit) { + String remark = String.valueOf(studentToEdit.getRemark()); + String message = !remark.isEmpty() ? MESSAGE_UPDATE_REMARK_SUCCESS : MESSAGE_DELETE_REMARK_SUCCESS; + return String.format(message, studentToEdit); + } + + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof RemarkCommand)) { + return false; + } + + // state check + RemarkCommand e = (RemarkCommand) other; + return index.equals(e.index); + } +} diff --git a/src/main/java/seedu/address/logic/commands/RemoveStudentCommand.java b/src/main/java/seedu/address/logic/commands/RemoveStudentCommand.java new file mode 100644 index 00000000000..a8f9d6e328a --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/RemoveStudentCommand.java @@ -0,0 +1,110 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_STUDENT_INDEX; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TUITION_CLASS; + +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Logger; + +import seedu.address.commons.core.LogsCenter; +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.student.Student; +import seedu.address.model.tuition.TuitionClass; + +/** + * Removes students from an existing tuition class in TutAssistor. + */ +public class RemoveStudentCommand extends Command { + public static final String COMMAND_WORD = "remove"; + public static final String SHORTCUT = "rm"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Removes a student from a tuition class.\n" + + "Parameters: " + + PREFIX_STUDENT_INDEX + "STUDENT_INDEX [STUDENT_INDEX]... " + + PREFIX_TUITION_CLASS + "CLASS_INDEX\n" + + "Example: " + COMMAND_WORD + " " + PREFIX_STUDENT_INDEX + "1 2 " + PREFIX_TUITION_CLASS + "2"; + + public static final String MESSAGE_REMOVE_STUDENT_SUCCESS = "Removed students: %1$s from class: %2$s"; + public static final String MESSAGE_REMOVE_STUDENT_FAILURE = "Students: %1$s are not found in class: %2$s"; + private static final Logger logger = LogsCenter.getLogger(RemoveStudentCommand.class); + private final List studentIndexes; + private final Index classIndex; + private List studentsNotInClass = new ArrayList<>(); + private List studentsRemoved = new ArrayList<>(); + + /** + * Constructor for RemoveStudent command using student index and class index. + * + * @param studentIndexes Index of the student to be removed from the class. + * @param classIndex Index of the class. + */ + public RemoveStudentCommand(List studentIndexes, Index classIndex) { + this.studentIndexes = studentIndexes; + this.classIndex = classIndex; + this.studentsNotInClass = new ArrayList<>(); + this.studentsRemoved = new ArrayList<>(); + } + + /** + * Executes the command and returns the result message. + * + * @param model {@code Model} which the command should operate on. + * @return feedback message of the operation result for display + * @throws CommandException If an error occurs during command execution. + */ + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + TuitionClass tuitionClass = model.getTuitionClass(classIndex); + if (tuitionClass == null) { + throw new CommandException(Messages.MESSAGE_CLASS_NOT_FOUND); + } + for (Index currIndex : studentIndexes) { + Student studentToRemove = model.getStudent(currIndex); + if (studentToRemove == null) { + throw new CommandException(Messages.MESSAGE_STUDENT_NOT_FOUND); + } + if (tuitionClass.containsStudent(studentToRemove)) { + updateStudentInClass(model, studentToRemove, tuitionClass); + } else { + updateInvalidStudents(studentToRemove); + } + } + String feedback = (!studentsRemoved.isEmpty() + ? String.format(MESSAGE_REMOVE_STUDENT_SUCCESS + "\n", studentsRemoved, tuitionClass.getName()) : "") + + (!studentsNotInClass.isEmpty() + ? String.format(MESSAGE_REMOVE_STUDENT_FAILURE, studentsNotInClass, tuitionClass.getName()) : ""); + return new CommandResult(feedback); + } + + private void updateStudentInClass(Model model, Student studentToRemove, TuitionClass tuitionClass) { + TuitionClass updatedClass = tuitionClass.removeStudent(studentToRemove); + Student updatedStudent = studentToRemove.removeClass(tuitionClass); + if (updatedClass != null && updatedStudent != null) { + model.setTuition(tuitionClass, updatedClass); + model.setStudent(studentToRemove, updatedStudent); + studentsRemoved.add(studentToRemove.getName().fullName); + } + logger.info(String.format("Students to be removed %s from class: %s", studentsRemoved, tuitionClass)); + + } + + private boolean updateInvalidStudents(Student studentToRemove) { + return studentsNotInClass.add(studentToRemove.getName().fullName); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof RemoveStudentCommand // instanceof handles nulls + && classIndex.equals(((RemoveStudentCommand) other).classIndex) + && studentIndexes.containsAll(((RemoveStudentCommand) other).studentIndexes)); + } +} + diff --git a/src/main/java/seedu/address/logic/commands/SortCommand.java b/src/main/java/seedu/address/logic/commands/SortCommand.java new file mode 100644 index 00000000000..da25ea7dd9f --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/SortCommand.java @@ -0,0 +1,101 @@ +package seedu.address.logic.commands; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_SORT_ORDER; + +import java.util.logging.Logger; + +import seedu.address.commons.core.LogsCenter; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.logic.parser.SortCommandParser; +import seedu.address.model.Model; + +/** + * Sorts the tuition class list. + */ +public class SortCommand extends Command { + public static final String COMMAND_WORD = "sort"; + public static final String SHORTCUT = "s"; + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Sort the tuition class list.\n" + + "Parameters: [" + PREFIX_SORT_ORDER + "ORDER] \n" + + "ORDER includes 'o/time', 'o/asc' (for ascending alphabetical order), " + + "and 'o/desc' (for descending alphabetical order)\n" + + "Example1: " + COMMAND_WORD + " " + PREFIX_SORT_ORDER + "asc\n" + + "Example2: " + COMMAND_WORD; + public static final String MESSAGE_SORTED = "The tuition class list is now sorted by %1$s order"; + private static final Logger logger = LogsCenter.getLogger(SortCommand.class); + private SortCommandParser.Order order; + + /** + * Constructs a SortCommand with a sort order. + * @param order the order to sort. + */ + public SortCommand(SortCommandParser.Order order) { + logger.info("Start sorting by " + getSuccessMessage(order.toString()) + " order"); + this.order = order; + } + + /** + * Sorts the tuition class list by time or alphabetical order. + * @param model {@code Model} which the command should operate on. + * @return a CommandResult of the sort command. + * @throws CommandException default path throws a CommandException if + * the sorting order is not defined by the software + */ + @Override + public CommandResult execute(Model model) throws CommandException { + SortCommandParser.Order resultOrder; + assert this.order != null : "Order to sort should not be null"; + switch (this.order) { + case ASCENDING: + resultOrder = SortCommandParser.Order.ASCENDING; + sort(SortCommandParser.Order.ASCENDING, model); + break; + case DESCENDING: + resultOrder = SortCommandParser.Order.DESCENDING; + sort(SortCommandParser.Order.DESCENDING, model); + break; + case TIME: + resultOrder = SortCommandParser.Order.TIME; + sort(SortCommandParser.Order.TIME, model); + break; + default: + throw new CommandException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, SortCommand.MESSAGE_USAGE)); + } + return new CommandResult(String.format(MESSAGE_SORTED, getSuccessMessage(resultOrder.toString()))); + } + + private void sort(SortCommandParser.Order order, Model model) { + model.sort(order); + model.updateFilteredStudentList(Model.PREDICATE_SHOW_ALL_STUDENTS); + } + + private String getSuccessMessage(String order) { + switch (order) { + case "time": + return "time"; + case "asc": + return "ascending"; + case "desc": + return "descending"; + default: + //This line should not be reached + return order; + } + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + SortCommand that = (SortCommand) o; + + return order == that.order; + } +} diff --git a/src/main/java/seedu/address/logic/commands/TimetableCommand.java b/src/main/java/seedu/address/logic/commands/TimetableCommand.java new file mode 100644 index 00000000000..ea28be0a1ee --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/TimetableCommand.java @@ -0,0 +1,40 @@ +package seedu.address.logic.commands; + +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.tuition.UniqueTuitionList; + +/** + * Executes a TimetableCommand to display a timetable on screen. + */ +public class TimetableCommand extends Command { + public static final String COMMAND_WORD = "timetable"; + public static final String SHORTCUT = "tt"; + public static final String NO_CLASS = "No class found."; + public static final String MESSAGE_USAGE = COMMAND_WORD + ": show timetable\n" + + "Parameters: nil\n" + + "Example: " + COMMAND_WORD; + + @Override + public CommandResult execute(Model model) throws CommandException { + if (UniqueTuitionList.getMostRecentTuitionClasses().size() == 0) { + return new CommandResult(NO_CLASS); + } + return new CommandResult("Timetable shown.", + CommandResult.UiAction.SHOW_TIMETABLE); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (obj instanceof TimetableCommand) { + return true; + } + return false; + } +} diff --git a/src/main/java/seedu/address/logic/commands/ViewClassCommand.java b/src/main/java/seedu/address/logic/commands/ViewClassCommand.java new file mode 100644 index 00000000000..1aa40d1a4b3 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/ViewClassCommand.java @@ -0,0 +1,56 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import java.util.List; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.tuition.TuitionClass; + +/** + * Command to display full information on a Tuition Class to user. + */ +public class ViewClassCommand extends Command { + public static final String COMMAND_WORD = "class"; + public static final String SHORTCUT = "vc"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Shows full information on a class of " + + "a specified index number.\n" + + "Parameters: INDEX (must be a positive integer)\n" + + "Example: " + COMMAND_WORD + " 1"; + + public static final String MESSAGE_SUCCESS = "Viewing details of class: "; + + private final Index index; + + /** + * Constructor for a command to view a tuition class + * @param index Index of tuition class to be viewed. + */ + public ViewClassCommand(Index index) { + requireNonNull(index); + this.index = index; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + List tuitionClassList = model.getFilteredTuitionList(); + + if (index.getOneBased() > tuitionClassList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_CLASS_DISPLAYED_INDEX); + } + + TuitionClass tuitionClass = tuitionClassList.get(index.getZeroBased()); + + TuitionClass.setMostRecentTo(tuitionClass); + + return new CommandResult(MESSAGE_SUCCESS + tuitionClass.getName(), + CommandResult.UiAction.SHOW_TUITION_PAGE); + } + +} diff --git a/src/main/java/seedu/address/logic/commands/ViewCommand.java b/src/main/java/seedu/address/logic/commands/ViewCommand.java new file mode 100644 index 00000000000..6080b997737 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/ViewCommand.java @@ -0,0 +1,56 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import java.util.List; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.student.Student; + +/** + * Command to display full information of a student to user. + */ +public class ViewCommand extends Command { + public static final String COMMAND_WORD = "student"; + public static final String SHORTCUT = "vs"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Shows full information on a student of " + + "a specified index number.\n" + + "Parameters: INDEX (must be a positive integer)\n" + + "Example: " + COMMAND_WORD + " 1"; + + public static final String MESSAGE_SUCCESS = "Viewing details of student: "; + + private final Index index; + + /** + * Constructor for a command to view a student. + * @param index Index of student to be viewed. + */ + public ViewCommand(Index index) { + requireNonNull(index); + this.index = index; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + List studentList = model.getFilteredStudentList(); + + if (index.getOneBased() > studentList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_STUDENT_DISPLAYED_INDEX); + } + + Student student = studentList.get(index.getZeroBased()); + + Student.setMostRecentTo(student); + + return new CommandResult(MESSAGE_SUCCESS + student.getName(), + CommandResult.UiAction.SHOW_STUDENT_PAGE); + } + +} diff --git a/src/main/java/seedu/address/logic/commands/ViewTodayTuitionCommand.java b/src/main/java/seedu/address/logic/commands/ViewTodayTuitionCommand.java new file mode 100644 index 00000000000..ab8c7e5092c --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/ViewTodayTuitionCommand.java @@ -0,0 +1,36 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import java.util.List; + +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.tuition.TuitionClass; + +public class ViewTodayTuitionCommand extends Command { + public static final String COMMAND_WORD = "today"; + public static final String SHORTCUT = "td"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Shows all tuition classes for today " + + "Example: " + COMMAND_WORD; + + public static final String MESSAGE_SUCCESS = "Today's tuition classes are shown below \n" + + "Number of tuition classes today: "; + + /** + * Constructor for a command to view today tuition classes. + */ + public ViewTodayTuitionCommand() {} + + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + List todayTuitionList = model.getTodayTuitionList(); + + return new CommandResult(MESSAGE_SUCCESS + todayTuitionList.size(), + CommandResult.UiAction.SHOW_TODAY_TUITIONS_PAGE); + } +} diff --git a/src/main/java/seedu/address/logic/commands/exceptions/CommandException.java b/src/main/java/seedu/address/logic/commands/exceptions/CommandException.java index a16bd14f2cd..6071fc473ef 100644 --- a/src/main/java/seedu/address/logic/commands/exceptions/CommandException.java +++ b/src/main/java/seedu/address/logic/commands/exceptions/CommandException.java @@ -1,7 +1,7 @@ package seedu.address.logic.commands.exceptions; /** - * Represents an error which occurs during execution of a {@link Command}. + * Represents an error which occurs during execution of a Command. */ public class CommandException extends Exception { public CommandException(String message) { diff --git a/src/main/java/seedu/address/logic/parser/AddClassCommandParser.java b/src/main/java/seedu/address/logic/parser/AddClassCommandParser.java new file mode 100644 index 00000000000..8f469dc2187 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/AddClassCommandParser.java @@ -0,0 +1,72 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_LIMIT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_REMARK; +import static seedu.address.logic.parser.CliSyntax.PREFIX_STUDENT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TIMESLOT; + +import java.util.ArrayList; +import java.util.logging.Logger; +import java.util.stream.Stream; + +import seedu.address.commons.core.LogsCenter; +import seedu.address.logic.commands.AddClassCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.student.Remark; +import seedu.address.model.tuition.ClassLimit; +import seedu.address.model.tuition.ClassName; +import seedu.address.model.tuition.StudentList; +import seedu.address.model.tuition.Timeslot; +import seedu.address.model.tuition.TuitionClass; + +public class AddClassCommandParser implements Parser { + + private static final Logger logger = LogsCenter.getLogger(AddClassCommand.class); + /** + * Parses the given {@code String} of arguments in the context of the AddClassCommand + * and returns an AddCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public AddClassCommand parse(String args) throws ParseException { + boolean hasStudents = false; + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_LIMIT, PREFIX_TIMESLOT, + PREFIX_STUDENT, PREFIX_REMARK); + + if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_LIMIT, PREFIX_TIMESLOT) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + AddClassCommand.MESSAGE_USAGE)); + } + if (arePrefixesPresent(argMultimap, PREFIX_STUDENT)) { + hasStudents = true; + } + try { + ClassName name = ParserUtil.parseClassName(argMultimap.getValue(PREFIX_NAME).get()); + ClassLimit limit = ParserUtil.parseLimit(argMultimap.getValue(PREFIX_LIMIT).get()); + Timeslot timeslot = ParserUtil.parseTimeslot(argMultimap.getValue(PREFIX_TIMESLOT).get()); + StudentList student = hasStudents ? ParserUtil.parseStudent(argMultimap.getAllValues(PREFIX_STUDENT)) + : new StudentList(new ArrayList<>()); + Remark remark = ParserUtil.parseRemark(argMultimap.getOptionalValue(PREFIX_REMARK).get()); + TuitionClass tuitionClass = new TuitionClass(name, limit, timeslot, student, remark); + logger.info("AddClassCommandParser " + tuitionClass); + return new AddClassCommand(tuitionClass); + + } catch (ParseException pe) { + String displayMessage = String.format(MESSAGE_INVALID_COMMAND_FORMAT, pe.getMessage()) + + "\n" + AddClassCommand.MESSAGE_USAGE; + throw new ParseException(displayMessage); + } + } + + /** + * 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/AddCommandParser.java b/src/main/java/seedu/address/logic/parser/AddCommandParser.java index 3b8bfa035e8..53ad05705c6 100644 --- a/src/main/java/seedu/address/logic/parser/AddCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/AddCommandParser.java @@ -5,18 +5,22 @@ import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; -import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; +import static seedu.address.logic.parser.CliSyntax.PREFIX_REMARK; +import java.util.ArrayList; +import java.util.HashSet; import java.util.Set; import java.util.stream.Stream; import seedu.address.logic.commands.AddCommand; import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Person; -import seedu.address.model.person.Phone; +import seedu.address.model.student.Address; +import seedu.address.model.student.Classes; +import seedu.address.model.student.Email; +import seedu.address.model.student.Name; +import seedu.address.model.student.Phone; +import seedu.address.model.student.Remark; +import seedu.address.model.student.Student; import seedu.address.model.tag.Tag; /** @@ -31,7 +35,8 @@ 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_REMARK); if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_ADDRESS, PREFIX_PHONE, PREFIX_EMAIL) || !argMultimap.getPreamble().isEmpty()) { @@ -42,11 +47,13 @@ public AddCommand parse(String args) throws ParseException { 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)); + Set tagList = new HashSet<>(); + Remark remark = ParserUtil.parseRemark(argMultimap.getOptionalValue(PREFIX_REMARK).get()); + Classes classes = new Classes(new ArrayList()); - Person person = new Person(name, phone, email, address, tagList); + Student student = new Student(name, phone, email, address, remark, tagList, classes); - return new AddCommand(person); + return new AddCommand(student); } /** diff --git a/src/main/java/seedu/address/logic/parser/AddToClassCommandParser.java b/src/main/java/seedu/address/logic/parser/AddToClassCommandParser.java new file mode 100644 index 00000000000..64da11d1ea6 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/AddToClassCommandParser.java @@ -0,0 +1,58 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_STUDENT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_STUDENT_INDEX; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TUITION_CLASS; + +import java.util.List; +import java.util.stream.Stream; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.AddToClassCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.tuition.StudentList; + +/** + * Parses input arguments and creates a new AddToClassCommand object + */ +public class AddToClassCommandParser implements Parser { + + @Override + public AddToClassCommand parse(String args) throws ParseException { + //Two ArgumentMultimap for checking whether student names or indices are used + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_STUDENT_INDEX, PREFIX_TUITION_CLASS); + ArgumentMultimap argMultimapName = + ArgumentTokenizer.tokenize(args, PREFIX_STUDENT, PREFIX_TUITION_CLASS); + + if (!arePrefixesPresent(argMultimap, PREFIX_STUDENT_INDEX, PREFIX_TUITION_CLASS)) { + if (!arePrefixesPresent(argMultimapName, PREFIX_STUDENT, PREFIX_TUITION_CLASS) + || !argMultimapName.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + AddToClassCommand.MESSAGE_USAGE)); + } + //Student names are used + StudentList student = ParserUtil.parseStudent(argMultimapName.getAllValues(PREFIX_STUDENT)); + Index classIndex = ParserUtil.parseIndex(argMultimapName.getValue(PREFIX_TUITION_CLASS).get()); + return new AddToClassCommand(student, classIndex); + } + + if (!argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + AddToClassCommand.MESSAGE_USAGE)); + } + //Student indices are used + List studentIndex = ParserUtil.parseIndices(argMultimap.getAllValues(PREFIX_STUDENT_INDEX)); + Index classIndex = ParserUtil.parseIndex(argMultimap.getValue(PREFIX_TUITION_CLASS).get()); + return new AddToClassCommand(studentIndex, classIndex); + } + + /** + * Returns true if none of the prefixes contains empty {@code Optional} values in the given + * {@code ArgumentMultimap}. + */ + private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); + } +} diff --git a/src/main/java/seedu/address/logic/parser/AddressBookParser.java b/src/main/java/seedu/address/logic/parser/AddressBookParser.java index 1e466792b46..3e85ce8eb64 100644 --- a/src/main/java/seedu/address/logic/parser/AddressBookParser.java +++ b/src/main/java/seedu/address/logic/parser/AddressBookParser.java @@ -6,15 +6,29 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; +import seedu.address.logic.commands.AddClassCommand; import seedu.address.logic.commands.AddCommand; +import seedu.address.logic.commands.AddToClassCommand; import seedu.address.logic.commands.ClearCommand; import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.DeleteClassCommand; import seedu.address.logic.commands.DeleteCommand; +import seedu.address.logic.commands.EditClassCommand; import seedu.address.logic.commands.EditCommand; import seedu.address.logic.commands.ExitCommand; +import seedu.address.logic.commands.FindClassCommand; import seedu.address.logic.commands.FindCommand; import seedu.address.logic.commands.HelpCommand; +import seedu.address.logic.commands.ListClassCommand; import seedu.address.logic.commands.ListCommand; +import seedu.address.logic.commands.RemarkClassCommand; +import seedu.address.logic.commands.RemarkCommand; +import seedu.address.logic.commands.RemoveStudentCommand; +import seedu.address.logic.commands.SortCommand; +import seedu.address.logic.commands.TimetableCommand; +import seedu.address.logic.commands.ViewClassCommand; +import seedu.address.logic.commands.ViewCommand; +import seedu.address.logic.commands.ViewTodayTuitionCommand; import seedu.address.logic.parser.exceptions.ParseException; /** @@ -45,29 +59,91 @@ public Command parseCommand(String userInput) throws ParseException { switch (commandWord) { case AddCommand.COMMAND_WORD: + case AddCommand.SHORTCUT: return new AddCommandParser().parse(arguments); case EditCommand.COMMAND_WORD: + case EditCommand.SHORTCUT: return new EditCommandParser().parse(arguments); case DeleteCommand.COMMAND_WORD: + case DeleteCommand.SHORTCUT: return new DeleteCommandParser().parse(arguments); case ClearCommand.COMMAND_WORD: return new ClearCommand(); case FindCommand.COMMAND_WORD: + case FindCommand.SHORTCUT: return new FindCommandParser().parse(arguments); + case FindClassCommand.COMMAND_WORD: + case FindClassCommand.SHORTCUT: + return new FindClassCommandParser().parse(arguments); + case ListCommand.COMMAND_WORD: + case ListCommand.SHORTCUT: return new ListCommand(); + case ListClassCommand.COMMAND_WORD: + case ListClassCommand.SHORTCUT: + return new ListClassCommand(); + case ExitCommand.COMMAND_WORD: return new ExitCommand(); case HelpCommand.COMMAND_WORD: + case HelpCommand.SHORTCUT: return new HelpCommand(); + case RemarkCommand.COMMAND_WORD: + case RemarkCommand.SHORTCUT: + return new RemarkCommandParser().parse(arguments); + + case RemarkClassCommand.COMMAND_WORD: + case RemarkClassCommand.SHORTCUT: + return new RemarkClassCommandParser().parse(arguments); + + case AddClassCommand.COMMAND_WORD: + case AddClassCommand.SHORTCUT: + return new AddClassCommandParser().parse(arguments); + + case AddToClassCommand.COMMAND_WORD: + case AddToClassCommand.SHORTCUT: + return new AddToClassCommandParser().parse(arguments); + + case DeleteClassCommand.COMMAND_WORD: + case DeleteClassCommand.SHORTCUT: + return new DeleteClassCommandParser().parse(arguments); + + case ViewCommand.COMMAND_WORD: + case ViewCommand.SHORTCUT: + return new ViewCommandParser().parse(arguments); + + case ViewClassCommand.COMMAND_WORD: + case ViewClassCommand.SHORTCUT: + return new ViewClassCommandParser().parse(arguments); + + case RemoveStudentCommand.COMMAND_WORD: + case RemoveStudentCommand.SHORTCUT: + return new RemoveStudentCommandParser().parse(arguments); + + case SortCommand.COMMAND_WORD: + case SortCommand.SHORTCUT: + return new SortCommandParser().parse(arguments); + + case ViewTodayTuitionCommand.COMMAND_WORD: + case ViewTodayTuitionCommand.SHORTCUT: + return new ViewTodayTuitionCommandParser().parse(arguments); + + case TimetableCommand.COMMAND_WORD: + case TimetableCommand.SHORTCUT: + return new TimetableParser().parse(arguments); + + case EditClassCommand.COMMAND_WORD: + case EditClassCommand.SHORTCUT: + return new EditClassCommandParser().parse(arguments); + default: throw new ParseException(MESSAGE_UNKNOWN_COMMAND); } diff --git a/src/main/java/seedu/address/logic/parser/ArgumentMultimap.java b/src/main/java/seedu/address/logic/parser/ArgumentMultimap.java index 954c8e18f8e..24ea715f061 100644 --- a/src/main/java/seedu/address/logic/parser/ArgumentMultimap.java +++ b/src/main/java/seedu/address/logic/parser/ArgumentMultimap.java @@ -39,6 +39,17 @@ public Optional getValue(Prefix prefix) { return values.isEmpty() ? Optional.empty() : Optional.of(values.get(values.size() - 1)); } + /** + * Returns the last value of an optional {@code prefix}. + */ + public Optional getOptionalValue(Prefix prefix) { + if (!argMultimap.containsKey(prefix)) { + return Optional.of(""); + } + List values = getAllValues(prefix); + return Optional.of(values.get(values.size() - 1)); + } + /** * Returns all values of {@code prefix}. * If the prefix does not exist or has no values, this will return an empty list. diff --git a/src/main/java/seedu/address/logic/parser/CliSyntax.java b/src/main/java/seedu/address/logic/parser/CliSyntax.java index 75b1a9bf119..4ab0b08cfb2 100644 --- a/src/main/java/seedu/address/logic/parser/CliSyntax.java +++ b/src/main/java/seedu/address/logic/parser/CliSyntax.java @@ -11,5 +11,11 @@ public class CliSyntax { 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_REMARK = new Prefix("r/"); + public static final Prefix PREFIX_LIMIT = new Prefix("l/"); + public static final Prefix PREFIX_TIMESLOT = new Prefix("ts/"); + public static final Prefix PREFIX_STUDENT = new Prefix("s/"); + public static final Prefix PREFIX_STUDENT_INDEX = new Prefix("si/"); + public static final Prefix PREFIX_TUITION_CLASS = new Prefix("tc/"); + public static final Prefix PREFIX_SORT_ORDER = new Prefix("o/"); } diff --git a/src/main/java/seedu/address/logic/parser/DeleteClassCommandParser.java b/src/main/java/seedu/address/logic/parser/DeleteClassCommandParser.java new file mode 100644 index 00000000000..1a2b462fbd2 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/DeleteClassCommandParser.java @@ -0,0 +1,34 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import java.util.ArrayList; +import java.util.List; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.DeleteClassCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a new DeleteClassCommand object. + */ +public class DeleteClassCommandParser implements Parser { + /** + * Parses {@code userInput} into a DeleteClassCcommand and returns it. + * + * @param userInput The indices input by user. + * @throws ParseException if {@code userInput} does not conform the expected format + */ + @Override + public DeleteClassCommand parse(String userInput) throws ParseException { + try { + List argList = new ArrayList<>(); + argList.add(userInput.trim()); + List classes = ParserUtil.parseIndices(argList); + return new DeleteClassCommand(classes); + } catch (ParseException pe) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteClassCommand.MESSAGE_USAGE), pe); + } + } +} diff --git a/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java b/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java index 522b93081cc..5b8dbf47ce6 100644 --- a/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java @@ -2,28 +2,34 @@ import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import java.util.ArrayList; +import java.util.List; + import seedu.address.commons.core.index.Index; import seedu.address.logic.commands.DeleteCommand; import seedu.address.logic.parser.exceptions.ParseException; /** - * Parses input arguments and creates a new DeleteCommand object + * Parses input arguments and creates a new DeleteCommand object. */ public class DeleteCommandParser implements Parser { /** * Parses the given {@code String} of arguments in the context of the DeleteCommand * and returns a DeleteCommand object for execution. + * + * @param args The indices input by user. * @throws ParseException if the user input does not conform the expected format */ public DeleteCommand parse(String args) throws ParseException { try { - Index index = ParserUtil.parseIndex(args); - return new DeleteCommand(index); + List argList = new ArrayList(); + argList.add(args.trim()); + List students = ParserUtil.parseIndices(argList); + return new DeleteCommand(students); } catch (ParseException pe) { throw new ParseException( String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteCommand.MESSAGE_USAGE), pe); } } - } diff --git a/src/main/java/seedu/address/logic/parser/EditClassCommandParser.java b/src/main/java/seedu/address/logic/parser/EditClassCommandParser.java new file mode 100644 index 00000000000..7c3ef5d352b --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/EditClassCommandParser.java @@ -0,0 +1,84 @@ +package seedu.address.logic.parser; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_LIMIT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TIMESLOT; + +import java.util.logging.Logger; +import java.util.stream.Stream; + +import seedu.address.commons.core.LogsCenter; +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.EditClassCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.tuition.ClassLimit; +import seedu.address.model.tuition.ClassName; +import seedu.address.model.tuition.Timeslot; + + +/** + * Parses input arguments and creates a new EditClassCommand object. + */ +public class EditClassCommandParser implements Parser { + + private static final Logger logger = LogsCenter.getLogger(EditClassCommandParser.class); + + /** + * Parses {@code userInput} into a command and returns it. + * + * @param userInput The user input to be parsed + * @throws ParseException if {@code userInput} does not conform the expected format + */ + @Override + public EditClassCommand parse(String userInput) throws ParseException { + requireNonNull(userInput); + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(userInput, PREFIX_NAME, PREFIX_LIMIT, PREFIX_TIMESLOT); + if (noPrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_LIMIT, PREFIX_TIMESLOT)) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditClassCommand.MESSAGE_USAGE)); + } + Index index; + EditClassCommand.EditClassDescriptor editClassDescriptor = new EditClassCommand.EditClassDescriptor(); + + try { + index = ParserUtil.parseIndex(argMultimap.getPreamble()); + } catch (ParseException pe) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditClassCommand.MESSAGE_USAGE), pe); + } + parseClassDetails(editClassDescriptor, argMultimap); + return new EditClassCommand(index, editClassDescriptor); + } + + private void parseClassDetails(EditClassCommand.EditClassDescriptor editClassDescriptor, + ArgumentMultimap argMultimap) throws ParseException { + if (argMultimap.getValue(PREFIX_NAME).isPresent()) { + ClassName className = new ClassName(ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get()).fullName); + editClassDescriptor.setClassName(className); + } + if (argMultimap.getValue(PREFIX_LIMIT).isPresent()) { + ClassLimit limit = ParserUtil.parseLimit(argMultimap.getValue(PREFIX_LIMIT).get()); + editClassDescriptor.setLimit(limit); + } + if (argMultimap.getValue(PREFIX_TIMESLOT).isPresent()) { + Timeslot timeslot = ParserUtil.parseTimeslot(argMultimap.getValue(PREFIX_TIMESLOT).get()); + editClassDescriptor.setTimeslot(timeslot); + } + if (!editClassDescriptor.isAnyFieldEdited()) { + throw new ParseException(Messages.MESSAGE_NOT_EDITED); + } + } + + /** + * Returns true if none of the relevant prefixes contain some {@code Optional} values in the given + * {@code ArgumentMultimap}. + */ + private static boolean noPrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isEmpty()); + } +} + + diff --git a/src/main/java/seedu/address/logic/parser/EditCommandParser.java b/src/main/java/seedu/address/logic/parser/EditCommandParser.java index 845644b7dea..72f988d04e9 100644 --- a/src/main/java/seedu/address/logic/parser/EditCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/EditCommandParser.java @@ -6,77 +6,68 @@ import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; -import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; -import java.util.Collection; -import java.util.Collections; -import java.util.Optional; -import java.util.Set; +import java.util.stream.Stream; +import seedu.address.commons.core.Messages; 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 + * Parses input arguments and creates a new EditCommand object. */ public class EditCommandParser implements Parser { /** * Parses the given {@code String} of arguments in the context of the EditCommand * and returns an EditCommand object for execution. + * @param args the user input * @throws ParseException if the user input does not conform the expected format */ public EditCommand parse(String args) throws ParseException { requireNonNull(args); ArgumentMultimap argMultimap = - ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, PREFIX_TAG); - + ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS); + if (noPrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS)) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditCommand.MESSAGE_USAGE)); + } Index index; - try { index = ParserUtil.parseIndex(argMultimap.getPreamble()); } catch (ParseException pe) { - throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditCommand.MESSAGE_USAGE), pe); + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditCommand.MESSAGE_USAGE)); } - EditPersonDescriptor editPersonDescriptor = new EditPersonDescriptor(); + EditCommand.EditStudentDescriptor editStudentDescriptor = new EditCommand.EditStudentDescriptor(); + parseStudentDetails(editStudentDescriptor, argMultimap); + return new EditCommand(index, editStudentDescriptor); + } + + private void parseStudentDetails(EditCommand.EditStudentDescriptor editStudentDescriptor, + ArgumentMultimap argMultimap) throws ParseException { if (argMultimap.getValue(PREFIX_NAME).isPresent()) { - editPersonDescriptor.setName(ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get())); + editStudentDescriptor.setName(ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get())); } if (argMultimap.getValue(PREFIX_PHONE).isPresent()) { - editPersonDescriptor.setPhone(ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE).get())); + editStudentDescriptor.setPhone(ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE).get())); } if (argMultimap.getValue(PREFIX_EMAIL).isPresent()) { - editPersonDescriptor.setEmail(ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL).get())); + editStudentDescriptor.setEmail(ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL).get())); } if (argMultimap.getValue(PREFIX_ADDRESS).isPresent()) { - editPersonDescriptor.setAddress(ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS).get())); + editStudentDescriptor.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 (!editStudentDescriptor.isAnyFieldEdited()) { + throw new ParseException(Messages.MESSAGE_NOT_EDITED); } - - return new EditCommand(index, editPersonDescriptor); } /** - * Parses {@code Collection tags} into a {@code Set} if {@code tags} is non-empty. - * If {@code tags} contain only one element which is an empty string, it will be parsed into a - * {@code Set} containing zero tags. + * Returns true if none of the relevant prefixes contain some {@code Optional} values in the given + * {@code ArgumentMultimap}. */ - private Optional> parseTagsForEdit(Collection tags) throws ParseException { - assert tags != null; - - if (tags.isEmpty()) { - return Optional.empty(); - } - Collection tagSet = tags.size() == 1 && tags.contains("") ? Collections.emptySet() : tags; - return Optional.of(ParserUtil.parseTags(tagSet)); + private static boolean noPrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isEmpty()); } - } diff --git a/src/main/java/seedu/address/logic/parser/FindClassCommandParser.java b/src/main/java/seedu/address/logic/parser/FindClassCommandParser.java new file mode 100644 index 00000000000..fe38d738a5b --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/FindClassCommandParser.java @@ -0,0 +1,33 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import java.util.Arrays; + +import seedu.address.logic.commands.FindClassCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.NameContainsKeywordsPredicate; + +/** + * Parses input arguments and creates a new FindCommand object + */ +public class FindClassCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the FindClassCommand + * and returns a FindClassCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public FindClassCommand parse(String args) throws ParseException { + String trimmedArgs = args.trim(); + if (trimmedArgs.isEmpty()) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindClassCommand.MESSAGE_USAGE)); + } + + String[] nameKeywords = trimmedArgs.split("\\s+"); + + return new FindClassCommand(new NameContainsKeywordsPredicate(Arrays.asList(nameKeywords))); + } + +} diff --git a/src/main/java/seedu/address/logic/parser/FindCommandParser.java b/src/main/java/seedu/address/logic/parser/FindCommandParser.java index 4fb71f23103..af804cb62ab 100644 --- a/src/main/java/seedu/address/logic/parser/FindCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/FindCommandParser.java @@ -6,7 +6,7 @@ import seedu.address.logic.commands.FindCommand; import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.person.NameContainsKeywordsPredicate; +import seedu.address.model.NameContainsKeywordsPredicate; /** * Parses input arguments and creates a new FindCommand object diff --git a/src/main/java/seedu/address/logic/parser/ParserUtil.java b/src/main/java/seedu/address/logic/parser/ParserUtil.java index b117acb9c55..38144d250de 100644 --- a/src/main/java/seedu/address/logic/parser/ParserUtil.java +++ b/src/main/java/seedu/address/logic/parser/ParserUtil.java @@ -2,18 +2,34 @@ import static java.util.Objects.requireNonNull; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.time.DateTimeException; +import java.time.LocalTime; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; +import java.util.Date; import java.util.HashSet; +import java.util.List; +import java.util.Locale; import java.util.Set; +import seedu.address.commons.core.Messages; import seedu.address.commons.core.index.Index; import seedu.address.commons.util.StringUtil; import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Phone; +import seedu.address.model.student.Address; +import seedu.address.model.student.Email; +import seedu.address.model.student.Name; +import seedu.address.model.student.Phone; +import seedu.address.model.student.Remark; import seedu.address.model.tag.Tag; +import seedu.address.model.tuition.ClassLimit; +import seedu.address.model.tuition.ClassName; +import seedu.address.model.tuition.StudentList; +import seedu.address.model.tuition.Timeslot; /** * Contains utility methods used for parsing strings in the various *Parser classes. @@ -21,6 +37,7 @@ public class ParserUtil { public static final String MESSAGE_INVALID_INDEX = "Index is not a non-zero unsigned integer."; + private static String[] days = new String[] {"Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"}; /** * Parses {@code oneBasedIndex} into an {@code Index} and returns it. Leading and trailing whitespaces will be @@ -121,4 +138,158 @@ public static Set parseTags(Collection tags) throws ParseException } return tagSet; } + + /** + * Parses a {@code String remark} into an {@code Remark}. + * Leading and trailing whitespaces will be trimmed. + * Does not throw ParseException as there are no restrictions for remark input. + */ + public static Remark parseRemark(String remark) { + requireNonNull(remark); + String trimmedRemark = remark.trim(); + return new Remark(trimmedRemark); + } + + /** + * Parses a {@code String limit} into an {@code ClassLimit}. + * Leading and trailing whitespaces will be trimmed. + * + * + */ + public static ClassLimit parseLimit(String limit) throws ParseException { + requireNonNull(limit); + String trimmedLimit = limit.trim(); + try { + int myLimit = Integer.parseInt(trimmedLimit); + if (!ClassLimit.isValidLimit(myLimit)) { + throw new ParseException(ClassLimit.MESSAGE_CONSTRAINTS); + } + return new ClassLimit(myLimit); + } catch (NumberFormatException e) { + throw new ParseException(ClassLimit.MESSAGE_CONSTRAINTS); + } + } + + /** + * Parses a {@code String order} into a trimmed string. + * @param order the intended sorting order input by user. + * @return an Order object. + */ + public static SortCommandParser.Order parseOrder(String order) throws ParseException { + requireNonNull(order); + String trimmedOrder = order.trim(); + if (trimmedOrder.equals(SortCommandParser.Order.TIME.toString())) { + return SortCommandParser.Order.TIME; + } else if (trimmedOrder.equals(SortCommandParser.Order.ASCENDING.toString())) { + return SortCommandParser.Order.ASCENDING; + } else if (trimmedOrder.equals(SortCommandParser.Order.DESCENDING.toString())) { + return SortCommandParser.Order.DESCENDING; + } else { + throw new ParseException(SortCommandParser.Order.ORDER_CONSTRAINT); + } + } + + /** + * Parses a {@code String className} into a {@code ClassName}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code name} is invalid. + */ + public static ClassName parseClassName(String className) throws ParseException { + requireNonNull(className); + String trimmedName = className.trim(); + if (!Name.isValidName(trimmedName)) { + throw new ParseException(Name.MESSAGE_CONSTRAINTS); + } + return new ClassName(trimmedName); + } + /** + * Parses a {@code List students} into a {@code Student}. + * @param students a list of students, each of which is a string + * @return a single student object containing an arraylist + * @throws ParseException if the given {@code name} is invalid. + */ + public static StudentList parseStudent(List students) throws ParseException { + requireNonNull(students); + String trimmedStudents = (String) students.get(0); + String[] studentNames = trimmedStudents.split(","); + ArrayList studentList = new ArrayList<>(); + for (String s: studentNames) { + if (!Name.isValidName(s)) { + throw new ParseException(Name.MESSAGE_CONSTRAINTS_ADD_STUDENT_TO_CLASS); + } + studentList.add(s); + } + return new StudentList(studentList); + } + + /** + * Parses a {@code List studentIndexes} into a List of indexes. + * + * @param indices List of indexes, each representing a student. + * @return List of student indexes sorted in descending order. + * @throws ParseException If any index is invalid. + */ + public static List parseIndices(List indices) throws ParseException { + List args = new ArrayList(); + String[] students; + try { + students = ((String) indices.get(0)).split(" "); + for (String student : students) { + Index i = parseIndex(student); + if (!args.contains(i)) { + args.add(i); + } + } + } catch (IndexOutOfBoundsException | java.util.regex.PatternSyntaxException | ParseException pe) { + throw new ParseException(Messages.MESSAGE_INVALID_INDICES); + } + args.sort((index1, index2) -> { + if (index1.getOneBased() < index2.getOneBased()) { + return 1; + } else if (index1.getOneBased() > index2.getOneBased()) { + return -1; + } else { + return 0; + } + }); + return args; + } + + /** + * Parses a {@code String timeslot} into a {@code Timeslot}. + * Uses EEE HH:mm-HH:mm format. + * + * @param timeslot The String that represents the day and timings. + * @return The timeslot. + * @throws ParseException If there are parsing errors or the timings are invalid. + */ + public static Timeslot parseTimeslot(String timeslot) throws ParseException { + requireNonNull(timeslot); + String[] arr = timeslot.trim().split(" ", 2); + String[] times = arr.length == 2 ? arr[1].split("-", 2) : null; + + if (arr.length < 2 || arr[0].length() != 3 || times == null || times.length < 2) { + throw new ParseException(Messages.MESSAGE_TIMESLOT_FORMAT); + } + boolean validDay = Arrays.stream(days).anyMatch(arr[0]::equals); + if (!validDay) { + throw new ParseException(Messages.MESSAGE_TIMESLOT_FORMAT); + } + DateFormat dayFormat = new SimpleDateFormat("EEE"); + DateTimeFormatter timeFormat = DateTimeFormatter.ofPattern("HH:mm", Locale.ENGLISH); + try { + Date day = dayFormat.parse(arr[0]); + LocalTime start = LocalTime.parse(times[0], timeFormat); + LocalTime end = LocalTime.parse(times[1], timeFormat); + + if (!start.isBefore(end)) { + throw new ParseException(Messages.MESSAGE_TIMESLOT_FORMAT); + } + return new Timeslot(day, start, end); + } catch (DateTimeException | java.text.ParseException de) { + throw new ParseException(Messages.MESSAGE_TIMESLOT_FORMAT); + } + } } + diff --git a/src/main/java/seedu/address/logic/parser/RemarkClassCommandParser.java b/src/main/java/seedu/address/logic/parser/RemarkClassCommandParser.java new file mode 100644 index 00000000000..57309c8a296 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/RemarkClassCommandParser.java @@ -0,0 +1,31 @@ +package seedu.address.logic.parser; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.address.commons.core.index.Index; +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.logic.commands.RemarkClassCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +public class RemarkClassCommandParser implements Parser { + /** + * Parses the given {@code String} of arguments in the context of the {@code RemarkClassCommand} + * and returns a {@code RemarkClassCommand} object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public RemarkClassCommand parse(String args) throws ParseException { + requireNonNull(args); + ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args); + + Index index; + try { + index = ParserUtil.parseIndex(argMultimap.getPreamble()); + } catch (IllegalValueException ive) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + RemarkClassCommand.MESSAGE_USAGE), ive); + } + + return new RemarkClassCommand(index); + } +} diff --git a/src/main/java/seedu/address/logic/parser/RemarkCommandParser.java b/src/main/java/seedu/address/logic/parser/RemarkCommandParser.java new file mode 100644 index 00000000000..a01e52890de --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/RemarkCommandParser.java @@ -0,0 +1,34 @@ +package seedu.address.logic.parser; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_REMARK; + +import seedu.address.commons.core.index.Index; +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.logic.commands.RemarkCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.student.Remark; + +public class RemarkCommandParser implements Parser { + /** + * Parses the given {@code String} of arguments in the context of the {@code RemarkCommand} + * and returns a {@code RemarkCommand} object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public RemarkCommand parse(String args) throws ParseException { + requireNonNull(args); + ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_REMARK); + + Index index; + try { + index = ParserUtil.parseIndex(argMultimap.getPreamble()); + } catch (IllegalValueException ive) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, RemarkCommand.MESSAGE_USAGE), ive); + } + + String remark = argMultimap.getValue(PREFIX_REMARK).orElse(""); + + return new RemarkCommand(index, new Remark(remark)); + } +} diff --git a/src/main/java/seedu/address/logic/parser/RemoveStudentCommandParser.java b/src/main/java/seedu/address/logic/parser/RemoveStudentCommandParser.java new file mode 100644 index 00000000000..20155dd4a86 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/RemoveStudentCommandParser.java @@ -0,0 +1,48 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_STUDENT_INDEX; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TUITION_CLASS; + +import java.util.List; +import java.util.stream.Stream; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.RemoveStudentCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a new RemoveStudentCommand object. + */ +public class RemoveStudentCommandParser implements Parser { + + /** + * Parses {@code userInput} into a command and returns it. + * + * @param args The user input + * @throws ParseException if {@code userInput} does not conform the expected format + */ + @Override + public RemoveStudentCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_STUDENT_INDEX, PREFIX_TUITION_CLASS); + + if (!arePrefixesPresent(argMultimap, PREFIX_STUDENT_INDEX, PREFIX_TUITION_CLASS) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + RemoveStudentCommand.MESSAGE_USAGE)); + } + List students = ParserUtil.parseIndices(argMultimap.getAllValues(PREFIX_STUDENT_INDEX)); + Index classIndex = ParserUtil.parseIndex(argMultimap.getValue(PREFIX_TUITION_CLASS).get()); + return new RemoveStudentCommand(students, classIndex); + } + + /** + * 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/SortCommandParser.java b/src/main/java/seedu/address/logic/parser/SortCommandParser.java new file mode 100644 index 00000000000..df8cb0b6d07 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/SortCommandParser.java @@ -0,0 +1,50 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_SORT_ORDER; + +import java.util.Optional; + +import seedu.address.logic.commands.SortCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a new SortCommand object + */ +public class SortCommandParser implements Parser { + public enum Order { + TIME("time"), + ASCENDING("asc"), + DESCENDING("desc"); + public static final String ORDER_CONSTRAINT = "Sort order should be 'o/time', 'o/asc', or 'o/desc'"; + private String order; + Order(String order) { + this.order = order; + } + @Override + public String toString() { + return order; + } + } + + @Override + public SortCommand parse(String args) throws ParseException { + if (args.equals("")) { + //Sort by time + return new SortCommand(Order.TIME); + } + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_SORT_ORDER); + Optional s = argMultimap.getValue(PREFIX_SORT_ORDER); + if (s.isEmpty()) { + //Wrong format, not using prefix 'o/' + throw new ParseException(SortCommand.MESSAGE_USAGE); + } + try { + Order order = ParserUtil.parseOrder((String) s.get()); + return new SortCommand(order); + } catch (ParseException pe) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, SortCommand.MESSAGE_USAGE), pe); + } + } +} diff --git a/src/main/java/seedu/address/logic/parser/TimetableParser.java b/src/main/java/seedu/address/logic/parser/TimetableParser.java new file mode 100644 index 00000000000..03c61980722 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/TimetableParser.java @@ -0,0 +1,19 @@ +package seedu.address.logic.parser; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.address.logic.commands.TimetableCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Parses TimetableCommand and returns a TimetableCommand object. + */ +public class TimetableParser implements Parser { + @Override + public TimetableCommand parse(String args) throws ParseException { + if (args.equals("")) { + return new TimetableCommand(); + } else { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, TimetableCommand.MESSAGE_USAGE)); + } + } +} diff --git a/src/main/java/seedu/address/logic/parser/ViewClassCommandParser.java b/src/main/java/seedu/address/logic/parser/ViewClassCommandParser.java new file mode 100644 index 00000000000..c621d2896c7 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/ViewClassCommandParser.java @@ -0,0 +1,29 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.ViewClassCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a new ViewClassCommand object + */ +public class ViewClassCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the ViewClassCommand + * and returns a ViewClassCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public ViewClassCommand parse(String args) throws ParseException { + try { + Index index = ParserUtil.parseIndex(args); + return new ViewClassCommand(index); + } catch (ParseException pe) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, ViewClassCommand.MESSAGE_USAGE), pe); + } + } + +} diff --git a/src/main/java/seedu/address/logic/parser/ViewCommandParser.java b/src/main/java/seedu/address/logic/parser/ViewCommandParser.java new file mode 100644 index 00000000000..8d96b89d842 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/ViewCommandParser.java @@ -0,0 +1,29 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.ViewCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a new ViewCommand object + */ +public class ViewCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the ViewCommand + * and returns a ViewCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public ViewCommand parse(String args) throws ParseException { + try { + Index index = ParserUtil.parseIndex(args); + return new ViewCommand(index); + } catch (ParseException pe) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, ViewCommand.MESSAGE_USAGE), pe); + } + } + +} diff --git a/src/main/java/seedu/address/logic/parser/ViewTodayTuitionCommandParser.java b/src/main/java/seedu/address/logic/parser/ViewTodayTuitionCommandParser.java new file mode 100644 index 00000000000..4fbc47371ba --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/ViewTodayTuitionCommandParser.java @@ -0,0 +1,16 @@ +package seedu.address.logic.parser; + +import seedu.address.logic.commands.ViewTodayTuitionCommand; +import seedu.address.logic.parser.exceptions.ParseException; + + +public class ViewTodayTuitionCommandParser implements Parser { + /** + * Parses the given {@code String} of arguments in the context of the ViewTodayTuitionCommand + * and returns a ViewTodayTuitionCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public ViewTodayTuitionCommand parse(String args) throws ParseException { + return new ViewTodayTuitionCommand(); + } +} diff --git a/src/main/java/seedu/address/logic/parser/exceptions/ParseException.java b/src/main/java/seedu/address/logic/parser/exceptions/ParseException.java index 158a1a54c1c..c151777a89b 100644 --- a/src/main/java/seedu/address/logic/parser/exceptions/ParseException.java +++ b/src/main/java/seedu/address/logic/parser/exceptions/ParseException.java @@ -14,4 +14,5 @@ public ParseException(String message) { public ParseException(String message, Throwable cause) { super(message, cause); } + } diff --git a/src/main/java/seedu/address/model/AddressBook.java b/src/main/java/seedu/address/model/AddressBook.java index 1a943a0781a..4fbcdaa2076 100644 --- a/src/main/java/seedu/address/model/AddressBook.java +++ b/src/main/java/seedu/address/model/AddressBook.java @@ -3,18 +3,27 @@ import static java.util.Objects.requireNonNull; import java.util.List; +import java.util.logging.Logger; import javafx.collections.ObservableList; -import seedu.address.model.person.Person; -import seedu.address.model.person.UniquePersonList; +import seedu.address.commons.core.LogsCenter; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.parser.SortCommandParser; +import seedu.address.model.student.Student; +import seedu.address.model.student.UniqueStudentList; +import seedu.address.model.tuition.TuitionClass; +import seedu.address.model.tuition.UniqueTuitionList; /** * Wraps all data at the address-book level - * Duplicates are not allowed (by .isSamePerson comparison) + * Duplicates are not allowed (by .isSameStudent comparison) */ public class AddressBook implements ReadOnlyAddressBook { + private static final Logger LOGGER = LogsCenter.getLogger(AddressBook.class); - private final UniquePersonList persons; + private final UniqueStudentList students; + private final UniqueTuitionList tuitions; + private SortCommandParser.Order order; /* * The 'unusual' code block below is a non-static initialization block, sometimes used to avoid duplication @@ -24,13 +33,14 @@ public class AddressBook implements ReadOnlyAddressBook { * among constructors. */ { - persons = new UniquePersonList(); + students = new UniqueStudentList(); + tuitions = new UniqueTuitionList(); } public AddressBook() {} /** - * Creates an AddressBook using the Persons in the {@code toBeCopied} + * Creates an AddressBook using the Students in the {@code toBeCopied} */ public AddressBook(ReadOnlyAddressBook toBeCopied) { this(); @@ -40,11 +50,38 @@ public AddressBook(ReadOnlyAddressBook toBeCopied) { //// list overwrite operations /** - * Replaces the contents of the person list with {@code persons}. - * {@code persons} must not contain duplicate persons. + * Replaces the contents of the student list with {@code students}. + * {@code students} must not contain duplicate students. */ - public void setPersons(List persons) { - this.persons.setPersons(persons); + public void setStudents(List students) { + this.students.setStudents(students); + } + + /** + * Replaces the contents of the student list with {@code students}. + * {@code students} must not contain duplicate students. + */ + public void setTuition(List tuitionClasses) { + this.tuitions.setTuitions(tuitionClasses); + if (this.order != null) { + this.tuitions.sort(order); + } + } + + + + /** + * Replaces the given student {@code target} in the list with {@code editedStudent}. + * {@code target} must exist in the address book. + * The student identity of {@code editedStudent} must not be the same as + * another existing student in the address book. + */ + public void setTuition(TuitionClass target, TuitionClass editedTuition) { + requireNonNull(editedTuition); + tuitions.setTuition(target, editedTuition); + if (this.order != null) { + this.tuitions.sort(order); + } } /** @@ -53,68 +90,164 @@ public void setPersons(List persons) { public void resetData(ReadOnlyAddressBook newData) { requireNonNull(newData); - setPersons(newData.getPersonList()); + setStudents(newData.getStudentList()); + setTuition(newData.getTuitionList()); } - //// person-level operations + //// student-level operations /** - * Returns true if a person with the same identity as {@code person} exists in the address book. + * Returns true if a student with the same identity as {@code student} exists in the address book. */ - public boolean hasPerson(Person person) { - requireNonNull(person); - return persons.contains(person); + public boolean hasStudent(Student student) { + requireNonNull(student); + return students.contains(student); + } + + public Student getStudent(Index index) { + requireNonNull(index); + if (students.getStudentListSize() < index.getOneBased()) { + return null; + } + return students.getStudent(index.getOneBased() - 1); + } + + public TuitionClass getTuition(Index index) { + requireNonNull(index); + if (tuitions.getTuitionListSize() < index.getOneBased()) { + return null; + } + return tuitions.getTuitionClass(index.getOneBased() - 1); } /** - * Adds a person to the address book. - * The person must not already exist in the address book. + * Adds a student to the address book. + * The student must not already exist in the address book. */ - public void addPerson(Person p) { - persons.add(p); + public void addStudent(Student p) { + students.add(p); } /** - * Replaces the given person {@code target} in the list with {@code editedPerson}. + * Replaces the given student {@code target} in the list with {@code editedStudent}. * {@code target} must exist in the address book. - * The person identity of {@code editedPerson} must not be the same as another existing person in the address book. + * The student identity of {@code editedStudent} must not be the same + * as another existing student in the address book. */ - public void setPerson(Person target, Person editedPerson) { - requireNonNull(editedPerson); + public void setStudent(Student target, Student editedStudent) { + requireNonNull(editedStudent); - persons.setPerson(target, editedPerson); + students.setStudent(target, editedStudent); } /** * Removes {@code key} from this {@code AddressBook}. * {@code key} must exist in the address book. */ - public void removePerson(Person key) { - persons.remove(key); + public void removeStudent(Student key) { + students.remove(key); } //// util methods @Override public String toString() { - return persons.asUnmodifiableObservableList().size() + " persons"; + return students.asUnmodifiableObservableList().size() + " students"; // TODO: refine later } @Override - public ObservableList getPersonList() { - return persons.asUnmodifiableObservableList(); + public ObservableList getStudentList() { + return students.asUnmodifiableObservableList(); } + @Override + public ObservableList getTuitionList() { + return tuitions.asUnmodifiableObservableList(); + } + + @Override public boolean equals(Object other) { return other == this // short circuit if same object || (other instanceof AddressBook // instanceof handles nulls - && persons.equals(((AddressBook) other).persons)); + && students.equals(((AddressBook) other).students)); } @Override public int hashCode() { - return persons.hashCode(); + return students.hashCode(); + } + + /** + * Returns true if a student with the same identity as {@code student} exists in the address book. + */ + public boolean hasTuition(TuitionClass tuitionClass) { + requireNonNull(tuitionClass); + return tuitions.contains(tuitionClass); + } + + /** + * Adds a tuition class to the address book. + * The tuition class must not already exist in the address book. + */ + public void addTuition(TuitionClass t) { + tuitions.add(t); + if (this.order != null) { + this.tuitions.sort(order); + } + } + + /** + * Removes {@code key} from this {@code AddressBook}. + * {@code key} must exist in the address book. + */ + public void removeTuition(TuitionClass key) { + tuitions.remove(key); + } + + public TuitionClass addToClass(TuitionClass tuitionClass, Student student) { + return tuitionClass.addStudent(student); + } + + /** + * Returns a student with the same name as the input student. + * + * @param otherStudent the student to be checked + * @return the student with the same name as input. + */ + public Student getSameNameStudent(Student otherStudent) { + return this.students.getSameNameStudent(otherStudent); + } + + /** + * Returns the tuition class with a certain id. + * @param id the class with this id is to be found. + * @return the class found if exists, return null otherwise. + */ + public TuitionClass getClassById(Integer id) { + for (TuitionClass tuitionClass: tuitions) { + if (tuitionClass.getId() == id) { + return tuitionClass; + } + } + return null; + } + + /** + * Sorts the tuition class list and memorize the order prefered by the tutor. + * @param order the order the list is to be sorted with. + */ + public void sort(SortCommandParser.Order order) { + this.order = order; + this.tuitions.sort(order); + } + + /** + * Gets today tuition classes + * @return a list contains today tuition classes + */ + public ObservableList getTodayTuitionList() { + return tuitions.getTodayTuition(); } } diff --git a/src/main/java/seedu/address/model/Model.java b/src/main/java/seedu/address/model/Model.java index d54df471c1f..e01aba370ed 100644 --- a/src/main/java/seedu/address/model/Model.java +++ b/src/main/java/seedu/address/model/Model.java @@ -5,14 +5,18 @@ import javafx.collections.ObservableList; import seedu.address.commons.core.GuiSettings; -import seedu.address.model.person.Person; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.parser.SortCommandParser; +import seedu.address.model.student.Student; +import seedu.address.model.tuition.TuitionClass; /** * The API of the Model component. */ public interface Model { /** {@code Predicate} that always evaluate to true */ - Predicate PREDICATE_SHOW_ALL_PERSONS = unused -> true; + Predicate PREDICATE_SHOW_ALL_STUDENTS = unused -> true; + Predicate PREDICATE_SHOW_ALL_TUITIONS = unused -> true; /** * Replaces user prefs data with the data in {@code userPrefs}. @@ -53,35 +57,110 @@ public interface Model { ReadOnlyAddressBook getAddressBook(); /** - * Returns true if a person with the same identity as {@code person} exists in the address book. + * Returns true if a student with the same identity as {@code student} exists in the address book. */ - boolean hasPerson(Person person); + boolean hasStudent(Student student); /** - * Deletes the given person. - * The person must exist in the address book. + * Deletes the given student. + * The student must exist in the address book. */ - void deletePerson(Person target); + void deleteStudent(Student target); /** - * Adds the given person. - * {@code person} must not already exist in the address book. + * Adds the given student. + * {@code student} must not already exist in the address book. */ - void addPerson(Person person); + void addStudent(Student student); /** - * Replaces the given person {@code target} with {@code editedPerson}. + * Replaces the given student {@code target} with {@code editedStudent}. * {@code target} must exist in the address book. - * The person identity of {@code editedPerson} must not be the same as another existing person in the address book. + * The student identity of {@code editedStudent} must not be the same as + * another existing student in the address book. */ - void setPerson(Person target, Person editedPerson); + void setStudent(Student target, Student editedStudent); - /** Returns an unmodifiable view of the filtered person list */ - ObservableList getFilteredPersonList(); + /** Returns an unmodifiable view of the filtered student list */ + ObservableList getFilteredStudentList(); /** - * Updates the filter of the filtered person list to filter by the given {@code predicate}. + * Updates the filter of the filtered student list to filter by the given {@code predicate}. * @throws NullPointerException if {@code predicate} is null. */ - void updateFilteredPersonList(Predicate predicate); + void updateFilteredStudentList(Predicate predicate); + + /** Returns an unmodifiable view of the filtered tuition list */ + ObservableList getFilteredTuitionList(); + + /** Returns an unmodifiable view of today tuition list */ + ObservableList getTodayTuitionList(); + + /** + * Updates the filter of the filtered tuition list to filter by the given {@code predicate}. + * @throws NullPointerException if {@code predicate} is null. + */ + void updateFilteredTuitionList(Predicate predicate); + + boolean hasTuition(TuitionClass tuitionClass); + + /** + * Deletes the tuition class given by {@code target}. + * The class must exist in the address book. + */ + void deleteTuition(TuitionClass target); + + /** + * Adds the given student. + * {@code student} must not already exist in the address book. + */ + void addTuition(TuitionClass tuitionClass); + + /** + * Replaces the given student {@code target} with {@code editedStudent}. + * {@code target} must exist in the address book. + * The student identity of {@code editedStudent} must not be the same as + * another existing student in the address book. + */ + void setTuition(TuitionClass target, TuitionClass editedTuition); + + /** + * Checks whether the list of students contains the index from input. + * @param index the index of student to be checked. + * @return the student if the index is present. + */ + Student getStudent(Index index); + + /** + * Checks whether the list of tuition classes contains the index from input. + * @param index the index of class to be checked. + * @return the class if the index is present. + */ + TuitionClass getTuitionClass(Index index); + + /** + * Adds a new student to an existing class. + * @param tuitionClass an existing class. + * @param student an existing student. + * @return the tuition class after modification. + */ + TuitionClass addToClass(TuitionClass tuitionClass, Student student); + + /** + * Returns a student with the same name as the input student. + * @param otherStudent the student to be checked + * @return the student with the same name as input. + */ + Student getSameNameStudent(Student otherStudent); + + /** + * Checks whether the list of tuition classes contains the id from input. + * @param id the id of class to be checked. + * @return the class if the id is present. + */ + TuitionClass getClassById(Integer id); + + + + void sort(SortCommandParser.Order order); } diff --git a/src/main/java/seedu/address/model/ModelManager.java b/src/main/java/seedu/address/model/ModelManager.java index 0650c954f5c..3bb6b91f826 100644 --- a/src/main/java/seedu/address/model/ModelManager.java +++ b/src/main/java/seedu/address/model/ModelManager.java @@ -11,7 +11,10 @@ import javafx.collections.transformation.FilteredList; import seedu.address.commons.core.GuiSettings; import seedu.address.commons.core.LogsCenter; -import seedu.address.model.person.Person; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.parser.SortCommandParser; +import seedu.address.model.student.Student; +import seedu.address.model.tuition.TuitionClass; /** * Represents the in-memory model of the address book data. @@ -21,7 +24,8 @@ public class ModelManager implements Model { private final AddressBook addressBook; private final UserPrefs userPrefs; - private final FilteredList filteredPersons; + private FilteredList filteredStudents; + private FilteredList filteredTuition; /** * Initializes a ModelManager with the given addressBook and userPrefs. @@ -34,7 +38,9 @@ public ModelManager(ReadOnlyAddressBook addressBook, ReadOnlyUserPrefs userPrefs this.addressBook = new AddressBook(addressBook); this.userPrefs = new UserPrefs(userPrefs); - filteredPersons = new FilteredList<>(this.addressBook.getPersonList()); + filteredStudents = new FilteredList<>(this.addressBook.getStudentList()); + filteredTuition = new FilteredList<>(this.addressBook.getTuitionList()); + } public ModelManager() { @@ -89,44 +95,63 @@ public ReadOnlyAddressBook getAddressBook() { } @Override - public boolean hasPerson(Person person) { - requireNonNull(person); - return addressBook.hasPerson(person); + public boolean hasStudent(Student student) { + requireNonNull(student); + return addressBook.hasStudent(student); + } + + @Override + public void deleteStudent(Student target) { + addressBook.removeStudent(target); + updateFilteredStudentList(PREDICATE_SHOW_ALL_STUDENTS); + } + + @Override + public void addStudent(Student student) { + addressBook.addStudent(student); + updateFilteredStudentList(PREDICATE_SHOW_ALL_STUDENTS); } @Override - public void deletePerson(Person target) { - addressBook.removePerson(target); + public void setStudent(Student target, Student editedStudent) { + requireAllNonNull(target, editedStudent); + addressBook.setStudent(target, editedStudent); } @Override - public void addPerson(Person person) { - addressBook.addPerson(person); - updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + public Student getStudent(Index index) { + requireNonNull(index); + return addressBook.getStudent(index); } @Override - public void setPerson(Person target, Person editedPerson) { - requireAllNonNull(target, editedPerson); + public Student getSameNameStudent(Student otherStudent) { + return this.addressBook.getSameNameStudent(otherStudent); + } + - addressBook.setPerson(target, editedPerson); + @Override + public void sort(SortCommandParser.Order order) { + requireAllNonNull(order); + addressBook.sort(order); } - //=========== Filtered Person List Accessors ============================================================= + //=========== Filtered Student List Accessors ============================================================= /** - * Returns an unmodifiable view of the list of {@code Person} backed by the internal list of + * Returns an unmodifiable view of the list of {@code Student} backed by the internal list of * {@code versionedAddressBook} */ @Override - public ObservableList getFilteredPersonList() { - return filteredPersons; + public ObservableList getFilteredStudentList() { + return filteredStudents; } @Override - public void updateFilteredPersonList(Predicate predicate) { + public void updateFilteredStudentList(Predicate predicate) { requireNonNull(predicate); - filteredPersons.setPredicate(predicate); + filteredStudents.setPredicate(predicate); + logger.info("filter" + filteredStudents.toString()); } @Override @@ -145,7 +170,77 @@ public boolean equals(Object obj) { ModelManager other = (ModelManager) obj; return addressBook.equals(other.addressBook) && userPrefs.equals(other.userPrefs) - && filteredPersons.equals(other.filteredPersons); + && filteredStudents.equals(other.filteredStudents); + } + + + //=========== Filtered Tuition List Accessors ============================================================= + + /** + * Returns an unmodifiable view of the list of {@code Student} backed by the internal list of + * {@code versionedAddressBook} + */ + @Override + public ObservableList getFilteredTuitionList() { + return filteredTuition; + } + + @Override + public void updateFilteredTuitionList(Predicate predicate) { + requireNonNull(predicate); + filteredTuition.setPredicate(predicate); + logger.info(filteredStudents.toString()); + } + + @Override + public ObservableList getTodayTuitionList() { + return addressBook.getTodayTuitionList(); + } + + @Override + public boolean hasTuition(TuitionClass tuitionClass) { + requireNonNull(tuitionClass); + return addressBook.hasTuition(tuitionClass); + } + + @Override + public void setTuition(TuitionClass target, TuitionClass editedTuition) { + + requireAllNonNull(target, editedTuition); + addressBook.setTuition(target, editedTuition); + } + + @Override + public TuitionClass getTuitionClass(Index index) { + requireNonNull(index); + return addressBook.getTuition(index); } + @Override + public TuitionClass getClassById(Integer id) { + requireAllNonNull(id); + return addressBook.getClassById(id); + } + + @Override + public TuitionClass addToClass(TuitionClass tuitionClass, Student student) { + requireNonNull(tuitionClass); + requireNonNull(student); + TuitionClass c = addressBook.addToClass(tuitionClass, student); + updateFilteredTuitionList(PREDICATE_SHOW_ALL_TUITIONS); + return c; + } + + @Override + public void addTuition(TuitionClass tuitionClass) { + addressBook.addTuition(tuitionClass); + updateFilteredTuitionList(PREDICATE_SHOW_ALL_TUITIONS); + } + + @Override + public void deleteTuition(TuitionClass target) { + addressBook.removeTuition(target); + this.updateFilteredTuitionList(PREDICATE_SHOW_ALL_TUITIONS); + this.updateFilteredStudentList(PREDICATE_SHOW_ALL_STUDENTS); + } } diff --git a/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/NameContainsKeywordsPredicate.java similarity index 71% rename from src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java rename to src/main/java/seedu/address/model/NameContainsKeywordsPredicate.java index c9b5868427c..9cbbfe60d3c 100644 --- a/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java +++ b/src/main/java/seedu/address/model/NameContainsKeywordsPredicate.java @@ -1,4 +1,4 @@ -package seedu.address.model.person; +package seedu.address.model; import java.util.List; import java.util.function.Predicate; @@ -6,9 +6,9 @@ import seedu.address.commons.util.StringUtil; /** - * Tests that a {@code Person}'s {@code Name} matches any of the keywords given. + * Tests that a {@code Student}'s {@code Name} matches any of the keywords given. */ -public class NameContainsKeywordsPredicate implements Predicate { +public class NameContainsKeywordsPredicate implements Predicate { private final List keywords; public NameContainsKeywordsPredicate(List keywords) { @@ -16,9 +16,9 @@ public NameContainsKeywordsPredicate(List keywords) { } @Override - public boolean test(Person person) { + public boolean test(T nameable) { return keywords.stream() - .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(person.getName().fullName, keyword)); + .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(nameable.getNameString(), keyword)); } @Override diff --git a/src/main/java/seedu/address/model/Nameable.java b/src/main/java/seedu/address/model/Nameable.java new file mode 100644 index 00000000000..d8ba070c388 --- /dev/null +++ b/src/main/java/seedu/address/model/Nameable.java @@ -0,0 +1,5 @@ +package seedu.address.model; + +public interface Nameable { + public String getNameString(); +} diff --git a/src/main/java/seedu/address/model/ReadOnlyAddressBook.java b/src/main/java/seedu/address/model/ReadOnlyAddressBook.java index 6ddc2cd9a29..ef7d502433e 100644 --- a/src/main/java/seedu/address/model/ReadOnlyAddressBook.java +++ b/src/main/java/seedu/address/model/ReadOnlyAddressBook.java @@ -1,7 +1,8 @@ package seedu.address.model; import javafx.collections.ObservableList; -import seedu.address.model.person.Person; +import seedu.address.model.student.Student; +import seedu.address.model.tuition.TuitionClass; /** * Unmodifiable view of an address book @@ -9,9 +10,12 @@ public interface ReadOnlyAddressBook { /** - * Returns an unmodifiable view of the persons list. - * This list will not contain any duplicate persons. + * Returns an unmodifiable view of the students list. + * This list will not contain any duplicate students. */ - ObservableList getPersonList(); + ObservableList getStudentList(); + ObservableList getTuitionList(); + + } diff --git a/src/main/java/seedu/address/model/UserPrefs.java b/src/main/java/seedu/address/model/UserPrefs.java index 25a5fd6eab9..a9c8287cf58 100644 --- a/src/main/java/seedu/address/model/UserPrefs.java +++ b/src/main/java/seedu/address/model/UserPrefs.java @@ -5,27 +5,34 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.Objects; +import java.util.logging.Logger; import seedu.address.commons.core.GuiSettings; +import seedu.address.commons.core.LogsCenter; /** * Represents User's preferences. */ public class UserPrefs implements ReadOnlyUserPrefs { + private static final Logger LOGGER = LogsCenter.getLogger(UserPrefs.class); private GuiSettings guiSettings = new GuiSettings(); - private Path addressBookFilePath = Paths.get("data" , "addressbook.json"); + private Path addressBookFilePath = Paths.get("data" , "tutassistor.json"); /** * Creates a {@code UserPrefs} with default values. */ - public UserPrefs() {} + public UserPrefs() { + LOGGER.info("UserPrefs---Not--Reset"); + + } /** * Creates a {@code UserPrefs} with the prefs in {@code userPrefs}. */ public UserPrefs(ReadOnlyUserPrefs userPrefs) { this(); + LOGGER.info("UserPrefs---Reset"); resetData(userPrefs); } diff --git a/src/main/java/seedu/address/model/person/Person.java b/src/main/java/seedu/address/model/person/Person.java deleted file mode 100644 index 8ff1d83fe89..00000000000 --- a/src/main/java/seedu/address/model/person/Person.java +++ /dev/null @@ -1,123 +0,0 @@ -package seedu.address.model.person; - -import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; - -import java.util.Collections; -import java.util.HashSet; -import java.util.Objects; -import java.util.Set; - -import seedu.address.model.tag.Tag; - -/** - * Represents a Person in the address book. - * Guarantees: details are present and not null, field values are validated, immutable. - */ -public class Person { - - // Identity fields - private final Name name; - private final Phone phone; - private final Email email; - - // Data fields - private final Address address; - private final Set tags = new HashSet<>(); - - /** - * Every field must be present and not null. - */ - public Person(Name name, Phone phone, Email email, Address address, Set tags) { - requireAllNonNull(name, phone, email, address, tags); - this.name = name; - this.phone = phone; - this.email = email; - this.address = address; - this.tags.addAll(tags); - } - - public Name getName() { - return name; - } - - public Phone getPhone() { - return phone; - } - - public Email getEmail() { - return email; - } - - public Address getAddress() { - return address; - } - - /** - * Returns an immutable tag set, which throws {@code UnsupportedOperationException} - * if modification is attempted. - */ - public Set getTags() { - return Collections.unmodifiableSet(tags); - } - - /** - * Returns true if both persons have the same name. - * This defines a weaker notion of equality between two persons. - */ - public boolean isSamePerson(Person otherPerson) { - if (otherPerson == this) { - return true; - } - - return otherPerson != null - && otherPerson.getName().equals(getName()); - } - - /** - * Returns true if both persons have the same identity and data fields. - * This defines a stronger notion of equality between two persons. - */ - @Override - public boolean equals(Object other) { - if (other == this) { - return true; - } - - if (!(other instanceof Person)) { - return false; - } - - Person otherPerson = (Person) other; - return otherPerson.getName().equals(getName()) - && otherPerson.getPhone().equals(getPhone()) - && otherPerson.getEmail().equals(getEmail()) - && otherPerson.getAddress().equals(getAddress()) - && otherPerson.getTags().equals(getTags()); - } - - @Override - public int hashCode() { - // use this method for custom fields hashing instead of implementing your own - return Objects.hash(name, phone, email, address, tags); - } - - @Override - public String toString() { - final StringBuilder builder = new StringBuilder(); - builder.append(getName()) - .append("; Phone: ") - .append(getPhone()) - .append("; Email: ") - .append(getEmail()) - .append("; Address: ") - .append(getAddress()); - - Set tags = getTags(); - if (!tags.isEmpty()) { - builder.append("; Tags: "); - tags.forEach(builder::append); - } - return builder.toString(); - } - -} diff --git a/src/main/java/seedu/address/model/person/UniquePersonList.java b/src/main/java/seedu/address/model/person/UniquePersonList.java deleted file mode 100644 index 0fee4fe57e6..00000000000 --- a/src/main/java/seedu/address/model/person/UniquePersonList.java +++ /dev/null @@ -1,137 +0,0 @@ -package seedu.address.model.person; - -import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; - -import java.util.Iterator; -import java.util.List; - -import javafx.collections.FXCollections; -import javafx.collections.ObservableList; -import seedu.address.model.person.exceptions.DuplicatePersonException; -import seedu.address.model.person.exceptions.PersonNotFoundException; - -/** - * A list of persons that enforces uniqueness between its elements and does not allow nulls. - * A person is considered unique by comparing using {@code Person#isSamePerson(Person)}. As such, adding and updating of - * persons uses Person#isSamePerson(Person) for equality so as to ensure that the person being added or updated is - * unique in terms of identity in the UniquePersonList. However, the removal of a person uses Person#equals(Object) so - * as to ensure that the person with exactly the same fields will be removed. - * - * Supports a minimal set of list operations. - * - * @see Person#isSamePerson(Person) - */ -public class UniquePersonList implements Iterable { - - private final ObservableList internalList = FXCollections.observableArrayList(); - private final ObservableList internalUnmodifiableList = - FXCollections.unmodifiableObservableList(internalList); - - /** - * Returns true if the list contains an equivalent person as the given argument. - */ - public boolean contains(Person toCheck) { - requireNonNull(toCheck); - return internalList.stream().anyMatch(toCheck::isSamePerson); - } - - /** - * Adds a person to the list. - * The person must not already exist in the list. - */ - public void add(Person toAdd) { - requireNonNull(toAdd); - if (contains(toAdd)) { - throw new DuplicatePersonException(); - } - internalList.add(toAdd); - } - - /** - * Replaces the person {@code target} in the list with {@code editedPerson}. - * {@code target} must exist in the list. - * The person identity of {@code editedPerson} must not be the same as another existing person in the list. - */ - public void setPerson(Person target, Person editedPerson) { - requireAllNonNull(target, editedPerson); - - int index = internalList.indexOf(target); - if (index == -1) { - throw new PersonNotFoundException(); - } - - if (!target.isSamePerson(editedPerson) && contains(editedPerson)) { - throw new DuplicatePersonException(); - } - - internalList.set(index, editedPerson); - } - - /** - * Removes the equivalent person from the list. - * The person must exist in the list. - */ - public void remove(Person toRemove) { - requireNonNull(toRemove); - if (!internalList.remove(toRemove)) { - throw new PersonNotFoundException(); - } - } - - public void setPersons(UniquePersonList replacement) { - requireNonNull(replacement); - internalList.setAll(replacement.internalList); - } - - /** - * Replaces the contents of this list with {@code persons}. - * {@code persons} must not contain duplicate persons. - */ - public void setPersons(List persons) { - requireAllNonNull(persons); - if (!personsAreUnique(persons)) { - throw new DuplicatePersonException(); - } - - internalList.setAll(persons); - } - - /** - * Returns the backing list as an unmodifiable {@code ObservableList}. - */ - public ObservableList asUnmodifiableObservableList() { - return internalUnmodifiableList; - } - - @Override - public Iterator iterator() { - return internalList.iterator(); - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof UniquePersonList // instanceof handles nulls - && internalList.equals(((UniquePersonList) other).internalList)); - } - - @Override - public int hashCode() { - return internalList.hashCode(); - } - - /** - * Returns true if {@code persons} contains only unique persons. - */ - private boolean personsAreUnique(List persons) { - for (int i = 0; i < persons.size() - 1; i++) { - for (int j = i + 1; j < persons.size(); j++) { - if (persons.get(i).isSamePerson(persons.get(j))) { - return false; - } - } - } - return true; - } -} diff --git a/src/main/java/seedu/address/model/person/exceptions/DuplicatePersonException.java b/src/main/java/seedu/address/model/person/exceptions/DuplicatePersonException.java deleted file mode 100644 index d7290f59442..00000000000 --- a/src/main/java/seedu/address/model/person/exceptions/DuplicatePersonException.java +++ /dev/null @@ -1,11 +0,0 @@ -package seedu.address.model.person.exceptions; - -/** - * Signals that the operation will result in duplicate Persons (Persons are considered duplicates if they have the same - * identity). - */ -public class DuplicatePersonException extends RuntimeException { - public DuplicatePersonException() { - super("Operation would result in duplicate persons"); - } -} diff --git a/src/main/java/seedu/address/model/person/exceptions/PersonNotFoundException.java b/src/main/java/seedu/address/model/person/exceptions/PersonNotFoundException.java deleted file mode 100644 index fa764426ca7..00000000000 --- a/src/main/java/seedu/address/model/person/exceptions/PersonNotFoundException.java +++ /dev/null @@ -1,6 +0,0 @@ -package seedu.address.model.person.exceptions; - -/** - * Signals that the operation is unable to find the specified person. - */ -public class PersonNotFoundException extends RuntimeException {} diff --git a/src/main/java/seedu/address/model/person/Address.java b/src/main/java/seedu/address/model/student/Address.java similarity index 94% rename from src/main/java/seedu/address/model/person/Address.java rename to src/main/java/seedu/address/model/student/Address.java index 60472ca22a0..52499d566af 100644 --- a/src/main/java/seedu/address/model/person/Address.java +++ b/src/main/java/seedu/address/model/student/Address.java @@ -1,10 +1,10 @@ -package seedu.address.model.person; +package seedu.address.model.student; import static java.util.Objects.requireNonNull; import static seedu.address.commons.util.AppUtil.checkArgument; /** - * Represents a Person's address in the address book. + * Represents a Student's address in the address book. * Guarantees: immutable; is valid as declared in {@link #isValidAddress(String)} */ public class Address { diff --git a/src/main/java/seedu/address/model/student/Classes.java b/src/main/java/seedu/address/model/student/Classes.java new file mode 100644 index 00000000000..cfd31bbed86 --- /dev/null +++ b/src/main/java/seedu/address/model/student/Classes.java @@ -0,0 +1,99 @@ +package seedu.address.model.student; + +import static java.util.Objects.requireNonNull; + +import java.util.ArrayList; + +/** + * Represents the tuition classes that this student takes + */ +public class Classes { + + private ArrayList classes; + + /** + * Constructor for classes. + * + * @param classes The list of all class ids. + */ + public Classes(ArrayList classes) { + requireNonNull(classes); + this.classes = classes; + } + + public int getNumofClass() { + return classes.size(); + } + + public void updateClasses(ArrayList classes) { + this.classes = classes; + } + + public ArrayList getClasses() { + return this.classes; + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + for (Integer t : classes) { + builder.append(t.toString()); + builder.append("\n"); + } + return builder.toString(); + } + + @Override + public int hashCode() { + return classes.hashCode(); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Classes // instanceof handles nulls + && equalClasses(((Classes) other).classes)); // state check + } + + /** + * Returns true if classes are equal. + * + * @param otherClasses List of classes to compare to. + * @return True if the classes are identical, false otherwise. + */ + public boolean equalClasses(ArrayList otherClasses) { + if (otherClasses.size() != this.classes.size()) { + return false; + } + for (int i = 0; i < classes.size(); i++) { + if (!otherClasses.get(i).equals(this.classes.get(i))) { + return false; + } + } + return true; + } + + /** + * Adds a class into the student's class list. + * @param tuitionClass the tuition class to be added. + * @return the updated Classes object. + */ + public Classes addClass(Integer tuitionClass) { + this.getClasses().add(tuitionClass); + return this; + } + + /** + * Returns a copy of the classes after removing a particular tuition class. + * + * @param tuitionClass The tuition class to be removed. + * @return Copy of classes. + */ + public Classes removeClass(Integer tuitionClass) { + this.classes.remove(tuitionClass); + ArrayList copyOfClass = new ArrayList<>(); + copyOfClass.addAll(classes); + Classes classesCopy = new Classes(copyOfClass); + return classesCopy; + } +} diff --git a/src/main/java/seedu/address/model/person/Email.java b/src/main/java/seedu/address/model/student/Email.java similarity index 96% rename from src/main/java/seedu/address/model/person/Email.java rename to src/main/java/seedu/address/model/student/Email.java index f866e7133de..51b59860552 100644 --- a/src/main/java/seedu/address/model/person/Email.java +++ b/src/main/java/seedu/address/model/student/Email.java @@ -1,10 +1,10 @@ -package seedu.address.model.person; +package seedu.address.model.student; import static java.util.Objects.requireNonNull; import static seedu.address.commons.util.AppUtil.checkArgument; /** - * Represents a Person's email in the address book. + * Represents a Student's email in the address book. * Guarantees: immutable; is valid as declared in {@link #isValidEmail(String)} */ public class Email { diff --git a/src/main/java/seedu/address/model/person/Name.java b/src/main/java/seedu/address/model/student/Name.java similarity index 85% rename from src/main/java/seedu/address/model/person/Name.java rename to src/main/java/seedu/address/model/student/Name.java index 79244d71cf7..a18678a02f1 100644 --- a/src/main/java/seedu/address/model/person/Name.java +++ b/src/main/java/seedu/address/model/student/Name.java @@ -1,16 +1,18 @@ -package seedu.address.model.person; +package seedu.address.model.student; import static java.util.Objects.requireNonNull; import static seedu.address.commons.util.AppUtil.checkArgument; /** - * Represents a Person's name in the address book. + * Represents a Student's name in the address book. * Guarantees: immutable; is valid as declared in {@link #isValidName(String)} */ public class Name { public static final String MESSAGE_CONSTRAINTS = "Names should only contain alphanumeric characters and spaces, and it should not be blank"; + public static final String MESSAGE_CONSTRAINTS_ADD_STUDENT_TO_CLASS = + "Use comma to separate names, and there should be no space around comma."; /* * The first character of the address must not be a whitespace, diff --git a/src/main/java/seedu/address/model/person/Phone.java b/src/main/java/seedu/address/model/student/Phone.java similarity index 93% rename from src/main/java/seedu/address/model/person/Phone.java rename to src/main/java/seedu/address/model/student/Phone.java index 872c76b382f..c8573a772b2 100644 --- a/src/main/java/seedu/address/model/person/Phone.java +++ b/src/main/java/seedu/address/model/student/Phone.java @@ -1,10 +1,10 @@ -package seedu.address.model.person; +package seedu.address.model.student; import static java.util.Objects.requireNonNull; import static seedu.address.commons.util.AppUtil.checkArgument; /** - * Represents a Person's phone number in the address book. + * Represents a Student's phone number in the address book. * Guarantees: immutable; is valid as declared in {@link #isValidPhone(String)} */ public class Phone { diff --git a/src/main/java/seedu/address/model/student/Remark.java b/src/main/java/seedu/address/model/student/Remark.java new file mode 100644 index 00000000000..beb963fc34d --- /dev/null +++ b/src/main/java/seedu/address/model/student/Remark.java @@ -0,0 +1,38 @@ +package seedu.address.model.student; + +import static java.util.Objects.requireNonNull; + +/** + * Represents a Student's remark in the address book. + * Guarantees: immutable; is always valid + */ +public class Remark { + public final String value; + + /** + * Constructor for Remark. + * + * @param remark The remark to be added. + */ + public Remark(String remark) { + requireNonNull(remark); + this.value = remark; + } + + @Override + public String toString() { + return value; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Remark // instanceof handles nulls + && value.equals(((Remark) other).value)); // state check + } + + @Override + public int hashCode() { + return value.hashCode(); + } +} diff --git a/src/main/java/seedu/address/model/student/Student.java b/src/main/java/seedu/address/model/student/Student.java new file mode 100644 index 00000000000..849e645bcac --- /dev/null +++ b/src/main/java/seedu/address/model/student/Student.java @@ -0,0 +1,270 @@ +package seedu.address.model.student; + +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; + +import seedu.address.model.Nameable; +import seedu.address.model.tag.Tag; +import seedu.address.model.tuition.ClassName; +import seedu.address.model.tuition.Timeslot; +import seedu.address.model.tuition.TuitionClass; + +/** + * Represents a Student in the address book. + * Guarantees: details are present and not null, field values are validated, immutable. + */ +public class Student implements Nameable { + /** Most recently view student */ + private static Student mostRecent; + + // Identity fields + private final Name name; + private final Phone phone; + private final Email email; + private final Remark remark; + + + // Data fields + private final Address address; + private final Set tags = new HashSet<>(); + private final Classes classes; + + + /** + * Every field must be present and not null. + */ + public Student(Name name, Phone phone, Email email, Address address, + Remark remark, Set tags, Classes classes) { + requireAllNonNull(name, phone, email, address, tags, classes); + this.name = name; + this.phone = phone; + this.email = email; + this.address = address; + this.remark = remark; + this.tags.addAll(tags); + this.classes = classes; + mostRecent = this; + } + + /** + * Every field must be present and not null. + */ + public Student(Name name) { + requireAllNonNull(name); + this.name = name; + this.phone = null; + this.email = null; + this.address = null; + this.remark = null; + this.classes = null; + } + + public Remark getRemark() { + return remark; + } + + public Name getName() { + return name; + } + + public Phone getPhone() { + return phone; + } + + public Email getEmail() { + return email; + } + + public Address getAddress() { + return address; + } + + public Classes getClasses() { + return classes; + } + + public ArrayList getClassesArray() { + return classes.getClasses(); + } + + public String getNameString() { + return name.fullName; + } + + /** + * Adds a new tuition class to the student's class list. + * @param tuitionClass The tuition class to be added. + * @return the updated Classes object. + */ + public Classes addClass(TuitionClass tuitionClass) { + Classes updatedClass = this.getClasses().addClass(tuitionClass.getId()); + return updatedClass; + } + + /** + * Removes a tuition class from the student enrolled in the class. + * + * @param tuitionClass The tuition class to be removed. + * @return Copy of student after removing the tuition class. + */ + public Student removeClass(TuitionClass tuitionClass) { + for (Integer id : classes.getClasses()) { + if (tuitionClass.getId() == id) { + classes.removeClass(id); + Set updatedTags = removeTag(tuitionClass.getName(), tuitionClass.getTimeslot()); + return new Student(name, phone, email, address, remark, updatedTags, classes); + } + } + return null; + } + + /** + * Returns an immutable tag set, which throws {@code UnsupportedOperationException} + * if modification is attempted. + */ + public Set getTags() { + return Collections.unmodifiableSet(tags); + } + + /** + * Adds a new tag to the student. + * + * @param tag The tag to be added. + * @return Copy of the updated set of tags. + */ + public Set addTag(Tag tag) { + tags.add(tag); + Set updatedTags = new HashSet<>(); + updatedTags.addAll(tags); + return updatedTags; + } + + /** + * Returns the set of tags belonging to the student after removing a tuition class tag. + * + * @param name The name of the tuition class. + * @param slot The timeslot of the tuition class. + * @return Copy of the updated tags after removing a class tag. + */ + public Set removeTag(ClassName name, Timeslot slot) { + Set updatedTags = new HashSet(); + tags.remove(new Tag(String.format("%s | %s", name, slot))); + updatedTags.addAll(tags); + return updatedTags; + } + + /** + * Updates the name and timeslot of the class tag. + * + * @param tuitionClass The original tuition class. + * @param updatedClass The updated tuition class. + * @return The student after updating the class. + */ + public Student updateTag(TuitionClass tuitionClass, TuitionClass updatedClass) { + Tag tag = new Tag(String.format("%s | %s", tuitionClass.getNameString(), tuitionClass.getTimeslot())); + tags.remove(tag); + tags.add(new Tag(String.format("%s | %s", updatedClass.getNameString(), updatedClass.getTimeslot()))); + Set updatedTags = new HashSet(tags); + + return new Student(this.name, phone, email, address, remark, updatedTags, classes); + } + /** + * Returns true if both students have the same name. + * This defines a weaker notion of equality between two students. + */ + public boolean isSameStudent(Student otherStudent) { + if (otherStudent == this) { + return true; + } + + if (otherStudent == null || !(otherStudent instanceof Student)) { + return false; + } + + String thisName = this.getNameString().trim().replaceAll(" +", " ").toLowerCase(); + String otherName = otherStudent.getNameString().trim().replaceAll(" +", " ").toLowerCase(); + return thisName.equals(otherName); + } + + public Student getSameNameStudent(Student otherStudent) { + if (otherStudent == this) { + return this; + } + boolean sameName = otherStudent != null && otherStudent.getName().fullName.equals(name.fullName); + if (sameName) { + return this; + } + return null; + } + + /** + * Returns true if both students have the same identity and data fields. + * This defines a stronger notion of equality between two students. + */ + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof Student)) { + return false; + } + + Student otherStudent = (Student) other; + return otherStudent.getName().equals(getName()) + && otherStudent.getPhone().equals(getPhone()) + && otherStudent.getEmail().equals(getEmail()) + && otherStudent.getAddress().equals(getAddress()) + && otherStudent.getTags().equals(getTags()); + } + + @Override + public int hashCode() { + // use this method for custom fields hashing instead of implementing your own + return Objects.hash(name, phone, email, address, tags, classes); + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append(getName()) + .append("; Phone: ") + .append(getPhone()) + .append("; Email: ") + .append(getEmail()) + .append(" Remark: ") + .append(getRemark()) + .append("; Address: ") + .append(getAddress()); + + Set tags = getTags(); + if (!tags.isEmpty()) { + builder.append("; Classes: "); + tags.forEach(builder::append); + } + return builder.toString(); + } + + /** + * Sets most recently viewed student to a given Student. + * @param student Student to set as most recently looked at. + */ + public static void setMostRecentTo(Student student) { + mostRecent = student; + } + + /** + * Returns the most recently viewed student + * @return most recently viewed student. + */ + public static Student getMostRecent() { + return mostRecent; + } + +} diff --git a/src/main/java/seedu/address/model/student/UniqueStudentList.java b/src/main/java/seedu/address/model/student/UniqueStudentList.java new file mode 100644 index 00000000000..8ae8fb60d02 --- /dev/null +++ b/src/main/java/seedu/address/model/student/UniqueStudentList.java @@ -0,0 +1,162 @@ +package seedu.address.model.student; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; + +import java.util.Iterator; +import java.util.List; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import seedu.address.model.student.exceptions.DuplicateStudentException; +import seedu.address.model.student.exceptions.StudentNotFoundException; + +/** + * A list of students that enforces uniqueness between its elements and does not allow nulls. + * A student is considered unique by comparing using {@code Student#isSameStudent(Student)}. + * As such, adding and updating of students uses Student#isSameStudent(Student) for equality so as to ensure that + * the student being added or updated is unique in terms of identity in the UniqueStudentList. + * However, the removal of a student uses Student#equals(Object) so as to ensure that the student with exactly the + * same fields will be removed. + * + * Supports a minimal set of list operations. + * + * @see Student#isSameStudent(Student) + */ +public class UniqueStudentList implements Iterable { + + private final ObservableList internalList = FXCollections.observableArrayList(); + private final ObservableList internalUnmodifiableList = + FXCollections.unmodifiableObservableList(internalList); + + /** + * Returns true if the list contains an equivalent student as the given argument. + */ + public boolean contains(Student toCheck) { + requireNonNull(toCheck); + return internalList.stream().anyMatch(toCheck::isSameStudent); + } + + /** + * Returns a student with the same name as the input student. + * @param toGet the student to be gotten. + * @return a student object with the same name as the input. + */ + public Student getSameNameStudent(Student toGet) { + requireAllNonNull(toGet); + for (Student student : internalList) { + if (student.getSameNameStudent(toGet) != null) { + Student returnStudent = student.getSameNameStudent(toGet); + return returnStudent; + } + } + return null; + } + + /** + * Adds a student to the list. + * The student must not already exist in the list. + */ + public void add(Student toAdd) { + requireNonNull(toAdd); + if (contains(toAdd)) { + throw new DuplicateStudentException(); + } + internalList.add(toAdd); + } + + /** + * Replaces the student {@code target} in the list with {@code editedStudent}. + * {@code target} must exist in the list. + * The student identity of {@code editedStudent} must not be the same as another existing student in the list. + */ + public void setStudent(Student target, Student editedStudent) { + requireAllNonNull(target, editedStudent); + + int index = internalList.indexOf(target); + if (index == -1) { + throw new StudentNotFoundException(); + } + + if (!target.isSameStudent(editedStudent) && contains(editedStudent)) { + throw new DuplicateStudentException(); + } + + internalList.set(index, editedStudent); + } + + /** + * Removes the equivalent student from the list. + * The student must exist in the list. + */ + public void remove(Student toRemove) { + requireNonNull(toRemove); + if (!internalList.remove(toRemove)) { + throw new StudentNotFoundException(); + } + } + + public void setStudents(UniqueStudentList replacement) { + requireNonNull(replacement); + internalList.setAll(replacement.internalList); + } + + /** + * Replaces the contents of this list with {@code students}. + * {@code students} must not contain duplicate students. + */ + public void setStudents(List students) { + requireAllNonNull(students); + if (!studentsAreUnique(students)) { + throw new DuplicateStudentException(); + } + + internalList.setAll(students); + } + + /** + * Returns the backing list as an unmodifiable {@code ObservableList}. + */ + public ObservableList asUnmodifiableObservableList() { + return internalUnmodifiableList; + } + + @Override + public Iterator iterator() { + return internalList.iterator(); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof UniqueStudentList // instanceof handles nulls + && internalList.equals(((UniqueStudentList) other).internalList)); + } + + @Override + public int hashCode() { + return internalList.hashCode(); + } + + /** + * Returns true if {@code students} contains only unique students. + */ + private boolean studentsAreUnique(List students) { + for (int i = 0; i < students.size() - 1; i++) { + for (int j = i + 1; j < students.size(); j++) { + if (students.get(i).isSameStudent(students.get(j))) { + return false; + } + } + } + return true; + } + + public Student getStudent(int index) { + return this.internalList.get(index); + } + + public int getStudentListSize() { + return this.internalList.size(); + } +} diff --git a/src/main/java/seedu/address/model/student/exceptions/DuplicateStudentException.java b/src/main/java/seedu/address/model/student/exceptions/DuplicateStudentException.java new file mode 100644 index 00000000000..a8c68dd840f --- /dev/null +++ b/src/main/java/seedu/address/model/student/exceptions/DuplicateStudentException.java @@ -0,0 +1,11 @@ +package seedu.address.model.student.exceptions; + +/** + * Signals that the operation will result in duplicate Students (Students are considered duplicates + * if they have the same identity). + */ +public class DuplicateStudentException extends RuntimeException { + public DuplicateStudentException() { + super("Operation would result in duplicate students"); + } +} diff --git a/src/main/java/seedu/address/model/student/exceptions/StudentNotFoundException.java b/src/main/java/seedu/address/model/student/exceptions/StudentNotFoundException.java new file mode 100644 index 00000000000..2b41e9e0296 --- /dev/null +++ b/src/main/java/seedu/address/model/student/exceptions/StudentNotFoundException.java @@ -0,0 +1,6 @@ +package seedu.address.model.student.exceptions; + +/** + * Signals that the operation is unable to find the specified student. + */ +public class StudentNotFoundException extends RuntimeException {} diff --git a/src/main/java/seedu/address/model/tag/Tag.java b/src/main/java/seedu/address/model/tag/Tag.java index b0ea7e7dad7..5328534b68f 100644 --- a/src/main/java/seedu/address/model/tag/Tag.java +++ b/src/main/java/seedu/address/model/tag/Tag.java @@ -11,6 +11,7 @@ public class Tag { public static final String MESSAGE_CONSTRAINTS = "Tags names should be alphanumeric"; public static final String VALIDATION_REGEX = "\\p{Alnum}+"; + public static final String VALIDATION_CLASS_STAMP = ".*(0[0-9]|1[0-9]|2[0-3]):[0-5][0-9]$"; public final String tagName; @@ -29,7 +30,7 @@ public Tag(String tagName) { * Returns true if a given string is a valid tag name. */ public static boolean isValidTagName(String test) { - return test.matches(VALIDATION_REGEX); + return test.matches(VALIDATION_REGEX) || test.matches(VALIDATION_CLASS_STAMP); } @Override diff --git a/src/main/java/seedu/address/model/tuition/ClassLimit.java b/src/main/java/seedu/address/model/tuition/ClassLimit.java new file mode 100644 index 00000000000..9780230d71d --- /dev/null +++ b/src/main/java/seedu/address/model/tuition/ClassLimit.java @@ -0,0 +1,58 @@ +package seedu.address.model.tuition; + +import static java.util.Objects.requireNonNull; + +/** + * Represent the limit of the participant + */ +public class ClassLimit { + public static final String MESSAGE_CONSTRAINTS = + "The limit is invalid. It should be a positive integer and no more than 1000."; + public final int limit; + + /** + * Constructor for class limit. + * + * @param limit The maximum student size of the class. + */ + public ClassLimit(int limit) { + requireNonNull(limit); + this.limit = limit; + } + + /** + * Returns true if a given integer is a valid class limit. + */ + public static boolean isValidLimit(int limit) { + return limit <= 1000 && limit > 0; + } + + @Override + public String toString() { + return limit + ""; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof ClassLimit // instanceof handles nulls + && limit == ((ClassLimit) other).limit); // state check + } + + + public int getLimit() { + return limit; + } + + /** + * checks whether the class limit is valid(limit should be larger than 0) + * @param myLimit the limit to be checked + * @return true if the limit is valid. + */ + public static boolean isValid(int myLimit) { + if (myLimit <= 0) { + return false; + } + return Math.floor(myLimit) == myLimit; + } +} diff --git a/src/main/java/seedu/address/model/tuition/ClassName.java b/src/main/java/seedu/address/model/tuition/ClassName.java new file mode 100644 index 00000000000..8ae6311bf43 --- /dev/null +++ b/src/main/java/seedu/address/model/tuition/ClassName.java @@ -0,0 +1,41 @@ +package seedu.address.model.tuition; + +import static java.util.Objects.requireNonNull; + +/** + * Represents the name of the tuition class. + */ +public class ClassName { + + public final String name; + + /** + * Constructor for Class Name. + * + * @param name The name of the class. + */ + public ClassName(String name) { + requireNonNull(name); + this.name = name; + } + + @Override + public String toString() { + return name; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + if (other instanceof ClassName) { + return name.equals(((ClassName) other).name); + } + return false; + } + + public String getName() { + return name; + } +} diff --git a/src/main/java/seedu/address/model/tuition/StudentList.java b/src/main/java/seedu/address/model/tuition/StudentList.java new file mode 100644 index 00000000000..c879292677d --- /dev/null +++ b/src/main/java/seedu/address/model/tuition/StudentList.java @@ -0,0 +1,83 @@ +package seedu.address.model.tuition; + +import static java.util.Objects.requireNonNull; + +import java.util.ArrayList; +import java.util.List; + +/** + * Represents all students who are in a tuition class. + */ +public class StudentList { + private final ArrayList students; + + /** + * Constructor for student class. + * + * @param students The list of students. + */ + public StudentList(List students) { + requireNonNull(students); + this.students = (ArrayList) students; + } + + @Override + public String toString() { + return students.toString(); + } + + @Override + public int hashCode() { + return students.hashCode(); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof StudentList // instanceof handles nulls + && equalClasses(((StudentList) other).students)); // state check + } + + /** + * Returns true if both student lists are identical. + * + * @param otherStudents The list of students to compare to. + * @return True if the student lists are equal and false otherwise. + */ + public boolean equalClasses(ArrayList otherStudents) { + if (otherStudents.size() != this.students.size()) { + return false; + } + for (int i = 0; i < students.size(); i++) { + if (!otherStudents.get(i).equals(this.students.get(i))) { + return false; + } + } + return true; + } + + public ArrayList getStudents() { + return students; + } + + /** + * Returns whether or not Student List is empty. + * @returns true if there are no students in the list, false otherwise + */ + public boolean isEmpty() { + return students.isEmpty(); + } + + /** + * Updates student name in this student list. + * + * @param oldName The original name of the student. + * @param newName The updated name of the student. + */ + public void changeStudentName(String oldName, String newName) { + if (students.contains(oldName)) { + students.remove(oldName); + students.add(newName); + } + } +} diff --git a/src/main/java/seedu/address/model/tuition/Timeslot.java b/src/main/java/seedu/address/model/tuition/Timeslot.java new file mode 100644 index 00000000000..d5c90fa9a4f --- /dev/null +++ b/src/main/java/seedu/address/model/tuition/Timeslot.java @@ -0,0 +1,209 @@ +package seedu.address.model.tuition; + +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.time.LocalTime; +import java.time.format.DateTimeFormatter; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; + +import seedu.address.logic.parser.ParserUtil; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Represents the time slot that the tuition class occupies in the Timetable. + */ +public class Timeslot { + public static final String TIME_FORMAT_INCORRECT = "The time format is not correct"; + + //used to sorting tuition classes by comparing date + private static HashMap days = new HashMap<>() {{ + put("Mon", 1); + put("Tue", 2); + put("Wed", 3); + put("Thu", 4); + put("Fri", 5); + put("Sat", 6); + put("Sun", 7); + }}; + + private Date day; + private LocalTime start; + private LocalTime end; + + /** + * Constructor for timeslot. + * + * @param day Day in the week in EEE format + * @param start Start time of class in HH:mm format + * @param end End time of class in HH:mm format + */ + public Timeslot(Date day, LocalTime start, LocalTime end) { + this.start = start; + this.end = end; + this.day = day; + } + + public String getDayString() { + return (this.getTime().split(" "))[0]; + } + + /** + * Returns the start time of the class in LocalTime format. + * + * @return LocalTime representation of starting time of the class. + */ + public LocalTime getStart() { + return this.start; + } + + /** + * Returns the end time of the class in LocalTime format. + * + * @return LocalTime representation of ending time of the class. + */ + public LocalTime getEnd() { + return this.end; + } + + /** + * Returns the day of the class in Date format. + * + * @return Date representation of the day of the class. + */ + public Date getDay() { + return this.day; + } + + /** + * Compare two time slots to detect any overlaps or conflicts. + * + * @return Boolean true if conflict exists and false otherwise. + */ + public boolean checkClassConflict(Timeslot otherSlot) { + LocalTime otherStart = otherSlot.getStart(); + LocalTime otherEnd = otherSlot.getEnd(); + if (otherSlot.getDay().getDay() != day.getDay()) { + return false; + } + LocalTime max = otherStart.isAfter(start) ? otherStart : start; + LocalTime min = otherEnd.isBefore(end) ? otherEnd : end; + return min.isAfter(max); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + if (other instanceof Timeslot) { + Timeslot otherSlot = ((Timeslot) other); + return day.getDay() == otherSlot.getDay().getDay() + && otherSlot.getStart().equals(start) + && otherSlot.getEnd().equals(end); + } + return false; + } + + /** + * Returns String representation of this Timeslot in a specified format. + * + * @return String that represents the timeslot. + */ + public String getTime() { + DateFormat dayFormat = new SimpleDateFormat("EEE"); + DateTimeFormatter timeFormat = DateTimeFormatter.ofPattern("HH:mm", Locale.ENGLISH); + return String.format("%s %s-%s", dayFormat.format(day), start.format(timeFormat), end.format(timeFormat)); + } + + /** + * Returns a string representation of the object. In general, the + * {@code toString} method returns a string that + * "textually represents" this object. The result should + * be a concise but informative representation that is easy for a + * person to read. + * + * @return a string representation of the object. + */ + @Override + public String toString() { + return getTime(); + } + + /** + * Parses a time range in the form of string into LocalTime format. + * @return the starting time and end time in an array. + */ + public LocalTime[] parseTime() { + String[] time = this.getTime().split(" "); + String start = time[1].substring(0, 5); + String end = time[1].substring(6, 11); + if (start.substring(0, 2).equals("24")) { + start = "00" + start.substring(2); + } + if (end.substring(0, 2).equals("24")) { + end = "00" + end.substring(2); + } + LocalTime localStart = LocalTime.parse(start); + LocalTime localEnd = LocalTime.parse(end); + return new LocalTime[]{localStart, localEnd}; + } + + public static HashMap getDays() { + return days; + } + + /** + * Returns timeslot given timeslot as String in the correct format. + * @param slot + * @return + */ + public static Timeslot parseString(String slot) { + Timeslot timeslot = null; + try { + timeslot = ParserUtil.parseTimeslot(slot); + } catch (ParseException e) { + e.printStackTrace(); + } + return timeslot; + } + + /** + * Returns true if any timeslot of existing classes clashes with this timeslot. + * + * @param classList The list of existing tuition classes. + * @return Boolean true if there is a conflict and false otherwise. + */ + public boolean checkTimetableConflicts(List classList) { + for (TuitionClass tc: classList) { + if (this.checkClassConflict(tc.getTimeslot())) { + return true; + } + } + return false; + } + + /** + * Returns a negative integer, zero, or a positive integer by comparing the two timeslots. + * + * @param timeslot The timeslot to be compared to. + * @return An integer indicating the relative size of two timeslots compared to each other. + */ + public int compareTimeOrder(Timeslot timeslot) { + int otherDay = timeslot.getDay().getDay(); + int compareDay = day.getDay() - otherDay; + if (compareDay != 0) { + return compareDay > 0 ? 1 : -1; + } + + if (start.isBefore(timeslot.getStart())) { + return -1; + } else { + return 1; + } + } +} + + diff --git a/src/main/java/seedu/address/model/tuition/Timetable.java b/src/main/java/seedu/address/model/tuition/Timetable.java new file mode 100644 index 00000000000..a59fd533738 --- /dev/null +++ b/src/main/java/seedu/address/model/tuition/Timetable.java @@ -0,0 +1,278 @@ +package seedu.address.model.tuition; + +import java.time.LocalTime; +import java.time.temporal.ChronoUnit; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.logging.Logger; + +import javafx.collections.ObservableList; +import javafx.geometry.HPos; +import javafx.geometry.Pos; +import javafx.scene.control.Label; +import javafx.scene.layout.GridPane; +import seedu.address.commons.core.LogsCenter; +import seedu.address.ui.ResultDisplay; +import seedu.address.ui.infopage.TimetableInfoPage; + +/** + * Parses tuition classes to determine how to construct a timetable. + */ +public class Timetable { + public static final String COLOR_ODD = "-fx-background-color: #3e7589; -fx-text-fill:WHITE; -fx-font-size:%1$spt;"; + public static final String COLOR_EVEN = "-fx-background-color: #515658; -fx-text-fill:WHITE;" + + " -fx-font-size:%1$spt;"; + private static final int DEFAULT_FONT_SIZE = 8; + private static final String NOT_SHOWN = "The following class details are not shown due to space limit: "; + private static final HashMap dates = Timeslot.getDays(); + private static final Logger logger = LogsCenter.getLogger(Timetable.class); + private final ObservableList tuitionClasses; + private int start; + private int end; + private int totalRows; + private ArrayList timeStart = new ArrayList<>(); + private ArrayList timeEnd = new ArrayList<>(); + private ArrayList notShown = new ArrayList<>(); + private TimetableInfoPage infoPage; + private ResultDisplay resultDisplay; + + /** + * Constructor of timetable. + * @param tuitionClassList tuition classes to be shown on the timetable. + * @param resultDisplay display tuition classes not shown, if any. + * @param timetableInfoPage the UI component responsible for showing the timetable. + */ + public Timetable(ObservableList tuitionClassList, ResultDisplay resultDisplay, + TimetableInfoPage timetableInfoPage) { + if (timetableInfoPage == null) { + logger.warning("TimetableInfoPage is empty."); + } + this.tuitionClasses = tuitionClassList; + this.infoPage = timetableInfoPage; + this.resultDisplay = resultDisplay; + } + + /** + * Displays the timetable constructed from the tuition classes. + */ + public void showTimetable() { + //Determine size of timetable + parseTime(this.tuitionClasses); + infoPage.setTableTime(start, totalRows); + + insertSlot(); + + //Show tuition class details that are not shown due to space limit + if (notShown.size() > 0) { + String notShownClasses = ""; + for (String s: notShown) { + notShownClasses += "\n" + s; + } + resultDisplay.setFeedbackToUser(NOT_SHOWN + notShownClasses); + } + } + + /** + * Parses timeslot of tuition class list to determine the size of timetable. + * @param tuitionClassesPresent Tuition classes to be added to the timetable. + */ + public void parseTime(ObservableList tuitionClassesPresent) { + LocalTime[] times = getStartAndEnd(tuitionClassesPresent); + setStartAndEnd(times[0], times[1]); + } + + /** + * Gets the starting time and ending time of the timetable. + * @param tuitionClassesPresent uition classes to be added to the timetable. + * @return the starting and ending time in an array. + */ + private LocalTime[] getStartAndEnd(ObservableList tuitionClassesPresent) { + LocalTime earliest = null; + LocalTime latest = null; + for (TuitionClass tc: tuitionClassesPresent) { + LocalTime[] times = tc.getTimeslot().parseTime(); + LocalTime localStart = times[0]; + LocalTime localEnd = times[1]; + timeStart.add(localStart); + timeEnd.add(localEnd); + if (earliest == null) { + earliest = localStart; + } + if (latest == null) { + latest = localEnd; + } + if (localStart.compareTo(earliest) < 0) { + earliest = localStart; + } + if (localEnd.compareTo(latest) > 0) { + latest = localEnd; + } + } + return new LocalTime[]{earliest, latest}; + } + + private void setStartAndEnd(LocalTime earliest, LocalTime latest) { + this.start = Integer.parseInt(earliest.toString().substring(0, 2)); + int end = Integer.parseInt(latest.toString().substring(0, 2)); + this.end = end + 1; + this.totalRows = (this.end - this.start + 1) * 6; + } + + /** + * Inserts tuition classes into the timetable. + */ + public void insertSlot() { + if (tuitionClasses.size() == 0) { + return; + } + + //Determine starting time of the timetable + LocalTime startTime; + if ((this.start + "").length() == 1) { + startTime = LocalTime.parse("0" + this.start + ":00"); + } else { + startTime = LocalTime.parse(this.start + ":00"); + } + + //Start to insert tuition classes + for (int i = 0; i < tuitionClasses.size(); i++) { + insertATuitionClass(tuitionClasses.get(i), startTime, i); + } + } + + /** + * Inserts a tuition class into timetable. + * @param tuitionClass the tuition class to be inserted. + * @param startTime the starting time of the timetable. + * @param i the position of the tuition class in the tuition class list + */ + private void insertATuitionClass(TuitionClass tuitionClass, LocalTime startTime, int i) { + String date = tuitionClass.getTimeslot().getDayString(); + + //Determine position and size of the lesson in the timetable + int colInsert = dates.get(date); + int rowStartInsert = 6 + (int) Math.floor(startTime.until(timeStart.get(i), ChronoUnit.MINUTES) / (float) 10.0); + int rowFinishInsert; + String message = tuitionClass.getNameString() + "\n" + tuitionClass.getTimeslot().getTime().substring(4); + rowFinishInsert = 6 + Math.round(startTime.until(timeEnd.get(i), ChronoUnit.MINUTES) / (float) 10.0); + int rowSpan = rowFinishInsert - rowStartInsert; + + //Insert the lesson with the help of TimetableInfoPage + Label lesson = getLabel(message, getFontSize(rowSpan), + colInsert, tuitionClass); + if (infoPage != null) { + infoPage.addLesson(lesson, colInsert, rowStartInsert, 1, + rowSpan == 0 ? 1 : rowSpan); + GridPane.setHalignment(lesson, HPos.CENTER); + GridPane.setFillWidth(lesson, true); + } + } + + /** + * Determines the color of a cell depending on its column number. + * @param col the column of the cell whose color is to be determined. + * @return color to be used in String format. + */ + public String getColor(int col) { + String color = col % 2 == 0 ? COLOR_EVEN : COLOR_ODD; + return color; + } + + /** + * Produces a label of tuition class to be inserted into the timetable. + * @param message message shown on the label. + * @param fontSize size of text of label. + * @param col column of timetable the label should be shown at. + * @param tuitionClass the tuition class to be shown. + * @return a label with information of tuition class on it. + */ + public Label getLabel(String message, int fontSize, int col, TuitionClass tuitionClass) { + Label lesson; + if (fontSize == 1) { + //Tuition class duration too short to include class details + lesson = produceLabel(""); + notShown.add(tuitionClass.getNameString() + " " + tuitionClass.getTimeslot()); + } else if (fontSize == 2) { + //Tuition class duration is only enough to include class name + lesson = produceLabel(tuitionClass.getNameString()); + fontSize = 8; + notShown.add(tuitionClass.getNameString() + " " + tuitionClass.getTimeslot()); + } else { + //Tuition class duration long enough to include both name and time range + lesson = produceLabel(message); + } + + //Set format of the lesson in the timetable + if (lesson != null) { + lesson.setStyle(String.format(getColor(col), fontSize)); + lesson.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE); + lesson.setAlignment(Pos.CENTER); + } + return lesson; + } + + /** + * Produces a Label with given message. + * @param message message to shown on the label. + * @return the label instance. + */ + public Label produceLabel(String message) { + return new Label(message); + } + + /** + * Determines font size of each tuition class according to its time span. + * @param span time range of the tuition class. + * @return the font size to be used. + */ + public int getFontSize(int span) { + if (span < 3) { + return 1; + } + if (span <= 4) { + return 2; + } + if (span < 6) { + return 6; + } + if (span < 9) { + return 7; + } + return DEFAULT_FONT_SIZE; + } + + /** + * Gets starting hour. + * @return the starting hour. + */ + public int getStart() { + return start; + } + + /** + * Gets ending hour. + * @return the ending hour. + */ + public int getEnd() { + return end; + } + + /** + * Gets total rows of the timetable. + * @return the number of rows. + */ + public int getTotalRows() { + return totalRows; + } + + /** + * Returns a copy of the notshown arraylist. + * @return an arraylist with same elements as notshown. + */ + public ArrayList getNotShown() { + //Defensive programming: a copy is returned + ArrayList result = new ArrayList<>(); + result.addAll(notShown); + return result; + } +} diff --git a/src/main/java/seedu/address/model/tuition/TuitionClass.java b/src/main/java/seedu/address/model/tuition/TuitionClass.java new file mode 100644 index 00000000000..a5f4f76bc41 --- /dev/null +++ b/src/main/java/seedu/address/model/tuition/TuitionClass.java @@ -0,0 +1,263 @@ +package seedu.address.model.tuition; + +import java.util.ArrayList; +import java.util.Objects; + +import seedu.address.model.Nameable; +import seedu.address.model.student.Remark; +import seedu.address.model.student.Student; + +/** + * Represents a tuition class in TutAssistor. + */ +public class TuitionClass implements Nameable { + /** Most recently viewed tuition class */ + private static TuitionClass mostRecent; + + private final ClassName name; + private final ClassLimit limit; + private final Timeslot timeslot; + private StudentList studentList; + private final Remark remark; + private int id; + + /** + * Constructor for Tuition Class. + * + * @param name The name of the tuition class. + * @param limit The maximum number of students allowed. + * @param timeslot The date and time of the tuition. + * @param studentList The list of students attending the tuition. + * @param remark Any remarks noted for the tuition class. + */ + public TuitionClass(ClassName name, ClassLimit limit, Timeslot timeslot, StudentList studentList, + Remark remark) { + this.name = name; + this.limit = limit; + this.timeslot = timeslot; + this.studentList = studentList; + this.remark = remark; + this.id = this.hashCode(); + mostRecent = this; + } + + /** + * Constructor for Tuition Class used in reading data. + * + * @param name The name of the tuition class. + * @param limit The maximum number of students allowed. + * @param timeslot The date and time of the tuition. + * @param studentList The list of students attending the tuition. + * @param id the auto generated unique id for each tuition class + */ + public TuitionClass(ClassName name, ClassLimit limit, Timeslot timeslot, + StudentList studentList, Remark remark, int id) { + this.name = name; + this.limit = limit; + this.timeslot = timeslot; + this.studentList = studentList; + this.remark = remark; + this.id = id; + mostRecent = this; + } + + public ClassName getName() { + return name; + } + + public ClassLimit getLimit() { + return limit; + } + + + public Timeslot getTimeslot() { + return timeslot; + } + + public StudentList getStudentList() { + return studentList; + } + + public Remark getRemark() { + return remark; + } + + public int getStudentCount() { + return studentList.getStudents().size(); + } + + public boolean isFull() { + return this.getStudentCount() == this.limit.getLimit(); + } + + public int getId() { + return id; + } + + /** + * determine whether this tuition happens today + * @param weekday + * @return + */ + public boolean matchTheDay(int weekday) { + return weekday == timeslot.getDay().getDay(); + } + + @Override + public String getNameString() { + return name.getName(); + } + + /** + * Returns true if both Tuition have the same time slot. + * This defines a weaker notion of equality between two tuition classes. + */ + public boolean isSameTuition(TuitionClass otherTuition) { + return this.equals(otherTuition); + } + + //addn/John Doe p/98765432 e/johnd@example.com a/John street, block 123 + //addclass n/cs2100 l/10 c/4 ts/Mon 13:00-14:00 s/John Doe + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + if (!(other instanceof Student)) { + return false; + } + TuitionClass otherClass = (TuitionClass) other; + return otherClass.getTimeslot().equals(getTimeslot()); + } + + @Override + public int hashCode() { + // use this method for custom fields hashing instead of implementing your own + return Objects.hash(name, limit, timeslot, studentList, remark); + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append("Name: ") + .append(getName()) + .append("; Limit: ") + .append(getLimit()) + .append(" Timeslot: ") + .append(getTimeslot()) + .append("; Students: ") + .append(getStudentList()) + .append("; Remark: ") + .append(getRemark()); + return builder.toString(); + } + + /** + * Changes the students in this class to the students in the input. + * @param students a list of students names to be added. + * @return this TuitionClass + */ + public TuitionClass changeStudents(ArrayList students) { + this.studentList = new StudentList(students); + return this; + } + + /** + * Return updated Tuition class after removing student. + * + * @param student the student to be removed. + * @return Updated tuition class. + */ + public TuitionClass removeStudent(Student student) { + this.studentList.getStudents().remove(student.getName().fullName); + return this; + } + + /** + * Updates the name of a student in the tuition class. + * + * @param student The current student in the class. + * @param updatedStudent The updated student. + */ + public void updateStudent(Student student, Student updatedStudent) { + this.studentList.changeStudentName(student.getNameString(), updatedStudent.getNameString()); + } + + /** + * Returns true if the student is enrolled in the tuition class and false otherwise. + * + * @param student The student to be checked. + * @return A boolean true if the student is in the class, false otherwise. + */ + public boolean containsStudent(Student student) { + return this.studentList.getStudents().contains(student.getName().fullName); + } + + /** + * Returns a tuition class after adding a new student to this class, if the student is not already in the class. + * + * @param student Student to be added. + * @return The tuition class after adding the student. + */ + public TuitionClass addStudent(Student student) { + ArrayList nowStudents = this.studentList.getStudents(); + String name = student.getName().fullName; + for (String s: nowStudents) { + if (s.equals(name)) { + return null; + } + } + nowStudents.add(student.getName().fullName); + return this; + } + + /** + * Convert students from an arraylist to a string to be displayed in UI. + * @return a string of all the student names combined into a list. + */ + public String listStudents() { + String studentString = ""; + if (this.studentList.isEmpty()) { + studentString = "No student yet."; + return studentString; + } + String lastStudent = studentList.getStudents().get(studentList.getStudents().size() - 1); + for (String name: studentList.getStudents()) { + studentString += name; + if (!name.equals(lastStudent)) { + studentString += "\n"; + } + } + return studentString; + } + + /** + * Sets most recently viewed tuition class to a given TuitionClass. + * @param tuitionClass Tuition Class to set as most recently looked at. + */ + public static void setMostRecentTo(TuitionClass tuitionClass) { + mostRecent = tuitionClass; + } + + /** + * Returns the most recently viewed tuition class + * @return most recently viewed tuition class. + */ + public static TuitionClass getMostRecent() { + return mostRecent; + } + + /** + * Returns true if the limit, timeslot and names of two classes are identical. + * + * @param editedClass The class to compare to. + * @return boolean true if the limit, timeslot and names of two classes match, false otherwise. + */ + public boolean sameClassDetails(TuitionClass editedClass) { + return editedClass.getTimeslot().equals(timeslot) + && editedClass.getName().equals(name) + && editedClass.getLimit().equals(limit); + } +} + diff --git a/src/main/java/seedu/address/model/tuition/UniqueTuitionList.java b/src/main/java/seedu/address/model/tuition/UniqueTuitionList.java new file mode 100644 index 00000000000..6f026de0d3b --- /dev/null +++ b/src/main/java/seedu/address/model/tuition/UniqueTuitionList.java @@ -0,0 +1,240 @@ +package seedu.address.model.tuition; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; + +import java.time.LocalDate; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; +import java.util.stream.Collectors; + +import javafx.collections.FXCollections; +import javafx.collections.ListChangeListener; +import javafx.collections.ObservableList; +import seedu.address.logic.parser.SortCommandParser; +import seedu.address.model.student.UniqueStudentList; +import seedu.address.model.student.exceptions.DuplicateStudentException; +import seedu.address.model.student.exceptions.StudentNotFoundException; +import seedu.address.model.tuition.exceptions.DuplicateTuitionException; +import seedu.address.model.tuition.exceptions.TuitionNotFoundException; + +/** + * A list of tuition that enforces uniqueness between its elements and does not allow nulls. + * A student is considered unique by comparing using {@code TuitionClass#isSameTuition(TuitionClass)}. + * As such, adding and updating of tuition uses TuitionClass#isSameTuition(TuitionClass) for + * equality to ensure that the Tuition being added or updated is + * unique in terms of identity in the UniqueTuitionList. + * However, the removal of a student uses TuitionClass#equals(Object) to ensure + * that the TuitionClass with exactly the same fields will be removed. + * + * Supports a minimal set of list operations. + * + * @see TuitionClass#isSameTuition(TuitionClass) + */ +public class UniqueTuitionList implements Iterable { + private static ObservableList mostRecentTuitionClasses = FXCollections.observableArrayList(); + + private final ObservableList internalList = FXCollections.observableArrayList(); + private final ObservableList internalUnmodifiableList = + FXCollections.unmodifiableObservableList(internalList); + private SortCommandParser.Order order; + + /** + * Constructor for UniqueTuitionList. + */ + public UniqueTuitionList() { + internalList.addListener(new ListChangeListener() { + @Override + public void onChanged(Change c) { + mostRecentTuitionClasses = internalList; + } + }); + } + + /** + * Returns true if the list contains an equivalent tuitionClass as the given argument. + */ + public boolean contains(TuitionClass toCheck) { + requireNonNull(toCheck); + return internalList.stream().anyMatch(toCheck::isSameTuition); + } + + /** + * Adds a tuition to the list. + * The tuition must not already exist in the list. + */ + public void add(TuitionClass toAdd) { + requireNonNull(toAdd); + if (contains(toAdd)) { + throw new DuplicateTuitionException(); + } + internalList.add(toAdd); + } + + /** + * Replaces the tuition {@code target} in the list with {@code editedTuition}. + * {@code target} must exist in the list. + * The student identity of {@code editedStudent} must not be the same as another existing student in the list. + */ + public void setTuition(TuitionClass target, TuitionClass editedTuition) { + requireAllNonNull(target, editedTuition); + + int index = internalList.indexOf(target); + if (index == -1) { + throw new StudentNotFoundException(); + } + + if (!target.isSameTuition(editedTuition) && contains(editedTuition)) { + throw new DuplicateStudentException(); + } + + internalList.set(index, editedTuition); + } + + /** + * Removes the equivalent tuition class from the list. + * The class must exist in the list. + */ + public void remove(TuitionClass toRemove) { + requireNonNull(toRemove); + if (!internalList.remove(toRemove)) { + throw new TuitionNotFoundException(); + } + } + + public void setTuitions(seedu.address.model.tuition.UniqueTuitionList replacement) { + requireNonNull(replacement); + internalList.setAll(replacement.internalList); + } + + + /** + * Replaces the contents of this list with {@code tuitions}. + * {@code students} must not contain duplicate students. + */ + public void setTuitions(List tuitions) { + requireAllNonNull(tuitions); + if (!tuitionsAreUnique(tuitions)) { + throw new DuplicateStudentException(); + } + + internalList.setAll(tuitions); + } + + /** + * Returns the backing list as an unmodifiable {@code ObservableList}. + */ + public ObservableList asUnmodifiableObservableList() { + return internalUnmodifiableList; + } + + @Override + public Iterator iterator() { + return internalList.iterator(); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof UniqueStudentList // instanceof handles nulls + && internalList.equals(((seedu.address.model.tuition.UniqueTuitionList) other).internalList)); + } + + @Override + public int hashCode() { + return internalList.hashCode(); + } + + /** + * Returns true if {@code students} contains only unique students. + */ + private boolean tuitionsAreUnique(List tuitionClasses) { + for (int i = 0; i < tuitionClasses.size() - 1; i++) { + for (int j = i + 1; j < tuitionClasses.size(); j++) { + if (tuitionClasses.get(i).isSameTuition(tuitionClasses.get(j))) { + return false; + } + } + } + return true; + } + + public TuitionClass getTuitionClass(int index) { + return this.internalList.get(index); + } + + public int getTuitionListSize() { + return this.internalList.size(); + } + + /** + * Gets today tuition classes + * @return + */ + public ObservableList getTodayTuition() { + LocalDate localDate = LocalDate.now(); + int today = LocalDate.now().getDayOfWeek().getValue(); + List todayTuitionClass = internalList.stream() + .filter(tuitionClass -> tuitionClass.matchTheDay(today)).collect(Collectors.toList()); + ObservableList observableList = FXCollections.observableList(todayTuitionClass); + return observableList; + + } + + /** + * Sorts the tuition class list according to time or alphabetically order. + * @param order the order to sort the list with. + */ + public void sort(SortCommandParser.Order order) { + this.order = order; + internalList.sort(new TuitionClassComparator(order)); + } + + /** + * Comparator class for sorting the tuition list. + */ + class TuitionClassComparator implements Comparator { + private SortCommandParser.Order order; + public TuitionClassComparator(SortCommandParser.Order order) { + this.order = order; + } + + /** + * Compares two tuition classes. + * @param o1 First tuition class to compare. + * @param o2 Second tuition class to compare. + * @return 0 if two classes are equal, 1 if o1 is larger and -1 if o2 is larger. + */ + @Override + public int compare(TuitionClass o1, TuitionClass o2) { + switch (order) { + case ASCENDING: + return o1.getName().getName().compareToIgnoreCase(o2.getName().getName()); + case DESCENDING: + return o2.getName().getName().compareToIgnoreCase(o1.getName().getName()); + case TIME: + return o1.getTimeslot().compareTimeOrder(o2.getTimeslot()); + default: + return 0; + } + } + } + + /** + * Returns all time slots that have been occupied from the most recent updates. + * @return all time slots in an arraylist. + */ + public static ObservableList getMostRecentTuitionClasses() { + return mostRecentTuitionClasses; + } + + /** + * Sets mostRecentTuitionClasses to empty list. + */ + public static void setMostRecentTuitionClasses() { + mostRecentTuitionClasses = FXCollections.observableArrayList(); + } + +} + diff --git a/src/main/java/seedu/address/model/tuition/exceptions/DuplicateTuitionException.java b/src/main/java/seedu/address/model/tuition/exceptions/DuplicateTuitionException.java new file mode 100644 index 00000000000..3f381575658 --- /dev/null +++ b/src/main/java/seedu/address/model/tuition/exceptions/DuplicateTuitionException.java @@ -0,0 +1,7 @@ +package seedu.address.model.tuition.exceptions; + +public class DuplicateTuitionException extends RuntimeException { + public DuplicateTuitionException() { + super("Operation would result in duplicate tuition classes"); + } +} diff --git a/src/main/java/seedu/address/model/tuition/exceptions/TuitionNotFoundException.java b/src/main/java/seedu/address/model/tuition/exceptions/TuitionNotFoundException.java new file mode 100644 index 00000000000..d4c16784055 --- /dev/null +++ b/src/main/java/seedu/address/model/tuition/exceptions/TuitionNotFoundException.java @@ -0,0 +1,4 @@ +package seedu.address.model.tuition.exceptions; + +public class TuitionNotFoundException extends RuntimeException{ +} diff --git a/src/main/java/seedu/address/model/util/SampleDataUtil.java b/src/main/java/seedu/address/model/util/SampleDataUtil.java index 1806da4facf..8e65cbeb258 100644 --- a/src/main/java/seedu/address/model/util/SampleDataUtil.java +++ b/src/main/java/seedu/address/model/util/SampleDataUtil.java @@ -1,49 +1,84 @@ package seedu.address.model.util; +import java.util.ArrayList; import java.util.Arrays; import java.util.Set; import java.util.stream.Collectors; import seedu.address.model.AddressBook; import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Person; -import seedu.address.model.person.Phone; +import seedu.address.model.student.Address; +import seedu.address.model.student.Classes; +import seedu.address.model.student.Email; +import seedu.address.model.student.Name; +import seedu.address.model.student.Phone; +import seedu.address.model.student.Remark; +import seedu.address.model.student.Student; import seedu.address.model.tag.Tag; +import seedu.address.model.tuition.ClassLimit; +import seedu.address.model.tuition.ClassName; +import seedu.address.model.tuition.StudentList; +import seedu.address.model.tuition.Timeslot; +import seedu.address.model.tuition.TuitionClass; /** * Contains utility methods for populating {@code AddressBook} with sample data. */ public class SampleDataUtil { - public static Person[] getSamplePersons() { - return new Person[] { - new Person(new Name("Alex Yeoh"), new Phone("87438807"), new Email("alexyeoh@example.com"), - new Address("Blk 30 Geylang Street 29, #06-40"), - getTagSet("friends")), - new Person(new Name("Bernice Yu"), new Phone("99272758"), new Email("berniceyu@example.com"), - new Address("Blk 30 Lorong 3 Serangoon Gardens, #07-18"), - getTagSet("colleagues", "friends")), - new Person(new Name("Charlotte Oliveiro"), new Phone("93210283"), new Email("charlotte@example.com"), - new Address("Blk 11 Ang Mo Kio Street 74, #11-04"), - getTagSet("neighbours")), - new Person(new Name("David Li"), new Phone("91031282"), new Email("lidavid@example.com"), - new Address("Blk 436 Serangoon Gardens Street 26, #16-43"), - getTagSet("family")), - new Person(new Name("Irfan Ibrahim"), new Phone("92492021"), new Email("irfan@example.com"), - new Address("Blk 47 Tampines Street 20, #17-35"), - getTagSet("classmates")), - new Person(new Name("Roy Balakrishnan"), new Phone("92624417"), new Email("royb@example.com"), - new Address("Blk 45 Aljunied Street 85, #11-31"), - getTagSet("colleagues")) + public static final Remark EMPTY_REMARK = new Remark(""); + public static final ArrayList SAMPLE_STUDENT = new ArrayList<>(); + public static final StudentList SAMPLE_STUDENTS = new StudentList(SAMPLE_STUDENT); + + public static final ArrayList SAMPLE_CLASSES = new ArrayList<>() { + { + add(1234567); + add(2234567); + } + }; + + public static Student[] getSampleStudents() { + //sampleClasses.add(sampleTuitionClass); + return new Student[] { + new Student(new Name("Alex Yeoh"), new Phone("87438807"), new Email("alexyeoh@example.com"), + new Address("Blk 30 Geylang Street 29, #06-40"), EMPTY_REMARK, + getTagSet("Physics | Mon 10:00-12:00", "Chemistry | Tue 10:00-12:00"), new Classes(SAMPLE_CLASSES)), + new Student(new Name("Bernice Yu"), new Phone("99272758"), new Email("berniceyu@example.com"), + new Address("Blk 30 Lorong 3 Serangoon Gardens, #07-18"), EMPTY_REMARK, + getTagSet(), getEmptyClasses()), + new Student(new Name("Charlotte"), new Phone("93210283"), new Email("charlotte@example.com"), + new Address("Blk 11 Ang Mo Kio Street 74, #11-04"), EMPTY_REMARK, + getTagSet(), getEmptyClasses()), + new Student(new Name("David"), new Phone("91031282"), new Email("lidavid@example.com"), + new Address("Blk 436 Serangoon Gardens Street 26, #16-43"), EMPTY_REMARK, + getTagSet(), getEmptyClasses()), + new Student(new Name("Irfan"), new Phone("92492021"), new Email("irfan@example.com"), + new Address("Blk 47 Tampines Street 20, #17-35"), EMPTY_REMARK, + getTagSet(), getEmptyClasses()), + new Student(new Name("Roy"), new Phone("92624417"), new Email("royb@example.com"), + new Address("Blk 45 Aljunied Street 85, #11-31"), EMPTY_REMARK, + getTagSet(), getEmptyClasses()) + }; + } + + public static TuitionClass[] getSampleClass() { + return new TuitionClass[] { + new TuitionClass(new ClassName("Physics"), + new ClassLimit(5), Timeslot.parseString("Mon 10:00-12:00"), + getSampleStudentList(), EMPTY_REMARK, 1234567), + new TuitionClass(new ClassName("Chemistry"), + new ClassLimit(10), Timeslot.parseString("Tue 10:00-12:00"), + getSampleStudentList(), EMPTY_REMARK, 2234567) }; } public static ReadOnlyAddressBook getSampleAddressBook() { AddressBook sampleAb = new AddressBook(); - for (Person samplePerson : getSamplePersons()) { - sampleAb.addPerson(samplePerson); + for (Student sampleStudent : getSampleStudents()) { + sampleAb.addStudent(sampleStudent); + + } + for (TuitionClass tuitionClass: getSampleClass()) { + sampleAb.addTuition(tuitionClass); } return sampleAb; } @@ -57,4 +92,15 @@ public static Set getTagSet(String... strings) { .collect(Collectors.toSet()); } + private static Classes getEmptyClasses() { + return new Classes(new ArrayList<>()); + } + + private static StudentList getSampleStudentList() { + ArrayList studentToAdd = new ArrayList<>(); + studentToAdd.add("Alex Yeoh"); + StudentList students = new StudentList(studentToAdd); + return students; + } + } diff --git a/src/main/java/seedu/address/storage/JsonAdaptedPerson.java b/src/main/java/seedu/address/storage/JsonAdaptedStudent.java similarity index 54% rename from src/main/java/seedu/address/storage/JsonAdaptedPerson.java rename to src/main/java/seedu/address/storage/JsonAdaptedStudent.java index a6321cec2ea..ac309fc52a6 100644 --- a/src/main/java/seedu/address/storage/JsonAdaptedPerson.java +++ b/src/main/java/seedu/address/storage/JsonAdaptedStudent.java @@ -10,64 +10,79 @@ import com.fasterxml.jackson.annotation.JsonProperty; import seedu.address.commons.exceptions.IllegalValueException; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Person; -import seedu.address.model.person.Phone; +import seedu.address.model.student.Address; +import seedu.address.model.student.Classes; +import seedu.address.model.student.Email; +import seedu.address.model.student.Name; +import seedu.address.model.student.Phone; +import seedu.address.model.student.Remark; +import seedu.address.model.student.Student; import seedu.address.model.tag.Tag; /** - * Jackson-friendly version of {@link Person}. + * Jackson-friendly version of {@link Student}. */ -class JsonAdaptedPerson { +class JsonAdaptedStudent { - public static final String MISSING_FIELD_MESSAGE_FORMAT = "Person's %s field is missing!"; + public static final String MISSING_FIELD_MESSAGE_FORMAT = "Student's %s field is missing!"; private final String name; private final String phone; private final String email; private final String address; + private final String remark; + private final List classes = new ArrayList<>(); + private final List tagged = new ArrayList<>(); /** - * Constructs a {@code JsonAdaptedPerson} with the given person details. + * Constructs a {@code JsonAdaptedStudent} with the given student details. */ @JsonCreator - public JsonAdaptedPerson(@JsonProperty("name") String name, @JsonProperty("phone") String phone, - @JsonProperty("email") String email, @JsonProperty("address") String address, - @JsonProperty("tagged") List tagged) { + public JsonAdaptedStudent(@JsonProperty("name") String name, @JsonProperty("phone") String phone, + @JsonProperty("email") String email, @JsonProperty("address") String address, + @JsonProperty("remark") String remark, + @JsonProperty("classes") List classes, + @JsonProperty("tagged") List tagged) { this.name = name; this.phone = phone; this.email = email; this.address = address; + this.remark = remark; + if (classes != null) { + this.classes.addAll(classes); + } + if (tagged != null) { this.tagged.addAll(tagged); } } /** - * Converts a given {@code Person} into this class for Jackson use. + * Converts a given {@code Student} into this class for Jackson use. */ - public JsonAdaptedPerson(Person source) { + public JsonAdaptedStudent(Student source) { name = source.getName().fullName; phone = source.getPhone().value; email = source.getEmail().value; address = source.getAddress().value; + remark = source.getRemark().value; + tagged.addAll(source.getTags().stream() .map(JsonAdaptedTag::new) .collect(Collectors.toList())); + classes.addAll(source.getClasses().getClasses()); } /** - * Converts this Jackson-friendly adapted person object into the model's {@code Person} object. + * Converts this Jackson-friendly adapted student object into the model's {@code Student} object. * - * @throws IllegalValueException if there were any data constraints violated in the adapted person. + * @throws IllegalValueException if there were any data constraints violated in the adapted student. */ - public Person toModelType() throws IllegalValueException { - final List personTags = new ArrayList<>(); + public Student toModelType() throws IllegalValueException { + final List studentTags = new ArrayList<>(); for (JsonAdaptedTag tag : tagged) { - personTags.add(tag.toModelType()); + studentTags.add(tag.toModelType()); } if (name == null) { @@ -102,8 +117,13 @@ 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); + final Set modelTags = new HashSet<>(studentTags); + if (remark == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Remark.class.getSimpleName())); + } + final Remark modelRemark = new Remark(remark); + final Classes modelClasses = new Classes(new ArrayList(classes)); + return new Student(modelName, modelPhone, modelEmail, modelAddress, modelRemark, modelTags, modelClasses); } } diff --git a/src/main/java/seedu/address/storage/JsonAdaptedTuition.java b/src/main/java/seedu/address/storage/JsonAdaptedTuition.java new file mode 100644 index 00000000000..3b96bd73f47 --- /dev/null +++ b/src/main/java/seedu/address/storage/JsonAdaptedTuition.java @@ -0,0 +1,102 @@ +package seedu.address.storage; + +import java.util.ArrayList; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.student.Email; +import seedu.address.model.student.Name; +import seedu.address.model.student.Remark; +import seedu.address.model.tuition.ClassLimit; +import seedu.address.model.tuition.ClassName; +import seedu.address.model.tuition.StudentList; +import seedu.address.model.tuition.Timeslot; +import seedu.address.model.tuition.TuitionClass; + +/** + * Jackson-friendly version of {@link TuitionClass}. + */ +class JsonAdaptedTuition { + public static final String MISSING_FIELD_MESSAGE_FORMAT = "Tuition class' %s field is missing!"; + + private final String name; + private final int limit; + private final String timeslot; + + private final ArrayList students = new ArrayList<>(); + private final String remark; + + private final int id; + //private final ArrayList student = new ArrayList<>(); + + /** + * Constructs a {@code JsonAdaptedStudent} with the given student details. + */ + @JsonCreator + public JsonAdaptedTuition(@JsonProperty("name") String name, @JsonProperty("limit") int limit, + @JsonProperty("timeslot") String timeslot, + @JsonProperty("students") ArrayList student, + @JsonProperty("remark") String remark, @JsonProperty("id") int id) { + this.name = name; + this.limit = limit; + this.timeslot = timeslot; + this.remark = remark; + this.id = id; + + if (student != null) { + this.students.addAll(student); + } + } + + /** + * Converts a given {@code TuitionClass} into this class for Jackson use. + */ + public JsonAdaptedTuition(TuitionClass source) { + name = source.getName().getName(); + limit = source.getLimit().getLimit(); + timeslot = source.getTimeslot().getTime(); + students.addAll(source.getStudentList().getStudents()); + remark = source.getRemark().value; + id = source.getId(); + } + + /** + * Converts this Jackson-friendly adapted tuition object into the model's {@code TuitionClass} object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted student. + */ + public TuitionClass toModelType() throws IllegalValueException { + + if (name == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Name.class.getSimpleName())); + } + if (!Name.isValidName(name)) { + throw new IllegalValueException(Name.MESSAGE_CONSTRAINTS); + } + final ClassName modelName = new ClassName(name); + + final ClassLimit modelLimit = new ClassLimit(limit); + + + if (timeslot == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Email.class.getSimpleName())); + } + + final Timeslot modelTimeslot = Timeslot.parseString(timeslot); + + if (modelTimeslot == null) { + throw new IllegalValueException(Timeslot.TIME_FORMAT_INCORRECT); + } + + final StudentList modelStudent = new StudentList(students); + + if (remark == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Remark.class.getSimpleName())); + } + final Remark modelRemark = new Remark(remark); + + return new TuitionClass(modelName, modelLimit, modelTimeslot, modelStudent, modelRemark, this.id); + } +} diff --git a/src/main/java/seedu/address/storage/JsonAddressBookStorage.java b/src/main/java/seedu/address/storage/JsonAddressBookStorage.java index dfab9daaa0d..ef2fe4bb1af 100644 --- a/src/main/java/seedu/address/storage/JsonAddressBookStorage.java +++ b/src/main/java/seedu/address/storage/JsonAddressBookStorage.java @@ -74,6 +74,7 @@ public void saveAddressBook(ReadOnlyAddressBook addressBook, Path filePath) thro requireNonNull(filePath); FileUtil.createIfMissing(filePath); + JsonUtil.saveJsonFile(new JsonSerializableAddressBook(addressBook), filePath); } diff --git a/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java b/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java index 5efd834091d..28d5028cce0 100644 --- a/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java +++ b/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java @@ -2,34 +2,56 @@ import java.util.ArrayList; import java.util.List; +import java.util.logging.Logger; import java.util.stream.Collectors; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonRootName; +import seedu.address.commons.core.LogsCenter; import seedu.address.commons.exceptions.IllegalValueException; import seedu.address.model.AddressBook; import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.person.Person; +import seedu.address.model.student.Student; +import seedu.address.model.tuition.TuitionClass; /** * An Immutable AddressBook that is serializable to JSON format. */ -@JsonRootName(value = "addressbook") +@JsonRootName(value = "tutassistor") class JsonSerializableAddressBook { - public static final String MESSAGE_DUPLICATE_PERSON = "Persons list contains duplicate person(s)."; + public static final String MESSAGE_DUPLICATE_STUDENT = "Student list contains duplicate student(s)."; + public static final String MESSAGE_DUPLICATE_TUITION = "Tuition list contains duplicate tuition(s)."; + private static final Logger logger = LogsCenter.getLogger(JsonSerializableAddressBook.class); + + private final List students = new ArrayList<>(); + + private final List tuitions = new ArrayList<>(); - private final List persons = new ArrayList<>(); /** - * Constructs a {@code JsonSerializableAddressBook} with the given persons. + * Constructs a {@code JsonSerializableAddressBook} with the given students. */ @JsonCreator - public JsonSerializableAddressBook(@JsonProperty("persons") List persons) { - this.persons.addAll(persons); + public JsonSerializableAddressBook(@JsonProperty("students") List students, + @JsonProperty("tuitions") List tuitions) { + this.students.addAll(students); + this.tuitions.addAll(tuitions); + } + + /* + @JsonCreator + public JsonSerializableAddressBook(@JsonProperty("students") List students) { + this.students.addAll(students); + } + + @JsonCreator + public JsonSerializableAddressBook(@JsonProperty("tuition") List students) { + this.students.addAll(students); } + */ /** * Converts a given {@code ReadOnlyAddressBook} into this class for Jackson use. @@ -37,7 +59,10 @@ public JsonSerializableAddressBook(@JsonProperty("persons") List { private final CommandExecutor commandExecutor; + private InputHistory inputHistory; + @FXML private TextField commandTextField; @@ -27,6 +30,7 @@ public class CommandBox extends UiPart { public CommandBox(CommandExecutor commandExecutor) { super(FXML); this.commandExecutor = commandExecutor; + this.inputHistory = new InputHistory(); // calls #setStyleToDefault() whenever there is a change to the text of the command box. commandTextField.textProperty().addListener((unused1, unused2, unused3) -> setStyleToDefault()); } @@ -41,14 +45,43 @@ private void handleCommandEntered() { return; } + inputHistory.add(commandText); + try { commandExecutor.execute(commandText); commandTextField.setText(""); } catch (CommandException | ParseException e) { + commandTextField.setText(inputHistory.getPreviousInput()); + commandTextField.positionCaret(commandTextField.getText().length()); setStyleToIndicateCommandFailure(); } } + /** + * Handles arrow key button pressed events. + */ + @FXML + private void handleButtonPressed(KeyEvent key) { + switch(key.getCode()) { + case UP: + String previousInput = inputHistory.getPreviousInput(); + commandTextField.setText(previousInput); + commandTextField.positionCaret(commandTextField.getText().length()); + break; + case DOWN: + String nextInput = inputHistory.getNextInput(); + commandTextField.setText(nextInput); + commandTextField.positionCaret(commandTextField.getText().length()); + break; + case LEFT: + case RIGHT: + commandTextField.positionCaret(commandTextField.getCaretPosition()); + break; + default: + break; + } + } + /** * Sets the command box style to use the default style. */ diff --git a/src/main/java/seedu/address/ui/CommandFormat.java b/src/main/java/seedu/address/ui/CommandFormat.java new file mode 100644 index 00000000000..1ab32a57d9d --- /dev/null +++ b/src/main/java/seedu/address/ui/CommandFormat.java @@ -0,0 +1,47 @@ +package seedu.address.ui; + +public class CommandFormat { + + public static final String ADD_COMMAND = "add | a"; + public static final String ADD_DESC = "Creates a student."; + public static final String ADD_CLASS_COMMAND = "addclass | ac"; + public static final String ADD_CLASS_DESC = "Creates a tuition class."; + public static final String DELETE_COMMAND = "delete | del"; + public static final String DELETE_DESC = "Deletes a student."; + public static final String DELETE_CLASS_COMMAND = "deleteclass | delc"; + public static final String DELETE_CLASS_DESC = "Deletes a tuition class."; + public static final String ADD_TO_CLASS_COMMAND = "addtoclass | atc"; + public static final String ADD_TO_CLASS_DESC = "Adds a student to a class."; + public static final String REMOVE_FROM_CLASS_COMMAND = "remove | rm"; + public static final String REMOVE_FROM_CLASS_DESC = "Removes a student from a class."; + public static final String EDIT_COMMAND = "edit | e"; + public static final String EDIT_DESC = "Edits a student."; + public static final String EDIT_CLASS_COMMAND = "editclass | ec"; + public static final String EDIT_CLASS_DESC = "Edits a class."; + public static final String VIEW_COMMAND = "student | vs"; + public static final String VIEW_DESC = "Views a student details."; + public static final String VIEW_CLASS_COMMAND = "class | vc"; + public static final String VIEW_CLASS_DESC = "Views a class details."; + public static final String REMARK_COMMAND = "remark | re"; + public static final String REMARK_DESC = "Add remark to a student."; + public static final String REMARK_CLASS_COMMAND = "remarkclass | rec"; + public static final String REMARK_CLASS_DESC = "Add remark to a class."; + public static final String SORT_COMMAND = "sort | s"; + public static final String SORT_DESC = "Sorts the classes."; + public static final String FIND_COMMAND = "find | f"; + public static final String FIND_DESC = "Finds a student by name."; + public static final String FIND_CLASS_COMMAND = "findclass | fc"; + public static final String FIND_CLASS_DESC = "Finds a class by name."; + public static final String LIST_COMMAND = "list | l"; + public static final String LIST_DESC = "Lists all students."; + public static final String LIST_CLASS_COMMAND = "listclass | lc"; + public static final String LIST_CLASS_DESC = "Lists all classes."; + public static final String TIMETABLE_COMMAND = "timetable | tt"; + public static final String TIMETABLE_DESC = "Displays this week's lessons."; + public static final String TODAY_COMMAND = "today | td"; + public static final String TODAY_DESC = "Displays today's lessons."; + public static final String CLEAR_COMMAND = "clear"; + public static final String CLEAR_DESC = "Clears all data."; + public static final String EXIT_COMMAND = "exit"; + public static final String EXIT_DESC = "Exits the app."; +} diff --git a/src/main/java/seedu/address/ui/HelpWindow.java b/src/main/java/seedu/address/ui/HelpWindow.java index 9a665915949..75da3d4243d 100644 --- a/src/main/java/seedu/address/ui/HelpWindow.java +++ b/src/main/java/seedu/address/ui/HelpWindow.java @@ -1,32 +1,157 @@ package seedu.address.ui; +import static seedu.address.ui.CommandFormat.ADD_CLASS_COMMAND; +import static seedu.address.ui.CommandFormat.ADD_CLASS_DESC; +import static seedu.address.ui.CommandFormat.ADD_COMMAND; +import static seedu.address.ui.CommandFormat.ADD_DESC; +import static seedu.address.ui.CommandFormat.ADD_TO_CLASS_COMMAND; +import static seedu.address.ui.CommandFormat.ADD_TO_CLASS_DESC; +import static seedu.address.ui.CommandFormat.CLEAR_COMMAND; +import static seedu.address.ui.CommandFormat.CLEAR_DESC; +import static seedu.address.ui.CommandFormat.DELETE_CLASS_COMMAND; +import static seedu.address.ui.CommandFormat.DELETE_CLASS_DESC; +import static seedu.address.ui.CommandFormat.DELETE_COMMAND; +import static seedu.address.ui.CommandFormat.DELETE_DESC; +import static seedu.address.ui.CommandFormat.EDIT_CLASS_COMMAND; +import static seedu.address.ui.CommandFormat.EDIT_CLASS_DESC; +import static seedu.address.ui.CommandFormat.EDIT_COMMAND; +import static seedu.address.ui.CommandFormat.EDIT_DESC; +import static seedu.address.ui.CommandFormat.EXIT_COMMAND; +import static seedu.address.ui.CommandFormat.EXIT_DESC; +import static seedu.address.ui.CommandFormat.FIND_CLASS_COMMAND; +import static seedu.address.ui.CommandFormat.FIND_CLASS_DESC; +import static seedu.address.ui.CommandFormat.FIND_COMMAND; +import static seedu.address.ui.CommandFormat.FIND_DESC; +import static seedu.address.ui.CommandFormat.LIST_CLASS_COMMAND; +import static seedu.address.ui.CommandFormat.LIST_CLASS_DESC; +import static seedu.address.ui.CommandFormat.LIST_COMMAND; +import static seedu.address.ui.CommandFormat.LIST_DESC; +import static seedu.address.ui.CommandFormat.REMARK_CLASS_COMMAND; +import static seedu.address.ui.CommandFormat.REMARK_CLASS_DESC; +import static seedu.address.ui.CommandFormat.REMARK_COMMAND; +import static seedu.address.ui.CommandFormat.REMARK_DESC; +import static seedu.address.ui.CommandFormat.REMOVE_FROM_CLASS_COMMAND; +import static seedu.address.ui.CommandFormat.REMOVE_FROM_CLASS_DESC; +import static seedu.address.ui.CommandFormat.SORT_COMMAND; +import static seedu.address.ui.CommandFormat.SORT_DESC; +import static seedu.address.ui.CommandFormat.TIMETABLE_COMMAND; +import static seedu.address.ui.CommandFormat.TIMETABLE_DESC; +import static seedu.address.ui.CommandFormat.TODAY_COMMAND; +import static seedu.address.ui.CommandFormat.TODAY_DESC; +import static seedu.address.ui.CommandFormat.VIEW_CLASS_COMMAND; +import static seedu.address.ui.CommandFormat.VIEW_CLASS_DESC; +import static seedu.address.ui.CommandFormat.VIEW_COMMAND; +import static seedu.address.ui.CommandFormat.VIEW_DESC; + import java.util.logging.Logger; import javafx.fxml.FXML; import javafx.scene.control.Button; import javafx.scene.control.Label; -import javafx.scene.input.Clipboard; -import javafx.scene.input.ClipboardContent; import javafx.stage.Stage; +import seedu.address.commons.core.Browser; import seedu.address.commons.core.LogsCenter; /** * Controller for a help page */ public class HelpWindow extends UiPart { - - public static final String USERGUIDE_URL = "https://se-education.org/addressbook-level3/UserGuide.html"; - public static final String HELP_MESSAGE = "Refer to the user guide: " + USERGUIDE_URL; + public static final String USERGUIDE_URL = + "https://ay2122s1-cs2103t-t12-4.github.io/tp/UserGuide.html#command-summary"; + public static final String HELP_MESSAGE = "Click the button to visit our user guide:\n"; private static final Logger logger = LogsCenter.getLogger(HelpWindow.class); private static final String FXML = "HelpWindow.fxml"; @FXML - private Button copyButton; - + private Button openButton; @FXML private Label helpMessage; + @FXML + private Label add; + @FXML + private Label addDesc; + @FXML + private Label addToClass; + @FXML + private Label addToClassDesc; + @FXML + private Label del; + @FXML + private Label delDesc; + @FXML + private Label delc; + @FXML + private Label delcDesc; + @FXML + private Label atc; + @FXML + private Label atcDesc; + @FXML + private Label rm; + @FXML + private Label rmDesc; + @FXML + private Label e; + @FXML + private Label eDesc; + @FXML + private Label ec; + @FXML + private Label ecDesc; + @FXML + private Label vs; + @FXML + private Label vsDesc; + @FXML + private Label vc; + @FXML + private Label vcDesc; + @FXML + private Label re; + @FXML + private Label reDesc; + @FXML + private Label rec; + @FXML + private Label recDesc; + @FXML + private Label s; + @FXML + private Label sDesc; + @FXML + private Label f; + @FXML + private Label fc; + @FXML + private Label l; + @FXML + private Label lc; + @FXML + private Label tt; + @FXML + private Label fDesc; + @FXML + private Label fcDesc; + @FXML + private Label lDesc; + @FXML + private Label lcDesc; + @FXML + private Label ttDesc; + @FXML + private Label td; + @FXML + private Label clear; + @FXML + private Label exit; + @FXML + private Label tdDesc; + @FXML + private Label clearDesc; + @FXML + private Label exitDesc; /** * Creates a new HelpWindow. * @@ -35,6 +160,48 @@ public class HelpWindow extends UiPart { public HelpWindow(Stage root) { super(FXML, root); helpMessage.setText(HELP_MESSAGE); + add.setText(ADD_COMMAND); + addDesc.setText(ADD_DESC); + addToClass.setText(ADD_CLASS_COMMAND); + addToClassDesc.setText(ADD_CLASS_DESC); + del.setText(DELETE_COMMAND); + delDesc.setText(DELETE_DESC); + delc.setText(DELETE_CLASS_COMMAND); + delcDesc.setText(DELETE_CLASS_DESC); + atc.setText(ADD_TO_CLASS_COMMAND); + atcDesc.setText(ADD_TO_CLASS_DESC); + rm.setText(REMOVE_FROM_CLASS_COMMAND); + rmDesc.setText(REMOVE_FROM_CLASS_DESC); + e.setText(EDIT_COMMAND); + eDesc.setText(EDIT_DESC); + ec.setText(EDIT_CLASS_COMMAND); + ecDesc.setText(EDIT_CLASS_DESC); + vs.setText(VIEW_COMMAND); + vsDesc.setText(VIEW_DESC); + vc.setText(VIEW_CLASS_COMMAND); + vcDesc.setText(VIEW_CLASS_DESC); + re.setText(REMARK_COMMAND); + reDesc.setText(REMARK_DESC); + rec.setText(REMARK_CLASS_COMMAND); + recDesc.setText(REMARK_CLASS_DESC); + s.setText(SORT_COMMAND); + sDesc.setText(SORT_DESC); + f.setText(FIND_COMMAND); + fDesc.setText(FIND_DESC); + fc.setText(FIND_CLASS_COMMAND); + fcDesc.setText(FIND_CLASS_DESC); + l.setText(LIST_COMMAND); + lDesc.setText(LIST_DESC); + lc.setText(LIST_CLASS_COMMAND); + lcDesc.setText(LIST_CLASS_DESC); + tt.setText(TIMETABLE_COMMAND); + ttDesc.setText(TIMETABLE_DESC); + td.setText(TODAY_COMMAND); + tdDesc.setText(TODAY_DESC); + clear.setText(CLEAR_COMMAND); + clearDesc.setText(CLEAR_DESC); + exit.setText(EXIT_COMMAND); + exitDesc.setText(EXIT_DESC); } /** @@ -90,13 +257,11 @@ public void focus() { } /** - * Copies the URL to the user guide to the clipboard. + * Opens the URL to the user's desktop browser. */ @FXML - private void copyUrl() { - final Clipboard clipboard = Clipboard.getSystemClipboard(); - final ClipboardContent url = new ClipboardContent(); - url.putString(USERGUIDE_URL); - clipboard.setContent(url); + private void openUserGuide() { + Browser.openUrl(USERGUIDE_URL); } + } diff --git a/src/main/java/seedu/address/ui/InputHistory.java b/src/main/java/seedu/address/ui/InputHistory.java new file mode 100644 index 00000000000..5b6a233b8cd --- /dev/null +++ b/src/main/java/seedu/address/ui/InputHistory.java @@ -0,0 +1,74 @@ +package seedu.address.ui; + +import java.util.ArrayList; + +/** + * This class manages the history of prior inputs entered by the user. + */ +public class InputHistory { + + private ArrayList historyList; + private int indexPointer; + + /** + * Constructor for an InputHistory object. + */ + public InputHistory() { + this.historyList = new ArrayList<>(); + indexPointer = 0; + } + + /** + * Add an input to the end of the history list. + * Does not add input if it is the same String as the last input added. + * Resets the pointer to the back of the list. + * @param input String to be added to history + */ + public void add(String input) { + assert(input != null); + + int lastItemIndex = historyList.size() - 1; + if (historyList.isEmpty() || !input.equals(historyList.get(lastItemIndex))) { + historyList.add(input); + } + indexPointer = historyList.size(); + } + + /** + * Returns the input just before the current pointer position in the list. + * If history is empty, returns an empty string. + * If the pointer is already at the start of the list, returns first input saved. + * @return String of the previous input + */ + public String getPreviousInput() { + if (historyList.isEmpty()) { + return ""; + } + + int indexToRetrieve; + if (indexPointer == 0) { + indexToRetrieve = 0; + } else { + indexPointer -= 1; + indexToRetrieve = indexPointer; + } + + return historyList.get(indexToRetrieve); + } + + /** + * Returns the input just after the current pointer position in the list. + * If the pointer is already at the end of the list, returns an empty String. + * @return String of the next input + */ + public String getNextInput() { + if (indexPointer >= historyList.size() - 1) { + indexPointer = historyList.size(); + return ""; + } else { + indexPointer += 1; + return historyList.get(indexPointer); + } + } + +} diff --git a/src/main/java/seedu/address/ui/MainWindow.java b/src/main/java/seedu/address/ui/MainWindow.java index 9106c3aa6e5..1d02ba1cc7f 100644 --- a/src/main/java/seedu/address/ui/MainWindow.java +++ b/src/main/java/seedu/address/ui/MainWindow.java @@ -1,7 +1,9 @@ package seedu.address.ui; + import java.util.logging.Logger; +import javafx.collections.ObservableList; import javafx.event.ActionEvent; import javafx.fxml.FXML; import javafx.scene.control.MenuItem; @@ -16,6 +18,14 @@ import seedu.address.logic.commands.CommandResult; import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.student.Student; +import seedu.address.model.tuition.TuitionClass; +import seedu.address.model.tuition.UniqueTuitionList; +import seedu.address.ui.infopage.InfoPage; +import seedu.address.ui.infopage.StudentInfoPage; +import seedu.address.ui.infopage.TimetableInfoPage; +import seedu.address.ui.infopage.TodayTuitionClassInfoPage; +import seedu.address.ui.infopage.TuitionClassInfoPage; /** * The Main Window. Provides the basic application layout containing @@ -31,7 +41,8 @@ public class MainWindow extends UiPart { private Logic logic; // Independent Ui parts residing in this Ui container - private PersonListPanel personListPanel; + private StudentListPanel studentListPanel; + private TuitionListPanel tuitionListPanel; private ResultDisplay resultDisplay; private HelpWindow helpWindow; @@ -42,7 +53,10 @@ public class MainWindow extends UiPart { private MenuItem helpMenuItem; @FXML - private StackPane personListPanelPlaceholder; + private StackPane studentListPanelPlaceholder; + + @FXML + private StackPane tuitionListPanelPlaceholder; @FXML private StackPane resultDisplayPlaceholder; @@ -50,6 +64,9 @@ public class MainWindow extends UiPart { @FXML private StackPane statusbarPlaceholder; + @FXML + private StackPane infoPagePlaceholder; + /** * Creates a {@code MainWindow} with the given {@code Stage} and {@code Logic}. */ @@ -110,8 +127,11 @@ private void setAccelerator(MenuItem menuItem, KeyCombination keyCombination) { * Fills up all the placeholders of this window. */ void fillInnerParts() { - personListPanel = new PersonListPanel(logic.getFilteredPersonList()); - personListPanelPlaceholder.getChildren().add(personListPanel.getRoot()); + studentListPanel = new StudentListPanel(logic.getFilteredStudentList()); + studentListPanelPlaceholder.getChildren().add(studentListPanel.getRoot()); + + tuitionListPanel = new TuitionListPanel(logic.getFilteredTuitionList()); + tuitionListPanelPlaceholder.getChildren().add(tuitionListPanel.getRoot()); resultDisplay = new ResultDisplay(); resultDisplayPlaceholder.getChildren().add(resultDisplay.getRoot()); @@ -121,6 +141,18 @@ void fillInnerParts() { CommandBox commandBox = new CommandBox(this::executeCommand); commandBoxPlaceholder.getChildren().add(commandBox.getRoot()); + + reminderDisplay(); + } + + + /** + * Display a reminder to tutor about today tuition classes + */ + public void reminderDisplay() { + ObservableList tuitionClasses = logic.getTodayTuitionList(); + TodayTuitionClassInfoPage todayTuitionClassInfoPage = new TodayTuitionClassInfoPage(tuitionClasses); + updateInfoPage(todayTuitionClassInfoPage); } /** @@ -163,8 +195,8 @@ private void handleExit() { primaryStage.hide(); } - public PersonListPanel getPersonListPanel() { - return personListPanel; + public StudentListPanel getStudentListPanel() { + return studentListPanel; } /** @@ -178,13 +210,7 @@ private CommandResult executeCommand(String commandText) throws CommandException logger.info("Result: " + commandResult.getFeedbackToUser()); resultDisplay.setFeedbackToUser(commandResult.getFeedbackToUser()); - if (commandResult.isShowHelp()) { - handleHelp(); - } - - if (commandResult.isExit()) { - handleExit(); - } + executeUiAction(commandResult.getUiAction()); return commandResult; } catch (CommandException | ParseException e) { @@ -193,4 +219,76 @@ private CommandResult executeCommand(String commandText) throws CommandException throw e; } } + + private void executeUiAction(CommandResult.UiAction action) { + switch (action) { + case EXIT: + handleExit(); + break; + case SHOW_HELP: + handleHelp(); + break; + case SHOW_TUITION_PAGE: + showTuitionPage(); + break; + case SHOW_TIMETABLE: + showTimetable(); + break; + case SHOW_STUDENT_PAGE: + showStudentPage(); + break; + case SHOW_TODAY_TUITIONS_PAGE: + reminderDisplay(); + break; + case SET_TUITIONS_DEFAULT: + updateTuitionListTitle(false); + break; + case SET_STUDENTS_DEFAULT: + updateStudentListTitle(false); + break; + case SET_TUITIONS_FILTERED: + updateTuitionListTitle(true); + break; + case SET_STUDENTS_FILTERED: + updateStudentListTitle(true); + break; + case NONE: + updateInfoPage(null); + break; + default: + break; + } + } + + private void showTimetable() { + updateInfoPage(new TimetableInfoPage(UniqueTuitionList.getMostRecentTuitionClasses(), resultDisplay)); + } + + private void showStudentPage() { + updateInfoPage(new StudentInfoPage(Student.getMostRecent())); + } + + private void showTuitionPage() { + updateInfoPage(new TuitionClassInfoPage(TuitionClass.getMostRecent())); + } + + /** + * Updates the Info Page section of the UI with a given info card. + * @param infoPage InfoPage to be placed in the Info Page section. + */ + private void updateInfoPage(InfoPage infoPage) { + infoPagePlaceholder.getChildren().clear(); + if (infoPage != null) { + infoPagePlaceholder.getChildren().add(infoPage.getRoot()); + } + } + + private void updateStudentListTitle(boolean bool) { + studentListPanel.setFiltered(bool); + } + + private void updateTuitionListTitle(boolean bool) { + tuitionListPanel.setFiltered(bool); + } + } diff --git a/src/main/java/seedu/address/ui/PersonListPanel.java b/src/main/java/seedu/address/ui/PersonListPanel.java deleted file mode 100644 index f4c501a897b..00000000000 --- a/src/main/java/seedu/address/ui/PersonListPanel.java +++ /dev/null @@ -1,49 +0,0 @@ -package seedu.address.ui; - -import java.util.logging.Logger; - -import javafx.collections.ObservableList; -import javafx.fxml.FXML; -import javafx.scene.control.ListCell; -import javafx.scene.control.ListView; -import javafx.scene.layout.Region; -import seedu.address.commons.core.LogsCenter; -import seedu.address.model.person.Person; - -/** - * Panel containing the list of persons. - */ -public class PersonListPanel extends UiPart { - private static final String FXML = "PersonListPanel.fxml"; - private final Logger logger = LogsCenter.getLogger(PersonListPanel.class); - - @FXML - private ListView personListView; - - /** - * Creates a {@code PersonListPanel} with the given {@code ObservableList}. - */ - public PersonListPanel(ObservableList personList) { - super(FXML); - personListView.setItems(personList); - personListView.setCellFactory(listView -> new PersonListViewCell()); - } - - /** - * Custom {@code ListCell} that displays the graphics of a {@code Person} using a {@code PersonCard}. - */ - class PersonListViewCell extends ListCell { - @Override - protected void updateItem(Person person, boolean empty) { - super.updateItem(person, empty); - - if (empty || person == null) { - setGraphic(null); - setText(null); - } else { - setGraphic(new PersonCard(person, getIndex() + 1).getRoot()); - } - } - } - -} diff --git a/src/main/java/seedu/address/ui/RemarkEditor.java b/src/main/java/seedu/address/ui/RemarkEditor.java new file mode 100644 index 00000000000..86f2eb811ae --- /dev/null +++ b/src/main/java/seedu/address/ui/RemarkEditor.java @@ -0,0 +1,39 @@ +package seedu.address.ui; + +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; + +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.control.TextArea; +import seedu.address.model.student.Remark; + +/** + * Controller for the remark editor + */ +public class RemarkEditor { + + @FXML + private TextArea textArea; + + @FXML + private Label name; + + /** + * Sets a remark into the text area. + * @param name The name of the student or tuition class being modified. + * @param remark The current remark of the student or tuition class. + */ + public void setRemark(String name, String remark) { + requireAllNonNull(name, remark); + this.name.setText(name); + textArea.setText(remark); + } + + /** + * Returns the text in the text area as a remark. + * @return The new edited remark. + */ + public Remark getNewRemark() { + return new Remark(textArea.getText()); + } +} diff --git a/src/main/java/seedu/address/ui/PersonCard.java b/src/main/java/seedu/address/ui/StudentCard.java similarity index 51% rename from src/main/java/seedu/address/ui/PersonCard.java rename to src/main/java/seedu/address/ui/StudentCard.java index 7fc927bc5d9..9431a369c2e 100644 --- a/src/main/java/seedu/address/ui/PersonCard.java +++ b/src/main/java/seedu/address/ui/StudentCard.java @@ -7,14 +7,17 @@ import javafx.scene.layout.FlowPane; import javafx.scene.layout.HBox; import javafx.scene.layout.Region; -import seedu.address.model.person.Person; +import javafx.scene.layout.StackPane; +import seedu.address.model.student.Student; /** - * An UI component that displays information of a {@code Person}. + * An UI component that displays information of a {@code Student}. */ -public class PersonCard extends UiPart { +public class StudentCard extends UiPart { - private static final String FXML = "PersonListCard.fxml"; + private static final double MAX_TAG_WIDTH = 250; + + private static final String FXML = "StudentListCard.fxml"; /** * Note: Certain keywords such as "location" and "resources" are reserved keywords in JavaFX. @@ -24,7 +27,7 @@ public class PersonCard extends UiPart { * @see The issue on AddressBook level 4 */ - public final Person person; + public final Student student; @FXML private HBox cardPane; @@ -33,28 +36,29 @@ public class PersonCard extends UiPart { @FXML private Label id; @FXML - private Label phone; - @FXML - private Label address; - @FXML - private Label email; - @FXML private FlowPane tags; /** - * Creates a {@code PersonCode} with the given {@code Person} and index to display. + * Creates a {@code StudentCode} with the given {@code Student} and index to display. */ - public PersonCard(Person person, int displayedIndex) { + public StudentCard(Student student, int displayedIndex) { super(FXML); - this.person = person; + this.student = student; id.setText(displayedIndex + ". "); - name.setText(person.getName().fullName); - phone.setText(person.getPhone().value); - address.setText(person.getAddress().value); - email.setText(person.getEmail().value); - person.getTags().stream() + name.setText(student.getName().fullName); + student.getTags().stream() .sorted(Comparator.comparing(tag -> tag.tagName)) - .forEach(tag -> tags.getChildren().add(new Label(tag.tagName))); + .forEach(tag -> tags.getChildren().add(makeTag(tag.tagName))); + } + + private StackPane makeTag(String text) { + Label label = new Label(text); + label.setMaxWidth(MAX_TAG_WIDTH); + label.setWrapText(true); + StackPane stackPane = new StackPane(); + stackPane.setMaxWidth(MAX_TAG_WIDTH); + stackPane.getChildren().add(label); + return stackPane; } @Override @@ -65,13 +69,13 @@ public boolean equals(Object other) { } // instanceof handles nulls - if (!(other instanceof PersonCard)) { + if (!(other instanceof StudentCard)) { return false; } // state check - PersonCard card = (PersonCard) other; + StudentCard card = (StudentCard) other; return id.getText().equals(card.id.getText()) - && person.equals(card.person); + && student.equals(card.student); } } diff --git a/src/main/java/seedu/address/ui/StudentListPanel.java b/src/main/java/seedu/address/ui/StudentListPanel.java new file mode 100644 index 00000000000..b739add5ea0 --- /dev/null +++ b/src/main/java/seedu/address/ui/StudentListPanel.java @@ -0,0 +1,66 @@ +package seedu.address.ui; + +import java.util.logging.Logger; + +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.control.ListCell; +import javafx.scene.control.ListView; +import javafx.scene.layout.Region; +import seedu.address.commons.core.LogsCenter; +import seedu.address.model.student.Student; + +/** + * Panel containing the list of students. + */ +public class StudentListPanel extends UiPart { + private static final String FXML = "StudentListPanel.fxml"; + + private static final String TITLE_DEFAULT = "Students"; + private static final String TITLE_FILTERED = "Students (filtered)"; + + private final Logger logger = LogsCenter.getLogger(StudentListPanel.class); + + @FXML + private ListView studentListView; + + @FXML + private Label title; + + /** + * Creates a {@code StudentListPanel} with the given {@code ObservableList}. + */ + public StudentListPanel(ObservableList studentList) { + super(FXML); + studentListView.setItems(studentList); + studentListView.setCellFactory(listView -> new StudentListViewCell()); + title.setText(TITLE_DEFAULT); + } + + public void setFiltered(boolean bool) { + if (bool) { + title.setText(TITLE_FILTERED); + } else { + title.setText(TITLE_DEFAULT); + } + } + + /** + * Custom {@code ListCell} that displays the graphics of a {@code Student} using a {@code StudentCard}. + */ + class StudentListViewCell extends ListCell { + @Override + protected void updateItem(Student student, boolean empty) { + super.updateItem(student, empty); + + if (empty || student == null) { + setGraphic(null); + setText(null); + } else { + setGraphic(new StudentCard(student, getIndex() + 1).getRoot()); + } + } + } + +} diff --git a/src/main/java/seedu/address/ui/TuitionCard.java b/src/main/java/seedu/address/ui/TuitionCard.java new file mode 100644 index 00000000000..1b5db6145f9 --- /dev/null +++ b/src/main/java/seedu/address/ui/TuitionCard.java @@ -0,0 +1,75 @@ +package seedu.address.ui; + +import java.util.logging.Logger; + +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Region; +import seedu.address.commons.core.LogsCenter; +import seedu.address.model.tuition.TuitionClass; + +/** + * An UI component that displays information of a {@code TuitionClass}. + */ +public class TuitionCard extends UiPart { + + private static final String FXML = "TuitionListCard.fxml"; + + private static final Logger logger = LogsCenter.getLogger(TuitionCard.class); + + private static final String CAPACITY_LABEL = "Capacity: "; + + /** + * Note: Certain keywords such as "location" and "resources" are reserved keywords in JavaFX. + * As a consequence, UI elements' variable names cannot be set to such keywords + * or an exception will be thrown by JavaFX during runtime. + * + * @see The issue on AddressBook level 4 + */ + + public final TuitionClass tuitionClass; + + @FXML + private HBox tuitionPane; + @FXML + private Label id; + @FXML + private Label capacity; + @FXML + private Label timeSlot; + @FXML + private Label name; + + /** + * Creates a {@code TuitionCode} with the given {@code TuitionClass} and index to display. + */ + public TuitionCard(TuitionClass tuitionClass, int displayedIndex) { + super(FXML); + logger.info("TuitionCard" + tuitionClass.toString()); + + this.tuitionClass = tuitionClass; + id.setText(displayedIndex + ". "); + name.setText(tuitionClass.getName().getName()); + capacity.setText(CAPACITY_LABEL + tuitionClass.getStudentCount() + "/" + tuitionClass.getLimit()); + timeSlot.setText(tuitionClass.getTimeslot().getTime()); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof TuitionCard)) { + return false; + } + + // state check + TuitionCard card = (TuitionCard) other; + return id.getText().equals(card.id.getText()) + && tuitionClass.equals(card.tuitionClass); + } +} diff --git a/src/main/java/seedu/address/ui/TuitionListPanel.java b/src/main/java/seedu/address/ui/TuitionListPanel.java new file mode 100644 index 00000000000..b9859d4314c --- /dev/null +++ b/src/main/java/seedu/address/ui/TuitionListPanel.java @@ -0,0 +1,66 @@ +package seedu.address.ui; + + +import java.util.logging.Logger; + +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.scene.control.Label; +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.tuition.TuitionClass; + +/** + * Panel containing the list of tuition classes. + */ +public class TuitionListPanel extends UiPart { + private static final String FXML = "TuitionListPanel.fxml"; + + private static final String TITLE_DEFAULT = "Classes"; + private static final String TITLE_FILTERED = "Classes (filtered)"; + + private final Logger logger = LogsCenter.getLogger(TuitionListPanel.class); + + @FXML + private ListView tuitionListView; + + @FXML + private Label title; + + /** + * Creates a {@code TuitionListPanel} with the given {@code ObservableList}. + */ + public TuitionListPanel(ObservableList tuitionList) { + super(FXML); + tuitionListView.setItems(tuitionList); + tuitionListView.setCellFactory(listView -> new TuitionListViewCell()); + title.setText(TITLE_DEFAULT); + } + + public void setFiltered(boolean bool) { + if (bool) { + title.setText(TITLE_FILTERED); + } else { + title.setText(TITLE_DEFAULT); + } + } + + /** + * Custom {@code ListCell} that displays the graphics of a {@code TuitionClass} using a {@code TuitionCard}. + */ + class TuitionListViewCell extends ListCell { + @Override + protected void updateItem(TuitionClass tuitionClass, boolean empty) { + super.updateItem(tuitionClass, empty); + + if (empty || tuitionClass == null) { + setGraphic(null); + setText(null); + } else { + setGraphic(new TuitionCard(tuitionClass, getIndex() + 1).getRoot()); + } + } + } +} diff --git a/src/main/java/seedu/address/ui/UiManager.java b/src/main/java/seedu/address/ui/UiManager.java index 882027e4537..7ccf8bbde98 100644 --- a/src/main/java/seedu/address/ui/UiManager.java +++ b/src/main/java/seedu/address/ui/UiManager.java @@ -1,16 +1,24 @@ package seedu.address.ui; +import java.io.IOException; +import java.util.Optional; import java.util.logging.Logger; import javafx.application.Platform; +import javafx.fxml.FXMLLoader; import javafx.scene.control.Alert; import javafx.scene.control.Alert.AlertType; +import javafx.scene.control.ButtonType; +import javafx.scene.control.Dialog; +import javafx.scene.control.DialogPane; import javafx.scene.image.Image; import javafx.stage.Stage; import seedu.address.MainApp; import seedu.address.commons.core.LogsCenter; import seedu.address.commons.util.StringUtil; import seedu.address.logic.Logic; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.student.Remark; /** * The manager of the UI component. @@ -18,6 +26,7 @@ public class UiManager implements Ui { public static final String ALERT_DIALOG_PANE_FIELD_ID = "alertDialogPane"; + public static final String REMARK_EDITOR_ERROR_MESSAGE = "Something went wrong with the remark editor!"; private static final Logger logger = LogsCenter.getLogger(UiManager.class); private static final String ICON_APPLICATION = "/images/address_book_32.png"; @@ -86,4 +95,42 @@ private void showFatalErrorDialogAndShutdown(String title, Throwable e) { System.exit(1); } + /** + * Displays a dialog box for the user to edit remarks. + * @param name The name of the student or tuition class being modified. + * @param remarkToEdit The current remark of the student or tuition class. + * @return Returns the updated remark. + * @throws CommandException If unable to load the fxml file for the remark editor. + */ + public static Remark showRemarkEditor(String name, String remarkToEdit) throws CommandException { + try { + FXMLLoader fxmlLoader = new FXMLLoader(); + fxmlLoader.setLocation(UiManager.class.getResource("/view/RemarkEditor.fxml")); + DialogPane remarkEditor = fxmlLoader.load(); + + logger.fine("Showing the remark editor..."); + + RemarkEditor remarkController = fxmlLoader.getController(); + remarkController.setRemark(name, remarkToEdit); + + Dialog dialog = new Dialog<>(); + dialog.setDialogPane(remarkEditor); + dialog.setTitle("Remark Editor"); + Stage stage = (Stage) remarkEditor.getScene().getWindow(); + stage.getIcons().add(new Image(String.valueOf(UiManager.class.getResource("/images/edit_icon.png")))); + + Optional clickedButton = dialog.showAndWait(); + if (clickedButton.get() == ButtonType.OK) { + logger.info("Ok button clicked. Remark Editor closing now."); + return remarkController.getNewRemark(); + } + } catch (IOException e) { + logger.severe(StringUtil.getDetails(e)); + throw new CommandException(REMARK_EDITOR_ERROR_MESSAGE, e); + } + + logger.info("Cancel remark updates. Remark Editor closing now."); + return new Remark(remarkToEdit); + } + } diff --git a/src/main/java/seedu/address/ui/infopage/InfoPage.java b/src/main/java/seedu/address/ui/infopage/InfoPage.java new file mode 100644 index 00000000000..2226664cc63 --- /dev/null +++ b/src/main/java/seedu/address/ui/infopage/InfoPage.java @@ -0,0 +1,12 @@ +package seedu.address.ui.infopage; + +import javafx.scene.layout.Region; +import seedu.address.ui.UiPart; + +public abstract class InfoPage extends UiPart { + + public InfoPage(String fxmlFile) { + super(fxmlFile); + } + +} diff --git a/src/main/java/seedu/address/ui/infopage/StudentInfoPage.java b/src/main/java/seedu/address/ui/infopage/StudentInfoPage.java new file mode 100644 index 00000000000..b0ba8eef0cf --- /dev/null +++ b/src/main/java/seedu/address/ui/infopage/StudentInfoPage.java @@ -0,0 +1,84 @@ +package seedu.address.ui.infopage; + +import java.util.Comparator; +import java.util.logging.Logger; + +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.layout.FlowPane; +import javafx.scene.layout.VBox; +import seedu.address.commons.core.LogsCenter; +import seedu.address.model.student.Student; + +/** + * A container to contain student details. + */ +public class StudentInfoPage extends InfoPage { + + private static final String FXML = "StudentInfoPage.fxml"; + + private static final Logger logger = LogsCenter.getLogger(StudentInfoPage.class); + + /** + * Note: Certain keywords such as "location" and "resources" are reserved keywords in JavaFX. + * As a consequence, UI elements' variable names cannot be set to such keywords + * or an exception will be thrown by JavaFX during runtime. + * + * @see The issue on AddressBook level 4 + */ + + private final Student student; + + @FXML + private VBox studentPagePane; + @FXML + private Label name; + @FXML + private Label phone; + @FXML + private Label address; + @FXML + private Label email; + @FXML + private FlowPane tags; + @FXML + private Label remark; + + /** + * Constructor for a StudentInfoPage + * @param student Student to display information of. + */ + public StudentInfoPage(Student student) { + super(FXML); + logger.info("StudentInfoPage " + student); + + this.student = student; + this.name.setText(student.getName().fullName); + this.phone.setText(student.getPhone().value); + this.remark.setText(student.getRemark().value); + this.address.setText(student.getAddress().value); + this.email.setText(student.getEmail().value); + student.getTags().stream() + .sorted(Comparator.comparing(tag -> tag.tagName)) + .forEach(tag -> tags.getChildren().add(new Label(tag.tagName))); + + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof TuitionClassInfoPage)) { + return false; + } + + // state check + StudentInfoPage otherInfoPage = (StudentInfoPage) other; + return this.student.equals(otherInfoPage.student); + } + +} diff --git a/src/main/java/seedu/address/ui/infopage/TimetableInfoPage.java b/src/main/java/seedu/address/ui/infopage/TimetableInfoPage.java new file mode 100644 index 00000000000..ac370bdd46d --- /dev/null +++ b/src/main/java/seedu/address/ui/infopage/TimetableInfoPage.java @@ -0,0 +1,135 @@ +package seedu.address.ui.infopage; +import java.util.logging.Logger; + +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.geometry.Pos; +import javafx.scene.control.Label; +import javafx.scene.layout.ColumnConstraints; +import javafx.scene.layout.GridPane; +import javafx.scene.layout.RowConstraints; +import javafx.scene.layout.StackPane; +import seedu.address.commons.core.LogsCenter; +import seedu.address.model.tuition.Timetable; +import seedu.address.model.tuition.TuitionClass; +import seedu.address.ui.ResultDisplay; + +/** + * Timetable class which outputs a timetable according to tuition list. + */ +public class TimetableInfoPage extends InfoPage { + private static final String FXML = "TimetableInfoPage.fxml"; + private static final String PANE_BORDER = "-fx-border-width:0.3px;-fx-border-color:black"; + private static final String PANE_NO_BORDER = "-fx-border-width:0px;"; + private static final Logger logger = LogsCenter.getLogger(TimetableInfoPage.class); + private static final String[] days = convertDays(); + + private final int cols = 8; + private Timetable timetable; + + @FXML + private GridPane timetableShown; + + /** + * Displays a timetable on screen to user. + * @param tuitionClasses tuition classes to be shown. + * @param resultDisplay the ResultDisplay panel. + * Used if information of certain classes are not shown due to space limit. + */ + public TimetableInfoPage(ObservableList tuitionClasses, ResultDisplay resultDisplay) { + super(FXML); + logger.info("Starting construction of timetable."); + this.timetable = new Timetable(tuitionClasses, resultDisplay, this); + setTableDay(); + timetable.showTimetable(); + logger.info("Timetable shown."); + } + + /** + * Adds a new lesson into the timetable. + * @param lesson the lesson to be added + * @param col the column of the timetable the lesson is to be added to. + * @param row the row of the timetable the lesson is to be added to. + * @param colSpan the column span of the lesson. + * @param rows the row span of the lesson. + */ + public void addLesson(Label lesson, int col, int row, int colSpan, int rows) { + timetableShown.add(lesson, col, row, colSpan, + rows); + } + + private void setTableDay() { + for (int j = 0; j < cols; j++) { + ColumnConstraints colConst = new ColumnConstraints(); + colConst.setPercentWidth(100.0 / cols); + timetableShown.getColumnConstraints().add(colConst); + } + } + + /** + * Sets vertical size of the table. + * @param start starting time of timetable. + */ + public void setTableTime(int start, int totalRows) { + assert totalRows >= 0 : "Timetable cannot have negative number of rows."; + for (int i = 0; i < totalRows; i++) { + RowConstraints rowConst = new RowConstraints(); + rowConst.setPercentHeight(100.0 / totalRows); + timetableShown.getRowConstraints().add(rowConst); + } + for (int i = 0; i < 8; i++) { + for (int j = 0; j < totalRows; j++) { + StackPane pane; + if (j == 0 && i != 0) { + pane = getDayPane(i); + } else if (j % 6 == 0 && i == 0 && j != 0) { + pane = getTimePane(start - 1 + j / 6); + } else { + pane = new StackPane(); + } + + if (j % 6 == 0) { + pane.setStyle(PANE_BORDER); + } else { + pane.setStyle(PANE_NO_BORDER); + } + int rowSpan = j % 6 == 0 ? 6 : i == 0 ? 6 : 1; + timetableShown.add(pane, i, j, 1, rowSpan); + } + } + } + + /** + * Produces a header for the timetable. + * @param i determines which day to put in the header. + * @return a StackPane with the day in it. + */ + private StackPane getDayPane(int i) { + StackPane pane = new StackPane(); + Label label = new Label(days[i - 1]); + label.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE); + label.setAlignment(Pos.CENTER); + pane.getChildren().add(label); + return pane; + } + + /** + * Produces a header for the timetable. + * @param i determines what time to put in the header. + * @return a StackPane with the time in it. + */ + private StackPane getTimePane(int i) { + StackPane pane = new StackPane(); + Label label = new Label(i + ":00"); + label.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE); + label.setAlignment(Pos.CENTER); + pane.getChildren().add(label); + return pane; + } + + private static String[] convertDays() { + String[] dayOfWeek = new String[]{"Mon", "Tue", "Wed", + "Thu", "Fri", "Sat", "Sun"}; + return dayOfWeek; + } +} diff --git a/src/main/java/seedu/address/ui/infopage/TodayTuitionClassInfoPage.java b/src/main/java/seedu/address/ui/infopage/TodayTuitionClassInfoPage.java new file mode 100644 index 00000000000..bf121f1df8e --- /dev/null +++ b/src/main/java/seedu/address/ui/infopage/TodayTuitionClassInfoPage.java @@ -0,0 +1,60 @@ +package seedu.address.ui.infopage; + +import java.util.logging.Logger; + +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.control.ListCell; +import javafx.scene.control.ListView; +import seedu.address.commons.core.LogsCenter; +import seedu.address.model.tuition.TuitionClass; +import seedu.address.ui.TuitionCard; + + +public class TodayTuitionClassInfoPage extends InfoPage { + private static final String FXML = "TodayTuitionClassInfoPage.fxml"; + + private static final Logger logger = LogsCenter.getLogger(TodayTuitionClassInfoPage.class); + + @FXML + private ListView todayTuitionInfoPageList; + + @FXML + private Label title; + + private ObservableList tuitionClassList; + + /** + * Constructor fot TodayTuitionClassInfoPage + * @param tuitionClasses a list that contains today tuition classes + */ + public TodayTuitionClassInfoPage(ObservableList tuitionClasses) { + super(FXML); + logger.info("TodayTuitionClassInfoPage " + tuitionClasses.toString()); + this.tuitionClassList = tuitionClasses; + + todayTuitionInfoPageList.setItems(tuitionClassList); + todayTuitionInfoPageList.setCellFactory(listView -> new TuitionListViewCell()); + + title.setText("Today's Tuition Classes"); + + } + + /** + * Custom {@code ListCell} that displays the graphics of a {@code TuitionClass} using a {@code TuitionCard}. + */ + class TuitionListViewCell extends ListCell { + @Override + protected void updateItem(TuitionClass tuitionClass, boolean empty) { + super.updateItem(tuitionClass, empty); + + if (empty || tuitionClass == null) { + setGraphic(null); + setText(null); + } else { + setGraphic(new TuitionCard(tuitionClass, getIndex() + 1).getRoot()); + } + } + } +} diff --git a/src/main/java/seedu/address/ui/infopage/TuitionClassInfoPage.java b/src/main/java/seedu/address/ui/infopage/TuitionClassInfoPage.java new file mode 100644 index 00000000000..081fb3d9e43 --- /dev/null +++ b/src/main/java/seedu/address/ui/infopage/TuitionClassInfoPage.java @@ -0,0 +1,80 @@ +package seedu.address.ui.infopage; + +import java.util.logging.Logger; + +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.layout.VBox; +import seedu.address.commons.core.LogsCenter; +import seedu.address.model.tuition.TuitionClass; + +/** + * A container to contain tuition class details. + */ +public class TuitionClassInfoPage extends InfoPage { + + private static final String FXML = "TuitionClassInfoPage.fxml"; + + private static final Logger logger = LogsCenter.getLogger(TuitionClassInfoPage.class); + + /** + * Note: Certain keywords such as "location" and "resources" are reserved keywords in JavaFX. + * As a consequence, UI elements' variable names cannot be set to such keywords + * or an exception will be thrown by JavaFX during runtime. + * + * @see The issue on AddressBook level 4 + */ + + private final TuitionClass tuitionClass; + + @FXML + private VBox tuitionPagePane; + @FXML + private Label name; + @FXML + private Label sizeLimit; + @FXML + private Label studentCount; + @FXML + private Label timeSlot; + @FXML + private Label students; + @FXML + private Label remark; + + /** + * Constructor for a TuitionClassInfoPage + * @param tuitionClass Tuition Class to display information of. + */ + public TuitionClassInfoPage(TuitionClass tuitionClass) { + super(FXML); + logger.info("TuitionClassInfoPage " + tuitionClass); + + this.tuitionClass = tuitionClass; + this.name.setText(tuitionClass.getName().toString()); + this.sizeLimit.setText(tuitionClass.getLimit().toString()); + this.studentCount.setText("" + tuitionClass.getStudentCount()); + this.timeSlot.setText(tuitionClass.getTimeslot().toString()); + this.students.setText(tuitionClass.listStudents()); + this.remark.setText(tuitionClass.getRemark().toString()); + } + + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof TuitionClassInfoPage)) { + return false; + } + + // state check + TuitionClassInfoPage otherInfoPage = (TuitionClassInfoPage) other; + return this.tuitionClass.equals(otherInfoPage.tuitionClass); + } + +} diff --git a/src/main/resources/images/edit_icon.png b/src/main/resources/images/edit_icon.png new file mode 100644 index 00000000000..8d0f9fbb8f3 Binary files /dev/null and b/src/main/resources/images/edit_icon.png differ diff --git a/src/main/resources/images/female_student_icon.png b/src/main/resources/images/female_student_icon.png new file mode 100644 index 00000000000..802b7640d92 Binary files /dev/null and b/src/main/resources/images/female_student_icon.png differ diff --git a/src/main/resources/images/male_student_icon.png b/src/main/resources/images/male_student_icon.png new file mode 100644 index 00000000000..5f697554fe6 Binary files /dev/null and b/src/main/resources/images/male_student_icon.png differ diff --git a/src/main/resources/view/CommandBox.fxml b/src/main/resources/view/CommandBox.fxml index 09f6d6fe9e4..028543e00ba 100644 --- a/src/main/resources/view/CommandBox.fxml +++ b/src/main/resources/view/CommandBox.fxml @@ -3,7 +3,6 @@ - - + + - diff --git a/src/main/resources/view/DarkTheme.css b/src/main/resources/view/DarkTheme.css index 9ce9bcfb569..cff75e38641 100644 --- a/src/main/resources/view/DarkTheme.css +++ b/src/main/resources/view/DarkTheme.css @@ -27,6 +27,7 @@ .text-field { -fx-font-size: 12pt; -fx-font-family: "Open Sans Semibold"; + -fx-text-fill: white; } .tab-pane { @@ -123,13 +124,13 @@ .cell_big_label { -fx-font-family: "Open Sans Semibold"; -fx-font-size: 16px; - -fx-text-fill: #010504; + -fx-text-fill: white; } .cell_small_label { -fx-font-family: "Open Sans Regular"; -fx-font-size: 13px; - -fx-text-fill: #010504; + -fx-text-fill: #ECECEC; } .stack-pane { @@ -328,7 +329,7 @@ -fx-text-fill: white; } -#filterField, #personListPanel, #personWebpage { +#filterField, #studentListPanel, #studentWebpage { -fx-effect: innershadow(gaussian, black, 10, 0, 0, 0); } diff --git a/src/main/resources/view/HelpWindow.fxml b/src/main/resources/view/HelpWindow.fxml index c9a38f2b105..56378b34cf8 100644 --- a/src/main/resources/view/HelpWindow.fxml +++ b/src/main/resources/view/HelpWindow.fxml @@ -5,8 +5,14 @@ + + + + + + @@ -19,27 +25,232 @@ - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/MainWindow.fxml b/src/main/resources/view/MainWindow.fxml index 32bcf2c8e70..6d28ff23fac 100644 --- a/src/main/resources/view/MainWindow.fxml +++ b/src/main/resources/view/MainWindow.fxml @@ -6,25 +6,29 @@ - + + + + + + - + - + - + @@ -34,27 +38,64 @@ - + - + - + - + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - + + + + + + + + + + + + + + + + + + + + + - + diff --git a/src/main/resources/view/PersonListPanel.fxml b/src/main/resources/view/PersonListPanel.fxml deleted file mode 100644 index 8836d323cc5..00000000000 --- a/src/main/resources/view/PersonListPanel.fxml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/src/main/resources/view/RemarkEditor.fxml b/src/main/resources/view/RemarkEditor.fxml new file mode 100644 index 00000000000..ae2771639fe --- /dev/null +++ b/src/main/resources/view/RemarkEditor.fxml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + +