diff --git a/README.md b/README.md index 16208adb9b6..76e45b50c4d 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,17 @@ -[![CI Status](https://github.com/se-edu/addressbook-level3/workflows/Java%20CI/badge.svg)](https://github.com/se-edu/addressbook-level3/actions) +[![CI Status](https://github.com/AY2425S2-CS2103T-T13-4/tp/workflows/Java%20CI/badge.svg)](https://github.com/AY2425S2-CS2103T-T13-4/tp/actions) ![Ui](docs/images/Ui.png) +The product is designed for freelance tutors who teach small groups or individual students. These tutors often manage multiple students across different subjects and require an efficient way to track student progress, lesson schedules, and important notes. The user is proficient in typing and prefers CLI over mouse-based interactions. -* 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. +### Value Propositions: + +Freelance tutors often struggle with managing student records efficiently using scattered documents, spreadsheets, or manual note-taking. Existing software may be too complex, designed for large institutions, or require unnecessary overhead. + +Our product provides a CLI-based, lightweight tutor management system that allows tutors to: +* Quickly organise student contact details. +* Track lesson schedules. +* Log student progress. + +The project is built upon 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/#contributing-to-se-edu) for more info. diff --git a/build.gradle b/build.gradle index 0db3743584e..1bae5a10d2a 100644 --- a/build.gradle +++ b/build.gradle @@ -66,7 +66,11 @@ dependencies { } shadowJar { - archiveFileName = 'addressbook.jar' + archiveFileName = 'tutortrack.jar' } defaultTasks 'clean', 'test' + +run { + enableAssertions = true +} diff --git a/copyright.txt b/copyright.txt index 93aa2a39ce2..80ef4e5439f 100644 --- a/copyright.txt +++ b/copyright.txt @@ -1,5 +1,6 @@ Some code adapted from http://code.makery.ch/library/javafx-8-tutorial/ by Marco Jakob + Copyright by Susumu Yoshida - http://www.mcdodesign.com/ - address_book_32.png - AddressApp.ico diff --git a/docs/AboutUs.md b/docs/AboutUs.md index ff3f04abd02..707a1a0aeb0 100644 --- a/docs/AboutUs.md +++ b/docs/AboutUs.md @@ -9,51 +9,52 @@ You can reach us at the email `seer[at]comp.nus.edu.sg` ## Project team -### John Doe +### Charles - + -[[homepage](http://www.comp.nus.edu.sg/~damithch)] -[[github](https://github.com/johndoe)] -[[portfolio](team/johndoe.md)] +[[github](https://github.com/charlesl12)] +[[portfolio](team/charlesl12.md)] -* Role: Project Advisor +* Role: Developer +* Responsibilities: Code quality + Testing -### Jane Doe +### Nicholas Oh - + -[[github](http://github.com/johndoe)] -[[portfolio](team/johndoe.md)] +[[homepage](http://www.comp.nus.edu.sg/~nohjj)] +[[github](https://github.com/nicholasohjj)] +[[portfolio](team/nicholasohjj.md)] * Role: Team Lead -* Responsibilities: UI +* Responsibilities: Documentation, deliverables and deadlines -### Johnny Doe +### Guan Zhong - + -[[github](http://github.com/johndoe)] [[portfolio](team/johndoe.md)] +[[github](https://github.com/gztan23)] +[[portfolio](team/gztan23.md)] * Role: Developer -* Responsibilities: Data +* Responsibilities: Code quality -### Jean Doe +### Yan Andong - + -[[github](http://github.com/johndoe)] -[[portfolio](team/johndoe.md)] +[[github](http://github.com/andong0909)] [[portfolio](team/andong.md)] * Role: Developer -* Responsibilities: Dev Ops + Threading +* Responsibilities: Data -### James Doe +### Wang Zimeng - + -[[github](http://github.com/johndoe)] -[[portfolio](team/johndoe.md)] +[[github](http://github.com/jiangsuwangjing)] +[[portfolio](team/jiangsuwangjing.md)] * Role: Developer * Responsibilities: UI diff --git a/docs/DeveloperGuide.md b/docs/DeveloperGuide.md index 743c65a49d2..65445c1d369 100644 --- a/docs/DeveloperGuide.md +++ b/docs/DeveloperGuide.md @@ -2,20 +2,23 @@ layout: page title: Developer Guide --- + * Table of Contents {:toc} --------------------------------------------------------------------------------------------------------------------- +---------------------------------------------------------------------------------------------------------------------- ## **Acknowledgements** -* {list here sources of all reused/adapted ideas, code, documentation, and third-party libraries -- include links to the original source as well} +- JavaFX Tutorial adapted from [Oracle JavaFX documentation](https://openjfx.io/) +- PlantUML diagrams adapted from [se-edu PlantUML Guide](https://se-education.org/guides/tutorials/plantUml.html) +- Command parsing approach adapted from [AddressBook-Level3](https://github.com/se-edu/addressbook-level3) -------------------------------------------------------------------------------------------------------------------- ## **Setting up, getting started** -Refer to the guide [_Setting up and getting started_](SettingUp.md). +Refer to the [_Setting up and Getting Started Guide_](SettingUp.md). -------------------------------------------------------------------------------------------------------------------- @@ -51,9 +54,9 @@ The bulk of the app's work is done by the following four components: **How the architecture components interact with each other** -The *Sequence Diagram* below shows how the components interact with each other for the scenario where the user issues the command `delete 1`. +The *Sequence Diagram* below shows how the components interact with each other for the scenario where the user issues the command `delete_student 1`. - + Each of the four main components (also shown in the diagram above), @@ -72,7 +75,7 @@ The **API** of this component is specified in [`Ui.java`](https://github.com/se- ![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`, `ListPanel`, `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) @@ -81,7 +84,11 @@ 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 and `Lesson` object residing in the `Model`. + +In addition to displaying students via `ListPanel` and `ListCard`, the `UI` also supports displaying lessons. This is achieved through: +* `ListPanel`: a JavaFX `UI` component that lists all lessons in the application. It is managed by the MainWindow class. +* `LessonCard`: a reusable `UI` part that renders information about a single lesson (e.g. subject, time, and date) using FXML and JavaFX. It is used within `ListPanel`. ### Logic component @@ -91,54 +98,48 @@ Here's a (partial) class diagram of the `Logic` component: -The sequence diagram below illustrates the interactions within the `Logic` component, taking `execute("delete 1")` API call as an example. +The sequence diagram below illustrates the interactions within the `Logic` component, taking `execute("delete_student 1")` API call as an example. -![Interactions Inside the Logic Component for the `delete 1` Command](images/DeleteSequenceDiagram.png) +![Interactions Inside the Logic Component for the `delete_student 1` Command](images/DeleteStudentSequenceDiagram.png) -
:information_source: **Note:** The lifeline for `DeleteCommandParser` should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline continues till the end of diagram. +
:information_source: **Note:** The lifeline for `DeleteStudentCommandParser` should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline continues till the end of diagram.
How the `Logic` component works: -1. When `Logic` is called upon to execute a command, it is passed to an `AddressBookParser` object which in turn creates a parser that matches the command (e.g., `DeleteCommandParser`) and uses it to parse the command. -1. This results in a `Command` object (more precisely, an object of one of its subclasses e.g., `DeleteCommand`) which is executed by the `LogicManager`. -1. The command can communicate with the `Model` when it is executed (e.g. to delete a person).
+1. When `Logic` is called upon to execute a command, it is passed to an `AddressBookParser` object which in turn creates a parser that matches the command (e.g., `DeleteStudentCommandParser`) and uses it to parse the command. +1. This results in a `Command` object (more precisely, an object of one of its subclasses e.g., `DeleteStudentCommandParser`) which is executed by the `LogicManager`. +1. The command can communicate with the `Model` when it is executed (e.g. to delete a student).
Note that although this is shown as a single step in the diagram above (for simplicity), in the code it can take several interactions (between the command object and the `Model`) to achieve. 1. The result of the command execution is encapsulated as a `CommandResult` object which is returned back from `Logic`. Here are the other classes in `Logic` (omitted from the class diagram above) that are used for parsing a user command: - + How the parsing works: -* When called upon to parse a user command, the `AddressBookParser` class creates an `XYZCommandParser` (`XYZ` is a placeholder for the specific command name e.g., `AddCommandParser`) which uses the other classes shown above to parse the user command and create a `XYZCommand` object (e.g., `AddCommand`) which the `AddressBookParser` returns back as a `Command` object. -* All `XYZCommandParser` classes (e.g., `AddCommandParser`, `DeleteCommandParser`, ...) inherit from the `Parser` interface so that they can be treated similarly where possible e.g, during testing. +* When called upon to parse a user command, the `AddressBookParser` class creates an `XYZCommandParser` (`XYZ` is a placeholder for the specific command name e.g., `AddStudentCommandParser`) which uses the other classes shown above to parse the user command and create a `XYZCommand` object (e.g., `AddStudentCommand`) which the `AddressBookParser` returns back as a `Command` object. +* All `XYZCommandParser` classes (e.g., `AddStudentCommandParser`, `DeleteStudentCommandParser`, ...) 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) - +![Overview of the Model component](images/BetterModelClassDiagram.png) The `Model` component, -* stores the address book data i.e., all `Person` objects (which are contained in a `UniquePersonList` object). -* stores the currently 'selected' `Person` objects (e.g., results of a search query) as a separate _filtered_ list which is exposed to outsiders as an unmodifiable `ObservableList` that can be 'observed' e.g. the UI can be bound to this list so that the UI automatically updates when the data in the list change. +* stores the address book data i.e., all `Student` and `Lesson` objects (which are contained in a `UniqueStudentList` and `UniqueLessonList` object respectively). +* maintains a list of currently selected `Student` and `Lesson` objects (e.g., results of a search query) as separate filtered lists, each exposed as an unmodifiable`ObservableList` and `ObservableList` that can be observed. This allows the UI to bind to these lists so that it automatically updates when the data in the lists change. * stores a `UserPref` object that represents the user’s preferences. This is exposed to the outside as a `ReadOnlyUserPref` objects. * does not depend on any of the other three components (as the `Model` represents data entities of the domain, they should make sense on their own without depending on other components) -
:information_source: **Note:** An alternative (arguably, a more OOP) model is given below. It has a `Tag` list in the `AddressBook`, which `Person` references. This allows `AddressBook` to only require one `Tag` object per unique tag, instead of each `Person` needing their own `Tag` objects.
- - - -
- ### Storage component **API** : [`Storage.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/storage/Storage.java) - + The `Storage` component, * can save both address book data and user preference data in JSON format, and read them back into corresponding objects. @@ -159,37 +160,36 @@ This section describes some noteworthy details on how certain features are imple #### Proposed Implementation -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: +The proposed undo/redo mechanism is facilitated by `VersionedTutorTrack`. It extends `TutorTrack` with an undo/redo history, stored internally as a `tutorTrackStateList` and `currentStatePointer`. Additionally, it implements the following operations: -* `VersionedAddressBook#commit()` — Saves the current address book state in its history. -* `VersionedAddressBook#undo()` — Restores the previous address book state from its history. -* `VersionedAddressBook#redo()` — Restores a previously undone address book state from its history. +* `VersionedTutorTrack#commit()` — Saves the current TutorTrack state in its history. +* `VersionedTutorTrack#undo()` — Restores the previous TutorTrack state from its history. +* `VersionedTutorTrack#redo()` — Restores a previously undone TutorTrack state from its history. -These operations are exposed in the `Model` interface as `Model#commitAddressBook()`, `Model#undoAddressBook()` and `Model#redoAddressBook()` respectively. +These operations are exposed in the `Model` interface as `Model#commitTutorTrack()`, `Model#undoTutorTrack()` and `Model#redoTutorTrack()` respectively. Given below is an example usage scenario and how the undo/redo mechanism behaves at each step. -Step 1. The user launches the application for the first time. The `VersionedAddressBook` will be initialized with the initial address book state, and the `currentStatePointer` pointing to that single address book state. +Step 1. The user launches the application for the first time. The `VersionedTutorTrack` will be initialized with the initial TutorTrack state, and the `currentStatePointer` will point to that single TutorTrack state. ![UndoRedoState0](images/UndoRedoState0.png) -Step 2. The user executes `delete 5` command to delete the 5th person in the address book. The `delete` command calls `Model#commitAddressBook()`, causing the modified state of the address book after the `delete 5` command executes to be saved in the `addressBookStateList`, and the `currentStatePointer` is shifted to the newly inserted address book state. +Step 2. The user executes the command `delete_student 5` to delete the 5th student in TutorTrack. The `delete` command calls `Model#commitTutorTrack()`, causing the modified state after the command execution to be saved in the `tutorTrackStateList`. The `currentStatePointer` shifts to the newly added state. ![UndoRedoState1](images/UndoRedoState1.png) -Step 3. The user executes `add n/David …​` to add a new person. The `add` command also calls `Model#commitAddressBook()`, causing another modified address book state to be saved into the `addressBookStateList`. +Step 3. The user executes `add_student n/David …​` to add a new student. The `add_student` command also calls `Model#commitTutorTrack()`, causing another modified TutorTrack state to be saved into the `tutorTrackStateList`. ![UndoRedoState2](images/UndoRedoState2.png) -
:information_source: **Note:** If a command fails its execution, it will not call `Model#commitAddressBook()`, so the address book state will not be saved into the `addressBookStateList`. - +
:information_source: **Note:** If the `currentStatePointer` is at index 0 (the initial state), there are no earlier states to restore. `Model#canUndoTutorTrack()` checks this and returns an error if undo is not possible.
-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#undoTutorTrack()`, which will shift the `currentStatePointer` once to the left, pointing it to the previous TutorTrack state, and restores the previous 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 +
:information_source: **Note:** If the `currentStatePointer` is at index 0, pointing to the initial TutorTrack state, then there are no previous states to restore. The `undo` command uses `Model#canUndoTutorTrack()` to check if this is the case. If so, it will return an error to the user rather than attempting to perform the undo.
@@ -206,17 +206,17 @@ Similarly, how an undo operation goes through the `Model` component is shown bel ![UndoSequenceDiagram](images/UndoSequenceDiagram-Model.png) -The `redo` command does the opposite — it calls `Model#redoAddressBook()`, which shifts the `currentStatePointer` once to the right, pointing to the previously undone state, and restores the address book to that state. +The `redo` command reverses the undo. It calls `Model#redoTutorTrack()`, shifting the `currentStatePointer` one step forward to restore the next state. -
:information_source: **Note:** If the `currentStatePointer` is at index `addressBookStateList.size() - 1`, pointing to the latest address book state, then there are no undone AddressBook states to restore. The `redo` command uses `Model#canRedoAddressBook()` to check if this is the case. If so, it will return an error to the user rather than attempting to perform the redo. +
:information_source: **Note:** If the `currentStatePointer` is already at the latest state (i.e., index `tutorTrackStateList.size() - 1`), `redo` is not possible. `Model#canRedoTutorTrack()` handles this check.
-Step 5. The user then decides to execute the command `list`. Commands that do not modify the address book, such as `list`, will usually not call `Model#commitAddressBook()`, `Model#undoAddressBook()` or `Model#redoAddressBook()`. Thus, the `addressBookStateList` remains unchanged. +Step 5. The user executes a non-mutating command like `list`. Such commands do not modify the state and do not trigger any undo/redo behavior. The `tutorTrackStateList` remains unchanged. ![UndoRedoState4](images/UndoRedoState4.png) -Step 6. The user executes `clear`, which calls `Model#commitAddressBook()`. Since the `currentStatePointer` is not pointing at the end of the `addressBookStateList`, all address book states after the `currentStatePointer` will be purged. Reason: It no longer makes sense to redo the `add n/David …​` command. This is the behavior that most modern desktop applications follow. +Step 6. The user executes `clear`. Since `clear` calls `Model#commitTutorTrack()` and the `currentStatePointer` is not at the latest state, all states after the pointer are purged — making redo impossible. This mirrors typical desktop application behavior. ![UndoRedoState5](images/UndoRedoState5.png) @@ -224,26 +224,19 @@ The following activity diagram summarizes what happens when a user executes a ne -#### Design considerations: +#### Design considerations **Aspect: How undo & redo executes:** -* **Alternative 1 (current choice):** Saves the entire address book. +* **Alternative 1 (current choice):** Saves the entire addressbook. * 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). + * Pros: Will use less memory (e.g. for `delete_student`, 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** @@ -262,121 +255,439 @@ _{Explain here how the data archiving feature will be implemented}_ **Target user profile**: -* has a need to manage a significant number of contacts +* is a Singaporean freelance tutor, who teaches small groups or individual students +* has a need to manage a significant number of students, with various lessons and assignments * 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**: Quickly organise student contact details, track lesson schedules, log student progress all in one app ### User stories Priorities: High (must have) - `* * *`, Medium (nice to have) - `* *`, Low (unlikely to have) - `*` -| Priority | As a …​ | I want to …​ | So that I can…​ | -| -------- | ------------------------------------------ | ------------------------------ | ---------------------------------------------------------------------- | -| `* * *` | new user | see usage instructions | refer to instructions when I forget how to use the App | -| `* * *` | user | add a new person | | -| `* * *` | user | delete a person | remove entries that I no longer need | -| `* * *` | user | find a person by name | locate details of persons without having to go through the entire list | -| `* *` | user | hide private contact details | minimize chance of someone else seeing them by accident | -| `*` | user with many persons in the address book | sort persons by name | locate a person easily | +| Priority | As a …​ | I want to …​ | So that I can…​ | +|----------|-----------------|----------------------------------------------------------------|-------------------------------------------------------------------------------------------| +| `* * *` | tutor | add a new student | keep track of their details | +| `* * *` | tutor | add lessons for each student | make a lesson plan suited for my students | +| `* * *` | tutor | view lessons for each student | have an overview of the students' learning curriculum | +| `* * *` | tutor | assign tasks to students | track student's workload and assignments | +| `* * *` | tutor | view list of all students | view all students that I am teaching | +| `* * *` | tutor | delete a student | remove students that I am no longer tutoring | +| `* * *` | tutor | track completion status of assignments | know if my students have completed them | +| `* *` | tutor | reschedule lessons | make changes to lesson plans to better fit mine or my student's schedule | +| `* *` | tutor | mark lessons as complete | review session history and track my students' lesson progress | +| `* *` | tutor | set personalized reminders for students | address individual needs effectively | +| `* *` | tutor | set reminders for my own tasks | keep up with what I need to do | +| `* *` | user | add custom tags to contacts | organise my contacts better | +| `* *` | tutor | filter students by status (e.g. active or inactive) | manage my student's long-term engagements | +| `* *` | tutor | filter students by keywords and tags | easily find a student | +| `* *` | tutor | search for a student by name | quickly find their records | +| `* *` | tutor | update student details | keep their information accurate | +| `* *` | new user | view help documentation | understand how to interact with the application effectively | +| `* *` | long-time user | add shortcuts to commands | personalize my use of the app | +| `*` | tutor | export schedules to my personal calendar | manage the tutoring schedule with my other commitments | +| `*` | new user | import data from a spreadsheet | start to keep track of my students | +| `*` | first time user | use commands with contextual help | learn proper command syntax and options without having to refer to external documentation | +| `*` | tutor | get a timeline overview of all events within a period of time | view the overall structure of the schedule for said period of time | + +# **Use Cases for TutorTrack** + +## Use Case 1: Add Student + +**System**: TutorTrack\ +**Actor**: Tutor\ +**Use Case**: UC01 - Add Student + +### Main Success Scenario (MSS) + +1. Tutor enters a command to add a student, including name, phone number, address, and email. Subject(s) may be optionally included. +2. TutorTrack validates the input and ensures the student does not already exist. +3. TutorTrack saves the student information and confirms the addition. + + **Use case ends.** + +### Extensions + +- **1a**: Tutor enters an invalid phone number format. + - 1a1: TutorTrack warns the tutor about the invalid phone number. + - 1a2: TutorTrack prompts the tutor to enter the command again. + - Use case resumes from step 1. + +- **1b**: Tutor enters an invalid email format. + - 1b1: TutorTrack warns the tutor about the invalid email. + - 1b2: TutorTrack prompts the tutor to enter the command again. + - Use case resumes from step 1. + +- **1c**: Tutor tries to add a student that already exists (same name and phone number). + - 1c1: TutorTrack informs the tutor that the student already exists. + - **Use case ends.** + +- **1d**: Tutor enters invalid subject format. + - 1d1: TutorTrack warns the tutor about the invalid subject. + - 1d2: TutorTrack prompts the tutor to enter the command again. + - Use case resumes from step 1. + +--- + +## Use Case 2: View Student List + +**System**: TutorTrack\ +**Actor**: Tutor\ +**Use Case**: UC02 - View Student List + +### Main Success Scenario (MSS) + +1. Tutor enters the command to view all students. +2. TutorTrack retrieves and displays all registered students. + + **Use case ends.** + +### Extensions + +- **1a**: No students are found in the system. + - 1a1: TutorTrack informs the tutor that no students are available. + - **Use case ends.** + +--- + +## Use Case 3: Delete Student + +**System**: TutorTrack\ +**Actor**: Tutor\ +**Use Case**: UC03 - Delete Student + +### Main Success Scenario (MSS) + +1. Tutor enters the command to delete a student using the student index. +2. TutorTrack validates the index and removes the student. +3. TutorTrack confirms the deletion. + + **Use case ends.** + +### Extensions + +- **1a**: The entered student index is out of bound. + - 1a1: TutorTrack warns the tutor that the index is not valid. + - **Use case ends.** + +--- + +## Use Case 4: Add Lesson + +**System**: TutorTrack\ +**Actor**: Tutor\ +**Use Case**: UC04 - Add Lesson + +### Main Success Scenario (MSS) + +1. Tutor enters a command to add a lesson, including student name, date, time, and subject. +2. TutorTrack validates the input and ensures no duplicate lesson exists at the same time. +3. TutorTrack saves the lesson and confirms the addition. + + **Use case ends.** + +### Extensions + +- **1a**: Student not found. + - 1a1: TutorTrack informs the tutor that the student does not exist. + - **Use case ends.** + +- **1b**: Tutor enters an invalid date or time format. + - 1b1: TutorTrack warns the tutor about the incorrect format. + - 1b2: TutorTrack prompts the tutor to enter the command again. + - Use case resumes from step 1. + +- **1c**: A duplicate lesson exists for the same student at the same time. + - 1c1: TutorTrack warns the tutor about the duplicate lesson. + - **Use case ends.** + +--- + +## Use Case 5: View Lessons + +**System**: TutorTrack\ +**Actor**: Tutor\ +**Use Case**: UC05 - View Lessons -*{More to be added}* +### Main Success Scenario (MSS) -### Use cases +1. Tutor enters the command to view all lessons, optionally with keywords of student names. +2. TutorTrack retrieves and displays all lessons. -(For all use cases below, the **System** is the `AddressBook` and the **Actor** is the `user`, unless specified otherwise) + **Use case ends.** -**Use case: Delete a person** +### Extensions + +- **1a**: No student matches the keywords. + - 1a1: TutorTrack informs the tutor that no lessons are available. + - **Use case ends.** + +--- -**MSS** +## Use Case 6: Create Assignment to a Student -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 +**System**: TutorTrack\ +**Actor**: Tutor\ +**Use Case**: UC06 - Create Assignment - Use case ends. +### Main Success Scenario (MSS) -**Extensions** +1. Tutor enters a command to create an assignment, including the student's index, assignment name, and due date. +2. TutorTrack validates the input and ensures the assignment does not already exist for the student. +3. TutorTrack saves the assignment and confirms the addition. -* 2a. The list is empty. + **Use case ends.** - Use case ends. +### Extensions -* 3a. The given index is invalid. +- **1a**: Student index is out of bounds. + - 1a1: TutorTrack informs the tutor that the student was not found. + - **Use case ends.** + +- **1b**: Tutor enters an invalid due date (e.g., past date). + - 1b1: TutorTrack warns the tutor about the invalid date. + - 1b2: TutorTrack prompts the tutor to enter the command again. + - Use case resumes from step 1. + +- **1c**: Assignment already exists for the student. + - 1c1: TutorTrack warns the tutor about the duplicate assignment. + - **Use case ends.** + +- **1d**: Tutor enters a blank assignment name. + - 1d1: TutorTrack warns the tutor about the blank assignment name. + - 1d2: TutorTrack prompts the tutor to enter the command again. + - **Use case ends.** + +--- - * 3a1. AddressBook shows an error message. +## Use Case 7: Track Assignment Completion - Use case resumes at step 2. +**System**: TutorTrack\ +**Actor**: Tutor\ +**Use Case**: UC07 - Toggle Assignment Completion Status -*{More to be added}* +### Main Success Scenario (MSS) -### Non-Functional Requirements +1. Tutor enters the command to mark/unmark an assignment as completed/incomplete. +2. TutorTrack updates the assignment status and confirms completion. + + **Use case ends.** + +### Extensions + +- **1a**: Student index out of bound. + - 1a1: TutorTrack informs the tutor that the index is out of bound. + - 1a2: TutorTrack prompts the tutor to enter the command again. + - Use case resumes from step 1. + +- **1b**: Assignment not found. + - 1b1: TutorTrack informs the tutor that the assignment does not exist. + - **Use case ends.** + +- **1c**: Tutor enters a blank assignment name. + - 1c1: TutorTrack warns the tutor about the assignment name is missing. + - 1c2: TutorTrack prompts the tutor to enter the command again. + - **Use case ends.** + +- **1d**: Assignment already in the target (e.g., tutor tries to mark completed when already completed). + - 1d1: TutorTrack informs the tutor that the assignment is already in the target state. + - **Use case ends.** + +--- + +## **Non-Functional Requirements** 1. Should work on any _mainstream OS_ as long as it has Java `17` or above installed. -2. Should be able to hold up to 1000 persons without a noticeable sluggishness in performance for typical usage. +2. Should be able to hold up to 1000 students without a noticeable sluggishness in performance for typical usage. 3. A user with above average typing speed for regular English text (i.e. not code, not system admin commands) should be able to accomplish most of the tasks faster using commands than using the mouse. +4. Any command should respond in 10 seconds or less for up to 100 students. +5. System should be usable by a user who is comfortable with typing and using a CLI. +6. System should be able to store data for at least 1 year without data loss. +7. Project is expected to adhere to the [Java Code Quality Guide](https://se-education.org/guides/contributing/javaCodeQualityGuide.html). +8. Project is expected to adhere to a schedule that delivers a feature set every week. +9. This project is not expected to connect to the internet or any external services (email, cloud storage, telegram, etc). -*{More to be added}* +--- -### Glossary +## **Glossary** -* **Mainstream OS**: Windows, Linux, Unix, MacOS -* **Private contact detail**: A contact detail that is not meant to be shared with others +* **Mainstream OS**: Windows, Linux, Unix, macOS +* **Tutor**: A person who does free-lance tutoring +* **Student**: A person that is being or has been tutored by the current user of the application +* **Spreadsheet**: An Excel spreadsheet +* **Assignment**: A homework assignment or task that has been given by the tutor to the student -------------------------------------------------------------------------------------------------------------------- -## **Appendix: Instructions for manual testing** +## **Appendix: Effort** -Given below are instructions to test the app manually. +**Team size**: 5 -
:information_source: **Note:** These instructions only provide a starting point for testers to work on; -testers are expected to do more *exploratory* testing. +**Difficulty level**: Moderate to High -
+**Challenges faced**: -### Launch and shutdown +1. **Complexity of Features**: Implementing features such as lessons and assignments required a deep understanding of the existing codebase and careful planning to ensure seamless integration. +2. **Data Management**: Handling multiple entity types (students, lessons, assignments) added complexity to the data management and storage components. +3. **User Interface**: Ensuring the CLI-based interface remains user-friendly while accommodating new features was challenging. +4. **Testing**: Writing comprehensive tests for new features required significant effort. -1. Initial launch +**Effort required**: - 1. Download the jar file and copy into an empty folder +- **Planning and Design**: Approximately 20 hours were spent on planning and designing the new features and their integration with the existing system. +- **Implementation**: Around 80 hours were dedicated to coding, debugging, and refining the new features. +- **Testing**: About 30 hours were spent on writing and executing test cases to ensure the reliability of the new features. +- **Documentation**: Approximately 10 hours were used to update the Developer Guide and User Guide to reflect the new features and changes. - 1. Double-click the jar file Expected: Shows the GUI with a set of sample contacts. The window size may not be optimum. +**Achievements**: -1. Saving window preferences +- Successfully implemented the lessons and assignments feature, allowing users to add lessons and assignments to students with ease. +- Enhanced data management capabilities to handle multiple entity types efficiently. +- Improved the user interface to provide better feedback and usability. +- Maintained high code quality and adherence to coding standards throughout the project. - 1. Resize the window to an optimum size. Move the window to a different location. Close the window. +**Reuse**: - 1. Re-launch the app by double-clicking the jar file.
- Expected: The most recent window size and location is retained. +- The lessons/assignments feature was inspired by similar implementations in other projects, but our implementation was tailored to fit the specific needs of TutorTrack. +- Some utility functions and classes were reused from the AddressBook-Level3 project, with minimal modifications to suit our requirements. -1. _{ more test cases …​ }_ +--- -### Deleting a person +## **Appendix: Instructions for manual testing** -1. Deleting a person while all persons are being shown +### Launch and Shutdown Testing - 1. Prerequisites: List all persons using the `list` command. Multiple persons in the list. +1. **First-time launch** + - Delete any existing **`data/TutorTrack.json`** file + - Launch the application via **`java -jar tutortrack.jar`** + - *Expected*: Loads with sample data, creates new data file +2. **Persisting window preferences** + - Resize and reposition the window + - Close and relaunch the application + - *Expected*: Retains previous window size and position - 1. Test case: `delete 1`
- Expected: First contact is deleted from the list. Details of the deleted contact shown in the status message. Timestamp in the status bar is updated. +--- - 1. Test case: `delete 0`
- Expected: No person is deleted. Error details shown in the status message. Status bar remains the same. +### Student Management Testing + +1. **Adding a student** + - Test case: **`add_student n/John Doe p/98765432 e/john@email.com a/123 Street s/Math`** + - *Expected*: Student added with proper formatting (name in Title Case) + - Test invalid: + - **`add_student n/John@Doe...`** (special characters) + - **`add_student n/John p/abc...`** (invalid phone) + - *Expected*: Clear error messages for each invalid field +2. **Deleting a student** + - First list students: **`list_students`** + - Then: **`delete_student 1`** + - *Expected*: First student removed, confirmation shown + - Test invalid: + - **`delete_student 0`** + - **`delete_student 999`** (non-existent index) + - *Expected*: Appropriate index errors - 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 …​ }_ +### Lesson Management Testing + +1. **Adding a lesson** + - Prerequisite: At least one student exists + - Test case: **`add_lesson n/John Doe d/01-09-2025 t/14:00 s/Math`** + - *Expected*: Lesson added to student + - Test conflicts: + - Same time for different students + - Invalid dates (past dates, 31-04-2025) + - *Expected*: Clear time conflict/validation errors +2. **Editing a lesson** + - First list lessons: **`list_lessons`** + - Then: **`edit_lesson 1 t/15:00`** + - *Expected*: Lesson time updated + - Test invalid: + - Overlapping times + - Invalid date formats + - *Expected*: Appropriate error messages -### Saving data +--- + +### Assignment Management Testing + +1. **Creating an assignment** + - Prerequisite: At least one student exists + - Test case: **`add_assignment 1 as/MathHomework d/01-09-2025`** + - *Expected*: Assignment added with future date + - Test invalid: + - Past dates + - Duplicate assignment names + - *Expected*: Validation errors +2. **Marking assignments** + - **`mark_assignment 1 as/MathHomework`** + - *Expected*: Assignment marked complete (visual indicator) + - **`unmark_assignment 1 as/MathHomework`** + - *Expected*: Assignment marked incomplete -1. Dealing with missing/corrupted data files +--- - 1. _{explain how to simulate a missing/corrupted file, and the expected behavior}_ +### Data Persistence Testing + +1. **Corrupted data file** + - Manually edit **`data/TutorTrack.json`** to: + - Remove closing brackets + - Add invalid field values + - Launch application + - *Expected*: Creates new empty data file, logs error +2. **Missing data file** + - Delete **`data/TutorTrack.json`** + - Launch application + - *Expected*: Creates new file with sample data +3. **Data integrity** + - Perform series of add/edit/delete operations + - Close and reopen application + - *Expected*: All changes persist correctly + +--- + +### Edge Case Testing + +1. **Mass data operations** + - Add 50+ students via script + - *Expected*: No performance lag in commands +2. **Special characters** + - Test names with apostrophes: **`n/O'Connor`** + - *Expected*: Handled properly (may need validation adjustment) +3. **Timezone testing** + - Change system timezone + - Test date-sensitive commands + - *Expected*: Consistent behavior across timezones + +--- + +### Verification Steps + +For each test case: + +1. Check command output for success/error messages +2. Verify UI updates match expected state +3. For data operations, restart app to verify persistence +4. Check **`logs/TutorTrack.log`** for any unexpected errors + +**Tip**: Use the **`clear`** command between test scenarios to reset state. + +--- -1. _{ more test cases …​ }_ +## **Planned Enhancements** + +1. **Undo/Redo Feature**: Allow users to undo their previous commands. It improves the users experience by providing a way to recover from mistakes. +2. **Enhanced Error Messages**: Improve error messages to be more specific and actionable. For example, instead of showing "Operation failed!", the message could indicate the exact reason for the failure, such as "The student 'John Doe' could not be added because the name already exists." +2. **Data Validation**: Implement more robust data validation to prevent invalid inputs from being processed. For instance, validate email formats, phone numbers, and date formats before saving them. +3. **Batch Operations**: Add support for batch operations, such as adding multiple students or assignments at once, to improve efficiency for users managing large datasets. +4. **Improved Search Functionality**: Enhance the search feature to support more complex queries, such as searching by multiple criteria (e.g., name, subject, and status) simultaneously. +5. **Customizable Commands**: Enable users to customize commands to their own liking and preferences, to cater to tutors who like shorter commands to have a more efficient workflow. +6. **Customizable Reminders**: Enable users to set customizable reminders for lessons and assignments, with options for recurring reminders and notifications. +7. **Input Working Hours**: Enable the tutors to input their working hours so to restrict the timings of the lessons to within their comfortable timings. +8. **Performance Optimization**: Optimize the application's performance to handle larger datasets more efficiently, ensuring smooth operation even with thousands of entries. +9. **Enhanced Reporting**: Add reporting features to generate summaries and insights, such as student progress reports, lesson attendance, and assignment completion rates. +10. **Integration with Calendars**: Allow users to integrate their lesson schedules with external calendar applications (e.g., Google Calendar) for better schedule management. diff --git a/docs/UserGuide.md b/docs/UserGuide.md index 27c2d1cf16c..b4e47c7a276 100644 --- a/docs/UserGuide.md +++ b/docs/UserGuide.md @@ -3,40 +3,60 @@ 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. +TutorTrack is a **desktop application designed for Singaporean freelance tutors** to efficiently manage their students, lessons, and assignments. It combines the speed of a **Command Line Interface (CLI)** with the convenience of a **Graphical User Interface (GUI)**, making it ideal for tutors who prefer typing over mouse interactions. If you can type fast, TutorTrack will help you manage your tutoring tasks faster than traditional GUI apps. + +TutorTrack uses a **dual-list system** to manage students and lessons. Users can easily modify the student list and lesson list, allowing for quick access to student information and lesson schedules with their unique indexes. The application also supports assignment management, enabling tutors to keep track of their students' assignments and their completion status uniquely identified by the name of the assignment. Users can easily toggle between the lists with simple CLI-based commands. + +--- * Table of Contents {:toc} --------------------------------------------------------------------------------------------------------------------- +--- -## Quick start +## Target Users -1. Ensure you have Java `17` or above installed in your Computer.
- **Mac users:** Ensure you have the precise JDK version prescribed [here](https://se-education.org/guides/tutorials/javaInstallationMac.html). +Singaporean freelance tutors who: -1. Download the latest `.jar` file from [here](https://github.com/se-edu/addressbook-level3/releases). +- Juggle multiple students/lessons and need centralized tracking +- Prefer keyboard-driven efficiency over mouse navigation +- Want lightweight but capable task management -1. Copy the file to the folder you want to use as the _home folder_ for your AddressBook. +## Assumptions about Users -1. Open a command terminal, `cd` into the folder you put the jar file in, and use the `java -jar addressbook.jar` command to run the application.
- A GUI similar to the below should appear in a few seconds. Note how the app contains some sample data.
- ![Ui](images/Ui.png) +- Users are familiar with basic CLI commands. +- Users have a basic understanding of file management (e.g., creating folders, moving files). +- Users are comfortable with Java-based applications. -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: +-------------------------------------------------------------------------------------------------------------------- + +## Quick start - * `list` : Lists all contacts. - * `add n/John Doe p/98765432 e/johnd@example.com a/John street, block 123, #01-01` : Adds a contact named `John Doe` to the Address Book. +1. **Ensure Java 17 or above is installed** on your computer.
+ **Mac users:** Follow the installation guide [here](https://se-education.org/guides/tutorials/javaInstallationMac.html). - * `delete 3` : Deletes the 3rd contact shown in the current list. +1. Download the latest `.jar` file from the [releases page](https://github.com/AY2425S2-CS2103T-T13-4/tp/releases). - * `clear` : Deletes all contacts. +1. Copy the `.jar` file to the folder you want to use as the _home folder_ for your TutorTrack. + +1. Open a command terminal, navigate to the folder containing the `.jar` file using the `cd` command, and run the application with: + ```bash + java -jar tutortrack.jar + ``` + A GUI similar to the below should appear in a few seconds, preloaded with sample data.
+ ![Ui](images/Ui.png) - * `exit` : Exits the app. +1. Type commands in the command box and press `Enter` to execute it. e.g. typing **`help`** and pressing Enter will open the help window.
+ - `help`: Opens the help window. + - `list_students`: Lists all students. + - `list_lessons n/Jackie` : Lists all lessons under a student or all lessons in the data + - `clear`: Deletes all students. + - `find_student Bernice`: Finds student containing Bernice. + - `exit`: Exits the app. + More commands listed below. -1. Refer to the [Features](#features) below for details of each command. +1. Refer to the [Features](#features) section below for detailed instructions on each command. -------------------------------------------------------------------------------------------------------------------- @@ -46,155 +66,460 @@ AddressBook Level 3 (AB3) is a **desktop app for managing contacts, optimized fo **:information_source: Notes about the command format:**
-* Words in `UPPER_CASE` are the parameters to be supplied by the user.
- e.g. in `add n/NAME`, `NAME` is a parameter which can be used as `add n/John Doe`. +- **Parameters in `UPPER_CASE`** are to be supplied by the user. Example: In `add_student n/STUDENT_NAME`, `STUDENT_NAME` can be replaced with `John Doe`. + +- **Optional fields** are enclosed in square brackets `[]`. Example: `add_student n/NAME p/PHONE e/EMAIL a/ADDRESS [s/SUBJECT]` can be used as `add_student n/John Doe p/91234567 e/ johndoe@gmail.com a/ 311, Clementi Ave 2, #02-25 s/Math` or `add_student n/John Doe p/91234567 e/ johndoe@gmail.com a/ 311, Clementi Ave 2, #02-25`. + +- **Multiple uses** of a field are indicated by `…`. + + Example: `[s/SUBJECT]…` can be used as `s/Math`, `s/Math s/Science`, or omitted entirely. -* 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`. +- **Parameters can be in any order**. -* 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. + Example: `n/STUDENT_NAME p/PHONE_NUMBER` is equivalent to `p/PHONE_NUMBER n/STUDENT_NAME`. -* 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. +- **Extraneous parameters** for commands like `help`, `list_students`, `exit`, and `clear` will be ignored. -* 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`. + Example: `help 123` is interpreted as `help`. -* If you are using a PDF version of this document, be careful when copying and pasting commands that span multiple lines as space characters surrounding line-breaks may be omitted when copied over to the application. +- **Use of indexes**: To facilitate ease of typing, lessons and students are referred as indexes in the current view. + + For example, if you are viewing the student list and you want to delete the 2nd student, you can type `delete_student 2` to delete the 2nd student in the list. The same applies for lessons. + + - Depends on the filtered view of each list, each student/lesson may have different index. + + - Avoid accessing lessons through index while viewing the student list and vice versa. It is recommended to use the `list_students` or `list_lessons` command to view the respective lists before using the index. + +- If you are using a PDF version of this document, be careful when copying and pasting commands that span multiple lines as space characters surrounding line-breaks may be omitted when copied over to the application.
-### Viewing help : `help` +### General features -Shows a message explaning how to access the help page. +#### Viewing help : `help` + +Shows a message explaining how to access the help page. + +**Format:** + +`help` ![help message](images/helpMessage.png) -Format: `help` +#### Clearing all entries : `clear` + +Clears all entries from both the student list and the lesson list. + +
:exclamation: **Caution:** +There is no confirmation for this command and the data will be cleared immediately after execution. Use at your own risk. +
+ +**Format:** + +`clear` + +#### Exiting the program : `exit` + +Exits the program. + +**Format:** + +`exit` + +#### Saving the data -### Adding a person: `add` +TutorTrack data is saved in the hard disk automatically after any command that changes the data. There is no need to save manually. -Adds a person to the address book. +#### Editing the data file -Format: `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]…​` +TutorTrack data is saved automatically as a JSON file `[JAR file location]/data/TutorTrack.json`. Advanced users are welcome to update data directly by editing that data file. -
:bulb: **Tip:** -A person can have any number of tags (including 0) +
:exclamation: **Caution:** +If your changes to the data file makes its format invalid, TutorTrack will discard all data and start with an empty data file at the next run. Hence, it is recommended to take a backup of the file before editing it.
+Furthermore, certain edits can cause the TutorTrack to behave in unexpected ways (e.g., if a value entered is outside of the acceptable range). Therefore, edit the data file only if you are confident that you can update it correctly.
-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` +### Managing students -### Listing all persons : `list` +TutorTrack allows for easy management of students, including adding, editing, deleting, and finding students. You can also view all students in the list. -Shows a list of all persons in the address book. +The student list is displayed on default when the application is opened. The student list shows the name, phone number, email, address, subjects and assignments of each student. -Format: `list` +#### Adding a student: `add_student` -### Editing a person : `edit` +Adds a student to the student list, with their name, phone number, address, email and subjects. +You can add multiple subjects by using the subject prefix  `s/` for each subject. -Edits an existing person in the address book. +**Format:** -Format: `edit INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [t/TAG]…​` +`add_student n/STUDENT_NAME p/PHONE_NUMBER a/ADDRESS e/EMAIL [s/SUBJECTS]…` -* 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. +- `STUDENT_NAME` is the student's name displayed on the student list. + - Alphanumerical characters and spaces (e.g., **`Alex Yeoh`**, **`John Doe 2`**) + - Auto-converted to Title Case (e.g., **`alex yeoh`** → **`Alex Yeoh`**) + - Special constructs (**`d/o`**, **`s/o`**) and Symbols/hyphens (**`-`**, **`'`**) or numerals (**`0-9`**) not allowed +- `PHONE_NUMBER` is the student's contact number + - Has to be 8 digits +- `ADDRESS` is the student's postal address +- `EMAIL` is the student's email + - Must be a valid email address (details in the app) +- `SUBJECT` is the subject the student is being taught + - Can have any number of subjects + - Alphanumeric characters allowed, case-insensitive and automatically converts to Title Case -Examples: -* `edit 1 p/91234567 e/johndoe@example.com` Edits the phone number and email address of the 1st person to be `91234567` and `johndoe@example.com` respectively. -* `edit 2 n/Betsy Crower t/` Edits the name of the 2nd person to be `Betsy Crower` and clears all existing tags. +**Examples**: +* `add_student n/John Doe p/98765432 e/johndoe@email.com a/311, Clementi Ave 2, #02-25 s/Math` +* `add_student n/Mary Jane p/12345678 e/maryjane@email.com a/Blk 47 Tampines Street 20, #17-35 s/Math s/Science` -### Locating persons by name: `find` +#### Editing a student : `edit_student` -Finds persons whose names contain any of the given keywords. +Edits an existing student in the student list. -Format: `find KEYWORD [MORE_KEYWORDS]` +**Format:** -* 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` +`edit_student STUDENT_INDEX [n/STUDENT_NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS]` -Examples: -* `find John` returns `john` and `John Doe` -* `find alex david` returns `Alex Yeoh`, `David Li`
- ![result for 'find alex david'](images/findAlexDavidResult.png) +- `STUDENT_NAME` is the student's name displayed on the student list. + - Alphabetic characters and spaces (e.g., **`Alex Yeoh`**) + - Auto-converted to Title Case (e.g., **`alex yeoh`** → **`Alex Yeoh`**) + - Special constructs (**`d/o`**, **`s/o`**) and Symbols/hyphens (**`-`**, **`'`**) or numerals (**`0-9`**) not allowed +- `PHONE_NUMBER` is the student's contact number + - Has to be 8 digits +- `ADDRESS` is the student's postal address +- `EMAIL` is the student's email + - Must be a valid email address (details in the app) +- Subjects cannot be edited at this stage. + - To edit the subjects, you can delete the student and add a new one with the updated subjects. -### Deleting a person : `delete` +**Examples**: +* `edit_student 1 p/91234567 e/johndoe@example.com`
Edits the phone number and email address of the 1st student to be `91234567` and `johndoe@example.com` respectively. -Deletes the specified person from the address book. +#### Deleting a student : `delete_student` -Format: `delete INDEX` +Deletes the specified student from the student list. -* 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, …​ +
:bulb: **Note** This command can only be executed when viewing the student list (e.g. `list_students`)
-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. +**Format:** -### Clearing all entries : `clear` +`delete_student STUDENT_INDEX` -Clears all entries from the address book. +* `STUDENT_INDEX` corresponds to the index of the student as displayed in the student list + * Has to be a **positive integer** + +**Example**: +* `list_students` followed by `delete_student 2` deletes the 2nd student in the student list. -Format: `clear` +#### Listing all students : `list_students` -### Exiting the program : `exit` +Switch to a view that shows all students in the student list. -Exits the program. +**Format:** -Format: `exit` +`list_students` -### Saving the data +#### Locating students by name: `find_student` -AddressBook data are saved in the hard disk automatically after any command that changes the data. There is no need to save manually. +Finds students whose names contain any of the given keywords. -### Editing the data file +
:information_source: **Note**: Partial-word matching (e.g., searching for "Sre" to match "Sree Haridos") is currently not supported but may be added in a future update.
-AddressBook data are saved automatically as a JSON file `[JAR file location]/data/addressbook.json`. Advanced users are welcome to update data directly by editing that data file. +**Format:** -
:exclamation: **Caution:** -If your changes to the data file makes its format invalid, AddressBook will discard all data and start with an empty data file at the next run. Hence, it is recommended to take a backup of the file before editing it.
-Furthermore, certain edits can cause the AddressBook to behave in unexpected ways (e.g., if a value entered is outside of the acceptable range). Therefore, edit the data file only if you are confident that you can update it correctly. -
+`find_student KEYWORD [MORE_KEYWORDS...]` + +* `STUDENT_NAME` is the name of the student. + * It must be a valid name of a student in the student list. + * Search is case-insensitive. e.g `hans` will match `Hans` + * Only matches complete words in the name (e.g., `Alex` matches `Alex Yeoh` but not `Alexander`). + * The order of the keywords does not matter. e.g. `Hans Bo` will match `Bo Hans` + * Only the name is searched. + * Students matching at least one keyword will be returned. + e.g. `Hans Bo` will return `Hans Gruber`, `Bo Yang` + +**Examples**: +* `find_student John` returns `john` and `John Doe` +* `find_student alex david` returns `Alex Yeoh`, `David Li`
+ +### Managing lessons + +TutorTrack allows for easy management of lessons, including adding, editing, deleting, and listing lessons. You can also view all lessons in the list. + +The lesson list is displayed when you type `list_lessons` in the command box. The lesson list shows the name of the student, date, time and subject of each lesson. + +![lesson list](images/lessonList.png) + +#### Adding a lesson: `add_lesson` + +Adds a lesson to the lesson list. + +
:information_source: **Group Lessons Note**: Currently, TutorTrack doesn't support group lessons where multiple students share the same time slot. Each lesson time must be unique to the tutor's schedule. We're considering adding group lesson support in a future update.
+ +**Format:** + +`add_lesson n/STUDENT_NAME s/SUBJECT d/DATE t/TIME​` + +
:bulb: **Note:** Student names will be automatically matched in a case-insensitive manner and stored in Title Case.
+ +* `STUDENT_NAME` is the name of the student. + * It must be a valid name of a student in the student list. + * Names are case-insensitive + * **`alice chan`** matches **`Alice Chan`** +* `SUBJECT` is the subject of the lesson. + * It must be a valid subject of the student in the student list. +* `DATE` is the date of the lesson. + * It must be in the format `d-M-yyyy` and must be in the future. +* `TIME` is the time of the lesson. + * It must be in the format `HH:mm`. + * Two lessons cannot be scheduled at the same time (e.g. 15:00 and 15:01 are considered different) + +**Example:** +* `add_lesson n/Alice Chan d/17-09-2025 t/15:00 s/Math` +* `add_lesson n/Bob Lee d/17-09-2025 t/16:00 s/Math` (different time slot) + +#### Editing a lesson: `edit_lesson` + +Edits the details of an existing lesson +* Edit individual details or edit multiple together. +* Student names in lessons will be automatically converted to Title Case when edited + +**Format:** + +`edit_lesson LESSON_INDEX [n/STUDENT_NAME] [d/DATE] [t/TIME] [s/SUBJECT]` + +* `LESSON_INDEX` corresponds to the index of the lesson on the displayed lesson list + * Has to be a **positive integer** +* `STUDENT_NAME` is the name of the student. + * It must be a valid name of a student in the student list. + * Names are case-insensitive + * **`alice chan`** matches **`Alice Chan`** +* `SUBJECT` is the subject of the lesson. + * It must be a valid subject of the student in the student list. +* `DATE` is the date of the lesson. + * It must be in the format `d-M-yyyy` and must be in the future. +* `TIME` is the time of the lesson. + * It must be in the format `HH:mm`. + * Two lessons cannot be scheduled at the same time (e.g. 15:00 and 15:01 are considered different) + +**Examples**: +* `edit_lesson 1 d/16-02-2026` +* `edit_lesson 2 n/Jone King t/16:00 d/18-9-2025 s/Math` + +#### Deleting a lesson: `delete_lesson` + +Deletes the specified lesson from the lesson list. -### Archiving data files `[coming in v2.0]` +**Format:** -_Details coming soon ..._ +`delete_lesson LESSON_INDEX` + +* `LESSON_INDEX` corresponds to the index of the lesson on the displayed lesson list. + * Has to be a **positive integer**. + +**Examples:** +* `list_lessons` followed by `delete_lesson 2` deletes the 2nd lesson in the lesson list. +* `list_lessons n/John Lee` followed by `delete_lesson 1` deletes the 1st lesson in the lesson list. + +#### Listing lessons : `list_lessons` + +Shows a list of all lessons under a student in the lesson list.
If no student is specified, shows all lessons in the list. + +**Format:** + +`list_lessons [n/STUDENT_NAME]` + +* `STUDENT_NAME` is as displayed on the student list (case-insensitive) + +**Example:** +* `list_lessons n/John Lee` +* `list_lessons` + +### Managing assignments + +TutorTrack allows for easy management of assignments, including adding, deleting, marking and unmarking assignments. You can also view all assignments in the student list under each student entry. + +#### Adding an assignment: `add_assignment` + +Adds an assignment to a student in the student list + +**Format:** + +`add_assignment STUDENT_INDEX as/ASSIGNMENT_NAME d/DATE` + +- `STUDENT_INDEX` corresponds to the student to which the assignment belongs in the displayed list. + - Has to be a **positive integer** +- `ASSIGNMENT_NAME` is the name of the assignment to add. + - The name of the assignment must be unique within the student. + - Alphanumeric characters allowed (e.g., **`Math Exercise 1`**) + - Automatically converted to Title Case (e.g., **`math exercise`** → **`Math Exercise`**) +- `DATE` is the due date of the assignment. + - The date must be in the format `d-M-yyyy` (e.g., `27-09-2026` or `1-1-2026`). + - The date must be in the future (i.e., not in the past). + +**Example:** +* `add_assignment 2 as/Science 101 d/27-09-2026` + +#### Deleting an assignment: `delete_assignment` + +Deletes the assignment identified by the index number of the student and the assignment name.
The assignment will be removed from the student's list of assignments. + +**Format:** + +`delete_assignment STUDENT_INDEX as/ASSIGNMENT_NAME` + +- `INDEX` corresponds to the student to which the assignment belongs in the displayed list. + - Has to be a **positive integer** +- `ASSIGNMENT_NAME` is the name of the assignment to add. + - The name of the assignment must be unique within the student. + - Alphanumeric characters allowed (e.g., **`Math Exercise 1`**) + - Automatically converted to Title Case (e.g., **`math exercise`** → **`Math Exercise`**) + +**Example:** +- `delete_assignment 1 as/Assignment 1` deletes the assignment named `Assignment 1` for the first student in the list. + +#### Editing an assignment: `edit_assignment` + +Edits an assignment for a student in the student list + +**Format:** + +`edit assignment STUDENT_INDEX as/ASSIGNMENT_NAME [nas/NEW_ASSIGNMENT_NAME] [d/DATE]` + +- `STUDENT_INDEX` corresponds to the student to which the assignment belongs in the displayed list. + - Has to be a **positive integer** +- `ASSIGNMENT_NAME` is the name of the assignment to add. + - The name of the assignment must be unique within the student. + - Alphanumeric characters allowed (e.g., **`Math Exercise 1`**) + - Automatically converted to Title Case (e.g., **`math exercise`** → **`Math Exercise`**) +- `NEW_ASSIGNMENT_NAME` is the new name of the assignment to be changed to. + - Follows as above +- `DATE` is the due date of the assignment. + - The date must be in the format `d-M-yyyy` (e.g., `27-09-2026` or `1-1-2026`). + - The date must be in the future (i.e., not in the past). + +**Example:** +* `edit_assignment 1 as/Science nas/Math` changes the name of the assignment of the first student in the display student list from `Science` to `Math`. +* `edit_assignment 1 as/Science d/12-12-2025` changes the date of the assignment named "Science" of the first student in the display student list. +* `edit_assignment 1 as/Science nas/Math d/12-12-2025` combination of the two example above. + +#### Marking an assignment: `mark_assignment` + +Marks an assignment of a student as complete.
Complete assignments are denoted by a green colour. + +**Format:** + +`mark_assignment STUDENT_INDEX as/ASSIGNMENT_NAME​` +- `STUDENT_INDEX` corresponds to the student to which the assignment belongs in the displayed list. + - Has to be a **positive integer** +- `ASSIGNMENT_NAME` is the name of the assignment to add. + - The name of the assignment must be unique within the student. + - Alphanumeric characters allowed (e.g., **`Math Exercise 1`**) + - Automatically converted to Title Case (e.g., **`math exercise`** → **`Math Exercise`**) + +**Example:** + +- `mark_assignment 1 as/Assignment 1` marks the assignment named "Assignment 1" of the first student in the list as completed. + +#### Unmarking an Assignment: `unmark_assignment` + +Marks an assignment of a student as incomplete.
Incomplete assignments are denoted by a red colour. + +**Format:** + +`unmark_assignment STUDENT_INDEX as/ASSIGNMENT_NAME` + +- `STUDENT_INDEX` corresponds to the student to which the assignment belongs in the displayed list. + - Has to be a **positive integer** +- `ASSIGNMENT_NAME` is the name of the assignment to add. + - The name of the assignment must be unique within the student. + - Alphanumeric characters allowed (e.g., **`Math Exercise 1`**) + - Automatically converted to Title Case (e.g., **`math exercise`** → **`Math Exercise`**) + +**Example:** +- `unmark_assignment 1 as/Assignment 1` unmarks the first assignment in the list, setting it to incomplete. -------------------------------------------------------------------------------------------------------------------- ## FAQ -**Q**: How do I transfer my data to another Computer?
-**A**: Install the app in the other computer and overwrite the empty data file it creates with the file that contains the data of your previous AddressBook home folder. +**Q:** How do I transfer my data to another computer? + +**A:** +Install the app on the new computer and replace the empty data file with the one from your previous TutorTrack folder. + + +**Q:** Why can't I add students with identical names? + +**A:** TutorTrack prevents students with identical names to: + +1. **Prevent Confusion** + - Ensures each student has a unique identity in your records + - Eliminates ambiguity when scheduling lessons or assignments +2. **Maintain Data Accuracy** + - Guarantees commands like **`delete_student`** or **`edit_student`** affect the correct individual + - Prevents accidental merging of different students' records +3. **Optimize Workflow** + - Makes student selection faster when managing lessons/assignments + - Reduces cognitive load when scanning your student list + +**Q:** What if I need to add students with similar names? + +**A:** The system will accept names that: Differ by at least one character (e.g., "John Doe" vs "Jon Doe") + +
:bulb: **Tip:** For students who share names naturally, consider adding identifiers: middle initials (e.g., "John A Doe" vs "John B Doe"), numerals (e.g., "John Doe 1" vs "John Doe 2")
-------------------------------------------------------------------------------------------------------------------- ## Known issues -1. **When using multiple screens**, if you move the application to a secondary screen, and later switch to using only the primary screen, the GUI will open off-screen. The remedy is to delete the `preferences.json` file created by the application before running the application again. -2. **If you minimize the Help Window** and then run the `help` command (or use the `Help` menu, or the keyboard shortcut `F1`) again, the original Help Window will remain minimized, and no new Help Window will appear. The remedy is to manually restore the minimized Help Window. +1. **Multiple Screens Issue:** If you move the app to a secondary screen and later switch to a single screen, the GUI may open off-screen. To fix, delete the `preferences.json` file before running the app again. +2. **Help Window Issue:** Minimizing the Help Window and running `help` again may not open a new window. Restore the minimized window manually. -------------------------------------------------------------------------------------------------------------------- ## Command summary -Action | Format, Examples ---------|------------------ -**Add** | `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]…​`
e.g., `add n/James Ho p/22224444 e/jamesho@example.com a/123, Clementi Rd, 1234665 t/friend t/colleague` -**Clear** | `clear` -**Delete** | `delete INDEX`
e.g., `delete 3` -**Edit** | `edit INDEX [n/NAME] [p/PHONE_NUMBER] [e/EMAIL] [a/ADDRESS] [t/TAG]…​`
e.g.,`edit 2 n/James Lee e/jameslee@example.com` -**Find** | `find KEYWORD [MORE_KEYWORDS]`
e.g., `find James Jake` -**List** | `list` -**Help** | `help` +### 👤 Student Management + +| Action | Format, Examples | +|--------------------|------------------| +| **Add Student** | `add_student n/NAME p/PHONE e/EMAIL a/ADDRESS s/SUBJECT`
e.g., `add_student n/John Doe p/91234567 e/john@example.com a/123 Street s/Math` | +| **Edit Student** | `edit_student INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS]`
e.g., `edit_student 1 p/98765432` | +| **Delete Student** | `delete_student INDEX`
e.g., `delete_student 3` | +| **Find Student** | `find_student KEYWORD [MORE_KEYWORDS]`
e.g., find_student James Jake +| **List Students** | `list_students` | + + +### 📚 Lesson Management + +| Action | Format, Examples | +|--------------------|------------------| +| **Add Lesson** | `add_lesson n/STUDENT_NAME d/DATE t/TIME s/SUBJECT`
e.g., `add_lesson n/Jack d/16-10-2025 t/15:00 s/CS2103T` | +| **Edit Lesson** | `edit_lesson INDEX [n/STUDENT_NAME] [d/DATE] [t/TIME] [s/SUBJECT]`
e.g., `edit_lesson 1 d/20-10-2025 t/14:00` | +| **Delete Lesson** | `delete_lesson INDEX`
e.g., `delete_lesson 1` | +| **List Lessons** | `list_lessons [n/STUDENT_NAME]`
e.g., `list_lessons`, `list_lessons n/Sally Mood` | + + +### 📝 Assignment Management + +| Action | Format, Examples | +|------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------| +| **Add Assignment** | `add_assignment STUDENT_INDEX as/ASSIGNMENT d/DATE`
e.g., `add_assignment 1 as/Math Homework d/23-07-2025` | +| **Edit Assignment** | `edit_assignment STUDENT_INDEX as/ASSIGNMENT_NAME [nas/NEW_NAME] [d/NEW_DATE]`
e.g., `edit_assignment 1 as/Assignment1 nas/UpdatedAssignment d/25-12-2025` | +| **Delete Assignment** | `delete_assignment STUDENT_INDEX as/ASSIGNMENT_NAME`
e.g., `delete_assignment 1 as/Assignment 1` | +| **Mark Assignment** | `mark_assignment STUDENT_INDEX as/ASSIGNMENT_NAME`
e.g., `mark_assignment 1 as/Assignment 1` | +| **Unmark Assignment** | `unmark_assignment STUDENT_INDEX as/ASSIGNMENT_NAME`
e.g., `unmark_assignment 1 as/Assignment 1` | + + +### 🧹 General Utility + +| Action | Format, Examples | +|----------------|------------------| +| **Clear** | `clear` | +| **Help** | `help` | +| **Exit** | `exit` | | + + diff --git a/docs/_config.yml b/docs/_config.yml index 6bd245d8f4e..ec7ad27d992 100644 --- a/docs/_config.yml +++ b/docs/_config.yml @@ -1,4 +1,4 @@ -title: "AB-3" +title: "TutorTrack" theme: minima header_pages: diff --git a/docs/_sass/minima/_base.scss b/docs/_sass/minima/_base.scss index 0d3f6e80ced..7b9dcd6816d 100644 --- a/docs/_sass/minima/_base.scss +++ b/docs/_sass/minima/_base.scss @@ -13,6 +13,16 @@ dl, dd, ol, ul, figure { } +h1, h2, h3 { + margin-bottom: 10px; + font-weight: bold; +} + +p { + line-height: 1.6; + margin-bottom: 15px; +} + /** @@ -288,7 +298,7 @@ table { text-align: center; } .site-header:before { - content: "AB-3"; + content: "TutorTrack"; font-size: 32px; } } diff --git a/docs/diagrams/ArchitectureDiagram.puml b/docs/diagrams/ArchitectureDiagram.puml index 4c5cf58212e..8e22ba34652 100644 --- a/docs/diagrams/ArchitectureDiagram.puml +++ b/docs/diagrams/ArchitectureDiagram.puml @@ -4,30 +4,57 @@ !include !include style.puml -Package " "<>{ - Class UI UI_COLOR - Class Logic LOGIC_COLOR - Class Storage STORAGE_COLOR - Class Model MODEL_COLOR - Class Main #grey - Class Commons LOGIC_COLOR_T2 +skinparam { + Shadowing false + ArrowFontSize 12 } -Class "<$user>" as User MODEL_COLOR_T2 -Class "<$documents>" as File UI_COLOR_T1 +title "TutorTrack Architecture Diagram" +package "Main Components" <> { + class "UI" as UI UI_COLOR + class "Logic" as Logic LOGIC_COLOR + class "Storage" as Storage STORAGE_COLOR + class "Model" as Model MODEL_COLOR + class "Main" as Main #grey +} + +class "<$user>\nUser" as User MODEL_COLOR_T2 +class "<$documents>\nData File" as File UI_COLOR_T1 + +' Component Relationships +Main --> UI : launches +Main --> Logic : launches +Main --> Storage : launches +Main --> Model : accesses + +UI -> Logic : executes\ncommands +UI -> Model : observes +Logic -> Storage : saves/loads +Logic -> Model : modifies + +Storage ..> Model : contains\npersisted data +Storage ..> File : reads/writes\nJSON + +User ..> UI : interacts with + +note right of Storage + Data Flow: + • JSON format + • Auto-saves on changes +end note -UI -[#green]> Logic -UI -right[#green]-> Model -Logic -[#blue]-> Storage -Logic -down[#blue]-> Model -Main -[#grey]-> UI -Main -[#grey]-> Logic -Main -[#grey]-> Storage -Main -up[#grey]-> Model -Main -down[hidden]-> Commons +note top of Logic + Command Processing: + • Parses input + • Executes logic + • Returns results +end note -Storage -up[STORAGE_COLOR].> Model -Storage .right[STORAGE_COLOR].>File -User ..> UI +legend right + Color Legend: + <$globe_internet> = External Interaction + <$documents> = Data Storage + <$user> = Human Actor +end legend @enduml diff --git a/docs/diagrams/ArchitectureSequenceDiagram.puml b/docs/diagrams/ArchitectureSequenceDiagram.puml index 48b6cc4333c..1d92bde10d0 100644 --- a/docs/diagrams/ArchitectureSequenceDiagram.puml +++ b/docs/diagrams/ArchitectureSequenceDiagram.puml @@ -1,39 +1,53 @@ @startuml !include style.puml -skinparam ArrowFontStyle plain -Actor User as user USER_COLOR -Participant ":UI" as ui UI_COLOR -Participant ":Logic" as logic LOGIC_COLOR -Participant ":Model" as model MODEL_COLOR -Participant ":Storage" as storage STORAGE_COLOR +skinparam { + ArrowFontStyle bold + ArrowFontSize 13 + ParticipantFontStyle bold + ActorFontStyle bold + SequenceMessageAlignment center +} -user -[USER_COLOR]> ui : "delete 1" +title "Sequence Diagram: Delete Student Command" + +actor "User" as user USER_COLOR +participant ":UI" as ui UI_COLOR +participant ":Logic" as logic LOGIC_COLOR +participant ":Model" as model MODEL_COLOR +participant ":Storage" as storage STORAGE_COLOR + +== Command Execution == +user -> ui : "delete_student 1" activate ui UI_COLOR -ui -[UI_COLOR]> logic : execute("delete 1") +ui -> logic : execute("delete_student 1") activate logic LOGIC_COLOR -logic -[LOGIC_COLOR]> model : deletePerson(p) +logic -> model : deleteStudent(1) activate model MODEL_COLOR -model -[MODEL_COLOR]-> logic +model --> logic : Student removed deactivate model -logic -[LOGIC_COLOR]> storage : saveAddressBook(addressBook) +== Data Persistence == +logic -> storage : saveAddressBook(addressBook) activate storage STORAGE_COLOR -storage -[STORAGE_COLOR]> storage : Save to file +storage -> storage : JSON Serialization activate storage STORAGE_COLOR_T1 -storage --[STORAGE_COLOR]> storage +note right: Saves to tutortrack.json +storage --> storage deactivate storage -storage --[STORAGE_COLOR]> logic +storage --> logic : Save confirmed deactivate storage -logic --[LOGIC_COLOR]> ui +== UI Update == +logic --> ui : CommandResult deactivate logic -ui--[UI_COLOR]> user +ui --> user : "Deleted Student: Alice\n(1 student remaining)" deactivate ui + @enduml diff --git a/docs/diagrams/BetterModelClassDiagram.puml b/docs/diagrams/BetterModelClassDiagram.puml deleted file mode 100644 index 598474a5c82..00000000000 --- a/docs/diagrams/BetterModelClassDiagram.puml +++ /dev/null @@ -1,21 +0,0 @@ -@startuml -!include style.puml -skinparam arrowThickness 1.1 -skinparam arrowColor MODEL_COLOR -skinparam classBackgroundColor MODEL_COLOR - -AddressBook *-right-> "1" UniquePersonList -AddressBook *-right-> "1" UniqueTagList -UniqueTagList -[hidden]down- UniquePersonList -UniqueTagList -[hidden]down- UniquePersonList - -UniqueTagList -right-> "*" Tag -UniquePersonList -right-> Person - -Person -up-> "*" Tag - -Person *--> Name -Person *--> Phone -Person *--> Email -Person *--> Address -@enduml diff --git a/docs/diagrams/BetterModelClassDiagram_StudentManagementStructure.puml b/docs/diagrams/BetterModelClassDiagram_StudentManagementStructure.puml new file mode 100644 index 00000000000..edeb6014311 --- /dev/null +++ b/docs/diagrams/BetterModelClassDiagram_StudentManagementStructure.puml @@ -0,0 +1,114 @@ +@startuml +!include style.puml + +skinparam { + classFontSize 13 + classFontStyle bold + arrowThickness 1.3 + arrowColor #444444 + classBackgroundColor #000000 + classBorderColor MODEL_COLOR + classArrowColor MODEL_COLOR +} + +title "Class Diagram of Models in TutorTrack" + +class AddressBook { + - students: UniqueStudentList + - subjects: UniqueLessonList +} + +class UniqueStudentList { + - internalList: List + + add(Student) + + remove(Student) +} + +class UniqueLessonList { + - internalList: List + + add(Subject) + + remove(Subject) +} + +class UniqueAssignmentList { + - internalList: List + + add(Assignment) + + remove(Assignment) +} + +class Student { + - name: Name + - phone: Phone + - email: Email + - address: Address + - subjects: Set + + addSubject(Subject) + + removeSubject(Subject) +} + +class Lesson { + - studentName: Name + - subjects: Set + - date: Date + - time: Time +} + +class Name { + - fullName: String +} +class Phone { + - value: String +} +class Email { + - value: String +} +class Address { + - value: String +} +class Subject { + - value: String +} +class Date { + - value: LocalDate +} +class Time { + - value: LocalTime +} +class Assignment { + - assignmentName: Name + - dueDate: Date + - isComplete: boolean +} + +' Composition Relationships +AddressBook "1" *-- "1" UniqueStudentList : contains +AddressBook "1" *-- "1" UniqueLessonList : contains + +UniqueStudentList "1" *-- "*" Student : maintains +UniqueLessonList "1" *-- "*" Lesson : maintains + +' Student Components +Student *-- "1" Name : has +Student *-- "1" Phone : has +Student *-- "1" Email : has +Student *-- "1" Address : has +Student *-- "*" Subject : has +Student *-- "1" UniqueAssignmentList : has + +' Subject Association +Student "1" --> "*" Lesson : subjected with + +' Lesson Components +Lesson *-- "1" Name : for student +Lesson *-- "*" Subject : teaches +Lesson *-- "1" Date : on +Lesson *-- "1" Time : at + +' Assignment Components +UniqueAssignmentList *-- "*" Assignment : maintains + +' Layout improvements +UniqueLessonList -[hidden]right- UniqueStudentList +Name -[hidden]down- Phone +Email -[hidden]down- Address +@enduml diff --git a/docs/diagrams/CommitActivityDiagram.puml b/docs/diagrams/CommitActivityDiagram.puml index 8c0892d6a70..6b5b903d875 100644 --- a/docs/diagrams/CommitActivityDiagram.puml +++ b/docs/diagrams/CommitActivityDiagram.puml @@ -8,10 +8,10 @@ start 'Since the beta syntax does not support placing the condition outside the 'diamond we place it as the true branch instead. -if () then ([command commits AddressBook]) +if () then ([command commits TutorTrack]) :Purge redundant states; - :Save AddressBook to - addressBookStateList; + :Save TutorTrack to + tutorTrackStateList; else ([else]) endif stop diff --git a/docs/diagrams/ComponentManagers.puml b/docs/diagrams/ComponentManagers.puml index 564dd1ae32f..793bdaddc7d 100644 --- a/docs/diagrams/ComponentManagers.puml +++ b/docs/diagrams/ComponentManagers.puml @@ -4,6 +4,19 @@ skinparam arrowThickness 1.1 skinparam arrowColor LOGIC_COLOR_T4 skinparam classBackgroundColor LOGIC_COLOR + +skinparam { + ArrowThickness 1.3 + ArrowColor LOGIC_COLOR_T4 + ClassFontStyle bold + ClassFontSize 13 + PackageBorderColor #444444 + PackageFontSize 15 + PackageFontStyle bold +} + +title "Component Dependency Diagram" + package Logic as LogicPackage { Class "<>\nLogic" as Logic Class LogicManager @@ -26,6 +39,7 @@ LogicManager .up.|> Logic ModelManager .up.|> Model StorageManager .up.|> Storage -LogicManager --> Model -LogicManager --> Storage +LogicManager -> Model : uses +LogicManager -> Storage : saves to + @enduml diff --git a/docs/diagrams/DeleteSequenceDiagram.puml b/docs/diagrams/DeleteSequenceDiagram.puml index 5241e79d7da..5675507faf6 100644 --- a/docs/diagrams/DeleteSequenceDiagram.puml +++ b/docs/diagrams/DeleteSequenceDiagram.puml @@ -5,8 +5,8 @@ skinparam ArrowFontStyle plain box Logic LOGIC_COLOR_T1 participant ":LogicManager" as LogicManager LOGIC_COLOR participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR -participant ":DeleteCommandParser" as DeleteCommandParser LOGIC_COLOR -participant "d:DeleteCommand" as DeleteCommand LOGIC_COLOR +participant ":DeleteStudentCommandParser" as DeleteStudentCommandParser LOGIC_COLOR +participant "d:DeleteStudentCommand" as DeleteStudentCommand LOGIC_COLOR participant "r:CommandResult" as CommandResult LOGIC_COLOR end box @@ -14,56 +14,56 @@ box Model MODEL_COLOR_T1 participant "m:Model" as Model MODEL_COLOR end box -[-> LogicManager : execute("delete 1") +[-> LogicManager : execute("delete_student 1") activate LogicManager -LogicManager -> AddressBookParser : parseCommand("delete 1") +LogicManager -> AddressBookParser : parseCommand("delete_student 1") activate AddressBookParser -create DeleteCommandParser -AddressBookParser -> DeleteCommandParser -activate DeleteCommandParser +create DeleteStudentCommandParser +AddressBookParser -> DeleteStudentCommandParser +activate DeleteStudentCommandParser -DeleteCommandParser --> AddressBookParser -deactivate DeleteCommandParser +DeleteStudentCommandParser --> AddressBookParser +deactivate DeleteStudentCommandParser -AddressBookParser -> DeleteCommandParser : parse("1") -activate DeleteCommandParser +AddressBookParser -> DeleteStudentCommandParser : parse("1") +activate DeleteStudentCommandParser -create DeleteCommand -DeleteCommandParser -> DeleteCommand -activate DeleteCommand +create DeleteStudentCommand +DeleteStudentCommandParser -> DeleteStudentCommand +activate DeleteStudentCommand -DeleteCommand --> DeleteCommandParser : -deactivate DeleteCommand +DeleteStudentCommand --> DeleteStudentCommandParser : +deactivate DeleteStudentCommand -DeleteCommandParser --> AddressBookParser : d -deactivate DeleteCommandParser +DeleteStudentCommandParser --> AddressBookParser : d +deactivate DeleteStudentCommandParser 'Hidden arrow to position the destroy marker below the end of the activation bar. -DeleteCommandParser -[hidden]-> AddressBookParser -destroy DeleteCommandParser +DeleteStudentCommandParser -[hidden]-> AddressBookParser +destroy DeleteStudentCommandParser AddressBookParser --> LogicManager : d deactivate AddressBookParser -LogicManager -> DeleteCommand : execute(m) -activate DeleteCommand +LogicManager -> DeleteStudentCommand : execute(m) +activate DeleteStudentCommand -DeleteCommand -> Model : deletePerson(1) +DeleteStudentCommand -> Model : deleteStudent(1) activate Model -Model --> DeleteCommand +Model --> DeleteStudentCommand deactivate Model create CommandResult -DeleteCommand -> CommandResult +DeleteStudentCommand -> CommandResult activate CommandResult -CommandResult --> DeleteCommand +CommandResult --> DeleteStudentCommand deactivate CommandResult -DeleteCommand --> LogicManager : r -deactivate DeleteCommand +DeleteStudentCommand --> LogicManager : r +deactivate DeleteStudentCommand [<--LogicManager deactivate LogicManager diff --git a/docs/diagrams/LogicClassDiagram.puml b/docs/diagrams/LogicClassDiagram.puml index 58b4f602ce6..784dd707706 100644 --- a/docs/diagrams/LogicClassDiagram.puml +++ b/docs/diagrams/LogicClassDiagram.puml @@ -29,19 +29,19 @@ HiddenOutside ..> Logic LogicManager .right.|> Logic LogicManager -right->"1" ParserClasses -ParserClasses ..> XYZCommand : <> +ParserClasses ..> XYZCommand : create XYZCommand -up-|> Command -LogicManager .left.> Command : <> +LogicManager .left.> Command : call LogicManager --> Model LogicManager --> Storage Storage --[hidden] Model Command .[hidden]up.> Storage Command .right.> Model -note right of XYZCommand: XYZCommand = AddCommand, \nFindCommand, etc +note right of XYZCommand: XYZCommand = AddStudentCommand, \nFindStudentCommand, etc Logic ..> CommandResult LogicManager .down.> CommandResult -Command .up.> CommandResult : <> +Command .up.> CommandResult : create @enduml diff --git a/docs/diagrams/ModelClassDiagram.puml b/docs/diagrams/ModelClassDiagram.puml index 0de5673070d..7c3e431dfab 100644 --- a/docs/diagrams/ModelClassDiagram.puml +++ b/docs/diagrams/ModelClassDiagram.puml @@ -12,13 +12,15 @@ Class AddressBook Class ModelManager Class UserPrefs -Class UniquePersonList -Class Person +Class UniqueStudentList +Class Student Class Address Class Email Class Name Class Phone -Class Tag +Class Subject +Class UniqueAssignmentList +Class Assignment Class I #FFFFFF } @@ -35,20 +37,22 @@ ModelManager -left-> "1" AddressBook ModelManager -right-> "1" UserPrefs UserPrefs .up.|> ReadOnlyUserPrefs -AddressBook *--> "1" UniquePersonList -UniquePersonList --> "~* all" Person -Person *--> Name -Person *--> Phone -Person *--> Email -Person *--> Address -Person *--> "*" Tag +AddressBook *--> "1" UniqueStudentList +UniqueStudentList --> "~* all" Student +Student *--> Name +Student *--> Phone +Student *--> Email +Student *--> Address +Student *--> "*" Subject +Student *--> "1" UniqueAssignmentList +UniqueAssignmentList --> "*" Assignment -Person -[hidden]up--> I -UniquePersonList -[hidden]right-> I +Student -[hidden]up--> I +UniqueStudentList -[hidden]right-> I Name -[hidden]right-> Phone Phone -[hidden]right-> Address Address -[hidden]right-> Email -ModelManager --> "~* filtered" Person +ModelManager --> "~* filtered" Student @enduml diff --git a/docs/diagrams/ParserClasses.puml b/docs/diagrams/ParserClasses.puml index ce4c5ce8c8d..fcd6dedebc3 100644 --- a/docs/diagrams/ParserClasses.puml +++ b/docs/diagrams/ParserClasses.puml @@ -3,6 +3,11 @@ skinparam arrowThickness 1.1 skinparam arrowColor LOGIC_COLOR_T4 skinparam classBackgroundColor LOGIC_COLOR +skinparam class { + FontSize 13 + FontStyle plain + BorderColor #333333 +} Class "{abstract}\nCommand" as Command Class XYZCommand @@ -21,10 +26,11 @@ Class Prefix Class HiddenOutside #FFFFFF HiddenOutside ..> AddressBookParser -AddressBookParser .down.> XYZCommandParser: <> +AddressBookParser .down.> XYZCommandParser: create + -XYZCommandParser ..> XYZCommand : <> -AddressBookParser ..> Command : <> +XYZCommandParser ..> XYZCommand : create +AddressBookParser ..> Command : use XYZCommandParser .up.|> Parser XYZCommandParser ..> ArgumentMultimap XYZCommandParser ..> ArgumentTokenizer diff --git a/docs/diagrams/StorageClassDiagram.puml b/docs/diagrams/StorageClassDiagram.puml index a821e06458c..907d0a0f1e5 100644 --- a/docs/diagrams/StorageClassDiagram.puml +++ b/docs/diagrams/StorageClassDiagram.puml @@ -18,8 +18,10 @@ package "AddressBook Storage" #F4F6F6{ Class "<>\nAddressBookStorage" as AddressBookStorage Class JsonAddressBookStorage Class JsonSerializableAddressBook -Class JsonAdaptedPerson -Class JsonAdaptedTag +Class JsonAdaptedStudent +Class JsonAdaptedLesson +Class JsonAdaptedSubject +Class JsonAdaptedAssignment } } @@ -37,7 +39,9 @@ Storage -right-|> AddressBookStorage JsonUserPrefsStorage .up.|> UserPrefsStorage JsonAddressBookStorage .up.|> AddressBookStorage JsonAddressBookStorage ..> JsonSerializableAddressBook -JsonSerializableAddressBook --> "*" JsonAdaptedPerson -JsonAdaptedPerson --> "*" JsonAdaptedTag +JsonSerializableAddressBook --> "*" JsonAdaptedStudent +JsonSerializableAddressBook --> "*" JsonAdaptedLesson +JsonAdaptedStudent --> "*" JsonAdaptedSubject +JsonAdaptedStudent --> "*" JsonAdaptedAssignment @enduml diff --git a/docs/diagrams/UiClassDiagram.puml b/docs/diagrams/UiClassDiagram.puml index 95473d5aa19..ae742bb1050 100644 --- a/docs/diagrams/UiClassDiagram.puml +++ b/docs/diagrams/UiClassDiagram.puml @@ -11,8 +11,9 @@ Class UiManager Class MainWindow Class HelpWindow Class ResultDisplay -Class PersonListPanel -Class PersonCard +Class ListPanel +Class ListCard +Class AssignmentCard Class StatusBarFooter Class CommandBox } @@ -32,26 +33,28 @@ UiManager .left.|> Ui UiManager -down-> "1" MainWindow MainWindow *-down-> "1" CommandBox MainWindow *-down-> "1" ResultDisplay -MainWindow *-down-> "1" PersonListPanel +MainWindow *-down-> "1" ListPanel MainWindow *-down-> "1" StatusBarFooter MainWindow --> "0..1" HelpWindow -PersonListPanel -down-> "*" PersonCard +ListPanel -down-> "*" ListCard +ListCard -down-> “*” AssignmentCard MainWindow -left-|> UiPart ResultDisplay --|> UiPart CommandBox --|> UiPart -PersonListPanel --|> UiPart -PersonCard --|> UiPart +ListPanel --|> UiPart +ListCard --|> UiPart +AssignmentCard --|> UiPart StatusBarFooter --|> UiPart HelpWindow --|> UiPart -PersonCard ..> Model +ListCard ..> Model UiManager -right-> Logic MainWindow -left-> Logic -PersonListPanel -[hidden]left- HelpWindow +ListPanel -[hidden]left- HelpWindow HelpWindow -[hidden]left- CommandBox CommandBox -[hidden]left- ResultDisplay ResultDisplay -[hidden]left- StatusBarFooter diff --git a/docs/diagrams/UndoRedoState0.puml b/docs/diagrams/UndoRedoState0.puml index 43a45903ac9..2263fed2d12 100644 --- a/docs/diagrams/UndoRedoState0.puml +++ b/docs/diagrams/UndoRedoState0.puml @@ -7,9 +7,9 @@ skinparam ClassBackgroundColor #FFFFAA title Initial state package States { - class State1 as "ab0:AddressBook" - class State2 as "ab1:AddressBook" - class State3 as "ab2:AddressBook" + class State1 as "tt0:TutorTrack" + class State2 as "tt1:TutorTrack" + class State3 as "tt2:TutorTrack" } State1 -[hidden]right-> State2 State2 -[hidden]right-> State3 diff --git a/docs/diagrams/UndoRedoState1.puml b/docs/diagrams/UndoRedoState1.puml index 5a41e9e1651..785ce49ffdc 100644 --- a/docs/diagrams/UndoRedoState1.puml +++ b/docs/diagrams/UndoRedoState1.puml @@ -4,12 +4,12 @@ skinparam ClassFontColor #000000 skinparam ClassBorderColor #000000 skinparam ClassBackgroundColor #FFFFAA -title After command "delete 5" +title After command "delete_student 5" package States <> { - class State1 as "ab0:AddressBook" - class State2 as "ab1:AddressBook" - class State3 as "ab2:AddressBook" + class State1 as "tt0:TutorTrack" + class State2 as "tt1:TutorTrack" + class State3 as "tt2:TutorTrack" } State1 -[hidden]right-> State2 diff --git a/docs/diagrams/UndoRedoState2.puml b/docs/diagrams/UndoRedoState2.puml index ad32fce1b0b..702acb202fe 100644 --- a/docs/diagrams/UndoRedoState2.puml +++ b/docs/diagrams/UndoRedoState2.puml @@ -4,12 +4,12 @@ skinparam ClassFontColor #000000 skinparam ClassBorderColor #000000 skinparam ClassBackgroundColor #FFFFAA -title After command "add n/David" +title After command "add_student n/David" package States <> { - class State1 as "ab0:AddressBook" - class State2 as "ab1:AddressBook" - class State3 as "ab2:AddressBook" + class State1 as "tt0:TutorTrack" + class State2 as "tt1:TutorTrack" + class State3 as "tt2:TutorTrack" } State1 -[hidden]right-> State2 diff --git a/docs/diagrams/UndoRedoState3.puml b/docs/diagrams/UndoRedoState3.puml index 9187a690036..ae473537caa 100644 --- a/docs/diagrams/UndoRedoState3.puml +++ b/docs/diagrams/UndoRedoState3.puml @@ -7,9 +7,9 @@ skinparam ClassBackgroundColor #FFFFAA title After command "undo" package States <> { - class State1 as "ab0:AddressBook" - class State2 as "ab1:AddressBook" - class State3 as "ab2:AddressBook" + class State1 as "tt0:TutorTrack" + class State2 as "tt1:TutorTrack" + class State3 as "tt2:TutorTrack" } State1 -[hidden]right-> State2 diff --git a/docs/diagrams/UndoRedoState4.puml b/docs/diagrams/UndoRedoState4.puml index 2bc631ffcd0..ae37f744922 100644 --- a/docs/diagrams/UndoRedoState4.puml +++ b/docs/diagrams/UndoRedoState4.puml @@ -4,12 +4,12 @@ skinparam ClassFontColor #000000 skinparam ClassBorderColor #000000 skinparam ClassBackgroundColor #FFFFAA -title After command "list" +title After command "list_students" package States <> { - class State1 as "ab0:AddressBook" - class State2 as "ab1:AddressBook" - class State3 as "ab2:AddressBook" + class State1 as "tt0:TutorTrack" + class State2 as "tt1:TutorTrack" + class State3 as "tt2:TutorTrack" } State1 -[hidden]right-> State2 diff --git a/docs/diagrams/UndoRedoState5.puml b/docs/diagrams/UndoRedoState5.puml index e77b04104aa..7adc8346c08 100644 --- a/docs/diagrams/UndoRedoState5.puml +++ b/docs/diagrams/UndoRedoState5.puml @@ -7,9 +7,9 @@ skinparam ClassBackgroundColor #FFFFAA title After command "clear" package States <> { - class State1 as "ab0:AddressBook" - class State2 as "ab1:AddressBook" - class State3 as "ab3:AddressBook" + class State1 as "tt0:TutorTrack" + class State2 as "tt1:TutorTrack" + class State3 as "tt2:TutorTrack" } State1 -[hidden]right-> State2 @@ -18,5 +18,5 @@ State2 -[hidden]right-> State3 class Pointer as "Current State" #FFFFFF Pointer -up-> State3 -note right on link: State ab2 deleted. +note right on link: State tt2 deleted. @end diff --git a/docs/diagrams/UndoSequenceDiagram-Logic.puml b/docs/diagrams/UndoSequenceDiagram-Logic.puml index e57368c5159..6b80e94dc96 100644 --- a/docs/diagrams/UndoSequenceDiagram-Logic.puml +++ b/docs/diagrams/UndoSequenceDiagram-Logic.puml @@ -2,35 +2,36 @@ !include style.puml skinparam ArrowFontStyle plain -box Logic LOGIC_COLOR_T1 -participant ":LogicManager" as LogicManager LOGIC_COLOR -participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR -participant "u:UndoCommand" as UndoCommand LOGIC_COLOR +box "Logic" LOGIC_COLOR_T1 + participant ":LogicManager" as LogicManager LOGIC_COLOR + participant ":TutorTrackParser" as TutorTrackParser LOGIC_COLOR + participant "u:UndoCommand" as UndoCommand LOGIC_COLOR end box -box Model MODEL_COLOR_T1 -participant ":Model" as Model MODEL_COLOR +box "Model" MODEL_COLOR_T1 + participant ":Model" as Model MODEL_COLOR end box -[-> LogicManager : execute(undo) + +[-> LogicManager : execute("undo") activate LogicManager -LogicManager -> AddressBookParser : parseCommand(undo) -activate AddressBookParser +LogicManager -> TutorTrackParser : parseCommand("undo") +activate TutorTrackParser create UndoCommand -AddressBookParser -> UndoCommand +TutorTrackParser -> UndoCommand **: create activate UndoCommand -UndoCommand --> AddressBookParser +UndoCommand --> TutorTrackParser deactivate UndoCommand -AddressBookParser --> LogicManager : u -deactivate AddressBookParser +TutorTrackParser --> LogicManager : u +deactivate TutorTrackParser LogicManager -> UndoCommand : execute() activate UndoCommand -UndoCommand -> Model : undoAddressBook() +UndoCommand -> Model : undoTutorTrack() activate Model Model --> UndoCommand @@ -38,9 +39,10 @@ deactivate Model UndoCommand --> LogicManager : result deactivate UndoCommand -UndoCommand -[hidden]-> LogicManager : result + +UndoCommand -[hidden]-> LogicManager destroy UndoCommand -[<--LogicManager +[<-- LogicManager deactivate LogicManager @enduml diff --git a/docs/diagrams/UndoSequenceDiagram-Model.puml b/docs/diagrams/UndoSequenceDiagram-Model.puml index 54d83208cb8..ec890135333 100644 --- a/docs/diagrams/UndoSequenceDiagram-Model.puml +++ b/docs/diagrams/UndoSequenceDiagram-Model.puml @@ -2,20 +2,20 @@ !include style.puml skinparam ArrowFontStyle plain -box Model MODEL_COLOR_T1 -participant ":Model" as Model MODEL_COLOR -participant ":VersionedAddressBook" as VersionedAddressBook MODEL_COLOR +box "Model" MODEL_COLOR_T1 + participant ":Model" as Model MODEL_COLOR + participant ":VersionedTutorTrack" as VersionedTutorTrack MODEL_COLOR end box -[-> Model : undoAddressBook() +[-> Model : undoTutorTrack() activate Model -Model -> VersionedAddressBook : undo() -activate VersionedAddressBook +Model -> VersionedTutorTrack : undo() +activate VersionedTutorTrack -VersionedAddressBook -> VersionedAddressBook :resetData(ReadOnlyAddressBook) -VersionedAddressBook --> Model : -deactivate VersionedAddressBook +VersionedTutorTrack -> VersionedTutorTrack : resetData(ReadOnlyTutorTrack) +VersionedTutorTrack --> Model +deactivate VersionedTutorTrack [<-- Model deactivate Model diff --git a/docs/diagrams/tracing/LogicSequenceDiagram.puml b/docs/diagrams/tracing/LogicSequenceDiagram.puml index 42bf46d3ce8..eadb42975e9 100644 --- a/docs/diagrams/tracing/LogicSequenceDiagram.puml +++ b/docs/diagrams/tracing/LogicSequenceDiagram.puml @@ -4,8 +4,8 @@ skinparam ArrowFontStyle plain Participant ":LogicManager" as logic LOGIC_COLOR Participant ":AddressBookParser" as abp LOGIC_COLOR -Participant ":EditCommandParser" as ecp LOGIC_COLOR -Participant "command:EditCommand" as ec LOGIC_COLOR +Participant ":EditStudentCommandParser" as ecp LOGIC_COLOR +Participant "command:EditStudentCommand" as ec LOGIC_COLOR [-> logic : execute activate logic @@ -14,7 +14,7 @@ create ecp abp -> ecp abp -> ecp ++: parse(arguments) create ec -ecp -> ec ++: index, editPersonDescriptor +ecp -> ec ++: index, editStudentDescriptor ec --> ecp -- ecp --> abp --: command abp --> logic --: command diff --git a/docs/images/ArchitectureSequenceDiagram.png b/docs/images/ArchitectureSequenceDiagram.png index 37ad06a2803..3297e3267ff 100644 Binary files a/docs/images/ArchitectureSequenceDiagram.png and b/docs/images/ArchitectureSequenceDiagram.png differ diff --git a/docs/images/BetterModelClassDiagram.png b/docs/images/BetterModelClassDiagram.png index 02a42e35e76..f4126df9e0a 100644 Binary files a/docs/images/BetterModelClassDiagram.png and b/docs/images/BetterModelClassDiagram.png differ diff --git a/docs/images/CommitActivityDiagram.png b/docs/images/CommitActivityDiagram.png index 5b464126b35..1739ebf9a54 100644 Binary files a/docs/images/CommitActivityDiagram.png and b/docs/images/CommitActivityDiagram.png differ diff --git a/docs/images/ComponentManagers-Component Dependency Diagram.png b/docs/images/ComponentManagers-Component Dependency Diagram.png new file mode 100755 index 00000000000..a527f0aa6b4 Binary files /dev/null and b/docs/images/ComponentManagers-Component Dependency Diagram.png differ diff --git a/docs/images/ComponentManagers-Component_Dependency_Diagram.png b/docs/images/ComponentManagers-Component_Dependency_Diagram.png new file mode 100755 index 00000000000..a527f0aa6b4 Binary files /dev/null and b/docs/images/ComponentManagers-Component_Dependency_Diagram.png differ diff --git a/docs/images/DeleteSequenceDiagram.png b/docs/images/DeleteSequenceDiagram.png index ac2ae217c51..54e1d22cab4 100644 Binary files a/docs/images/DeleteSequenceDiagram.png and b/docs/images/DeleteSequenceDiagram.png differ diff --git a/docs/images/DeleteStudentSequenceDiagram.png b/docs/images/DeleteStudentSequenceDiagram.png new file mode 100644 index 00000000000..54e1d22cab4 Binary files /dev/null and b/docs/images/DeleteStudentSequenceDiagram.png differ diff --git a/docs/images/LogicClassDiagram.png b/docs/images/LogicClassDiagram.png old mode 100644 new mode 100755 index fe91c69efe7..5fb12c7845f Binary files a/docs/images/LogicClassDiagram.png and b/docs/images/LogicClassDiagram.png differ diff --git a/docs/images/ModelClassDiagram.png b/docs/images/ModelClassDiagram.png old mode 100644 new mode 100755 index a19fb1b4ac8..a5a81274194 Binary files a/docs/images/ModelClassDiagram.png and b/docs/images/ModelClassDiagram.png differ diff --git a/docs/images/ParserClasses-0.png b/docs/images/ParserClasses-0.png new file mode 100755 index 00000000000..bbf6018c689 Binary files /dev/null and b/docs/images/ParserClasses-0.png differ diff --git a/docs/images/ParserClasses.png b/docs/images/ParserClasses.png index 2caeeb1a067..bbfa9b45a20 100644 Binary files a/docs/images/ParserClasses.png and b/docs/images/ParserClasses.png differ diff --git a/docs/images/StorageClassDiagram.png b/docs/images/StorageClassDiagram.png index 18fa4d0d51f..55b015e7ab7 100644 Binary files a/docs/images/StorageClassDiagram.png and b/docs/images/StorageClassDiagram.png differ diff --git a/docs/images/Ui.png b/docs/images/Ui.png index 5bd77847aa2..a1a00d64d97 100644 Binary files a/docs/images/Ui.png and b/docs/images/Ui.png differ diff --git a/docs/images/UiClassDiagram.png b/docs/images/UiClassDiagram.png index 11f06d68671..113edb7b3b5 100644 Binary files a/docs/images/UiClassDiagram.png and b/docs/images/UiClassDiagram.png differ diff --git a/docs/images/UndoRedoState0.png b/docs/images/UndoRedoState0.png index c5f91b58533..59c0092dce4 100644 Binary files a/docs/images/UndoRedoState0.png and b/docs/images/UndoRedoState0.png differ diff --git a/docs/images/UndoRedoState1.png b/docs/images/UndoRedoState1.png index 2d3ad09c047..16698b48f6e 100644 Binary files a/docs/images/UndoRedoState1.png and b/docs/images/UndoRedoState1.png differ diff --git a/docs/images/UndoRedoState2.png b/docs/images/UndoRedoState2.png index 20853694e03..2bc367cd2ef 100644 Binary files a/docs/images/UndoRedoState2.png and b/docs/images/UndoRedoState2.png differ diff --git a/docs/images/UndoRedoState3.png b/docs/images/UndoRedoState3.png index 1a9551b31be..5874ae34018 100644 Binary files a/docs/images/UndoRedoState3.png and b/docs/images/UndoRedoState3.png differ diff --git a/docs/images/UndoRedoState4.png b/docs/images/UndoRedoState4.png index 46dfae78c94..c197f8a64d4 100644 Binary files a/docs/images/UndoRedoState4.png and b/docs/images/UndoRedoState4.png differ diff --git a/docs/images/UndoRedoState5.png b/docs/images/UndoRedoState5.png index f45889b5fdf..82a7f60cae6 100644 Binary files a/docs/images/UndoRedoState5.png and b/docs/images/UndoRedoState5.png differ diff --git a/docs/images/UndoSequenceDiagram-Logic-0.png b/docs/images/UndoSequenceDiagram-Logic-0.png new file mode 100755 index 00000000000..97cdfaa0423 Binary files /dev/null and b/docs/images/UndoSequenceDiagram-Logic-0.png differ diff --git a/docs/images/UndoSequenceDiagram-Logic.png b/docs/images/UndoSequenceDiagram-Logic.png index 78e95214294..94512f8c54d 100644 Binary files a/docs/images/UndoSequenceDiagram-Logic.png and b/docs/images/UndoSequenceDiagram-Logic.png differ diff --git a/docs/images/UndoSequenceDiagram-Model-0.png b/docs/images/UndoSequenceDiagram-Model-0.png new file mode 100755 index 00000000000..203a1cd01a8 Binary files /dev/null and b/docs/images/UndoSequenceDiagram-Model-0.png differ diff --git a/docs/images/UndoSequenceDiagram-Model.png b/docs/images/UndoSequenceDiagram-Model.png index f0f3da6ae50..5e712bd3464 100644 Binary files a/docs/images/UndoSequenceDiagram-Model.png and b/docs/images/UndoSequenceDiagram-Model.png differ diff --git a/docs/images/andong0909.png b/docs/images/andong0909.png new file mode 100644 index 00000000000..59fdf4cd45c Binary files /dev/null and b/docs/images/andong0909.png differ diff --git a/docs/images/charlesl12.png b/docs/images/charlesl12.png new file mode 100644 index 00000000000..83e680b965f Binary files /dev/null and b/docs/images/charlesl12.png differ diff --git a/docs/images/gztan23.png b/docs/images/gztan23.png new file mode 100644 index 00000000000..f056736bffa Binary files /dev/null and b/docs/images/gztan23.png differ diff --git a/docs/images/helpMessage.png b/docs/images/helpMessage.png index b1f70470137..2817f9b12a2 100644 Binary files a/docs/images/helpMessage.png and b/docs/images/helpMessage.png differ diff --git a/docs/images/img.png b/docs/images/img.png new file mode 100755 index 00000000000..30224642dc8 Binary files /dev/null and b/docs/images/img.png differ diff --git a/docs/images/jiangsuwangjing.png b/docs/images/jiangsuwangjing.png new file mode 100644 index 00000000000..1af2b2af885 Binary files /dev/null and b/docs/images/jiangsuwangjing.png differ diff --git a/docs/images/lessonList.png b/docs/images/lessonList.png new file mode 100644 index 00000000000..8c9d92948c4 Binary files /dev/null and b/docs/images/lessonList.png differ diff --git a/docs/images/nicholasohjj.png b/docs/images/nicholasohjj.png new file mode 100755 index 00000000000..0ece495e6d2 Binary files /dev/null and b/docs/images/nicholasohjj.png differ diff --git a/docs/index.md b/docs/index.md index 7601dbaad0d..45a317c0393 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,17 +1,17 @@ --- layout: page -title: AddressBook Level-3 +title: TutorTrack --- -[![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/AY2425S2-CS2103T-T13-4/tp/workflows/Java%20CI/badge.svg)](https://github.com/AY2425S2-CS2103T-T13-4/tp/actions) [![codecov](https://codecov.io/gh/se-edu/addressbook-level3/branch/master/graph/badge.svg)](https://codecov.io/gh/se-edu/addressbook-level3) ![Ui](images/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). +**TutorTrack is a desktop application for managing your contact details.** While it has a GUI, most of the user interactions happen using a CLI (Command Line Interface). -* If you are interested in using AddressBook, head over to the [_Quick Start_ section of the **User Guide**](UserGuide.html#quick-start). -* If you are interested about developing AddressBook, the [**Developer Guide**](DeveloperGuide.html) is a good place to start. +* If you are interested in using TutorTrack, head over to the [_Quick Start_ section of the **User Guide**](UserGuide.html#quick-start). +* If you are interested about developing TutorTrack, the [**Developer Guide**](DeveloperGuide.html) is a good place to start. **Acknowledgements** diff --git a/src/main/java/seedu/address/commons/core/LogsCenter.java b/src/main/java/seedu/address/commons/core/LogsCenter.java index 8cf8e15a0f0..3a9bca738fb 100644 --- a/src/main/java/seedu/address/commons/core/LogsCenter.java +++ b/src/main/java/seedu/address/commons/core/LogsCenter.java @@ -20,7 +20,7 @@ public class LogsCenter { private static final int MAX_FILE_COUNT = 5; private static final int MAX_FILE_SIZE_IN_BYTES = (int) (Math.pow(2, 20) * 5); // 5MB - private static final String LOG_FILE = "addressbook.log"; + private static final String LOG_FILE = "TutorTrack.log"; private static final Logger logger; // logger for this class private static Logger baseLogger; // to be used as the parent of all other loggers created by this class. private static Level currentLogLevel = Level.INFO; @@ -75,11 +75,11 @@ private static void removeHandlers(Logger logger) { } /** - * Creates a logger named 'ab3', containing a {@code ConsoleHandler} and a {@code FileHandler}. + * Creates a logger named 'tutortrack', containing a {@code ConsoleHandler} and a {@code FileHandler}. * Sets it as the {@code baseLogger}, to be used as the parent logger of all other loggers. */ private static void setBaseLogger() { - baseLogger = Logger.getLogger("ab3"); + baseLogger = Logger.getLogger("tutortrack"); baseLogger.setUseParentHandlers(false); removeHandlers(baseLogger); diff --git a/src/main/java/seedu/address/logic/Logic.java b/src/main/java/seedu/address/logic/Logic.java index 92cd8fa605a..66a00a2773d 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.lesson.Lesson; +import seedu.address.model.student.Student; /** * API of the Logic component @@ -30,8 +31,14 @@ 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 lessons */ + ObservableList getFilteredLessonList(); + + /** Returns the current list on display */ + ObservableList getFilteredCurrList(); /** * Returns the user prefs' address book file path. diff --git a/src/main/java/seedu/address/logic/LogicManager.java b/src/main/java/seedu/address/logic/LogicManager.java index 5aa3b91c7d0..82578af58b2 100644 --- a/src/main/java/seedu/address/logic/LogicManager.java +++ b/src/main/java/seedu/address/logic/LogicManager.java @@ -15,7 +15,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.lesson.Lesson; +import seedu.address.model.student.Student; import seedu.address.storage.Storage; /** @@ -67,8 +68,18 @@ public ReadOnlyAddressBook getAddressBook() { } @Override - public ObservableList getFilteredPersonList() { - return model.getFilteredPersonList(); + public ObservableList getFilteredStudentList() { + return model.getFilteredStudentList(); + } + + @Override + public ObservableList getFilteredLessonList() { + return model.getFilteredLessonList(); + } + + @Override + public ObservableList getFilteredCurrList() { + return model.getFilteredCurrList(); } @Override diff --git a/src/main/java/seedu/address/logic/Messages.java b/src/main/java/seedu/address/logic/Messages.java index ecd32c31b53..ef5a7f6e090 100644 --- a/src/main/java/seedu/address/logic/Messages.java +++ b/src/main/java/seedu/address/logic/Messages.java @@ -5,19 +5,31 @@ import java.util.stream.Stream; import seedu.address.logic.parser.Prefix; -import seedu.address.model.person.Person; +import seedu.address.model.assignment.Assignment; +import seedu.address.model.lesson.Lesson; +import seedu.address.model.student.Student; /** * Container for user visible messages. */ public class Messages { - public static final String MESSAGE_UNKNOWN_COMMAND = "Unknown command"; - public static final String MESSAGE_INVALID_COMMAND_FORMAT = "Invalid command format! \n%1$s"; - public static final String MESSAGE_INVALID_PERSON_DISPLAYED_INDEX = "The person index provided is invalid"; - public static final String MESSAGE_PERSONS_LISTED_OVERVIEW = "%1$d persons listed!"; + public static final String MESSAGE_UNKNOWN_COMMAND = "Error: Unknown command"; + public static final String MESSAGE_INVALID_COMMAND_FORMAT = "Error: Invalid command format! \n%1$s"; + public static final String MESSAGE_INVALID_STUDENT_DISPLAYED_INDEX = "Error: The student index" + + " provided is out of bounds!"; + public static final String MESSAGE_INDEX_OUT_OF_BOUNDS = "Error: The student index provided is out of bounds!"; + public static final String MESSAGE_INVALID_LESSON_DISPLAYED_INDEX = "Error: The lesson index provided is invalid!"; + public static final String MESSAGE_STUDENTS_LISTED_OVERVIEW = "%1$d students listed!"; + public static final String MESSAGE_LESSONS_LISTED_OVERVIEW = "%1$d lessons listed!"; public static final String MESSAGE_DUPLICATE_FIELDS = - "Multiple values specified for the following single-valued field(s): "; + "Error: Multiple values specified for the following single-valued field(s): "; + public static final String MESSAGE_ADD_ASSIGNMENT_SUCCESS = "New assignment added to %1$s: %2$s"; + public static final String MESSAGE_EMPTY_STUDENT_LIST = "Error: Student list is empty! Nothing to delete"; + public static final String MESSAGE_EMPTY_LESSON_LIST = "Error: Lesson list is empty! Nothing to delete"; + public static final String MESSAGE_ASSIGNMENT_NOT_FOUND = "Error: Assignment with name \"%1$s\" not found!"; + public static final String MESSAGE_STUDENT_VIEW_REQUIRED = "Error: You can only delete student on student list!"; + public static final String MESSAGE_LESSON_VIEW_REQUIRED = "Error: You can only delete lesson on lesson list!"; /** * Returns an error message indicating the duplicate prefixes. @@ -32,20 +44,48 @@ public static String getErrorMessageForDuplicatePrefixes(Prefix... duplicatePref } /** - * Formats the {@code person} for display to the user. + * Formats the {@code student} for display to the user. */ - public static String format(Person person) { + public static String format(Student student) { final StringBuilder builder = new StringBuilder(); - builder.append(person.getName()) + builder.append(student.getName()) .append("; Phone: ") - .append(person.getPhone()) + .append(student.getPhone()) .append("; Email: ") - .append(person.getEmail()) + .append(student.getEmail()) .append("; Address: ") - .append(person.getAddress()) - .append("; Tags: "); - person.getTags().forEach(builder::append); + .append(student.getAddress()) + .append("; Subjects: ") + .append(student.getSubjects().toString().replace("[", "").replace("]", "")); return builder.toString(); } + /** + * Formats the {@code Lesson} for display to the user. + */ + public static String format(Lesson lesson) { + final StringBuilder builder = new StringBuilder(); + builder.append(lesson.getSubject()) + .append(" lesson; Student Name: ") + .append(lesson.getStudentName()) + .append("; Date: ") + .append(lesson.getDate()) + .append("; Time: ") + .append(lesson.getTime()); + + return builder.toString(); + } + + + /** + * Formats the {@code Assignment} for display to the user. + */ + public static String format(Student student, Assignment assignment) { + final StringBuilder builder = new StringBuilder(); + builder.append("\"") + .append(assignment.getAssignmentName()) + .append("\" for ") + .append(student.getName()); + return builder.toString(); + } } diff --git a/src/main/java/seedu/address/logic/commands/AddAssignmentCommand.java b/src/main/java/seedu/address/logic/commands/AddAssignmentCommand.java new file mode 100644 index 00000000000..37e66346322 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/AddAssignmentCommand.java @@ -0,0 +1,133 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ASSIGNMENT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DATE; +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.index.Index; +import seedu.address.logic.Messages; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.assignment.Assignment; +import seedu.address.model.datetimeutil.Date; +import seedu.address.model.student.Student; + +/** + * Adds an assignment to a student. + */ +public class AddAssignmentCommand extends Command { + + public static final String COMMAND_WORD = "add_assignment"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Assigns an assignment to a student.\n" + + "Parameters: STUDENT INDEX (must be a positive integer) " + + PREFIX_ASSIGNMENT + "ASSIGNMENT_NAME " + + PREFIX_DATE + "DUE_DATE\n" + + "Example: " + COMMAND_WORD + " 1 " + + PREFIX_ASSIGNMENT + "Math Exercise 1 " + + PREFIX_DATE + "31-12-2025"; + + public static final String MESSAGE_DUPLICATE_ASSIGNMENT = "Error: This assignment already exists in the student.\n" + + "Assignments of the same students cannot have the same name.\n" + + "Please use the edit_assignment command to edit the assignment."; + public static final String MESSAGE_EMPTY_STUDENT_LIST = "Error: Student list is empty"; + + private static final Logger logger = LogsCenter.getLogger(AddAssignmentCommand.class); + + private final Index index; + private final Assignment assignment; + private final Date dueDate; + + /** + * Creates an AddAssignmentCommand to add the specified {@code Assignment} to the student at the specified index. + */ + public AddAssignmentCommand(Index index, Assignment assignment) { + requireAllNonNull(index, assignment, assignment.getDueDate()); + this.index = index; + this.assignment = assignment; + this.dueDate = assignment.getDueDate(); + logger.info("AddAssignmentCommand created for student index: " + index.getOneBased() + + " with assignment: " + assignment); + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + logger.info("Executing AddAssignmentCommand for student index: " + index.getOneBased()); + + List lastShownList = model.getFilteredStudentList(); + + if (lastShownList.isEmpty()) { + logger.warning("Attempted to add assignment to empty student list"); + throw new CommandException(MESSAGE_EMPTY_STUDENT_LIST); + } + + // Check if the index is valid + if (index.getZeroBased() >= lastShownList.size()) { + logger.warning("Invalid student index: " + index.getOneBased() + + " (list size: " + lastShownList.size() + ")"); + throw new CommandException(Messages.MESSAGE_INDEX_OUT_OF_BOUNDS); + } + + Student studentToEdit = lastShownList.get(index.getZeroBased()); + assert studentToEdit != null : "Student should not be null"; + + // Check if the assignment already exists + if (studentToEdit.getAssignments().contains(assignment)) { + logger.warning("Duplicate assignment detected: " + assignment + " for student: " + + studentToEdit.getName()); + throw new CommandException(MESSAGE_DUPLICATE_ASSIGNMENT); + } + + // Add the new assignment to the student + if (studentToEdit.hasAssignment(assignment)) { + throw new CommandException(MESSAGE_DUPLICATE_ASSIGNMENT); + } + + Student editedStudent = studentToEdit.addAssignment(assignment); + logger.info("Added assignment: " + assignment + " to student: " + studentToEdit.getName()); + + // Update the model with the edited student + model.setStudent(studentToEdit, editedStudent); + model.updateFilteredStudentList(PREDICATE_SHOW_ALL_STUDENTS); + + String successMessage = generateSuccessMessage(editedStudent); + logger.info("Success: " + successMessage); + return new CommandResult(successMessage, true); + } + + private String generateSuccessMessage(Student editedStudent) { + return String.format(Messages.MESSAGE_ADD_ASSIGNMENT_SUCCESS, editedStudent.getName(), assignment); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof AddAssignmentCommand)) { + return false; + } + + AddAssignmentCommand e = (AddAssignmentCommand) other; + return index.equals(e.index) + && assignment.equals(e.assignment) + && dueDate.equals(e.dueDate); + } + + @Override + public String toString() { + return getClass().getCanonicalName() + "{index=" + index + + ", assignment=" + assignment + + ", dueDate=" + assignment.getDueDate() + "}"; + } + +} diff --git a/src/main/java/seedu/address/logic/commands/AddCommand.java b/src/main/java/seedu/address/logic/commands/AddCommand.java deleted file mode 100644 index 5d7185a9680..00000000000 --- a/src/main/java/seedu/address/logic/commands/AddCommand.java +++ /dev/null @@ -1,84 +0,0 @@ -package seedu.address.logic.commands; - -import static java.util.Objects.requireNonNull; -import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; -import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; -import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; -import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; -import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; - -import seedu.address.commons.util.ToStringBuilder; -import seedu.address.logic.Messages; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.model.Model; -import seedu.address.model.person.Person; - -/** - * Adds a person to the address book. - */ -public class AddCommand extends Command { - - public static final String COMMAND_WORD = "add"; - - public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a person to the address book. " - + "Parameters: " - + PREFIX_NAME + "NAME " - + PREFIX_PHONE + "PHONE " - + PREFIX_EMAIL + "EMAIL " - + PREFIX_ADDRESS + "ADDRESS " - + "[" + PREFIX_TAG + "TAG]...\n" - + "Example: " + COMMAND_WORD + " " - + PREFIX_NAME + "John Doe " - + PREFIX_PHONE + "98765432 " - + PREFIX_EMAIL + "johnd@example.com " - + PREFIX_ADDRESS + "311, Clementi Ave 2, #02-25 " - + PREFIX_TAG + "friends " - + PREFIX_TAG + "owesMoney"; - - public static final String MESSAGE_SUCCESS = "New person added: %1$s"; - public static final String MESSAGE_DUPLICATE_PERSON = "This person already exists in the address book"; - - private final Person toAdd; - - /** - * Creates an AddCommand to add the specified {@code Person} - */ - public AddCommand(Person person) { - requireNonNull(person); - toAdd = person; - } - - @Override - public CommandResult execute(Model model) throws CommandException { - requireNonNull(model); - - if (model.hasPerson(toAdd)) { - throw new CommandException(MESSAGE_DUPLICATE_PERSON); - } - - model.addPerson(toAdd); - return new CommandResult(String.format(MESSAGE_SUCCESS, Messages.format(toAdd))); - } - - @Override - public boolean equals(Object other) { - if (other == this) { - return true; - } - - // instanceof handles nulls - if (!(other instanceof AddCommand)) { - return false; - } - - AddCommand otherAddCommand = (AddCommand) other; - return toAdd.equals(otherAddCommand.toAdd); - } - - @Override - public String toString() { - return new ToStringBuilder(this) - .add("toAdd", toAdd) - .toString(); - } -} diff --git a/src/main/java/seedu/address/logic/commands/AddLessonCommand.java b/src/main/java/seedu/address/logic/commands/AddLessonCommand.java new file mode 100644 index 00000000000..83884154fd7 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/AddLessonCommand.java @@ -0,0 +1,122 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DATE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_SUBJECT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TIME; + +import java.util.HashSet; +import java.util.logging.Logger; + +import seedu.address.commons.core.LogsCenter; +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.logic.Messages; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.assignment.UniqueAssignmentList; +import seedu.address.model.lesson.Lesson; +import seedu.address.model.student.Address; +import seedu.address.model.student.Email; +import seedu.address.model.student.Phone; +import seedu.address.model.student.Student; + +/** + * Adds lesson to the addressbook + */ +public class AddLessonCommand extends Command { + public static final String COMMAND_WORD = "add_lesson"; + public static final Address VALID_ADDRESS = new Address("123 Main Street"); + public static final Phone VALID_PHONE = new Phone("12345678"); + public static final Email VALID_EMAIL = new Email("john@example.com"); + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a lesson to the address book. " + + "Parameters: " + + PREFIX_NAME + "NAME " + + PREFIX_DATE + "DATE " + + PREFIX_TIME + "TIME " + + PREFIX_SUBJECT + "SUBJECT\n" + + "Example: " + COMMAND_WORD + " " + + PREFIX_NAME + "Alex Yeoh " + + PREFIX_DATE + "17-09-2027 " + + PREFIX_TIME + "14:00 " + + PREFIX_SUBJECT + "Science"; + + public static final String MESSAGE_SUCCESS = "New lesson added: %1$s."; + public static final String MESSAGE_DUPLICATE_LESSON = "Error: This lesson already exists in the address book."; + public static final String MESSAGE_STUDENT_NOT_FOUND = "Error: " + + "The specified student does not exist in the address book."; + public static final String MESSAGE_LESSON_CONFLICT = "Error: The lesson clashes with an existing lesson " + + "in the address book."; + public static final String MESSAGE_SUBJECT_MISMATCH = "Error: The specified " + + "subject does not exist for the student."; + + private static final Logger logger = LogsCenter.getLogger(AddLessonCommand.class); + + private final Lesson toAdd; + + /** + * Creates an AddLessonCommand to add the specified {@code Lesson} + */ + public AddLessonCommand(Lesson lesson) { + requireNonNull(lesson); + this.toAdd = lesson; + logger.info("AddLessonCommand created for lesson: " + lesson); + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + logger.info("Executing AddLessonCommand for lesson: " + toAdd); + + if (model.hasLesson(toAdd)) { + logger.warning("Duplicate lesson detected: " + toAdd); + throw new CommandException(MESSAGE_DUPLICATE_LESSON); + } + + if (model.hasLessonConflict(toAdd)) { + logger.warning("Lesson conflict detected: " + toAdd); + throw new CommandException(MESSAGE_LESSON_CONFLICT); + } + + Student dummyStudent = new Student(toAdd.getStudentName(), VALID_PHONE, VALID_EMAIL, VALID_ADDRESS, + new HashSet<>(), new UniqueAssignmentList()); + + if (!model.hasStudent(dummyStudent)) { + logger.warning("Student not found: " + toAdd.getStudentName()); + throw new CommandException(MESSAGE_STUDENT_NOT_FOUND); + } + + if (!model.hasStudentSubject(dummyStudent, toAdd.getSubject())) { + logger.warning("Subject mismatch for student: " + toAdd.getStudentName() + + " with subject: " + toAdd.getSubject()); + throw new CommandException(MESSAGE_SUBJECT_MISMATCH); + } + + model.addLesson(toAdd); + logger.info("Lesson successfully added: " + toAdd); + + return new CommandResult(String.format(MESSAGE_SUCCESS, Messages.format(toAdd)), true); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof AddLessonCommand otherAddLessonCommand)) { + return false; + } + + return toAdd.equals(otherAddLessonCommand.toAdd); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("toAdd", toAdd) + .toString(); + } +} diff --git a/src/main/java/seedu/address/logic/commands/AddStudentCommand.java b/src/main/java/seedu/address/logic/commands/AddStudentCommand.java new file mode 100644 index 00000000000..e0ce67d74d7 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/AddStudentCommand.java @@ -0,0 +1,94 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_SUBJECT; + +import java.util.logging.Logger; + +import seedu.address.commons.core.LogsCenter; +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.logic.Messages; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.student.Student; + +/** + * Adds a student to the address book. + */ +public class AddStudentCommand extends Command { + public static final String COMMAND_WORD = "add_student"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a student to the address book. " + + "Parameters: " + + PREFIX_NAME + "NAME " + + PREFIX_PHONE + "PHONE " + + PREFIX_EMAIL + "EMAIL " + + PREFIX_ADDRESS + "ADDRESS " + + "[" + PREFIX_SUBJECT + "SUBJECT]\n" + + "Example: " + COMMAND_WORD + " " + + "n/ John Doe " + + "p/ 98765432 " + + "e/ johndoe@gmail.com " + + "a/ 311, Clementi Ave 2, #02-25 " + + "s/ Math"; + + public static final String MESSAGE_SUCCESS = "New student added: %1$s"; + public static final String MESSAGE_DUPLICATE_STUDENT = "A student with the same name, email, or phone number " + + "already exists in the address book"; + + private static final Logger logger = LogsCenter.getLogger(AddStudentCommand.class); + + private final Student toAdd; + + /** + * Creates an AddStudentCommand to add the specified {@code Student} + */ + public AddStudentCommand(Student student) { + requireNonNull(student); + this.toAdd = student; + logger.info("AddStudentCommand created for student: " + student.getName()); + + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + logger.info("Executing AddStudentCommand for student: " + toAdd.getName()); + + + if (model.hasStudent(toAdd)) { + logger.warning("Duplicate student detected: " + toAdd.getName()); + throw new CommandException(MESSAGE_DUPLICATE_STUDENT); + } + + model.addStudent(toAdd); + logger.info("Student successfully added: " + toAdd.getName()); + return new CommandResult(String.format(MESSAGE_SUCCESS, Messages.format(toAdd)), true); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof AddStudentCommand)) { + return false; + } + + AddStudentCommand otherAddStudentCommand = (AddStudentCommand) other; + return toAdd.equals(otherAddStudentCommand.toAdd); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("toAdd", toAdd) + .toString(); + } +} diff --git a/src/main/java/seedu/address/logic/commands/ClearCommand.java b/src/main/java/seedu/address/logic/commands/ClearCommand.java index 9c86b1fa6e4..770dd6dc6c4 100644 --- a/src/main/java/seedu/address/logic/commands/ClearCommand.java +++ b/src/main/java/seedu/address/logic/commands/ClearCommand.java @@ -2,9 +2,14 @@ import static java.util.Objects.requireNonNull; +import java.util.logging.Logger; + +import seedu.address.commons.core.LogsCenter; import seedu.address.model.AddressBook; import seedu.address.model.Model; + + /** * Clears the address book. */ @@ -13,11 +18,26 @@ public class ClearCommand extends Command { public static final String COMMAND_WORD = "clear"; public static final String MESSAGE_SUCCESS = "Address book has been cleared!"; + private static final Logger logger = LogsCenter.getLogger(ClearCommand.class); + + /** + * Creates a ClearCommand. + * + */ + public ClearCommand() { + logger.info("ClearCommand created "); + } @Override public CommandResult execute(Model model) { requireNonNull(model); + logger.info("Executing ClearCommand"); + + int previousSize = model.getAddressBook().getStudentList().size(); model.setAddressBook(new AddressBook()); + logger.info("Address book cleared. Previous size: " + previousSize); + return new CommandResult(MESSAGE_SUCCESS); } + } diff --git a/src/main/java/seedu/address/logic/commands/CommandResult.java b/src/main/java/seedu/address/logic/commands/CommandResult.java index 249b6072d0d..cd3db8cb497 100644 --- a/src/main/java/seedu/address/logic/commands/CommandResult.java +++ b/src/main/java/seedu/address/logic/commands/CommandResult.java @@ -18,6 +18,8 @@ public class CommandResult { /** The application should exit. */ private final boolean exit; + /** Flag to update the list */ + private final boolean updateList; /** * Constructs a {@code CommandResult} with the specified fields. @@ -26,11 +28,23 @@ public CommandResult(String feedbackToUser, boolean showHelp, boolean exit) { this.feedbackToUser = requireNonNull(feedbackToUser); this.showHelp = showHelp; this.exit = exit; + this.updateList = false; + } + + /** + * Constructs a {@code CommandResult} with the specified fields. + * Specifically to flag whether the list needs to be updated after command execution + */ + public CommandResult(String feedbackToUser, boolean updateList) { + this.feedbackToUser = requireNonNull(feedbackToUser); + this.showHelp = false; + this.exit = false; + this.updateList = updateList; } /** * Constructs a {@code CommandResult} with the specified {@code feedbackToUser}, - * and other fields set to their default value. + * and other fields set to their default assignmentName. */ public CommandResult(String feedbackToUser) { this(feedbackToUser, false, false); @@ -48,6 +62,9 @@ public boolean isExit() { return exit; } + public boolean isUpdateList() { + return updateList; + } @Override public boolean equals(Object other) { if (other == this) { @@ -62,12 +79,13 @@ public boolean equals(Object other) { CommandResult otherCommandResult = (CommandResult) other; return feedbackToUser.equals(otherCommandResult.feedbackToUser) && showHelp == otherCommandResult.showHelp - && exit == otherCommandResult.exit; + && exit == otherCommandResult.exit + && updateList == otherCommandResult.updateList; } @Override public int hashCode() { - return Objects.hash(feedbackToUser, showHelp, exit); + return Objects.hash(feedbackToUser, showHelp, exit, updateList); } @Override @@ -76,6 +94,7 @@ public String toString() { .add("feedbackToUser", feedbackToUser) .add("showHelp", showHelp) .add("exit", exit) + .add("updateList", updateList) .toString(); } diff --git a/src/main/java/seedu/address/logic/commands/DeleteAssignmentCommand.java b/src/main/java/seedu/address/logic/commands/DeleteAssignmentCommand.java new file mode 100644 index 00000000000..f32426e3dba --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/DeleteAssignmentCommand.java @@ -0,0 +1,120 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; + +import java.util.Arrays; +import java.util.List; +import java.util.logging.Logger; +import java.util.stream.Collectors; + +import seedu.address.commons.core.LogsCenter; +import seedu.address.commons.core.index.Index; +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.logic.Messages; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.assignment.Assignment; +import seedu.address.model.datetimeutil.Date; +import seedu.address.model.student.Student; + +/** + * Deletes a assignment identified using it's displayed index from the address book. + */ +public class DeleteAssignmentCommand extends Command { + + public static final String COMMAND_WORD = "delete_assignment"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Deletes an assignment of student identified by the index number used in the displayed student list.\n" + + "Parameters: STUDENT INDEX (must be a positive integer)\n" + + "Parameters: ASSIGNMENT NAME (must be a string)\n" + + "Example: " + COMMAND_WORD + " 1 as/Math Exercise 1"; + + public static final String MESSAGE_DELETE_ASSIGNMENT_SUCCESS = "Assignment %1$s deleted successfully."; + public static final String MESSAGE_INVALID_ASSIGNMENT_DISPLAYED = "The assignment name provided cannot be found"; + public static final String MESSAGE_EMPTY_STUDENT_LIST = "Error: Student list is empty"; + + private static final Logger logger = LogsCenter.getLogger(DeleteAssignmentCommand.class); + + private final Index targetIndex; + private final String assignmentName; + + /** + * Creates a DeleteAssignmentCommand to delete the specified {@code Assignment}. + * + * @param targetIndex Index of the student in the filtered student list + * @param assignmentName Name of the assignment to delete + */ + public DeleteAssignmentCommand(Index targetIndex, String assignmentName) { + requireAllNonNull(targetIndex, assignmentName); + this.targetIndex = targetIndex; + this.assignmentName = Arrays.stream(assignmentName.split("\\s+")) + .map(word -> word.substring(0, 1).toUpperCase() + word.substring(1).toLowerCase()) + .collect(Collectors.joining(" ")).trim(); + logger.info("DeleteAssignmentCommand created for student index: " + targetIndex.getOneBased() + + " and assignment: " + assignmentName); + } + + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + logger.info("Executing DeleteAssignmentCommand for student index: " + targetIndex.getOneBased() + + " and assignment: " + assignmentName); + + List lastShownList = model.getFilteredStudentList(); + + if (lastShownList.isEmpty()) { + logger.warning("Attempted to delete assignment from empty student list"); + throw new CommandException(MESSAGE_EMPTY_STUDENT_LIST); + } + + if (targetIndex.getZeroBased() >= lastShownList.size()) { + logger.warning("Invalid student index: " + targetIndex.getOneBased() + + " (list size: " + lastShownList.size() + ")"); + throw new CommandException(Messages.MESSAGE_INVALID_STUDENT_DISPLAYED_INDEX); + } + + Student studentToDeleteAssignment = lastShownList.get(targetIndex.getZeroBased()); + assert studentToDeleteAssignment != null : "Student should not be null"; + + Assignment assignmentToDelete = new Assignment(assignmentName, new Date("31-12-9999")); + if (!studentToDeleteAssignment.getAssignments().contains(assignmentToDelete)) { + logger.warning("Assignment not found: " + assignmentName + " for student: " + + studentToDeleteAssignment.getName()); + throw new CommandException(MESSAGE_INVALID_ASSIGNMENT_DISPLAYED); + } + + model.deleteAssignment(studentToDeleteAssignment, assignmentName); + logger.info("Successfully deleted assignment: " + assignmentName + " for student: " + + studentToDeleteAssignment.getName()); + + return new CommandResult(String.format(MESSAGE_DELETE_ASSIGNMENT_SUCCESS, + Messages.format(studentToDeleteAssignment, assignmentToDelete)), true); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof DeleteAssignmentCommand)) { + return false; + } + + DeleteAssignmentCommand otherDeleteAssignmentCommand = (DeleteAssignmentCommand) other; + return targetIndex.equals(otherDeleteAssignmentCommand.targetIndex) + && assignmentName.equals(otherDeleteAssignmentCommand.assignmentName); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("targetIndex", targetIndex) + .add("assignmentName", assignmentName) + .toString(); + } +} diff --git a/src/main/java/seedu/address/logic/commands/DeleteCommand.java b/src/main/java/seedu/address/logic/commands/DeleteCommand.java deleted file mode 100644 index 1135ac19b74..00000000000 --- a/src/main/java/seedu/address/logic/commands/DeleteCommand.java +++ /dev/null @@ -1,69 +0,0 @@ -package seedu.address.logic.commands; - -import static java.util.Objects.requireNonNull; - -import java.util.List; - -import seedu.address.commons.core.index.Index; -import seedu.address.commons.util.ToStringBuilder; -import seedu.address.logic.Messages; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.model.Model; -import seedu.address.model.person.Person; - -/** - * Deletes a person identified using it's displayed index from the address book. - */ -public class DeleteCommand extends Command { - - public static final String COMMAND_WORD = "delete"; - - public static final String MESSAGE_USAGE = COMMAND_WORD - + ": Deletes the person identified by the index number used in the displayed person list.\n" - + "Parameters: INDEX (must be a positive integer)\n" - + "Example: " + COMMAND_WORD + " 1"; - - public static final String MESSAGE_DELETE_PERSON_SUCCESS = "Deleted Person: %1$s"; - - private final Index targetIndex; - - public DeleteCommand(Index targetIndex) { - this.targetIndex = targetIndex; - } - - @Override - public CommandResult execute(Model model) throws CommandException { - requireNonNull(model); - List lastShownList = model.getFilteredPersonList(); - - if (targetIndex.getZeroBased() >= lastShownList.size()) { - throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); - } - - Person personToDelete = lastShownList.get(targetIndex.getZeroBased()); - model.deletePerson(personToDelete); - return new CommandResult(String.format(MESSAGE_DELETE_PERSON_SUCCESS, Messages.format(personToDelete))); - } - - @Override - public boolean equals(Object other) { - if (other == this) { - return true; - } - - // instanceof handles nulls - if (!(other instanceof DeleteCommand)) { - return false; - } - - DeleteCommand otherDeleteCommand = (DeleteCommand) other; - return targetIndex.equals(otherDeleteCommand.targetIndex); - } - - @Override - public String toString() { - return new ToStringBuilder(this) - .add("targetIndex", targetIndex) - .toString(); - } -} diff --git a/src/main/java/seedu/address/logic/commands/DeleteLessonCommand.java b/src/main/java/seedu/address/logic/commands/DeleteLessonCommand.java new file mode 100644 index 00000000000..0e0f6cc5d6d --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/DeleteLessonCommand.java @@ -0,0 +1,96 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import java.util.List; +import java.util.logging.Logger; + +import seedu.address.commons.core.LogsCenter; +import seedu.address.commons.core.index.Index; +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.logic.Messages; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.lesson.Lesson; + +/** + * Deletes a lesson identified using it's displayed index from the address book. + */ +public class DeleteLessonCommand extends Command { + + public static final String COMMAND_WORD = "delete_lesson"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Deletes the lesson identified by the index number used in the displayed lesson list.\n" + + "Parameters: LESSON INDEX (must be a positive integer)\n" + + "Example: " + COMMAND_WORD + " 1"; + + public static final String MESSAGE_DELETE_LESSON_SUCCESS = "Deleted Lesson: %1$s"; + + private static final Logger logger = LogsCenter.getLogger(DeleteLessonCommand.class); + + private final Index targetIndex; + + /** + * Creates a DeleteLessonCommand to delete the specified {@code Lesson}. + * + * @param targetIndex of the lesson in the filtered lesson list to delete + */ + public DeleteLessonCommand(Index targetIndex) { + requireNonNull(targetIndex); + this.targetIndex = targetIndex; + logger.info("DeleteLessonCommand created for index: " + targetIndex.getOneBased()); + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + logger.info("Executing DeleteLessonCommand for index: " + targetIndex.getOneBased()); + + List lastShownList = model.getFilteredLessonList(); + if (model.isStudentView()) { + throw new CommandException(Messages.MESSAGE_LESSON_VIEW_REQUIRED); + } + + if (lastShownList.isEmpty()) { + logger.warning("Attempted to delete from empty lesson list"); + throw new CommandException(Messages.MESSAGE_EMPTY_LESSON_LIST); + } + + if (targetIndex.getZeroBased() >= lastShownList.size()) { + logger.warning("Invalid lesson index: " + targetIndex.getOneBased() + + " (list size: " + lastShownList.size() + ")"); + throw new CommandException(Messages.MESSAGE_INVALID_LESSON_DISPLAYED_INDEX); + } + + Lesson lessonToDelete = lastShownList.get(targetIndex.getZeroBased()); + assert lessonToDelete != null : "Lesson to delete should not be null"; + + model.deleteLesson(lessonToDelete); + logger.info("Successfully deleted lesson: " + lessonToDelete); + + return new CommandResult(String.format(MESSAGE_DELETE_LESSON_SUCCESS, Messages.format(lessonToDelete)), true); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof DeleteLessonCommand)) { + return false; + } + + DeleteLessonCommand otherDeleteLessonCommand = (DeleteLessonCommand) other; + return targetIndex.equals(otherDeleteLessonCommand.targetIndex); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("targetIndex", targetIndex) + .toString(); + } +} diff --git a/src/main/java/seedu/address/logic/commands/DeleteStudentCommand.java b/src/main/java/seedu/address/logic/commands/DeleteStudentCommand.java new file mode 100644 index 00000000000..319cd2c2f32 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/DeleteStudentCommand.java @@ -0,0 +1,110 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import java.util.List; +import java.util.logging.Logger; + +import seedu.address.commons.core.LogsCenter; +import seedu.address.commons.core.index.Index; +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.logic.Messages; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.lesson.Lesson; +import seedu.address.model.student.Student; + +/** + * Deletes a student identified using it's displayed index from the address book. + */ +public class DeleteStudentCommand extends Command { + + public static final String COMMAND_WORD = "delete_student"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Deletes the student identified by the index number used in the displayed student list.\n" + + "Parameters: STUDENT INDEX (must be a positive integer)\n" + + "Example: " + COMMAND_WORD + " 1"; + + public static final String MESSAGE_DELETE_STUDENT_SUCCESS = + "Student %1$s\ndeleted successfully, along with all associated lessons and assignments."; + + private static final Logger logger = LogsCenter.getLogger(DeleteStudentCommand.class); + + private final Index targetIndex; + + /** + * Creates a DeleteStudentCommand to delete the specified {@code Student}. + * + * @param targetIndex of the student in the filtered student list to delete + */ + public DeleteStudentCommand(Index targetIndex) { + requireNonNull(targetIndex); + this.targetIndex = targetIndex; + logger.info("DeleteStudentCommand created for index: " + targetIndex.getOneBased()); + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + logger.info("Executing DeleteStudentCommand for index: " + targetIndex.getOneBased()); + + List lastShownList = model.getFilteredStudentList(); + if (!model.isStudentView()) { + throw new CommandException(Messages.MESSAGE_STUDENT_VIEW_REQUIRED); + } + + if (lastShownList.isEmpty()) { + logger.warning("Attempted to delete from empty student list"); + throw new CommandException(Messages.MESSAGE_EMPTY_STUDENT_LIST); + } + + if (targetIndex.getZeroBased() >= lastShownList.size()) { + logger.warning("Invalid student index: " + targetIndex.getOneBased()); + throw new CommandException(Messages.MESSAGE_INVALID_STUDENT_DISPLAYED_INDEX); + } + + Student studentToDelete = lastShownList.get(targetIndex.getZeroBased()); + assert studentToDelete != null : "Student to delete should not be null"; + + // Delete all lessons associated with the student + List lessonsToDelete = model.getFilteredLessonList() + .stream() + .filter(lesson -> lesson.getStudentName().equals(studentToDelete.getName())) + .toList(); + + logger.info("Deleting " + lessonsToDelete.size() + " associated lessons"); + lessonsToDelete.forEach(lesson -> { + model.deleteLesson(lesson); + logger.fine("Deleted lesson: " + lesson); + }); + + // Finally, delete the student + model.deleteStudent(studentToDelete); + logger.info("Successfully deleted student: " + studentToDelete.getName()); + + return new CommandResult(String.format(MESSAGE_DELETE_STUDENT_SUCCESS, Messages.format(studentToDelete)), true); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof DeleteStudentCommand)) { + return false; + } + + DeleteStudentCommand otherDeleteStudentCommand = (DeleteStudentCommand) other; + return targetIndex.equals(otherDeleteStudentCommand.targetIndex); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("targetIndex", targetIndex) + .toString(); + } +} diff --git a/src/main/java/seedu/address/logic/commands/EditAssignmentCommand.java b/src/main/java/seedu/address/logic/commands/EditAssignmentCommand.java new file mode 100644 index 00000000000..0f96e3b7a45 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/EditAssignmentCommand.java @@ -0,0 +1,239 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ASSIGNMENT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DATE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NEW_ASSIGNMENT; + +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +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.index.Index; +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.logic.Messages; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.assignment.Assignment; +import seedu.address.model.assignment.UniqueAssignmentList; +import seedu.address.model.datetimeutil.Date; +import seedu.address.model.student.Student; + +/** + * Edits the details of an existing assignment in the address book. + */ +public class EditAssignmentCommand extends Command { + + public static final String COMMAND_WORD = "edit_assignment"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits the details of the assignment identified " + + "by the index number of the student and the assignment name. " + + "Existing values will be overwritten by the input values.\n" + + "Parameters: STUDENT INDEX (must be a positive integer) " + + PREFIX_ASSIGNMENT + "ASSIGNMENT_NAME " + + "[" + PREFIX_NEW_ASSIGNMENT + "NEW_ASSIGNMENT_NAME] " + + "[" + PREFIX_DATE + "DATE] \n" + + "Example: " + COMMAND_WORD + " 1 " + + PREFIX_ASSIGNMENT + "Assignment 1 " + + PREFIX_NEW_ASSIGNMENT + "Assignment 1a " + + PREFIX_DATE + "30-09-2025"; + + public static final String MESSAGE_EDIT_ASSIGNMENT_SUCCESS = "Edited Assignment: \"%1$s\""; + public static final String MESSAGE_NOT_EDITED = "Error: At least one field to edit must be provided."; + public static final String MESSAGE_DUPLICATE_ASSIGNMENT = "Error: This assignment " + + "already exists in the address book."; + private static final Logger logger = LogsCenter.getLogger(EditAssignmentCommand.class); + + private final Index index; + private final String assignmentName; + private final EditAssignmentDescriptor editAssignmentDescriptor; + + /** + * @param index of the student in the filtered student list to edit + * @param editAssignmentDescriptor details to edit the assignment with + */ + public EditAssignmentCommand(Index index, String assignmentName, + EditAssignmentDescriptor editAssignmentDescriptor) { + requireNonNull(index); + requireNonNull(assignmentName); + requireNonNull(editAssignmentDescriptor); + + this.index = index; + this.assignmentName = Arrays.stream(assignmentName.split("\\s+")) + .map(word -> word.substring(0, 1).toUpperCase() + word.substring(1).toLowerCase()) + .collect(Collectors.joining(" ")).trim(); + this.editAssignmentDescriptor = new EditAssignmentDescriptor(editAssignmentDescriptor); + + logger.info("EditAssignmentCommand created for student index: " + index.getOneBased() + + " with assignment: " + assignmentName); + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + logger.info("Executing EditAssignmentCommand for student index: " + index.getOneBased() + + " and assignment: " + assignmentName); + List lastShownList = model.getFilteredStudentList(); + + if (index.getZeroBased() >= lastShownList.size()) { + logger.warning("Invalid student index: " + index.getOneBased()); + throw new CommandException(Messages.MESSAGE_INVALID_STUDENT_DISPLAYED_INDEX); + } + + Student studentToEdit = lastShownList.get(index.getZeroBased()); + UniqueAssignmentList assignmentsToEdit = studentToEdit.getAssignments(); + + assert assignmentsToEdit != null : "Assignments list should not be null"; + + Assignment assignmentToEdit = assignmentsToEdit.getAssignment(assignmentName); + + if (assignmentToEdit == null) { + logger.warning("Assignment not found: " + assignmentName); + throw new CommandException(String.format(Messages.MESSAGE_ASSIGNMENT_NOT_FOUND, assignmentName)); + } + + if (!editAssignmentDescriptor.isAnyFieldEdited()) { + throw new CommandException(MESSAGE_NOT_EDITED); + } + + Assignment editedAssignment = createEditedAssignment(assignmentToEdit, editAssignmentDescriptor); + assert editedAssignment != null : "Edited assignment should not be null"; + + if (!assignmentToEdit.isSameAssignment(editedAssignment)) { + if (assignmentsToEdit.contains(editedAssignment)) { + logger.warning("Duplicate assignment detected: " + editedAssignment.getAssignmentName()); + throw new CommandException(MESSAGE_DUPLICATE_ASSIGNMENT); + } + } + + UniqueAssignmentList updatedAssignments = assignmentsToEdit; + updatedAssignments.setAssignment(assignmentToEdit, editedAssignment); + + Student editedStudent = new Student(studentToEdit.getName(), studentToEdit.getPhone(), studentToEdit.getEmail(), + studentToEdit.getAddress(), studentToEdit.getSubjects(), updatedAssignments); + + model.setStudent(studentToEdit, editedStudent); + model.updateFilteredStudentList(Model.PREDICATE_SHOW_ALL_STUDENTS); + + logger.info("Assignment successfully edited: " + editedAssignment); + return new CommandResult(String.format(MESSAGE_EDIT_ASSIGNMENT_SUCCESS, + editedAssignment), true); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof EditAssignmentCommand)) { + return false; + } + + // state check + EditAssignmentCommand e = (EditAssignmentCommand) other; + return index.equals(e.index) + && assignmentName.equals(e.assignmentName) + && editAssignmentDescriptor.equals(e.editAssignmentDescriptor); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("index", index) + .add("assignmentName", assignmentName) + .add("editAssignmentDescriptor", editAssignmentDescriptor) + .toString(); + } + + /** + * Creates and returns a {@code Assignment} with the details of {@code assignmentToEdit} + * edited with {@code editAssignmentDescriptor}. + */ + public Assignment createEditedAssignment(Assignment assignmentToEdit, + EditAssignmentDescriptor editAssignmentDescriptor) { + assert assignmentToEdit != null : "Assignment to edit cannot be null"; + assert editAssignmentDescriptor != null : "Edit descriptor cannot be null"; + assert editAssignmentDescriptor.isAnyFieldEdited() : "At least one field should be edited"; + + + String updatedAssignmentName = editAssignmentDescriptor.getNewAssignmentName() + .orElse(assignmentToEdit.getAssignmentName()); + Date updatedDate = editAssignmentDescriptor.getDate().orElse(assignmentToEdit.getDueDate()); + + return new Assignment(updatedAssignmentName, updatedDate, assignmentToEdit.isDone()); + } + + /** + * Stores the details to edit the assignment with. Each non-empty field value will replace the + * corresponding field value of the assignment. + */ + public static class EditAssignmentDescriptor { + private String newAssignmentName; + private Date date; + + public EditAssignmentDescriptor() {} + + /** + * Copy constructor. + * A defensive copy of {@code tags} is used internally. + */ + public EditAssignmentDescriptor(EditAssignmentDescriptor toCopy) { + setNewAssignmentName(toCopy.newAssignmentName); + setDate(toCopy.date); + } + + /** + * Returns true if at least one field is edited. + */ + public boolean isAnyFieldEdited() { + return newAssignmentName != null || date != null; + } + + public void setNewAssignmentName(String assignmentName) { + this.newAssignmentName = assignmentName; + } + + public void setDate(Date date) { + this.date = date; + } + + public Optional getNewAssignmentName() { + return Optional.ofNullable(newAssignmentName); + } + + public Optional getDate() { + return Optional.ofNullable(date); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof EditAssignmentDescriptor)) { + return false; + } + + EditAssignmentDescriptor otherEditAssignmentDescriptor = (EditAssignmentDescriptor) other; + return Objects.equals(newAssignmentName, otherEditAssignmentDescriptor.newAssignmentName) + && Objects.equals(date, otherEditAssignmentDescriptor.date); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("assignmentName", newAssignmentName) + .add("date", date) + .toString(); + } + + } +} diff --git a/src/main/java/seedu/address/logic/commands/EditCommand.java b/src/main/java/seedu/address/logic/commands/EditCommand.java deleted file mode 100644 index 4b581c7331e..00000000000 --- a/src/main/java/seedu/address/logic/commands/EditCommand.java +++ /dev/null @@ -1,242 +0,0 @@ -package seedu.address.logic.commands; - -import static java.util.Objects.requireNonNull; -import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; -import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; -import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; -import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; -import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; -import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; - -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; - -import seedu.address.commons.core.index.Index; -import seedu.address.commons.util.CollectionUtil; -import seedu.address.commons.util.ToStringBuilder; -import seedu.address.logic.Messages; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.model.Model; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Person; -import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; - -/** - * Edits the details of an existing person in the address book. - */ -public class EditCommand extends Command { - - public static final String COMMAND_WORD = "edit"; - - public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits the details of the person identified " - + "by the index number used in the displayed person list. " - + "Existing values will be overwritten by the input values.\n" - + "Parameters: INDEX (must be a positive integer) " - + "[" + PREFIX_NAME + "NAME] " - + "[" + PREFIX_PHONE + "PHONE] " - + "[" + PREFIX_EMAIL + "EMAIL] " - + "[" + PREFIX_ADDRESS + "ADDRESS] " - + "[" + PREFIX_TAG + "TAG]...\n" - + "Example: " + COMMAND_WORD + " 1 " - + PREFIX_PHONE + "91234567 " - + PREFIX_EMAIL + "johndoe@example.com"; - - public static final String MESSAGE_EDIT_PERSON_SUCCESS = "Edited Person: %1$s"; - public static final String MESSAGE_NOT_EDITED = "At least one field to edit must be provided."; - public static final String MESSAGE_DUPLICATE_PERSON = "This person already exists in the address book."; - - private final Index index; - private final EditPersonDescriptor editPersonDescriptor; - - /** - * @param index of the person in the filtered person list to edit - * @param editPersonDescriptor details to edit the person with - */ - public EditCommand(Index index, EditPersonDescriptor editPersonDescriptor) { - requireNonNull(index); - requireNonNull(editPersonDescriptor); - - this.index = index; - this.editPersonDescriptor = new EditPersonDescriptor(editPersonDescriptor); - } - - @Override - public CommandResult execute(Model model) throws CommandException { - requireNonNull(model); - List lastShownList = model.getFilteredPersonList(); - - if (index.getZeroBased() >= lastShownList.size()) { - throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); - } - - Person personToEdit = lastShownList.get(index.getZeroBased()); - Person editedPerson = createEditedPerson(personToEdit, editPersonDescriptor); - - if (!personToEdit.isSamePerson(editedPerson) && model.hasPerson(editedPerson)) { - throw new CommandException(MESSAGE_DUPLICATE_PERSON); - } - - model.setPerson(personToEdit, editedPerson); - model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); - return new CommandResult(String.format(MESSAGE_EDIT_PERSON_SUCCESS, Messages.format(editedPerson))); - } - - /** - * Creates and returns a {@code Person} with the details of {@code personToEdit} - * edited with {@code editPersonDescriptor}. - */ - private static Person createEditedPerson(Person personToEdit, EditPersonDescriptor editPersonDescriptor) { - assert personToEdit != null; - - Name updatedName = editPersonDescriptor.getName().orElse(personToEdit.getName()); - Phone updatedPhone = editPersonDescriptor.getPhone().orElse(personToEdit.getPhone()); - Email updatedEmail = editPersonDescriptor.getEmail().orElse(personToEdit.getEmail()); - Address updatedAddress = editPersonDescriptor.getAddress().orElse(personToEdit.getAddress()); - Set updatedTags = editPersonDescriptor.getTags().orElse(personToEdit.getTags()); - - return new Person(updatedName, updatedPhone, updatedEmail, updatedAddress, updatedTags); - } - - @Override - public boolean equals(Object other) { - if (other == this) { - return true; - } - - // instanceof handles nulls - if (!(other instanceof EditCommand)) { - return false; - } - - EditCommand otherEditCommand = (EditCommand) other; - return index.equals(otherEditCommand.index) - && editPersonDescriptor.equals(otherEditCommand.editPersonDescriptor); - } - - @Override - public String toString() { - return new ToStringBuilder(this) - .add("index", index) - .add("editPersonDescriptor", editPersonDescriptor) - .toString(); - } - - /** - * Stores the details to edit the person with. Each non-empty field value will replace the - * corresponding field value of the person. - */ - public static class EditPersonDescriptor { - private Name name; - private Phone phone; - private Email email; - private Address address; - private Set tags; - - public EditPersonDescriptor() {} - - /** - * Copy constructor. - * A defensive copy of {@code tags} is used internally. - */ - public EditPersonDescriptor(EditPersonDescriptor toCopy) { - setName(toCopy.name); - setPhone(toCopy.phone); - setEmail(toCopy.email); - setAddress(toCopy.address); - setTags(toCopy.tags); - } - - /** - * Returns true if at least one field is edited. - */ - public boolean isAnyFieldEdited() { - return CollectionUtil.isAnyNonNull(name, phone, email, address, tags); - } - - public void setName(Name name) { - this.name = name; - } - - public Optional getName() { - return Optional.ofNullable(name); - } - - public void setPhone(Phone phone) { - this.phone = phone; - } - - public Optional getPhone() { - return Optional.ofNullable(phone); - } - - public void setEmail(Email email) { - this.email = email; - } - - public Optional getEmail() { - return Optional.ofNullable(email); - } - - public void setAddress(Address address) { - this.address = address; - } - - public Optional
getAddress() { - return Optional.ofNullable(address); - } - - /** - * Sets {@code tags} to this object's {@code tags}. - * A defensive copy of {@code tags} is used internally. - */ - public void setTags(Set tags) { - this.tags = (tags != null) ? new HashSet<>(tags) : null; - } - - /** - * Returns an unmodifiable tag set, which throws {@code UnsupportedOperationException} - * if modification is attempted. - * Returns {@code Optional#empty()} if {@code tags} is null. - */ - public Optional> getTags() { - return (tags != null) ? Optional.of(Collections.unmodifiableSet(tags)) : Optional.empty(); - } - - @Override - public boolean equals(Object other) { - if (other == this) { - return true; - } - - // instanceof handles nulls - if (!(other instanceof EditPersonDescriptor)) { - return false; - } - - EditPersonDescriptor otherEditPersonDescriptor = (EditPersonDescriptor) other; - return Objects.equals(name, otherEditPersonDescriptor.name) - && Objects.equals(phone, otherEditPersonDescriptor.phone) - && Objects.equals(email, otherEditPersonDescriptor.email) - && Objects.equals(address, otherEditPersonDescriptor.address) - && Objects.equals(tags, otherEditPersonDescriptor.tags); - } - - @Override - public String toString() { - return new ToStringBuilder(this) - .add("name", name) - .add("phone", phone) - .add("email", email) - .add("address", address) - .add("tags", tags) - .toString(); - } - } -} diff --git a/src/main/java/seedu/address/logic/commands/EditLessonCommand.java b/src/main/java/seedu/address/logic/commands/EditLessonCommand.java new file mode 100644 index 00000000000..aaa72cbccca --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/EditLessonCommand.java @@ -0,0 +1,261 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.commands.AddLessonCommand.MESSAGE_LESSON_CONFLICT; +import static seedu.address.logic.commands.AddLessonCommand.MESSAGE_STUDENT_NOT_FOUND; +import static seedu.address.logic.commands.AddLessonCommand.MESSAGE_SUBJECT_MISMATCH; +import static seedu.address.logic.commands.AddLessonCommand.VALID_ADDRESS; +import static seedu.address.logic.commands.AddLessonCommand.VALID_EMAIL; +import static seedu.address.logic.commands.AddLessonCommand.VALID_PHONE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DATE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_SUBJECT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TIME; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_LESSONS; + +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.logging.Logger; + +import seedu.address.commons.core.LogsCenter; +import seedu.address.commons.core.index.Index; +import seedu.address.commons.util.CollectionUtil; +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.logic.Messages; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.assignment.UniqueAssignmentList; +import seedu.address.model.datetimeutil.Date; +import seedu.address.model.datetimeutil.Time; +import seedu.address.model.lesson.Lesson; +import seedu.address.model.student.Name; +import seedu.address.model.student.Student; +import seedu.address.model.subject.Subject; + +/** + * Edits the details of an existing lesson in the address book. + */ +public class EditLessonCommand extends Command { + + public static final String COMMAND_WORD = "edit_lesson"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits the details of the lesson identified " + + "by the index number used in the displayed lesson list. " + + "Existing values will be overwritten by the input values.\n" + + "Parameters: LESSON INDEX (must be a positive integer) " + + "[" + PREFIX_SUBJECT + "SUBJECT] " + + "[" + PREFIX_NAME + "NAME] " + + "[" + PREFIX_DATE + "DATE] " + + "[" + PREFIX_TIME + "TIME]\n" + + "Example: " + COMMAND_WORD + " 1 " + + PREFIX_DATE + "20-7-2026 " + + PREFIX_TIME + "19:00"; + + public static final String MESSAGE_EDIT_LESSON_SUCCESS = "Edited Lesson: %1$s"; + public static final String MESSAGE_NOT_EDITED = "Error: At least one field to edit must be provided."; + public static final String MESSAGE_DUPLICATE_LESSON = "Error: This lesson already exists in the address book."; + private static final Logger logger = LogsCenter.getLogger(EditLessonCommand.class); + + private final Index index; + private final EditLessonDescriptor editLessonDescriptor; + + /** + * @param index of the student in the filtered lesson list to edit + * @param editLessonDescriptor details to edit the lesson with + */ + public EditLessonCommand(Index index, EditLessonDescriptor editLessonDescriptor) { + requireNonNull(index); + requireNonNull(editLessonDescriptor); + + this.index = index; + this.editLessonDescriptor = new EditLessonDescriptor(editLessonDescriptor); + logger.info("EditLessonCommand created for lesson index: " + index.getOneBased()); + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + logger.info("Executing EditLessonCommand for lesson index: " + index.getOneBased()); + List lastShownList = model.getFilteredLessonList(); + + if (index.getZeroBased() >= lastShownList.size()) { + logger.warning("Invalid lesson index: " + index.getOneBased()); + throw new CommandException(Messages.MESSAGE_INVALID_LESSON_DISPLAYED_INDEX); + } + + + Lesson lessonToEdit = lastShownList.get(index.getZeroBased()); + Lesson editedLesson = createEditedLesson(lessonToEdit, editLessonDescriptor); + assert editedLesson != null : "Edited lesson should not be null"; + + //check duplicate + if (!lessonToEdit.equals(editedLesson) && model.hasLesson(editedLesson)) { + logger.warning("Duplicate lesson detected: " + editedLesson); + throw new CommandException(MESSAGE_DUPLICATE_LESSON); + } + + //check whether after editing, lesson clashes with any lesson in the addressbook + if (model.hasLessonConflict(editedLesson)) { + if (!lessonToEdit.isConflict(editedLesson)) { + logger.warning("Lesson conflict detected for: " + editedLesson); + throw new CommandException(MESSAGE_LESSON_CONFLICT); + } + } + //check whether student exists in the addressbook + Student dummyStudent = new Student(editedLesson.getStudentName(), VALID_PHONE, VALID_EMAIL, VALID_ADDRESS, + new HashSet<>(), new UniqueAssignmentList()); + if (!model.hasStudent(dummyStudent)) { + logger.warning("Student not found: " + editedLesson.getStudentName()); + throw new CommandException(MESSAGE_STUDENT_NOT_FOUND); + } + //check whether student has the subject + if (!model.hasStudentSubject(dummyStudent, editedLesson.getSubject())) { + throw new CommandException(MESSAGE_SUBJECT_MISMATCH); + } + model.setLesson(lessonToEdit, editedLesson); + model.updateFilteredLessonList(PREDICATE_SHOW_ALL_LESSONS); + + logger.info("Lesson successfully edited: " + editedLesson); + return new CommandResult(String.format(MESSAGE_EDIT_LESSON_SUCCESS, Messages.format(editedLesson)), true); + } + + /** + * Creates and returns a {@code Student} with the details of {@code studentToEdit} + * edited with {@code editStudentDescriptor}. + */ + public static Lesson createEditedLesson(Lesson lessonToEdit, EditLessonDescriptor editLessonDescriptor) { + assert lessonToEdit != null : "Lesson to edit cannot be null"; + assert editLessonDescriptor != null : "Edit descriptor cannot be null"; + assert editLessonDescriptor.isAnyFieldEdited() : "At least one field should be edited"; + + Name updatedName = editLessonDescriptor.getName().orElse(lessonToEdit.getStudentName()); + Date updatedDate = editLessonDescriptor.getDate().orElse(lessonToEdit.getDate()); + Time updatedTime = editLessonDescriptor.getTime().orElse(lessonToEdit.getTime()); + Subject updatedSubject = editLessonDescriptor.getSubject().orElse(lessonToEdit.getSubject()); + + return new Lesson(updatedSubject, updatedName, updatedDate, updatedTime); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof EditLessonCommand)) { + return false; + } + + EditLessonCommand otherEditCommand = (EditLessonCommand) other; + return index.equals(otherEditCommand.index) + && editLessonDescriptor.equals(otherEditCommand.editLessonDescriptor); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("index", index) + .add("editLessonDescriptor", editLessonDescriptor) + .toString(); + } + + /** + * Stores the details to edit the lesson with. + */ + public static class EditLessonDescriptor { + private Name name; + private Date date; + private Time time; + private Subject subject; + + public EditLessonDescriptor() {} + + /** + * Copy constructor. + * A defensive copy of {@code subjects} is used internally. + */ + public EditLessonDescriptor(EditLessonDescriptor toCopy) { + setName(toCopy.name); + setDate(toCopy.date); + setTime(toCopy.time); + setSubject(toCopy.subject); + } + + + + /** + * Returns true if at least one field is edited. + */ + public boolean isAnyFieldEdited() { + return CollectionUtil.isAnyNonNull(name, date, time, subject); + } + + public void setName(Name name) { + this.name = name; + } + + public Optional getName() { + return Optional.ofNullable(name); + } + public void setDate(Date date) { + this.date = date; + } + + public Optional getDate() { + return Optional.ofNullable(date); + } + + public void setTime(Time time) { + this.time = time; + } + + public Optional