diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 81267b247eb..8fb0c6ce802 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -30,10 +30,12 @@ jobs: uses: gradle/wrapper-validation-action@v1 - name: Setup JDK 11 - uses: actions/setup-java@v1 + uses: actions/setup-java@v3 with: + distribution: 'zulu' java-version: '11' java-package: jdk+fx + cache: 'gradle' - name: Build and check with Gradle run: ./gradlew check coverage diff --git a/.gitignore b/.gitignore index 71c9194e8bd..6c09a1dccb4 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,29 @@ src/test/data/sandbox/ # MacOS custom attributes files created by Finder .DS_Store docs/_site/ + +# Compiled class file +/bin/ +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* +replay_pid* diff --git a/README.md b/README.md index 13f5c77403f..6910f92b6f1 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,41 @@ -[![CI Status](https://github.com/se-edu/addressbook-level3/workflows/Java%20CI/badge.svg)](https://github.com/se-edu/addressbook-level3/actions) +# Duke Driver + +[![CI Status](https://github.com/AY2223S2-CS2103-F11-2/tp/workflows/Java%20CI/badge.svg)](https://github.com/AY2223S2-CS2103-F11-2/tp/actions) ![Ui](docs/images/Ui.png) -* This is **a sample project for Software Engineering (SE) students**.
- Example usages: - * as a starting point of a course project (as opposed to writing everything from scratch) - * as a case study -* The project simulates an ongoing software project for a desktop application (called _AddressBook_) used for managing contact details. - * It is **written in OOP fashion**. It provides a **reasonably well-written** code base **bigger** (around 6 KLoC) than what students usually write in beginner-level SE modules, without being overwhelmingly big. - * It comes with a **reasonable level of user and developer documentation**. -* It is named `AddressBook Level 3` (`AB3` for short) because it was initially created as a part of a series of `AddressBook` projects (`Level 1`, `Level 2`, `Level 3` ...). -* For the detailed documentation of this project, see the **[Address Book Product Website](https://se-education.org/addressbook-level3)**. -* This project is a **part of the se-education.org** initiative. If you would like to contribute code to this project, see [se-education.org](https://se-education.org#https://se-education.org/#contributing) for more info. +Duke Driver is a desktop app assisting delivery men in managing delivery jobs and contacts. If you are looking to perform better at your delivery job, Duke Driver can assist you to finish your daily tasks more efficiently, according to your requirements. + +Features +* Customers' contacts management system: + - View list of contacts of customers + - Add contact + - Delete contact + - Edit contact + - Find contact + - Clear all contacts +* Delivery tasking management system: + - View jobs + - Add jobs + - Delete jobs + - Edit jobs + - Find jobs + - Mark jobs as completed/uncompleted + - Mass import jobs from file +* Reminder and notifications: + - Add reminder + - View list of reminders + - Delete reminder + - Set reminder for upcoming deadlines/jobs + - Get notified of upcoming reminders and jobs as soon as you open the app +* Timetable: + - Display timetable for the week specified by users + - Display lists of unscheduled and completed jobs +* Stats dashboard: + - Show total number of all jobs, completed and pending jobs + - Show total earning + - Show total number of all jobs, completed and pending jobs for last week + - Show total earning for last week + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------ +* This project is based on the AddressBook-Level3 project created by the [SE-EDU initiative](https://se-education.org). diff --git a/build.gradle b/build.gradle index 108397716bd..04c226a1cb9 100644 --- a/build.gradle +++ b/build.gradle @@ -41,7 +41,7 @@ task coverage(type: JacocoReport) { } dependencies { - String jUnitVersion = '5.4.0' + String jUnitVersion = '5.9.2' String javaFxVersion = '11' implementation group: 'org.openjfx', name: 'javafx-base', version: javaFxVersion, classifier: 'win' @@ -60,13 +60,19 @@ dependencies { implementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.7.0' implementation group: 'com.fasterxml.jackson.datatype', name: 'jackson-datatype-jsr310', version: '2.7.4' + implementation 'org.controlsfx:controlsfx:11.1.2' + testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: jUnitVersion testRuntimeOnly group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: jUnitVersion } shadowJar { - archiveFileName = 'addressbook.jar' + archiveFileName = 'dukedriver.jar' +} + +run { + enableAssertions = true } defaultTasks 'clean', 'test' diff --git a/docs/AboutUs.md b/docs/AboutUs.md index 1c9514e966a..098cfb4637d 100644 --- a/docs/AboutUs.md +++ b/docs/AboutUs.md @@ -9,51 +9,51 @@ You can reach us at the email `seer[at]comp.nus.edu.sg` ## Project team -### John Doe +### Chin Jun An - + -[[homepage](http://www.comp.nus.edu.sg/~damithch)] -[[github](https://github.com/johndoe)] -[[portfolio](team/johndoe.md)] +[[github](https://github.com/ChinJunAn)] +[[portfolio]](https://ay2223s2-cs2103-f11-2.github.io/tp/team/chinjunan.html) -* Role: Project Advisor - -### Jane Doe +* Role: Developer +* Responsibilities: Notification and reminder function - +### Do Ha Duong -[[github](http://github.com/johndoe)] -[[portfolio](team/johndoe.md)] + -* Role: Team Lead -* Responsibilities: UI +[[github](https://github.com/dohaduong)] +[[portfolio]](https://ay2223s2-cs2103-f11-2.github.io/tp/team/dohaduong.html) +* Role: Developer +* Responsibility: Timetable function -### Johnny Doe +### Chen Zuo Hui - + -[[github](http://github.com/johndoe)] [[portfolio](team/johndoe.md)] +[[github](https://github.com/zuohui48)] +[[portfolio]](https://ay2223s2-cs2103-f11-2.github.io/tp/team/zuohui48.html) * Role: Developer -* Responsibilities: Data +* Responsibilities: Statistics function -### Jean Doe +### Zhu Le Yao - + -[[github](http://github.com/johndoe)] -[[portfolio](team/johndoe.md)] +[[github](http://github.com/ZhuLeYao)] +[[portfolio]](https://ay2223s2-cs2103-f11-2.github.io/tp/team/zhuleyao.html) * Role: Developer -* Responsibilities: Dev Ops + Threading +* Responsibilities: Data, Add and view jobs function -### James Doe +### Chen Junsheng - + -[[github](http://github.com/johndoe)] -[[portfolio](team/johndoe.md)] +[[github](http://github.com/c0j0s)] +[[portfolio]](https://ay2223s2-cs2103-f11-2.github.io/tp/team/c0j0s.html) * Role: Developer -* Responsibilities: UI +* Responsibilities: Delivery jobs function diff --git a/docs/DeveloperGuide.md b/docs/DeveloperGuide.md index 46eae8ee565..0b7e701187a 100644 --- a/docs/DeveloperGuide.md +++ b/docs/DeveloperGuide.md @@ -2,6 +2,7 @@ layout: page title: Developer Guide --- +**Table of Contents** * Table of Contents {:toc} @@ -9,7 +10,8 @@ title: Developer Guide ## **Acknowledgements** -* {list here sources of all reused/adapted ideas, code, documentation, and third-party libraries -- include links to the original source as well} +* Libraries: [JavaFX](https://openjfx.io/), [ControlsFX](https://github.com/controlsfx/controlsfx) +* Evolved and forked from [AddressBook 3](https://github.com/nus-cs2103-AY2223S2/tp) -------------------------------------------------------------------------------------------------------------------- @@ -23,7 +25,7 @@ Refer to the guide [_Setting up and getting started_](SettingUp.md).
-:bulb: **Tip:** The `.puml` files used to create diagrams in this document can be found in the [diagrams](https://github.com/se-edu/addressbook-level3/tree/master/docs/diagrams/) folder. Refer to the [_PlantUML Tutorial_ at se-edu/guides](https://se-education.org/guides/tutorials/plantUml.html) to learn how to create and edit diagrams. +:bulb: **Tip:** The `.puml` files used to create diagrams in this document can be found in the [diagrams](https://github.com/AY2223S2-CS2103-F11-2/tp/tree/master/docs/diagrams/) folder. Refer to the [_PlantUML Tutorial_ at se-edu/guides](https://se-education.org/guides/tutorials/plantUml.html) to learn how to create and edit diagrams.
### Architecture @@ -36,7 +38,7 @@ Given below is a quick overview of main components and how they interact with ea **Main components of the architecture** -**`Main`** has two classes called [`Main`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/Main.java) and [`MainApp`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/MainApp.java). It is responsible for, +**`Main`** has two classes called [`Main`](https://github.com/AY2223S2-CS2103-F11-2/tp/blob/master/src/main/java/seedu/address/Main.java) and [`MainApp`](https://github.com/AY2223S2-CS2103-F11-2/tp/blob/master/src/main/java/seedu/address/MainApp.java). It is responsible for, * At app launch: Initializes the components in the correct sequence, and connects them up with each other. * At shut down: Shuts down the components and invokes cleanup methods where necessary. @@ -68,25 +70,86 @@ For example, the `Logic` component defines its API in the `Logic.java` interface The sections below give more details of each component. ### UI component - -The **API** of this component is specified in [`Ui.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/ui/Ui.java) +#### General +The **API** of this component is specified in [`Ui.java`](https://github.com/AY2223S2-CS2103-F11-2/tp/blob/master/src/main/java/seedu/address/ui/Ui.java) ![Structure of the UI Component](images/UiClassDiagram.png) -The UI consists of a `MainWindow` that is made up of parts e.g.`CommandBox`, `ResultDisplay`, `PersonListPanel`, `StatusBarFooter` etc. All these, including the `MainWindow`, inherit from the abstract `UiPart` class which captures the commonalities between classes that represent parts of the visible GUI. +The UI layer consist of multiple windows from different components. All windows inherits the abstract `UiPart` class which captures the commonalities between classes that represent parts of the visible GUI. -The `UI` component uses the JavaFx UI framework. The layout of these UI parts are defined in matching `.fxml` files that are in the `src/main/resources/view` folder. For example, the layout of the [`MainWindow`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/ui/MainWindow.java) is specified in [`MainWindow.fxml`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/resources/view/MainWindow.fxml) +The `UI` component uses the JavaFx UI framework. The layout of these UI parts are defined in matching `.fxml` files that are in the `src/main/resources/view` folder. For example, the layout of the [`MainWindow`](https://github.com/AY2223S2-CS2103-F11-2/tp/blob/master/src/main/java/seedu/address/ui/MainWindow.java) is specified in [`MainWindow.fxml`](https://github.com/AY2223S2-CS2103-F11-2/tp/blob/master/src/main/resources/view/MainWindow.fxml) The `UI` component, * executes user commands using the `Logic` component. -* listens for changes to `Model` data so that the UI can be updated with the modified data. -* keeps a reference to the `Logic` component, because the `UI` relies on the `Logic` to execute commands. -* depends on some classes in the `Model` component, as it displays `Person` object residing in the `Model`. +* listens for changes to `Model` data so that the UI can be updated with the modified data (using `addEventListener`). +* calls predefined event handlers when an action, `button`, `mouse` or `keyboard` etc, is performed by the user. +* some window 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 `DeliveryJob` or `Person` object residing in the `Model`. +* Although not represented in the diagram, the UI component starts the Notification function as soon as the app runs. + +#### Main Window + +On start, the `UIManager` will display the `MainWindow` that is made up of parts e.g.`CommandBox`, `ResultDisplay`, `DeliveryJobListPanel`, `StatusBarFooter` etc. The `MainWindow` serve as a main interaction and entry point to other windows for users. + +![Structure of the Main Window](images/UiClassDiagramMainWindow.png) + +#### Timetable Window + +Timetable Window displays timetable of the specific week - which is specified by user. The "main" timetable window only contains scheduled jobs (jobs that are scheduled and not yet completed). However, we also have separated windows for completed and unscheduled jobs (`UnscheduleWindow` and `CompleteWindow`). Timetable Window helps users to stay organized and structure their plans for the week. + +1. `Timetable Window`: + +![Structure of the Timetable Window](images/UiClassDiagramTimetableWindow.png) + +* displays the corresponding job list with the correct date and slot +* returns command execution result. +* remains up-to-date with the job list by using `ObservableList#addListener()` on `Logic#getFilteredDeliveryJobList()` - which will listen to changes made to the delivery job list. +* `TimetableWindow` gets the sorted job list by date and slot using `Logic` component and fills in the timetable. + +Upon calling `MainWindow#handleTimetable()`, the diagram below shows how `TimetableWindow` is being instantiated by calling `TimetableWindow#fillInnerParts()` to initialize the UI, and fill in the job list/detail for the corresponding parts. + +![Sequence Diagram of initialization the Timetable Window](images/UiSequenceDiagramTimetableWindow.png) + + +2. `Unschedule Window`: + +![Structure of the Unscheduled Window](images/UiClassDiagramUnscheduleWindow.png) + +* displays the list of unscheduled jobs +* gets the unscheduled job list using `Logic` component: `Logic#getUnscheduledDeliveryJobList()` +* remains up-to-date with the job list by using `ObservableList#addListener()` on `Logic#getFilteredDeliveryJobList()` - which will listen to changes made to the delivery job list. + +Upon calling `MainWindow#handleUnscheduledTimetable()`, the diagram below shows how `UnscheduleWindow` is being instantiated by calling `UnscheduleWindow#fillInnerParts()` to initialize the UI, and fill in the job list for the corresponding parts. + +![Sequence Diagram of initialization the Unschedule Window](images/UiSequenceDiagramUnscheduledTimetableWindow.png) + + +3. `Complete Window`: + +![Structure of the Completed Window](images/UiClassDiagramCompleteWindow.png) + +* displays the list of completed jobs +* gets the completed job list using `Logic` component: `Logic#getCompletedDeliveryJobList()` +* remains up-to-date with the job list by using `ObservableList#addListener()` on `Logic#getFilteredDeliveryJobList()` - which will listen to changes made to the delivery job list. + +Upon calling `MainWindow#handleCompletedTimetable()`, the diagram below shows how `CompleteWindow` is being instantiated by calling `CompleteWindow#fillInnerParts()` to initialize the UI, and fill in the job list for the corresponding parts. + +![Sequence Diagram of initialization the Complete Window](images/UiSequenceDiagramCompleteWindow.png) + + +#### Create Job Window + +* executes create/edit delivery job commands using the `Logic` component. +* it can handle both create and edit mode for delivery jobs. +* it returns the command execution result through a callback handler. +* it opens a address book dialog for user to choose the sender and recipient for the job. + +![Structure of the Create Job Component](images/UiClassDiagramCreateJob.png) ### Logic component -**API** : [`Logic.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/logic/Logic.java) +**API** : [`Logic.java`](https://github.com/AY2223S2-CS2103-F11-2/tp/blob/master/src/main/java/seedu/address/logic/Logic.java) Here's a (partial) class diagram of the `Logic` component: @@ -98,8 +161,11 @@ How the `Logic` component works: 1. The command can communicate with the `Model` when it is executed (e.g. to add a person). 1. The result of the command execution is encapsulated as a `CommandResult` object which is returned back from `Logic`. -The Sequence Diagram below illustrates the interactions within the `Logic` component for the `execute("delete 1")` API call. +The Sequence Diagram below illustrates the interactions within the `Logic` component for the `execute("delete_job ABCDEF")` API call (accessed from Main Window): +![Interactions Inside the Logic Component for the `delete_job ABCDEF` Command](images/DeleteDeliveryJobSequenceDiagram.png) + +Another example would be `execute("delete 1")` - accessed from Customers' Window: ![Interactions Inside the Logic Component for the `delete 1` Command](images/DeleteSequenceDiagram.png)
:information_source: **Note:** The lifeline for `DeleteCommandParser` should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram. @@ -114,17 +180,19 @@ How the parsing works: * All `XYZCommandParser` classes (e.g., `AddCommandParser`, `DeleteCommandParser`, ...) inherit from the `Parser` interface so that they can be treated similarly where possible e.g, during testing. ### Model component -**API** : [`Model.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/model/Model.java) +**API** : [`Model.java`](https://github.com/AY2223S2-CS2103-F11-2/tp/blob/master/src/main/java/seedu/address/model/Model.java) The `Model` component, -* stores the address book data i.e., all `Person` objects (which are contained in a `UniquePersonList` object). -* stores the currently 'selected' `Person` objects (e.g., results of a search query) as a separate _filtered_ list which is exposed to outsiders as an unmodifiable `ObservableList` that can be 'observed' e.g. the UI can be bound to this list so that the UI automatically updates when the data in the list change. +* stores the address book data i.e., all `Person` objects (which are contained in a `UniquePersonList` object) and `Reminder` Objects (which are contained in a `ReminderList` object). +* store the delivery job system data (all `DeliveryJob` objects are contained in a `UniqueDeliveryJobList` object). +* stores the currently 'selected' `DeliveryJob` objects (e.g., results of a search query) as a separate _filtered_ list which is exposed to outsiders as an unmodifiable `ObservableList` that can be 'observed' e.g. the UI can be bound to this list so that the UI automatically updates when the data in the list change. * stores a `UserPref` object that represents the user’s preferences. This is exposed to the outside as a `ReadOnlyUserPref` objects. * does not depend on any of the other three components (as the `Model` represents data entities of the domain, they should make sense on their own without depending on other components) +* The address book structure largely remains the same.
: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.
@@ -135,18 +203,18 @@ The `Model` component, ### Storage component -**API** : [`Storage.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/storage/Storage.java) +**API** : [`Storage.java`](https://github.com/AY2223S2-CS2103-F11-2/tp/blob/master/src/main/java/seedu/address/storage/Storage.java) The `Storage` component, -* can save both address book data and user preference data in json format, and read them back into corresponding objects. -* inherits from both `AddressBookStorage` and `UserPrefStorage`, which means it can be treated as either one (if only the functionality of only one is needed). +* can save delivery job system, address book data and user preference data in json format, and read them back into corresponding objects. +* inherits from `DeliveryJobSystemStorage`, `AddressBookStorage` and `UserPrefStorage`, which means it can be treated as either one (if only the functionality of only one is needed). * depends on some classes in the `Model` component (because the `Storage` component's job is to save/retrieve objects that belong to the `Model`) ### Common classes -Classes used by multiple components are in the `seedu.addressbook.commons` package. +Classes used by multiple components are in the `seedu.address.commons` package. -------------------------------------------------------------------------------------------------------------------- @@ -232,13 +300,212 @@ The following activity diagram summarizes what happens when a user executes a ne * Pros: Will use less memory (e.g. for `delete`, just save the person being deleted). * Cons: We must ensure that the implementation of each individual command are correct. -_{more aspects and alternatives to be added}_ +### Delivery Job System +#### Implementation + +##### 1. Edit job command +Given below is an example usage scenario and how the update job mechanism behaves at each step. The other job system commands follow a similar execution pattern with their own command logics. + +GUI Mode: +Step 1. The user launches the application for the first time. +Step 2. The user selects a job to be updated. +Step 3. The user edits preexisting inputs to update the job. + +The command pattern was followed closely with difference only in the execution layer where the `EditDeliveryJobCommand` constructs a `DeliveryJob` object through a builder pattern. + +The builder construct was initially introduced to handle optional and null arguments from `find_job` command and GUI. Here, we are reusing the builder to construct a `DeliveryJob` class. + +The builder class returns a `DeliveryJob` object only when `EditDeliveryJobCommand` calls the `DeliveryJob.Builder#build()` method. + +Operation resume to standard process from this point onwards. + +The following sequence diagram shows how the update operation works: + +![EditDeliveryJobSequenceDiagram](images/EditDeliveryJobSequenceDiagram.png) + +##### 2. Delete job command +Similarly to `edit_job`, we have an example for the implementation for Delete Delivery Job command: + +Given below is an example usage scenario and how the delete job mechanism behaves at each step. The logic is quite similar to `edit_job` command. + +GUI Mode: +Step 1. The user launches the application for the first time. +Step 2. The user selects a job to delete. +Step 3. The user deletes the job by using the corresponding button. + +The command pattern was followed closely with difference only in the execution layer where the `DeleteDeliveryJobCommand` prompts `Model` to delete the job. + +Operation resume to standard process from this point onwards. + +The following sequence diagram shows how the update operation works: + + +_In this example, user input is `delete_job ABCDEF`. The logic for `add_job` command is similar to this._ +![Delete Delivery job for the `delete_job ABCDEF` Command](images/DeleteDeliveryJobSequenceDiagram.png) + + +### Timetable feature +#### Implementation +#### 1. Display timetable of specific week (current week or week specified by users) +Given below is an example usage scenario and how the timetable mechanism behaves at each step. + + +Step 1. The user launches the application for the first time. + +Step 2. The user inputs a series of commands to modify the state of the deliveryJobList. + + +
:information_source: **Note:** If a command fails its execution, it will not call `Model#commitDeliveryJob()`, so the delivery job list state will not be saved into the `deliveryJobListSystem`. + +
+ +Step 4. The user now wants to view timetable of the current week by executing the `timetable` command. The `timetable` command will call `Model#updateFocusDate(LocalDate.now())`, `Model#updateSortedDeliveryJobList()`, `Model#updateSortedDeliveryJobListByDate()` and `Model#updateWeekDeliveryJobList`. The sorter functions will re-sort the most updated delivery job list by date. Then, `Model#updateWeekDeliveryJobList` will update the week job list to the specific week's job list. + +The `timetable_date` command is quite similar — it calls `Model#updateFocusDate()` using the date specified by user, before calling `Model#updateSortedDeliveryJobList()`, `Model#updateSortedDeliveryJobListByDate()` and `Model#updateWeekDeliveryJobList`, which will update the job list in the week according to the given date. + + +The following sequence diagram shows how the timetable operation works: + +![TimetableSequenceDiagram](images/TimetableSequenceDiagram.png) + +The following sequence diagram shows how the timetable_date operation works: + +![TimetableSequenceDiagram](images/TimetableDateSequenceDiagram.png) + + +
:information_source: **Note:** The lifeline for `TimetableCommand` should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram. + +
+ + +#### Design considerations: + +**Aspect: How timetable executes:** + +* **Alternative 1 (current choice):** Timetable uses sorted job list which saves the entire delivery job list system. + * Pros: Easy to implement. + * Cons: May have performance issues in terms of memory usage. + +* **Alternative 2:** Individual command knows which date to show timetable of specific week - timetable will only need to use job list of specific week. + * Pros: Will use less memory. + * Cons: We must ensure that the implementation of each individual command are correct. + +#### 2. Display list of unscheduled or completed jobs +Given below is an example usage scenario and how the mechanism for showing list of unscheduled or completed jobs behaves at each step. + -### \[Proposed\] Data archiving +Step 1. The user launches the application for the first time. -_{Explain here how the data archiving feature will be implemented}_ +Step 2. The user inputs a series of commands to modify the state of the deliveryJobList. +
:information_source: **Note:** If a command fails its execution, it will not call `Model#commitDeliveryJob()`, so the delivery job list state will not be saved into the `deliveryJobListSystem`. + +
+ +Step 4. The user now wants to view list of unscheduled or completed jobs by executing the `timetable_unscheduled` or `timetable_completed` command. + +The `timetable_unscheduled`/`timetable_completed` command will call `Model#updateSortedDeliveryJobList()` and `Model#getUnscheduledDeliveryJobList()` or `Model#getCompletedDeliveryJobList()` correspondingly. + +The sorter functions will re-sort the most updated delivery job list by date. Then, the model will proceed to update the lists of unscheduled or completed jobs. + +The following sequence diagram shows how the `timetable_unscheduled` operation works: + +![TimetableSequenceDiagram](images/TimetableUnscheduledSequenceDiagram.png) + +The following sequence diagram shows how the `timetable_completed` operation works: + +![TimetableSequenceDiagram](images/TimetableCompletedSequenceDiagram.png) + + +
:information_source: **Note:** The lifeline for `TimetableCommand` should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram. + +
+ + +### Statistics feature +Allow the user to view summary statistics about the delivery jobs. +#### Implementation + +Given below is an example usage scenario and how the statistics mechanism behaves at each step. + +Step 1. The user launches the application for the first time. + +Step 2. The user inputs a series of commands to modify the state of the deliveryJobList. + +Step 3. The user now wants to view a summary of the statistics of the jobs in the deliveryJobList. +The `stats` command will open up the statistics window, where a list of statistics will be shown. + +The `stats` command will call `Logic#getFilteredDeliveryJobList()` which will return a list of delivery jobs. +The statistics is then generated based on the list of delivery jobs and shown to the user in the Statistics Window. + +The following sequence diagram shows how the statistics operation works: + +![StatisticsSequenceDiagram](images/StatisticsSequenceDiagram.png) + + +#### Design considerations: + +**Aspect: How statistics are stored:** + +* **Alternative 1 (current choice):** Saves each statistic as a Statistic object in the list + * Pros: Easy to store and display statistics + * Cons: May have performance issues in terms of memory usage and execution delay. + +* **Alternative 2:** Display each statistic individually + * Pros: Will use less memory and faster to execute + * Cons: Will make code longer and increases coupling + + +### Notification feature +#### Implementation + +The Notification feature is facilitated by an external library, [ControlsFx](https://github.com/controlsfx/controlsfx). +It is an open source project for JavaFX that aims to provide really high quality UI controls and other tools to +complement the core JavaFX distribution. The mechanism is handled by `NotificationManager`, which implements the +following operations: + +* `NotificationManager#checkReminderList()` — Check against the `reminderList` found in `Model` and display a reminder notification with `NotificationManager#show()`. +* `NotificationManager#checkNowSchedule()` — Check against the timetable to create a notification of scheduled jobs to be carried out at in the present scheduled slot. Displays notification with `NotificationManager#show()`. +* `NotificationManager#checkNextSchedule()` — Check against the timetable to create a notification of upcoming scheduled jobs to be carried out in the next scheduled slot. Displays notification with `NotificationManager#show()`. +* `NotificationManager#show()` — Creates the actual notification with details picked up by the other methods, and displays it on the screen. + +Additionally, to allow notifications to display even when the app is running in the background, `TimerTask` and `Timer` from `java.util` is utilised. This mechanism is started by +`BackgroundNotificationScheduler`, which schedules 2 `TimerTask`: + +* `BackgroundReminderTask` — Runs `NotificationManager#checkReminderList()`. This task is scheduled every minute after the app has run. It will not show notifications of reminders that it has already shown (this check resets after the app shutdown). +* `BackgroundScheduleTask` — Runs `NotificationManager#checkNextSchedule()`. This task is scheduled 20 minutes before the next scheduled slot to display upcoming scheduled jobs. If the current time is +after the last slot timing, no notification for upcoming jobs will be displayed until the end of the day. + +Given below is an example of the usage scenario and how the Notification feature behaves at each step. + +Step 1. The user launches the application. `UiManager` will create an instance of `NotificationManager`. + +Step 2. The instance of `NotificationManager` will then call `NotificationManager#checkReminderList()` and `NotificationManager#checkNowSchedule()`, which will display the corresponding notifications + +Step 3. An instance of `BackgroundNotificationScheduler` will be created and its `BackgroundNotificationScheduler#run()` function will be called to schedule the 2 `TimerTask`. + +Step 4. At the appropriate timings, `NotificationManager#checkReminderList()` and `NotificationManager#checkNextSchedule()` will run and display the appropriate notifications accordingly. + +
+:information_source: **Note:** Users can click on the notification to dismiss the reminders. This effectively snoozes the reminder until another +new reminder is active. This is implemented by counting the number of active reminder through the `hasShown` attribute in `Reminder` and tracking the +`isDismissed` variable in `NotificationManager`. +
+ +The following sequence diagram shows how the Notification feature works: +![NotificationSequenceDiagram](images/NotificationSequenceDiagram.png) + +Design considerations: + +**Aspect: How background notifications run its checks:** +* **Alternative 1 (current choice):** `TimerTask` and `Timer` + * Pros: Easy to implement. Better OOP. + * Cons: Cannot run random checks against the current time or date. +* **Alternative 2**: `Thread` + * Pros: Allows random checks against the current time or date. + * Cons: May slow down the app, or worst case scenario, hang the app. + -------------------------------------------------------------------------------------------------------------------- ## **Documentation, logging, testing, configuration, dev-ops** @@ -255,73 +522,249 @@ _{Explain here how the data archiving feature will be implemented}_ ### Product scope -**Target user profile**: +**Target user profile: Delivery man** * has a need to manage a significant number of contacts -* prefer desktop apps over other types +* prefer desktop apps over other mediums * can type fast -* prefers typing to mouse interactions +* prefers typing over mouse interactions * is reasonably comfortable using CLI apps +* delivery man with >50 deliveries in a day +* drives constantly with a laptop in the van +* lazy, doesn't like to micromanage +* forgetful -**Value proposition**: manage contacts faster than a typical mouse/GUI driven app +**Value proposition**: +* Manage contacts faster than a typical mouse/GUI driven app. +* Helps delivery men to stay on track with their delivery schedule. +* Assists delivery men to structure their upcoming tasks. +* Manage job lists and customers' contacts - keep their information descriptive and updated/concise information. ### User stories Priorities: High (must have) - `* * *`, Medium (nice to have) - `* *`, Low (unlikely to have) - `*` -| Priority | As a …​ | I want to …​ | So that I can…​ | -| -------- | ------------------------------------------ | ------------------------------ | ---------------------------------------------------------------------- | -| `* * *` | new user | see usage instructions | refer to instructions when I forget how to use the App | -| `* * *` | user | add a new person | | -| `* * *` | user | delete a person | remove entries that I no longer need | -| `* * *` | user | find a person by name | locate details of persons without having to go through the entire list | -| `* *` | user | hide private contact details | minimize chance of someone else seeing them by accident | -| `*` | user with many persons in the address book | sort persons by name | locate a person easily | - -*{More to be added}* +| Priority | As a …​ | I want to …​ | So that I can…​ | +|----------|-----------------------------------------------------------------|--------------------------------------------------------------------|--------------------------------------------------------------------------------------| +| `* * *` | new delivery driver and Duke Driver user | see usage instructions | refer to instructions when I forget how to use the App | +| `* * *` | delivery driver | add/delete jobs | keep track of my upcoming and old jobs | +| `* * *` | delivery driver | mark/unmark jobs | keep track of completed jobs | +| `* * *` | organised and busy delivery driver | search for jobs in my Duke Driver job list | easily find the information that I need without having to go through the entire list | +| `* * *` | busy delivery driver | mass import job list from file to Duke Driver | easily move my data between devices or other apps. | +| `* * *` | delivery driver | add a new person | keep in touch with my clients | +| `* * *` | delivery driver | delete a person | remove entries that I no longer need | +| `* * *` | organised delivery driver | find a person by their information | locate details of persons without having to go through the entire list | +| `* * *` | busy Duke Driver user | edit existing contacts in my address book | update their information if it changes | +| `* * *` | busy Duke Driver user | edit existing jobs in my Duke Driver job list | update their information if it changes | +| `* * *` | forgetful person | received notifications/be reminded of upcoming tasks and deadlines | complete all my jobs on time and not forget a task | +| `* * *` | forgetful person | add reminders to the app | complete all my jobs on time and not forget a task | +| `* * *` | organised person | delete reminders from the app | keep myself updated to my progress | +| `* * *` | busy person | snooze reminders | ignore/forget about jobs that I am not able to complete at the scheduled time | +| `* * *` | busy person | view list of reminders | keep track of my progress | +| `* * *` | organised user | be prepared for upcoming tasks and deadlines | plan for my next schedule | +| `* * *` | busy and organised person | view schedule of my tasks in a week | organise my timetable/to-do list and keep track/complete everything on time | +| `* * *` | busy and organised person | view list of completed and unscheduled jobs | keep track of my work | +| `* * *` | productive and motivated delivery driver | view statistics of my jobs and earnings | keep track of my work and get motivated to work harder | +| `* *` | delivery driver | hide private contact details | minimize chance of someone else seeing them by accident | +| `* *` | delivery driver who wants to learn how to maximise his earnings | view my aggregated information | track my earnings and other statistics | +| `*` | delivery driver with many jobs in Duke Driver | sort and filter jobs | locate jobs easily and thus increase delivery efficiency | +| `*` | user | adjust how my notifications are shown | have a clutter free desktop | +| `*` | picky person | decide on and design how my timetable will look like | view my timetable more easily and to my liking | ### Use cases -(For all use cases below, the **System** is the `AddressBook` and the **Actor** is the `user`, unless specified otherwise) - -**Use case: Delete a person** - -**MSS** - -1. User requests to list persons -2. AddressBook shows a list of persons -3. User requests to delete a specific person in the list -4. AddressBook deletes the person - - Use case ends. - -**Extensions** +(For all use cases below, the **System** is the `Duke Driver` and the **Actor** is the `user`, unless specified otherwise) + +#### [ST1] View statistics +_**MSS**_ +1. User is on homepage of list of jobs. +2. User requests to view overall statistics. +3. System shows total earnings, statistics on jobs completed/pending in total and in the previous week. + Use case ends. + +_**Extensions**_ +* 2a. The list is empty. + Use case ends. + + +#### [DE1] View delivery job detail +_**MSS**_ +1. User opens the system. +2. System list all pending jobs. +3. User selects the job for details. +4. System displays the full detail of the delivery job. + Use case ends. + +_**Extensions**_ +* 2a. The list is empty. + Use case ends. + +#### [DE2] Add a delivery job +_**MSS**_ +1. User is on homepage of list of jobs. +2. User requests to add a job in the list. +3. System adds job and job appears in list of jobs. + Use case ends. + +#### [DE3] Delete a delivery job +_**MSS**_ +1. User is on homepage of list of jobs. +2. System shows a list of jobs. +3. User requests to delete a specific job in the list. +4. System deletes the job. + Use case ends. + +_**Extensions**_ +* 2a. The list is empty. + Use case ends. +* 3a. The given index is invalid. + * 3a1. System shows an error message. + Use case resumes at step 2. -* 2a. The list is empty. +#### [DE4] Edit a delivery job - Use case ends. +_**MSS**_ +1. User is on homepage of list of jobs. +2. System shows a list of jobs. +3. User requests to edit a specific job in the list. +4. User fill in and submit the changes. +4. System update the job and list the new information. + Use case ends. +_**Extensions**_ +* 2a. The list is empty. + Use case ends. * 3a. The given index is invalid. + * 3a1. System shows an error message. + Use case resumes at step 2. - * 3a1. AddressBook shows an error message. - +#### [DE5] Find a delivery job +_**MSS**_ +1. User is on homepage of list of jobs. +2. System shows a list of jobs. +3. User requests search for a job with options. +4. System displays search results that matches the query. + Use case ends. + +_**Extensions**_ +* 3a. Invalid search option given. + * 3a1. System shows an error message. + Use case resumes at step 2. +* 4a. No item matches the query options. + * 4a. System shows empty list. Use case resumes at step 2. -*{More to be added}* +#### [TT1] Display timetable of scheduling tasks of current week + +_**MSS**_ +1. User requests to display timetable by selecting Timetable option on homepage. +2. System displays timetable of uncompleted/upcoming jobs in current week in Timetable Window. + Use case ends. + +#### [TT2] Display timetable of scheduling tasks of week containing a specific date +_**MSS**_ +1. User requests to display timetable of specific week containing a specific date. +2. System displays timetable of uncompleted/upcoming jobs in the week in Timetable Window. + Use case ends. + +#### [TT3] Display list of unscheduled/completed jobs + +_**MSS**_ +1. User requests to display list of unscheduled/completed jobs. +2. System displays list of unscheduled/completed jobs in Unscheduled/Completed Window. + Use case ends. + +#### [RE1] Alert scheduled jobs + +_**MSS**_ + +1. User starts up System. +2. System checks the time. +3. If the current timing falls more than 20 mins before a timetable slot, System will check if there is any current job. +If yes, it will count and alert the user through the notification feature. +4. System runs in the background to only check the timetable for upcoming jobs. + System will repeat step 2 every hour, 20 mins before the next timetable slot. + Use case ends. + +_**Extensions**_ +* 3a. If the current time is within 20 mins before the next timetable slot + * 3a1. System will check the next timetable slot and count number of upcoming jobs. + * 3a2. Alert the user through the notification feature. +* 3b. If the current time is before the first timetable slot. + * 3b1. System will check in the first timetable slot and count number of upcoming jobs. + * 3b2. Alert the user through the notification feature. +* 3c. If the current time is after the last timetable slot + * 3c1. System will not check for any or upcoming scheduled jobs. + Use case resumes from step 4. + +#### [RE2] Alert reminders +_**MSS**_ +1. User starts up System. +2. System loads address book from memory. +3. System checks from address book, list of reminders. If the current date and time has pass or is equal to the date +specified in a reminder, System will count it as an active reminder. +4. System will display the number of active notifications. +5. System runs in the background to check against the list of reminders after every minute. + System will repeat the check at step 3. + Use case ends. + +_**Extensions**_ +* 4a. User can dismiss the reminder. Doing will prevent the app from showing anymore notifications. + * 4a1. A new reminder is activated. + Use case resumes from step 4. + * 4a2. No new reminder is activated. + Use case resumes from step 5 + +#### [RE3] Add reminders +_**MSS**_ + +1. User details the description, date and time of a reminder to the System. +2. System adds the reminder into the reminder list. + Use case ends. + +_**Extensions**_ +* 2a. date and time of reminder is not provide. + * 2a1. System will prompt user again. + Use case resumes from step 1. + +#### [RE4] Delete reminders + +_**MSS**_ + +1. User specifies a reminder to be deleted based on its index number. +2. System finds the corresponding reminder, and deletes it from the reminder list. + Use case ends. + +_**Extensions**_ +* 2a. Index provided by user is not found in reminder list. + * 2a1. System will prompt user again. + Use case resumes from step 1. + +#### [RE5] List reminders + +_**MSS**_ + +1. User request System to show all reminders in reminder list. +2. System displays all reminders. + Use case ends. + ### Non-Functional Requirements 1. Should work on any _mainstream OS_ as long as it has Java `11` or above installed. -2. Should be able to hold up to 1000 persons without a noticeable sluggishness in performance for typical usage. -3. A user with above average typing speed for regular English text (i.e. not code, not system admin commands) should be able to accomplish most of the tasks faster using commands than using the mouse. - -*{More to be added}* +2. Should be used for a single user only i.e. (not a multi-user product). +3. The system should respond within two seconds (after receiving input from user). +4. A user with above average typing speed for regular English text (i.e. not code, not system admin commands) should be able to accomplish most of the tasks faster using commands than using the mouse. ### Glossary * **Mainstream OS**: Windows, Linux, Unix, OS-X * **Private contact detail**: A contact detail that is not meant to be shared with others +* **Customer/Client**: Those who placed orders and created work for delivery men. +* **GUI**: Graphical User Interface +* **PlantUML**: An open-source tool allowing users to create diagrams from a plain text language. Refer to [PlantUML](https://plantuml.com/). +* **CLI**: Command Line Interface -------------------------------------------------------------------------------------------------------------------- @@ -329,49 +772,205 @@ Priorities: High (must have) - `* * *`, Medium (nice to have) - `* *`, Low (unli Given below are instructions to test the app manually. -
:information_source: **Note:** These instructions only provide a starting point for testers to work on; +
:information_source: **Note:** These instructions only provide a starting point for testers to work on; testers are expected to do more *exploratory* testing. -
### Launch and shutdown 1. Initial launch - 1. Download the jar file and copy into an empty folder - 1. Double-click the jar file Expected: Shows the GUI with a set of sample contacts. The window size may not be optimum. - 1. Saving window preferences - 1. Resize the window to an optimum size. Move the window to a different location. Close the window. - 1. Re-launch the app by double-clicking the jar file.
Expected: The most recent window size and location is retained. -1. _{ more test cases …​ }_ - -### Deleting a person +### Address Book + +1. Open address book by command + 1. Prerequisites: Access to main window. + 1. Test case: `list`
+ Expected: Customer address book window launched. +1. Open address book by GUI + 1. Prerequisites: Access to main window. + 1. Test case: `Customer` menu > `Address Book`
+ Expected: Customer address book window launched. +1. Add a person + 1. Prerequisites: Access to Customers Window using the `list` command. + 1. Test case: `add n/John Doe p/98765432 e/johnd@example.com a/John street, block 123, #01-01`
+ Expected: A new person with the name `John Doe` added to the list. + 1. Other incorrect format to try: + - missing mandatory fields: `add n/John Doe e/johnd@example.com` + - invalid phone number: `add n/John Doe p/8 e/johnd@example.com` + Expected: Error message displayed in result box. +1. Edit a person + 1. Prerequisites: List all persons in Customers Window using the `list` command. person to edit in the list. + 1. Test case: `edit 1 p/81234567 e/johndoe@example.com`
+ Expected: The phone number and email address of person at index 1 is updated. + 1. Other incorrect format to try: + - invalid index: `edit 0 p/81234567` + Expected: Error message displayed in result box. +1. Find a person by name + 1. Prerequisites: List all persons in Customers Window using the `list` command. target person in the list. + 1. Test case: `find David`
+ Expected: Matching person in the person list only. + 1. Other incorrect format to try: + - `not found query`: `find abc` + Expected: 0 persons listed. 1. Deleting a person while all persons are being shown - - 1. Prerequisites: List all persons using the `list` command. Multiple persons in the list. - + 1. Prerequisites: List all persons in Customers Window using the `list` command. Multiple persons in the list. 1. Test case: `delete 1`
- Expected: First contact is deleted from the list. Details of the deleted contact shown in the status message. Timestamp in the status bar is updated. - + Expected: First contact is deleted from the list. Details of the deleted contact shown in the status message. 1. Test case: `delete 0`
Expected: No person is deleted. Error details shown in the status message. Status bar remains the same. - 1. Other incorrect delete commands to try: `delete`, `delete x`, `...` (where x is larger than the list size)
Expected: Similar to previous. -1. _{ more test cases …​ }_ - -### Saving data - -1. Dealing with missing/corrupted data files - - 1. _{explain how to simulate a missing/corrupted file, and the expected behavior}_ -1. _{ more test cases …​ }_ +### Delivery Job System + +1. Add a job by command + 1. Prerequisites: User is in the main window. Valid recipient and sender id. + 1. Test case: `add_job si/DAVSAM ri/CHASAM earn/1`
+ Expected: A delivery job is created without delivery schedule. + The sender should be DAVSAM and recipient should be CHASAM. + With earning value of $1. + 1. Test case: `add_job si/DAVSAM ri/CHASAM earn/1 date/2024-05-01 slot/1`
+ Expected: A delivery job is created with delivery schedule. + Similar to previous. + 1. Other incorrect format to try: + - `add_job si/invalid_id ri/invalid_id earn/empty` invalid argument supply. + - `add_job si/... ri/... earn/... [date/...] [slot/...]` with either date or slot given. + Expected: Error message displayed in result box. +1. Add a job by GUI + 1. Prerequisites: User have access to GUI. Select `menu` > `Delivery Job System` > `Create Job`. + 1. Test case: fill in mandatory fields only (recipient/sender/earning)
+ Expected: Similar to `1.2` + 1. Test case: fill in all fields
+ Expected: Similar to `1.3` + 1. Other incorrect approach to try: + - sender/recipient: invalid person id. + - earning: multiple decimal points. + - date: invalid date. + Expected: Error message at the bottom of dialog. +1. Edit a job by command + 1. Prerequisites: Job to edit is valid. + 1. Test case: `edit_job 1 slot/4`
+ Expected: The slot of job at index 1 is updated. + 1. Test case: `edit_job ji/BECHE8833A slot/4`
+ Expected: The slot of job with id `BECHE8833A` is updated. + 1. Other incorrect format to try: + - Invalid index: `edit_job 0 slot/4` + - Invalid Job Id: `edit_job ji/BA slot/4` + Expected: Error message displayed in result box. +1. Edit a job by GUI + 1. Prerequisites: Access to edit job dialog. + 1. Test case: `MainWindow` > `Job Detail` > `🖊️` button
+ Expected: The changes made is reflected in the detail pane after submission. + 1. Other incorrect approach to try: + - Similar to create job GUI. +1. Find a job + 1. Prerequisites: Target job is valid. + 1. Test case: `find_job ji/BECHE8833A`
+ Expected: Job with id `BECHE8833A` displayed in the list. + 1. Test case: `find_job date/2023-03-01 slot/4`
+ Expected: Jobs that satisfies both date **and** slot conditions are displayed in the list. + 1. Other incorrect format to try: + - Invalid command: `find_job` + Expected: Error message displayed in result box. +1. Delete a job + 1. Prerequisites: Job to delete is valid. + 1. Test case: `delete_job DAIR765586`
+ Expected: Job with id `DAIR765586` removed from the list. + 1. Test case: `select a job` > press `del` key
+ Expected: Job selected is removed. + 1. Other incorrect format to try: + - Invalid command: `delete_job` + Expected: Error message displayed in result box. +1. Delete a job by Hot Key + 1. Prerequisites: Job to delete is in the list. + 1. Test case: `select a job` > press `del` key
+ Expected: Job selected is removed. +2. Import jobs by GUI + 1. Prerequisites: + 1. File to be imported is prepared. + 2. File to be imported is in csv. + 3. File to be imported has header as specified in UG. + 4. File to be imported has no empty cells. + 5. File to be imported has "na" filled into optional cells. + 2. Test case: `click on DeliveryJobSystem in menu bar` > `click import jobs` > select the csv file to be imported + 1. Files are available for testing in docs. + 1. testimportfile.csv + 1. Expected: File is imported with new delivery jobs and new customers if customers do not already exist in address book. + 2. empty.csv (incorrect format to try) + 1. Expected: File is empty. Nothing is imported. + 3. some_na.csv (Some optional cells left empty) + 1. Expected: File is imported with new delivery jobs and new customers if customers do not already exist in address book. + 4. missingelements.csv (incorrect format to try) + 1. Missing elements in import. Check if there are empty cells. + + + +### Notifications + +1. Display a notification for a reminder + 1. Prerequisites: Added a reminder using the `add_reminder` command. Make sure the `time/` field is set to an appropriate time. + 2. Test case: Current time has pass indicated time for Reminder
Expected: After adding reminder, notification for that reminder should pop up at the next clock minute. + 3. Test case: Current time has not pass indicated time for Reminder
Expected: No notification should show, unless clock minute has pass time indicated for reminder. +2. Snooze reminder notification + 1. Prerequisites: Reminder has been added, current time has pass time indicated in reminder, and notification is showing. + 2. Test case: Clicking on body of notification should open up the Reminder List Window, and dismiss the notification. Notification should not show up again at next clock minute, unless a new reminder +has been activated. + 3. Due to the in-built nature of ControlsFX's notification and how reminders are checked at every minute, expect the notification to disappear and appear again every minute. +3. Display a notification for next Scheduled slot + 1. Prerequisites: Assign a job to an appropriate timetable slot. + 2. Test case: Once current time is 20 minutes before the next timetable slot (e.g. 10:40 to 10:59), a notification would pop up for the number of jobs assigned to the next timetable slot. + +### Timetable + +1. Display timetable for specific week containing a specific date from Main Window + 1. Prerequisites: User is at the main window. + 2. Command: `timetable` (Show timetable of current week - LocalDate.now()) or `timetable date date/YYYY-mm-DD`. The date should not be left empty. Alternative, for `timetable` command, user can use GUI mode instead: Menu bar > Timetable > Scheduled jobs. + 3. The output box in the window should show a message confirming that Timetable window is opened for the specific week containing the input date. + 4. The system should open Timetable for the specific week, showing job list in respective day and slot (if there are any jobs for that day - else, the column for the day will be empty). +2. Display list of unscheduled jobs (i.e. jobs with invalid date and slot) + 1. Prerequisites: User is at the main window. + 2. Command: `timetable_unscheduled` or GUI mode: Menu bar > Timetable > Unscheduled jobs. + 3. The output box in the Main window should show a message confirming that Unscheduled job is opened for the specific week. + 4. The system should open the Unscheduled Window, showing list of unscheduled jobs and total number of unscheduled jobs. +3. Display list of completed jobs + 1. Prerequisites: User is at the main window. + 2. Command: `timetable_completed` or GUI mode: Menu bar > Timetable > Completed jobs. + 3. The output box in the Main window should show a message confirming that Completed job is opened for the specific week. + 4. The system should open the Completed Window, showing list of completed jobs and total number of completed jobs. +4. Display timetable for specific week containing a specific date from Timetable Window + 1. Prerequisites: User is at Timetable window - user may open this window by using `timetable` command or GUI Mode: Menu bar > Timetable > Scheduled jobs from Main Window. Timetable of current week is shown in current Timetable Window. + 2. Command: `timetable date date/YYYY-mm-DD`. The date should not be left empty. + 3. The output box in the Timetable window should show a message confirming that Timetable window is opened for the specific week which contains the input date. + 4. The system should display Timetable for the specific week, showing job list in respective day in the week and slot (if there are any jobs for that day - else, the column for the day will be empty). + +### Statistics + +1. Display statistics of all jobs from Main Window + 1. Prerequisites: None. + 2. Command: `stats`. Alternative, for `stats` command, user can use GUI mode instead: Menu bar > Statistics > Display Statistics. + 3. The text in the window should show lists of statistics for current week's and previous week's jobs. + +### Appendix: Effort +As our application contains different windows and features, such as Timetable Window, Reminder Window,.. - one challenge that we had to face was deciding on the UI and design of our app. We learnt to work with JavaFX to open different windows when needed, and decide on the structure/design of each window to maintain good design principles. To make sure that Duke Driver is friendly to typing-preferred users, asides from including buttons on GUI mode, we also include commands for users to switch between windows. + +Moreover, AB3 code base only consists of features supporting only `Person` class, meanwhile for Duke Driver, we had to support different entity types - for example, `Delivery Job` and `Reminder`. With these extensions, we had to update the codebase (i.e. `Parser`, `Model` and `Logic`) to support a much larger set of commands, as we were working with numerous commands from different windows (Timetable, Reminder, Statistics,...). + +For Delivery Job Management System, as the job details were very long and could not be viewed inside a small cell in the job list, we wanted to add a feature for users to use mouse or arrow keys and click on the job to view its detail correspondingly. This was challenging as we need to learn how to make use of the UI and keyboard clicks. + +We also added a GUI mode for our app - to speed up the speed of each command and thus, enhance users' experience. Throughout this process, we learn how to use various JavaFX classes and interfaces. + +We wanted to introduce a notification function to the app, so that users could be reminded of their jobs and upcoming tasks more easily. It was challenging to implement this function as JavaFx did not have any native notification feature. Hence, we had to find an external library that did so. We decided to used ControlFX, an open source project for JavaFX that serves to provide high quality UI controls on top of the existing JavaFX distribution. Through abstraction, this feature can be used by other portions of the applications (i.e. reminders, scheduling, etc). Hence, the implementation extended the functionalities and benefits of the application. + +Also, we wanted users to be able to structure their plans for the week, thus we added a Timetable feature. However, as Timetable is directly linked with the delivery job list, the feature is closely related to the existing commands and functions. We also had to decide on the design/structure of the Timetable Window, and learn to use the `TitledPane` and `ListView` class in our timetable. + +Overall, the Team Project for us was quite challenging, as it requires us to learn to work together and help each other. We had to divide the work among ourselves so that everyone can get a grasp of and understand the code base. Understanding and updating the code base was quite tough at first, due to high levels of abstraction and the amount of classes that AB3 has. We tried to break it down into small tasks and understand it little by little each week. These small improvements day-by-day helped us get used to the codebase and the workload eventually. + +We also learnt to work as a team and contributed, reviewed and helped to debug each other's work through weekly meetings and constant updates. Distributing the work and assigning small task to each member helped us gain more confidence throughout the project. diff --git a/docs/Testing.md b/docs/Testing.md index 8a99e82438a..e283a6b7190 100644 --- a/docs/Testing.md +++ b/docs/Testing.md @@ -30,7 +30,7 @@ This project has three types of tests: 1. *Unit tests* targeting the lowest level methods/classes.
e.g. `seedu.address.commons.StringUtilTest` -1. *Integration tests* that are checking the integration of multiple code units (those code units are assumed to be working).
+2. *Integration tests* that are checking the integration of multiple code units (those code units are assumed to be working).
e.g. `seedu.address.storage.StorageManagerTest` -1. Hybrids of unit and integration tests. These test are checking multiple code units as well as how the are connected together.
+3. Hybrids of unit and integration tests. These test are checking multiple code units as well as how the are connected together.
e.g. `seedu.address.logic.LogicManagerTest` diff --git a/docs/UserGuide.md b/docs/UserGuide.md index e7df68b01ea..5e0ca3a4016 100644 --- a/docs/UserGuide.md +++ b/docs/UserGuide.md @@ -3,43 +3,64 @@ 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. +Duke Driver is a desktop app assisting delivery men with managing delivery jobs and contacts. If you are looking to perform better at your delivery job, Duke Driver can assist you to finish your tasks more efficiently, according to your requirements. +**Table of Content** * Table of Contents {:toc} -------------------------------------------------------------------------------------------------------------------- +## How to use this guide? -## Quick start +This user guide is an in-depth documentation on all the features that Duke Driver has. -1. Ensure you have Java `11` or above installed in your Computer. -1. Download the latest `addressbook.jar` from [here](https://github.com/se-edu/addressbook-level3/releases). +The [command summary](#command-summary) section allows you to quickly scan all the commands if you are an experienced user attempting to discover a specific command. -1. Copy the file to the folder you want to use as the _home folder_ for your AddressBook. +Look up the feature in the [Table of Contents](#table-of-contents) and navigate to the appropriate section to obtain a detailed description of Duke Driver's features. +Each section includes a thorough explanation of the function, its command syntax, some examples of potential applications, and information on expected results. -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) +If you are a first-time user, you can begin by reading the [Quick Start](#quick-start) section! -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: - * `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. +## Quick start - * `delete 3` : Deletes the 3rd contact shown in the current list. +1. Ensure you have Java `11` or above installed in your Computer. +2. Download the latest `dukeDriver.jar` in the latest release from [here](https://github.com/AY2223S2-CS2103-F11-2/tp/releases). +3. Copy the file to the folder you want to use as the _home folder_ for your Duke Driver. +4. Open a command terminal, `cd` into the folder you put the jar file in, and use the `java -jar dukeDriver.jar` command to run the application.
+ A GUI similar to the below should appear in a few seconds. Note how the app may contain some sample data.
+ ![Ui](images/Ui.png) +5. Type the command in the command box and press Enter to execute it. e.g. typing **`help`** and pressing Enter will open the help window.
+ Some example commands you can try: + * `list` : Lists all contacts. + * `list_job` : Lists all jobs. + * `stats` : Opens statistics window. + * `timetable` : Shows timetable of current week. + * `exit` : Exits the app. +6. Refer to the [Windows and Features Overview](#windows-and-features-overview) and [Features](#features) below for details of each command. - * `clear` : Deletes all contacts. +## Windows and Features Overview +Duke Driver has 5 main windows: **Delivery Job Window** (Main Window), **Customer Window** (Window for customers' address book), **Timetable Window**, **Reminder Window**, **Statistics Window**. - * `exit` : Exits the app. +Moreover, apart from the main **Timetable Window** (i.e. window for Scheduled Jobs), there are also 2 more minor types of window: **Unscheduled Jobs Window** and **Completed Jobs Window**. + +All windows could be accessed from Main Window, simply by using these commands: +1. `list`: Opens **Customer Window**, lists all customers in Address Book, refer to [1. Features related to Customers](#1-features-related-to-customers) for more details. +2. * `timetable` or `timetable_date date/YYYY-mm-DD`: Opens **Timetable Window** of appropriate week, consists of scheduled jobs in that week (i.e. uncompleted jobs with delivery dates and slots). + * `timetable_unscheduled`: Opens **Unscheduled Jobs Window** - list of unscheduled jobs (i.e. jobs without delivery dates and/or (invalid) slot). + * `timetable_completed`: Opens **Completed Jobs Window** - list of completed jobs. + * Refer to [4. Features available for Timetable](#4-features-available-for-timetable) for more details. +3. `stats`: Open **Statistics Window**, refer to [5. Features available for Statistics](#5-features-available-for-statistics) for more details. +4. `list_reminder`: Lists all reminders and opens **Reminder Window**, refer to [3. Features available for Reminders](#3-features-available-for-reminders) for more details. -1. Refer to the [Features](#features) below for details of each command. +Alternatively, you can simply click on the corresponding button to open the window you want. -------------------------------------------------------------------------------------------------------------------- -## Features +# Features
@@ -49,7 +70,7 @@ AddressBook Level 3 (AB3) is a **desktop app for managing contacts, optimized fo e.g. in `add n/NAME`, `NAME` is a parameter which can be used as `add n/John Doe`. * Items in square brackets are optional.
- e.g `n/NAME [t/TAG]` can be used as `n/John Doe t/friend` or as `n/John Doe`. + e.g. `n/NAME [t/TAG]` can be used as `n/John Doe t/friend` or as `n/John Doe`. * Items with `…`​ after them can be used multiple times including zero times.
e.g. `[t/TAG]…​` can be used as ` ` (i.e. 0 times), `t/friend`, `t/friend t/family` etc. @@ -60,43 +81,51 @@ AddressBook Level 3 (AB3) is a **desktop app for managing contacts, optimized fo * If a parameter is expected only once in the command but you specified it multiple times, only the last occurrence of the parameter will be taken.
e.g. if you specify `p/12341234 p/56785678`, only `p/56785678` will be taken. -* Extraneous parameters for commands that do not take in parameters (such as `help`, `list`, `exit` and `clear`) will be ignored.
+* Extraneous parameters for commands that do not take in parameters (such as `help`, `list_job`, `exit` and `clear`) will be ignored.
e.g. if the command specifies `help 123`, it will be interpreted as `help`.
-### Viewing help : `help` - -Shows a message explaning how to access the help page. - -![help message](images/helpMessage.png) - -Format: `help` - +## 1. Features related to Customers +
+:bulb: **Tip:** Command only available from Customer Window. +To access the address book containing all customers from Main Window, please use command `list` to list all customers and open Customer Window. +Alternatively, you can click on `Customers` in menu bar > `Address Book`. +
-### Adding a person: `add` +### 1.1. Adding a person: `add` Adds a person to the address book. Format: `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]…​` -
:bulb: **Tip:** -A person can have any number of tags (including 0) +* Customers' name should only include Latin alphabet and/or numeric characters. +* Phone number must be at least 3 digits long. + +
+:bulb: **Tip:** +1. A person can have any number of tags (including 0). +2. System does not check for duplicate emails. +
+
+:bulb: **Future improvement:** Support for special characters (e.g /, Æ) for name field.
+ 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` -### Listing all persons : `list` +### 1.2. Listing all persons : `list` -Shows a list of all persons in the address book. +Shows a list of all persons in the address book in Customer Window. +Opens Customer Window. Format: `list` -### Editing a person : `edit` +### 1.3. Editing a person : `edit` -Edits an existing person in the address book. +Edits an existing person in Customer Window. Format: `edit INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [t/TAG]…​` @@ -104,16 +133,15 @@ Format: `edit INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [t/TAG]…​` * 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. +* You can remove all the person’s tags by typing `t/` without specifying any tags after it. Examples: * `edit 1 p/91234567 e/johndoe@example.com` Edits the phone number and email address of the 1st person to be `91234567` and `johndoe@example.com` respectively. * `edit 2 n/Betsy Crower t/` Edits the name of the 2nd person to be `Betsy Crower` and clears all existing tags. -### Locating persons by name: `find` +### 1.4. Locating persons by name: `find` -Finds persons whose names contain any of the given keywords. +Finds and lists persons whose names contain any of the given keywords. Format: `find KEYWORD [MORE_KEYWORDS]` @@ -129,7 +157,7 @@ Examples: * `find alex david` returns `Alex Yeoh`, `David Li`
![result for 'find alex david'](images/findAlexDavidResult.png) -### Deleting a person : `delete` +### 1.5. Deleting a person : `delete` Deletes the specified person from the address book. @@ -143,34 +171,367 @@ 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. -### Clearing all entries : `clear` +### 1.6 Clearing all contact entries : `clear` -Clears all entries from the address book. +Clears all contact entries from address book (i.e. in Customer Window). Format: `clear` + +## 2. Features related to Delivery Jobs +
+:bulb: **Tip:** Command only available from Main Window. +
+ +
+:bulb: **Tip:** User can copy the job ID by selecting a job and press `ctrl-c` from the job list. +
+ + +### 2.1. Adding a job: `add_job` + +Adds a delivery job to the delivery job system. + +**Format**: `add_job si/SENDER_ID ri/RECIPIENT_ID earn/EARNING [date/DELIVERY_DATE] [slot/DELIVERY_SLOT]` + +* Adds the job to delivery job system. +* `SENDER_ID` and `RECEIPIENT_ID` **must be valid IDs** (i.e. must exist in address book). +* `SENDER_ID` and `RECEIPIENT_ID` **is case sensitive**. +* Delivery date and slot are optional, however, if specified, they need to be specified together for scheduling (i.e. users are not allowed to specify only delivery date and leave delivery slot empty - and vice versa). +* Delivery date **must be in format YYYY-mm-DD** and **must be a valid date**. +* Delivery slot **must be a positive integer** and valid slots should be within the range from 1 to 5 (example: `slot/1`). + - Slot 1: 10AM - 11AM + - Slot 2: 11AM - 12PM + - Slot 3: 1PM - 2PM + - Slot 4: 2PM - 3PM + - Slot 5: 3PM - 4PM +* Delivery slots must start from 1 (i.e. `slot/1` - delivery slots only start from 10AM), however, could also be larger than 5 (i.e. `slot/6`). +* Delivery slots larger than 5 (outside valid range, i.e. `slot/6`) will be classified as "Extra hours (4PM++)". These delivery slots are still considered invalid slots. +* Earning **must be a double** and could contain more than 2 decimal inputs, Earning cannot be larger than 99999 (validation avaiable as of v1.4). + +Examples: +* `add_job si/ALESAM ri/DAVSAM earn/1.1` +* `add_job si/ALESAM ri/DAVSAM date/2023-04-01 slot/3 earn/20` + +**Alternative**: +Click on `Delivery Job System` in menu bar > `Create Job`. Fill in relevant details and click `Create Job` button. (Click `Cancel` button to stop adding) + +
+:bulb: **Tip:** +1. Description field is only available in GUI mode. +2. Earning field allows for more than 2 decimal inputs. +3. Command constrains applies to GUI mode. +
+ +![Create Job](images/Addjob.png) +### 2.2. Mass importing jobs + +Format: Click on `Delivery Job System` in menu bar > `Import Jobs` > select a file containing all the jobs to be imported + +* File **must be a CSV file**. +* There must be a header row as the first row will be skipped when file is parsed. +* These columns `Recipient` `Sender` `Delivery date` `Delivery slot` `Price` `Recipient` `Recipient Name` `Recipient Phone` `Recipient Email` `Recipient Address` `Recipient Tag` `Sender Name` `Sender Phone` `Sender Email` `Sender Address` `Sender Tag` must exist. +* The optional details may be filled in with "na". e.g. `Sender's Tag` may be "na". +* Tags should be input with a spacing between each tag. e.g. "Customer Nearby", to tag as 2 tags: customer and nearby. +* If recipient/ sender does not already exist in customer address book i.e. new customer, recipient/ sender will also be added into the address book. + +### 2.3. Listing all jobs : `list_job` + +Shows a list of all jobs in the delivery job system in Main Window. + +Format: `list_job` + +![list job](images/listJob.png) + +#### Sorting and filtering job list +Simply click on the corresponding button to sort and filter jobs. + +
+:bulb: **Tip:** Click again for switching between ascending and descending mode. +
+ +![sort filter job](images/listJobSortFilter.png) + +#### View details of a specific job + +
+:bulb: **Future improvement:** Application should remember the hidden state of contact details. +
+ +Simply click on the job card in job list to view its details in the right half of Main Window. + +To hide/un-hide the contact details of the chosen job, simply click on the (Un)Lock Button. + +![hide_contact_detail](images/hideDetail.png) +![unhide_contact_detail](images/unhideDetail.png) + +### 2.4 Edit job : `edit_job` + +Edit a selected job by Index or Job ID. + +Format: `edit_job INDEX [si/SENDER_ID] [ri/RECEIPIENT_ID] [date/DELIVERY_DATE] [slot/DELIVERY_SLOT] [earn/EARNING] [done/t | f]` + +or `edit_job ji/JOB_ID [si/SENDER_ID] [ri/RECEIPIENT_ID] [date/DELIVERY_DATE] [slot/DELIVERY_SLOT] [earn/EARNING] [done/t | f]` + +* Existing values will be overwritten by the input values. +* At least one of the optional fields should be provided. +* The optional field `done/t | f` indicates whether the job should be marked as completed or uncompleted. +* Edits the delivery job at the specified `INDEX` or `JOB_ID` provided by user. +* The index refers to the index number shown in the displayed job list. +* The index **must be a positive integer** 1, 2, 3, …​ +* Editing a completed job will reset the job to pending state. +* Refer to `add_job` for argument constrains. + +Examples: +* `edit_job 1 slot/4` +* `edit_job 1 done/t` +* `edit_job 1 date/2023-03-01` +* `edit_job ji/BECHE8833A si/ALESAM ri/DAVSAM date/2023-04-01 slot/3 earn/20` + +**Alternative**: +Select the delivery job to view its detail > Click on the middle pen button ![pen](images/penbutton.png) on top right hand corner > Make changes > Click on `Edit Job` button + + +### 2.5 Find job : `find_job` + +Finds all jobs which contain any of the specified keywords and displays them as a list with index numbers. + +Format: `find_job [ji/JOB_ID] [si/SENDER_ID] [ri/RECIPIENT_ID] [date/DELIVER_DATE] [slot/DELIVERY_SLOT] [earn/EARNING] [done/ t | f]` + +* There can be multiple keywords. +* Keywords are case-sensitive. +* The optional field `done/t | f` indicates whether the job should be marked as completed or uncompleted. +* At least one of the optional fields must be provided. +* Refer to `list_job` for argument constrains. + +Examples: +* `find_job ji/BECHE8833A` +* `find_job date/2023-03-01 slot/4` +* `find_job ji/BECHE8833A si/ALESAM ri/DAVSAM date/2023-03-01 slot/3 earn/20` + +### 2.6 Delete job : `delete_job` + +Deletes the job identified by the job ID. Support `del` key in job list by selecting in job list. + +Format: `delete_job JOB_ID` + +
+:bulb: **Tip:** User can copy the job ID by selecting a job and press `ctrl-c` from the job list. +
+ +* The job ID must be **valid**. + +Example: +* `delete_job ALBE29E66F` + +**Alternative**: +Double-lick on the delivery job you want to delete to view its detail > Click on the right bin button ![delete](images/deletebutton.png) on top right hand corner + +### 2.7 Mark job as completed/uncompleted : `com_job` / `uncom_job` + +Marks the job identified by the job ID as completed or uncompleted. + +Format: `com_job JOB_ID` / `uncom_job JOB_ID` + +* The job ID **must be valid**. + +
+:bulb: **Tip:** User can copy the job ID by selecting a job and press `ctrl-c` from the job list. +
+ + +**Alternative 1**: +Scroll to the job and click on the circle on the left, which a tick will appear in to show completion. Click again to uncomplete the job. +![checked](images/Checked.png) + +**Alternative 2**: +Select the delivery job to view its details > Click on the left tick button ![complete](images/completebutton.png) on top right hand corner. Click on the button again to uncomplete job. + + +Example: +* `com_job ALBE29E66F` +* `uncom_job ALBE29E66F` + + +## 3. Features available for Reminders + +
+:bulb: **Tip:** Command only available from Main Window. +
+ +### 3.1. Listing all reminders : `list_reminder` + +Shows a list of all reminders in Reminder Window. + +Opens Reminder Window. + +Format: `list_reminder` + +![Reminder List Window](images/reminderListWindow.png) + +### 3.2. Adding a reminder : `add_reminder` + +Adds a reminder into Duke Driver. + +Format: `add_reminder [d/DESCRIPTION] time/YYYY-MM-DD HH:mm` + +* Adds a reminder with the specified `DESCRIPTION` and `time`. +* User will be notified at the specified `time`. +* The reminder will be reminded from the date time specified in `time/YYYY-MM-DD HH:mm`. +* If not snoozed, the reminder notification will refresh itself and pop up every 1 minute. +* Date must be valid. +* `DESCRIPTION` can be left empty (optional). +* `DESCRIPTION` is limited to 50 characters including space. + +Examples: +* `add_reminder` followed by `d/Submit homework time/2023-12-12 12:00` adds a reminder that will remind the user to submit their homework. The reminder will occur at 12pm, 12 December 2023 and user will be notified at such timing. + +### 3.3. Deleting a reminder : `delete_reminder` + +Deletes a reminder in Duke Driver. + +Format: `delete_reminder INDEX` + +* Deletes the reminder at the specified `INDEX`. +* The index refers to the index number shown beside the reminder. +* The index **must be a positive integer** 1, 2, 3, …​ + +Examples: +* `list_reminder` followed by `delete_reminder 2` deletes the 2nd reminder in the address book. + + +## 4. Features available for Timetable +
+:bulb: **Tip:** Command only available from Main Window. +
+ +### 4.1. Showing timetable : `timetable` + +Shows timetable of jobs, with the week shown being current week (LocalDate.now()). + +Opens Timetable Window. + +Format: `timetable` + +Alternative: Click on `Timetable` in menu bar > `Scheduled Jobs` +![timetable](images/Timetable.png) + +### 4.2. Showing timetable of week containing specific date: `timetable_date` +
:bulb: **Tip:** +Asides from general commands (`help`, `exit`), this is the only command that Timetable Window can access +
+ +Shows timetable of specific week containing a specific date. + +Opens Timetable Window. + +Format: `timetable_date date/YYYY-mm-DD` + +* Shows timetable of the week containing the given date. +* Date must be valid and must only contain numeric characters and spaces. +* Date must be of format: YYYY-mm-DD. +* Along with `help` and `exit`, this is the one and only "special" command that Timetable Window can access (identical format). + +Examples: +* `timetable_date date/2023-03-16` shows timetable of jobs in week from 13th - 19th March 2023. + +### 4.3. Showing list of completed jobs: `timetable_completed` +Shows list of completed jobs, sorted in increasing date and decreasing earning order. + +Format: `timetable_completed` + +Alternative: Click on `Timetable` in menu bar > `Completed Jobs` +![completed jobs](images/Completedjobs.png) + +### 4.4. Showing list of unscheduled jobs: `timetable_unscheduled` +Shows list of unscheduled jobs (i.e. jobs with no delivery dates and/or (invalid) slots). +Jobs are sorted in increasing date and decreasing earning order. + +Format: `timetable_unscheduled` + +Alternative: Click on `Timetable` in menu bar > `Unscheduled Jobs` +![Unscheduled jobs](images/Unscheduledjobs.png) + +## 5. Features available for Statistics + +
+:bulb: **Tip:** Command only available from Main Window. +
+ +### 5.1. Showing Statistics : `stats` +![Statistics](images/Statistics.png) + +Opens Statistics Window. + +Shows a summary of statistics related to the jobs in the job list +* Total number of jobs in the job list +* Total earnings from all jobs in the job list +* Total number of completed jobs in the job list +* Total number of pending jobs in the job list + +Similar statistics are shown for jobs in the previous week + + +## Other features + +### Viewing help : `help` + +Shows a message explaining how to access the help page. + +![help message](images/helpMessage.png) + +Format: `help` + +* Command also available for Timetable and Customer Window (identical format). + +Alternative: Click "Help" in menu bar > "Help" + +
:bulb: **Tip:** +The `help` command also applies for **Timetable Windows** and **Customer Window** +
+ +### Notifications + +Display notifications for reminders and upcoming scheduled jobs. Click on the respective notification body for +more information. + +![reminder notification](images/reminderNotification.png) + +![schedule notification](images/scheduleNotification.png) + +
:bulb: **Alert:** +This is not a command! +
+ ### Exiting the program : `exit` -Exits the program. +Exits the program or current window. Format: `exit` +* Command also available for Timetable and Customer Window (identical format). + +Alternative: Click "File" in menu bar > "Exit" + +
:bulb: **Tip:** +The `exit` command and alternative (GUI mode) also apply for **Timetable Windows** and **Customer Window**. +
+ ### Saving the data -AddressBook data are saved in the hard disk automatically after any command that changes the data. There is no need to save manually. +Duke Driver data are saved in the hard disk automatically after any command that changes the data. There is no need to save manually. ### Editing the data file -AddressBook data are saved as a JSON file `[JAR file location]/data/addressbook.json`. Advanced users are welcome to update data directly by editing that data file. +Duke Driver data are saved as a JSON file `[JAR file location]/data/addressbook.json` and `[JAR file location]/data/deliveryjobsystem.json`. Advanced users are welcome to update data directly by editing that data file. + +More specifically, Customers' contacts and Reminder List is saved in `[JAR file location]/data/addressbook.json`, whereas delivery job list is saved in `[JAR file location]/data/deliveryjobsystem.json`.
:exclamation: **Caution:** -If your changes to the data file makes its format invalid, AddressBook will discard all data and start with an empty data file at the next run. +If your changes to the data file makes its format invalid, Delivery Job System and Address Book will discard all data and start with an empty data file at the next run. +* Invalid sender, recipient ID, slot however will not clear the job system, user can use the edit function to correct the job detail.
-### Archiving data files `[coming in v2.0]` - -_Details coming soon ..._ - -------------------------------------------------------------------------------------------------------------------- ## FAQ @@ -181,13 +542,35 @@ _Details coming soon ..._ -------------------------------------------------------------------------------------------------------------------- ## 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` +**:information_source: Notes about the command information:**
+* Commands that start with *(C)* could only be accessed from Customer Window +* Commands that start with *(M)* could only be accessed from Main Window +* Commands that start with *(T)* could only be accessed from Timetable Window +* Commands that start with *(B)* could be accessed from both Main and Customer Window +* Commands that start with *(A)* could be accessed from all 3 windows: Main, Customer and Timetable Window. + + +| Action | Format, Examples | +|--------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| ***(C)* Add Customer** | `list`
**OR**
Click on `Customers` in menu bar > `Address Book`.

Then, input `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` | +| ***(C)* Delete Customer** | `list`
**OR**
Click on `Customers` in menu bar > `Address Book`.

Then, input `delete INDEX`
e.g., `delete 3` | +| ***(C)* Edit Customer details** | `list`
**OR**
Click on `Customers` in menu bar > `Address Book`.

Then, input `edit INDEX [n/NAME] [p/PHONE_NUMBER] [e/EMAIL] [a/ADDRESS] [t/TAG]…​`

e.g.,`edit 2 n/James Lee e/jameslee@example.com` | +| ***(C)* Find Customer** | `list`
**OR**
Click on `Customers` in menu bar > `Address Book`.

Then, input `find KEYWORD [MORE_KEYWORDS]`
e.g., `find James Jake` | +| ***(C)* Clear all contact entries in Customer Window** | `list`
**OR**
Click on `Customers` in menu bar > `Address Book`.

Then, input `clear` | +| ***(B)* List Customer** | `list`
**OR**
Click on `Customers` in menu bar > `Address Book` then input `list`. | +| ***(M)* Add Job** | `add_job si/SENDER_ID ri/RECIPIENT_ID earn/EARNING [date/DELIVERY_DATE] [slot/DELIVERY_SLOT]`
e.g., `add_job si/ALE874 ri/DAV910 date/2023-03-01 slot/3 earn/20`

**OR**
**Alternative**: Click on `Delivery Job System` in menu bar > `Create Job` > Specify job details > Click on `Create Job`. | +| ***(M)* Import Jobs** | Click on `Delivery Job System` in menu bar > `Import Jobs` > select CSV file containing jobs to be imported > `open` | +| ***(M)* List Job** | `list_job` | +| ***(M)* Edit Job** | `edit_job INDEX [si/SENDER_ID] [ri/RECIPIENT_ID] [date/DELIVERY_DATE] [slot/DELIVERY_SLOT] [earn/EARNING] [done/t OR f]`

OR `edit_job ji/JOB_ID [si/SENDER_ID] [ri/RECIPIENT_ID] [date/DELIVERY_DATE] [slot/DELIVERY_SLOT] [earn/EARNING] [done/t OR f]`

e.g., `edit_job 1 slot/4`

**OR**
Click on the specific delivery job > Click on the middle pen button ![pen](images/penbutton.png) on top right hand corner > Make changes > Click on `Edit Job` button. | +| ***(M)* Find Job** | `find_job [ji/JOB_ID] [si/SENDER_ID] [ri/RECIPIENT_ID] [date/DELIVER_DATE] [slot/DELIVERY_SLOT] [earn/EARNING] [done/t OR f]`

e.g., `find_job si/ALE874` | +| ***(M)* Delete Job** | `delete_job JOB_ID`

e.g., `delete_job ALBE29E66F`

**OR**
**Alternative:** Click on the delivery job > Click on the right bin button ![delete](images/deletebutton.png) on top right hand corner. | +| ***(M)* (Un)Complete Job** | `com_job JOB_ID` / `uncom_job JOB_ID`

e.g.,`com_job ALBE29E66F` / `uncom_job ALBE29E66F`

**OR**
**Alternative 1:** Scroll to the job and click on the circle on the left, which a tick will appear in to show completion. Click again to mark job as uncompleted.

**OR**
**Alternative 2:** Click on the specific delivery job > Click on the left tick button ![complete](images/completebutton.png) on top right hand corner. Click on the button again to uncomplete job. | +| ***(M)* List reminder** | `list_reminder` | +| ***(M)* Add reminder** | `add_reminder [d/DESCRIPTION] time/YYY-MM-DD HH:mm`
e.g.,`add_reminder d/Submit homework time/2023-12-12 12:00` | +| ***(M)* Delete reminder** | `delete_reminder INDEX`
e.g., `delete_reminder 3` | +| ***(M)* Show Timetable** | `timetable`
**OR**
Click on `Timetable` in menu bar > `Scheduled Jobs` | +| ***(M) (T)* Show Timetable of Specific Week** | `timetable_date date/YYYY-mm-DD`
**OR**
Click on `Timetable` in menu bar > `Scheduled Jobs` > input `timetable_date date/YYYY-mm-DD`
e.g., `timetable_date date/2023-03-30` | +| ***(M)* Show List of Completed Jobs** | `timetable_completed`
**OR**
Click on `Timetable` in menu bar > `Completed Jobs` | +| ***(M)* Show List of Unscheduled Jobs** | `timetable_unscheduled`
**OR**
Click on `Timetable` in menu bar > `Unscheduled Jobs` | +| ***(A)* Help** | `help`
**OR**
Click on `Help` in menu bar > `Help` | +| ***(A)* Exit** | `exit`
**OR**
Click on `File` in menu bar > `Exit` | diff --git a/docs/_config.yml b/docs/_config.yml index 6bd245d8f4e..306e791e6f8 100644 --- a/docs/_config.yml +++ b/docs/_config.yml @@ -1,4 +1,4 @@ -title: "AB-3" +title: "Duke Driver" theme: minima header_pages: @@ -8,7 +8,7 @@ header_pages: markdown: kramdown -repository: "se-edu/addressbook-level3" +repository: "AY2223S2-CS2103-F11-2/tp" github_icon: "images/github-icon.png" plugins: diff --git a/docs/_sass/minima/_base.scss b/docs/_sass/minima/_base.scss index 0d3f6e80ced..68d8304c9b1 100644 --- a/docs/_sass/minima/_base.scss +++ b/docs/_sass/minima/_base.scss @@ -288,7 +288,7 @@ table { text-align: center; } .site-header:before { - content: "AB-3"; + content: "Duke Driver"; font-size: 32px; } } diff --git a/docs/diagrams/DeleteDeliveryJobSequenceDiagram.puml b/docs/diagrams/DeleteDeliveryJobSequenceDiagram.puml new file mode 100644 index 00000000000..78b50c0b3d2 --- /dev/null +++ b/docs/diagrams/DeleteDeliveryJobSequenceDiagram.puml @@ -0,0 +1,74 @@ +@startuml +!include style.puml + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":DukeDriverParser" as DukeDriverParser LOGIC_COLOR +participant ":DeleteDeliveryJobCommandParser" as DeleteDeliveryJobCommandParser LOGIC_COLOR +participant "d:DeleteDeliveryJobCommand" as DeleteDeliveryJobCommand LOGIC_COLOR +participant ":CommandResult" as CommandResult LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant ":Model" as Model MODEL_COLOR +end box + +[-> LogicManager : execute("delete_job ABCDEF", g -> !g.equals(CommandGroup.PERSON)) +activate LogicManager + +activate DukeDriverParser + + + + LogicManager -> DukeDriverParser : parse("delete_job ABCDEF") + create DeleteDeliveryJobCommandParser + DukeDriverParser -> DeleteDeliveryJobCommandParser + activate DeleteDeliveryJobCommandParser + + DeleteDeliveryJobCommandParser --> DukeDriverParser + deactivate DeleteDeliveryJobCommandParser + + DukeDriverParser -> DeleteDeliveryJobCommandParser : parse("ABCDEF") + activate DeleteDeliveryJobCommandParser + + create DeleteDeliveryJobCommand + DeleteDeliveryJobCommandParser -> DeleteDeliveryJobCommand + activate DeleteDeliveryJobCommand + + DeleteDeliveryJobCommand --> DeleteDeliveryJobCommandParser : d + deactivate DeleteDeliveryJobCommand + + DeleteDeliveryJobCommandParser --> DukeDriverParser : d + deactivate DeleteDeliveryJobCommandParser + 'Hidden arrow to position the destroy marker below the end of the activation bar. + DeleteDeliveryJobCommandParser -[hidden]-> DukeDriverParser + destroy DeleteDeliveryJobCommandParser + + DukeDriverParser --> LogicManager : d + deactivate DukeDriverParser + + LogicManager -> DeleteDeliveryJobCommand : execute() + activate DeleteDeliveryJobCommand + + DeleteDeliveryJobCommand -> Model : deleteDeliveryJob("ABCDEF") + activate Model + + Model --> DeleteDeliveryJobCommand + deactivate Model + + create CommandResult + DeleteDeliveryJobCommand -> CommandResult + activate CommandResult + + CommandResult --> DeleteDeliveryJobCommand + deactivate CommandResult + + DeleteDeliveryJobCommand --> LogicManager : result + deactivate DeleteDeliveryJobCommand + + [<--LogicManager + deactivate LogicManager + + + +@enduml diff --git a/docs/diagrams/DeleteSequenceDiagram.puml b/docs/diagrams/DeleteSequenceDiagram.puml index 1dc2311b245..f9dbfa85d38 100644 --- a/docs/diagrams/DeleteSequenceDiagram.puml +++ b/docs/diagrams/DeleteSequenceDiagram.puml @@ -3,7 +3,7 @@ box Logic LOGIC_COLOR_T1 participant ":LogicManager" as LogicManager LOGIC_COLOR -participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR +participant ":DukeDriverParser" as DukeDriverParser LOGIC_COLOR participant ":DeleteCommandParser" as DeleteCommandParser LOGIC_COLOR participant "d:DeleteCommand" as DeleteCommand LOGIC_COLOR participant ":CommandResult" as CommandResult LOGIC_COLOR @@ -16,17 +16,17 @@ end box [-> LogicManager : execute("delete 1") activate LogicManager -LogicManager -> AddressBookParser : parseCommand("delete 1") -activate AddressBookParser +LogicManager -> DukeDriverParser : parseCommand("delete 1") +activate DukeDriverParser create DeleteCommandParser -AddressBookParser -> DeleteCommandParser +DukeDriverParser -> DeleteCommandParser activate DeleteCommandParser -DeleteCommandParser --> AddressBookParser +DeleteCommandParser --> DukeDriverParser deactivate DeleteCommandParser -AddressBookParser -> DeleteCommandParser : parse("1") +DukeDriverParser -> DeleteCommandParser : parse("1") activate DeleteCommandParser create DeleteCommand @@ -36,14 +36,14 @@ activate DeleteCommand DeleteCommand --> DeleteCommandParser : d deactivate DeleteCommand -DeleteCommandParser --> AddressBookParser : d +DeleteCommandParser --> DukeDriverParser : d deactivate DeleteCommandParser 'Hidden arrow to position the destroy marker below the end of the activation bar. -DeleteCommandParser -[hidden]-> AddressBookParser +DeleteCommandParser -[hidden]-> DukeDriverParser destroy DeleteCommandParser -AddressBookParser --> LogicManager : d -deactivate AddressBookParser +DukeDriverParser --> LogicManager : d +deactivate DukeDriverParser LogicManager -> DeleteCommand : execute() activate DeleteCommand diff --git a/docs/diagrams/EditDeliveryJobSequenceDiagram.puml b/docs/diagrams/EditDeliveryJobSequenceDiagram.puml new file mode 100644 index 00000000000..1b348a5b0ef --- /dev/null +++ b/docs/diagrams/EditDeliveryJobSequenceDiagram.puml @@ -0,0 +1,108 @@ +@startuml +!include style.puml + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":DukeDriverParser" as DukeDriverParser LOGIC_COLOR +participant ":EditDeliveryJobCommandParser" as EditDeliveryJobCommandParser LOGIC_COLOR +participant ":EditDeliveryJobDescriptor" as EditDeliveryJobDescriptor LOGIC_COLOR +participant "d:EditDeliveryJobCommand" as EditDeliveryJobCommand LOGIC_COLOR +participant ":CommandResult" as CommandResult LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant "toEdit:DeliveryJob.Builder" as DeliveryJob.Builder MODEL_COLOR +participant ":DeliveryJob" as DeliveryJob MODEL_COLOR +end box + +[-> LogicManager : execute("edit_job ji/ABCXX slot/1") +activate LogicManager + +LogicManager -> DukeDriverParser : parseCommand("edit_job ji/ABCXX slot/1") +activate DukeDriverParser + +create EditDeliveryJobCommandParser +DukeDriverParser -> EditDeliveryJobCommandParser +activate EditDeliveryJobCommandParser + +EditDeliveryJobCommandParser --> DukeDriverParser +deactivate EditDeliveryJobCommandParser + +DukeDriverParser -> EditDeliveryJobCommandParser : parse("ji/ABCXX slot/1") +activate EditDeliveryJobCommandParser + +create EditDeliveryJobDescriptor +EditDeliveryJobCommandParser -> EditDeliveryJobDescriptor +activate EditDeliveryJobDescriptor + +EditDeliveryJobDescriptor --> EditDeliveryJobCommandParser +deactivate EditDeliveryJobDescriptor + +EditDeliveryJobCommandParser -> EditDeliveryJobDescriptor: setJobId("ABCXX") +activate EditDeliveryJobDescriptor +EditDeliveryJobDescriptor --> EditDeliveryJobCommandParser +deactivate EditDeliveryJobDescriptor + +EditDeliveryJobCommandParser -> EditDeliveryJobDescriptor: setDeliverySlot("1") +activate EditDeliveryJobDescriptor +EditDeliveryJobDescriptor --> EditDeliveryJobCommandParser +deactivate EditDeliveryJobDescriptor + +create EditDeliveryJobCommand +EditDeliveryJobCommandParser -> EditDeliveryJobCommand +activate EditDeliveryJobCommand + +EditDeliveryJobCommand --> EditDeliveryJobCommandParser : d +deactivate EditDeliveryJobCommand + +EditDeliveryJobCommandParser --> DukeDriverParser : d +deactivate EditDeliveryJobCommandParser +'Hidden arrow to position the destroy marker below the end of the activation bar. +EditDeliveryJobCommandParser -[hidden]-> DukeDriverParser +destroy EditDeliveryJobCommandParser + +DukeDriverParser --> LogicManager : d +deactivate DukeDriverParser + +LogicManager -> EditDeliveryJobCommand : execute() +activate EditDeliveryJobCommand + +Create DeliveryJob.Builder +EditDeliveryJobCommand -> DeliveryJob.Builder : Builder() +activate DeliveryJob.Builder +DeliveryJob.Builder --> EditDeliveryJobCommand +deactivate DeliveryJob.Builder +EditDeliveryJobCommand -> DeliveryJob.Builder : setJobId("ABCXX") +activate DeliveryJob.Builder +DeliveryJob.Builder --> EditDeliveryJobCommand +deactivate DeliveryJob.Builder +EditDeliveryJobCommand -> DeliveryJob.Builder : setSlot("1") +activate DeliveryJob.Builder +DeliveryJob.Builder --> EditDeliveryJobCommand +deactivate DeliveryJob.Builder +EditDeliveryJobCommand -> DeliveryJob.Builder : build() +activate DeliveryJob.Builder +DeliveryJob.Builder --> EditDeliveryJobCommand : toEdit +deactivate DeliveryJob.Builder + +EditDeliveryJobCommand -> DeliveryJob : setDeliveryJob(toEdit) +activate DeliveryJob + +DeliveryJob --> EditDeliveryJobCommand +deactivate DeliveryJob + +destroy DeliveryJob.Builder + +create CommandResult +EditDeliveryJobCommand -> CommandResult +activate CommandResult + +CommandResult --> EditDeliveryJobCommand +deactivate CommandResult + +EditDeliveryJobCommand --> LogicManager : result +deactivate EditDeliveryJobCommand + +[<--LogicManager +deactivate LogicManager +@enduml diff --git a/docs/diagrams/ModelClassDiagram.puml b/docs/diagrams/ModelClassDiagram.puml index 4439108973a..91c8a07ecfc 100644 --- a/docs/diagrams/ModelClassDiagram.puml +++ b/docs/diagrams/ModelClassDiagram.puml @@ -20,6 +20,16 @@ Class Name Class Phone Class Tag +Class ReminderList +Class Reminder + +Class "<>\nReadOnlyDeliveryJobSystem" as ReadOnlyDeliveryJobSystem +Class DeliveryJobSystem +Class UniqueDeliveryJobList +Class DeliveryJob +Class DeliveryDate +Class DeliverySlot +Class Earning } Class HiddenOutside #FFFFFF @@ -28,10 +38,14 @@ HiddenOutside ..> Model AddressBook .up.|> ReadOnlyAddressBook ModelManager .up.|> Model -Model .right.> ReadOnlyUserPrefs -Model .left.> ReadOnlyAddressBook +Model ..> ReadOnlyUserPrefs +Model ..> ReadOnlyAddressBook +Model ..> ReadOnlyDeliveryJobSystem + ModelManager -left-> "1" AddressBook ModelManager -right-> "1" UserPrefs +ModelManager --> "1" DeliveryJobSystem + UserPrefs .up.|> ReadOnlyUserPrefs AddressBook *--> "1" UniquePersonList @@ -42,9 +56,22 @@ Person *--> Email Person *--> Address Person *--> "*" Tag +AddressBook *--> "1" ReminderList +ReminderList --> "~*" Reminder + Name -[hidden]right-> Phone Phone -[hidden]right-> Address Address -[hidden]right-> Email ModelManager -->"~* filtered" Person +ModelManager -->"~* filtered" DeliveryJob + +DeliveryJobSystem .up.|> ReadOnlyDeliveryJobSystem +DeliveryJobSystem *--> "1" UniqueDeliveryJobList +UniqueDeliveryJobList --> "~* all" DeliveryJob +DeliveryJob *--> DeliveryDate +DeliveryJob *--> DeliverySlot +DeliveryJob *--> Earning +DeliveryJob *--> "Sender" Person +DeliveryJob *--> "Recipient" Person @enduml diff --git a/docs/diagrams/NotificationSequenceDiagram.puml b/docs/diagrams/NotificationSequenceDiagram.puml new file mode 100644 index 00000000000..03fd965d0c8 --- /dev/null +++ b/docs/diagrams/NotificationSequenceDiagram.puml @@ -0,0 +1,69 @@ +@startuml +!include style.puml + +box UI UI_COLOR_T1 +participant ":UI" as UI UI_COLOR +end box + +box Notification #FFCC80 +participant "notificationManager:NotificationManager" as NotificationManager #F57C00 +end box + +box BackgroundNotification #DCE775 +participant ":BackgroundNotificationScheduler" as BackgroundNotificationScheduler #9E9D24 +end box + +box Timer #BA68C8 +participant ":Timer" as Timer1 #7B1FA2 +participant ":Timer" as Timer2 #7B1FA2 +end box + +box TimerTask #EF9A9A +participant ":BackgroundReminderTask" as BackgroundReminderTask #E53935 +participant ":BackgroundScheduleTask" as BackgroundScheduleTask #E53935 +end box + + +create NotificationManager +UI-> NotificationManager +activate NotificationManager + +NotificationManager -> NotificationManager : checkReminderList() & display notification +NotificationManager -> NotificationManager : checkNowSchedule() & display notification + +create BackgroundNotificationScheduler +UI -> BackgroundNotificationScheduler +activate BackgroundNotificationScheduler + + +create Timer1 +BackgroundNotificationScheduler -> Timer1 +activate Timer1 + +create Timer2 +BackgroundNotificationScheduler -> Timer2 +activate Timer2 +deactivate BackgroundNotificationScheduler + +loop every minute +create BackgroundReminderTask +Timer1 -> BackgroundReminderTask : run() +deactivate Timer1 +activate BackgroundReminderTask +BackgroundReminderTask -> NotificationManager : checkReminderList() & display notification +deactivate BackgroundReminderTask + +end loop + +loop every 20 minutes before next schedule slot +create BackgroundScheduleTask +Timer2 -> BackgroundScheduleTask : run() +activate BackgroundScheduleTask +BackgroundScheduleTask -> NotificationManager : checkNextSchedule() & display notification +deactivate BackgroundScheduleTask + + +deactivate Timer2 +end loop + +@enduml diff --git a/docs/diagrams/StatisticsSequenceDiagram.puml b/docs/diagrams/StatisticsSequenceDiagram.puml new file mode 100644 index 00000000000..63d739093b3 --- /dev/null +++ b/docs/diagrams/StatisticsSequenceDiagram.puml @@ -0,0 +1,64 @@ +@startuml +!include style.puml + +box UI UI_COLOR_T1 +participant ":UI" as UI UI_COLOR +end box + +box Logic LOGIC_COLOR_T1 +participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR +participant "s:StatisticsCommand" as StatisticsCommand LOGIC_COLOR +participant "l:logicManager" as LogicManager LOGIC_COLOR +participant "sw: StatisticsWindow" as StatisticWindow LOGIC_COLOR +participant "sl: StatisticItemList" as StatisticItemList LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant ":Model" as Model MODEL_COLOR +end box + + +UI -> AddressBookParser +activate AddressBookParser + +create StatisticsCommand +AddressBookParser -> StatisticsCommand +activate StatisticsCommand +deactivate AddressBookParser + + + + +StatisticsCommand --> StatisticWindow +deactivate StatisticsCommand +activate StatisticWindow + + + +StatisticWindow -> LogicManager :getFilteredDeliveryJobList() +activate LogicManager +LogicManager -->StatisticWindow :ObservableList list +deactivate LogicManager + +activate Model +StatisticWindow -> Model : getTotalJob(list) +StatisticWindow-> Model : getTotalEarnings(list) +StatisticWindow -> Model : getTotalCompleted(list) +StatisticWindow -> Model : getTotalPending(list) + + +Model --> StatisticWindow: stats +deactivate Model + +StatisticWindow -> StatisticItemList :newStatisticItemList() +activate StatisticItemList +StatisticItemList -> StatisticItemList :addStats(stats) +StatisticItemList -> StatisticItemList :printStats() + +StatisticItemList --> UI +deactivate StatisticItemList +deactivate StatisticWindow + + +[<--UI +@enduml diff --git a/docs/diagrams/StorageClassDiagram.puml b/docs/diagrams/StorageClassDiagram.puml index 760305e0e58..2e67bd053b3 100644 --- a/docs/diagrams/StorageClassDiagram.puml +++ b/docs/diagrams/StorageClassDiagram.puml @@ -20,6 +20,14 @@ Class JsonAddressBookStorage Class JsonSerializableAddressBook Class JsonAdaptedPerson Class JsonAdaptedTag +Class JsonAdaptedReminder +} + +package "DeliveryJob Storage" #F4F6F6{ +Class "<>\nDeliveryJobSystemStroage" as DeliveryJobSystemStroage +Class JsonDeliveryJobStorage +Class JsonSerializableDeliveryJob +Class JsonAdaptedDeliveryJob } } @@ -30,14 +38,20 @@ HiddenOutside ..> Storage StorageManager .up.|> Storage StorageManager -up-> "1" UserPrefsStorage StorageManager -up-> "1" AddressBookStorage +StorageManager -up-> "1" DeliveryJobSystemStroage -Storage -left-|> UserPrefsStorage -Storage -right-|> AddressBookStorage +Storage --|> UserPrefsStorage +Storage --|> AddressBookStorage JsonUserPrefsStorage .up.|> UserPrefsStorage JsonAddressBookStorage .up.|> AddressBookStorage +JsonAdaptedReminder .up.|> AddressBookStorage JsonAddressBookStorage ..> JsonSerializableAddressBook JsonSerializableAddressBook --> "*" JsonAdaptedPerson JsonAdaptedPerson --> "*" JsonAdaptedTag +JsonDeliveryJobStorage .up.|> DeliveryJobSystemStroage +JsonDeliveryJobStorage ..> JsonSerializableDeliveryJob +JsonSerializableDeliveryJob --> "*" JsonAdaptedDeliveryJob + @enduml diff --git a/docs/diagrams/TimetableCompletedSequenceDiagram.puml b/docs/diagrams/TimetableCompletedSequenceDiagram.puml new file mode 100644 index 00000000000..44edea28334 --- /dev/null +++ b/docs/diagrams/TimetableCompletedSequenceDiagram.puml @@ -0,0 +1,46 @@ +@startuml +!include style.puml + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":DukeDriverParser" as DukeDriverParser LOGIC_COLOR +participant "t:TimetableCompleteCommand" as TimetableCompleteCommand LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant ":Model" as Model MODEL_COLOR +end box +[-> LogicManager : execute("timetable_completed") +activate LogicManager + +LogicManager -> DukeDriverParser : parseCommand("timetable_completed") +activate DukeDriverParser + +create TimetableCompleteCommand +DukeDriverParser -> TimetableCompleteCommand +activate TimetableCompleteCommand + +TimetableCompleteCommand --> DukeDriverParser : t +deactivate TimetableCompleteCommand + +DukeDriverParser --> LogicManager : t +deactivate DukeDriverParser + +LogicManager -> TimetableCompleteCommand : execute() +activate TimetableCompleteCommand +TimetableCompleteCommand -> Model : updateSortedDeliveryJobList(new SortbyTimeAndEarn()) +TimetableCompleteCommand -> Model : getCompletedDeliveryJobList() +activate Model + + +Model --> TimetableCompleteCommand +deactivate Model + +TimetableCompleteCommand --> LogicManager : result +deactivate TimetableCompleteCommand +TimetableCompleteCommand -[hidden]-> LogicManager : result +destroy TimetableCompleteCommand + +[<--LogicManager +deactivate LogicManager +@enduml diff --git a/docs/diagrams/TimetableDateSequenceDiagram.puml b/docs/diagrams/TimetableDateSequenceDiagram.puml new file mode 100644 index 00000000000..e7093b539b6 --- /dev/null +++ b/docs/diagrams/TimetableDateSequenceDiagram.puml @@ -0,0 +1,67 @@ +@startuml +!include style.puml + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":DukeDriverParser" as DukeDriverParser LOGIC_COLOR +participant ":TimetableDateCommandParser" as TimetableDateCommandParser LOGIC_COLOR +participant "t:TimetableDateCommand" as TimetableDateCommand LOGIC_COLOR +participant ":CommandResult" as CommandResult LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant ":Model" as Model MODEL_COLOR +end box +[-> LogicManager : execute("timetable_date date/2023-03-03") +activate LogicManager + +LogicManager -> DukeDriverParser : parseCommand("timetable_date date/2023-03-03") +activate DukeDriverParser + +create TimetableDateCommandParser +DukeDriverParser -> TimetableDateCommandParser +activate TimetableDateCommandParser + +TimetableDateCommandParser --> DukeDriverParser +deactivate TimetableDateCommandParser + +DukeDriverParser -> TimetableDateCommandParser : parse("2023-03-03") +activate TimetableDateCommandParser + +create TimetableDateCommand +TimetableDateCommandParser -> TimetableDateCommand +activate TimetableDateCommand + +TimetableDateCommand --> TimetableDateCommandParser : t +deactivate TimetableDateCommand + +TimetableDateCommandParser --> DukeDriverParser : t +deactivate TimetableDateCommandParser +'Hidden arrow to position the destroy marker below the end of the activation bar. +TimetableDateCommandParser -[hidden]-> DukeDriverParser +destroy TimetableDateCommandParser + +DukeDriverParser --> LogicManager : t +deactivate DukeDriverParser + +LogicManager -> TimetableDateCommand : execute() + +activate TimetableDateCommand + +TimetableDateCommand -> Model : updateFocusDate(2023-03-03) +TimetableDateCommand -> Model : updateSortedDeliveryJobList(new SortbyTimeAndEarn()) +TimetableDateCommand -> Model : updateSortedDeliveryJobListByDate() +TimetableDateCommand -> Model : updateWeekDeliveryJobList(LocalDate.now()) +activate Model + +Model --> TimetableDateCommand +deactivate Model + +TimetableDateCommand --> LogicManager : result +deactivate TimetableDateCommand +TimetableDateCommand -[hidden]-> LogicManager : result +destroy TimetableDateCommand + +[<--LogicManager +deactivate LogicManager +@enduml diff --git a/docs/diagrams/TimetableSequenceDiagram.puml b/docs/diagrams/TimetableSequenceDiagram.puml new file mode 100644 index 00000000000..319010d77ae --- /dev/null +++ b/docs/diagrams/TimetableSequenceDiagram.puml @@ -0,0 +1,48 @@ +@startuml +!include style.puml + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":DukeDriverParser" as DukeDriverParser LOGIC_COLOR +participant "t:TimetableCommand" as TimetableCommand LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant ":Model" as Model MODEL_COLOR +end box +[-> LogicManager : execute("timetable") +activate LogicManager + +LogicManager -> DukeDriverParser : parseCommand("timetable") +activate DukeDriverParser + +create TimetableCommand +DukeDriverParser -> TimetableCommand +activate TimetableCommand + +TimetableCommand --> DukeDriverParser : t +deactivate TimetableCommand + +DukeDriverParser --> LogicManager : t +deactivate DukeDriverParser + +LogicManager -> TimetableCommand : execute() +activate TimetableCommand +TimetableCommand -> Model : updateFocusDate(LocalDate.now()) +TimetableCommand -> Model : updateSortedDeliveryJobList(new SortbyTimeAndEarn()) +TimetableCommand -> Model : updateSortedDeliveryJobListByDate() +TimetableCommand -> Model : updateWeekDeliveryJobList(LocalDate.now()) +activate Model + + +Model --> TimetableCommand +deactivate Model + +TimetableCommand --> LogicManager : result +deactivate TimetableCommand +TimetableCommand -[hidden]-> LogicManager : result +destroy TimetableCommand + +[<--LogicManager +deactivate LogicManager +@enduml diff --git a/docs/diagrams/TimetableUnscheduledSequenceDiagram.puml b/docs/diagrams/TimetableUnscheduledSequenceDiagram.puml new file mode 100644 index 00000000000..2ae0e999857 --- /dev/null +++ b/docs/diagrams/TimetableUnscheduledSequenceDiagram.puml @@ -0,0 +1,46 @@ +@startuml +!include style.puml + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":DukeDriverParser" as DukeDriverParser LOGIC_COLOR +participant "t:TimetableUnscheduleCommand" as TimetableUnscheduleCommand LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant ":Model" as Model MODEL_COLOR +end box +[-> LogicManager : execute("timetable_unscheduled") +activate LogicManager + +LogicManager -> DukeDriverParser : parseCommand("timetable_unscheduled") +activate DukeDriverParser + +create TimetableUnscheduleCommand +DukeDriverParser -> TimetableUnscheduleCommand +activate TimetableUnscheduleCommand + +TimetableUnscheduleCommand --> DukeDriverParser : t +deactivate TimetableUnscheduleCommand + +DukeDriverParser --> LogicManager : t +deactivate DukeDriverParser + +LogicManager -> TimetableUnscheduleCommand : execute() +activate TimetableUnscheduleCommand +TimetableUnscheduleCommand -> Model : updateSortedDeliveryJobList(new SortbyTimeAndEarn()) +TimetableUnscheduleCommand -> Model : getUnscheduledDeliveryJobList() +activate Model + + +Model --> TimetableUnscheduleCommand +deactivate Model + +TimetableUnscheduleCommand --> LogicManager : result +deactivate TimetableUnscheduleCommand +TimetableUnscheduleCommand -[hidden]-> LogicManager : result +destroy TimetableUnscheduleCommand + +[<--LogicManager +deactivate LogicManager +@enduml diff --git a/docs/diagrams/UiClassDiagram.puml b/docs/diagrams/UiClassDiagram.puml index 95473d5aa19..a1787371c54 100644 --- a/docs/diagrams/UiClassDiagram.puml +++ b/docs/diagrams/UiClassDiagram.puml @@ -8,17 +8,9 @@ package UI <>{ Class "<>\nUi" as Ui Class "{abstract}\nUiPart" as UiPart Class UiManager -Class MainWindow -Class HelpWindow -Class ResultDisplay -Class PersonListPanel -Class PersonCard -Class StatusBarFooter -Class CommandBox -} +Class NotifcationManager +Class XYZWindow -package Model <> { -Class HiddenModel #FFFFFF } package Logic <> { @@ -29,32 +21,12 @@ Class HiddenOutside #FFFFFF HiddenOutside ..> Ui UiManager .left.|> Ui -UiManager -down-> "1" MainWindow -MainWindow *-down-> "1" CommandBox -MainWindow *-down-> "1" ResultDisplay -MainWindow *-down-> "1" PersonListPanel -MainWindow *-down-> "1" StatusBarFooter -MainWindow --> "0..1" HelpWindow - -PersonListPanel -down-> "*" PersonCard +UiManager -down-> "1" XYZWindow +UiManager *-right-> "1" NotifcationManager -MainWindow -left-|> UiPart +XYZWindow -left-|> UiPart -ResultDisplay --|> UiPart -CommandBox --|> UiPart -PersonListPanel --|> UiPart -PersonCard --|> UiPart -StatusBarFooter --|> UiPart -HelpWindow --|> UiPart - -PersonCard ..> Model UiManager -right-> Logic -MainWindow -left-> Logic - -PersonListPanel -[hidden]left- HelpWindow -HelpWindow -[hidden]left- CommandBox -CommandBox -[hidden]left- ResultDisplay -ResultDisplay -[hidden]left- StatusBarFooter +XYZWindow -left-> Logic -MainWindow -[hidden]-|> UiPart @enduml diff --git a/docs/diagrams/UiClassDiagramCompleteWindow.puml b/docs/diagrams/UiClassDiagramCompleteWindow.puml new file mode 100644 index 00000000000..c15aa399248 --- /dev/null +++ b/docs/diagrams/UiClassDiagramCompleteWindow.puml @@ -0,0 +1,26 @@ +@startuml +!include style.puml +skinparam arrowThickness 1.1 +skinparam arrowColor UI_COLOR_T4 +skinparam classBackgroundColor UI_COLOR + +package UI <>{ + +Class CompleteWindow +Class UnscheduledDeliveryJobListPanel +Class DeliveryJobCard + +} + +package Logic <> { +Class HiddenLogic #FFFFFF +} + +Class TimetableWindow +TimetableWindow *-down-> "1" CompleteWindow +CompleteWindow --> Logic + + +CompleteWindow *-down-> "1" UnscheduledDeliveryJobListPanel +UnscheduledDeliveryJobListPanel *-down-> "*" DeliveryJobCard +@enduml diff --git a/docs/diagrams/UiClassDiagramCreateJob.puml b/docs/diagrams/UiClassDiagramCreateJob.puml new file mode 100644 index 00000000000..aaa14a5b517 --- /dev/null +++ b/docs/diagrams/UiClassDiagramCreateJob.puml @@ -0,0 +1,27 @@ +@startuml +!include style.puml +skinparam arrowThickness 1.1 +skinparam arrowColor UI_COLOR_T4 +skinparam classBackgroundColor UI_COLOR + + +Class "Create Job Menu Button" as CreatJobButton + +package JobSystem <>{ + +Class "Add/EditDeliveryJobDialog" as AddDeliveryDialog +Class AddressBookDialog + +} + +package Logic <> { +Class HiddenLogic #FFFFFF +} + +CreatJobButton ..> AddDeliveryDialog + +AddDeliveryDialog --> AddressBookDialog + +AddDeliveryDialog --> Logic + +@enduml diff --git a/docs/diagrams/UiClassDiagramMainWindow.puml b/docs/diagrams/UiClassDiagramMainWindow.puml new file mode 100644 index 00000000000..856b35df763 --- /dev/null +++ b/docs/diagrams/UiClassDiagramMainWindow.puml @@ -0,0 +1,70 @@ +@startuml +!include style.puml +skinparam arrowThickness 1.1 +skinparam arrowColor UI_COLOR_T4 +skinparam classBackgroundColor UI_COLOR + +package UI <>{ + +package MainWindow <> { +Class StatusBarFooter +Class CommandBox +Class ResultDisplay +Class DeliveryJobListPane +Class DeliveryJobDetailPane +Class HelpWindow +Class JobListCard +Class PersonCard +} + +package JobSystem <> { +Class AddDeliveryJobDialog +} + +package AddressBook <> { +Class AddressBookDialog +} + +package Statistics <> { +Class StatisticsWindow +} + +package Timetable <> { +Class TimetableWindow +Class CompleteWindow +Class UnscheduleWindow +} + +package Reminder <> { +Class ReminderListWindow +} + +} + +Class UiManager +UiManager ..> MainWindow + +DeliveryJobDetailPane -[hidden]left- DeliveryJobListPane +DeliveryJobListPane -[hidden]left- HelpWindow +HelpWindow -[hidden]down- CommandBox +CommandBox -[hidden]down- ResultDisplay +ResultDisplay -[hidden]down- StatusBarFooter + +DeliveryJobListPane -down-> "*" JobListCard +DeliveryJobDetailPane -down-> "2" PersonCard + +MainWindow *-down-> "1" JobSystem +MainWindow *-down-> "1" Timetable +MainWindow *--> "1" AddressBook +MainWindow *--> "1" Reminder +MainWindow *-down-> "1" Statistics + +TimetableWindow -[hidden]- CompleteWindow +CompleteWindow -[hidden]- UnscheduleWindow + + + +JobSystem *-down-> "1" AddressBook +Statistics -[hidden]down- Reminder + +@enduml diff --git a/docs/diagrams/UiClassDiagramTimetableWindow.puml b/docs/diagrams/UiClassDiagramTimetableWindow.puml new file mode 100644 index 00000000000..50676440a48 --- /dev/null +++ b/docs/diagrams/UiClassDiagramTimetableWindow.puml @@ -0,0 +1,45 @@ +@startuml +!include style.puml +skinparam arrowThickness 1.1 +skinparam arrowColor UI_COLOR_T4 +skinparam classBackgroundColor UI_COLOR + +package UI <>{ + +package TimetableWindow <> { +Class StatusBarFooter +Class CommandBox +Class ResultDisplay +Class HelpWindow + +} +package TimetableDetailPanel <> { +Class DayOfWeekPanel +Class DayOfMonthPanel +Class WeekJobListPanel +Class DayJobListPanel +Class DayDeliveryJobCard +} + + +} + + +Class MainWindow +MainWindow *-down-> "1" TimetableWindow + +HelpWindow -[hidden]down- CommandBox +CommandBox -[hidden]down- ResultDisplay +ResultDisplay -[hidden]down- StatusBarFooter + +TimetableWindow *-down-> "1" TimetableDetailPanel + + +DayOfWeekPanel -[hidden]down- DayOfMonthPanel +WeekJobListPanel -[hidden]left- DayOfWeekPanel +DayOfMonthPanel -[hidden]left- DayDeliveryJobCard +WeekJobListPanel *-down-> "7" DayJobListPanel +DayJobListPanel *-down-> "*" DayDeliveryJobCard + + +@enduml diff --git a/docs/diagrams/UiClassDiagramUnscheduleWindow.puml b/docs/diagrams/UiClassDiagramUnscheduleWindow.puml new file mode 100644 index 00000000000..a9f9fae8bfd --- /dev/null +++ b/docs/diagrams/UiClassDiagramUnscheduleWindow.puml @@ -0,0 +1,26 @@ +@startuml +!include style.puml +skinparam arrowThickness 1.1 +skinparam arrowColor UI_COLOR_T4 +skinparam classBackgroundColor UI_COLOR + +package UI <>{ + +Class UnscheduleWindow +Class UnscheduledDeliveryJobListPanel +Class DeliveryJobCard + +} + +package Logic <> { +Class HiddenLogic #FFFFFF +} + +Class TimetableWindow +TimetableWindow *-down-> "1" UnscheduleWindow +UnscheduleWindow --> Logic + + +UnscheduleWindow *-down-> "1" UnscheduledDeliveryJobListPanel +UnscheduledDeliveryJobListPanel *-down-> "*" DeliveryJobCard +@enduml diff --git a/docs/diagrams/UiSequenceDiagramCompleteWindow.puml b/docs/diagrams/UiSequenceDiagramCompleteWindow.puml new file mode 100644 index 00000000000..6bcfe70be75 --- /dev/null +++ b/docs/diagrams/UiSequenceDiagramCompleteWindow.puml @@ -0,0 +1,56 @@ +@startuml +!include style.puml + +box UI +participant ":MainWindow" as MainWindow UI_COLOR_T1 +participant ":CompleteWindow" as CompleteWindow UI_COLOR_T3 +participant ":UnscheduledDeliveryJobListPanel" UI_COLOR_T2 +participant ":UnscheduledDeliveryJobListPanel" as UnscheduledDeliveryJobListPanel UI_COLOR_T2 +participant ":DeliveryJobCard" as DeliveryJobCard UI_COLOR +end box + +box Logic LOGIC_COLOR +participant ":Logic" as Logic LOGIC_COLOR_T1 +participant "filteredDeliveryJobList:FilteredList" as FilteredList LOGIC_COLOR_T2 +end box + + +[-> MainWindow : handleCompletedTimetable() +activate MainWindow +MainWindow -> CompleteWindow : fillInnerParts() +activate CompleteWindow + +CompleteWindow -> Logic : getCompletedDeliveryJobList() +activate Logic +Logic --> CompleteWindow + +CompleteWindow -> FilteredList : addListener() +activate FilteredList +FilteredList --> CompleteWindow +deactivate FilteredList + +create UnscheduledDeliveryJobListPanel +CompleteWindow -> UnscheduledDeliveryJobListPanel +activate UnscheduledDeliveryJobListPanel + +create DeliveryJobCard +UnscheduledDeliveryJobListPanel -> DeliveryJobCard +activate DeliveryJobCard +DeliveryJobCard --> UnscheduledDeliveryJobListPanel +deactivate DeliveryJobCard +DeliveryJobCard -[hidden]-> UnscheduledDeliveryJobListPanel +destroy DeliveryJobCard + +UnscheduledDeliveryJobListPanel --> CompleteWindow +deactivate UnscheduledDeliveryJobListPanel +UnscheduledDeliveryJobListPanel -[hidden]-> CompleteWindow +destroy UnscheduledDeliveryJobListPanel + +CompleteWindow --> MainWindow + +[<--MainWindow +deactivate MainWindow + + + +@enduml diff --git a/docs/diagrams/UiSequenceDiagramTimetableWindow.puml b/docs/diagrams/UiSequenceDiagramTimetableWindow.puml new file mode 100644 index 00000000000..cb44f4f8a43 --- /dev/null +++ b/docs/diagrams/UiSequenceDiagramTimetableWindow.puml @@ -0,0 +1,101 @@ +@startuml +!include style.puml + +box UI +participant ":MainWindow" as MainWindow UI_COLOR_T1 +participant ":TimetableWindow" as TimetableWindow UI_COLOR_T2 +participant "detail:TimetableDetailPanel" as TimetableDetailPanel UI_COLOR_T3 +participant "weekDate:DayOfWeekPanel" as DayOfWeekPanel UI_COLOR_T4 +participant "monthDate:DayOfMonthPanel" as DayOfMonthPanel UI_COLOR +participant ":WeekJobListPanel" as WeekJobListPanel UI_COLOR_T2 +end box + +box Logic LOGIC_COLOR +participant ":Logic" as Logic LOGIC_COLOR_T4 +participant "filteredDeliveryJobList:FilteredList" as FilteredList LOGIC_COLOR_T2 +end box + +[-> MainWindow : handleTimetable() + +MainWindow -> TimetableWindow : fillInnerParts() + + +activate TimetableWindow + +create TimetableDetailPanel +TimetableWindow -> TimetableDetailPanel +activate TimetableDetailPanel + + +TimetableWindow -> FilteredList : addListener() +activate FilteredList +FilteredList --> TimetableWindow +deactivate FilteredList + +TimetableDetailPanel -> TimetableDetailPanel : fillInnerParts() +activate TimetableDetailPanel +TimetableDetailPanel -> Logic : getFocusDate() +activate Logic +Logic --> TimetableDetailPanel : focusDate +TimetableDetailPanel -> Logic : updateSortedDeliveryJobListByDate() +Logic --> TimetableDetailPanel +TimetableDetailPanel -> Logic : setWeekDeliveryJobList(focusDate) +Logic --> TimetableDetailPanel + + + +create WeekJobListPanel +TimetableDetailPanel -> WeekJobListPanel + +activate WeekJobListPanel + +WeekJobListPanel -> WeekJobListPanel : addAllPlaceholderJobs(); + + +activate WeekJobListPanel +WeekJobListPanel -> Logic : getDayofWeekJob() +Logic --> WeekJobListPanel +deactivate Logic + + + + +deactivate WeekJobListPanel + +create DayOfMonthPanel +TimetableDetailPanel -> DayOfMonthPanel +activate DayOfMonthPanel +DayOfMonthPanel --> TimetableDetailPanel : monthDate +deactivate DayOfMonthPanel +DayOfMonthPanel -[hidden]-> TimetableDetailPanel : monthDate +destroy DayOfMonthPanel + +create DayOfWeekPanel +TimetableDetailPanel -> DayOfWeekPanel +activate DayOfWeekPanel +DayOfWeekPanel --> TimetableDetailPanel : weekDate +deactivate DayOfWeekPanel +DayOfWeekPanel -[hidden]-> TimetableDetailPanel : weekDate +destroy DayOfWeekPanel + +WeekJobListPanel --> TimetableDetailPanel +deactivate WeekJobListPanel +WeekJobListPanel -[hidden]-> TimetableDetailPanel +destroy WeekJobListPanel +deactivate TimetableDetailPanel + + + + +TimetableDetailPanel --> TimetableWindow : detail +deactivate TimetableDetailPanel +TimetableDetailPanel -[hidden]-> TimetableWindow : detail +destroy TimetableDetailPanel + +TimetableWindow --> MainWindow +deactivate TimetableWindow + +[<--MainWindow +deactivate MainWindow + +@enduml diff --git a/docs/diagrams/UiSequenceDiagramUnscheduledTimetableWindow.puml b/docs/diagrams/UiSequenceDiagramUnscheduledTimetableWindow.puml new file mode 100644 index 00000000000..d0780acb152 --- /dev/null +++ b/docs/diagrams/UiSequenceDiagramUnscheduledTimetableWindow.puml @@ -0,0 +1,56 @@ +@startuml +!include style.puml + +box UI +participant ":MainWindow" as MainWindow UI_COLOR_T1 +participant ":UnscheduleWindow" as UnscheduleWindow UI_COLOR_T3 +participant ":UnscheduledDeliveryJobListPanel" UI_COLOR_T2 +participant ":UnscheduledDeliveryJobListPanel" as UnscheduledDeliveryJobListPanel UI_COLOR_T2 +participant ":DeliveryJobCard" as DeliveryJobCard UI_COLOR +end box + +box Logic LOGIC_COLOR +participant ":Logic" as Logic LOGIC_COLOR_T1 +participant "filteredDeliveryJobList:FilteredList" as FilteredList LOGIC_COLOR_T2 +end box + + +[-> MainWindow : handleUnscheduledTimetable() +activate MainWindow +MainWindow -> UnscheduleWindow : fillInnerParts() +activate UnscheduleWindow + +UnscheduleWindow -> Logic : getUnscheduledDeliveryJobList() +activate Logic +Logic --> UnscheduleWindow + +UnscheduleWindow -> FilteredList : addListener() +activate FilteredList +FilteredList --> UnscheduleWindow +deactivate FilteredList + +create UnscheduledDeliveryJobListPanel +UnscheduleWindow -> UnscheduledDeliveryJobListPanel +activate UnscheduledDeliveryJobListPanel + +create DeliveryJobCard +UnscheduledDeliveryJobListPanel -> DeliveryJobCard +activate DeliveryJobCard +DeliveryJobCard --> UnscheduledDeliveryJobListPanel +deactivate DeliveryJobCard +DeliveryJobCard -[hidden]-> UnscheduledDeliveryJobListPanel +destroy DeliveryJobCard + +UnscheduledDeliveryJobListPanel --> UnscheduleWindow +deactivate UnscheduledDeliveryJobListPanel +UnscheduledDeliveryJobListPanel -[hidden]-> UnscheduleWindow +destroy UnscheduledDeliveryJobListPanel + +UnscheduleWindow --> MainWindow + +[<--MainWindow +deactivate MainWindow + + + +@enduml diff --git a/docs/diagrams/style.puml b/docs/diagrams/style.puml index fad8b0adeaa..cd05e001824 100644 --- a/docs/diagrams/style.puml +++ b/docs/diagrams/style.puml @@ -12,6 +12,7 @@ !define UI_COLOR_T2 #3FC71B !define UI_COLOR_T3 #166800 !define UI_COLOR_T4 #0E4100 +!define UI_COLOR_T5 #00FF00 !define LOGIC_COLOR #3333C4 !define LOGIC_COLOR_T1 #C8C8FA diff --git a/docs/empty.csv b/docs/empty.csv new file mode 100644 index 00000000000..ad239874777 --- /dev/null +++ b/docs/empty.csv @@ -0,0 +1,2 @@ +, +, diff --git a/docs/images/Addjob.png b/docs/images/Addjob.png new file mode 100644 index 00000000000..673e9d555b9 Binary files /dev/null and b/docs/images/Addjob.png differ diff --git a/docs/images/Checked.png b/docs/images/Checked.png new file mode 100644 index 00000000000..81f546d3873 Binary files /dev/null and b/docs/images/Checked.png differ diff --git a/docs/images/Completedjobs.png b/docs/images/Completedjobs.png new file mode 100644 index 00000000000..5598ed0fa59 Binary files /dev/null and b/docs/images/Completedjobs.png differ diff --git a/docs/images/DeleteDeliveryJobSequenceDiagram.png b/docs/images/DeleteDeliveryJobSequenceDiagram.png new file mode 100644 index 00000000000..42a0089368d Binary files /dev/null and b/docs/images/DeleteDeliveryJobSequenceDiagram.png differ diff --git a/docs/images/Dukedeliveryaddressbook.png b/docs/images/Dukedeliveryaddressbook.png new file mode 100644 index 00000000000..e24631ae85f Binary files /dev/null and b/docs/images/Dukedeliveryaddressbook.png differ diff --git a/docs/images/EditDeliveryJobSequenceDiagram.png b/docs/images/EditDeliveryJobSequenceDiagram.png new file mode 100644 index 00000000000..00210907a72 Binary files /dev/null and b/docs/images/EditDeliveryJobSequenceDiagram.png differ diff --git a/docs/images/Inputcommands.png b/docs/images/Inputcommands.png new file mode 100644 index 00000000000..7ca2bc0c178 Binary files /dev/null and b/docs/images/Inputcommands.png differ diff --git a/docs/images/ModelClassDiagram.png b/docs/images/ModelClassDiagram.png index 04070af60d8..66cf0446fcf 100644 Binary files a/docs/images/ModelClassDiagram.png and b/docs/images/ModelClassDiagram.png differ diff --git a/docs/images/NotificationSequenceDiagram.png b/docs/images/NotificationSequenceDiagram.png new file mode 100644 index 00000000000..82de9e079f8 Binary files /dev/null and b/docs/images/NotificationSequenceDiagram.png differ diff --git a/docs/images/Statistics.png b/docs/images/Statistics.png new file mode 100644 index 00000000000..1c4fe64b3d2 Binary files /dev/null and b/docs/images/Statistics.png differ diff --git a/docs/images/StatisticsSequenceDiagram.png b/docs/images/StatisticsSequenceDiagram.png new file mode 100644 index 00000000000..87e439d0c6a Binary files /dev/null and b/docs/images/StatisticsSequenceDiagram.png differ diff --git a/docs/images/StorageClassDiagram.png b/docs/images/StorageClassDiagram.png index 2533a5c1af0..a7ab7ec8057 100644 Binary files a/docs/images/StorageClassDiagram.png and b/docs/images/StorageClassDiagram.png differ diff --git a/docs/images/Timetable.png b/docs/images/Timetable.png new file mode 100644 index 00000000000..0c43a9c1fea Binary files /dev/null and b/docs/images/Timetable.png differ diff --git a/docs/images/TimetableCompletedSequenceDiagram.png b/docs/images/TimetableCompletedSequenceDiagram.png new file mode 100644 index 00000000000..8135e36b84e Binary files /dev/null and b/docs/images/TimetableCompletedSequenceDiagram.png differ diff --git a/docs/images/TimetableDateSequenceDiagram.png b/docs/images/TimetableDateSequenceDiagram.png new file mode 100644 index 00000000000..5493b22ca3c Binary files /dev/null and b/docs/images/TimetableDateSequenceDiagram.png differ diff --git a/docs/images/TimetableSequenceDiagram.png b/docs/images/TimetableSequenceDiagram.png new file mode 100644 index 00000000000..fd5b59e94a2 Binary files /dev/null and b/docs/images/TimetableSequenceDiagram.png differ diff --git a/docs/images/TimetableUnscheduledSequenceDiagram.png b/docs/images/TimetableUnscheduledSequenceDiagram.png new file mode 100644 index 00000000000..54fa458b6aa Binary files /dev/null and b/docs/images/TimetableUnscheduledSequenceDiagram.png differ diff --git a/docs/images/Ui.png b/docs/images/Ui.png index 5bd77847aa2..5eb4ac2b27e 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 785e04dbab4..1e6c2b7e8b0 100644 Binary files a/docs/images/UiClassDiagram.png and b/docs/images/UiClassDiagram.png differ diff --git a/docs/images/UiClassDiagramCompleteWindow.png b/docs/images/UiClassDiagramCompleteWindow.png new file mode 100644 index 00000000000..a49470c00d2 Binary files /dev/null and b/docs/images/UiClassDiagramCompleteWindow.png differ diff --git a/docs/images/UiClassDiagramCreateJob.png b/docs/images/UiClassDiagramCreateJob.png new file mode 100644 index 00000000000..7d39ed8960f Binary files /dev/null and b/docs/images/UiClassDiagramCreateJob.png differ diff --git a/docs/images/UiClassDiagramMainWindow.png b/docs/images/UiClassDiagramMainWindow.png new file mode 100644 index 00000000000..b8508cdd240 Binary files /dev/null and b/docs/images/UiClassDiagramMainWindow.png differ diff --git a/docs/images/UiClassDiagramTimetableWindow.png b/docs/images/UiClassDiagramTimetableWindow.png new file mode 100644 index 00000000000..3cd843d7dcd Binary files /dev/null and b/docs/images/UiClassDiagramTimetableWindow.png differ diff --git a/docs/images/UiClassDiagramUnscheduleWindow.png b/docs/images/UiClassDiagramUnscheduleWindow.png new file mode 100644 index 00000000000..65309a79022 Binary files /dev/null and b/docs/images/UiClassDiagramUnscheduleWindow.png differ diff --git a/docs/images/UiSequenceDiagramCompleteWindow.png b/docs/images/UiSequenceDiagramCompleteWindow.png new file mode 100644 index 00000000000..7adf5f461c2 Binary files /dev/null and b/docs/images/UiSequenceDiagramCompleteWindow.png differ diff --git a/docs/images/UiSequenceDiagramTimetableWindow.png b/docs/images/UiSequenceDiagramTimetableWindow.png new file mode 100644 index 00000000000..5bf7e8bb818 Binary files /dev/null and b/docs/images/UiSequenceDiagramTimetableWindow.png differ diff --git a/docs/images/UiSequenceDiagramUnscheduledTimetableWindow.png b/docs/images/UiSequenceDiagramUnscheduledTimetableWindow.png new file mode 100644 index 00000000000..a37a6c2d84a Binary files /dev/null and b/docs/images/UiSequenceDiagramUnscheduledTimetableWindow.png differ diff --git a/docs/images/Unscheduledjobs.png b/docs/images/Unscheduledjobs.png new file mode 100644 index 00000000000..936242195d5 Binary files /dev/null and b/docs/images/Unscheduledjobs.png differ diff --git a/docs/images/addressbookEntry.png b/docs/images/addressbookEntry.png new file mode 100644 index 00000000000..0f1d314c9be Binary files /dev/null and b/docs/images/addressbookEntry.png differ diff --git a/docs/images/c0j0s.png b/docs/images/c0j0s.png new file mode 100644 index 00000000000..28b4474c57f Binary files /dev/null and b/docs/images/c0j0s.png differ diff --git a/docs/images/chinjunan.png b/docs/images/chinjunan.png new file mode 100644 index 00000000000..445d29f7d61 Binary files /dev/null and b/docs/images/chinjunan.png differ diff --git a/docs/images/comJob.png b/docs/images/comJob.png new file mode 100644 index 00000000000..5e17080593a Binary files /dev/null and b/docs/images/comJob.png differ diff --git a/docs/images/completebutton.png b/docs/images/completebutton.png new file mode 100644 index 00000000000..4616e120348 Binary files /dev/null and b/docs/images/completebutton.png differ diff --git a/docs/images/delJob.png b/docs/images/delJob.png new file mode 100644 index 00000000000..db3fc54ab4e Binary files /dev/null and b/docs/images/delJob.png differ diff --git a/docs/images/deletebutton.png b/docs/images/deletebutton.png new file mode 100644 index 00000000000..4435b79df53 Binary files /dev/null and b/docs/images/deletebutton.png differ diff --git a/docs/images/dohaduong.png b/docs/images/dohaduong.png new file mode 100644 index 00000000000..cccf989e5cb Binary files /dev/null and b/docs/images/dohaduong.png differ diff --git a/docs/images/editJob.png b/docs/images/editJob.png new file mode 100644 index 00000000000..d03f28e6886 Binary files /dev/null and b/docs/images/editJob.png differ diff --git a/docs/images/helpMessage.png b/docs/images/helpMessage.png index b1f70470137..7d5c94967dd 100644 Binary files a/docs/images/helpMessage.png and b/docs/images/helpMessage.png differ diff --git a/docs/images/hideDetail.png b/docs/images/hideDetail.png new file mode 100644 index 00000000000..4e9e313d615 Binary files /dev/null and b/docs/images/hideDetail.png differ diff --git a/docs/images/johndoe.png b/docs/images/johndoe.png deleted file mode 100644 index 1ce7ce16dc8..00000000000 Binary files a/docs/images/johndoe.png and /dev/null differ diff --git a/docs/images/listJob.png b/docs/images/listJob.png new file mode 100644 index 00000000000..deb46f8ecae Binary files /dev/null and b/docs/images/listJob.png differ diff --git a/docs/images/listJobSortFilter.png b/docs/images/listJobSortFilter.png new file mode 100644 index 00000000000..87a9fc14acc Binary files /dev/null and b/docs/images/listJobSortFilter.png differ diff --git a/docs/images/penbutton.png b/docs/images/penbutton.png new file mode 100644 index 00000000000..6140ff078c4 Binary files /dev/null and b/docs/images/penbutton.png differ diff --git a/docs/images/reminderListWindow.png b/docs/images/reminderListWindow.png new file mode 100644 index 00000000000..d39270b3b4f Binary files /dev/null and b/docs/images/reminderListWindow.png differ diff --git a/docs/images/reminderNotification.png b/docs/images/reminderNotification.png new file mode 100644 index 00000000000..794a9d85797 Binary files /dev/null and b/docs/images/reminderNotification.png differ diff --git a/docs/images/scheduleNotification.png b/docs/images/scheduleNotification.png new file mode 100644 index 00000000000..4806cf9275b Binary files /dev/null and b/docs/images/scheduleNotification.png differ diff --git a/docs/images/unhideDetail.png b/docs/images/unhideDetail.png new file mode 100644 index 00000000000..a0a5f18a5d6 Binary files /dev/null and b/docs/images/unhideDetail.png differ diff --git a/docs/images/zhuleyao.png b/docs/images/zhuleyao.png new file mode 100644 index 00000000000..0ca1b6d62f6 Binary files /dev/null and b/docs/images/zhuleyao.png differ diff --git a/docs/images/zuohui48.png b/docs/images/zuohui48.png new file mode 100644 index 00000000000..6043bd279d0 Binary files /dev/null and b/docs/images/zuohui48.png differ diff --git a/docs/index.md b/docs/index.md index 7601dbaad0d..b389970ed13 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,17 +1,23 @@ --- layout: page -title: AddressBook Level-3 +title: Duke Driver --- -[![CI Status](https://github.com/se-edu/addressbook-level3/workflows/Java%20CI/badge.svg)](https://github.com/se-edu/addressbook-level3/actions) -[![codecov](https://codecov.io/gh/se-edu/addressbook-level3/branch/master/graph/badge.svg)](https://codecov.io/gh/se-edu/addressbook-level3) + +[![CI Status](https://github.com/AY2223S2-CS2103-F11-2/tp/actions/workflows/gradle.yml/badge.svg)](https://github.com/AY2223S2-CS2103-F11-2/tp/actions) + ![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). +**Duke Driver is a desktop application for +managing your delivery job details.** + +On top of that, you are able to manage your client details, derive statistics of your deliveries and view a timetable for your deliveries! + + +* If you are interested in using Duke Driver, head over to the [_Quick Start_ section of the **User Guide**](UserGuide.html#quick-start). +* If you are interested about developing Duke Driver, the [**Developer Guide**](DeveloperGuide.html) is a good place to start. -* 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. **Acknowledgements** diff --git a/docs/missingelements.csv b/docs/missingelements.csv new file mode 100644 index 00000000000..227eba55112 --- /dev/null +++ b/docs/missingelements.csv @@ -0,0 +1,2 @@ +R,S,,,,, +JAB456,JUI789,,,,, diff --git a/docs/some_na.csv b/docs/some_na.csv new file mode 100644 index 00000000000..39df2a0f813 --- /dev/null +++ b/docs/some_na.csv @@ -0,0 +1,4 @@ +Recipient ID,Sender ID,Delivery date,Delivery slot,Price,Recipient ID,Recipient Name,Recipient Phone,Recipient Email,Recipient Address,Recipient Tag,Sender ID,Sender Name,Sender Phone,Sender Email,Sender Address,Sender Tag +JUS456,JUL789,2023-01-01,1,5.00,JUS456,Justin,23456789,jus@gmail.com,Sg st 42,Customer,JUL789,Julian,34567890,jul@gmail.com,Sg st 43,na +JUS456,JUL789,na,na,5.00,JUS456,Justin,23456789,jus@gmail.com,Sg st 42,Customer,JUL789,Julian,34567890,jul@gmail.com,Sg st 43,Customer +JUS456,JUL789,2023-01-03,1,5.00,JUS456,Justin,23456789,jus@gmail.com,Sg st 42,Customer,JUL789,Julian,34567890,jul@gmail.com,Sg st 43,Customer diff --git a/docs/team/c0j0s.md b/docs/team/c0j0s.md new file mode 100644 index 00000000000..701a63fbcb6 --- /dev/null +++ b/docs/team/c0j0s.md @@ -0,0 +1,82 @@ +--- +layout: page +title: c0j0s's Project Portfolio Page +--- + +### Project: Duke Driver + +Duke Driver - a delivery tasking and planning application used by delivery personnel from DUKE pte ltd. Duke Driver provides bulk management and personalised job listing for drivers. + +Given below are my contributions to the project. + +* **New Feature**: Added Delivery Job System Storage. + * What is does: Handle saving and loading of delivery job list from data files. + * Justification: This feature allow delivery job data to be saved in a separate data file to avoid congesting the address book. + * Highlights: Largely inline with address book storage structure with modification to json adapted classes to fit our custom delivery job model. + * Credits: Address book storage. + +* **New Feature**: Delivery Job List and Detail Pane in MainWindow. + * What is does: Display a list of pending delivery jobs for user. Able to view the job detail when selected. + * Justification: The user needs to see all the available jobs and its details. + * Highlights: User can select to view the job detail by mouse or arrow keys. + * Credits: MainWindow. + +* **New Feature**: List Delivery Job Command. + * What is does: List all the available jobs. + * Justification: Core function. + * Highlights: N.A. + * Credits: ListCommand. + +* **New Feature**: Add Delivery Job Command. + * What is does: Adds a new delivery jobs. + * Justification: Core function. + * Highlights: Support GUI mode. + * Credits: AddCommand. + +* **New Feature**: Delete Delivery Job Command. + * What is does: Delete a selected jobs. + * Justification: Core function. + * Highlights: User can select and delete a job using `del` key from the MainWindow. + * Credits: DeleteCommand. + +* **New Feature**: Edit Delivery Job Command. + * What is does: Edit a selected jobs. + * Justification: Core function. + * Highlights: Able to select jobs using list index or job id. Support GUI mode. + * Credits: EditCommand. + +* **New Feature**: Find Delivery Job Command. + * What is does: Find a selected jobs. + * Justification: Core function. + * Highlights: All attributes of `DeliveryJob` can be use as a command option for searching. + * Credits: FindCommand. + +* **New Feature**: Sort and Filter Job List. + * What is does: Order job list. + * Justification: Allows user to filter out completed or sort by high earning jobs. + * Highlights: Support both ascending and descending mode. + +* **Code contributed**: [RepoSense link](https://nus-cs2103-ay2223s2.github.io/tp-dashboard/?search=c0j0s&breakdown=true) + +* **Project management**: + * Setting up the GitHub team org/repo + * Setup project dashboard. + * Setup project milestones. + * Add cache support for github actions. + +* **Enhancements to existing features**: + * Expandable result display box in contact and main window. + * Command grouping support to control specific command groups in desired command locations. + +* **Documentation**: + * User Guide: + * Added documentation for the delivery tasking management system features `list`, `add job`, `edit job`, `delete job` and `find job`. + * Developer Guide: + * Update diagram for `UI Componets`, `Model` and `Storage`. + * Added use case for `list delivery job detail`, `delete job`, `edit job` and `find job`. + * Added implementation details foe delivery job system in developer guide. + * Added manual test cases for delivery jobs. + +* **Community**: + * Review PRs. + * Forum discussions. diff --git a/docs/team/chinjunan.md b/docs/team/chinjunan.md new file mode 100644 index 00000000000..e27722c4e5e --- /dev/null +++ b/docs/team/chinjunan.md @@ -0,0 +1,43 @@ +--- +layout: page +title: Chin Jun An's Project Portfolio Page +--- + +### Project: Duke Delivery + +Duke Driver - a delivery tasking and planning application for delivery personnel. Duke Driver provides bulk management and personalised job listing for drivers. The user can interact with it using both CLI and GUI. The GUI is created with JavaFX. It is written in Java and has about 10 kLoC. + +Given below are my contributions to the project. + +* **New Feature**: Added notification function. + * What it does: Shows pop up notifications when app starts up, or when certain events/deadline has passed + * Justification: This feature is implemented to alert the user of any events from the app. It is used in conjunction with the reminder and scheduling function of the app + * Highlights: It was challenging to implement this function as JavaFx did not have any native notification feature. Hence, I had to find an external library that did so. I decided to used ControlFX, an open source project for JavaFX that serves to provide high quality UI controls on top of the existing JavaFX distribution. Through abstraction, this feature can be used by other portions of the applications (i.e. reminders, scheduling, etc). Hence, the implementation extended the functionalities and benefits of the application. + * Credits: ControlFx library, Genuine Coder YouTube Channel on implementing the ControlFX notification feature. + +* **New Feature**: Added reminder function. + * What it does: Allows users to add reminders into the address book. This function is used in conjunction with the notification function, to fire a pop-up notification when an upcoming deadline/schedule is approaching + * Justification: This feature helps users with busy schedules to remind them of certain task they might have forgotten to do in the upcoming future. Essentially, it reduces the load of the shoulders of the users in remembering upcoming task/schedules. + * Highlights: This feature required an addition to the data structure of the address book (an additional list to record reminders). This led to additional classes that involved managing the storage and reading from local memory and hard disk. Fortunately, not much have to be done as it was similar to existing codes. Further improvement to this feature involves running process threads to fire notifications while the app is running in the background. + * Credits: Existing codes from AB3 which involved the models and storage functionality of the app. + +* **New Feature**: Added schedule notification function. + * What it does: Notifies users of upcoming and current jobs that have been scheduled. + * Justification: This feature allows users to keep up with their busy schedule by being notified of upcoming and current jobs. The user is prepared by being reminded every 20 minutes before the next timetable slot. + * Highlights: This feature worked in conjunction with the timetable feature done by fellow programmer, Ha Duong. It required the extraction of the daily job listing that have been scheduled for that day. Also, with the help of the notification abstraction, creating this feature went really smooth. + * Credits: Ha Duong for her implementation of the timetable feature, and the notification function. + +* **Code contributed**: [RepoSense link](https://nus-cs2103-ay2223s2.github.io/tp-dashboard/?search=chinjunan&breakdown=true&sort=groupTitle%20dsc&sortWithin=title&since=2023-02-17&timeframe=commit&mergegroup=&groupSelect=groupByRepos&checkedFileTypes=docs~functional-code~test-code~other) + +* **Project management**: + * Managed releases v1.3 - v1.4 (2 releases) on GitHub + +* **Enhancements to existing features**: + * Modified existing storage and model to handle storing reminders in the app. + +* **Documentation**: + * User Guide: + * Added documentation for the usage of the add_reminder, delete_reminder, list_reminder features (PR #92) + * Developer Guide: + * Added implementation details of the notification function. (PR #92) + * Added implementation details of the reminder function. (PR #92) diff --git a/docs/team/dohaduong.md b/docs/team/dohaduong.md new file mode 100644 index 00000000000..c503637a8eb --- /dev/null +++ b/docs/team/dohaduong.md @@ -0,0 +1,61 @@ +--- +layout: page +title: Do Ha Duong's Project Portfolio Page +--- + +### Project: Duke Driver + +Duke Driver is a task and contact management app that aims to help busy delivery men. It is built by implementing CLI and GUI, helping to enhance users' experience. + +Given below are my contributions to the project. + +* **New Feature**: Added the ability to view timetable of specific week + * What it does: Displays timetable of the week specified by users using commands or using GUI mode (clicks on buttons). + * Justification: This feature is implemented to help users to structure their plans in the day - they can view upcoming tasks more easily. It is linked with the delivery job list in the app. + * Highlight: This feature was challenging at first to implement as I have to decide on the design and GUI of the timetable myself - JavaFX did not have any integrated timetable format. Moreover, as this enhancement is directly linked with job list, it could affect and requires change in existing commands and commands to be added in the future. + +* **New Feature**: Added the ability to view list of completed jobs and jobs with invalid date and/or slot + * What it does: Allows users to view list of completed jobs and jobs with invalid date and/or slot using the corresponding commands or click on buttons (GUI). + * Justification: This features improves the product and enhances the timetable feature significantly, as completed or jobs with invalid dates/slots are not shown in the Timetable. Thus, users can refer to this feature instead if they want to view the mentioned job lists. + * Highlight: This feature is directly linked with job list, thus changes made in the existing functions could affect the features directly. Moreover, the implementation also required changes to the existing commands. + +* **New Feature**: Added the ability to add jobs with empty delivery date and/or slot + * What it does: Allows users to add jobs without specifying its delivery date and slot (making delivery date and slot optional), making the `add_job` command much shorter. + * Justification: This features improves the product as the `add_job` command is quite long (with compulsory delivery date and slot), and users may want to add jobs without delivery date and slot. Thus, the app should allow optional delivery date and timeslot. + * Highlight: This feature is directly linked with job list and with the existing command/functions, thus it required an in-depth analysis of design alternatives and changes to existing commands. + + +* **Code contributed**: [RepoSense link](https://nus-cs2103-ay2223s2.github.io/tp-dashboard/?search=dohaduong&breakdown=true) + +* **Project management**: + * Managed releases v1.2 and v1.3.trial (2 releases) on GitHub + * Update release v1.3 with description on GitHub + * Create tags and labels for issues/PRs on GitHub + +* **Enhancements to existing features**: + * Wrote tests for existing features, such as: all Timetable commands, reminder commands (`add_reminder`, `delete_reminder`, `list_reminder`), jobs commands (`com_job/uncom_job`, `delete_job`,..), etc. (PR #189, #260) + * Added and updated JavaDoc comments for public methods (PR #165) + * Updated to make feedbacks and command instructions clearer to users (provide examples on how to use the commands in the app) (PR #194) + * Added ability to navigate to Help Window from different windows (PR #189) + +* **Documentation**: + * User Guide: + * Added _How to use this Guide?_ and _Windows and Features Overview_ sections (PR #253) + * Added documentation for Timetable commands (PR #184) + * Added and updated documentation for Delivery Job commands (PR #184, #253) + * Added documentation for Job Detail (UI) in Main Window (PR #254) + * Added examples for commands in UG and summary table (PR #198, #254) + * Re-format, touch up, fix minor bugs and finalize UG (PR #200, #205, #260) + + * Developer Guide: + * Added implementation details and diagrams for `UI component/Timetable Window`, `Logic Component`, `Timetable` and `delete_job` (PR #128, #270, #276) + * Added user stories for timetable, reminder; use cases for timetable and glossary (PR #20, #23, #34, #270) + * Added Appendix: Effort (PR #270) + +* **Community**: + * PRs reviewed (with non-trivial review comments): example: #180, #156, #264,.. + * Identified bugs in commands and brought up discussion among the group in weekly meeting + * Contributed to forum discussions (examples: #24) + + + diff --git a/docs/team/johndoe.md b/docs/team/johndoe.md deleted file mode 100644 index 773a07794e2..00000000000 --- a/docs/team/johndoe.md +++ /dev/null @@ -1,46 +0,0 @@ ---- -layout: page -title: John Doe's Project Portfolio Page ---- - -### Project: AddressBook Level 3 - -AddressBook - Level 3 is a desktop address book application used for teaching Software Engineering principles. The user interacts with it using a CLI, and it has a GUI created with JavaFX. It is written in Java, and has about 10 kLoC. - -Given below are my contributions to the project. - -* **New Feature**: Added the ability to undo/redo previous commands. - * What it does: allows the user to undo all previous commands one at a time. Preceding undo commands can be reversed by using the redo command. - * Justification: This feature improves the product significantly because a user can make mistakes in commands and the app should provide a convenient way to rectify them. - * Highlights: This enhancement affects existing commands and commands to be added in future. It required an in-depth analysis of design alternatives. The implementation too was challenging as it required changes to existing commands. - * Credits: *{mention here if you reused any code/ideas from elsewhere or if a third-party library is heavily used in the feature so that a reader can make a more accurate judgement of how much effort went into the feature}* - -* **New Feature**: Added a history command that allows the user to navigate to previous commands using up/down keys. - -* **Code contributed**: [RepoSense link]() - -* **Project management**: - * Managed releases `v1.3` - `v1.5rc` (3 releases) on GitHub - -* **Enhancements to existing features**: - * Updated the GUI color scheme (Pull requests [\#33](), [\#34]()) - * Wrote additional tests for existing features to increase coverage from 88% to 92% (Pull requests [\#36](), [\#38]()) - -* **Documentation**: - * User Guide: - * Added documentation for the features `delete` and `find` [\#72]() - * Did cosmetic tweaks to existing documentation of features `clear`, `exit`: [\#74]() - * Developer Guide: - * Added implementation details of the `delete` feature. - -* **Community**: - * PRs reviewed (with non-trivial review comments): [\#12](), [\#32](), [\#19](), [\#42]() - * Contributed to forum discussions (examples: [1](), [2](), [3](), [4]()) - * Reported bugs and suggestions for other teams in the class (examples: [1](), [2](), [3]()) - * Some parts of the history feature I added was adopted by several other class mates ([1](), [2]()) - -* **Tools**: - * Integrated a third party library (Natty) to the project ([\#42]()) - * Integrated a new Github plugin (CircleCI) to the team repo - -* _{you can add/remove categories in the list above}_ diff --git a/docs/team/zhuleyao.md b/docs/team/zhuleyao.md new file mode 100644 index 00000000000..4e3e3a9119d --- /dev/null +++ b/docs/team/zhuleyao.md @@ -0,0 +1,61 @@ + --- +layout: page +title: Zhu Le Yao's Project Portfolio Page +--- + +### Project: Duke Driver + +Duke Driver - A contact and job management app to aid delivery drivers in better managing their jobs and customer information. + +Given below are my contributions to the project. + +* **Code contributed**: [RepoSense link](https://nus-cs2103-ay2223s2.github.io/tp-dashboard/?search=zhuleyao&breakdown=true&sort=groupTitle%20dsc&sortWithin=title&since=2023-02-17&timeframe=commit&mergegroup=&groupSelect=groupByRepos&checkedFileTypes=docs~functional-code~test-code~other) + +* **Enhancements implemented**: + * Partially implemented backend of data management. + * Implemented mass import delivery jobs and customer information function. + +* **Contributions to the UG**: + * Features related to delivery job + * Add job + * Mass import job + * Edit job + * List job + * Find job + * Delete job + * Mark job as completed or uncompleted + * Add images and alternative access methods for timetable and statistics features + * Update Command Summary with all of the above + +* **Contributions to the DG**: + * User stories + * Use cases + * Instructions for manual testing + +* **Contributions to team-based tasks**: + * Review/mentoring contributions: + * Links to PRs reviewed: + * https://github.com/AY2223S2-CS2103-F11-2/tp/pull/23 + * https://github.com/AY2223S2-CS2103-F11-2/tp/pull/33 + * https://github.com/AY2223S2-CS2103-F11-2/tp/pull/34 + * https://github.com/AY2223S2-CS2103-F11-2/tp/pull/57 + * https://github.com/AY2223S2-CS2103-F11-2/tp/pull/180 + * https://github.com/AY2223S2-CS2103-F11-2/tp/pull/198 + * https://github.com/AY2223S2-CS2103-F11-2/tp/pull/205 + * Other ways: + * Submitted jar file for PE-D + * Help with UG and supplementary images + * Contributions beyond the project team: + * 10 Bug reported in PE-D: + * Feature flaws: + * Unused tag function in customer details + * List UI not intuitive + * List sort case sensitivity + * Set tier is customer information but not implemented + * Help guide points to address book + * Functionality bug: + * 3 commands showing up as unknown commands + * Edit order feature not working for quantity parameter + * Documentation bug: + * Incomplete user guide for tutorial section + diff --git a/docs/team/zuohui48.md b/docs/team/zuohui48.md new file mode 100644 index 00000000000..c18346f92bf --- /dev/null +++ b/docs/team/zuohui48.md @@ -0,0 +1,33 @@ +--- +layout: page +title: Chen Zuo Hui's Project Portfolio Page +--- + +### Project: Duke Driver + +Duke Driver - a delivery tasking and planning application used by delivery drivers. The user interacts with it using a CLI, and it has a GUI created with JavaFX. + +Given below are my contributions to the project. + +* **New Feature**: Statistics function + * What it does: Shows a list of summary statistics of delivery jobs + * Justification: This feature is implemented to allow users to view different statistics about the jobs that they have added + + +* **Code contributed**: [RepoSense link](https://nus-cs2103-ay2223s2.github.io/tp-dashboard/?search=zuohui&breakdown=true) + + +* **Documentation**: + * User Guide: + * Added documentation for the usage of statistics features (PR #130) + * Fix documentation bugs + * Developer Guide: + * Added implementation details and diagrams for statistics component (PR #190) + * Fix documentation bugs + * Contributions beyond the project team: + * Reported 13 bugs for PE-D + +* **Team-Based Tasks**: + * Brainstorm and add user stories to Duke Driver + * Brainstorm product name + diff --git a/docs/testimportfile.csv b/docs/testimportfile.csv new file mode 100644 index 00000000000..51ee10f08e2 --- /dev/null +++ b/docs/testimportfile.csv @@ -0,0 +1,4 @@ +Recipient ID,Sender ID,Delivery date,Delivery slot,Price,Recipient ID,Recipient Name,Recipient Phone,Recipient Email,Recipient Address,Recipient Tag,Sender ID,Sender Name,Sender Phone,Sender Email,Sender Address,Sender Tag +JUS456,JUL789,2023-01-01,1,5.00,JUS456,Justin,23456789,jus@gmail.com,Sg st 42,Customer,JUL789,Julian,34567890,jul@gmail.com,Sg st 43,na +JUS456,JUL789,2023-01-02,1,5.00,JUS456,Justin,23456789,jus@gmail.com,Sg st 42,Customer,JUL789,Julian,34567890,jul@gmail.com,Sg st 43,Customer +JUS456,JUL789,2023-01-03,1,5.00,JUS456,Justin,23456789,jus@gmail.com,Sg st 42,Customer,JUL789,Julian,34567890,jul@gmail.com,Sg st 43,Customer diff --git a/fileEmpty.csv b/fileEmpty.csv new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/main/java/seedu/address/AppParameters.java b/src/main/java/seedu/address/AppParameters.java index ab552c398f3..56c76c7f73f 100644 --- a/src/main/java/seedu/address/AppParameters.java +++ b/src/main/java/seedu/address/AppParameters.java @@ -18,14 +18,6 @@ public class AppParameters { private Path configPath; - public Path getConfigPath() { - return configPath; - } - - public void setConfigPath(Path configPath) { - this.configPath = configPath; - } - /** * Parses the application command-line parameters. */ @@ -43,6 +35,14 @@ public static AppParameters parse(Application.Parameters parameters) { return appParameters; } + public Path getConfigPath() { + return configPath; + } + + public void setConfigPath(Path configPath) { + this.configPath = configPath; + } + @Override public boolean equals(Object other) { if (other == this) { diff --git a/src/main/java/seedu/address/Main.java b/src/main/java/seedu/address/Main.java index 052a5068631..01c1f20205d 100644 --- a/src/main/java/seedu/address/Main.java +++ b/src/main/java/seedu/address/Main.java @@ -4,17 +4,17 @@ /** * The main entry point to the application. - * + *

* This is a workaround for the following error when MainApp is made the * entry point of the application: - * - * Error: JavaFX runtime components are missing, and are required to run this application - * + *

+ * Error: JavaFX runtime components are missing, and are required to run this application + *

* The reason is that MainApp extends Application. In that case, the * LauncherHelper will check for the javafx.graphics module to be present - * as a named module. We don't use JavaFX via the module system so it can't + * as a named module. We don't use JavaFX via the module system so it ctiman't * find the javafx.graphics module, and so the launch is aborted. - * + *

* By having a separate main class (Main) that doesn't extend Application * to be the entry point of the application, we avoid this issue. */ diff --git a/src/main/java/seedu/address/MainApp.java b/src/main/java/seedu/address/MainApp.java index 4133aaa0151..554f88fe53f 100644 --- a/src/main/java/seedu/address/MainApp.java +++ b/src/main/java/seedu/address/MainApp.java @@ -6,6 +6,7 @@ import java.util.logging.Logger; import javafx.application.Application; +import javafx.application.Platform; import javafx.stage.Stage; import seedu.address.commons.core.Config; import seedu.address.commons.core.LogsCenter; @@ -16,18 +17,22 @@ import seedu.address.logic.Logic; import seedu.address.logic.LogicManager; import seedu.address.model.AddressBook; +import seedu.address.model.DeliveryJobSystem; import seedu.address.model.Model; import seedu.address.model.ModelManager; import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.model.ReadOnlyDeliveryJobSystem; import seedu.address.model.ReadOnlyUserPrefs; import seedu.address.model.UserPrefs; import seedu.address.model.util.SampleDataUtil; import seedu.address.storage.AddressBookStorage; -import seedu.address.storage.JsonAddressBookStorage; -import seedu.address.storage.JsonUserPrefsStorage; +import seedu.address.storage.DeliveryJobSystemStorage; import seedu.address.storage.Storage; import seedu.address.storage.StorageManager; import seedu.address.storage.UserPrefsStorage; +import seedu.address.storage.json.storage.JsonAddressBookStorage; +import seedu.address.storage.json.storage.JsonDeliveryJobSystemStorage; +import seedu.address.storage.json.storage.JsonUserPrefsStorage; import seedu.address.ui.Ui; import seedu.address.ui.UiManager; @@ -36,7 +41,7 @@ */ public class MainApp extends Application { - public static final Version VERSION = new Version(0, 2, 0, true); + public static final Version VERSION = new Version(1, 4, 0, true); private static final Logger logger = LogsCenter.getLogger(MainApp.class); @@ -48,7 +53,7 @@ public class MainApp extends Application { @Override public void init() throws Exception { - logger.info("=============================[ Initializing AddressBook ]==========================="); + logger.info("=============================[ Initializing DukeDriver ]==========================="); super.init(); AppParameters appParameters = AppParameters.parse(getParameters()); @@ -57,7 +62,9 @@ public void init() throws Exception { UserPrefsStorage userPrefsStorage = new JsonUserPrefsStorage(config.getUserPrefsFilePath()); UserPrefs userPrefs = initPrefs(userPrefsStorage); AddressBookStorage addressBookStorage = new JsonAddressBookStorage(userPrefs.getAddressBookFilePath()); - storage = new StorageManager(addressBookStorage, userPrefsStorage); + DeliveryJobSystemStorage deliveryJobSystemStorage = new JsonDeliveryJobSystemStorage( + userPrefs.getDeliveryJobSystemFilePath()); + storage = new StorageManager(addressBookStorage, deliveryJobSystemStorage, userPrefsStorage); initLogging(config); @@ -69,28 +76,55 @@ public void init() throws Exception { } /** - * Returns a {@code ModelManager} with the data from {@code storage}'s address book and {@code userPrefs}.
- * The data from the sample address book will be used instead if {@code storage}'s address book is not found, - * or an empty address book will be used instead if errors occur when reading {@code storage}'s address book. + * Returns a {@code ModelManager} with the data from {@code storage}'s address + * book and {@code userPrefs}.
+ * The data from the sample address book will be used instead if + * {@code storage}'s address book is not found, + * or an empty address book will be used instead if errors occur when reading + * {@code storage}'s address book. */ private Model initModelManager(Storage storage, ReadOnlyUserPrefs userPrefs) { Optional addressBookOptional; - ReadOnlyAddressBook initialData; + ReadOnlyAddressBook initialAddressData; + + Optional deliveryJobSystemOptional; + ReadOnlyDeliveryJobSystem initialDeliveryJobSystemData; + try { addressBookOptional = storage.readAddressBook(); if (!addressBookOptional.isPresent()) { - logger.info("Data file not found. Will be starting with a sample AddressBook"); + logger.info("[AB] Data file not found. Will be starting with a sample AddressBook"); } - initialData = addressBookOptional.orElseGet(SampleDataUtil::getSampleAddressBook); + initialAddressData = addressBookOptional.orElseGet(SampleDataUtil::getSampleAddressBook); + } catch (DataConversionException e) { - logger.warning("Data file not in the correct format. Will be starting with an empty AddressBook"); - initialData = new AddressBook(); + logger.warning("[AB] Data file not in the correct format. Will be starting with an empty AddressBook"); + initialAddressData = new AddressBook(); } catch (IOException e) { - logger.warning("Problem while reading from the file. Will be starting with an empty AddressBook"); - initialData = new AddressBook(); + logger.warning("[AB] Problem while reading from the file. Will be starting with an empty AddressBook"); + initialAddressData = new AddressBook(); + } + + try { + deliveryJobSystemOptional = storage.readDeliveryJobSystem(); + + if (!deliveryJobSystemOptional.isPresent()) { + logger.info("[DS] Data file not found. Will be starting with a sample DeliveryJobSystem"); + } + + initialDeliveryJobSystemData = deliveryJobSystemOptional + .orElseGet(SampleDataUtil::getSampleDeliveryJobSystem); + } catch (IllegalArgumentException | NullPointerException | DataConversionException e) { + logger.warning( + "[DS] Data file not in the correct format. Will be starting with an empty DeliveryJobSystem"); + initialDeliveryJobSystemData = new DeliveryJobSystem(); + } catch (IOException e) { + logger.warning( + "[DS] Problem while reading from the file. Will be starting with an empty DeliveryJobSystem"); + initialDeliveryJobSystemData = new DeliveryJobSystem(); } - return new ModelManager(initialData, userPrefs); + return new ModelManager(initialAddressData, initialDeliveryJobSystemData, userPrefs); } private void initLogging(Config config) { @@ -124,7 +158,8 @@ protected Config initConfig(Path configFilePath) { initializedConfig = new Config(); } - //Update config file in case it was missing to begin with or there are new/unused fields + // Update config file in case it was missing to begin with or there are + // new/unused fields try { ConfigUtil.saveConfig(initializedConfig, configFilePathUsed); } catch (IOException e) { @@ -134,7 +169,8 @@ protected Config initConfig(Path configFilePath) { } /** - * Returns a {@code UserPrefs} using the file at {@code storage}'s user prefs file path, + * Returns a {@code UserPrefs} using the file at {@code storage}'s user prefs + * file path, * or a new {@code UserPrefs} with default configuration if errors occur when * reading from the file. */ @@ -155,7 +191,8 @@ protected UserPrefs initPrefs(UserPrefsStorage storage) { initializedPrefs = new UserPrefs(); } - //Update prefs file in case it was missing to begin with or there are new/unused fields + // Update prefs file in case it was missing to begin with or there are + // new/unused fields try { storage.saveUserPrefs(initializedPrefs); } catch (IOException e) { @@ -167,17 +204,19 @@ protected UserPrefs initPrefs(UserPrefsStorage storage) { @Override public void start(Stage primaryStage) { - logger.info("Starting AddressBook " + MainApp.VERSION); + logger.info("Starting DukeDriver " + MainApp.VERSION); ui.start(primaryStage); } @Override public void stop() { - logger.info("============================ [ Stopping Address Book ] ============================="); + logger.info("============================ [ Stopping DukeDriver ] ============================="); try { storage.saveUserPrefs(model.getUserPrefs()); } catch (IOException e) { logger.severe("Failed to save preferences " + StringUtil.getDetails(e)); } + Platform.exit(); + System.exit(0); } } diff --git a/src/main/java/seedu/address/commons/core/LogsCenter.java b/src/main/java/seedu/address/commons/core/LogsCenter.java index 431e7185e76..70a7f56b8b0 100644 --- a/src/main/java/seedu/address/commons/core/LogsCenter.java +++ b/src/main/java/seedu/address/commons/core/LogsCenter.java @@ -12,17 +12,17 @@ * Configures and manages loggers and handlers, including their logging level * Named {@link Logger}s can be obtained from this class
* These loggers have been configured to output messages to the console and a {@code .log} file by default, - * at the {@code INFO} level. A new {@code .log} file with a new numbering will be created after the log - * file reaches 5MB big, up to a maximum of 5 files.
+ * at the {@code INFO} level. A new {@code .log} file with a new numbering will be created after the log + * file reaches 5MB big, up to a maximum of 5 files.
*/ 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 Level currentLogLevel = Level.INFO; - private static final Logger logger = LogsCenter.getLogger(LogsCenter.class); private static FileHandler fileHandler; private static ConsoleHandler consoleHandler; + private static final Logger logger = LogsCenter.getLogger(LogsCenter.class); /** * Initializes with a custom log level (specified in the {@code config} object) @@ -95,6 +95,7 @@ private static void addFileHandler(Logger logger) { /** * Creates a {@code FileHandler} for the log file. + * * @throws IOException if there are problems opening the file. */ private static FileHandler createFileHandler() throws IOException { diff --git a/src/main/java/seedu/address/commons/core/Messages.java b/src/main/java/seedu/address/commons/core/Messages.java index 1deb3a1e469..0a9b45b1d62 100644 --- a/src/main/java/seedu/address/commons/core/Messages.java +++ b/src/main/java/seedu/address/commons/core/Messages.java @@ -6,8 +6,19 @@ public class Messages { public static final String MESSAGE_UNKNOWN_COMMAND = "Unknown command"; + public static final String MESSAGE_UNKNOWN_TIMETABLE_COMMAND = "Unknown command " + + "\nOnly timetable_date command is allowed in timetable:" + + " timetable_date date/YYYY-mm-DD" + + "\nFor example: timetable_date date/2023-03-01"; + public static final String MESSAGE_INVALID_COMMAND_FORMAT = "Invalid command format! \n%1$s"; + public static final String MESSAGE_EITHER_INDEX_OR_ID = "Select either by list or job id only."; public static final String MESSAGE_INVALID_PERSON_DISPLAYED_INDEX = "The person index provided is invalid"; + public static final String MESSAGE_INVALID_REMINDER_DISPLAYED_INDEX = "The reminder index provided is invalid"; public static final String MESSAGE_PERSONS_LISTED_OVERVIEW = "%1$d persons listed!"; + public static final String MESSAGE_INVALID_JOB_ID = "The job id provided is invalid"; + public static final String MESSAGE_INVALID_JOB_DISPLAYED_INDEX = "The job index provided is invalid"; + public static final String MESSAGE_DELIVERY_JOB_LISTED_OVERVIEW = "%1$d jobs listed!"; + public static final String COMMAND_NOT_ALLOW = "Command not allowed in this window!"; } diff --git a/src/main/java/seedu/address/commons/core/Version.java b/src/main/java/seedu/address/commons/core/Version.java index 12142ec1e32..7e897b61257 100644 --- a/src/main/java/seedu/address/commons/core/Version.java +++ b/src/main/java/seedu/address/commons/core/Version.java @@ -32,24 +32,9 @@ public Version(int major, int minor, int patch, boolean isEarlyAccess) { this.isEarlyAccess = isEarlyAccess; } - public int getMajor() { - return major; - } - - public int getMinor() { - return minor; - } - - public int getPatch() { - return patch; - } - - public boolean isEarlyAccess() { - return isEarlyAccess; - } - /** * Parses a version number string in the format V1.2.3. + * * @param versionString version number string * @return a Version object */ @@ -67,6 +52,22 @@ public static Version fromString(String versionString) throws IllegalArgumentExc versionMatcher.group(4) == null ? false : true); } + public int getMajor() { + return major; + } + + public int getMinor() { + return minor; + } + + public int getPatch() { + return patch; + } + + public boolean isEarlyAccess() { + return isEarlyAccess; + } + @JsonValue public String toString() { return String.format("V%d.%d.%d%s", major, minor, patch, isEarlyAccess ? "ea" : ""); diff --git a/src/main/java/seedu/address/commons/core/index/Index.java b/src/main/java/seedu/address/commons/core/index/Index.java index 19536439c09..82776e53f4b 100644 --- a/src/main/java/seedu/address/commons/core/index/Index.java +++ b/src/main/java/seedu/address/commons/core/index/Index.java @@ -2,7 +2,7 @@ /** * Represents a zero-based or one-based index. - * + *

* {@code Index} should be used right from the start (when parsing in a new user input), so that if the current * component wants to communicate with another component, it can send an {@code Index} to avoid having to know what * base the other component is using for its index. However, after receiving the {@code Index}, that component can @@ -23,14 +23,6 @@ private Index(int zeroBasedIndex) { this.zeroBasedIndex = zeroBasedIndex; } - public int getZeroBased() { - return zeroBasedIndex; - } - - public int getOneBased() { - return zeroBasedIndex + 1; - } - /** * Creates a new {@code Index} using a zero-based index. */ @@ -45,6 +37,14 @@ public static Index fromOneBased(int oneBasedIndex) { return new Index(oneBasedIndex - 1); } + public int getZeroBased() { + return zeroBasedIndex; + } + + public int getOneBased() { + return zeroBasedIndex + 1; + } + @Override public boolean equals(Object other) { return other == this // short circuit if same object diff --git a/src/main/java/seedu/address/commons/exceptions/IllegalValueException.java b/src/main/java/seedu/address/commons/exceptions/IllegalValueException.java index 19124db485c..651ce290208 100644 --- a/src/main/java/seedu/address/commons/exceptions/IllegalValueException.java +++ b/src/main/java/seedu/address/commons/exceptions/IllegalValueException.java @@ -13,7 +13,7 @@ public IllegalValueException(String message) { /** * @param message should contain relevant information on the failed constraint(s) - * @param cause of the main exception + * @param cause of the main exception */ public IllegalValueException(String message, Throwable cause) { super(message, cause); diff --git a/src/main/java/seedu/address/commons/util/AppUtil.java b/src/main/java/seedu/address/commons/util/AppUtil.java index 87aa89c0326..c0992318360 100644 --- a/src/main/java/seedu/address/commons/util/AppUtil.java +++ b/src/main/java/seedu/address/commons/util/AppUtil.java @@ -4,7 +4,6 @@ import javafx.scene.image.Image; import seedu.address.MainApp; - /** * A container for App specific utility functions */ diff --git a/src/main/java/seedu/address/commons/util/CollectionUtil.java b/src/main/java/seedu/address/commons/util/CollectionUtil.java index eafe4dfd681..942edda97d7 100644 --- a/src/main/java/seedu/address/commons/util/CollectionUtil.java +++ b/src/main/java/seedu/address/commons/util/CollectionUtil.java @@ -12,7 +12,9 @@ */ public class CollectionUtil { - /** @see #requireAllNonNull(Collection) */ + /** + * @see #requireAllNonNull(Collection) + */ public static void requireAllNonNull(Object... items) { requireNonNull(items); Stream.of(items).forEach(Objects::requireNonNull); diff --git a/src/main/java/seedu/address/commons/util/ConfigUtil.java b/src/main/java/seedu/address/commons/util/ConfigUtil.java index f7f8a2bd44c..0cefc577a11 100644 --- a/src/main/java/seedu/address/commons/util/ConfigUtil.java +++ b/src/main/java/seedu/address/commons/util/ConfigUtil.java @@ -6,7 +6,6 @@ import seedu.address.commons.core.Config; import seedu.address.commons.exceptions.DataConversionException; - /** * A class for accessing the Config File. */ diff --git a/src/main/java/seedu/address/commons/util/DateTimeUtil.java b/src/main/java/seedu/address/commons/util/DateTimeUtil.java new file mode 100644 index 00000000000..11ba3dc2980 --- /dev/null +++ b/src/main/java/seedu/address/commons/util/DateTimeUtil.java @@ -0,0 +1,19 @@ +package seedu.address.commons.util; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; + +/** + * Utility methods to parse LocalDateTime to String, vice versa. + */ +public class DateTimeUtil { + public static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"); + + public static LocalDateTime toDateTime(String s) { + return LocalDateTime.parse(s, DATE_TIME_FORMATTER); + } + + public static String dateTimeToString(LocalDateTime t) { + return t.format(DATE_TIME_FORMATTER); + } +} diff --git a/src/main/java/seedu/address/commons/util/FileUtil.java b/src/main/java/seedu/address/commons/util/FileUtil.java index b1e2767cdd9..f811215474c 100644 --- a/src/main/java/seedu/address/commons/util/FileUtil.java +++ b/src/main/java/seedu/address/commons/util/FileUtil.java @@ -20,6 +20,7 @@ public static boolean isFileExists(Path file) { /** * Returns true if {@code path} can be converted into a {@code Path} via {@link Paths#get(String)}, * otherwise returns false. + * * @param path A string representing the file path. Cannot be null. */ public static boolean isValidPath(String path) { @@ -33,6 +34,7 @@ public static boolean isValidPath(String path) { /** * Creates a file if it does not exist along with its missing parent directories. + * * @throws IOException if the file or directory cannot be created. */ public static void createIfMissing(Path file) throws IOException { diff --git a/src/main/java/seedu/address/commons/util/JsonUtil.java b/src/main/java/seedu/address/commons/util/JsonUtil.java index 8ef609f055d..8d1143d5ce0 100644 --- a/src/main/java/seedu/address/commons/util/JsonUtil.java +++ b/src/main/java/seedu/address/commons/util/JsonUtil.java @@ -51,7 +51,8 @@ static T deserializeObjectFromJsonFile(Path jsonFile, Class classOfObject /** * Returns the Json object from the given file or {@code Optional.empty()} object if the file is not found. * If any values are missing from the file, default values will be used, as long as the file is a valid json file. - * @param filePath cannot be null. + * + * @param filePath cannot be null. * @param classOfObjectToDeserialize Json file has to correspond to the structure in the class given here. * @throws DataConversionException if the file format is not as expected. */ @@ -79,6 +80,7 @@ public static Optional readJsonFile( /** * Saves the Json object to the specified file. * Overwrites existing file if it exists, creates a new file if it doesn't. + * * @param jsonFile cannot be null * @param filePath cannot be null * @throws IOException if there was an error during writing to the file @@ -93,6 +95,7 @@ public static void saveJsonFile(T jsonFile, Path filePath) throws IOExceptio /** * Converts a given string representation of a JSON data to instance of a class + * * @param The generic type to create an instance of * @return The instance of T with the specified values in the JSON string */ @@ -102,8 +105,9 @@ public static T fromJsonString(String json, Class instanceClass) throws I /** * Converts a given instance of a class into its JSON data string representation + * * @param instance The T object to be converted into the JSON string - * @param The generic type to create an instance of + * @param The generic type to create an instance of * @return JSON data representation of the given class instance, in string */ public static String toJsonString(T instance) throws JsonProcessingException { @@ -128,7 +132,6 @@ protected Level _deserialize(String value, DeserializationContext ctxt) { * Gets the logging level that matches loggingLevelString *

* Returns null if there are no matches - * */ private Level getLoggingLevel(String loggingLevelString) { return Level.parse(loggingLevelString); diff --git a/src/main/java/seedu/address/commons/util/StringUtil.java b/src/main/java/seedu/address/commons/util/StringUtil.java index 61cc8c9a1cb..edacbf58bba 100644 --- a/src/main/java/seedu/address/commons/util/StringUtil.java +++ b/src/main/java/seedu/address/commons/util/StringUtil.java @@ -14,14 +14,15 @@ public class StringUtil { /** * Returns true if the {@code sentence} contains the {@code word}. - * Ignores case, but a full word match is required. - *
examples:

+     * Ignores case, but a full word match is required.
+     * 
examples:
      *       containsWordIgnoreCase("ABc def", "abc") == true
      *       containsWordIgnoreCase("ABc def", "DEF") == true
      *       containsWordIgnoreCase("ABc def", "AB") == false //not a full word match
      *       
+ * * @param sentence cannot be null - * @param word cannot be null, cannot be empty, must be a single word + * @param word cannot be null, cannot be empty, must be a single word */ public static boolean containsWordIgnoreCase(String sentence, String word) { requireNonNull(sentence); @@ -53,6 +54,7 @@ public static String getDetails(Throwable t) { * e.g. 1, 2, 3, ..., {@code Integer.MAX_VALUE}
* Will return false for any other non-null string input * e.g. empty string, "-1", "0", "+1", and " 2 " (untrimmed), "3 0" (contains whitespace), "1 a" (contains letters) + * * @throws NullPointerException if {@code s} is null. */ public static boolean isNonZeroUnsignedInteger(String s) { diff --git a/src/main/java/seedu/address/logic/Logic.java b/src/main/java/seedu/address/logic/Logic.java index 92cd8fa605a..b44af38a40c 100644 --- a/src/main/java/seedu/address/logic/Logic.java +++ b/src/main/java/seedu/address/logic/Logic.java @@ -1,14 +1,26 @@ package seedu.address.logic; +import java.io.FileNotFoundException; import java.nio.file.Path; +import java.time.LocalDate; +import java.util.Comparator; +import java.util.Map; +import java.util.function.Predicate; import javafx.collections.ObservableList; import seedu.address.commons.core.GuiSettings; +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandGroup; import seedu.address.logic.commands.CommandResult; import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.Model; import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.model.jobs.DeliveryJob; +import seedu.address.model.jobs.DeliveryList; import seedu.address.model.person.Person; +import seedu.address.model.reminder.Reminder; +import seedu.address.model.stats.WeeklyStats; /** * API of the Logic component @@ -16,12 +28,40 @@ public interface Logic { /** * Executes the command and returns the result. + * + * @param commandText The command as entered by the user. + * @return the result of the command execution. + * @throws CommandException If an error occurs during command execution. + * @throws ParseException If an error occurs during parsing. + */ + CommandResult execute(String commandText) throws CommandException, ParseException, FileNotFoundException; + + /** + * Executes the command for chosen group and returns the result. + * * @param commandText The command as entered by the user. + * @param condition execute when true. + * @return the result of the command execution. + * @throws CommandException If an error occurs during command execution. + * @throws ParseException If an error occurs during parsing. + */ + CommandResult execute(String commandText, Predicate condition) + throws CommandException, ParseException, FileNotFoundException; + + /** + * Executes specific command object and returns the result. + * + * @param command Prebuild command object. * @return the result of the command execution. * @throws CommandException If an error occurs during command execution. - * @throws ParseException If an error occurs during parsing. + * @throws ParseException If an error occurs during parsing. */ - CommandResult execute(String commandText) throws CommandException, ParseException; + CommandResult execute(Command command) throws CommandException, ParseException, FileNotFoundException; + + CommandResult executeTimetableCommand(String commandText) + throws CommandException, ParseException, FileNotFoundException; + + // ADDRESS BOOK SYSTEM =================================== /** * Returns the AddressBook. @@ -30,7 +70,9 @@ public interface Logic { */ ReadOnlyAddressBook getAddressBook(); - /** Returns an unmodifiable view of the filtered list of persons */ + /** + * Returns an unmodifiable view of the filtered list of persons + */ ObservableList getFilteredPersonList(); /** @@ -38,13 +80,127 @@ public interface Logic { */ Path getAddressBookFilePath(); + // REMINDER =================================== + /** + * Returns an unmodifiable view of the filtered list of reminders + */ + ObservableList getReminderList(); + + /** + * Sorts reminder list + */ + void sortReminderList(); + + // DELIVERY JOB SYSTEM =================================== + + /** + * Returns an unmodifiable view of the filtered list of delivery jobs + */ + ObservableList getFilteredDeliveryJobList(); + + /** + * Returns an unmodifiable view of the sorted list of delivery jobs + */ + ObservableList getSortedDeliveryJobList(); + + /** + * Returns delivery job list in the week sorted into day + */ + Map getWeekDeliveryJobList(); + + /** + * Returns job on specific day of week + * @param dayOfWeek day of week + * @return job list in the specific day + */ + DeliveryList getDayofWeekJob(int dayOfWeek); + + /** + * Returns an unmodifiable view of the list of unscheduled delivery jobs, + * sorted by time and earning + */ + ObservableList getUnscheduledDeliveryJobList(); + + /** + * Returns an unmodifiable view of the list of completed delivery jobs, + * sorted by time and earning + */ + ObservableList getCompletedDeliveryJobList(); + + /** + * Gets total earning of all jobs in job list + * @param list + */ + double getTotalEarnings(ObservableList list); + + /** + * Gets total number of completed jobs in job list + * @param list + */ + int getTotalCompleted(ObservableList list); + + int getTotalPending(ObservableList list); + + ObservableList weekJobsList(ObservableList list, LocalDate date); + + boolean sameWeek(DeliveryJob job, WeeklyStats weeklyStats); + + /** + * Returns the user prefs' delivery job system file path. + */ + Path getDeliveryJobSystemFilePath(); + + /** + * Set focus date + * @param focusDate + */ + void setWeekDeliveryJobList(LocalDate focusDate); + + /** + * Updates filter delivery job list + * @return + */ + void updateFilteredDeliveryJobList(Predicate pre); + + /** + * Updates sorted delivery job list + * @return + */ + void updateSortedDeliveryJobList(Comparator sorter); + + /** + * Updates sorted delivery job list + * @return + */ + void updateSortedDeliveryJobListByComparator(Comparator sorter); + + /** + * Updates sorted delivery job list by date + */ + void updateSortedDeliveryJobListByDate(); + + /** + * Gets user input focus date + * @return focus date + */ + LocalDate getFocusDate(); + + // MODEL =================================== + + /** + * Gets model + * @return model + */ + Model getModel(); + /** * Returns the user prefs' GUI settings. */ GuiSettings getGuiSettings(); /** - * Set the user prefs' GUI settings. + * Sets the user prefs' GUI settings. */ void setGuiSettings(GuiSettings guiSettings); + } diff --git a/src/main/java/seedu/address/logic/LogicManager.java b/src/main/java/seedu/address/logic/LogicManager.java index 9d9c6d15bdc..a5b3c3e84e7 100644 --- a/src/main/java/seedu/address/logic/LogicManager.java +++ b/src/main/java/seedu/address/logic/LogicManager.java @@ -1,20 +1,34 @@ package seedu.address.logic; +import java.io.FileNotFoundException; import java.io.IOException; import java.nio.file.Path; +import java.time.LocalDate; +import java.util.Comparator; +import java.util.Map; +import java.util.function.Predicate; import java.util.logging.Logger; import javafx.collections.ObservableList; import seedu.address.commons.core.GuiSettings; import seedu.address.commons.core.LogsCenter; +import seedu.address.commons.core.Messages; import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandGroup; import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.HelpCommand; import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.logic.parser.AddressBookParser; +import seedu.address.logic.parser.DukeDriverParser; import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.logic.parser.timetable.TimetableParser; import seedu.address.model.Model; import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.model.jobs.DeliveryJob; +import seedu.address.model.jobs.DeliveryList; +import seedu.address.model.jobs.sorters.SortbyTimeAndEarn; import seedu.address.model.person.Person; +import seedu.address.model.reminder.Reminder; +import seedu.address.model.stats.WeeklyStats; import seedu.address.storage.Storage; /** @@ -22,11 +36,14 @@ */ public class LogicManager implements Logic { public static final String FILE_OPS_ERROR_MESSAGE = "Could not save data to file: "; + public static final SortbyTimeAndEarn SORTER_BY_DATE = new SortbyTimeAndEarn(); private final Logger logger = LogsCenter.getLogger(LogicManager.class); private final Model model; private final Storage storage; - private final AddressBookParser addressBookParser; + private final DukeDriverParser addressBookParser; + private final TimetableParser timetableParser; + /** * Constructs a {@code LogicManager} with the given {@code Model} and {@code Storage}. @@ -34,24 +51,67 @@ public class LogicManager implements Logic { public LogicManager(Model model, Storage storage) { this.model = model; this.storage = storage; - addressBookParser = new AddressBookParser(); + addressBookParser = new DukeDriverParser(); + timetableParser = new TimetableParser(); + } + + public Model getModel() { + return this.model; } @Override - public CommandResult execute(String commandText) throws CommandException, ParseException { + public CommandResult execute(String commandText) throws CommandException, ParseException, FileNotFoundException { logger.info("----------------[USER COMMAND][" + commandText + "]"); - CommandResult commandResult; Command command = addressBookParser.parseCommand(commandText); + return execute(command); + } + + @Override + public CommandResult execute(String commandText, Predicate condition) + throws CommandException, ParseException, FileNotFoundException { + logger.info("----------------[USER COMMAND][" + commandText + "]"); + + if (condition.test(addressBookParser.parseCommandGroup(commandText))) { + return execute(addressBookParser.parseCommand(commandText)); + } else { + return new CommandResult(String.format(Messages.COMMAND_NOT_ALLOW + + "\nPlease refer to User Guide for more details: \n" + + HelpCommand.MESSAGE_USAGE)); + } + } + + @Override + public CommandResult execute(Command command) throws CommandException, ParseException, FileNotFoundException { + logger.info("----------------[USER COMMAND][" + command.getClass().getSimpleName() + "]"); + CommandResult commandResult = command.execute(model); + try { + storage.saveAddressBook(model.getAddressBook()); + storage.saveDeliveryJobSystem(model.getDeliveryJobSystem()); + } catch (IOException ioe) { + throw new CommandException(FILE_OPS_ERROR_MESSAGE + ioe, ioe); + } + return commandResult; + } + + @Override + public CommandResult executeTimetableCommand(String commandText) + throws CommandException, ParseException, FileNotFoundException { + logger.info("----------------[USER COMMAND][" + commandText + "]"); + + CommandResult commandResult; + Command command = timetableParser.parseCommand(commandText); commandResult = command.execute(model); try { storage.saveAddressBook(model.getAddressBook()); + storage.saveDeliveryJobSystem(model.getDeliveryJobSystem()); } catch (IOException ioe) { throw new CommandException(FILE_OPS_ERROR_MESSAGE + ioe, ioe); } return commandResult; + } @Override @@ -64,11 +124,75 @@ public ObservableList getFilteredPersonList() { return model.getFilteredPersonList(); } + @Override + public ObservableList getFilteredDeliveryJobList() { + return model.getDeliveryJobList(); + } + + @Override + public ObservableList getSortedDeliveryJobList() { + return model.getSortedDeliveryJobListByComparator(); + } + + @Override + public Map getWeekDeliveryJobList() { + return model.getWeekDeliveryJobList(); + } + + @Override + public DeliveryList getDayofWeekJob(int dayOfWeek) { + return model.getDayOfWeekJob(dayOfWeek); + } + + @Override + public double getTotalEarnings(ObservableList list) { + return model.getTotalEarnings(list); + } + + @Override + public int getTotalCompleted(ObservableList list) { + return model.getTotalCompleted(list); + } + + @Override + public int getTotalPending(ObservableList list) { + return model.getTotalPending(list); + } + + @Override + public boolean sameWeek(DeliveryJob job, WeeklyStats weeklyStats) { + return model.sameWeek(job, weeklyStats); + } + + @Override + public ObservableList weekJobsList(ObservableList list, LocalDate date) { + return model.weekJobsList(list, date); + } + + public ObservableList getUnscheduledDeliveryJobList() { + return model.getUnscheduledDeliveryJobList(); + } + + @Override + public ObservableList getReminderList() { + return model.getReminderList(); + } + + @Override + public void sortReminderList() { + model.sortReminderList(); + } + @Override public Path getAddressBookFilePath() { return model.getAddressBookFilePath(); } + @Override + public Path getDeliveryJobSystemFilePath() { + return model.getDeliveryJobSystemFilePath(); + } + @Override public GuiSettings getGuiSettings() { return model.getGuiSettings(); @@ -78,4 +202,42 @@ public GuiSettings getGuiSettings() { public void setGuiSettings(GuiSettings guiSettings) { model.setGuiSettings(guiSettings); } + + @Override + public void setWeekDeliveryJobList(LocalDate focusDate) { + model.updateFocusDate(focusDate); + model.updateWeekDeliveryJobList(focusDate); + } + + @Override + public void updateFilteredDeliveryJobList(Predicate pre) { + model.updateFilteredDeliveryJobList(pre); + } + + @Override + public void updateSortedDeliveryJobList(Comparator sorter) { + model.updateSortedDeliveryJobList(sorter); + } + + public void updateSortedDeliveryJobListByComparator(Comparator sorter) { + model.updateSortedDeliveryJobListByComparator(sorter); + } + + @Override + public void updateSortedDeliveryJobListByDate() { + model.updateSortedDeliveryJobList(SORTER_BY_DATE); + model.updateSortedDeliveryJobListByDate(); + } + + @Override + public ObservableList getCompletedDeliveryJobList() { + return model.getCompletedDeliveryJobList(); + } + + @Override + public LocalDate getFocusDate() { + return model.getFocusDate(); + } + + } diff --git a/src/main/java/seedu/address/logic/commands/Command.java b/src/main/java/seedu/address/logic/commands/Command.java index 64f18992160..c82c4981716 100644 --- a/src/main/java/seedu/address/logic/commands/Command.java +++ b/src/main/java/seedu/address/logic/commands/Command.java @@ -1,13 +1,19 @@ package seedu.address.logic.commands; +import java.io.FileNotFoundException; + import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.logic.parser.exceptions.ParseException; import seedu.address.model.Model; + /** * Represents a command with hidden internal logic and the ability to be executed. */ public abstract class Command { + public static final CommandGroup COMMAND_GROUP = CommandGroup.GENERAL; + /** * Executes the command and returns the result message. * @@ -15,6 +21,5 @@ public abstract class Command { * @return feedback message of the operation result for display * @throws CommandException If an error occurs during command execution. */ - public abstract CommandResult execute(Model model) throws CommandException; - + public abstract CommandResult execute(Model model) throws CommandException, ParseException, FileNotFoundException; } diff --git a/src/main/java/seedu/address/logic/commands/CommandGroup.java b/src/main/java/seedu/address/logic/commands/CommandGroup.java new file mode 100644 index 00000000000..5101f163718 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/CommandGroup.java @@ -0,0 +1,13 @@ +package seedu.address.logic.commands; + +/** + * Groups commands types. + */ +public enum CommandGroup { + GENERAL, + PERSON, + TIMETABLE, + REMINDER, + STAT, + JOB +} diff --git a/src/main/java/seedu/address/logic/commands/CommandResult.java b/src/main/java/seedu/address/logic/commands/CommandResult.java index 92f900b7916..f13dbbda713 100644 --- a/src/main/java/seedu/address/logic/commands/CommandResult.java +++ b/src/main/java/seedu/address/logic/commands/CommandResult.java @@ -11,18 +11,112 @@ public class CommandResult { private final String feedbackToUser; - /** Help information should be shown to the user. */ + /** + * Help information should be shown to the user. + */ private final boolean showHelp; - /** The application should exit. */ + /** + * Timetable information should be shown to the user. + */ + private final boolean showTimetable; + + /** + * Timetable information of specific date should be shown to the user. + */ + private boolean showTimetableDate; + + /** + * List of unscheduled jobs should be shown to user. + */ + private final boolean showUnschedule; + + /** + * List of completed jobs should be shown to user. + */ + private final boolean showComplete; + + /** + * Statistics information should be shown to the user. + */ + private final boolean showStatistics; + + /** + * Reminder list should be shown to the user. + */ + private final boolean showReminderList; + + /** + * Address book should be shown to the user. + */ + private final boolean showAddressBook; + + /** + * The application should exit. + */ private final boolean exit; /** * Constructs a {@code CommandResult} with the specified fields. */ - public CommandResult(String feedbackToUser, boolean showHelp, boolean exit) { + + public CommandResult(String feedbackToUser, boolean showHelp, boolean showTimetable, + boolean showUnschedule, boolean showComplete, boolean showReminderList, + boolean showStatistics, boolean showAddressBook, + boolean exit) { + this.feedbackToUser = requireNonNull(feedbackToUser); + this.showHelp = showHelp; + this.showTimetable = showTimetable; + this.showTimetableDate = false; + this.showUnschedule = showUnschedule; + this.showComplete = showComplete; + this.showReminderList = showReminderList; + this.showStatistics = showStatistics; + this.showAddressBook = showAddressBook; + this.exit = exit; + } + + /** + * Constructs a {@code CommandResult} with the specified fields. + */ + + public CommandResult(String feedbackToUser, boolean showHelp, boolean showTimetable, + boolean showUnschedule, boolean showComplete, boolean showReminderList, + boolean showStatistics, boolean exit) { this.feedbackToUser = requireNonNull(feedbackToUser); this.showHelp = showHelp; + this.showTimetable = showTimetable; + this.showTimetableDate = false; + this.showUnschedule = showUnschedule; + this.showComplete = showComplete; + this.showReminderList = showReminderList; + this.showStatistics = showStatistics; + this.showAddressBook = false; + this.exit = exit; + } + + /** + * Simplified constructor for CommandResult + * which does not show Unscheduled job window + * @param feedbackToUser + * @param showHelp + * @param showTimetable + * @param showReminderList + * @param showStatistics + * @param exit + */ + public CommandResult(String feedbackToUser, boolean showHelp, boolean showTimetable, + boolean showReminderList, + boolean showStatistics, boolean exit) { + this.feedbackToUser = requireNonNull(feedbackToUser); + this.showHelp = showHelp; + this.showTimetable = showTimetable; + this.showTimetableDate = false; + this.showUnschedule = false; + this.showComplete = false; + this.showReminderList = showReminderList; + this.showStatistics = showStatistics; + this.showAddressBook = false; this.exit = exit; } @@ -31,21 +125,88 @@ public CommandResult(String feedbackToUser, boolean showHelp, boolean exit) { * and other fields set to their default value. */ public CommandResult(String feedbackToUser) { - this(feedbackToUser, false, false); + this(feedbackToUser, false, false, false, false, false); } + /** + * Returns feedback to user + */ public String getFeedbackToUser() { return feedbackToUser; } + /** + * Checks if help window is shown + */ public boolean isShowHelp() { return showHelp; } + /** + * Checks if timetable window for any date is shown + */ + public boolean isShowTimetable() { + return showTimetable; + } + + /** + * Checks if timetable window for specific date is shown + */ + public boolean isShowTimetableDate() { + return showTimetableDate; + } + + /** + * Checks if unscheduled job window is shown + */ + public boolean isShowUnschedule() { + return showUnschedule; + } + + /** + * Checks if completed job window is shown + */ + public boolean isShowComplete() { + return showComplete; + } + + + /** + * Checks if stats window is shown + */ + public boolean isShowStatistics() { + return showStatistics; + } + + /** + * Checks if reminder window is shown + */ + public boolean isShowReminderList() { + return showReminderList; + } + + /** + * Checks if address window is shown + */ + public boolean isShowAddressBook() { + return showAddressBook; + } + + /** + * Checks if main window is exited + */ public boolean isExit() { return exit; } + /** + * Sets status of show timetable for specific date + * @param isShowTimetableDate + */ + public void setShowTimetableDate(boolean isShowTimetableDate) { + this.showTimetableDate = isShowTimetableDate; + } + @Override public boolean equals(Object other) { if (other == this) { @@ -60,12 +221,15 @@ public boolean equals(Object other) { CommandResult otherCommandResult = (CommandResult) other; return feedbackToUser.equals(otherCommandResult.feedbackToUser) && showHelp == otherCommandResult.showHelp + && showTimetable == otherCommandResult.showTimetable + && showStatistics == otherCommandResult.showStatistics + && showReminderList == otherCommandResult.showReminderList && exit == otherCommandResult.exit; } @Override public int hashCode() { - return Objects.hash(feedbackToUser, showHelp, exit); + return Objects.hash(feedbackToUser, showHelp, showTimetable, showReminderList, showStatistics, exit); } } diff --git a/src/main/java/seedu/address/logic/commands/ExitCommand.java b/src/main/java/seedu/address/logic/commands/ExitCommand.java index 3dd85a8ba90..5a004fb9fde 100644 --- a/src/main/java/seedu/address/logic/commands/ExitCommand.java +++ b/src/main/java/seedu/address/logic/commands/ExitCommand.java @@ -13,7 +13,7 @@ public class ExitCommand extends Command { @Override public CommandResult execute(Model model) { - return new CommandResult(MESSAGE_EXIT_ACKNOWLEDGEMENT, false, true); + return new CommandResult(MESSAGE_EXIT_ACKNOWLEDGEMENT, false, false, false, false, true); } } diff --git a/src/main/java/seedu/address/logic/commands/HelpCommand.java b/src/main/java/seedu/address/logic/commands/HelpCommand.java index bf824f91bd0..f4996722342 100644 --- a/src/main/java/seedu/address/logic/commands/HelpCommand.java +++ b/src/main/java/seedu/address/logic/commands/HelpCommand.java @@ -16,6 +16,6 @@ public class HelpCommand extends Command { @Override public CommandResult execute(Model model) { - return new CommandResult(SHOWING_HELP_MESSAGE, true, false); + return new CommandResult(SHOWING_HELP_MESSAGE, true, false, false, false, false); } } diff --git a/src/main/java/seedu/address/logic/commands/StatisticsCommand.java b/src/main/java/seedu/address/logic/commands/StatisticsCommand.java new file mode 100644 index 00000000000..75d64913bf5 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/StatisticsCommand.java @@ -0,0 +1,30 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; + +/** + * Shows summary of descriptive statistics to user. + */ +public class StatisticsCommand extends Command { + public static final CommandGroup COMMAND_GROUP = CommandGroup.STAT; + + public static final String COMMAND_WORD = "stats"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Shows summary of jobs"; + + public static final String SHOWING_STATISTICS_MESSAGE = "Opened statistics window."; + + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + + return new CommandResult(SHOWING_STATISTICS_MESSAGE, false, false, false, true, false); + } + +} diff --git a/src/main/java/seedu/address/logic/commands/exceptions/CommandException.java b/src/main/java/seedu/address/logic/commands/exceptions/CommandException.java index a16bd14f2cd..f44c0c19186 100644 --- a/src/main/java/seedu/address/logic/commands/exceptions/CommandException.java +++ b/src/main/java/seedu/address/logic/commands/exceptions/CommandException.java @@ -1,5 +1,7 @@ package seedu.address.logic.commands.exceptions; +import seedu.address.logic.commands.Command; + /** * Represents an error which occurs during execution of a {@link Command}. */ diff --git a/src/main/java/seedu/address/logic/commands/jobs/AddDeliveryJobCommand.java b/src/main/java/seedu/address/logic/commands/jobs/AddDeliveryJobCommand.java new file mode 100644 index 00000000000..9a96715e26a --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/jobs/AddDeliveryJobCommand.java @@ -0,0 +1,176 @@ +package seedu.address.logic.commands.jobs; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DELIVERY_DATE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DELIVERY_SLOT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_EARNING; +import static seedu.address.logic.parser.CliSyntax.PREFIX_RECIPIENT_ID; +import static seedu.address.logic.parser.CliSyntax.PREFIX_SENDER_ID; + +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.jobs.DeliveryJob; + +/** + * Adds a delivery job to the delivery job system. + */ +public class AddDeliveryJobCommand extends DeliveryJobCommand { + + public static final String COMMAND_WORD = "add_job"; + + public static final String MESSAGE_SENDER_CONSTRAINT = "Sender ID should contain " + + "numeric and alphabetical characters." + + "\nAnd it should not be blank."; + + public static final String MESSAGE_RECIPIENT_CONSTRAINT = "Recipient ID should contain numeric" + + "and alphabetical characters" + + "\nand it should not be blank."; + + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a job to the delivery job system. " + + "\nParameters: " + + PREFIX_SENDER_ID + "SENDER ID " + + PREFIX_RECIPIENT_ID + "RECIPIENT ID " + + PREFIX_EARNING + "Earning [" + + PREFIX_DELIVERY_DATE + "DELIVERY DATE] [" + + PREFIX_DELIVERY_SLOT + "DELIVERY SLOT] " + + "\nExample: " + COMMAND_WORD + " " + + PREFIX_SENDER_ID + "ALE874 " + + PREFIX_RECIPIENT_ID + "DAV910 " + + PREFIX_EARNING + "20" + + PREFIX_DELIVERY_DATE + "2023-03-03 " + + PREFIX_DELIVERY_SLOT + "3 "; + + public static final String MESSAGE_SUCCESS = "New job added: %1$s"; + public static final String MESSAGE_SUCCESS_WITH_NO_DATE = "New job added, with" + + " unspecified delivery date: \n%1$s"; + public static final String MESSAGE_SUCCESS_WITH_NO_SLOT = "New job added, " + + "with unspecified delivery slot: \n%1$s"; + public static final String MESSAGE_SUCCESS_WITH_MISSING_2_ARGS = "New job added, with unspecified " + + "%s and %s: \n%s"; + public static final String MESSAGE_SUCCESS_LATE_SLOT = "New job added with " + + "slot outside of working hours: \n%1$s" + + "\nValid slot should only be in the range [1,5]"; + public static final String MESSAGE_SUCCESS_LATE_SLOT_NO_DATE = "New job added with: " + + "\nslot outside of working hours and unspecified delivery date: \n%1$s" + + "\nValid slot should only be in the range [1,5]"; + + public static final String MESSAGE_SUCCESS_LATE_SLOT_NO_EARN = "New job added with: " + + "\nslot outside of working hours and unspecified earning: \n%1$s" + + "\nValid slot should only be in the range [1,5]"; + public static final String MESSAGE_SUCCESS_LATE_SLOT_NO_DATE_EARN = "New job added with: " + + "\nslot outside of working hours, unspecified delivery date and earning: \n%1$s" + + "\nValid slot should only be in the range [1,5]"; + + + public static final String MESSAGE_SUCCESS_WITH_NO_EARN = "New job added with unspecified earning: \n%1$s"; + public static final String MESSAGE_SUCCESS_WITH_NO_DATE_SLOT_EARN = "New job added with unspecified" + + " delivery date, slot and earning: \n%1$s"; + + + public static final String MESSAGE_DUPLICATE_JOB = "This job already exists in the delivery job system"; + public static final String MESSAGE_INVALID_SENDER = "Sender not found."; + public static final String MESSAGE_INVALID_RECIPIENT = "Recipient not found."; + public static final String MESSAGE_SAME_SENDER_RECIPIENT = "Sender and Recipient cannot be the same."; + + private final DeliveryJob toAdd; + + /** + * Creates an AddDeliveryJobCommand to add the specified {@code DeliveryJob} + */ + public AddDeliveryJobCommand(DeliveryJob job) { + requireNonNull(job); + toAdd = job; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + if (toAdd.getRecipientId().isEmpty()) { + throw new CommandException(MESSAGE_INVALID_RECIPIENT); + } + + if (model.getPersonById(toAdd.getRecipientId()).isEmpty()) { + throw new CommandException(MESSAGE_INVALID_RECIPIENT); + } + + if (toAdd.getSenderId().isEmpty()) { + throw new CommandException(MESSAGE_INVALID_SENDER); + } + + if (model.getPersonById(toAdd.getSenderId()).isEmpty()) { + throw new CommandException(MESSAGE_INVALID_SENDER); + } + + if (toAdd.getSenderId().equals(toAdd.getRecipientId())) { + throw new CommandException(MESSAGE_SAME_SENDER_RECIPIENT); + } + + model.addDeliveryJob(toAdd); + + if (toAdd.hasInvalidSlot()) { + if (toAdd.hasEarning() && toAdd.hasDate()) { + return new CommandResult(String.format(MESSAGE_SUCCESS_LATE_SLOT, toAdd)); + } else if (toAdd.hasEarning() && (!toAdd.hasDate())) { + return new CommandResult(String.format(MESSAGE_SUCCESS_LATE_SLOT_NO_DATE, toAdd)); + } else if ((!toAdd.hasEarning()) && toAdd.hasDate()) { + return new CommandResult(String.format(MESSAGE_SUCCESS_LATE_SLOT_NO_EARN, toAdd)); + } else { + return new CommandResult(String.format(MESSAGE_SUCCESS_LATE_SLOT_NO_DATE_EARN, toAdd)); + } + } + + return getMessageForMissingArgs(toAdd.hasDate(), toAdd.hasSlot(), toAdd.hasEarning()); + } + + private CommandResult getMessageForMissingArgs(boolean hasDate, boolean hasSlot, boolean hasEarn) { + if (hasDate && hasSlot && hasEarn) { + return new CommandResult(String.format(MESSAGE_SUCCESS, toAdd)); + } + + int numberOfMissingArgs = 0; + if (!hasDate) { + numberOfMissingArgs++; + } + if (!hasSlot) { + numberOfMissingArgs++; + } + if (!hasEarn) { + numberOfMissingArgs++; + } + + if (numberOfMissingArgs == 1) { + if (!hasDate) { + return new CommandResult(String.format(MESSAGE_SUCCESS_WITH_NO_DATE, toAdd)); + } else if (!hasEarn) { + return new CommandResult(String.format(MESSAGE_SUCCESS_WITH_NO_EARN, toAdd)); + } else if (!hasSlot) { + return new CommandResult(String.format(MESSAGE_SUCCESS_WITH_NO_SLOT, toAdd)); + } + } else if (numberOfMissingArgs == 2) { + if ((!hasDate) && (!hasSlot)) { + return new CommandResult(String.format(MESSAGE_SUCCESS_WITH_MISSING_2_ARGS, + "delivery date", "delivery slot", toAdd)); + } else if ((!hasDate) && (!hasEarn)) { + return new CommandResult(String.format(MESSAGE_SUCCESS_WITH_MISSING_2_ARGS, + "delivery date", "earning", toAdd)); + } else if ((!hasSlot) && (!hasEarn)) { + return new CommandResult(String.format(MESSAGE_SUCCESS_WITH_MISSING_2_ARGS, + "delivery slot", "earning", toAdd)); + } + } else if (numberOfMissingArgs == 3) { + return new CommandResult(String.format(MESSAGE_SUCCESS_WITH_NO_DATE_SLOT_EARN, toAdd)); + + } + return new CommandResult(String.format(MESSAGE_SUCCESS, toAdd)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof AddDeliveryJobCommand // instanceof handles nulls + && toAdd.equals(((AddDeliveryJobCommand) other).toAdd)); + } +} diff --git a/src/main/java/seedu/address/logic/commands/jobs/CompleteDeliveryJobCommand.java b/src/main/java/seedu/address/logic/commands/jobs/CompleteDeliveryJobCommand.java new file mode 100644 index 00000000000..5b7988f4ff6 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/jobs/CompleteDeliveryJobCommand.java @@ -0,0 +1,60 @@ +package seedu.address.logic.commands.jobs; + +import static java.util.Objects.requireNonNull; + +import java.util.List; +import java.util.Optional; + +import seedu.address.commons.core.Messages; +import seedu.address.logic.commands.CommandResult; +import seedu.address.model.Model; +import seedu.address.model.jobs.DeliveryJob; + +/** + * Sets delivered status for jobs in the delivery job system. + */ +public class CompleteDeliveryJobCommand extends DeliveryJobCommand { + + public static final String COMMAND_WORD_MARK = "com_job"; + public static final String COMMAND_WORD_UNMARK = "uncom_job"; + + public static final String MESSAGE_SUCCESS = "Job now %s"; + + public static final String MESSAGE_USAGE = "Sets the delivered status as not/complete.\n" + + "Parameters: KEYWORD [JOB ID]...\n" + + "Example: " + COMMAND_WORD_MARK + "|" + COMMAND_WORD_UNMARK + " "; + + private final String jobId; + private final Boolean setDelivered; + + /** + * CompleteDeliveryJobCommand。 + * + * @param jobId + * @param setDelivered + */ + public CompleteDeliveryJobCommand(String jobId, Boolean setDelivered) { + requireNonNull(jobId); + requireNonNull(setDelivered); + this.jobId = jobId; + this.setDelivered = setDelivered; + } + + @Override + public CommandResult execute(Model model) { + requireNonNull(model); + List lastShownList = model.getDeliveryJobList(); + Optional deliveryJobToEdit = lastShownList.stream() + .filter(x -> x.getJobId().equals(jobId)) + .findAny(); + + if (deliveryJobToEdit.isPresent()) { + DeliveryJob.Builder toEdit = new DeliveryJob.Builder().copy(deliveryJobToEdit.get()); + toEdit.setDeliveredStatus(setDelivered); + model.setDeliveryJob(deliveryJobToEdit.get(), toEdit.build()); + return new CommandResult(String.format(MESSAGE_SUCCESS, setDelivered ? "completed" : "not completed")); + } else { + return new CommandResult(Messages.MESSAGE_INVALID_JOB_ID); + } + } +} diff --git a/src/main/java/seedu/address/logic/commands/jobs/DeleteDeliveryJobCommand.java b/src/main/java/seedu/address/logic/commands/jobs/DeleteDeliveryJobCommand.java new file mode 100644 index 00000000000..106192d03a4 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/jobs/DeleteDeliveryJobCommand.java @@ -0,0 +1,66 @@ +package seedu.address.logic.commands.jobs; + +import static java.util.Objects.requireNonNull; + +import java.util.List; +import java.util.Optional; + +import seedu.address.commons.core.Messages; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.jobs.DeliveryJob; + +/** + * Deletes a job identified using it's displayed index from the job system. + */ +public class DeleteDeliveryJobCommand extends DeliveryJobCommand { + + public static final String COMMAND_WORD = "delete_job"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Deletes the job identified by the job ID.\n" + + "Parameters: STRING job ID\n" + + "Example: " + COMMAND_WORD + " ALBE29E66F\n" + + "Tip: you can copy the job ID by\n" + + " selecting the job and press\n" + + " Ctrl+C\n" + + "Tip: you can delete job directly by\n" + + " selecting and press DEL key"; + public static final String MESSAGE_DELETE_JOB_SUCCESS = "Deleted Job: %1$s"; + + private final String jobId; + + /** + * Constructs a DeleteDeliveryJobCommand + * @param jobId of which job needs to be deleted + */ + public DeleteDeliveryJobCommand(String jobId) { + requireNonNull(jobId); + this.jobId = jobId; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + List lastShownList = model.getDeliveryJobList(); + Optional deliveryJobToDelete = lastShownList.stream() + .filter(x -> x.getJobId().equals(jobId)) + .findAny(); + + if (deliveryJobToDelete.isEmpty()) { + throw new CommandException(Messages.MESSAGE_INVALID_JOB_ID); + } + + model.deleteDeliveryJob(deliveryJobToDelete.get()); + return new CommandResult(String.format(MESSAGE_DELETE_JOB_SUCCESS, deliveryJobToDelete.get().getJobId())); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof DeleteDeliveryJobCommand // instanceof handles nulls + && jobId.equals(((DeleteDeliveryJobCommand) other).jobId)); // state check + } +} diff --git a/src/main/java/seedu/address/logic/commands/jobs/DeliveryJobCommand.java b/src/main/java/seedu/address/logic/commands/jobs/DeliveryJobCommand.java new file mode 100644 index 00000000000..dd49caed87c --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/jobs/DeliveryJobCommand.java @@ -0,0 +1,11 @@ +package seedu.address.logic.commands.jobs; + +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandGroup; + +/** + * Defines PersonCommand. + */ +abstract class DeliveryJobCommand extends Command { + public static final CommandGroup COMMAND_GROUP = CommandGroup.JOB; +} diff --git a/src/main/java/seedu/address/logic/commands/jobs/EditDeliveryJobCommand.java b/src/main/java/seedu/address/logic/commands/jobs/EditDeliveryJobCommand.java new file mode 100644 index 00000000000..f075f598691 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/jobs/EditDeliveryJobCommand.java @@ -0,0 +1,388 @@ +package seedu.address.logic.commands.jobs; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DELIVERY_DATE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DELIVERY_SLOT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_EARNING; +import static seedu.address.logic.parser.CliSyntax.PREFIX_IS_DELIVERED; +import static seedu.address.logic.parser.CliSyntax.PREFIX_JOB_ID; +import static seedu.address.logic.parser.CliSyntax.PREFIX_RECIPIENT_ID; +import static seedu.address.logic.parser.CliSyntax.PREFIX_SENDER_ID; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_DELIVERY_JOBS; + +import java.util.List; +import java.util.Optional; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.commons.util.CollectionUtil; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.jobs.DeliveryDate; +import seedu.address.model.jobs.DeliveryJob; +import seedu.address.model.jobs.DeliverySlot; +import seedu.address.model.jobs.Earning; + +/** + * Edits the details of an existing job in the job system. + */ +public class EditDeliveryJobCommand extends DeliveryJobCommand { + + public static final String COMMAND_WORD = "edit_job"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits the details of the job identified " + + "by the index number/job id used in the displayed job list. " + + "Existing values will be overwritten by the input values.\n" + + "Parameters: INDEX (must be a positive integer) [" + + PREFIX_JOB_ID + "JOB_ID] " + + "[" + PREFIX_SENDER_ID + "SENDER_ID] " + + "[" + PREFIX_RECIPIENT_ID + "RECIPIENT_ID] " + + "[" + PREFIX_DELIVERY_DATE + "DELIVERY_DATE] " + + "[" + PREFIX_DELIVERY_SLOT + "DELIVERY_SLOT] " + + "[" + PREFIX_EARNING + "EARN] " + + "[" + PREFIX_IS_DELIVERED + "COMPLETE_STATUS]\n" + + "OR\n" + + "Parameters: " + + PREFIX_JOB_ID + "JOB_ID " + + "[" + PREFIX_SENDER_ID + "SENDER_ID] " + + "[" + PREFIX_RECIPIENT_ID + "RECIPIENT_ID] " + + "[" + PREFIX_DELIVERY_DATE + "DELIVERY_DATE] " + + "[" + PREFIX_DELIVERY_SLOT + "DELIVERY_SLOT] " + + "[" + PREFIX_EARNING + "EARN] " + + "[" + PREFIX_IS_DELIVERED + "COMPLETE_STATUS]\n" + + "Example: " + COMMAND_WORD + " 1 " + + PREFIX_SENDER_ID + "ALE874 \n" + + COMMAND_WORD + " " + PREFIX_JOB_ID + "ALBE29E66F " + + PREFIX_IS_DELIVERED + "t"; + + public static final String MESSAGE_EDIT_JOB_SUCCESS = "Edited Job: %1$s"; + public static final String MESSAGE_NOT_EDITED = "At least one field to edit must be provided."; + public static final String MESSAGE_DUPLICATE_JOB = "This job already exists in the address book."; + + private final Optional index; + private final EditDeliveryJobDescriptor editDeliveryJobDescriptor; + + /** + * @param index of the job in the filtered job list to edit + * @param editDeliveryJobDescriptor details to edit the job with + */ + public EditDeliveryJobCommand(Index index, EditDeliveryJobDescriptor editDeliveryJobDescriptor) { + requireNonNull(index); + requireNonNull(editDeliveryJobDescriptor); + + this.index = Optional.of(index); + this.editDeliveryJobDescriptor = new EditDeliveryJobDescriptor(editDeliveryJobDescriptor); + } + + /** + * @param editDeliveryJobDescriptor details to edit the job with + */ + public EditDeliveryJobCommand(EditDeliveryJobDescriptor editDeliveryJobDescriptor) { + requireNonNull(editDeliveryJobDescriptor); + + this.index = Optional.empty(); + this.editDeliveryJobDescriptor = new EditDeliveryJobDescriptor(editDeliveryJobDescriptor); + } + + /** + * Creates and returns a {@code job} with the details of + * {@code deliveryJobToEdit} + * edited with {@code editjobDescriptor}. + */ + private static DeliveryJob createEditedDeliveryJob(DeliveryJob deliveryJobToEdit, + EditDeliveryJobDescriptor editjobDescriptor) { + assert deliveryJobToEdit != null; + DeliveryJob.Builder toEdit = new DeliveryJob.Builder(); + + toEdit.setJobId(deliveryJobToEdit.getJobId()); + + editjobDescriptor.getRecipient().ifPresentOrElse(val -> { + toEdit.setRecipient(val); + }, () -> { + toEdit.setRecipient(deliveryJobToEdit.getRecipientId()); + }); + + editjobDescriptor.getSender().ifPresentOrElse(val -> { + toEdit.setSender(val); + }, () -> { + toEdit.setSender(deliveryJobToEdit.getSenderId()); + }); + + editjobDescriptor.getDeliveryDate().ifPresentOrElse(val -> { + toEdit.setDeliveryDate(val.date); + }, () -> { + deliveryJobToEdit.getDeliveryDate().ifPresent(val -> { + editjobDescriptor.ifClearDeliveryDate(()-> { + toEdit.clearDeliveryDate(); + }, () -> { + toEdit.setDeliveryDate(val.date); + }); + }); + }); + + editjobDescriptor.getDeliverySlot().ifPresentOrElse(val -> { + toEdit.setDeliverySlot(val.value); + }, () -> { + deliveryJobToEdit.getDeliverySlot().ifPresent(val -> { + editjobDescriptor.ifClearDeliverySlot(()-> { + toEdit.clearDeliverySlot(); + }, () -> { + toEdit.setDeliverySlot(val.value); + }); + }); + }); + + editjobDescriptor.getEarning().ifPresentOrElse(val -> { + toEdit.setEarning(val.value); + }, () -> { + deliveryJobToEdit.getEarning().ifPresent(val -> { + toEdit.setEarning(val.value); + }); + }); + + editjobDescriptor.getDelivered().ifPresentOrElse(val -> { + toEdit.setDeliveredStatus(val); + }, () -> { + toEdit.setDeliveredStatus(deliveryJobToEdit.getDeliveredStatus()); + }); + + editjobDescriptor.getDescription().ifPresentOrElse(val -> { + toEdit.setDescription(val); + }, () -> { + toEdit.setDescription(deliveryJobToEdit.getDescription()); + }); + + return toEdit.build(); + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + List lastShownList = model.getDeliveryJobList(); + + Optional deliveryJobToEdit; + if (index.isPresent()) { + if (index.get().getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + deliveryJobToEdit = Optional.of(lastShownList.get(index.get().getZeroBased())); + } else { + if (!editDeliveryJobDescriptor.getJobId().isPresent()) { + throw new CommandException(Messages.MESSAGE_INVALID_JOB_ID); + } + deliveryJobToEdit = lastShownList.stream().filter(x -> x.getJobId().equals(editDeliveryJobDescriptor.jobId)) + .findAny(); + } + + if (deliveryJobToEdit.isPresent()) { + DeliveryJob editedDeliveryJob = createEditedDeliveryJob(deliveryJobToEdit.get(), editDeliveryJobDescriptor); + + if (!deliveryJobToEdit.get().isSameDeliveryJob(editedDeliveryJob) + && model.hasDeliveryJob(editedDeliveryJob)) { + throw new CommandException(MESSAGE_DUPLICATE_JOB); + } + + model.setDeliveryJob(deliveryJobToEdit.get(), editedDeliveryJob); + model.updateFilteredDeliveryJobList(PREDICATE_SHOW_ALL_DELIVERY_JOBS); + return new CommandResult(String.format(MESSAGE_EDIT_JOB_SUCCESS, editedDeliveryJob)); + } else { + throw new CommandException(Messages.MESSAGE_INVALID_JOB_ID); + } + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof EditDeliveryJobCommand)) { + return false; + } + + // state check + EditDeliveryJobCommand e = (EditDeliveryJobCommand) other; + return index.equals(e.index) + && editDeliveryJobDescriptor.equals(e.editDeliveryJobDescriptor); + } + + /** + * Stores the details to edit the job with. Each non-empty field value will + * replace the + * corresponding field value of the job. + */ + public static class EditDeliveryJobDescriptor { + // Identity fields + private String jobId; + + // Delivery informations + private String recipient; + private String sender; + private DeliveryDate deliveryDate; + private DeliverySlot deliverySlot; + private Earning earning; + private boolean isDelivered; + private String description; + + private boolean isDateCleared = false; + private boolean isSlotCleared = false; + + public EditDeliveryJobDescriptor() { + } + + /** + * Copy constructor. + * A defensive copy of {@code tags} is used internally. + */ + public EditDeliveryJobDescriptor(EditDeliveryJobDescriptor toCopy) { + setJobId(toCopy.jobId); + setSender(toCopy.sender); + setRecipient(toCopy.recipient); + setDeliveryDate(toCopy.deliveryDate); + setDeliverySlot(toCopy.deliverySlot); + setEarning(toCopy.earning); + setDelivered(toCopy.isDelivered); + setDescription(toCopy.description); + isDateCleared = toCopy.isDateCleared; + isSlotCleared = toCopy.isSlotCleared; + } + + /** + * Returns true if at least one field is edited. + */ + public boolean isAnyFieldEdited() { + return CollectionUtil.isAnyNonNull(jobId, sender, recipient, deliveryDate, deliverySlot, isDelivered); + } + + public Optional getJobId() { + return Optional.ofNullable(jobId); + } + + public void setJobId(String jobId) { + this.jobId = jobId; + } + + public Optional getRecipient() { + return Optional.ofNullable(recipient); + } + + public void setRecipient(String recipient) { + this.recipient = recipient; + } + + public Optional getSender() { + return Optional.ofNullable(sender); + } + + public void setSender(String sender) { + this.sender = sender; + } + + public Optional getDeliveryDate() { + return Optional.ofNullable(deliveryDate); + } + + public void setDeliveryDate(DeliveryDate deliveryDate) { + this.deliveryDate = deliveryDate; + } + + public Optional getDeliverySlot() { + return Optional.ofNullable(deliverySlot); + } + + public void setDeliverySlot(DeliverySlot deliverySlot) { + this.deliverySlot = deliverySlot; + } + + public Optional getEarning() { + return Optional.ofNullable(earning); + } + + public void setEarning(Earning earning) { + this.earning = earning; + } + + public Optional getDelivered() { + return Optional.ofNullable(isDelivered); + } + + public void setDelivered(boolean isDelivered) { + this.isDelivered = isDelivered; + } + + public void setDescription(String description) { + this.description = description; + } + + public Optional getDescription() { + return Optional.ofNullable(description); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof EditDeliveryJobDescriptor)) { + return false; + } + + // state check + EditDeliveryJobDescriptor e = (EditDeliveryJobDescriptor) other; + + return getJobId().equals(e.getJobId()) + && getSender().equals(e.getSender()) + && getRecipient().equals(e.getRecipient()) + && getDeliveryDate().equals(e.getDeliveryDate()) + && getDeliverySlot().equals(e.getDeliverySlot()) + && getEarning().equals(e.getEarning()) + && getDelivered().equals(e.getDelivered()); + } + + /** + * Sets the clear slot state. + */ + public void clearDeliverySlot() { + isSlotCleared = true; + } + + /** + * Sets the clear date state. + */ + public void clearDeliveryDate() { + isDateCleared = true; + } + + /** + * Handles slot clearing. + * @param s + * @param f + */ + public void ifClearDeliverySlot(Runnable s, Runnable f) { + if (isSlotCleared) { + s.run(); + } else { + f.run(); + } + } + + /** + * Handles date clearing. + * @param s + * @param f + */ + public void ifClearDeliveryDate(Runnable s, Runnable f) { + if (isDateCleared) { + s.run(); + } else { + f.run(); + } + } + } +} diff --git a/src/main/java/seedu/address/logic/commands/jobs/FindDeliveryJobCommand.java b/src/main/java/seedu/address/logic/commands/jobs/FindDeliveryJobCommand.java new file mode 100644 index 00000000000..455dedd3870 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/jobs/FindDeliveryJobCommand.java @@ -0,0 +1,67 @@ +package seedu.address.logic.commands.jobs; + +import static java.util.Objects.requireNonNull; + +import java.util.function.Predicate; + +import seedu.address.commons.core.Messages; +import seedu.address.logic.commands.CommandResult; +import seedu.address.model.Model; +import seedu.address.model.jobs.DeliveryJob; + +/** + * Finds and lists all jobs in job system whose name contains any of the argument keywords. + * Keyword matching is case insensitive. + */ +public class FindDeliveryJobCommand extends DeliveryJobCommand { + + public static final String COMMAND_WORD = "find_job"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all jobs which contain any of " + + "the specified keywords (case-insensitive) and displays them as a list with index numbers.\n" + + "Parameters: keyword_type/KEYWORD [more_type/MORE_KEYWORDS]...\n" + + "Format: " + COMMAND_WORD + " [ji/JOB_ID]" + " [si/SENDER_ID] [ri/RECIPIENT_ID] " + + "[date/DELIVER_DATE] [slot/DELIVERY_SLOT] [earn/EARNING]..." + + "\nExample: " + COMMAND_WORD + " si/ALE874"; + + private final Predicate predicate; + private final String query; + + /** + * Constructs a FindDeliveryJobCommand. + * @param predicate + * @param query for feedback + */ + public FindDeliveryJobCommand(Predicate predicate, String query) { + this.predicate = predicate; + this.query = query; + } + + /** + * Constructs a FindDeliveryJobCommand without feedback. + * @param predicate + * @param query for feedback + */ + public FindDeliveryJobCommand(Predicate predicate) { + this.predicate = predicate; + this.query = ""; + } + + @Override + public CommandResult execute(Model model) { + requireNonNull(model); + model.updateFilteredDeliveryJobList(predicate); + return new CommandResult( + String.format( + Messages.MESSAGE_DELIVERY_JOB_LISTED_OVERVIEW + "\nQuery options:\n" + query, + model.getFilteredDeliveryJobList().size()), + false, false, false, false, false, false, false, false); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof FindDeliveryJobCommand // instanceof handles nulls + && predicate.equals(((FindDeliveryJobCommand) other).predicate)); // state check + } +} diff --git a/src/main/java/seedu/address/logic/commands/jobs/ImportDeliveryJobCommand.java b/src/main/java/seedu/address/logic/commands/jobs/ImportDeliveryJobCommand.java new file mode 100644 index 00000000000..5af6abe269a --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/jobs/ImportDeliveryJobCommand.java @@ -0,0 +1,92 @@ +package seedu.address.logic.commands.jobs; + +import static java.util.Objects.requireNonNull; + +import java.io.File; +import java.io.FileNotFoundException; +import java.util.ArrayList; +import java.util.List; + +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.logic.parser.jobs.ImportDeliveryJobCommandParser; +import seedu.address.model.Model; +import seedu.address.model.jobs.DeliveryJob; +import seedu.address.model.person.Person; + +/** + * Mass imports delivery jobs from CSV file to the delivery job system. + */ +public class ImportDeliveryJobCommand extends DeliveryJobCommand { + + public static final String COMMAND_WORD = "import_job"; + public static final String MESSAGE_EMPTY_FILE = "File is empty"; + public static final String MESSAGE_SUCCESS = "File is imported"; + public static final String MESSAGE_WRONG_FILE = "File type is unsupported. Please upload a CSV file and check if " + + "file extension is named .csv"; + + private final File toAdd; + + /** + * Creates an ImportDeliveryJobCommand to mass add delivery jobs + * from file. + */ + public ImportDeliveryJobCommand(File file) { + requireNonNull(file); + toAdd = file; + } + + @Override + public CommandResult execute(Model model) throws CommandException, ParseException, FileNotFoundException { + requireNonNull(model); + List listOfCustomers = new ArrayList<>(); + + if (!getFileExtensions(toAdd).equals("csv")) { + throw new CommandException(MESSAGE_WRONG_FILE); + } + if (toAdd.length() == 0) { + throw new CommandException(MESSAGE_EMPTY_FILE); + } + + List listOfAddDeliveryJob; + listOfAddDeliveryJob = ImportDeliveryJobCommandParser.parse(toAdd, listOfCustomers); + + for (int i = 0; i < listOfCustomers.size(); i++) { + Person customer = listOfCustomers.get(i); + if (!model.hasPerson(customer) && !customer.getPersonId().equals("null")) { + model.addPerson(customer); + } + } + + for (int i = 0; i < listOfAddDeliveryJob.size(); i++) { + if (!model.hasDeliveryJob(listOfAddDeliveryJob.get(i))) { + model.addDeliveryJob(listOfAddDeliveryJob.get(i)); + } + } + + return new CommandResult(MESSAGE_SUCCESS); + } + + /** + * Gets type of file extension. + * + * @param file File that extension is checked + * @return String file extension name + */ + public String getFileExtensions(File file) { + String fileName = file.getName(); + String extension = ""; + int i = fileName.lastIndexOf("."); + extension = fileName.substring(i + 1, fileName.length()); + return extension; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof ImportDeliveryJobCommand // instanceof handles nulls + && toAdd.equals(((ImportDeliveryJobCommand) other).toAdd)); + } +} + diff --git a/src/main/java/seedu/address/logic/commands/jobs/ListDeliveryJobCommand.java b/src/main/java/seedu/address/logic/commands/jobs/ListDeliveryJobCommand.java new file mode 100644 index 00000000000..3ca1b87f85e --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/jobs/ListDeliveryJobCommand.java @@ -0,0 +1,24 @@ +package seedu.address.logic.commands.jobs; + +import static java.util.Objects.requireNonNull; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_DELIVERY_JOBS; + +import seedu.address.logic.commands.CommandResult; +import seedu.address.model.Model; + +/** + * Lists all jobs in the delivery job system to the user. + */ +public class ListDeliveryJobCommand extends DeliveryJobCommand { + + public static final String COMMAND_WORD = "list_job"; + + public static final String MESSAGE_SUCCESS = "Listed all jobs"; + + @Override + public CommandResult execute(Model model) { + requireNonNull(model); + model.updateFilteredDeliveryJobList(PREDICATE_SHOW_ALL_DELIVERY_JOBS); + return new CommandResult(MESSAGE_SUCCESS, false, false, false, false, false, false, false, false); + } +} diff --git a/src/main/java/seedu/address/logic/commands/AddCommand.java b/src/main/java/seedu/address/logic/commands/person/AddCommand.java similarity index 94% rename from src/main/java/seedu/address/logic/commands/AddCommand.java rename to src/main/java/seedu/address/logic/commands/person/AddCommand.java index 71656d7c5c8..e02d60a603b 100644 --- a/src/main/java/seedu/address/logic/commands/AddCommand.java +++ b/src/main/java/seedu/address/logic/commands/person/AddCommand.java @@ -1,4 +1,4 @@ -package seedu.address.logic.commands; +package seedu.address.logic.commands.person; import static java.util.Objects.requireNonNull; import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; @@ -7,14 +7,16 @@ import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; +import seedu.address.logic.commands.CommandResult; 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 class AddCommand extends PersonCommand { public static final String COMMAND_WORD = "add"; diff --git a/src/main/java/seedu/address/logic/commands/ClearCommand.java b/src/main/java/seedu/address/logic/commands/person/ClearCommand.java similarity index 65% rename from src/main/java/seedu/address/logic/commands/ClearCommand.java rename to src/main/java/seedu/address/logic/commands/person/ClearCommand.java index 9c86b1fa6e4..7cc2ebf27fd 100644 --- a/src/main/java/seedu/address/logic/commands/ClearCommand.java +++ b/src/main/java/seedu/address/logic/commands/person/ClearCommand.java @@ -1,14 +1,17 @@ -package seedu.address.logic.commands; +package seedu.address.logic.commands.person; import static java.util.Objects.requireNonNull; +import seedu.address.logic.commands.CommandGroup; +import seedu.address.logic.commands.CommandResult; import seedu.address.model.AddressBook; import seedu.address.model.Model; /** * Clears the address book. */ -public class ClearCommand extends Command { +public class ClearCommand extends PersonCommand { + public static final CommandGroup COMMAND_GROUP = CommandGroup.PERSON; public static final String COMMAND_WORD = "clear"; public static final String MESSAGE_SUCCESS = "Address book has been cleared!"; diff --git a/src/main/java/seedu/address/logic/commands/DeleteCommand.java b/src/main/java/seedu/address/logic/commands/person/DeleteCommand.java similarity index 92% rename from src/main/java/seedu/address/logic/commands/DeleteCommand.java rename to src/main/java/seedu/address/logic/commands/person/DeleteCommand.java index 02fd256acba..ab4e4ddd90e 100644 --- a/src/main/java/seedu/address/logic/commands/DeleteCommand.java +++ b/src/main/java/seedu/address/logic/commands/person/DeleteCommand.java @@ -1,4 +1,4 @@ -package seedu.address.logic.commands; +package seedu.address.logic.commands.person; import static java.util.Objects.requireNonNull; @@ -6,6 +6,7 @@ import seedu.address.commons.core.Messages; import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.CommandResult; import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.model.Model; import seedu.address.model.person.Person; @@ -13,7 +14,7 @@ /** * Deletes a person identified using it's displayed index from the address book. */ -public class DeleteCommand extends Command { +public class DeleteCommand extends PersonCommand { public static final String COMMAND_WORD = "delete"; diff --git a/src/main/java/seedu/address/logic/commands/EditCommand.java b/src/main/java/seedu/address/logic/commands/person/EditCommand.java similarity index 91% rename from src/main/java/seedu/address/logic/commands/EditCommand.java rename to src/main/java/seedu/address/logic/commands/person/EditCommand.java index 7e36114902f..d9c075536b8 100644 --- a/src/main/java/seedu/address/logic/commands/EditCommand.java +++ b/src/main/java/seedu/address/logic/commands/person/EditCommand.java @@ -1,4 +1,4 @@ -package seedu.address.logic.commands; +package seedu.address.logic.commands.person; import static java.util.Objects.requireNonNull; import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; @@ -17,6 +17,7 @@ import seedu.address.commons.core.Messages; import seedu.address.commons.core.index.Index; import seedu.address.commons.util.CollectionUtil; +import seedu.address.logic.commands.CommandResult; import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.model.Model; import seedu.address.model.person.Address; @@ -29,7 +30,7 @@ /** * Edits the details of an existing person in the address book. */ -public class EditCommand extends Command { +public class EditCommand extends PersonCommand { public static final String COMMAND_WORD = "edit"; @@ -54,7 +55,7 @@ public class EditCommand extends Command { private final EditPersonDescriptor editPersonDescriptor; /** - * @param index of the person in the filtered person list to edit + * @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) { @@ -65,6 +66,23 @@ public EditCommand(Index index, EditPersonDescriptor editPersonDescriptor) { this.editPersonDescriptor = new EditPersonDescriptor(editPersonDescriptor); } + /** + * 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; + + String personId = editPersonDescriptor.getPersonId().orElse(personToEdit.getPersonId()); + 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(personId, updatedName, updatedPhone, updatedEmail, updatedAddress, updatedTags); + } + @Override public CommandResult execute(Model model) throws CommandException { requireNonNull(model); @@ -86,22 +104,6 @@ public CommandResult execute(Model model) throws CommandException { return new CommandResult(String.format(MESSAGE_EDIT_PERSON_SUCCESS, editedPerson)); } - /** - * Creates and returns a {@code Person} with the details of {@code personToEdit} - * edited with {@code editPersonDescriptor}. - */ - private static Person createEditedPerson(Person personToEdit, EditPersonDescriptor editPersonDescriptor) { - assert personToEdit != null; - - Name updatedName = editPersonDescriptor.getName().orElse(personToEdit.getName()); - Phone updatedPhone = editPersonDescriptor.getPhone().orElse(personToEdit.getPhone()); - Email updatedEmail = editPersonDescriptor.getEmail().orElse(personToEdit.getEmail()); - Address updatedAddress = editPersonDescriptor.getAddress().orElse(personToEdit.getAddress()); - Set updatedTags = editPersonDescriptor.getTags().orElse(personToEdit.getTags()); - - return new Person(updatedName, updatedPhone, updatedEmail, updatedAddress, updatedTags); - } - @Override public boolean equals(Object other) { // short circuit if same object @@ -125,13 +127,15 @@ public boolean equals(Object other) { * corresponding field value of the person. */ public static class EditPersonDescriptor { + private String personId; private Name name; private Phone phone; private Email email; private Address address; private Set tags; - public EditPersonDescriptor() {} + public EditPersonDescriptor() { + } /** * Copy constructor. @@ -152,44 +156,44 @@ public boolean isAnyFieldEdited() { return CollectionUtil.isAnyNonNull(name, phone, email, address, tags); } - public void setName(Name name) { - this.name = name; + public void setPersonId(String personId) { + this.personId = personId; + } + + public Optional getPersonId() { + return Optional.ofNullable(personId); } public Optional getName() { return Optional.ofNullable(name); } - public void setPhone(Phone phone) { - this.phone = phone; + public void setName(Name name) { + this.name = name; } public Optional getPhone() { return Optional.ofNullable(phone); } - public void setEmail(Email email) { - this.email = email; + public void setPhone(Phone phone) { + this.phone = phone; } public Optional getEmail() { return Optional.ofNullable(email); } - public void setAddress(Address address) { - this.address = address; + public void setEmail(Email email) { + this.email = email; } public Optional
getAddress() { return Optional.ofNullable(address); } - /** - * Sets {@code tags} to this object's {@code tags}. - * A defensive copy of {@code tags} is used internally. - */ - public void setTags(Set tags) { - this.tags = (tags != null) ? new HashSet<>(tags) : null; + public void setAddress(Address address) { + this.address = address; } /** @@ -201,6 +205,14 @@ public Optional> getTags() { return (tags != null) ? Optional.of(Collections.unmodifiableSet(tags)) : Optional.empty(); } + /** + * 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; + } + @Override public boolean equals(Object other) { // short circuit if same object diff --git a/src/main/java/seedu/address/logic/commands/FindCommand.java b/src/main/java/seedu/address/logic/commands/person/FindCommand.java similarity index 84% rename from src/main/java/seedu/address/logic/commands/FindCommand.java rename to src/main/java/seedu/address/logic/commands/person/FindCommand.java index d6b19b0a0de..15222b7715a 100644 --- a/src/main/java/seedu/address/logic/commands/FindCommand.java +++ b/src/main/java/seedu/address/logic/commands/person/FindCommand.java @@ -1,8 +1,9 @@ -package seedu.address.logic.commands; +package seedu.address.logic.commands.person; import static java.util.Objects.requireNonNull; import seedu.address.commons.core.Messages; +import seedu.address.logic.commands.CommandResult; import seedu.address.model.Model; import seedu.address.model.person.NameContainsKeywordsPredicate; @@ -10,7 +11,7 @@ * Finds and lists all persons in address book whose name contains any of the argument keywords. * Keyword matching is case insensitive. */ -public class FindCommand extends Command { +public class FindCommand extends PersonCommand { public static final String COMMAND_WORD = "find"; @@ -30,7 +31,8 @@ public CommandResult execute(Model model) { requireNonNull(model); model.updateFilteredPersonList(predicate); return new CommandResult( - String.format(Messages.MESSAGE_PERSONS_LISTED_OVERVIEW, model.getFilteredPersonList().size())); + String.format(Messages.MESSAGE_PERSONS_LISTED_OVERVIEW, model.getFilteredPersonList().size()), + false, false, false, false, false, false, false, false); } @Override diff --git a/src/main/java/seedu/address/logic/commands/ListCommand.java b/src/main/java/seedu/address/logic/commands/person/ListCommand.java similarity index 57% rename from src/main/java/seedu/address/logic/commands/ListCommand.java rename to src/main/java/seedu/address/logic/commands/person/ListCommand.java index 84be6ad2596..91e9633f09a 100644 --- a/src/main/java/seedu/address/logic/commands/ListCommand.java +++ b/src/main/java/seedu/address/logic/commands/person/ListCommand.java @@ -1,17 +1,19 @@ -package seedu.address.logic.commands; +package seedu.address.logic.commands.person; import static java.util.Objects.requireNonNull; import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; +import seedu.address.logic.commands.CommandGroup; +import seedu.address.logic.commands.CommandResult; import seedu.address.model.Model; /** * Lists all persons in the address book to the user. */ -public class ListCommand extends Command { +public class ListCommand extends PersonCommand { + public static final CommandGroup COMMAND_GROUP = CommandGroup.GENERAL; public static final String COMMAND_WORD = "list"; - public static final String MESSAGE_SUCCESS = "Listed all persons"; @@ -19,6 +21,6 @@ public class ListCommand extends Command { public CommandResult execute(Model model) { requireNonNull(model); model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); - return new CommandResult(MESSAGE_SUCCESS); + return new CommandResult(MESSAGE_SUCCESS, false, false, false, false, false, false, true, false); } } diff --git a/src/main/java/seedu/address/logic/commands/person/PersonCommand.java b/src/main/java/seedu/address/logic/commands/person/PersonCommand.java new file mode 100644 index 00000000000..6b931b16231 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/person/PersonCommand.java @@ -0,0 +1,11 @@ +package seedu.address.logic.commands.person; + +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandGroup; + +/** + * Defines PersonCommand. + */ +abstract class PersonCommand extends Command { + public static final CommandGroup COMMAND_GROUP = CommandGroup.PERSON; +} diff --git a/src/main/java/seedu/address/logic/commands/reminder/AddReminderCommand.java b/src/main/java/seedu/address/logic/commands/reminder/AddReminderCommand.java new file mode 100644 index 00000000000..dd0a1e3c80c --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/reminder/AddReminderCommand.java @@ -0,0 +1,62 @@ +package seedu.address.logic.commands.reminder; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DESCRIPTION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TIME; + +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandGroup; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.reminder.Reminder; + +/** + * Adds a reminder to the address book. + */ +public class AddReminderCommand extends Command { + public static final CommandGroup COMMAND_GROUP = CommandGroup.REMINDER; + + public static final String COMMAND_WORD = "add_reminder"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a reminder.\n" + + "Parameters: [" + + PREFIX_DESCRIPTION + "DESCRIPTION(Max 50 characters, including space!)] " + + PREFIX_TIME + "YYYY-MM-DD HH:MM" + + "\nExample: " + COMMAND_WORD + " " + + PREFIX_DESCRIPTION + "go home " + + PREFIX_TIME + "2023-03-03 17:00"; + + public static final String MESSAGE_SUCCESS = "New reminder added"; + + public static final String MESSAGE_DATE_TIME_CONSTRAINT = "Dates must be valid date " + + "- only contain numeric characters and spaces, " + + "and it should not be blank.\n" + + "Date must have format like this: YYYY-mm-DD" + + "\nTime must be a valid timing - only contains numeric characters and colon." + + "\nTime must be of format HH:mm"; + + private final Reminder toAdd; + + /** + * Creates an AddReminderCommand to add the specified {@code Reminder} + */ + public AddReminderCommand(Reminder reminder) { + requireNonNull(reminder); + toAdd = reminder; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + model.addReminder(toAdd); + return new CommandResult(String.format(MESSAGE_SUCCESS, toAdd)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof AddReminderCommand // instanceof handles nulls + && toAdd.equals(((AddReminderCommand) other).toAdd)); + } +} diff --git a/src/main/java/seedu/address/logic/commands/reminder/DeleteReminderCommand.java b/src/main/java/seedu/address/logic/commands/reminder/DeleteReminderCommand.java new file mode 100644 index 00000000000..e7f944242f3 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/reminder/DeleteReminderCommand.java @@ -0,0 +1,55 @@ +package seedu.address.logic.commands.reminder; + +import static java.util.Objects.requireNonNull; + +import java.util.List; + +import seedu.address.commons.core.Messages; +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandGroup; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.reminder.Reminder; + +/** + * Deletes a reminder using it's displayed index from notifications. + */ +public class DeleteReminderCommand extends Command { + public static final CommandGroup COMMAND_GROUP = CommandGroup.REMINDER; + + public static final String COMMAND_WORD = "delete_reminder"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Deletes the reminder identified by the index number used in the displayed notification.\n" + + "Parameters: INDEX (must be a positive integer)\n" + + "Example: " + COMMAND_WORD + " 1"; + + public static final String MESSAGE_DELETE_REMINDER_SUCCESS = "Deleted Reminder"; + + private final int targetIndex; + + public DeleteReminderCommand(int targetIndex) { + this.targetIndex = targetIndex - 1; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + List reminderList = model.getReminderList(); + + if (targetIndex > reminderList.size() - 1 || targetIndex < 0) { + throw new CommandException(Messages.MESSAGE_INVALID_REMINDER_DISPLAYED_INDEX); + } + model.deleteReminder(targetIndex); + return new CommandResult(String.format(MESSAGE_DELETE_REMINDER_SUCCESS)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof DeleteReminderCommand // instanceof handles nulls + && targetIndex == (((DeleteReminderCommand) other).targetIndex)); // state check + } + +} diff --git a/src/main/java/seedu/address/logic/commands/reminder/ListReminderCommand.java b/src/main/java/seedu/address/logic/commands/reminder/ListReminderCommand.java new file mode 100644 index 00000000000..b56585c3854 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/reminder/ListReminderCommand.java @@ -0,0 +1,27 @@ +package seedu.address.logic.commands.reminder; + +import static java.util.Objects.requireNonNull; + +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandGroup; +import seedu.address.logic.commands.CommandResult; +import seedu.address.model.Model; + +/** + * Lists all reminders as Notifications to the user. + */ +public class ListReminderCommand extends Command { + public static final CommandGroup COMMAND_GROUP = CommandGroup.REMINDER; + + public static final String COMMAND_WORD = "list_reminder"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Lists all reminders.\n"; + public static final String SHOWING_REMINDER_LIST_MESSAGE = "Opened reminder list window."; + + @Override + public CommandResult execute(Model model) { + requireNonNull(model); + return new CommandResult(SHOWING_REMINDER_LIST_MESSAGE, + false, false, true, false, false); + } +} diff --git a/src/main/java/seedu/address/logic/commands/timetable/TimetableCommand.java b/src/main/java/seedu/address/logic/commands/timetable/TimetableCommand.java new file mode 100644 index 00000000000..d0cd9bc0ccc --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/timetable/TimetableCommand.java @@ -0,0 +1,49 @@ +package seedu.address.logic.commands.timetable; + +import static java.util.Objects.requireNonNull; + +import java.time.LocalDate; + +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandGroup; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.jobs.sorters.SortbyTimeAndEarn; + +/** + * Formats full timetable instructions for every command for display. + */ +public class TimetableCommand extends Command { + public static final CommandGroup COMMAND_GROUP = CommandGroup.TIMETABLE; + + public static final String COMMAND_WORD = "timetable"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Shows timetable of scheduled/uncompleted jobs, " + + "sorted based on increasing timing\n" + + "Example: " + COMMAND_WORD; + + public static final String SHOWING_TIMETABLE_MESSAGE = "Opened timetable window of current week."; + + public static final SortbyTimeAndEarn SORTER = new SortbyTimeAndEarn(); + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + model.updateFocusDate(LocalDate.now()); + model.updateSortedDeliveryJobList(SORTER); + model.updateSortedDeliveryJobListByDate(); + model.updateWeekDeliveryJobList(LocalDate.now()); + + return new CommandResult(SHOWING_TIMETABLE_MESSAGE, false, true, false, false, false); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || other instanceof TimetableCommand; // instanceof handles nulls + } +} + diff --git a/src/main/java/seedu/address/logic/commands/timetable/TimetableCompletedCommand.java b/src/main/java/seedu/address/logic/commands/timetable/TimetableCompletedCommand.java new file mode 100644 index 00000000000..cd554cbd521 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/timetable/TimetableCompletedCommand.java @@ -0,0 +1,34 @@ +package seedu.address.logic.commands.timetable; + +import static java.util.Objects.requireNonNull; + +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandGroup; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.jobs.sorters.SortbyTimeAndEarn; + +/** + * Finds and lists unscheduled jobs - those with invalid slot/date + */ +public class TimetableCompletedCommand extends Command { + public static final CommandGroup COMMAND_GROUP = CommandGroup.TIMETABLE; + public static final SortbyTimeAndEarn SORTER_BY_DATE = new SortbyTimeAndEarn(); + + public static final String COMMAND_WORD = "timetable_completed"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Lists completed jobs"; + public static final String MESSAGE_SUCCESS = "Listed all completed jobs"; + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + model.updateSortedDeliveryJobList(SORTER_BY_DATE); + model.getCompletedDeliveryJobList(); + + return new CommandResult(MESSAGE_SUCCESS, false, false, false, true, false, false, false); + } + +} diff --git a/src/main/java/seedu/address/logic/commands/timetable/TimetableDateCommand.java b/src/main/java/seedu/address/logic/commands/timetable/TimetableDateCommand.java new file mode 100644 index 00000000000..b3af6f0858b --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/timetable/TimetableDateCommand.java @@ -0,0 +1,67 @@ +package seedu.address.logic.commands.timetable; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DATE; + +import java.time.LocalDate; + +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandGroup; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.jobs.sorters.SortbyTimeAndEarn; + +/** + * Shows timetable of the requested week based on user's date input + */ +public class TimetableDateCommand extends Command { + public static final CommandGroup COMMAND_GROUP = CommandGroup.TIMETABLE; + + public static final String COMMAND_WORD = "timetable_date"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Shows timetable of scheduled/uncompleted jobs in the week, " + + "based on user's input date\n" + + "Parameters: " + + PREFIX_DATE + "DATE " + + "\nExample: " + COMMAND_WORD + " " + + PREFIX_DATE + "2023-03-15"; + + public static final String SHOWING_TIMETABLE_MESSAGE = "Showed timetable of the week containing day: %s."; + + public static final SortbyTimeAndEarn SORTER = new SortbyTimeAndEarn(); + private final LocalDate jobDate; + + /** + * Updates and shows timetable of jobs in week based on input date + * @param jobDate input date + */ + public TimetableDateCommand(LocalDate jobDate) { + requireNonNull(jobDate); + this.jobDate = jobDate; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + model.updateFocusDate(jobDate); + model.updateSortedDeliveryJobList(SORTER); + model.updateSortedDeliveryJobListByDate(); + model.updateWeekDeliveryJobList(jobDate); + + String showTimetableMessage = String.format(SHOWING_TIMETABLE_MESSAGE, jobDate.toString()); + CommandResult commandResult = new CommandResult(showTimetableMessage, false, true, false, false, false); + commandResult.setShowTimetableDate(true); + return commandResult; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof TimetableDateCommand // instanceof handles nulls + && jobDate.equals(((TimetableDateCommand) other).jobDate)); + } + +} diff --git a/src/main/java/seedu/address/logic/commands/timetable/TimetableUnscheduleCommand.java b/src/main/java/seedu/address/logic/commands/timetable/TimetableUnscheduleCommand.java new file mode 100644 index 00000000000..affcb841d2c --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/timetable/TimetableUnscheduleCommand.java @@ -0,0 +1,34 @@ +package seedu.address.logic.commands.timetable; + +import static java.util.Objects.requireNonNull; + +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandGroup; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.jobs.sorters.SortbyTimeAndEarn; + +/** + * Finds and lists unscheduled jobs - those with invalid slot/date + */ +public class TimetableUnscheduleCommand extends Command { + public static final CommandGroup COMMAND_GROUP = CommandGroup.TIMETABLE; + public static final SortbyTimeAndEarn SORTER_BY_DATE = new SortbyTimeAndEarn(); + + public static final String COMMAND_WORD = "timetable_unscheduled"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Lists unscheduled jobs with invalid slot/date"; + public static final String MESSAGE_SUCCESS = "Listed all unscheduled jobs with invalid slot/date"; + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + model.updateSortedDeliveryJobList(SORTER_BY_DATE); + model.getUnscheduledDeliveryJobList(); + + return new CommandResult(MESSAGE_SUCCESS, false, false, true, false, false, false, false); + } + +} diff --git a/src/main/java/seedu/address/logic/parser/ArgumentMultimap.java b/src/main/java/seedu/address/logic/parser/ArgumentMultimap.java index 954c8e18f8e..5711b786037 100644 --- a/src/main/java/seedu/address/logic/parser/ArgumentMultimap.java +++ b/src/main/java/seedu/address/logic/parser/ArgumentMultimap.java @@ -15,7 +15,9 @@ */ public class ArgumentMultimap { - /** Prefixes mapped to their respective arguments**/ + /** + * Prefixes mapped to their respective arguments + **/ private final Map> argMultimap = new HashMap<>(); /** diff --git a/src/main/java/seedu/address/logic/parser/ArgumentTokenizer.java b/src/main/java/seedu/address/logic/parser/ArgumentTokenizer.java index 5c9aebfa488..3be8e89059d 100644 --- a/src/main/java/seedu/address/logic/parser/ArgumentTokenizer.java +++ b/src/main/java/seedu/address/logic/parser/ArgumentTokenizer.java @@ -7,11 +7,11 @@ /** * Tokenizes arguments string of the form: {@code preamble value value ...}
- * e.g. {@code some preamble text t/ 11.00 t/12.00 k/ m/ July} where prefixes are {@code t/ k/ m/}.
+ * e.g. {@code some preamble text t/ 11.00 t/12.00 k/ m/ July} where prefixes are {@code t/ k/ m/}.
* 1. An argument's value can be an empty string e.g. the value of {@code k/} in the above example.
* 2. Leading and trailing whitespaces of an argument value will be discarded.
* 3. An argument may be repeated and all its values will be accumulated e.g. the value of {@code t/} - * in the above example.
+ * in the above example.
*/ public class ArgumentTokenizer { @@ -21,7 +21,7 @@ public class ArgumentTokenizer { * * @param argsString Arguments string of the form: {@code preamble value value ...} * @param prefixes Prefixes to tokenize the arguments string with - * @return ArgumentMultimap object that maps prefixes to their arguments + * @return ArgumentMultimap object that maps prefixes to their arguments */ public static ArgumentMultimap tokenize(String argsString, Prefix... prefixes) { List positions = findAllPrefixPositions(argsString, prefixes); @@ -33,7 +33,7 @@ public static ArgumentMultimap tokenize(String argsString, Prefix... prefixes) { * * @param argsString Arguments string of the form: {@code preamble value value ...} * @param prefixes Prefixes to find in the arguments string - * @return List of zero-based prefix positions in the given arguments string + * @return List of zero-based prefix positions in the given arguments string */ private static List findAllPrefixPositions(String argsString, Prefix... prefixes) { return Arrays.stream(prefixes) @@ -62,7 +62,7 @@ private static List findPrefixPositions(String argsString, Prefi * {@code argsString} starting from index {@code fromIndex}. An occurrence * is valid if there is a whitespace before {@code prefix}. Returns -1 if no * such occurrence can be found. - * + *

* E.g if {@code argsString} = "e/hip/900", {@code prefix} = "p/" and * {@code fromIndex} = 0, this method returns -1 as there are no valid * occurrences of "p/" with whitespace before it. However, if @@ -82,7 +82,7 @@ private static int findPrefixPosition(String argsString, String prefix, int from * * @param argsString Arguments string of the form: {@code preamble value value ...} * @param prefixPositions Zero-based positions of all prefixes in {@code argsString} - * @return ArgumentMultimap object that maps prefixes to their arguments + * @return ArgumentMultimap object that maps prefixes to their arguments */ private static ArgumentMultimap extractArguments(String argsString, List prefixPositions) { @@ -114,8 +114,8 @@ private static ArgumentMultimap extractArguments(String argsString, List\\S+)(?.*)"); + + /** + * Returns the command group before actual parsing. + * + * @param userInput full user input string + * @return the command group on the user input + * @throws ParseException if the user input does not conform the expected format + */ + public CommandGroup parseCommandGroup(String userInput) throws ParseException { + final Matcher matcher = BASIC_COMMAND_FORMAT.matcher(userInput.trim()); + if (!matcher.matches()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, HelpCommand.MESSAGE_USAGE)); + } + + final String commandWord = matcher.group("commandWord"); + switch (commandWord.toLowerCase()) { + + case AddCommand.COMMAND_WORD: + case EditCommand.COMMAND_WORD: + case DeleteCommand.COMMAND_WORD: + case FindCommand.COMMAND_WORD: + case ClearCommand.COMMAND_WORD: + return FindCommand.COMMAND_GROUP; + case ListCommand.COMMAND_WORD: + case ExitCommand.COMMAND_WORD: + case HelpCommand.COMMAND_WORD: + return HelpCommand.COMMAND_GROUP; + case AddReminderCommand.COMMAND_WORD: + case ListReminderCommand.COMMAND_WORD: + case DeleteReminderCommand.COMMAND_WORD: + return DeleteReminderCommand.COMMAND_GROUP; + case TimetableCommand.COMMAND_WORD: + case TimetableDateCommand.COMMAND_WORD: + case TimetableUnscheduleCommand.COMMAND_WORD: + case TimetableCompletedCommand.COMMAND_WORD: + return TimetableCompletedCommand.COMMAND_GROUP; + case ListDeliveryJobCommand.COMMAND_WORD: + case AddDeliveryJobCommand.COMMAND_WORD: + case EditDeliveryJobCommand.COMMAND_WORD: + case FindDeliveryJobCommand.COMMAND_WORD: + case DeleteDeliveryJobCommand.COMMAND_WORD: + case CompleteDeliveryJobCommand.COMMAND_WORD_MARK: + case CompleteDeliveryJobCommand.COMMAND_WORD_UNMARK: + return CompleteDeliveryJobCommand.COMMAND_GROUP; + case StatisticsCommand.COMMAND_WORD: + return StatisticsCommand.COMMAND_GROUP; + default: + throw new ParseException(MESSAGE_UNKNOWN_COMMAND); + } + } + + /** + * Parses user input into command for execution. + * + * @param userInput full user input string + * @return the command based on the user input + * @throws ParseException if the user input does not conform the expected format + */ + public Command parseCommand(String userInput) throws ParseException { + final Matcher matcher = BASIC_COMMAND_FORMAT.matcher(userInput.trim()); + if (!matcher.matches()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, HelpCommand.MESSAGE_USAGE)); + } + + final String commandWord = matcher.group("commandWord"); + final String arguments = matcher.group("arguments"); + switch (commandWord.toLowerCase()) { + + case AddCommand.COMMAND_WORD: + return new AddCommandParser().parse(arguments); + + case EditCommand.COMMAND_WORD: + return new EditCommandParser().parse(arguments); + + case DeleteCommand.COMMAND_WORD: + return new DeleteCommandParser().parse(arguments); + + case ClearCommand.COMMAND_WORD: + return new ClearCommand(); + + case FindCommand.COMMAND_WORD: + return new FindCommandParser().parse(arguments); + + case ListCommand.COMMAND_WORD: + return new ListCommand(); + + case ExitCommand.COMMAND_WORD: + return new ExitCommand(); + + case HelpCommand.COMMAND_WORD: + return new HelpCommand(); + + case AddReminderCommand.COMMAND_WORD: + return new AddReminderParser().parse(arguments); + + case ListReminderCommand.COMMAND_WORD: + return new ListReminderCommand(); + + case DeleteReminderCommand.COMMAND_WORD: + return new DeleteReminderParser().parse(arguments); + + case TimetableCommand.COMMAND_WORD: + return new TimetableCommand(); + + case TimetableDateCommand.COMMAND_WORD: + return new TimetableDateCommandParser().parse(arguments); + + case TimetableUnscheduleCommand.COMMAND_WORD: + return new TimetableUnscheduleCommand(); + + case TimetableCompletedCommand.COMMAND_WORD: + return new TimetableCompletedCommand(); + + case ListDeliveryJobCommand.COMMAND_WORD: + return new ListDeliveryJobCommand(); + + case AddDeliveryJobCommand.COMMAND_WORD: + return new AddDeliveryJobCommandParser().parse(arguments); + + case EditDeliveryJobCommand.COMMAND_WORD: + return new EditDeliveryJobCommandParser().parse(arguments); + + case FindDeliveryJobCommand.COMMAND_WORD: + return new FindDeliveryJobCommandParser().parse(arguments); + + case DeleteDeliveryJobCommand.COMMAND_WORD: + return new DeleteDeliveryJobCommandParser().parse(arguments); + + case CompleteDeliveryJobCommand.COMMAND_WORD_MARK: + return new CompleteDeliveryJobCommandParser(true).parse(arguments); + + case CompleteDeliveryJobCommand.COMMAND_WORD_UNMARK: + return new CompleteDeliveryJobCommandParser(false).parse(arguments); + + case StatisticsCommand.COMMAND_WORD: + return new StatisticsCommand(); + + default: + throw new ParseException(MESSAGE_UNKNOWN_COMMAND); + } + } + +} diff --git a/src/main/java/seedu/address/logic/parser/Parser.java b/src/main/java/seedu/address/logic/parser/Parser.java index d6551ad8e3f..ce644a9c6fd 100644 --- a/src/main/java/seedu/address/logic/parser/Parser.java +++ b/src/main/java/seedu/address/logic/parser/Parser.java @@ -10,6 +10,7 @@ public interface Parser { /** * Parses {@code userInput} into a command and returns it. + * * @throws ParseException if {@code userInput} does not conform the expected format */ T parse(String userInput) throws ParseException; diff --git a/src/main/java/seedu/address/logic/parser/ParserUtil.java b/src/main/java/seedu/address/logic/parser/ParserUtil.java index b117acb9c55..e766de63701 100644 --- a/src/main/java/seedu/address/logic/parser/ParserUtil.java +++ b/src/main/java/seedu/address/logic/parser/ParserUtil.java @@ -2,6 +2,8 @@ import static java.util.Objects.requireNonNull; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; import java.util.Collection; import java.util.HashSet; import java.util.Set; @@ -9,6 +11,7 @@ import seedu.address.commons.core.index.Index; import seedu.address.commons.util.StringUtil; import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.jobs.DeliveryDate; import seedu.address.model.person.Address; import seedu.address.model.person.Email; import seedu.address.model.person.Name; @@ -25,6 +28,7 @@ public class ParserUtil { /** * Parses {@code oneBasedIndex} into an {@code Index} and returns it. Leading and trailing whitespaces will be * trimmed. + * * @throws ParseException if the specified index is invalid (not non-zero unsigned integer). */ public static Index parseIndex(String oneBasedIndex) throws ParseException { @@ -95,6 +99,24 @@ public static Email parseEmail(String email) throws ParseException { return new Email(trimmedEmail); } + /** + * Parses a {@code String date} into an {@code LocalDate date}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code LocalDate} is invalid. + */ + public static LocalDate parseDate(String date) throws ParseException { + requireNonNull(date); + String trimmedDate = date.trim(); + if (!DeliveryDate.isValidDate(trimmedDate)) { + throw new ParseException(DeliveryDate.MESSAGE_CONSTRAINTS); + } + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + LocalDate jobDate = LocalDate.parse(trimmedDate, formatter); + return jobDate; + } + + /** * Parses a {@code String tag} into a {@code Tag}. * Leading and trailing whitespaces will be trimmed. diff --git a/src/main/java/seedu/address/logic/parser/jobs/AddDeliveryJobCommandParser.java b/src/main/java/seedu/address/logic/parser/jobs/AddDeliveryJobCommandParser.java new file mode 100644 index 00000000000..c4c36576c62 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/jobs/AddDeliveryJobCommandParser.java @@ -0,0 +1,111 @@ +package seedu.address.logic.parser.jobs; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DELIVERY_DATE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DELIVERY_SLOT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_EARNING; +import static seedu.address.logic.parser.CliSyntax.PREFIX_RECIPIENT_ID; +import static seedu.address.logic.parser.CliSyntax.PREFIX_SENDER_ID; + +import java.util.Optional; +import java.util.logging.Logger; +import java.util.stream.Stream; + +import seedu.address.commons.core.LogsCenter; +import seedu.address.logic.commands.jobs.AddDeliveryJobCommand; +import seedu.address.logic.parser.ArgumentMultimap; +import seedu.address.logic.parser.ArgumentTokenizer; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.Prefix; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.jobs.DeliveryDate; +import seedu.address.model.jobs.DeliveryJob; +import seedu.address.model.jobs.DeliverySlot; +import seedu.address.model.jobs.Earning; + +/** + * Parses input arguments and creates a new AddDeliveryJobCommand object + */ +public class AddDeliveryJobCommandParser implements Parser { + private static final String MESSAGE_SLOT_MISSING = "Delivery slot missing, " + + "both date and slot are required for scheduling"; + private static final String MESSAGE_DATE_MISSING = "Delivery date missing, " + + "both date and slot are required for scheduling"; + private final Logger logger = LogsCenter.getLogger(getClass()); + + /** + * Returns true if none of the prefixes contains empty {@code Optional} values + * in the given + * {@code ArgumentMultimap}. + */ + private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); + } + + /** + * Parses the given {@code String} of arguments in the context of the AddCommand + * and returns an AddCommand object for execution. + * + * @throws ParseException if the user input does not conform the expected format + */ + public AddDeliveryJobCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_SENDER_ID, PREFIX_RECIPIENT_ID, + PREFIX_DELIVERY_DATE, PREFIX_DELIVERY_SLOT, PREFIX_EARNING); + + if (!arePrefixesPresent(argMultimap, PREFIX_SENDER_ID, PREFIX_RECIPIENT_ID) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddDeliveryJobCommand.MESSAGE_USAGE)); + } + + String sid = argMultimap.getValue(PREFIX_SENDER_ID).get(); + String rid = argMultimap.getValue(PREFIX_RECIPIENT_ID).get(); + Optional date = Optional.empty(); + Optional slot = Optional.empty(); + Optional earn; + + if (argMultimap.getValue(PREFIX_DELIVERY_DATE).isPresent() + && !argMultimap.getValue(PREFIX_DELIVERY_SLOT).isPresent()) { + throw new ParseException( + String.format(MESSAGE_SLOT_MISSING)); + } else if (!argMultimap.getValue(PREFIX_DELIVERY_DATE).isPresent() + && argMultimap.getValue(PREFIX_DELIVERY_SLOT).isPresent()) { + throw new ParseException( + String.format(MESSAGE_DATE_MISSING)); + } else if (argMultimap.getValue(PREFIX_DELIVERY_DATE).isPresent() + && argMultimap.getValue(PREFIX_DELIVERY_SLOT).isPresent()) { + try { + date = argMultimap.getValue(PREFIX_DELIVERY_DATE).map(x -> new DeliveryDate(x)); + } catch (Exception e) { + throw new ParseException(DeliveryDate.MESSAGE_CONSTRAINTS); + } + + try { + slot = argMultimap.getValue(PREFIX_DELIVERY_SLOT).map(x -> new DeliverySlot(x)); + } catch (Exception e) { + throw new ParseException(DeliverySlot.MESSAGE_CONSTRAINTS); + } + } + + if (argMultimap.getValue(PREFIX_EARNING).isPresent()) { + try { + earn = argMultimap.getValue(PREFIX_EARNING).map(x -> new Earning(x)); + } catch (Exception e) { + throw new ParseException(Earning.MESSAGE_CONSTRAINTS); + } + } else { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddDeliveryJobCommand.MESSAGE_USAGE)); + } + + if (sid.equals("")) { + throw new ParseException(AddDeliveryJobCommand.MESSAGE_SENDER_CONSTRAINT); + } + + if (rid.equals("")) { + throw new ParseException(AddDeliveryJobCommand.MESSAGE_RECIPIENT_CONSTRAINT); + } + + return new AddDeliveryJobCommand(new DeliveryJob(rid, sid, date, slot, earn, "")); + } +} diff --git a/src/main/java/seedu/address/logic/parser/jobs/CompleteDeliveryJobCommandParser.java b/src/main/java/seedu/address/logic/parser/jobs/CompleteDeliveryJobCommandParser.java new file mode 100644 index 00000000000..63759f13ff9 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/jobs/CompleteDeliveryJobCommandParser.java @@ -0,0 +1,35 @@ +package seedu.address.logic.parser.jobs; + +import seedu.address.logic.commands.jobs.CompleteDeliveryJobCommand; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a new CompleteDeliveryJobCommandParser object + */ +public class CompleteDeliveryJobCommandParser implements Parser { + + private Boolean setDelivered; + + /** + * Constructs a CompleteDeliveryJobCommandParser. + * + * @param setDelivered + */ + public CompleteDeliveryJobCommandParser(Boolean setDelivered) { + this.setDelivered = setDelivered; + } + + /** + * Parses the given {@code String} of arguments in the context of the + * FindCommand + * and returns a FindCommand object for execution. + * + * @throws ParseException if the user input does not conform the expected format + */ + public CompleteDeliveryJobCommand parse(String args) throws ParseException { + String jobId = args.trim(); + return new CompleteDeliveryJobCommand(jobId, setDelivered); + } + +} diff --git a/src/main/java/seedu/address/logic/parser/jobs/DeleteDeliveryJobCommandParser.java b/src/main/java/seedu/address/logic/parser/jobs/DeleteDeliveryJobCommandParser.java new file mode 100644 index 00000000000..a32ed797e17 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/jobs/DeleteDeliveryJobCommandParser.java @@ -0,0 +1,27 @@ +package seedu.address.logic.parser.jobs; + +import seedu.address.logic.commands.jobs.DeleteDeliveryJobCommand; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a new DeleteCommand object + */ +public class DeleteDeliveryJobCommandParser implements Parser { + + private static final String EMPTY_CMD = "Job ID cannot be empty!\n"; + /** + * Parses the given {@code String} of arguments in the context of the DeleteCommand + * and returns a DeleteCommand object for execution. + * + * @throws ParseException if the user input does not conform the expected format + */ + public DeleteDeliveryJobCommand parse(String args) throws ParseException { + String jobId = args.trim(); + if (jobId.isEmpty()) { + throw new ParseException(EMPTY_CMD + DeleteDeliveryJobCommand.MESSAGE_USAGE); + } + return new DeleteDeliveryJobCommand(jobId); + } + +} diff --git a/src/main/java/seedu/address/logic/parser/jobs/EditDeliveryJobCommandParser.java b/src/main/java/seedu/address/logic/parser/jobs/EditDeliveryJobCommandParser.java new file mode 100644 index 00000000000..e61df239479 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/jobs/EditDeliveryJobCommandParser.java @@ -0,0 +1,157 @@ +package seedu.address.logic.parser.jobs; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_EITHER_INDEX_OR_ID; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DELIVERY_DATE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DELIVERY_SLOT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_EARNING; +import static seedu.address.logic.parser.CliSyntax.PREFIX_IS_DELIVERED; +import static seedu.address.logic.parser.CliSyntax.PREFIX_JOB_ID; +import static seedu.address.logic.parser.CliSyntax.PREFIX_RECIPIENT_ID; +import static seedu.address.logic.parser.CliSyntax.PREFIX_SENDER_ID; + +import java.util.Optional; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.jobs.EditDeliveryJobCommand; +import seedu.address.logic.commands.jobs.EditDeliveryJobCommand.EditDeliveryJobDescriptor; +import seedu.address.logic.parser.ArgumentMultimap; +import seedu.address.logic.parser.ArgumentTokenizer; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.ParserUtil; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.jobs.DeliveryDate; +import seedu.address.model.jobs.DeliverySlot; +import seedu.address.model.jobs.Earning; + +/** + * Parses input arguments and creates a new EditCommand object + */ +public class EditDeliveryJobCommandParser implements Parser { + + private static final String MESSAGE_DATE_FORMAT = "Invalid date format!\nDate/ | YYYY-MM-DD"; + private static final String MESSAGE_SLOT_FORMAT = "Invalid slot format!\nSlot/ | 1..5"; + private static final String MESSAGE_EARN_FORMAT = "Invalid earning format!\nearn/ d.f "; + private static final String MESSAGE_EMPTY_SENDER_ID = "Sender id missing!\nsi/ id "; + private static final String MESSAGE_EMPTY_JOB_ID = "Job id missing!\nji/ id "; + private static final String MESSAGE_EMPTY_RECIPIENT_ID = "Recipient id missing!\nri/ id "; + private static final String MESSAGE_EMPTY_STATUS = "Invalid status!\nDone/ t | f "; + + /** + * Parses the given {@code String} of arguments in the context of the + * EditCommand + * and returns an EditCommand object for execution. + * + * @throws ParseException if the user input does not conform the expected format + */ + public EditDeliveryJobCommand parse(String args) throws ParseException { + requireNonNull(args); + ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_JOB_ID, PREFIX_SENDER_ID, + PREFIX_RECIPIENT_ID, + PREFIX_DELIVERY_DATE, PREFIX_DELIVERY_SLOT, PREFIX_EARNING, PREFIX_IS_DELIVERED); + + Optional index = Optional.empty(); + + try { + index = Optional.of(ParserUtil.parseIndex(argMultimap.getPreamble())); + } catch (ParseException pe) { + // try if index exist + } + + if (argMultimap.getValue(PREFIX_JOB_ID).isPresent() && index.isPresent()) { + throw new ParseException( + String.format(MESSAGE_EITHER_INDEX_OR_ID, EditDeliveryJobCommand.MESSAGE_USAGE)); + } + + if (argMultimap.getValue(PREFIX_JOB_ID).isEmpty() && index.isEmpty()) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditDeliveryJobCommand.MESSAGE_USAGE)); + } + + EditDeliveryJobDescriptor editDeliveryJobDescriptor = new EditDeliveryJobDescriptor(); + argMultimap.getValue(PREFIX_JOB_ID).ifPresent(val -> editDeliveryJobDescriptor.setJobId(val)); + + if (argMultimap.getValue(PREFIX_SENDER_ID).isPresent()) { + String val = argMultimap.getValue(PREFIX_SENDER_ID).get(); + if (val.isBlank()) { + throw new ParseException(String.format(MESSAGE_EMPTY_SENDER_ID)); + } + + editDeliveryJobDescriptor.setSender(val); + } + + if (argMultimap.getValue(PREFIX_RECIPIENT_ID).isPresent()) { + String val = argMultimap.getValue(PREFIX_RECIPIENT_ID).get(); + if (val.isBlank()) { + throw new ParseException(String.format(MESSAGE_EMPTY_RECIPIENT_ID)); + } + + editDeliveryJobDescriptor.setRecipient(val); + } + + if (argMultimap.getValue(PREFIX_DELIVERY_DATE).isPresent()) { + try { + String val = argMultimap.getValue(PREFIX_DELIVERY_DATE).get(); + DeliveryDate toEditDate; + if (val.isBlank()) { + toEditDate = DeliveryDate.placeholder(); + } else { + toEditDate = new DeliveryDate(val); + } + editDeliveryJobDescriptor.setDeliveryDate(toEditDate); + } catch (IllegalArgumentException e) { + throw new ParseException( + String.format(MESSAGE_DATE_FORMAT, e.getMessage())); + } + } + + if (argMultimap.getValue(PREFIX_DELIVERY_SLOT).isPresent()) { + try { + String val = argMultimap.getValue(PREFIX_DELIVERY_SLOT).get(); + DeliverySlot toEditSlot; + if (val.isBlank()) { + toEditSlot = DeliverySlot.placeholder(); + } else { + toEditSlot = new DeliverySlot(val); + } + editDeliveryJobDescriptor.setDeliverySlot(toEditSlot); + } catch (IllegalArgumentException e) { + throw new ParseException( + String.format(MESSAGE_SLOT_FORMAT, e.getMessage())); + } + } + + if (argMultimap.getValue(PREFIX_EARNING).isPresent()) { + try { + String val = argMultimap.getValue(PREFIX_EARNING).get(); + editDeliveryJobDescriptor.setEarning(new Earning(val)); + } catch (IllegalArgumentException e) { + throw new ParseException( + String.format(MESSAGE_EARN_FORMAT, e.getMessage())); + } + } + + if (argMultimap.getValue(PREFIX_IS_DELIVERED).isPresent()) { + String val = argMultimap.getValue(PREFIX_IS_DELIVERED).get().toLowerCase(); + boolean isDone = false; + if (val.isBlank() || (!val.equals("t") && !val.equals("f"))) { + throw new ParseException(String.format(MESSAGE_EMPTY_STATUS)); + } + if (val.startsWith("t")) { + isDone = true; + } + editDeliveryJobDescriptor.setDelivered(isDone); + } + + if (!editDeliveryJobDescriptor.isAnyFieldEdited()) { + throw new ParseException(EditDeliveryJobCommand.MESSAGE_NOT_EDITED); + } + + if (index.isPresent()) { + return new EditDeliveryJobCommand(index.get(), editDeliveryJobDescriptor); + } else { + return new EditDeliveryJobCommand(editDeliveryJobDescriptor); + } + } +} diff --git a/src/main/java/seedu/address/logic/parser/jobs/FindDeliveryJobCommandParser.java b/src/main/java/seedu/address/logic/parser/jobs/FindDeliveryJobCommandParser.java new file mode 100644 index 00000000000..b4919c93b05 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/jobs/FindDeliveryJobCommandParser.java @@ -0,0 +1,197 @@ +package seedu.address.logic.parser.jobs; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DELIVERY_DATE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DELIVERY_SLOT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_EARNING; +import static seedu.address.logic.parser.CliSyntax.PREFIX_IS_DELIVERED; +import static seedu.address.logic.parser.CliSyntax.PREFIX_JOB_ID; +import static seedu.address.logic.parser.CliSyntax.PREFIX_RECIPIENT_ID; +import static seedu.address.logic.parser.CliSyntax.PREFIX_SENDER_ID; + +import java.util.function.Predicate; +import java.util.stream.Stream; + +import seedu.address.logic.commands.jobs.FindDeliveryJobCommand; +import seedu.address.logic.parser.ArgumentMultimap; +import seedu.address.logic.parser.ArgumentTokenizer; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.Prefix; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.jobs.DeliveryDate; +import seedu.address.model.jobs.DeliveryJob; +import seedu.address.model.jobs.DeliverySlot; +import seedu.address.model.jobs.Earning; +import seedu.address.model.jobs.predicate.DeliveryJobContainsDeliveryDatePredicate; +import seedu.address.model.jobs.predicate.DeliveryJobContainsDeliverySlotPredicate; +import seedu.address.model.jobs.predicate.DeliveryJobContainsEarningPredicate; +import seedu.address.model.jobs.predicate.DeliveryJobContainsJobIdPredicate; +import seedu.address.model.jobs.predicate.DeliveryJobContainsRecipientIdPredicate; +import seedu.address.model.jobs.predicate.DeliveryJobContainsSenderIdPredicate; +import seedu.address.model.jobs.predicate.DeliveryJobContainsStatusPredicate; + +/** + * Parses input arguments and creates a new FindCommand object + */ +public class FindDeliveryJobCommandParser implements Parser { + + private static final String MESSAGE_DATE_FORMAT = "Invalid date format!\nDate/ | YYYY-MM-DD"; + private static final String MESSAGE_SLOT_FORMAT = "Invalid slot format!\nSlot/ | 1..5"; + private static final String MESSAGE_EARN_FORMAT = "Invalid earning format!\nearn/ d.f "; + private static final String MESSAGE_EMPTY_SENDER_ID = "Sender id missing!\nsi/ id "; + private static final String MESSAGE_EMPTY_JOB_ID = "Job id missing!\nji/ id "; + private static final String MESSAGE_EMPTY_RECIPIENT_ID = "Recipient id missing!\nri/ id "; + private static final String MESSAGE_EMPTY_STATUS = "Invalid status!\nDone/ t | f "; + + /** + * Returns true if none of the prefixes contains empty {@code Optional} values + * in the given + * {@code ArgumentMultimap}. + */ + private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).anyMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); + } + + /** + * Parses the given {@code String} of arguments in the context of the + * FindCommand + * and returns a FindCommand object for execution. + * + * @throws ParseException if the user input does not conform the expected format + */ + public FindDeliveryJobCommand parse(String args) throws ParseException { + + ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_JOB_ID, PREFIX_SENDER_ID, + PREFIX_RECIPIENT_ID, + PREFIX_DELIVERY_DATE, PREFIX_DELIVERY_SLOT, PREFIX_EARNING, PREFIX_IS_DELIVERED); + + if (!arePrefixesPresent(argMultimap, PREFIX_JOB_ID, PREFIX_SENDER_ID, PREFIX_RECIPIENT_ID, + PREFIX_DELIVERY_DATE, PREFIX_DELIVERY_SLOT, PREFIX_EARNING, PREFIX_IS_DELIVERED) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindDeliveryJobCommand.MESSAGE_USAGE)); + } + + Predicate predicate = null; + String query = ""; + + if (argMultimap.getValue(PREFIX_JOB_ID).isPresent()) { + String val = argMultimap.getValue(PREFIX_JOB_ID).get(); + if (val.isBlank()) { + throw new ParseException(String.format(MESSAGE_EMPTY_JOB_ID)); + } + query += "job id: " + val + "\n"; + predicate = new DeliveryJobContainsJobIdPredicate(val.toUpperCase()); + } + + if (argMultimap.getValue(PREFIX_SENDER_ID).isPresent()) { + String val = argMultimap.getValue(PREFIX_SENDER_ID).get(); + if (val.isBlank()) { + throw new ParseException(String.format(MESSAGE_EMPTY_SENDER_ID)); + } + + if (predicate != null) { + predicate = predicate.and(new DeliveryJobContainsSenderIdPredicate(val.toUpperCase())); + } else { + predicate = new DeliveryJobContainsSenderIdPredicate(val.toUpperCase()); + } + query += "sender id: " + val + "\n"; + } + + if (argMultimap.getValue(PREFIX_RECIPIENT_ID).isPresent()) { + String val = argMultimap.getValue(PREFIX_RECIPIENT_ID).get(); + if (val.isBlank()) { + throw new ParseException(String.format(MESSAGE_EMPTY_RECIPIENT_ID)); + } + + if (predicate != null) { + predicate = predicate.and(new DeliveryJobContainsRecipientIdPredicate(val.toUpperCase())); + } else { + predicate = new DeliveryJobContainsRecipientIdPredicate(val.toUpperCase()); + } + query += "recipient id: " + val + "\n"; + } + + if (argMultimap.getValue(PREFIX_DELIVERY_DATE).isPresent()) { + try { + String val = argMultimap.getValue(PREFIX_DELIVERY_DATE).get(); + DeliveryDate toFindDate; + if (val.isBlank()) { + toFindDate = DeliveryDate.placeholder(); + } else { + toFindDate = new DeliveryDate(val); + } + if (predicate != null) { + predicate = predicate.and(new DeliveryJobContainsDeliveryDatePredicate(toFindDate)); + } else { + predicate = new DeliveryJobContainsDeliveryDatePredicate(toFindDate); + } + query += "delivery date: " + toFindDate.toString() + "\n"; + } catch (IllegalArgumentException e) { + throw new ParseException( + String.format(MESSAGE_DATE_FORMAT, e.getMessage())); + } + } + + if (argMultimap.getValue(PREFIX_DELIVERY_SLOT).isPresent()) { + try { + String val = argMultimap.getValue(PREFIX_DELIVERY_SLOT).get(); + DeliverySlot toFindSlot; + if (val.isBlank()) { + toFindSlot = DeliverySlot.placeholder(); + } else { + toFindSlot = new DeliverySlot(val); + } + if (predicate != null) { + predicate = predicate.and(new DeliveryJobContainsDeliverySlotPredicate(toFindSlot)); + } else { + predicate = new DeliveryJobContainsDeliverySlotPredicate(toFindSlot); + } + query += "delivery slot: " + toFindSlot.toString() + "\n"; + } catch (IllegalArgumentException e) { + throw new ParseException( + String.format(MESSAGE_SLOT_FORMAT, e.getMessage())); + } + } + + if (argMultimap.getValue(PREFIX_EARNING).isPresent()) { + try { + String val = argMultimap.getValue(PREFIX_EARNING).get(); + if (predicate != null) { + predicate = predicate.and(new DeliveryJobContainsEarningPredicate(new Earning(val))); + } else { + predicate = new DeliveryJobContainsEarningPredicate(new Earning(val)); + } + query += "earning: " + val + "\n"; + } catch (IllegalArgumentException e) { + throw new ParseException( + String.format(MESSAGE_EARN_FORMAT, e.getMessage())); + } + } + + if (argMultimap.getValue(PREFIX_IS_DELIVERED).isPresent()) { + String val = argMultimap.getValue(PREFIX_IS_DELIVERED).get().toLowerCase(); + boolean done = false; + if (val.isBlank() || (!val.equals("t") && !val.equals("f"))) { + throw new ParseException(String.format(MESSAGE_EMPTY_STATUS)); + } + if (val.startsWith("t")) { + done = true; + } + if (predicate != null) { + predicate = predicate.and(new DeliveryJobContainsStatusPredicate(done)); + } else { + predicate = new DeliveryJobContainsStatusPredicate(done); + } + query += "status: " + Boolean.toString(done) + "\n"; + } + + if (predicate != null) { + return new FindDeliveryJobCommand(predicate, query); + } else { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindDeliveryJobCommand.MESSAGE_USAGE)); + } + } + +} diff --git a/src/main/java/seedu/address/logic/parser/jobs/ImportDeliveryJobCommandParser.java b/src/main/java/seedu/address/logic/parser/jobs/ImportDeliveryJobCommandParser.java new file mode 100644 index 00000000000..b3f17222af3 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/jobs/ImportDeliveryJobCommandParser.java @@ -0,0 +1,181 @@ +package seedu.address.logic.parser.jobs; + +import java.io.File; +import java.io.FileNotFoundException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Optional; +import java.util.Scanner; +import java.util.Set; + +import seedu.address.logic.parser.ParserUtil; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.jobs.DeliveryDate; +import seedu.address.model.jobs.DeliveryJob; +import seedu.address.model.jobs.DeliverySlot; +import seedu.address.model.jobs.Earning; +import seedu.address.model.person.Address; +import seedu.address.model.person.Email; +import seedu.address.model.person.Name; +import seedu.address.model.person.Person; +import seedu.address.model.person.Phone; +import seedu.address.model.tag.Tag; + + +/** + * Parses CSV file contents into separate jobs and job details + */ +public class ImportDeliveryJobCommandParser { + + public static final String MESSAGE_MISSING_ELEMENT_IN_IMPORT = "Missing element in import"; + public static final String MESSAGE_EMPTY_FILE = "File is empty"; + public static final String MESSAGE_WRONG_FILE = "File type is unsupported. Please upload a CSV file and check if " + + "file extension is named .csv"; + + public static final String MESSAGE_WRONG_DELIMITER = "Wrong delimiter used, please use comma as delimiter."; + public static final String MESSAGE_MISSING_HEADER = "Missing header in import. " + + "Header needs to consist in this order of Recipient ID, Sender ID, Delivery date, Delivery slot, Price, " + + "Recipient ID, Recipient Name, Recipient Phone, Recipient Email, Recipient Address, " + + "Recipient Tag, Sender ID, Sender Name, Sender Phone, Sender Email, Sender Address," + + " Sender Tag."; + + /** + * Parses the given CSV File in the context of the ImportCommand + * + * @param file CSV file containing delivery jobs + * @return List of delivery jobs to be added + * @throws ParseException if the user input does not conform to + * the expected format + */ + public static List parse(File file, List listOfCustomers) + throws ParseException, FileNotFoundException { + Scanner sc = new Scanner(file); + if (sc.hasNextLine()) { + List listOfAddDeliveryJob = new ArrayList<>(); + String header = sc.nextLine(); + String[] arrOfHeader = header.split(","); + if (arrOfHeader.length == 0) { + throw new ParseException(MESSAGE_EMPTY_FILE); + } else if (!getFileExtensions(file).equals("csv")) { + throw new ParseException(MESSAGE_WRONG_FILE); + } else if (arrOfHeader.length == 1) { + throw new ParseException(MESSAGE_WRONG_DELIMITER); + } else if (!headerValidity(arrOfHeader)) { + throw new ParseException(MESSAGE_MISSING_HEADER); + } + while (sc.hasNextLine()) { + String line = sc.nextLine(); + String[] arrOfStr = line.split(","); + if (arrOfStr.length < 17) { + throw new ParseException(MESSAGE_MISSING_ELEMENT_IN_IMPORT); + } + addJobs(arrOfStr, listOfCustomers, listOfAddDeliveryJob); + } + sc.close(); + return listOfAddDeliveryJob; + } else { + throw new ParseException(MESSAGE_EMPTY_FILE); + } + } + + /** + * Parses recipient or sender from jobs in CSV file. + * + * @param arrOfStr array of person details + * @param index index of array + * @return Person object using the details + * @throws ParseException if the user input does not conform to + * the expected format + */ + public static Person recipientOrSender(String[] arrOfStr, int index) throws ParseException { + String personID = arrOfStr[index]; + Name name = new Name(arrOfStr[index + 1]); + Phone phone = new Phone(arrOfStr[index + 2]); + Email email = new Email(arrOfStr[index + 3]); + Address address = new Address(arrOfStr[index + 4]); + String[] arrOfTags = arrOfStr[index + 5].split(" "); + Set tags = ParserUtil.parseTags(Arrays.asList(arrOfTags)); + Person person; + if (arrOfStr[0].equals("na")) { + Set tagSet = new HashSet<>(); + person = new Person(name, phone, email, address, tags); + } else { + person = new Person(personID, name, phone, email, address, tags); + } + return person; + } + + /** + * Ensures Header elements are all present and in right order. + * + * @param arrOfHeader array of header elements + * @return Boolean whether header elements are correct + */ + public static boolean headerValidity(String[] arrOfHeader) { + if (!arrOfHeader[0].equals("Recipient ID") || !arrOfHeader[1].equals("Sender ID") + || !arrOfHeader[2].equals("Delivery date") || !arrOfHeader[3].equals("Delivery slot") + || !arrOfHeader[4].equals("Price") + || !arrOfHeader[5].equals("Recipient ID") || !arrOfHeader[6].equals("Recipient Name") + || !arrOfHeader[7].equals("Recipient Phone") || !arrOfHeader[8].equals("Recipient Email") + || !arrOfHeader[9].equals("Recipient Address") || !arrOfHeader[10].equals("Recipient Tag") + || !arrOfHeader[11].equals("Sender ID") + || !arrOfHeader[12].equals("Sender Name") || !arrOfHeader[13].equals("Sender Phone") + || !arrOfHeader[14].equals("Sender Email") || !arrOfHeader[15].equals("Sender Address") + || !arrOfHeader[16].equals("Sender Tag")) { + return false; + } + return true; + } + + /** + * Gets type of file extension. + * + * @param file File that extension is checked + * @return String file extension name + */ + public static String getFileExtensions(File file) { + String fileName = file.getName(); + String extension = ""; + int i = fileName.lastIndexOf("."); + extension = fileName.substring(i + 1, fileName.length()); + return extension; + } + + + /** + * Adds new delivery job to list of jobs to be imported in. + * + * @param arrOfStr array of job and its customers details + * @param listOfCustomers list of job's customers to be checked if + * exist and added into address book. + * @param listOfAddDeliveryJob list of delivery jobs to be + * imported + */ + public static void addJobs(String[] arrOfStr, List listOfCustomers, + List listOfAddDeliveryJob) throws ParseException { + String sid = arrOfStr[0]; + String rid = arrOfStr[1]; + String ded = arrOfStr[2]; + String des = arrOfStr[3]; + String ear = arrOfStr[4]; + Person recipient = recipientOrSender(arrOfStr, 5); + Person sender = recipientOrSender(arrOfStr, 11); + listOfCustomers.add(sender); + listOfCustomers.add(recipient); + + if (ded.equals("na") && des.equals("na")) { + Optional date = Optional.empty(); + Optional slot = Optional.empty(); + Optional earn = Optional.of(new Earning(ear)); + DeliveryJob job = new DeliveryJob(rid, sid, date, slot, earn, ""); + listOfAddDeliveryJob.add(job); + } else { + DeliveryJob job = new DeliveryJob(rid, sid, ded, des, ear, ""); + listOfAddDeliveryJob.add(job); + } + } + +} + diff --git a/src/main/java/seedu/address/logic/parser/AddCommandParser.java b/src/main/java/seedu/address/logic/parser/person/AddCommandParser.java similarity index 88% rename from src/main/java/seedu/address/logic/parser/AddCommandParser.java rename to src/main/java/seedu/address/logic/parser/person/AddCommandParser.java index 3b8bfa035e8..fd90628d94a 100644 --- a/src/main/java/seedu/address/logic/parser/AddCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/person/AddCommandParser.java @@ -1,4 +1,4 @@ -package seedu.address.logic.parser; +package seedu.address.logic.parser.person; import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; @@ -10,7 +10,12 @@ import java.util.Set; import java.util.stream.Stream; -import seedu.address.logic.commands.AddCommand; +import seedu.address.logic.commands.person.AddCommand; +import seedu.address.logic.parser.ArgumentMultimap; +import seedu.address.logic.parser.ArgumentTokenizer; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.ParserUtil; +import seedu.address.logic.parser.Prefix; import seedu.address.logic.parser.exceptions.ParseException; import seedu.address.model.person.Address; import seedu.address.model.person.Email; @@ -24,9 +29,18 @@ */ public class AddCommandParser implements Parser { + /** + * Returns true if none of the prefixes contains empty {@code Optional} values in the given + * {@code ArgumentMultimap}. + */ + private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); + } + /** * Parses the given {@code String} of arguments in the context of the AddCommand * and returns an AddCommand object for execution. + * * @throws ParseException if the user input does not conform the expected format */ public AddCommand parse(String args) throws ParseException { @@ -49,12 +63,4 @@ public AddCommand parse(String args) throws ParseException { return new AddCommand(person); } - /** - * Returns true if none of the prefixes contains empty {@code Optional} values in the given - * {@code ArgumentMultimap}. - */ - private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { - return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); - } - } diff --git a/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java b/src/main/java/seedu/address/logic/parser/person/DeleteCommandParser.java similarity index 83% rename from src/main/java/seedu/address/logic/parser/DeleteCommandParser.java rename to src/main/java/seedu/address/logic/parser/person/DeleteCommandParser.java index 522b93081cc..b9851b66604 100644 --- a/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/person/DeleteCommandParser.java @@ -1,9 +1,11 @@ -package seedu.address.logic.parser; +package seedu.address.logic.parser.person; import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; import seedu.address.commons.core.index.Index; -import seedu.address.logic.commands.DeleteCommand; +import seedu.address.logic.commands.person.DeleteCommand; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.ParserUtil; import seedu.address.logic.parser.exceptions.ParseException; /** @@ -14,6 +16,7 @@ public class DeleteCommandParser implements Parser { /** * Parses the given {@code String} of arguments in the context of the DeleteCommand * and returns a DeleteCommand object for execution. + * * @throws ParseException if the user input does not conform the expected format */ public DeleteCommand parse(String args) throws ParseException { diff --git a/src/main/java/seedu/address/logic/parser/EditCommandParser.java b/src/main/java/seedu/address/logic/parser/person/EditCommandParser.java similarity index 89% rename from src/main/java/seedu/address/logic/parser/EditCommandParser.java rename to src/main/java/seedu/address/logic/parser/person/EditCommandParser.java index 845644b7dea..6b6ffcc3143 100644 --- a/src/main/java/seedu/address/logic/parser/EditCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/person/EditCommandParser.java @@ -1,4 +1,4 @@ -package seedu.address.logic.parser; +package seedu.address.logic.parser.person; import static java.util.Objects.requireNonNull; import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; @@ -14,8 +14,12 @@ import java.util.Set; import seedu.address.commons.core.index.Index; -import seedu.address.logic.commands.EditCommand; -import seedu.address.logic.commands.EditCommand.EditPersonDescriptor; +import seedu.address.logic.commands.person.EditCommand; +import seedu.address.logic.commands.person.EditCommand.EditPersonDescriptor; +import seedu.address.logic.parser.ArgumentMultimap; +import seedu.address.logic.parser.ArgumentTokenizer; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.ParserUtil; import seedu.address.logic.parser.exceptions.ParseException; import seedu.address.model.tag.Tag; @@ -27,6 +31,7 @@ public class EditCommandParser implements Parser { /** * Parses the given {@code String} of arguments in the context of the EditCommand * and returns an EditCommand object for execution. + * * @throws ParseException if the user input does not conform the expected format */ public EditCommand parse(String args) throws ParseException { diff --git a/src/main/java/seedu/address/logic/parser/FindCommandParser.java b/src/main/java/seedu/address/logic/parser/person/FindCommandParser.java similarity index 87% rename from src/main/java/seedu/address/logic/parser/FindCommandParser.java rename to src/main/java/seedu/address/logic/parser/person/FindCommandParser.java index 4fb71f23103..7d2e1a399dc 100644 --- a/src/main/java/seedu/address/logic/parser/FindCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/person/FindCommandParser.java @@ -1,10 +1,11 @@ -package seedu.address.logic.parser; +package seedu.address.logic.parser.person; import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; import java.util.Arrays; -import seedu.address.logic.commands.FindCommand; +import seedu.address.logic.commands.person.FindCommand; +import seedu.address.logic.parser.Parser; import seedu.address.logic.parser.exceptions.ParseException; import seedu.address.model.person.NameContainsKeywordsPredicate; @@ -16,6 +17,7 @@ public class FindCommandParser implements Parser { /** * Parses the given {@code String} of arguments in the context of the FindCommand * and returns a FindCommand object for execution. + * * @throws ParseException if the user input does not conform the expected format */ public FindCommand parse(String args) throws ParseException { diff --git a/src/main/java/seedu/address/logic/parser/reminder/AddReminderParser.java b/src/main/java/seedu/address/logic/parser/reminder/AddReminderParser.java new file mode 100644 index 00000000000..b86a731c6cc --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/reminder/AddReminderParser.java @@ -0,0 +1,68 @@ +package seedu.address.logic.parser.reminder; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DESCRIPTION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TIME; + +import java.time.LocalDateTime; +import java.time.format.DateTimeParseException; +import java.util.stream.Stream; + +import seedu.address.commons.util.DateTimeUtil; +import seedu.address.logic.commands.reminder.AddReminderCommand; +import seedu.address.logic.parser.ArgumentMultimap; +import seedu.address.logic.parser.ArgumentTokenizer; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.Prefix; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.jobs.DeliveryDate; +import seedu.address.model.reminder.Reminder; + +/** + * Parses input arguments and creates a new AddReminderCommand object + */ +public class AddReminderParser implements Parser { + + /** + * Returns true if none of the prefixes contains empty {@code Optional} values in the given + * {@code ArgumentMultimap}. + */ + private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); + } + + /** + * Parses the given {@code String} of arguments in the context of the AddReminderCommand + * and returns an AddReminderCommand object for execution. + * + * @throws ParseException if the user input does not conform the expected format + */ + @Override + public AddReminderCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_DESCRIPTION, PREFIX_TIME); + + if (!arePrefixesPresent(argMultimap, PREFIX_TIME) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddReminderCommand.MESSAGE_USAGE)); + } + + String description = argMultimap.getValue(PREFIX_DESCRIPTION).orElse(""); + System.out.println(description.length()); + if (description.length() > 50) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddReminderCommand.MESSAGE_USAGE)); + } + + String dateTimeString = argMultimap.getValue(PREFIX_TIME).orElse("none"); + LocalDateTime dateTime; + try { + dateTime = DateTimeUtil.toDateTime(dateTimeString); + } catch (DateTimeParseException e) { + throw new ParseException(DeliveryDate.MESSAGE_CONSTRAINTS); + } + + Reminder reminder = new Reminder(description, dateTime); + + return new AddReminderCommand(reminder); + } +} diff --git a/src/main/java/seedu/address/logic/parser/reminder/DeleteReminderParser.java b/src/main/java/seedu/address/logic/parser/reminder/DeleteReminderParser.java new file mode 100644 index 00000000000..88df198222e --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/reminder/DeleteReminderParser.java @@ -0,0 +1,22 @@ +package seedu.address.logic.parser.reminder; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.address.logic.commands.reminder.DeleteReminderCommand; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a new DeleteReminderCommand object + */ +public class DeleteReminderParser implements Parser { + @Override + public DeleteReminderCommand parse(String args) throws ParseException { + try { + return new DeleteReminderCommand(Integer.parseInt(args.trim())); + } catch (NumberFormatException e) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteReminderCommand.MESSAGE_USAGE), e); + } + } +} diff --git a/src/main/java/seedu/address/logic/parser/timetable/TimetableDateCommandParser.java b/src/main/java/seedu/address/logic/parser/timetable/TimetableDateCommandParser.java new file mode 100644 index 00000000000..90b4b347125 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/timetable/TimetableDateCommandParser.java @@ -0,0 +1,49 @@ +package seedu.address.logic.parser.timetable; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DATE; + +import java.time.LocalDate; +import java.util.stream.Stream; + +import seedu.address.logic.commands.timetable.TimetableDateCommand; +import seedu.address.logic.parser.ArgumentMultimap; +import seedu.address.logic.parser.ArgumentTokenizer; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.ParserUtil; +import seedu.address.logic.parser.Prefix; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a new TimetableDateCommand object + */ +public class TimetableDateCommandParser implements Parser { + + /** + * Returns true if none of the prefixes contains empty {@code Optional} values in the given + * {@code ArgumentMultimap}. + */ + private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); + } + + /** + * Parses the given {@code String} of arguments in the context of the TimetableDateCommand + * and returns an TimetableDateCommand object for execution. + * + * @throws ParseException if the user input does not conform the expected format + */ + public TimetableDateCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_DATE); + + if (!arePrefixesPresent(argMultimap, PREFIX_DATE) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, TimetableDateCommand.MESSAGE_USAGE)); + } + + LocalDate date = ParserUtil.parseDate(argMultimap.getValue(PREFIX_DATE).get()); + return new TimetableDateCommand(date); + } + +} diff --git a/src/main/java/seedu/address/logic/parser/AddressBookParser.java b/src/main/java/seedu/address/logic/parser/timetable/TimetableParser.java similarity index 60% rename from src/main/java/seedu/address/logic/parser/AddressBookParser.java rename to src/main/java/seedu/address/logic/parser/timetable/TimetableParser.java index 1e466792b46..db8c963a6c9 100644 --- a/src/main/java/seedu/address/logic/parser/AddressBookParser.java +++ b/src/main/java/seedu/address/logic/parser/timetable/TimetableParser.java @@ -1,26 +1,23 @@ -package seedu.address.logic.parser; +package seedu.address.logic.parser.timetable; import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; -import static seedu.address.commons.core.Messages.MESSAGE_UNKNOWN_COMMAND; +import static seedu.address.commons.core.Messages.MESSAGE_UNKNOWN_TIMETABLE_COMMAND; import java.util.regex.Matcher; import java.util.regex.Pattern; -import seedu.address.logic.commands.AddCommand; -import seedu.address.logic.commands.ClearCommand; import seedu.address.logic.commands.Command; -import seedu.address.logic.commands.DeleteCommand; -import seedu.address.logic.commands.EditCommand; import seedu.address.logic.commands.ExitCommand; -import seedu.address.logic.commands.FindCommand; import seedu.address.logic.commands.HelpCommand; -import seedu.address.logic.commands.ListCommand; +import seedu.address.logic.commands.timetable.TimetableCompletedCommand; +import seedu.address.logic.commands.timetable.TimetableDateCommand; +import seedu.address.logic.commands.timetable.TimetableUnscheduleCommand; import seedu.address.logic.parser.exceptions.ParseException; /** - * Parses user input. + * Represent a parser for timetable command */ -public class AddressBookParser { +public class TimetableParser { /** * Used for initial separation of command word and args. @@ -43,33 +40,23 @@ public Command parseCommand(String userInput) throws ParseException { final String commandWord = matcher.group("commandWord"); final String arguments = matcher.group("arguments"); switch (commandWord) { - - case AddCommand.COMMAND_WORD: - return new AddCommandParser().parse(arguments); - - case EditCommand.COMMAND_WORD: - return new EditCommandParser().parse(arguments); - - case DeleteCommand.COMMAND_WORD: - return new DeleteCommandParser().parse(arguments); - - case ClearCommand.COMMAND_WORD: - return new ClearCommand(); - - case FindCommand.COMMAND_WORD: - return new FindCommandParser().parse(arguments); - - case ListCommand.COMMAND_WORD: - return new ListCommand(); + case HelpCommand.COMMAND_WORD: + return new HelpCommand(); case ExitCommand.COMMAND_WORD: return new ExitCommand(); - case HelpCommand.COMMAND_WORD: - return new HelpCommand(); + case TimetableDateCommand.COMMAND_WORD: + return new TimetableDateCommandParser().parse(arguments); + + case TimetableUnscheduleCommand.COMMAND_WORD: + return new TimetableUnscheduleCommand(); + + case TimetableCompletedCommand.COMMAND_WORD: + return new TimetableCompletedCommand(); default: - throw new ParseException(MESSAGE_UNKNOWN_COMMAND); + throw new ParseException(MESSAGE_UNKNOWN_TIMETABLE_COMMAND); } } diff --git a/src/main/java/seedu/address/model/AddressBook.java b/src/main/java/seedu/address/model/AddressBook.java index 1a943a0781a..837748d15b1 100644 --- a/src/main/java/seedu/address/model/AddressBook.java +++ b/src/main/java/seedu/address/model/AddressBook.java @@ -3,10 +3,14 @@ import static java.util.Objects.requireNonNull; import java.util.List; +import java.util.Optional; import javafx.collections.ObservableList; +import javafx.collections.transformation.FilteredList; import seedu.address.model.person.Person; import seedu.address.model.person.UniquePersonList; +import seedu.address.model.reminder.Reminder; +import seedu.address.model.reminder.ReminderList; /** * Wraps all data at the address-book level @@ -15,19 +19,21 @@ public class AddressBook implements ReadOnlyAddressBook { private final UniquePersonList persons; - + private final ReminderList reminderList; /* - * The 'unusual' code block below is a non-static initialization block, sometimes used to avoid duplication - * between constructors. See https://docs.oracle.com/javase/tutorial/java/javaOO/initial.html - * - * Note that non-static init blocks are not recommended to use. There are other ways to avoid duplication - * among constructors. - */ + * The 'unusual' code block below is a non-static initialization block, sometimes used to avoid duplication + * between constructors. See https://docs.oracle.com/javase/tutorial/java/javaOO/initial.html + * + * Note that non-static init blocks are not recommended to use. There are other ways to avoid duplication + * among constructors. + */ { persons = new UniquePersonList(); + reminderList = new ReminderList(); } - public AddressBook() {} + public AddressBook() { + } /** * Creates an AddressBook using the Persons in the {@code toBeCopied} @@ -54,10 +60,9 @@ public void resetData(ReadOnlyAddressBook newData) { requireNonNull(newData); setPersons(newData.getPersonList()); + setReminderList(newData.getReminderList()); } - //// person-level operations - /** * Returns true if a person with the same identity as {@code person} exists in the address book. */ @@ -66,6 +71,8 @@ public boolean hasPerson(Person person) { return persons.contains(person); } + //// person-level operations + /** * Adds a person to the address book. * The person must not already exist in the address book. @@ -81,7 +88,6 @@ public void addPerson(Person p) { */ public void setPerson(Person target, Person editedPerson) { requireNonNull(editedPerson); - persons.setPerson(target, editedPerson); } @@ -93,7 +99,26 @@ public void removePerson(Person key) { persons.remove(key); } - //// util methods + /** + * Adds a reminder to the address book. + */ + public void addReminder(Reminder r) { + reminderList.add(r); + } + + //// reminder-level operations + + /** + * Removes {@code Reminder} from this {@code AddressBook}. + * {@code Reminder} must exist in the address book. + */ + public void removeReminder(int i) { + reminderList.remove(i); + } + + public void sortReminderList() { + reminderList.sortByOldest(); + } @Override public String toString() { @@ -101,11 +126,22 @@ public String toString() { // TODO: refine later } + //// util methods + @Override public ObservableList getPersonList() { return persons.asUnmodifiableObservableList(); } + @Override + public ObservableList getReminderList() { + return reminderList.asUnmodifiableObservableList(); + } + + public void setReminderList(List reminderList) { + this.reminderList.setReminderList(reminderList); + } + @Override public boolean equals(Object other) { return other == this // short circuit if same object @@ -117,4 +153,12 @@ public boolean equals(Object other) { public int hashCode() { return persons.hashCode(); } + + public Optional getPersonById(String id) { + FilteredList list = persons.asUnmodifiableObservableList().filtered(x -> x.getPersonId().equals(id)); + if (list.size() == 1) { + return Optional.of(list.get(0)); + } + return Optional.empty(); + } } diff --git a/src/main/java/seedu/address/model/DeliveryJobSystem.java b/src/main/java/seedu/address/model/DeliveryJobSystem.java new file mode 100644 index 00000000000..abb6430d44f --- /dev/null +++ b/src/main/java/seedu/address/model/DeliveryJobSystem.java @@ -0,0 +1,120 @@ +package seedu.address.model; + +import static java.util.Objects.requireNonNull; + +import java.util.List; + +import javafx.collections.ObservableList; +import seedu.address.model.jobs.DeliveryJob; +import seedu.address.model.jobs.UniqueDeliveryList; + +/** + * DeliveryJobSystem + */ +public class DeliveryJobSystem implements ReadOnlyDeliveryJobSystem { + + private final UniqueDeliveryList jobs; + + { + jobs = new UniqueDeliveryList(); + } + + /** + * DeliveryJobSystem + */ + public DeliveryJobSystem() {} + + /** + * DeliveryJobSystem + * + * @param deliveryJobSystem + */ + public DeliveryJobSystem(ReadOnlyDeliveryJobSystem deliveryJobSystem) { + this(); + resetData(deliveryJobSystem); + } + + private void setDeliveryJobs(List jobs) { + this.jobs.setDeliveryJobs(jobs); + } + + /** + * resetData + * + * @param newData + */ + public void resetData(ReadOnlyDeliveryJobSystem newData) { + requireNonNull(newData); + + setDeliveryJobs(newData.getDeliveryJobList()); + } + + /** + * setDeliveryJob + * + * @param target + * @param editedJob + */ + public void setDeliveryJob(DeliveryJob target, DeliveryJob editedJob) { + requireNonNull(editedJob); + + jobs.setDeliveryJob(target, editedJob); + } + + /** + * DeliveryJobSystem + */ + public ObservableList getDeliveryJobList() { + return jobs.asUnmodifiableObservableList(); + } + + /** + * addDeliveryJob + * + * @param job + */ + public void addDeliveryJob(DeliveryJob job) { + jobs.add(job); + } + + /** + * removeDeliveryJob + * + * @param key + */ + public void removeDeliveryJob(DeliveryJob key) { + jobs.remove(key); + } + + /** + * Returns true if a delivery job with the same identity as {@code delivery job} exists in the address book. + */ + public boolean hasDeliveryJob(DeliveryJob job) { + requireNonNull(job); + return jobs.contains(job); + } + + /** + * @return number of jobs in list + */ + public int size() { + return jobs.size(); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof DeliveryJobSystem // instanceof handles nulls + && jobs.equals(((DeliveryJobSystem) other).jobs)); + } + + @Override + public String toString() { + return jobs.asUnmodifiableObservableList().size() + " delivery jobs"; + } + + @Override + public int hashCode() { + return jobs.hashCode(); + } +} diff --git a/src/main/java/seedu/address/model/Model.java b/src/main/java/seedu/address/model/Model.java index d54df471c1f..ec33ec397a8 100644 --- a/src/main/java/seedu/address/model/Model.java +++ b/src/main/java/seedu/address/model/Model.java @@ -1,11 +1,19 @@ package seedu.address.model; import java.nio.file.Path; +import java.time.LocalDate; +import java.util.Comparator; +import java.util.Map; +import java.util.Optional; import java.util.function.Predicate; import javafx.collections.ObservableList; import seedu.address.commons.core.GuiSettings; +import seedu.address.model.jobs.DeliveryJob; +import seedu.address.model.jobs.DeliveryList; import seedu.address.model.person.Person; +import seedu.address.model.reminder.Reminder; +import seedu.address.model.stats.WeeklyStats; /** * The API of the Model component. @@ -13,16 +21,17 @@ public interface Model { /** {@code Predicate} that always evaluate to true */ Predicate PREDICATE_SHOW_ALL_PERSONS = unused -> true; + Predicate PREDICATE_SHOW_ALL_DELIVERY_JOBS = unused -> true; /** - * Replaces user prefs data with the data in {@code userPrefs}. + * Returns the user prefs. */ - void setUserPrefs(ReadOnlyUserPrefs userPrefs); + ReadOnlyUserPrefs getUserPrefs(); /** - * Returns the user prefs. + * Replaces user prefs data with the data in {@code userPrefs}. */ - ReadOnlyUserPrefs getUserPrefs(); + void setUserPrefs(ReadOnlyUserPrefs userPrefs); /** * Returns the user prefs' GUI settings. @@ -44,19 +53,27 @@ public interface Model { */ void setAddressBookFilePath(Path addressBookFilePath); + /** + * Returns the AddressBook + */ + ReadOnlyAddressBook getAddressBook(); + /** * Replaces address book data with the data in {@code addressBook}. */ void setAddressBook(ReadOnlyAddressBook addressBook); - /** Returns the AddressBook */ - ReadOnlyAddressBook getAddressBook(); - /** * Returns true if a person with the same identity as {@code person} exists in the address book. */ boolean hasPerson(Person person); + /** + * Returns person with specified ID + * @param id + */ + Optional getPersonById(String id); + /** * Deletes the given person. * The person must exist in the address book. @@ -76,12 +93,208 @@ public interface Model { */ void setPerson(Person target, Person editedPerson); - /** Returns an unmodifiable view of the filtered person list */ + /** + * Returns an unmodifiable view of the filtered person list + */ ObservableList getFilteredPersonList(); /** * Updates the filter of the filtered person list to filter by the given {@code predicate}. + * * @throws NullPointerException if {@code predicate} is null. */ void updateFilteredPersonList(Predicate predicate); + + // DELIVERY JOB SYSTEM =================================== + + /** + * Returns the user prefs' delivery job system file path. + */ + Path getDeliveryJobSystemFilePath(); + + /** + * Sets the user prefs' delivery job system file path. + */ + void setDeliveryJobSystemFilePath(Path deliveryJobSystemFilePath); + + /** + * Sets delivery job system + * @param jobSystem + */ + void setDeliveryJobSystem(ReadOnlyDeliveryJobSystem jobSystem); + + /** + * Returns delivery job system + */ + ReadOnlyDeliveryJobSystem getDeliveryJobSystem(); + + /** + * Returns the filtered delivery job list + */ + ObservableList getFilteredDeliveryJobList(); + + /** + * Checks if the job list has a certain job + * @param job job to find + */ + boolean hasDeliveryJob(DeliveryJob job); + + /** + * Deletes delivery job in job list + * @param target job to delete + */ + void deleteDeliveryJob(DeliveryJob target); + + /** + * Adds delivery job to job list + * @param job job to add + */ + void addDeliveryJob(DeliveryJob job); + + /** + * Sets/updates delivery job in job list + * @param target job to edit/to be replaced + * @param editedJob new job to replace + */ + void setDeliveryJob(DeliveryJob target, DeliveryJob editedJob); + + /** + * Returns job list + */ + ObservableList getDeliveryJobList(); + + /** + * Returns job list sorted + */ + ObservableList getSortedDeliveryJobListByComparator(); + + /** + * Updates filtered delivery job list based on new predicate + * @param predicate + */ + void updateFilteredDeliveryJobList(Predicate predicate); + + /** + * Updates sorted delivery job list based on new sorter + * @param sorter + */ + void updateSortedDeliveryJobList(Comparator sorter); + + /** + * Updates sorted delivery job list based on new sorter + * @param sorter + */ + void updateSortedDeliveryJobListByComparator(Comparator sorter); + + /** + * Updates sorted delivery job list by date and earning + */ + void updateSortedDeliveryJobListByDate(); + + /** + * Updates delivery job list in week containing given date + * @param date date to focus + */ + void updateWeekDeliveryJobList(LocalDate date); + + /** + * Updates focus date + * @param jobDate + */ + void updateFocusDate(LocalDate jobDate); + + /** + * Returns sorted delivery job list + */ + ObservableList getSortedDeliveryJobList(); + + /** + * Returns sorted delivery job list by date + */ + Map getSortedDeliveryJobListByDate(); + + /** + * Returns job list in the week + */ + Map getWeekDeliveryJobList(); + + /** + * Returns job list in a specific day of week + * @param dayOfWeek + */ + DeliveryList getDayOfWeekJob(int dayOfWeek); + + /** + * Returns list of unscheduled jobs + */ + ObservableList getUnscheduledDeliveryJobList(); + + /** + * Returns list of completed jobs + */ + ObservableList getCompletedDeliveryJobList(); + + /** + * Returns focus date + */ + LocalDate getFocusDate(); + + + + // NOTIFICATION ========================================= + + /** + * Deletes the given reminder. + * The reminder must exist in reminders. + */ + void deleteReminder(int i); + + /** + * Adds the given reminder. + */ + void addReminder(Reminder reminder); + + /** + * Returns an unmodifiable view of the filtered reminder list + */ + ObservableList getReminderList(); + + /** + * Sorts reminder list + */ + + void sortReminderList(); + + /** + * Indicate that a reminder has already been shown for this app's runtime + */ + void setHasShown(int i, boolean b); + + // STATISTICS ========================================= + + /** + * Checks if the given job should be place in weekly stats. + */ + boolean sameWeek(DeliveryJob job, WeeklyStats weeklyStats); + + /** + * Returns an unmodifiable view of the Delivery Job list with only + * jobs that are in the same week as the given date. + */ + ObservableList weekJobsList(ObservableList list, LocalDate date); + + /** + * Returns the total earnings of delivery jobs in the list. + */ + double getTotalEarnings(ObservableList list); + + /** + * Returns the total number of completed delivery jobs in the list. + */ + int getTotalCompleted(ObservableList list); + + /** + * Returns the total number of pending delivery jobs in the list. + */ + int getTotalPending(ObservableList list); } diff --git a/src/main/java/seedu/address/model/ModelManager.java b/src/main/java/seedu/address/model/ModelManager.java index 86c1df298d7..246e5fad1f0 100644 --- a/src/main/java/seedu/address/model/ModelManager.java +++ b/src/main/java/seedu/address/model/ModelManager.java @@ -4,53 +4,96 @@ import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; import java.nio.file.Path; +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; import java.util.function.Predicate; import java.util.logging.Logger; +import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.collections.transformation.FilteredList; +import javafx.collections.transformation.SortedList; import seedu.address.commons.core.GuiSettings; import seedu.address.commons.core.LogsCenter; +import seedu.address.model.jobs.DeliveryDate; +import seedu.address.model.jobs.DeliveryJob; +import seedu.address.model.jobs.DeliveryList; +import seedu.address.model.jobs.Earning; +import seedu.address.model.jobs.sorters.SortbyTimeAndEarn; import seedu.address.model.person.Person; +import seedu.address.model.reminder.Reminder; +import seedu.address.model.stats.WeeklyStats; /** * Represents the in-memory model of the address book data. */ public class ModelManager implements Model { + public static final SortbyTimeAndEarn SORTER_BY_DATE = new SortbyTimeAndEarn(); + private static final Logger logger = LogsCenter.getLogger(ModelManager.class); private final AddressBook addressBook; + private final DeliveryJobSystem deliveryJobSystem; private final UserPrefs userPrefs; private final FilteredList filteredPersons; + private final FilteredList filteredDeliveryJobs; + private final SortedList sortedDeliveryJobsList; + private final ObservableList reminderList; + private final Map weekJobListGroupedByDate; + private SortedList sortedDeliveryJobs; + private LocalDate focusDate; + private Map jobListGroupedByDate; + + /** * Initializes a ModelManager with the given addressBook and userPrefs. + * @param addressBook + * @param deliveryJobSystem + * @param userPrefs */ - public ModelManager(ReadOnlyAddressBook addressBook, ReadOnlyUserPrefs userPrefs) { - requireAllNonNull(addressBook, userPrefs); + public ModelManager(ReadOnlyAddressBook addressBook, ReadOnlyDeliveryJobSystem deliveryJobSystem, + ReadOnlyUserPrefs userPrefs) { + requireAllNonNull(addressBook, deliveryJobSystem, userPrefs); logger.fine("Initializing with address book: " + addressBook + " and user prefs " + userPrefs); this.addressBook = new AddressBook(addressBook); + this.deliveryJobSystem = new DeliveryJobSystem(deliveryJobSystem); this.userPrefs = new UserPrefs(userPrefs); - filteredPersons = new FilteredList<>(this.addressBook.getPersonList()); + this.filteredPersons = new FilteredList(this.addressBook.getPersonList()); + this.filteredDeliveryJobs = new FilteredList(this.deliveryJobSystem.getDeliveryJobList()); + this.sortedDeliveryJobs = new SortedList(this.deliveryJobSystem.getDeliveryJobList()); + this.sortedDeliveryJobsList = new SortedList(filteredDeliveryJobs); + //updateSortedDeliveryJobListByDate(); + this.jobListGroupedByDate = new HashMap(); + this.weekJobListGroupedByDate = new HashMap(); + this.reminderList = this.addressBook.getReminderList(); + this.focusDate = LocalDate.now(); } + /** + * ModelManager. + */ public ModelManager() { - this(new AddressBook(), new UserPrefs()); + this(new AddressBook(), new DeliveryJobSystem(), new UserPrefs()); } - //=========== UserPrefs ================================================================================== + // UserPrefs =================================================================== @Override - public void setUserPrefs(ReadOnlyUserPrefs userPrefs) { - requireNonNull(userPrefs); - this.userPrefs.resetData(userPrefs); + public ReadOnlyUserPrefs getUserPrefs() { + return userPrefs; } @Override - public ReadOnlyUserPrefs getUserPrefs() { - return userPrefs; + public void setUserPrefs(ReadOnlyUserPrefs userPrefs) { + requireNonNull(userPrefs); + this.userPrefs.resetData(userPrefs); } @Override @@ -75,16 +118,16 @@ public void setAddressBookFilePath(Path addressBookFilePath) { userPrefs.setAddressBookFilePath(addressBookFilePath); } - //=========== AddressBook ================================================================================ + // AddressBook =============================================================== @Override - public void setAddressBook(ReadOnlyAddressBook addressBook) { - this.addressBook.resetData(addressBook); + public ReadOnlyAddressBook getAddressBook() { + return addressBook; } @Override - public ReadOnlyAddressBook getAddressBook() { - return addressBook; + public void setAddressBook(ReadOnlyAddressBook addressBook) { + this.addressBook.resetData(addressBook); } @Override @@ -93,6 +136,12 @@ public boolean hasPerson(Person person) { return addressBook.hasPerson(person); } + @Override + public Optional getPersonById(String id) { + requireNonNull(id); + return addressBook.getPersonById(id); + } + @Override public void deletePerson(Person target) { addressBook.removePerson(target); @@ -111,10 +160,11 @@ public void setPerson(Person target, Person editedPerson) { addressBook.setPerson(target, editedPerson); } - //=========== Filtered Person List Accessors ============================================================= + // =========== Filtered Person List Accessors ================== /** - * Returns an unmodifiable view of the list of {@code Person} backed by the internal list of + * Returns an unmodifiable view of the list of {@code Person} backed by the + * internal list of * {@code versionedAddressBook} */ @Override @@ -128,6 +178,260 @@ public void updateFilteredPersonList(Predicate predicate) { filteredPersons.setPredicate(predicate); } + // DeliveryJob System ===================================================== + + @Override + public Path getDeliveryJobSystemFilePath() { + return userPrefs.getDeliveryJobSystemFilePath(); + } + + @Override + public void setDeliveryJobSystemFilePath(Path deliveryJobSystemFilePath) { + requireNonNull(deliveryJobSystemFilePath); + userPrefs.setDeliveryJobSystemFilePath(deliveryJobSystemFilePath); + } + + @Override + public void setDeliveryJobSystem(ReadOnlyDeliveryJobSystem jobSystem) { + this.deliveryJobSystem.resetData(jobSystem); + } + + @Override + public ReadOnlyDeliveryJobSystem getDeliveryJobSystem() { + return deliveryJobSystem; + } + + @Override + public boolean hasDeliveryJob(DeliveryJob job) { + requireNonNull(job); + return deliveryJobSystem.hasDeliveryJob(job); + } + + @Override + public void deleteDeliveryJob(DeliveryJob target) { + deliveryJobSystem.removeDeliveryJob(target); + } + + @Override + public void addDeliveryJob(DeliveryJob job) { + deliveryJobSystem.addDeliveryJob(job); + updateFilteredDeliveryJobList(PREDICATE_SHOW_ALL_DELIVERY_JOBS); + } + + @Override + public void setDeliveryJob(DeliveryJob target, DeliveryJob editedJob) { + requireAllNonNull(target, editedJob); + + deliveryJobSystem.setDeliveryJob(target, editedJob); + } + + // =========== Filtered Delivery Job List Accessors ============ + + @Override + public ObservableList getDeliveryJobList() { + updateFilteredDeliveryJobList(PREDICATE_SHOW_ALL_DELIVERY_JOBS); + return filteredDeliveryJobs; + } + + @Override + public ObservableList getFilteredDeliveryJobList() { + return filteredDeliveryJobs; + } + + @Override + public void updateFilteredDeliveryJobList(Predicate predicate) { + requireAllNonNull(predicate); + filteredDeliveryJobs.setPredicate(predicate); + } + + @Override + public void updateSortedDeliveryJobList(Comparator sorter) { + requireNonNull(sorter); + sortedDeliveryJobs = new SortedList(this.deliveryJobSystem.getDeliveryJobList()); + sortedDeliveryJobs.setComparator(sorter); + } + + @Override + public void updateSortedDeliveryJobListByComparator(Comparator sorter) { + requireNonNull(sorter); + sortedDeliveryJobsList.setComparator(sorter); + } + + @Override + public ObservableList getSortedDeliveryJobListByComparator() { + return sortedDeliveryJobsList; + } + + @Override + public ObservableList getSortedDeliveryJobList() { + return FXCollections.observableArrayList(sortedDeliveryJobs); + } + + @Override + public void updateSortedDeliveryJobListByDate() { + updateSortedDeliveryJobList(SORTER_BY_DATE); + jobListGroupedByDate.clear(); + for (int i = 0; i < sortedDeliveryJobs.size(); i++) { + if (sortedDeliveryJobs.get(i).isScheduled() + && (!sortedDeliveryJobs.get(i).getDeliveredStatus())) { + addJobToJobListBasedOnDay(jobListGroupedByDate, sortedDeliveryJobs.get(i)); + } + } + } + + /** + * Adds job to job list grouped by date according to delivery date + * Given that job has delivery date and slot + * @param jobListGroupedByDate + * @param toAdd + */ + private void addJobToJobListBasedOnDay(Map jobListGroupedByDate, DeliveryJob toAdd) { + LocalDate jobDate = toAdd.getDate(); + int jobSlot = toAdd.getSlot(); + int slotIndex = (jobSlot) - 1; + + if (jobListGroupedByDate.containsKey(jobDate)) { + DeliveryList jobsInCurrentSlot = jobListGroupedByDate.get(jobDate); + if (jobsInCurrentSlot.size() == 0) { + jobsInCurrentSlot = createEmptyDayJobList(); + } + if (slotIndex > 4) { + jobsInCurrentSlot.get(5).add(toAdd); + } else { + jobsInCurrentSlot.get(slotIndex).add(toAdd); + } + jobListGroupedByDate.put(jobDate, jobsInCurrentSlot); + } else { + DeliveryList newDateJobList = createEmptyDayJobList(); + if (slotIndex > 4) { + newDateJobList.get(5).add(toAdd); + } else { + newDateJobList.get(slotIndex).add(toAdd); + } + jobListGroupedByDate.put(jobDate, newDateJobList); + } + } + + /** + * Update list of week delivery job list, + * period spans from 1 week before given date + * to 1 week after given date + * @param date given/focused date + */ + @Override + public void updateWeekDeliveryJobList(LocalDate date) { + requireNonNull(date); + weekJobListGroupedByDate.clear(); + this.focusDate = date; + + int focusDayOfWeek = date.getDayOfWeek().getValue(); + int dayOfWeekTracker = 1; + + while (dayOfWeekTracker < 8) { + int dayToAdd = dayOfWeekTracker - focusDayOfWeek; + LocalDate dayInWeek = date.plusDays(dayToAdd); + addWeekJobList(dayInWeek); + dayOfWeekTracker++; + } + } + + @Override + public void updateFocusDate(LocalDate jobDate) { + requireNonNull(jobDate); + this.focusDate = jobDate; + } + + private void addWeekJobList(LocalDate dayToAdd) { + if (jobListGroupedByDate.containsKey(dayToAdd)) { + DeliveryList currentJobList = jobListGroupedByDate.get(dayToAdd); + weekJobListGroupedByDate.put(dayToAdd, currentJobList); + } else { + weekJobListGroupedByDate.put(dayToAdd, null); + } + } + + private DeliveryList createEmptyDayJobList() { + ArrayList> newEmptyArr = new ArrayList>(); + for (int i = 0; i < 6; i++) { + newEmptyArr.add(new ArrayList()); + } + return new DeliveryList(newEmptyArr); + } + + @Override + public Map getSortedDeliveryJobListByDate() { + return jobListGroupedByDate; + } + + @Override + public Map getWeekDeliveryJobList() { + return weekJobListGroupedByDate; + } + + @Override + public DeliveryList getDayOfWeekJob(int dayOfWeek) { + int focusDayOfWeek = focusDate.getDayOfWeek().getValue(); + LocalDate dayToGet = focusDate.plusDays(dayOfWeek - focusDayOfWeek); + return weekJobListGroupedByDate.get(dayToGet); + } + + @Override + public ObservableList getUnscheduledDeliveryJobList() { + updateSortedDeliveryJobList(SORTER_BY_DATE); + FilteredList unscheduledJobList = + new FilteredList<>(FXCollections.observableArrayList(sortedDeliveryJobs)); + unscheduledJobList.setPredicate(job -> (!job.isValidScheduled())); + return FXCollections.observableArrayList(unscheduledJobList); + } + + @Override + public ObservableList getCompletedDeliveryJobList() { + updateSortedDeliveryJobList(SORTER_BY_DATE); + FilteredList unscheduledJobList = + new FilteredList<>(FXCollections.observableArrayList(sortedDeliveryJobs)); + unscheduledJobList.setPredicate(job -> (job.getDeliveredStatus())); + return FXCollections.observableArrayList(unscheduledJobList); + } + + @Override + public LocalDate getFocusDate() { + return focusDate; + } + + //=========== ReminderList Accessors ============================================================= + + @Override + public void deleteReminder(int i) { + addressBook.removeReminder(i); + } + + @Override + public void addReminder(Reminder reminder) { + addressBook.addReminder(reminder); + } + + /** + * Returns an unmodifiable view of the list of {@code Person} backed by the internal list of + * {@code versionedAddressBook} + */ + @Override + public ObservableList getReminderList() { + return reminderList; + } + + /** + * Sorts reminder list + */ + @Override + public void sortReminderList() { + addressBook.sortReminderList(); + } + + @Override + public void setHasShown(int i, boolean b) { + reminderList.get(i).setHasShown(b); + } + @Override public boolean equals(Object obj) { // short circuit if same object @@ -147,4 +451,62 @@ public boolean equals(Object obj) { && filteredPersons.equals(other.filteredPersons); } + @Override + public boolean sameWeek(DeliveryJob job, WeeklyStats weeklyStats) { + Optional deliveryDate = job.getDeliveryDate(); + if (!deliveryDate.isEmpty()) { + DeliveryDate date = deliveryDate.get(); + LocalDate localDate = date.getDate(); + return weeklyStats.getDates().contains(localDate); + } + return false; + } + + @Override + public ObservableList weekJobsList(ObservableList list, LocalDate date) { + ObservableList newList = FXCollections.observableArrayList(list); + WeeklyStats weeklyStats = new WeeklyStats(date); + for (DeliveryJob job: list) { + if (!sameWeek(job, weeklyStats)) { + newList.remove(job); + } + } + return newList; + } + + @Override + public double getTotalEarnings(ObservableList list) { + double earnings = 0; + for (DeliveryJob job: list) { + Optional earning = job.getEarning(); + Earning earn = earning.get(); + if (earn != null) { + earnings += Double.parseDouble(earn.value); + } + } + return earnings; + } + + @Override + public int getTotalCompleted(ObservableList list) { + int completed = 0; + for (DeliveryJob job: list) { + if (job.getDeliveredStatus()) { + completed += 1; + } + } + return completed; + } + + @Override + public int getTotalPending(ObservableList list) { + int pending = 0; + for (DeliveryJob job: list) { + if (!job.getDeliveredStatus()) { + pending += 1; + } + } + return pending; + } + } diff --git a/src/main/java/seedu/address/model/ReadOnlyAddressBook.java b/src/main/java/seedu/address/model/ReadOnlyAddressBook.java index 6ddc2cd9a29..9f721412f52 100644 --- a/src/main/java/seedu/address/model/ReadOnlyAddressBook.java +++ b/src/main/java/seedu/address/model/ReadOnlyAddressBook.java @@ -1,7 +1,10 @@ package seedu.address.model; +import java.util.Optional; + import javafx.collections.ObservableList; import seedu.address.model.person.Person; +import seedu.address.model.reminder.Reminder; /** * Unmodifiable view of an address book @@ -14,4 +17,8 @@ public interface ReadOnlyAddressBook { */ ObservableList getPersonList(); + ObservableList getReminderList(); + + Optional getPersonById(String id); + } diff --git a/src/main/java/seedu/address/model/ReadOnlyDeliveryJobSystem.java b/src/main/java/seedu/address/model/ReadOnlyDeliveryJobSystem.java new file mode 100644 index 00000000000..2b4bc9a1e62 --- /dev/null +++ b/src/main/java/seedu/address/model/ReadOnlyDeliveryJobSystem.java @@ -0,0 +1,13 @@ +package seedu.address.model; + +import javafx.collections.ObservableList; +import seedu.address.model.jobs.DeliveryJob; + +/** + * ReadOnlyDeliveryJobSystem + */ +public interface ReadOnlyDeliveryJobSystem { + + ObservableList getDeliveryJobList(); + +} diff --git a/src/main/java/seedu/address/model/UserPrefs.java b/src/main/java/seedu/address/model/UserPrefs.java index 25a5fd6eab9..7ab403f58cf 100644 --- a/src/main/java/seedu/address/model/UserPrefs.java +++ b/src/main/java/seedu/address/model/UserPrefs.java @@ -15,11 +15,13 @@ public class UserPrefs implements ReadOnlyUserPrefs { private GuiSettings guiSettings = new GuiSettings(); private Path addressBookFilePath = Paths.get("data" , "addressbook.json"); + private Path deliveryJobSystemFilePath = Paths.get("data" , "deliveryjobsystem.json"); /** * Creates a {@code UserPrefs} with default values. */ - public UserPrefs() {} + public UserPrefs() { + } /** * Creates a {@code UserPrefs} with the prefs in {@code userPrefs}. @@ -84,4 +86,13 @@ public String toString() { return sb.toString(); } + public Path getDeliveryJobSystemFilePath() { + return deliveryJobSystemFilePath; + } + + public void setDeliveryJobSystemFilePath(Path deliveryJobSystemFilePath) { + requireNonNull(deliveryJobSystemFilePath); + this.deliveryJobSystemFilePath = deliveryJobSystemFilePath; + } + } diff --git a/src/main/java/seedu/address/model/jobs/DeliveryDate.java b/src/main/java/seedu/address/model/jobs/DeliveryDate.java new file mode 100644 index 00000000000..a4cf4d0efcd --- /dev/null +++ b/src/main/java/seedu/address/model/jobs/DeliveryDate.java @@ -0,0 +1,95 @@ +package seedu.address.model.jobs; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; + +/** + * Represents a job's job date in the address book. + * Guarantees: immutable; is valid as declared in {@link #isValidDate(String)} + */ +public class DeliveryDate { + + public static final String MESSAGE_CONSTRAINTS = "Dates must be valid date " + + "- only contain numeric characters and spaces, " + + "and it should not be blank.\n" + + "Date should have format like this: YYYY-mm-DD"; + + /* + * The first character of the address must not be a whitespace, + * otherwise " " (a blank string) becomes a valid input. + */ + public static final String VALIDATION_REGEX = "\\d{4}-\\d{2}-\\d{2}"; + public static final DateTimeFormatter VALID_FORMAT = DateTimeFormatter.ofPattern("YYYY-MM-dd"); + + /** + * Represents invalid date in storage. + */ + private static final String PLACEHOLDER = "9999-12-31"; + + public final String date; + + /** + * Constructs a {@code JobDate}. + * + * @param date A valid date. + */ + public DeliveryDate(String date) { + requireNonNull(date); + checkArgument(isValidDate(date), MESSAGE_CONSTRAINTS); + this.date = date; + } + + /** + * Constructs a placeholder {@code JobDate}. + * + * @param date A valid date. + */ + private DeliveryDate() { + this.date = PLACEHOLDER; + } + + /** + * Returns true if a given string is a valid date. + */ + public static boolean isValidDate(String test) { + try { + LocalDate.parse(test); + } catch (DateTimeParseException e) { + return false; + } + return test.matches(VALIDATION_REGEX); + } + + /** + * Returns date in LocalDate format + */ + public LocalDate getDate() { + return LocalDate.parse(this.date); + } + + @Override + public String toString() { + return date; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof DeliveryDate // instanceof handles nulls + && date.equals(((DeliveryDate) other).date)); // state check + } + + @Override + public int hashCode() { + return date.hashCode(); + } + + public static DeliveryDate placeholder() { + return new DeliveryDate(); + } + +} diff --git a/src/main/java/seedu/address/model/jobs/DeliveryJob.java b/src/main/java/seedu/address/model/jobs/DeliveryJob.java new file mode 100644 index 00000000000..9e36a3f71a9 --- /dev/null +++ b/src/main/java/seedu/address/model/jobs/DeliveryJob.java @@ -0,0 +1,476 @@ +package seedu.address.model.jobs; + +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; + +import java.time.LocalDate; +import java.util.Optional; +import java.util.UUID; + +/** + * Represents delivery jobs entities. + */ +public class DeliveryJob { + // Identity fields + private final String jobId; + + // Delivery informations + private final String recipient; + private final String sender; // aka customer + private final Optional deliveryDate; + private final Optional deliverySlot; + private final Optional earning; + private final Boolean isDelivered; + private final String description; + + /** + * Constructs a job entity. + * + * @param recipient + * @param sender + * @param earning + */ + public DeliveryJob(String recipient, String sender, String earning, String description) { + this(genJobId(recipient, sender), recipient, sender, Optional.empty(), Optional.empty(), + Optional.of(new Earning(earning)), false, description); + } + + /** + * Constructs a job entity. + * + * @param recipient + * @param sender + * @param deliveryDate + * @param deliverySlot + * @param earning + */ + public DeliveryJob(String recipient, String sender, Optional deliveryDate, + Optional deliverySlot, Optional earning, + String description) { + this(genJobId(recipient, sender), recipient, sender, deliveryDate, + deliverySlot, earning, false, description); + } + + /** + * Constructs a job entity. + * + * @param recipient + * @param sender + * @param deliveryDate + * @param deliverySlot + * @param earning + */ + public DeliveryJob(String recipient, String sender, String deliveryDate, String deliverySlot, String earning, + String description) { + this(genJobId(recipient, sender), recipient, sender, Optional.of(new DeliveryDate(deliveryDate)), + Optional.of(new DeliverySlot(deliverySlot)), Optional.of(new Earning(earning)), false, description); + } + + /** + * DeliveryJob + * + * @param jobId + * @param recipient + * @param deliverySlot + * @param sender + * @param earning + * @param isDelivered + */ + public DeliveryJob(String jobId, String recipient, String sender, Optional deliveryDate, + Optional deliverySlot, + Optional earning, + Boolean isDelivered, String description) { + this(true, jobId, recipient, sender, deliveryDate, + deliverySlot, earning, isDelivered, + description); + } + + /** + * A special constructor to bypass null check. + * + * @param jobId + * @param recipient + * @param deliverySlot + * @param sender + * @param earning + * @param isDelivered + */ + public DeliveryJob(boolean checkNull, String jobId, String recipient, String sender, + Optional deliveryDate, + Optional deliverySlot, + Optional earning, + Boolean isDelivered, String description) { + if (checkNull) { + requireAllNonNull(jobId, recipient, sender, deliveryDate, deliverySlot, earning, isDelivered, description); + } + this.jobId = jobId; + this.recipient = recipient; + this.sender = sender; + this.deliveryDate = deliveryDate; + this.deliverySlot = deliverySlot; + this.earning = earning; + this.isDelivered = isDelivered; + this.description = description; + } + + private static String genJobId(String recipient, String sender) { + requireAllNonNull(recipient, sender); + return recipient.substring(0, 2) + .concat(sender.substring(0, 2)) + .concat(UUID.randomUUID().toString().substring(0, 6)) + .toUpperCase(); + } + + /** + * Returns job ID + */ + public String getJobId() { + return jobId; + } + + /** + * Returns recipient ID + */ + public String getRecipientId() { + return recipient; + } + + /** + * Returns sender ID + */ + public String getSenderId() { + return sender; + } + + /** + * Returns delivery date + */ + public Optional getDeliveryDate() { + return deliveryDate; + } + + /** + * Returns delivery slot + */ + public Optional getDeliverySlot() { + return deliverySlot; + } + + /** + * Returns delivery date in LocalDate + */ + public LocalDate getDate() { + return deliveryDate.get().getDate(); + } + + /** + * Returns delivery slot in Integer + */ + public int getSlot() { + return deliverySlot.get().getSlot(); + } + + /** + * Returns delivery earning + */ + public Optional getEarning() { + return earning; + } + + /** + * Returns delivered status + */ + public Boolean getDeliveredStatus() { + return isDelivered; + } + + /** + * Returns delivery description in proper String format + */ + public String getDescription() { + if (description == null) { + return ""; + } + return description; + } + + /** + * Checks if job has delivery date or slot + * @return + */ + public boolean hasDateOrSlot() { + return hasDate() || hasSlot(); + } + + /** + * Checks if job has delivery date + * @return boolean + */ + public boolean hasDate() { + return getDeliveryDate().isPresent(); + } + + /** + * Checks if job has delivery slot + * @return boolean + */ + public boolean hasSlot() { + return getDeliverySlot().isPresent(); + } + + /** + * Checks if job has invalid delivery slot + * @return boolean + */ + public boolean hasInvalidSlot() { + return getDeliverySlot().isPresent() && (!getDeliverySlot().get().isValidRange()); + } + + /** + * Checks if job has earning + * @return boolean + */ + public boolean hasEarning() { + return getEarning().isPresent(); + } + + /** + * Checks if job has delivery date and slot. + * + * @return boolean + */ + public boolean isScheduled() { + return getDeliveryDate().isPresent() && getDeliverySlot().isPresent() + && getDeliverySlot().get().isPositive(); + } + + /** + * Checks if job has valid delivery date and slot + * @return + */ + public boolean isValidScheduled() { + return isScheduled() && deliverySlot.get().isValidRange(); + } + + /** + * isSameDeliveryJob. + * + * @param otherJob + * @return + */ + public boolean isSameDeliveryJob(DeliveryJob otherJob) { + if (otherJob == this) { + return true; + } + + return otherJob != null && otherJob.getJobId().equals(getJobId()); + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append("[Job detail]\n"); + if (getJobId() != null) { + builder.append("Job Id: " + getJobId() + "\n"); + } + + if (getSenderId() != null) { + builder.append("Sender Id: " + getSenderId() + "\n"); + } + + if (getRecipientId() != null) { + builder.append("Recipient Id: " + getRecipientId() + "\n"); + } + + if (getDeliveryDate().isPresent()) { + builder.append("Delivery Date: " + getDeliveryDate().get() + "\n"); + } + + if (getDeliverySlot().isPresent()) { + builder.append("Delivery Slot: " + getDeliverySlot().get() + "\n"); + } + + if (getEarning().isPresent()) { + builder.append("Earning: " + getEarning().get() + "\n"); + } + + if (getDeliveredStatus() != null) { + builder.append("Status: " + (getDeliveredStatus() ? "Delivered" : "Pending") + "\n"); + } + + return builder.toString(); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof DeliveryJob)) { + return false; + } + + DeliveryJob otherJob = (DeliveryJob) other; + return otherJob.getRecipientId().equals(getRecipientId()) + && otherJob.getSenderId().equals(getSenderId()) + && otherJob.getEarning().equals(getEarning()); + } + + /** + * Buider class for building DeliveryJob. + */ + public static class Builder { + private String jobId; + private String recipient; + private String sender; // aka customer + private Optional deliveryDate = Optional.empty(); + private Optional deliverySlot = Optional.empty(); + private Optional earning = Optional.empty(); + private Boolean isDelivered; + private String description; + + /** + * Copys from an existing job. + * + * @param job + * @return + */ + public Builder copy(DeliveryJob job) { + this.jobId = job.getJobId(); + this.recipient = job.getRecipientId(); + this.sender = job.getSenderId(); + this.deliveryDate = job.getDeliveryDate(); + this.deliverySlot = job.getDeliverySlot(); + this.earning = job.getEarning(); + this.isDelivered = job.getDeliveredStatus(); + this.description = job.getDescription(); + return this; + } + + /** + * Sets jobid. + * + * @param id + * @return + */ + public Builder setJobId(String id) { + this.jobId = id; + return this; + } + + /** + * Sets recipient. + * + * @param id + * @return + */ + public Builder setRecipient(String id) { + this.recipient = id; + return this; + } + + /** + * Sets sender. + * + * @param id + * @return + */ + public Builder setSender(String id) { + this.sender = id; + return this; + } + + /** + * Sets deliveryDate. + * + * @param date + * @return + */ + public Builder setDeliveryDate(String date) { + this.deliveryDate = Optional.of(new DeliveryDate(date)); + return this; + } + + /** + * Sets deliverySlot. + * + * @param slot + * @return + */ + public Builder setDeliverySlot(String slot) { + this.deliverySlot = Optional.of(new DeliverySlot(slot)); + return this; + } + + /** + * Clears deliverySlot. + * + * @return + */ + public Builder clearDeliverySlot() { + this.deliverySlot = Optional.of(DeliverySlot.placeholder()); + return this; + } + + /** + * Clears deliveryDate. + * + * @return + */ + public Builder clearDeliveryDate() { + this.deliveryDate = Optional.of(DeliveryDate.placeholder()); + return this; + } + + /** + * Sets earning. + * + * @param earn + * @return + */ + public Builder setEarning(String earn) { + this.earning = Optional.of(new Earning(earn)); + return this; + } + + /** + * Sets isDelivered. + * + * @param isDelivered + * @return + */ + public Builder setDeliveredStatus(boolean isDelivered) { + this.isDelivered = isDelivered; + return this; + } + + /** + * Sets description. + * + * @param description + * @return + */ + public Builder setDescription(String description) { + this.description = description; + return this; + } + + /** + * Builds DeliveryJob. + */ + public DeliveryJob build() { + return new DeliveryJob(jobId, recipient, sender, + deliveryDate, + deliverySlot, earning, isDelivered, description); + } + + /** + * Builds DeliveryJob. + */ + public DeliveryJob buildNullable() { + return new DeliveryJob(false, jobId, recipient, sender, + deliveryDate, + deliverySlot, earning, isDelivered, description); + } + } +} diff --git a/src/main/java/seedu/address/model/jobs/DeliveryList.java b/src/main/java/seedu/address/model/jobs/DeliveryList.java new file mode 100644 index 00000000000..9d13ec6ad1b --- /dev/null +++ b/src/main/java/seedu/address/model/jobs/DeliveryList.java @@ -0,0 +1,47 @@ +package seedu.address.model.jobs; + +import java.util.ArrayList; + +/** + * Represents an arraylist of array list of jobs + * Represents list of jobs in a day - classified into different slots + */ +public class DeliveryList { + private ArrayList> jobList; + + public DeliveryList(ArrayList> jobList) { + this.jobList = jobList; + } + + public DeliveryList() { + this.jobList = new ArrayList>(); + } + + /** + * Returns job list + */ + public ArrayList> getJobList() { + return this.jobList; + } + + /** + * Returns size of list + */ + public int size() { + return jobList.size(); + } + + /** + * Returns element of job list at specified index + * @param index + */ + public ArrayList get(int index) { + return jobList.get(index); + } + + @Override + public String toString() { + return jobList.toString(); + } + +} diff --git a/src/main/java/seedu/address/model/jobs/DeliverySlot.java b/src/main/java/seedu/address/model/jobs/DeliverySlot.java new file mode 100644 index 00000000000..e0e10acc26b --- /dev/null +++ b/src/main/java/seedu/address/model/jobs/DeliverySlot.java @@ -0,0 +1,109 @@ +package seedu.address.model.jobs; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +/** + * Represents a Delivery's Delivery Slot in the delivery jobs book. + * Guarantees: immutable; is valid as declared in + * {@link #isValidDeliverySlot(String)} + */ +public class DeliverySlot { + + public static final String MESSAGE_CONSTRAINTS = "Delivery Slot must be larger than 0 - should be between [1-5]." + + "\nIt should not be blank"; + + public static final String VALIDATION_REGEX = "\\d+"; + + public final String value; + + /** + * Constructs an {@code DeliverySlot}. + * + * @param value A valid slot. + */ + public DeliverySlot(String value) { + requireNonNull(value); + checkArgument(isValidDeliverySlot(value), MESSAGE_CONSTRAINTS); + this.value = value; + } + + /** + * Constructs an {@code DeliverySlot}. + */ + private DeliverySlot() { + this.value = "-1"; + } + + /** + * Returns true if a given string is a valid slot. + */ + public static boolean isValidDeliverySlot(String value) { + return value.matches(VALIDATION_REGEX); + } + + /** + * Returns slot value in integer + */ + public int getSlot() { + return Integer.parseInt(value); + } + + /** + * Checks if delivery slot is valid (within range 1-5) + * @return + */ + public boolean isValidRange() { + return ((Integer.parseInt(value) < 6) && (Integer.parseInt(value) > 0)); + } + + /** + * Checks if delivery slot is positive (larger than 0) + * @return + */ + public boolean isPositive() { + return Integer.parseInt(value) > 0; + } + + @Override + public String toString() { + return value; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof DeliverySlot // instanceof handles nulls + && value.equals(((DeliverySlot) other).value)); // state check + } + + @Override + public int hashCode() { + return value.hashCode(); + } + + public static DeliverySlot placeholder() { + return new DeliverySlot(); + } + + public String getDescription() { + switch (value) { + case "1": + return "10AM - 11AM"; + case "2": + return "11AM - 12PM"; + case "3": + return "1PM - 2PM"; + case "4": + return "2PM - 3PM"; + case "5": + return "3PM - 4PM"; + default: + if (Integer.parseInt(value) > 5) { + return "Extra hours (4PM++)"; + } + return "N.A."; + } + } + +} diff --git a/src/main/java/seedu/address/model/jobs/Earning.java b/src/main/java/seedu/address/model/jobs/Earning.java new file mode 100644 index 00000000000..7d532fe5552 --- /dev/null +++ b/src/main/java/seedu/address/model/jobs/Earning.java @@ -0,0 +1,97 @@ +package seedu.address.model.jobs; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +/** + * Represents a Delivery's earning in the delivery jobs book. + * Guarantees: immutable; is valid as declared in {@link #isValidEarning(String)} + */ +public class Earning implements Comparable { + + public static final String MESSAGE_CONSTRAINTS = "Earning should only contain double, and it should not be blank"; + + public static final String VALIDATION_REGEX = "\\d+"; + public static final String VALIDATION_REGEX_DECI = "\\d+\\.\\d+"; + + public final String value; + public final String dollar; + public final String cent; + + /** + * Constructs an {@code earning}. + * + * @param earning A valid earning. + */ + public Earning(String earning) { + requireNonNull(earning); + checkArgument(isValidEarning(earning), MESSAGE_CONSTRAINTS); + value = Double.toString(Double.parseDouble(earning)); + + String[] format = earning.split("\\."); + if (format.length == 2) { + dollar = format[0]; + cent = String.format("%-2s", format[1]).replace(' ', '0').substring(0, 2); + } else { + dollar = format[0]; + cent = "00"; + } + } + + /** + * Returns earning in double data type + */ + public double getEarning() { + return Double.parseDouble(value); + } + + /** + * Returns new earning as 0.0 + */ + public static Earning placeholder() { + return new Earning("0.00"); + } + + /** + * Returns true if a given string is a valid earning. + */ + public static boolean isValidEarning(String test) { + return test.matches(VALIDATION_REGEX) || test.matches(VALIDATION_REGEX_DECI); + } + + @Override + public String toString() { + return value; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Earning // instanceof handles nulls + && value.equals(((Earning) other).value)); // state check + } + + @Override + public int hashCode() { + return value.hashCode(); + } + + /** + * Parses string value to double. + * + * @return + */ + public double toDouble() { + return Double.parseDouble(value); + } + + @Override + public int compareTo(Earning other) { + if (this.getEarning() - other.getEarning() < 0) { + return 1; + } else if (this.getEarning() - other.getEarning() > 0) { + return -1; + } + return 0; + } +} diff --git a/src/main/java/seedu/address/model/jobs/UniqueDeliveryList.java b/src/main/java/seedu/address/model/jobs/UniqueDeliveryList.java new file mode 100644 index 00000000000..30e496431d8 --- /dev/null +++ b/src/main/java/seedu/address/model/jobs/UniqueDeliveryList.java @@ -0,0 +1,141 @@ +package seedu.address.model.jobs; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; + +import java.util.Iterator; +import java.util.List; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import seedu.address.model.jobs.exceptions.DeliveryJobNotFoundException; +import seedu.address.model.jobs.exceptions.DuplicateDeliveryJobException; + +/** + * Represents a UniqueDeliveryList in the delivery jobs book. + */ +public class UniqueDeliveryList implements Iterable { + + private final ObservableList internalList = FXCollections.observableArrayList(); + private final ObservableList internalUnmodifiableList = + FXCollections.unmodifiableObservableList(internalList); + + /** + * Checks if job list contain a job + */ + public boolean contains(DeliveryJob toCheck) { + requireNonNull(toCheck); + return internalList.stream().anyMatch(toCheck::isSameDeliveryJob); + } + + /** + * Adds a delivery job to job list + * + * @param toAdd + */ + public void add(DeliveryJob toAdd) { + requireNonNull(toAdd); + if (contains(toAdd)) { + throw new DuplicateDeliveryJobException(); + } + internalList.add(toAdd); + } + + /** + * Sets/updates a specific delivery job + * + * @param target + * @param editedJob + */ + public void setDeliveryJob(DeliveryJob target, DeliveryJob editedJob) { + requireAllNonNull(target, editedJob); + + int index = internalList.indexOf(target); + if (index == -1) { + throw new DeliveryJobNotFoundException(); + } + + if (!target.isSameDeliveryJob(editedJob) && contains(editedJob)) { + throw new DuplicateDeliveryJobException(); + } + + internalList.set(index, editedJob); + } + + /** + * Removes a delivery job. + */ + public void remove(DeliveryJob toRemove) { + requireNonNull(toRemove); + if (!internalList.remove(toRemove)) { + throw new DeliveryJobNotFoundException(); + } + } + + /** + * Sets delivery job + * + * @param replacement + */ + public void setDeliveryJobs(UniqueDeliveryList replacement) { + requireNonNull(replacement); + internalList.setAll(replacement.internalList); + } + + /** + * Sets job list to a new list + * + * @param jobs + */ + public void setDeliveryJobs(List jobs) { + requireAllNonNull(jobs); + if (!deliveryJobsAreUnique(jobs)) { + throw new DuplicateDeliveryJobException(); + } + + internalList.setAll(jobs); + } + + /** + * Returns the backing list as an unmodifiable {@code ObservableList}. + */ + public ObservableList asUnmodifiableObservableList() { + return internalUnmodifiableList; + } + + /** + * Returns number of jobs + */ + public int size() { + return internalList.size(); + } + + @Override + public Iterator iterator() { + return internalList.iterator(); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof UniqueDeliveryList // instanceof handles nulls + && internalList.equals(((UniqueDeliveryList) other).internalList)); + } + + @Override + public int hashCode() { + return internalList.hashCode(); + } + + private boolean deliveryJobsAreUnique(List jobs) { + for (int i = 0; i < jobs.size() - 1; i++) { + for (int j = i + 1; j < jobs.size(); j++) { + if (jobs.get(i).isSameDeliveryJob(jobs.get(j))) { + return false; + } + } + } + return true; + } + +} diff --git a/src/main/java/seedu/address/model/jobs/exceptions/DeliveryJobNotFoundException.java b/src/main/java/seedu/address/model/jobs/exceptions/DeliveryJobNotFoundException.java new file mode 100644 index 00000000000..1abfb1d89a3 --- /dev/null +++ b/src/main/java/seedu/address/model/jobs/exceptions/DeliveryJobNotFoundException.java @@ -0,0 +1,10 @@ +package seedu.address.model.jobs.exceptions; + +/** + * DeliveryJobNotFoundException + */ +public class DeliveryJobNotFoundException extends RuntimeException { + public DeliveryJobNotFoundException() { + super("Operation would result in duplicate delivery jobs"); + } +} diff --git a/src/main/java/seedu/address/model/jobs/exceptions/DuplicateDeliveryJobException.java b/src/main/java/seedu/address/model/jobs/exceptions/DuplicateDeliveryJobException.java new file mode 100644 index 00000000000..5aeff877221 --- /dev/null +++ b/src/main/java/seedu/address/model/jobs/exceptions/DuplicateDeliveryJobException.java @@ -0,0 +1,6 @@ +package seedu.address.model.jobs.exceptions; + +/** + * DuplicateDeliveryJobException + */ +public class DuplicateDeliveryJobException extends RuntimeException {} diff --git a/src/main/java/seedu/address/model/jobs/predicate/DeliveryJobContainsDeliveryDatePredicate.java b/src/main/java/seedu/address/model/jobs/predicate/DeliveryJobContainsDeliveryDatePredicate.java new file mode 100644 index 00000000000..ed57f18c8ce --- /dev/null +++ b/src/main/java/seedu/address/model/jobs/predicate/DeliveryJobContainsDeliveryDatePredicate.java @@ -0,0 +1,38 @@ +package seedu.address.model.jobs.predicate; + +import java.util.function.Predicate; + +import seedu.address.model.jobs.DeliveryDate; +import seedu.address.model.jobs.DeliveryJob; + +/** + * Predicate for delivery date. + */ +public class DeliveryJobContainsDeliveryDatePredicate implements Predicate { + private final DeliveryDate toFind; + + public DeliveryJobContainsDeliveryDatePredicate(DeliveryDate toFind) { + this.toFind = toFind; + } + + @Override + public boolean test(DeliveryJob job) { + if (job.getDeliveryDate().isPresent()) { + if (job.getDeliveryDate().get().equals(toFind)) { + return true; + } + } else { + if (toFind.equals(DeliveryDate.placeholder())) { + if (job.getDeliveryDate().isEmpty()) { + return true; + } + } + } + return false; + } + + @Override + public String toString() { + return toFind.toString(); + } +} diff --git a/src/main/java/seedu/address/model/jobs/predicate/DeliveryJobContainsDeliverySlotPredicate.java b/src/main/java/seedu/address/model/jobs/predicate/DeliveryJobContainsDeliverySlotPredicate.java new file mode 100644 index 00000000000..1528f722723 --- /dev/null +++ b/src/main/java/seedu/address/model/jobs/predicate/DeliveryJobContainsDeliverySlotPredicate.java @@ -0,0 +1,38 @@ +package seedu.address.model.jobs.predicate; + +import java.util.function.Predicate; + +import seedu.address.model.jobs.DeliveryJob; +import seedu.address.model.jobs.DeliverySlot; + +/** + * Predicate for delivery slot. + */ +public class DeliveryJobContainsDeliverySlotPredicate implements Predicate { + private final DeliverySlot toFind; + + public DeliveryJobContainsDeliverySlotPredicate(DeliverySlot toFind) { + this.toFind = toFind; + } + + @Override + public boolean test(DeliveryJob job) { + if (job.getDeliverySlot().isPresent()) { + if (job.getDeliverySlot().get().equals(toFind)) { + return true; + } + } else { + if (toFind.equals(DeliverySlot.placeholder())) { + if (job.getDeliverySlot().isEmpty()) { + return true; + } + } + } + return false; + } + + @Override + public String toString() { + return toFind.toString(); + } +} diff --git a/src/main/java/seedu/address/model/jobs/predicate/DeliveryJobContainsEarningPredicate.java b/src/main/java/seedu/address/model/jobs/predicate/DeliveryJobContainsEarningPredicate.java new file mode 100644 index 00000000000..86ddfc3338f --- /dev/null +++ b/src/main/java/seedu/address/model/jobs/predicate/DeliveryJobContainsEarningPredicate.java @@ -0,0 +1,30 @@ +package seedu.address.model.jobs.predicate; + +import java.util.function.Predicate; + +import seedu.address.model.jobs.DeliveryJob; +import seedu.address.model.jobs.Earning; + +/** + * Predicate for earning. + */ +public class DeliveryJobContainsEarningPredicate implements Predicate { + private final Earning toFind; + + public DeliveryJobContainsEarningPredicate(Earning toFind) { + this.toFind = toFind; + } + + @Override + public boolean test(DeliveryJob job) { + if (Double.compare(job.getEarning().get().getEarning(), toFind.getEarning()) == 0) { + return true; + } + return false; + } + + @Override + public String toString() { + return toFind.toString(); + } +} diff --git a/src/main/java/seedu/address/model/jobs/predicate/DeliveryJobContainsJobIdPredicate.java b/src/main/java/seedu/address/model/jobs/predicate/DeliveryJobContainsJobIdPredicate.java new file mode 100644 index 00000000000..a7eae5fc8fc --- /dev/null +++ b/src/main/java/seedu/address/model/jobs/predicate/DeliveryJobContainsJobIdPredicate.java @@ -0,0 +1,29 @@ +package seedu.address.model.jobs.predicate; + +import java.util.function.Predicate; + +import seedu.address.model.jobs.DeliveryJob; + +/** + * Predicate for job id. + */ +public class DeliveryJobContainsJobIdPredicate implements Predicate { + private final String toFind; + + public DeliveryJobContainsJobIdPredicate(String toFind) { + this.toFind = toFind; + } + + @Override + public boolean test(DeliveryJob job) { + if (job.getJobId().toUpperCase().contains(toFind.toUpperCase())) { + return true; + } + return false; + } + + @Override + public String toString() { + return toFind; + } +} diff --git a/src/main/java/seedu/address/model/jobs/predicate/DeliveryJobContainsRecipientIdPredicate.java b/src/main/java/seedu/address/model/jobs/predicate/DeliveryJobContainsRecipientIdPredicate.java new file mode 100644 index 00000000000..3d5295a8bfa --- /dev/null +++ b/src/main/java/seedu/address/model/jobs/predicate/DeliveryJobContainsRecipientIdPredicate.java @@ -0,0 +1,29 @@ +package seedu.address.model.jobs.predicate; + +import java.util.function.Predicate; + +import seedu.address.model.jobs.DeliveryJob; + +/** + * Predicate for recipient id. + */ +public class DeliveryJobContainsRecipientIdPredicate implements Predicate { + private final String toFind; + + public DeliveryJobContainsRecipientIdPredicate(String toFind) { + this.toFind = toFind; + } + + @Override + public boolean test(DeliveryJob job) { + if (job.getRecipientId().toUpperCase().contains(toFind.toUpperCase())) { + return true; + } + return false; + } + + @Override + public String toString() { + return toFind; + } +} diff --git a/src/main/java/seedu/address/model/jobs/predicate/DeliveryJobContainsSenderIdPredicate.java b/src/main/java/seedu/address/model/jobs/predicate/DeliveryJobContainsSenderIdPredicate.java new file mode 100644 index 00000000000..3b0de5deeff --- /dev/null +++ b/src/main/java/seedu/address/model/jobs/predicate/DeliveryJobContainsSenderIdPredicate.java @@ -0,0 +1,29 @@ +package seedu.address.model.jobs.predicate; + +import java.util.function.Predicate; + +import seedu.address.model.jobs.DeliveryJob; + +/** + * Predicate for sender id. + */ +public class DeliveryJobContainsSenderIdPredicate implements Predicate { + private final String toFind; + + public DeliveryJobContainsSenderIdPredicate(String toFind) { + this.toFind = toFind; + } + + @Override + public boolean test(DeliveryJob job) { + if (job.getSenderId().toUpperCase().contains(toFind.toUpperCase())) { + return true; + } + return false; + } + + @Override + public String toString() { + return toFind; + } +} diff --git a/src/main/java/seedu/address/model/jobs/predicate/DeliveryJobContainsStatusPredicate.java b/src/main/java/seedu/address/model/jobs/predicate/DeliveryJobContainsStatusPredicate.java new file mode 100644 index 00000000000..73edf782479 --- /dev/null +++ b/src/main/java/seedu/address/model/jobs/predicate/DeliveryJobContainsStatusPredicate.java @@ -0,0 +1,29 @@ +package seedu.address.model.jobs.predicate; + +import java.util.function.Predicate; + +import seedu.address.model.jobs.DeliveryJob; + +/** + * Predicate for delivery status. + */ +public class DeliveryJobContainsStatusPredicate implements Predicate { + private final boolean isDelivered; + + public DeliveryJobContainsStatusPredicate(boolean isDelivered) { + this.isDelivered = isDelivered; + } + + @Override + public boolean test(DeliveryJob job) { + if (job.getDeliveredStatus() == isDelivered) { + return true; + } + return false; + } + + @Override + public String toString() { + return Boolean.toString(isDelivered); + } +} diff --git a/src/main/java/seedu/address/model/jobs/sorters/DeliveryFilterOption.java b/src/main/java/seedu/address/model/jobs/sorters/DeliveryFilterOption.java new file mode 100644 index 00000000000..2efea943a49 --- /dev/null +++ b/src/main/java/seedu/address/model/jobs/sorters/DeliveryFilterOption.java @@ -0,0 +1,10 @@ +package seedu.address.model.jobs.sorters; + +/** + * DeliveryFilterOption + */ +public enum DeliveryFilterOption { + ALL, + COM, + PEN +} diff --git a/src/main/java/seedu/address/model/jobs/sorters/DeliverySortOption.java b/src/main/java/seedu/address/model/jobs/sorters/DeliverySortOption.java new file mode 100644 index 00000000000..ce016f4ba15 --- /dev/null +++ b/src/main/java/seedu/address/model/jobs/sorters/DeliverySortOption.java @@ -0,0 +1,10 @@ +package seedu.address.model.jobs.sorters; + +/** + * DeliverySortOption + */ +public enum DeliverySortOption { + COM, + EARN, + DATE +} diff --git a/src/main/java/seedu/address/model/jobs/sorters/OrderedSorter.java b/src/main/java/seedu/address/model/jobs/sorters/OrderedSorter.java new file mode 100644 index 00000000000..4a7703074bc --- /dev/null +++ b/src/main/java/seedu/address/model/jobs/sorters/OrderedSorter.java @@ -0,0 +1,17 @@ +package seedu.address.model.jobs.sorters; + +import java.util.Comparator; + +import seedu.address.model.jobs.DeliveryJob; + +abstract class OrderedSorter implements Comparator { + private boolean isAsc; + + public OrderedSorter(boolean isAsc) { + this.isAsc = isAsc; + } + + public boolean isAsc() { + return isAsc; + } +} diff --git a/src/main/java/seedu/address/model/jobs/sorters/SortbyDate.java b/src/main/java/seedu/address/model/jobs/sorters/SortbyDate.java new file mode 100644 index 00000000000..3e83790d484 --- /dev/null +++ b/src/main/java/seedu/address/model/jobs/sorters/SortbyDate.java @@ -0,0 +1,53 @@ +package seedu.address.model.jobs.sorters; + +import seedu.address.model.jobs.DeliveryJob; + +/** + * Helper class implementing Comparator + * Sort by job's scheduled date + */ +public class SortbyDate extends OrderedSorter { + + public SortbyDate(boolean asc) { + super(asc); + } + + /** + * Method sort by Delivered (increasing) + * + * @param a the first job to be compared. + * @param b the second job to be compared. + * @return + */ + public int compare(DeliveryJob a, DeliveryJob b) { + if (a.getDeliveryDate().isPresent() && b.getDeliveryDate().isPresent()) { + return isAsc() ? compareByDate(a, b) + : compareByDate(b, a); + } else if (a.getDeliveryDate().isPresent() && b.getDeliveryDate().isEmpty()) { + return isAsc() ? 1 : -1; + } else { + return isAsc() ? -1 : 1; + } + } + + private int compareByDate(DeliveryJob a, DeliveryJob b) { + if (a.getDeliveryDate().get().getDate().compareTo( + b.getDeliveryDate().get().getDate()) == 0) { + return compareBySlot(a, b); + } else { + return a.getDeliveryDate().get().getDate().compareTo( + b.getDeliveryDate().get().getDate()); + } + } + + private int compareBySlot(DeliveryJob a, DeliveryJob b) { + if (a.getDeliverySlot().isPresent() && b.getDeliverySlot().isPresent()) { + return isAsc() ? a.getSlot() - b.getSlot() + : b.getSlot() - a.getSlot(); + } else if (a.getDeliverySlot().isPresent() && b.getDeliverySlot().isEmpty()) { + return isAsc() ? 1 : -1; + } else { + return isAsc() ? -1 : 1; + } + } +} diff --git a/src/main/java/seedu/address/model/jobs/sorters/SortbyDelivered.java b/src/main/java/seedu/address/model/jobs/sorters/SortbyDelivered.java new file mode 100644 index 00000000000..6277bcc6ae5 --- /dev/null +++ b/src/main/java/seedu/address/model/jobs/sorters/SortbyDelivered.java @@ -0,0 +1,32 @@ +package seedu.address.model.jobs.sorters; + +import seedu.address.model.jobs.DeliveryJob; + +/** + * Helper class implementing Comparator + * Sort by job's delivered status + */ +public class SortbyDelivered extends OrderedSorter { + + public SortbyDelivered(boolean asc) { + super(asc); + } + + /** + * Method sort by Delivered (increasing) + * @param a the first job to be compared. + * @param b the second job to be compared. + * @return + */ + public int compare(DeliveryJob a, DeliveryJob b) { + if (a.getDeliveredStatus() == b.getDeliveredStatus()) { + return 0; + } else { + if (a.getDeliveredStatus()) { + return isAsc() ? 1 : -1; + } else { + return isAsc() ? -1 : 1; + } + } + } +} diff --git a/src/main/java/seedu/address/model/jobs/sorters/SortbyEarning.java b/src/main/java/seedu/address/model/jobs/sorters/SortbyEarning.java new file mode 100644 index 00000000000..f685cbd084d --- /dev/null +++ b/src/main/java/seedu/address/model/jobs/sorters/SortbyEarning.java @@ -0,0 +1,31 @@ +package seedu.address.model.jobs.sorters; + +import seedu.address.model.jobs.DeliveryJob; + +/** + * Helper class implementing Comparator + * Sort by job's earning + */ +public class SortbyEarning extends OrderedSorter { + + public SortbyEarning(boolean asc) { + super(asc); + } + + /** + * Method sort by Delivered (increasing) + * @param a the first job to be compared. + * @param b the second job to be compared. + * @return + */ + public int compare(DeliveryJob a, DeliveryJob b) { + if (a.getEarning().isPresent() && b.getEarning().isPresent()) { + return isAsc() ? a.getEarning().get().compareTo(b.getEarning().get()) + : b.getEarning().get().compareTo(a.getEarning().get()); + } else if (a.getEarning().isPresent() && b.getEarning().isEmpty()) { + return isAsc() ? 1 : -1; + } else { + return isAsc() ? -1 : 1; + } + } +} diff --git a/src/main/java/seedu/address/model/jobs/sorters/SortbyTimeAndEarn.java b/src/main/java/seedu/address/model/jobs/sorters/SortbyTimeAndEarn.java new file mode 100644 index 00000000000..37ef2f7fbd6 --- /dev/null +++ b/src/main/java/seedu/address/model/jobs/sorters/SortbyTimeAndEarn.java @@ -0,0 +1,62 @@ +package seedu.address.model.jobs.sorters; + +import java.util.Comparator; +import java.util.NoSuchElementException; + +import seedu.address.model.jobs.DeliveryJob; + +/** + * Helper class implementing Comparator + * Sort by job's scheduled timing + */ +public class SortbyTimeAndEarn implements Comparator { + + /** + * Method sort by time (increasing) + * If time is the same, sort by earning (decreasing) + * @param a the first job to be compared. + * @param b the second job to be compared. + * @return difference between jobs' timing/earning + */ + public int compare(DeliveryJob a, DeliveryJob b) { + try { + if (compareByDate(a, b) != 0) { + return compareByDate(a, b); + } else { + return a.getEarning().get().compareTo(b.getEarning().get()); + } + } catch (NoSuchElementException e) { + if (b.isScheduled()) { + return 1; + } else if (a.isScheduled()) { + return -1; + } else if ((a.hasDateOrSlot()) && (!b.hasDateOrSlot())) { + return -1; + } else if ((b.hasDateOrSlot()) && (!a.hasDateOrSlot())) { + return 1; + } else if (a.hasEarning() && b.hasEarning()) { + return a.getEarning().get().compareTo(b.getEarning().get()); + } else if (b.hasEarning()) { + return 1; + } else if (a.hasEarning()) { + return -1; + } + return 0; + } + } + + /** + * Sorts by date + */ + private int compareByDate(DeliveryJob a, DeliveryJob b) { + if (a.getDeliveryDate().isPresent() && b.getDeliveryDate().isPresent()) { + if (a.getDate().compareTo(b.getDate()) == 0) { + return a.getSlot() - b.getSlot(); + } else { + return a.getDate().compareTo(b.getDate()); + } + } else { + return 0; + } + } +} diff --git a/src/main/java/seedu/address/model/person/Person.java b/src/main/java/seedu/address/model/person/Person.java index 8ff1d83fe89..29ecf826a13 100644 --- a/src/main/java/seedu/address/model/person/Person.java +++ b/src/main/java/seedu/address/model/person/Person.java @@ -6,15 +6,19 @@ import java.util.HashSet; import java.util.Objects; import java.util.Set; +import java.util.UUID; import seedu.address.model.tag.Tag; /** * Represents a Person in the address book. - * Guarantees: details are present and not null, field values are validated, immutable. + * Guarantees: details are present and not null, field values are validated, + * immutable. */ public class Person { + private final String personId; + // Identity fields private final Name name; private final Phone phone; @@ -27,8 +31,9 @@ public class Person { /** * Every field must be present and not null. */ - public Person(Name name, Phone phone, Email email, Address address, Set tags) { - requireAllNonNull(name, phone, email, address, tags); + public Person(String personId, Name name, Phone phone, Email email, Address address, Set tags) { + requireAllNonNull(personId, name, phone, email, address, tags); + this.personId = personId; this.name = name; this.phone = phone; this.email = email; @@ -36,6 +41,40 @@ public Person(Name name, Phone phone, Email email, Address address, Set tag this.tags.addAll(tags); } + /** + * Every field must be present and not null. + */ + public Person(Name name, Phone phone, Email email, Address address, Set tags) { + this(genPersonId(name, phone), name, phone, email, address, tags); + } + + /** + * Empty initialisation for ImportDeliveryJobCommand. + */ + public Person() { + this.personId = "null"; + this.name = null; + this.phone = null; + this.email = null; + this.address = null; + this.tags.add(null); + } + + private static String genPersonId(Name name, Phone phone) { + requireAllNonNull(name, phone); + String prefix = name.fullName.trim(); + if (name.fullName.length() < 3) { + prefix = name.fullName + "0".repeat(3 - name.fullName.length()); + } + prefix = prefix.substring(0, 3); + return prefix.concat(UUID.randomUUID().toString().substring(0, 3)) + .toUpperCase(); + } + + public String getPersonId() { + return personId; + } + public Name getName() { return name; } @@ -53,7 +92,8 @@ public Address getAddress() { } /** - * Returns an immutable tag set, which throws {@code UnsupportedOperationException} + * Returns an immutable tag set, which throws + * {@code UnsupportedOperationException} * if modification is attempted. */ public Set getTags() { @@ -98,7 +138,7 @@ public boolean equals(Object other) { @Override public int hashCode() { // use this method for custom fields hashing instead of implementing your own - return Objects.hash(name, phone, email, address, tags); + return Objects.hash(personId, name, phone, email, address, tags); } @Override diff --git a/src/main/java/seedu/address/model/person/UniquePersonList.java b/src/main/java/seedu/address/model/person/UniquePersonList.java index 0fee4fe57e6..e39f688c1e9 100644 --- a/src/main/java/seedu/address/model/person/UniquePersonList.java +++ b/src/main/java/seedu/address/model/person/UniquePersonList.java @@ -17,7 +17,7 @@ * persons uses Person#isSamePerson(Person) for equality so as to ensure that the person being added or updated is * unique in terms of identity in the UniquePersonList. However, the removal of a person uses Person#equals(Object) so * as to ensure that the person with exactly the same fields will be removed. - * + *

* Supports a minimal set of list operations. * * @see Person#isSamePerson(Person) @@ -113,7 +113,7 @@ public Iterator iterator() { public boolean equals(Object other) { return other == this // short circuit if same object || (other instanceof UniquePersonList // instanceof handles nulls - && internalList.equals(((UniquePersonList) other).internalList)); + && internalList.equals(((UniquePersonList) other).internalList)); } @Override diff --git a/src/main/java/seedu/address/model/person/exceptions/PersonNotFoundException.java b/src/main/java/seedu/address/model/person/exceptions/PersonNotFoundException.java index fa764426ca7..588cfb5fbf8 100644 --- a/src/main/java/seedu/address/model/person/exceptions/PersonNotFoundException.java +++ b/src/main/java/seedu/address/model/person/exceptions/PersonNotFoundException.java @@ -3,4 +3,5 @@ /** * Signals that the operation is unable to find the specified person. */ -public class PersonNotFoundException extends RuntimeException {} +public class PersonNotFoundException extends RuntimeException { +} diff --git a/src/main/java/seedu/address/model/reminder/Reminder.java b/src/main/java/seedu/address/model/reminder/Reminder.java new file mode 100644 index 00000000000..71137f07f74 --- /dev/null +++ b/src/main/java/seedu/address/model/reminder/Reminder.java @@ -0,0 +1,57 @@ +package seedu.address.model.reminder; + +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; +import static seedu.address.commons.util.DateTimeUtil.dateTimeToString; + +import java.time.LocalDateTime; + +/** + * Represents a Reminder in the Reminders. + * Guarantees: details are present and not null, field values are validated, immutable. + */ +public class Reminder { + private final String description; + private final LocalDateTime reminderDateTime; + private boolean hasShown; + + /** + * Constructor to create a Reminder object. + * @param description Description of the reminder. Description can be left blank, but not null (ie. ""). + * @param reminderDateTime When the reminder will be activated. Cannot be null. + */ + public Reminder(String description, LocalDateTime reminderDateTime) { + requireAllNonNull(description, reminderDateTime); + this.description = description; + this.reminderDateTime = reminderDateTime; + this.hasShown = false; + } + + public String getDescription() { + return this.description; + } + + public LocalDateTime getReminderDateTime() { + return this.reminderDateTime; + } + + public String reminderDateTimeToString() { + return dateTimeToString(reminderDateTime); + } + + public Boolean getHasShown() { + return this.hasShown; + } + + public void setHasShown(Boolean b) { + this.hasShown = b; + } + + /** + * Returns Description of reminder + * @return Description + */ + public String toString() { + return getDescription(); + } + +} diff --git a/src/main/java/seedu/address/model/reminder/ReminderList.java b/src/main/java/seedu/address/model/reminder/ReminderList.java new file mode 100644 index 00000000000..508a2d252ab --- /dev/null +++ b/src/main/java/seedu/address/model/reminder/ReminderList.java @@ -0,0 +1,70 @@ +package seedu.address.model.reminder; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; + +import java.util.Iterator; +import java.util.List; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; + +/** + * A list of reminders that does not allow nulls. + *

+ * Supports a minimal set of list operations. + */ + +public class ReminderList implements Iterable { + private final ObservableList internalList = FXCollections.observableArrayList(); + private final ObservableList internalUnmodifiableList = + FXCollections.unmodifiableObservableList(internalList); + + /** + * Adds a reminder to the list. + */ + public void add(Reminder toAdd) { + requireNonNull(toAdd); + internalList.add(toAdd); + } + + /** + * Removes the reminder from the list. + * The reminder must exist in the list. + */ + public void remove(int i) { + requireNonNull(internalList.get(i)); + internalList.remove(i); + } + + public void setReminderList(ReminderList replacement) { + requireNonNull(replacement); + internalList.setAll(replacement.internalList); + } + + /** + * Replaces the contents of this list with {@code reminderList}. + */ + public void setReminderList(List reminderList) { + requireAllNonNull(reminderList); + internalList.setAll(reminderList); + } + + public void sortByOldest() { + internalList.sort(new ReminderListComparator()); + } + + /** + * Returns the backing list as an unmodifiable {@code ObservableList}. + */ + public ObservableList asUnmodifiableObservableList() { + return internalUnmodifiableList; + } + + @Override + public Iterator iterator() { + return internalList.iterator(); + } + + +} diff --git a/src/main/java/seedu/address/model/reminder/ReminderListComparator.java b/src/main/java/seedu/address/model/reminder/ReminderListComparator.java new file mode 100644 index 00000000000..fb6960f485e --- /dev/null +++ b/src/main/java/seedu/address/model/reminder/ReminderListComparator.java @@ -0,0 +1,13 @@ +package seedu.address.model.reminder; + +import java.util.Comparator; + +/** + * Comparator to allow sorting by oldest reminders + */ +public class ReminderListComparator implements Comparator { + @Override + public int compare(Reminder o1, Reminder o2) { + return o1.getReminderDateTime().compareTo(o2.getReminderDateTime()); + } +} diff --git a/src/main/java/seedu/address/model/stats/Statistic.java b/src/main/java/seedu/address/model/stats/Statistic.java new file mode 100644 index 00000000000..7fd72395c2a --- /dev/null +++ b/src/main/java/seedu/address/model/stats/Statistic.java @@ -0,0 +1,7 @@ +package seedu.address.model.stats; + +/** + * Represents a Statistic in the statistic window + */ +public interface Statistic { +} diff --git a/src/main/java/seedu/address/model/stats/StatisticItemList.java b/src/main/java/seedu/address/model/stats/StatisticItemList.java new file mode 100644 index 00000000000..b38518a3f07 --- /dev/null +++ b/src/main/java/seedu/address/model/stats/StatisticItemList.java @@ -0,0 +1,45 @@ +package seedu.address.model.stats; + +import java.util.ArrayList; + +/** + * Represents a list of Statistics to be displayed. + */ +public class StatisticItemList { + private ArrayList statsList; + + /** + * Constructor to create a StatisticItemList object. + */ + public StatisticItemList() { + this.statsList = new ArrayList<>(); + } + + /** + * Returns statistic list + */ + public ArrayList getStatsList() { + return this.statsList; + } + + /** + * Adds a statistic to statistic list + * + * @param toAdd + */ + public void addStats(Statistic toAdd) { + this.statsList.add(toAdd); + } + + /** + * Prints the statistics to be displayed + * + */ + public String printStats() { + String stats = ""; + for (Statistic stat: statsList) { + stats += stat.toString(); + } + return stats; + } +} diff --git a/src/main/java/seedu/address/model/stats/TotalCompleted.java b/src/main/java/seedu/address/model/stats/TotalCompleted.java new file mode 100644 index 00000000000..97ab73b8a60 --- /dev/null +++ b/src/main/java/seedu/address/model/stats/TotalCompleted.java @@ -0,0 +1,40 @@ +package seedu.address.model.stats; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +/** + * Represents Total number of Completed jobs in Statistics Window. + * Guarantees: details are present and not null. + */ +public class TotalCompleted implements Statistic { + public static final String MESSAGE_CONSTRAINTS = "TotalCompleted must be a non-negative integer"; + private static final String OUTPUT_MESSAGE = "Total number of Completed Jobs: "; + private int numCompleted; + + /** + * Constructor to create a TotalCompleted object. + * @param completed Total number of completed jobs in the job list. Cannot be negative and cannot be null. + */ + public TotalCompleted(int completed) { + requireNonNull(completed); + checkArgument(isValidCompleted(completed), MESSAGE_CONSTRAINTS); + this.numCompleted = completed; + } + + public double getCompleted() { + return numCompleted; + } + + /** + * Returns true if a given int is non-negative + */ + public static boolean isValidCompleted(int test) { + return test >= 0; + } + + @Override + public String toString() { + return OUTPUT_MESSAGE + numCompleted + "\n"; + } +} diff --git a/src/main/java/seedu/address/model/stats/TotalEarnings.java b/src/main/java/seedu/address/model/stats/TotalEarnings.java new file mode 100644 index 00000000000..5c566eb9d45 --- /dev/null +++ b/src/main/java/seedu/address/model/stats/TotalEarnings.java @@ -0,0 +1,41 @@ +package seedu.address.model.stats; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +/** + * Represents TotalEarnings in Statistics Window. + * Guarantees: details are present and not null. + */ +public class TotalEarnings implements Statistic { + public static final String MESSAGE_CONSTRAINTS = "TotalEarnings must be a non-negative number"; + private static final String OUTPUT_MESSAGE = "Total Earnings: $"; + private double earnings; + + /** + * Constructor to create a TotalEarnings object. + * @param earnings Total earnings from all jobs in the joblist. Cannot be negative and cannot be null. + */ + public TotalEarnings(double earnings) { + requireNonNull(earnings); + checkArgument(isValidEarnings(earnings), MESSAGE_CONSTRAINTS); + this.earnings = earnings; + } + + public double getEarnings() { + return earnings; + } + + /** + * Returns true if a given int is non-negative + */ + public static boolean isValidEarnings(double test) { + return test >= 0; + } + + @Override + public String toString() { + return OUTPUT_MESSAGE + earnings + "\n"; + } + +} diff --git a/src/main/java/seedu/address/model/stats/TotalJobs.java b/src/main/java/seedu/address/model/stats/TotalJobs.java new file mode 100644 index 00000000000..47fe14f2100 --- /dev/null +++ b/src/main/java/seedu/address/model/stats/TotalJobs.java @@ -0,0 +1,41 @@ +package seedu.address.model.stats; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +/** + * Represents TotalJobs in Statistics Window. + * Guarantees: details are present and not null. + */ +public class TotalJobs implements Statistic { + + public static final String MESSAGE_CONSTRAINTS = "TotalJobs must be a non-negative integer"; + private static final String OUTPUT_MESSAGE = "Total number of jobs: "; + private int numJobs; + + /** + * Constructor to create a TotalJobs object. + * @param numJobs Number of jobs in job list. Cannot be negative and cannot be null. + */ + public TotalJobs(int numJobs) { + requireNonNull(numJobs); + checkArgument(isValidTotalJobs(numJobs), MESSAGE_CONSTRAINTS); + this.numJobs = numJobs; + } + + public int getNumJobs() { + return numJobs; + } + + /** + * Returns true if a given int is non-negative + */ + public static boolean isValidTotalJobs(int test) { + return test >= 0; + } + + @Override + public String toString() { + return OUTPUT_MESSAGE + Integer.toString(numJobs) + "\n"; + } +} diff --git a/src/main/java/seedu/address/model/stats/TotalPending.java b/src/main/java/seedu/address/model/stats/TotalPending.java new file mode 100644 index 00000000000..438b64080b9 --- /dev/null +++ b/src/main/java/seedu/address/model/stats/TotalPending.java @@ -0,0 +1,40 @@ +package seedu.address.model.stats; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +/** + * Represents Total number of Pending jobs in Statistics Window. + * Guarantees: details are present and not null. + */ +public class TotalPending implements Statistic { + public static final String MESSAGE_CONSTRAINTS = "TotalPending must be a non-negative integer"; + private static final String OUTPUT_MESSAGE = "Total number of Pending Jobs: "; + private int numPending; + + /** + * Constructor to create a TotalPending object. + * @param pending Total number of pending jobs in the job list. Cannot be negative and cannot be null. + */ + public TotalPending(int pending) { + requireNonNull(pending); + checkArgument(isValidPending(pending), MESSAGE_CONSTRAINTS); + this.numPending = pending; + } + + public int getPending() { + return numPending; + } + + /** + * Returns true if a given int is non-negative + */ + public static boolean isValidPending(int test) { + return test >= 0; + } + + @Override + public String toString() { + return OUTPUT_MESSAGE + numPending + "\n"; + } +} diff --git a/src/main/java/seedu/address/model/stats/WeeklyStats.java b/src/main/java/seedu/address/model/stats/WeeklyStats.java new file mode 100644 index 00000000000..d23aeea06e3 --- /dev/null +++ b/src/main/java/seedu/address/model/stats/WeeklyStats.java @@ -0,0 +1,37 @@ +package seedu.address.model.stats; + +import static java.util.Objects.requireNonNull; + +import java.time.DayOfWeek; +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.List; + +/** + * Represents statistics to be displayed for a given week. + */ +public class WeeklyStats { + private List dates; + + /** + * Constructor of the WeeklyStats object. + * @param date Week which date is in. Cannot be negative and cannot be null. + */ + public WeeklyStats(LocalDate date) { + requireNonNull(date); + this.dates = getWeekDates(date); + } + + public List getWeekDates(LocalDate date) { + List dates = new ArrayList<>(); + LocalDate startOfWeek = date.with(DayOfWeek.MONDAY); + for (int i = 0; i < 7; i++) { + dates.add(startOfWeek.plusDays(i)); + } + return dates; + } + + public List getDates() { + return this.dates; + } +} diff --git a/src/main/java/seedu/address/model/util/SampleDataUtil.java b/src/main/java/seedu/address/model/util/SampleDataUtil.java index 1806da4facf..ec1dcad376d 100644 --- a/src/main/java/seedu/address/model/util/SampleDataUtil.java +++ b/src/main/java/seedu/address/model/util/SampleDataUtil.java @@ -5,7 +5,10 @@ import java.util.stream.Collectors; import seedu.address.model.AddressBook; +import seedu.address.model.DeliveryJobSystem; import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.model.ReadOnlyDeliveryJobSystem; +import seedu.address.model.jobs.DeliveryJob; import seedu.address.model.person.Address; import seedu.address.model.person.Email; import seedu.address.model.person.Name; @@ -19,27 +22,68 @@ public class SampleDataUtil { public static Person[] getSamplePersons() { return new Person[] { - new Person(new Name("Alex Yeoh"), new Phone("87438807"), new Email("alexyeoh@example.com"), - new Address("Blk 30 Geylang Street 29, #06-40"), - getTagSet("friends")), - new Person(new Name("Bernice Yu"), new Phone("99272758"), new Email("berniceyu@example.com"), - new Address("Blk 30 Lorong 3 Serangoon Gardens, #07-18"), - getTagSet("colleagues", "friends")), - new Person(new Name("Charlotte Oliveiro"), new Phone("93210283"), new Email("charlotte@example.com"), - new Address("Blk 11 Ang Mo Kio Street 74, #11-04"), - getTagSet("neighbours")), - new Person(new Name("David Li"), new Phone("91031282"), new Email("lidavid@example.com"), - new Address("Blk 436 Serangoon Gardens Street 26, #16-43"), - getTagSet("family")), - new Person(new Name("Irfan Ibrahim"), new Phone("92492021"), new Email("irfan@example.com"), - new Address("Blk 47 Tampines Street 20, #17-35"), - getTagSet("classmates")), - new Person(new Name("Roy Balakrishnan"), new Phone("92624417"), new Email("royb@example.com"), - new Address("Blk 45 Aljunied Street 85, #11-31"), - getTagSet("colleagues")) + new Person("ALESAM", new Name("Alex Yeoh"), new Phone("87438807"), new Email("alexyeoh@example.com"), + new Address("Blk 30 Geylang Street 29, #06-40"), + getTagSet("Regular")), + new Person("BERSAM", new Name("Bernice Yu"), new Phone("99272758"), new Email("berniceyu@example.com"), + new Address("Blk 30 Lorong 3 Serangoon Gardens, #07-18"), + getTagSet("Business", "Regular")), + new Person("CHASAM", new Name("Charlotte Oliveiro"), new Phone("93210283"), + new Email("charlotte@example.com"), + new Address("Blk 11 Ang Mo Kio Street 74, #11-04"), + getTagSet("New")), + new Person("DAVSAM", new Name("David Li"), new Phone("91031282"), new Email("lidavid@example.com"), + new Address("Blk 436 Serangoon Gardens Street 26, #16-43"), + getTagSet("Individual")), + new Person("IRFSAM", new Name("Irfan Ibrahim"), new Phone("92492021"), new Email("irfan@example.com"), + new Address("Blk 47 Tampines Street 20, #17-35"), + getTagSet("Regular")), + new Person("ROYSAM", new Name("Roy Balakrishnan"), new Phone("92624417"), new Email("royb@example.com"), + new Address("Blk 45 Aljunied Street 85, #11-31"), + getTagSet("Business")) }; } + public static DeliveryJob[] getSampleDeliveryJob() { + Person[] persons = getSamplePersons(); + return new DeliveryJob[] { + new DeliveryJob( + persons[0].getPersonId(), + persons[1].getPersonId(), + "2023-03-01", + "1", + "0.0", ""), + new DeliveryJob( + persons[1].getPersonId(), + persons[2].getPersonId(), + "2023-03-01", + "2", + "1.0", ""), + new DeliveryJob( + persons[2].getPersonId(), + persons[3].getPersonId(), + "2023-03-01", + "2", + "2.0", ""), + new DeliveryJob( + persons[3].getPersonId(), + persons[4].getPersonId(), + "2023-03-01", + "3", + "3.0", ""), + new DeliveryJob( + persons[4].getPersonId(), + persons[5].getPersonId(), + "2023-03-02", + "5", + "4.0", ""), + new DeliveryJob( + persons[5].getPersonId(), + persons[0].getPersonId(), + "5.0", "") + }; + } + public static ReadOnlyAddressBook getSampleAddressBook() { AddressBook sampleAb = new AddressBook(); for (Person samplePerson : getSamplePersons()) { @@ -48,6 +92,14 @@ public static ReadOnlyAddressBook getSampleAddressBook() { return sampleAb; } + public static ReadOnlyDeliveryJobSystem getSampleDeliveryJobSystem() { + DeliveryJobSystem sampleDS = new DeliveryJobSystem(); + for (DeliveryJob sampleJob : getSampleDeliveryJob()) { + sampleDS.addDeliveryJob(sampleJob); + } + return sampleDS; + } + /** * Returns a tag set containing the list of strings given. */ diff --git a/src/main/java/seedu/address/storage/AddressBookStorage.java b/src/main/java/seedu/address/storage/AddressBookStorage.java index 4599182b3f9..f98d1376317 100644 --- a/src/main/java/seedu/address/storage/AddressBookStorage.java +++ b/src/main/java/seedu/address/storage/AddressBookStorage.java @@ -19,9 +19,10 @@ public interface AddressBookStorage { /** * Returns AddressBook data as a {@link ReadOnlyAddressBook}. - * Returns {@code Optional.empty()} if storage file is not found. + * Returns {@code Optional.empty()} if storage file is not found. + * * @throws DataConversionException if the data in storage is not in the expected format. - * @throws IOException if there was any problem when reading from the storage. + * @throws IOException if there was any problem when reading from the storage. */ Optional readAddressBook() throws DataConversionException, IOException; @@ -32,6 +33,7 @@ public interface AddressBookStorage { /** * Saves the given {@link ReadOnlyAddressBook} to the storage. + * * @param addressBook cannot be null. * @throws IOException if there was any problem writing to the file. */ diff --git a/src/main/java/seedu/address/storage/DeliveryJobSystemStorage.java b/src/main/java/seedu/address/storage/DeliveryJobSystemStorage.java new file mode 100644 index 00000000000..bdb2014f509 --- /dev/null +++ b/src/main/java/seedu/address/storage/DeliveryJobSystemStorage.java @@ -0,0 +1,25 @@ +package seedu.address.storage; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.Optional; + +import seedu.address.commons.exceptions.DataConversionException; +import seedu.address.model.ReadOnlyDeliveryJobSystem; + +/** + * DeliveryJobSystemStorage + */ +public interface DeliveryJobSystemStorage { + + Path getDeliveryJobFilePath(); + + Optional readDeliveryJobSystem() throws DataConversionException, IOException; + + Optional readDeliveryJobSystem(Path filePath) + throws DataConversionException, IOException; + + void saveDeliveryJobSystem(ReadOnlyDeliveryJobSystem deliveryJobSystem) throws IOException; + + void saveDeliveryJobSystem(ReadOnlyDeliveryJobSystem deliveryJobSystem, Path filePath) throws IOException; +} diff --git a/src/main/java/seedu/address/storage/Storage.java b/src/main/java/seedu/address/storage/Storage.java index beda8bd9f11..713b655c195 100644 --- a/src/main/java/seedu/address/storage/Storage.java +++ b/src/main/java/seedu/address/storage/Storage.java @@ -6,13 +6,14 @@ import seedu.address.commons.exceptions.DataConversionException; import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.model.ReadOnlyDeliveryJobSystem; import seedu.address.model.ReadOnlyUserPrefs; import seedu.address.model.UserPrefs; /** * API of the Storage component */ -public interface Storage extends AddressBookStorage, UserPrefsStorage { +public interface Storage extends AddressBookStorage, UserPrefsStorage, DeliveryJobSystemStorage { @Override Optional readUserPrefs() throws DataConversionException, IOException; @@ -29,4 +30,13 @@ public interface Storage extends AddressBookStorage, UserPrefsStorage { @Override void saveAddressBook(ReadOnlyAddressBook addressBook) throws IOException; + @Override + Path getDeliveryJobFilePath(); + + @Override + Optional readDeliveryJobSystem() throws DataConversionException, IOException; + + @Override + void saveDeliveryJobSystem(ReadOnlyDeliveryJobSystem deliveryJob) throws IOException; + } diff --git a/src/main/java/seedu/address/storage/StorageManager.java b/src/main/java/seedu/address/storage/StorageManager.java index 6cfa0162164..c2a5b5774e6 100644 --- a/src/main/java/seedu/address/storage/StorageManager.java +++ b/src/main/java/seedu/address/storage/StorageManager.java @@ -8,6 +8,7 @@ import seedu.address.commons.core.LogsCenter; import seedu.address.commons.exceptions.DataConversionException; import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.model.ReadOnlyDeliveryJobSystem; import seedu.address.model.ReadOnlyUserPrefs; import seedu.address.model.UserPrefs; @@ -18,13 +19,17 @@ public class StorageManager implements Storage { private static final Logger logger = LogsCenter.getLogger(StorageManager.class); private AddressBookStorage addressBookStorage; + private DeliveryJobSystemStorage deliveryJobSystemStorage; private UserPrefsStorage userPrefsStorage; /** - * Creates a {@code StorageManager} with the given {@code AddressBookStorage} and {@code UserPrefStorage}. + * Creates a {@code StorageManager} with the given {@code AddressBookStorage} + * and {@code UserPrefStorage}. */ - public StorageManager(AddressBookStorage addressBookStorage, UserPrefsStorage userPrefsStorage) { + public StorageManager(AddressBookStorage addressBookStorage, DeliveryJobSystemStorage deliveryJobSystemStorage, + UserPrefsStorage userPrefsStorage) { this.addressBookStorage = addressBookStorage; + this.deliveryJobSystemStorage = deliveryJobSystemStorage; this.userPrefsStorage = userPrefsStorage; } @@ -45,7 +50,6 @@ public void saveUserPrefs(ReadOnlyUserPrefs userPrefs) throws IOException { userPrefsStorage.saveUserPrefs(userPrefs); } - // ================ AddressBook methods ============================== @Override @@ -60,7 +64,7 @@ public Optional readAddressBook() throws DataConversionExce @Override public Optional readAddressBook(Path filePath) throws DataConversionException, IOException { - logger.fine("Attempting to read data from file: " + filePath); + logger.fine("[AB] Attempting to read data from file: " + filePath); return addressBookStorage.readAddressBook(filePath); } @@ -71,8 +75,38 @@ public void saveAddressBook(ReadOnlyAddressBook addressBook) throws IOException @Override public void saveAddressBook(ReadOnlyAddressBook addressBook, Path filePath) throws IOException { - logger.fine("Attempting to write to data file: " + filePath); + logger.fine("[AB] Attempting to write to data file: " + filePath); addressBookStorage.saveAddressBook(addressBook, filePath); } + // ================ Delivery Job system methods ============================== + + @Override + public Path getDeliveryJobFilePath() { + return deliveryJobSystemStorage.getDeliveryJobFilePath(); + } + + @Override + public Optional readDeliveryJobSystem() throws DataConversionException, IOException { + return readDeliveryJobSystem(deliveryJobSystemStorage.getDeliveryJobFilePath()); + } + + @Override + public Optional readDeliveryJobSystem(Path filePath) + throws DataConversionException, IOException { + logger.fine("[DJ] Attempting to read data from file: " + filePath); + return deliveryJobSystemStorage.readDeliveryJobSystem(filePath); + } + + @Override + public void saveDeliveryJobSystem(ReadOnlyDeliveryJobSystem deliveryJobSystem) throws IOException { + saveDeliveryJobSystem(deliveryJobSystem, deliveryJobSystemStorage.getDeliveryJobFilePath()); + } + + @Override + public void saveDeliveryJobSystem(ReadOnlyDeliveryJobSystem deliveryJobSystem, Path filePath) throws IOException { + logger.fine("[DJ] Attempting to write to data file: " + filePath); + deliveryJobSystemStorage.saveDeliveryJobSystem(deliveryJobSystem, filePath); + } + } diff --git a/src/main/java/seedu/address/storage/UserPrefsStorage.java b/src/main/java/seedu/address/storage/UserPrefsStorage.java index 29eef178dbc..ad6a5ca95b1 100644 --- a/src/main/java/seedu/address/storage/UserPrefsStorage.java +++ b/src/main/java/seedu/address/storage/UserPrefsStorage.java @@ -20,14 +20,16 @@ public interface UserPrefsStorage { /** * Returns UserPrefs data from storage. - * Returns {@code Optional.empty()} if storage file is not found. + * Returns {@code Optional.empty()} if storage file is not found. + * * @throws DataConversionException if the data in storage is not in the expected format. - * @throws IOException if there was any problem when reading from the storage. + * @throws IOException if there was any problem when reading from the storage. */ Optional readUserPrefs() throws DataConversionException, IOException; /** * Saves the given {@link seedu.address.model.ReadOnlyUserPrefs} to the storage. + * * @param userPrefs cannot be null. * @throws IOException if there was any problem writing to the file. */ diff --git a/src/main/java/seedu/address/storage/json/model/JsonAdapted.java b/src/main/java/seedu/address/storage/json/model/JsonAdapted.java new file mode 100644 index 00000000000..857217a60d8 --- /dev/null +++ b/src/main/java/seedu/address/storage/json/model/JsonAdapted.java @@ -0,0 +1,7 @@ +package seedu.address.storage.json.model; + +import seedu.address.commons.exceptions.IllegalValueException; + +abstract class JsonAdapted { + public abstract T toModelType() throws IllegalValueException; +} diff --git a/src/main/java/seedu/address/storage/json/model/JsonAdaptedDeliveryJob.java b/src/main/java/seedu/address/storage/json/model/JsonAdaptedDeliveryJob.java new file mode 100644 index 00000000000..333f2d9704d --- /dev/null +++ b/src/main/java/seedu/address/storage/json/model/JsonAdaptedDeliveryJob.java @@ -0,0 +1,86 @@ +package seedu.address.storage.json.model; + +import java.util.Optional; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.jobs.DeliveryDate; +import seedu.address.model.jobs.DeliveryJob; +import seedu.address.model.jobs.DeliverySlot; +import seedu.address.model.jobs.Earning; + +/** + * JsonAdaptedDeliveryJob + */ +public class JsonAdaptedDeliveryJob extends JsonAdapted { + + private final String jobId; + private final String recipient; + private final String sender; + private final String deliveryDate; + private final String deliverySlot; + private final String earning; + private final boolean isDelivered; + private final String description; + + /** + * JsonAdaptedDeliveryJob + * + * @param jobId + * @param recipient + * @param sender + * @param deliverySlot + * @param earning + */ + public JsonAdaptedDeliveryJob( + @JsonProperty("jobId") String jobId, + @JsonProperty("recipientId") String recipient, + @JsonProperty("senderId") String sender, + @JsonProperty("date") String deliveryDate, + @JsonProperty("slot") String deliverySlot, + @JsonProperty("earning") String earning, + @JsonProperty("isDelivered") boolean isDelivered, + @JsonProperty("description") String description) { + this.jobId = jobId; + this.recipient = recipient; + this.sender = sender; + this.deliveryDate = deliveryDate; + this.deliverySlot = deliverySlot; + this.earning = earning; + this.isDelivered = isDelivered; + this.description = description; + } + + /** + * JsonAdaptedDeliveryJob. + * + * @param source + */ + public JsonAdaptedDeliveryJob(DeliveryJob source) { + this.jobId = source.getJobId(); + this.recipient = source.getRecipientId(); + this.sender = source.getSenderId(); + this.deliveryDate = source.getDeliveryDate().orElseGet(() -> DeliveryDate.placeholder()).date; + this.deliverySlot = source.getDeliverySlot().orElseGet(() -> DeliverySlot.placeholder()).value; + this.earning = source.getEarning().orElseGet(() -> Earning.placeholder()).value; + this.isDelivered = source.getDeliveredStatus(); + this.description = source.getDescription(); + } + + @Override + public DeliveryJob toModelType() throws IllegalValueException { + Optional deliveryDateOptional = Optional.empty(); + Optional deliverySlotOptional = Optional.empty(); + if (!deliveryDate.equals(DeliveryDate.placeholder().date)) { + deliveryDateOptional = Optional.of(new DeliveryDate(deliveryDate)); + } + if (!deliverySlot.equals(DeliverySlot.placeholder().value)) { + deliverySlotOptional = Optional.of(new DeliverySlot(deliverySlot)); + } + return new DeliveryJob(jobId, recipient, sender, deliveryDateOptional, + deliverySlotOptional, + Optional.of(new Earning(earning)), isDelivered, description); + } + +} diff --git a/src/main/java/seedu/address/storage/JsonAdaptedPerson.java b/src/main/java/seedu/address/storage/json/model/JsonAdaptedPerson.java similarity index 82% rename from src/main/java/seedu/address/storage/JsonAdaptedPerson.java rename to src/main/java/seedu/address/storage/json/model/JsonAdaptedPerson.java index a6321cec2ea..9e98dd7450c 100644 --- a/src/main/java/seedu/address/storage/JsonAdaptedPerson.java +++ b/src/main/java/seedu/address/storage/json/model/JsonAdaptedPerson.java @@ -1,4 +1,4 @@ -package seedu.address.storage; +package seedu.address.storage.json.model; import java.util.ArrayList; import java.util.HashSet; @@ -20,10 +20,10 @@ /** * Jackson-friendly version of {@link Person}. */ -class JsonAdaptedPerson { +public class JsonAdaptedPerson extends JsonAdapted { public static final String MISSING_FIELD_MESSAGE_FORMAT = "Person's %s field is missing!"; - + private final String personId; private final String name; private final String phone; private final String email; @@ -34,9 +34,12 @@ class JsonAdaptedPerson { * Constructs a {@code JsonAdaptedPerson} with the given person details. */ @JsonCreator - public JsonAdaptedPerson(@JsonProperty("name") String name, @JsonProperty("phone") String phone, + public JsonAdaptedPerson( + @JsonProperty("personId") String personId, + @JsonProperty("name") String name, @JsonProperty("phone") String phone, @JsonProperty("email") String email, @JsonProperty("address") String address, @JsonProperty("tagged") List tagged) { + this.personId = personId; this.name = name; this.phone = phone; this.email = email; @@ -50,6 +53,7 @@ public JsonAdaptedPerson(@JsonProperty("name") String name, @JsonProperty("phone * Converts a given {@code Person} into this class for Jackson use. */ public JsonAdaptedPerson(Person source) { + personId = source.getPersonId(); name = source.getName().fullName; phone = source.getPhone().value; email = source.getEmail().value; @@ -60,9 +64,11 @@ public JsonAdaptedPerson(Person source) { } /** - * Converts this Jackson-friendly adapted person object into the model's {@code Person} object. + * Converts this Jackson-friendly adapted person object into the model's + * {@code Person} object. * - * @throws IllegalValueException if there were any data constraints violated in the adapted person. + * @throws IllegalValueException if there were any data constraints violated in + * the adapted person. */ public Person toModelType() throws IllegalValueException { final List personTags = new ArrayList<>(); @@ -70,6 +76,10 @@ public Person toModelType() throws IllegalValueException { personTags.add(tag.toModelType()); } + if (personId == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, "PersonId")); + } + if (name == null) { throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Name.class.getSimpleName())); } @@ -103,7 +113,7 @@ public Person toModelType() throws IllegalValueException { final Address modelAddress = new Address(address); final Set modelTags = new HashSet<>(personTags); - return new Person(modelName, modelPhone, modelEmail, modelAddress, modelTags); + return new Person(personId, modelName, modelPhone, modelEmail, modelAddress, modelTags); } } diff --git a/src/main/java/seedu/address/storage/json/model/JsonAdaptedReminder.java b/src/main/java/seedu/address/storage/json/model/JsonAdaptedReminder.java new file mode 100644 index 00000000000..04d5875c602 --- /dev/null +++ b/src/main/java/seedu/address/storage/json/model/JsonAdaptedReminder.java @@ -0,0 +1,42 @@ +package seedu.address.storage.json.model; + +import static seedu.address.commons.util.DateTimeUtil.toDateTime; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +import seedu.address.model.reminder.Reminder; + +/** + * Jackson-friendly version of {@link Reminder}. + */ +public class JsonAdaptedReminder { + public static final String MISSING_FIELD_MESSAGE_FORMAT = "Reminder's %s field is missing!"; + + private final String description; + private final String time; + + /** + * Constructs a {@code JsonAdaptedReminder} with the given reminder details. + */ + @JsonCreator + public JsonAdaptedReminder(@JsonProperty("description") String description, @JsonProperty("time") String time) { + this.description = description; + this.time = time; + } + + /** + * Converts a given {@code Reminder} into this class for Jackson use. + */ + public JsonAdaptedReminder(Reminder source) { + description = source.getDescription(); + time = source.reminderDateTimeToString(); + } + + /** + * Converts this Jackson-friendly adapted reminder object into the model's {@code Reminder} object. + */ + public Reminder toModelType() { + return new Reminder(description, toDateTime(time)); + } +} diff --git a/src/main/java/seedu/address/storage/JsonAdaptedTag.java b/src/main/java/seedu/address/storage/json/model/JsonAdaptedTag.java similarity index 92% rename from src/main/java/seedu/address/storage/JsonAdaptedTag.java rename to src/main/java/seedu/address/storage/json/model/JsonAdaptedTag.java index 0df22bdb754..804f2d50c98 100644 --- a/src/main/java/seedu/address/storage/JsonAdaptedTag.java +++ b/src/main/java/seedu/address/storage/json/model/JsonAdaptedTag.java @@ -1,4 +1,4 @@ -package seedu.address.storage; +package seedu.address.storage.json.model; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonValue; @@ -9,7 +9,7 @@ /** * Jackson-friendly version of {@link Tag}. */ -class JsonAdaptedTag { +public class JsonAdaptedTag extends JsonAdapted { private final String tagName; diff --git a/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java b/src/main/java/seedu/address/storage/json/serializable/JsonSerializableAddressBook.java similarity index 69% rename from src/main/java/seedu/address/storage/JsonSerializableAddressBook.java rename to src/main/java/seedu/address/storage/json/serializable/JsonSerializableAddressBook.java index 5efd834091d..42d582e06dd 100644 --- a/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java +++ b/src/main/java/seedu/address/storage/json/serializable/JsonSerializableAddressBook.java @@ -1,4 +1,4 @@ -package seedu.address.storage; +package seedu.address.storage.json.serializable; import java.util.ArrayList; import java.util.List; @@ -12,23 +12,30 @@ import seedu.address.model.AddressBook; import seedu.address.model.ReadOnlyAddressBook; import seedu.address.model.person.Person; +import seedu.address.model.reminder.Reminder; +import seedu.address.storage.json.model.JsonAdaptedPerson; +import seedu.address.storage.json.model.JsonAdaptedReminder; /** * An Immutable AddressBook that is serializable to JSON format. */ @JsonRootName(value = "addressbook") -class JsonSerializableAddressBook { +public class JsonSerializableAddressBook { public static final String MESSAGE_DUPLICATE_PERSON = "Persons list contains duplicate person(s)."; private final List persons = new ArrayList<>(); + private final List reminderList = new ArrayList<>(); + /** - * Constructs a {@code JsonSerializableAddressBook} with the given persons. + * Constructs a {@code JsonSerializableAddressBook} with the given persons and reminderList. */ @JsonCreator - public JsonSerializableAddressBook(@JsonProperty("persons") List persons) { + public JsonSerializableAddressBook(@JsonProperty("persons") List persons, + @JsonProperty("reminderList") List reminderList) { this.persons.addAll(persons); + this.reminderList.addAll(reminderList); } /** @@ -38,6 +45,8 @@ public JsonSerializableAddressBook(@JsonProperty("persons") List jobs = new ArrayList<>(); + + /** + * Constructs a {@code JsonSerializableDeliveryJobSystem} with the given persons and reminderList. + */ + @JsonCreator + public JsonSerializableDeliveryJobSystem(@JsonProperty("jobs") List jobs) { + this.jobs.addAll(jobs); + } + + /** + * Converts a given {@code ReadOnlyDeliveryJobSystem} into this class for Jackson use. + * + * @param source future changes to this will not affect the created {@code JsonSerializableDeliveryJobSystem}. + */ + public JsonSerializableDeliveryJobSystem(ReadOnlyDeliveryJobSystem source) { + jobs.addAll(source.getDeliveryJobList().stream().map(JsonAdaptedDeliveryJob::new).collect(Collectors.toList())); + } + + /** + * ToModelType + * + * @return DeliveryJobSystem + * @throws IllegalValueException + */ + public DeliveryJobSystem toModelType() throws IllegalValueException { + DeliveryJobSystem deliveryJobSystem = new DeliveryJobSystem(); + for (JsonAdaptedDeliveryJob jsonAdaptedDeliveryJob : jobs) { + DeliveryJob job = jsonAdaptedDeliveryJob.toModelType(); + if (deliveryJobSystem.hasDeliveryJob(job)) { + throw new IllegalValueException(MESSAGE_DUPLICATE_JOB); + } + deliveryJobSystem.addDeliveryJob(job); + } + return deliveryJobSystem; + } + +} diff --git a/src/main/java/seedu/address/storage/JsonAddressBookStorage.java b/src/main/java/seedu/address/storage/json/storage/JsonAddressBookStorage.java similarity index 93% rename from src/main/java/seedu/address/storage/JsonAddressBookStorage.java rename to src/main/java/seedu/address/storage/json/storage/JsonAddressBookStorage.java index dfab9daaa0d..799b87ff3fa 100644 --- a/src/main/java/seedu/address/storage/JsonAddressBookStorage.java +++ b/src/main/java/seedu/address/storage/json/storage/JsonAddressBookStorage.java @@ -1,4 +1,4 @@ -package seedu.address.storage; +package seedu.address.storage.json.storage; import static java.util.Objects.requireNonNull; @@ -13,6 +13,8 @@ import seedu.address.commons.util.FileUtil; import seedu.address.commons.util.JsonUtil; import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.storage.AddressBookStorage; +import seedu.address.storage.json.serializable.JsonSerializableAddressBook; /** * A class to access AddressBook data stored as a json file on the hard disk. diff --git a/src/main/java/seedu/address/storage/json/storage/JsonDeliveryJobSystemStorage.java b/src/main/java/seedu/address/storage/json/storage/JsonDeliveryJobSystemStorage.java new file mode 100644 index 00000000000..83be38b1cc1 --- /dev/null +++ b/src/main/java/seedu/address/storage/json/storage/JsonDeliveryJobSystemStorage.java @@ -0,0 +1,73 @@ +package seedu.address.storage.json.storage; + +import static java.util.Objects.requireNonNull; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.Optional; +import java.util.logging.Logger; + +import seedu.address.commons.core.LogsCenter; +import seedu.address.commons.exceptions.DataConversionException; +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.commons.util.FileUtil; +import seedu.address.commons.util.JsonUtil; +import seedu.address.model.ReadOnlyDeliveryJobSystem; +import seedu.address.storage.DeliveryJobSystemStorage; +import seedu.address.storage.json.serializable.JsonSerializableDeliveryJobSystem; + +/** + * JsonDeliveryJobSystemStorage + */ +public class JsonDeliveryJobSystemStorage implements DeliveryJobSystemStorage { + + private static final Logger logger = LogsCenter.getLogger(JsonAddressBookStorage.class); + + private Path filePath; + + public JsonDeliveryJobSystemStorage(Path filePath) { + this.filePath = filePath; + } + + public Path getDeliveryJobFilePath() { + return filePath; + } + + @Override + public Optional readDeliveryJobSystem() throws DataConversionException, IOException { + return readDeliveryJobSystem(filePath); + } + + @Override + public Optional readDeliveryJobSystem(Path filePath) + throws DataConversionException, IOException { + requireNonNull(filePath); + + Optional jsonDeliveryJobSystem = JsonUtil.readJsonFile( + filePath, JsonSerializableDeliveryJobSystem.class); + if (!jsonDeliveryJobSystem.isPresent()) { + return Optional.empty(); + } + + try { + return Optional.of(jsonDeliveryJobSystem.get().toModelType()); + } catch (IllegalValueException ive) { + logger.info("Illegal values found in " + filePath + ": " + ive.getMessage()); + throw new DataConversionException(ive); + } + } + + @Override + public void saveDeliveryJobSystem(ReadOnlyDeliveryJobSystem deliveryJobSystem) throws IOException { + saveDeliveryJobSystem(deliveryJobSystem, filePath); + } + + @Override + public void saveDeliveryJobSystem(ReadOnlyDeliveryJobSystem deliveryJobSystem, Path filePath) throws IOException { + requireNonNull(deliveryJobSystem); + requireNonNull(filePath); + + FileUtil.createIfMissing(filePath); + JsonUtil.saveJsonFile(new JsonSerializableDeliveryJobSystem(deliveryJobSystem), filePath); + } +} diff --git a/src/main/java/seedu/address/storage/JsonUserPrefsStorage.java b/src/main/java/seedu/address/storage/json/storage/JsonUserPrefsStorage.java similarity index 92% rename from src/main/java/seedu/address/storage/JsonUserPrefsStorage.java rename to src/main/java/seedu/address/storage/json/storage/JsonUserPrefsStorage.java index bc2bbad84aa..96c8b1a0dc3 100644 --- a/src/main/java/seedu/address/storage/JsonUserPrefsStorage.java +++ b/src/main/java/seedu/address/storage/json/storage/JsonUserPrefsStorage.java @@ -1,4 +1,4 @@ -package seedu.address.storage; +package seedu.address.storage.json.storage; import java.io.IOException; import java.nio.file.Path; @@ -8,6 +8,7 @@ import seedu.address.commons.util.JsonUtil; import seedu.address.model.ReadOnlyUserPrefs; import seedu.address.model.UserPrefs; +import seedu.address.storage.UserPrefsStorage; /** * A class to access UserPrefs stored in the hard disk as a json file @@ -32,6 +33,7 @@ public Optional readUserPrefs() throws DataConversionException { /** * Similar to {@link #readUserPrefs()} + * * @param prefsFilePath location of the data. Cannot be null. * @throws DataConversionException if the file format is not as expected. */ diff --git a/src/main/java/seedu/address/ui/HelpWindow.java b/src/main/java/seedu/address/ui/HelpWindow.java index 3f16b2fcf26..9f5edcc32d1 100644 --- a/src/main/java/seedu/address/ui/HelpWindow.java +++ b/src/main/java/seedu/address/ui/HelpWindow.java @@ -10,12 +10,14 @@ import javafx.stage.Stage; import seedu.address.commons.core.LogsCenter; + + /** * Controller for a help page */ public class HelpWindow extends UiPart { - public static final String USERGUIDE_URL = "https://se-education.org/addressbook-level3/UserGuide.html"; + public static final String USERGUIDE_URL = "https://ay2223s2-cs2103-f11-2.github.io/tp/UserGuide.html"; public static final String HELP_MESSAGE = "Refer to the user guide: " + USERGUIDE_URL; private static final Logger logger = LogsCenter.getLogger(HelpWindow.class); diff --git a/src/main/java/seedu/address/ui/MainWindow.java b/src/main/java/seedu/address/ui/MainWindow.java index 9106c3aa6e5..f5566da6fc4 100644 --- a/src/main/java/seedu/address/ui/MainWindow.java +++ b/src/main/java/seedu/address/ui/MainWindow.java @@ -1,7 +1,13 @@ package seedu.address.ui; +import java.io.File; +import java.io.FileNotFoundException; +import java.util.function.BiConsumer; +import java.util.function.BiFunction; +import java.util.function.Consumer; import java.util.logging.Logger; +import javafx.collections.ObservableList; import javafx.event.ActionEvent; import javafx.fxml.FXML; import javafx.scene.control.MenuItem; @@ -9,13 +15,33 @@ import javafx.scene.input.KeyCombination; import javafx.scene.input.KeyEvent; import javafx.scene.layout.StackPane; +import javafx.stage.FileChooser; import javafx.stage.Stage; import seedu.address.commons.core.GuiSettings; import seedu.address.commons.core.LogsCenter; import seedu.address.logic.Logic; +import seedu.address.logic.commands.CommandGroup; import seedu.address.logic.commands.CommandResult; import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.logic.commands.jobs.CompleteDeliveryJobCommand; +import seedu.address.logic.commands.jobs.DeleteDeliveryJobCommand; +import seedu.address.logic.commands.jobs.ImportDeliveryJobCommand; import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.jobs.DeliveryJob; +import seedu.address.model.jobs.sorters.DeliveryFilterOption; +import seedu.address.model.jobs.sorters.DeliverySortOption; +import seedu.address.model.jobs.sorters.SortbyDate; +import seedu.address.model.jobs.sorters.SortbyDelivered; +import seedu.address.model.jobs.sorters.SortbyEarning; +import seedu.address.ui.jobs.AddDeliveryJobWindow; +import seedu.address.ui.jobs.DeliveryJobDetailPane; +import seedu.address.ui.jobs.DeliveryJobListPanel; +import seedu.address.ui.main.CommandBox; +import seedu.address.ui.main.ResultDisplay; +import seedu.address.ui.main.StatusBarFooter; +import seedu.address.ui.person.AddressBookDialog; +import seedu.address.ui.timetable.CompleteWindow; +import seedu.address.ui.timetable.UnscheduleWindow; /** * The Main Window. Provides the basic application layout containing @@ -31,25 +57,134 @@ public class MainWindow extends UiPart { private Logic logic; // Independent Ui parts residing in this Ui container - private PersonListPanel personListPanel; + private AddressBookDialog addressBookWindow; + private AddDeliveryJobWindow addDeliveryJobWindow; + private CompleteWindow completeWindow; + private DeliveryJobListPanel deliveryJobListPanel; private ResultDisplay resultDisplay; private HelpWindow helpWindow; + private TimetableWindow timetableWindow; + private ReminderListWindow reminderListWindow; + private StatisticsWindow statsWindow; + private UnscheduleWindow unscheduleWindow; @FXML private StackPane commandBoxPlaceholder; - + @FXML + private StackPane deliveryJobDetailPlaceholder; @FXML private MenuItem helpMenuItem; - @FXML - private StackPane personListPanelPlaceholder; - + private MenuItem timetableMenuItem; + @FXML + private MenuItem reminderListMenuItem; + @FXML + private MenuItem statsItem; + @FXML + private MenuItem addressBookMenuItem; + @FXML + private StackPane deliveryJobListPanelPlaceholder; + @FXML + private StackPane emptyDeliveryJobListPanelPlaceholder; @FXML private StackPane resultDisplayPlaceholder; - @FXML private StackPane statusbarPlaceholder; + private Consumer completeDeliveryJobHandler = (job) -> { + try { + logger.info("[completeDeliveryJobHandler] complete: " + job.getJobId()); + CommandResult commandResult = logic + .execute(new CompleteDeliveryJobCommand(job.getJobId(), !job.getDeliveredStatus())); + resultDisplay.setFeedbackToUser(commandResult.getFeedbackToUser()); + } catch (ParseException | CommandException e) { + logger.warning(e.getMessage()); + } catch (FileNotFoundException | IllegalArgumentException e) { + logger.warning(e.getMessage()); + } + }; + + private Consumer editDeliveryJobHandler = (job) -> { + logger.info("[editDeliveryJobHandler] edit: " + job.getJobId()); + if (addDeliveryJobWindow != null) { + addDeliveryJobWindow.hide(); + } + addDeliveryJobWindow = new AddDeliveryJobWindow(new Stage(), logic, job); + addDeliveryJobWindow.setResultHandler(commandResult -> { + logger.info("[editDeliveryJobHandler] edit complete: " + job.getJobId()); + resultDisplay.setFeedbackToUser(commandResult.getFeedbackToUser()); + }); + addDeliveryJobWindow.show(); + addDeliveryJobWindow.fillInnerParts(); + }; + + private Consumer deleteDeliveryJobHandler = job -> { + try { + logger.info("[deleteDeliveryJobHandler] delete: " + job.getJobId()); + CommandResult commandResult = logic.execute(new DeleteDeliveryJobCommand(job.getJobId())); + resultDisplay.setFeedbackToUser(commandResult.getFeedbackToUser()); + } catch (ParseException | CommandException | FileNotFoundException | IllegalArgumentException e) { + logger.warning(e.getMessage()); + } + }; + + private BiConsumer selectDeliveryJobHandler = (idx, job) -> { + logger.info("[selectDeliveryJobHandler] select: " + idx); + refreshDeliveryJobListView(); + + if (idx >= 0) { + DeliveryJobDetailPane detailPane = new DeliveryJobDetailPane(job); + detailPane.fillInnerParts(logic.getAddressBook()); + deliveryJobDetailPlaceholder.getChildren().add(detailPane.getRoot()); + detailPane.setCompleteHandler(completeDeliveryJobHandler); + detailPane.setEditHandler(editDeliveryJobHandler); + detailPane.setDeleteHandler(deleteDeliveryJobHandler); + return; + } + }; + + private BiFunction> sortDeliveryJobHandler = (by, asc) -> { + logger.info("[sortDeliveryJobHandler] sort: " + by); + String feedback = "List empty!"; + if (deliveryJobListPanel.size() > 0) { + switch (by) { + case COM: + logic.updateSortedDeliveryJobListByComparator(new SortbyDelivered(asc)); + feedback = "Sort by delivery status."; + break; + case EARN: + logic.updateSortedDeliveryJobListByComparator(new SortbyEarning(asc)); + feedback = "Sort by earning."; + break; + default: + logic.updateSortedDeliveryJobListByComparator(new SortbyDate(asc)); + feedback = "Sort by delivery schedule."; + break; + } + } + resultDisplay.setFeedbackToUser(feedback + (asc ? " (ASC)" : " (DESC)")); + return logic.getSortedDeliveryJobList(); + }; + + private Consumer filterDeliveryJobHandler = by -> { + logger.info("[filterDeliveryJobHandler] filter: " + by); + switch (by) { + case COM: + logic.updateFilteredDeliveryJobList(job -> job.getDeliveredStatus()); + resultDisplay.setFeedbackToUser("Filter completed delivery."); + break; + case PEN: + logic.updateFilteredDeliveryJobList(job -> !job.getDeliveredStatus()); + resultDisplay.setFeedbackToUser("Filter pending delivery."); + break; + default: + logic.updateFilteredDeliveryJobList(job -> true); + logic.updateSortedDeliveryJobListByComparator(new SortbyDate(true)); + resultDisplay.setFeedbackToUser("Show all delivery jobs."); + break; + } + }; + /** * Creates a {@code MainWindow} with the given {@code Stage} and {@code Logic}. */ @@ -66,18 +201,35 @@ public MainWindow(Stage primaryStage, Logic logic) { setAccelerators(); helpWindow = new HelpWindow(); + timetableWindow = new TimetableWindow(new Stage(), logic, this.helpWindow); + unscheduleWindow = new UnscheduleWindow(new Stage(), logic); + completeWindow = new CompleteWindow(new Stage(), logic); + reminderListWindow = new ReminderListWindow(new Stage(), logic); + statsWindow = new StatisticsWindow(new Stage(), logic); + addressBookWindow = new AddressBookDialog(new Stage(), logic, (person) -> {}, helpWindow); } + /** + * Returns primary stage of Main Window + */ public Stage getPrimaryStage() { return primaryStage; } + /** + * Returns help window of Main Window + */ + public HelpWindow getHelpWindow() { + return helpWindow; + } + private void setAccelerators() { setAccelerator(helpMenuItem, KeyCombination.valueOf("F1")); } /** * Sets the accelerator of a MenuItem. + * * @param keyCombination the KeyCombination value of the accelerator */ private void setAccelerator(MenuItem menuItem, KeyCombination keyCombination) { @@ -106,21 +258,44 @@ private void setAccelerator(MenuItem menuItem, KeyCombination keyCombination) { }); } + private void refreshDeliveryJobListView() { + logger.info("[refreshDeliveryJobListView] Refresh List (size): " + deliveryJobListPanel.size()); + if (deliveryJobListPanel.size() > 0) { + emptyDeliveryJobListPanelPlaceholder.setVisible(false); + } else { + deliveryJobDetailPlaceholder.getChildren().clear(); + emptyDeliveryJobListPanelPlaceholder.setVisible(true); + } + } + /** * Fills up all the placeholders of this window. */ void fillInnerParts() { - personListPanel = new PersonListPanel(logic.getFilteredPersonList()); - personListPanelPlaceholder.getChildren().add(personListPanel.getRoot()); + // Append views + deliveryJobListPanel = new DeliveryJobListPanel(logic.getFilteredDeliveryJobList()); + deliveryJobListPanel.setSelectHandler(selectDeliveryJobHandler); + deliveryJobListPanel.setCheckHandler(completeDeliveryJobHandler); + deliveryJobListPanel.setDeleteHandler(deleteDeliveryJobHandler); + deliveryJobListPanel.setOrderByHandler(sortDeliveryJobHandler); + deliveryJobListPanel.setFilterHandler(filterDeliveryJobHandler); + deliveryJobListPanelPlaceholder.getChildren().add(deliveryJobListPanel.getRoot()); + deliveryJobListPanel.selectDefault(); resultDisplay = new ResultDisplay(); resultDisplayPlaceholder.getChildren().add(resultDisplay.getRoot()); - StatusBarFooter statusBarFooter = new StatusBarFooter(logic.getAddressBookFilePath()); + StatusBarFooter statusBarFooter = new StatusBarFooter(logic.getDeliveryJobSystemFilePath()); statusbarPlaceholder.getChildren().add(statusBarFooter.getRoot()); CommandBox commandBox = new CommandBox(this::executeCommand); commandBoxPlaceholder.getChildren().add(commandBox.getRoot()); + + // Set default ordering + logic.updateSortedDeliveryJobListByComparator(new SortbyDate(true)); + + // Initialise detail panel + refreshDeliveryJobListView(); } /** @@ -135,6 +310,13 @@ private void setWindowDefaultSize(GuiSettings guiSettings) { } } + /** + * Focuses on the main window. + */ + public void focus() { + getRoot().requestFocus(); + } + /** * Opens the help window or focuses on it if it's already opened. */ @@ -142,11 +324,186 @@ private void setWindowDefaultSize(GuiSettings guiSettings) { public void handleHelp() { if (!helpWindow.isShowing()) { helpWindow.show(); + logger.info("Opened help window."); } else { helpWindow.focus(); } } + /** + * Reloads and opens Timetable window. + */ + @FXML + public void handleTimetable() { + logger.info("Opened timetable window of current week."); + try { + CommandResult commandResult = logic.execute("timetable"); + resultDisplay.setFeedbackToUser(commandResult.getFeedbackToUser()); + } catch (CommandException | ParseException e) { + resultDisplay.setFeedbackToUser(e.getMessage()); + } catch (FileNotFoundException | IllegalArgumentException e) { + resultDisplay.setFeedbackToUser(e.getMessage()); + } + if (timetableWindow.isShowing()) { + timetableWindow.focus(); + } else { + timetableWindow.show(); + } + timetableWindow.fillInnerParts(); + } + + /** + * Opens timetable window + */ + public void openTimetable() { + if (!timetableWindow.isShowing()) { + logger.info("Opened timetable window of week containing " + logic.getFocusDate()); + timetableWindow.show(); + timetableWindow.fillInnerParts(); + } else { + timetableWindow.focus(); + timetableWindow.fillInnerParts(); + } + } + + /** + * Opens updated unscheduled jobs window + */ + @FXML + public void handleUnscheduledTimetable() { + if (!unscheduleWindow.isShowing()) { + logger.info("Opened window of unscheduled jobs."); + try { + CommandResult commandResult = logic.execute("timetable_unscheduled"); + resultDisplay.setFeedbackToUser(commandResult.getFeedbackToUser()); + } catch (CommandException | ParseException e) { + resultDisplay.setFeedbackToUser(e.getMessage()); + } catch (FileNotFoundException e) { + resultDisplay.setFeedbackToUser(e.getMessage()); + } + unscheduleWindow.show(); + unscheduleWindow.fillInnerParts(); + } else { + unscheduleWindow.focus(); + } + } + + /** + * Opens updated completed jobs window + */ + @FXML + public void handleCompletedTimetable() { + if (!completeWindow.isShowing()) { + logger.info("Opened window of completed jobs."); + try { + CommandResult commandResult = logic.execute("timetable_completed"); + resultDisplay.setFeedbackToUser(commandResult.getFeedbackToUser()); + } catch (CommandException | ParseException e) { + resultDisplay.setFeedbackToUser(e.getMessage()); + } catch (FileNotFoundException | IllegalArgumentException e) { + resultDisplay.setFeedbackToUser(e.getMessage()); + } + completeWindow.show(); + completeWindow.fillInnerParts(); + } else { + completeWindow.focus(); + } + } + + /** + * Opens Reminder List window. + */ + @FXML + public void handleReminderList() { + if (!reminderListWindow.isShowing()) { + reminderListWindow.show(); + reminderListWindow.fillInnerParts(); + logger.info("Opened reminder window."); + } else { + reminderListWindow.focus(); + } + } + + /** + * Opens Statistics window. + */ + @FXML + public void handleStats() { + if (!statsWindow.isShowing()) { + statsWindow.show(); + statsWindow.fillInnerParts(); + logger.info("Opened statistics window"); + } else { + statsWindow.focus(); + } + } + + /** + * Opens Address book window. + */ + @FXML + public void handleAddressBook() { + if (addDeliveryJobWindow != null) { + addDeliveryJobWindow.hide(); + } + + if (!addressBookWindow.isShowing()) { + addressBookWindow.show(); + addressBookWindow.fillInnerParts(); + logger.info("Opened address book window."); + } else { + addressBookWindow.focus(); + } + } + + /** + * Returns delivery job list panel + */ + public DeliveryJobListPanel getDeliveryJobListPanel() { + return deliveryJobListPanel; + } + + /** + * Handles create job ui. + */ + @FXML + private void handleDeliveryJobSystemCreateAction() { + if (addDeliveryJobWindow != null) { + addDeliveryJobWindow.hide(); + } + addDeliveryJobWindow = new AddDeliveryJobWindow(new Stage(), logic); + addDeliveryJobWindow.setResultHandler(commandResult -> { + logger.info("[editDeliveryJobHandler] add complete."); + resultDisplay.setFeedbackToUser(commandResult.getFeedbackToUser()); + }); + addDeliveryJobWindow.show(); + addDeliveryJobWindow.fillInnerParts(); + } + + /** + * Handles mass import job ui. + */ + @FXML + private void handleDeliveryJobSystemImportAction() { + try { + FileChooser fileChooser = new FileChooser(); + fileChooser.setTitle("Open Files"); + File selectedFile = fileChooser.showOpenDialog(new Stage()); + + CommandResult commandResult = logic.execute(new ImportDeliveryJobCommand(selectedFile)); + resultDisplay.setFeedbackToUser(commandResult.getFeedbackToUser()); + } catch (ParseException | CommandException e) { + logger.warning("[Event] importDeliveryJob " + e.getMessage()); + resultDisplay.setFeedbackToUser(e.getMessage()); + } catch (FileNotFoundException | IllegalArgumentException e) { + logger.warning("[Event] importDeliveryJob " + e.getMessage()); + resultDisplay.setFeedbackToUser(e.getMessage()); + } + } + + /** + * Shows main window + */ void show() { primaryStage.show(); } @@ -160,21 +517,27 @@ private void handleExit() { (int) primaryStage.getX(), (int) primaryStage.getY()); logic.setGuiSettings(guiSettings); helpWindow.hide(); + timetableWindow.hide(); + unscheduleWindow.hide(); + completeWindow.hide(); + statsWindow.hide(); + reminderListWindow.hide(); + addressBookWindow.hide(); + if (addDeliveryJobWindow != null) { + addDeliveryJobWindow.hide(); + } primaryStage.hide(); } - public PersonListPanel getPersonListPanel() { - return personListPanel; - } - /** * Executes the command and returns the result. * * @see seedu.address.logic.Logic#execute(String) */ - private CommandResult executeCommand(String commandText) throws CommandException, ParseException { + private CommandResult executeCommand(String commandText) + throws CommandException, ParseException, FileNotFoundException { try { - CommandResult commandResult = logic.execute(commandText); + CommandResult commandResult = logic.execute(commandText, g -> !g.equals(CommandGroup.PERSON)); logger.info("Result: " + commandResult.getFeedbackToUser()); resultDisplay.setFeedbackToUser(commandResult.getFeedbackToUser()); @@ -182,15 +545,47 @@ private CommandResult executeCommand(String commandText) throws CommandException handleHelp(); } + if (commandResult.isShowTimetable()) { + if (commandResult.isShowTimetableDate()) { + openTimetable(); + } else { + handleTimetable(); + } + } + + if (commandResult.isShowUnschedule()) { + handleUnscheduledTimetable(); + } + + if (commandResult.isShowComplete()) { + handleCompletedTimetable(); + } + + if (commandResult.isShowStatistics()) { + handleStats(); + } + + if (commandResult.isShowReminderList()) { + handleReminderList(); + } + + if (commandResult.isShowAddressBook()) { + handleAddressBook(); + } + if (commandResult.isExit()) { handleExit(); } return commandResult; - } catch (CommandException | ParseException e) { + } catch (CommandException | ParseException | IllegalArgumentException e) { logger.info("Invalid command: " + commandText); resultDisplay.setFeedbackToUser(e.getMessage()); throw e; + } catch (FileNotFoundException e) { + logger.info("File not found: " + commandText); + resultDisplay.setFeedbackToUser(e.getMessage()); + throw e; } } } diff --git a/src/main/java/seedu/address/ui/ReminderListWindow.java b/src/main/java/seedu/address/ui/ReminderListWindow.java new file mode 100644 index 00000000000..9353b20ab7c --- /dev/null +++ b/src/main/java/seedu/address/ui/ReminderListWindow.java @@ -0,0 +1,118 @@ +package seedu.address.ui; + +import java.util.logging.Logger; + +import javafx.fxml.FXML; +import javafx.scene.Scene; +import javafx.scene.control.Button; +import javafx.scene.control.ListCell; +import javafx.scene.control.ListView; +import javafx.scene.layout.VBox; +import javafx.stage.Stage; +import seedu.address.commons.core.LogsCenter; +import seedu.address.logic.Logic; +import seedu.address.model.reminder.Reminder; + +/** + * The reminder list Window. Displays all the reminders that have been added so far. + */ +public class ReminderListWindow extends UiPart { + private static final String FXML = "ReminderListWindow.fxml"; + private final Logger logger = LogsCenter.getLogger(getClass()); + private final Logic logic; + private final Stage stage; + + @FXML + private Button sortByOldestButton; + @FXML + private ListView reminderList; + + /** + * Creates a new ReminderListWindow. + * + * @param logic Stores the reminder list. + */ + public ReminderListWindow(Stage root, Logic logic) { + super(FXML, root); + // Set dependencies + this.stage = root; + this.logic = logic; + reminderList = new ListView<>(logic.getReminderList()); + VBox vbox = new VBox(sortByOldestButton, reminderList); + stage.setAlwaysOnTop(true); + stage.setScene(new Scene(vbox)); + } + + /** + * Shows the help window. + * @throws IllegalStateException + *

    + *
  • + * if this method is called on a thread other than the JavaFX Application Thread. + *
  • + *
  • + * if this method is called during animation or layout processing. + *
  • + *
  • + * if this method is called on the primary stage. + *
  • + *
  • + * if {@code dialogStage} is already showing. + *
  • + *
+ */ + public void show() { + logger.fine("Showing reminder list window."); + getRoot().show(); + getRoot().centerOnScreen(); + } + + /** + * Hides the reminder list window. + */ + public void hide() { + getRoot().hide(); + } + + /** + * Returns true if the reminder list window is currently being shown. + */ + public boolean isShowing() { + return getRoot().isShowing(); + } + + /** + * Focuses on the reminder list window. + */ + public void focus() { + getRoot().requestFocus(); + } + + /** + * Fills up all the placeholders of this window. + */ + public void fillInnerParts() { + reminderList.setCellFactory(param -> new ListCell<>() { + @Override + protected void updateItem(Reminder item, boolean empty) { + super.updateItem(item, empty); + int i = super.getIndex() + 1; + if (empty || item == null) { + setText(null); + } else { + setText(i + ". " + item.getDescription() + " " + item.reminderDateTimeToString()); + } + } + }); + stage.show(); + } + + /** + * Specifies button action. Sorts the reminder list by oldest reminders. + */ + @FXML + public void sortByOldest() { + logger.info("[Event] sort Reminder List by Oldest"); + logic.sortReminderList(); + } +} diff --git a/src/main/java/seedu/address/ui/StatisticsWindow.java b/src/main/java/seedu/address/ui/StatisticsWindow.java new file mode 100644 index 00000000000..e039b31037f --- /dev/null +++ b/src/main/java/seedu/address/ui/StatisticsWindow.java @@ -0,0 +1,196 @@ +package seedu.address.ui; + +import java.io.FileNotFoundException; +import java.time.LocalDate; +import java.util.logging.Logger; + +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.layout.StackPane; +import javafx.stage.Stage; +import seedu.address.commons.core.GuiSettings; +import seedu.address.commons.core.LogsCenter; +import seedu.address.logic.Logic; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.jobs.DeliveryJob; +import seedu.address.model.stats.StatisticItemList; +import seedu.address.model.stats.TotalCompleted; +import seedu.address.model.stats.TotalEarnings; +import seedu.address.model.stats.TotalJobs; +import seedu.address.model.stats.TotalPending; +import seedu.address.ui.jobs.DeliveryJobListPanel; +import seedu.address.ui.main.ResultDisplay; + +/** + * Controller for a statistics page + */ +public class StatisticsWindow extends UiPart { + public static final String ALL_STATS = "All time statistics: "; + public static final String LAST_WEEK = "Last Week's statistics:"; + public static final String STATISTICS = "Here are your statistics: "; + private static final String FXML = "StatisticsWindow.fxml"; + private final Logger logger = LogsCenter.getLogger(getClass()); + private Stage primaryStage; + private Logic logic; + + private ResultDisplay resultDisplay; + private DeliveryJobListPanel deliveryJobListPanel; + @FXML + private Label header; + @FXML + private Label allStats; + @FXML + private Label lastWeekStats; + @FXML + private Label titleAllStats; + @FXML + private Label titleLastWeek; + @FXML + private StackPane deliveryJobListPanelPlaceholder; + @FXML + private StackPane deliveryJobDetailPlaceholder; + + /** + * Creates a {@code StatisticsWindow} with the given {@code Stage} and {@code Logic}. + */ + public StatisticsWindow(Stage primaryStage, Logic logic) { + super(FXML, primaryStage); + + // Set dependencies + this.primaryStage = primaryStage; + this.logic = logic; + + header.setText(STATISTICS); + titleAllStats.setText(ALL_STATS); + titleLastWeek.setText(LAST_WEEK); + } + + /** + * Shows the help window. + * @throws IllegalStateException + *
    + *
  • + * if this method is called on a thread other than the JavaFX Application Thread. + *
  • + *
  • + * if this method is called during animation or layout processing. + *
  • + *
  • + * if this method is called on the primary stage. + *
  • + *
  • + * if {@code dialogStage} is already showing. + *
  • + *
+ */ + public void show() { + logger.fine("Showing stats page about the driver."); + getRoot().show(); + getRoot().centerOnScreen(); + } + + /** + * Returns true if the stats window is currently being shown. + */ + public boolean isShowing() { + return getRoot().isShowing(); + } + + /** + * Hides the stats window. + */ + public void hide() { + getRoot().hide(); + } + + /** + * Focuses on the stats window. + */ + public void focus() { + getRoot().requestFocus(); + } + + public DeliveryJobListPanel getDeliveryJobListPanel() { + return deliveryJobListPanel; + } + + /** + * Returns a String that represents the statistics to be displayed + * + * @param list List of delivery jobs to generate statistic from + */ + public String fillStats(ObservableList list) { + TotalJobs totalJobs = new TotalJobs(list.size()); + TotalEarnings totalEarnings = new TotalEarnings(logic.getTotalEarnings(list)); + TotalCompleted totalCompleted = new TotalCompleted(logic.getTotalCompleted(list)); + TotalPending totalPending = new TotalPending(logic.getTotalPending(list)); + + StatisticItemList statisticItemList = new StatisticItemList(); + + statisticItemList.addStats((totalJobs)); + statisticItemList.addStats(totalEarnings); + statisticItemList.addStats(totalCompleted); + statisticItemList.addStats(totalPending); + + return statisticItemList.printStats(); + } + + /** + * Fills up all the placeholders of this window. + */ + void fillInnerParts() { + ObservableList list = logic.getFilteredDeliveryJobList(); + LocalDate lastWeekDate = LocalDate.now().minusDays(7); + ObservableList currWeekJobs = logic.weekJobsList(list, lastWeekDate); + allStats.setText(fillStats(list)); + lastWeekStats.setText(fillStats(currWeekJobs)); + } + + /** + * Sets the default size based on {@code guiSettings}. + */ + private void setWindowDefaultSize(GuiSettings guiSettings) { + primaryStage.setHeight(guiSettings.getWindowHeight()); + primaryStage.setWidth(guiSettings.getWindowWidth()); + if (guiSettings.getWindowCoordinates() != null) { + primaryStage.setX(guiSettings.getWindowCoordinates().getX()); + primaryStage.setY(guiSettings.getWindowCoordinates().getY()); + } + } + + @FXML + private void handleExit() { + GuiSettings guiSettings = new GuiSettings(primaryStage.getWidth(), primaryStage.getHeight(), + (int) primaryStage.getX(), (int) primaryStage.getY()); + logic.setGuiSettings(guiSettings); + primaryStage.hide(); + } + /** + * Executes the command and returns the result. + * + * @see seedu.address.logic.Logic#execute(String) + */ + private CommandResult executeCommand(String commandText) + throws CommandException, ParseException, FileNotFoundException { + try { + CommandResult commandResult = logic.execute(commandText); + logger.info("Result: " + commandResult.getFeedbackToUser()); + resultDisplay.setFeedbackToUser(commandResult.getFeedbackToUser()); + + return commandResult; + } catch (CommandException | ParseException e) { + logger.info("Invalid command: " + commandText); + resultDisplay.setFeedbackToUser(e.getMessage()); + //resultDisplay.setFeedbackToUser(e.getMessage()); + throw e; + } catch (FileNotFoundException e) { + logger.info("File not found: " + commandText); + resultDisplay.setFeedbackToUser(e.getMessage()); + //resultDisplay.setFeedbackToUser(e.getMessage()); + throw e; + } + } +} diff --git a/src/main/java/seedu/address/ui/TimetableWindow.java b/src/main/java/seedu/address/ui/TimetableWindow.java new file mode 100644 index 00000000000..69e09bb1aaf --- /dev/null +++ b/src/main/java/seedu/address/ui/TimetableWindow.java @@ -0,0 +1,230 @@ +package seedu.address.ui; + +import java.io.FileNotFoundException; +import java.time.LocalDate; +import java.util.logging.Logger; + +import javafx.collections.ListChangeListener; +import javafx.fxml.FXML; +import javafx.scene.Scene; +import javafx.scene.layout.StackPane; +import javafx.scene.layout.VBox; +import javafx.stage.Stage; +import seedu.address.commons.core.GuiSettings; +import seedu.address.commons.core.LogsCenter; +import seedu.address.logic.Logic; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.jobs.DeliveryJob; +import seedu.address.ui.main.CommandBox; +import seedu.address.ui.main.ResultDisplay; +import seedu.address.ui.main.StatusBarFooter; +import seedu.address.ui.timetable.TimetableDetailPanel; + +/** + * Controller for a timetable page + */ +public class TimetableWindow extends UiPart { + + private static final String FXML = "TimetableWindow.fxml"; + private final Logger logger = LogsCenter.getLogger(getClass()); + private Stage primaryStage; + private Logic logic; + private LocalDate focusDate; + private int focusDayOfWeek; + private ResultDisplay resultDisplay; + private HelpWindow helpWindow; + private TimetableDetailPanel timetableDetail; + + + @FXML + private Scene scene; + + @FXML + private VBox contentContainer; + + @FXML + private StackPane commandBoxPlaceholder; + + @FXML + private StackPane resultDisplayPlaceholder; + + @FXML + private StackPane statusbarPlaceholder; + + @FXML + private StackPane timetablePlaceholder; + + /** + * Creates a {@code TimeTableWindow} with the given {@code Stage} and {@code Logic}. + */ + public TimetableWindow(Stage primaryStage, Logic logic, HelpWindow helpWindow) { + super(FXML, primaryStage); + + // Set dependencies + this.primaryStage = primaryStage; + this.primaryStage.setResizable(true); + contentContainer.prefHeightProperty().bind(scene.heightProperty()); + contentContainer.prefWidthProperty().bind(scene.widthProperty()); + + this.logic = logic; + this.helpWindow = helpWindow; + focusDate = LocalDate.now(); + focusDayOfWeek = focusDate.getDayOfWeek().getValue(); + + // Configure the UI + setWindowDefaultSize(logic.getGuiSettings()); + } + + /** + * Shows the timetable window. + */ + public void show() { + logger.info("Showing timetable of week of " + focusDate.toString()); + getRoot().show(); + getRoot().centerOnScreen(); + } + + /** + * Returns true if the timetable window is currently being shown. + */ + public boolean isShowing() { + return getRoot().isShowing(); + } + + /** + * Hides the timetable window. + */ + public void hide() { + getRoot().hide(); + } + + /** + * Focuses on the timetable window. + */ + public void focus() { + getRoot().requestFocus(); + } + + /** + * Fills up all the placeholders of this window. + */ + public void fillInnerParts() { + + updateTimetable(); + + resultDisplay = new ResultDisplay(); + resultDisplayPlaceholder.getChildren().add(resultDisplay.getRoot()); + + StatusBarFooter statusBarFooter = new StatusBarFooter(logic.getAddressBookFilePath()); + statusbarPlaceholder.getChildren().add(statusBarFooter.getRoot()); + + CommandBox commandBox = new CommandBox(this::executeCommand); + commandBoxPlaceholder.getChildren().add(commandBox.getRoot()); + + logic.getFilteredDeliveryJobList().addListener(new ListChangeListener() { + @Override + public void onChanged(Change c) { + refreshTimetable(); + } + }); + } + + /** + * Updates timetable with the updated job list + */ + void updateTimetable() { + //Get year and month of week + logger.fine("Update timetable with updated job list"); + focusDate = logic.getFocusDate(); + + timetablePlaceholder.getChildren().clear(); + timetableDetail = new TimetableDetailPanel(focusDate, logic, primaryStage); + timetablePlaceholder.getChildren().add(timetableDetail.getRoot()); + } + + /** + * Refreshes timetable with the updated job list + */ + void refreshTimetable() { + //Get year and month of week + logger.info("[Timetable] Refresh timetable with updated job list"); + focusDate = logic.getFocusDate(); + logic.updateSortedDeliveryJobListByDate(); + logic.setWeekDeliveryJobList(focusDate); + + timetablePlaceholder.getChildren().clear(); + TimetableDetailPanel timetableDetail = new TimetableDetailPanel(focusDate, logic, primaryStage); + timetablePlaceholder.getChildren().add(timetableDetail.getRoot()); + } + + + /** + * Sets the default size based on {@code guiSettings}. + */ + private void setWindowDefaultSize(GuiSettings guiSettings) { + primaryStage.setHeight(guiSettings.getWindowHeight()); + primaryStage.setWidth(guiSettings.getWindowWidth()); + if (guiSettings.getWindowCoordinates() != null) { + primaryStage.setX(guiSettings.getWindowCoordinates().getX()); + primaryStage.setY(guiSettings.getWindowCoordinates().getY()); + } + } + + @FXML + private void handleExit() { + GuiSettings guiSettings = new GuiSettings(primaryStage.getWidth(), primaryStage.getHeight(), + (int) primaryStage.getX(), (int) primaryStage.getY()); + logic.setGuiSettings(guiSettings); + primaryStage.hide(); + } + + /** + * Opens the help window or focuses on it if it's already opened. + */ + public void handleHelp() { + if (!helpWindow.isShowing()) { + helpWindow.show(); + logger.info("Opened help window from Timetable Window."); + } else { + helpWindow.focus(); + } + } + + + /** + * Executes the command and returns the result. + * + * @see seedu.address.logic.Logic#executeTimetableCommand(String) + */ + private CommandResult executeCommand(String commandText) + throws CommandException, ParseException, FileNotFoundException { + try { + CommandResult commandResult = logic.executeTimetableCommand(commandText); + logger.info("Result: " + commandResult.getFeedbackToUser()); + resultDisplay.setFeedbackToUser(commandResult.getFeedbackToUser()); + focusDate = logic.getFocusDate(); + updateTimetable(); + + if (commandResult.isExit()) { + handleExit(); + } + + if (commandResult.isShowHelp()) { + handleHelp(); + } + + return commandResult; + } catch (CommandException | ParseException e) { + logger.info("Invalid command: " + commandText); + resultDisplay.setFeedbackToUser(e.getMessage()); + throw e; + } catch (FileNotFoundException e) { + logger.info("File not found: " + commandText); + resultDisplay.setFeedbackToUser(e.getMessage()); + throw e; + } + } + +} diff --git a/src/main/java/seedu/address/ui/Ui.java b/src/main/java/seedu/address/ui/Ui.java index 17aa0b494fe..e28dae7e990 100644 --- a/src/main/java/seedu/address/ui/Ui.java +++ b/src/main/java/seedu/address/ui/Ui.java @@ -7,7 +7,9 @@ */ public interface Ui { - /** Starts the UI (and the App). */ + /** + * Starts the UI (and the App). + */ void start(Stage primaryStage); } diff --git a/src/main/java/seedu/address/ui/UiManager.java b/src/main/java/seedu/address/ui/UiManager.java index fdf024138bc..5d3d4d499f6 100644 --- a/src/main/java/seedu/address/ui/UiManager.java +++ b/src/main/java/seedu/address/ui/UiManager.java @@ -1,5 +1,8 @@ package seedu.address.ui; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.List; import java.util.logging.Logger; import javafx.application.Platform; @@ -11,6 +14,8 @@ import seedu.address.commons.core.LogsCenter; import seedu.address.commons.util.StringUtil; import seedu.address.logic.Logic; +import seedu.address.ui.notification.BackgroundNotificationScheduler; +import seedu.address.ui.notification.NotificationManager; /** * The manager of the UI component. @@ -43,6 +48,21 @@ public void start(Stage primaryStage) { mainWindow = new MainWindow(primaryStage, logic); mainWindow.show(); //This should be called before creating other UI parts mainWindow.fillInnerParts(); + List l = new ArrayList<>(); + l.add(() -> mainWindow.handleReminderList()); + l.add(() -> mainWindow.openTimetable()); + NotificationManager notificationManager = new NotificationManager(logic, l); + notificationManager.checkReminderList(); + + Calendar now = Calendar.getInstance(); + if (now.get(Calendar.HOUR_OF_DAY) >= 10 && now.get(Calendar.HOUR_OF_DAY) < 16 + && now.get(Calendar.MINUTE) < 40) { + notificationManager.checkNowSchedule(); + } else { + notificationManager.checkNextSchedule(); + } + + new BackgroundNotificationScheduler(notificationManager).run(); } catch (Throwable e) { logger.severe(StringUtil.getDetails(e)); @@ -50,6 +70,7 @@ public void start(Stage primaryStage) { } } + private Image getImage(String imagePath) { return new Image(MainApp.class.getResourceAsStream(imagePath)); } diff --git a/src/main/java/seedu/address/ui/UiPart.java b/src/main/java/seedu/address/ui/UiPart.java index fc820e01a9c..017725f04db 100644 --- a/src/main/java/seedu/address/ui/UiPart.java +++ b/src/main/java/seedu/address/ui/UiPart.java @@ -14,7 +14,9 @@ */ public abstract class UiPart { - /** Resource folder where FXML files are stored. */ + /** + * Resource folder where FXML files are stored. + */ public static final String FXML_FILE_FOLDER = "/view/"; private final FXMLLoader fxmlLoader = new FXMLLoader(); @@ -29,6 +31,7 @@ public UiPart(URL fxmlFileUrl) { /** * Constructs a UiPart using the specified FXML file within {@link #FXML_FILE_FOLDER}. + * * @see #UiPart(URL) */ public UiPart(String fxmlFileName) { @@ -45,12 +48,23 @@ public UiPart(URL fxmlFileUrl, T root) { /** * Constructs a UiPart with the specified FXML file within {@link #FXML_FILE_FOLDER} and root object. + * * @see #UiPart(URL, T) */ public UiPart(String fxmlFileName, T root) { this(getFxmlFileUrl(fxmlFileName), root); } + /** + * Returns the FXML file URL for the specified FXML file name within {@link #FXML_FILE_FOLDER}. + */ + private static URL getFxmlFileUrl(String fxmlFileName) { + requireNonNull(fxmlFileName); + String fxmlFileNameWithFolder = FXML_FILE_FOLDER + fxmlFileName; + URL fxmlFileUrl = MainApp.class.getResource(fxmlFileNameWithFolder); + return requireNonNull(fxmlFileUrl); + } + /** * Returns the root object of the scene graph of this UiPart. */ @@ -60,8 +74,9 @@ public T getRoot() { /** * Loads the object hierarchy from a FXML document. + * * @param location Location of the FXML document. - * @param root Specifies the root of the object hierarchy. + * @param root Specifies the root of the object hierarchy. */ private void loadFxmlFile(URL location, T root) { requireNonNull(location); @@ -75,14 +90,4 @@ private void loadFxmlFile(URL location, T root) { } } - /** - * Returns the FXML file URL for the specified FXML file name within {@link #FXML_FILE_FOLDER}. - */ - private static URL getFxmlFileUrl(String fxmlFileName) { - requireNonNull(fxmlFileName); - String fxmlFileNameWithFolder = FXML_FILE_FOLDER + fxmlFileName; - URL fxmlFileUrl = MainApp.class.getResource(fxmlFileNameWithFolder); - return requireNonNull(fxmlFileUrl); - } - } diff --git a/src/main/java/seedu/address/ui/jobs/AddDeliveryJobWindow.java b/src/main/java/seedu/address/ui/jobs/AddDeliveryJobWindow.java new file mode 100644 index 00000000000..5e5402dade3 --- /dev/null +++ b/src/main/java/seedu/address/ui/jobs/AddDeliveryJobWindow.java @@ -0,0 +1,506 @@ +package seedu.address.ui.jobs; + +import java.io.FileNotFoundException; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.util.Optional; +import java.util.function.Consumer; +import java.util.function.UnaryOperator; +import java.util.logging.Logger; + +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.scene.control.Button; +import javafx.scene.control.ChoiceBox; +import javafx.scene.control.DatePicker; +import javafx.scene.control.Label; +import javafx.scene.control.TextArea; +import javafx.scene.control.TextField; +import javafx.scene.control.TextFormatter; +import javafx.scene.control.TextFormatter.Change; +import javafx.scene.layout.VBox; +import javafx.stage.Stage; +import javafx.util.StringConverter; +import seedu.address.commons.core.LogsCenter; +import seedu.address.logic.Logic; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.logic.commands.jobs.AddDeliveryJobCommand; +import seedu.address.logic.commands.jobs.EditDeliveryJobCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.jobs.DeliveryDate; +import seedu.address.model.jobs.DeliveryJob; +import seedu.address.model.jobs.DeliverySlot; +import seedu.address.model.jobs.Earning; +import seedu.address.ui.UiPart; +import seedu.address.ui.person.AddressBookDialog; + +/** + * AddDeliveryJobWindow + */ +public class AddDeliveryJobWindow extends UiPart { + + private static final String FXML = "ModifyDeliveryJobWindow.fxml"; + + private static final String EDIT_TITLE = "Edit Delivery Job"; + + private final Optional toEdit; + private final Logger logger = LogsCenter.getLogger(getClass()); + + private Stage primaryStage; + private Logic logic; + private Optional> completeEditCallback = Optional.empty(); + private AddressBookDialog addressBookWindow; + + @FXML + private TextField inputSender; + @FXML + private TextField inputRecipient; + @FXML + private TextField inputEarning; + @FXML + private TextArea inputDescription; + @FXML + private DatePicker inputDeliveryDate; + @FXML + private ChoiceBox inputDeliverySlot; + @FXML + private Button createButton; + @FXML + private Button editButton; + @FXML + private VBox outputErrorPlaceholder; + + // Solution below adapted from: + // https://stackoverflow.com/questions/26831978/javafx-datepicker-getvalue-in-a-specific-format + private StringConverter deliveryDateConverter = new StringConverter() { + private String pattern = "yyyy-MM-dd"; + private DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern(pattern); + + { + inputDeliveryDate.setPromptText(pattern.toLowerCase()); + } + + @Override + public String toString(LocalDate date) { + if (date != null) { + return dateFormatter.format(date); + } else { + return ""; + } + } + + @Override + public LocalDate fromString(String string) { + if (string != null && !string.isEmpty()) { + return LocalDate.parse(string, dateFormatter); + } else { + return null; + } + } + }; + + // Solution below adapted from: + // https://stackoverflow.com/questions/7555564/what-is-the-recommended-way-to-make-a-numeric-textfield-in-javafx + private UnaryOperator earningValidator = change -> { + String text = change.getText(); + String fullText = inputEarning.getText(); + if (text.equals(".") && fullText.split("\\d+").length < 2) { + return change; + } + if (text.isEmpty() || text.matches(Earning.VALIDATION_REGEX) || text.matches(Earning.VALIDATION_REGEX_DECI)) { + return change; + } + return null; + }; + + private ChangeListener emptyDateHandler = new ChangeListener() { + @Override + public void changed(final ObservableValue observable, + final Boolean oldValue, final Boolean newValue) { + if (newValue) { + inputDeliveryDate.setValue(null); + } + } + }; + + private ObservableList deliverySlotOptions = FXCollections.observableArrayList( + "", + new DeliverySlot("1").getDescription(), + new DeliverySlot("2").getDescription(), + new DeliverySlot("3").getDescription(), + new DeliverySlot("4").getDescription(), + new DeliverySlot("5").getDescription()); + + /** + * Create mode. + */ + public AddDeliveryJobWindow(Stage primaryStage, Logic logic) { + super(FXML, primaryStage); + this.primaryStage = primaryStage; + this.logic = logic; + toEdit = Optional.empty(); + } + + /** + * Edit mode. + */ + public AddDeliveryJobWindow(Stage primaryStage, Logic logic, DeliveryJob job) { + super(FXML, primaryStage); + this.primaryStage = primaryStage; + this.logic = logic; + toEdit = Optional.of(job); + } + + /** + * fillInnerParts. + */ + public void fillInnerParts() { + inputDeliverySlot.setItems(deliverySlotOptions); + inputEarning.setTextFormatter(new TextFormatter(earningValidator)); + inputDeliveryDate.getEditor().textProperty().isEmpty().addListener(emptyDateHandler); + inputDeliveryDate.setConverter(deliveryDateConverter); + + toEdit.ifPresent(job -> { + fillDetails(job); + primaryStage.setTitle(EDIT_TITLE); + createButton.setVisible(false); + editButton.setVisible(true); + }); + } + + private void fillDetails(DeliveryJob job) { + inputSender.setText(job.getSenderId()); + inputRecipient.setText(job.getRecipientId()); + + job.getDeliveryDate().ifPresentOrElse(val -> { + if (val.getDate().isEqual(DeliveryDate.placeholder().getDate())) { + inputDeliveryDate.setValue(null); + inputDeliveryDate.getEditor().clear(); + } else { + inputDeliveryDate.setValue(val.getDate()); + } + }, () -> { + inputDeliveryDate.getEditor().clear(); + }); + + job.getDeliverySlot().ifPresentOrElse(val -> { + inputDeliverySlot.getSelectionModel().select(val.getSlot()); + }, () -> { + inputDeliverySlot.getSelectionModel().select(0); + }); + + job.getEarning().ifPresent(val -> { + inputEarning.setText(val.value); + }); + + inputDescription.setText(job.getDescription()); + } + + @FXML + private void viewSenderAddressBook() { + logger.info("[Event] viewSenderAddressBook"); + if (addressBookWindow != null) { + addressBookWindow.getRoot().close(); + } + addressBookWindow = new AddressBookDialog(new Stage(), logic, person -> { + inputSender.setText(person.getPersonId()); + addressBookWindow.getRoot().close(); + }); + addressBookWindow.getRoot().setTitle("Select sender"); + addressBookWindow.fillInnerParts(); + addressBookWindow.show(); + } + + @FXML + private void viewRecipientAddressBook() { + logger.info("[Event] viewRecipientAddressBook"); + if (addressBookWindow != null) { + addressBookWindow.getRoot().close(); + } + addressBookWindow = new AddressBookDialog(new Stage(), logic, person -> { + inputRecipient.setText(person.getPersonId()); + addressBookWindow.getRoot().close(); + }); + addressBookWindow.getRoot().setTitle("Select recipient"); + addressBookWindow.fillInnerParts(); + addressBookWindow.show(); + } + + /** + * Sets the result callback handler. + * + * @param handler + */ + public void setResultHandler(Consumer handler) { + assert handler != null; + + this.completeEditCallback = Optional.of(handler); + } + + @FXML + private void editDeliveryJob() { + logger.info("[Event] editDeliveryJob"); + clearError(); + if (!validateFields()) { + return; + } + + try { + EditDeliveryJobCommand.EditDeliveryJobDescriptor des = prepareChange(); + CommandResult commandResult = logic.execute(new EditDeliveryJobCommand(des)); + completeEditCallback.ifPresent(handler -> { + handler.accept(commandResult); + }); + getRoot().close(); + } catch (FileNotFoundException | ParseException | CommandException e) { + logger.warning("[Event] editDeliveryJob" + e.getMessage()); + outputError(e.getMessage()); + } + } + + @FXML + private void createDeliveryJob() { + logger.info("[Event] createDeliveryJob"); + clearError(); + if (!validateFields()) { + return; + } + + try { + DeliveryJob job; + + if (inputDeliverySlot.getValue() == null) { + // if date and slot is empty + job = new DeliveryJob( + inputRecipient.getText(), + inputSender.getText(), + inputEarning.getText(), + inputDescription.getText()); + } else { + // if date and slot has value + job = new DeliveryJob( + inputRecipient.getText(), + inputSender.getText(), + inputDeliveryDate.getValue().toString(), + Integer.toString(inputDeliverySlot.getSelectionModel().getSelectedIndex()), + inputEarning.getText(), + inputDescription.getText()); + } + + CommandResult commandResult = logic.execute(new AddDeliveryJobCommand(job)); + completeEditCallback.ifPresent(handler -> { + handler.accept(commandResult); + }); + getRoot().close(); + } catch (FileNotFoundException | ParseException | CommandException e) { + logger.warning("[Event] createDeliveryJob" + e.getMessage()); + outputError(e.getMessage()); + } + } + + private EditDeliveryJobCommand.EditDeliveryJobDescriptor prepareChange() { + EditDeliveryJobCommand.EditDeliveryJobDescriptor des = new EditDeliveryJobCommand.EditDeliveryJobDescriptor(); + DeliveryJob job = toEdit.get(); + des.setJobId(job.getJobId()); + if (!inputSender.getText().equalsIgnoreCase(job.getSenderId())) { + des.setSender(inputSender.getText()); + } + + if (!inputRecipient.getText().equalsIgnoreCase(job.getRecipientId())) { + des.setRecipient(inputRecipient.getText()); + } + + job.getEarning().ifPresentOrElse(val -> { + if (!inputEarning.getText().equals(val.value)) { + des.setEarning(new Earning(inputEarning.getText())); + } + }, () -> { + if (!inputEarning.getText().isEmpty()) { + des.setEarning(new Earning(inputEarning.getText())); + } + }); + + if (!inputDeliveryDate.getEditor().getText().isEmpty()) { + // date field has value + job.getDeliveryDate().ifPresentOrElse(val -> { + // date is different from existing value, overwrite. + if (!val.date.equals(inputDeliveryDate.getEditor().getText())) { + des.setDeliveryDate(new DeliveryDate(inputDeliveryDate.getEditor().getText())); + } + }, () -> { + // new date value + des.setDeliveryDate(new DeliveryDate(inputDeliveryDate.getEditor().getText())); + }); + } else { + // date field is empty to overwrite existing value. + job.getDeliveryDate().ifPresent(val -> { + des.clearDeliveryDate(); + }); + } + + if (inputDeliverySlot.getValue() != null) { + // slot field has value + job.getDeliverySlot().ifPresentOrElse(val -> { + // slot is different from existing value, overwrite. + if (!inputDeliverySlot.getValue().equals(val.value)) { + des.setDeliverySlot(new DeliverySlot( + Integer.toString(inputDeliverySlot.getSelectionModel().getSelectedIndex()))); + } + }, () -> { + // new slot value + des.setDeliverySlot(new DeliverySlot( + Integer.toString(inputDeliverySlot.getSelectionModel().getSelectedIndex()))); + }); + } else { + // slot field is empty to overwrite existing value. + job.getDeliverySlot().ifPresent(val -> { + des.clearDeliverySlot(); + }); + } + + if (!inputDescription.getText().equalsIgnoreCase(job.getDescription())) { + des.setDescription(inputDescription.getText()); + } + return des; + } + + @FXML + private void handleExit() { + if (addressBookWindow != null) { + addressBookWindow.getRoot().close();; + } + getRoot().close(); + } + + private boolean validateFields() { + boolean isError = true; + if (inputSender.getText().isEmpty()) { + inputSender.getStyleClass().add("error-input"); + outputError("Sender cannot be empty."); + isError = false; + } + + if (inputRecipient.getText().isEmpty()) { + inputRecipient.getStyleClass().add("error-input"); + outputError("Recipient cannot be empty."); + isError = false; + } + + if (!inputSender.getText().isEmpty() && !inputRecipient.getText().isEmpty()) { + if (inputSender.getText().equals(inputRecipient.getText())) { + inputSender.getStyleClass().add("error-input"); + inputRecipient.getStyleClass().add("error-input"); + outputError("Sender and Recipient cannot be the same."); + isError = false; + } + } + + if (inputEarning.getText().isEmpty()) { + inputEarning.getStyleClass().add("error-input"); + outputError("Earning cannot be empty."); + isError = false; + } + + // any has value + if (inputDeliveryDate.getValue() != null || inputDeliverySlot.getValue() != null) { + boolean isDateFilled = false; + boolean isSlotFilled = false; + + // date filled + if (inputDeliveryDate.getValue() != null && !inputDeliveryDate.getEditor().getText().isBlank()) { + isDateFilled = true; + } + + // slot filled + if (inputDeliverySlot.getValue() != null && !inputDeliverySlot.getValue().isBlank()) { + isSlotFilled = true; + } + + if (isDateFilled || isSlotFilled) { + // date empty + if (!isDateFilled) { + showDateError(); + isError = false; + } else { + // date filled, check format + if (!DeliveryDate.isValidDate(inputDeliveryDate.getEditor().getText())) { + showDateError(); + isError = false; + } + } + + // slot empty + if (!isSlotFilled) { + showSlotError(); + isError = false; + } else { + // slot might have value, check field + if (inputDeliverySlot.getValue().isBlank()) { + showSlotError(); + isError = false; + } + } + } else { + inputDeliverySlot.setValue(null); + inputDeliveryDate.getEditor().clear(); + inputDeliveryDate.setValue(null); + } + } + + return isError; + } + + private void showDateError() { + inputDeliveryDate.getStyleClass().add("error-input"); + outputError("Invalid schedule date."); + } + + private void showSlotError() { + inputDeliverySlot.getStyleClass().add("error-input"); + outputError("Invalid schedule slot."); + } + + private void outputError(String msg) { + Label err = new Label(msg); + err.getStyleClass().add("error"); + outputErrorPlaceholder.getChildren().add(err); + } + + private void clearError() { + outputErrorPlaceholder.getChildren().clear(); + } + + /** + * Show main window. + */ + public void show() { + logger.fine("Showing add job window"); + getRoot().show(); + getRoot().centerOnScreen(); + } + + /** + * Returns true if the stats window is currently being shown. + */ + public boolean isShowing() { + return getRoot().isShowing(); + } + + /** + * Hides the stats window. + */ + public void hide() { + handleExit(); + } + + /** + * Focuses on the stats window. + */ + public void focus() { + getRoot().requestFocus(); + } +} diff --git a/src/main/java/seedu/address/ui/jobs/DeliveryJobCard.java b/src/main/java/seedu/address/ui/jobs/DeliveryJobCard.java new file mode 100644 index 00000000000..1e721760d8e --- /dev/null +++ b/src/main/java/seedu/address/ui/jobs/DeliveryJobCard.java @@ -0,0 +1,116 @@ +package seedu.address.ui.jobs; + +import java.util.function.Consumer; + +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.image.ImageView; +import javafx.scene.layout.BorderPane; +import javafx.scene.layout.Region; +import seedu.address.model.jobs.DeliveryDate; +import seedu.address.model.jobs.DeliveryJob; +import seedu.address.ui.UiPart; + +/** + * An UI component that displays information of a {@code DeliveryJobCard}. + */ +public class DeliveryJobCard extends UiPart { + + private static final String FXML = "DeliveryJobListCard.fxml"; + + /** + * Note: Certain keywords such as "location" and "resources" are reserved keywords in JavaFX. + * As a consequence, UI elements' variable names cannot be set to such keywords + * or an exception will be thrown by JavaFX during runtime. + * + * @see The issue on AddressBook level 4 + */ + private final DeliveryJob job; + private Consumer onCheckHandler; + + @FXML + private BorderPane cardPane; + @FXML + private Label label; + @FXML + private Label id; + @FXML + private Label receipient; + @FXML + private Label address; + @FXML + private Label deliveryTimeDate; + @FXML + private Label deliveryTimeSlot; + @FXML + private Label earning; + @FXML + private Label earningDollar; + @FXML + private Label earningCent; + @FXML + private ImageView checkmarkIcon; + + /** + * Creates a {@code DeliveryJobCard} with the given {@code DeliveryJob}, index to display + * and {@code Consumer}. + */ + public DeliveryJobCard(DeliveryJob job, int displayedIndex, Consumer onCheckHandler) { + super(FXML); + this.job = job; + id.setText(displayedIndex + ". "); + label.setText(job.getJobId()); + receipient.setText(job.getRecipientId()); + address.setText("Refine later"); + + job.getDeliveryDate().ifPresentOrElse(val -> { + if (val.date.equals(DeliveryDate.placeholder().date)) { + deliveryTimeDate.setText("Not scheduled"); + } else { + deliveryTimeDate.setText(val.date); + } + }, () -> { + deliveryTimeDate.setText("Not scheduled"); + }); + + job.getDeliverySlot().ifPresentOrElse(val -> { + deliveryTimeSlot.setText(val.getDescription()); + }, () -> { + deliveryTimeSlot.setText(""); + }); + + job.getEarning().ifPresentOrElse(val -> { + earningDollar.setText(val.dollar); + earningCent.setText(val.cent); + }, () -> { + earningDollar.setText("0"); + earningCent.setText("00"); + }); + + checkmarkIcon.setVisible(job.getDeliveredStatus()); + this.onCheckHandler = onCheckHandler; + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof DeliveryJobCard)) { + return false; + } + + // state check + DeliveryJobCard card = (DeliveryJobCard) other; + return id.getText().equals(card.id.getText()) + && job.equals(card.job); + } + + @FXML + private void handleChecked() { + onCheckHandler.accept(job); + } +} diff --git a/src/main/java/seedu/address/ui/jobs/DeliveryJobDetailPane.java b/src/main/java/seedu/address/ui/jobs/DeliveryJobDetailPane.java new file mode 100644 index 00000000000..4eb59c69ee4 --- /dev/null +++ b/src/main/java/seedu/address/ui/jobs/DeliveryJobDetailPane.java @@ -0,0 +1,211 @@ +package seedu.address.ui.jobs; + +import java.util.function.Consumer; +import java.util.logging.Logger; + +import javafx.fxml.FXML; +import javafx.scene.control.Button; +import javafx.scene.control.Label; +import javafx.scene.control.TextArea; +import javafx.scene.layout.GridPane; +import javafx.scene.layout.Region; +import javafx.scene.layout.StackPane; +import javafx.scene.layout.VBox; +import seedu.address.commons.core.LogsCenter; +import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.model.jobs.DeliveryDate; +import seedu.address.model.jobs.DeliveryJob; +import seedu.address.ui.UiPart; +import seedu.address.ui.person.PersonCard; + +/** + * DeliveryJobDetailPane. + */ +public class DeliveryJobDetailPane extends UiPart { + + private static final String FXML = "DeliveryJobDetailPane.fxml"; + private static final String BUTTON_LABEL_NOTCOMPLETE = "❌"; + private static final String BUTTON_LABEL_COMPLETE = "✅"; + private static final String BUTTON_LABEL_SHOW = "🔓"; + private static final String BUTTON_LABEL_HIDE = "🔒"; + + private final Logger logger = LogsCenter.getLogger(getClass()); + + private final DeliveryJob job; + + private Consumer handleEdit; + private Consumer handleComplete; + private Consumer handleDelete; + + @FXML + private StackPane senderContactInfoPlaceholder; + @FXML + private StackPane recipientContactInfoPlaceholder; + @FXML + private Label label; + @FXML + private Label id; + @FXML + private Label deliveryTimeDate; + @FXML + private Label deliveryTimeSlot; + @FXML + private Label earning; + @FXML + private Label earningDollar; + @FXML + private Label earningCent; + @FXML + private TextArea description; + @FXML + private Button completeButton; + @FXML + private Label deliveryAddress; + @FXML + private GridPane contactDetailPane; + @FXML + private Button toggleContactButton; + @FXML + private VBox detailMessageContainer; + @FXML + private VBox invalidMessageContainer; + + /** + * DeliveryJobDetailPanel + * + * @param job + */ + public DeliveryJobDetailPane(DeliveryJob job) { + super(FXML); + this.job = job; + } + + /** + * fillInnerParts. + * + * @param ab + */ + public void fillInnerParts(ReadOnlyAddressBook ab) { + label.setText(job.getJobId()); + + job.getDeliveryDate().ifPresentOrElse(val -> { + if (val.date.equals(DeliveryDate.placeholder().toString())) { + deliveryTimeDate.setText("Not scheduled"); + } else { + deliveryTimeDate.setText(val.date); + } + }, () -> { + deliveryTimeDate.setText("Not scheduled"); + }); + + job.getDeliverySlot().ifPresentOrElse(val -> { + deliveryTimeSlot.setText(val.getDescription()); + }, () -> { + deliveryTimeSlot.setText("Not scheduled"); + }); + + job.getEarning().ifPresentOrElse(val -> { + earningDollar.setText(val.dollar); + earningCent.setText(val.cent); + }, () -> { + earningDollar.setText("0"); + earningCent.setText("00"); + }); + + if (ab.getPersonById(job.getSenderId()).isPresent() && ab.getPersonById(job.getRecipientId()).isPresent()) { + PersonCard cardSender = new PersonCard(ab.getPersonById(job.getSenderId()).get(), "Sender"); + cardSender.getRoot().getStyleClass().add("job_person_card"); + senderContactInfoPlaceholder.getChildren().add(cardSender.getRoot()); + + PersonCard cardRecipient = new PersonCard(ab.getPersonById(job.getRecipientId()).get(), "Recipient"); + cardRecipient.getRoot().getStyleClass().add("job_person_card"); + deliveryAddress.setText(ab.getPersonById(job.getRecipientId()).get().getAddress().toString()); + recipientContactInfoPlaceholder.getChildren().add(cardRecipient.getRoot()); + } else { + setErrorMode(); + } + + description.setText(job.getDescription()); + handleEdit = (job) -> {}; + handleComplete = (job) -> {}; + handleDelete = (job) -> {}; + updateCompleteButton(); + } + + /** + * Sets the edit handler. + * + * @param handler + */ + public void setEditHandler(Consumer handler) { + assert handler != null; + + handleEdit = handler; + } + + /** + * Sets the complete handler. + * + * @param handler + */ + public void setCompleteHandler(Consumer handler) { + assert handler != null; + + handleComplete = handler; + } + + /** + * Sets the delete handler. + * + * @param handler + */ + public void setDeleteHandler(Consumer handler) { + assert handler != null; + + handleDelete = handler; + } + + @FXML + void handleEditAction() { + logger.info("[Event] handleEditAction"); + handleEdit.accept(job); + } + + @FXML + void handleCompleteAction() { + logger.info("[Event] handleCompleteAction"); + updateCompleteButton(); + handleComplete.accept(job); + } + + @FXML + void handleDeleteAction() { + logger.info("[Event] handleDeleteAction"); + handleDelete.accept(job); + } + + @FXML + void toggleVisibility() { + contactDetailPane.setVisible(!contactDetailPane.isVisible()); + if (!contactDetailPane.isVisible()) { + toggleContactButton.setText(BUTTON_LABEL_SHOW); + } else { + toggleContactButton.setText(BUTTON_LABEL_HIDE); + } + } + + private void updateCompleteButton() { + if (job.getDeliveredStatus()) { + completeButton.setText(BUTTON_LABEL_NOTCOMPLETE); + } else { + completeButton.setText(BUTTON_LABEL_COMPLETE); + } + } + + private void setErrorMode() { + deliveryAddress.setText(""); + completeButton.setDisable(true); + detailMessageContainer.setVisible(false); + invalidMessageContainer.setVisible(true); + } +} diff --git a/src/main/java/seedu/address/ui/jobs/DeliveryJobListPanel.java b/src/main/java/seedu/address/ui/jobs/DeliveryJobListPanel.java new file mode 100644 index 00000000000..b6dd5c8f74b --- /dev/null +++ b/src/main/java/seedu/address/ui/jobs/DeliveryJobListPanel.java @@ -0,0 +1,286 @@ +package seedu.address.ui.jobs; + +import java.util.Optional; +import java.util.function.BiConsumer; +import java.util.function.BiFunction; +import java.util.function.Consumer; +import java.util.logging.Logger; + +import javafx.collections.ListChangeListener; +import javafx.collections.ObservableList; +import javafx.event.Event; +import javafx.event.EventHandler; +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.control.ListCell; +import javafx.scene.control.ListView; +import javafx.scene.control.Menu; +import javafx.scene.control.RadioMenuItem; +import javafx.scene.control.ToggleGroup; +import javafx.scene.input.Clipboard; +import javafx.scene.input.ClipboardContent; +import javafx.scene.input.KeyCode; +import javafx.scene.input.KeyEvent; +import javafx.scene.layout.Region; +import seedu.address.commons.core.LogsCenter; +import seedu.address.model.jobs.DeliveryJob; +import seedu.address.model.jobs.sorters.DeliveryFilterOption; +import seedu.address.model.jobs.sorters.DeliverySortOption; +import seedu.address.ui.UiPart; + +/** + * Panel containing the list of jobs. + */ +public class DeliveryJobListPanel extends UiPart { + private static final String FXML = "DeliveryJobListPanel.fxml"; + private final Logger logger = LogsCenter.getLogger(DeliveryJobListPanel.class); + + private Optional> onSelectHandler = Optional.empty(); + private Optional> onCheckHandler = Optional.empty(); + private Optional> onDeleteHandler = Optional.empty(); + private Optional>> sortHandler = Optional + .empty(); + + private boolean isSortByAsc = false; + + @FXML + private ListView deliveryJobListView; + @FXML + private Menu orderOption; + @FXML + private Menu filterOption; + @FXML + private Label orderIndicator; + @FXML + private RadioMenuItem filterAll; + @FXML + private RadioMenuItem filterDelivered; + @FXML + private RadioMenuItem filterPending; + @FXML + private ToggleGroup toggleGroup; + + private EventHandler listKeyEventHandler = new EventHandler() { + @Override + public void handle(KeyEvent event) { + if (event.getCode().equals(KeyCode.DELETE)) { + onDeleteHandler.ifPresent(handler -> { + logger.info("[DeliveryJobListPanel] KeyPressed: " + + deliveryJobListView.getSelectionModel().getSelectedIndex()); + handler.accept(deliveryJobListView.getSelectionModel().getSelectedItem()); + }); + return; + } + if (event.getCode().equals(KeyCode.UP) || event.getCode().equals(KeyCode.DOWN)) { + selectItem(deliveryJobListView.getSelectionModel().getSelectedIndex()); + return; + } + if (event.isControlDown() && event.getCode().equals(KeyCode.C)) { + copyToClipboard(); + } + } + }; + + private ListChangeListener listChangeHandler = new ListChangeListener() { + @Override + public void onChanged(Change c) { + logger.info("[DeliveryJobListPanel] list update event"); + selectItem(deliveryJobListView.getSelectionModel().getSelectedIndex()); + } + }; + + private EventHandler listClickEventHandler = new EventHandler() { + @Override + public void handle(Event event) { + logger.info("[DeliveryJobListPanel] MouseClicked: " + + deliveryJobListView.getSelectionModel().getSelectedIndex()); + selectItem(deliveryJobListView.getSelectionModel().getSelectedIndex()); + } + }; + + /** + * Creates a {@code DeliveryJobListPanel} with the given {@code ObservableList}. + */ + public DeliveryJobListPanel(ObservableList jobList) { + super(FXML); + deliveryJobListView.setItems(jobList); + deliveryJobListView.setCellFactory(listView -> new DeliveryJobListViewCell()); + + deliveryJobListView.setOnMouseClicked(listClickEventHandler); + + deliveryJobListView.setOnKeyPressed(listKeyEventHandler); + jobList.addListener(listChangeHandler); + } + + /** + * Selects first applicable item in the job list. + * + */ + public void selectDefault() { + selectItem(0); + } + + /** + * Selects the item in the job list. + * + * @param idx + */ + private void selectItem(int idx) { + if (onSelectHandler.isEmpty()) { + return; + } + if (deliveryJobListView.getItems().size() > 0) { + logger.info("[selectItem] selected:" + deliveryJobListView.getSelectionModel().getSelectedIndex()); + if (idx < 0) { + idx = 0; + } + if (onSelectHandler.isPresent()) { + onSelectHandler.get().accept(idx + 1, deliveryJobListView.getItems().get(idx)); + } + } else { + if (onSelectHandler.isPresent()) { + onSelectHandler.get().accept(-1, null); + } + } + } + + /** + * Custom {@code ListCell} that displays the graphics of a {@code job} using a + * {@code PersonCard}. + */ + class DeliveryJobListViewCell extends ListCell { + @Override + protected void updateItem(DeliveryJob job, boolean empty) { + super.updateItem(job, empty); + + if (empty || job == null) { + setGraphic(null); + setText(null); + } else { + setGraphic(new DeliveryJobCard(job, getIndex() + 1, check -> { + onCheckHandler.ifPresent(handler -> handler.accept(check)); + }).getRoot()); + } + } + } + + private void copyToClipboard() { + final Clipboard clipboard = Clipboard.getSystemClipboard(); + final ClipboardContent url = new ClipboardContent(); + url.putString(deliveryJobListView.getSelectionModel().getSelectedItem().getJobId()); + clipboard.setContent(url); + } + + /** + * @return the number of item in listview. + */ + public int size() { + return deliveryJobListView.getItems().size(); + } + + /** + * Sets the select handler. + * + * @param handler + */ + public void setSelectHandler(BiConsumer handler) { + assert handler != null; + + this.onSelectHandler = Optional.of(handler); + } + + /** + * Sets the check status complete handler. + * + * @param handler + */ + public void setCheckHandler(Consumer handler) { + assert handler != null; + + this.onCheckHandler = Optional.of(handler); + } + + /** + * Sets the delete handler. + * + * @param handler + */ + public void setDeleteHandler(Consumer handler) { + assert handler != null; + + this.onDeleteHandler = Optional.of(handler); + } + + /** + * Sets the order event handlers. + * + * @param handler + */ + public void setOrderByHandler(BiFunction> handler) { + assert handler != null; + + sortHandler = Optional.of(handler); + orderOption.setVisible(true); + } + + /** + * Sets handler to filter + */ + public void setFilterHandler(Consumer con) { + assert con != null; + + filterOption.setVisible(true); + filterDelivered.setToggleGroup(toggleGroup); + filterPending.setToggleGroup(toggleGroup); + filterAll.setToggleGroup(toggleGroup); + toggleGroup.selectedToggleProperty().addListener((observable, oldVal, newVal) -> { + switch (((RadioMenuItem) newVal).getText()) { + case "Delivered": + con.accept(DeliveryFilterOption.COM); + break; + case "Pending": + con.accept(DeliveryFilterOption.PEN); + break; + default: + con.accept(DeliveryFilterOption.ALL); + break; + } + }); + } + + private void toggleOrder() { + isSortByAsc = !isSortByAsc; + if (isSortByAsc) { + orderIndicator.setRotate(0); + } else { + orderIndicator.setRotate(180); + } + } + + @FXML + void handleOrderByDelivered() { + logger.info("[handleOrderByDelivered]"); + sortHandler.ifPresent(fun -> { + toggleOrder(); + deliveryJobListView.setItems(fun.apply(DeliverySortOption.COM, isSortByAsc)); + }); + } + + @FXML + void handleOrderByEarning() { + logger.info("[handleOrderByEarning]"); + sortHandler.ifPresent(fun -> { + toggleOrder(); + deliveryJobListView.setItems(fun.apply(DeliverySortOption.EARN, isSortByAsc)); + }); + } + + @FXML + void handleOrderBySchedule() { + logger.info("[handleOrderBySchedule]"); + sortHandler.ifPresent(fun -> { + toggleOrder(); + deliveryJobListView.setItems(fun.apply(DeliverySortOption.DATE, isSortByAsc)); + }); + } +} diff --git a/src/main/java/seedu/address/ui/CommandBox.java b/src/main/java/seedu/address/ui/main/CommandBox.java similarity index 91% rename from src/main/java/seedu/address/ui/CommandBox.java rename to src/main/java/seedu/address/ui/main/CommandBox.java index 9e75478664b..a01cfad7578 100644 --- a/src/main/java/seedu/address/ui/CommandBox.java +++ b/src/main/java/seedu/address/ui/main/CommandBox.java @@ -1,4 +1,6 @@ -package seedu.address.ui; +package seedu.address.ui.main; + +import java.io.FileNotFoundException; import javafx.collections.ObservableList; import javafx.fxml.FXML; @@ -7,6 +9,7 @@ import seedu.address.logic.commands.CommandResult; import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.ui.UiPart; /** * The UI component that is responsible for receiving user command inputs. @@ -44,7 +47,7 @@ private void handleCommandEntered() { try { commandExecutor.execute(commandText); commandTextField.setText(""); - } catch (CommandException | ParseException e) { + } catch (CommandException | ParseException | FileNotFoundException e) { setStyleToIndicateCommandFailure(); } } @@ -79,7 +82,7 @@ public interface CommandExecutor { * * @see seedu.address.logic.Logic#execute(String) */ - CommandResult execute(String commandText) throws CommandException, ParseException; + CommandResult execute(String commandText) throws CommandException, ParseException, FileNotFoundException; } } diff --git a/src/main/java/seedu/address/ui/ResultDisplay.java b/src/main/java/seedu/address/ui/main/ResultDisplay.java similarity index 90% rename from src/main/java/seedu/address/ui/ResultDisplay.java rename to src/main/java/seedu/address/ui/main/ResultDisplay.java index 7d98e84eedf..70a3c3b7657 100644 --- a/src/main/java/seedu/address/ui/ResultDisplay.java +++ b/src/main/java/seedu/address/ui/main/ResultDisplay.java @@ -1,10 +1,11 @@ -package seedu.address.ui; +package seedu.address.ui.main; import static java.util.Objects.requireNonNull; import javafx.fxml.FXML; import javafx.scene.control.TextArea; import javafx.scene.layout.Region; +import seedu.address.ui.UiPart; /** * A ui for the status bar that is displayed at the header of the application. diff --git a/src/main/java/seedu/address/ui/StatusBarFooter.java b/src/main/java/seedu/address/ui/main/StatusBarFooter.java similarity index 91% rename from src/main/java/seedu/address/ui/StatusBarFooter.java rename to src/main/java/seedu/address/ui/main/StatusBarFooter.java index b577f829423..24febd46224 100644 --- a/src/main/java/seedu/address/ui/StatusBarFooter.java +++ b/src/main/java/seedu/address/ui/main/StatusBarFooter.java @@ -1,4 +1,4 @@ -package seedu.address.ui; +package seedu.address.ui.main; import java.nio.file.Path; import java.nio.file.Paths; @@ -6,6 +6,7 @@ import javafx.fxml.FXML; import javafx.scene.control.Label; import javafx.scene.layout.Region; +import seedu.address.ui.UiPart; /** * A ui for the status bar that is displayed at the footer of the application. diff --git a/src/main/java/seedu/address/ui/notification/BackgroundNotificationScheduler.java b/src/main/java/seedu/address/ui/notification/BackgroundNotificationScheduler.java new file mode 100644 index 00000000000..4c3bb0fc45b --- /dev/null +++ b/src/main/java/seedu/address/ui/notification/BackgroundNotificationScheduler.java @@ -0,0 +1,58 @@ +package seedu.address.ui.notification; + +import java.util.Calendar; +import java.util.Timer; + +/** + * Runs Java Timer to notify the user of upcoming jobs or reminders + */ +public class BackgroundNotificationScheduler extends Timer { + private final NotificationManager notificationManager; + + /** + * Creates a {@code BackgroundNotificationScheduler} with the given {@code NotificationManager}. + * @param notificationManager Notification Manager responsible for this app + */ + public BackgroundNotificationScheduler(NotificationManager notificationManager) { + this.notificationManager = notificationManager; + } + + /** + * Create a scheduled task with {@code Timer}. + * A {@code BackgroundReminderTask} is scheduled to check every minute + */ + public void backgroundReminder() { + Calendar now = Calendar.getInstance(); + now.add(Calendar.MINUTE, 1); + now.set(Calendar.SECOND, 0); + + new Timer().schedule(new BackgroundReminderTask(notificationManager), now.getTime(), 1000 * 60); + } + + /** + * Create a scheduled task with {@code Timer}. + * A {@code } is scheduled 20 minutes before the closet upcoming hour, and + * every subsequent hour + */ + public void backgroundSchedule() { + Calendar now = Calendar.getInstance(); + if (now.get(Calendar.MINUTE) >= 40) { + now.add(Calendar.HOUR_OF_DAY, 1); + } + now.set(Calendar.MINUTE, 40); + now.set(Calendar.SECOND, 0); + + new Timer().schedule(new BackgroundScheduleTask(notificationManager), now.getTime(), 1000 * 60 * 60); + } + + /** + * Create a scheduled task with {@code Timer}. + * A {@code BackgroundReminderTask} is scheduled from the closet upcoming hour, and + * every subsequent hour + */ + public void run() { + backgroundReminder(); + backgroundSchedule(); + } + +} diff --git a/src/main/java/seedu/address/ui/notification/BackgroundReminderTask.java b/src/main/java/seedu/address/ui/notification/BackgroundReminderTask.java new file mode 100644 index 00000000000..4ca47070da5 --- /dev/null +++ b/src/main/java/seedu/address/ui/notification/BackgroundReminderTask.java @@ -0,0 +1,29 @@ +package seedu.address.ui.notification; + +import java.util.TimerTask; + +import javafx.application.Platform; + +/** + * Runs a {@code TimerTask} to notify the user of upcoming reminders + */ +public class BackgroundReminderTask extends TimerTask { + private final NotificationManager notificationManager; + + /** + * Creates a {@code BackgroundReminderTask} with the given {@code NotificationManager}. + * @param notificationManager Notification Manager responsible for this app + */ + public BackgroundReminderTask(NotificationManager notificationManager) { + this.notificationManager = notificationManager; + } + + /** + * Changes the focus of the app to allow the notification to pop up even when the app is running in the + * background + */ + @Override + public void run() { + Platform.runLater(notificationManager::checkReminderList); + } +} diff --git a/src/main/java/seedu/address/ui/notification/BackgroundScheduleTask.java b/src/main/java/seedu/address/ui/notification/BackgroundScheduleTask.java new file mode 100644 index 00000000000..77f713c2548 --- /dev/null +++ b/src/main/java/seedu/address/ui/notification/BackgroundScheduleTask.java @@ -0,0 +1,29 @@ +package seedu.address.ui.notification; + +import java.util.TimerTask; + +import javafx.application.Platform; + +/** + * Runs a {@code TimerTask} to notify the user of upcoming schedules + */ +public class BackgroundScheduleTask extends TimerTask { + private final NotificationManager notificationManager; + + /** + * Creates a {@code BackgroundScheduleTask} with the given {@code NotificationManager}. + * @param notificationManager Notification Manager responsible for this app + */ + public BackgroundScheduleTask(NotificationManager notificationManager) { + this.notificationManager = notificationManager; + } + + /** + * Changes the focus of the app to allow the notification to pop up even when the app is running in the + * background + */ + @Override + public void run() { + Platform.runLater(notificationManager::checkNextSchedule); + } +} diff --git a/src/main/java/seedu/address/ui/notification/NotificationManager.java b/src/main/java/seedu/address/ui/notification/NotificationManager.java new file mode 100644 index 00000000000..ed1556041c8 --- /dev/null +++ b/src/main/java/seedu/address/ui/notification/NotificationManager.java @@ -0,0 +1,235 @@ +package seedu.address.ui.notification; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.Calendar; +import java.util.List; + +import org.controlsfx.control.Notifications; + +import javafx.application.Platform; +import javafx.geometry.Pos; +import javafx.util.Duration; +import seedu.address.logic.Logic; +import seedu.address.model.Model; +import seedu.address.model.jobs.DeliveryJob; +import seedu.address.model.jobs.DeliveryList; +import seedu.address.model.jobs.sorters.SortbyTimeAndEarn; +import seedu.address.model.reminder.Reminder; + +/** + * Provides basic functionality for using the Notification function + */ +public class NotificationManager { + //initialisation + private final Logic logic; + private final Model model; + private final Runnable reminderWindow; + private final Runnable timetableWindow; + + //notification settings + private final Duration duration = Duration.INDEFINITE; + + //reminders + private boolean isDismissed = false; + + /** + * Constructor to create a Notification. + * @param logic Stores information from the app + * @param runnableList Required for respective notifications to open their corresponding windows. + */ + public NotificationManager(Logic logic, List runnableList) { + this.logic = logic; + this.model = logic.getModel(); + this.reminderWindow = runnableList.get(0); + this.timetableWindow = runnableList.get(1); + } + + /** + * Method to display Reminders at the start of the app. + */ + public void checkReminderList() { + List reminderList = this.logic.getReminderList(); + LocalDateTime now = LocalDateTime.now(); + int shownReminderCount = 0; + int newReminderCount = 0; + for (Reminder r : reminderList) { + if (now.isAfter(r.getReminderDateTime())) { + if (r.getHasShown()) { + shownReminderCount++; + } else { + r.setHasShown(true); + newReminderCount++; + } + } + } + if (!isDismissed) { + final int i = shownReminderCount + newReminderCount; + Platform.runLater(() -> showReminderNotification(i)); + } else { + if (newReminderCount > 0) { + isDismissed = false; + final int i = newReminderCount + shownReminderCount; + Platform.runLater(() -> showReminderNotification(i)); + } + } + } + + private DeliveryList getDeliveryList() { + this.model.updateFocusDate(LocalDate.now()); + this.model.updateSortedDeliveryJobList(new SortbyTimeAndEarn()); + this.model.updateSortedDeliveryJobListByDate(); + this.model.updateWeekDeliveryJobList(LocalDate.now()); + return this.model.getSortedDeliveryJobListByDate().get(LocalDate.now()); + } + + /** + * Method to check the current schedule in the timetable for any ongoing jobs. Creates a notification should a job + * exists. + */ + public void checkNowSchedule() { + DeliveryList deliveryList = getDeliveryList(); + List jobList; + Calendar now = Calendar.getInstance(); + int hour = now.get(Calendar.HOUR_OF_DAY); + if (deliveryList != null) { + switch (hour) { + case 10: + jobList = deliveryList.get(0); + break; + case 11: + jobList = deliveryList.get(1); + break; + case 13: + jobList = deliveryList.get(2); + break; + case 14: + jobList = deliveryList.get(3); + break; + case 15: + jobList = deliveryList.get(4); + break; + default: + //nothing scheduled at the moment + jobList = null; + } + if (jobList != null) { + if (jobList.size() > 0) { + showScheduleNotification(jobList.size(), "now"); + } + } + } + } + + /** + * Method to check if there are any upcoming jobs in the next scheduled slot in the timetable. Creates a + * notification should an upcoming job exists. + */ + public void checkNextSchedule() { + DeliveryList deliveryList = getDeliveryList(); + List jobList; + Calendar now = Calendar.getInstance(); + int hour = now.get(Calendar.HOUR_OF_DAY); + if (deliveryList != null) { + switch (hour) { + case 10: + jobList = deliveryList.get(1); + break; + case 12: + jobList = deliveryList.get(2); + break; + case 13: + jobList = deliveryList.get(3); + break; + case 14: + jobList = deliveryList.get(4); + break; + default: + //nothing scheduled at the moment + jobList = null; + } + if (hour < 10) { + jobList = deliveryList.get(0); + } + if (jobList != null) { + if (jobList.size() > 0) { + showScheduleNotification(jobList.size(), "next"); + } + } + } + } + + /** + * Displays notification that shows users how many reminders that have at the moment + * @param i Number of active reminders + */ + public void showReminderNotification(int i) { + Notifications notif = Notifications.create() + .title("You have " + i + " active reminder(s)!") + .text("Click here to snooze and view your list of Reminder(s)") + .hideAfter(duration) + .position(Pos.TOP_RIGHT) + .hideCloseButton() + .onAction(event -> { + isDismissed = true; + reminderWindow.run(); + }); + Calendar now = Calendar.getInstance(); + int t = 60 - now.get(Calendar.SECOND); + notif.hideAfter(Duration.seconds(t)); + notif.showInformation(); + } + + /** + * Displays notification that shows users how many scheduled jobs they have at the moment or in the next timetable + * slot. + * @param i Number of jobs + * @param when indicates what string to show, depending on looking a current or next timetable slot + */ + public void showScheduleNotification(int i, String when) { + Calendar now = Calendar.getInstance(); + Notifications notif = Notifications.create(); + switch (when) { + case "now": + notif.title("You have " + i + " job(s) in this scheduled slot. (" + now.getTime() + ")"); + notif.hideAfter(Duration.minutes(60 - now.get(Calendar.MINUTE))); + break; + case "next": + notif.title("You have " + i + " upcoming job(s) from " + nextSlotTime()); + double d; + if (now.get(Calendar.MINUTE) >= 40) { + d = 40 + (60 - now.get(Calendar.MINUTE)); + } else { + d = 40 - now.get(Calendar.MINUTE); + } + notif.hideAfter(Duration.minutes(d)); + break; + default: + break; + } + notif.text("Click here to view more") + .hideAfter(duration) + .position(Pos.TOP_LEFT); + notif.onAction(event -> timetableWindow.run()); + notif.showInformation(); + } + private String nextSlotTime() { + Calendar now = Calendar.getInstance(); + int hour = now.get(Calendar.HOUR_OF_DAY); + if (hour < 10) { + return "10:00 - 11:00"; + } + switch (hour) { + case 10: + return "11:00 - 12:00"; + case 12: + return "13:00 - 14:00"; + case 13: + return "14:00 - 15:00"; + case 14: + return "15:00 - 16:00"; + default: + return ""; + } + } +} diff --git a/src/main/java/seedu/address/ui/person/AddressBookDialog.java b/src/main/java/seedu/address/ui/person/AddressBookDialog.java new file mode 100644 index 00000000000..2d827509805 --- /dev/null +++ b/src/main/java/seedu/address/ui/person/AddressBookDialog.java @@ -0,0 +1,192 @@ +package seedu.address.ui.person; + +import java.io.FileNotFoundException; +import java.util.function.Consumer; +import java.util.logging.Logger; + +import javafx.fxml.FXML; +import javafx.scene.layout.StackPane; +import javafx.stage.Stage; +import seedu.address.commons.core.LogsCenter; +import seedu.address.logic.Logic; +import seedu.address.logic.commands.CommandGroup; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.logic.commands.person.DeleteCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.person.Person; +import seedu.address.ui.HelpWindow; +import seedu.address.ui.UiPart; +import seedu.address.ui.main.CommandBox; +import seedu.address.ui.main.ResultDisplay; +import seedu.address.ui.main.StatusBarFooter; + +/** + * Displays contact list. + */ +public class AddressBookDialog extends UiPart { + + private static final String FXML = "AddressBookDialog.fxml"; + + private final Logger logger = LogsCenter.getLogger(getClass()); + private final Consumer selectHandler; + private final HelpWindow helpWindow; + private Stage primaryStage; + private Logic logic; + + private ResultDisplay resultDisplay; + private PersonListPanel personListPanel; + + @FXML + private StackPane commandBoxPlaceholder; + @FXML + private StackPane resultDisplayPlaceholder; + @FXML + private StackPane personListPanelPlaceholder; + @FXML + private StackPane statusbarPlaceholder; + + /** + * Creates a {@code AddressBookWindow} with the given {@code Stage} and {@code Logic}. + */ + public AddressBookDialog(Stage primaryStage, Logic logic) { + this(primaryStage, logic, (person) -> {}); + } + + /** + * Creates a {@code AddressBookWindow} with the given {@code Stage} and + * {@code Logic} with a select handler. + */ + public AddressBookDialog(Stage primaryStage, Logic logic, Consumer selectHandler) { + super(FXML, primaryStage); + this.helpWindow = new HelpWindow(); + + // Set dependencies + this.primaryStage = primaryStage; + this.logic = logic; + this.selectHandler = selectHandler; + } + + /** + * Creates a {@code AddressBookWindow} with the given {@code Stage} and + * {@code Logic} with a select handler and {@code HelpWindow}. + */ + public AddressBookDialog(Stage primaryStage, Logic logic, + Consumer selectHandler, + HelpWindow helpWindow) { + super(FXML, primaryStage); + this.helpWindow = helpWindow; + + // Set dependencies + this.primaryStage = primaryStage; + this.logic = logic; + this.selectHandler = selectHandler; + } + + /** + * Show address book window. + */ + public void show() { + logger.fine("Showing address book page"); + getRoot().show(); + getRoot().centerOnScreen(); + } + + /** + * Returns true if the address book window is currently being shown. + */ + public boolean isShowing() { + return getRoot().isShowing(); + } + + /** + * Hides the address book window. + */ + public void hide() { + getRoot().hide(); + } + + /** + * Focuses on the address book window. + */ + public void focus() { + getRoot().requestFocus(); + } + + /** + * Opens the help window or focuses on it if it's already opened. + */ + public void handleHelp() { + if (!helpWindow.isShowing()) { + helpWindow.show(); + logger.info("Opened help window."); + } else { + helpWindow.focus(); + } + } + + /** + * Fills inner parts and contents in all placeholders in the window. + */ + public void fillInnerParts() { + personListPanel = new PersonListPanel(logic.getFilteredPersonList(), (person) -> { + selectHandler.accept(person); + }, personIndex -> { + try { + logic.execute(new DeleteCommand(personIndex)); + } catch (ParseException | CommandException e) { + logger.warning(e.getMessage()); + } catch (FileNotFoundException e) { + logger.warning(e.getMessage()); + } + }); + personListPanelPlaceholder.getChildren().add(personListPanel.getRoot()); + + resultDisplay = new ResultDisplay(); + resultDisplayPlaceholder.getChildren().add(resultDisplay.getRoot()); + + StatusBarFooter statusBarFooter = new StatusBarFooter(logic.getAddressBookFilePath()); + statusbarPlaceholder.getChildren().add(statusBarFooter.getRoot()); + + CommandBox commandBox = new CommandBox(this::executeCommand); + commandBoxPlaceholder.getChildren().add(commandBox.getRoot()); + } + + @FXML + private void handleExit() { + primaryStage.hide(); + } + + /** + * Executes the command and returns the result. + * + * @see seedu.address.logic.Logic#execute(String) + */ + private CommandResult executeCommand(String commandText) + throws CommandException, ParseException, FileNotFoundException { + try { + CommandResult commandResult = logic.execute(commandText, + g -> g.equals(CommandGroup.PERSON) || g.equals(CommandGroup.GENERAL)); + logger.info("Result: " + commandResult.getFeedbackToUser()); + resultDisplay.setFeedbackToUser(commandResult.getFeedbackToUser()); + + if (commandResult.isExit()) { + handleExit(); + } + + if (commandResult.isShowHelp()) { + handleHelp(); + } + return commandResult; + } catch (CommandException | ParseException e) { + logger.info("Invalid command: " + commandText); + resultDisplay.setFeedbackToUser(e.getMessage()); + throw e; + } catch (FileNotFoundException e) { + logger.info("File not found: " + commandText); + resultDisplay.setFeedbackToUser(e.getMessage()); + throw e; + } + } + +} diff --git a/src/main/java/seedu/address/ui/PersonCard.java b/src/main/java/seedu/address/ui/person/PersonCard.java similarity index 75% rename from src/main/java/seedu/address/ui/PersonCard.java rename to src/main/java/seedu/address/ui/person/PersonCard.java index 7fc927bc5d9..4d03329da6f 100644 --- a/src/main/java/seedu/address/ui/PersonCard.java +++ b/src/main/java/seedu/address/ui/person/PersonCard.java @@ -1,4 +1,4 @@ -package seedu.address.ui; +package seedu.address.ui.person; import java.util.Comparator; @@ -8,6 +8,7 @@ import javafx.scene.layout.HBox; import javafx.scene.layout.Region; import seedu.address.model.person.Person; +import seedu.address.ui.UiPart; /** * An UI component that displays information of a {@code Person}. @@ -31,6 +32,8 @@ public class PersonCard extends UiPart { @FXML private Label name; @FXML + private Label personId; + @FXML private Label id; @FXML private Label phone; @@ -49,6 +52,7 @@ public PersonCard(Person person, int displayedIndex) { this.person = person; id.setText(displayedIndex + ". "); name.setText(person.getName().fullName); + personId.setText(String.format("(%s)", person.getPersonId())); phone.setText(person.getPhone().value); address.setText(person.getAddress().value); email.setText(person.getEmail().value); @@ -57,6 +61,23 @@ public PersonCard(Person person, int displayedIndex) { .forEach(tag -> tags.getChildren().add(new Label(tag.tagName))); } + /** + * PersonCard + * + * @param person + * @param displayedPrefix + */ + public PersonCard(Person person, String displayedPrefix) { + super(FXML); + this.person = person; + id.setText(displayedPrefix + ": "); + name.setText(person.getName().fullName); + phone.setText(person.getPhone().value); + address.setText(person.getAddress().value); + email.setText(person.getEmail().value); + personId.setText(String.format("(%s)", person.getPersonId())); + } + @Override public boolean equals(Object other) { // short circuit if same object diff --git a/src/main/java/seedu/address/ui/PersonListPanel.java b/src/main/java/seedu/address/ui/person/PersonListPanel.java similarity index 51% rename from src/main/java/seedu/address/ui/PersonListPanel.java rename to src/main/java/seedu/address/ui/person/PersonListPanel.java index f4c501a897b..0baa594da8a 100644 --- a/src/main/java/seedu/address/ui/PersonListPanel.java +++ b/src/main/java/seedu/address/ui/person/PersonListPanel.java @@ -1,14 +1,21 @@ -package seedu.address.ui; +package seedu.address.ui.person; +import java.util.function.Consumer; import java.util.logging.Logger; import javafx.collections.ObservableList; +import javafx.event.Event; +import javafx.event.EventHandler; import javafx.fxml.FXML; import javafx.scene.control.ListCell; import javafx.scene.control.ListView; +import javafx.scene.input.KeyCode; +import javafx.scene.input.KeyEvent; import javafx.scene.layout.Region; import seedu.address.commons.core.LogsCenter; +import seedu.address.commons.core.index.Index; import seedu.address.model.person.Person; +import seedu.address.ui.UiPart; /** * Panel containing the list of persons. @@ -24,9 +31,37 @@ public class PersonListPanel extends UiPart { * Creates a {@code PersonListPanel} with the given {@code ObservableList}. */ public PersonListPanel(ObservableList personList) { + this(personList, (person) -> {}, (personIndex) -> {}); + } + + /** + * Creates a {@code PersonListPanel} with the given {@code ObservableList} with a delete handler. + */ + public PersonListPanel(ObservableList personList, Consumer selectHandler, + Consumer deleteHandler) { super(FXML); personListView.setItems(personList); personListView.setCellFactory(listView -> new PersonListViewCell()); + + personListView.setOnMouseClicked(new EventHandler() { + @Override + public void handle(Event event) { + selectHandler.accept(personListView.getSelectionModel().getSelectedItem()); + } + }); + + personListView.setOnKeyPressed(new EventHandler() { + + @Override + public void handle(KeyEvent event) { + if (event.getCode().equals(KeyCode.DELETE)) { + logger.info(personListView.getSelectionModel().getSelectedItem().getPersonId() + " for Deletion"); + deleteHandler.accept(Index.fromZeroBased(personListView.getSelectionModel().getSelectedIndex())); + return; + } + } + + }); } /** @@ -45,5 +80,4 @@ protected void updateItem(Person person, boolean empty) { } } } - } diff --git a/src/main/java/seedu/address/ui/timetable/CompleteWindow.java b/src/main/java/seedu/address/ui/timetable/CompleteWindow.java new file mode 100644 index 00000000000..efda6379ca6 --- /dev/null +++ b/src/main/java/seedu/address/ui/timetable/CompleteWindow.java @@ -0,0 +1,118 @@ +package seedu.address.ui.timetable; + +import java.util.logging.Logger; + +import javafx.collections.ListChangeListener; +import javafx.fxml.FXML; +import javafx.scene.layout.StackPane; +import javafx.scene.text.Text; +import javafx.stage.Stage; +import seedu.address.commons.core.LogsCenter; +import seedu.address.logic.Logic; +import seedu.address.model.jobs.DeliveryJob; +import seedu.address.ui.UiPart; +import seedu.address.ui.main.ResultDisplay; +import seedu.address.ui.main.StatusBarFooter; + +/** + * Displays list of completed jobs. + */ +public class CompleteWindow extends UiPart { + + private static final String FXML = "CompletedJobWindow.fxml"; + + private final Logger logger = LogsCenter.getLogger(getClass()); + private Stage primaryStage; + private Logic logic; + + private ResultDisplay resultDisplay; + private UnscheduledDeliveryJobListPanel jobListPanel; + + @FXML + private Text numberOfJobs; + @FXML + private StackPane jobListPanelPlaceholder; + @FXML + private StackPane statusbarPlaceholder; + + /** + * Creates a {@code CompleteWindow} with the given {@code Stage} and {@code Logic}. + */ + public CompleteWindow(Stage primaryStage, Logic logic) { + super(FXML, primaryStage); + this.primaryStage = primaryStage; + this.logic = logic; + } + + /** + * Show complete window. + */ + public void show() { + logger.fine("Showing window of completed jobs"); + getRoot().show(); + getRoot().centerOnScreen(); + } + + /** + * Returns true if the complete window is currently being shown. + */ + public boolean isShowing() { + return getRoot().isShowing(); + } + + /** + * Hides the Complete window. + */ + public void hide() { + getRoot().hide(); + } + + /** + * Focuses on the Complete window. + */ + public void focus() { + getRoot().requestFocus(); + } + + /** + * Fills inner parts and content of complete window. + */ + public void fillInnerParts() { + jobListPanel = new UnscheduledDeliveryJobListPanel(logic.getCompletedDeliveryJobList()); + int jobListLen = logic.getCompletedDeliveryJobList().size(); + numberOfJobs.setText(String.format("Total: %d job(s)", jobListLen)); + jobListPanelPlaceholder.getChildren().add(jobListPanel.getRoot()); + + StatusBarFooter statusBarFooter = new StatusBarFooter(logic.getAddressBookFilePath()); + statusbarPlaceholder.getChildren().add(statusBarFooter.getRoot()); + + logic.getFilteredDeliveryJobList().addListener(new ListChangeListener() { + @Override + public void onChanged(Change c) { + refreshCompletedJobList(); + } + }); + } + + /** + * Refreshes completed job list with updated job list + */ + private void refreshCompletedJobList() { + logger.info("[Complete Window] Refresh list of completed jobs"); + jobListPanelPlaceholder.getChildren().clear(); + jobListPanel = new UnscheduledDeliveryJobListPanel(logic.getCompletedDeliveryJobList()); + int jobListLen = logic.getCompletedDeliveryJobList().size(); + numberOfJobs.setText(String.format("Total: %d job(s)", jobListLen)); + jobListPanelPlaceholder.getChildren().add(jobListPanel.getRoot()); + + } + + /** + * Closes complete window. + */ + @FXML + private void handleExit() { + primaryStage.hide(); + } + +} diff --git a/src/main/java/seedu/address/ui/timetable/DayDeliveryJobCard.java b/src/main/java/seedu/address/ui/timetable/DayDeliveryJobCard.java new file mode 100644 index 00000000000..14f23d53417 --- /dev/null +++ b/src/main/java/seedu/address/ui/timetable/DayDeliveryJobCard.java @@ -0,0 +1,92 @@ +package seedu.address.ui.timetable; + +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Region; +import seedu.address.logic.Logic; +import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.model.jobs.DeliveryJob; +import seedu.address.ui.UiPart; + +/** + * An UI component that displays information of a {@code DayDeliveryJobCard}. + */ +public class DayDeliveryJobCard extends UiPart { + + private static final String FXML = "DayDeliveryJobListCard.fxml"; + + /** + * Note: Certain keywords such as "location" and "resources" are reserved keywords in JavaFX. + * As a consequence, UI elements' variable names cannot be set to such keywords + * or an exception will be thrown by JavaFX during runtime. + * + * @see The issue on AddressBook level 4 + */ + + private final DeliveryJob job; + private final Logic logic; + + @FXML + private HBox cardPane; + @FXML + private Label jobID; + @FXML + private Label id; + @FXML + private Label receipient; + @FXML + private Label earn; + @FXML + private Label address; + + /** + * Creates a {@code DayDeliveryJobCard} with the given {@code Logic}, + * {@code DeliveryJob} and index to display. + */ + public DayDeliveryJobCard(Logic logic, DeliveryJob job, int displayedIndex) { + super(FXML); + this.job = job; + this.logic = logic; + ReadOnlyAddressBook addressBook = logic.getAddressBook(); + + id.setText(displayedIndex + ". "); + jobID.setText(job.getJobId()); + + if (job.getRecipientId() != null) { + receipient.setText("To: " + job.getRecipientId()); + } else { + receipient.setText("To: N.A."); + } + + addressBook.getPersonById(job.getRecipientId()).ifPresentOrElse(per -> { + address.setText("@" + per.getAddress().toString()); + }, () -> { + address.setText("Dest: N.A."); + }); + + if (job.getEarning().isPresent()) { + earn.setText("Earn: +$" + job.getEarning().get()); + } else { + earn.setText("Earn: +$0.0"); + } + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof DayDeliveryJobCard)) { + return false; + } + + // state check + DayDeliveryJobCard card = (DayDeliveryJobCard) other; + return id.getText().equals(card.id.getText()) + && job.equals(card.job); + } +} diff --git a/src/main/java/seedu/address/ui/timetable/DayJobListPanel.java b/src/main/java/seedu/address/ui/timetable/DayJobListPanel.java new file mode 100644 index 00000000000..52c19d0ef78 --- /dev/null +++ b/src/main/java/seedu/address/ui/timetable/DayJobListPanel.java @@ -0,0 +1,99 @@ +package seedu.address.ui.timetable; + +import java.util.ArrayList; +import java.util.logging.Logger; + +import javafx.collections.FXCollections; +import javafx.fxml.FXML; +import javafx.scene.control.ListCell; +import javafx.scene.control.ListView; +import javafx.scene.control.ScrollPane; +import javafx.scene.layout.Region; +import seedu.address.commons.core.LogsCenter; +import seedu.address.logic.Logic; +import seedu.address.model.jobs.DeliveryJob; +import seedu.address.model.jobs.DeliveryList; +import seedu.address.ui.UiPart; + +/** + * Panel containing the list of jobs divided into slots in day. + */ +public class DayJobListPanel extends UiPart { + private static final String FXML = "DayJobListPane.fxml"; + private final Logger logger = LogsCenter.getLogger(DayJobListPanel.class); + private final Logic logic; + + @FXML + private ListView jobList1; + + @FXML + private ListView jobList2; + + @FXML + private ListView jobList3; + + @FXML + private ListView jobList4; + + @FXML + private ListView jobList5; + + @FXML + private ListView jobList6; + + + /** + * Creates a {@code DayJobListPanel} + * @param logic logic + * @param jobListInDay observable list of jobs + */ + public DayJobListPanel(Logic logic, DeliveryList jobListInDay) { + super(FXML); + this.logic = logic; + + addJobListToSlot(jobList1, jobListInDay.get(0)); + addJobListToSlot(jobList2, jobListInDay.get(1)); + addJobListToSlot(jobList3, jobListInDay.get(2)); + addJobListToSlot(jobList4, jobListInDay.get(3)); + addJobListToSlot(jobList5, jobListInDay.get(4)); + addJobListToSlot(jobList6, jobListInDay.get(5)); + } + + private void addScrollPaneToAll() { + ScrollPane pane = new ScrollPane(); + + } + + private void addJobListToSlot(ListView jobSlot, ArrayList jobList) { + if (jobList != null) { + jobSlot.setItems(FXCollections.observableArrayList(jobList)); + jobSlot.setCellFactory(listView -> new DayJobListViewCell()); + } + } + + /** + * Custom {@code ListCell} that displays the graphics of a {@code job} using a + * {@code DayDeliveryJobCard}. + */ + class DayJobListViewCell extends ListCell { + @Override + protected void updateItem(DeliveryJob job, boolean empty) { + super.updateItem(job, empty); + + if (empty || job == null) { + setGraphic(null); + setText(null); + setStyle("-fx-background-color: transparent;"); + } else { + setGraphic(new DayDeliveryJobCard(logic, job, getIndex() + 1).getRoot()); + } + + if (getIndex() % 2 == 1) { + setStyle("-fx-background-color: DARKCYAN;"); + } else { + setStyle("-fx-background-color: GREY;"); + } + } + } + +} diff --git a/src/main/java/seedu/address/ui/timetable/DayOfMonthPanel.java b/src/main/java/seedu/address/ui/timetable/DayOfMonthPanel.java new file mode 100644 index 00000000000..c3ca3b46625 --- /dev/null +++ b/src/main/java/seedu/address/ui/timetable/DayOfMonthPanel.java @@ -0,0 +1,114 @@ +package seedu.address.ui.timetable; + +import java.time.LocalDate; +import java.util.logging.Logger; + +import javafx.fxml.FXML; +import javafx.geometry.Pos; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Region; +import javafx.scene.paint.Color; +import javafx.scene.text.Font; +import javafx.scene.text.Text; +import javafx.stage.Stage; +import seedu.address.commons.core.LogsCenter; +import seedu.address.logic.Logic; +import seedu.address.ui.UiPart; + + +/** + * Panel containing the days of month (11th, 12th,...) in the week + */ +public class DayOfMonthPanel extends UiPart { + + private static final String FXML = "DayOfMonthPanel.fxml"; + private final Logger logger = LogsCenter.getLogger(getClass()); + + private LocalDate focusDate; + private Stage primaryStage; + private Logic logic; + + private Text day1; + private Text day2; + private Text day3; + private Text day4; + private Text day5; + private Text day6; + private Text day7; + + @FXML + private HBox dayOfMonthPanel; + + /** + * Creates a {@code DayofMonthPanel} with the given {@code focusDate}, {@code Logic} + * and {@code Stage}. + */ + public DayOfMonthPanel(LocalDate focusDate, Logic logic, Stage primaryStage) { + super(FXML); + + // Set dependencies + this.focusDate = focusDate; + this.logic = logic; + this.primaryStage = primaryStage; + + int focusDayOfWeek = focusDate.getDayOfWeek().getValue(); + setAllDateText(focusDayOfWeek); + setAllFont(12.0); + setAllTextColorWhite(); + + dayOfMonthPanel.getChildren().addAll(day1, day2, day3, day4, day5, day6, day7); + dayOfMonthPanel.setSpacing((primaryStage.getWidth() - 200) / 6); + dayOfMonthPanel.setAlignment(Pos.CENTER); + } + + /** + * Sets all text font to specific font size + * @param fontSize + */ + private void setAllFont(double fontSize) { + day1.setFont(new Font(fontSize)); + day2.setFont(new Font(fontSize)); + day3.setFont(new Font(fontSize)); + day4.setFont(new Font(fontSize)); + day5.setFont(new Font(fontSize)); + day6.setFont(new Font(fontSize)); + day7.setFont(new Font(fontSize)); + + } + + /** + * Sets all text color to white + */ + private void setAllTextColorWhite() { + day1.setFill(Color.WHITE); + day2.setFill(Color.WHITE); + day3.setFill(Color.WHITE); + day4.setFill(Color.WHITE); + day5.setFill(Color.WHITE); + day6.setFill(Color.WHITE); + day7.setFill(Color.WHITE); + } + + /** + * Sets text in day of month for all days in week + * @param focusDayOfWeek day in week of focus date + */ + private void setAllDateText(int focusDayOfWeek) { + int day1Text = focusDate.plusDays(1 - focusDayOfWeek).getDayOfMonth(); + int day2Text = focusDate.plusDays(2 - focusDayOfWeek).getDayOfMonth(); + int day3Text = focusDate.plusDays(3 - focusDayOfWeek).getDayOfMonth(); + int day4Text = focusDate.plusDays(4 - focusDayOfWeek).getDayOfMonth(); + int day5Text = focusDate.plusDays(5 - focusDayOfWeek).getDayOfMonth(); + int day6Text = focusDate.plusDays(6 - focusDayOfWeek).getDayOfMonth(); + int day7Text = focusDate.plusDays(7 - focusDayOfWeek).getDayOfMonth(); + + day1 = new Text(String.valueOf(day1Text)); + day2 = new Text(String.valueOf(day2Text)); + day3 = new Text(String.valueOf(day3Text)); + day4 = new Text(String.valueOf(day4Text)); + day5 = new Text(String.valueOf(day5Text)); + day6 = new Text(String.valueOf(day6Text)); + day7 = new Text(String.valueOf(day7Text)); + + } +} diff --git a/src/main/java/seedu/address/ui/timetable/DayOfWeekPanel.java b/src/main/java/seedu/address/ui/timetable/DayOfWeekPanel.java new file mode 100644 index 00000000000..23e00872ded --- /dev/null +++ b/src/main/java/seedu/address/ui/timetable/DayOfWeekPanel.java @@ -0,0 +1,107 @@ +package seedu.address.ui.timetable; + +import java.util.logging.Logger; + +import javafx.fxml.FXML; +import javafx.geometry.Pos; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Region; +import javafx.scene.paint.Color; +import javafx.scene.text.Font; +import javafx.scene.text.Text; +import javafx.stage.Stage; +import seedu.address.commons.core.LogsCenter; +import seedu.address.logic.Logic; +import seedu.address.ui.UiPart; + +/** + * Panel containing day of week (Mon, Tue, Wed,..) + */ +public class DayOfWeekPanel extends UiPart { + + private static final String FXML = "DayOfWeekPanel.fxml"; + private final Logger logger = LogsCenter.getLogger(getClass()); + private Stage primaryStage; + private Logic logic; + + private Text mon; + private Text tue; + private Text wed; + private Text thur; + private Text fri; + private Text sat; + private Text sun; + + @FXML + private HBox dayOfWeekPanel; + + /** + * Creates a {@code DayofWeekPanel} with the given {@code Logic} and {@code Stage}. + */ + public DayOfWeekPanel(Logic logic, Stage primaryStage) { + super(FXML); + + // Set dependencies + this.logic = logic; + this.primaryStage = primaryStage; + + mon = new Text("Mon"); + tue = new Text("Tue"); + wed = new Text("Wed"); + thur = new Text("Thurs"); + fri = new Text("Fri"); + sat = new Text("Sat"); + sun = new Text("Sun"); + + setAllFont(18.0); + setAllWrappingWidth(60.0); + setAllTextColorWhite(); + + dayOfWeekPanel.getChildren().addAll(mon, tue, wed, thur, fri, sat, sun); + dayOfWeekPanel.setSpacing((primaryStage.getWidth() - 420) / 7); + dayOfWeekPanel.setAlignment(Pos.CENTER); + } + + /** + * Sets all text font to a specific size + * @param fontSize + */ + private void setAllFont(double fontSize) { + mon.setFont(new Font(fontSize)); + tue.setFont(new Font(fontSize)); + wed.setFont(new Font(fontSize)); + thur.setFont(new Font(fontSize)); + fri.setFont(new Font(fontSize)); + sat.setFont(new Font(fontSize)); + sun.setFont(new Font(fontSize)); + + } + + /** + * Sets all text color to white + */ + private void setAllTextColorWhite() { + mon.setFill(Color.WHITE); + tue.setFill(Color.WHITE); + wed.setFill(Color.WHITE); + thur.setFill(Color.WHITE); + fri.setFill(Color.WHITE); + sat.setFill(Color.WHITE); + sun.setFill(Color.WHITE); + } + + /** + * Sets all text with a specific wrapping width + * @param widthSize + */ + private void setAllWrappingWidth(double widthSize) { + mon.setWrappingWidth(widthSize); + tue.setWrappingWidth(widthSize); + wed.setWrappingWidth(widthSize); + thur.setWrappingWidth(widthSize); + fri.setWrappingWidth(widthSize); + sat.setWrappingWidth(widthSize); + sun.setWrappingWidth(widthSize); + + } +} diff --git a/src/main/java/seedu/address/ui/timetable/TimetableDetailPanel.java b/src/main/java/seedu/address/ui/timetable/TimetableDetailPanel.java new file mode 100644 index 00000000000..78fbf8f81a0 --- /dev/null +++ b/src/main/java/seedu/address/ui/timetable/TimetableDetailPanel.java @@ -0,0 +1,91 @@ +package seedu.address.ui.timetable; + +import java.time.LocalDate; +import java.util.logging.Logger; + +import javafx.fxml.FXML; +import javafx.geometry.Pos; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Region; +import javafx.scene.paint.Color; +import javafx.scene.text.Font; +import javafx.scene.text.Text; +import javafx.stage.Stage; +import seedu.address.commons.core.LogsCenter; +import seedu.address.logic.Logic; +import seedu.address.ui.UiPart; + +/** + * Panel containing timetable detail + */ +public class TimetableDetailPanel extends UiPart { + + private static final String FXML = "TimetablePanel.fxml"; + private final Logger logger = LogsCenter.getLogger(getClass()); + private Stage primaryStage; + private Logic logic; + private LocalDate focusDate; + + @FXML + private HBox monthYearPanel; + + @FXML + private HBox dayOfWeekPanel; + + @FXML + private HBox dayOfMonthPanel; + + @FXML + private HBox jobListPanel; + + /** + * Creates a {@code TimetableDetailPanel} with the given {@code Stage} and {@code Logic}. + */ + public TimetableDetailPanel(LocalDate focusDate, Logic logic, Stage primaryStage) { + super(FXML); + + // Set dependencies + this.logic = logic; + this.primaryStage = primaryStage; + this.focusDate = focusDate; + fillInnerParts(); + } + + /** + * Fills up all the placeholders of this window. + */ + void fillInnerParts() { + focusDate = logic.getFocusDate(); + + //Get year and month of week + setMonthYearPanel(); + + logic.updateSortedDeliveryJobListByDate(); + logic.setWeekDeliveryJobList(focusDate); + + dayOfWeekPanel.getChildren().add(new DayOfWeekPanel(logic, primaryStage).getRoot()); + dayOfMonthPanel.getChildren().add(new DayOfMonthPanel(focusDate, logic, primaryStage).getRoot()); + jobListPanel.getChildren().add(new WeekJobListPanel(logic, primaryStage).getRoot()); + + logger.fine("[Timetable Detail] Filled in timetable detail with focus date as " + focusDate.toString()); + + } + + /** + * Sets up month year in timetable based on focus date + */ + private void setMonthYearPanel() { + Text year = new Text(String.valueOf(focusDate.getYear())); + Text month = new Text(String.valueOf(focusDate.getMonth())); + year.setFont(new Font(24)); + year.setFill(Color.WHITE); + month.setFont(new Font(24)); + month.setFill(Color.WHITE); + + year.setText(String.valueOf(focusDate.getYear())); + month.setText(String.valueOf(focusDate.getMonth())); + monthYearPanel.getChildren().addAll(year, month); + monthYearPanel.setSpacing(20); + monthYearPanel.setAlignment(Pos.CENTER); + } +} diff --git a/src/main/java/seedu/address/ui/timetable/UnscheduleWindow.java b/src/main/java/seedu/address/ui/timetable/UnscheduleWindow.java new file mode 100644 index 00000000000..4ee286d7780 --- /dev/null +++ b/src/main/java/seedu/address/ui/timetable/UnscheduleWindow.java @@ -0,0 +1,118 @@ +package seedu.address.ui.timetable; + +import java.util.logging.Logger; + +import javafx.collections.ListChangeListener; +import javafx.fxml.FXML; +import javafx.scene.layout.StackPane; +import javafx.scene.text.Text; +import javafx.stage.Stage; +import seedu.address.commons.core.LogsCenter; +import seedu.address.logic.Logic; +import seedu.address.model.jobs.DeliveryJob; +import seedu.address.ui.UiPart; +import seedu.address.ui.main.ResultDisplay; +import seedu.address.ui.main.StatusBarFooter; + +/** + * Controller for unschedule window. + */ +public class UnscheduleWindow extends UiPart { + + private static final String FXML = "UnscheduleWindow.fxml"; + private final Logger logger = LogsCenter.getLogger(getClass()); + + private Stage primaryStage; + private Logic logic; + + private ResultDisplay resultDisplay; + private UnscheduledDeliveryJobListPanel jobListPanel; + + @FXML + private Text numberOfJobs; + + @FXML + private StackPane jobListPanelPlaceholder; + @FXML + private StackPane statusbarPlaceholder; + + /** + * Creates a {@code AddressBookWindow} with the given {@code Stage} and {@code Logic}. + */ + public UnscheduleWindow(Stage primaryStage, Logic logic) { + super(FXML, primaryStage); + this.primaryStage = primaryStage; + this.logic = logic; + } + + + /** + * Show main window. + */ + public void show() { + logger.fine("Showing unscheduled window page"); + getRoot().show(); + getRoot().centerOnScreen(); + } + + /** + * Returns true if the unscheduled window is currently being shown. + */ + public boolean isShowing() { + return getRoot().isShowing(); + } + + /** + * Hides the unscheduled window. + */ + public void hide() { + getRoot().hide(); + } + + /** + * Focuses on the unscheduled window. + */ + public void focus() { + getRoot().requestFocus(); + } + + /** + * Fills Inner Parts and content of unscheduled window. + */ + public void fillInnerParts() { + jobListPanel = new UnscheduledDeliveryJobListPanel(logic.getUnscheduledDeliveryJobList()); + int jobListLen = logic.getUnscheduledDeliveryJobList().size(); + numberOfJobs.setText(String.format("Total: %d job(s)", jobListLen)); + jobListPanelPlaceholder.getChildren().add(jobListPanel.getRoot()); + + StatusBarFooter statusBarFooter = new StatusBarFooter(logic.getAddressBookFilePath()); + statusbarPlaceholder.getChildren().add(statusBarFooter.getRoot()); + + logic.getFilteredDeliveryJobList().addListener(new ListChangeListener() { + @Override + public void onChanged(Change c) { + refreshUnscheduledJobList(); + } + }); + } + + private void refreshUnscheduledJobList() { + logger.info("[Unscheduled Window] Refresh list of unscheduled jobs"); + jobListPanelPlaceholder.getChildren().clear(); + jobListPanel = new UnscheduledDeliveryJobListPanel(logic.getUnscheduledDeliveryJobList()); + int jobListLen = logic.getUnscheduledDeliveryJobList().size(); + numberOfJobs.setText(String.format("Total: %d job(s)", jobListLen)); + jobListPanelPlaceholder.getChildren().add(jobListPanel.getRoot()); + } + + + + /** + * Exits unscheduled window + */ + @FXML + private void handleExit() { + primaryStage.hide(); + } + +} diff --git a/src/main/java/seedu/address/ui/timetable/UnscheduledDeliveryJobListPanel.java b/src/main/java/seedu/address/ui/timetable/UnscheduledDeliveryJobListPanel.java new file mode 100644 index 00000000000..df053b46012 --- /dev/null +++ b/src/main/java/seedu/address/ui/timetable/UnscheduledDeliveryJobListPanel.java @@ -0,0 +1,60 @@ +package seedu.address.ui.timetable; + +import java.util.logging.Logger; + +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.scene.control.ListCell; +import javafx.scene.control.ListView; +import javafx.scene.layout.Region; +import seedu.address.commons.core.LogsCenter; +import seedu.address.model.jobs.DeliveryJob; +import seedu.address.ui.UiPart; +import seedu.address.ui.jobs.DeliveryJobCard; + +/** + * Panel containing the list of unscheduled jobs. + */ +public class UnscheduledDeliveryJobListPanel extends UiPart { + private static final String FXML = "UnscheduledDeliveryJobListPanel.fxml"; + private final Logger logger = LogsCenter.getLogger(UnscheduledDeliveryJobListPanel.class); + + @FXML + private ListView deliveryJobListView; + + /** + * Creates a {@code UnscheduledDeliveryJobListPanel} with the given {@code ObservableList}. + */ + public UnscheduledDeliveryJobListPanel(ObservableList jobList) { + super(FXML); + deliveryJobListView.setItems(jobList); + deliveryJobListView.setCellFactory(listView -> new DeliveryJobListViewCell()); + logger.info("Updated and showed list of unscheduled jobs"); + } + + /** + * Custom {@code ListCell} that displays the graphics of a {@code job} using a + * {@code DeliveryJobCard}. + */ + class DeliveryJobListViewCell extends ListCell { + @Override + protected void updateItem(DeliveryJob job, boolean empty) { + super.updateItem(job, empty); + + if (empty || job == null) { + setGraphic(null); + setText(null); + } else { + setGraphic(new DeliveryJobCard(job, getIndex() + 1, (card) -> {}).getRoot()); + } + } + } + + /** + * Returns total number of jobs listed. + */ + public int size() { + return deliveryJobListView.getItems().size(); + } + +} diff --git a/src/main/java/seedu/address/ui/timetable/WeekJobListPanel.java b/src/main/java/seedu/address/ui/timetable/WeekJobListPanel.java new file mode 100644 index 00000000000..2eb9e6d4cfd --- /dev/null +++ b/src/main/java/seedu/address/ui/timetable/WeekJobListPanel.java @@ -0,0 +1,122 @@ +package seedu.address.ui.timetable; + +import java.util.logging.Logger; + +import javafx.fxml.FXML; +import javafx.geometry.Pos; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Region; +import javafx.scene.layout.StackPane; +import javafx.stage.Stage; +import seedu.address.commons.core.LogsCenter; +import seedu.address.logic.Logic; +import seedu.address.model.jobs.DeliveryList; +import seedu.address.ui.UiPart; + +/** + * Panel containing job list in the week + */ +public class WeekJobListPanel extends UiPart { + + private static final String FXML = "WeekJobListPanel.fxml"; + private final Logger logger = LogsCenter.getLogger(getClass()); + private Stage primaryStage; + private Logic logic; + + private StackPane deliveryJobListPanelPlaceholder1; + private StackPane deliveryJobListPanelPlaceholder2; + private StackPane deliveryJobListPanelPlaceholder3; + private StackPane deliveryJobListPanelPlaceholder4; + private StackPane deliveryJobListPanelPlaceholder5; + private StackPane deliveryJobListPanelPlaceholder6; + private StackPane deliveryJobListPanelPlaceholder7; + + @FXML + private HBox jobListPanel; + + /** + * Creates a {@code WeekJobListPanel} with the given {@code Stage} and {@code Logic}. + */ + public WeekJobListPanel(Logic logic, Stage primaryStage) { + super(FXML); + + // Set dependencies + this.logic = logic; + this.primaryStage = primaryStage; + + deliveryJobListPanelPlaceholder1 = new StackPane(); + deliveryJobListPanelPlaceholder2 = new StackPane(); + deliveryJobListPanelPlaceholder3 = new StackPane(); + deliveryJobListPanelPlaceholder4 = new StackPane(); + deliveryJobListPanelPlaceholder5 = new StackPane(); + deliveryJobListPanelPlaceholder6 = new StackPane(); + deliveryJobListPanelPlaceholder7 = new StackPane(); + + setAllPlaceholderBackgroundColor("DARKCYAN"); + setAllPalceholderPrefWidth((primaryStage.getWidth() - 96) / 7); + addAllPlaceholderJobs(); + + + jobListPanel.getChildren().addAll(deliveryJobListPanelPlaceholder1, deliveryJobListPanelPlaceholder2, + deliveryJobListPanelPlaceholder3, deliveryJobListPanelPlaceholder4, + deliveryJobListPanelPlaceholder5, deliveryJobListPanelPlaceholder6, + deliveryJobListPanelPlaceholder7); + jobListPanel.setSpacing(12); + jobListPanel.setAlignment(Pos.CENTER); + } + + /** + * Sets background color for all job list panel placeholders for all days in week + * @param backgroundColor + */ + private void setAllPlaceholderBackgroundColor(String backgroundColor) { + deliveryJobListPanelPlaceholder1.setStyle("-fx-background-color: " + backgroundColor); + deliveryJobListPanelPlaceholder2.setStyle("-fx-background-color: " + backgroundColor); + deliveryJobListPanelPlaceholder3.setStyle("-fx-background-color: " + backgroundColor); + deliveryJobListPanelPlaceholder4.setStyle("-fx-background-color: " + backgroundColor); + deliveryJobListPanelPlaceholder5.setStyle("-fx-background-color: " + backgroundColor); + deliveryJobListPanelPlaceholder6.setStyle("-fx-background-color: " + backgroundColor); + deliveryJobListPanelPlaceholder7.setStyle("-fx-background-color: " + backgroundColor); + + } + + /** + * Sets width size for all job list panel placeholders for all days in week + * @param widthSize + */ + private void setAllPalceholderPrefWidth(double widthSize) { + deliveryJobListPanelPlaceholder1.setPrefWidth(widthSize); + deliveryJobListPanelPlaceholder2.setPrefWidth(widthSize); + deliveryJobListPanelPlaceholder3.setPrefWidth(widthSize); + deliveryJobListPanelPlaceholder4.setPrefWidth(widthSize); + deliveryJobListPanelPlaceholder5.setPrefWidth(widthSize); + deliveryJobListPanelPlaceholder6.setPrefWidth(widthSize); + deliveryJobListPanelPlaceholder7.setPrefWidth(widthSize); + + } + + /** + * Sets job lists for all job list panel placeholders for all days in week + */ + private void addAllPlaceholderJobs() { + addJobSlotsToPanel(deliveryJobListPanelPlaceholder1, logic.getDayofWeekJob(1)); + addJobSlotsToPanel(deliveryJobListPanelPlaceholder2, logic.getDayofWeekJob(2)); + addJobSlotsToPanel(deliveryJobListPanelPlaceholder3, logic.getDayofWeekJob(3)); + addJobSlotsToPanel(deliveryJobListPanelPlaceholder4, logic.getDayofWeekJob(4)); + addJobSlotsToPanel(deliveryJobListPanelPlaceholder5, logic.getDayofWeekJob(5)); + addJobSlotsToPanel(deliveryJobListPanelPlaceholder6, logic.getDayofWeekJob(6)); + addJobSlotsToPanel(deliveryJobListPanelPlaceholder7, logic.getDayofWeekJob(7)); + } + + /** + * Sets job list for each job list panel placeholder + * @param panelPlaceholder job list panel placeholder for specific day + * @param jobListInDay job list in specific day + */ + private void addJobSlotsToPanel(StackPane panelPlaceholder, DeliveryList jobListInDay) { + if (jobListInDay != null) { + DayJobListPanel jobListInDayPane = new DayJobListPanel(logic, jobListInDay); + panelPlaceholder.getChildren().add(jobListInDayPane.getRoot()); + } + } +} diff --git a/src/main/resources/images/checkmark.png b/src/main/resources/images/checkmark.png new file mode 100644 index 00000000000..e2107e55c01 Binary files /dev/null and b/src/main/resources/images/checkmark.png differ diff --git a/src/main/resources/images/stats_icon.png b/src/main/resources/images/stats_icon.png new file mode 100644 index 00000000000..b33b655c9c9 Binary files /dev/null and b/src/main/resources/images/stats_icon.png differ diff --git a/src/main/resources/view/AddressBookDialog.fxml b/src/main/resources/view/AddressBookDialog.fxml new file mode 100644 index 00000000000..4fad51e770e --- /dev/null +++ b/src/main/resources/view/AddressBookDialog.fxml @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + +
+ + + + +
+
+
diff --git a/src/main/resources/view/CompletedJobWindow.fxml b/src/main/resources/view/CompletedJobWindow.fxml new file mode 100644 index 00000000000..ddd35ccf3c2 --- /dev/null +++ b/src/main/resources/view/CompletedJobWindow.fxml @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/DarkTheme.css b/src/main/resources/view/DarkTheme.css index 36e6b001cd8..10002482768 100644 --- a/src/main/resources/view/DarkTheme.css +++ b/src/main/resources/view/DarkTheme.css @@ -17,6 +17,13 @@ -fx-opacity: 1; } +.label-bright-header { + -fx-font-size: 16pt; + -fx-font-family: "Segoe UI Semibold"; + -fx-text-fill: white; + -fx-opacity: 1; +} + .label-header { -fx-font-size: 32pt; -fx-font-family: "Segoe UI Light"; @@ -81,6 +88,11 @@ -fx-border-color: transparent transparent transparent #4d4d4d; } +.split-pane:vertical .split-pane-divider { + -fx-background-color: derive(#1d1d1d, 20%); + -fx-border-color: transparent transparent transparent #4d4d4d; +} + .split-pane { -fx-border-radius: 1; -fx-border-width: 1; @@ -132,6 +144,12 @@ -fx-text-fill: #010504; } +.titled_pane { + -fx-background-color: derive(#1d1d1d, 20%); + -fx-border-color: derive(#1d1d1d, 10%); + -fx-border-width: 0px; +} + .stack-pane { -fx-background-color: derive(#1d1d1d, 20%); } @@ -309,7 +327,8 @@ #cardPane { -fx-background-color: transparent; - -fx-border-width: 0; + -fx-border-color: #383838; + -fx-border-width: 1; } #commandTypeLabel { diff --git a/src/main/resources/view/DayDeliveryJobListCard.fxml b/src/main/resources/view/DayDeliveryJobListCard.fxml new file mode 100644 index 00000000000..1fffc761493 --- /dev/null +++ b/src/main/resources/view/DayDeliveryJobListCard.fxml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/DayJobListPane.fxml b/src/main/resources/view/DayJobListPane.fxml new file mode 100644 index 00000000000..be8dbbe892f --- /dev/null +++ b/src/main/resources/view/DayJobListPane.fxml @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/DayOfMonthPanel.fxml b/src/main/resources/view/DayOfMonthPanel.fxml new file mode 100644 index 00000000000..ad10a5c54e1 --- /dev/null +++ b/src/main/resources/view/DayOfMonthPanel.fxml @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/main/resources/view/DayOfWeekPanel.fxml b/src/main/resources/view/DayOfWeekPanel.fxml new file mode 100644 index 00000000000..511e9707ab1 --- /dev/null +++ b/src/main/resources/view/DayOfWeekPanel.fxml @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/main/resources/view/DeliveryJobDetailPane.fxml b/src/main/resources/view/DeliveryJobDetailPane.fxml new file mode 100644 index 00000000000..72d0eade82d --- /dev/null +++ b/src/main/resources/view/DeliveryJobDetailPane.fxml @@ -0,0 +1,202 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+ + + + + + + + + diff --git a/src/main/resources/view/PersonListCard.fxml b/src/main/resources/view/PersonListCard.fxml index f08ea32ad55..ab81e58e8e3 100644 --- a/src/main/resources/view/PersonListCard.fxml +++ b/src/main/resources/view/PersonListCard.fxml @@ -7,30 +7,39 @@ + - + - - - - - + + - + + + diff --git a/src/main/resources/view/ReminderListWindow.fxml b/src/main/resources/view/ReminderListWindow.fxml new file mode 100644 index 00000000000..5ba3e2aa2a3 --- /dev/null +++ b/src/main/resources/view/ReminderListWindow.fxml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + +