diff --git a/.gitignore b/.gitignore index 71c9194e8bd..82435a1fa98 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,4 @@ src/test/data/sandbox/ # MacOS custom attributes files created by Finder .DS_Store docs/_site/ + diff --git a/AllocateRoomSequenceDiagram.png b/AllocateRoomSequenceDiagram.png new file mode 100644 index 00000000000..a9b641f0dfe Binary files /dev/null and b/AllocateRoomSequenceDiagram.png differ diff --git a/README.md b/README.md index 13f5c77403f..ef7ffd96b04 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,13 @@ -[![CI Status](https://github.com/se-edu/addressbook-level3/workflows/Java%20CI/badge.svg)](https://github.com/se-edu/addressbook-level3/actions) +Covigent + +Covigent + +[![CI Status](https://github.com/se-edu/addressbook-level3/workflows/Java%20CI/badge.svg)](https://github.com/AY2021S1-CS2103T-W12-1/tp/actions) ![Ui](docs/images/Ui.png) -* This is **a sample project for Software Engineering (SE) students**.
- Example usages: - * as a starting point of a course project (as opposed to writing everything from scratch) - * as a case study -* The project simulates an ongoing software project for a desktop application (called _AddressBook_) used for managing contact details. - * It is **written in OOP fashion**. It provides a **reasonably well-written** code base **bigger** (around 6 KLoC) than what students usually write in beginner-level SE modules, without being overwhelmingly big. - * It comes with a **reasonable level of user and developer documentation**. -* It is named `AddressBook Level 3` (`AB3` for short) because it was initially created as a part of a series of `AddressBook` projects (`Level 1`, `Level 2`, `Level 3` ...). -* For the detailed documentation of this project, see the **[Address Book Product Website](https://se-education.org/addressbook-level3)**. -* This project is a **part of the se-education.org** initiative. If you would like to contribute code to this project, see [se-education.org](https://se-education.org#https://se-education.org/#contributing) for more info. +Covigent is a desktop app for managing information of quarantined individuals and the tasks to be done by staff of the quarantine facilities. It is optimized for use via a Command Line Interface (CLI) while still having the benefits of a Graphical User Interface (GUI). If you type fast, Covigent can improve your efficiency in managing your patients and tasks instead of using traditional GUI apps. + +Check out our [User Guide](https://ay2021s1-cs2103t-w12-1.github.io/tp/UserGuide.html) to get started. + +This project is based on the AddressBook-Level3 project created by the [SE-EDU initiative](https://se-education.org). diff --git a/UiClassDiagram.png b/UiClassDiagram.png new file mode 100644 index 00000000000..f42ca39f859 Binary files /dev/null and b/UiClassDiagram.png differ diff --git a/build.gradle b/build.gradle index be2d2905dde..0692cb662f3 100644 --- a/build.gradle +++ b/build.gradle @@ -1,7 +1,7 @@ plugins { id 'java' id 'checkstyle' - id 'com.github.johnrengelman.shadow' version '4.0.4' + id 'com.github.johnrengelman.shadow' version '6.1.0' id 'application' id 'jacoco' } @@ -17,7 +17,11 @@ repositories { } checkstyle { - toolVersion = '8.29' + toolVersion = '8.36.2' +} + +run { + enableAssertions = true } test { @@ -25,6 +29,10 @@ test { finalizedBy jacocoTestReport } +run { + enableAssertions = true +} + task coverage(type: JacocoReport) { sourceDirectories.from files(sourceSets.main.allSource.srcDirs) classDirectories.from files(sourceSets.main.output) @@ -41,7 +49,7 @@ task coverage(type: JacocoReport) { } dependencies { - String jUnitVersion = '5.4.0' + String jUnitVersion = '5.7.0' String javaFxVersion = '11' implementation group: 'org.openjfx', name: 'javafx-base', version: javaFxVersion, classifier: 'win' @@ -60,13 +68,15 @@ 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' + testImplementation group: 'org.mockito', name: 'mockito-core', version: '3.5.13' + testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: jUnitVersion testRuntimeOnly group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: jUnitVersion } shadowJar { - archiveName = 'addressbook.jar' + archiveFileName = 'covigent.jar' } defaultTasks 'clean', 'test' diff --git a/docs/AboutUs.md b/docs/AboutUs.md index 1c9514e966a..a83aef28ae4 100644 --- a/docs/AboutUs.md +++ b/docs/AboutUs.md @@ -3,57 +3,51 @@ layout: page title: About Us --- -We are a team based in the [School of Computing, National University of Singapore](http://www.comp.nus.edu.sg). - -You can reach us at the email `seer[at]comp.nus.edu.sg` +We are team CS2103T-W12-1 based in the [School of Computing, National University of Singapore](http://www.comp.nus.edu.sg). ## Project team -### John Doe +### Wee Yee Hong - + -[[homepage](http://www.comp.nus.edu.sg/~damithch)] -[[github](https://github.com/johndoe)] -[[portfolio](team/johndoe.md)] +[[Github](https://github.com/w-yeehong)][[Portfolio](team/w-yeehong.md)] -* Role: Project Advisor +* Role: Team Lead +* Responsibilities: Project Coordination -### Jane Doe +### Ge Wai Lok - + -[[github](http://github.com/johndoe)] -[[portfolio](team/johndoe.md)] +[[Github](http://github.com/raymondge)][[Portfolio](team/raymondge.md)] -* Role: Team Lead -* Responsibilities: UI +* Role: Developer +* Responsibilities: Documentation -### Johnny Doe +### Noorul Azlina - + -[[github](http://github.com/johndoe)] [[portfolio](team/johndoe.md)] +[[Github](http://github.com/itssodium)][[Portfolio](team/itssodium.md)] * Role: Developer -* Responsibilities: Data +* Responsibilities: Code Quality -### Jean Doe +### Chiam Yun Qing - + -[[github](http://github.com/johndoe)] -[[portfolio](team/johndoe.md)] +[[Github](https://github.com/chiamyunqing)][[Portfolio](team/chiamyunqing.md)] * Role: Developer -* Responsibilities: Dev Ops + Threading +* Responsibilities: Testing -### James Doe +### Lee Ming De - + -[[github](http://github.com/johndoe)] -[[portfolio](team/johndoe.md)] +[[Github](http://github.com/LeeMingDe)][[Portfolio](team/leemingde.md)] * Role: Developer -* Responsibilities: UI +* Responsibilities: Integration (version the code, maintain the code repository and integrate various parts into a software) diff --git a/docs/DevOps.md b/docs/DevOps.md index 4414eea3344..ee48825f37c 100644 --- a/docs/DevOps.md +++ b/docs/DevOps.md @@ -19,7 +19,7 @@ Given below are how to use Gradle for some important project tasks. * **`clean`**: Deletes the files created during the previous build tasks (e.g. files in the `build` folder).
e.g. `./gradlew clean` -* **`shadowJar`**: Uses the ShadowJar plugin to creat a fat JAR file in the `build/lib` folder, *if the current file is outdated*.
+* **`shadowJar`**: Uses the ShadowJar plugin to create a fat JAR file in the `build/lib` folder, *if the current file is outdated*.
e.g. `./gradlew shadowJar`. * **`run`**: Builds and runs the application.
diff --git a/docs/DeveloperGuide.md b/docs/DeveloperGuide.md index 4829fe43011..9190b20e98d 100644 --- a/docs/DeveloperGuide.md +++ b/docs/DeveloperGuide.md @@ -1,44 +1,100 @@ --- layout: page -title: Developer Guide +title: Covigent - Developer Guide --- -* Table of Contents -{:toc} +1. [Preface](#1-preface) +1. [Setting Up](#2-setting-up) +1. [Design](#3-design)
+ 3.1 [Architecture: High Level View](#31-architecture-high-level-view)
+ 3.2 [UI Component](#32-ui-component)
+ 3.3 [Logic Component](#33-logic-component)
+ 3.4 [Model Component](#34-model-component)
+ 3.5 [Storage Component](#35-storage-component)
+ 3.6 [Commons Component](#36-commons-component)
+ 1. [Implementation](#4-implementation)
+ 4.1 [Patient Feature](#41-patient-feature)
+       4.1.1 [Overview](#411-overview)
+       4.1.2 [Implementation](#412-implementation)
+       4.1.3 [Design Considerations](#413-design-considerations)
+       4.1.4 [Create, Read, Update, Delete](#414-create-read-update-delete)
+       4.1.5 [Search](#415-search)
+ 4.2 [Room Feature](#42-room-feature)
+       4.2.1 [Overview](#421-overview)
+       4.2.2 [Implementation](#422-implementation)
+       4.2.3 [Design Considerations](#423-design-considerations)
+       4.2.4 [Create, Read, Update](#424-create-read-update)
+ 4.3 [Task Feature](#43-task-feature)
+       4.3.1 [Overview](#431-overview)
+       4.3.2 [Implementation](#432-implementation)
+       4.3.3 [Design Considerations](#433-design-considerations)
+       4.3.4 [Create, Read, Update, Delete](#434-create-read-update-delete)
+       4.3.5 [Search](#435-search)
+ 4.4 [Logging Feature](#44-logging-feature)
+ 4.5 [Configuration Feature](#45-configuration-feature)
+ 1. [Planned Features](#5-planned-features)
+ 1. [Documentation](#6-documentation)
+ 1. [Appendix](#7-appendix)
+ A1. [Product Scope](#a1-product-scope)
+ A2. [User Stories](#a2-user-stories)
+ A3. [Use Cases](#a3-use-cases)
+ A4. [Non-Functional Requirements](#a4-non-functional-requirements)
+ A5. [Glossary](#a5-glossary)
+ A6. [Instructions for Manual Testing](#a6-instructions-for-manual-testing)
+ -------------------------------------------------------------------------------------------------------------------- +## 1. Preface -## **Setting up, getting started** +Covigent is a desktop management application that helps to keep track of the information of quarantined individuals and the tasks to be done by the staff of a small hotel that has been converted to a quarantine facility. + +The Developer Guide for Covigent v1.4 is designed to illustrate and identify the high level architecture systems used to design and implement the Covigent application. The document contains an overall view of the system hierarchy, logical views of the system components, and a process view of the system’s communication. We hope that this Developer Guide serves you well in understanding and maintaining the Covigent application. + +The link to the GitHub repository can be found [here](https://github.com/AY2021S1-CS2103T-W12-1/tp). + + _Written by: Yun Qing_ + + +-------------------------------------------------------------------------------------------------------------------- + +## 2. Setting Up Refer to the guide [_Setting up and getting started_](SettingUp.md). + -------------------------------------------------------------------------------------------------------------------- -## **Design** +## 3. Design -### Architecture +This section describes some noteworthy details on how Covigent is designed. - +### 3.1 Architecture: High Level View -The ***Architecture Diagram*** given above explains the high-level design of the App. Given below is a quick overview of each component. +

+ +
+ Figure 1. Architecture diagram of Covigent +

+ +The ***Architecture Diagram*** given above explains the high-level design of Covigent. Given below is a quick overview of each component.
-: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/AY2021S1-CS2103T-W12-1/tp/tree/master/docs/diagrams/plantuml) 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.
-**`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/AY2021S1-CS2103T-W12-1/tp/blob/master/src/main/java/seedu/address/Main.java) and [`MainApp`](https://github.com/AY2021S1-CS2103T-W12-1/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. -[**`Commons`**](#common-classes) represents a collection of classes used by multiple other components. +[**`Commons`**](#36-commons-component) represents a collection of classes used by multiple other components. The rest of the App consists of four components. -* [**`UI`**](#ui-component): The UI of the App. -* [**`Logic`**](#logic-component): The command executor. -* [**`Model`**](#model-component): Holds the data of the App in memory. -* [**`Storage`**](#storage-component): Reads data from, and writes data to, the hard disk. +* [**`UI`**](#32-ui-component): The UI of the App. +* [**`Logic`**](#33-logic-component): The command executor. +* [**`Model`**](#34-model-component): Holds the data of the App in memory. +* [**`Storage`**](#35-storage-component): Reads data from, and writes data to, the hard disk. Each of the four components, @@ -47,180 +103,717 @@ Each of the four components, For example, the `Logic` component (see the class diagram given below) defines its API in the `Logic.java` interface and exposes its functionality using the `LogicManager.java` class which implements the `Logic` interface. -![Class Diagram of the Logic Component](images/LogicClassDiagram.png) +

+ +
+ Figure 2. Class diagram of Logic component +

**How the architecture components interact with each other** -The *Sequence Diagram* below shows how the components interact with each other for the scenario where the user issues the command `delete 1`. +The *Sequence Diagram* below shows how the components interact with each other for the scenario where the user issues the command `deletepatient Alex Yeoh`. - +

+ +
+ Figure 3. Sequence diagram of deletepatient alex command +

The sections below give more details of each component. -### UI component -![Structure of the UI Component](images/UiClassDiagram.png) +### 3.2 UI Component + +The `UI` component displays information for the users based on user's input. The GUI to displayed is based on the return from logic. +It uses 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/AY2021S1-CS2103T-W12-1/tp/blob/master/src/main/java/seedu/address/ui/MainWindow.java) is specified in [`MainWindow.fxml`](https://github.com/AY2021S1-CS2103T-W12-1/tp/blob/master/src/main/resources/view/MainWindow.fxml) + +The `UI` component, +* Executes user commands using the `Logic` component. +* Consists of a `MainWindow` that is made up of many different parts that inherit from the abstract `UiPart` class. + +The `MainWindow` is made up of +* A `PatientListPanel` that displays the list of patients. The layout is defined by `PatientCard` and `PatientDetailsPanel`. +* A `RoomListPanel` that displays the list of rooms. The layout is defined by `RoomCard` and `RoomDetailPanel`. +* A `RoomTaskListPanel` that displays the list of tasks. The layout is defined by `TaskCard`. +* A `HelpWindow` that displays the link to the help page. +* A `CommandBox` that displays the area for command input. +* A `ResultDisplay` that displays the robot response. +* A `StatusBarFooter` that displays the status bar footer.
+Below is a class diagram for `Ui` +

+ +
+ Figure 4. Class diagram of Ui component +

**API** : -[`Ui.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/ui/Ui.java) +[`Ui.java`](https://github.com/AY2021S1-CS2103T-W12-1/tp/blob/master/src/main/java/seedu/address/ui/Ui.java) -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. +The `UI` component listens for changes to `Model` data so that the `UI` can be updated with the modified data. +Below shows the interaction with `Model` +

+ +
+ Figure 5. Structure of Ui component +

-The `UI` component uses 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) +_Written by: Wai Lok_ -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. +### 3.3 Logic Component -### Logic component +The `Logic` component is the "brains" of Covigent. While the `Ui` defines the GUI and `Model` defines in-memory data, +the `Logic` component does most of the heavy-lifting in terms of deciding what to change within the `Model` and what to +return to the `Ui`.
+The diagram below shows the structure of the `Logic` component and how it interacts with its internal parts. -![Structure of the Logic Component](images/LogicClassDiagram.png) +

+ +
+ Figure 6. Structure of the Logic Component +

**API** : -[`Logic.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/logic/Logic.java) +[`Logic.java`](https://github.com/AY2021S1-CS2103T-W12-1/tp/blob/master/src/main/java/seedu/address/logic/Logic.java) -1. `Logic` uses the `AddressBookParser` class to parse the user command. -1. This results in a `Command` object which is executed by the `LogicManager`. -1. The command execution can affect the `Model` (e.g. adding a person). -1. The result of the command execution is encapsulated as a `CommandResult` object which is passed back to the `Ui`. -1. In addition, the `CommandResult` object can also instruct the `Ui` to perform certain actions, such as displaying help to the user. +1. Once a user input is obtained from the GUI, `Logic` uses the `CovigentAppParser` class to parse the users' commands +and return a `Command` object. +1. The `Command` is executed by `LogicManager`. +1. Depending on the command input by the user, it may mutate the `Model`, such as adding a new patient, room or task. +1. The result of the command execution is encapsulated as a `CommandResult` that is returned to the `Ui`. +1. These `CommandResults` can instruct the `Ui` to perform certain actions, such as displaying help or error messages to the user. -Given below is the Sequence Diagram for interactions within the `Logic` component for the `execute("delete 1")` API call. +Shown below is the Sequence Diagram within the `Logic` component for the API call: `execute("deletepatient alex")`. -![Interactions Inside the Logic Component for the `delete 1` Command](images/DeleteSequenceDiagram.png) +

+ +
+ Figure 7. Interactions inside the Logic Component for the deletepatient alex Command +

-
: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. +
:information_source: **Note:** The lifeline for `DeletePatientCommandParser` should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram.
+
-### Model component +_Written by: Ming De_ -![Structure of the Model Component](images/ModelClassDiagram.png) -**API** : [`Model.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/model/Model.java) +### 3.4 Model Component -The `Model`, +The `Model` API acts as a facade that handles interaction between different kinds of data in Covigent. These data include user's preferences, patient records and room list. The `Model` API exposes the methods that allow the logic component to utilise to perform retrieving and updating of data. -* stores a `UserPref` object that represents the user’s preferences. -* stores the address book data. -* exposes 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. -* does not depend on any of the other three components. +The `Model` component, + * stores a `UserPref` object that represents the user’s preferences. + * stores a `PatientRecords` object that stores the data of all the patients. + * stores a `RoomList` object that stores the data of all the rooms. + * stores a `RoomTaskRecords` object that stores all the `RoomTaskAssociation` objects, which keep track of the room that a task belongs to. + * exposes unmodifiable `ObservableList`, `ObservableList` and `ObservableList` which can be observed. This means that the UI can be bound to the lists so that the UI automatically updates when data in the lists changes. + * does not depend on any of the three components. +The concrete class `ModelManager` implements `Model` interface and manages the data for Covigent. `ModelManager` contains `UserPrefs`, `PatientRecords`, `RoomList` and `RoomTaskRecords`. These classes manage the data related to their specific features. -
: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` object.
-![BetterModelClassDiagram](images/BetterModelClassDiagram.png) +Below is a class diagram for `ModelManager`. +

+ +
+ Figure 7. Class Diagram for Model Component +

+ +**API** : [`Model.java`](https://github.com/AY2021S1-CS2103T-W12-1/tp/blob/master/src/main/java/seedu/address/model/Model.java) + +The breakdown for each type of data in `ModelManager`, which include `PatientRecords`, `RoomList` and `RoomTaskRecords`, can be found below. + +The `PatientRecords` class is in charge of maintaining the data of the patients and in ensuring the uniqueness of patients according to their names. Below is a class diagram for `PatientRecords`. + +

+ +
+ Figure 8. Class Diagram for PatientRecords +

+ +
:information_source: **Note:** The `PatientRecords` class implements the interface ReadOnlyList<Patient> but due to a limitation of PlantUML, the interface is reflected as simply `ReadOnlyList`.
+
+The `RoomList` class is in charge of maintaining the data in the rooms and in ensuring the uniqueness of rooms according to the room numbers. As each room stores the data of the patient who resides in the room and the tasks meant for the room, it incorporates data from both `Patient` and `RoomTasks`. `RoomTasks` class is in charge of maintaining the data of the tasks in a room. The full details of `Patient` can be found in the previous class diagram for `PatientRecords` so it is no longer reflected in the class diagram for `RoomList`. The class diagram for `RoomList` is shown below. +

+ +
+ Figure 9. Class Diagram for RoomList +

-### Storage component +
:information_source: **Note:** The `RoomList` class implements the interface ReadOnlyList <Room> and the `RoomTasks` class implements the interface ReadOnlyList<Task>. However, due to a limitation of PlantUML, the interface is reflected as simply `ReadOnlyList`. +
+
-![Structure of the Storage Component](images/StorageClassDiagram.png) +The `RoomTaskRecords` class is in charge of maintaining the data regarding the association of a task in a room. The `RoomTaskAssociation` class acts as an association class that ties `Task` and `Room` together so that the `Task` object does not need to know of the details of `Room` and we are still able to identify the room number that `Task` belongs to and its index in `Room`. The class diagram for `RoomTaskRecords` is shown below. -**API** : [`Storage.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/storage/Storage.java) +

+ +
+ Figure 10. Class Diagram for RoomTaskRecords +

-The `Storage` component, -* can save `UserPref` objects in json format and read it back. -* can save the address book data in json format and read it back. +
:information_source: **Note:** The `RoomTaskRecords` class implements the interface ReadOnlyList <RoomTaskAssociation>. However, due to a limitation of PlantUML, the interface is reflected as simply `ReadOnlyList`. +
+
-### Common classes + _Written by: Yun Qing_ + + +### 3.5 Storage Component + +

+ +
+ Figure 11. Structure of the Storage Component +

+ +**API** : [`Storage.java`](https://github.com/AY2021S1-CS2103T-W12-1/tp/blob/master/src/main/java/seedu/address/storage/Storage.java) + +The `Storage` API handles the reading and writing of data in Json format, enabling Covigent to remember information stored by user even when the application is closed. The Storage API behaves like a façade by handling the storage classes and interfaces. + +The `Storage` interface class diagram is shown below, and as it can be seen it inherits from specific storage interfaces. + +The `Storage` component, + * Saves `UserPref` objects in Json format + * Reads `UserPred` objects in Json format + * Saves `RoomRecords` and `PatientRecords` data in json format + * Reads `RoomRecords` and `PatientRecords` data in json format + +The information of the `Patient` and `Room` feature of Covigent is stored locally in the Json format. This is done by adapting the Patient and Room feature into JsonSerializablePatientRecords and JsonSerializableRoomRecords respectively. + +The class diagram for StorageManager is shown below +

+ +
+ Figure 12. Structure of the StorageManager Component +

+ +The information of the `Patient` and `Room` feature of Covigent is stored locally in the Json format. This is done by adapting the Patient and Room feature into JsonSerializablePatientRecords and JsonSerializableRoomRecords respectively. + +The class diagrams for the above stated adapted classes are shown below +

+ +
+ Figure 13. Structure of the JsonSerializableRoomRecords +

+ +

+ +
+ Figure 14. Structure of the JsonSerializablePatientRecords +

+ + _Written by: Noorul Azlina_ + + +### 3.6 Commons Component Classes used by multiple components are in the `seedu.addressbook.commons` package. + -------------------------------------------------------------------------------------------------------------------- -## **Implementation** +## 4. Implementation -This section describes some noteworthy details on how certain features are implemented. +This section describes some noteworthy details on how [Patient](#41-patient-feature), [Room](#42-room-feature) and [Task](#43-task-feature) features are implemented. -### \[Proposed\] Undo/redo feature +### 4.1 Patient Feature -#### Proposed Implementation +#### 4.1.1 Overview -The proposed undo/redo mechanism is facilitated by `VersionedAddressBook`. It extends `AddressBook` with an undo/redo history, stored internally as an `addressBookStateList` and `currentStatePointer`. Additionally, it implements the following operations: +The patient feature in Covigent allows hotel staff to store important information about the individuals that are quarantined in the hotels. Some of these important dynamic information include temperature and comment regarding the patient. To fully understand the patient feature, it is important to learn about the [implementation](#412-implementation) and [design considerations](#413-design-considerations) of the patient object before looking at the possible [commands](#414-create-read-update-delete) that can operate on the patient object. A patient is meant to be allocated to a room in the facility, hence tying the patient feature with the room feature together. -* `VersionedAddressBook#commit()` — Saves the current address book state in its history. -* `VersionedAddressBook#undo()` — Restores the previous address book state from its history. -* `VersionedAddressBook#redo()` — Restores a previously undone address book state from its history. +#### 4.1.2 Implementation +A `Patient` object in Covigent contains the following attributes, which is also reflected in Figure 15: +1. Name +1. Temperature +1. PeriodOfStay +1. Age +1. Phone +1. Comment -These operations are exposed in the `Model` interface as `Model#commitAddressBook()`, `Model#undoAddressBook()` and `Model#redoAddressBook()` respectively. +Each of the attribute is a stand-alone class on its own. -Given below is an example usage scenario and how the undo/redo mechanism behaves at each step. +

+ +
+ Figure 15. Class Diagram for Patient +

-Step 1. The user launches the application for the first time. The `VersionedAddressBook` will be initialized with the initial address book state, and the `currentStatePointer` pointing to that single address book state. +#### 4.1.3 Design Considerations +**Aspect: Encapsulation of fields for `Patient` object** -![UndoRedoState0](images/UndoRedoState0.png) +* Option 1: Using primitive data types for `Temperature`, `Age` and `Comment` classes -Step 2. The user executes `delete 5` command to delete the 5th person in the address book. The `delete` command calls `Model#commitAddressBook()`, causing the modified state of the address book after the `delete 5` command executes to be saved in the `addressBookStateList`, and the `currentStatePointer` is shifted to the newly inserted address book state. +Classes like `Temperature`, `Age` and `Comment` can be easily treated as primitive data types including double and string. However, this option goes against the spirit of OOP. Furthermore, it does not align to the original design of AddressBook3, which Covigent was morphed from, in which `Name` and `Phone` attributes were abstracted out as separate classes. -![UndoRedoState1](images/UndoRedoState1.png) +* Option 2: Encapsulate constituent patient attributes in their own classes -Step 3. The user executes `add n/David …​` to add a new person. The `add` command also calls `Model#commitAddressBook()`, causing another modified address book state to be saved into the `addressBookStateList`. +This option increases OOP and is aligned to the original design of AddressBook3. In addition, this design has proven itself to be extensible in the long run and allows for adjustments to `Patient` attributes easily. For example, when setting the valid temperature range as input for `Temperature`, only the `Temperature` class needs to change, which demonstrates the essence of the single responsibility principle. This allows for better understanding and maintenance of our code base in the future. -![UndoRedoState2](images/UndoRedoState2.png) +**Aspect: Decision on the uniqueness of `Patient` object** -
:information_source: **Note:** If a command fails its execution, it will not call `Model#commitAddressBook()`, so the address book state will not be saved into the `addressBookStateList`. +* Option 1: Uniqueness is identified by name, age and phone -
+Originally, we intended to define two `Patient` to be equals if they have the same `Name`, `Age` and `Phone`. However, such a design will make it difficult for users of Covigent as they will have to key in all these fields when executing `EditPatientCommand` and `DeletePatientCommand` in order for Covigent to uniquely identify the `Patient` to manipulate. -Step 4. The user now decides that adding the person was a mistake, and decides to undo that action by executing the `undo` command. The `undo` command will call `Model#undoAddressBook()`, which will shift the `currentStatePointer` once to the left, pointing it to the previous address book state, and restores the address book to that state. +* Option 2: Uniqueness is identified by name -![UndoRedoState3](images/UndoRedoState3.png) +Keeping in mind the ease of usage of Covigent for users, we chose to identify `Patient` uniquely by `Name` only. As such, when users need to manipulate the data of a `Patient`, all they need to input is the `Name`. Furthermore, we believes that since Covigent is used by small hotels, there is a very low chance of two patients having the same names. -
:information_source: **Note:** If the `currentStatePointer` is at index 0, pointing to the initial AddressBook state, then there are no previous AddressBook states to restore. The `undo` command uses `Model#canUndoAddressBook()` to check if this is the case. If so, it will return an error to the user rather -than attempting to perform the undo. +##### Features related to Patient +Having looked at the design of `Patient`, we can now explore the possible features related to `Patient`. In particular, our commands support [create, read, update, delete](#414-create-read-update-delete) and [search](#415-search). -
+The features comprise of five commands namely, +* `AddPatientCommand` - Adding patients +* `ListPatientCommand` - Listing all the patients +* `EditPatientCommand` - Editing patients +* `DeletePatientCommand` - Deleting patients +* `SearchPatientCommand`- Searching for patients -The following sequence diagram shows how the undo operation works: +_Written by Yun Qing_ -![UndoSequenceDiagram](images/UndoSequenceDiagram.png) +#### 4.1.4 Create, Read, Update, Delete +In this section, we will cover the implementation of the manipulation of the `Patient` data. The commands that allow creating, reading, updating and deleting of `Patient` include `AddPatientCommand`, `ListPatientCommand`, `EditPatientCommand` and `DeletePatientCommand`. -
:information_source: **Note:** The lifeline for `UndoCommand` should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram. +As the `Patient` data are stored in `UniquePatientList`, which ensures the uniqueness of `Patient`, the actual manipulation of the `Patient` data is made in `UniquePatientList` class. -
+Some of the significant methods within `UniquePatientList` class that allows the manipulation of the `Patient` data are shown below: +* `UniquePatientList#add(Patient toAdd)` - Adds a `Patient` to `UniquePatientList`. +* `UniquePatientList#setPatient(Patient target, Patient editedPatient)` - Edits the attributes of `Patient`. +* `UniquePatientList#remove(Patient toRemove)` - Deletes a `Patient` from `UniquePatientList`. +* `UniquePatientList#asUnmodifiableObservableList()` - Returns an observable list of `Patient` +* `UniquePatientList#getPatientWithName(Name name)` - Returns the `Patient` with the given `Name` if the `Patient` object exists in `UniquePatientList`. Since `Name` is an identifier of a `Patient`, this method is used in `EditPatientCommand` and `DeletePatientCommand` to modify the `Patient` or check if the `Patient` to be deleted exists. + +These methods in `UniquePatientList` class support the corresponding methods in the facade classes `PatientRecords` and `ModelManager`. In particular, the `Model` interface exposes the methods `Model#addPatient(Patient patient)`, `Model#setPatient(Patient target, Patient editedPatient)`, `Model#deletePatient(Patient target)`, `Model#getPatientWithName(Name name)` and `Model#getFilteredPatientList()`. + +For brevity's sake, we will only illustrate the implementation of 2 specific commands - `AddPatientCommand` and `EditPatientCommand`. + +**Implementation of AddPatientCommand** -The `redo` command does the opposite — it calls `Model#redoAddressBook()`, which shifts the `currentStatePointer` once to the right, pointing to the previously undone state, and restores the address book to that state. +The following is a detailed explanation of the operations that `AddPatientCommand` performs. -
:information_source: **Note:** If the `currentStatePointer` is at index `addressBookStateList.size() - 1`, pointing to the latest address book state, then there are no undone AddressBook states to restore. The `redo` command uses `Model#canRedoAddressBook()` to check if this is the case. If so, it will return an error to the user rather than attempting to perform the redo. +**Step 1.** The user executes `addpatient [input all attributes of Patient]` command to add a patient to Covigent. An `AddPatientCommandParser` object is created and the `AddPatientCommandParser#parse(String args)` method is called, which helps to parse the different attributes of `Patient`. The parse method returns a new `AddPatientCommand` object and the `AddPatientCommand` object stores the `Patient` to be added. +**Step 2.** The `Patient` to be added is then searched through `UniquePatientList#internalList`using the `Model#hasPatient(Patient patient)` method to check if the patient already exists. If the patient already exists, a `CommandException` object will be thrown with an error message. + +**Step 3.** Through `Model#addPatient(Patient toAdd)`, the `Patient` will be added to `UniquePatientList`. + +**Step 4.** A success message with the new patient details will be appended with the `AddPatientCommand#MESSAGE_SUCCESS` constant. A new `CommandResult` will be returned with the message. + +The sequence diagram for a successful execution of `AddPatientCommand` can be found below. + +

+ +
+ Figure 16. Sequence Diagram for AddPatientCommand +

+ +
:information_source: **Note:** Due to space limitation in PlantUML diagram, the addpatient command example given in the sequence diagram only consists of two patient attributes, name and age.
+
+ +_Written by Yun Qing_ + +**Implementation of EditPatientCommand** +The following is a detailed explanation of the operations that `EditPatientCommand` performs. + +**Step 1.** The `EditPatientCommand#execute(Model model)` method is executed and it checks if the `Name` defined when instantiating +`EditPatientCommand(Name patientToBeEdited, EditPatientDescriptor editPatientDescriptor)` is valid. The `EditPatientDescriptor` holds +the edited information of the `Patient`. + +**Step 2.** A new `Patient` with the updated values will be created and the patient is then searched through `UniquePatientList#internalList` +using the `Model#hasPatient(Patient patient)` method to check if the patient already exists. If the patient already exists, +`CommandException` will be thrown with an error message. + +**Step 3.** The newly created `Patient` will replace the existing patient object through the `Model#setPatient(Patient target, Patient editedPatient)` +method. + +**Step 4.** A success message with the edited patient will be appended with the `EditPatientCommand#MESSAGE_EDIT_PATIENT_SUCCESS` constant. A +new `CommandResult` will be returned with the message. + +_Written by Ming De_ + + +#### 4.1.5 Search + +The following is a detailed explanation of the operations that `SearchPatientCommand` performs. + +**Step 1.** The `SearchPatientCommand#execute(Model model)` method is executed and checks the `criteriaToSearch` via `confirmCriteria(SearchPatientDescriptor searchPatientDescriptor)`. +The `SearchPatientDescriptor` holds the `name` or `temperatureRange` of the Command. + +**Step 2.** If the `criteriaToSearch` is `SearchCriteria.CRITERIA_NOT_FOUND`, `CommandException` will be thrown with an error message. + +**Step 3.** If the `criteriaToSearch` is `SearchCriteria.CRITERIA_IS_NAME`, we update the `Model`'s `FilteredPatientList` Predicate via `updateNamePredicate(Model model, SearchPatientDescriptor searchPatientDescriptor)` +Then `findPatientWithName(SearchPatientDescriptor searchPatientDescriptor, List patientList)` will go through `Model`'s `FilteredPatientList`, if no patient is found, `CommandException` will be thrown with an error message. If there is at least one patient found, a new `CommandResult` will be returned with the message. + +**Step 4.** If the `criteriaToSearch` is `CRITERIA_IS_TEMPERATURE`, we update the `Model`'s `FilteredPatientList` Predicate via `updateTemperaturePredicate(Model model, SearchPatientDescriptor searchPatientDescriptor)` +Then `findPatientWithTemperature(SearchPatientDescriptor searchPatientDescriptor, List patientList)` will go through `Model`'s `FilteredPatientList`, if no patient is found, `CommandException` will be thrown with an error message. If there is at least one patient found, a new `CommandResult` will be returned with the message. + +**Step 5.** If the `criteriaToSearch` is `TOO_MANY_CRITERIA` A new `CommandResult` will be returned with the message. +method. + +_Written by Wai Lok_ + +### 4.2 Room Feature + +#### 4.2.1 Overview +The application is able to track the room details. It keep tracks of the whether a room is occupied and the patient inside the room if it is occupied. +It also keeps track of the tasks assigned to a specific room. Hence, there is a need to represent the Room List as a list of Rooms on which the application can perform read and update operations. + +#### 4.2.2 Implementation + +The class diagram for RoomList is shown below. +

+ +
+ Figure 17. Class diagram for RoomList +

+ +From the diagram above, the `RoomList` contains of one `UniqueRoomList`. This `UniqueRoomList` is a wrapper class around the `RoomList` +which contains an ObservableList of `Patient` and PriorityQueue of `Patient`. The `RoomList` can contain from about 1 to 500 rooms. + +Each Room contains the following member attributes, all of which are non-nullable attributes: +1. **roomNumber** +This gives the room number of the Room object +2. **isOccupied** +This gives the information of whether the Room is occupied or not. If there is a `Patient` inside the `Room`, then the isOccupied is true, else false +3. **patient** +This gives the patient details and it is stored as an Optional object. If there is no `Patient`, then the Optional.empty() is assigned. +4. **tasks** +This gives all the tasks that are assigned to a specific room. The number of tasks assigned can be zero. + +The proposed room feature is facilitated by `RoomList`. It extends `ReadOnlyRoomList` which reads the Room information Json file, stored internally as an `addressBookStateList` and `currentStatePointer`. Additionally, it implements the following operations: +* `RoomList#addRooms(int num)` — adds the number of which are said to add together and retains infromation previously stored in each room +* `RoomList#containsRoom(Room toCehck)` - checks whether the given room exists +* `RoomList#clearRoom(Name patientName)` - removes patient from the room +* `RoomList#setSingleRoom(Room target, Room editedRoom)` - sets the editedRoom to the target room + +These operations are exposed in the `Model` interface as `Model#addRooms(int num)`, `Model#hasroom(Room room)`, `Model#clearRoom(Name patientName)` and `Model#setSingleRoom(Room target, Room editedRoom)` respectivley. + +_Written by Noorul Azlina_ + +The GUI for room feature is based on a `ListView` that updates whenever the `RoomList` is updated and a `scroll pane` that +displays the details of the room. To ensure that the information displayed in the `scroll pane` is updated dynamically, +we employ the use of `Listeners` that listens for changes and notify the `scroll pane` to update. + +_Written by: Ming De_ + + +#### 4.2.3 Design Considerations +**Aspect: Decision on allowing `editRoomCommand` that allows changing of room number to remain** + +* Option 1: Do not change the `editRoomCommand` +Allowing the users to change room numbers will give the user more power in customising the rooms. However, this option introduced a bug into the system +that could not be easily resolved unless we changed our entire implementation of `InitRoomCommand`. + +* Option 2: Remove `editRoomCommand` ability to change room number and rename it to `AllocateRoomCommand` +Removing the `editRoomCommand` to change room number is much more time-efficient compared to changing the entire implementation of `InitRoomCommand`. We +also decided that there should be no reason that a user would need to change the room number. Renaming the method to `AllocateRoomCommand` would provide +more clarity for the method. + +Ultimately, we decided on Option 2. This is because keeping `editRoomCommand` would lead to a large consumption of time to redesign the system. In order +to not stray from our schedule, we have to remove `editRoomCommand` to ensure that we can develop the other features on time. +Furthermore, to solve the bug that was introduced, we would have to store the count of the number of times `InitRoomCommand` was called. This would +cause us to store information in another `.json` file which is unnecessary. Therefore, we decided that the forgoing a +small function like this would be a better choice. + +_Written by Ming De_ + +##### Feature related to Room + +The features comprise of five commands namely, +* `InitRoomCommand` - Initializes the number of rooms in Covigent app. +* `ListRoomCommand` - Lists all the rooms in Covigent app. +* `AllocateRoomCommand` - Allocates a patient to a room. +* `SearchRoomCommand` - Searches for the room with the specified room number. +* `FindEmptyRoomCommand` - Finds an empty room with the lowest room number. + +We will illustrate the progress of two of the above commands for simplicity. + + +#### 4.2.4 Create, Read, Update + +**Implementation of InitRoomCommand** + +The following is a detailed explanation of the operations that `InitRoomCommand` performs. + +**Step 1.** The `InitRoomCommand#execute(Model model)` method is executed and it check if the `Integer`defined when instantiating +If it is a positive integer and the number of rooms is more than or equal to the existing number of occupied rooms, InitRoomCommand is valid. +The checking of the number of rooms is done by `Model#hasSpaceForRooms()`. If this is true then `Integer` is valid, else it is invalid. + +**Step 2.** The stated number of rooms is then set to `Integer`, if `Integer` is greater than the existing number of rooms then excess rooms are added to the back of the +UniqueRoomList. If the number of rooms is less than the `Integer` and there are occupied rooms the information of patients in that room is transferred to an empty room +in the reduced number of rooms + +**Step 4.** A success message with the `Intger` appended with the `InitRoomCommand#MESSAGE_SUCCESS` constant is displayed on the UI. A new `CommandResult` +returns this message. + +The activity diagram below illustrates the `initRoom`. +

+ +
+ Figure 18. Activity diagram for initRoom +

+ +The Sequence Diagram for `initRooms` is shown below. +

+ +
+ Figure 19. Sequence diagram for initRoom +

+ + _Written By: Noorul Azlina_ + +**Implementation of AllocateRoomCommand** + +The following is a detailed explanation of the operations that `AllocateRoomCommand` performs. + +**Step 1.** The `AllocateRoomCommand#execute(Model model)` method is executed and it checks if the `Integer` defined when instantiating +`AllocateRoomCommand(Integer roomNumberToAllocate, AllocateRoomDescriptor AllocateRoomDescriptor)` is valid. This is done using the `Model#getRoomWithRoomNumber` method + where it is used to get an `Optional`. If `Optional` is empty, the `Integer` is not valid. + The `AllocateRoomDescriptor` holds the information of the `Room` with the patient allocated. + +**Step 2.** A new `Room` with the allocated patient will be created and the room is then searched through `RoomList#internalList` +using the `Model#hasRoom(Room room)` method to check if a room with the same room number exists. If it already exists, +`CommandException` will be thrown with an error message. + +**Step 3.** The newly created `Room` will replace the existing room object through the `Model#setSingleRoom(Room target, Room editedRoom)` +method. + +**Step 4.** A success message with the allocated room will be appended with the `AllocateRoomCommand#MESSAGE_ALLOCATE_ROOM_SUCCESS ` constant. A +new `CommandResult` will be returned with the message. + +The activity diagram below illustrates `allocateRoomCommand`. +

+ +
+ Figure 20. Activity Diagram for AllocateRoomCommand +

+ +The sequence diagram for `AllocateRoomCommand` is shown below. +

+ +
+ Figure 21. Sequence Diagram for AllocateRoomCommand +

+ +_Written by Mingde_ + + +### 4.3 Task Feature + +#### 4.3.1 Overview +The task feature in Covigent allows hotel staff to manage and organize time-critical work related to a room in a quarantine facility. +Every room can be allocated any number of tasks, with each task keeping track of the description of the work and a due date by which +it should be completed. + +It is important to note that Covigent handles tasks on a per-room basis. Tasks not specific to any room (e.g. tasks related to the operations +of the quarantine facility) are out of scope. + +The task feature in Covigent includes the following: + +* Adding a task to a room +* Displaying all tasks in the user interface +* Editing the description and due date of a task +* Removing a task from a room +* Filtering tasks based on a criterion and displays the filtered tasks in the user interface (currently on supports filtering by due date) + +#### 4.3.2 Implementation +At a higher level, tasks share a composition type relationship with rooms. That is, if a room is deleted, all tasks in that room are similarly deleted. +We have implemented the task feature based on the class diagram in Figure 22. +

+ +
+ Figure 22. Class Diagram for Task +

+ +Each `Room` contains a `RoomTasks` class, which is a wrapper around `TaskList`. + +Task-related operations that alter the tasks in a `Room` must be performed through that `Room`. +The API calls for these operations first proceed to `Room`, which redirects them to `RoomTasks`. To enforce this constraint, `RoomTasks` is not publicly exposed in the API for `Room` +(i.e. there is no getter for `RoomTasks` in `Room`). + +`TaskList` emulates the other `List` classes in Covigent such as `UniquePatientList` and `UniqueRoomList`, exposing only an unmodifiable `ObservableList`. +This `ObservableList` is subsequently returned by `RoomTasks` in the `getReadOnlyList()` method to fulfill its contract with the `ReadOnlyList` interface. + +#### 4.3.3 Design Considerations + +**Aspect: Retrieving list of tasks from `Room`** + +* Option 1: Supplying a getter for `RoomTasks` in `Room` + +Having a `getRoomTasks()` method to retrieve all the tasks in `Room` greatly enhances convenience, especially when copying the information from one `Room` to another. +This is because we can use the constructor of `Room` in this manner: `Room copyOfRoom = new Room(..., originalRoom.getRoomTasks()))` (`...` refers to other attributes of `Room` that need to be passed into constructor). +It is a quick way to transfer tasks between `Room`. + +However, this option is not optimal from a defensive programming perspective. Because a client can retrieve `RoomTasks` from `Room`, there is potential for abuse. +The `RoomTasks` object may be passed to some other classes that are not `Room`. Consequently, there is no guarantee that a client will not circumvent `Room` and call an operation that changes its tasks such as `addTask(Task)` directly from `RoomTasks`. +This destroys the abstraction barrier of `Room` as clients can modify the tasks in it via external means. + +* Option 2: Add a `getReadOnlyTasks()` method in `Room` to retrieve an unmodifiable list of tasks from `RoomTasks` + +We chose option 2 for Covigent as we thought it was the safer option. Since `RoomTasks` implements `ReadOnlyList`, `Room` can retrieve and expose it via a `getReadOnlyTasks()` method for clients to access the tasks. +This is more secure from a defensive programming perspective and respects the abstraction barrier. Without the getter for `RoomTasks`, clients are only allowed to change the tasks in `Room` via its API. +It ensures that the tasks in a `Room` are not modified without knowledge of `Room`, while providing the flexibility for clients to access the information in them. +An additional benefit of this option is that it obscures the fact that `Room` depends on `RoomTasks` for its task-related operations, thus strengthening the composition type relationship between tasks and rooms. + +With option 2, the operation for copying tasks from one `Room` to another can be performed as such: `Room copyOfRoom = new Room(..., new RoomTasks(originalRoom.getReadOnlyTasks()))`. +It is probably better for the constructor of `Room` to take in `List` instead of `RoomTasks` to completely hide the existence of `RoomTasks` from clients. +This can be considered as part of the improvements to be made to Covigent in the future. + +A downside of this option is that the implementor (future programmers of Covigent) must be aware of this design decision and avoid exposing `RoomTasks` through the public API of `Room`. +Moreover, there is a slight increase in complexity now that `RoomTasks` has to implement `ReadOnlyList`. + +**Aspect: Storing a list of tasks in `Room`** + +* Option 1: Using `List` in `Room` + +This option is simple as it does not require any additional classes. However, `Room` has to handle all task-related operations, which violates the single-responsibility principle. +Conceptually, we consider `Room` to be a container for a patient and tasks. It should not contain implementations of methods related to patients and tasks. + +* Option 2: Using `TaskList` in `Room` + +While entirely possible to use `TaskList` to store the tasks in `Room` (see Figure 23), we cannot be certain that `TaskList` will not be used elsewhere in `Covigent` in the future. +

+ +
+ Figure 23. Class Diagram for Task using TaskList +

+ +For instance, suppose we want to support a list of tasks for specific patients (instead of rooms). It may not make much sense to use `TaskList` for both patients and rooms as they can have different behaviors. +Perhaps a maximum of 5 tasks can be assigned to each patient, while there is no limit to the number of tasks that can be assigned to each room. +Taking that into consideration, we decided to add a wrapper class. + +* Option 3: Using an extra class in addition to `TaskList` + +We chose option 3 and added a new class `RoomTasks` to `Room`. `RoomTasks` is a wrapper around `TaskList` to support room-specific behavior associated with tasks. +A simple example is how we can set a maximum number of tasks each `Room` can hold through `RoomTasks` without modifying `TaskList`. In that sense, option 3 respects the open-closed principle. + +An alternative is to use a new class that extends from `TaskList`. This would improve polymorphism as methods that work with `TaskList` will also work with the new class. +However, Liskov substitution principle might be a concern. For instance, if `TaskList` allows an unlimited number of tasks to be stored but `RoomTasks` only allows 500, it would be a violation of Liskov substitution principle. +As such, we did not choose this alternative. + +The disadvantage of using another class is the added complexity as API calls for task-related operations need to be routed from the new class to `TaskList`. + +#### 4.3.4 Create, Read, Update, Delete +In this section, we will cover the implementation of the manipulation of `Task` data. The commands that allow the task-related operations of creating, reading, updating, and deleting of `Task` are `AddTaskCommand`, `ListTaskCommand`, `EditTaskCommand`, and `DeleteTaskCommand` respectively. + +The actual manipulation of `Task` data is performed in the `TaskList` class. Some significant methods within `TaskList` that allows the manipulation of `Task` data are shown below: + +* `TaskList#add(Task toAdd)` - Adds a `Task` to `TaskList`. +* `TaskList#setTask(Task target, Task editedTask)` - Replaces a `Task` in `TaskList` with the edited `Task`. +* `TaskList#remove(Task toRemove)` - Removes a `Task` from `TaskList`. +* `TaskList#asUnmodifiableObservableList()` - Returns a read-only list containing all `Task` in `TaskList`. +* `RoomTasks#getTaskWithTaskIndex(Index index)` - Returns the `Task` with the `Index` in the `TaskList`. This method is currently in `RoomTasks` but can be safely refactored into `TaskList`. + +These methods in `TaskList` class support the corresponding methods in `RoomTasks`. For clients to perform these operations, `Room` exposes `addTask(Task task)`, `setTask(Task task, Task editedTask)`, and `deleteTask(Task task)` in its public API. +Calls to these APIs are redirected to `RoomTasks` and eventually `TaskList`. -Step 5. The user then decides to execute the command `list`. Commands that do not modify the address book, such as `list`, will usually not call `Model#commitAddressBook()`, `Model#undoAddressBook()` or `Model#redoAddressBook()`. Thus, the `addressBookStateList` remains unchanged. +Since the commands do not directly interact with `Room` but rather the `Model`, the `Model` interface has to expose `addTaskToRoom(Task task, Room room)`, `deleteTaskFromRoom(Task task, Room room)` and `setTaskToRoom(Task task, Task editedTask, Room room)`. -![UndoRedoState4](images/UndoRedoState4.png) +For brevity's sake, we will only illustrate the implementation of the most complex command, `EditTaskCommand`. -Step 6. The user executes `clear`, which calls `Model#commitAddressBook()`. Since the `currentStatePointer` is not pointing at the end of the `addressBookStateList`, all address book states after the `currentStatePointer` will be purged. Reason: It no longer makes sense to redo the `add n/David …​` command. This is the behavior that most modern desktop applications follow. +**Implementation of EditTaskCommand** -![UndoRedoState5](images/UndoRedoState5.png) +The following is an explanation of the operations that `EditTaskCommand` performs, with the higher level details (`LogicManager` and `CovigentAppParser`) omitted for simplicity. -The following activity diagram summarizes what happens when a user executes a new command: +**Step 1.** The user executes `edittask [room number] [task index] [other attributes to edit]` command to edit the description of a `Task` in a `Room`. An `EditTaskCommandParser` object is created and the `EditTaskCommandParser#parse(String args)` method is called, which helps to parse the different attributes of `Task`. The parse method returns a new `EditTaskCommand` object with the room number, task index, and a descriptor containing the updated values to replace the original. -![CommitActivityDiagram](images/CommitActivityDiagram.png) +**Step 2.** The `Room` is retrieved from the list of rooms in Covigent using the `Model#getRoomWithRoomNumber(int roomNumber)` method. If no such `Room` exists, a `CommandException` object will be thrown with an error message. -#### Design consideration: +**Step 3.** The `Task` is retrieved from `RoomTasks` of `Room` using the `Model#getTaskFromRoomWithTaskIndex(Index taskIndex, Room room)` method. If no such `Task` exists, a `CommandException` object will be thrown with an error message. -##### Aspect: How undo & redo executes +**Step 4.** The `Task` is edited in `EditTaskCommand` and replaces the original `Task` the room with `Model#setTaskToRoom(Task target, Task editedTask, Room room)`. -* **Alternative 1 (current choice):** Saves the entire address book. - * Pros: Easy to implement. - * Cons: May have performance issues in terms of memory usage. +**Step 5.** A success message with the new task details will be appended with the `EditTaskCommand#MESSAGE_SUCCESS` constant. A new `CommandResult` will be returned with the message. -* **Alternative 2:** Individual command knows how to undo/redo by - itself. - * Pros: Will use less memory (e.g. for `delete`, just save the person being deleted). - * Cons: We must ensure that the implementation of each individual command are correct. +The sequence diagram for `EditTaskCommand` can be found below. +

+ +
+ Figure 24. Sequence Diagram for EditTaskCommand +

+ +_Written by Yee Hong_ -_{more aspects and alternatives to be added}_ +#### 4.3.5 Search -### \[Proposed\] Data archiving +The following is a detailed explanation of the operations that `SearchTaskCommand` performs. -_{Explain here how the data archiving feature will be implemented}_ +**Step 1.** The `SearchTaskCommand#execute(Model model)` method is executed and it gets information of `task` from each `room` in the `roomList`. + +**Step 2.** If the `task` has a `duedate` before the `duedate` from user command, it is stored in `taskListWithDesirableResult`. + +**Step 3.** If no `task` is found, `CommandException` will be thrown with an error message. + +**Step 4.** If there is at least one task found, the `model`'s `filteredRoomTaskRecords` is updated with a `dueDatePredicate` using `updateTasksInFilteredRoomTaskRecords`. + +**Step 5.** a new `CommandResult` will be returned with the message. + +_Written by Wai Lok_ + +### 4.4 Logging Feature + +We are using `java.util.logging` package for logging. The `LogsCenter` class is used to manage the logging levels and logging destinations. + +* The logging level can be controlled using the `logLevel` setting in the configuration file (See [Section 4.5, "Configuration Features"](#45-configuration-feature)) +* The `Logger` for a class can be obtained using `LogsCenter.getLogger(Class)` which will log messages according to the specified logging level +* Log messages are output through: `Console` and to a `.log` file + +**Logging Levels**: +* `SEVERE` : Critical problem detected which may possibly cause the termination of the application +* `WARNING` : Can continue, but with caution +* `INFO` : Information showing the noteworthy actions by the App +* `FINE` : Details that is not usually noteworthy but may be useful in debugging e.g. print the actual list instead of just its size + +_Written by Ming De_ + +### 4.5 Configuration Feature + +Certain properties of the application can be controlled (e.g user prefs file location, logging level) through the configuration file (default: `config.json`). + + +-------------------------------------------------------------------------------------------------------------------- + +## 5. Planned Features + +This section describes the features planned for the next iteration of Covigent. + +### 5.1 Single Command to Create and Allocate Patient to Room + +When the user wants to assign a patient to a room, she must run 2 commands: `addpatient` and `allocateroom`. The former creates a new `Patient` object in Covigent and the latter assigns that `Patient` to an empty room. +To improve the user's productivity, we can allow the user to specify a room number when executing `addpatient`. If the room and patient are valid, the patient is added to Covigent and assigned to the room in a single command. + +Since these changes will add another layer of complexity, we plan to complete this feature in Covigent v1.5. + +### 5.2 Search for Task by Description + +The `searchtask` command allows searching for tasks only by due dates. A user with many tasks would most likely want to search for tasks by description. + +In addition to modifying `SearchTaskCommand` and `SearchTaskCommandParser`, a new class `DescriptionPredicate` might have to be introduced to filter the tasks by description. +Considering these additional changes, we plan to complete this feature in Covigent v1.5. + +### 5.3 Scroll to Modified Task in User Interface + +Whenever a user adds or edits a `Patient` or `Room`, the Patients and Rooms tabs in the user interface scrolls to the entry corresponding to the most recent change. +This is useful as the user can immediately review the latest modification on the user interface. To accomplish this, `PatientListPanel` and `RoomListPanel` attach a listener to `ListView` and `ListView` respectively to update the user interface when changes are detected. + +Currently, when the user modifies a `Task`, the user interface does not scroll to the latest change. The reason is that the current implementation of the Tasks tab retrieves the room and task details from a `ListView`, as seen in `RoomTaskListPanel`. +The underlying list for `ListView`, found in `RoomTaskRecords` is altered each time there are changes to a room, including not only the tasks but also the room itself and the patient in it. +Hence, the list is unable to distinguish between modifications to tasks, to rooms, or to patients in rooms. Since we do not want the user interface for `Task` to scroll when there are changes to `Room` or `Patient`, we have disabled the listener for `ListView`. + +Fixing this issue likely requires a significant overhaul of the implementation of the Tasks tab. We plan to complete this feature in Covigent v2.0. + +_Written by Yee Hong_ -------------------------------------------------------------------------------------------------------------------- -## **Documentation, logging, testing, configuration, dev-ops** +## 6. Documentation * [Documentation guide](Documentation.md) * [Testing guide](Testing.md) @@ -230,81 +823,327 @@ _{Explain here how the data archiving feature will be implemented}_ -------------------------------------------------------------------------------------------------------------------- -## **Appendix: Requirements** +## 7. Appendix -### Product scope +### A1. Product scope **Target user profile**: +* Needs to manage a significant number of patients and their tasks +* Needs to manage a significant number of rooms +* Wants to keep track of patients and their tasks efficiently +* Wants to look up patients, rooms and tasks details quickly +* Prefers desktop apps over other types +* Prefers typing to mouse interactions +* Prefers all information to be available at one place +* Can type fast +* Is reasonably comfortable using Command Line Interface (CLI) apps -* has a need to manage a significant number of contacts -* prefer desktop apps over other types -* can type fast -* prefers typing to mouse interactions -* is reasonably comfortable using CLI apps - -**Value proposition**: manage contacts faster than a typical mouse/GUI driven app +**Value proposition**: +* Covigent is a handy tool for quarantine facility managers to manage the rooms and patients in the quarantine facility with increased productivity. +* Covigent stores and retrieves information faster than a typical mouse/GUI driven app. -### User stories +### A2. 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 | +| `* * *` | staff of a quarantine facility | key in new patient information | keep track of the information of new patients in the facility | +| `* * *` | staff of a quarantine facility | edit patient information | update his/her health status | +| `* * *` | staff of a quarantine facility | delete the records of patients who no longer resides in the facility | focus on information of existing patients residing in the facility| +| `* * *` | staff of a quarantine facility | view which rooms are empty | allocate patients to them | +| `* * *` | staff of a quarantine facility | allocate patients to room |keep track of which room they are living in| +| `* * *` | staff of a quarantine facility | quickly find the room that a patient is staying in | locate the patient in the facility easily | +| `* * *` | staff of a quarantine facility | key in new task information | keep track of the details of the tasks that I must complete | +| `* *` | staff of a quarantine facility | quickly find the room details of a given room number | track the patient and tasks in that room easily | +| `* *` | staff of the quarantine facility | indicate that I have completed the task in the room | let other staff know that they no longer have to handle them| +| `* *` | staff of a quarantine facility | find out all the outstanding tasks left in each room | serve the quarantined individuals better | +| `* *` | staff of a quarantine facility | quickly search through patient information | find the patients that match my criteria| +| `* *` | staff of a quarantine facility | look at all the rooms| find out which rooms are occupied and which are not | +| `* *`| staff of a quarantine facility | initialise the number of rooms many times with previous information retained | define correct number of rooms if mistake is made I can correct it without having to tediously key in information again| +|`* * `| staff of a quarantine facility | find out an empty room | to allocate patient to that empty room easily without having to search through the rooms for an empty room | +| `* * `| careless staff of a quarantine facility | edit a task | to allow for me to easily change a mistake that I made while adding tasks | +| `* * `| forgetful staff of a quarantine facility | search a task | to allow for me to easily search for a task that end before a certain due date | + +### A3. Use cases + +(For all use cases below, the **System** is `Covigent` and the **Actor** is `Hotel Staff`, unless specified otherwise. In addition, due to limitation in Github markdown, for use cases which include another use case, there will be no underlining of text.) + +**Use case: UC01 Add a patient** -*{More to be added}* +**MSS** -### Use cases +1. Hotel Staff requests to add a patient into Covigent. +2. Covigent adds the patient. +3. Covigent shows the details of the newly added patient. -(For all use cases below, the **System** is the `AddressBook` and the **Actor** is the `user`, unless specified otherwise) +Use case ends. -**Use case: Delete a person** +**Extensions** +* 2a. Covigent realises that the patient name already exists. + * 2a1. Covigent displays an error message. + +Use case ends. + +* 2b. Covigent realises that the patient information being entered does not conform to the system format. + * 2b1. Covigent displays an error message. + +Use case ends. + +**Use case: UC02 Delete a patient** **MSS** -1. User requests to list persons -2. AddressBook shows a list of persons -3. User requests to delete a specific person in the list -4. AddressBook deletes the person +1. Hotel Staff requests to delete a patient from Covigent. +2. Covigent deletes the patient and removes the patient from the room he/she is residing in. +3. Covigent shows the details of the deleted patient. - Use case ends. +Use case ends. **Extensions** +* 2a. Covigent realizes the patient does not exist. + * 2a1. Covigent displays an error message. + +Use case ends. -* 2a. The list is empty. +**Use case: UC03 Edit a patient** - Use case ends. +**MSS** -* 3a. The given index is invalid. +1. Hotel Staff inputs the new information about the patient. +2. Covigent edits the patient information to the new information. +3. Covigent shows the details of the edited patient. - * 3a1. AddressBook shows an error message. +Use case ends. - Use case resumes at step 2. +**Extensions** +* 1a. Covigent realizes that no optional fields are input. + * 1a1. Covigent displays an error message. + +Use case ends. -*{More to be added}* +**Use case: UC04 Search for a patient** -### Non-Functional Requirements +**MSS** -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. +1. Hotel Staff requests to search patients with a criterion. +2. Covigent searches the patients with the input criteria. +3. Covigent shows the search results. -*{More to be added}* +Use case ends. -### Glossary +**Extensions** +* 2a. Covigent realizes that no such patient is recorded + * 2a1. Covigent displays an error message. + +Use case ends. -* **Mainstream OS**: Windows, Linux, Unix, OS-X -* **Private contact detail**: A contact detail that is not meant to be shared with others +**Use case: UC05 Allocate a patient to a room** + +**MSS** + +1. Hotel Staff adds a patient to Covigent (UC01). +2. Hotel Staff requests to allocate the patient to a specified room. +3. Covigent adds the patient to the specified room. + +Use case ends. + +**Extensions** +* 2a. Covigent realizes that the specified room does not exist. + * 2a1. Covigent displays an error message. + +Use case ends. + +* 2b. Covigent realizes that the specified room is not empty. + * 2b1. Covigent displays an error message. + +Use case ends. + + +**Use case: UC06 List all rooms** + +**MSS** + +1. Hotel Staff requests to list all the rooms in quarantine facility. +2. Covigent lists all the rooms in the hotel and whether they are occupied or unoccupied. + +Use case ends. + +**Extensions** +* 1a. Covgient realizes that there are no rooms existing. + * 1a1. Covigent displays an error message. + +Use case ends. + +**Use case: UC07 Find empty room** + +**MSS** + +1. Hotel Staff requests to search for an empty room to accommodate patient. +2. Covigent finds an empty room. +3. Covigent shows the room to Hotel Staff. + +Use case ends. + +**Extensions** +* 1a. Covigent realizes that there are no rooms. + * 1a1. Covigent displays an error message. + +Use case ends. + +* 1b. Covigent realizes that there are no empty rooms. + * 1b1. Covigent displays an error message. + +Use case ends. + +**Use case: UC08 Initialize rooms** + +**MSS** + +1. Hotel Staff requests to change number of rooms. +2. Hotel Staff inputs the number of rooms to change into. +3. Covigent changes the number of rooms in the system. +4. Covigent displays all the current rooms. + +Use case ends. + +**Extensions** +* 2a. Covigent realizes that the Hotel Staff inputs an invalid number. + * 2a1. Covigent displays an error message. + +Use case ends. + +* 2b. Covigent realizes that the number of rooms input is less than number of occupied room + * 2b1. Covigent displays an error message. + +Use case ends. + +**Use case: UC09 Search for a room** + +**MSS** + +1. Hotel Staff requests to search for a room with the given patient. +2. Hotel Staff inputs the name of the patient to find the corresponding room. +3. Covigent displays the room details that the patient resides in. + +Use case ends. + +**Extensions** +* 2a. Covigent realizes that the patient does not exists in the system. + * 2a1. Covigent displays an error message. + +Use case ends. + +* 2b. Covigent realizes that the patient is not allocated to any room. + * 2b1. Covigent displays an error message. + +Use case ends. + +**Use case: UC10 Add a task to a room** + +**MSS** + +1. Hotel Staff requests to add a task to a room. +2. Covigent adds the task to the room. + +Use case ends. + +**Extensions** +* 1a. Covigent realizes that the specified room does not exist. + * 1a1. Covigent displays an error message. + +Use case ends. + +**Use case: UC11 Delete a task from a room** + +**MSS** + +1. Hotel Staff requests to delete a task from a room. +2. Covigent deletes the task from the room. + +Use case ends. + +**Extensions** +* 1a. Covigent realizes that the specified room does not exist. + * 1a1. Covigent displays an error message. + + Use case ends. + + * 1b. Covigent realizes that the specified task does not exist. + * 1b1. Covigent displays an error message. + + Use case ends. --------------------------------------------------------------------------------------------------------------------- -## **Appendix: Instructions for manual testing** +**Use case: UC12 Edit a task in a room** + +**MSS** + +1. Hotel Staff inputs the new information about the task. +2. Covigent edits the task information to the new information. +3. Covigent shows the details of the edited task. + +Use case ends. + +**Extensions** +* 1a. Covigent realizes that no optional fields are input. + * 1a1. Covigent displays an error message. + + Use case ends. + + * 1b. Covigent realizes that the specified room does not exist. + * 1b1. Covigent displays an error message. + + Use case ends. + + * 1c. Covigent realizes that the specified task does not exist. + * 1c1. Covigent displays an error message. + + Use case ends. + + * 1d. Covigent realizes that the new information for the task is the same as the original. + * 1d1. Covigent displays an error message. + + Use case ends. + +**Use case: UC13 Search Task** + +**MSS** + +1. Hotel Staff requests to search for a task before a specific date. +2. Hotel Staff inputs the specific date. +3. Covigent searches for tasks before the specific date. +4. Covigent displays the tasks before the specific date and success message. + +Use case ends. + +**Extensions** +* 2a. Covigent realizes that the format of the date is incorrect. + * 2a1. Covigent displays an error message. + +Use case ends. + +* 3a. Covigent realizes that there is no task matching the criteria. + * 3a1. Covigent displays an error message. + +Use case ends. + +### A4. Non-Functional Requirements + +1. Should work on any _mainstream OS_ as long as it has Java `11` or above installed. +1. Should be able to hold up to 100 patients without a noticeable sluggishness in performance for typical usage. +1. 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. +1. Should work even without internet connection. +1. Should respond to commands within 3 seconds. + +### A5. Glossary + +* **Mainstream OS**: Windows, Linux, Unix, OS-X +* **Patient**: An individual residing in the quarantine facility +* **Task**: Task is to be completed by staff of the quarantine facility + +## A6. Instructions for manual testing Given below are instructions to test the app manually. @@ -315,42 +1154,70 @@ testers are expected to do more *exploratory* testing. ### Launch and shutdown -1. Initial launch +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. Double-click the jar file

+ **Expected**: Shows the GUI with a set of sample patients. +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. **Storage** + + 1. Launch the application and make a change that changes the state of the program, such as `addpatient` or `initroom`. Close the window. + + 1. Re-launch the app by double-clicking the jar file.

+ **Expected**: The app should re-launch into the same state as when it was closed. - 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 +### Adding a patient -1. Deleting a person while all persons are being shown +1. **Adding a patient to Covigent** - 1. Prerequisites: List all persons using the `list` command. Multiple persons in the list. + 1. Prerequisites: User is viewing the patient tab. - 1. Test case: `delete 1`
- Expected: First contact is deleted from the list. Details of the deleted contact shown in the status message. Timestamp in the status bar is updated. + 1. Test case: `addpatient n/John Doe t/37.0 d/20200101-20200114 p/91234567 a/22`
+ **Expected**: Patient John Doe is added to the list. Details of the newly added patient is shown in the result box and the details panel. - 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 …​ }_ + 1. Test case: `addpatient n/John Doe`
+ **Expected**: No patient is added. Error details shown in the result box. + 1. Other incorrect add patient commands to try: `addpatient n/John Doe t/37.0 d/20200101-20190114 p/91234567 a/22`
+ **Expected**: Similar to previous. + ### Saving data -1. Dealing with missing/corrupted data files - - 1. _{explain how to simulate a missing/corrupted file, and the expected behavior}_ +1. **Dealing with missing data files** + + 1. Run the app once and play around with the application. Once a change is made, the program will generate a data files in `./data/`. + + 1. In `./data/`, delete `covigentapp.json` and `roomInformation.json`. + + 1. Re-launch the app.

+ **Expected**: Default Patient information should now be present in the Patient tab. No Rooms present in Room tab. + +1. **Corrupted data files** + + 1. Run the app once and play around with the application. Once a change is made, the program will generate a data files in `./data/`. + + 1. In `./data/`, open `covigentapp.json`. On line 2, delete the `[`:

+ ``` + 1 { + 2 "patients" : [ { + 3 ... + ``` + should become + + ``` + 1 { + 2 "patients" : { + 3 ... + ``` + 1. Re-launch the app.

+ **Expected**: Go to the Patient tab and the tab should not have any data. `covigentapp.json` still exists. -1. _{ more test cases …​ }_ diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 00000000000..899901192e2 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,10 @@ +### Covigent + +[![CI Status](https://github.com/se-edu/addressbook-level3/workflows/Java%20CI/badge.svg)](https://github.com/AY2021S1-CS2103T-W12-1/tp/actions) + +![Ui](images/Ui.png) +Covigent is a desktop app for managing information of quarantined individuals and the tasks to be done by staff of the quarantine facilities. It is optimized for use via a Command Line Interface (CLI) while still having the benefits of a Graphical User Interface (GUI). If you type fast, Covigent can improve your efficiency in managing your patients and tasks instead of using traditional GUI apps. + +Check out our [User Guide](https://ay2021s1-cs2103t-w12-1.github.io/tp/UserGuide.html) to get started. + +This project is based on the AddressBook-Level3 project created by the [SE-EDU initiative](https://se-education.org). diff --git a/docs/SettingUp.md b/docs/SettingUp.md index b89b691fed9..392f994ee0a 100644 --- a/docs/SettingUp.md +++ b/docs/SettingUp.md @@ -45,11 +45,4 @@ If you plan to use Intellij IDEA (highly recommended): 1. **Learn the design** - When you are ready to start coding, we recommend that you get some sense of the overall design by reading about [AddressBook’s architecture](DeveloperGuide.md#architecture). - -1. **Do the tutorials** - These tutorials will help you get acquainted with the codebase. - - * [Tracing code](tutorials/TracingCode.md) - * [Removing fields](tutorials/RemovingFields.md) - * [Adding a new command](tutorials/AddRemark.md) + When you are ready to start coding, we recommend that you get some sense of the overall design by reading about [Covigent’s architecture](DeveloperGuide.md#architecture). diff --git a/docs/UserGuide.md b/docs/UserGuide.md index b91c3bab04d..f83773f7a2e 100644 --- a/docs/UserGuide.md +++ b/docs/UserGuide.md @@ -1,178 +1,1202 @@ --- layout: page -title: User Guide +title: Covigent - 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. +1. [Introduction](#1-introduction) +1. [About this Guide](#2-about-this-guide)
+ 2.1. [What's in Covigent](#21-what-is-in-covigent)
+ 2.2. [Formatting in the Guide](#22-formatting-in-the-guide)
+1. [Getting Started](#3-getting-started)
+ 3.1. [Setting Up](#31-setting-up)
+ 3.2. [Parts of the Graphical User Interface](#32-parts-of-the-graphical-user-interface)
+       3.2.1. [Patients Tab](#321-patients-tab)
+       3.2.2. [Rooms Tab](#322-rooms-tab)
+       3.2.3. [Tasks Tab](#323-tasks-tab)
+ 3.3. [Tutorial on How to Use Covigent](#33-tutorial-on-how-to-use-covigent)
+1. [Glossary](#4-glossary) +1. [Features](#5-features)
+ 5.1. [Command Format](#51-command-format)
+ 5.2. [Patient](#52-patient)
+       5.2.1. [Add a Patient: `addpatient`](#521-add-a-patient-addpatient)
+       5.2.2 [Delete a Patient: `deletepatient`](#522-delete-a-patient-deletepatient)
+       5.2.3 [Edit Patient Details: `editpatient`](#523-edit-patient-details-editpatient)
+       5.2.4 [Search Patients by Information: `searchpatient`](#524-search-patients-by-information-searchpatient)
+       5.2.5 [List all Patients: `listpatient`](#525-list-all-patients-listpatient)
+ 5.3. [Room](#53-room)
+       5.3.1 [Initialise Rooms in Hotel: `initroom`](#531-initialise-rooms-in-hotel-initroom)
+       5.3.2 [Allocate Patient to Room: `allocateroom`](#532-allocate-patient-to-room-allocateroom)
+       5.3.3 [Search by Room Number: `searchroom`](#533-search-by-room-number-searchroom)
+       5.3.4 [Search for Room with Patient: `searchroom`](#534-search-for-room-with-patient-searchroom)
+       5.3.5 [List the Current Rooms: `listroom`](#535-list-the-current-rooms-listroom)
+       5.3.6 [Find the first free room: `findemptyroom`](#536-find-the-first-free-room-findemptyroom)
+ 5.4 [Task](#54-task)
+       5.4.1 [Add a Task to a Room: `addtask`](#541-add-a-task-to-a-room-addtask)
+       5.4.2 [Delete a Task from a Room: `deletetask`](#542-delete-a-task-from-a-room-deletetask)
+       5.4.3 [Edit Task Description or Due Date: `edittask`](#543-edit-task-description-or-due-date-edittask)
+       5.4.4 [Remove Due Date from a Task: `edittask`](#544-remove-due-date-from-a-task-edittask)
+       5.4.5 [Search all Tasks before the Given Date: `searchtask`](#545-search-tasks-before-a-given-date-searchtask)
+       5.4.6 [List all Tasks: `listtask`](#546-list-all-tasks-listtask)
+ 5.5 [View Help: `help`](#55-view-help-help)
+ 5.6 [Exit Covigent: `exit`](#56-exit-covigent-exit)
+ 5.7 [Autosave](#57-autosave)
+1. [Command Summary](#6-command-summary)
+ 6.1 [Patient](#61-patient)
+ 6.2 [Room](#62-room)
+ 6.3 [Task](#63-task)
+ 6.4 [General](#64-general)
+1. [FAQ](#7-faq) -* Table of Contents -{:toc} -------------------------------------------------------------------------------------------------------------------- -## Quick start +## 1. Introduction -1. Ensure you have Java `11` or above installed in your Computer. +Welcome to User Guide for our application, Covigent! Has Covid-19 hit your boutique hotel hard and left you scrambling to search for an application +that can ease your transition from a boutique hotel to a quarantine facility? Your search ends here! -1. Download the latest `addressbook.jar` from [here](https://github.com/se-edu/addressbook-level3/releases). +Covigent is a desktop management application to ease boutique hotels' transitions into quarantine facilities. Covigent aims to help boutique hotel managers +keep track of the information of quarantined individuals (i.e. patients) and the tasks to be done by their staff. The main features include:
-1. Copy the file to the folder you want to use as the _home folder_ for your AddressBook. +      1. Managing patient information - Period of Stay, temperature, phone number etc.
+      2. Managing the rooms of the quarantine facility - Allocate patients to rooms, search for a room, etc.
+      3. Managing the tasks to be done by each room - Add a task to a room, editing a task to the room, etc.
-1. Double-click the file to start the app. The GUI similar to the below should appear in a few seconds. Note how the app contains some sample data.
- ![Ui](images/Ui.png) +As an application optimized for use with a keyboard rather than the mouse, you operate Covigent mainly by +typing commands into a Command Box. If you can type quickly, Covigent can improve your efficiency in managing your patients and tasks instead of using traditional apps. +But wait! Don't forget our beautiful Graphical User Interface (GUI) too! +With our easy to navigate GUI, it has served it's purpose well as an interface that facilitates interaction with our application +through graphical icons. -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: +Getting interested? Jump to [Section 3, "Getting Started"](#3-getting-started) to get started. - * **`list`** : Lists all contacts. +This is what Covigent looks like: - * **`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. +

+ +
+ Figure 1. The Graphical User Interface for Covigent +

- * **`delete`**`3` : Deletes the 3rd contact shown in the current list. +_Written by: Ming De_ - * **`clear`** : Deletes all contacts. - * **`exit`** : Exits the app. +-------------------------------------------------------------------------------------------------------------------- + +## 2. About this Guide + +This guide gives you an overview of the features in Covigent and shows you how to get started using Covigent. +Choose a link in the [Feature](#5-features) section to get a step-by-step instruction, and understand how to use Covigent. + +### 2.1 What is in Covigent + +In our patient features, you can [add](#521-add-a-patient-addpatient), [delete](#522-delete-a-patient-deletepatient), +[edit](#523-edit-patient-details-editpatient), [search](#524-search-patients-by-information-searchpatient) for a patient, +or you can [list](#525-list-all-patients-listpatient) out all the patients in Covigent. + +Moving on to our room features, you can [initialise](#531-initialise-rooms-in-hotel-initroom), [allocate](#532-allocate-patient-to-room-allocateroom), +[search](#533-search-by-room-number-searchroom) and [find](#536-find-the-first-free-room-findemptyroom) the rooms which you need. +If you want an overview of the rooms, you can [list](#535-list-the-current-rooms-listroom) out all the rooms in Covigent. + +Next, for our task features, you can [add](#541-add-a-task-to-a-room-addtask), [delete](#542-delete-a-task-from-a-room-deletetask), +[edit](#543-edit-task-description-or-due-date-edittask) and [search](#545-search-tasks-before-a-given-date-searchtask) for a task +and see it be displayed on our amazing GUI. If you want to see all the tasks, you can [list](#546-list-all-tasks-listtask) out all the tasks in Covigent. + +Lastly, our miscellaneous features will ensure that you can seek [help](#55-view-help-help) if you are lost, or +[exit](#56-exit-covigent-exit) the program when you are done with it. Our app also has an [autosave](#57-autosave) feature that allows you to save the data without manual command. + + +### 2.2 Formatting in the Guide + +Note the following formatting used in this document: +* ![icon](images/ug/icon_info.png) +This symbol indicates important information. + +* ![icon](images/ug/icon_keyword.png) +A grey highlight (called a mark-up) indicates a keyword. It denotes either i) a field or command that can be typed into the Command Box and executed by Covigent; or ii) the name of a file. + +* ![icon](images/ug/icon_italics.png) +Italicised words indicate text that is displayed on the Graphical User Interface. + +* ![icon](images/ug/icon_hyperlink.png) +A light blue font color indicates that this is a Hyperlink that you can click on +and be transferred to the corresponding section in Covigent. + +_Written by: Wai Lok_ -1. Refer to the [Features](#features) below for details of each command. -------------------------------------------------------------------------------------------------------------------- -## Features +## 3. Getting Started -
+If you are tired of lengthy and problematic installation processes, Covigent is perfect for you. +The setup is minimal and can be completed in 4 simple steps. Follow the instructions below to try it out! -**:information_source: Notes about the command format:**
+### 3.1 Setting Up -* Words in `UPPER_CASE` are the parameters to be supplied by the user.
- e.g. in `add n/NAME`, `NAME` is a parameter which can be used as `add n/John Doe`. +The following steps to set up Covigent are applicable to Windows, Mac OS X, and Linux. -* 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`. +1. Install Java 11 or a later version. The latest version of Java can be found [here](https://java.com/en/download/). -* 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. +1. Download the latest version of Covigent from [here](https://github.com/AY2021S1-CS2103T-W12-1/tp/releases). To do so, look for the file `covigent.jar` and click on it. + Please refer to Figure 2 if you require assistance with locating the file. Figure 2 shows how to download version 1.3 of Covigent. `covigent.jar` has been highlighted in red for your convenience. +

+ +
+ Figure 2. How to Download Covigent +

-* Parameters can be in any order.
- e.g. if the command specifies `n/NAME p/PHONE_NUMBER`, `p/PHONE_NUMBER n/NAME` is also acceptable. +1. You may move `covigent.jar` to any other folder. Your save data and preferences for Covigent will be saved to the same folder. -
+1. Double-click on `covigent.jar` to start Covigent. Here is what you will see if everything goes right: +

+ +
+ Figure 3. Covigent after Starting Up +

-### Viewing help : `help` +In case there are any problems during setup, please refer to [Section 7, "FAQ"](#7-faq) for instructions to resolve them. -Shows a message explaning how to access the help page. +### 3.2 Parts of the Graphical User Interface -![help message](images/helpMessage.png) +The user interface of Covigent comprises 3 main tabs: Patients, Rooms, and Tasks. The tabs respectively display information on the +patients, the rooms in the quarantine facility, and the tasks assigned to the rooms. A tab turns a deeper shade of pink +when you click on it to indicate that you are currently in the tab. For example, Figure 4 shows how the Tasks tab changes colours when you click on it. +

+ +
+ Figure 4. User Interface of Covigent when a Tab is Clicked +

-Format: `help` +#### 3.2.1 Patients Tab +The main purpose of the Patients tab is to display information about the patients. -### Adding a person: `add` +When you first start Covigent, you will find yourself in the Patients tab. You may notice that there is already some sample data for quarantined +individuals. There is no need to worry! The data can be edited or deleted to suit your needs later. If you urgently need to change the data, +please proceed to [Section 5.2.3, "Edit Patient Details"](#523-edit-patient-details-editpatient) for the relevant instructions. -Adds a person to the address book. +Using the Patients tab is easy. Click on any of the patients. This will highlight that individual and display her information. +Figure 5 illustrates what happens when you click on a patient _David Li_. Observe that his details appear on the right of the +user interface, in the Details Panel. -Format: `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]…​` +

+ +
+ Figure 5. Highlighted Individual in Patients Tab +

-
:bulb: **Tip:** -A person can have any number of tags (including 0) -
+#### 3.2.2 Rooms Tab -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` +The main purpose of the Rooms tab is to display information about the rooms in the quarantine facility. Each room contains 1 quarantined +individual and multiple tasks. -### Listing all persons : `list` +When you first start Covigent and click into the Rooms tab, it will be empty. Rest assured that this is normal. Once you learn how to add rooms +to Covigent, the Rooms tab will be populated in no time. You may refer to [Section 3.3, "Tutorial on How to Use Covigent"](#33-tutorial-on-how-to-use-covigent) +for step-by-step guidance or [Section 5.3.1, "Initialise Rooms in Hotel"](#531-initialise-rooms-in-hotel-initroom) if you prefer a more hands-on approach. -Shows a list of all persons in the address book. +Using the Rooms tab is easy. Click on any of the rooms. This will highlight that room and display the patient and tasks allocated +to the room. Figure 6 shows what happens when you click on _Room #2_. The details of the patient _Alex Yeoh_ and task _Sanitise the room_ +are displayed on the right of the user interface, in the Details Panel. -Format: `list` +

+ +
+ Figure 6. Highlighted Room in Rooms Tab +

-### Editing a person : `edit` +#### 3.2.3 Tasks Tab -Edits an existing person in the address book. +The main purpose of the Tasks tab is to display information about the tasks assigned to the rooms. When you have too many tasks, it is common +to forget which room you have allocated a certain task to. The Tasks tab provides a quick overview of all the tasks in Covigent such that you do not +have to look through each room to find a specific task. -Format: `edit INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [t/TAG]…​` +When you first start Covigent and click into the Tasks tab, it will be empty. Do not panic! This is the default behaviour. As you add more tasks to the rooms, +those tasks will start to appear in the Tasks tab. You may refer to [Section 3.3, "Tutorial on How to Use Covigent"](#33-tutorial-on-how-to-use-covigent) +for step-by-step guidance or [Section 5.4.1, "Add a Task to a Room"](#541-add-a-task-to-a-room-addtask) if you prefer a more hands-on approach. -* Edits the person at the specified `INDEX`. The index refers to the index number shown in the displayed person list. The index **must be a positive integer** 1, 2, 3, …​ -* At least one of the optional fields must be provided. -* Existing values will be updated to the input values. -* When editing tags, the existing tags of the person will be removed i.e adding of tags is not cumulative. -* You can remove all the person’s tags by typing `t/` without - specifying any tags after it. +Using the Tasks tab is easy. Simply read the description and due date of the tasks directly from the user interface. For tasks with very long +descriptions, the user interface is unable to fully display them. In those cases, you may highlight the descriptions with your cursor and drag right to scroll through them. +Alternatively, you may triple-click on the descriptions, followed by a right click to copy them. Refer to Figure 7 for an example of the latter method. + +

+ +
+ Figure 7. Copying a Long Description in Tasks Tab +

-Examples: -* `edit 1 p/91234567 e/johndoe@example.com` Edits the phone number and email address of the 1st person to be `91234567` and `johndoe@example.com` respectively. -* `edit 2 n/Betsy Crower t/` Edits the name of the 2nd person to be `Betsy Crower` and clears all existing tags. +### 3.3 Tutorial on How to Use Covigent -### Locating persons by name: `find` +Covigent has numerous features, including adding patients, creating rooms, assigning tasks to rooms, and more. +Every feature has a corresponding command. Using a feature is as simple as typing a command in the Command Box (see Figure 8) and pressing Enter to execute it. -Finds persons whose names contain any of the given keywords. +

+ +
+ Figure 8. Command Box of Covigent +

-Format: `find KEYWORD [MORE_KEYWORDS]` +You have heard so much about the potential of Covigent. Excited? Without further ado, let's give these features a try! We will role-play the +following scenario to get you acquainted with a few common commands: your hotel has 10 rooms; a guest is arriving today and will quarantine with +your hotel for 14 days; and you must not forget to sanitise the room the day after he leaves. -* The search is case-insensitive. e.g `hans` will match `Hans` -* The order of the keywords does not matter. e.g. `Hans Bo` will match `Bo Hans` -* Only the name is searched. -* Only full words will be matched e.g. `Han` will not match `Hans` -* Persons matching at least one keyword will be returned (i.e. `OR` search). - e.g. `Hans Bo` will return `Hans Gruber`, `Bo Yang` +You may copy-and-paste the following commands (bold and in grey highlights) into the Command Box. Do not forget to press Enter, or the command will not execute! -Examples: -* `find John` returns `john` and `John Doe` -* `find alex david` returns `Alex Yeoh`, `David Li`
- ![result for 'find alex david'](images/findAlexDavidResult.png) +1. **`initroom 10`** : Let's set up the hotel room first. This command creates _10_ rooms in Covigent. If you proceed to the Rooms tab, you will see that 10 rooms + have been created (see Figure 9). -### Deleting a person : `delete` +

+ +
+ Figure 9. Creating Hotel Rooms in Covigent +

-Deletes the specified person from the address book. +2. **`addpatient n/John Doe t/37.4 d/20200910-20200924 p/98765432 a/35 c/Vegan`** : The guest has arrived. You have taken his temperature and need to enter his details into Covigent. + This command adds a new patient named _John Doe_ with age _35_, phone number _98765432_, period of stay _10 Sep 2020 to 24 Sep 2020_, temperature _37.4_, and a comment that he is _Vegan_. + If you proceed to the Patients tab, you will see that _John Doe's_ entry has been added. -Format: `delete INDEX` +

+ +
+ Figure 10. Adding a Patient to Covigent +

-* Deletes the person at the specified `INDEX`. -* The index refers to the index number shown in the displayed person list. -* The index **must be a positive integer** 1, 2, 3, …​ +3. **`findemptyroom`** : You would like to assign _John Doe_ a room but are unsure which rooms are empty. This command will search for an empty room. + Proceed to the Rooms tab. The result can be seen in Figure 11. -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. +

+ +
+ Figure 11. Finding an Empty Room in Covigent +

-### Clearing all entries : `clear` +4. **`allocateroom 1 n/John Doe`** : Now that you know _Room #1_ is empty, let's assign _Room #1_ to _John Doe_. This command will do exactly that. + Notice that _Room #1_ is now occupied and _John Doe's_ details are reflected in the Details Panel on the right of the user interface. -Clears all entries from the address book. +

+ +
+ Figure 12. Allocating a Room to a Patient in Covigent +

-Format: `clear` +5. **`addtask r/1 d/Sanitise the room dd/20200925 1800`** : Finally, we would like a reminder to sanitise the room after _John Doe_ leaves. + This command adds a task to _Room #1_ with description _Sanitise the room_ and due date _25 Sep 2020 1800_. Notice that the Details Panel + reflects the task now (see Figure 12). You may also click on the Tasks tab to view the newly added task. -### Exiting the program : `exit` +

+ +
+ Figure 13. Adding a Task to Covigent +

-Exits the program. +If you are curious about what other features are available in Covigent, please visit [Section 5, Features](#5-features). -Format: `exit` +_Written by: Yee Hong_ + + +-------------------------------------------------------------------------------------------------------------------- -### Saving the data +## 4. Glossary -AddressBook data are saved in the hard disk automatically after any command that changes the data. There is no need to save manually. +* **Graphical User Interface**: A form of user interface that allows users to interact with electronic devices through graphical icons.
+* **Patient**: An individual who resides in the quarantine facility.
+* **Task**: A task to be completed by staff of the quarantine facility.
-### Archiving data files `[coming in v2.0]` +_Written by: Yun Qing_ -_{explain the feature here}_ -------------------------------------------------------------------------------------------------------------------- -## FAQ +## 5. Features + +This section covers all the commands that you can type into the Command Box of Covigent. The commands are categorised into [patient features](#52-patient), [room features](#53-room) and [task features](#54-task). If it is the first time that you are using the command, we recommend that you refer to [Command Format](#51-command-format) to find out how to interpret the format of the commands. + +_Written by: Yun Qing_ + +### 5.1 Command Format + +**:information_source: Notes about the command format:**
+* Words in `UPPER_CASE` are the fields to be supplied by the user.
+ e.g. in `addpatient n/NAME`, `NAME` is a field which can be used as `addpatient n/John Doe`. + +* Fields in square brackets are optional.
+ e.g `n/NAME [c/COMMENT]` can be used as `n/John Doe c/Is vegan` or as `n/John Doe`. + +* Fields can be in any order.
+ e.g. if the command specifies `n/NAME p/PHONE_NUMBER`, `p/PHONE_NUMBER n/NAME` is also acceptable. + +* If the same field is provided multiple times, only the last input for that field will be processed.
+ e.g. if you input `n/John Doe n/Mary Doe`, Covigent will only process `n/Mary Doe` and ignore `n/John Doe`. Similarly, if you input `n/John Doe t/37.4 n/Mary Doe t/36.5`, Covigent will interpret the fields provided as simply `n/Mary Doe t/36.5`. + + +### 5.2 Patient + +This section contains all the commands related to patients. You can [add](#521-add-a-patient-addpatient), [delete](#522-delete-a-patient-deletepatient), +[edit](#523-edit-patient-details-editpatient), [search](#524-search-patients-by-information-searchpatient) for a patient, or you can [list](#525-list-all-patients-listpatient) out all the patients in Covigent. If you are unsure of how to interpret the command format, head back to [Command Format](#51-command-format) before executing the commands! + +#### 5.2.1 Add a Patient: `addpatient` + +You can use this command to add a patient with the following details: name, temperature, period of stay, phone number, age and comment.
+ +**Format:** `addpatient n/NAME t/TEMPERATURE d/PERIOD_OF_STAY p/PHONE_NUMBER a/AGE [c/COMMENT]`
+ +Field | Description +------------ | ------------- +`NAME` | The name of the patient. It should preferably be the full name of the patient. The maximum number of characters allowed is 150. +`TEMPERATURE` | The temperature of the patient. It must be keyed in to 1 decimal place (e.g. 37.0 instead of 37) and must be within the range 32.0 to 41.0 degree celsius, both inclusive. +`PERIOD_OF_STAY` | The period of stay of the patient in the facility. It must be in the format YYYYMMDD-YYYYMMDD. Both dates must be valid and the start date must be before or equals to the end date. +`PHONE_NUMBER` | The phone number of the patient that the patient wishes to be contacted by. The maximum number of digits allowed is 20. +`AGE` | The age of the patient, which is between 0 (inclusive) and 120 (exclusive). +`COMMENT` | An optional field that is used to indicate any special details of the patient such as dietary preferences or health conditions. + +**:information_source: Important Information:**
+* Duplicate names are not allowed. If an entry of name _John Doe_ is recorded in Covigent, you should not add a patient of the name _John Doe_ again. +
+ +**Example(s):** +1. `addpatient n/Betsy Crowe t/36.5 d/20201001-20201014 p/91234567 a/19 c/Is asthmatic` A patient named _Betsy Crowe_ with temperature _36.5_, period of stay from _1 October 2020 to 14 October 2020_, phone number _91234567_, age _19_ and comment _Is asthmatic_ is added to Covigent. +
+ +**Step By Step Usage:**
+1. Navigate to the Patients tab under the navigation bar as shown in Figure 14. +

+ +
+ Figure 14. Navigation to Patients Tab +

+1. Using the first example, key in the command into the Command Box as shown below. +

+ +
+ Figure 15. addpatient Command in Command Box +

+1. Press Enter to run the command. +1. The Result Box displays the sucess message shown in Figure 16. The Details Panel now reflects the information of the newly added patient and the patient is selected in the Patient List. +

+ +
+ Figure 16. A Successful Execution of addpatient Command +

+1. If you do not see the success message as shown in Figure 16, please repeat step 2 onwards. + +_Written by: Yun Qing_ + +#### 5.2.2 Delete a Patient: `deletepatient` + +You can use this command to delete the details of the existing patient identified by his/her name from Covigent.
+ +**Format:** `deletepatient NAME`
+ +Field | Description +------------ | ------------- +`NAME` | The name of the patient to be deleted. It is case-insensitive and must match exactly with the name of the patient that was input into Covigent previously. + +**:information_source: Important Information:**
+* If the patient to be deleted was allocated a room previously, the room will be updated to unoccupied in Covigent but the tasks (if any) in that room will remain unchanged. +
+ +**Example(s):** +1. `deletepatient Mary Doe` The patient details of Mary Doe will be deleted from Covigent. +
+ +**Step By Step Usage:**
+1. Navigate to the Patients tab under the navigation bar as shown in Figure 17. +

+ +
+ Figure 17. Navigation to Patients Tab +

+1. Using the first example, key in the command into the Command Box as shown below. +

+ +
+ Figure 18. deletepatient Command in Command Box +

+1. Press Enter to run the command. +1. The Result Box displays the success message shown in Figure 19 and the deleted patient can no longer be found in the list of patients in Covigent. The Patient List now hovers back to the first patient in the list and the Details Panel displays the information of the first patient. +

+ +
+ Figure 19. A Successful Execution of deletepatient +

+1. If you do not see the success message as shown in Figure 19, please repeat step 2 onwards. + +_Written by: Yun Qing_ + +#### 5.2.3 Edit Patient Details: `editpatient` + +You can use this command to edit an existing patient's details in Covigent. + +**Format**: `editpatient ORIGINAL_NAME [n/NEW_NAME] [t/TEMPERATURE] [d/PERIOD_OF_STAY] [p/PHONE_NUMBER] [a/AGE] [c/COMMENT]`
+ +Field | Description +------------ | ------------- +`ORIGINAL_NAME` | The name of the patient whom details are to be edited. It must match exactly with the name of the patient that was input into Covigent previously. It is case-insensitive. +`NEW_NAME` | The new name of the patient. The new name must not already exist within Covigent and the maximum number of characters allowed is 150. +`TEMPERATURE` | The new temperature of the patient. It must be keyed in to 1 decimal place (e.g. 37.0 instead of 37) and must be within the range 32.0 to 41.0 degree Celsius, both inclusive. +`PERIOD_OF_STAY` | The new period of stay of the patient. It must be in the format YYYYMMDD-YYYYMMDD. Both dates must be valid and the start date must be before or equals to the end date. +`PHONE_NUMBER` | The new phone number of the patient. The maximum number of digits allowed is 20. +`AGE` | The new age of the patient. Age should be between 0 (inclusive) and 120 (exclusive). +`COMMENT` | The new comments about the patient. + +**:information_source: Important Information:**
+* At least one of the optional fields must be provided. +* Existing values will be updated to the input values. +
+ +**Example(s):** +1. `editpatient john doe p/91234567` The phone number of the patient named John Doe will be updated to _91234567_. +1. `editpatient alex t/36.7 a/21 d/20200303-20200315` The temperature, age and period of stay of the patient named Alex will be updated to _36.7_, _21_ and _20200303-20200315_ respectively. + +**Step By Step Usage:**
+1. Navigate to the Patients tab under the navigation bar as shown in Figure 20. +

+ +
+ Figure 20. Navigation to Patients Tab +

+1. Using the first example, key in the command into the Command Box as shown below. +

+ +
+ Figure 21. editpatient Command in Command Box +

+1. Press Enter to run the command. +1. The Result Box displays the success message shown in Figure 22 and the patient has been edited in Covigent. +1. The details of the newly edited patient will be shown in the Details Panel. +

+ +
+ Figure 22. A Successful Execution of editpatient Command +

+1. If you do not see the success message as shown in Figure 11, please repeat step 2 onwards. + +_Written by: Ming De_ + +#### 5.2.4 Search Patients by Information: `searchpatient` + +You can use this command to search patients that match the given criteria(name or a range of temperature) in Covigent. + +**Format**: `searchpatient [n/NAME] [tr/TEMPERATURE_RANGE]` + +Field | Description +------------ | ------------- +`NAME` | The full name or the sub-name of the patient you want to look for. If the patient's name is "Alex Joe", then you must enter "Alex", "Joe" or "Alex Joe" to find him. You input is case-insensitive. +`TEMPERATURE_RANGE` | The temperature range that you want to look for. It is made up of two valid temperatures. A temperature must be keyed in to 1 decimal place (e.g. 37.0 instead of 37). The two temperature are linked using a dash "-". The `TEMPERATURE_RANGE` is inclusive of start and end temperatures. `tr/35.5-36.0` means a temperature range of 35.5-36.0 degree, celsius, both inclusive. + +**:information_source: Important Information:**
+* Only one of the fields can be provided. If the field `n/name` is entered, you should not enter the field `tr/TEMPERATURE_RANGE`.
+* The two temperature you enter for field `tr/TEMPERATURE_RANGE` must both be valid, if one of the temperature is invalid, the search function will fail.
+
+ +**Example(s):** +1. `searchpatient n/john` Searches patients with names containing _john_. +1. `searchpatient tr/36.5-36.7` Searches patients within temperature range _36.5 to 36.7_ degree celsius, both inclusive. + +**Step By Step Usage:**
+ +1. Navigate to the Patients tab under the navigation bar as shown in Figure 23. +

+ +
+ Figure 23. Navigation to Patients Tab +

+1. Before the search, this is what you see in the list of patient. +

+ +
+ Figure 24. Patient List before searchpatient Command +

+1. Using the second example, key in the command into the Command Box as shown below. +

+ +
+ Figure 25. searchpatient Command in Command Box +

+1. Press Enter to run the command. +1. The Result Box displays the success message shown in Figure 26 and you can find the patients with temperatures in the inputted temperature range in Covigent. +

+ +
+ Figure 26. A Successful Execution of searchpatient +

+ +_Written by: Wai Lok_ + +#### 5.2.5 List all Patients: `listpatient` + +You can use this command to look at the list of all patients in the patient tab. + +**Format**: `listpatient` + +**Example(s)**: +1. `listpatient` Lists all patients that are present in Covigent. + +**Step By Step Usage:**
+ +1. Navigate to the Patients tab under the navigation bar as shown in Figure 27. +

+ +
+ Figure 27. Navigation to Patients Tab +

+1. Key in the command into the Command Box as shown below. +

+ +
+ Figure 28. listpatient Command in Command Box +

+1. Press Enter to run the command. +1. The Result Box displays the sucess message shown in Figure 29 and you can find the full list of patient in Covigent. +

+ +
+ Figure 29. A Successful Execution of listpatient +

+ +_Written by: Wai Lok_ + +### 5.3 Room + +This section contains all the commands related to rooms. You can [initialise](#531-initialise-rooms-in-hotel-initroom), [allocate](#532-allocate-patient-to-room-allocateroom), [search](#533-search-by-room-number-searchroom) and [find](#536-find-the-first-free-room-findemptyroom) the rooms which you need. +If you want an overview of the rooms, you can [list](#535-list-the-current-rooms-listroom) out all the rooms in Covigent. If you are unsure of how to interpret the command format, head back to [Command Format](#51-command-format) before executing the commands! + + +#### 5.3.1 Initialise Rooms in Hotel: `initroom` + +You can use this command to initialise a number of rooms in the quarantine facility to the app, if there was data given previously, they would +be stored. + +Format: `initroom NUMBER_OF_ROOMS` + +Field | Description +----------|------------- +`NUMBER_OF_ROOMS` | The number of rooms you wish to have in hotel + +**:information_source: Important Information:**
+* The maximum number of rooms that can be defined is 500 as most boutique hotels have a maximum of 500 rooms. +* If suppose you `initroom x` and x is already the number of rooms, no error would be seen to improve user experience. +* If the number of rooms is less than the number of patients error is thrown when decreasing the number of existing rooms. +* Adds `NUMBER_OF_ROOMS` rooms into the hotel system, if there were previously added information that information for respective rooms will still be there. +
+ +**Example(s):** +1. `initroom 123`. 123 rooms are initialised in Covigent. +1. `initroom 400`. 400 rooms are initialised in Covigent. + +**Step By Step Usage:**
+1. Navigate to the Rooms tab under the navigation bar as shown in Figure 30. +

+ +
+ Figure 30. Navigation to Rooms Tab +

+1. Using the first example, key in the command into the Command Box as shown below. +

+ +
+ Figure 31. Navigation to Rooms Tab +

+1. Press Enter to run the command. +1. The Result Box shows a success message "Initialise the number of rooms to 400 rooms in the application.". +

+ +
+ Figure 32. A Successful Execution of initroom +

+ +_Written by: Noorul Azlina_ + +#### 5.3.2 Allocate Patient to Room: `allocateroom` + +You can use this command to allocate a patient to a room. + +**Format**: `allocateroom ROOM_NUMBER n/PATIENT_NAME` + +Field | Description +----------|------------- +`ROOM_NUMBER` | The room number of the room of which the patient is to be allocated to. A room with the `ROOM_NUMBER` must already exist within Covigent. +`PATIENT_NAME` | The name of the patient to be allocated to the room. It is case-sensitive but must match exactly with the name of the patient that was input into Covigent previously. + +**:information_source: Important Information:**
+* To remove a patient from the room, input the patient name as "-". Refer to the example below for more clarity. +
+ +**Example(s):** +1. `allocateroom 1 n/john doe`. The patient named _John Doe_ will be allocated to _Room #1_. +1. `allocateroom 1 n/-`. The previous patient will be removed from _Room #1_. + +**Step By Step Usage:**
+1. Navigate to the Rooms tab under the navigation bar as shown in Figure 33. +

+ +
+ Figure 33. Navigation to Rooms Tab +

+1. Using the first example, key in the command into the Command Box as shown below. +

+ +
+ Figure 34. allocateroom Command in Command Box +

+1. Press Enter to run the command. +1. The Result Box displays the sucess message shown in Figure 35 and the patient is allocated to the room. +The room with the newly allocated patient is shown in the room Details Panel. +

+ +
+ Figure 35. A Successful Execution of allocateroom for Room Number +

+1. If you do not see the success message as shown in Figure 35, please repeat step 2 onwards. + +_Written by: Ming De_ + +#### 5.3.3 Search by Room Number: `searchroom` + +You can use this command to search for the room details with the specified room number. + +**Format:** `searchroom r/ROOM_NUMBER` + +Field | Description +----------|------------- +`ROOM_NUMBER` | The room number of the room to be searched for, which is a positive integer. The room number should be present in the list of rooms in Covigent. + +**Example(s):** +1. `searchroom r/6` The room details of room number 6 will be searched. + +**Step By Step Usage:**
+1. Navigate to the Rooms tab under the navigation bar as shown in Figure 36. +

+ +
+ Figure 36. Navigation to Rooms Tab +

+1. Using the first example, key in the command into the Command Box as shown below. +

+ +
+ Figure 37. searchroom Command in Command Box +

+1. Press Enter to run the command. +1. The Result Box displays the success message shown in Figure 38 and the room with the specified room number is shown in the room Details Panel. +

+ +
+ Figure 38. A Successful Execution of searchroom for Room Number +

+1. If you do not see the success message as shown in Figure 38, please repeat step 2 onwards. + +_Written by: Yun Qing_ + +#### 5.3.4 Search for Room with Patient: `searchroom` + +You can use this command to search for the room that the specified patient is residing in. + +**Format:** `searchroom n/NAME` + +Field | Description +----------|------------- +`NAME` | The name of the patient whose room you are looking for. It is case-insensitive and must match exactly with the name of the patient that was input into Covigent previously. + +**Example(s):** +1. `searchroom n/Mary Doe` The room details of the room that Mary Doe resides in will be searched. + +**Step By Step Usage:**
+1. Navigate to the Rooms tab under the navigation bar as shown in Figure 39. +

+ +
+ Figure 39. Navigation to Rooms Tab +

+1. Using the first example, key in the command into the Command Box as shown below. +

+ +
+ Figure 40. searchroom Command in Command Box +

+1. Press Enter to run the command. +1. The Result Box displays the success message shown in Figure 41 and the room that the specified patient resides in is shown in the room Details Panel. +

+ +
+ Figure 41. A Successful Execution of searchroom for Patient +

+1. If you do not see the success message as shown in Figure 41, please repeat step 2 onwards. + +_Written by: Yun Qing_ + +#### 5.3.5 List the Current Rooms: `listroom` + +You can use this command to list all the rooms in the hotel together with information of whether the room is occupied or not, provided there is at least one room defined + +Format: `listroom` All the rooms in Covigent. + +**Example(s):** +1. `listroom` all the rooms will be listed +2. `listroom abdcfbhfr` all rooms will be listed +3. `listroom 21232198` all rooms will be listed +4. `listroom 123ebhbh11` all rooms will be listed + +**:information_source: Important Information:**
+* If there are no rooms, then no rooms will be mentioned and informs user to initialize rooms using the `initroom` command. +* You need at least one room for this command to work.
+
+ +**Step By Step Usage:**
+1. Navigate to Rooms tab under the navigation bar as shown in Figure 42 +

+ +
+ Figure 42. Navigation to Rooms Tab +

+1. Next, type the command `listroom` in the Command Box as shown below +

+ +
+ Figure 43. listroom Command in Command Box +

+1. Press Enter to run the command +1. The Result Box shows a success message "All rooms are listed." +

+ +
+ Figure 44. A Successful Execution of listroom +

+ +_Written by: Noorul Azlina_ + +#### 5.3.6 Find the First Free Room: `findemptyroom` + +You can use this command to find the room with the lowest room number that is free for use. + +Format: `findemptyroom` The unoccupied room in Covigent with the lowest room number will be displayed on UI. + +**Example(s)**: +1. `findemptyroom` gives room with the lowest room number +2. `findemptyroom 238405` gives room with the lowest room number +3. `findemptyroom ahfbhrf` gives room with the lowest room number +4. `findemptyroom hsbf835dfj` gives room with the lowest room number + +**:information_source: Important Information:**
+* If there are no empty rooms then an error message informing hotel staff is mentioned. +
+ +**Step By Step Usage:**
+ +1. Navigate to Rooms tab under the navigation bar as shown in Figure 45. +

+ +
+ Figure 45. Navigation to Rooms Tab +

+1. Next, type the command `findemptyroom` in the Command Box as shown below +

+ +
+ Figure 46. findemptyroom Command in Command Box +

+1. Press Enter to run the command +1. The Result Box shows a success message that "Room Number 4 is empty" +

+ +
+ Figure 47. A Successful Execution of findemptyroom +

+ +_Written by: Noorul Azlina_ + +### 5.4 Task + +This section contains all the commands related to tasks. You can [add](#541-add-a-task-to-a-room-addtask), [delete](#542-delete-a-task-from-a-room-deletetask), +[edit](#543-edit-task-description-or-due-date-edittask) and [search](#545-search-tasks-before-a-given-date-searchtask) for a task in a room. +If you want to see all the tasks, you can [list](#546-list-all-tasks-listtask) out all the tasks in Covigent. If you are unsure of how to interpret the command format, head back to [Command Format](#51-command-format) before executing the commands! + +#### 5.4.1 Add a Task to a Room: `addtask` + +You can use this command to add a task to a room. The task has the following details: description and due date. + +Format: `addtask r/ROOM_NUMBER d/DESCRIPTION [dd/DUE_DATE]` + +Field | Description +------------ | ------------- +`ROOM_NUMBER` | The room number of the room to which you want to add the task. It refers to the number displayed beside each room under the list of rooms. An example of the room number highlighted in red:
+`DESCRIPTION` | The description of the task. It must be between 1 and 4000 characters long. Note that empty inputs such as multiple spaces are considered 0 characters and are not allowed. The description can be related to the patient in the room, e.g. _Call the patient_; or related to the room, e.g. _Restock the supplies in this room_. +`DUE_DATE` | An optional field that is used to indicate by what date and time should the task be completed. It can in any of the following formats:
i) YYYYMMDD, e.g. `20210131`
ii) YYYYMMDD HHmm, e.g. `20210131 2359`
iii) D/M/YYYY, e.g. `31/1/2021` or `31/01/2021`
iv) D/M/YYYY HHmm, e.g. `31/1/2021 2359` or `31/01/2021 2359` + +**:information_source: Important Information:**
+* After you add a task successfully, the task will appear in both the room you have added it to, and also in the Tasks tab. +* `DUE_DATE` defaults to `-` if you leave out the field, i.e. the task has no due date. +* If you do not provide the time for a `DUE_DATE`, it defaults to 0000 (12am). +
+ +**Example(s):** +1. `addtask r/5 d/Running low on masks and needs to be restocked. dd/12/1/2021` A task with description _Running low on masks and needs to be restocked._ and due date _12 Jan 2021 0000_ is added to _Room #5_. +1. `addtask r/1 d/Remind patient to change bedsheets.` A task with description _Remind patient to change bedsheets._ and no due date is added to _Room #1_. + +**Step By Step Usage:**
+1. Navigate to the Rooms tab under the navigation bar as shown in Figure 48. +

+ +
+ Figure 48. Navigation to Rooms Tab +

+1. For this example, the task will be added to _Room #5_. Please ensure there at least 5 rooms in Covigent, i.e. you can find _Room #5_ in the Rooms tab. + If you cannot find _Room #5_, do not worry. That means that the room has not been created yet. To create the rooms, please key in `initroom 5` into the Command Box. + For more details on creating room, refer to [Section 5.3.1, "Initialise Rooms in Hotel"](#531-initialise-rooms-in-hotel-initroom). +1. Key the command into the Command Box as shown below. +

+ +
+ Figure 49. addtask Command in Command Box +

+1. Press Enter to run the command. +1. The Result Box displays a success message. Click on _Room #5_ to view the newly added task. Refer to Figure 50 for the success message and to confirm that details of the task are displayed in the Details Panel. +

+ +
+ Figure 50. A Successful Execution of addtask Command +

+1. If you do not see the success message as shown in Figure 50, please check that there are at least 5 rooms in Covigent as per step 2, and that you have keyed in the command correctly as in step 3. + +_Written by: Yee Hong_ + +#### 5.4.2 Delete a Task from a Room: `deletetask` + +You can use this command to delete an existing task from a room. + +Format: `deletetask r/ROOM_NUMBER t/TASK_NUMBER` + +Field | Description +------------ | ------------- +`ROOM_NUMBER` | The room number of the room from which you want to delete the task. It refers to the number displayed beside each room under the list of rooms. An example of the room number highlighted in red:
+`TASK_NUMBER` | The task number of the task to be deleted. It refers to the number displayed beside each task in the Details Panel for rooms. An example of the task number highlighted in red:

Alternatively, you may find the task number in the Tasks tab. Here is the task number of the same task, but found in the Tasks tab:
+ +**:information_source: Important Information:**
+* After you delete a task successfully, the task will not only disappear from room it was in, but also from the Tasks tab. +* Once you delete a task, the `TASK_NUMBER` of the remaining tasks changes accordingly. Figure 51 illustrates how _Task 3_ is updated to _Task 2_ after the original Task 2 is deleted. +

+ +
+ Figure 51. Task Number Changing after deletetask +

+
+ +**Example(s):** +1. `deletetask r/5 t/1` The first task (_Task 1_) of _Room #5_ is deleted. + +**Step By Step Usage:**
+1. Navigate to the Rooms tab under the navigation bar as shown in Figure 52. +

+ +
+ Figure 52. Navigation to Rooms Tab +

+1. For this example, _Task 1_ of _Room #5_ is deleted. Please replace the `5` with the `ROOM_NUMBER` of the room containing the task and the `1` with the `TASK_NUMBER` of the task that you want to delete. +1. Key the command into the Command Box as shown below, replacing `ROOM_NUMBER` and `TASK_NUMBER` as necessary. +

+ +
+ Figure 53. deletetask Command in Command Box +

+1. Press Enter to run the command. +1. The Result Box displays a success message. You have successfully deleted _Task 1_ of _Room #5_ (see Figure 54). +

+ +
+ Figure 54. A Successful Execution of deletetask Command +

+1. If you do not see a success message similar to Figure 54, please check that you have entered the correct `ROOM_NUMBER` and `TASK_NUMBER`. + +_Written by: Yee Hong_ + +#### 5.4.3 Edit Task Description or Due Date: `edittask` + +You can use this command to edit the description or due date of an existing task in a room. + +Format: `edittask r/ROOM_NUMBER t/TASK_NUMBER [d/DESCRIPTION] [dd/DUE_DATE]` + +Field | Description +------------ | ------------- +`ROOM_NUMBER` | The room number of the room from which you want to edit the task. It refers to the number displayed beside each room under the list of rooms. An example of the room number highlighted in red:
+`TASK_NUMBER` | The task number of the task to be edited. It refers to the number displayed beside each task in the Details Panel for rooms. An example of the task number highlighted in red:

Alternatively, you may find the task number in the Tasks tab. Here is the task number of the same task, but found in the Tasks tab:
+`DESCRIPTION` | The description of the task. It must be between 1 and 4000 characters long. Note that empty inputs such as multiple spaces are considered 0 characters and are not allowed. The description can be related to the patient in the room, e.g. _Call the patient_; or related to the room, e.g. _Restock the supplies in this room_. +`DUE_DATE` | An optional field that is used to indicate by what date and time should the task be completed. It can in any of the following formats:
i) YYYYMMDD, e.g. `20210131`
ii) YYYYMMDD HHmm, e.g. `20210131 2359`
iii) D/M/YYYY, e.g. `31/1/2021` or `31/01/2021`
iv) D/M/YYYY HHmm, e.g. `31/1/2021 2359` or `31/01/2021 2359` + +**:information_source: Important Information:**
+* At least one of the optional fields must be provided. +* Existing values will be updated to the input values. +* A task will not be edited if there are no changes to its description or due date. +* If you do not provide the time for a `DUE_DATE`, it defaults to _0000_ (12am). +
+ +**Example(s):** +1. `edittask r/5 t/1 d/Return a call to the patient. dd/12/1/2021 1500` The description and due date of the first task (_Task 1_) in _Room #5_ are updated to _Return a call to the patient._ and _12 Jan 2021 1500_ respectively. +1. `edittask r/2 t/2 dd/12/1/2021` The due date for the second task (_Task 2_) of _Room #2_ is updated to _12 Jan 2021 0000_. The description remains unchanged. + +**Step By Step Usage:**
+1. Navigate to the Rooms tab under the navigation bar as shown in Figure 55. +

+ +
+ Figure 55. Navigation to Rooms Tab +

+1. For this example, _Task 1_ of _Room #5_ is edited. Please replace the `5` with the `ROOM_NUMBER` of the room containing the task and the `1` with the `TASK_NUMBER` of the task that you want to edit. +1. Key the command into the Command Box as shown below, replacing `ROOM_NUMBER`, `TASK_NUMBER`, `DESCRIPTION`, and `DUE_DATE` as necessary. +

+ +
+ Figure 56. edittask Command to Edit Description and Due Date in Command Box +

+1. Press Enter to run the command. +1. The Result Box displays a success message. Click on _Room #5_ to view the edited task. Refer to Figure 57 for the success message and to confirm that details of the edited task are displayed in the Details Panel. +

+ +
+ Figure 57. A Successful Execution of edittask Command to Edit Description and Due Date +

+1. If you do not see a success message similar to Figure 57, please check that you have entered the correct `ROOM_NUMBER` and `TASK_NUMBER`, and at least one of the optional fields. + +_Written by: Yee Hong_ + +#### 5.4.4 Remove Due Date from a Task: `edittask` + +You can use this command to remove a due date from a task in a room. + +Format: `edittask r/ROOM_NUMBER t/TASK_NUMBER dd/-` + +Field | Description +------------ | ------------- +`ROOM_NUMBER` | The room number of the room from which you want to edit the task. It refers to the number displayed beside each room under the list of rooms. An example of the room number highlighted in red:
+`TASK_NUMBER` | The task number of the task to be edited. It refers to the number displayed beside each task in the Details Panel for rooms. An example of the task number highlighted in red:

Alternatively, you may find the task number in the Tasks tab. Here is the task number of the same task, but found in the Tasks tab:
+`-` | A `-` indicates the removal of a due date. + +**:information_source: Important Information:**
+* Existing values will be updated to the input values. +* The due date of a task cannot be removed if it does not originally have a due date. +* This is only one feature of the `edittask` command. Refer to the full command [here](#543-edit-task-description-or-due-date-edittask). +
+ +**Example(s):** +1. `edittask r/5 t/1 dd/-` The due date of the first task (_Task 1_) in Room #5 is removed, i.e. set to `-`. + +**Step By Step Usage:**
+1. Navigate to the Rooms tab under the navigation bar as shown in Figure 58. +

+ +
+ Figure 58. Navigation to Rooms Tab +

+1. For this example, the due date of _Task 1_ of _Room #5_ is removed. Please replace the `5` with the `ROOM_NUMBER` of the room containing the task and the `1` with the `TASK_NUMBER` of the task from which you want to remove the due date. +1. Key the command into the Command Box as shown below, replacing `ROOM_NUMBER`, `TASK_NUMBER` as necessary. +

+ +
+ Figure 59. edittask Command to Remove Due Date in Command Box +

+1. Press Enter to run the command. +1. The Result Box displays a success message. Click on _Room #5_ to view the edited task. Refer to Figure 57 for the success message and to confirm that details of the edited task are displayed in the Details Panel. +

+ +
+ Figure 60. A Successful Execution of edittask Command to Remove Due Date +

+1. If you do not see a success message similar to Figure 60, please check that you have entered the correct `ROOM_NUMBER` and `TASK_NUMBER`, and that the task you want to edit has a due date. + +_Written by: Yee Hong_ + +#### 5.4.5 Search Tasks before a Given Date: `searchtask` + +You can use this command to search all tasks before a date in Covigent. + +**Format**: `searchtask dd/DUE_DATE` + +Field | Description +----------|------------- +`DUE_DATE` | The due date you are looking for. It can in any of the following formats:
i) YYYYMMDD, e.g. `20210131`
ii) YYYYMMDD HHmm, e.g. `20210131 2359`
iii) D/M/YYYY, e.g. `31/1/2021` or `31/01/2021`
iv) D/M/YYYY HHmm, e.g. `31/1/2021 2359` or `31/01/2021 2359` + +**:information_source: Important Information:**
+* If you do not provide the time for a `DUE_DATE`, it defaults to _0000_ (12am). +
+ +**Example(s)**: +1. `searchtask dd/12/1/2021` Search all tasks before and including 12 January 2021 0000. + +**Step By Step Usage:**
+1. Navigate to the Tasks tab under the navigation bar as shown in Figure 61. +

+ +
+ Figure 61. Navigation to Tasks Tab +

+1. Using the example, key in the command into the Command Box as shown below. +

+ +
+ Figure 62. searchtask Command in Command Box +

+1. Press Enter to run the command. +1. With reference to Figure 63, the Result Box displays the message, "Tasks before the due date found.". +1. Now you can find tasks before and including _12 Jan 2021 0000_ can now be found in the list of tasks in Covigent. +

+ +
+ Figure 63. A Successful Execution of searchtask +

+ +_Written by: Wai Lok_ + +#### 5.4.6 List all Tasks: `listtask` + +You can use this command to look at the list of all tasks in the task tab. + +**Format**: `listtask` + +**Example(s)**: +1. `listtask` Lists all tasks that are present in Covigent. + +**Step By Step Usage:**
+ +1. Navigate to the Tasks tab under the navigation bar as shown in Figure 64. +

+ +
+ Figure 64. Navigation to Tasks tab +

+1. Key in the command into the Command Box as shown below. +

+ +
+ Figure 65. listtask Command in Command Box +

+1. Press Enter to run the command. +1. Now you can find the full list of patient in Covigent. +

+ +
+ Figure 66. A Successful Execution of listtask +

+ +_Written by: Wai Lok_ + +### 5.5 View help: `help` + +If you encounter any issues using Covigent and need to access this user guide again, simply key in `help` into the Command Box. + +**Format:** `help` + +**Step By Step Usage:**
+ +1. Key in the command into the Command Box as shown below. +

+ +
+ Figure 67. help Command in Command Box +

+1. Press Enter to run the command. +1. The Result Box displays the message as shown in Figure 68. +

+ +
+ Figure 68. Result Display Box +

+1. A new help window appears. +

+ +
+ Figure 69. Help Window +

+ +_Written by Yun Qing_ + +### 5.6 Exit Covigent: `exit` + +You can use this command to exit from Covigent. + +**Format:** `exit` + +**Step By Step Usage:**
+1. Key in the command into the Command Box as shown below. +

+ +
+ Figure 70. exit Command in Command Box +

+1. Press Enter to run the command. +1. Covigent exits and closes. + +_Written by: MingDe_ + +### 5.7 Autosave + +Covigent data are saved in the hard disk automatically after any command that changes the data. There is no need to save manually. -**Q**: How do I transfer my data to another Computer?
-**A**: Install the app in the other computer and overwrite the empty data file it creates with the file that contains the data of your previous AddressBook home folder. -------------------------------------------------------------------------------------------------------------------- -## Command summary +## 6. Command Summary + +This section provides a quick summary of the commands for [Patient](#61-patient), [Room](#62-room), [Task](#63-task) and [General](#64-general). For the detailed explanation of the usage of each command, head on to the [Features](#5-features) section to find out more. + +### 6.1 Patient + +Action | Format, Examples +--------|------------------ +**Add Patient** | `addpatient n/NAME t/TEMPERATURE d/PERIOD_OF_STAY p/PHONE_NUMBER a/AGE [c/COMMENT]`
e.g., addpatient n/Betsy Crowe t/36.5 d/20201001-20201014 p/91234567 a/19 c/Is asthmatic +**Delete Patient** | `deletepatient NAME`
e.g., deletepatient Mary Doe +**Edit Patient** | `editpatient NAME [n/NAME] [t/TEMPERATURE] [d/PERIOD_OF_STAY] [p/PHONE_NUMBER] [a/AGE] [c/COMMENT]`
e.g., editpatient James Lee t/36.5 +**Search Patient** | `searchpatient [n/NAME] [tr/TEMPERATURE_RANGE]`
e.g., searchpatient tr/36.5-36.7 +**List Patients** | `listpatient`
+ +### 6.2 Room + +Action | Format, Examples +--------|------------------ +**Initialise Room** | `initroom NUMBER_OF_ROOMS`
e.g., initroom 123 +**Allocate Patient to Room** | `allocateroom ROOM_NUMBER n/NAME`
e.g., allocateroom 5 n/David Li +**Search by Room Number** | `searchroom r/ROOM_NUMBER`
e.g., searchroom r/15 +**Search for Room with Patient** | `searchroom n/NAME`
e.g., searchroom n/Jane Doe +**List Rooms** | `listroom`
+**Find Empty Room** | `findemptyroom`
+ +### 6.3 Task + +Action | Format, Examples +--------|------------------ +**Add Task to Room** | `addtask r/ROOM_NUMBER d/DESCRIPTION [dd/DUE_DATE]`
e.g., addtask d/Running low on masks and needs to be restocked. r/5 dd/12/1/2021 +**Delete Task from Room** | `deletetask r/ROOM_NUMBER t/TASK_NUMBER`
e.g., deletetask r/5 t/1 +**Edit Task Description or Due Date** | `edittask r/ROOM_NUMBER t/TASK_NUMBER [d/DESCRIPTION] [dd/DUE_DATE]`
e.g., edittask r/5 t/1 dd/12/1/2021 1500 +**Remove Due Date from a Task** | `edittask r/ROOM_NUMBER t/TASK_NUMBER dd/-`
e.g., edittask r/5 t/1 dd/- +**Search Task** | `searchtask dd/DUE_DATE`
e.g., searchtask dd/12/1/2021 +**List Tasks** | `listtask`
+ +### 6.4 General 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` +**Exit** | `exit` + +_Written by: Yun Qing_ + +-------------------------------------------------------------------------------------------------------------------- + +## 7. FAQ + +**Q**: Why does the output sometimes appear red and sometimes black?
+**A**: If the command input is given in the wrong format, then the output is given in red. Also the correct format for the particular command is given.
+ + Example:
+ Invalid command format! + Please give the number of digits in numbers + Example: addRooms 200 + +**Q**: Will data be stored in the system after closing the app?
+**A**: The data is stored in the hard disk and therefore would not be deleted even if you close the app + +**Q**: How do I look at all the rooms and patient when only one is being displayed after commands such as `findroom`?
+**A**: Use the command `listroom` for rooms and `listpatient` for patients. + +**Q**: What should I do if nothing happens when I double click the Jar file?
+**A**: Open Terminal(Mac)/Command Prompt(Windows) . Type `cd [PATH TO DIRECTORY CONTAINING .JAR FILE]` . Type java `-jar covigent.jar` to run the application. + +_Written by: Noorul Azlina_
diff --git a/docs/_config.yml b/docs/_config.yml index 6bd245d8f4e..c4a25e8a3a9 100644 --- a/docs/_config.yml +++ b/docs/_config.yml @@ -1,4 +1,4 @@ -title: "AB-3" +title: "Covigent" theme: minima header_pages: @@ -8,7 +8,7 @@ header_pages: markdown: kramdown -repository: "se-edu/addressbook-level3" +repository: "AY2021S1-CS2103T-W12-1/tp" github_icon: "images/github-icon.png" plugins: diff --git a/docs/diagrams/DeleteSequenceDiagram.puml b/docs/diagrams/DeleteSequenceDiagram.puml deleted file mode 100644 index 1dc2311b245..00000000000 --- a/docs/diagrams/DeleteSequenceDiagram.puml +++ /dev/null @@ -1,69 +0,0 @@ -@startuml -!include style.puml - -box Logic LOGIC_COLOR_T1 -participant ":LogicManager" as LogicManager LOGIC_COLOR -participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR -participant ":DeleteCommandParser" as DeleteCommandParser LOGIC_COLOR -participant "d:DeleteCommand" as DeleteCommand LOGIC_COLOR -participant ":CommandResult" as CommandResult LOGIC_COLOR -end box - -box Model MODEL_COLOR_T1 -participant ":Model" as Model MODEL_COLOR -end box - -[-> LogicManager : execute("delete 1") -activate LogicManager - -LogicManager -> AddressBookParser : parseCommand("delete 1") -activate AddressBookParser - -create DeleteCommandParser -AddressBookParser -> DeleteCommandParser -activate DeleteCommandParser - -DeleteCommandParser --> AddressBookParser -deactivate DeleteCommandParser - -AddressBookParser -> DeleteCommandParser : parse("1") -activate DeleteCommandParser - -create DeleteCommand -DeleteCommandParser -> DeleteCommand -activate DeleteCommand - -DeleteCommand --> DeleteCommandParser : d -deactivate DeleteCommand - -DeleteCommandParser --> AddressBookParser : d -deactivate DeleteCommandParser -'Hidden arrow to position the destroy marker below the end of the activation bar. -DeleteCommandParser -[hidden]-> AddressBookParser -destroy DeleteCommandParser - -AddressBookParser --> LogicManager : d -deactivate AddressBookParser - -LogicManager -> DeleteCommand : execute() -activate DeleteCommand - -DeleteCommand -> Model : deletePerson(1) -activate Model - -Model --> DeleteCommand -deactivate Model - -create CommandResult -DeleteCommand -> CommandResult -activate CommandResult - -CommandResult --> DeleteCommand -deactivate CommandResult - -DeleteCommand --> LogicManager : result -deactivate DeleteCommand - -[<--LogicManager -deactivate LogicManager -@enduml diff --git a/docs/diagrams/ModelClassDiagram.puml b/docs/diagrams/ModelClassDiagram.puml deleted file mode 100644 index e85a00d4107..00000000000 --- a/docs/diagrams/ModelClassDiagram.puml +++ /dev/null @@ -1,56 +0,0 @@ -@startuml -!include style.puml -skinparam arrowThickness 1.1 -skinparam arrowColor MODEL_COLOR -skinparam classBackgroundColor MODEL_COLOR - -Package Model <>{ -Interface ReadOnlyAddressBook <> -Interface Model <> -Interface ObservableList <> -Class AddressBook -Class ReadOnlyAddressBook -Class Model -Class ModelManager -Class UserPrefs -Class ReadOnlyUserPrefs - -Package Person { -Class Person -Class Address -Class Email -Class Name -Class Phone -Class UniquePersonList -} - -Package Tag { -Class Tag -} -} - -Class HiddenOutside #FFFFFF -HiddenOutside ..> Model - -AddressBook .up.|> ReadOnlyAddressBook - -ModelManager .up.|> Model -Model .right.> ObservableList -ModelManager o--> "1" AddressBook -ModelManager o-left-> "1" UserPrefs -UserPrefs .up.|> ReadOnlyUserPrefs - -AddressBook *--> "1" UniquePersonList -UniquePersonList o--> "*" Person -Person *--> Name -Person *--> Phone -Person *--> Email -Person *--> Address -Person *--> "*" Tag - -Name -[hidden]right-> Phone -Phone -[hidden]right-> Address -Address -[hidden]right-> Email - -ModelManager -->"1" Person : filtered list -@enduml diff --git a/docs/diagrams/plantuml/AddPatientSequenceDiagram.puml b/docs/diagrams/plantuml/AddPatientSequenceDiagram.puml new file mode 100644 index 00000000000..2bba66d0544 --- /dev/null +++ b/docs/diagrams/plantuml/AddPatientSequenceDiagram.puml @@ -0,0 +1,76 @@ +@startuml +!include style.puml + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":CovigentAppParser" as CovigentAppParser LOGIC_COLOR +participant ":AddPatientCommandParser" as AddPatientCommandParser LOGIC_COLOR +participant "d:AddPatientCommand" as AddPatientCommand 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("addpatient n/Alex a/36") +activate LogicManager + +LogicManager -> CovigentAppParser : parseCommand("addpatient n/Alex a/36") +activate CovigentAppParser + +create AddPatientCommandParser +CovigentAppParser -> AddPatientCommandParser +activate AddPatientCommandParser + +AddPatientCommandParser --> CovigentAppParser +deactivate AddPatientCommandParser + +CovigentAppParser -> AddPatientCommandParser : parse("n/Alex a/36") +activate AddPatientCommandParser + +create AddPatientCommand +AddPatientCommandParser -> AddPatientCommand +activate AddPatientCommand + + +AddPatientCommand --> AddPatientCommandParser : +deactivate AddPatientCommand + +AddPatientCommandParser --> CovigentAppParser : +deactivate AddPatientCommandParser +'Hidden arrow to position the destroy marker below the end of the activation bar. +AddPatientCommandParser -[hidden]-> CovigentAppParser +destroy AddPatientCommandParser + +CovigentAppParser --> LogicManager : +deactivate CovigentAppParser + +LogicManager -> AddPatientCommand : execute() +activate AddPatientCommand + +AddPatientCommand -> Model : hasPatient(Patient) +activate Model + +Model --> AddPatientCommand +deactivate Model + +AddPatientCommand -> Model : addPatient(Patient) +activate Model + +Model --> AddPatientCommand +deactivate Model + +create CommandResult +AddPatientCommand -> CommandResult +activate CommandResult + +CommandResult --> AddPatientCommand +deactivate CommandResult + +AddPatientCommand --> LogicManager : result +deactivate AddPatientCommand + +[<--LogicManager +deactivate LogicManager +@enduml diff --git a/docs/diagrams/plantuml/AllocateRoomActivityDiagram.puml b/docs/diagrams/plantuml/AllocateRoomActivityDiagram.puml new file mode 100644 index 00000000000..5f0f90efacd --- /dev/null +++ b/docs/diagrams/plantuml/AllocateRoomActivityDiagram.puml @@ -0,0 +1,34 @@ +@startuml +skinparam activity { + BackgroundColor 176CC1 + FontName Calibri + FontColor White + BorderColor 176CC1 +} + +skinparam activityDiamond { + BackgroundColor 176CC1 + BorderColor 176CC1 +} + +start +:user inputs allocateroom; + +'Since the beta syntax does not support placing the condition outside the +'diamond we place it as the true branch instead. + +if () then (["-" is input as patient name]) + if () then ([No Patient in Room]) + :Show error message; + else ([else]) + :Remove patient from room; + endif +else ([else]) + if () then ([Patient name is valid]) + :Allocate patient to room; + else ([else]) + :Show error message; + endif +endif +stop +@enduml diff --git a/docs/diagrams/plantuml/AllocateRoomSequenceDiagram.puml b/docs/diagrams/plantuml/AllocateRoomSequenceDiagram.puml new file mode 100644 index 00000000000..2c561774c5d --- /dev/null +++ b/docs/diagrams/plantuml/AllocateRoomSequenceDiagram.puml @@ -0,0 +1,70 @@ +@startuml +!include style.puml + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":CovigentAppParser" as CovigentAppParser LOGIC_COLOR +participant ":AllocateRoomCommandParser" as AllocateRoomCommandParser LOGIC_COLOR +participant "d:AllocateRoomCommand" as AllocateRoomCommand 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("allocateroom 1 n/Alex") +activate LogicManager + +LogicManager -> CovigentAppParser : parseCommand("allocateroom 1 n/Alex") +activate CovigentAppParser + +create AllocateRoomCommandParser +CovigentAppParser -> AllocateRoomCommandParser +activate AllocateRoomCommandParser + +AllocateRoomCommandParser --> CovigentAppParser +deactivate AllocateRoomCommandParser + +CovigentAppParser -> AllocateRoomCommandParser : parse("1 n/Alex") +activate AllocateRoomCommandParser + +create AllocateRoomCommand +AllocateRoomCommandParser -> AllocateRoomCommand +activate AllocateRoomCommand + + +AllocateRoomCommand --> AllocateRoomCommandParser : d +deactivate AllocateRoomCommand + +AllocateRoomCommandParser --> CovigentAppParser : d +deactivate AllocateRoomCommandParser +'Hidden arrow to position the destroy marker below the end of the activation bar. +AllocateRoomCommandParser -[hidden]-> CovigentAppParser +destroy AllocateRoomCommandParser + +CovigentAppParser --> LogicManager : d +deactivate CovigentAppParser + +LogicManager -> AllocateRoomCommand : execute() +activate AllocateRoomCommand + +AllocateRoomCommand -> Model : setSingleRoom(roomToEdit, editedRoom) +activate Model + +Model --> AllocateRoomCommand +deactivate Model + +create CommandResult +AllocateRoomCommand -> CommandResult +activate CommandResult + +CommandResult --> AllocateRoomCommand +deactivate CommandResult + +AllocateRoomCommand --> LogicManager : result +deactivate AllocateRoomCommand + +[<--LogicManager +deactivate LogicManager +@enduml diff --git a/docs/diagrams/ArchitectureDiagram.puml b/docs/diagrams/plantuml/ArchitectureDiagram.puml similarity index 100% rename from docs/diagrams/ArchitectureDiagram.puml rename to docs/diagrams/plantuml/ArchitectureDiagram.puml diff --git a/docs/diagrams/ArchitectureSequenceDiagram.puml b/docs/diagrams/plantuml/ArchitectureSequenceDiagram.puml similarity index 74% rename from docs/diagrams/ArchitectureSequenceDiagram.puml rename to docs/diagrams/plantuml/ArchitectureSequenceDiagram.puml index ef81d18c337..5377276a89a 100644 --- a/docs/diagrams/ArchitectureSequenceDiagram.puml +++ b/docs/diagrams/plantuml/ArchitectureSequenceDiagram.puml @@ -7,19 +7,19 @@ Participant ":Logic" as logic LOGIC_COLOR Participant ":Model" as model MODEL_COLOR Participant ":Storage" as storage STORAGE_COLOR -user -[USER_COLOR]> ui : "delete 1" +user -[USER_COLOR]> ui : "deletepatient Alex Yeoh" activate ui UI_COLOR -ui -[UI_COLOR]> logic : execute("delete 1") +ui -[UI_COLOR]> logic : execute("deletepatient Alex Yeoh") activate logic LOGIC_COLOR -logic -[LOGIC_COLOR]> model : deletePerson(p) +logic -[LOGIC_COLOR]> model : deletePatient(p) activate model MODEL_COLOR model -[MODEL_COLOR]-> logic deactivate model -logic -[LOGIC_COLOR]> storage : saveAddressBook(addressBook) +logic -[LOGIC_COLOR]> storage : savePatientRecords(covigentApp) activate storage STORAGE_COLOR storage -[STORAGE_COLOR]> storage : Save to file diff --git a/docs/diagrams/BetterModelClassDiagram.puml b/docs/diagrams/plantuml/BetterModelClassDiagram.puml similarity index 100% rename from docs/diagrams/BetterModelClassDiagram.puml rename to docs/diagrams/plantuml/BetterModelClassDiagram.puml diff --git a/docs/diagrams/CommitActivityDiagram.puml b/docs/diagrams/plantuml/CommitActivityDiagram.puml similarity index 100% rename from docs/diagrams/CommitActivityDiagram.puml rename to docs/diagrams/plantuml/CommitActivityDiagram.puml diff --git a/docs/diagrams/plantuml/DeletePatientSequenceDiagram.puml b/docs/diagrams/plantuml/DeletePatientSequenceDiagram.puml new file mode 100644 index 00000000000..d6587324da7 --- /dev/null +++ b/docs/diagrams/plantuml/DeletePatientSequenceDiagram.puml @@ -0,0 +1,69 @@ +@startuml +!include style.puml + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":CovigentAppParser" as CovigentAppParser LOGIC_COLOR +participant ":DeletePatientCommandParser" as DeletePatientCommandParser LOGIC_COLOR +participant "d:DeletePatientCommand" as DeletePatientCommand 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("deletepatient alex") +activate LogicManager + +LogicManager -> CovigentAppParser : parseCommand("deletepatient 1") +activate CovigentAppParser + +create DeletePatientCommandParser +CovigentAppParser -> DeletePatientCommandParser +activate DeletePatientCommandParser + +DeletePatientCommandParser --> CovigentAppParser +deactivate DeletePatientCommandParser + +CovigentAppParser -> DeletePatientCommandParser : parse("alex") +activate DeletePatientCommandParser + +create DeletePatientCommand +DeletePatientCommandParser -> DeletePatientCommand +activate DeletePatientCommand + +DeletePatientCommand --> DeletePatientCommandParser : d +deactivate DeletePatientCommand + +DeletePatientCommandParser --> CovigentAppParser : d +deactivate DeletePatientCommandParser +'Hidden arrow to position the destroy marker below the end of the activation bar. +DeletePatientCommandParser -[hidden]-> CovigentAppParser +destroy DeletePatientCommandParser + +CovigentAppParser --> LogicManager : d +deactivate CovigentAppParser + +LogicManager -> DeletePatientCommand : execute() +activate DeletePatientCommand + +DeletePatientCommand -> Model : deletePatient(alex) +activate Model + +Model --> DeletePatientCommand +deactivate Model + +create CommandResult +DeletePatientCommand -> CommandResult +activate CommandResult + +CommandResult --> DeletePatientCommand +deactivate CommandResult + +DeletePatientCommand --> LogicManager : result +deactivate DeletePatientCommand + +[<--LogicManager +deactivate LogicManager +@enduml diff --git a/docs/diagrams/plantuml/EditTaskSequenceDiagram.puml b/docs/diagrams/plantuml/EditTaskSequenceDiagram.puml new file mode 100644 index 00000000000..7290f80ce6b --- /dev/null +++ b/docs/diagrams/plantuml/EditTaskSequenceDiagram.puml @@ -0,0 +1,66 @@ +@startuml +!include style.puml + +box Logic LOGIC_COLOR_T1 +participant ":EditTaskCommandParser" as EditTaskCommandParser LOGIC_COLOR +participant ":EditTaskCommand" as EditTaskCommand LOGIC_COLOR +participant ":CommandResult" as CommandResult LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant ":Model" as Model MODEL_COLOR +end box + +[-> EditTaskCommandParser : parse("r/2 t/1 d/HelloWorld") +activate EditTaskCommandParser + +create EditTaskCommand +EditTaskCommandParser -> EditTaskCommand +activate EditTaskCommand + +EditTaskCommand --> EditTaskCommandParser +deactivate EditTaskCommand + +[<-- EditTaskCommandParser +deactivate EditTaskCommandParser +'Hidden arrow to position the destroy marker below the end of the activation bar. +EditTaskCommandParser -[hidden]->[ +destroy EditTaskCommandParser + +[-> EditTaskCommand : execute() +activate EditTaskCommand + +EditTaskCommand -> Model : getRoomWithRoomNumber(2) +activate Model + +Model --> EditTaskCommand : room2 +deactivate Model + +EditTaskCommand -> Model : getTaskFromRoomWithTaskIndex(1, room2) +activate Model + +Model --> EditTaskCommand : task1OfRoom2 +deactivate Model + +EditTaskCommand -> EditTaskCommand : createEditedTask("HelloWorld") +activate EditTaskCommand + +EditTaskCommand --> EditTaskCommand : editedTask +deactivate EditTaskCommand + +EditTaskCommand -> Model : setTaskToRoom(task1OfRoom2, editedTask, room2) +activate Model + +Model --> EditTaskCommand +deactivate Model + +create CommandResult +EditTaskCommand -> CommandResult +activate CommandResult + +CommandResult --> EditTaskCommand +deactivate CommandResult + +[<-- EditTaskCommand : result +deactivate EditTaskCommand +@enduml diff --git a/docs/diagrams/LogicClassDiagram.puml b/docs/diagrams/plantuml/LogicClassDiagram.puml similarity index 91% rename from docs/diagrams/LogicClassDiagram.puml rename to docs/diagrams/plantuml/LogicClassDiagram.puml index 016ef33e2e2..3174414507b 100644 --- a/docs/diagrams/LogicClassDiagram.puml +++ b/docs/diagrams/plantuml/LogicClassDiagram.puml @@ -8,7 +8,7 @@ package Logic { package Parser { Interface Parser <> -Class AddressBookParser +Class CovigentAppParser Class XYZCommandParser Class CliSyntax Class ParserUtil @@ -35,8 +35,8 @@ Class HiddenOutside #FFFFFF HiddenOutside ..> Logic LogicManager .up.|> Logic -LogicManager -->"1" AddressBookParser -AddressBookParser .left.> XYZCommandParser: creates > +LogicManager -->"1" CovigentAppParser +CovigentAppParser .left.> XYZCommandParser: creates > XYZCommandParser ..> XYZCommand : creates > XYZCommandParser ..|> Parser @@ -55,6 +55,7 @@ LogicManager --> Model Command .right.> Model note right of XYZCommand: XYZCommand = AddCommand, \nFindCommand, etc + Logic ..> CommandResult LogicManager .down.> CommandResult Command .up.> CommandResult diff --git a/docs/diagrams/plantuml/ModelClassDiagram.puml b/docs/diagrams/plantuml/ModelClassDiagram.puml new file mode 100644 index 00000000000..018e569ec79 --- /dev/null +++ b/docs/diagrams/plantuml/ModelClassDiagram.puml @@ -0,0 +1,39 @@ +@startuml +!include style.puml +skinparam arrowThickness 1.1 +skinparam arrowColor MODEL_COLOR +skinparam classBackgroundColor MODEL_COLOR + +Package Model <>{ +Interface Model <> +Class Model +Class ModelManager +Class UserPrefs +Interface ReadOnlyUserPrefs<> + +Package PatientRecords { +Class PatientRecords +} + +Package RoomList { +Class RoomList +} + +Package RoomTaskRecords { +Class RoomTaskRecords +} +} + + +Class HiddenOutside #FFFFFF +HiddenOutside ..> Model + + +ModelManager .up.|> Model +ModelManager o-left-> "1" UserPrefs +ModelManager o--> "1" PatientRecords +ModelManager o--> "1" RoomList +ModelManager o--> "1" RoomTaskRecords +UserPrefs .up.|> ReadOnlyUserPrefs + +@enduml diff --git a/docs/diagrams/plantuml/PatientRecordsClassDiagram.puml b/docs/diagrams/plantuml/PatientRecordsClassDiagram.puml new file mode 100644 index 00000000000..2d916bd2f26 --- /dev/null +++ b/docs/diagrams/plantuml/PatientRecordsClassDiagram.puml @@ -0,0 +1,43 @@ +@startuml +!include style.puml +skinparam arrowThickness 1.1 +skinparam arrowColor MODEL_COLOR +skinparam classBackgroundColor MODEL_COLOR + +Package PatientRecords <>{ +Class PatientRecords +Interface ReadOnlyList <> + +Package Patient { +Class Patient +Class Name +Class Phone +Class Age +Class PeriodOfStay +Class Temperature +Class Comment +Class UniquePatientList +} +} + +Class HiddenOutside #FFFFFF +HiddenOutside ..> PatientRecords + +PatientRecords .left.|> ReadOnlyList + +PatientRecords *--> "1" UniquePatientList +UniquePatientList o--> "*" Patient +Patient *--> "1" Name +Patient *--> "1" Phone +Patient *--> "1" Age +Patient *--> "1" PeriodOfStay +Patient *--> "1" Temperature +Patient *--> "0..1" Comment + +Name -[hidden]right-> Phone +Phone -[hidden]right-> Age +Age -[hidden]right-> PeriodOfStay +PeriodOfStay -[hidden]right->Temperature +Temperature -[hidden]right->Comment + +@enduml diff --git a/docs/diagrams/plantuml/RoomListClassDiagram.puml b/docs/diagrams/plantuml/RoomListClassDiagram.puml new file mode 100644 index 00000000000..ea12c9f1297 --- /dev/null +++ b/docs/diagrams/plantuml/RoomListClassDiagram.puml @@ -0,0 +1,41 @@ +@startuml +!include style.puml +skinparam arrowThickness 1.1 +skinparam arrowColor MODEL_COLOR +skinparam classBackgroundColor MODEL_COLOR + +Package RoomList <> { +Class RoomList +Interface ReadOnlyList<> + +Package Room { +Class Room +Class UniqueRoomList +Class Patient +Class RoomTasks + +Package Task { +Class Task +Class DateTimeDue +Class Description +Class TaskList +} +} +} + +Class HiddenOutside #FFFFFF +HiddenOutside ..> RoomList + +RoomList .left.|> ReadOnlyList +RoomList *--> "1" UniqueRoomList +UniqueRoomList o--> "*" Room +Room *--> "0..1" Patient +Room *--> "1" RoomTasks +RoomTasks .left.|> ReadOnlyList + +RoomTasks *--> "1" TaskList +TaskList o--> "*" Task +Task *--> "1" Description +Task *--> "0..1" DateTimeDue + +@enduml diff --git a/docs/diagrams/plantuml/RoomTaskRecordsClassDiagram.puml b/docs/diagrams/plantuml/RoomTaskRecordsClassDiagram.puml new file mode 100644 index 00000000000..2f24e696e18 --- /dev/null +++ b/docs/diagrams/plantuml/RoomTaskRecordsClassDiagram.puml @@ -0,0 +1,25 @@ +@startuml +!include style.puml +skinparam arrowThickness 1.1 +skinparam arrowColor MODEL_COLOR +skinparam classBackgroundColor MODEL_COLOR + +Package RoomTaskRecords <>{ + +Class RoomTaskRecords +Interface ReadOnlyList <> + +Package RoomTaskRecords { +Class RoomTaskRecords +Class RoomTaskAssociation +Class Task +Class Room +} + +RoomTaskRecords .left.|> ReadOnlyList + +RoomTaskRecords *--> "*" RoomTaskAssociation + +RoomTaskAssociation *-->"1" Task +RoomTaskAssociation *--> "1" Room +@enduml diff --git a/docs/diagrams/StorageClassDiagram.puml b/docs/diagrams/plantuml/StorageClassDiagram.puml similarity index 100% rename from docs/diagrams/StorageClassDiagram.puml rename to docs/diagrams/plantuml/StorageClassDiagram.puml diff --git a/docs/diagrams/plantuml/TaskAlternativeClassDiagram.puml b/docs/diagrams/plantuml/TaskAlternativeClassDiagram.puml new file mode 100644 index 00000000000..d1bfaf9601d --- /dev/null +++ b/docs/diagrams/plantuml/TaskAlternativeClassDiagram.puml @@ -0,0 +1,35 @@ +@startuml +!include style.puml +skinparam arrowThickness 1.1 +skinparam arrowColor MODEL_COLOR +skinparam classBackgroundColor MODEL_COLOR + +Interface ReadOnlyList <> { + getReadOnlyList(): ObservableList +} + +Package Room <> { + Class Room { + +addtask(Task) + +deleteTask(Task) + +setTask(Task, editedTask: Task) + } +} + +Package Task <> { + Class Task + Class TaskList + Class Description + Class DateTimeDue +} + +TaskList -.|> ReadOnlyList + +Room *--r> "1" TaskList +TaskList *--> "*" Task +Task *--> "1" Description +Task *--> "0..1" DateTimeDue + +Description -[hidden]right-> DateTimeDue + +@enduml diff --git a/docs/diagrams/plantuml/TaskClassDiagram.puml b/docs/diagrams/plantuml/TaskClassDiagram.puml new file mode 100644 index 00000000000..ee1748df62b --- /dev/null +++ b/docs/diagrams/plantuml/TaskClassDiagram.puml @@ -0,0 +1,38 @@ +@startuml +!include style.puml +skinparam arrowThickness 1.1 +skinparam arrowColor MODEL_COLOR +skinparam classBackgroundColor MODEL_COLOR + +Interface ReadOnlyList <> { + getReadOnlyList(): ObservableList +} + +Package Room <> { + Class Room + Class RoomTasks { + +addTask(Task) + +removeTask(Task) + +setTask(Task, editedTask: Task) + } +} + +Room *--r> "1" RoomTasks +RoomTasks -.|> ReadOnlyList + +Package Task <> { + Class Task + Class TaskList + Class Description + Class DateTimeDue +} + +RoomTasks ..> Task +RoomTasks *--l> "1" TaskList +TaskList *--> "*" Task +Task *--> "1" Description +Task *--> "0..1" DateTimeDue + +Description -[hidden]right-> DateTimeDue + +@enduml diff --git a/docs/diagrams/UiClassDiagram.puml b/docs/diagrams/plantuml/UiClassDiagram.puml similarity index 55% rename from docs/diagrams/UiClassDiagram.puml rename to docs/diagrams/plantuml/UiClassDiagram.puml index 92746f9fcf7..c4d3028b0a3 100644 --- a/docs/diagrams/UiClassDiagram.puml +++ b/docs/diagrams/plantuml/UiClassDiagram.puml @@ -11,15 +11,18 @@ Class UiManager Class MainWindow Class HelpWindow Class ResultDisplay -Class PersonListPanel -Class PersonCard +Class PatientListPanel +Class RoomListPanel +Class RoomDetailsPanel +Class RoomTaskListPanel +Class RoomTaskDetailsPanel +Class TaskCard +Class RoomCard +Class PatientCard Class StatusBarFooter Class CommandBox } -package Model <> { -Class HiddenModel #FFFFFF -} package Logic <> { Class HiddenLogic #FFFFFF @@ -33,25 +36,36 @@ UiManager -down-> MainWindow MainWindow --> HelpWindow MainWindow *-down-> CommandBox MainWindow *-down-> ResultDisplay -MainWindow *-down-> PersonListPanel +MainWindow *-down-> PatientListPanel +MainWindow *-down-> RoomTaskListPanel +MainWindow *-down-> RoomListPanel MainWindow *-down-> StatusBarFooter -PersonListPanel -down-> PersonCard +PatientListPanel .right-> PatientCard : defined by > +RoomTaskListPanel *.left-> TaskCard : defined by > +RoomTaskListPanel *.right-> RoomTaskDetailsPanel : defined by > +RoomListPanel *.left-> RoomCard : defined by > +RoomListPanel *.right-> RoomDetailsPanel : defined by > MainWindow -left-|> UiPart ResultDisplay --|> UiPart CommandBox --|> UiPart -PersonListPanel --|> UiPart -PersonCard --|> UiPart +RoomDetailsPanel --|> UiPart +RoomCard --|> UiPart +TaskCard --|> UiPart +PatientListPanel --|> UiPart +RoomListPanel --|> UiPart +RoomTaskListPanel --|> UiPart +PatientCard --|> UiPart StatusBarFooter --|> UiPart HelpWindow -down-|> UiPart -PersonCard ..> Model UiManager -right-> Logic MainWindow -left-> Logic -PersonListPanel -[hidden]left- HelpWindow + +PatientListPanel -[hidden]left- HelpWindow HelpWindow -[hidden]left- CommandBox CommandBox -[hidden]left- ResultDisplay ResultDisplay -[hidden]left- StatusBarFooter diff --git a/docs/diagrams/plantuml/UiClassDiagram1.puml b/docs/diagrams/plantuml/UiClassDiagram1.puml new file mode 100644 index 00000000000..3185e022291 --- /dev/null +++ b/docs/diagrams/plantuml/UiClassDiagram1.puml @@ -0,0 +1,25 @@ +@startuml +!include style.puml +skinparam arrowThickness 1.1 +skinparam arrowColor UI_COLOR_T4 +skinparam classBackgroundColor UI_COLOR + +package UI <>{ +Class RoomCard +Class TaskCard +Class PatientCard +} + + +package Model <> { +Class HiddenLogic #FFFFFF +} + + + +RoomCard -down-> Model +TaskCard -down-> Model +PatientCard -down-> Model + +Model -[hidden]left- UI +@enduml diff --git a/docs/diagrams/UndoRedoState0.puml b/docs/diagrams/plantuml/UndoRedoState0.puml similarity index 100% rename from docs/diagrams/UndoRedoState0.puml rename to docs/diagrams/plantuml/UndoRedoState0.puml diff --git a/docs/diagrams/UndoRedoState1.puml b/docs/diagrams/plantuml/UndoRedoState1.puml similarity index 100% rename from docs/diagrams/UndoRedoState1.puml rename to docs/diagrams/plantuml/UndoRedoState1.puml diff --git a/docs/diagrams/UndoRedoState2.puml b/docs/diagrams/plantuml/UndoRedoState2.puml similarity index 100% rename from docs/diagrams/UndoRedoState2.puml rename to docs/diagrams/plantuml/UndoRedoState2.puml diff --git a/docs/diagrams/UndoRedoState3.puml b/docs/diagrams/plantuml/UndoRedoState3.puml similarity index 100% rename from docs/diagrams/UndoRedoState3.puml rename to docs/diagrams/plantuml/UndoRedoState3.puml diff --git a/docs/diagrams/UndoRedoState4.puml b/docs/diagrams/plantuml/UndoRedoState4.puml similarity index 100% rename from docs/diagrams/UndoRedoState4.puml rename to docs/diagrams/plantuml/UndoRedoState4.puml diff --git a/docs/diagrams/UndoRedoState5.puml b/docs/diagrams/plantuml/UndoRedoState5.puml similarity index 100% rename from docs/diagrams/UndoRedoState5.puml rename to docs/diagrams/plantuml/UndoRedoState5.puml diff --git a/docs/diagrams/UndoSequenceDiagram.puml b/docs/diagrams/plantuml/UndoSequenceDiagram.puml similarity index 100% rename from docs/diagrams/UndoSequenceDiagram.puml rename to docs/diagrams/plantuml/UndoSequenceDiagram.puml diff --git a/docs/diagrams/style.puml b/docs/diagrams/plantuml/style.puml similarity index 95% rename from docs/diagrams/style.puml rename to docs/diagrams/plantuml/style.puml index fad8b0adeaa..93690b421ce 100644 --- a/docs/diagrams/style.puml +++ b/docs/diagrams/plantuml/style.puml @@ -38,6 +38,8 @@ skinparam BackgroundColor #FFFFFFF skinparam Shadowing false skinparam Class { + AttributeFontColor #FFFFFF + AttributeIconSize 0 FontColor #FFFFFF BorderThickness 1 BorderColor #FFFFFF @@ -71,5 +73,5 @@ skinparam DefaultTextAlignment center skinparam packageStyle Rectangle hide footbox -hide members +hide empty members hide circle diff --git a/docs/images/ArchitectureSequenceDiagram.png b/docs/images/ArchitectureSequenceDiagram.png deleted file mode 100644 index 2f1346869d0..00000000000 Binary files a/docs/images/ArchitectureSequenceDiagram.png and /dev/null differ diff --git a/docs/images/DeleteSequenceDiagram.png b/docs/images/DeleteSequenceDiagram.png deleted file mode 100644 index fa327b39618..00000000000 Binary files a/docs/images/DeleteSequenceDiagram.png and /dev/null differ diff --git a/docs/images/LogicClassDiagram.png b/docs/images/LogicClassDiagram.png deleted file mode 100644 index b9e853cef12..00000000000 Binary files a/docs/images/LogicClassDiagram.png and /dev/null differ diff --git a/docs/images/ModelClassDiagram.png b/docs/images/ModelClassDiagram.png deleted file mode 100644 index 280064118cf..00000000000 Binary files a/docs/images/ModelClassDiagram.png and /dev/null differ diff --git a/docs/images/Ui.png b/docs/images/Ui.png index 5bd77847aa2..3f36fa47c69 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 deleted file mode 100644 index 7b4b3dbea45..00000000000 Binary files a/docs/images/UiClassDiagram.png and /dev/null differ diff --git a/docs/images/chiamyunqing.png b/docs/images/chiamyunqing.png new file mode 100644 index 00000000000..731dde15b8f Binary files /dev/null and b/docs/images/chiamyunqing.png differ diff --git a/docs/images/dg/ActivityDiagramForInitRoom.png b/docs/images/dg/ActivityDiagramForInitRoom.png new file mode 100644 index 00000000000..b2187b51f37 Binary files /dev/null and b/docs/images/dg/ActivityDiagramForInitRoom.png differ diff --git a/docs/images/dg/ActivityDiagramForRoomFeature.png b/docs/images/dg/ActivityDiagramForRoomFeature.png new file mode 100644 index 00000000000..fa53addb648 Binary files /dev/null and b/docs/images/dg/ActivityDiagramForRoomFeature.png differ diff --git a/docs/images/dg/AddPatientSequenceDiagram.png b/docs/images/dg/AddPatientSequenceDiagram.png new file mode 100644 index 00000000000..4e59fb3f95b Binary files /dev/null and b/docs/images/dg/AddPatientSequenceDiagram.png differ diff --git a/docs/images/dg/AllocateRoomActivityDiagram.png b/docs/images/dg/AllocateRoomActivityDiagram.png new file mode 100644 index 00000000000..0b0fc8f29e7 Binary files /dev/null and b/docs/images/dg/AllocateRoomActivityDiagram.png differ diff --git a/docs/images/dg/AllocateRoomSequenceDiagram.png b/docs/images/dg/AllocateRoomSequenceDiagram.png new file mode 100644 index 00000000000..a9b641f0dfe Binary files /dev/null and b/docs/images/dg/AllocateRoomSequenceDiagram.png differ diff --git a/docs/images/ArchitectureDiagram.png b/docs/images/dg/ArchitectureDiagram.png similarity index 100% rename from docs/images/ArchitectureDiagram.png rename to docs/images/dg/ArchitectureDiagram.png diff --git a/docs/images/dg/ArchitectureSequenceDiagram.png b/docs/images/dg/ArchitectureSequenceDiagram.png new file mode 100644 index 00000000000..49bd8e47495 Binary files /dev/null and b/docs/images/dg/ArchitectureSequenceDiagram.png differ diff --git a/docs/images/BetterModelClassDiagram.png b/docs/images/dg/BetterModelClassDiagram.png similarity index 100% rename from docs/images/BetterModelClassDiagram.png rename to docs/images/dg/BetterModelClassDiagram.png diff --git a/docs/images/CommitActivityDiagram.png b/docs/images/dg/CommitActivityDiagram.png similarity index 100% rename from docs/images/CommitActivityDiagram.png rename to docs/images/dg/CommitActivityDiagram.png diff --git a/docs/images/dg/DeletePatientSequenceDiagram.png b/docs/images/dg/DeletePatientSequenceDiagram.png new file mode 100644 index 00000000000..d3802b9b5c0 Binary files /dev/null and b/docs/images/dg/DeletePatientSequenceDiagram.png differ diff --git a/docs/images/dg/EditTaskSequenceDiagram.png b/docs/images/dg/EditTaskSequenceDiagram.png new file mode 100644 index 00000000000..fb2a0acf460 Binary files /dev/null and b/docs/images/dg/EditTaskSequenceDiagram.png differ diff --git a/docs/images/dg/JsonSerializablePatientList.png b/docs/images/dg/JsonSerializablePatientList.png new file mode 100644 index 00000000000..cc4a84ea4ee Binary files /dev/null and b/docs/images/dg/JsonSerializablePatientList.png differ diff --git a/docs/images/dg/JsonSerializablePatientRecords.png b/docs/images/dg/JsonSerializablePatientRecords.png new file mode 100644 index 00000000000..4e5e49e289c Binary files /dev/null and b/docs/images/dg/JsonSerializablePatientRecords.png differ diff --git a/docs/images/dg/JsonSerializableRoomList.png b/docs/images/dg/JsonSerializableRoomList.png new file mode 100644 index 00000000000..1eaa4a93b3e Binary files /dev/null and b/docs/images/dg/JsonSerializableRoomList.png differ diff --git a/docs/images/dg/JsonSerializableRoomRecords.png b/docs/images/dg/JsonSerializableRoomRecords.png new file mode 100644 index 00000000000..4be3e9b7221 Binary files /dev/null and b/docs/images/dg/JsonSerializableRoomRecords.png differ diff --git a/docs/images/dg/JsonSerializableTaskList.png b/docs/images/dg/JsonSerializableTaskList.png new file mode 100644 index 00000000000..a37711964f8 Binary files /dev/null and b/docs/images/dg/JsonSerializableTaskList.png differ diff --git a/docs/images/dg/LogicClassDiagram.png b/docs/images/dg/LogicClassDiagram.png new file mode 100644 index 00000000000..0e2452b57b8 Binary files /dev/null and b/docs/images/dg/LogicClassDiagram.png differ diff --git a/docs/images/LogicStorageDIP.png b/docs/images/dg/LogicStorageDIP.png similarity index 100% rename from docs/images/LogicStorageDIP.png rename to docs/images/dg/LogicStorageDIP.png diff --git a/docs/images/dg/ModelClassDiagram.png b/docs/images/dg/ModelClassDiagram.png new file mode 100644 index 00000000000..12055ea9523 Binary files /dev/null and b/docs/images/dg/ModelClassDiagram.png differ diff --git a/docs/images/dg/PatientClassDiagram.png b/docs/images/dg/PatientClassDiagram.png new file mode 100644 index 00000000000..c89d8e0f802 Binary files /dev/null and b/docs/images/dg/PatientClassDiagram.png differ diff --git a/docs/images/dg/PatientRecordsClassDiagram.png b/docs/images/dg/PatientRecordsClassDiagram.png new file mode 100644 index 00000000000..2d7728823bd Binary files /dev/null and b/docs/images/dg/PatientRecordsClassDiagram.png differ diff --git a/docs/images/dg/RoomListClassDiagram.png b/docs/images/dg/RoomListClassDiagram.png new file mode 100644 index 00000000000..ef0f6f95a46 Binary files /dev/null and b/docs/images/dg/RoomListClassDiagram.png differ diff --git a/docs/images/dg/RoomTaskRecordsClassDiagram.png b/docs/images/dg/RoomTaskRecordsClassDiagram.png new file mode 100644 index 00000000000..e5de8b9c416 Binary files /dev/null and b/docs/images/dg/RoomTaskRecordsClassDiagram.png differ diff --git a/docs/images/dg/RoomTaskRecordsClassDiagrams.png b/docs/images/dg/RoomTaskRecordsClassDiagrams.png new file mode 100644 index 00000000000..e62652ac607 Binary files /dev/null and b/docs/images/dg/RoomTaskRecordsClassDiagrams.png differ diff --git a/docs/images/dg/Room_SequenceDiagram.png b/docs/images/dg/Room_SequenceDiagram.png new file mode 100644 index 00000000000..28bdc09c0a2 Binary files /dev/null and b/docs/images/dg/Room_SequenceDiagram.png differ diff --git a/docs/images/dg/SequenceDiagramForInitRoom.png b/docs/images/dg/SequenceDiagramForInitRoom.png new file mode 100644 index 00000000000..b59199ac765 Binary files /dev/null and b/docs/images/dg/SequenceDiagramForInitRoom.png differ diff --git a/docs/images/StorageClassDiagram.png b/docs/images/dg/StorageClassDiagram.png similarity index 100% rename from docs/images/StorageClassDiagram.png rename to docs/images/dg/StorageClassDiagram.png diff --git a/docs/images/dg/TaskAlternativeClassDiagram.png b/docs/images/dg/TaskAlternativeClassDiagram.png new file mode 100644 index 00000000000..9072679c0e2 Binary files /dev/null and b/docs/images/dg/TaskAlternativeClassDiagram.png differ diff --git a/docs/images/dg/TaskClassDiagram.png b/docs/images/dg/TaskClassDiagram.png new file mode 100644 index 00000000000..2b985621445 Binary files /dev/null and b/docs/images/dg/TaskClassDiagram.png differ diff --git a/docs/images/dg/UML_Diagram_StorageManager.png b/docs/images/dg/UML_Diagram_StorageManager.png new file mode 100644 index 00000000000..e251503e7a5 Binary files /dev/null and b/docs/images/dg/UML_Diagram_StorageManager.png differ diff --git a/docs/images/dg/UML_RoomFeature.png b/docs/images/dg/UML_RoomFeature.png new file mode 100644 index 00000000000..26bf47261cf Binary files /dev/null and b/docs/images/dg/UML_RoomFeature.png differ diff --git a/docs/images/dg/UML_Storage_Diagram.png b/docs/images/dg/UML_Storage_Diagram.png new file mode 100644 index 00000000000..6f5d211b05b Binary files /dev/null and b/docs/images/dg/UML_Storage_Diagram.png differ diff --git a/docs/images/dg/UiClassDiagram.png b/docs/images/dg/UiClassDiagram.png new file mode 100644 index 00000000000..f42ca39f859 Binary files /dev/null and b/docs/images/dg/UiClassDiagram.png differ diff --git a/docs/images/dg/UiClassDiagram1.png b/docs/images/dg/UiClassDiagram1.png new file mode 100644 index 00000000000..da9937f0fbb Binary files /dev/null and b/docs/images/dg/UiClassDiagram1.png differ diff --git a/docs/images/UndoRedoState0.png b/docs/images/dg/UndoRedoState0.png similarity index 100% rename from docs/images/UndoRedoState0.png rename to docs/images/dg/UndoRedoState0.png diff --git a/docs/images/UndoRedoState1.png b/docs/images/dg/UndoRedoState1.png similarity index 100% rename from docs/images/UndoRedoState1.png rename to docs/images/dg/UndoRedoState1.png diff --git a/docs/images/UndoRedoState2.png b/docs/images/dg/UndoRedoState2.png similarity index 100% rename from docs/images/UndoRedoState2.png rename to docs/images/dg/UndoRedoState2.png diff --git a/docs/images/UndoRedoState3.png b/docs/images/dg/UndoRedoState3.png similarity index 100% rename from docs/images/UndoRedoState3.png rename to docs/images/dg/UndoRedoState3.png diff --git a/docs/images/UndoRedoState4.png b/docs/images/dg/UndoRedoState4.png similarity index 100% rename from docs/images/UndoRedoState4.png rename to docs/images/dg/UndoRedoState4.png diff --git a/docs/images/UndoRedoState5.png b/docs/images/dg/UndoRedoState5.png similarity index 100% rename from docs/images/UndoRedoState5.png rename to docs/images/dg/UndoRedoState5.png diff --git a/docs/images/UndoSequenceDiagram.png b/docs/images/dg/UndoSequenceDiagram.png similarity index 100% rename from docs/images/UndoSequenceDiagram.png rename to docs/images/dg/UndoSequenceDiagram.png diff --git a/docs/images/dg/storage.png b/docs/images/dg/storage.png new file mode 100644 index 00000000000..749d73a3d62 Binary files /dev/null and b/docs/images/dg/storage.png differ diff --git a/docs/images/findAlexDavidResult.png b/docs/images/findAlexDavidResult.png deleted file mode 100644 index 235da1c273e..00000000000 Binary files a/docs/images/findAlexDavidResult.png and /dev/null differ diff --git a/docs/images/itssodium.png b/docs/images/itssodium.png new file mode 100644 index 00000000000..23b095b9d41 Binary files /dev/null and b/docs/images/itssodium.png differ diff --git a/docs/images/leemingde.png b/docs/images/leemingde.png new file mode 100644 index 00000000000..87540cd1970 Binary files /dev/null and b/docs/images/leemingde.png differ diff --git a/docs/images/raymondge.png b/docs/images/raymondge.png new file mode 100644 index 00000000000..2e0824346f1 Binary files /dev/null and b/docs/images/raymondge.png differ diff --git a/docs/images/ug/f10_addpatientexample.png b/docs/images/ug/f10_addpatientexample.png new file mode 100644 index 00000000000..55613d112fa Binary files /dev/null and b/docs/images/ug/f10_addpatientexample.png differ diff --git a/docs/images/ug/f11_findemptyroomexample.png b/docs/images/ug/f11_findemptyroomexample.png new file mode 100644 index 00000000000..3b16b7740ca Binary files /dev/null and b/docs/images/ug/f11_findemptyroomexample.png differ diff --git a/docs/images/ug/f12_allocateroomexample.png b/docs/images/ug/f12_allocateroomexample.png new file mode 100644 index 00000000000..70ed7df1ce3 Binary files /dev/null and b/docs/images/ug/f12_allocateroomexample.png differ diff --git a/docs/images/ug/f13_addtaskexample.png b/docs/images/ug/f13_addtaskexample.png new file mode 100644 index 00000000000..3763c6d289e Binary files /dev/null and b/docs/images/ug/f13_addtaskexample.png differ diff --git a/docs/images/ug/f15_addpatientcommand.png b/docs/images/ug/f15_addpatientcommand.png new file mode 100644 index 00000000000..84a03311e00 Binary files /dev/null and b/docs/images/ug/f15_addpatientcommand.png differ diff --git a/docs/images/ug/f16_addpatientsuccess.png b/docs/images/ug/f16_addpatientsuccess.png new file mode 100644 index 00000000000..90d40f78f77 Binary files /dev/null and b/docs/images/ug/f16_addpatientsuccess.png differ diff --git a/docs/images/ug/f18_deletepatientcommand.png b/docs/images/ug/f18_deletepatientcommand.png new file mode 100644 index 00000000000..bffb9ee778d Binary files /dev/null and b/docs/images/ug/f18_deletepatientcommand.png differ diff --git a/docs/images/ug/f19_deletepatientsuccess.png b/docs/images/ug/f19_deletepatientsuccess.png new file mode 100644 index 00000000000..8a38fdb2651 Binary files /dev/null and b/docs/images/ug/f19_deletepatientsuccess.png differ diff --git a/docs/images/ug/f1_ui.png b/docs/images/ug/f1_ui.png new file mode 100644 index 00000000000..afac4339df1 Binary files /dev/null and b/docs/images/ug/f1_ui.png differ diff --git a/docs/images/ug/f21_editpatientcommand.png b/docs/images/ug/f21_editpatientcommand.png new file mode 100644 index 00000000000..9ec5286882e Binary files /dev/null and b/docs/images/ug/f21_editpatientcommand.png differ diff --git a/docs/images/ug/f22_editpatientsuccess.PNG b/docs/images/ug/f22_editpatientsuccess.PNG new file mode 100644 index 00000000000..e8d16b792f6 Binary files /dev/null and b/docs/images/ug/f22_editpatientsuccess.PNG differ diff --git a/docs/images/ug/f24_patientuibeforesearch.PNG b/docs/images/ug/f24_patientuibeforesearch.PNG new file mode 100644 index 00000000000..9b60f24a8cb Binary files /dev/null and b/docs/images/ug/f24_patientuibeforesearch.PNG differ diff --git a/docs/images/ug/f25_searchpatientcommand.PNG b/docs/images/ug/f25_searchpatientcommand.PNG new file mode 100644 index 00000000000..c4f4bfcdd54 Binary files /dev/null and b/docs/images/ug/f25_searchpatientcommand.PNG differ diff --git a/docs/images/ug/f26_searchpatient.png b/docs/images/ug/f26_searchpatient.png new file mode 100644 index 00000000000..ec81ef7342d Binary files /dev/null and b/docs/images/ug/f26_searchpatient.png differ diff --git a/docs/images/ug/f28_listpatientcommand.png b/docs/images/ug/f28_listpatientcommand.png new file mode 100644 index 00000000000..079b179ba0f Binary files /dev/null and b/docs/images/ug/f28_listpatientcommand.png differ diff --git a/docs/images/ug/f29_listpatient.png b/docs/images/ug/f29_listpatient.png new file mode 100644 index 00000000000..92146dafb19 Binary files /dev/null and b/docs/images/ug/f29_listpatient.png differ diff --git a/docs/images/ug/f2_downloadcovigent.png b/docs/images/ug/f2_downloadcovigent.png new file mode 100644 index 00000000000..d1d0c6fac62 Binary files /dev/null and b/docs/images/ug/f2_downloadcovigent.png differ diff --git a/docs/images/ug/f31_initroomcommand.png b/docs/images/ug/f31_initroomcommand.png new file mode 100644 index 00000000000..0d17e6bed4d Binary files /dev/null and b/docs/images/ug/f31_initroomcommand.png differ diff --git a/docs/images/ug/f32_initroom.png b/docs/images/ug/f32_initroom.png new file mode 100644 index 00000000000..592f905e325 Binary files /dev/null and b/docs/images/ug/f32_initroom.png differ diff --git a/docs/images/ug/f34_allocateroomcommand.PNG b/docs/images/ug/f34_allocateroomcommand.PNG new file mode 100644 index 00000000000..cba62a47c7b Binary files /dev/null and b/docs/images/ug/f34_allocateroomcommand.PNG differ diff --git a/docs/images/ug/f35_allocateroomsuccess.PNG b/docs/images/ug/f35_allocateroomsuccess.PNG new file mode 100644 index 00000000000..099bb59f5fc Binary files /dev/null and b/docs/images/ug/f35_allocateroomsuccess.PNG differ diff --git a/docs/images/ug/f37_searchroomnumbercommand.png b/docs/images/ug/f37_searchroomnumbercommand.png new file mode 100644 index 00000000000..eece2dcef8a Binary files /dev/null and b/docs/images/ug/f37_searchroomnumbercommand.png differ diff --git a/docs/images/ug/f38_searchroomnumbersuccess.png b/docs/images/ug/f38_searchroomnumbersuccess.png new file mode 100644 index 00000000000..b6a3b2e8c03 Binary files /dev/null and b/docs/images/ug/f38_searchroomnumbersuccess.png differ diff --git a/docs/images/ug/f3_startingcovigent.png b/docs/images/ug/f3_startingcovigent.png new file mode 100644 index 00000000000..e7ba1b6dcb6 Binary files /dev/null and b/docs/images/ug/f3_startingcovigent.png differ diff --git a/docs/images/ug/f40_searchroompatientcommand.png b/docs/images/ug/f40_searchroompatientcommand.png new file mode 100644 index 00000000000..4bcaeb210b2 Binary files /dev/null and b/docs/images/ug/f40_searchroompatientcommand.png differ diff --git a/docs/images/ug/f41_searchroompatientsuccess.png b/docs/images/ug/f41_searchroompatientsuccess.png new file mode 100644 index 00000000000..5a19acc22eb Binary files /dev/null and b/docs/images/ug/f41_searchroompatientsuccess.png differ diff --git a/docs/images/ug/f43_listroomcommand.png b/docs/images/ug/f43_listroomcommand.png new file mode 100644 index 00000000000..fc7d4ed85e6 Binary files /dev/null and b/docs/images/ug/f43_listroomcommand.png differ diff --git a/docs/images/ug/f44_listroom.png b/docs/images/ug/f44_listroom.png new file mode 100644 index 00000000000..7677b6a4320 Binary files /dev/null and b/docs/images/ug/f44_listroom.png differ diff --git a/docs/images/ug/f46_findemptyroomcommand.png b/docs/images/ug/f46_findemptyroomcommand.png new file mode 100644 index 00000000000..4dd858849ae Binary files /dev/null and b/docs/images/ug/f46_findemptyroomcommand.png differ diff --git a/docs/images/ug/f47_findemptyroom.png b/docs/images/ug/f47_findemptyroom.png new file mode 100644 index 00000000000..b8efb39dc3c Binary files /dev/null and b/docs/images/ug/f47_findemptyroom.png differ diff --git a/docs/images/ug/f49_addtaskcommand.png b/docs/images/ug/f49_addtaskcommand.png new file mode 100644 index 00000000000..b1ad897eed0 Binary files /dev/null and b/docs/images/ug/f49_addtaskcommand.png differ diff --git a/docs/images/ug/f4_covigenttabs.png b/docs/images/ug/f4_covigenttabs.png new file mode 100644 index 00000000000..2bc1b5071e4 Binary files /dev/null and b/docs/images/ug/f4_covigenttabs.png differ diff --git a/docs/images/ug/f50_addtasksuccess.png b/docs/images/ug/f50_addtasksuccess.png new file mode 100644 index 00000000000..3fec8919954 Binary files /dev/null and b/docs/images/ug/f50_addtasksuccess.png differ diff --git a/docs/images/ug/f51_changingtasknumber.png b/docs/images/ug/f51_changingtasknumber.png new file mode 100644 index 00000000000..fe03b9d0477 Binary files /dev/null and b/docs/images/ug/f51_changingtasknumber.png differ diff --git a/docs/images/ug/f53_deletetaskcommand.png b/docs/images/ug/f53_deletetaskcommand.png new file mode 100644 index 00000000000..19f797dd517 Binary files /dev/null and b/docs/images/ug/f53_deletetaskcommand.png differ diff --git a/docs/images/ug/f54_deletetasksuccess.png b/docs/images/ug/f54_deletetasksuccess.png new file mode 100644 index 00000000000..f85178d09c7 Binary files /dev/null and b/docs/images/ug/f54_deletetasksuccess.png differ diff --git a/docs/images/ug/f56_edittaskcommand.png b/docs/images/ug/f56_edittaskcommand.png new file mode 100644 index 00000000000..0d4740e7192 Binary files /dev/null and b/docs/images/ug/f56_edittaskcommand.png differ diff --git a/docs/images/ug/f57_edittasksuccess.png b/docs/images/ug/f57_edittasksuccess.png new file mode 100644 index 00000000000..905b5a4e89a Binary files /dev/null and b/docs/images/ug/f57_edittasksuccess.png differ diff --git a/docs/images/ug/f59_edittaskcommand_removeduedate.png b/docs/images/ug/f59_edittaskcommand_removeduedate.png new file mode 100644 index 00000000000..2efccc2c6f0 Binary files /dev/null and b/docs/images/ug/f59_edittaskcommand_removeduedate.png differ diff --git a/docs/images/ug/f5_patientstab.png b/docs/images/ug/f5_patientstab.png new file mode 100644 index 00000000000..7f360189ed9 Binary files /dev/null and b/docs/images/ug/f5_patientstab.png differ diff --git a/docs/images/ug/f60_edittasksuccess_removeduedate.png b/docs/images/ug/f60_edittasksuccess_removeduedate.png new file mode 100644 index 00000000000..5806bb3281b Binary files /dev/null and b/docs/images/ug/f60_edittasksuccess_removeduedate.png differ diff --git a/docs/images/ug/f62_searchtaskcommand.png b/docs/images/ug/f62_searchtaskcommand.png new file mode 100644 index 00000000000..6e9c867cd30 Binary files /dev/null and b/docs/images/ug/f62_searchtaskcommand.png differ diff --git a/docs/images/ug/f63_searchtasksuccess.png b/docs/images/ug/f63_searchtasksuccess.png new file mode 100644 index 00000000000..db8576e845c Binary files /dev/null and b/docs/images/ug/f63_searchtasksuccess.png differ diff --git a/docs/images/ug/f65_listtaskcommand.png b/docs/images/ug/f65_listtaskcommand.png new file mode 100644 index 00000000000..0b4eff9d698 Binary files /dev/null and b/docs/images/ug/f65_listtaskcommand.png differ diff --git a/docs/images/ug/f66_listtasksuccess.png b/docs/images/ug/f66_listtasksuccess.png new file mode 100644 index 00000000000..07db12fc2d2 Binary files /dev/null and b/docs/images/ug/f66_listtasksuccess.png differ diff --git a/docs/images/ug/f67_helpcommand.png b/docs/images/ug/f67_helpcommand.png new file mode 100644 index 00000000000..75537c9b1de Binary files /dev/null and b/docs/images/ug/f67_helpcommand.png differ diff --git a/docs/images/ug/f68_helpresultdisplay.png b/docs/images/ug/f68_helpresultdisplay.png new file mode 100644 index 00000000000..55208d8a6eb Binary files /dev/null and b/docs/images/ug/f68_helpresultdisplay.png differ diff --git a/docs/images/ug/f69_helpwindow.png b/docs/images/ug/f69_helpwindow.png new file mode 100644 index 00000000000..6c2f2bf824f Binary files /dev/null and b/docs/images/ug/f69_helpwindow.png differ diff --git a/docs/images/ug/f6_roomstab.png b/docs/images/ug/f6_roomstab.png new file mode 100644 index 00000000000..8db3882ba85 Binary files /dev/null and b/docs/images/ug/f6_roomstab.png differ diff --git a/docs/images/ug/f70_exitcommand.png b/docs/images/ug/f70_exitcommand.png new file mode 100644 index 00000000000..e18c3a55fd8 Binary files /dev/null and b/docs/images/ug/f70_exitcommand.png differ diff --git a/docs/images/ug/f7_taskstab.png b/docs/images/ug/f7_taskstab.png new file mode 100644 index 00000000000..00ebfaa8e61 Binary files /dev/null and b/docs/images/ug/f7_taskstab.png differ diff --git a/docs/images/ug/f8_commandbox.png b/docs/images/ug/f8_commandbox.png new file mode 100644 index 00000000000..7b75dfb4c1d Binary files /dev/null and b/docs/images/ug/f8_commandbox.png differ diff --git a/docs/images/ug/f9_initroomexample.png b/docs/images/ug/f9_initroomexample.png new file mode 100644 index 00000000000..ec24d65473d Binary files /dev/null and b/docs/images/ug/f9_initroomexample.png differ diff --git a/docs/images/ug/icon_hyperlink.png b/docs/images/ug/icon_hyperlink.png new file mode 100644 index 00000000000..627898132f4 Binary files /dev/null and b/docs/images/ug/icon_hyperlink.png differ diff --git a/docs/images/ug/icon_info.png b/docs/images/ug/icon_info.png new file mode 100644 index 00000000000..38c5c9c287c Binary files /dev/null and b/docs/images/ug/icon_info.png differ diff --git a/docs/images/ug/icon_italics.png b/docs/images/ug/icon_italics.png new file mode 100644 index 00000000000..6fce08e4305 Binary files /dev/null and b/docs/images/ug/icon_italics.png differ diff --git a/docs/images/ug/icon_keyword.png b/docs/images/ug/icon_keyword.png new file mode 100644 index 00000000000..49f2df06129 Binary files /dev/null and b/docs/images/ug/icon_keyword.png differ diff --git a/docs/images/ug/icon_roomnumber.png b/docs/images/ug/icon_roomnumber.png new file mode 100644 index 00000000000..4e80a223902 Binary files /dev/null and b/docs/images/ug/icon_roomnumber.png differ diff --git a/docs/images/ug/icon_tasknumber.png b/docs/images/ug/icon_tasknumber.png new file mode 100644 index 00000000000..2dd37052974 Binary files /dev/null and b/docs/images/ug/icon_tasknumber.png differ diff --git a/docs/images/ug/icon_tasknumbertaskstab.png b/docs/images/ug/icon_tasknumbertaskstab.png new file mode 100644 index 00000000000..dc8fa895831 Binary files /dev/null and b/docs/images/ug/icon_tasknumbertaskstab.png differ diff --git a/docs/images/ug/navigatepatientstab.png b/docs/images/ug/navigatepatientstab.png new file mode 100644 index 00000000000..5dc6712ac23 Binary files /dev/null and b/docs/images/ug/navigatepatientstab.png differ diff --git a/docs/images/ug/navigateroomstab.png b/docs/images/ug/navigateroomstab.png new file mode 100644 index 00000000000..4df31ff8104 Binary files /dev/null and b/docs/images/ug/navigateroomstab.png differ diff --git a/docs/images/ug/navigatetaskstab.png b/docs/images/ug/navigatetaskstab.png new file mode 100644 index 00000000000..ed2534b61bf Binary files /dev/null and b/docs/images/ug/navigatetaskstab.png differ diff --git a/docs/images/w-yeehong.png b/docs/images/w-yeehong.png new file mode 100644 index 00000000000..5e827d4da4f Binary files /dev/null and b/docs/images/w-yeehong.png differ diff --git a/docs/index.md b/docs/index.md index 7601dbaad0d..0ab60ae8615 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,6 +1,6 @@ --- layout: page -title: AddressBook Level-3 +title: Covigent --- [![CI Status](https://github.com/se-edu/addressbook-level3/workflows/Java%20CI/badge.svg)](https://github.com/se-edu/addressbook-level3/actions) @@ -8,12 +8,9 @@ title: AddressBook Level-3 ![Ui](images/Ui.png) -**AddressBook is a desktop application for managing your contact details.** While it has a GUI, most of the user interactions happen using a CLI (Command Line Interface). +**Covigent is a desktop app for managing information of quarantined individuals and the tasks to be done by staff of the quarantine facilities**. It is optimized for use via a Command Line Interface (CLI) while still having the benefits of a Graphical User Interface (GUI). If you type fast, Covigent can improve your efficiency in managing your patients and tasks instead of using traditional GUI apps. -* 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. +Check out our [User Guide](https://ay2021s1-cs2103t-w12-1.github.io/tp/UserGuide.html) to get started. +This project is based on the AddressBook-Level3 project created by the [SE-EDU initiative](https://se-education.org). -**Acknowledgements** - -* Libraries used: [JavaFX](https://openjfx.io/), [Jackson](https://github.com/FasterXML/jackson), [JUnit5](https://github.com/junit-team/junit5) diff --git a/docs/storage.png b/docs/storage.png new file mode 100644 index 00000000000..749d73a3d62 Binary files /dev/null and b/docs/storage.png differ diff --git a/docs/team/chiamyunqing.md b/docs/team/chiamyunqing.md new file mode 100644 index 00000000000..c3d46826a8c --- /dev/null +++ b/docs/team/chiamyunqing.md @@ -0,0 +1,55 @@ +--- +layout: page +title: Chiam Yun Qing's Project Portfolio Page +--- + +## Project: Covigent + +Covigent is a desktop application that is used to aid hotel staff handle the Covid-19 situation better. 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 add a patient to Covigent and the attributes of the patient. + * What it does: allows the user to create a new patient with attributes (name, period of stay, temperature, age, phone and an optional comment) in Covigent. + + * Justification: This feature is highly necessary because the user will not be able to manage the quarantine facility with Covigent if new patient cannot be added. The attributes of the patient such as temperature and period of stay are also central and important in the context of a quarantine facility. + + * Highlights: This enhancement affects patient-related commands to be added in future. In particular, if the attributes of the patient are not properly designed in an OOP fashion to be extensible, future developers may find it difficult to make any updates to the attributes of patient. + +* **New Feature**: Added the ability to delete a patient from Covigent. + * What it does: allows the user to remove a patient from Covigent when the patient is no longer staying in the quarantine facility. + + * Justification: This feature improves the product significantly because the user may no longer want to keep track of the data of patient that are no longer staying in the quarantine facility. This allows the user to focus only on patients residing in the quarantine facility. + +* **New Feature**: Added the ability to search for a room in Covigent. + * What it does: allows the user to search for a room that a particular patient is residing in or search for a room with a given room number. + + * Justification: This feature improves the user experience of the product significantly because it makes it easier for the user to search for a room to get the room details easily without having to scroll through the room list manually. + +* **Code contributed**: [RepoSense link](https://nus-cs2103-ay2021s1.github.io/tp-dashboard/#breakdown=true&search=chiamyunqing&sort=groupTitle&sortWithin=title&since=2020-08-14&timeframe=commit&mergegroup=&groupSelect=groupByRepos&checkedFileTypes=docs~functional-code~test-code~other) + + +* **Project management**: + * Managed release [v1.3](https://github.com/AY2021S1-CS2103T-W12-1/tp/releases) on GitHub. + + +* **Enhancements to existing features**: + * Modified the GUI from AB3 to get a basic structure for the GUI of Covigent. (Pull request [\#24](https://github.com/AY2021S1-CS2103T-W12-1/tp/pull/24)) + * Improved the GUI by adding in a panel to display patient information. (Credit: Code reused from LeeMingDe)(Pull requests [\#110](https://github.com/AY2021S1-CS2103T-W12-1/tp/pull/110)) + * Wrote unit tests for `addpatient`, `deletepatient`, `searchroom`, `allocateroom` and `listroom` commands and patient attributes.(Pull requests [\#10](https://github.com/AY2021S1-CS2103T-W12-1/tp/pull/10), [\#59](https://github.com/AY2021S1-CS2103T-W12-1/tp/pull/59), [\#60](https://github.com/AY2021S1-CS2103T-W12-1/tp/pull/60), [\#99](https://github.com/AY2021S1-CS2103T-W12-1/tp/pull/99)) + * Refactored the codebase to reflect the functionalities of classes clearly. (Pull request [\#80](https://github.com/AY2021S1-CS2103T-W12-1/tp/pull/80)) + + +* **Documentation**: + * User Guide: + * Added documentation for the features `addpatient`, `deletepatient`, `searchroom`. + * Did cosmetic tweaks to existing documentation of features `help`. + * Added a command summary section for user to refer to easily. + * Developer Guide: + * Updated the implementation of `Model Component`. + * Added the implementation details for Patient and its CRUD operations. + + +* **Community**: + * PRs reviewed (with non-trivial review comments): [\#12](https://github.com/AY2021S1-CS2103T-W12-1/tp/pull/12), [\#26](https://github.com/AY2021S1-CS2103T-W12-1/tp/pull/26), [\#27](https://github.com/AY2021S1-CS2103T-W12-1/tp/pull/27), [\#79](https://github.com/AY2021S1-CS2103T-W12-1/tp/pull/79), [#115](https://github.com/AY2021S1-CS2103T-W12-1/tp/pull/115) + * Reported bugs and provided suggestions for another team, Homerce: [Link](https://github.com/chiamyunqing/ped/issues) diff --git a/docs/team/itssodium.md b/docs/team/itssodium.md new file mode 100644 index 00000000000..ed294b8bd7d --- /dev/null +++ b/docs/team/itssodium.md @@ -0,0 +1,44 @@ +--- +layout: page +title: Noorul Azlina's Project Portfolio Page +--- + +## Project: Covigent + +Covigent is a desktop application that is used to aid hotel staff handle the Covid-19 situation better. 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 search for an empty room. + * What it does: allows user to find out an empty room, if there are any. This information can be used to add in the + * Justification: This feature allows the user to check the room number for an empty room, then inserting the patient there. + +* **New Feature**: Added the ability to initialise the a certain number of rooms. + * What it does: allows user to define a certain number of rooms to exist in the hotel. + * Justification: This feature allows the user to continuously define the number of rooms as needed as the users can make a mistake in defining them. + * Highlights: This feature retains the information that is previously stored in the rooms and does not delete them. However, if user were to define the number of rooms less than existing number of occupied rooms, then error is thrown as it is not practical for visitors to leave hotel. + +* **New Feature**: Added the ability to list all rooms defined by user + * What it does: allows user to look at all rooms defined by user as the UI can change when displaying an empty room. + +* **Code contributed**: [RepoSense link](https://nus-cs2103-ay2021s1.github.io/tp-dashboard/#breakdown=true&search=itssodium) + +* **Enhancement to existing features**: + * move tab panel from top to the left [\#105](https://github.com/AY2021S1-CS2103T-W12-1/tp/pull/105) + * wrote unit test cases for `initroom` and `findemptyroom` commands and room attributes. [\#21](https://github.com/AY2021S1-CS2103T-W12-1/tp/pull/21) + +* **Additional Code to aid Feature**: + * Added most of the classes for rooms [\#26](https://github.com/AY2021S1-CS2103T-W12-1/tp/pull/26) + * Handled storage in Json format for Rooms and Tasks. [\#64](https://github.com/AY2021S1-CS2103T-W12-1/tp/pull/64), [\#84](https://github.com/AY2021S1-CS2103T-W12-1/tp/pull/84) + +* **Documentation**: + * User Guide: + * Added documentation for features `initroom`, `findemptyroom` and `listroom` + * Add FAQ section for UG to answer common questions asked my users + * Developer Guide: + * Updated the implementation for Storage + * Added implementation details for Room + +* **Community**: + * Reported Bugs and suggestions for another team, TrackIt [link](https://github.com/itssodium/ped/issues) + * PRs reviewed (with non-trivial review comments): [\#69](https://github.com/AY2021S1-CS2103T-W12-1/tp/pull/69), [\#85](https://github.com/AY2021S1-CS2103T-W12-1/tp/pull/85) and [\#90](https://github.com/AY2021S1-CS2103T-W12-1/tp/pull/90) diff --git a/docs/team/leemingde.md b/docs/team/leemingde.md new file mode 100644 index 00000000000..d0df520c2a1 --- /dev/null +++ b/docs/team/leemingde.md @@ -0,0 +1,46 @@ +--- +layout: page +title: Lee Ming De's Project Portfolio Page +--- + +## Project: Covigent + +Covigent is a desktop application that is used to aid hotel staff handle the Covid-19 situation better. 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 edit a patient's information. + * What it does: allows the user to change a patient's information (eg, name, temperature, age) after a patient has been added to Covigent. + * Justification: This feature improves the product significantly because a user can make mistakes in typing in the details of the patient and the application should provide a convenient way to rectify them. + +* **New Feature**: Added the ability for a user to allocate a patient to a room. + * What it does: allows the user to allocate a patient to a room or remove a patient from a room. + * Justification: This feature improves the product because it allows the user to keep track of the room the patient is in which fulfills the functional requirements of the application. + +* **Code contributed**: [RepoSense link](https://nus-cs2103-ay2021s1.github.io/tp-dashboard/#breakdown=true&search=leemingde) + +* **Enhancements to existing features**: + * Updated the GUI color scheme + * Improved the GUI by adding in a panel to display information + * Added CSS file for the details panel which was later reused by others. + +* **Testing**: + * Added unit tests for the `allocateRoomParser`, `editPatientCommand`, and `editPatientCommandParser` features. + * Added integration tests for `allocateRoomCommand`, `editPatientCommand`. + +* **Documentation**: + * User Guide: + * Added documentation for the features `editpatient` and `allocateroom` [\#44](https://github.com/AY2021S1-CS2103T-W12-1/tp/pull/44) + * Added introduction for Covigent [\#44](https://github.com/AY2021S1-CS2103T-W12-1/tp/pull/44) + * Developer Guide: + * Added implementation details of the `allocateroom` and `logging` features + * Added implementation details of GUI + * Added/updated UML diagrams for `Logic Component`, `allocateroom`, `UI` implementation + * Helped with formatting for the entire developer guide + +* **Community**: + * PRs reviewed (with non-trivial review comments): [\#10](https://github.com/AY2021S1-CS2103T-W12-1/tp/pull/10), + [\#26](https://github.com/AY2021S1-CS2103T-W12-1/tp/pull/26), [\#27](https://github.com/AY2021S1-CS2103T-W12-1/tp/pull/27), + [\#69](https://github.com/AY2021S1-CS2103T-W12-1/tp/pull/69), [\#80](https://github.com/AY2021S1-CS2103T-W12-1/tp/pull/80), + [\#84](https://github.com/AY2021S1-CS2103T-W12-1/tp/pull/84), [\#99](https://github.com/AY2021S1-CS2103T-W12-1/tp/pull/99) + * Reported bugs and provided suggestions for StonksBook, a project by another teams in the class: [link](https://github.com/leemingde/ped/issues) diff --git a/docs/team/raymondge.md b/docs/team/raymondge.md new file mode 100644 index 00000000000..dfc5ee5959c --- /dev/null +++ b/docs/team/raymondge.md @@ -0,0 +1,42 @@ +--- +layout: page +title: Ge Wai Lok's Project Portfolio Page +--- + +## Project: Covigent + +Covigent is a desktop application that is used to aid hotel staff handle the Covid-19 situation better. 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 search for patients based on name or temperature range. + * What it does: allows user to search for patient based on the input, it can be either a sub-name of the patient's name, or a temperature range. All patients matching the criteria will be displayed. + * Justification: This feature improves the product by providing the user to find the patients in mind in a more efficient way. + +* **New Feature**: Added the ability to search for tasks based on due date. + * What it does: allows user look for tasks before a given due date(deadline). + * Justification: This feature improves the product by providing the user to prioritise the tasks. The users may use the search result to decide what task to complete first. + * Highlights: This feature displays the search result based on the Room number, tasks are grouped to be displayed under room number in the task tab. + +* **New Feature**: Added the ability to list all tasks defined by user + * What it does: allows user to look at all tasks defined by user as the UI can change when adding, searching or deleting tasks. + +* **Code contributed**: [RepoSense link](https://nus-cs2103-ay2021s1.github.io/tp-dashboard/#breakdown=true&search=raymondge) + +* **Enhancements to existing features**: + * Wrote additional test cases for temperatureRange class. + +* **Enhancements Code to aid Feature**: + * Added most of the implementation of the UI and storage for task. + * Improved css for task to make the UI for tasks show one scollbar only instead of showing two scollbar, the problem was due to the implementation of task UI using the room UI. + +* **Documentation**: + * User Guide: + * Added documentation for the features `searchpatient` , `listpatient`, `searchtask` and `listtask` + * Added documentation for the section `About this Guide` + * Developer Guide: + * Added implementation details of the `searchpatient` , `listpatient`, `searchtask` and `listtask` feature. + * Added documentation and UML diagram for the section `UI` + + * **Community**: + * PRs reviewed (with non-trivial review comments): [\#72](https://github.com/AY2021S1-CS2103T-W12-1/tp/pull/72), [\#79](https://github.com/AY2021S1-CS2103T-W12-1/tp/pull/79), [\#80](https://github.com/AY2021S1-CS2103T-W12-1/tp/pull/80) diff --git a/docs/team/w-yeehong.md b/docs/team/w-yeehong.md new file mode 100644 index 00000000000..9d56515d96a --- /dev/null +++ b/docs/team/w-yeehong.md @@ -0,0 +1,61 @@ +--- +layout: page +title: Wee Yee Hong's Project Portfolio Page +--- + +## Project: Covigent + +Covigent is a desktop application that is used to aid hotel staff handle the Covid-19 situation better. 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 add a task to a room. + * What it does: allows the user to create a new task with description and due date in any room in Covigent. + * Justification: This feature improves the product significantly because a user may want to keep track of time-critical tasks related to patients in each of the rooms. + +* **New Feature**: Added the ability to delete a task from a room. + * What it does: allows the user remove any tasks that she has completed or does not need from a room. + * Justification: This feature improves the product significantly because too many tasks in a room can clutter the user interface and make it more difficult for the user to organize her tasks. + +* **New Feature**: Added the ability to edit the information of a task in a room. + * What it does: allows the user to change the description or due date after a task has been added to a room in Covigent. + * Justification: This feature improves the product significantly because a user can make mistakes in typing in the details of a task and the application should provide a convenient way to rectify them. + +* **New Feature**: Added the ability to view a list of tasks in the user interface. + * What it does: allows the user to see in which rooms the tasks can be found and get an overview of all tasks that are in Covigent, without needing to know the room number of the room in which the task has been added. + * Justification: This feature improves the product significantly because a user may forget which room she has added a task to and wants a quick way to look up the task. + +* **New Feature**: Allowed commands to support different date-time formats. + * What it does: allows the user to select their preferred date-time formats when using commands that have a date or time component. + * Justification: This feature improves the product because it allows the user to comfortably use a common and familiar date-time format (e.g. dd/MM/YYYY) in her country without memorizing a specific format. + +* **Code contributed**: [RepoSense link](https://nus-cs2103-ay2021s1.github.io/tp-dashboard/#breakdown=true&search=w-yeehong) + +* **Enhancements to existing features**: + * Updated GUI color scheme and separate CSS into multiple files based on the names of the views for easier lookup (Pull request [\#101](https://github.com/AY2021S1-CS2103T-W12-1/tp/pull/101)). + * Refactored and repackaged the codebase for better organization of files based on the model type handled (Pull requests [\#22](https://github.com/AY2021S1-CS2103T-W12-1/tp/pull/22), [#107](https://github.com/AY2021S1-CS2103T-W12-1/tp/pull/107)). + * Reduced code duplication (Pull request [\#154](https://github.com/AY2021S1-CS2103T-W12-1/tp/pull/154)). + +* **Testing**: + * Added unit tests to test and validate that the description and due date of a task should conform to a specified format. (Pull requests [\#27](https://github.com/AY2021S1-CS2103T-W12-1/tp/pull/27), [\#228](https://github.com/AY2021S1-CS2103T-W12-1/tp/pull/228)) + * Added unit tests for the `addtask`, `deletetask`, and `edittask` features (Pull requests [\#27](https://github.com/AY2021S1-CS2103T-W12-1/tp/pull/27), [\#78](https://github.com/AY2021S1-CS2103T-W12-1/tp/pull/78), [\#89](https://github.com/AY2021S1-CS2103T-W12-1/tp/pull/89), [\#242](https://github.com/AY2021S1-CS2103T-W12-1/tp/pull/242)) + +* **Documentation**: + * User Guide: + * Added documentation for the features `addtask`, `deletetask`, and `edittask` + * Improved "Getting Started" section for Covigent + * Developer Guide: + * Added the implementation details for `Task` and its CRUD operations + * Added the section on planned features + * Updated to reflect the high-level view of the architecture of Covigent + +* **Community**: + * PRs reviewed (with non-trivial review comments): [\#10](https://github.com/AY2021S1-CS2103T-W12-1/tp/pull/10), + [\#12](https://github.com/AY2021S1-CS2103T-W12-1/tp/pull/12), [\#26](https://github.com/AY2021S1-CS2103T-W12-1/tp/pull/26), + [\#60](https://github.com/AY2021S1-CS2103T-W12-1/tp/pull/60), [\#64](https://github.com/AY2021S1-CS2103T-W12-1/tp/pull/64), + [\#72](https://github.com/AY2021S1-CS2103T-W12-1/tp/pull/72), [\#100](https://github.com/AY2021S1-CS2103T-W12-1/tp/pull/100), + [\#105](https://github.com/AY2021S1-CS2103T-W12-1/tp/pull/105), [\#244](https://github.com/AY2021S1-CS2103T-W12-1/tp/pull/244) + * Reported bugs and provided suggestions for Jarvis, a project by another teams in the class: [link](https://github.com/w-yeehong/ped/issues) + +* **Tools**: + * Integrated a third party library (Mockito) to the project ([\#104](https://github.com/AY2021S1-CS2103T-W12-1/tp/pull/104)) diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 44e7c4d1d7b..be52383ef49 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.2.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/src/main/java/seedu/address/MainApp.java b/src/main/java/seedu/address/MainApp.java index e5cfb161b73..7d3298458e7 100644 --- a/src/main/java/seedu/address/MainApp.java +++ b/src/main/java/seedu/address/MainApp.java @@ -15,19 +15,24 @@ import seedu.address.commons.util.StringUtil; import seedu.address.logic.Logic; import seedu.address.logic.LogicManager; -import seedu.address.model.AddressBook; import seedu.address.model.Model; import seedu.address.model.ModelManager; -import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.model.PatientRecords; +import seedu.address.model.ReadOnlyList; import seedu.address.model.ReadOnlyUserPrefs; +import seedu.address.model.RoomList; import seedu.address.model.UserPrefs; +import seedu.address.model.patient.Patient; +import seedu.address.model.room.Room; import seedu.address.model.util.SampleDataUtil; -import seedu.address.storage.AddressBookStorage; -import seedu.address.storage.JsonAddressBookStorage; import seedu.address.storage.JsonUserPrefsStorage; import seedu.address.storage.Storage; import seedu.address.storage.StorageManager; import seedu.address.storage.UserPrefsStorage; +import seedu.address.storage.patient.JsonPatientRecordsStorage; +import seedu.address.storage.patient.PatientRecordsStorage; +import seedu.address.storage.rooms.JsonRoomOccupancyStorage; +import seedu.address.storage.rooms.RoomRecordsStorage; import seedu.address.ui.Ui; import seedu.address.ui.UiManager; @@ -48,7 +53,7 @@ public class MainApp extends Application { @Override public void init() throws Exception { - logger.info("=============================[ Initializing AddressBook ]==========================="); + logger.info("=============================[ Initializing CovigentApp ]==========================="); super.init(); AppParameters appParameters = AppParameters.parse(getParameters()); @@ -56,8 +61,11 @@ 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); + PatientRecordsStorage patientRecordsStorage = + new JsonPatientRecordsStorage(userPrefs.getCovigentAppFilePath()); + RoomRecordsStorage roomOccupancyStorage = new JsonRoomOccupancyStorage( + userPrefs.getRoomsOccupiedFilePath()); + storage = new StorageManager(patientRecordsStorage, roomOccupancyStorage, userPrefsStorage); initLogging(config); @@ -69,28 +77,45 @@ 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 covigent app and RoomList + * and {@code userPrefs}.
+ * The data from the sample covigent app and covigentApp will be used instead if {@code storage}'s covigent app is + * not found,or an empty covigent app will be used instead if errors occur when reading {@code storage}'s + * address book. */ private Model initModelManager(Storage storage, ReadOnlyUserPrefs userPrefs) { - Optional addressBookOptional; - ReadOnlyAddressBook initialData; + Optional> patientRecordsOptional; + ReadOnlyList initialData; + Optional> readOnlyRoomOccupancy; + ReadOnlyList initialRoomList; + try { - addressBookOptional = storage.readAddressBook(); - if (!addressBookOptional.isPresent()) { - logger.info("Data file not found. Will be starting with a sample AddressBook"); - } - initialData = addressBookOptional.orElseGet(SampleDataUtil::getSampleAddressBook); + readOnlyRoomOccupancy = storage.readOnlyRoomOccupancy(); + initialRoomList = readOnlyRoomOccupancy.orElseGet(SampleDataUtil::getSampleRoomList); } catch (DataConversionException e) { - logger.warning("Data file not in the correct format. Will be starting with an empty AddressBook"); - initialData = new AddressBook(); + logger.warning( + "Room Data file not in the correct format. Will be starting with an empty CovigentApp"); + initialRoomList = new RoomList(); } catch (IOException e) { - logger.warning("Problem while reading from the file. Will be starting with an empty AddressBook"); - initialData = new AddressBook(); + logger.warning("Problem while reading from the file. Will be starting with an empty CovigentApp"); + initialRoomList = new RoomList(); } - return new ModelManager(initialData, userPrefs); + try { + patientRecordsOptional = storage.readPatientRecords(); + if (!patientRecordsOptional.isPresent()) { + logger.info("Data file not found. Will be starting with a sample CovigentApp"); + } + initialData = patientRecordsOptional.orElseGet(SampleDataUtil::getSampleCovigentApp); + } catch (DataConversionException e) { + logger.warning( + "Patient Data file not in the correct format. Will be starting with an empty CovigentApp"); + initialData = new PatientRecords(); + } catch (IOException e) { + logger.warning("Problem while reading from the file. Will be starting with an empty CovigentApp"); + initialData = new PatientRecords(); + } + return new ModelManager(initialData, initialRoomList, userPrefs); } private void initLogging(Config config) { @@ -151,7 +176,7 @@ protected UserPrefs initPrefs(UserPrefsStorage storage) { + "Using default user prefs"); initializedPrefs = new UserPrefs(); } catch (IOException e) { - logger.warning("Problem while reading from the file. Will be starting with an empty AddressBook"); + logger.warning("Problem while reading from the file. Will be starting with an empty CovigentApp"); initializedPrefs = new UserPrefs(); } @@ -167,7 +192,7 @@ protected UserPrefs initPrefs(UserPrefsStorage storage) { @Override public void start(Stage primaryStage) { - logger.info("Starting AddressBook " + MainApp.VERSION); + logger.info("Starting CovigentApp " + MainApp.VERSION); ui.start(primaryStage); } diff --git a/src/main/java/seedu/address/commons/core/GuiSettings.java b/src/main/java/seedu/address/commons/core/GuiSettings.java index ba33653be67..5d4b37dba72 100644 --- a/src/main/java/seedu/address/commons/core/GuiSettings.java +++ b/src/main/java/seedu/address/commons/core/GuiSettings.java @@ -10,8 +10,8 @@ */ public class GuiSettings implements Serializable { - private static final double DEFAULT_HEIGHT = 600; - private static final double DEFAULT_WIDTH = 740; + private static final double DEFAULT_HEIGHT = 750; + private static final double DEFAULT_WIDTH = 1000; private final double windowWidth; private final double windowHeight; diff --git a/src/main/java/seedu/address/commons/core/LogsCenter.java b/src/main/java/seedu/address/commons/core/LogsCenter.java index 431e7185e76..6ec61e6dfca 100644 --- a/src/main/java/seedu/address/commons/core/LogsCenter.java +++ b/src/main/java/seedu/address/commons/core/LogsCenter.java @@ -18,7 +18,7 @@ public class LogsCenter { private static final int MAX_FILE_COUNT = 5; private static final int MAX_FILE_SIZE_IN_BYTES = (int) (Math.pow(2, 20) * 5); // 5MB - private static final String LOG_FILE = "addressbook.log"; + private static final String LOG_FILE = "covigentapp.log"; private static Level currentLogLevel = Level.INFO; private static final Logger logger = LogsCenter.getLogger(LogsCenter.class); private static FileHandler fileHandler; diff --git a/src/main/java/seedu/address/commons/core/Messages.java b/src/main/java/seedu/address/commons/core/Messages.java index 1deb3a1e469..1f0f23dd6e1 100644 --- a/src/main/java/seedu/address/commons/core/Messages.java +++ b/src/main/java/seedu/address/commons/core/Messages.java @@ -5,9 +5,31 @@ */ public class Messages { + // common + public static final String MESSAGE_UNKNOWN_COMMAND = "Unknown command"; public static final String MESSAGE_INVALID_COMMAND_FORMAT = "Invalid command format! \n%1$s"; - public static final String MESSAGE_INVALID_PERSON_DISPLAYED_INDEX = "The person index provided is invalid"; - public static final String MESSAGE_PERSONS_LISTED_OVERVIEW = "%1$d persons listed!"; + public static final String MESSAGE_TOO_MANY_COMMANDS = "Too many commands entered!"; + + // patients + + public static final String MESSAGE_INVALID_PATIENT_NAME = "The patient name provided " + + "cannot be found in Covigent."; + public static final String MESSAGE_PATIENT_LISTED_OVERVIEW = "%1$d patient listed!"; + + // rooms + + public static final String MESSAGE_INVALID_ROOM_NUMBER = "The room number provided " + + "cannot be found in Covigent."; + public static final String MESSAGE_PATIENT_ALREADY_ASSIGNED = "Patient has already been assigned a room.\nPlease" + + " clear patient from the room first."; + public static final String MESSAGE_PATIENT_NO_ROOM = "Patient is not assigned to any room yet."; + + // tasks + + public static final String MESSAGE_INVALID_TASK_INDEX = "The task number provided " + + "cannot be found in the room."; + public static final String MESSAGE_TASK_NOT_EDITED = "Task has not been edited.\nPlease ensure that at least" + + " one valid and different value has been provided."; } 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..5cd4d643d26 --- /dev/null +++ b/src/main/java/seedu/address/commons/util/DateTimeUtil.java @@ -0,0 +1,108 @@ +package seedu.address.commons.util; + +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; + +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeFormatterBuilder; +import java.time.format.DateTimeParseException; +import java.time.temporal.ChronoField; +import java.time.temporal.TemporalQuery; + +//@@author w-yeehong +/** + * Helper functions for handling date and date-time objects. + */ +public class DateTimeUtil { + + // Date formats + /** e.g. 20200920 */ + public static final DateTimeFormatter DATE_FORMAT_YEAR_MONTH_DAY = + DateTimeFormatter.ofPattern("yyyyMMdd"); + + /** e.g. 20 Sep 2020 */ + public static final DateTimeFormatter DATE_FORMAT_DAY_MONTH_YEAR_LONG_SPACE_DELIMITED = + DateTimeFormatter.ofPattern("d MMM yyyy"); + + /** e.g. 20/9/2020 */ + public static final DateTimeFormatter DATE_FORMAT_DAY_MONTH_YEAR_SLASH_DELIMITED = + DateTimeFormatter.ofPattern("d/M/yyyy"); + + + // Date-time formats + /** e.g. 20200920 or 20200920 2359 */ + public static final DateTimeFormatter DATETIME_FORMAT_YEAR_MONTH_DAY_OPTIONAL_TIME = + createFormatterWithOptionalTime("yyyyMMdd", " HHmm"); + + /** e.g. 20 Sep 2020 (time defaults to 0000) or 20 Sep 2020 2359 */ + public static final DateTimeFormatter DATETIME_FORMAT_DAY_MONTH_YEAR_LONG_SPACE_DELIMITED_OPTIONAL_TIME = + createFormatterWithOptionalTime("d MMM yyyy", " HHmm"); + + /** e.g. 20/9/2020 (time defaults to 0000) or 20/09/2020 2359 */ + public static final DateTimeFormatter DATETIME_FORMAT_DAY_MONTH_YEAR_SLASH_DELIMITED_OPTIONAL_TIME = + createFormatterWithOptionalTime("d/M/yyyy", " HHmm"); + + + /** + * Parses the text into a date-time object (e.g. {@code LocalDate} or {@code LocalDateTime}) + * using all given {@code DateTimeFormatter} and returns the first successful result. + * + * The text is parsed using each formatter in turn. No output is generated if a formatter fails to + * parse the text. However, a {@code DateTimeParseException} is thrown if all formatters fail. + * + * The {@code TemporalQuery} is typically a method reference to a + * {@code from(TemporalAccessor)} method. + * For example: + *
+     *     LocalDate date = DateTimeUtil.parseFirstMatching(text, LocalDate::from, formatter1, formatter2);
+     * 
+ * + * For more details, refer to + * https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatter.html# + * parse-java.lang.CharSequence-java.time.temporal.TemporalQuery- + * + * @param text The text to parse, cannot be null. + * @param query A way to retrieve information from a temporal-based object. + * @param formatters Formatters to parse the text with, cannot be null. + * @param A date-time class, usually a {@code TemporalAccessor}. + * @return The date-time object parsed from text. + * @throws DateTimeParseException if text cannot be parsed with any of the formatters or no formatters provided. + */ + public static T parseFirstMatching( + CharSequence text, TemporalQuery query, DateTimeFormatter... formatters) { + requireAllNonNull(text, query, formatters); + + for (DateTimeFormatter formatter : formatters) { + try { + return formatter.parse(text, query); + } catch (DateTimeParseException e) { + // Current formatter is invalid. + // Proceed to test next formatter. + } + } + + String exceptionMessage = String.format("Unable to parse %s", text); + int errorIndex = 0; // index in parsed text that was invalid, set to 0 to indicate entirety of text + + throw new DateTimeParseException(exceptionMessage, text, errorIndex); + } + + /** + * Creates a {@code DateTimeFormatter} with default values of time. + * If a time conforms to the time format, the {@code DateTimeFormatter} sets the parsed date-time + * to that value. Otherwise, the hour and minute of the parsed date-time are set to 0. + * + * @param dateFormat The pattern for the date portion of a string parsed by a{@code DateTimeFormatter}. + * @param timeFormat The pattern for the time portion of a string parsed by a {@code DateTimeFormatter}. + * @return A {@code DateTimeFormatter} that defaults the hour and minute to 0 if time does not conform to format. + */ + private static DateTimeFormatter createFormatterWithOptionalTime(String dateFormat, String timeFormat) { + return new DateTimeFormatterBuilder() + .appendPattern(dateFormat) + .optionalStart() + .appendPattern(timeFormat) + .optionalEnd() + .parseDefaulting(ChronoField.HOUR_OF_DAY, 0) + .parseDefaulting(ChronoField.MINUTE_OF_HOUR, 0) + .toFormatter(); + } +} diff --git a/src/main/java/seedu/address/commons/util/FileUtil.java b/src/main/java/seedu/address/commons/util/FileUtil.java index b1e2767cdd9..9f73565a49d 100644 --- a/src/main/java/seedu/address/commons/util/FileUtil.java +++ b/src/main/java/seedu/address/commons/util/FileUtil.java @@ -18,7 +18,7 @@ public static boolean isFileExists(Path file) { } /** - * Returns true if {@code path} can be converted into a {@code Path} via {@link Paths#get(String)}, + * Returns true if {@code path} can be converted into a {@code Path} via {@link Paths#get(String, String...)})}}, * otherwise returns false. * @param path A string representing the file path. Cannot be null. */ diff --git a/src/main/java/seedu/address/logic/Logic.java b/src/main/java/seedu/address/logic/Logic.java index 92cd8fa605a..0290f6bd230 100644 --- a/src/main/java/seedu/address/logic/Logic.java +++ b/src/main/java/seedu/address/logic/Logic.java @@ -7,8 +7,10 @@ import seedu.address.logic.commands.CommandResult; import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.person.Person; +import seedu.address.model.ReadOnlyList; +import seedu.address.model.patient.Patient; +import seedu.address.model.room.Room; +import seedu.address.model.room.RoomTaskAssociation; /** * API of the Logic component @@ -16,6 +18,7 @@ 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. @@ -24,19 +27,28 @@ public interface Logic { CommandResult execute(String commandText) throws CommandException, ParseException; /** - * Returns the AddressBook. + * Returns Covigent. * - * @see seedu.address.model.Model#getAddressBook() + * @see seedu.address.model.Model#getPatientRecords() */ - ReadOnlyAddressBook getAddressBook(); + ReadOnlyList getCovigentApp(); + + /** Returns an unmodifiable view of the filtered list of patients. */ + ObservableList getFilteredPatientList(); + + /** Returns an unmodifiable view of the list of rooms. */ + ObservableList getRoomList(); - /** Returns an unmodifiable view of the filtered list of persons */ - ObservableList getFilteredPersonList(); + /** Returns an unmodifiable view of the filtered list of rooms. */ + ObservableList getFilteredRoomList(); + + /** Returns an unmodifiable view of the filtered list of room-task associations. */ + ObservableList getFilteredRoomTaskRecords(); /** - * Returns the user prefs' address book file path. + * Returns the user prefs' Covigent file path. */ - Path getAddressBookFilePath(); + Path getCovigentAppFilePath(); /** * Returns the user prefs' GUI settings. @@ -47,4 +59,5 @@ public interface Logic { * Set 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..0ab7e964cee 100644 --- a/src/main/java/seedu/address/logic/LogicManager.java +++ b/src/main/java/seedu/address/logic/LogicManager.java @@ -10,13 +10,16 @@ import seedu.address.logic.commands.Command; import seedu.address.logic.commands.CommandResult; import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.logic.parser.AddressBookParser; +import seedu.address.logic.parser.CovigentAppParser; import seedu.address.logic.parser.exceptions.ParseException; import seedu.address.model.Model; -import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.person.Person; +import seedu.address.model.ReadOnlyList; +import seedu.address.model.patient.Patient; +import seedu.address.model.room.Room; +import seedu.address.model.room.RoomTaskAssociation; import seedu.address.storage.Storage; + /** * The main LogicManager of the app. */ @@ -26,7 +29,7 @@ public class LogicManager implements Logic { private final Model model; private final Storage storage; - private final AddressBookParser addressBookParser; + private final CovigentAppParser covigentAppParser; /** * Constructs a {@code LogicManager} with the given {@code Model} and {@code Storage}. @@ -34,7 +37,7 @@ public class LogicManager implements Logic { public LogicManager(Model model, Storage storage) { this.model = model; this.storage = storage; - addressBookParser = new AddressBookParser(); + covigentAppParser = new CovigentAppParser(); } @Override @@ -42,11 +45,16 @@ public CommandResult execute(String commandText) throws CommandException, ParseE logger.info("----------------[USER COMMAND][" + commandText + "]"); CommandResult commandResult; - Command command = addressBookParser.parseCommand(commandText); + Command command = covigentAppParser.parseCommand(commandText); commandResult = command.execute(model); try { - storage.saveAddressBook(model.getAddressBook()); + storage.saveRoomsInformation(model.getModifiableRoomList()); + } catch (IOException ioe) { + throw new CommandException(FILE_OPS_ERROR_MESSAGE); + } + try { + storage.savePatientRecords(model.getPatientRecords()); } catch (IOException ioe) { throw new CommandException(FILE_OPS_ERROR_MESSAGE + ioe, ioe); } @@ -55,18 +63,23 @@ public CommandResult execute(String commandText) throws CommandException, ParseE } @Override - public ReadOnlyAddressBook getAddressBook() { - return model.getAddressBook(); + public ReadOnlyList getCovigentApp() { + return model.getPatientRecords(); + } + + @Override + public ObservableList getFilteredPatientList() { + return model.getFilteredPatientList(); } @Override - public ObservableList getFilteredPersonList() { - return model.getFilteredPersonList(); + public ObservableList getRoomList() { + return model.getRoomListObservableList(); } @Override - public Path getAddressBookFilePath() { - return model.getAddressBookFilePath(); + public Path getCovigentAppFilePath() { + return model.getCovigentAppFilePath(); } @Override @@ -78,4 +91,14 @@ public GuiSettings getGuiSettings() { public void setGuiSettings(GuiSettings guiSettings) { model.setGuiSettings(guiSettings); } + + @Override + public ObservableList getFilteredRoomList() { + return model.getFilteredRoomList(); + } + + @Override + public ObservableList getFilteredRoomTaskRecords() { + return model.getFilteredRoomTaskRecords(); + } } diff --git a/src/main/java/seedu/address/logic/commands/AddCommand.java b/src/main/java/seedu/address/logic/commands/AddCommand.java deleted file mode 100644 index 71656d7c5c8..00000000000 --- a/src/main/java/seedu/address/logic/commands/AddCommand.java +++ /dev/null @@ -1,67 +0,0 @@ -package seedu.address.logic.commands; - -import static java.util.Objects.requireNonNull; -import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; -import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; -import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; -import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; -import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; - -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.model.Model; -import seedu.address.model.person.Person; - -/** - * Adds a person to the address book. - */ -public class AddCommand extends Command { - - public static final String COMMAND_WORD = "add"; - - public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a person to the address book. " - + "Parameters: " - + PREFIX_NAME + "NAME " - + PREFIX_PHONE + "PHONE " - + PREFIX_EMAIL + "EMAIL " - + PREFIX_ADDRESS + "ADDRESS " - + "[" + PREFIX_TAG + "TAG]...\n" - + "Example: " + COMMAND_WORD + " " - + PREFIX_NAME + "John Doe " - + PREFIX_PHONE + "98765432 " - + PREFIX_EMAIL + "johnd@example.com " - + PREFIX_ADDRESS + "311, Clementi Ave 2, #02-25 " - + PREFIX_TAG + "friends " - + PREFIX_TAG + "owesMoney"; - - public static final String MESSAGE_SUCCESS = "New person added: %1$s"; - public static final String MESSAGE_DUPLICATE_PERSON = "This person already exists in the address book"; - - private final Person toAdd; - - /** - * Creates an AddCommand to add the specified {@code Person} - */ - public AddCommand(Person person) { - requireNonNull(person); - toAdd = person; - } - - @Override - public CommandResult execute(Model model) throws CommandException { - requireNonNull(model); - - if (model.hasPerson(toAdd)) { - throw new CommandException(MESSAGE_DUPLICATE_PERSON); - } - - model.addPerson(toAdd); - return new CommandResult(String.format(MESSAGE_SUCCESS, toAdd)); - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof AddCommand // instanceof handles nulls - && toAdd.equals(((AddCommand) other).toAdd)); - } -} diff --git a/src/main/java/seedu/address/logic/commands/ClearCommand.java b/src/main/java/seedu/address/logic/commands/ClearCommand.java deleted file mode 100644 index 9c86b1fa6e4..00000000000 --- a/src/main/java/seedu/address/logic/commands/ClearCommand.java +++ /dev/null @@ -1,23 +0,0 @@ -package seedu.address.logic.commands; - -import static java.util.Objects.requireNonNull; - -import seedu.address.model.AddressBook; -import seedu.address.model.Model; - -/** - * Clears the address book. - */ -public class ClearCommand extends Command { - - public static final String COMMAND_WORD = "clear"; - public static final String MESSAGE_SUCCESS = "Address book has been cleared!"; - - - @Override - public CommandResult execute(Model model) { - requireNonNull(model); - model.setAddressBook(new AddressBook()); - return new CommandResult(MESSAGE_SUCCESS); - } -} diff --git a/src/main/java/seedu/address/logic/commands/DeleteCommand.java b/src/main/java/seedu/address/logic/commands/DeleteCommand.java deleted file mode 100644 index 02fd256acba..00000000000 --- a/src/main/java/seedu/address/logic/commands/DeleteCommand.java +++ /dev/null @@ -1,53 +0,0 @@ -package seedu.address.logic.commands; - -import static java.util.Objects.requireNonNull; - -import java.util.List; - -import seedu.address.commons.core.Messages; -import seedu.address.commons.core.index.Index; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.model.Model; -import seedu.address.model.person.Person; - -/** - * Deletes a person identified using it's displayed index from the address book. - */ -public class DeleteCommand extends Command { - - public static final String COMMAND_WORD = "delete"; - - public static final String MESSAGE_USAGE = COMMAND_WORD - + ": Deletes the person identified by the index number used in the displayed person list.\n" - + "Parameters: INDEX (must be a positive integer)\n" - + "Example: " + COMMAND_WORD + " 1"; - - public static final String MESSAGE_DELETE_PERSON_SUCCESS = "Deleted Person: %1$s"; - - private final Index targetIndex; - - public DeleteCommand(Index targetIndex) { - this.targetIndex = targetIndex; - } - - @Override - public CommandResult execute(Model model) throws CommandException { - requireNonNull(model); - List lastShownList = model.getFilteredPersonList(); - - if (targetIndex.getZeroBased() >= lastShownList.size()) { - throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); - } - - Person personToDelete = lastShownList.get(targetIndex.getZeroBased()); - model.deletePerson(personToDelete); - return new CommandResult(String.format(MESSAGE_DELETE_PERSON_SUCCESS, personToDelete)); - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof DeleteCommand // instanceof handles nulls - && targetIndex.equals(((DeleteCommand) other).targetIndex)); // state check - } -} diff --git a/src/main/java/seedu/address/logic/commands/EditCommand.java b/src/main/java/seedu/address/logic/commands/EditCommand.java deleted file mode 100644 index 7e36114902f..00000000000 --- a/src/main/java/seedu/address/logic/commands/EditCommand.java +++ /dev/null @@ -1,226 +0,0 @@ -package seedu.address.logic.commands; - -import static java.util.Objects.requireNonNull; -import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; -import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; -import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; -import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; -import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; -import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; - -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Optional; -import java.util.Set; - -import seedu.address.commons.core.Messages; -import seedu.address.commons.core.index.Index; -import seedu.address.commons.util.CollectionUtil; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.model.Model; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Person; -import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; - -/** - * Edits the details of an existing person in the address book. - */ -public class EditCommand extends Command { - - public static final String COMMAND_WORD = "edit"; - - public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits the details of the person identified " - + "by the index number used in the displayed person list. " - + "Existing values will be overwritten by the input values.\n" - + "Parameters: INDEX (must be a positive integer) " - + "[" + PREFIX_NAME + "NAME] " - + "[" + PREFIX_PHONE + "PHONE] " - + "[" + PREFIX_EMAIL + "EMAIL] " - + "[" + PREFIX_ADDRESS + "ADDRESS] " - + "[" + PREFIX_TAG + "TAG]...\n" - + "Example: " + COMMAND_WORD + " 1 " - + PREFIX_PHONE + "91234567 " - + PREFIX_EMAIL + "johndoe@example.com"; - - public static final String MESSAGE_EDIT_PERSON_SUCCESS = "Edited Person: %1$s"; - public static final String MESSAGE_NOT_EDITED = "At least one field to edit must be provided."; - public static final String MESSAGE_DUPLICATE_PERSON = "This person already exists in the address book."; - - private final Index index; - private final EditPersonDescriptor editPersonDescriptor; - - /** - * @param index of the person in the filtered person list to edit - * @param editPersonDescriptor details to edit the person with - */ - public EditCommand(Index index, EditPersonDescriptor editPersonDescriptor) { - requireNonNull(index); - requireNonNull(editPersonDescriptor); - - this.index = index; - this.editPersonDescriptor = new EditPersonDescriptor(editPersonDescriptor); - } - - @Override - public CommandResult execute(Model model) throws CommandException { - requireNonNull(model); - List lastShownList = model.getFilteredPersonList(); - - if (index.getZeroBased() >= lastShownList.size()) { - throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); - } - - Person personToEdit = lastShownList.get(index.getZeroBased()); - Person editedPerson = createEditedPerson(personToEdit, editPersonDescriptor); - - if (!personToEdit.isSamePerson(editedPerson) && model.hasPerson(editedPerson)) { - throw new CommandException(MESSAGE_DUPLICATE_PERSON); - } - - model.setPerson(personToEdit, editedPerson); - model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); - return new CommandResult(String.format(MESSAGE_EDIT_PERSON_SUCCESS, editedPerson)); - } - - /** - * Creates and returns a {@code Person} with the details of {@code personToEdit} - * edited with {@code editPersonDescriptor}. - */ - private static Person createEditedPerson(Person personToEdit, EditPersonDescriptor editPersonDescriptor) { - assert personToEdit != null; - - Name updatedName = editPersonDescriptor.getName().orElse(personToEdit.getName()); - Phone updatedPhone = editPersonDescriptor.getPhone().orElse(personToEdit.getPhone()); - Email updatedEmail = editPersonDescriptor.getEmail().orElse(personToEdit.getEmail()); - Address updatedAddress = editPersonDescriptor.getAddress().orElse(personToEdit.getAddress()); - Set updatedTags = editPersonDescriptor.getTags().orElse(personToEdit.getTags()); - - return new Person(updatedName, updatedPhone, updatedEmail, updatedAddress, updatedTags); - } - - @Override - public boolean equals(Object other) { - // short circuit if same object - if (other == this) { - return true; - } - - // instanceof handles nulls - if (!(other instanceof EditCommand)) { - return false; - } - - // state check - EditCommand e = (EditCommand) other; - return index.equals(e.index) - && editPersonDescriptor.equals(e.editPersonDescriptor); - } - - /** - * Stores the details to edit the person with. Each non-empty field value will replace the - * corresponding field value of the person. - */ - public static class EditPersonDescriptor { - private Name name; - private Phone phone; - private Email email; - private Address address; - private Set tags; - - public EditPersonDescriptor() {} - - /** - * Copy constructor. - * A defensive copy of {@code tags} is used internally. - */ - public EditPersonDescriptor(EditPersonDescriptor toCopy) { - setName(toCopy.name); - setPhone(toCopy.phone); - setEmail(toCopy.email); - setAddress(toCopy.address); - setTags(toCopy.tags); - } - - /** - * Returns true if at least one field is edited. - */ - public boolean isAnyFieldEdited() { - return CollectionUtil.isAnyNonNull(name, phone, email, address, tags); - } - - public void setName(Name name) { - this.name = name; - } - - public Optional getName() { - return Optional.ofNullable(name); - } - - public void setPhone(Phone phone) { - this.phone = phone; - } - - public Optional getPhone() { - return Optional.ofNullable(phone); - } - - public void setEmail(Email email) { - this.email = email; - } - - public Optional getEmail() { - return Optional.ofNullable(email); - } - - public void setAddress(Address address) { - this.address = address; - } - - public Optional
getAddress() { - return Optional.ofNullable(address); - } - - /** - * Sets {@code tags} to this object's {@code tags}. - * A defensive copy of {@code tags} is used internally. - */ - public void setTags(Set tags) { - this.tags = (tags != null) ? new HashSet<>(tags) : null; - } - - /** - * Returns an unmodifiable tag set, which throws {@code UnsupportedOperationException} - * if modification is attempted. - * Returns {@code Optional#empty()} if {@code tags} is null. - */ - public Optional> getTags() { - return (tags != null) ? Optional.of(Collections.unmodifiableSet(tags)) : Optional.empty(); - } - - @Override - public boolean equals(Object other) { - // short circuit if same object - if (other == this) { - return true; - } - - // instanceof handles nulls - if (!(other instanceof EditPersonDescriptor)) { - return false; - } - - // state check - EditPersonDescriptor e = (EditPersonDescriptor) other; - - return getName().equals(e.getName()) - && getPhone().equals(e.getPhone()) - && getEmail().equals(e.getEmail()) - && getAddress().equals(e.getAddress()) - && getTags().equals(e.getTags()); - } - } -} diff --git a/src/main/java/seedu/address/logic/commands/FindCommand.java b/src/main/java/seedu/address/logic/commands/FindCommand.java deleted file mode 100644 index d6b19b0a0de..00000000000 --- a/src/main/java/seedu/address/logic/commands/FindCommand.java +++ /dev/null @@ -1,42 +0,0 @@ -package seedu.address.logic.commands; - -import static java.util.Objects.requireNonNull; - -import seedu.address.commons.core.Messages; -import seedu.address.model.Model; -import seedu.address.model.person.NameContainsKeywordsPredicate; - -/** - * Finds and lists all persons in address book whose name contains any of the argument keywords. - * Keyword matching is case insensitive. - */ -public class FindCommand extends Command { - - public static final String COMMAND_WORD = "find"; - - public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all persons whose names contain any of " - + "the specified keywords (case-insensitive) and displays them as a list with index numbers.\n" - + "Parameters: KEYWORD [MORE_KEYWORDS]...\n" - + "Example: " + COMMAND_WORD + " alice bob charlie"; - - private final NameContainsKeywordsPredicate predicate; - - public FindCommand(NameContainsKeywordsPredicate predicate) { - this.predicate = predicate; - } - - @Override - public CommandResult execute(Model model) { - requireNonNull(model); - model.updateFilteredPersonList(predicate); - return new CommandResult( - String.format(Messages.MESSAGE_PERSONS_LISTED_OVERVIEW, model.getFilteredPersonList().size())); - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof FindCommand // instanceof handles nulls - && predicate.equals(((FindCommand) other).predicate)); // state check - } -} diff --git a/src/main/java/seedu/address/logic/commands/ListCommand.java b/src/main/java/seedu/address/logic/commands/ListCommand.java deleted file mode 100644 index 84be6ad2596..00000000000 --- a/src/main/java/seedu/address/logic/commands/ListCommand.java +++ /dev/null @@ -1,24 +0,0 @@ -package seedu.address.logic.commands; - -import static java.util.Objects.requireNonNull; -import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; - -import seedu.address.model.Model; - -/** - * Lists all persons in the address book to the user. - */ -public class ListCommand extends Command { - - public static final String COMMAND_WORD = "list"; - - public static final String MESSAGE_SUCCESS = "Listed all persons"; - - - @Override - public CommandResult execute(Model model) { - requireNonNull(model); - model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); - return new CommandResult(MESSAGE_SUCCESS); - } -} diff --git a/src/main/java/seedu/address/logic/commands/patient/AddPatientCommand.java b/src/main/java/seedu/address/logic/commands/patient/AddPatientCommand.java new file mode 100644 index 00000000000..01ec627a007 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/patient/AddPatientCommand.java @@ -0,0 +1,70 @@ +package seedu.address.logic.commands.patient; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.patient.PatientCliSyntax.PREFIX_AGE; +import static seedu.address.logic.parser.patient.PatientCliSyntax.PREFIX_COMMENTS; +import static seedu.address.logic.parser.patient.PatientCliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.patient.PatientCliSyntax.PREFIX_PERIOD_OF_STAY; +import static seedu.address.logic.parser.patient.PatientCliSyntax.PREFIX_PHONE; +import static seedu.address.logic.parser.patient.PatientCliSyntax.PREFIX_TEMP; + +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.patient.Patient; + +//@@author chiamyunqing +/** + * Adds a patient to the app. + */ +public class AddPatientCommand extends Command { + + public static final String COMMAND_WORD = "addpatient"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a patient to Covigent. " + + "\nParameters: " + + PREFIX_NAME + "NAME " + + PREFIX_TEMP + "TEMPERATURE " + + PREFIX_PERIOD_OF_STAY + "PERIOD OF STAY " + + PREFIX_PHONE + "PHONE " + + PREFIX_AGE + "AGE " + + "[" + PREFIX_COMMENTS + "COMMENTS]\n" + + "Example: " + COMMAND_WORD + " " + + PREFIX_NAME + "John Doe " + + PREFIX_TEMP + "37.0 " + + PREFIX_PERIOD_OF_STAY + "20200914-20200928 " + + PREFIX_PHONE + "98765432 " + + PREFIX_AGE + "22 " + + PREFIX_COMMENTS + "Vegan,asthmatic "; + + public static final String MESSAGE_SUCCESS = "New patient added: %1$s"; + public static final String MESSAGE_DUPLICATE_PATIENT = "This patient already exists in Covigent."; + + private final Patient toAdd; + + /** + * Creates an AddCommand to add the specified {@code Patient}. + */ + public AddPatientCommand(Patient patient) { + requireNonNull(patient); + toAdd = patient; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + if (model.hasPatient(toAdd)) { + throw new CommandException(MESSAGE_DUPLICATE_PATIENT); + } + model.addPatient(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 AddPatientCommand // instanceof handles nulls + && toAdd.equals(((AddPatientCommand) other).toAdd)); + } +} diff --git a/src/main/java/seedu/address/logic/commands/patient/DeletePatientCommand.java b/src/main/java/seedu/address/logic/commands/patient/DeletePatientCommand.java new file mode 100644 index 00000000000..b74da521434 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/patient/DeletePatientCommand.java @@ -0,0 +1,66 @@ +package seedu.address.logic.commands.patient; + +import static java.util.Objects.requireNonNull; + +import java.util.Optional; + +import seedu.address.commons.core.Messages; +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.patient.Name; +import seedu.address.model.patient.Patient; + +//@@author chiamyunqing +/** + * Deletes a patient identified by the patient's name from the application. + * If the patient resides in a room, the patient will be removed and the room will be set to unoccupied. + */ +public class DeletePatientCommand extends Command { + + public static final String COMMAND_WORD = "deletepatient"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Deletes the patient identified by the patient's name used in the displayed patient list.\n" + + "Parameters: NAME (must match exactly with the name of the patient to be deleted from the patient list)\n" + + "Example: " + COMMAND_WORD + " Mary Doe"; + + public static final String MESSAGE_DELETE_PATIENT_SUCCESS = "Deleted Patient: %1$s"; + + private final Name nameOfPatientToDelete; + + /** + * Creates a DeleteCommand to delete the patient with the name {@code String}. + * @param nameOfPatientToDelete name in the filtered patient list to be deleted + */ + public DeletePatientCommand(Name nameOfPatientToDelete) { + requireNonNull(nameOfPatientToDelete); + this.nameOfPatientToDelete = nameOfPatientToDelete; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + Optional patientToDelete = model.getPatientWithName(nameOfPatientToDelete); + + if (patientToDelete.isEmpty()) { + throw new CommandException(Messages.MESSAGE_INVALID_PATIENT_NAME); + } + + if (model.isPatientAssignedToRoom(nameOfPatientToDelete)) { + model.removePatientFromRoom(nameOfPatientToDelete); + } + + Patient deletedPatient = patientToDelete.get(); + model.deletePatient(deletedPatient); + return new CommandResult(String.format(MESSAGE_DELETE_PATIENT_SUCCESS, deletedPatient)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof DeletePatientCommand // instanceof handles nulls + && nameOfPatientToDelete.equals(((DeletePatientCommand) other).nameOfPatientToDelete)); // state check + } +} diff --git a/src/main/java/seedu/address/logic/commands/patient/EditPatientCommand.java b/src/main/java/seedu/address/logic/commands/patient/EditPatientCommand.java new file mode 100644 index 00000000000..ca5965f5c70 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/patient/EditPatientCommand.java @@ -0,0 +1,235 @@ +package seedu.address.logic.commands.patient; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; +import static seedu.address.logic.parser.patient.PatientCliSyntax.PREFIX_AGE; +import static seedu.address.logic.parser.patient.PatientCliSyntax.PREFIX_COMMENTS; +import static seedu.address.logic.parser.patient.PatientCliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.patient.PatientCliSyntax.PREFIX_PERIOD_OF_STAY; +import static seedu.address.logic.parser.patient.PatientCliSyntax.PREFIX_PHONE; +import static seedu.address.logic.parser.patient.PatientCliSyntax.PREFIX_TEMP; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PATIENTS; + +import java.util.Optional; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.util.CollectionUtil; +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.patient.Age; +import seedu.address.model.patient.Comment; +import seedu.address.model.patient.Name; +import seedu.address.model.patient.Patient; +import seedu.address.model.patient.PeriodOfStay; +import seedu.address.model.patient.Phone; +import seedu.address.model.patient.Temperature; + +//@@author LeeMingDe +/** + * Edits a patient in the app with the selected name. + * Input of name is case insensitive. + */ +public class EditPatientCommand extends Command { + + public static final String COMMAND_WORD = "editpatient"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits the details of the patient identified " + + "by the patient's name used in the displayed patient list. \n" + + "Existing values will be overwritten by the input values.\n" + + "Parameters: NAME (must match exactly with the name of the patient to be edited in the patient list) " + + "[" + PREFIX_NAME + "NAME] " + + "[" + PREFIX_PHONE + "PHONE] " + + "[" + PREFIX_TEMP + "TEMPERATURE] " + + "[" + PREFIX_AGE + "AGE] " + + "[" + PREFIX_PERIOD_OF_STAY + "PERIOD OF STAY] " + + "[" + PREFIX_COMMENTS + "COMMENT] " + + "\nExample: " + COMMAND_WORD + " john " + + PREFIX_PHONE + "91234567 " + + PREFIX_TEMP + "36.5"; + + public static final String MESSAGE_EDIT_PATIENT_SUCCESS = "Edited Patient: %1$s"; + public static final String MESSAGE_PATIENT_NOT_EDITED = "At least one field to edit must be provided."; + public static final String MESSAGE_DUPLICATE_PATIENT = "This patient already exists in Covigent."; + + private final Name patientToBeEdited; + private final EditPatientDescriptor editPatientDescriptor; + + /** + * Constructs an EditCommand to edit the patient with the name {@code String}. + * + * @param patientToBeEdited name in the filtered patient list to edit + * @param editPatientDescriptor details to edit the patient with + */ + public EditPatientCommand(Name patientToBeEdited, EditPatientDescriptor editPatientDescriptor) { + requireAllNonNull(patientToBeEdited, editPatientDescriptor); + + this.patientToBeEdited = patientToBeEdited; + this.editPatientDescriptor = new EditPatientDescriptor(editPatientDescriptor); + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + Optional optionalPatient = model.getPatientWithName(patientToBeEdited); + if (optionalPatient.isEmpty()) { + throw new CommandException(Messages.MESSAGE_INVALID_PATIENT_NAME); + } + + Patient patientToEdit = optionalPatient.get(); + Patient editedPatient = createEditedPatient(patientToEdit, editPatientDescriptor); + + if (!patientToEdit.isSamePatient(editedPatient) && model.hasPatient(editedPatient)) { + throw new CommandException(MESSAGE_DUPLICATE_PATIENT); + } + + model.setPatient(patientToEdit, editedPatient); + model.updateFilteredPatientList(PREDICATE_SHOW_ALL_PATIENTS); + model.updateRoomListWhenPatientsChanges(patientToEdit, editedPatient); + return new CommandResult(String.format(MESSAGE_EDIT_PATIENT_SUCCESS, editedPatient)); + } + + /** + * Creates and returns a {@code Patient} with the details of {@code patientToEdit} + * edited with {@code editPatientDescriptor}. + * + * @param patientToEdit Patient that is to be edited. + * @param editPatientDescriptor Details to edit the patient with. + * @return Patient that has been edited. + */ + private static Patient createEditedPatient(Patient patientToEdit, EditPatientDescriptor editPatientDescriptor) { + assert patientToEdit != null; + assert editPatientDescriptor != null; + + Name updatedName = editPatientDescriptor.getName().orElse(patientToEdit.getName()); + Phone updatedPhone = editPatientDescriptor.getPhone().orElse(patientToEdit.getPhone()); + Temperature updatedTemperature = editPatientDescriptor.getTemperature().orElse(patientToEdit.getTemperature()); + Age updatedAge = editPatientDescriptor.getAge().orElse(patientToEdit.getAge()); + PeriodOfStay updatedPeriodOfStay = editPatientDescriptor + .getPeriodOfStay() + .orElse(patientToEdit.getPeriodOfStay()); + Comment updatedComment = editPatientDescriptor.getComment().orElse(patientToEdit.getComment()); + return new Patient(updatedName, updatedTemperature, updatedPeriodOfStay, + updatedPhone, updatedAge, updatedComment); + } + + @Override + public boolean equals(Object other) { + if (other == this) { //short circuit if same object + return true; + } + + if (!(other instanceof EditPatientCommand)) { // instanceof handles nulls + return false; + } + + EditPatientCommand e = (EditPatientCommand) other; // state check + return patientToBeEdited.equals(e.patientToBeEdited) + && editPatientDescriptor.equals(e.editPatientDescriptor); + } + + /** + * Stores the details to edit the patient with. Each non-empty field value will replace the + * corresponding field value of the patient. + */ + public static class EditPatientDescriptor { + private Name name; + private Phone phone; + private Temperature temperature; + private Age age; + private PeriodOfStay periodOfStay; + private Comment comment; + + public EditPatientDescriptor() {} + + + /** + * Constructs a EditPatientDescriptor object with the following fields. + * + * @param toCopy EditPatientDescriptor to copy the fields from. + */ + public EditPatientDescriptor(EditPatientDescriptor toCopy) { + setName(toCopy.name); + setPhone(toCopy.phone); + setTemperature(toCopy.temperature); + setAge(toCopy.age); + setPeriodOfStay(toCopy.periodOfStay); + setComment(toCopy.comment); + } + + public boolean isAnyFieldEdited() { + return CollectionUtil.isAnyNonNull(name, phone, temperature, age, periodOfStay, comment); + } + + public void setName(Name name) { + this.name = name; + } + + public Optional getName() { + return Optional.ofNullable(name); + } + + public void setPhone(Phone phone) { + this.phone = phone; + } + + public Optional getPhone() { + return Optional.ofNullable(phone); + } + + public void setTemperature(Temperature temperature) { + this.temperature = temperature; + } + + public Optional getTemperature() { + return Optional.ofNullable(temperature); + } + + public void setAge(Age age) { + this.age = age; + } + + public Optional getAge() { + return Optional.ofNullable(age); + } + + public void setPeriodOfStay(PeriodOfStay periodOfStay) { + this.periodOfStay = periodOfStay; + } + + public Optional getPeriodOfStay() { + return Optional.ofNullable(periodOfStay); + } + + public void setComment(Comment comment) { + this.comment = comment; + } + + public Optional getComment() { + return Optional.ofNullable(comment); + } + + @Override + public boolean equals(Object other) { + if (other == this) { // short circuit if same object + return true; + } + + if (!(other instanceof EditPatientDescriptor)) { // instanceof handles nulls + return false; + } + + EditPatientDescriptor e = (EditPatientDescriptor) other; // state check + + return getName().equals(e.getName()) + && getPhone().equals(e.getPhone()) + && getTemperature().equals(e.getTemperature()) + && getAge().equals(e.getAge()) + && getPeriodOfStay().equals(e.getPeriodOfStay()) + && getComment().equals(e.getComment()); + } + } + //@@author LeeMingDe +} diff --git a/src/main/java/seedu/address/logic/commands/patient/ListPatientCommand.java b/src/main/java/seedu/address/logic/commands/patient/ListPatientCommand.java new file mode 100644 index 00000000000..92b523de44b --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/patient/ListPatientCommand.java @@ -0,0 +1,25 @@ +package seedu.address.logic.commands.patient; + +import static java.util.Objects.requireNonNull; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PATIENTS; + +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandResult; +import seedu.address.model.Model; + +/** + * Lists all patients in the address book to the user. + */ +public class ListPatientCommand extends Command { + + public static final String COMMAND_WORD = "listpatient"; + + public static final String MESSAGE_SUCCESS = "All patients are listed."; + + @Override + public CommandResult execute(Model model) { + requireNonNull(model); + model.updateFilteredPatientList(PREDICATE_SHOW_ALL_PATIENTS); + return new CommandResult(MESSAGE_SUCCESS); + } +} diff --git a/src/main/java/seedu/address/logic/commands/patient/SearchPatientCommand.java b/src/main/java/seedu/address/logic/commands/patient/SearchPatientCommand.java new file mode 100644 index 00000000000..3d73e687e0d --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/patient/SearchPatientCommand.java @@ -0,0 +1,239 @@ +package seedu.address.logic.commands.patient; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.patient.PatientCliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.patient.PatientCliSyntax.PREFIX_TEMP_RANGE; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; + +import seedu.address.commons.core.Messages; +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.patient.Name; +import seedu.address.model.patient.NameContainsKeywordsPredicate; +import seedu.address.model.patient.Patient; +import seedu.address.model.patient.TemperatureRange; +import seedu.address.model.patient.TemperatureRangePredicate; + + +/** + * Searches a patient or a list of patient according to a name or a range of temperature. + */ +public class SearchPatientCommand extends Command { + + public static final String COMMAND_WORD = "searchpatient"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Searches a patient or a list of patients with only one specific criteria. " + + "\nParameters: " + + PREFIX_NAME + "NAME or " + + PREFIX_TEMP_RANGE + "TEMPERATURE RANGE " + + "\nExample: " + COMMAND_WORD + " " + + PREFIX_TEMP_RANGE + "36.1-37.9 "; + + public static final String MESSAGE_NOT_FOUND = "At least one field to edit must be provided."; + public static final String MESSAGE_PATIENT_NOT_FOUND = "No patient matching the criteria has been found."; + public static final String MESSAGE_SEARCH_PATIENT_LIST_SUCCESS = "Listed patient(s) matching the criteria."; + + private final SearchPatientDescriptor searchPatientDescriptor; + private NameContainsKeywordsPredicate namePredicate; + private TemperatureRangePredicate temperaturePredicate; + + /** + * Constructs an SearchPatientCommand to edit the patient with the name {@code String}. + * + * @param searchPatientDescriptor Description of the searchPatient command. + */ + public SearchPatientCommand(SearchPatientDescriptor searchPatientDescriptor) { + requireNonNull(searchPatientDescriptor); + this.searchPatientDescriptor = searchPatientDescriptor; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + final SearchCriteria criteriaToSearch = this.confirmCriteria(searchPatientDescriptor); + List patientList = model.getFilteredPatientList(); + if (criteriaToSearch == SearchCriteria.CRITERIA_NOT_FOUND) { + throw new CommandException(MESSAGE_NOT_FOUND); + } else if (criteriaToSearch == SearchCriteria.CRITERIA_IS_NAME) { + updateNamePredicate(model, searchPatientDescriptor); + return findPatientWithName(searchPatientDescriptor, patientList); + } else if (criteriaToSearch == SearchCriteria.CRITERIA_IS_TEMPERATURE) { + updateTemperaturePredicate(model, searchPatientDescriptor); + return findPatientWithTemperature(searchPatientDescriptor, patientList); + } else if (criteriaToSearch == SearchCriteria.TOO_MANY_CRITERIA) { + return new CommandResult(Messages.MESSAGE_TOO_MANY_COMMANDS); + } + throw new CommandException(MESSAGE_NOT_FOUND); + } + + /** + * Update the predicate for filteredlist. + * @param searchPatientDescriptor the patient's name in the descriptor. + * @param model the model. + */ + public void updateNamePredicate(Model model, SearchPatientDescriptor searchPatientDescriptor) { + namePredicate = new NameContainsKeywordsPredicate(Arrays + .asList(searchPatientDescriptor.getStringName().split("\\s+"))); + model.updateFilteredPatientList(namePredicate); + } + + /** + * Update the predicate for filteredlist. + * @param searchPatientDescriptor the patient's name in the descriptor. + * @param model the model. + */ + public void updateTemperaturePredicate(Model model, SearchPatientDescriptor searchPatientDescriptor) { + double startingTemperature = searchPatientDescriptor.getTemperatureRange().getStartingTemperature(); + double endingTemperature = searchPatientDescriptor.getTemperatureRange().getEndingTemperature(); + temperaturePredicate = new TemperatureRangePredicate(startingTemperature, endingTemperature); + model.updateFilteredPatientList(temperaturePredicate); + } + + /** + * Return the patient's detail with specific name + * @param searchPatientDescriptor the patient's name in the descriptor. + * @param patientList the patient list stored. + * @return a CommandResult of the patients' details. + * @throws CommandException if patient is not found. + */ + public CommandResult findPatientWithName(SearchPatientDescriptor searchPatientDescriptor, + List patientList) throws CommandException { + String nameToSearch = searchPatientDescriptor.getName().toString().trim().toLowerCase(); + ArrayList patientNameList = new ArrayList<>(); + + for (Patient patient : patientList) { + String patientName = patient.getName().toString().trim().toLowerCase(); + if (patientName.contains(nameToSearch)) { + patientNameList.add(patient); + } + } + + if (patientNameList.isEmpty()) { + throw new CommandException(MESSAGE_PATIENT_NOT_FOUND); + } else { + return new CommandResult(MESSAGE_SEARCH_PATIENT_LIST_SUCCESS); + } + } + + /** + * Return the patient's detail with specific Temperature Range. + * @param searchPatientDescriptor the patient's name in the descriptor. + * @param patientList the patient list stored. + * @return a CommandResult of the patients' details. + * @throws CommandException if no patient matching the criteria. + */ + public CommandResult findPatientWithTemperature(SearchPatientDescriptor searchPatientDescriptor, + List patientList) throws CommandException { + double startingTemperature = searchPatientDescriptor.getTemperatureRange().getStartingTemperature(); + double endingTemperature = searchPatientDescriptor.getTemperatureRange().getEndingTemperature(); + ArrayList patientWithinTemperatureRange = new ArrayList<>(); + for (Patient patient : patientList) { + if (patient.getTemperature().getValue() >= startingTemperature + && patient.getTemperature().getValue() <= endingTemperature) { + patientWithinTemperatureRange.add(patient); + } + } + + if (patientWithinTemperatureRange.isEmpty()) { + throw new CommandException(MESSAGE_PATIENT_NOT_FOUND); + } else { + return new CommandResult(MESSAGE_SEARCH_PATIENT_LIST_SUCCESS); + } + } + + /** + * Confirm the criteria to look for. + * @param searchPatientDescriptor Details of the searchPatient Command. + * @return criteria to look for. + */ + public SearchCriteria confirmCriteria(SearchPatientDescriptor searchPatientDescriptor) { + if (searchPatientDescriptor.getOptionalName().isEmpty() + && searchPatientDescriptor.getOptionalTemperatureRange().isEmpty()) { + return SearchCriteria.CRITERIA_NOT_FOUND; + } else if (searchPatientDescriptor.getOptionalName().isPresent() + && searchPatientDescriptor.getOptionalTemperatureRange().isEmpty()) { + return SearchCriteria.CRITERIA_IS_NAME; + } else if (searchPatientDescriptor.getOptionalName().isEmpty() + && searchPatientDescriptor.getOptionalTemperatureRange().isPresent()) { + return SearchCriteria.CRITERIA_IS_TEMPERATURE; + } else { + return SearchCriteria.TOO_MANY_CRITERIA; + } + + } + + private enum SearchCriteria { + CRITERIA_IS_NAME, + CRITERIA_IS_TEMPERATURE, + TOO_MANY_CRITERIA, + CRITERIA_NOT_FOUND + } + + /** + * Stores the details to search the patient . + * The class is used with @ConfirmArea method to confirm the area to look for. + */ + public static class SearchPatientDescriptor { + private Name name; + private TemperatureRange temperatureRange; + private String stringName; + public SearchPatientDescriptor() { + } + public void setStringName(String stringName) { + this.stringName = stringName; + } + + public String getStringName() { + return stringName; + } + + public void setName(Name name) { + this.name = name; + } + + public Optional getOptionalName() { + return Optional.ofNullable(name); + } + + public Name getName() { + return name; + } + + public void setTemperatureRange(TemperatureRange temperatureRange) { + this.temperatureRange = temperatureRange; + } + + public Optional getOptionalTemperatureRange() { + return Optional.ofNullable(temperatureRange); + } + + public TemperatureRange getTemperatureRange() { + return temperatureRange; + } + + @Override + public boolean equals(Object other) { + if (other == this) { // short circuit if same object + return true; + } + + if (!(other instanceof SearchPatientDescriptor)) { // instanceof handles nulls + return false; + } + + SearchPatientDescriptor e = (SearchPatientDescriptor) other; // state check + + return getName().equals(e.getName()) + && getTemperatureRange().equals(e.getTemperatureRange()); + } + } + +} diff --git a/src/main/java/seedu/address/logic/commands/room/AllocateRoomCommand.java b/src/main/java/seedu/address/logic/commands/room/AllocateRoomCommand.java new file mode 100644 index 00000000000..29846456ea4 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/room/AllocateRoomCommand.java @@ -0,0 +1,214 @@ +package seedu.address.logic.commands.room; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_PATIENT_NAME; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_ROOM_NUMBER; +import static seedu.address.commons.core.Messages.MESSAGE_PATIENT_ALREADY_ASSIGNED; +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; +import static seedu.address.logic.parser.patient.PatientCliSyntax.PREFIX_NAME; + +import java.util.Optional; + +import seedu.address.commons.util.CollectionUtil; +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.patient.Name; +import seedu.address.model.patient.Patient; +import seedu.address.model.room.Room; +import seedu.address.model.room.RoomTasks; + +//@@author LeeMingDe +/** + * Edits the details of a room identified by the room number in the app. + * Allows setting/removing/changing patient in the room and changing the room number of the room. + */ +public class AllocateRoomCommand extends Command { + + public static final String COMMAND_WORD = "allocateroom"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Allocate a patient to a room. \n" + + "Existing values will be overwritten by the input values. \n" + + "To make an occupied room empty, the parameter for patient name should be set to '-'.\n" + + "Parameters: ROOM NUMBER " + + "[" + PREFIX_NAME + "PATIENT NAME]\n" + + "Example: " + COMMAND_WORD + " 23 " + + PREFIX_NAME + "Mary Doe"; + + public static final String MESSAGE_ALLOCATE_ROOM_SUCCESS = "Allocated Room: %1$s"; + public static final String MESSAGE_NO_PATIENT_TO_REMOVE = "There is no patient in this room to remove."; + + private final Integer roomNumberToAllocate; + private final AllocateRoomDescriptor allocateRoomDescriptor; + private final boolean toRemove; + + /** + * Constructs an AllocateRoomCommand to edit the room with the room number {@code Integer}. + * + * @param roomNumberToAllocate Room number to edit. + * @param allocateRoomDescriptor Details to edit the room with. + * @param toRemove Room if it is true. + */ + public AllocateRoomCommand(Integer roomNumberToAllocate, AllocateRoomDescriptor allocateRoomDescriptor, + boolean toRemove) { + requireAllNonNull(roomNumberToAllocate, allocateRoomDescriptor, toRemove); + this.roomNumberToAllocate = roomNumberToAllocate; + this.allocateRoomDescriptor = new AllocateRoomDescriptor(allocateRoomDescriptor); + this.toRemove = toRemove; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + Optional roomOptional = model.getRoomWithRoomNumber(roomNumberToAllocate); + + if (roomOptional.isEmpty()) { + throw new CommandException(MESSAGE_INVALID_ROOM_NUMBER); + } + + Room roomToAllocate = roomOptional.get(); + Room roomWithAllocatedPatient = allocatePatientToRoom(model, roomToAllocate, allocateRoomDescriptor); + + if (!roomToAllocate.isSameRoom(roomWithAllocatedPatient) && model.hasRoom(roomWithAllocatedPatient)) { + throw new CommandException(MESSAGE_PATIENT_ALREADY_ASSIGNED); + } + + model.setSingleRoom(roomToAllocate, roomWithAllocatedPatient); + return new CommandResult(String.format(MESSAGE_ALLOCATE_ROOM_SUCCESS, roomWithAllocatedPatient)); + } + + /** + * Creates and returns a {@code Room} with the details of {@code roomToAllocate} + * edited with {@code allocateRoomDescriptor}. + * + * @param model Current model. + * @param roomToAllocate Room that is to be edited. + * @param allocateRoomDescriptor Details to edit the room with. + * @return Room that has been edited. + */ + private Room allocatePatientToRoom(Model model, Room roomToAllocate, + AllocateRoomDescriptor allocateRoomDescriptor) throws CommandException { + assert (roomToAllocate != null); + assert (allocateRoomDescriptor != null); + + int roomNumber = roomToAllocate.getRoomNumber(); + RoomTasks roomTaskList = new RoomTasks(roomToAllocate.getReadOnlyTasks()); + if (toRemove) { + if (roomToAllocate.getPatient().isEmpty()) { + throw new CommandException(MESSAGE_NO_PATIENT_TO_REMOVE); + } + return new Room(roomNumber, false, Optional.empty(), roomTaskList); + } + Name patientName = allocateRoomDescriptor.getPatientName().get(); //definitely has name + if (model.isPatientAssignedToRoom(patientName)) { + throw new CommandException(MESSAGE_PATIENT_ALREADY_ASSIGNED); + } + Optional updatedPatient = model.getPatientWithName(patientName); + if (updatedPatient.isEmpty()) { + throw new CommandException(MESSAGE_INVALID_PATIENT_NAME); + } else { + Room updatedRoom = new Room(roomNumber, updatedPatient, roomTaskList); + + return updatedRoom; + } + } + + @Override + public boolean equals(Object other) { + if (other == this) { //short circuit if same object + return true; + } + + if (!(other instanceof AllocateRoomCommand)) { // instanceof handles nulls + return false; + } + + AllocateRoomCommand e = (AllocateRoomCommand) other; // state check + return roomNumberToAllocate.equals(e.roomNumberToAllocate) + && allocateRoomDescriptor.equals(e.allocateRoomDescriptor); + } + + /** + * Stores the details to edit the room with. Each non-empty field value will replace the + * corresponding field value of the room. + */ + public static class AllocateRoomDescriptor { + private Integer roomNumber; + private Boolean isOccupied; + private Name patientName; + + public AllocateRoomDescriptor() {} + + /** + * Constructs an AllocateRoomDescriptor object with the following fields. + * + * @param toCopy AllocateRoomDescriptor to copy the fields from. + */ + public AllocateRoomDescriptor(AllocateRoomDescriptor toCopy) { + setOccupied(toCopy.isOccupied); + setPatientName(toCopy.patientName); + } + + public boolean isAnyFieldEdited() { + return CollectionUtil.isAnyNonNull(patientName, isOccupied); + } + + public void setRoomNumber(Integer roomNumber) { + this.roomNumber = roomNumber; + } + + public Optional getRoomNumber() { + return Optional.ofNullable(roomNumber); + } + + public void setOccupied(Boolean isOccupied) { + this.isOccupied = isOccupied; + } + + public Optional getIsOccupied() { + return Optional.ofNullable(isOccupied); + } + + public void setPatientName(Name patientName) { + this.patientName = patientName; + } + + public Optional getPatientName() { + return Optional.ofNullable(patientName); + } + + @Override + public boolean equals(Object other) { + if (other == this) { // short circuit if same object + return true; + } + + if (!(other instanceof AllocateRoomDescriptor)) { // instanceof handles nulls + return false; + } + + AllocateRoomDescriptor e = (AllocateRoomDescriptor) other; // state check + return getIsOccupied().equals(e.getIsOccupied()) + && getPatientName().equals(e.getPatientName()); + } + + @Override + public String toString() { + return "AllocateRoomDescriptor{" + + ", isOccupied=" + isOccupied + + ", patientName=" + patientName + + '}'; + } + } + + @Override + public String toString() { + return "AllocateRoomCommand{" + + "roomNumberToEdit=" + roomNumberToAllocate + + ", allocateRoomDescriptor=" + allocateRoomDescriptor + + '}'; + } + //@@author LeeMingDe +} diff --git a/src/main/java/seedu/address/logic/commands/room/FindEmptyRoomCommand.java b/src/main/java/seedu/address/logic/commands/room/FindEmptyRoomCommand.java new file mode 100644 index 00000000000..6d45abd1c7f --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/room/FindEmptyRoomCommand.java @@ -0,0 +1,44 @@ +package seedu.address.logic.commands.room; + +import java.util.PriorityQueue; +import java.util.function.Predicate; + +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.room.Room; + +/** + * Finds the next empty room with the lowest room number + */ +//@@author itssodium +public class FindEmptyRoomCommand extends Command { + public static final String COMMAND_WORD = "findemptyroom"; + public static final String MESSAGE_NO_EMPTY_ROOM = "There are unfortunately no more empty rooms!"; + public static final String NUMBER_OF_ROOMS_UNDEFINED = "Please define number of rooms"; + public static final String MESSAGE_SUCCESS = "Room Number %s is empty"; + + public FindEmptyRoomCommand() {} + + @Override + public CommandResult execute(Model model) throws CommandException { + PriorityQueue rooms = model.getRooms(); + if (model.getNumOfRooms() == 0) { + throw new CommandException(NUMBER_OF_ROOMS_UNDEFINED); + } + Room room = rooms.peek(); + assert room != null; + if (room.isOccupied()) { + throw new CommandException(MESSAGE_NO_EMPTY_ROOM); + } + Predicate predicate = givePredicate(room); + model.updateFilteredRoomList(predicate); + return new CommandResult(String.format(MESSAGE_SUCCESS, room.getRoomNumber())); + } + + private Predicate givePredicate(Room room) { + Predicate roomPredicate = room1 -> room1.getRoomNumber() == room.getRoomNumber(); + return roomPredicate; + } +} diff --git a/src/main/java/seedu/address/logic/commands/room/InitRoomCommand.java b/src/main/java/seedu/address/logic/commands/room/InitRoomCommand.java new file mode 100644 index 00000000000..e048278e55c --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/room/InitRoomCommand.java @@ -0,0 +1,76 @@ +package seedu.address.logic.commands.room; + +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_ROOMS; + +import java.util.Objects; + +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; + +/** + * Adds the number of hotel rooms in a hotel + */ +//@@author itssodium +public class InitRoomCommand extends Command { + public static final String COMMAND_WORD = "initroom"; + public static final String MESSAGE_ZERO_CANNOT_BE_AN_INPUT = "Please input a positive value"; + public static final String MESSAGE_NEGATIVE_VALUES_CANNOT_BE_INPUT = "Please check your value! " + + "You have input a negative value!"; + public static final String MESSAGE_LARGE_NUMBER_OF_ROOMS_INPUT = "Please reduce the number of rooms input." + + "maximum number of rooms are 5000"; + public static final String MESSAGE_INSUFFICIENT_ROOMS = "There would be insufficient rooms for existing number of " + + "patients of %d"; + public static final String MESSAGE_SUCCESS = "Initialize the number of rooms to %d rooms in the application"; + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Initializes the number of rooms in the " + + "quarantine facility to the app, if there was data given previously, they would be stored.\n" + + "Parameters: NUMBER_OF_ROOMS\n" + + "Example: " + COMMAND_WORD + " 123"; + + private static final int ZERO = 0; + private static final int MAXIMUM_NUMBER_OF_ROOMS = 500; + + private int numOfRooms; + + /** + * Creates an AddCommand to add the number of rooms available in a hotel + */ + public InitRoomCommand(int numOfRooms) { + this.numOfRooms = numOfRooms; + } + @Override + public CommandResult execute(Model model) throws CommandException { + model.setInitNumOfRooms(numOfRooms); + model.updateFilteredRoomList(PREDICATE_SHOW_ALL_ROOMS); + if (numOfRooms == ZERO) { + throw new CommandException(MESSAGE_ZERO_CANNOT_BE_AN_INPUT); + } else if (numOfRooms < ZERO) { + throw new CommandException(MESSAGE_NEGATIVE_VALUES_CANNOT_BE_INPUT); + } else if (numOfRooms > MAXIMUM_NUMBER_OF_ROOMS) { + throw new CommandException(MESSAGE_LARGE_NUMBER_OF_ROOMS_INPUT); + } else if (model.getNumOfRooms() > numOfRooms && !model.hasSpaceForRooms()) { + throw new CommandException(String.format(MESSAGE_INSUFFICIENT_ROOMS, model.getNumOfExcessOccupiedRooms())); + } + model.initRooms(numOfRooms); + model.updateFilteredRoomList(PREDICATE_SHOW_ALL_ROOMS); + return new CommandResult(String.format(MESSAGE_SUCCESS, numOfRooms)); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + InitRoomCommand that = (InitRoomCommand) o; + return numOfRooms == that.numOfRooms; + } + + @Override + public int hashCode() { + return Objects.hash(numOfRooms); + } +} diff --git a/src/main/java/seedu/address/logic/commands/room/ListRoomCommand.java b/src/main/java/seedu/address/logic/commands/room/ListRoomCommand.java new file mode 100644 index 00000000000..7e1465043b6 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/room/ListRoomCommand.java @@ -0,0 +1,23 @@ +package seedu.address.logic.commands.room; + +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_ROOMS; + +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; + +public class ListRoomCommand extends Command { + public static final String COMMAND_WORD = "listroom"; + public static final String MESSAGE_SUCCESS = "All rooms are listed."; + public static final String NUMBER_OF_ROOMS_UNDEFINED = + "There is no room in the app yet. Please define the number of rooms using initRooms command."; + @Override + public CommandResult execute(Model model) throws CommandException { + if (model.getRooms().size() == 0) { + throw new CommandException(NUMBER_OF_ROOMS_UNDEFINED); + } + model.updateFilteredRoomList(PREDICATE_SHOW_ALL_ROOMS); + return new CommandResult(MESSAGE_SUCCESS); + } +} diff --git a/src/main/java/seedu/address/logic/commands/room/SearchRoomCommand.java b/src/main/java/seedu/address/logic/commands/room/SearchRoomCommand.java new file mode 100644 index 00000000000..73b1d14f36e --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/room/SearchRoomCommand.java @@ -0,0 +1,143 @@ +package seedu.address.logic.commands.room; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_PATIENT_NAME; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_ROOM_NUMBER; +import static seedu.address.commons.core.Messages.MESSAGE_PATIENT_NO_ROOM; +import static seedu.address.logic.parser.patient.PatientCliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.room.RoomCliSyntax.PREFIX_ROOM_NUMBER; + +import java.util.Optional; + +import seedu.address.commons.core.index.Index; +import seedu.address.commons.util.CollectionUtil; +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.patient.Name; + +//@@author chiamyunqing +/** + * Searches a room by the given room number or by the patient name who resides in the room. + */ +public class SearchRoomCommand extends Command { + + public static final String COMMAND_WORD = "searchroom"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Searches the room with the given room number or with the given individual.\n" + + "Only one of the parameter can be provided.\n" + + "Parameters: " + + PREFIX_ROOM_NUMBER + "ROOM NUMBER or " + + PREFIX_NAME + "NAME \n" + + "Example: " + COMMAND_WORD + " " + + PREFIX_ROOM_NUMBER + "130 or " + + PREFIX_NAME + "John Doe"; + + public static final String MESSAGE_SUCCESS = "Room has been found and listed."; + private final SearchRoomDescriptor descriptor; + + /** + * Creates a SearchRoomCommand to look for the specified room based on the inputs + * in searchRoomDescriptor. + * + * @param descriptor Details to search the room with. + */ + public SearchRoomCommand(SearchRoomDescriptor descriptor) { + requireNonNull(descriptor); + this.descriptor = descriptor; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + assert(descriptor.hasParameter()); + + if (descriptor.getRoomNumber().isPresent()) { + Integer roomNumber = descriptor.roomNumber; + Index index = model.checkIfRoomPresent(roomNumber); + if (index.getZeroBased() == 0) { + throw new CommandException(MESSAGE_INVALID_ROOM_NUMBER); + } + model.updateFilteredRoomList(room -> room.getRoomNumber() == roomNumber); + return new CommandResult(MESSAGE_SUCCESS); + + } + + Name patientName = descriptor.patientName; + if (model.getPatientWithName(patientName).isEmpty()) { + throw new CommandException(MESSAGE_INVALID_PATIENT_NAME); + } else if (!model.isPatientAssignedToRoom(patientName)) { + throw new CommandException(MESSAGE_PATIENT_NO_ROOM); + } + model.updateFilteredRoomList(room -> room.isOccupied() + && room.getPatient().get().getName().equals(patientName)); + return new CommandResult(MESSAGE_SUCCESS); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof SearchRoomCommand // instanceof handles nulls + && descriptor.equals(((SearchRoomCommand) other).descriptor)); + } + + /** + * Stores the details to search the room. + * Each non-empty field value will replace the corresponding field value of the room. + */ + public static class SearchRoomDescriptor { + private Integer roomNumber; + private Name patientName; + + public SearchRoomDescriptor() { + } + /** + * Constructs a SearchRoomDescriptor object with the following fields. + * + * @param toCopy SearchRoomDescriptor to copy the fields from. + */ + public SearchRoomDescriptor(SearchRoomDescriptor toCopy) { + setRoomNumber(toCopy.roomNumber); + setPatientName(toCopy.patientName); + } + + public boolean hasParameter() { + return CollectionUtil.isAnyNonNull(roomNumber, patientName); + } + + public void setRoomNumber(Integer roomNumber) { + this.roomNumber = roomNumber; + } + + public Optional getRoomNumber() { + return Optional.ofNullable(roomNumber); + } + + public void setPatientName(Name patientName) { + this.patientName = patientName; + } + + public Optional getPatientName() { + return Optional.ofNullable(patientName); + } + + @Override + public boolean equals(Object other) { + if (other == this) { // short circuit if same object + return true; + } + + if (!(other instanceof SearchRoomDescriptor)) { // instanceof handles nulls + return false; + } + + SearchRoomDescriptor e = (SearchRoomDescriptor) other; // state check + return getRoomNumber().equals(e.getRoomNumber()) + && getPatientName().equals(e.getPatientName()); + } + } +} + + diff --git a/src/main/java/seedu/address/logic/commands/task/AddTaskCommand.java b/src/main/java/seedu/address/logic/commands/task/AddTaskCommand.java new file mode 100644 index 00000000000..cc17b83bb63 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/task/AddTaskCommand.java @@ -0,0 +1,74 @@ +package seedu.address.logic.commands.task; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.room.RoomCliSyntax.PREFIX_ROOM_NUMBER; +import static seedu.address.logic.parser.task.TaskCliSyntax.PREFIX_DESCRIPTION; +import static seedu.address.logic.parser.task.TaskCliSyntax.PREFIX_DUE_DATE; + +import java.util.Optional; + +import seedu.address.commons.core.Messages; +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.room.Room; +import seedu.address.model.task.Task; + +//@@author w-yeehong +/** + * Adds a {@code Task} to a {@code Room}. + */ +public class AddTaskCommand extends Command { + + public static final String COMMAND_WORD = "addtask"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a task to Covigent. " + + "\nParameters: " + + PREFIX_DESCRIPTION + "DESCRIPTION " + + PREFIX_ROOM_NUMBER + "ROOM NUMBER " + + "[" + PREFIX_DUE_DATE + "DUE DATE]\n" + + "Example: " + COMMAND_WORD + " " + + PREFIX_DESCRIPTION + "Remind John to return his bedsheets " + + PREFIX_ROOM_NUMBER + "3 " + + PREFIX_DUE_DATE + "20200928 2359"; + + public static final String MESSAGE_ADD_TASK_SUCCESS = "New Task added to Room %1$d. \nDescription: %2$s"; + + private final Task taskToAdd; + private final int roomNumber; + + /** + * Creates an AddTaskCommand to add the specified {@code Task} to a {@code Room} + * with the {@code roomNumber}. + */ + public AddTaskCommand(Task task, int roomNumber) { + requireNonNull(task); + assert roomNumber > 0 : "Room number should be greater than 0."; + taskToAdd = task; + this.roomNumber = roomNumber; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + Optional optionalRoom = model.getRoomWithRoomNumber(roomNumber); + assert optionalRoom != null : "The return value from Model#getRoomWithRoomNumber(...) should never be null."; + + Room room = optionalRoom.orElseThrow(() -> + new CommandException(Messages.MESSAGE_INVALID_ROOM_NUMBER)); + + model.addTaskToRoom(taskToAdd, room); + return new CommandResult(String.format(MESSAGE_ADD_TASK_SUCCESS, + roomNumber, taskToAdd)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof AddTaskCommand // instanceof handles nulls + && taskToAdd.equals(((AddTaskCommand) other).taskToAdd) + && roomNumber == (((AddTaskCommand) other).roomNumber)); + } +} diff --git a/src/main/java/seedu/address/logic/commands/task/DeleteTaskCommand.java b/src/main/java/seedu/address/logic/commands/task/DeleteTaskCommand.java new file mode 100644 index 00000000000..2e32a926d23 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/task/DeleteTaskCommand.java @@ -0,0 +1,82 @@ +package seedu.address.logic.commands.task; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.room.RoomCliSyntax.PREFIX_ROOM_NUMBER; +import static seedu.address.logic.parser.task.TaskCliSyntax.PREFIX_TASK_NUMBER; + +import java.util.Optional; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.room.Room; +import seedu.address.model.task.Task; + +//@@author w-yeehong +/** + * Deletes a {@code Task} from a {@code Room}. + * The {@code Room} is identified by its room number. + * The {@code Task} is identified by its one-based index in the {@code TaskList} + * of {@code Room}. + */ +public class DeleteTaskCommand extends Command { + + public static final String COMMAND_WORD = "deletetask"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Deletes a task from Covigent. " + + "\nParameters: " + + PREFIX_ROOM_NUMBER + "ROOM NUMBER " + + PREFIX_TASK_NUMBER + "TASK NUMBER " + + "\nExample: " + COMMAND_WORD + " " + + PREFIX_ROOM_NUMBER + "1 " + + PREFIX_TASK_NUMBER + "3"; + + public static final String MESSAGE_DELETE_TASK_SUCCESS = "Task %1$d deleted from Room %2$d. \nDescription: %3$s"; + + private final int roomNumber; + private final Index taskIndex; + + /** + * Creates a DeleteTaskCommand to remove the {@code Task} with {@code taskIndex} from + * the {@code Room} with the {@code roomNumber}. + */ + public DeleteTaskCommand(int roomNumber, Index taskIndex) { + requireNonNull(taskIndex); + assert roomNumber > 0 : "Room number should be greater than 0."; + this.roomNumber = roomNumber; + this.taskIndex = taskIndex; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + // Get the room from which the user wants to delete the task + Optional optionalRoom = model.getRoomWithRoomNumber(roomNumber); + assert optionalRoom != null : "The return value from Model#getRoomWithRoomNumber(...) should never be null."; + Room room = optionalRoom.orElseThrow(() -> + new CommandException(Messages.MESSAGE_INVALID_ROOM_NUMBER)); + + // Get the task which the user wants to delete + Optional optionalTask = model.getTaskFromRoomWithTaskIndex(taskIndex, room); + assert optionalTask != null : "The return value from Model#getTaskFromRoomWithTaskIndex(...)" + + " should not be null."; + Task taskToDelete = optionalTask.orElseThrow(() -> + new CommandException(Messages.MESSAGE_INVALID_TASK_INDEX)); + + model.deleteTaskFromRoom(taskToDelete, room); + return new CommandResult(String.format(MESSAGE_DELETE_TASK_SUCCESS, + taskIndex.getOneBased(), roomNumber, taskToDelete)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof DeleteTaskCommand // instanceof handles nulls + && roomNumber == (((DeleteTaskCommand) other).roomNumber) + && taskIndex.equals((((DeleteTaskCommand) other).taskIndex))); + } +} diff --git a/src/main/java/seedu/address/logic/commands/task/EditTaskCommand.java b/src/main/java/seedu/address/logic/commands/task/EditTaskCommand.java new file mode 100644 index 00000000000..5b78faff8c1 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/task/EditTaskCommand.java @@ -0,0 +1,168 @@ +package seedu.address.logic.commands.task; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; +import static seedu.address.logic.parser.room.RoomCliSyntax.PREFIX_ROOM_NUMBER; +import static seedu.address.logic.parser.task.TaskCliSyntax.PREFIX_DESCRIPTION; +import static seedu.address.logic.parser.task.TaskCliSyntax.PREFIX_DUE_DATE; +import static seedu.address.logic.parser.task.TaskCliSyntax.PREFIX_TASK_NUMBER; + +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.Command; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.room.Room; +import seedu.address.model.task.DateTimeDue; +import seedu.address.model.task.Description; +import seedu.address.model.task.Task; + +//@@author w-yeehong +/** + * Edits and replaces a {@code Task} in a {code Room}. + * The {@code Room} is identified by its index in the {@code RoomList} + * of {@code Model}. + * The {@code Task} is identified by its index in the {@code TaskList} + * of {@code Room}. + */ +public class EditTaskCommand extends Command { + + public static final String COMMAND_WORD = "edittask"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits a task in Covigent.\n" + + "Existing values will be overwritten by the input values.\n" + + "Parameters: " + + PREFIX_ROOM_NUMBER + "ROOM NUMBER " + + PREFIX_TASK_NUMBER + "TASK NUMBER " + + "[" + PREFIX_DESCRIPTION + "DESCRIPTION] " + + "[" + PREFIX_DUE_DATE + "DUE DATE]" + + "\nExample: " + COMMAND_WORD + " " + + PREFIX_ROOM_NUMBER + "1 " + + PREFIX_TASK_NUMBER + "3 " + + PREFIX_DUE_DATE + "20200930 1700"; + + public static final String MESSAGE_EDIT_TASK_SUCCESS = "Task %1$d edited from Room %2$d. \nDescription: %3$s"; + + private final int roomNumber; + private final Index taskIndex; + private final EditTaskDescriptor editTaskDescriptor; + + /** + * Creates an EditTaskCommand to replace a {@code Task} in a {@code Room} with another + * {@code Task} described by {@code editTaskDescriptor}. + */ + public EditTaskCommand(int roomNumber, Index taskIndex, EditTaskDescriptor editTaskDescriptor) { + requireAllNonNull(taskIndex, editTaskDescriptor); + assert roomNumber > 0 : "Room number should be greater than 0."; + this.roomNumber = roomNumber; + this.taskIndex = taskIndex; + this.editTaskDescriptor = new EditTaskDescriptor(editTaskDescriptor); + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + // Get the room from which the user wants to delete the task + Optional optionalRoom = model.getRoomWithRoomNumber(roomNumber); + assert optionalRoom != null : "The return value from Model#getRoomWithRoomNumber(...) should never be null."; + Room room = optionalRoom.orElseThrow(() -> + new CommandException(Messages.MESSAGE_INVALID_ROOM_NUMBER)); + + // Get the task which the user wants to edit + Optional optionalTask = model.getTaskFromRoomWithTaskIndex(taskIndex, room); + assert optionalTask != null : "The return value from Model#getTaskFromRoomWithTaskIndex(...)" + + " should not be null."; + Task taskToEdit = optionalTask.orElseThrow(() -> + new CommandException(Messages.MESSAGE_INVALID_TASK_INDEX)); + + Task editedTask = createEditedTask(taskToEdit, editTaskDescriptor); + if (taskToEdit.equals(editedTask)) { + throw new CommandException(Messages.MESSAGE_TASK_NOT_EDITED); + } + + model.setTaskToRoom(taskToEdit, editedTask, room); + return new CommandResult(String.format(MESSAGE_EDIT_TASK_SUCCESS, + taskIndex.getOneBased(), roomNumber, editedTask)); + } + + /** + * Creates and returns a {@code Task} with the details of {@code taskToEdit} + * edited with {@code editTaskDescriptor}. + * + * @param taskToEdit Task that is to be edited. + * @param editTaskDescriptor Details to edit the task with. + * @return Task that has been edited. + */ + private static Task createEditedTask(Task taskToEdit, EditTaskDescriptor editTaskDescriptor) { + requireAllNonNull(taskToEdit, editTaskDescriptor); + + Description updatedDescription = editTaskDescriptor.getDescription().orElse(taskToEdit.getDescription()); + DateTimeDue updatedDueAt = editTaskDescriptor.getDateTimeDue().orElse(taskToEdit.getDueAt()); + + return new Task(updatedDescription, updatedDueAt); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof EditTaskCommand // instanceof handles nulls + && roomNumber == (((EditTaskCommand) other).roomNumber) + && taskIndex.equals(((EditTaskCommand) other).taskIndex) + && editTaskDescriptor.equals(((EditTaskCommand) other).editTaskDescriptor)); + } + + /** + * Stores the details used to edit the task. Each non-empty field value will replace the + * corresponding field value of the task. + */ + public static class EditTaskDescriptor { + private Description description; + private DateTimeDue dueAt; + + // Prevents instantiation + public EditTaskDescriptor() {} + + /** + * Constructs a EditTaskDescriptor object with the following fields. + * + * @param toCopy EditTaskDescriptor to copy the fields from. + */ + public EditTaskDescriptor(EditTaskDescriptor toCopy) { + setDescription(toCopy.description); + setDateTimeDue(toCopy.dueAt); + } + + public boolean isAnyFieldEdited() { + return CollectionUtil.isAnyNonNull(description, dueAt); + } + + public void setDescription(Description description) { + this.description = description; + } + + public Optional getDescription() { + return Optional.ofNullable(description); + } + + public void setDateTimeDue(DateTimeDue dueAt) { + this.dueAt = dueAt; + } + + public Optional getDateTimeDue() { + return Optional.ofNullable(dueAt); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof EditTaskDescriptor // instanceof handles nulls + && getDescription().equals(((EditTaskDescriptor) other).getDescription()) + && getDateTimeDue().equals(((EditTaskDescriptor) other).getDateTimeDue())); + } + } +} diff --git a/src/main/java/seedu/address/logic/commands/task/ListTaskCommand.java b/src/main/java/seedu/address/logic/commands/task/ListTaskCommand.java new file mode 100644 index 00000000000..6f3433a498d --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/task/ListTaskCommand.java @@ -0,0 +1,25 @@ +package seedu.address.logic.commands.task; + +import static java.util.Objects.requireNonNull; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_TASKS; + +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandResult; +import seedu.address.model.Model; + +/** + * Lists all Tasks in the Covigent to the user. + */ +public class ListTaskCommand extends Command { + + public static final String COMMAND_WORD = "listtask"; + + public static final String MESSAGE_SUCCESS = "All tasks are listed."; + + @Override + public CommandResult execute(Model model) { + requireNonNull(model); + model.updateTasksInFilteredRoomTaskRecords(PREDICATE_SHOW_ALL_TASKS); + return new CommandResult(MESSAGE_SUCCESS); + } +} diff --git a/src/main/java/seedu/address/logic/commands/task/SearchTaskCommand.java b/src/main/java/seedu/address/logic/commands/task/SearchTaskCommand.java new file mode 100644 index 00000000000..fc7549c76c7 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/task/SearchTaskCommand.java @@ -0,0 +1,81 @@ +package seedu.address.logic.commands.task; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; +import static seedu.address.logic.parser.task.TaskCliSyntax.PREFIX_DUE_DATE; + +import java.util.ArrayList; +import java.util.List; + +import javafx.collections.ObservableList; +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.room.Room; +import seedu.address.model.task.DateTimeDue; +import seedu.address.model.task.DueDatePredicate; +import seedu.address.model.task.Task; + +/** + * Searches a {@code Task} before a {@code DateTimeDue}. + */ +public class SearchTaskCommand extends Command { + + public static final String COMMAND_WORD = "searchtask"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Searches tasks in Covigent. " + + "\nParameters: " + + "[" + PREFIX_DUE_DATE + "DUE DATE]\n" + + "Example: " + COMMAND_WORD + " " + + PREFIX_DUE_DATE + "20200928 2359"; + + public static final String MESSAGE_SEARCH_TASK_SUCCESS = "Tasks before the due date found."; + public static final String MESSAGE_TASK_NOT_FOUND = "There is no task that matches your criteria."; + + + private final DateTimeDue duedate; + private ObservableList tasks; + private DueDatePredicate datePredicate; + + /** + * Creates an SearchTaskCommand to Search the specified {@code Task} with {@code DateTimeDue} + */ + public SearchTaskCommand(DateTimeDue duedate) { + requireAllNonNull(duedate); + this.duedate = duedate; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + List rooms = model.getRoomListObservableList(); + ArrayList taskListWithDesirableResult = new ArrayList<>(); + datePredicate = new DueDatePredicate(duedate); + + for (Room room : rooms) { + tasks = room.getReadOnlyTasks(); + for (Task task : tasks) { + if (datePredicate.test(task)) { + taskListWithDesirableResult.add(task); + break; + } + } + } + + if (taskListWithDesirableResult.size() < 1) { + model.updateTasksInFilteredRoomTaskRecords(datePredicate); + throw new CommandException(MESSAGE_TASK_NOT_FOUND); + } + assert taskListWithDesirableResult.size() >= 1; + model.updateTasksInFilteredRoomTaskRecords(datePredicate); + return new CommandResult(String.format(MESSAGE_SEARCH_TASK_SUCCESS)); + } + + @Override + public boolean equals(Object other) { + return other == this + || (other instanceof SearchTaskCommand + && duedate.equals(((SearchTaskCommand) other).duedate)); + } +} diff --git a/src/main/java/seedu/address/logic/parser/AddCommandParser.java b/src/main/java/seedu/address/logic/parser/AddCommandParser.java deleted file mode 100644 index 3b8bfa035e8..00000000000 --- a/src/main/java/seedu/address/logic/parser/AddCommandParser.java +++ /dev/null @@ -1,60 +0,0 @@ -package seedu.address.logic.parser; - -import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; -import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; -import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; -import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; -import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; -import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; - -import java.util.Set; -import java.util.stream.Stream; - -import seedu.address.logic.commands.AddCommand; -import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Person; -import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; - -/** - * Parses input arguments and creates a new AddCommand object - */ -public class AddCommandParser implements Parser { - - /** - * Parses the given {@code String} of arguments in the context of the AddCommand - * and returns an AddCommand object for execution. - * @throws ParseException if the user input does not conform the expected format - */ - public AddCommand parse(String args) throws ParseException { - ArgumentMultimap argMultimap = - ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, PREFIX_TAG); - - if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_ADDRESS, PREFIX_PHONE, PREFIX_EMAIL) - || !argMultimap.getPreamble().isEmpty()) { - throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE)); - } - - Name name = ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get()); - Phone phone = ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE).get()); - Email email = ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL).get()); - Address address = ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS).get()); - Set tagList = ParserUtil.parseTags(argMultimap.getAllValues(PREFIX_TAG)); - - Person person = new Person(name, phone, email, address, tagList); - - return new AddCommand(person); - } - - /** - * Returns true if none of the prefixes contains empty {@code Optional} values in the given - * {@code ArgumentMultimap}. - */ - private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { - return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); - } - -} diff --git a/src/main/java/seedu/address/logic/parser/AddressBookParser.java b/src/main/java/seedu/address/logic/parser/AddressBookParser.java deleted file mode 100644 index 1e466792b46..00000000000 --- a/src/main/java/seedu/address/logic/parser/AddressBookParser.java +++ /dev/null @@ -1,76 +0,0 @@ -package seedu.address.logic.parser; - -import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; -import static seedu.address.commons.core.Messages.MESSAGE_UNKNOWN_COMMAND; - -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import seedu.address.logic.commands.AddCommand; -import seedu.address.logic.commands.ClearCommand; -import seedu.address.logic.commands.Command; -import seedu.address.logic.commands.DeleteCommand; -import seedu.address.logic.commands.EditCommand; -import seedu.address.logic.commands.ExitCommand; -import seedu.address.logic.commands.FindCommand; -import seedu.address.logic.commands.HelpCommand; -import seedu.address.logic.commands.ListCommand; -import seedu.address.logic.parser.exceptions.ParseException; - -/** - * Parses user input. - */ -public class AddressBookParser { - - /** - * Used for initial separation of command word and args. - */ - private static final Pattern BASIC_COMMAND_FORMAT = Pattern.compile("(?\\S+)(?.*)"); - - /** - * Parses user input into command for execution. - * - * @param userInput full user input string - * @return the command based on the user input - * @throws ParseException if the user input does not conform the expected format - */ - public Command parseCommand(String userInput) throws ParseException { - final Matcher matcher = BASIC_COMMAND_FORMAT.matcher(userInput.trim()); - if (!matcher.matches()) { - throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, HelpCommand.MESSAGE_USAGE)); - } - - final String commandWord = matcher.group("commandWord"); - final String arguments = matcher.group("arguments"); - switch (commandWord) { - - case AddCommand.COMMAND_WORD: - return new AddCommandParser().parse(arguments); - - case EditCommand.COMMAND_WORD: - return new EditCommandParser().parse(arguments); - - case DeleteCommand.COMMAND_WORD: - return new DeleteCommandParser().parse(arguments); - - case ClearCommand.COMMAND_WORD: - return new ClearCommand(); - - case FindCommand.COMMAND_WORD: - return new FindCommandParser().parse(arguments); - - case ListCommand.COMMAND_WORD: - return new ListCommand(); - - case ExitCommand.COMMAND_WORD: - return new ExitCommand(); - - case HelpCommand.COMMAND_WORD: - return new HelpCommand(); - - default: - throw new ParseException(MESSAGE_UNKNOWN_COMMAND); - } - } - -} diff --git a/src/main/java/seedu/address/logic/parser/CliSyntax.java b/src/main/java/seedu/address/logic/parser/CliSyntax.java deleted file mode 100644 index 75b1a9bf119..00000000000 --- a/src/main/java/seedu/address/logic/parser/CliSyntax.java +++ /dev/null @@ -1,15 +0,0 @@ -package seedu.address.logic.parser; - -/** - * Contains Command Line Interface (CLI) syntax definitions common to multiple commands - */ -public class CliSyntax { - - /* Prefix definitions */ - public static final Prefix PREFIX_NAME = new Prefix("n/"); - public static final Prefix PREFIX_PHONE = new Prefix("p/"); - public static final Prefix PREFIX_EMAIL = new Prefix("e/"); - public static final Prefix PREFIX_ADDRESS = new Prefix("a/"); - public static final Prefix PREFIX_TAG = new Prefix("t/"); - -} diff --git a/src/main/java/seedu/address/logic/parser/CovigentAppParser.java b/src/main/java/seedu/address/logic/parser/CovigentAppParser.java new file mode 100644 index 00000000000..eaa67def197 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/CovigentAppParser.java @@ -0,0 +1,134 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.commons.core.Messages.MESSAGE_UNKNOWN_COMMAND; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.ExitCommand; +import seedu.address.logic.commands.HelpCommand; +import seedu.address.logic.commands.patient.AddPatientCommand; +import seedu.address.logic.commands.patient.DeletePatientCommand; +import seedu.address.logic.commands.patient.EditPatientCommand; +import seedu.address.logic.commands.patient.ListPatientCommand; +import seedu.address.logic.commands.patient.SearchPatientCommand; +import seedu.address.logic.commands.room.AllocateRoomCommand; +import seedu.address.logic.commands.room.FindEmptyRoomCommand; +import seedu.address.logic.commands.room.InitRoomCommand; +import seedu.address.logic.commands.room.ListRoomCommand; +import seedu.address.logic.commands.room.SearchRoomCommand; +import seedu.address.logic.commands.task.AddTaskCommand; +import seedu.address.logic.commands.task.DeleteTaskCommand; +import seedu.address.logic.commands.task.EditTaskCommand; +import seedu.address.logic.commands.task.ListTaskCommand; +import seedu.address.logic.commands.task.SearchTaskCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.logic.parser.patient.AddPatientCommandParser; +import seedu.address.logic.parser.patient.DeletePatientCommandParser; +import seedu.address.logic.parser.patient.EditPatientCommandParser; +import seedu.address.logic.parser.patient.SearchPatientCommandParser; +import seedu.address.logic.parser.room.AllocateRoomCommandParser; +import seedu.address.logic.parser.room.InitRoomCommandParser; +import seedu.address.logic.parser.room.SearchRoomCommandParser; +import seedu.address.logic.parser.task.AddTaskCommandParser; +import seedu.address.logic.parser.task.DeleteTaskCommandParser; +import seedu.address.logic.parser.task.EditTaskCommandParser; +import seedu.address.logic.parser.task.SearchTaskCommandParser; + +/** + * Parses user input. + */ +public class CovigentAppParser { + + /** + * Used for initial separation of command word and args. + */ + private static final Pattern BASIC_COMMAND_FORMAT = Pattern.compile("(?\\S+)(?.*)"); + + /** + * Parses user input into command for execution. + * + * @param userInput full user input string + * @return the command based on the user input + * @throws ParseException if the user input does not conform the expected format + */ + public Command parseCommand(String userInput) throws ParseException { + final Matcher matcher = BASIC_COMMAND_FORMAT.matcher(userInput.trim()); + if (!matcher.matches()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, HelpCommand.MESSAGE_USAGE)); + } + + final String commandWord = matcher.group("commandWord").toLowerCase(); + final String arguments = matcher.group("arguments"); + + switch (commandWord) { + + // create + + case AddPatientCommand.COMMAND_WORD: + return new AddPatientCommandParser().parse(arguments); + + case InitRoomCommand.COMMAND_WORD: + return new InitRoomCommandParser().parse(arguments); + + case AddTaskCommand.COMMAND_WORD: + return new AddTaskCommandParser().parse(arguments); + + // update + + case EditPatientCommand.COMMAND_WORD: + return new EditPatientCommandParser().parse(arguments); + + case AllocateRoomCommand.COMMAND_WORD: + return new AllocateRoomCommandParser().parse(arguments); + + case EditTaskCommand.COMMAND_WORD: + return new EditTaskCommandParser().parse(arguments); + + // read + + case ListPatientCommand.COMMAND_WORD: + return new ListPatientCommand(); + + case ListTaskCommand.COMMAND_WORD: + return new ListTaskCommand(); + + // delete + + case DeletePatientCommand.COMMAND_WORD: + return new DeletePatientCommandParser().parse(arguments); + + case DeleteTaskCommand.COMMAND_WORD: + return new DeleteTaskCommandParser().parse(arguments); + + // others + + case SearchPatientCommand.COMMAND_WORD: + return new SearchPatientCommandParser().parse(arguments); + + case SearchRoomCommand.COMMAND_WORD: + return new SearchRoomCommandParser().parse(arguments); + + case SearchTaskCommand.COMMAND_WORD: + return new SearchTaskCommandParser().parse(arguments); + + case FindEmptyRoomCommand.COMMAND_WORD: + return new FindEmptyRoomCommand(); + + case ExitCommand.COMMAND_WORD: + return new ExitCommand(); + + case HelpCommand.COMMAND_WORD: + return new HelpCommand(); + + case ListRoomCommand.COMMAND_WORD: + return new ListRoomCommand(); + + default: + throw new ParseException(MESSAGE_UNKNOWN_COMMAND); + } + } + +} diff --git a/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java b/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java deleted file mode 100644 index 522b93081cc..00000000000 --- a/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java +++ /dev/null @@ -1,29 +0,0 @@ -package seedu.address.logic.parser; - -import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; - -import seedu.address.commons.core.index.Index; -import seedu.address.logic.commands.DeleteCommand; -import seedu.address.logic.parser.exceptions.ParseException; - -/** - * Parses input arguments and creates a new DeleteCommand object - */ -public class DeleteCommandParser implements Parser { - - /** - * Parses the given {@code String} of arguments in the context of the DeleteCommand - * and returns a DeleteCommand object for execution. - * @throws ParseException if the user input does not conform the expected format - */ - public DeleteCommand parse(String args) throws ParseException { - try { - Index index = ParserUtil.parseIndex(args); - return new DeleteCommand(index); - } catch (ParseException pe) { - throw new ParseException( - String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteCommand.MESSAGE_USAGE), pe); - } - } - -} diff --git a/src/main/java/seedu/address/logic/parser/EditCommandParser.java b/src/main/java/seedu/address/logic/parser/EditCommandParser.java deleted file mode 100644 index 845644b7dea..00000000000 --- a/src/main/java/seedu/address/logic/parser/EditCommandParser.java +++ /dev/null @@ -1,82 +0,0 @@ -package seedu.address.logic.parser; - -import static java.util.Objects.requireNonNull; -import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; -import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; -import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; -import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; -import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; -import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; - -import java.util.Collection; -import java.util.Collections; -import java.util.Optional; -import java.util.Set; - -import seedu.address.commons.core.index.Index; -import seedu.address.logic.commands.EditCommand; -import seedu.address.logic.commands.EditCommand.EditPersonDescriptor; -import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.tag.Tag; - -/** - * Parses input arguments and creates a new EditCommand object - */ -public class EditCommandParser implements Parser { - - /** - * Parses the given {@code String} of arguments in the context of the EditCommand - * and returns an EditCommand object for execution. - * @throws ParseException if the user input does not conform the expected format - */ - public EditCommand parse(String args) throws ParseException { - requireNonNull(args); - ArgumentMultimap argMultimap = - ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, PREFIX_TAG); - - Index index; - - try { - index = ParserUtil.parseIndex(argMultimap.getPreamble()); - } catch (ParseException pe) { - throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditCommand.MESSAGE_USAGE), pe); - } - - EditPersonDescriptor editPersonDescriptor = new EditPersonDescriptor(); - if (argMultimap.getValue(PREFIX_NAME).isPresent()) { - editPersonDescriptor.setName(ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get())); - } - if (argMultimap.getValue(PREFIX_PHONE).isPresent()) { - editPersonDescriptor.setPhone(ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE).get())); - } - if (argMultimap.getValue(PREFIX_EMAIL).isPresent()) { - editPersonDescriptor.setEmail(ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL).get())); - } - if (argMultimap.getValue(PREFIX_ADDRESS).isPresent()) { - editPersonDescriptor.setAddress(ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS).get())); - } - parseTagsForEdit(argMultimap.getAllValues(PREFIX_TAG)).ifPresent(editPersonDescriptor::setTags); - - if (!editPersonDescriptor.isAnyFieldEdited()) { - throw new ParseException(EditCommand.MESSAGE_NOT_EDITED); - } - - return new EditCommand(index, editPersonDescriptor); - } - - /** - * Parses {@code Collection tags} into a {@code Set} if {@code tags} is non-empty. - * If {@code tags} contain only one element which is an empty string, it will be parsed into a - * {@code Set} containing zero tags. - */ - private Optional> parseTagsForEdit(Collection tags) throws ParseException { - assert tags != null; - - if (tags.isEmpty()) { - return Optional.empty(); - } - Collection tagSet = tags.size() == 1 && tags.contains("") ? Collections.emptySet() : tags; - return Optional.of(ParserUtil.parseTags(tagSet)); - } - -} diff --git a/src/main/java/seedu/address/logic/parser/FindCommandParser.java b/src/main/java/seedu/address/logic/parser/FindCommandParser.java deleted file mode 100644 index 4fb71f23103..00000000000 --- a/src/main/java/seedu/address/logic/parser/FindCommandParser.java +++ /dev/null @@ -1,33 +0,0 @@ -package seedu.address.logic.parser; - -import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; - -import java.util.Arrays; - -import seedu.address.logic.commands.FindCommand; -import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.person.NameContainsKeywordsPredicate; - -/** - * Parses input arguments and creates a new FindCommand object - */ -public class FindCommandParser implements Parser { - - /** - * Parses the given {@code String} of arguments in the context of the FindCommand - * and returns a FindCommand object for execution. - * @throws ParseException if the user input does not conform the expected format - */ - public FindCommand parse(String args) throws ParseException { - String trimmedArgs = args.trim(); - if (trimmedArgs.isEmpty()) { - throw new ParseException( - String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE)); - } - - String[] nameKeywords = trimmedArgs.split("\\s+"); - - return new FindCommand(new NameContainsKeywordsPredicate(Arrays.asList(nameKeywords))); - } - -} diff --git a/src/main/java/seedu/address/logic/parser/ParserUtil.java b/src/main/java/seedu/address/logic/parser/ParserUtil.java index b117acb9c55..bb280472fb8 100644 --- a/src/main/java/seedu/address/logic/parser/ParserUtil.java +++ b/src/main/java/seedu/address/logic/parser/ParserUtil.java @@ -2,123 +2,72 @@ import static java.util.Objects.requireNonNull; -import java.util.Collection; -import java.util.HashSet; -import java.util.Set; +import java.util.stream.Stream; import seedu.address.commons.core.index.Index; import seedu.address.commons.util.StringUtil; import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; +import seedu.address.logic.parser.room.RoomCliSyntax; +import seedu.address.logic.parser.task.TaskCliSyntax; /** * Contains utility methods used for parsing strings in the various *Parser classes. */ public class ParserUtil { - public static final String MESSAGE_INVALID_INDEX = "Index is not a non-zero unsigned integer."; + public static final String MESSAGE_INVALID_UNSIGNED_INT = "Please ensure that the value " + + "for the field \"%1$s\" is a number between 1 and " + Integer.MAX_VALUE + "." + + "\nYour current input: %2$s"; + //@author LeeMingDe /** - * Parses {@code oneBasedIndex} into an {@code Index} and returns it. Leading and trailing whitespaces will be - * trimmed. - * @throws ParseException if the specified index is invalid (not non-zero unsigned integer). - */ - public static Index parseIndex(String oneBasedIndex) throws ParseException { - String trimmedIndex = oneBasedIndex.trim(); - if (!StringUtil.isNonZeroUnsignedInteger(trimmedIndex)) { - throw new ParseException(MESSAGE_INVALID_INDEX); - } - return Index.fromOneBased(Integer.parseInt(trimmedIndex)); - } - - /** - * Parses a {@code String name} into a {@code Name}. - * Leading and trailing whitespaces will be trimmed. - * - * @throws ParseException if the given {@code name} is invalid. - */ - public static Name parseName(String name) throws ParseException { - requireNonNull(name); - String trimmedName = name.trim(); - if (!Name.isValidName(trimmedName)) { - throw new ParseException(Name.MESSAGE_CONSTRAINTS); - } - return new Name(trimmedName); - } - - /** - * Parses a {@code String phone} into a {@code Phone}. + * Parses {@code roomNumber} into an {@code Integer} and returns it. * Leading and trailing whitespaces will be trimmed. * - * @throws ParseException if the given {@code phone} is invalid. + * @throws ParseException if the specified room number is invalid (not non-zero unsigned integer). */ - public static Phone parsePhone(String phone) throws ParseException { - requireNonNull(phone); - String trimmedPhone = phone.trim(); - if (!Phone.isValidPhone(trimmedPhone)) { - throw new ParseException(Phone.MESSAGE_CONSTRAINTS); + public static Integer parseRoomNumber(String roomNumber) throws ParseException { + requireNonNull(roomNumber); + String trimmedRoomNumber = roomNumber.trim(); + if (!StringUtil.isNonZeroUnsignedInteger(trimmedRoomNumber)) { + throw new ParseException(String.format(MESSAGE_INVALID_UNSIGNED_INT, + RoomCliSyntax.PREFIX_ROOM_NUMBER, roomNumber)); } - return new Phone(trimmedPhone); + return Integer.parseInt(trimmedRoomNumber); } + //@author LeeMingDe /** - * Parses a {@code String address} into an {@code Address}. + * Parses {@code taskIndex} into an {@code Index} and returns it. * Leading and trailing whitespaces will be trimmed. * - * @throws ParseException if the given {@code address} is invalid. + * @throws ParseException if the specified task index is invalid (not non-zero unsigned integer). */ - public static Address parseAddress(String address) throws ParseException { - requireNonNull(address); - String trimmedAddress = address.trim(); - if (!Address.isValidAddress(trimmedAddress)) { - throw new ParseException(Address.MESSAGE_CONSTRAINTS); - } - return new Address(trimmedAddress); - } - - /** - * Parses a {@code String email} into an {@code Email}. - * Leading and trailing whitespaces will be trimmed. - * - * @throws ParseException if the given {@code email} is invalid. - */ - public static Email parseEmail(String email) throws ParseException { - requireNonNull(email); - String trimmedEmail = email.trim(); - if (!Email.isValidEmail(trimmedEmail)) { - throw new ParseException(Email.MESSAGE_CONSTRAINTS); + public static Index parseTaskIndex(String taskIndex) throws ParseException { + requireNonNull(taskIndex); + String trimmedIndex = taskIndex.trim(); + if (!StringUtil.isNonZeroUnsignedInteger(trimmedIndex)) { + throw new ParseException(String.format(MESSAGE_INVALID_UNSIGNED_INT, + TaskCliSyntax.PREFIX_TASK_NUMBER, taskIndex)); } - return new Email(trimmedEmail); + return Index.fromOneBased(Integer.parseInt(trimmedIndex)); } /** - * Parses a {@code String tag} into a {@code Tag}. - * Leading and trailing whitespaces will be trimmed. - * - * @throws ParseException if the given {@code tag} is invalid. + * Returns true if none of the prefixes contains empty {@code Optional} values in the given + * {@code ArgumentMultimap}. */ - public static Tag parseTag(String tag) throws ParseException { - requireNonNull(tag); - String trimmedTag = tag.trim(); - if (!Tag.isValidTagName(trimmedTag)) { - throw new ParseException(Tag.MESSAGE_CONSTRAINTS); - } - return new Tag(trimmedTag); + public static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); } /** - * Parses {@code Collection tags} into a {@code Set}. + * Returns true if exactly one prefix contains a non-empty {@code Optional} value in the given + * {@code ArgumentMultimap}. */ - public static Set parseTags(Collection tags) throws ParseException { - requireNonNull(tags); - final Set tagSet = new HashSet<>(); - for (String tagName : tags) { - tagSet.add(parseTag(tagName)); - } - return tagSet; + public static boolean isExactlyOnePrefixPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes) + .filter(prefix -> argumentMultimap.getValue(prefix).isPresent()) + .count() == 1; } } diff --git a/src/main/java/seedu/address/logic/parser/patient/AddPatientCommandParser.java b/src/main/java/seedu/address/logic/parser/patient/AddPatientCommandParser.java new file mode 100644 index 00000000000..8de827da398 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/patient/AddPatientCommandParser.java @@ -0,0 +1,65 @@ +package seedu.address.logic.parser.patient; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.commands.patient.AddPatientCommand.MESSAGE_USAGE; +import static seedu.address.logic.parser.patient.PatientCliSyntax.PREFIX_AGE; +import static seedu.address.logic.parser.patient.PatientCliSyntax.PREFIX_COMMENTS; +import static seedu.address.logic.parser.patient.PatientCliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.patient.PatientCliSyntax.PREFIX_PERIOD_OF_STAY; +import static seedu.address.logic.parser.patient.PatientCliSyntax.PREFIX_PHONE; +import static seedu.address.logic.parser.patient.PatientCliSyntax.PREFIX_TEMP; + +import seedu.address.logic.commands.patient.AddPatientCommand; +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.patient.Age; +import seedu.address.model.patient.Comment; +import seedu.address.model.patient.Name; +import seedu.address.model.patient.Patient; +import seedu.address.model.patient.PeriodOfStay; +import seedu.address.model.patient.Phone; +import seedu.address.model.patient.Temperature; + +//@@author chiamyunqing +/** + * Parses input arguments and creates a new AddPatientCommand object. + */ +public class AddPatientCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the AddPatientCommand + * and returns an AddPatientCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public AddPatientCommand parse(String args) throws ParseException { + requireNonNull(args); + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_TEMP, PREFIX_PERIOD_OF_STAY, + PREFIX_PHONE, PREFIX_AGE, PREFIX_COMMENTS); + + if (!ParserUtil.arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_TEMP, + PREFIX_PERIOD_OF_STAY, PREFIX_PHONE, PREFIX_AGE) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, MESSAGE_USAGE)); + } + + Name name = PatientParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get()); + Temperature temp = PatientParserUtil.parseTemperature(argMultimap.getValue(PREFIX_TEMP).get()); + PeriodOfStay periodOfStay = PatientParserUtil.parsePeriodOfStay( + argMultimap.getValue(PREFIX_PERIOD_OF_STAY).get()); + Phone phone = PatientParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE).get()); + Age age = PatientParserUtil.parseAge(argMultimap.getValue(PREFIX_AGE).get()); + Comment comment = new Comment("-"); + + if (ParserUtil.arePrefixesPresent(argMultimap, PREFIX_COMMENTS)) { + comment = PatientParserUtil.parseComment(argMultimap.getValue(PREFIX_COMMENTS).get()); + } + + Patient patient = new Patient(name, temp, periodOfStay, phone, age, comment); + return new AddPatientCommand(patient); + } +} diff --git a/src/main/java/seedu/address/logic/parser/patient/DeletePatientCommandParser.java b/src/main/java/seedu/address/logic/parser/patient/DeletePatientCommandParser.java new file mode 100644 index 00000000000..9384bc92c66 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/patient/DeletePatientCommandParser.java @@ -0,0 +1,29 @@ +package seedu.address.logic.parser.patient; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.address.logic.commands.patient.DeletePatientCommand; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.patient.Name; + +//@@author chiamyunqing +/** + * Parses input arguments and creates a new DeletePatientCommand object. + */ +public class DeletePatientCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the DeletePatientCommand + * and returns a DeletePatientCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public DeletePatientCommand parse(String args) throws ParseException { + try { + Name patientName = PatientParserUtil.parseName(args); + return new DeletePatientCommand(patientName); + } catch (ParseException pe) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeletePatientCommand.MESSAGE_USAGE)); + } + } +} diff --git a/src/main/java/seedu/address/logic/parser/patient/EditPatientCommandParser.java b/src/main/java/seedu/address/logic/parser/patient/EditPatientCommandParser.java new file mode 100644 index 00000000000..b79589eaae6 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/patient/EditPatientCommandParser.java @@ -0,0 +1,80 @@ +package seedu.address.logic.parser.patient; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.patient.PatientCliSyntax.PREFIX_AGE; +import static seedu.address.logic.parser.patient.PatientCliSyntax.PREFIX_COMMENTS; +import static seedu.address.logic.parser.patient.PatientCliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.patient.PatientCliSyntax.PREFIX_PERIOD_OF_STAY; +import static seedu.address.logic.parser.patient.PatientCliSyntax.PREFIX_PHONE; +import static seedu.address.logic.parser.patient.PatientCliSyntax.PREFIX_TEMP; + +import seedu.address.logic.commands.patient.EditPatientCommand; +import seedu.address.logic.parser.ArgumentMultimap; +import seedu.address.logic.parser.ArgumentTokenizer; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.patient.Name; + +//@author LeeMingDe +/** + * Parses input arguments and creates a new EditPatientCommand object. + */ +public class EditPatientCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the EditPatientCommand + * and returns an EditPatientCommand object for execution. + * + * @param args that are inputted by the user. + * @return EditPatientCommand object for execution. + * @throws ParseException if the user input does not conform the expected format. + */ + public EditPatientCommand parse(String args) throws ParseException { + requireNonNull(args); + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, + PREFIX_COMMENTS, PREFIX_TEMP, PREFIX_AGE, PREFIX_PERIOD_OF_STAY); + + Name patientTobeEdited; + try { + patientTobeEdited = PatientParserUtil.parseName(argMultimap.getPreamble().trim().toLowerCase()); + } catch (ParseException pe) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditPatientCommand.MESSAGE_USAGE)); + } + if (args.trim().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditPatientCommand.MESSAGE_USAGE)); + } + EditPatientCommand.EditPatientDescriptor editPatientDescriptor = new EditPatientCommand.EditPatientDescriptor(); + if (argMultimap.getValue(PREFIX_NAME).isPresent()) { + editPatientDescriptor.setName(PatientParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get())); + } + if (argMultimap.getValue(PREFIX_PHONE).isPresent()) { + editPatientDescriptor.setPhone(PatientParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE).get())); + } + if (argMultimap.getValue(PREFIX_TEMP).isPresent()) { + editPatientDescriptor.setTemperature( + PatientParserUtil.parseTemperature(argMultimap.getValue(PREFIX_TEMP).get())); + } + if (argMultimap.getValue(PREFIX_AGE).isPresent()) { + editPatientDescriptor.setAge(PatientParserUtil.parseAge(argMultimap.getValue(PREFIX_AGE).get())); + } + if (argMultimap.getValue(PREFIX_PERIOD_OF_STAY).isPresent()) { + editPatientDescriptor.setPeriodOfStay( + PatientParserUtil.parsePeriodOfStay(argMultimap.getValue(PREFIX_PERIOD_OF_STAY).get())); + } + if (argMultimap.getValue(PREFIX_COMMENTS).isPresent()) { + editPatientDescriptor.setComment( + PatientParserUtil.parseComment(argMultimap.getValue(PREFIX_COMMENTS).get())); + } + + if (!editPatientDescriptor.isAnyFieldEdited()) { + throw new ParseException(EditPatientCommand.MESSAGE_PATIENT_NOT_EDITED); + } + + return new EditPatientCommand(patientTobeEdited, editPatientDescriptor); + } + +} + + diff --git a/src/main/java/seedu/address/logic/parser/patient/PatientCliSyntax.java b/src/main/java/seedu/address/logic/parser/patient/PatientCliSyntax.java new file mode 100644 index 00000000000..a82085b3d23 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/patient/PatientCliSyntax.java @@ -0,0 +1,19 @@ +package seedu.address.logic.parser.patient; + +import seedu.address.logic.parser.Prefix; + +/** + * Contains Command Line Interface (CLI) syntax definitions common to multiple patient-related commands + */ +public class PatientCliSyntax { + + /* Prefix definitions */ + public static final Prefix PREFIX_NAME = new Prefix("n/"); + public static final Prefix PREFIX_TEMP = new Prefix("t/"); + public static final Prefix PREFIX_PERIOD_OF_STAY = new Prefix("d/"); + public static final Prefix PREFIX_PHONE = new Prefix("p/"); + public static final Prefix PREFIX_AGE = new Prefix("a/"); + public static final Prefix PREFIX_COMMENTS = new Prefix("c/"); + public static final Prefix PREFIX_TEMP_RANGE = new Prefix("tr/"); + +} diff --git a/src/main/java/seedu/address/logic/parser/patient/PatientParserUtil.java b/src/main/java/seedu/address/logic/parser/patient/PatientParserUtil.java new file mode 100644 index 00000000000..5213c0846d6 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/patient/PatientParserUtil.java @@ -0,0 +1,126 @@ +package seedu.address.logic.parser.patient; + +import static java.util.Objects.requireNonNull; + +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.patient.Age; +import seedu.address.model.patient.Comment; +import seedu.address.model.patient.Name; +import seedu.address.model.patient.PeriodOfStay; +import seedu.address.model.patient.Phone; +import seedu.address.model.patient.Temperature; +import seedu.address.model.patient.TemperatureRange; + +/** + * Contains utility methods used for parsing strings in the various patient-related Parser classes. + */ +public class PatientParserUtil { + + /** + * Parses a {@code String name} into a {@code Name}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code name} is invalid. + */ + public static Name parseName(String name) throws ParseException { + requireNonNull(name); + String trimmedName = name.trim(); + if (!Name.isValidName(trimmedName)) { + throw new ParseException(Name.MESSAGE_CONSTRAINTS); + } + return new Name(trimmedName); + } + + /** + * Parses a {@code String phone} into a {@code Phone}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code phone} is invalid. + */ + public static Phone parsePhone(String phone) throws ParseException { + requireNonNull(phone); + String trimmedPhone = phone.trim(); + if (!Phone.isValidPhone(trimmedPhone)) { + throw new ParseException(Phone.MESSAGE_CONSTRAINTS); + } + return new Phone(trimmedPhone); + } + + //@@author chiamyunqing + /** + * Parses a {@code String temperature} into a {@code Temperature}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code temperature} is invalid. + */ + public static Temperature parseTemperature(String temp) throws ParseException { + requireNonNull(temp); + String trimmedTemp = temp.trim(); + if (!Temperature.isValidTemperature(trimmedTemp)) { + throw new ParseException(Temperature.MESSAGE_CONSTRAINTS); + } + return new Temperature(trimmedTemp); + } + + /** + * Parses a {@code String period of stay} into a {@code PeriodOfStay}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code period of stay} is invalid. + */ + public static PeriodOfStay parsePeriodOfStay(String periodOfStay) throws ParseException { + requireNonNull(periodOfStay); + String trimmedPeriodOfStay = periodOfStay.trim(); + if (!PeriodOfStay.isValidPeriodOfStay(trimmedPeriodOfStay)) { + throw new ParseException(PeriodOfStay.getErrorMessage(trimmedPeriodOfStay)); + } + return new PeriodOfStay(trimmedPeriodOfStay); + } + + /** + * Parses a {@code String age} into a {@code Age}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code age} is invalid. + */ + public static Age parseAge(String age) throws ParseException { + requireNonNull(age); + String trimmedAge = age.trim(); + if (!Age.isValidAge(trimmedAge)) { + throw new ParseException(Age.getErrorMessage(trimmedAge)); + } + return new Age(trimmedAge); + } + + /** + * Parses a {@code String comment} into a {@code Comment} + * Leading and trailing whitespaces will be trimmed + * + * @return an "-" comment if no comment is added + */ + public static Comment parseComment(String comment) { + if (comment == null || comment.trim().length() == 0) { + return new Comment("-"); + } else { + return new Comment(comment.trim()); + } + } + //@@author chiamyunqing + + //@@author raymondge + /** + * Parses a {@code String temperature} into a {@code Temperature}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code temperature} is invalid. + */ + public static TemperatureRange parseTemperatureRange(String tempRange) throws ParseException { + requireNonNull(tempRange); + String trimmedTempRange = tempRange.trim(); + if (!TemperatureRange.isValidTemperatureRange(trimmedTempRange)) { + throw new ParseException(TemperatureRange.MESSAGE_CONSTRAINTS_TEMPERATURERANGE); + } + return new TemperatureRange(trimmedTempRange); + } + //@@author raymondge +} diff --git a/src/main/java/seedu/address/logic/parser/patient/SearchPatientCommandParser.java b/src/main/java/seedu/address/logic/parser/patient/SearchPatientCommandParser.java new file mode 100644 index 00000000000..6ee4759f5f1 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/patient/SearchPatientCommandParser.java @@ -0,0 +1,49 @@ +package seedu.address.logic.parser.patient; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.patient.PatientCliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.patient.PatientCliSyntax.PREFIX_TEMP_RANGE; + +import seedu.address.logic.commands.patient.SearchPatientCommand; +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; + +/** + * Parses input arguments and creates a new SearchPatientCommand object + */ +public class SearchPatientCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the SearchPatientCommand + * and returns an SearchPatientCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public SearchPatientCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_TEMP_RANGE); + + if (!ParserUtil.isExactlyOnePrefixPresent(argMultimap, PREFIX_NAME, PREFIX_TEMP_RANGE) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, SearchPatientCommand.MESSAGE_USAGE)); + } + + SearchPatientCommand.SearchPatientDescriptor searchPatientDescriptor = + new SearchPatientCommand.SearchPatientDescriptor(); + + if (argMultimap.getValue(PREFIX_NAME).isPresent()) { + String nameWithoutPrefix = argMultimap.getValue(PREFIX_NAME).get(); + searchPatientDescriptor.setStringName(nameWithoutPrefix); + searchPatientDescriptor.setName(PatientParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get())); + } + + if (argMultimap.getValue(PREFIX_TEMP_RANGE).isPresent()) { + searchPatientDescriptor.setTemperatureRange( + PatientParserUtil.parseTemperatureRange(argMultimap.getValue(PREFIX_TEMP_RANGE).get())); + } + + return new SearchPatientCommand(searchPatientDescriptor); + } +} diff --git a/src/main/java/seedu/address/logic/parser/room/AllocateRoomCommandParser.java b/src/main/java/seedu/address/logic/parser/room/AllocateRoomCommandParser.java new file mode 100644 index 00000000000..5bba4c9caed --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/room/AllocateRoomCommandParser.java @@ -0,0 +1,66 @@ +package seedu.address.logic.parser.room; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.patient.PatientCliSyntax.PREFIX_NAME; + +import seedu.address.logic.commands.room.AllocateRoomCommand; +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.logic.parser.patient.PatientParserUtil; + +//@author LeeMingDe +/** + * Parses input arguments and creates a new AllocateRoomCommand object. + */ +public class AllocateRoomCommandParser implements Parser { + private static final String INPUT_REMOVE_PATIENT = "-"; + + /** + * Parses the given {@code String} of arguments in the context of the EditPatientCommand + * and returns an EditPatientCommand object for execution. + * + * @param args that are inputted by the user. + * @return EditPatientCommand object for execution. + * @throws ParseException if the user input does not conform the expected format. + */ + public AllocateRoomCommand parse(String args) throws ParseException { + requireNonNull(args); + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_NAME); + + Integer roomToBeAllocated; + boolean toRemove = false; + try { + roomToBeAllocated = ParserUtil.parseRoomNumber(argMultimap.getPreamble().trim()); + } catch (ParseException pe) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AllocateRoomCommand.MESSAGE_USAGE)); + } + + if (args.trim().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AllocateRoomCommand.MESSAGE_USAGE)); + } + + AllocateRoomCommand.AllocateRoomDescriptor + allocateRoomDescriptor = new AllocateRoomCommand.AllocateRoomDescriptor(); + + if (argMultimap.getValue(PREFIX_NAME).isPresent()) { + String patientFieldInput = argMultimap.getValue(PREFIX_NAME).get(); + if (patientFieldInput.equals(INPUT_REMOVE_PATIENT)) { + toRemove = true; //empty the room + allocateRoomDescriptor.setOccupied(false); + } else { + allocateRoomDescriptor.setPatientName(PatientParserUtil.parseName(patientFieldInput)); + } + } + + if (!allocateRoomDescriptor.isAnyFieldEdited()) { + throw new ParseException(AllocateRoomCommand.MESSAGE_USAGE); + } + + return new AllocateRoomCommand(roomToBeAllocated, allocateRoomDescriptor, toRemove); + } +} diff --git a/src/main/java/seedu/address/logic/parser/room/InitRoomCommandParser.java b/src/main/java/seedu/address/logic/parser/room/InitRoomCommandParser.java new file mode 100644 index 00000000000..f9b40193a71 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/room/InitRoomCommandParser.java @@ -0,0 +1,39 @@ +package seedu.address.logic.parser.room; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.commands.room.InitRoomCommand.MESSAGE_USAGE; + +import seedu.address.logic.commands.room.InitRoomCommand; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Parses user input and creates new AddRoomsCommand object + */ +//@@author itssodium +public class InitRoomCommandParser implements Parser { + public static final String NUMBER_OF_ROOMS_GIVEN_IN_DIGITS = "Please give the number of digits in integers\n" + + "Example: initRoom 200"; + + /** + * Parses the given {@code String} of arguments in the context of the AddPatientCommand + * and returns an AddRoomsCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public InitRoomCommand parse(String args) throws ParseException { + String trimmedArgs = args.trim(); + if (trimmedArgs.isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, MESSAGE_USAGE)); + //if user does not give number of room + } + int numberOfRooms = -1; //used so that it can be used to test for NumberFormatException + try { + numberOfRooms = Integer.parseInt(trimmedArgs); + } catch (NumberFormatException nfe) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, NUMBER_OF_ROOMS_GIVEN_IN_DIGITS)); + //if user does not give number of rooms in terms of integers but gives in terms of words + } + return new InitRoomCommand(numberOfRooms); + } + +} diff --git a/src/main/java/seedu/address/logic/parser/room/RoomCliSyntax.java b/src/main/java/seedu/address/logic/parser/room/RoomCliSyntax.java new file mode 100644 index 00000000000..096afe557ca --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/room/RoomCliSyntax.java @@ -0,0 +1,13 @@ +package seedu.address.logic.parser.room; + +import seedu.address.logic.parser.Prefix; + +/** + * Contains Command Line Interface (CLI) syntax definitions common to multiple commands. + */ +public class RoomCliSyntax { + + /* Prefix definitions */ + public static final Prefix PREFIX_ROOM_NUMBER = new Prefix("r/"); + +} diff --git a/src/main/java/seedu/address/logic/parser/room/SearchRoomCommandParser.java b/src/main/java/seedu/address/logic/parser/room/SearchRoomCommandParser.java new file mode 100644 index 00000000000..3a24d2286b6 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/room/SearchRoomCommandParser.java @@ -0,0 +1,52 @@ +package seedu.address.logic.parser.room; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.commands.room.SearchRoomCommand.MESSAGE_USAGE; +import static seedu.address.logic.parser.patient.PatientCliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.room.RoomCliSyntax.PREFIX_ROOM_NUMBER; + +import seedu.address.logic.commands.room.SearchRoomCommand; +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.logic.parser.patient.PatientParserUtil; +import seedu.address.model.patient.Name; + +//@@author chiamyunqing +/** + * Parses input arguments and creates a new SearchRoomCommand object. + */ +public class SearchRoomCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of SearchRoomCommand + * and returns a SearchRoomCommand object for execution. + * @throws ParseException if the user input does not conform to the expected format + */ + @Override + public SearchRoomCommand parse(String args) throws ParseException { + requireNonNull(args); + ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_ROOM_NUMBER); + + if (!ParserUtil.isExactlyOnePrefixPresent(argMultimap, PREFIX_NAME, PREFIX_ROOM_NUMBER) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, MESSAGE_USAGE)); + } + + SearchRoomCommand.SearchRoomDescriptor descriptor = new SearchRoomCommand.SearchRoomDescriptor(); + + if (argMultimap.getValue(PREFIX_NAME).isPresent()) { + Name patientName = PatientParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get()); + descriptor.setPatientName(patientName); + return new SearchRoomCommand(descriptor); + } + //definitely have prefix room number if no prefix name + Integer roomNumber = ParserUtil.parseRoomNumber(argMultimap.getValue(PREFIX_ROOM_NUMBER).get()); + descriptor.setRoomNumber(roomNumber); + return new SearchRoomCommand(descriptor); + + } +} diff --git a/src/main/java/seedu/address/logic/parser/task/AddTaskCommandParser.java b/src/main/java/seedu/address/logic/parser/task/AddTaskCommandParser.java new file mode 100644 index 00000000000..8529b14dba3 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/task/AddTaskCommandParser.java @@ -0,0 +1,46 @@ +package seedu.address.logic.parser.task; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.room.RoomCliSyntax.PREFIX_ROOM_NUMBER; +import static seedu.address.logic.parser.task.TaskCliSyntax.PREFIX_DESCRIPTION; +import static seedu.address.logic.parser.task.TaskCliSyntax.PREFIX_DUE_DATE; + +import seedu.address.logic.commands.task.AddTaskCommand; +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.task.DateTimeDue; +import seedu.address.model.task.Description; +import seedu.address.model.task.Task; + +//@@author w-yeehong +/** + * Parses input arguments and creates a new AddTaskCommand object. + */ +public class AddTaskCommandParser implements Parser { + /** + * Parses the given {@code String} of arguments in the context of the AddTaskCommand + * and returns an AddTaskCommand object for execution. + * + * @throws ParseException if the user input does not conform to the expected format. + */ + public AddTaskCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_DESCRIPTION, PREFIX_ROOM_NUMBER, PREFIX_DUE_DATE); + + if (!ParserUtil.arePrefixesPresent(argMultimap, PREFIX_DESCRIPTION, PREFIX_ROOM_NUMBER) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddTaskCommand.MESSAGE_USAGE)); + } + + Description description = TaskParserUtil.parseDescription(argMultimap.getValue(PREFIX_DESCRIPTION).get()); + int roomNumber = ParserUtil.parseRoomNumber(argMultimap.getValue(PREFIX_ROOM_NUMBER).get()); + DateTimeDue dueAt = TaskParserUtil.parseDateTimeDue(argMultimap.getValue(PREFIX_DUE_DATE)); // optional + + Task task = new Task(description, dueAt); + + return new AddTaskCommand(task, roomNumber); + } +} diff --git a/src/main/java/seedu/address/logic/parser/task/DeleteTaskCommandParser.java b/src/main/java/seedu/address/logic/parser/task/DeleteTaskCommandParser.java new file mode 100644 index 00000000000..2342556824e --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/task/DeleteTaskCommandParser.java @@ -0,0 +1,40 @@ +package seedu.address.logic.parser.task; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.room.RoomCliSyntax.PREFIX_ROOM_NUMBER; +import static seedu.address.logic.parser.task.TaskCliSyntax.PREFIX_TASK_NUMBER; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.task.DeleteTaskCommand; +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; + +//@@author w-yeehong +/** + * Parses input arguments and creates a new DeleteTaskCommand object. + */ +public class DeleteTaskCommandParser implements Parser { + /** + * Parses the given {@code String} of arguments in the context of the DeleteTaskCommand + * and returns a DeleteTaskCommand object for execution. + * + * @throws ParseException if the user input does not conform to the expected format. + */ + public DeleteTaskCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_ROOM_NUMBER, PREFIX_TASK_NUMBER); + + if (!ParserUtil.arePrefixesPresent(argMultimap, PREFIX_ROOM_NUMBER, PREFIX_TASK_NUMBER) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteTaskCommand.MESSAGE_USAGE)); + } + + int roomNumber = ParserUtil.parseRoomNumber(argMultimap.getValue(PREFIX_ROOM_NUMBER).get()); + Index taskNumber = ParserUtil.parseTaskIndex(argMultimap.getValue(PREFIX_TASK_NUMBER).get()); + + return new DeleteTaskCommand(roomNumber, taskNumber); + } +} diff --git a/src/main/java/seedu/address/logic/parser/task/EditTaskCommandParser.java b/src/main/java/seedu/address/logic/parser/task/EditTaskCommandParser.java new file mode 100644 index 00000000000..f87ef4c67c3 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/task/EditTaskCommandParser.java @@ -0,0 +1,71 @@ +package seedu.address.logic.parser.task; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.room.RoomCliSyntax.PREFIX_ROOM_NUMBER; +import static seedu.address.logic.parser.task.TaskCliSyntax.PREFIX_DESCRIPTION; +import static seedu.address.logic.parser.task.TaskCliSyntax.PREFIX_DUE_DATE; +import static seedu.address.logic.parser.task.TaskCliSyntax.PREFIX_TASK_NUMBER; + +import java.util.Optional; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.task.EditTaskCommand; +import seedu.address.logic.commands.task.EditTaskCommand.EditTaskDescriptor; +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; + +//@@author w-yeehong +/** + * Parses input arguments and creates a new EditTaskCommand object. + */ +public class EditTaskCommandParser implements Parser { + + public static final String INPUT_REMOVE_DUE_DATE = "-"; + + /** + * Parses the given {@code String} of arguments in the context of the EditTaskCommand + * and returns an EditTaskCommand object for execution. + * + * @throws ParseException if the user input does not conform to the expected format. + */ + public EditTaskCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, + PREFIX_ROOM_NUMBER, PREFIX_TASK_NUMBER, PREFIX_DESCRIPTION, PREFIX_DUE_DATE); + + if (!ParserUtil.arePrefixesPresent(argMultimap, PREFIX_ROOM_NUMBER, PREFIX_TASK_NUMBER) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditTaskCommand.MESSAGE_USAGE)); + } + + // Compulsory fields + int roomNumber = ParserUtil.parseRoomNumber(argMultimap.getValue(PREFIX_ROOM_NUMBER).get()); + Index taskIndex = ParserUtil.parseTaskIndex(argMultimap.getValue(PREFIX_TASK_NUMBER).get()); + + // Optional fields + EditTaskDescriptor editTaskDescriptor = new EditTaskDescriptor(); + + if (argMultimap.getValue(PREFIX_DESCRIPTION).isPresent()) { + String description = argMultimap.getValue(PREFIX_DESCRIPTION).get(); + editTaskDescriptor.setDescription(TaskParserUtil.parseDescription(description)); + } + + if (argMultimap.getValue(PREFIX_DUE_DATE).isPresent()) { + String dueAt = argMultimap.getValue(PREFIX_DUE_DATE).get(); + editTaskDescriptor.setDateTimeDue(TaskParserUtil.parseDateTimeDue((dueAt.equals(INPUT_REMOVE_DUE_DATE)) + ? Optional.empty() + : Optional.of(dueAt))); + } + + if (!editTaskDescriptor.isAnyFieldEdited()) { + // Do not execute as no changes have been made + throw new ParseException(Messages.MESSAGE_TASK_NOT_EDITED); + } + + return new EditTaskCommand(roomNumber, taskIndex, editTaskDescriptor); + } +} diff --git a/src/main/java/seedu/address/logic/parser/task/SearchTaskCommandParser.java b/src/main/java/seedu/address/logic/parser/task/SearchTaskCommandParser.java new file mode 100644 index 00000000000..1c026312cb1 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/task/SearchTaskCommandParser.java @@ -0,0 +1,38 @@ +package seedu.address.logic.parser.task; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.task.TaskCliSyntax.PREFIX_DUE_DATE; + +import seedu.address.logic.commands.task.SearchTaskCommand; +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.task.DateTimeDue; + +/** + * Parses input arguments and creates a new SearchTaskCommand object + */ +public class SearchTaskCommandParser implements Parser { + /** + * Parses the given {@code String} of arguments in the context of the SearchTaskCommand + * and returns an SearchTaskCommand object for execution. + * + * @throws ParseException if the user input does not conform to the expected format. + */ + public SearchTaskCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_DUE_DATE); + + if (!ParserUtil.isExactlyOnePrefixPresent(argMultimap, PREFIX_DUE_DATE) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, SearchTaskCommand.MESSAGE_USAGE)); + } + + DateTimeDue dueAt = TaskParserUtil.parseDateTimeDue(argMultimap.getValue(PREFIX_DUE_DATE)); // optional + + + return new SearchTaskCommand(dueAt); + } +} diff --git a/src/main/java/seedu/address/logic/parser/task/TaskCliSyntax.java b/src/main/java/seedu/address/logic/parser/task/TaskCliSyntax.java new file mode 100644 index 00000000000..66382072624 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/task/TaskCliSyntax.java @@ -0,0 +1,15 @@ +package seedu.address.logic.parser.task; + +import seedu.address.logic.parser.Prefix; + +/** + * Contains Command Line Interface (CLI) syntax definitions common to multiple task-related commands + */ +public class TaskCliSyntax { + + /* Prefix definitions */ + public static final Prefix PREFIX_DESCRIPTION = new Prefix("d/"); + public static final Prefix PREFIX_DUE_DATE = new Prefix("dd/"); + public static final Prefix PREFIX_TASK_NUMBER = new Prefix("t/"); + +} diff --git a/src/main/java/seedu/address/logic/parser/task/TaskParserUtil.java b/src/main/java/seedu/address/logic/parser/task/TaskParserUtil.java new file mode 100644 index 00000000000..3a4df78014b --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/task/TaskParserUtil.java @@ -0,0 +1,51 @@ +package seedu.address.logic.parser.task; + +import static java.util.Objects.requireNonNull; + +import java.util.Optional; + +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.task.DateTimeDue; +import seedu.address.model.task.Description; + +/** + * Contains utility methods used for parsing strings in the various task-related Parser classes. + */ +public class TaskParserUtil { + + /** + * Parses a {@code String description} into a {@code Description}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given description is invalid. + */ + public static Description parseDescription(String description) throws ParseException { + requireNonNull(description); + String trimmedDescription = description.trim(); + if (!Description.isValidDescription(trimmedDescription)) { + throw new ParseException(Description.MESSAGE_CONSTRAINTS); + } + return new Description(trimmedDescription); + } + + /** + * Parses a {@code Optional optionalDueAt} into a {@code DateTimeDue}. + * {@code optionalDueAt} can be empty but must not be null. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given date-time string is invalid. + */ + public static DateTimeDue parseDateTimeDue(Optional optionalDueAt) throws ParseException { + requireNonNull(optionalDueAt); + boolean isValid = optionalDueAt + .map(String::trim) + .map(DateTimeDue::isValidDateTimeDue) + .orElse(true); // empty Optional considered valid + + if (!isValid) { + throw new ParseException(DateTimeDue.MESSAGE_CONSTRAINTS); + } + + return new DateTimeDue(optionalDueAt); + } +} diff --git a/src/main/java/seedu/address/model/AddressBook.java b/src/main/java/seedu/address/model/AddressBook.java deleted file mode 100644 index 1a943a0781a..00000000000 --- a/src/main/java/seedu/address/model/AddressBook.java +++ /dev/null @@ -1,120 +0,0 @@ -package seedu.address.model; - -import static java.util.Objects.requireNonNull; - -import java.util.List; - -import javafx.collections.ObservableList; -import seedu.address.model.person.Person; -import seedu.address.model.person.UniquePersonList; - -/** - * Wraps all data at the address-book level - * Duplicates are not allowed (by .isSamePerson comparison) - */ -public class AddressBook implements ReadOnlyAddressBook { - - private final UniquePersonList persons; - - /* - * The 'unusual' code block below is a non-static initialization block, sometimes used to avoid duplication - * between constructors. See https://docs.oracle.com/javase/tutorial/java/javaOO/initial.html - * - * Note that non-static init blocks are not recommended to use. There are other ways to avoid duplication - * among constructors. - */ - { - persons = new UniquePersonList(); - } - - public AddressBook() {} - - /** - * Creates an AddressBook using the Persons in the {@code toBeCopied} - */ - public AddressBook(ReadOnlyAddressBook toBeCopied) { - this(); - resetData(toBeCopied); - } - - //// list overwrite operations - - /** - * Replaces the contents of the person list with {@code persons}. - * {@code persons} must not contain duplicate persons. - */ - public void setPersons(List persons) { - this.persons.setPersons(persons); - } - - /** - * Resets the existing data of this {@code AddressBook} with {@code newData}. - */ - public void resetData(ReadOnlyAddressBook newData) { - requireNonNull(newData); - - setPersons(newData.getPersonList()); - } - - //// person-level operations - - /** - * Returns true if a person with the same identity as {@code person} exists in the address book. - */ - public boolean hasPerson(Person person) { - requireNonNull(person); - return persons.contains(person); - } - - /** - * Adds a person to the address book. - * The person must not already exist in the address book. - */ - public void addPerson(Person p) { - persons.add(p); - } - - /** - * Replaces the given person {@code target} in the list with {@code editedPerson}. - * {@code target} must exist in the address book. - * The person identity of {@code editedPerson} must not be the same as another existing person in the address book. - */ - public void setPerson(Person target, Person editedPerson) { - requireNonNull(editedPerson); - - persons.setPerson(target, editedPerson); - } - - /** - * Removes {@code key} from this {@code AddressBook}. - * {@code key} must exist in the address book. - */ - public void removePerson(Person key) { - persons.remove(key); - } - - //// util methods - - @Override - public String toString() { - return persons.asUnmodifiableObservableList().size() + " persons"; - // TODO: refine later - } - - @Override - public ObservableList getPersonList() { - return persons.asUnmodifiableObservableList(); - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof AddressBook // instanceof handles nulls - && persons.equals(((AddressBook) other).persons)); - } - - @Override - public int hashCode() { - return persons.hashCode(); - } -} diff --git a/src/main/java/seedu/address/model/Model.java b/src/main/java/seedu/address/model/Model.java index d54df471c1f..14e0e79e157 100644 --- a/src/main/java/seedu/address/model/Model.java +++ b/src/main/java/seedu/address/model/Model.java @@ -1,18 +1,29 @@ package seedu.address.model; import java.nio.file.Path; +import java.util.Optional; +import java.util.PriorityQueue; import java.util.function.Predicate; import javafx.collections.ObservableList; import seedu.address.commons.core.GuiSettings; -import seedu.address.model.person.Person; +import seedu.address.commons.core.index.Index; +import seedu.address.model.patient.Name; +import seedu.address.model.patient.Patient; +import seedu.address.model.room.Room; +import seedu.address.model.room.RoomTaskAssociation; +import seedu.address.model.task.Task; /** * The API of the Model component. */ public interface Model { /** {@code Predicate} that always evaluate to true */ - Predicate PREDICATE_SHOW_ALL_PERSONS = unused -> true; + Predicate PREDICATE_SHOW_ALL_PATIENTS = unused -> true; + + Predicate PREDICATE_SHOW_ALL_ROOMS = unused -> true; + + Predicate PREDICATE_SHOW_ALL_TASKS = unused -> true; /** * Replaces user prefs data with the data in {@code userPrefs}. @@ -35,53 +46,226 @@ public interface Model { void setGuiSettings(GuiSettings guiSettings); /** - * Returns the user prefs' address book file path. + * Returns the user prefs' covigent app file path. + */ + Path getCovigentAppFilePath(); + + /** + * Sets the user prefs' covigent app file path. + */ + void setCovigentAppFilePath(Path covigentAppFilePath); + + /** + * Replaces patient records with the data in {@code covigentApp}. + */ + void setPatientRecords(ReadOnlyList patientRecords); + + /** Returns the patient records */ + ReadOnlyList getPatientRecords(); + + /** + * Replaces room list with the data in {@code covigentApp}. */ - Path getAddressBookFilePath(); + void setRoomList(ReadOnlyList rooms); /** - * Sets the user prefs' address book file path. + * Returns the room task records. */ - void setAddressBookFilePath(Path addressBookFilePath); + ReadOnlyList getRoomTaskRecords(); /** - * Replaces address book data with the data in {@code addressBook}. + * Returns true if a patient with the same identity as {@code patient} exists in the patient records. */ - void setAddressBook(ReadOnlyAddressBook addressBook); + boolean hasPatient(Patient patient); - /** Returns the AddressBook */ - ReadOnlyAddressBook getAddressBook(); + void setInitNumOfRooms(int numOfRooms); + /** + * Returns the patient with the {@code nameOfPatient} if it exists. Otherwise, an empty optional + * is returned. + */ + Optional getPatientWithName(Name nameOfPatient); /** - * Returns true if a person with the same identity as {@code person} exists in the address book. + * Deletes the given patient. + * The patient must exist in the patient records. */ - boolean hasPerson(Person person); + void deletePatient(Patient target); /** - * Deletes the given person. - * The person must exist in the address book. + * Adds the given patient. + * {@code patient} must not already exist in the patient records. */ - void deletePerson(Person target); + void addPatient(Patient patient); /** - * Adds the given person. - * {@code person} must not already exist in the address book. + * Replaces the given patient {@code target} with {@code editedPatient}. + * {@code target} must exist in the patient records. + * The patient identity of {@code editedPatient} must not be the same as + * another existing patient in the patient records. */ - void addPerson(Person person); + void setPatient(Patient target, Patient editedPatient); /** - * Replaces the given person {@code target} with {@code editedPerson}. - * {@code target} must exist in the address book. - * The person identity of {@code editedPerson} must not be the same as another existing person in the address book. + * Checks if patient is already assigned to a room. + * + * @param name Of the patient. + * @return Boolean value of whether patient is already assigned. */ - void setPerson(Person target, Person editedPerson); + boolean isPatientAssignedToRoom(Name name); - /** Returns an unmodifiable view of the filtered person list */ - ObservableList getFilteredPersonList(); + /** + * Returns an unmodifiable view of the list of {@code Patient} backed by the internal list of + * {@code UniquePatientList}. + */ + ObservableList getFilteredPatientList(); /** - * Updates the filter of the filtered person list to filter by the given {@code predicate}. + * Updates the filter of the filtered patient list to filter by the given {@code predicate}. * @throws NullPointerException if {@code predicate} is null. */ - void updateFilteredPersonList(Predicate predicate); + void updateFilteredPatientList(Predicate predicate); + + /** + * Returns total number of rooms in the application's {@code RoomList}. + */ + int getNumOfRooms(); + + /** + * @param num is the number of rooms to define in a hotel. + */ + void initRooms(int num); + + /** + * Returns whether a decrease in number of rooms would have space for existing rooms + */ + boolean hasSpaceForRooms(); + + /** + * Returns number of occupied rooms whose room number is more than the number of rooms to be defined + * using initRoom command + */ + int getNumOfExcessOccupiedRooms(); + /** + * Returns true if a room with the same identity as {@code room} exists in Covigent. + * + * @param room The room . + * @return true if {@code room} is in Covigent; false otherwise. + */ + boolean hasRoom(Room room); + + /** + * Returns the room with the {@code roomNumber} if it exists. Otherwise, an empty optional + * is returned. + */ + Optional getRoomWithRoomNumber(int roomNumber); + + /** + * sets specified room to roomList + */ + void setRoom(Room room); + /** + * Replaces the given room {@code target} with {@code editedRoom}. + * {@code target} must exist in the application. + * The room identity of {@code editedRoom} must not be the same as + * another existing room in the application. + * + * @param target Of the room to be changed. + * @param editedRoom Is the newly edited room. + */ + void setSingleRoom(Room target, Room editedRoom); + + /** + * Removes the patient with the given name {@code patientName} from the room. + */ + void removePatientFromRoom(Name patientName); + + /** + * Checks if the given room number is present in the application. + * + * @param roomNumber to check if it is in the application. + * @return Index Of room that is found. + */ + Index checkIfRoomPresent(Integer roomNumber); + + /** + * Updates the room with the new edited patient. + * + * @param patientToEdit Patient that is to be edited. + * @param editedPatient Patient that is edited. + */ + void updateRoomListWhenPatientsChanges(Patient patientToEdit, Patient editedPatient); + + /** + * Returns an unmodifiable view of the list of {@code Room} backed by the internal list of + * {@code RoomList}. + */ + ObservableList getRoomListObservableList(); + + RoomList getModifiableRoomList(); + + /** + * Returns an unmodifiable view of the list of {@code Room} backed by the internal list of + * {@code RoomList}. + */ + ObservableList getFilteredRoomList(); + + /** + * Updates the filter of the filtered rooms to filter by the given {@code predicate}. + * @throws NullPointerException if {@code predicate} is null. + */ + void updateFilteredRoomList(Predicate predicate); + + /** + * Returns Priority Queue of rooms + */ + PriorityQueue getRooms(); + + /** + * Returns the task with the {@code taskIndex} in {code room} if it exists. + * Otherwise, an empty optional is returned. + */ + Optional getTaskFromRoomWithTaskIndex(Index taskIndex, Room room); + + /** + * Adds {@code task} to {@code room}. + * The room must exist in {@code CovigentApp}. + * + * @param task The task to add. + * @param room The room to which the task should be added. + */ + void addTaskToRoom(Task task, Room room); + + /** + * Deletes {@code task} from {@code room}. + * The room must exist in Covigent. + * The task must exist in room. + * + * @param task The task to delete. + * @param room The room from which the task should be delete. + */ + void deleteTaskFromRoom(Task task, Room room); + + /** + * Replaces the given task {@code target} with {@code editedTask}. + * The room must exist in Covigent. + * The task must exist in room. + * + * @param target The task to replace. + * @param editedTask The modified task to replace the original. + * @param room The room from which the task should be replaced. + */ + void setTaskToRoom(Task target, Task editedTask, Room room); + + /** + * Update the tasks in {@code filteredRoomTaskRecords} with {@code taskPredicate}. + * + * @param taskPredicate The predicate to filter the tasks. + */ + void updateTasksInFilteredRoomTaskRecords(Predicate taskPredicate); + + /** + * Returns an unmodifiable view of the list of {@code RoomTaskAssociation} backed by the + * internal list of {@code RoomTaskRecords}. + */ + ObservableList getFilteredRoomTaskRecords(); } diff --git a/src/main/java/seedu/address/model/ModelManager.java b/src/main/java/seedu/address/model/ModelManager.java index 0650c954f5c..e9443c3aa14 100644 --- a/src/main/java/seedu/address/model/ModelManager.java +++ b/src/main/java/seedu/address/model/ModelManager.java @@ -4,6 +4,8 @@ import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; import java.nio.file.Path; +import java.util.Optional; +import java.util.PriorityQueue; import java.util.function.Predicate; import java.util.logging.Logger; @@ -11,7 +13,12 @@ import javafx.collections.transformation.FilteredList; import seedu.address.commons.core.GuiSettings; import seedu.address.commons.core.LogsCenter; -import seedu.address.model.person.Person; +import seedu.address.commons.core.index.Index; +import seedu.address.model.patient.Name; +import seedu.address.model.patient.Patient; +import seedu.address.model.room.Room; +import seedu.address.model.room.RoomTaskAssociation; +import seedu.address.model.task.Task; /** * Represents the in-memory model of the address book data. @@ -19,29 +26,46 @@ public class ModelManager implements Model { private static final Logger logger = LogsCenter.getLogger(ModelManager.class); - private final AddressBook addressBook; + private final PatientRecords patientRecords; + private final RoomList roomList; + private final RoomTaskRecords roomTaskRecords; private final UserPrefs userPrefs; - private final FilteredList filteredPersons; + private final FilteredList filteredPatients; + private final FilteredList filteredRooms; + private final FilteredList filteredRoomTaskRecords; /** - * Initializes a ModelManager with the given addressBook and userPrefs. + * Initializes a ModelManager with the given patient records, room records and userPrefs. */ - public ModelManager(ReadOnlyAddressBook addressBook, ReadOnlyUserPrefs userPrefs) { + public ModelManager(ReadOnlyList patientRecords, ReadOnlyList roomList, + ReadOnlyUserPrefs userPrefs) { super(); - requireAllNonNull(addressBook, userPrefs); + requireAllNonNull(patientRecords, userPrefs); - logger.fine("Initializing with address book: " + addressBook + " and user prefs " + userPrefs); + logger.fine("Initializing with Covigent App: " + patientRecords + " and user prefs " + userPrefs); - this.addressBook = new AddressBook(addressBook); + this.patientRecords = new PatientRecords(patientRecords); + this.roomList = new RoomList(roomList); this.userPrefs = new UserPrefs(userPrefs); - filteredPersons = new FilteredList<>(this.addressBook.getPersonList()); + + RoomTaskRecords theRoomTaskRecords; + try { + theRoomTaskRecords = RoomTaskRecords.getInstance(); + } catch (AssertionError e) { // need to first initialize RoomTasksRecords + theRoomTaskRecords = RoomTaskRecords.init(this.roomList.getReadOnlyList()); + } + + roomTaskRecords = theRoomTaskRecords; + filteredPatients = new FilteredList<>(this.patientRecords.getReadOnlyList()); + filteredRooms = new FilteredList<>(this.roomList.getReadOnlyList()); + filteredRoomTaskRecords = new FilteredList<>(this.roomTaskRecords.getReadOnlyList()); } public ModelManager() { - this(new AddressBook(), new UserPrefs()); + this(new PatientRecords(), new RoomList(), new UserPrefs()); } - //=========== UserPrefs ================================================================================== + //=========== UserPrefs ================================================================================= @Override public void setUserPrefs(ReadOnlyUserPrefs userPrefs) { @@ -66,69 +90,275 @@ public void setGuiSettings(GuiSettings guiSettings) { } @Override - public Path getAddressBookFilePath() { - return userPrefs.getAddressBookFilePath(); + public Path getCovigentAppFilePath() { + return userPrefs.getCovigentAppFilePath(); } @Override - public void setAddressBookFilePath(Path addressBookFilePath) { - requireNonNull(addressBookFilePath); - userPrefs.setAddressBookFilePath(addressBookFilePath); + public void setCovigentAppFilePath(Path covigentAppFilePath) { + requireNonNull(covigentAppFilePath); + userPrefs.setCovigentAppFilePath(covigentAppFilePath); } - //=========== AddressBook ================================================================================ + //=========== Patient Records =========================================================================== @Override - public void setAddressBook(ReadOnlyAddressBook addressBook) { - this.addressBook.resetData(addressBook); + public void setPatientRecords(ReadOnlyList patientRecords) { + this.patientRecords.resetData(patientRecords); } @Override - public ReadOnlyAddressBook getAddressBook() { - return addressBook; + public ReadOnlyList getPatientRecords() { + return patientRecords; } + //=========== RoomList ================================================================================== + @Override - public boolean hasPerson(Person person) { - requireNonNull(person); - return addressBook.hasPerson(person); + public void setRoomList(ReadOnlyList rooms) { + this.roomList.resetData(rooms); } + //=========== RoomTaskRecords =========================================================================== + @Override - public void deletePerson(Person target) { - addressBook.removePerson(target); + public ReadOnlyList getRoomTaskRecords() { + return roomTaskRecords; } + //=========== Patients ================================================================================== + @Override - public void addPerson(Person person) { - addressBook.addPerson(person); - updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + public boolean hasPatient(Patient patient) { + requireNonNull(patient); + return patientRecords.hasPatient(patient); } + //@@author chiamyunqing @Override - public void setPerson(Person target, Person editedPerson) { - requireAllNonNull(target, editedPerson); + public Optional getPatientWithName(Name nameOfPatient) { + requireNonNull(nameOfPatient); + return patientRecords.getPatientWithName(nameOfPatient); + } - addressBook.setPerson(target, editedPerson); + @Override + public void deletePatient(Patient target) { + patientRecords.removePatient(target); + //model's responsibility to update room list when patient is updated + this.updateRoomListWhenPatientsChanges(target, null); } + //@@author chiamyunqing - //=========== Filtered Person List Accessors ============================================================= + @Override + public void addPatient(Patient patient) { + updateFilteredPatientList(PREDICATE_SHOW_ALL_PATIENTS); + patientRecords.addPatient(patient); + } + + @Override + public void setPatient(Patient target, Patient editedPatient) { + requireAllNonNull(target, editedPatient); + patientRecords.setPatient(target, editedPatient); + } + + //@@author LeeMingDe + @Override + public boolean isPatientAssignedToRoom(Name name) { + requireNonNull(name); + for (Room room : roomList.getRoomObservableList()) { + if (room.getPatient().isPresent()) { + Name patientNameInRoom = room.getPatient().get().getName(); + if (patientNameInRoom.equals(name)) { + return true; + } + } + } + return false; + } + //@@author LeeMingDe + + //=========== Filtered Patient List Accessors =========================================================== + + @Override + public ObservableList getFilteredPatientList() { + return filteredPatients; + } + + @Override + public void updateFilteredPatientList(Predicate predicate) { + requireNonNull(predicate); + filteredPatients.setPredicate(predicate); + } + + //=========== Room List ================================================================================= + + //@@author itssodium + @Override + public int getNumOfExcessOccupiedRooms() { + return roomList.getNumOfExcessOccupiedRooms(); + } + @Override + public boolean hasSpaceForRooms() { + return roomList.hasSpaceForRooms(); + } + @Override + public int getNumOfRooms() { + return roomList.getNumOfRooms(); + } + @Override + public void setInitNumOfRooms(int numOfRooms) { + roomList.setPreferredNumOfRooms(numOfRooms); + } + @Override + public void initRooms(int num) { + roomList.initRooms(num); + } + @Override + public void setRoom(Room room) { + roomList.setRoom(room); + } + //@@author itssodium + //@@author LeeMingDe + @Override + public boolean hasRoom(Room room) { + requireNonNull(room); + return roomList.containsRoom(room); + } + + @Override + public void setSingleRoom(Room target, Room editedRoom) { + requireAllNonNull(target, editedRoom); + roomList.setSingleRoom(target, editedRoom); + } + //@@author LeeMingDe + + //@@author chiamyunqing + @Override + public void removePatientFromRoom(Name patientName) { + assert (isPatientAssignedToRoom(patientName)); + roomList.removePatientFromRoom(patientName); + } + //@@author chiamyunqing + + //@@author LeeMingDe + @Override + public Index checkIfRoomPresent(Integer roomNumber) { + ObservableList roomObservableList = this.getRoomListObservableList(); + Index index = Index.fromZeroBased(0); + for (int i = 1; i <= roomObservableList.size(); i++) { + int roomNum = roomObservableList.get(i - 1).getRoomNumber(); + boolean isValidRoom = (Integer.valueOf(roomNum)).equals(roomNumber); + if (isValidRoom) { + index = Index.fromZeroBased(i); + break; + } + } + return index; + } + + @Override + public void updateRoomListWhenPatientsChanges(Patient patientToEdit, Patient editedPatient) { + requireNonNull(patientToEdit); + ObservableList roomObservableList = this.roomList.getRoomObservableList(); + for (int i = 0; i < roomObservableList.size(); i++) { + Optional patient = roomObservableList.get(i).getPatient(); + if (isPatientAssignedToRoom(patientToEdit.getName()) && patient.isPresent() + && patient.get().isSamePatient(patientToEdit)) { + Room updatedRoom = roomObservableList.get(i); + if (editedPatient == null) { + updatedRoom.setOccupied(false); + } + updatedRoom.setPatient(editedPatient); + roomObservableList.set(i, updatedRoom); + break; + } + } + } + //@@author LeeMingDe + + //@@author w-yeehong + @Override + public Optional getRoomWithRoomNumber(int roomNumber) { + assert (roomNumber > 0) : "Room number should be greater than 0."; + return roomList.getRoomWithRoomNumber(roomNumber); + } + //@@author w-yeehong + + //=========== Filtered RoomList Accessors =============================================================== + + @Override + public ObservableList getRoomListObservableList() { + return roomList.getReadOnlyList(); + } + + @Override + public RoomList getModifiableRoomList() { + return roomList; + } + + @Override + public PriorityQueue getRooms() { + return this.getModifiableRoomList().getRooms(); + } - /** - * Returns an unmodifiable view of the list of {@code Person} backed by the internal list of - * {@code versionedAddressBook} - */ @Override - public ObservableList getFilteredPersonList() { - return filteredPersons; + public ObservableList getFilteredRoomList() { + return filteredRooms; } @Override - public void updateFilteredPersonList(Predicate predicate) { + public void updateFilteredRoomList(Predicate predicate) { requireNonNull(predicate); - filteredPersons.setPredicate(predicate); + filteredRooms.setPredicate(predicate); + + } + + //=========== Tasks ===================================================================================== + + //@@author w-yeehong + @Override + public Optional getTaskFromRoomWithTaskIndex(Index taskIndex, Room room) { + requireAllNonNull(taskIndex, room); + return room.getTaskWithTaskIndex(taskIndex); + } + + @Override + public void addTaskToRoom(Task task, Room room) { + requireAllNonNull(task, room); + assert roomList.containsRoom(room) : "Room must be one of the rooms in the RoomList."; + room.addTask(task); + } + + @Override + public void deleteTaskFromRoom(Task task, Room room) { + requireAllNonNull(task, room); + assert roomList.containsRoom(room) : "Room must be one of the rooms in the RoomList."; + room.deleteTask(task); } + @Override + public void setTaskToRoom(Task target, Task editedTask, Room room) { + requireAllNonNull(target, editedTask, room); + assert roomList.containsRoom(room) : "Room must be one of the rooms in the RoomList."; + room.setTask(target, editedTask); + } + //@@author w-yeehong + + //=========== Filtered RoomTaskRecords Accessors ======================================================== + + @Override + public void updateTasksInFilteredRoomTaskRecords(Predicate taskPredicate) { + requireNonNull(taskPredicate); + filteredRoomTaskRecords.setPredicate(roomTaskAssociation -> taskPredicate.test(roomTaskAssociation.getTask())); + } + + @Override + public ObservableList getFilteredRoomTaskRecords() { + return filteredRoomTaskRecords; + } + + //=========== Miscellaneous ============================================================================= + @Override public boolean equals(Object obj) { // short circuit if same object @@ -143,9 +373,11 @@ public boolean equals(Object obj) { // state check ModelManager other = (ModelManager) obj; - return addressBook.equals(other.addressBook) + + return patientRecords.equals(other.patientRecords) + && roomList.equals(other.roomList) && userPrefs.equals(other.userPrefs) - && filteredPersons.equals(other.filteredPersons); + && filteredPatients.equals(other.filteredPatients) + && filteredRooms.equals(other.filteredRooms); } - } diff --git a/src/main/java/seedu/address/model/PatientRecords.java b/src/main/java/seedu/address/model/PatientRecords.java new file mode 100644 index 00000000000..3518642b0ae --- /dev/null +++ b/src/main/java/seedu/address/model/PatientRecords.java @@ -0,0 +1,136 @@ +package seedu.address.model; + +import static java.util.Objects.requireNonNull; + +import java.util.List; +import java.util.Optional; + +import javafx.collections.ObservableList; +import seedu.address.model.patient.Name; +import seedu.address.model.patient.Patient; +import seedu.address.model.patient.UniquePatientList; + +/** + * Wraps all data at the app level + * Duplicate patients are not allowed (by .isSamePatient comparison) + */ +public class PatientRecords implements ReadOnlyList { + + private final UniquePatientList patients; + + /* + * 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. + */ + { + patients = new UniquePatientList(); + } + + public PatientRecords() {} + + /** + * Creates an CovigentApp using the Patients in the {@code toBeCopied} + */ + public PatientRecords(ReadOnlyList toBeCopied) { + this(); + resetData(toBeCopied); + } + + //// list overwrite operations + + /** + * Replaces the contents of the patient list with {@code patients}. + * {@code patients} must not contain duplicate patients. + */ + public void setPatients(List patients) { + this.patients.setPatients(patients); + } + + /** + * Resets the existing data of this {@code PatientRecords} with {@code newData}. + */ + public void resetData(ReadOnlyList newData) { + requireNonNull(newData); + + setPatients(newData.getReadOnlyList()); + } + + //// patient-level operations + + /** + * Returns true if a patient with the same identity as {@code patient} exists in the patient records. + */ + public boolean hasPatient(Patient patient) { + requireNonNull(patient); + return patients.contains(patient); + } + + //@@author chiamyunqing + /** + * Returns the patient with the input name. + * @param name of patient to find. + * @return the patient with the name if it exists. + */ + public Optional getPatientWithName(Name name) { + requireNonNull(name); + return patients.getPatientWithName(name); + } + + //@@author + + /** + * Adds a patient to the address book. + * The patient must not already exist in the address book. + */ + public void addPatient(Patient p) { + patients.add(p); + } + + /** + * Replaces the given patient {@code target} in the list with {@code editedPatient}. + * {@code target} must exist in the address book. + * The patient identity of {@code editedPatient} must not be the same as another existing patient + * in the address book. + */ + public void setPatient(Patient target, Patient editedPatient) { + requireNonNull(editedPatient); + + patients.setPatient(target, editedPatient); + } + + /** + * Removes {@code key} from this {@code PatientRecords}. + * {@code key} must exist in the address book. + */ + public void removePatient(Patient key) { + patients.remove(key); + } + + //// util methods + + @Override + public String toString() { + return patients.asUnmodifiableObservableList().size() + " patients"; + // TODO: refine later + } + + @Override + public ObservableList getReadOnlyList() { + return patients.asUnmodifiableObservableList(); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof PatientRecords // instanceof handles nulls + && patients.equals(((PatientRecords) other).patients)); + } + + @Override + public int hashCode() { + return patients.hashCode(); + } +} diff --git a/src/main/java/seedu/address/model/ReadOnlyAddressBook.java b/src/main/java/seedu/address/model/ReadOnlyAddressBook.java deleted file mode 100644 index 6ddc2cd9a29..00000000000 --- a/src/main/java/seedu/address/model/ReadOnlyAddressBook.java +++ /dev/null @@ -1,17 +0,0 @@ -package seedu.address.model; - -import javafx.collections.ObservableList; -import seedu.address.model.person.Person; - -/** - * Unmodifiable view of an address book - */ -public interface ReadOnlyAddressBook { - - /** - * Returns an unmodifiable view of the persons list. - * This list will not contain any duplicate persons. - */ - ObservableList getPersonList(); - -} diff --git a/src/main/java/seedu/address/model/ReadOnlyList.java b/src/main/java/seedu/address/model/ReadOnlyList.java new file mode 100644 index 00000000000..ea4967836ee --- /dev/null +++ b/src/main/java/seedu/address/model/ReadOnlyList.java @@ -0,0 +1,15 @@ +package seedu.address.model; + +import javafx.collections.ObservableList; + +/** + * Unmodifiable view of a list. + */ +public interface ReadOnlyList { + + /** + * Returns an unmodifiable view of a list. + */ + ObservableList getReadOnlyList(); + +} diff --git a/src/main/java/seedu/address/model/ReadOnlyUserPrefs.java b/src/main/java/seedu/address/model/ReadOnlyUserPrefs.java index befd58a4c73..3f9f081c223 100644 --- a/src/main/java/seedu/address/model/ReadOnlyUserPrefs.java +++ b/src/main/java/seedu/address/model/ReadOnlyUserPrefs.java @@ -11,6 +11,6 @@ public interface ReadOnlyUserPrefs { GuiSettings getGuiSettings(); - Path getAddressBookFilePath(); + Path getCovigentAppFilePath(); } diff --git a/src/main/java/seedu/address/model/RoomList.java b/src/main/java/seedu/address/model/RoomList.java new file mode 100644 index 00000000000..9048bdeb129 --- /dev/null +++ b/src/main/java/seedu/address/model/RoomList.java @@ -0,0 +1,179 @@ +package seedu.address.model; + +import static java.util.Objects.requireNonNull; + +import java.util.Optional; +import java.util.PriorityQueue; +import java.util.logging.Logger; + +import javafx.collections.ObservableList; +import seedu.address.commons.core.LogsCenter; +import seedu.address.model.patient.Name; +import seedu.address.model.room.Room; +import seedu.address.model.room.UniqueRoomList; +import seedu.address.storage.patient.JsonPatientRecordsStorage; + +/** + * Contains information regarding the Room information + */ +//@@author itssodium +public class RoomList implements ReadOnlyList { + private static final Logger logger = LogsCenter.getLogger(JsonPatientRecordsStorage.class); + + private final UniqueRoomList rooms; + + /* + * 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. + */ + { + rooms = new UniqueRoomList(); + } + + public RoomList() {} + + /** + * Converts data from readOnlyRoomList to roomList + */ + public RoomList(ReadOnlyList readOnlyRoomList) { + this(); + resetData(readOnlyRoomList); + } + + public boolean hasSpaceForRooms() { + return rooms.hasSpaceForRooms(); + } + + public int getNumOfExcessOccupiedRooms() { + return rooms.getNumOfExcessOccupiedRooms(); + } + /** + * Resets the existing data of this {@code RoomList} with {@code newData}. + */ + public void resetData(ReadOnlyList readOnlyRoomList) { + rooms.resetData(readOnlyRoomList); + } + /** + * Returns Priority Queue of rooms + */ + public PriorityQueue getRooms() { + return rooms.getRooms(); + } + + /** + * Returns number of rooms in hotel + */ + public int getNumOfRooms() { + return rooms.getNumOfRooms(); + } + + public void setInitNumOfRooms(int numOfRooms) { + rooms.setPreferredNumOfRooms(numOfRooms); + } + + public ObservableList getRoomObservableList() { + return rooms.getRoomObservableList(); + } + + public void setRoom(Room room) { + rooms.setRoom(room); + } + /** + * Adds the number of the rooms in a hotel + * + * @param numOfRooms is the number of rooms to be added + */ + public void initRooms(int numOfRooms) { + rooms.initRooms(numOfRooms); + } + + /** + * Adds this room to the RoomList + * @param room is added to RoomList + */ + public void initRooms(Room room) { + rooms.initRooms(room); + } + + //@@author w-yeehong + /** + * Returns the room with the provided {@code roomNumber}. + * An empty optional is returned if such a room is not found in the {@code RoomList}. + * + * @param roomNumber The room number of the room. + * @return the optional-wrapped room if found, otherwise an empty optional + */ + public Optional getRoomWithRoomNumber(int roomNumber) { + assert (roomNumber > 0) : "Room number should be greater than 0."; + for (Room room : getReadOnlyList()) { + if (roomNumber == room.getRoomNumber()) { + return Optional.of(room); + } + } + return Optional.empty(); + } + //@@author w-yeehong + + /** + * Returns the backing list as an unmodifiable {@code ObservableList}. + */ + @Override + public ObservableList getReadOnlyList() { + return rooms.asUnmodifiableObservableList(); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof RoomList // instanceof handles nulls + && rooms.equals(((RoomList) other).rooms)); + } + + /** + * Returns true if the list contains an equivalent room as the given argument. + */ + public boolean containsRoom(Room toCheck) { + requireNonNull(toCheck); + return rooms.containsRoom(toCheck); + } + + //@@author chiamyunqing + /** + * Removes the patient with the given name {@code patientName} from the room. + */ + public void removePatientFromRoom(Name patientName) { + requireNonNull(patientName); + rooms.clearRoom(patientName); + } + + //@author LeeMingDe + /** + * Replaces the room {@code target} in the list with {@code editedRoom}. + * {@code target} must exist in the list. + * The room identity of {@code editedRoom} must not be the same as another existing room in the list. + * + * @param target Room to be changed. + * @param editedRoom Room that has been changed. + */ + public void setSingleRoom(Room target, Room editedRoom) { + rooms.setSingleRoom(target, editedRoom); + } + //@author LeeMingDe + + @Override + public int hashCode() { + return rooms.hashCode(); + } + + public void setPreferredNumOfRooms(int numOfRooms) { + rooms.setPreferredNumOfRooms(numOfRooms); + } + + public void setRooms(PriorityQueue rooms) { + this.rooms.setRooms(rooms); + } + +} diff --git a/src/main/java/seedu/address/model/RoomTaskRecords.java b/src/main/java/seedu/address/model/RoomTaskRecords.java new file mode 100644 index 00000000000..2fac3268546 --- /dev/null +++ b/src/main/java/seedu/address/model/RoomTaskRecords.java @@ -0,0 +1,119 @@ +package seedu.address.model; + +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Logger; + +import javafx.collections.FXCollections; +import javafx.collections.ListChangeListener; +import javafx.collections.ObservableList; +import seedu.address.commons.core.LogsCenter; +import seedu.address.model.room.Room; +import seedu.address.model.room.RoomTaskAssociation; +import seedu.address.model.task.Task; + +/** + * RoomTaskRecords exists mainly for the purpose of exposing an ObservableList for the user interface of + * {@code Task} to display the list of tasks in all {@code Room}. + * + * It stores a list of {@code RoomTaskAssociation}. {@code RoomTaskAssociation} is preferred to {@code Task} + * as it provides an API to quickly query room number and task index of a task. + * + * RoomTaskRecords follows the Singleton pattern. Solution adapted with modifications from: + * https://stackoverflow.com/questions/1050991/singleton-with-arguments-in-java + */ +public class RoomTaskRecords implements ReadOnlyList { + + private static RoomTaskRecords theOne = null; + + private static final Logger logger = LogsCenter.getLogger(RoomTaskRecords.class); + + private final ObservableList internalList = FXCollections.observableArrayList(); + private final ObservableList internalUnmodifiableList = + FXCollections.unmodifiableObservableList(internalList); + + private RoomTaskRecords() { // set to private to ensure that creation is done through init(...) method + } + + /** + * Returns the RoomTaskRecords. + * + * @throws AssertionError if RoomTaskRecords has not been initialized. + */ + public static RoomTaskRecords getInstance() { + if (theOne == null) { + throw new AssertionError("RoomTaskRecords has to be initialized first."); + } + + return theOne; + } + + /** + * Creates and initializes the RoomTaskRecords. The information about the rooms and the tasks in + * those rooms are obtained from {@code roomList}. A listener is attached to {@code roomList} to + * ensure that the associations between rooms and tasks are synchronized even when there are + * changes to {@code roomList}. + * + * Only one instance of RoomTaskRecords can be created. + * + * @param roomList The list of rooms from which to create the associations between room and task. + * @return The singleton instance of RoomTaskRecords. + * @throw AssertionError if RoomTaskRecords has already been initialized + */ + public static synchronized RoomTaskRecords init(ObservableList roomList) { + if (theOne != null) { + throw new AssertionError("RoomTaskRecords has already been initialized."); + } + + logger.finer("Creating RoomTaskRecords and attaching a listener to refresh room-task associations..."); + + theOne = new RoomTaskRecords(); + theOne.createRoomTaskAssociations(roomList); + theOne.updateAssociationIfChanged(roomList); + + logger.finer("Successfully attached listener to RoomTaskRecords."); + + return theOne; + } + + /** + * Creates the associations between each room and all the tasks in the rooms. + * These associations are stored into {@code internalList}. + */ + private void createRoomTaskAssociations(ObservableList roomList) { + List associations = new ArrayList<>(); + + for (Room room : roomList) { + int taskIndex = 1; + for (Task task : room.getReadOnlyTasks()) { + associations.add(new RoomTaskAssociation(room, task, taskIndex)); + taskIndex++; + } + } + + internalList.setAll(associations); + } + + /** + * Adds a listener to {@code roomList} to recreate all {@code RoomTaskAssociation} whenever + * there is a change. + * + * This ensures that the associations are properly synchronized and the task indexes are correct. + */ + private void updateAssociationIfChanged(ObservableList roomList) { + roomList.addListener(new ListChangeListener() { + @Override + public void onChanged(Change change) { + while (change.next()) { + logger.fine("Changes detected in list of rooms. Updating room-task associations..."); + createRoomTaskAssociations(roomList); + } + } + }); + } + + @Override + public ObservableList getReadOnlyList() { + return internalUnmodifiableList; + } +} diff --git a/src/main/java/seedu/address/model/UserPrefs.java b/src/main/java/seedu/address/model/UserPrefs.java index 25a5fd6eab9..6fbbaa03852 100644 --- a/src/main/java/seedu/address/model/UserPrefs.java +++ b/src/main/java/seedu/address/model/UserPrefs.java @@ -14,8 +14,10 @@ public class UserPrefs implements ReadOnlyUserPrefs { private GuiSettings guiSettings = new GuiSettings(); - private Path addressBookFilePath = Paths.get("data" , "addressbook.json"); - + private Path covigentAppFilePath = Paths.get("data" , "covigentapp.json"); + private Path numberOfRoomsFilePath = Paths.get("data", "numberOfRooms.txt"); + private Path roomsOccupiedFilePath = Paths.get("data", "roomInformation.json"); + private Path taskFilePath = Paths.get("data", "task.json"); /** * Creates a {@code UserPrefs} with default values. */ @@ -35,7 +37,7 @@ public UserPrefs(ReadOnlyUserPrefs userPrefs) { public void resetData(ReadOnlyUserPrefs newUserPrefs) { requireNonNull(newUserPrefs); setGuiSettings(newUserPrefs.getGuiSettings()); - setAddressBookFilePath(newUserPrefs.getAddressBookFilePath()); + setCovigentAppFilePath(newUserPrefs.getCovigentAppFilePath()); } public GuiSettings getGuiSettings() { @@ -47,13 +49,25 @@ public void setGuiSettings(GuiSettings guiSettings) { this.guiSettings = guiSettings; } - public Path getAddressBookFilePath() { - return addressBookFilePath; + public Path getCovigentAppFilePath() { + return covigentAppFilePath; + } + + public void setCovigentAppFilePath(Path covigentAppFilePath) { + requireNonNull(covigentAppFilePath); + this.covigentAppFilePath = covigentAppFilePath; + } + + public Path getNumberOfRoomsFilePath() { + return numberOfRoomsFilePath; + } + + public Path getRoomsOccupiedFilePath() { + return roomsOccupiedFilePath; } - public void setAddressBookFilePath(Path addressBookFilePath) { - requireNonNull(addressBookFilePath); - this.addressBookFilePath = addressBookFilePath; + public Path getTaskOccupiedFilePath() { + return taskFilePath; } @Override @@ -68,19 +82,19 @@ public boolean equals(Object other) { UserPrefs o = (UserPrefs) other; return guiSettings.equals(o.guiSettings) - && addressBookFilePath.equals(o.addressBookFilePath); + && covigentAppFilePath.equals(o.covigentAppFilePath); } @Override public int hashCode() { - return Objects.hash(guiSettings, addressBookFilePath); + return Objects.hash(guiSettings, covigentAppFilePath); } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append("Gui Settings : " + guiSettings); - sb.append("\nLocal data file location : " + addressBookFilePath); + sb.append("\nLocal data file location : " + covigentAppFilePath); return sb.toString(); } diff --git a/src/main/java/seedu/address/model/patient/Age.java b/src/main/java/seedu/address/model/patient/Age.java new file mode 100644 index 00000000000..fa5fa2d10ab --- /dev/null +++ b/src/main/java/seedu/address/model/patient/Age.java @@ -0,0 +1,100 @@ +package seedu.address.model.patient; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +//@@author chiamyunqing +/** + * Represents a Patient's age recorded in the app. + * A valid patient age is between 0 (inclusive) and 120 (exclusive), + * as declared in {@link #isValidAge(String)}. + * If the age is invalid, the corresponding error message should be retrieved + * via {@link #getErrorMessage(String)}. + * Guarantees: immutable. + */ +public class Age { + + public static final String MESSAGE_AGE_NOT_NUMBER = + "Age should only contain numbers."; + + public static final String MESSAGE_INVALID_AGE_RANGE = + "Age should be between the range 0 (inclusive) to 120 (exclusive)."; + + public static final String VALIDATION_REGEX = "\\d{1,3}"; + private static final int MAX_AGE = 120; + private final int value; + + /** + * Constructs a {@code Age}. + * + * @param age A valid age. + */ + public Age(String age) { + requireNonNull(age); + checkArgument(isValidAge(age), MESSAGE_AGE_NOT_NUMBER); + value = Integer.parseInt(age); + } + + /** + * Returns true if a given string matches the validation regex. + */ + private static boolean isRegexCorrect(String test) { + return test.matches(VALIDATION_REGEX); + } + + /** + * Returns true if a given string is within the age range 0 (inclusive) to + * 120 (exclusive). + */ + private static boolean isWithinValidAgeRange(String test) { + assert(isRegexCorrect(test)); + return (Integer.parseInt(test) >= 0) && (Integer.parseInt(test) < MAX_AGE); + } + + /** + * Returns true if a given string is a valid age. + */ + public static boolean isValidAge(String test) { + return isRegexCorrect(test) && isWithinValidAgeRange(test); + } + + /** + * Returns the precise error message according to the error that arises. + * This method should only be called if isValidAge(test) returns false. + * + * @param test the string with error in converting to age. + * @return the exact error message that corresponds to the error. + */ + public static String getErrorMessage(String test) { + assert(!isValidAge(test)); + if (!isRegexCorrect(test)) { + return MESSAGE_AGE_NOT_NUMBER; + } else { + return MESSAGE_INVALID_AGE_RANGE; + } + } + + /** + * Returns the value of the age. + */ + public int getValue() { + return this.value; + } + + @Override + public String toString() { + return Integer.toString(value); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Age // instanceof handles nulls + && value == ((Age) other).getValue()); // state check + } + + @Override + public int hashCode() { + return Integer.toString(value).hashCode(); + } +} diff --git a/src/main/java/seedu/address/model/patient/Comment.java b/src/main/java/seedu/address/model/patient/Comment.java new file mode 100644 index 00000000000..efa4f286c4e --- /dev/null +++ b/src/main/java/seedu/address/model/patient/Comment.java @@ -0,0 +1,40 @@ +package seedu.address.model.patient; + +import static java.util.Objects.requireNonNull; + +//@@author chiamyunqing +/** + * Represents important comments of the Patient in the app. + * Guarantees: immutable; is an optional attribute of patient. + */ +public class Comment { + public final String value; + + /** + * Constructs a {@code Comment}. + * + * @param comment A comment that is represented by any string. + */ + public Comment(String comment) { + requireNonNull(comment); + assert(comment.length() != 0); + value = comment; + } + + @Override + public String toString() { + return value; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Comment // instanceof handles nulls + && value.equals(((Comment) other).value)); // state check + } + + @Override + public int hashCode() { + return value.hashCode(); + } +} diff --git a/src/main/java/seedu/address/model/person/Name.java b/src/main/java/seedu/address/model/patient/Name.java similarity index 74% rename from src/main/java/seedu/address/model/person/Name.java rename to src/main/java/seedu/address/model/patient/Name.java index 79244d71cf7..57cd8f69827 100644 --- a/src/main/java/seedu/address/model/person/Name.java +++ b/src/main/java/seedu/address/model/patient/Name.java @@ -1,23 +1,23 @@ -package seedu.address.model.person; +package seedu.address.model.patient; import static java.util.Objects.requireNonNull; import static seedu.address.commons.util.AppUtil.checkArgument; /** - * Represents a Person's name in the address book. + * Represents a Patient's name in the app. * Guarantees: immutable; is valid as declared in {@link #isValidName(String)} */ public class Name { public static final String MESSAGE_CONSTRAINTS = - "Names should only contain alphanumeric characters and spaces, and it should not be blank"; - - /* + "Names should only contain alphanumeric characters and spaces, and it should not be blank.\n" + + "The maximum length of name is set to 150 characters."; + /** * The first character of the address must not be a whitespace, * otherwise " " (a blank string) becomes a valid input. */ public static final String VALIDATION_REGEX = "[\\p{Alnum}][\\p{Alnum} ]*"; - + private static final int MAX_NAME_LENGTH = 150; public final String fullName; /** @@ -35,7 +35,8 @@ public Name(String name) { * Returns true if a given string is a valid name. */ public static boolean isValidName(String test) { - return test.matches(VALIDATION_REGEX); + return test.matches(VALIDATION_REGEX) + && test.length() <= MAX_NAME_LENGTH; } @@ -48,7 +49,7 @@ public String toString() { public boolean equals(Object other) { return other == this // short circuit if same object || (other instanceof Name // instanceof handles nulls - && fullName.equals(((Name) other).fullName)); // state check + && fullName.toLowerCase().equals(((Name) other).fullName.toLowerCase())); // state check } @Override diff --git a/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/patient/NameContainsKeywordsPredicate.java similarity index 77% rename from src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java rename to src/main/java/seedu/address/model/patient/NameContainsKeywordsPredicate.java index c9b5868427c..c68777ac1db 100644 --- a/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java +++ b/src/main/java/seedu/address/model/patient/NameContainsKeywordsPredicate.java @@ -1,4 +1,4 @@ -package seedu.address.model.person; +package seedu.address.model.patient; import java.util.List; import java.util.function.Predicate; @@ -6,9 +6,9 @@ import seedu.address.commons.util.StringUtil; /** - * Tests that a {@code Person}'s {@code Name} matches any of the keywords given. + * Tests that a {@code Patient}'s {@code Name} matches any of the keywords given. */ -public class NameContainsKeywordsPredicate implements Predicate { +public class NameContainsKeywordsPredicate implements Predicate { private final List keywords; public NameContainsKeywordsPredicate(List keywords) { @@ -16,9 +16,9 @@ public NameContainsKeywordsPredicate(List keywords) { } @Override - public boolean test(Person person) { + public boolean test(Patient patient) { return keywords.stream() - .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(person.getName().fullName, keyword)); + .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(patient.getName().fullName, keyword)); } @Override diff --git a/src/main/java/seedu/address/model/patient/Patient.java b/src/main/java/seedu/address/model/patient/Patient.java new file mode 100644 index 00000000000..370e091310c --- /dev/null +++ b/src/main/java/seedu/address/model/patient/Patient.java @@ -0,0 +1,115 @@ +package seedu.address.model.patient; + +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; + +import java.util.Objects; + +//@@author chiamyunqing +/** + * Represents a Patient in the app. + * Guarantees: details are present and not null, field values are validated, immutable. + */ +public class Patient { + + private final Name name; //identifier field + private final Temperature temperature; + private final PeriodOfStay periodOfStay; + private final Phone phone; + private final Age age; + private final Comment comment; // an optional field, if null is initialised to "-" + + /** + * Every field, except comment, must be present and not null. + */ + public Patient(Name name, Temperature temperature, PeriodOfStay periodOfStay, + Phone phone, Age age, Comment comment) { + requireAllNonNull(name, temperature, periodOfStay, phone, age); + this.name = name; + this.temperature = temperature; + this.periodOfStay = periodOfStay; + this.phone = phone; + this.age = age; + this.comment = comment == null ? new Comment("-") : comment; + } + + public Name getName() { + return name; + } + + public Phone getPhone() { + return phone; + } + + public Temperature getTemperature() { + return temperature; + } + + public PeriodOfStay getPeriodOfStay() { + return periodOfStay; + } + + public Age getAge() { + return age; + } + + public Comment getComment() { + return comment; + } + + /** + * Returns true if both patients have the same name. + * This defines a weaker notion of equality between two patients. + */ + public boolean isSamePatient(Patient otherPatient) { + if (otherPatient == this) { + return true; + } + return otherPatient != null + && otherPatient.getName().equals(getName()); + } + + /** + * Returns true if both patients have the same identity and data fields. + * This defines a stronger notion of equality between two patients. + */ + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof Patient)) { + return false; + } + + Patient otherPatient = (Patient) other; + return otherPatient.getName().equals(getName()) + && otherPatient.getTemperature().equals(getTemperature()) + && otherPatient.getPeriodOfStay().equals(getPeriodOfStay()) + && otherPatient.getPhone().equals(getPhone()) + && otherPatient.getAge().equals(getAge()); + } + + @Override + public int hashCode() { + // use this method for custom fields hashing instead of implementing your own + return Objects.hash(name, temperature, periodOfStay, phone, age, comment); + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append(getName() + "\n") + .append("Temperature: ") + .append(getTemperature() + "\n") + .append("Period of stay: ") + .append(getPeriodOfStay().readerFriendlyString() + "\n") + .append("Phone: ") + .append(getPhone() + "\n") + .append("Age: ") + .append(getAge() + "\n") + .append("Comment: ") + .append(getComment() + "\n"); + return builder.toString(); + } +} diff --git a/src/main/java/seedu/address/model/patient/PeriodOfStay.java b/src/main/java/seedu/address/model/patient/PeriodOfStay.java new file mode 100644 index 00000000000..2cb16ecf7df --- /dev/null +++ b/src/main/java/seedu/address/model/patient/PeriodOfStay.java @@ -0,0 +1,128 @@ +package seedu.address.model.patient; + +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; + +//@@author chiamyunqing +/** + * Represents a Patient's period of stay in the facility. + * Guarantees: immutable; is valid as declared in {@link #isValidPeriodOfStay(String)} + * If the period of stay is invalid, the corresponding error message should be retrieved + * via {@link #getErrorMessage(String)}. + */ +public class PeriodOfStay { + + public static final String MESSAGE_WRONG_REGEX = + "Period of stay should be in the format YYYYMMDD-YYYYMMDD. \nE.g. 20200911-20200924"; + public static final String MESSAGE_INVALID_DATE = + "The dates of the period of stay must be valid.\nIn particular, the start date of the period of stay " + + "must be before or equals the end date.\nE.g. 20200901-20200901 or 20200901-20200914."; + + public static final String VALIDATION_REGEX = "\\d{8}[-]\\d{8}"; + + private final LocalDate startDate; + private final LocalDate endDate; + + /** + * Constructs a {@code PeriodOfStay}. + * + * @param periodOfStay A valid period of stay. + */ + public PeriodOfStay(String periodOfStay) { + requireNonNull(periodOfStay); + checkArgument(isRegexCorrect(periodOfStay), MESSAGE_WRONG_REGEX); + checkArgument(isValidDates(periodOfStay), MESSAGE_INVALID_DATE); + startDate = LocalDate.parse(periodOfStay.split("-")[0], DateTimeFormatter.ofPattern("yyyyMMdd")); + endDate = LocalDate.parse(periodOfStay.split("-")[1], DateTimeFormatter.ofPattern("yyyyMMdd")); + } + + /** + * Returns true if a given string matches the validation regex. + */ + private static boolean isRegexCorrect(String test) { + return test.matches(VALIDATION_REGEX); + } + + /** + * Returns true if a given string has valid start and end dates. + * Start and end dates are valid if start date is before or equals end date. + */ + private static boolean isValidDates(String test) { + assert(isRegexCorrect(test)); + String[] dates = test.split("-"); + try { + LocalDate start = LocalDate.parse(dates[0], DateTimeFormatter.ofPattern("yyyyMMdd")); + LocalDate end = LocalDate.parse(dates[1], DateTimeFormatter.ofPattern("yyyyMMdd")); + return start.compareTo(end) <= 0; //check start date less than or equals end date + } catch (DateTimeParseException e) { + return false; + } + } + + /** + * Returns true if a given string is a valid period of stay. + */ + public static boolean isValidPeriodOfStay(String test) { + return isRegexCorrect(test) && isValidDates(test); + } + + /** + * Returns the precise error message according to the error that arises. + * This method should only be called if isValidPeriodOfStay(test) returns false. + * + * @param test the string with error in converting to periodOfStay. + * @return the exact error message that corresponds to the error. + */ + public static String getErrorMessage(String test) { + assert(!isValidPeriodOfStay(test)); + if (!isRegexCorrect(test)) { + return MESSAGE_WRONG_REGEX; + } else { + return MESSAGE_INVALID_DATE; + } + } + + public LocalDate getStartDate() { + return this.startDate; + } + + public LocalDate getEndDate() { + return this.endDate; + } + + /** + * Returns a string that is more human readable (e.g. 09 Jan 2020), meant for UI. + * + * @return reader friendly string. + */ + public String readerFriendlyString() { + return startDate.format(DateTimeFormatter.ofPattern("dd MMM yyyy")) + " to " + + endDate.format(DateTimeFormatter.ofPattern("dd MMM yyyy")); + } + + /** + * Primarily used for storage of data. + */ + @Override + public String toString() { + return startDate.format(DateTimeFormatter.ofPattern("yyyyMMdd")) + "-" + + endDate.format(DateTimeFormatter.ofPattern("yyyyMMdd")); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof PeriodOfStay // instanceof handles nulls + && startDate.compareTo(((PeriodOfStay) other).startDate) == 0 + && endDate.compareTo(((PeriodOfStay) other).endDate) == 0); // state check + } + + @Override + public int hashCode() { + return this.toString().hashCode(); + } +} diff --git a/src/main/java/seedu/address/model/person/Phone.java b/src/main/java/seedu/address/model/patient/Phone.java similarity index 74% rename from src/main/java/seedu/address/model/person/Phone.java rename to src/main/java/seedu/address/model/patient/Phone.java index 872c76b382f..eeda2704059 100644 --- a/src/main/java/seedu/address/model/person/Phone.java +++ b/src/main/java/seedu/address/model/patient/Phone.java @@ -1,18 +1,20 @@ -package seedu.address.model.person; +package seedu.address.model.patient; import static java.util.Objects.requireNonNull; import static seedu.address.commons.util.AppUtil.checkArgument; /** - * Represents a Person's phone number in the address book. + * Represents a Patient's phone number in the app. * Guarantees: immutable; is valid as declared in {@link #isValidPhone(String)} */ public class Phone { public static final String MESSAGE_CONSTRAINTS = - "Phone numbers should only contain numbers, and it should be at least 3 digits long"; + "Phone numbers should only contain numbers.\n" + + "Phone numbers should be at least 3 digits long and can only be up to 20 digits (inclusive)."; public static final String VALIDATION_REGEX = "\\d{3,}"; + private static final int MAX_PHONE_DIGITS_ALLOWED = 20; public final String value; /** @@ -30,7 +32,7 @@ public Phone(String phone) { * Returns true if a given string is a valid phone number. */ public static boolean isValidPhone(String test) { - return test.matches(VALIDATION_REGEX); + return test.matches(VALIDATION_REGEX) && test.length() <= MAX_PHONE_DIGITS_ALLOWED; } @Override diff --git a/src/main/java/seedu/address/model/patient/Temperature.java b/src/main/java/seedu/address/model/patient/Temperature.java new file mode 100644 index 00000000000..7687bde7179 --- /dev/null +++ b/src/main/java/seedu/address/model/patient/Temperature.java @@ -0,0 +1,79 @@ +package seedu.address.model.patient; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +//@@author chiamyunqing +/** + * Represents a Patient's temperature recorded in the app. + * A valid temperature must be to 1 decimal place and is between 32.0 (inclusive) + * to 41.0 (inclusive) degree Celsius as declared in {@link #isValidTemperature(String)}. + * Guarantees: immutable. + */ +public class Temperature { + public static final String MESSAGE_CONSTRAINTS = + "Temperature should only contain numbers and it should be to one decimal place. (E.g. 36.0).\n" + + "The range of temperature should be between 32.0 (inclusive) to 41.0 (inclusive) degree Celsius."; + + public static final String VALIDATION_REGEX = "\\d\\d[.]\\d"; + private static final double MIN_TEMP_RANGE = 32.0; + private static final double MAX_TEMP_RANGE = 41.0; + private final double value; + + /** + * Constructs a {@code Temperature}. + * @param temperature A valid temperature. + */ + public Temperature(String temperature) { + requireNonNull(temperature); + checkArgument(isValidTemperature(temperature), MESSAGE_CONSTRAINTS); //ensures temp is always to 1 d.p. + value = Double.parseDouble(temperature); + } + + /** + * Returns true if a given string matches the regex. + */ + private static boolean isRegexCorrect(String test) { + return test.matches(VALIDATION_REGEX); + } + + /** + * Returns true if a given string is within temperature range of 32.0 to 41.0 degree Celsius. + */ + private static boolean isWithinTempRange(String test) { + assert(isRegexCorrect(test)); + Double temperature = Double.parseDouble(test); + return temperature >= MIN_TEMP_RANGE && temperature <= MAX_TEMP_RANGE; + } + + /** + * Returns true if a given string is a valid temperature. + */ + public static boolean isValidTemperature(String test) { + return isRegexCorrect(test) && isWithinTempRange(test); + } + + /** + * Returns the float value of the temperature reading. + */ + public double getValue() { + return this.value; + } + + @Override + public String toString() { + return Double.toString(value); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Temperature // instanceof handles nulls + && value == ((Temperature) other).getValue()); // state check + } + + @Override + public int hashCode() { + return Double.toString(value).hashCode(); + } +} diff --git a/src/main/java/seedu/address/model/patient/TemperatureRange.java b/src/main/java/seedu/address/model/patient/TemperatureRange.java new file mode 100644 index 00000000000..56bf34da92b --- /dev/null +++ b/src/main/java/seedu/address/model/patient/TemperatureRange.java @@ -0,0 +1,84 @@ +package seedu.address.model.patient; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +/** + * The temperature range class serves to update a list of patients that fall within the temperature range. + * A temperature range object contains two immutable temperature objects for comparison. + */ +public class TemperatureRange { + public static final String MESSAGE_CONSTRAINTS_TEMPERATURERANGE = + "Temperature range should contain temperature-temperature"; + public static final String MESSAGE_CONSTRAINTS_TEMPERATURE = + "Temperature should only contain numbers, and it should be to one decimal place. (E.g. 36.0)"; + public static final String VALIDATION_REGEX_TEMPERATURE = "\\d\\d[.]\\d"; + public static final String VALIDATION_REGEX_TEMPERATURERANGE = + VALIDATION_REGEX_TEMPERATURE + "[-]" + VALIDATION_REGEX_TEMPERATURE; + public final double startingTemperature; + public final double endingTemperature; + + /** + * Constructs a {@code TemperatureRange}. + * @param temperatureRange A valid temperature range. + */ + public TemperatureRange (String temperatureRange) { + requireNonNull(temperatureRange); + checkArgument(isValidTemperatureRange(temperatureRange), MESSAGE_CONSTRAINTS_TEMPERATURERANGE); + String[] temperatures = temperatureRange.split("-"); + checkArgument(isValidTemperature(temperatures[0]), MESSAGE_CONSTRAINTS_TEMPERATURE); + //ensures temp is always to 1 d.p. + checkArgument(isValidTemperature(temperatures[0]), MESSAGE_CONSTRAINTS_TEMPERATURE); + //ensures temp is always to 1 d.p. + startingTemperature = Double.parseDouble(temperatures[0]); + endingTemperature = Double.parseDouble(temperatures[1]); + + } + + /** + * Returns the starting temperature for the temperature range . + */ + public double getStartingTemperature () { + return this.startingTemperature; + } + + /** + * Returns the ending temperature for the temperature range . + */ + public double getEndingTemperature () { + return this.endingTemperature; + } + + /** + * Returns true if a given string is a valid temperature. + */ + public static boolean isValidTemperature(String test) { + return test.matches(VALIDATION_REGEX_TEMPERATURE); + } + + /** + * Returns true if a given string is a valid temperature Range . + */ + public static boolean isValidTemperatureRange(String test) { + return test.matches(VALIDATION_REGEX_TEMPERATURERANGE); + } + + @Override + public String toString() { + return startingTemperature + " to " + endingTemperature; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof TemperatureRange // instanceof handles nulls + && startingTemperature == ((TemperatureRange) other).startingTemperature + && endingTemperature == ((TemperatureRange) other).endingTemperature); // state check + } + + @Override + public int hashCode() { + return this.toString().hashCode(); + } +} + diff --git a/src/main/java/seedu/address/model/patient/TemperatureRangePredicate.java b/src/main/java/seedu/address/model/patient/TemperatureRangePredicate.java new file mode 100644 index 00000000000..96df0e66730 --- /dev/null +++ b/src/main/java/seedu/address/model/patient/TemperatureRangePredicate.java @@ -0,0 +1,35 @@ +package seedu.address.model.patient; + +import java.util.function.Predicate; + +/** + * Tests that a {@code Patient}'s {@code Temperature} falls within the temperature range. + */ +public class TemperatureRangePredicate implements Predicate { + private final double startingTemperature; + private final double endingTemperature; + + /** + * Constructs a {@code TemperatureRangePredicate}. + * @param startingTemperature The start of temperature range. + * @param endingTemperature The end of temperature range. + */ + public TemperatureRangePredicate(double startingTemperature, double endingTemperature) { + this.startingTemperature = startingTemperature; + this.endingTemperature = endingTemperature; + } + + @Override + public boolean test(Patient patient) { + double temperature = patient.getTemperature().getValue(); + return startingTemperature <= temperature && endingTemperature >= temperature; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof TemperatureRangePredicate // instanceof handles nulls + && startingTemperature == (((TemperatureRangePredicate) other).startingTemperature) // state check + && endingTemperature == (((TemperatureRangePredicate) other).endingTemperature)); // state check + } +} diff --git a/src/main/java/seedu/address/model/patient/UniquePatientList.java b/src/main/java/seedu/address/model/patient/UniquePatientList.java new file mode 100644 index 00000000000..e6c7b5e9f99 --- /dev/null +++ b/src/main/java/seedu/address/model/patient/UniquePatientList.java @@ -0,0 +1,158 @@ +package seedu.address.model.patient; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; + +import java.util.Iterator; +import java.util.List; +import java.util.Optional; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import seedu.address.model.patient.exceptions.DuplicatePatientException; +import seedu.address.model.patient.exceptions.PatientNotFoundException; + +/** + * A list of patients that enforces uniqueness between its elements and does not allow nulls. + * A patient is considered unique by comparing using {@code Patient#isSamePatient(Patient)}. + * As such, adding and updating of patients uses Patient#isSamePatient(Patient) for equality so as to ensure + * that the patient being added or updated is unique in terms of identity in the UniquePatientList. + * However, the removal of a patient uses Patient#equals(Object) so as to ensure that the patient with + * exactly the same fields will be removed. + * + * Supports a minimal set of list operations. + * + * @see Patient#isSamePatient(Patient) + */ +public class UniquePatientList implements Iterable { + + private final ObservableList internalList = FXCollections.observableArrayList(); + private final ObservableList internalUnmodifiableList = + FXCollections.unmodifiableObservableList(internalList); + + /** + * Returns true if the list contains an equivalent patient as the given argument. + */ + public boolean contains(Patient toCheck) { + requireNonNull(toCheck); + return internalList.stream().anyMatch(toCheck::isSamePatient); + } + + //@@author chiamyunqing + /** + * Returns the patient with the input name. + * @param name of patient to find. + * @return the patient with the name if it exists. + */ + public Optional getPatientWithName(Name name) { + requireNonNull(name); + for (Patient patient : internalList) { + if (patient.getName().equals(name)) { + return Optional.of(patient); + } + } + return Optional.empty(); + } + + //@@author + /** + * Adds a patient to the list. + * The patient must not already exist in the list. + */ + public void add(Patient toAdd) { + requireNonNull(toAdd); + if (contains(toAdd)) { + throw new DuplicatePatientException(); + } + internalList.add(toAdd); + int index = internalList.indexOf(toAdd); + internalList.set(index, toAdd); //creates a change for listener + } + + /** + * Replaces the patient {@code target} in the list with {@code editedPatient}. + * {@code target} must exist in the list. + * The patient identity of {@code editedPatient} must not be the same as another existing patient in the list. + */ + public void setPatient(Patient target, Patient editedPatient) { + requireAllNonNull(target, editedPatient); + + int index = internalList.indexOf(target); + if (index == -1) { + throw new PatientNotFoundException(); + } + + if (!target.isSamePatient(editedPatient) && contains(editedPatient)) { + throw new DuplicatePatientException(); + } + + internalList.set(index, editedPatient); + } + + /** + * Removes the equivalent patient from the list. + * The patient must exist in the list. + */ + public void remove(Patient toRemove) { + requireNonNull(toRemove); + if (!internalList.remove(toRemove)) { + throw new PatientNotFoundException(); + } + } + + public void setPatients(UniquePatientList replacement) { + requireNonNull(replacement); + internalList.setAll(replacement.internalList); + } + + /** + * Replaces the contents of this list with {@code patients}. + * {@code patients} must not contain duplicate patients. + */ + public void setPatients(List patients) { + requireAllNonNull(patients); + if (!patientsAreUnique(patients)) { + throw new DuplicatePatientException(); + } + + internalList.setAll(patients); + } + + /** + * Returns the backing list as an unmodifiable {@code ObservableList}. + */ + public ObservableList asUnmodifiableObservableList() { + return internalUnmodifiableList; + } + + @Override + public Iterator iterator() { + return internalList.iterator(); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof UniquePatientList // instanceof handles nulls + && internalList.equals(((UniquePatientList) other).internalList)); + } + + @Override + public int hashCode() { + return internalList.hashCode(); + } + + /** + * Returns true if {@code patients} contains only unique patients. + */ + private boolean patientsAreUnique(List patients) { + for (int i = 0; i < patients.size() - 1; i++) { + for (int j = i + 1; j < patients.size(); j++) { + if (patients.get(i).isSamePatient(patients.get(j))) { + return false; + } + } + } + return true; + } +} diff --git a/src/main/java/seedu/address/model/patient/exceptions/DuplicatePatientException.java b/src/main/java/seedu/address/model/patient/exceptions/DuplicatePatientException.java new file mode 100644 index 00000000000..f690368941d --- /dev/null +++ b/src/main/java/seedu/address/model/patient/exceptions/DuplicatePatientException.java @@ -0,0 +1,11 @@ +package seedu.address.model.patient.exceptions; + +/** + * Signals that the operation will result in duplicate Patients (Patients are considered duplicates if they + * have the same identity). + */ +public class DuplicatePatientException extends RuntimeException { + public DuplicatePatientException() { + super("Operation would result in duplicate patients"); + } +} diff --git a/src/main/java/seedu/address/model/patient/exceptions/PatientNotFoundException.java b/src/main/java/seedu/address/model/patient/exceptions/PatientNotFoundException.java new file mode 100644 index 00000000000..681e9fe0af0 --- /dev/null +++ b/src/main/java/seedu/address/model/patient/exceptions/PatientNotFoundException.java @@ -0,0 +1,6 @@ +package seedu.address.model.patient.exceptions; + +/** + * Signals that the operation is unable to find the specified patient. + */ +public class PatientNotFoundException extends RuntimeException {} diff --git a/src/main/java/seedu/address/model/person/Address.java b/src/main/java/seedu/address/model/person/Address.java deleted file mode 100644 index 60472ca22a0..00000000000 --- a/src/main/java/seedu/address/model/person/Address.java +++ /dev/null @@ -1,57 +0,0 @@ -package seedu.address.model.person; - -import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.AppUtil.checkArgument; - -/** - * Represents a Person's address in the address book. - * Guarantees: immutable; is valid as declared in {@link #isValidAddress(String)} - */ -public class Address { - - public static final String MESSAGE_CONSTRAINTS = "Addresses can take any values, and it should not be blank"; - - /* - * The first character of the address must not be a whitespace, - * otherwise " " (a blank string) becomes a valid input. - */ - public static final String VALIDATION_REGEX = "[^\\s].*"; - - public final String value; - - /** - * Constructs an {@code Address}. - * - * @param address A valid address. - */ - public Address(String address) { - requireNonNull(address); - checkArgument(isValidAddress(address), MESSAGE_CONSTRAINTS); - value = address; - } - - /** - * Returns true if a given string is a valid email. - */ - public static boolean isValidAddress(String test) { - return test.matches(VALIDATION_REGEX); - } - - @Override - public String toString() { - return value; - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof Address // instanceof handles nulls - && value.equals(((Address) other).value)); // state check - } - - @Override - public int hashCode() { - return value.hashCode(); - } - -} diff --git a/src/main/java/seedu/address/model/person/Email.java b/src/main/java/seedu/address/model/person/Email.java deleted file mode 100644 index a5bbe0b6a5f..00000000000 --- a/src/main/java/seedu/address/model/person/Email.java +++ /dev/null @@ -1,67 +0,0 @@ -package seedu.address.model.person; - -import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.AppUtil.checkArgument; - -/** - * Represents a Person's email in the address book. - * Guarantees: immutable; is valid as declared in {@link #isValidEmail(String)} - */ -public class Email { - - private static final String SPECIAL_CHARACTERS = "!#$%&'*+/=?`{|}~^.-"; - public static final String MESSAGE_CONSTRAINTS = "Emails should be of the format local-part@domain " - + "and adhere to the following constraints:\n" - + "1. The local-part should only contain alphanumeric characters and these special characters, excluding " - + "the parentheses, (" + SPECIAL_CHARACTERS + ") .\n" - + "2. This is followed by a '@' and then a domain name. " - + "The domain name must:\n" - + " - be at least 2 characters long\n" - + " - start and end with alphanumeric characters\n" - + " - consist of alphanumeric characters, a period or a hyphen for the characters in between, if any."; - // alphanumeric and special characters - private static final String LOCAL_PART_REGEX = "^[\\w" + SPECIAL_CHARACTERS + "]+"; - private static final String DOMAIN_FIRST_CHARACTER_REGEX = "[^\\W_]"; // alphanumeric characters except underscore - private static final String DOMAIN_MIDDLE_REGEX = "[a-zA-Z0-9.-]*"; // alphanumeric, period and hyphen - private static final String DOMAIN_LAST_CHARACTER_REGEX = "[^\\W_]$"; - public static final String VALIDATION_REGEX = LOCAL_PART_REGEX + "@" - + DOMAIN_FIRST_CHARACTER_REGEX + DOMAIN_MIDDLE_REGEX + DOMAIN_LAST_CHARACTER_REGEX; - - public final String value; - - /** - * Constructs an {@code Email}. - * - * @param email A valid email address. - */ - public Email(String email) { - requireNonNull(email); - checkArgument(isValidEmail(email), MESSAGE_CONSTRAINTS); - value = email; - } - - /** - * Returns if a given string is a valid email. - */ - public static boolean isValidEmail(String test) { - return test.matches(VALIDATION_REGEX); - } - - @Override - public String toString() { - return value; - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof Email // instanceof handles nulls - && value.equals(((Email) other).value)); // state check - } - - @Override - public int hashCode() { - return value.hashCode(); - } - -} diff --git a/src/main/java/seedu/address/model/person/Person.java b/src/main/java/seedu/address/model/person/Person.java deleted file mode 100644 index 557a7a60cd5..00000000000 --- a/src/main/java/seedu/address/model/person/Person.java +++ /dev/null @@ -1,120 +0,0 @@ -package seedu.address.model.person; - -import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; - -import java.util.Collections; -import java.util.HashSet; -import java.util.Objects; -import java.util.Set; - -import seedu.address.model.tag.Tag; - -/** - * Represents a Person in the address book. - * Guarantees: details are present and not null, field values are validated, immutable. - */ -public class Person { - - // Identity fields - private final Name name; - private final Phone phone; - private final Email email; - - // Data fields - private final Address address; - private final Set tags = new HashSet<>(); - - /** - * Every field must be present and not null. - */ - public Person(Name name, Phone phone, Email email, Address address, Set tags) { - requireAllNonNull(name, phone, email, address, tags); - this.name = name; - this.phone = phone; - this.email = email; - this.address = address; - this.tags.addAll(tags); - } - - public Name getName() { - return name; - } - - public Phone getPhone() { - return phone; - } - - public Email getEmail() { - return email; - } - - public Address getAddress() { - return address; - } - - /** - * Returns an immutable tag set, which throws {@code UnsupportedOperationException} - * if modification is attempted. - */ - public Set getTags() { - return Collections.unmodifiableSet(tags); - } - - /** - * Returns true if both persons of the same name have at least one other identity field that is the same. - * This defines a weaker notion of equality between two persons. - */ - public boolean isSamePerson(Person otherPerson) { - if (otherPerson == this) { - return true; - } - - return otherPerson != null - && otherPerson.getName().equals(getName()) - && (otherPerson.getPhone().equals(getPhone()) || otherPerson.getEmail().equals(getEmail())); - } - - /** - * Returns true if both persons have the same identity and data fields. - * This defines a stronger notion of equality between two persons. - */ - @Override - public boolean equals(Object other) { - if (other == this) { - return true; - } - - if (!(other instanceof Person)) { - return false; - } - - Person otherPerson = (Person) other; - return otherPerson.getName().equals(getName()) - && otherPerson.getPhone().equals(getPhone()) - && otherPerson.getEmail().equals(getEmail()) - && otherPerson.getAddress().equals(getAddress()) - && otherPerson.getTags().equals(getTags()); - } - - @Override - public int hashCode() { - // use this method for custom fields hashing instead of implementing your own - return Objects.hash(name, phone, email, address, tags); - } - - @Override - public String toString() { - final StringBuilder builder = new StringBuilder(); - builder.append(getName()) - .append(" Phone: ") - .append(getPhone()) - .append(" Email: ") - .append(getEmail()) - .append(" Address: ") - .append(getAddress()) - .append(" Tags: "); - getTags().forEach(builder::append); - return builder.toString(); - } - -} diff --git a/src/main/java/seedu/address/model/person/UniquePersonList.java b/src/main/java/seedu/address/model/person/UniquePersonList.java deleted file mode 100644 index 0fee4fe57e6..00000000000 --- a/src/main/java/seedu/address/model/person/UniquePersonList.java +++ /dev/null @@ -1,137 +0,0 @@ -package seedu.address.model.person; - -import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; - -import java.util.Iterator; -import java.util.List; - -import javafx.collections.FXCollections; -import javafx.collections.ObservableList; -import seedu.address.model.person.exceptions.DuplicatePersonException; -import seedu.address.model.person.exceptions.PersonNotFoundException; - -/** - * A list of persons that enforces uniqueness between its elements and does not allow nulls. - * A person is considered unique by comparing using {@code Person#isSamePerson(Person)}. As such, adding and updating of - * persons uses Person#isSamePerson(Person) for equality so as to ensure that the person being added or updated is - * unique in terms of identity in the UniquePersonList. However, the removal of a person uses Person#equals(Object) so - * as to ensure that the person with exactly the same fields will be removed. - * - * Supports a minimal set of list operations. - * - * @see Person#isSamePerson(Person) - */ -public class UniquePersonList implements Iterable { - - private final ObservableList internalList = FXCollections.observableArrayList(); - private final ObservableList internalUnmodifiableList = - FXCollections.unmodifiableObservableList(internalList); - - /** - * Returns true if the list contains an equivalent person as the given argument. - */ - public boolean contains(Person toCheck) { - requireNonNull(toCheck); - return internalList.stream().anyMatch(toCheck::isSamePerson); - } - - /** - * Adds a person to the list. - * The person must not already exist in the list. - */ - public void add(Person toAdd) { - requireNonNull(toAdd); - if (contains(toAdd)) { - throw new DuplicatePersonException(); - } - internalList.add(toAdd); - } - - /** - * Replaces the person {@code target} in the list with {@code editedPerson}. - * {@code target} must exist in the list. - * The person identity of {@code editedPerson} must not be the same as another existing person in the list. - */ - public void setPerson(Person target, Person editedPerson) { - requireAllNonNull(target, editedPerson); - - int index = internalList.indexOf(target); - if (index == -1) { - throw new PersonNotFoundException(); - } - - if (!target.isSamePerson(editedPerson) && contains(editedPerson)) { - throw new DuplicatePersonException(); - } - - internalList.set(index, editedPerson); - } - - /** - * Removes the equivalent person from the list. - * The person must exist in the list. - */ - public void remove(Person toRemove) { - requireNonNull(toRemove); - if (!internalList.remove(toRemove)) { - throw new PersonNotFoundException(); - } - } - - public void setPersons(UniquePersonList replacement) { - requireNonNull(replacement); - internalList.setAll(replacement.internalList); - } - - /** - * Replaces the contents of this list with {@code persons}. - * {@code persons} must not contain duplicate persons. - */ - public void setPersons(List persons) { - requireAllNonNull(persons); - if (!personsAreUnique(persons)) { - throw new DuplicatePersonException(); - } - - internalList.setAll(persons); - } - - /** - * Returns the backing list as an unmodifiable {@code ObservableList}. - */ - public ObservableList asUnmodifiableObservableList() { - return internalUnmodifiableList; - } - - @Override - public Iterator iterator() { - return internalList.iterator(); - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof UniquePersonList // instanceof handles nulls - && internalList.equals(((UniquePersonList) other).internalList)); - } - - @Override - public int hashCode() { - return internalList.hashCode(); - } - - /** - * Returns true if {@code persons} contains only unique persons. - */ - private boolean personsAreUnique(List persons) { - for (int i = 0; i < persons.size() - 1; i++) { - for (int j = i + 1; j < persons.size(); j++) { - if (persons.get(i).isSamePerson(persons.get(j))) { - return false; - } - } - } - return true; - } -} diff --git a/src/main/java/seedu/address/model/person/exceptions/DuplicatePersonException.java b/src/main/java/seedu/address/model/person/exceptions/DuplicatePersonException.java deleted file mode 100644 index d7290f59442..00000000000 --- a/src/main/java/seedu/address/model/person/exceptions/DuplicatePersonException.java +++ /dev/null @@ -1,11 +0,0 @@ -package seedu.address.model.person.exceptions; - -/** - * Signals that the operation will result in duplicate Persons (Persons are considered duplicates if they have the same - * identity). - */ -public class DuplicatePersonException extends RuntimeException { - public DuplicatePersonException() { - super("Operation would result in duplicate persons"); - } -} diff --git a/src/main/java/seedu/address/model/person/exceptions/PersonNotFoundException.java b/src/main/java/seedu/address/model/person/exceptions/PersonNotFoundException.java deleted file mode 100644 index fa764426ca7..00000000000 --- a/src/main/java/seedu/address/model/person/exceptions/PersonNotFoundException.java +++ /dev/null @@ -1,6 +0,0 @@ -package seedu.address.model.person.exceptions; - -/** - * Signals that the operation is unable to find the specified person. - */ -public class PersonNotFoundException extends RuntimeException {} diff --git a/src/main/java/seedu/address/model/room/ComparableRoom.java b/src/main/java/seedu/address/model/room/ComparableRoom.java new file mode 100644 index 00000000000..8acca9fe18a --- /dev/null +++ b/src/main/java/seedu/address/model/room/ComparableRoom.java @@ -0,0 +1,20 @@ +package seedu.address.model.room; + +import java.util.Comparator; + +public class ComparableRoom implements Comparator { + @Override + public int compare(Room room1, Room room2) { + if (room1.isOccupied() == room2.isOccupied()) { + if (room1.getRoomNumber() < room2.getRoomNumber()) { + return -1; + } else { + return 1; + } + } else if (room1.isOccupied()) { + return 1; + } else { + return -1; + } + } +} diff --git a/src/main/java/seedu/address/model/room/Room.java b/src/main/java/seedu/address/model/room/Room.java new file mode 100644 index 00000000000..4b380c130a3 --- /dev/null +++ b/src/main/java/seedu/address/model/room/Room.java @@ -0,0 +1,240 @@ +package seedu.address.model.room; + +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; + +import java.util.List; +import java.util.Objects; +import java.util.Optional; + +import javafx.collections.ObservableList; +import seedu.address.commons.core.index.Index; +import seedu.address.model.ReadOnlyList; +import seedu.address.model.patient.Patient; +import seedu.address.model.task.Task; +import seedu.address.model.task.exceptions.TaskNotFoundException; + +/** + * Represents Room in the app + */ +public class Room { + + private int roomNumber; + private boolean isOccupied; + private Optional patient; + private RoomTasks tasks; + + /** + * Creates room object where isOccupied is always false + */ + public Room(int roomNumber) { + requireAllNonNull(roomNumber); + this.roomNumber = roomNumber; + this.isOccupied = false; + this.patient = Optional.empty(); + tasks = new RoomTasks(); + } + + /** + * Creates room object where roomNumber and isOccupied values are values given by user + */ + public Room(int roomNumber, boolean isOccupied) { + requireAllNonNull(roomNumber, isOccupied); + this.roomNumber = roomNumber; + this.isOccupied = isOccupied; + this.patient = Optional.empty(); + tasks = new RoomTasks(); + } + + /** + * Creates a room object containing a patient that can be found in the application. + * + * @param roomNumber Room Number of the room. + * @param patient Patient to be added to the room. + * @param tasks RoomTasks containing tasks for the room. + */ + public Room(int roomNumber, Optional patient, RoomTasks tasks) { + requireAllNonNull(roomNumber, patient, tasks); + this.roomNumber = roomNumber; + this.isOccupied = true; + this.patient = patient; + this.tasks = tasks; + } + + /** + * Creates a Room object where none of the values are pre determined by app + */ + public Room(int roomNumber, boolean isOccupied, Optional patient, RoomTasks tasks) { + requireAllNonNull(roomNumber, isOccupied, patient, tasks); + this.roomNumber = roomNumber; + this.isOccupied = isOccupied; + this.patient = patient; + this.tasks = tasks; + } + + public int getRoomNumber() { + return roomNumber; + } + + //// patient + + public Optional getPatient() { + return this.patient; + } + + public boolean isOccupied() { + return isOccupied; + } + + public void setOccupied(boolean isOccupied) { + this.isOccupied = isOccupied; + } + + public void setPatient(Patient patient) { + this.patient = Optional.ofNullable(patient); + } + + //// tasks + + /** + * Returns an unmodifiable version of the list of tasks in this room as an {@code ObservableList}. + */ + public ObservableList getReadOnlyTasks() { + return tasks.getReadOnlyList(); + } + + /** + * Returns an unmodifiable version of the list of tasks in this room as a {@code ReadOnlyList}. + */ + public ReadOnlyList getReadOnlyList() { + return tasks; + } + + /** + * Returns the task with the provided {@code taskIndex} from this room. + * An empty optional is returned if such a task is not found in the room. + * + * @param taskIndex The index of the task in this room. + * @return the optional-wrapped task if found, otherwise an empty optional + */ + public Optional getTaskWithTaskIndex(Index taskIndex) { + return tasks.getTaskWithTaskIndex(taskIndex); + } + + /** + * Adds a task to the task list of this room. + * + * @param task The task to add. + */ + public void addTask(Task task) { + tasks.addTask(task); + } + + /** + * Adds tasks in {@code roomTasks} to this room. + * + * @param roomTasks The tasks to add. + */ + public void addTasks(List roomTasks) { + tasks.addTasks(roomTasks); + } + + /** + * Deletes a task from the task list of this room. + * The task must be in this room. + * + * @param task The task to delete. + * @throws TaskNotFoundException if task is not found in the task list of this room. + */ + public void deleteTask(Task task) { + try { + tasks.removeTask(task); + } catch (TaskNotFoundException e) { + throw e; + } + } + + /** + * Replaces a task from the task list of this room with {@code editedTask}. + * The task must be in this room. + * + * @param target The task to replace. + * @param editedTask The modified task to replace the original. + * @throws TaskNotFoundException if task is not found in the task list of this room. + */ + public void setTask(Task target, Task editedTask) { + try { + tasks.setTask(target, editedTask); + } catch (TaskNotFoundException e) { + throw e; + } + } + + /** + * Enumerates the tasks in this room, numbering and specifying the details of each task. + * + * @return A print-friendly summary for the tasks in this room. + */ + public String getPrintFriendlyTaskSummary() { + return tasks.toString(); + } + + /** + * Returns true if both rooms of the same number have at least one other identity field that is the same. + * This defines a weaker notion of equality between two rooms. + */ + public boolean isSameRoom(Room otherRoom) { + if (otherRoom == this) { + return true; + } + + return otherRoom != null + && Integer.valueOf(otherRoom.getRoomNumber()).equals(getRoomNumber()); + } + + /** + * Returns true if both rooms have the same identity and data fields. + * This defines a stronger notion of equality between two rooms. + */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + Room room = (Room) o; + if (patient.isEmpty()) { + return roomNumber == room.roomNumber + && room.patient.isEmpty() + && isOccupied == room.isOccupied + && tasks.equals(room.tasks); + } else { + return roomNumber == room.roomNumber + && isOccupied == room.isOccupied + && patient.equals(room.getPatient()) + && tasks.equals(room.tasks); + } + } + + @Override + public int hashCode() { + return Objects.hash(roomNumber, isOccupied, tasks); + } + + + @Override + public String toString() { + String noPatientText = "-"; + String patientDetails = getPatient().map(Patient::toString).orElse(noPatientText); + final StringBuilder builder = new StringBuilder(); + builder.append("Room Number: ") + .append(getRoomNumber() + "\n") + .append("Patient: ") + .append(patientDetails + "\n") + .append("Tasks: ") + .append(tasks.toString() + "\n"); + return builder.toString(); + } +} diff --git a/src/main/java/seedu/address/model/room/RoomTaskAssociation.java b/src/main/java/seedu/address/model/room/RoomTaskAssociation.java new file mode 100644 index 00000000000..840d7fdd222 --- /dev/null +++ b/src/main/java/seedu/address/model/room/RoomTaskAssociation.java @@ -0,0 +1,68 @@ +package seedu.address.model.room; + +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; + +import seedu.address.model.task.Task; + +/** + * An association class between {@code Room} and {@code Task}. This class serves 2 primary purposes: + * i) stores critical information related to {@code Room} (e.g. room number) to be displayed on the + * Task user interface without creating a direct association between {@code Room} and {@code Task}. + * ii) provides a convenient way to retrieve the index of a task in room in constant time. + */ +public class RoomTaskAssociation { + + private final Room room; + private final Task task; + private final int taskIndex; + + /** + * Creates an association between {@code room} and {@code task}. + * + * @param room The room in which the task is found. + * @param task The task. + * @param taskIndex The index of the task in the room. + */ + public RoomTaskAssociation(Room room, Task task, int taskIndex) { + requireAllNonNull(room, task); + assert taskIndex > 0 : "Task index must be greater than 0."; + this.room = room; + this.task = task; + this.taskIndex = taskIndex; + } + + public Task getTask() { + return task; + } + + public int getRoomNumber() { + return room.getRoomNumber(); + } + + public int getTaskIndex() { + return taskIndex; + } + + /** + * Returns the total number of tasks in the room. + */ + public int getTotalTasksInRoom() { + return room.getReadOnlyTasks().size(); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof RoomTaskAssociation)) { + return false; + } + + RoomTaskAssociation otherRoomTaskAssociation = (RoomTaskAssociation) other; + return room.equals(otherRoomTaskAssociation.room) + && task.equals(otherRoomTaskAssociation.task) + && taskIndex == otherRoomTaskAssociation.taskIndex; + } +} diff --git a/src/main/java/seedu/address/model/room/RoomTasks.java b/src/main/java/seedu/address/model/room/RoomTasks.java new file mode 100644 index 00000000000..e8f0f836fe9 --- /dev/null +++ b/src/main/java/seedu/address/model/room/RoomTasks.java @@ -0,0 +1,135 @@ +package seedu.address.model.room; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; + +import java.util.List; +import java.util.Optional; + +import javafx.collections.ObservableList; +import seedu.address.commons.core.index.Index; +import seedu.address.model.ReadOnlyList; +import seedu.address.model.task.Task; +import seedu.address.model.task.TaskList; + +/** + * Wraps a {@code TaskList}. + * Represents the tasks in a room. + */ +public class RoomTasks implements ReadOnlyList { + + private final TaskList tasks; + + /** + * Creates an empty list of tasks in the room. + */ + public RoomTasks() { + tasks = new TaskList(); + } + + /** + * Creates a list of tasks in the room and populates it with the tasks in {@code roomTasks}. + */ + public RoomTasks(List tasksToAdd) { + requireAllNonNull(tasksToAdd); + tasks = new TaskList(); + tasks.setTasks(tasksToAdd); + } + + //// task-level operations + + /** + * Returns the task with the provided {@code taskIndex} from the room. + * An empty optional is returned if such a task is not found in the room. + * + * @param taskIndex The index of the task in the room. + * @return the optional-wrapped task if found, otherwise an empty optional + */ + public Optional getTaskWithTaskIndex(Index taskIndex) { + requireNonNull(taskIndex); + + List tasks = getReadOnlyList(); + if (taskIndex.getZeroBased() >= tasks.size()) { + return Optional.empty(); + } + return Optional.of(tasks.get(taskIndex.getZeroBased())); + } + + /** + * Adds a task to the room. + */ + public void addTask(Task t) { + requireNonNull(t); + tasks.add(t); + } + + /** + * Adds all tasks in {@code roomTasks} to the list. + */ + public void addTasks(List tasksToAdd) { + requireAllNonNull(tasksToAdd); + tasks.add(tasksToAdd); + } + + /** + * Replaces the given task {@code target} in the list with {@code editedTask}. + * {@code target} must exist in the room. + */ + public void setTask(Task target, Task editedTask) { + requireAllNonNull(target, editedTask); + tasks.setTask(target, editedTask); + } + + /** + * Removes {@code key} from {@code RoomTasks}. + * {@code key} must exist in the room. + */ + public void removeTask(Task key) { + tasks.remove(key); + } + + /** + * Returns true if a the room has no task. + */ + public boolean isEmpty() { + return tasks.isEmpty(); + } + + @Override + public ObservableList getReadOnlyList() { + return tasks.asUnmodifiableObservableList(); + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + + if (isEmpty()) { + builder.append("-"); + return builder.toString().trim(); + } + + int taskIndex = 1; + for (Task task : getReadOnlyList()) { + // Results in "1. \n2. ..." + builder.append(taskIndex++); + builder.append(". "); + builder.append(task); + builder.append("\n\n"); + } + + return builder.toString().trim(); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof RoomTasks // instanceof handles nulls + && tasks.equals(((RoomTasks) other).tasks)); + } + + @Override + public int hashCode() { + return tasks.hashCode(); + } +} diff --git a/src/main/java/seedu/address/model/room/UniqueRoomList.java b/src/main/java/seedu/address/model/room/UniqueRoomList.java new file mode 100644 index 00000000000..2245f48fabe --- /dev/null +++ b/src/main/java/seedu/address/model/room/UniqueRoomList.java @@ -0,0 +1,334 @@ +package seedu.address.model.room; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.PriorityQueue; +import java.util.stream.IntStream; + +import javafx.beans.Observable; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import seedu.address.commons.core.index.Index; +import seedu.address.model.ReadOnlyList; +import seedu.address.model.patient.Name; +import seedu.address.model.room.exceptions.DuplicateRoomException; +import seedu.address.model.room.exceptions.RoomNotFoundException; + +public class UniqueRoomList implements Iterable { + + private int numOfRooms; + private PriorityQueue rooms = new PriorityQueue<>(new ComparableRoom()); + private final ObservableList internalList = FXCollections.observableArrayList((Room room) -> { + Observable[] updatedTasks = new Observable[]{room.getReadOnlyTasks()}; + return updatedTasks; + }); + private final ObservableList internalUnmodifiableList = + FXCollections.unmodifiableObservableList(internalList); + + @Override + public Iterator iterator() { + return internalList.iterator(); + } + + /** + * Resets the existing data of this {@code RoomList} with {@code newData}. + */ + public void resetData(ReadOnlyList readOnlyRoomList) { + requireAllNonNull(readOnlyRoomList); + + ObservableList roomLists = readOnlyRoomList.getReadOnlyList(); + rooms.addAll(roomLists); + internalList.addAll(roomLists); + } + + /** + * Used it test cases to set rooms with a higher room number which can be more than number of rooms existing + * in hotel + * @param room is the room to be input into data + */ + public void setRoom(Room room) { + int roomNumber = room.getRoomNumber(); + if (room.getRoomNumber() > internalList.size()) { + setRoomForRoomNumberLessThanNumberOfRooms(room); + } else { + setRoomForRoomNumberMoreThanNumberOfRooms(room); + } + } + private void setRoomForRoomNumberLessThanNumberOfRooms(Room room) { + int roomNumber = room.getRoomNumber(); + for (int i = internalList.size(); i < roomNumber - 1; i++) { + Index index = Index.fromOneBased(i); + Room roomToAdd = new Room(index.getOneBased()); + internalList.add(roomToAdd); + rooms.add(room); + } + } + + private void setRoomForRoomNumberMoreThanNumberOfRooms(Room room) { + int roomNumber = room.getRoomNumber(); + Index index = Index.fromOneBased(roomNumber); + Room currRoom = internalList.get(index.getZeroBased()); + internalList.remove(index.getZeroBased()); + rooms.remove(currRoom); + internalList.add(index.getZeroBased(), room); + rooms.add(room); + } + /** + * Returns Priority Queue of rooms + */ + public PriorityQueue getRooms() { + return this.rooms; + } + + /** + * Returns number of rooms in hotel + */ + public int getNumOfRooms() { + return internalList.size(); + } + + public ObservableList getRoomObservableList() { + return internalList; + } + + /** + * Adds this room to the RoomList + * + * @param room is added to RoomList + */ + public void initRooms(Room room) { + rooms.add(room); + internalList.add(room); + } + + /** + * Adds the number of the rooms in a hotel + * + * @param numOfRooms is the number of rooms to be added + */ + public void initRooms(int numOfRooms) { + this.numOfRooms = numOfRooms; + initRooms(); + } + + private void initRooms() { + if (numOfRooms <= 0) { + return; + } else if (numOfRooms > internalList.size()) { + setRoomsInHotel_increaseInNumberOfRooms(); + } else if (numOfRooms < internalList.size()) { + setRoomsInHotel_decreaseInNumberOfRooms(); + } else { + //would not reach this block, exists to improve code quality + } + } + + private void setRoomsInHotel_increaseInNumberOfRooms() { + ArrayList roomArrayList = new ArrayList<>(internalList); + PriorityQueue rooms = new PriorityQueue<>(new ComparableRoom()); + + this.rooms = new PriorityQueue<>(new ComparableRoom()); //remove all the rooms in the PQ + for (int i = internalList.size(); i < numOfRooms; i++) { + Room room = new Room(i + 1); + rooms.add(room); + roomArrayList.add(room); + } + this.rooms.addAll(rooms); + internalList.setAll(roomArrayList); + } + + private void setRoomsInHotel_decreaseInNumberOfRooms() { + ArrayList roomArrayList = new ArrayList<>(internalList); + PriorityQueue rooms = new PriorityQueue<>(new ComparableRoom()); + + List occupiedRooms = occupiedRooms(); + List unoccupiedRooms = unOccupiedRooms(); + if (occupiedRooms.size() == 0) { + + } else { + combinedStream(occupiedRooms, unoccupiedRooms); + } + this.rooms = new PriorityQueue<>(new ComparableRoom()); //remove all the rooms in the PQ + for (int i = 0; i < numOfRooms; i++) { + Room room = internalList.get(i); + rooms.add(room); + } + int size = internalList.size(); + for (int i = numOfRooms; i < size; i++) { + roomArrayList.remove(numOfRooms); + } + this.rooms.addAll(rooms); + internalList.setAll(roomArrayList); + } + + /** + * Gives list of empty rooms in the region which exists after shrinkage. + */ + public List unOccupiedRooms() { + List rooms = new ArrayList<>(); + for (int i = 0; i < numOfRooms; i++) { + if (!internalList.get(i).isOccupied()) { + rooms.add(internalList.get(i)); + } + } + return rooms; + } + + /** + * Gives list of occupied rooms in the region which does not exist after shrinkage. + */ + public List occupiedRooms() { + List rooms = new ArrayList<>(); + for (int i = numOfRooms; i < internalList.size(); i++) { + if (internalList.get(i).isOccupied()) { + rooms.add(internalList.get(i)); + } + } + + return rooms; + } + + private void combinedStream(List occupiedRooms, List unoccupiedRooms) { + for (Room room : occupiedRooms) { + Room empty = unoccupiedRooms.get(0); + unoccupiedRooms.remove(0); + empty.setOccupied(true); + empty.setPatient(room.getPatient().get()); + empty.addTasks(room.getReadOnlyTasks()); + } + } + + public boolean hasSpaceForRooms() { + return getNumOfExcessOccupiedRooms() <= numOfEmptyRooms(); + } + + /** + * Gives the number of rooms that are occupied in the hotel facility beyond shrinkage + */ + public int getNumOfExcessOccupiedRooms() { + return (int) IntStream.rangeClosed(numOfRooms, internalList.size() - 1) + .mapToObj(x -> internalList.get(x)).filter(Room::isOccupied).count(); + } + + /** + * Gives the number of rooms that are empty for room numbers after shrinkage. + */ + public int numOfEmptyRooms() { + return (int) IntStream.rangeClosed(0, numOfRooms - 1) + .mapToObj(x -> internalList.get(x)).filter(room -> !room.isOccupied()).count(); + } + + /** + * Returns the backing list as an unmodifiable {@code ObservableList}. + */ + public ObservableList asUnmodifiableObservableList() { + return internalUnmodifiableList; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (other == null || getClass() != other.getClass()) { + return false; + } + + return equals(new PriorityQueue<>(rooms), new PriorityQueue<>(((UniqueRoomList) other).rooms)) + && internalList.equals(((UniqueRoomList) other).internalList); + } + + /** + * Tests whether 2 PriorityQueues are equal by checking whether at each relative position they contain the equal + * rooms + */ + public boolean equals(PriorityQueue rooms1, PriorityQueue rooms2) { + if (rooms1.size() != rooms2.size()) { + return false; + } else { + int size = rooms1.size(); + for (int i = 0; i < size; i++) { + if (!rooms1.poll().equals(rooms2.poll())) { + return false; + } + } + return true; + } + } + + /** + * Returns true if the list contains an equivalent room as the given argument. + */ + public boolean containsRoom(Room toCheck) { + requireNonNull(toCheck); + return internalList.stream().anyMatch(toCheck::isSameRoom); + } + + //@@author chiamyunqing + /** + * Clears the room which contains the patient with the given name. + * Tasks should still remain in the room. + * @param patientName to clear the room from. + */ + public void clearRoom(Name patientName) { + requireNonNull(patientName); + for (int i = 1; i <= internalList.size(); i++) { + if (!internalList.get(i - 1).isOccupied()) { + continue; + } + Name patientNameInRoom = internalList.get(i - 1).getPatient().get().getName(); + if (patientName.equals(patientNameInRoom)) { + Room roomToClear = internalList.get(i - 1); + setSingleRoom(roomToClear, new Room(roomToClear.getRoomNumber(), + false, Optional.empty(), new RoomTasks(roomToClear.getReadOnlyTasks()))); + break; + } + } + } + //@@author + + /** + * Replaces the room {@code target} in the list with {@code editedRoom}. + * {@code target} must exist in the list. + * The room identity of {@code editedRoom} must not be the same as another existing room in the list. + * + * @param target Room to be changed. + * @param editedRoom Room that has been changed. + */ + public void setSingleRoom(Room target, Room editedRoom) { + int index = internalList.indexOf(target); + if (index == -1) { + throw new RoomNotFoundException(); + } + + if (!target.isSameRoom(editedRoom) && containsRoom(editedRoom)) { + throw new DuplicateRoomException(); + } + rooms.remove(target); // this and the next LOC is to replace the room in the priority queue + rooms.add(editedRoom); + internalList.set(index, editedRoom); + } + + @Override + public int hashCode() { + int result = Objects.hash(numOfRooms, rooms, internalList); + result = 31 * result; + return result; + } + + public void setPreferredNumOfRooms(int numOfRooms) { + this.numOfRooms = numOfRooms; + } + + public void setRooms(PriorityQueue rooms) { + this.rooms = rooms; + } + +} diff --git a/src/main/java/seedu/address/model/room/exceptions/DuplicateRoomException.java b/src/main/java/seedu/address/model/room/exceptions/DuplicateRoomException.java new file mode 100644 index 00000000000..011ae782a77 --- /dev/null +++ b/src/main/java/seedu/address/model/room/exceptions/DuplicateRoomException.java @@ -0,0 +1,11 @@ +package seedu.address.model.room.exceptions; + +/** + * Signals that the operation will result in duplicate Rooms (Rooms are considered duplicates if they + * have the same identity). + */ +public class DuplicateRoomException extends RuntimeException { + public DuplicateRoomException() { + super("Operation would result in duplicate rooms"); + } +} diff --git a/src/main/java/seedu/address/model/room/exceptions/RoomNotFoundException.java b/src/main/java/seedu/address/model/room/exceptions/RoomNotFoundException.java new file mode 100644 index 00000000000..1d44976f2fa --- /dev/null +++ b/src/main/java/seedu/address/model/room/exceptions/RoomNotFoundException.java @@ -0,0 +1,6 @@ +package seedu.address.model.room.exceptions; + +/** + * Signals that the operation is unable to find the specified room. + */ +public class RoomNotFoundException extends RuntimeException {} diff --git a/src/main/java/seedu/address/model/tag/Tag.java b/src/main/java/seedu/address/model/tag/Tag.java deleted file mode 100644 index b0ea7e7dad7..00000000000 --- a/src/main/java/seedu/address/model/tag/Tag.java +++ /dev/null @@ -1,54 +0,0 @@ -package seedu.address.model.tag; - -import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.AppUtil.checkArgument; - -/** - * Represents a Tag in the address book. - * Guarantees: immutable; name is valid as declared in {@link #isValidTagName(String)} - */ -public class Tag { - - public static final String MESSAGE_CONSTRAINTS = "Tags names should be alphanumeric"; - public static final String VALIDATION_REGEX = "\\p{Alnum}+"; - - public final String tagName; - - /** - * Constructs a {@code Tag}. - * - * @param tagName A valid tag name. - */ - public Tag(String tagName) { - requireNonNull(tagName); - checkArgument(isValidTagName(tagName), MESSAGE_CONSTRAINTS); - this.tagName = tagName; - } - - /** - * Returns true if a given string is a valid tag name. - */ - public static boolean isValidTagName(String test) { - return test.matches(VALIDATION_REGEX); - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof Tag // instanceof handles nulls - && tagName.equals(((Tag) other).tagName)); // state check - } - - @Override - public int hashCode() { - return tagName.hashCode(); - } - - /** - * Format state as text for viewing. - */ - public String toString() { - return '[' + tagName + ']'; - } - -} diff --git a/src/main/java/seedu/address/model/task/DateTimeDue.java b/src/main/java/seedu/address/model/task/DateTimeDue.java new file mode 100644 index 00000000000..a2ca5f85bd1 --- /dev/null +++ b/src/main/java/seedu/address/model/task/DateTimeDue.java @@ -0,0 +1,152 @@ +package seedu.address.model.task; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; +import java.util.Optional; + +import seedu.address.commons.util.DateTimeUtil; + +//@@author w-yeehong +/** + * Represents the date-time when a Task is due. + * Guarantees: immutable; is an optional attribute of task; + * is valid as declared in {@link #isValidDateTimeDue(String)} + */ +public class DateTimeDue implements Comparable { + + public static final String MESSAGE_CONSTRAINTS = "Due dates should adhere to one of the " + + "following formats:\n" + + "1. yyyyMMdd (e.g. 20201230)\n" + + "2. yyyyMMdd HHmm (e.g. 20201230 2359)\n" + + "3. d/M/yyyy (e.g. 30/12/2020)\n" + + "4. d/M/yyyy HHmm (e.g. 30/12/2020 2359)\n" + + "Format 3 and 4 allow a single digit for day and month (e.g. 1/1/2020); \n" + + "Format 1 and 2 do not."; + + public static final DateTimeFormatter[] ALLOWED_DATETIME_FORMATS = { + DateTimeUtil.DATETIME_FORMAT_YEAR_MONTH_DAY_OPTIONAL_TIME, + DateTimeUtil.DATETIME_FORMAT_DAY_MONTH_YEAR_SLASH_DELIMITED_OPTIONAL_TIME + }; + + private static final String NULL_VALUE_TO_STRING = "-"; + + public final Optional value; + private String val; + /** + * Constructs a {@code DateTimeDue}. + * {@code optionalDueAt} must be non-null (but can be empty). + * + * If the date-time string is empty, the value defaults to {@code Optional.empty()}. + * If the date-time string is present and valid, the value parsed into is a {@code LocalDateTime} + * and wrapped in an {@code Optional}. + * + * Leading and trailing whitespaces will be trimmed. + * + * @param optionalDueAt A valid optional date-time string. + */ + public DateTimeDue(Optional optionalDueAt) { + requireNonNull(optionalDueAt); + value = optionalDueAt + .map((dueAt) -> { + String trimmedDueAt = dueAt.trim(); + checkArgument(isValidDateTimeDue(trimmedDueAt), MESSAGE_CONSTRAINTS); + val = trimmedDueAt; + return Optional.of(DateTimeUtil + .parseFirstMatching(trimmedDueAt, LocalDateTime::from, ALLOWED_DATETIME_FORMATS)); + }) + .orElse(Optional.empty()); + } + + /** + * Constructs a {@code DateTimeDue}. + * {@code dueAt} must be non-null. + * + * Leading and trailing whitespaces will be trimmed. + * + * @param dueAt A valid date-time string. + */ + public DateTimeDue(String dueAt) { + requireNonNull(dueAt); + + String trimmedDueAt = dueAt.trim(); + checkArgument(isValidDateTimeDue(trimmedDueAt), MESSAGE_CONSTRAINTS); + + value = Optional.of(DateTimeUtil + .parseFirstMatching(trimmedDueAt, LocalDateTime::from, ALLOWED_DATETIME_FORMATS)); + val = trimmedDueAt; + } + + public Optional getValue() { + return value; + } + public String getVal() { + return val; + } + /** + * Returns if a given string is a valid date-time. + */ + public static boolean isValidDateTimeDue(String test) { + try { + DateTimeUtil.parseFirstMatching(test, LocalDateTime::from, ALLOWED_DATETIME_FORMATS); + } catch (DateTimeParseException e) { + return false; + } + + return true; + } + + @Override + public String toString() { + return value + .map((dueAt) -> + dueAt.format(DateTimeUtil.DATETIME_FORMAT_DAY_MONTH_YEAR_LONG_SPACE_DELIMITED_OPTIONAL_TIME)) + .orElse(NULL_VALUE_TO_STRING); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof DateTimeDue // instanceof handles nulls + && value.equals(((DateTimeDue) other).value)); // state check + } + + @Override + public int hashCode() { + return value.hashCode(); + } + + @Override + public int compareTo(DateTimeDue other) { + // empty vs other empty -> returns 0 + if (value.isEmpty() && other.value.isEmpty()) { + return 0; + } + + // empty vs other not empty -> returns -1 + if (value.isEmpty() && other.value.isPresent()) { + return -1; + } + + // not empty vs other empty -> returns 1 + if (value.isPresent() && other.value.isEmpty()) { + return 1; + } + + // not empty vs not empty -> returns 1 if date comes after, 0 if same date, -1 if date comes before + assert (value.isPresent() && other.value.isPresent()) : "Both date-time values are present here."; + LocalDateTime dueAt = value.get(); + LocalDateTime otherDueAt = other.value.get(); + + if (dueAt.isEqual(otherDueAt)) { + return 0; + } else if (dueAt.isAfter(otherDueAt)) { + return 1; + } else { + return -1; + } + } +} diff --git a/src/main/java/seedu/address/model/task/Description.java b/src/main/java/seedu/address/model/task/Description.java new file mode 100644 index 00000000000..d9d245fcf43 --- /dev/null +++ b/src/main/java/seedu/address/model/task/Description.java @@ -0,0 +1,56 @@ +package seedu.address.model.task; + +import static java.util.Objects.requireNonNull; + +//@@author w-yeehong +/** + * Represents a description of a Task in the app. + * Guarantees: immutable + */ +public class Description { + + public static final int MINIMUM_LENGTH = 1; + + public static final int MAXIMUM_LENGTH = 4000; + + public static final String MESSAGE_CONSTRAINTS = "The description of a task must be between " + + MINIMUM_LENGTH + " and " + MAXIMUM_LENGTH + " characters long."; + + public final String value; + + /** + * Constructs a {@code Description}. + * + * @param description A description of the Task. + */ + public Description(String description) { + requireNonNull(description); + assert isValidDescription(description) : MESSAGE_CONSTRAINTS; + value = description; + } + + /** + * Returns true if a given string is a valid description. + */ + public static boolean isValidDescription(String test) { + return test.length() >= MINIMUM_LENGTH && test.length() <= MAXIMUM_LENGTH; + } + + @Override + public String toString() { + return value; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Description // instanceof handles nulls + && value.equals(((Description) other).value)); // state check + } + + @Override + public int hashCode() { + return value.hashCode(); + } + +} diff --git a/src/main/java/seedu/address/model/task/DueDatePredicate.java b/src/main/java/seedu/address/model/task/DueDatePredicate.java new file mode 100644 index 00000000000..f1d734c5deb --- /dev/null +++ b/src/main/java/seedu/address/model/task/DueDatePredicate.java @@ -0,0 +1,40 @@ +package seedu.address.model.task; + +import java.util.function.Predicate; + +/** + * Tests that a {@code Task}'s {@code DateTimeDue} falls within the range. + */ +public class DueDatePredicate implements Predicate { + private final DateTimeDue duedate; + + /** + * Constructs a {@code DueDatePredicate}. + * + * @param duedate The start of temperature range. + */ + public DueDatePredicate(DateTimeDue duedate) { + assert duedate.value.isPresent(); + this.duedate = duedate; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof DueDatePredicate // instanceof handles nulls + && duedate == (((DueDatePredicate) other).duedate)); // state check + } + + @Override + public boolean test(Task task) { + if (task.getDueAt().value.isEmpty()) { + return false; + } + + if (duedate.compareTo(task.getDueAt()) >= 0) { + return true; + } + + return false; + } +} diff --git a/src/main/java/seedu/address/model/task/Task.java b/src/main/java/seedu/address/model/task/Task.java new file mode 100644 index 00000000000..dd0b25e0b12 --- /dev/null +++ b/src/main/java/seedu/address/model/task/Task.java @@ -0,0 +1,68 @@ +package seedu.address.model.task; + +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; + +import java.util.Objects; + +//@@author w-yeehong +/** + * Represents a Task that can be assigned to a room. + * Guarantees: details are present and not null, field values are validated, immutable. + */ +public class Task { + + // Data fields (i.e. values entered by user). + private final Description description; + private final DateTimeDue dueAt; + + /** + * Every field apart must be present and not null. + */ + public Task(Description description, DateTimeDue dueAt) { + requireAllNonNull(description, dueAt); + this.description = description; + this.dueAt = dueAt; + } + + public Description getDescription() { + return description; + } + + public DateTimeDue getDueAt() { + return dueAt; + } + + /** + * Returns true if both tasks have the same data and metadata fields. + */ + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof Task)) { + return false; + } + + Task otherTask = (Task) other; + return otherTask.getDescription().equals(getDescription()) + && otherTask.getDueAt().equals(getDueAt()); + } + + @Override + public int hashCode() { + // Use this method for custom fields hashing instead of implementing your own. + return Objects.hash(description, dueAt); + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append(getDescription()) + .append("\n") + .append("Due Date: ") + .append(getDueAt()); + return builder.toString(); + } +} diff --git a/src/main/java/seedu/address/model/task/TaskList.java b/src/main/java/seedu/address/model/task/TaskList.java new file mode 100644 index 00000000000..a1bff9c3fd8 --- /dev/null +++ b/src/main/java/seedu/address/model/task/TaskList.java @@ -0,0 +1,130 @@ +package seedu.address.model.task; + +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.task.exceptions.TaskNotFoundException; + +/** + * A list of tasks that does not allow nulls. + * + * Supports a minimal set of list operations. + */ +public class TaskList implements Iterable { + + private final ObservableList internalList = FXCollections.observableArrayList(); + private final ObservableList internalUnmodifiableList = + FXCollections.unmodifiableObservableList(internalList); + + /** + * Returns true if the list contains an equivalent task as the given argument. + */ + public boolean contains(Task toCheck) { + requireNonNull(toCheck); + return internalList.stream().anyMatch(toCheck::equals); + } + + /** + * Adds a task to the list. + */ + public void add(Task toAdd) { + requireNonNull(toAdd); + internalList.add(toAdd); + } + + /** + * Adds all tasks in {@code tasks} to the list. + */ + public void add(List tasks) { + requireAllNonNull(tasks); + internalList.addAll(tasks); + } + + /** + * Replaces the task {@code target} in the list with {@code editedTask}. + * {@code target} must exist in the list. + */ + public void setTask(Task target, Task editedTask) { + requireAllNonNull(target, editedTask); + + int index = internalList.indexOf(target); + if (index == -1) { + throw new TaskNotFoundException(); + } + + internalList.set(index, editedTask); + } + + /** + * Removes the equivalent task from the list. + * The task must exist in the list. + */ + public void remove(Task toRemove) { + requireNonNull(toRemove); + if (!internalList.remove(toRemove)) { + throw new TaskNotFoundException(); + } + } + + public void setTasks(TaskList replacement) { + requireNonNull(replacement); + internalList.setAll(replacement.internalList); + } + + /** + * Replaces the contents of this list with {@code tasks}. + */ + public void setTasks(List tasks) { + requireAllNonNull(tasks); + internalList.setAll(tasks); + } + + /** + * Returns true if the list is empty. + */ + public boolean isEmpty() { + return internalList.isEmpty(); + } + + /** + * Returns the backing list as an unmodifiable {@code ObservableList}. + */ + public ObservableList asUnmodifiableObservableList() { + return internalUnmodifiableList; + } + + @Override + public Iterator iterator() { + return internalList.iterator(); + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + + for (Task task : internalList) { + builder.append(task); + builder.append("\n"); + } + + return builder.toString().trim(); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof TaskList // instanceof handles nulls + && internalList.equals(((TaskList) other).internalList)); + } + + @Override + public int hashCode() { + return internalList.hashCode(); + } + +} diff --git a/src/main/java/seedu/address/model/task/exceptions/TaskNotFoundException.java b/src/main/java/seedu/address/model/task/exceptions/TaskNotFoundException.java new file mode 100644 index 00000000000..8d122a5692c --- /dev/null +++ b/src/main/java/seedu/address/model/task/exceptions/TaskNotFoundException.java @@ -0,0 +1,6 @@ +package seedu.address.model.task.exceptions; + +/** + * Signals that the operation is unable to find the specified task. + */ +public class TaskNotFoundException extends RuntimeException {} diff --git a/src/main/java/seedu/address/model/util/SampleDataUtil.java b/src/main/java/seedu/address/model/util/SampleDataUtil.java index 1806da4facf..c4eb1b98ea5 100644 --- a/src/main/java/seedu/address/model/util/SampleDataUtil.java +++ b/src/main/java/seedu/address/model/util/SampleDataUtil.java @@ -1,60 +1,67 @@ package seedu.address.model.util; -import java.util.Arrays; -import java.util.Set; -import java.util.stream.Collectors; - -import seedu.address.model.AddressBook; -import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Person; -import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; +import seedu.address.model.PatientRecords; +import seedu.address.model.ReadOnlyList; +import seedu.address.model.RoomList; +import seedu.address.model.patient.Age; +import seedu.address.model.patient.Name; +import seedu.address.model.patient.Patient; +import seedu.address.model.patient.PeriodOfStay; +import seedu.address.model.patient.Phone; +import seedu.address.model.patient.Temperature; +import seedu.address.model.room.Room; +import seedu.address.model.room.RoomTasks; +import seedu.address.model.task.Task; /** - * Contains utility methods for populating {@code AddressBook} with sample data. + * Contains utility methods for populating {@code CovigentApp} with sample data. */ public class SampleDataUtil { - public static Person[] getSamplePersons() { - return new Person[] { - new Person(new Name("Alex Yeoh"), new Phone("87438807"), new Email("alexyeoh@example.com"), - new Address("Blk 30 Geylang Street 29, #06-40"), - getTagSet("friends")), - new Person(new Name("Bernice Yu"), new Phone("99272758"), new Email("berniceyu@example.com"), - new Address("Blk 30 Lorong 3 Serangoon Gardens, #07-18"), - getTagSet("colleagues", "friends")), - new Person(new Name("Charlotte Oliveiro"), new Phone("93210283"), new Email("charlotte@example.com"), - new Address("Blk 11 Ang Mo Kio Street 74, #11-04"), - getTagSet("neighbours")), - new Person(new Name("David Li"), new Phone("91031282"), new Email("lidavid@example.com"), - new Address("Blk 436 Serangoon Gardens Street 26, #16-43"), - getTagSet("family")), - new Person(new Name("Irfan Ibrahim"), new Phone("92492021"), new Email("irfan@example.com"), - new Address("Blk 47 Tampines Street 20, #17-35"), - getTagSet("classmates")), - new Person(new Name("Roy Balakrishnan"), new Phone("92624417"), new Email("royb@example.com"), - new Address("Blk 45 Aljunied Street 85, #11-31"), - getTagSet("colleagues")) + public static Patient[] getSamplePatient() { + return new Patient[] { + new Patient(new Name("Alex Yeoh"), new Temperature("36.7"), new PeriodOfStay("20200908-20200918"), + new Phone("87438807"), new Age("23"), null), + new Patient(new Name("Bernice Yu"), new Temperature("37.0"), new PeriodOfStay("20200808-20200819"), + new Phone("99272758"), new Age("37"), null), + new Patient(new Name("Charlotte Oliveiro"), new Temperature("38.0"), new PeriodOfStay("20200301-20200309"), + new Phone("93210283"), new Age("87"), null), + new Patient(new Name("David Li"), new Temperature("35.8"), new PeriodOfStay("20201001-20201014"), + new Phone("91031282"), new Age("13"), null), + new Patient(new Name("Irfan Ibrahim"), new Temperature("37.7"), new PeriodOfStay("20200901-20200915"), + new Phone("92492021"), new Age("65"), null) }; } - public static ReadOnlyAddressBook getSampleAddressBook() { - AddressBook sampleAb = new AddressBook(); - for (Person samplePerson : getSamplePersons()) { - sampleAb.addPerson(samplePerson); + public static ReadOnlyList getSampleCovigentApp() { + PatientRecords sampleAb = new PatientRecords(); + for (Patient samplePatient : getSamplePatient()) { + sampleAb.addPatient(samplePatient); } return sampleAb; } - /** - * Returns a tag set containing the list of strings given. - */ - public static Set getTagSet(String... strings) { - return Arrays.stream(strings) - .map(Tag::new) - .collect(Collectors.toSet()); + // TODO + public static Room[] getSampleRoom() { + return new Room[] {}; + } + + public static Task[] getSampleTask() { + return new Task[] {}; + } + + public static ReadOnlyList getSampleRoomList() { + RoomList roomList = new RoomList(); + for (Room room : getSampleRoom()) { + roomList.initRooms(room); + } + return roomList; } + public static ReadOnlyList getSampleRoomTasks() { + RoomTasks roomTasks = new RoomTasks(); + for (Task task : getSampleTask()) { + roomTasks.addTask(task); + } + return roomTasks; + } } diff --git a/src/main/java/seedu/address/storage/AddressBookStorage.java b/src/main/java/seedu/address/storage/AddressBookStorage.java deleted file mode 100644 index 4599182b3f9..00000000000 --- a/src/main/java/seedu/address/storage/AddressBookStorage.java +++ /dev/null @@ -1,45 +0,0 @@ -package seedu.address.storage; - -import java.io.IOException; -import java.nio.file.Path; -import java.util.Optional; - -import seedu.address.commons.exceptions.DataConversionException; -import seedu.address.model.ReadOnlyAddressBook; - -/** - * Represents a storage for {@link seedu.address.model.AddressBook}. - */ -public interface AddressBookStorage { - - /** - * Returns the file path of the data file. - */ - Path getAddressBookFilePath(); - - /** - * Returns AddressBook data as a {@link ReadOnlyAddressBook}. - * Returns {@code Optional.empty()} if storage file is not found. - * @throws DataConversionException if the data in storage is not in the expected format. - * @throws IOException if there was any problem when reading from the storage. - */ - Optional readAddressBook() throws DataConversionException, IOException; - - /** - * @see #getAddressBookFilePath() - */ - Optional readAddressBook(Path filePath) throws DataConversionException, IOException; - - /** - * Saves the given {@link ReadOnlyAddressBook} to the storage. - * @param addressBook cannot be null. - * @throws IOException if there was any problem writing to the file. - */ - void saveAddressBook(ReadOnlyAddressBook addressBook) throws IOException; - - /** - * @see #saveAddressBook(ReadOnlyAddressBook) - */ - void saveAddressBook(ReadOnlyAddressBook addressBook, Path filePath) throws IOException; - -} diff --git a/src/main/java/seedu/address/storage/JsonAdaptedPerson.java b/src/main/java/seedu/address/storage/JsonAdaptedPerson.java deleted file mode 100644 index a6321cec2ea..00000000000 --- a/src/main/java/seedu/address/storage/JsonAdaptedPerson.java +++ /dev/null @@ -1,109 +0,0 @@ -package seedu.address.storage; - -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import java.util.stream.Collectors; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; - -import seedu.address.commons.exceptions.IllegalValueException; -import seedu.address.model.person.Address; -import seedu.address.model.person.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; - -/** - * Jackson-friendly version of {@link Person}. - */ -class JsonAdaptedPerson { - - public static final String MISSING_FIELD_MESSAGE_FORMAT = "Person's %s field is missing!"; - - private final String name; - private final String phone; - private final String email; - private final String address; - private final List tagged = new ArrayList<>(); - - /** - * Constructs a {@code JsonAdaptedPerson} with the given person details. - */ - @JsonCreator - public JsonAdaptedPerson(@JsonProperty("name") String name, @JsonProperty("phone") String phone, - @JsonProperty("email") String email, @JsonProperty("address") String address, - @JsonProperty("tagged") List tagged) { - this.name = name; - this.phone = phone; - this.email = email; - this.address = address; - if (tagged != null) { - this.tagged.addAll(tagged); - } - } - - /** - * Converts a given {@code Person} into this class for Jackson use. - */ - public JsonAdaptedPerson(Person source) { - name = source.getName().fullName; - phone = source.getPhone().value; - email = source.getEmail().value; - address = source.getAddress().value; - tagged.addAll(source.getTags().stream() - .map(JsonAdaptedTag::new) - .collect(Collectors.toList())); - } - - /** - * 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. - */ - public Person toModelType() throws IllegalValueException { - final List personTags = new ArrayList<>(); - for (JsonAdaptedTag tag : tagged) { - personTags.add(tag.toModelType()); - } - - if (name == null) { - throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Name.class.getSimpleName())); - } - if (!Name.isValidName(name)) { - throw new IllegalValueException(Name.MESSAGE_CONSTRAINTS); - } - final Name modelName = new Name(name); - - if (phone == null) { - throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Phone.class.getSimpleName())); - } - if (!Phone.isValidPhone(phone)) { - throw new IllegalValueException(Phone.MESSAGE_CONSTRAINTS); - } - final Phone modelPhone = new Phone(phone); - - if (email == null) { - throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Email.class.getSimpleName())); - } - if (!Email.isValidEmail(email)) { - throw new IllegalValueException(Email.MESSAGE_CONSTRAINTS); - } - final Email modelEmail = new Email(email); - - if (address == null) { - throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Address.class.getSimpleName())); - } - if (!Address.isValidAddress(address)) { - throw new IllegalValueException(Address.MESSAGE_CONSTRAINTS); - } - final Address modelAddress = new Address(address); - - final Set modelTags = new HashSet<>(personTags); - return new Person(modelName, modelPhone, modelEmail, modelAddress, modelTags); - } - -} diff --git a/src/main/java/seedu/address/storage/JsonAdaptedTag.java b/src/main/java/seedu/address/storage/JsonAdaptedTag.java deleted file mode 100644 index 0df22bdb754..00000000000 --- a/src/main/java/seedu/address/storage/JsonAdaptedTag.java +++ /dev/null @@ -1,48 +0,0 @@ -package seedu.address.storage; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonValue; - -import seedu.address.commons.exceptions.IllegalValueException; -import seedu.address.model.tag.Tag; - -/** - * Jackson-friendly version of {@link Tag}. - */ -class JsonAdaptedTag { - - private final String tagName; - - /** - * Constructs a {@code JsonAdaptedTag} with the given {@code tagName}. - */ - @JsonCreator - public JsonAdaptedTag(String tagName) { - this.tagName = tagName; - } - - /** - * Converts a given {@code Tag} into this class for Jackson use. - */ - public JsonAdaptedTag(Tag source) { - tagName = source.tagName; - } - - @JsonValue - public String getTagName() { - return tagName; - } - - /** - * Converts this Jackson-friendly adapted tag object into the model's {@code Tag} object. - * - * @throws IllegalValueException if there were any data constraints violated in the adapted tag. - */ - public Tag toModelType() throws IllegalValueException { - if (!Tag.isValidTagName(tagName)) { - throw new IllegalValueException(Tag.MESSAGE_CONSTRAINTS); - } - return new Tag(tagName); - } - -} diff --git a/src/main/java/seedu/address/storage/JsonAdaptedTask.java b/src/main/java/seedu/address/storage/JsonAdaptedTask.java new file mode 100644 index 00000000000..186069ce8d8 --- /dev/null +++ b/src/main/java/seedu/address/storage/JsonAdaptedTask.java @@ -0,0 +1,60 @@ +package seedu.address.storage; + +import java.util.Optional; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.task.DateTimeDue; +import seedu.address.model.task.Description; +import seedu.address.model.task.Task; + + +/** + * Jackson-friendly version of {@link Task}. + */ +//@@author itssodium +public class JsonAdaptedTask { + public static final String DATE_WRONG_FORMAT = "The date is given in the wrong format."; + + private String description; + private String dueAt; + + /** + * Constructs a {@code JsonAdaptedTask} with the given Task details. + */ + @JsonCreator + public JsonAdaptedTask(@JsonProperty("description") String description, + @JsonProperty("dueAt") String dueAt) throws IllegalValueException { + this.description = description; + this.dueAt = dueAt; + } + + /** + * Converts a given {@code Task} into this class for Jackson use. + */ + public JsonAdaptedTask(Task source) { + this.description = source.getDescription().value; + this.dueAt = source.getDueAt().getVal(); + } + + /** + * Converts this Jackson-friendly adapted Task object into the model's {@code Task} object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted Task. + */ + public Task toModelType() throws IllegalValueException { + DateTimeDue dateTimeDue; + try { + if (dueAt == null) { + dateTimeDue = new DateTimeDue(Optional.empty()); + } else { + dateTimeDue = new DateTimeDue(dueAt); + } + } catch (IllegalArgumentException i) { + throw new IllegalValueException(DATE_WRONG_FORMAT); + } + return new Task(new Description(description), dateTimeDue); + } +} diff --git a/src/main/java/seedu/address/storage/JsonAddressBookStorage.java b/src/main/java/seedu/address/storage/JsonAddressBookStorage.java deleted file mode 100644 index dfab9daaa0d..00000000000 --- a/src/main/java/seedu/address/storage/JsonAddressBookStorage.java +++ /dev/null @@ -1,80 +0,0 @@ -package seedu.address.storage; - -import static java.util.Objects.requireNonNull; - -import java.io.IOException; -import java.nio.file.Path; -import java.util.Optional; -import java.util.logging.Logger; - -import seedu.address.commons.core.LogsCenter; -import seedu.address.commons.exceptions.DataConversionException; -import seedu.address.commons.exceptions.IllegalValueException; -import seedu.address.commons.util.FileUtil; -import seedu.address.commons.util.JsonUtil; -import seedu.address.model.ReadOnlyAddressBook; - -/** - * A class to access AddressBook data stored as a json file on the hard disk. - */ -public class JsonAddressBookStorage implements AddressBookStorage { - - private static final Logger logger = LogsCenter.getLogger(JsonAddressBookStorage.class); - - private Path filePath; - - public JsonAddressBookStorage(Path filePath) { - this.filePath = filePath; - } - - public Path getAddressBookFilePath() { - return filePath; - } - - @Override - public Optional readAddressBook() throws DataConversionException { - return readAddressBook(filePath); - } - - /** - * Similar to {@link #readAddressBook()}. - * - * @param filePath location of the data. Cannot be null. - * @throws DataConversionException if the file is not in the correct format. - */ - public Optional readAddressBook(Path filePath) throws DataConversionException { - requireNonNull(filePath); - - Optional jsonAddressBook = JsonUtil.readJsonFile( - filePath, JsonSerializableAddressBook.class); - if (!jsonAddressBook.isPresent()) { - return Optional.empty(); - } - - try { - return Optional.of(jsonAddressBook.get().toModelType()); - } catch (IllegalValueException ive) { - logger.info("Illegal values found in " + filePath + ": " + ive.getMessage()); - throw new DataConversionException(ive); - } - } - - @Override - public void saveAddressBook(ReadOnlyAddressBook addressBook) throws IOException { - saveAddressBook(addressBook, filePath); - } - - /** - * Similar to {@link #saveAddressBook(ReadOnlyAddressBook)}. - * - * @param filePath location of the data. Cannot be null. - */ - public void saveAddressBook(ReadOnlyAddressBook addressBook, Path filePath) throws IOException { - requireNonNull(addressBook); - requireNonNull(filePath); - - FileUtil.createIfMissing(filePath); - JsonUtil.saveJsonFile(new JsonSerializableAddressBook(addressBook), filePath); - } - -} diff --git a/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java b/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java deleted file mode 100644 index 5efd834091d..00000000000 --- a/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java +++ /dev/null @@ -1,60 +0,0 @@ -package seedu.address.storage; - -import java.util.ArrayList; -import java.util.List; -import java.util.stream.Collectors; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonRootName; - -import seedu.address.commons.exceptions.IllegalValueException; -import seedu.address.model.AddressBook; -import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.person.Person; - -/** - * An Immutable AddressBook that is serializable to JSON format. - */ -@JsonRootName(value = "addressbook") -class JsonSerializableAddressBook { - - public static final String MESSAGE_DUPLICATE_PERSON = "Persons list contains duplicate person(s)."; - - private final List persons = new ArrayList<>(); - - /** - * Constructs a {@code JsonSerializableAddressBook} with the given persons. - */ - @JsonCreator - public JsonSerializableAddressBook(@JsonProperty("persons") List persons) { - this.persons.addAll(persons); - } - - /** - * Converts a given {@code ReadOnlyAddressBook} into this class for Jackson use. - * - * @param source future changes to this will not affect the created {@code JsonSerializableAddressBook}. - */ - public JsonSerializableAddressBook(ReadOnlyAddressBook source) { - persons.addAll(source.getPersonList().stream().map(JsonAdaptedPerson::new).collect(Collectors.toList())); - } - - /** - * Converts this address book into the model's {@code AddressBook} object. - * - * @throws IllegalValueException if there were any data constraints violated. - */ - public AddressBook toModelType() throws IllegalValueException { - AddressBook addressBook = new AddressBook(); - for (JsonAdaptedPerson jsonAdaptedPerson : persons) { - Person person = jsonAdaptedPerson.toModelType(); - if (addressBook.hasPerson(person)) { - throw new IllegalValueException(MESSAGE_DUPLICATE_PERSON); - } - addressBook.addPerson(person); - } - return addressBook; - } - -} diff --git a/src/main/java/seedu/address/storage/JsonSerializableTaskList.java b/src/main/java/seedu/address/storage/JsonSerializableTaskList.java new file mode 100644 index 00000000000..50cbd49d587 --- /dev/null +++ b/src/main/java/seedu/address/storage/JsonSerializableTaskList.java @@ -0,0 +1,50 @@ +package seedu.address.storage; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.ReadOnlyList; +import seedu.address.model.room.RoomTasks; +import seedu.address.model.task.Task; + +//@@author itssodium +public class JsonSerializableTaskList { + + private final List tasks = new ArrayList<>(); + + /** + * Constructs a {@code JsonSerializableTaskList} with the given tasks. + */ + @JsonCreator + public JsonSerializableTaskList(@JsonProperty("tasks") List tasks) { + this.tasks.addAll(tasks); + } + + /** + * Converts a given {@code TaskList} into this class for Jackson use. + * + * @param source future changes to this will not affect the created {@code JsonSerializableTaskList}. + */ + public JsonSerializableTaskList(ReadOnlyList source) { + tasks.addAll(source.getReadOnlyList().stream().map(JsonAdaptedTask::new).collect(Collectors.toList())); + } + + /** + * Converts this Task List into the model's {@code TaskList} object. + * + * @throws IllegalValueException if there were any data constraints violated. + */ + public RoomTasks toModelType() throws IllegalValueException { + RoomTasks roomTasks = new RoomTasks(); + for (JsonAdaptedTask jsonAdaptedTask : tasks) { + Task task = jsonAdaptedTask.toModelType(); + roomTasks.addTask(task); + } + return roomTasks; + } +} diff --git a/src/main/java/seedu/address/storage/JsonTaskStorage.java b/src/main/java/seedu/address/storage/JsonTaskStorage.java new file mode 100644 index 00000000000..39a111d3939 --- /dev/null +++ b/src/main/java/seedu/address/storage/JsonTaskStorage.java @@ -0,0 +1,82 @@ +package seedu.address.storage; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +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.ReadOnlyList; +import seedu.address.model.room.RoomTasks; +import seedu.address.model.task.Task; + + +/** + * Reads data from storage data files and imports them into RoomTasks + */ +public class JsonTaskStorage { + private Path task; + + /** + * Creates the TaskOccupancy object + */ + public JsonTaskStorage() {} + + /** + * Creates the TaskOccupancy object + */ + public JsonTaskStorage(Path task) { + this.task = task; + } + + public Path getTask() { + return task; + } + + public Optional> readOnlyTask() throws IOException, DataConversionException { + return readOnlyTask(task); + } + + /** + * Returns RoomTasks data as a {@code ReadOnlyList}. + * 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. + */ + public Optional> readOnlyTask(Path filePath) throws IOException, DataConversionException { + Optional jsonCovigentApp = JsonUtil.readJsonFile( + filePath, JsonSerializableTaskList.class); + if (!jsonCovigentApp.isPresent()) { + return Optional.empty(); + } + try { + return Optional.of(jsonCovigentApp.get().toModelType()); + } catch (IllegalValueException ive) { + throw new DataConversionException(ive); + } + } + + /** + * Saves the task to the {@code roomTasks}. + */ + public void saveTask(RoomTasks roomTasks) throws IOException { + saveTasks(roomTasks, task); + } + + /** + * Saves the task to the {@code roomTasks} with the path. + * + * @throws IOException + */ + public void saveTasks(RoomTasks roomTasks, Path fileTask) throws IOException { + FileUtil.createIfMissing(fileTask); + List tasks = new ArrayList<>(); + tasks.addAll(roomTasks.getReadOnlyList()); + JsonUtil.saveJsonFile(new JsonSerializableTaskList(roomTasks), fileTask); + } +} diff --git a/src/main/java/seedu/address/storage/Storage.java b/src/main/java/seedu/address/storage/Storage.java index beda8bd9f11..df6a1fe9d66 100644 --- a/src/main/java/seedu/address/storage/Storage.java +++ b/src/main/java/seedu/address/storage/Storage.java @@ -5,14 +5,18 @@ import java.util.Optional; import seedu.address.commons.exceptions.DataConversionException; -import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.model.ReadOnlyList; import seedu.address.model.ReadOnlyUserPrefs; import seedu.address.model.UserPrefs; +import seedu.address.model.patient.Patient; +import seedu.address.model.room.Room; +import seedu.address.storage.patient.PatientRecordsStorage; +import seedu.address.storage.rooms.RoomRecordsStorage; /** * API of the Storage component */ -public interface Storage extends AddressBookStorage, UserPrefsStorage { +public interface Storage extends PatientRecordsStorage, UserPrefsStorage, RoomRecordsStorage { @Override Optional readUserPrefs() throws DataConversionException, IOException; @@ -21,12 +25,17 @@ public interface Storage extends AddressBookStorage, UserPrefsStorage { void saveUserPrefs(ReadOnlyUserPrefs userPrefs) throws IOException; @Override - Path getAddressBookFilePath(); + Path getPatientRecordsFilePath(); @Override - Optional readAddressBook() throws DataConversionException, IOException; + Optional> readPatientRecords() throws DataConversionException, IOException; @Override - void saveAddressBook(ReadOnlyAddressBook addressBook) throws IOException; + void savePatientRecords(ReadOnlyList patientRecords) throws IOException; + @Override + Optional> readOnlyRoomOccupancy() throws DataConversionException, IOException; + + @Override + void saveRoomsInformation(ReadOnlyList roomList) throws IOException; } diff --git a/src/main/java/seedu/address/storage/StorageManager.java b/src/main/java/seedu/address/storage/StorageManager.java index 79868290974..96596693161 100644 --- a/src/main/java/seedu/address/storage/StorageManager.java +++ b/src/main/java/seedu/address/storage/StorageManager.java @@ -7,26 +7,33 @@ import seedu.address.commons.core.LogsCenter; import seedu.address.commons.exceptions.DataConversionException; -import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.model.ReadOnlyList; import seedu.address.model.ReadOnlyUserPrefs; import seedu.address.model.UserPrefs; +import seedu.address.model.patient.Patient; +import seedu.address.model.room.Room; +import seedu.address.storage.patient.PatientRecordsStorage; +import seedu.address.storage.rooms.RoomRecordsStorage; /** - * Manages storage of AddressBook data in local storage. + * Manages storage of CovigentApp data in local storage. */ public class StorageManager implements Storage { private static final Logger logger = LogsCenter.getLogger(StorageManager.class); - private AddressBookStorage addressBookStorage; + private PatientRecordsStorage patientRecordsStorage; + private RoomRecordsStorage roomRecordsStorage; private UserPrefsStorage userPrefsStorage; /** - * Creates a {@code StorageManager} with the given {@code AddressBookStorage} and {@code UserPrefStorage}. + * Creates a {@code StorageManager} with the given {@code PatientRecordsStorage} and {@code UserPrefStorage}. */ - public StorageManager(AddressBookStorage addressBookStorage, UserPrefsStorage userPrefsStorage) { + public StorageManager(PatientRecordsStorage patientRecordsStorage, + RoomRecordsStorage roomOccupancyStorage, UserPrefsStorage userPrefsStorage) { super(); - this.addressBookStorage = addressBookStorage; + this.patientRecordsStorage = patientRecordsStorage; this.userPrefsStorage = userPrefsStorage; + this.roomRecordsStorage = roomOccupancyStorage; } // ================ UserPrefs methods ============================== @@ -47,33 +54,58 @@ public void saveUserPrefs(ReadOnlyUserPrefs userPrefs) throws IOException { } - // ================ AddressBook methods ============================== + // ================ Patient Records methods ============================== @Override - public Path getAddressBookFilePath() { - return addressBookStorage.getAddressBookFilePath(); + public Path getPatientRecordsFilePath() { + return patientRecordsStorage.getPatientRecordsFilePath(); } @Override - public Optional readAddressBook() throws DataConversionException, IOException { - return readAddressBook(addressBookStorage.getAddressBookFilePath()); + public Optional> readPatientRecords() throws DataConversionException, IOException { + return readPatientRecords(patientRecordsStorage.getPatientRecordsFilePath()); } @Override - public Optional readAddressBook(Path filePath) throws DataConversionException, IOException { + public Optional> readPatientRecords(Path filePath) throws DataConversionException, + IOException { logger.fine("Attempting to read data from file: " + filePath); - return addressBookStorage.readAddressBook(filePath); + return patientRecordsStorage.readPatientRecords(filePath); } @Override - public void saveAddressBook(ReadOnlyAddressBook addressBook) throws IOException { - saveAddressBook(addressBook, addressBookStorage.getAddressBookFilePath()); + public void savePatientRecords(ReadOnlyList patientRecords) throws IOException { + savePatientRecords(patientRecords, patientRecordsStorage.getPatientRecordsFilePath()); } @Override - public void saveAddressBook(ReadOnlyAddressBook addressBook, Path filePath) throws IOException { + public void savePatientRecords(ReadOnlyList patientRecords, Path filePath) throws IOException { logger.fine("Attempting to write to data file: " + filePath); - addressBookStorage.saveAddressBook(addressBook, filePath); + patientRecordsStorage.savePatientRecords(patientRecords, filePath); } + @Override + public Optional> readOnlyRoomOccupancy() throws DataConversionException { + return readOnlyRoomOccupancy(roomRecordsStorage.getRoomsRecordsFilePath()); + } + + @Override + public Optional> readOnlyRoomOccupancy(Path filePath) throws DataConversionException { + return roomRecordsStorage.readOnlyRoomOccupancy(filePath); + } + + @Override + public Path getRoomsRecordsFilePath() { + return roomRecordsStorage.getRoomsRecordsFilePath(); + } + + @Override + public void saveRoomsInformation(ReadOnlyList roomList) throws IOException { + saveRoomsInformation(roomList, roomRecordsStorage.getRoomsRecordsFilePath()); + } + + @Override + public void saveRoomsInformation(ReadOnlyList roomList, Path fileRoomsOccupied) throws IOException { + roomRecordsStorage.saveRoomsInformation(roomList, fileRoomsOccupied); + } } diff --git a/src/main/java/seedu/address/storage/patient/JsonAdaptedPatient.java b/src/main/java/seedu/address/storage/patient/JsonAdaptedPatient.java new file mode 100644 index 00000000000..5a8cbf183cb --- /dev/null +++ b/src/main/java/seedu/address/storage/patient/JsonAdaptedPatient.java @@ -0,0 +1,117 @@ +package seedu.address.storage.patient; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.patient.Age; +import seedu.address.model.patient.Comment; +import seedu.address.model.patient.Name; +import seedu.address.model.patient.Patient; +import seedu.address.model.patient.PeriodOfStay; +import seedu.address.model.patient.Phone; +import seedu.address.model.patient.Temperature; + +/** + * Jackson-friendly version of {@link Patient}. + */ +public class JsonAdaptedPatient { + + public static final String MISSING_FIELD_MESSAGE_FORMAT = "Patient's %s field is missing!"; + + private final String name; + private final String temperature; + private final String periodOfStay; + private final String phone; + private final String age; + private final String comment; + + /** + * Constructs a {@code JsonAdaptedPatient} with the given patient details. + */ + @JsonCreator + public JsonAdaptedPatient(@JsonProperty("name") String name, @JsonProperty("temperature") String temperature, + @JsonProperty("periodOfStay") String periodOfStay, @JsonProperty("phone") String phone, + @JsonProperty("age") String age, @JsonProperty("comment") String comment) { + this.name = name; + this.temperature = temperature; + this.periodOfStay = periodOfStay; + this.phone = phone; + this.age = age; + this.comment = comment; + } + + /** + * Converts a given {@code Patient} into this class for Jackson use. + */ + public JsonAdaptedPatient(Patient source) { + name = source.getName().fullName; + temperature = source.getTemperature().toString(); + periodOfStay = source.getPeriodOfStay().toString(); + phone = source.getPhone().value; + age = source.getAge().toString(); + comment = source.getComment().toString(); + } + + /** + * Converts this Jackson-friendly adapted Patient object into the model's {@code Patient} object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted patient. + */ + public Patient toModelType() throws IllegalValueException { + if (name == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Name.class.getSimpleName())); + } + if (!Name.isValidName(name)) { + throw new IllegalValueException(Name.MESSAGE_CONSTRAINTS); + } + final Name modelName = new Name(name); + + if (temperature == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + Temperature.class.getSimpleName())); + } + + if (!Temperature.isValidTemperature(temperature)) { + throw new IllegalValueException(Temperature.MESSAGE_CONSTRAINTS); + } + + final Temperature modelTemp = new Temperature(temperature); + + if (periodOfStay == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + PeriodOfStay.class.getSimpleName())); + } + + if (!PeriodOfStay.isValidPeriodOfStay(periodOfStay)) { + throw new IllegalValueException(PeriodOfStay.getErrorMessage(periodOfStay)); + } + + final PeriodOfStay modelPeriod = new PeriodOfStay(periodOfStay); + + if (phone == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Phone.class.getSimpleName())); + } + if (!Phone.isValidPhone(phone)) { + throw new IllegalValueException(Phone.MESSAGE_CONSTRAINTS); + } + final Phone modelPhone = new Phone(phone); + + if (age == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Age.class.getSimpleName())); + } + if (!Age.isValidAge(age)) { + throw new IllegalValueException(Age.getErrorMessage(age)); + } + final Age modelAge = new Age(age); + + if (comment == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Comment.class.getSimpleName())); + } + + final Comment modelComment = new Comment(comment); + + return new Patient(modelName, modelTemp, modelPeriod, modelPhone, modelAge, modelComment); + } + +} diff --git a/src/main/java/seedu/address/storage/patient/JsonPatientRecordsStorage.java b/src/main/java/seedu/address/storage/patient/JsonPatientRecordsStorage.java new file mode 100644 index 00000000000..9b0129c8702 --- /dev/null +++ b/src/main/java/seedu/address/storage/patient/JsonPatientRecordsStorage.java @@ -0,0 +1,83 @@ +package seedu.address.storage.patient; + +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.ReadOnlyList; +import seedu.address.model.patient.Patient; + +//@@author AB3 +/** + * A class to access patient records data stored as a json file on the hard disk. + */ +public class JsonPatientRecordsStorage implements PatientRecordsStorage { + + private static final Logger logger = LogsCenter.getLogger(JsonPatientRecordsStorage.class); + + private Path filePath; + + public JsonPatientRecordsStorage(Path filePath) { + this.filePath = filePath; + } + + public Path getPatientRecordsFilePath() { + return filePath; + } + + @Override + public Optional> readPatientRecords() + throws DataConversionException { + return readPatientRecords(filePath); + } + + /** + * Similar to {@link #readPatientRecords()}. + * + * @param filePath location of the data. Cannot be null. + * @throws DataConversionException if the file is not in the correct format. + */ + public Optional> readPatientRecords(Path filePath) throws DataConversionException { + requireNonNull(filePath); + + Optional jsonPatientRecords = JsonUtil.readJsonFile( + filePath, JsonSerializablePatientRecords.class); + if (!jsonPatientRecords.isPresent()) { + return Optional.empty(); + } + + try { + return Optional.of(jsonPatientRecords.get().toModelType()); + } catch (IllegalValueException ive) { + logger.info("Illegal values found in " + filePath + ": " + ive.getMessage()); + throw new DataConversionException(ive); + } + } + + @Override + public void savePatientRecords(ReadOnlyList patientRecords) throws IOException { + savePatientRecords(patientRecords, filePath); + } + + /** + * Similar to {@code #savePatientRecords(ReadOnlyList)}. + * + * @param filePath location of the data. Cannot be null. + */ + public void savePatientRecords(ReadOnlyList patientRecords, Path filePath) throws IOException { + requireNonNull(patientRecords); + requireNonNull(filePath); + + FileUtil.createIfMissing(filePath); + JsonUtil.saveJsonFile(new JsonSerializablePatientRecords(patientRecords), filePath); + } + +} diff --git a/src/main/java/seedu/address/storage/patient/JsonSerializablePatientRecords.java b/src/main/java/seedu/address/storage/patient/JsonSerializablePatientRecords.java new file mode 100644 index 00000000000..f46daad8394 --- /dev/null +++ b/src/main/java/seedu/address/storage/patient/JsonSerializablePatientRecords.java @@ -0,0 +1,61 @@ +package seedu.address.storage.patient; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonRootName; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.PatientRecords; +import seedu.address.model.ReadOnlyList; +import seedu.address.model.patient.Patient; + +//@@author AB3 +/** + * An Immutable Patient Records that is serializable to JSON format. + */ +@JsonRootName(value = "covigentapp") +public class JsonSerializablePatientRecords { + + public static final String MESSAGE_DUPLICATE_PATIENT = "Patients list contains duplicate patient(s)."; + + private final List patients = new ArrayList<>(); + + /** + * Constructs a {@code JsonSerializablePatientRecords} with the given patients. + */ + @JsonCreator + public JsonSerializablePatientRecords(@JsonProperty("patients") List patients) { + this.patients.addAll(patients); + } + + /** + * Converts a given {@code ReadOnlyPatientRecords} into this class for Jackson use. + * + * @param source future changes to this will not affect the created {@code JsonSerializablePatientRecords}. + */ + public JsonSerializablePatientRecords(ReadOnlyList source) { + patients.addAll(source.getReadOnlyList().stream().map(JsonAdaptedPatient::new).collect(Collectors.toList())); + } + + /** + * Converts this address book into the model's {@code PatientRecords} object. + * + * @throws IllegalValueException if there were any data constraints violated. + */ + public PatientRecords toModelType() throws IllegalValueException { + PatientRecords patientRecords = new PatientRecords(); + for (JsonAdaptedPatient jsonAdaptedPatient : patients) { + Patient patient = jsonAdaptedPatient.toModelType(); + if (patientRecords.hasPatient(patient)) { + throw new IllegalValueException(MESSAGE_DUPLICATE_PATIENT); + } + patientRecords.addPatient(patient); + } + return patientRecords; + } + +} diff --git a/src/main/java/seedu/address/storage/patient/PatientRecordsStorage.java b/src/main/java/seedu/address/storage/patient/PatientRecordsStorage.java new file mode 100644 index 00000000000..ff4291af799 --- /dev/null +++ b/src/main/java/seedu/address/storage/patient/PatientRecordsStorage.java @@ -0,0 +1,50 @@ +package seedu.address.storage.patient; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.Optional; + +import seedu.address.commons.exceptions.DataConversionException; +import seedu.address.model.PatientRecords; +import seedu.address.model.ReadOnlyList; +import seedu.address.model.patient.Patient; + +//@@author AB3 +/** + * Represents a storage for {@link PatientRecords}. + */ +public interface PatientRecordsStorage { + + /** + * Returns the file path of the data file. + */ + Path getPatientRecordsFilePath(); + + /** + * Returns CovigentApp data as a {@code ReadOnlyList}. + * Returns {@code Optional.empty()} if storage file is not found. + * + * @throws DataConversionException if the data in storage is not in the expected format. + * @throws IOException if there was any problem when reading from the storage. + */ + Optional> readPatientRecords() throws DataConversionException, IOException; + + /** + * @see #getPatientRecordsFilePath() + */ + Optional> readPatientRecords(Path filePath) throws DataConversionException, IOException; + + /** + * Saves the given {@code ReadOnlyList} to the storage. + * @param patientRecords cannot be null. + * + * @throws IOException if there was any problem writing to the file. + */ + void savePatientRecords(ReadOnlyList patientRecords) throws IOException; + + /** + * @see #savePatientRecords(ReadOnlyList) + */ + void savePatientRecords(ReadOnlyList patientRecords, Path filePath) throws IOException; + +} diff --git a/src/main/java/seedu/address/storage/rooms/JsonAdaptedRoom.java b/src/main/java/seedu/address/storage/rooms/JsonAdaptedRoom.java new file mode 100644 index 00000000000..8d727cb26b5 --- /dev/null +++ b/src/main/java/seedu/address/storage/rooms/JsonAdaptedRoom.java @@ -0,0 +1,98 @@ +package seedu.address.storage.rooms; + +import java.util.Optional; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.room.Room; +import seedu.address.storage.JsonSerializableTaskList; +import seedu.address.storage.patient.JsonAdaptedPatient; + +//@@author itssodium +public class JsonAdaptedRoom { + + public static final String PATIENT_PRESENT_IS_OCCUPIED_FALSE = "When patient is present isOccupied cannot be false"; + public static final String PATIENT_ABSENT_IS_OCCUPIED_TRUE = "When patient is absent isOccupied cannot be true"; + public static final String DATE_WRONG_FORMAT_IN_TASKS = "The date is given in the wrong format in tasks."; + public static final String PATIENT_WRONG_FORMAT = "The patient is given in the wrong format"; + + private int roomNumber; + private boolean isOccupied; + private JsonAdaptedPatient patient; + private JsonSerializableTaskList tasks; + + /** + * Creates JsonAdaptedRoom based on the inputs given by the user of roomNumber and isOccupied + */ + @JsonCreator + public JsonAdaptedRoom(@JsonProperty("roomNumber") int roomNumber, + @JsonProperty("isOccupied") boolean isOccupied, + @JsonProperty("patient") JsonAdaptedPatient patient, + @JsonProperty("tasks") JsonSerializableTaskList tasks) throws IllegalValueException { + this.roomNumber = roomNumber; + this.isOccupied = isOccupied; + if (patient != null) { + this.patient = patient; + } + if (tasks != null) { + this.tasks = tasks; + } + + } + + /** + * Creates JsonAdaptedRoom from Room object given + */ + public JsonAdaptedRoom(Room source) { + this.roomNumber = source.getRoomNumber(); + this.isOccupied = source.isOccupied(); + if (source.getPatient().isPresent()) { + this.patient = new JsonAdaptedPatient(source.getPatient().get()); + } + this.tasks = new JsonSerializableTaskList(source.getReadOnlyList()); + } + + /** + * Converts this Jackson-friendly adapted Room object into the model's {@code Room} object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted room. + */ + public Room toModelType() throws IllegalValueException { + if (this.patient != null && !isOccupied) { + throw new IllegalValueException(PATIENT_PRESENT_IS_OCCUPIED_FALSE); + } + if (this.patient == null && isOccupied) { + throw new IllegalValueException(PATIENT_ABSENT_IS_OCCUPIED_TRUE); + } + if (patient != null && isPatientInWrongFormat()) { + throw new IllegalValueException(PATIENT_WRONG_FORMAT); + } + if (isTaskNotInCorrectFormat()) { + throw new IllegalValueException(DATE_WRONG_FORMAT_IN_TASKS); + } + if (this.patient == null) { + return new Room(roomNumber, isOccupied, Optional.empty(), tasks.toModelType()); + } + return new Room(roomNumber, isOccupied, Optional.of(patient.toModelType()), tasks.toModelType()); + } + + private boolean isPatientInWrongFormat() { + try { + patient.toModelType(); + return false; + } catch (IllegalValueException i) { + return true; + } + } + + private boolean isTaskNotInCorrectFormat() { + try { + tasks.toModelType(); + return false; + } catch (IllegalValueException i) { + return true; + } + } +} diff --git a/src/main/java/seedu/address/storage/rooms/JsonRoomOccupancyStorage.java b/src/main/java/seedu/address/storage/rooms/JsonRoomOccupancyStorage.java new file mode 100644 index 00000000000..45b86ccd1c7 --- /dev/null +++ b/src/main/java/seedu/address/storage/rooms/JsonRoomOccupancyStorage.java @@ -0,0 +1,77 @@ +package seedu.address.storage.rooms; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +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.ReadOnlyList; +import seedu.address.model.room.Room; + +/** + * Reads data from storage data files and imports them into RoomList + */ +//@@author itssodium +public class JsonRoomOccupancyStorage implements RoomRecordsStorage { + private Path roomsOccupied; + + public JsonRoomOccupancyStorage() {} + /** + * Creates RoomOccupancyStorage object that reads the number of rooms a hotel has and the rooms which are + * occupied + */ + public JsonRoomOccupancyStorage(Path roomsOccupied) { + this.roomsOccupied = roomsOccupied; + } + + public Path getRoomsRecordsFilePath() { + return roomsOccupied; + } + + @Override + public Optional> readOnlyRoomOccupancy() throws DataConversionException { + return readOnlyRoomOccupancy(roomsOccupied); + } + /** + * Returns RoomList data as a {@code ReadOnlyList}. + * 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. + */ + @Override + public Optional> readOnlyRoomOccupancy(Path filePath) throws DataConversionException { + Optional jsonCovigentApp = JsonUtil.readJsonFile( + filePath, JsonSerializableRoomList.class); + if (!jsonCovigentApp.isPresent()) { + return Optional.empty(); + } + try { + return Optional.of(jsonCovigentApp.get().toModelType()); + } catch (IllegalValueException ive) { + throw new DataConversionException(ive); + } + } + + @Override + public void saveRoomsInformation(ReadOnlyList roomList) throws IOException { + saveRoomsInformation(roomList, roomsOccupied); + } + + /** + * Function saves the room numbers of occupied rooms + * + * @param roomList contains information of which rooms are occupied + * @param fileRoomsOccupied Path to where to write the room numbers of occupied rooms + */ + public void saveRoomsInformation(ReadOnlyList roomList, Path fileRoomsOccupied) throws IOException { + FileUtil.createIfMissing(fileRoomsOccupied); + List rooms = new ArrayList<>(); + rooms.addAll(roomList.getReadOnlyList()); + JsonUtil.saveJsonFile(new JsonSerializableRoomList(roomList), fileRoomsOccupied); + } +} diff --git a/src/main/java/seedu/address/storage/rooms/JsonSerializableRoomList.java b/src/main/java/seedu/address/storage/rooms/JsonSerializableRoomList.java new file mode 100644 index 00000000000..f666e160a8a --- /dev/null +++ b/src/main/java/seedu/address/storage/rooms/JsonSerializableRoomList.java @@ -0,0 +1,63 @@ +package seedu.address.storage.rooms; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonRootName; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.ReadOnlyList; +import seedu.address.model.RoomList; +import seedu.address.model.room.Room; + +//@@author itssodium +@JsonRootName(value = "roomList") +public class JsonSerializableRoomList { + + public static final String WRONG_ORDER_OF_ROOM = "Rooms are not being input in the correct order."; + @JsonProperty("rooms") + private final List rooms = new ArrayList<>(); + + /** + * Constructs a {@code JsonSerializableRoomList} with the given rooms. + */ + @JsonCreator + public JsonSerializableRoomList(@JsonProperty("rooms") List rooms) { + this.rooms.addAll(rooms); + } + + /** + * Converts a given {@code RoomList} into this class for Jackson use. + * + * @param source future changes to this will not affect the created {@code JsonSerializableRoomList}. + */ + public JsonSerializableRoomList(ReadOnlyList source) { + rooms.addAll(source.getReadOnlyList().stream().map(JsonAdaptedRoom::new).collect(Collectors.toList())); + } + + /** + * Converts this task list into the model's {@code RoomList} object. + * + * @throws IllegalValueException if there were any data constraints violated. + */ + public RoomList toModelType() throws IllegalValueException { + RoomList roomList = new RoomList(); + int currRoomNum = 1; + for (JsonAdaptedRoom jsonAdaptedRoom : rooms) { + Room room = jsonAdaptedRoom.toModelType(); + if (isNotInOrder(room, currRoomNum)) { + throw new IllegalValueException(WRONG_ORDER_OF_ROOM); + } + currRoomNum++; + roomList.initRooms(room); + } + return roomList; + } + + private boolean isNotInOrder(Room room, int currRoomNum) { + return room.getRoomNumber() != currRoomNum; + } +} diff --git a/src/main/java/seedu/address/storage/rooms/RoomRecordsStorage.java b/src/main/java/seedu/address/storage/rooms/RoomRecordsStorage.java new file mode 100644 index 00000000000..edf16450956 --- /dev/null +++ b/src/main/java/seedu/address/storage/rooms/RoomRecordsStorage.java @@ -0,0 +1,45 @@ +package seedu.address.storage.rooms; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.Optional; + +import seedu.address.commons.exceptions.DataConversionException; +import seedu.address.model.ReadOnlyList; +import seedu.address.model.room.Room; + +//@@author itssodium +public interface RoomRecordsStorage { + + /** + * Returns the file path of the data file. + */ + Path getRoomsRecordsFilePath(); + + /** + * Returns CovigentApp data as a {@code ReadOnlyList}. + * Returns {@code Optional.empty()} if storage file is not found. + * + * @throws DataConversionException if the data in storage is not in the expected format. + * @throws IOException if there was any problem when reading from the storage. + */ + Optional> readOnlyRoomOccupancy() throws DataConversionException, IOException; + + /** + * @see #readOnlyRoomOccupancy() + */ + Optional> readOnlyRoomOccupancy(Path filePath) throws DataConversionException; + + /** + * Saves the given {@code ReadOnlyList} to the storage. + * @param roomList cannot be null. + * + * @throws IOException if there was any problem writing to the file. + */ + void saveRoomsInformation(ReadOnlyList roomList) throws IOException; + + /** + * @see #saveRoomsInformation(ReadOnlyList) + */ + void saveRoomsInformation(ReadOnlyList roomList, Path fileRoomsOccupied) throws IOException; +} diff --git a/src/main/java/seedu/address/ui/HelpWindow.java b/src/main/java/seedu/address/ui/HelpWindow.java index 9a665915949..5b9abe11ebd 100644 --- a/src/main/java/seedu/address/ui/HelpWindow.java +++ b/src/main/java/seedu/address/ui/HelpWindow.java @@ -15,7 +15,7 @@ */ public class HelpWindow extends UiPart { - public static final String USERGUIDE_URL = "https://se-education.org/addressbook-level3/UserGuide.html"; + public static final String USERGUIDE_URL = "https://ay2021s1-cs2103t-w12-1.github.io/tp/UserGuide.html"; public static final String HELP_MESSAGE = "Refer to the user guide: " + USERGUIDE_URL; private static final Logger logger = LogsCenter.getLogger(HelpWindow.class); diff --git a/src/main/java/seedu/address/ui/MainWindow.java b/src/main/java/seedu/address/ui/MainWindow.java index 9106c3aa6e5..deac1f1b146 100644 --- a/src/main/java/seedu/address/ui/MainWindow.java +++ b/src/main/java/seedu/address/ui/MainWindow.java @@ -2,13 +2,17 @@ import java.util.logging.Logger; -import javafx.event.ActionEvent; import javafx.fxml.FXML; -import javafx.scene.control.MenuItem; -import javafx.scene.control.TextInputControl; -import javafx.scene.input.KeyCombination; -import javafx.scene.input.KeyEvent; +import javafx.geometry.Insets; +import javafx.scene.control.Label; +import javafx.scene.control.Tab; +import javafx.scene.control.TabPane; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; import javafx.scene.layout.StackPane; +import javafx.scene.layout.VBox; +import javafx.scene.text.Font; +import javafx.scene.text.FontWeight; import javafx.stage.Stage; import seedu.address.commons.core.GuiSettings; import seedu.address.commons.core.LogsCenter; @@ -30,26 +34,57 @@ public class MainWindow extends UiPart { private Stage primaryStage; private Logic logic; + private Image logoImage = new Image(this.getClass().getResourceAsStream("/images/covigent.png")); + + // attributes for tabs ---------------------- + private Image patientImage = new Image(this.getClass().getResourceAsStream("/images/patientlogo.png")); + private Image roomImage = new Image(this.getClass().getResourceAsStream("/images/roomlogo.png")); + private Image taskImage = new Image(this.getClass().getResourceAsStream("/images/tasklogo.png")); + private final String patientTabContent = "PATIENTS"; + private final String roomTabContent = "ROOMS"; + private final String taskTabContent = "TASKS"; + // Independent Ui parts residing in this Ui container - private PersonListPanel personListPanel; + private PatientListPanel patientListPanel; private ResultDisplay resultDisplay; + private RoomListPanel roomListPanel; + private RoomTaskListPanel roomTaskListPanel; private HelpWindow helpWindow; + @FXML + private ImageView logoIcon; + @FXML private StackPane commandBoxPlaceholder; @FXML - private MenuItem helpMenuItem; + private StackPane patientListPanelPlaceholder; + + @FXML + private TabPane covigentTabs; + + @FXML + private Tab patientTab; + + @FXML + private Tab roomTab; @FXML - private StackPane personListPanelPlaceholder; + private Tab taskTab; @FXML private StackPane resultDisplayPlaceholder; @FXML - private StackPane statusbarPlaceholder; + private StackPane statusBarPlaceholder; + + @FXML + private StackPane roomListPanelPlaceHolder; + + @FXML + private StackPane taskListPanelPlaceholder; + //@@author chiamyunqing /** * Creates a {@code MainWindow} with the given {@code Stage} and {@code Logic}. */ @@ -60,64 +95,62 @@ public MainWindow(Stage primaryStage, Logic logic) { this.primaryStage = primaryStage; this.logic = logic; + //set images + this.setTabContent(patientTab, patientImage, patientTabContent); + this.setTabContent(roomTab, roomImage, roomTabContent); + this.setTabContent(taskTab, taskImage, taskTabContent); + // Configure the UI setWindowDefaultSize(logic.getGuiSettings()); - setAccelerators(); - helpWindow = new HelpWindow(); } - public Stage getPrimaryStage() { - return primaryStage; + private void setTabContent(Tab tab, Image image, String text) { + VBox content = new VBox(); + //set image + ImageView icon = new ImageView(image); + icon.setFitHeight(70); + icon.setFitWidth(70); + //set text + Label label = new Label(text); + label.setFont(Font.font("American Typewriter", FontWeight.BOLD, 15)); + //manually centre-align text + if (!text.equals(patientTabContent)) { + label.setPadding(new Insets(0, 0, 0, 10)); + } + content.getChildren().addAll(icon, label); + tab.setGraphic(content); } + //@@author - private void setAccelerators() { - setAccelerator(helpMenuItem, KeyCombination.valueOf("F1")); + public void displayAppIcon() { + logoIcon.setImage(logoImage); } - /** - * Sets the accelerator of a MenuItem. - * @param keyCombination the KeyCombination value of the accelerator - */ - private void setAccelerator(MenuItem menuItem, KeyCombination keyCombination) { - menuItem.setAccelerator(keyCombination); - - /* - * TODO: the code below can be removed once the bug reported here - * https://bugs.openjdk.java.net/browse/JDK-8131666 - * is fixed in later version of SDK. - * - * According to the bug report, TextInputControl (TextField, TextArea) will - * consume function-key events. Because CommandBox contains a TextField, and - * ResultDisplay contains a TextArea, thus some accelerators (e.g F1) will - * not work when the focus is in them because the key event is consumed by - * the TextInputControl(s). - * - * For now, we add following event filter to capture such key events and open - * help window purposely so to support accelerators even when focus is - * in CommandBox or ResultDisplay. - */ - getRoot().addEventFilter(KeyEvent.KEY_PRESSED, event -> { - if (event.getTarget() instanceof TextInputControl && keyCombination.match(event)) { - menuItem.getOnAction().handle(new ActionEvent()); - event.consume(); - } - }); + + public Stage getPrimaryStage() { + return primaryStage; } /** * Fills up all the placeholders of this window. */ void fillInnerParts() { - personListPanel = new PersonListPanel(logic.getFilteredPersonList()); - personListPanelPlaceholder.getChildren().add(personListPanel.getRoot()); + patientListPanel = new PatientListPanel(logic.getFilteredPatientList()); + patientListPanelPlaceholder.getChildren().add(patientListPanel.getRoot()); + + roomListPanel = new RoomListPanel(logic.getFilteredRoomList()); + roomListPanelPlaceHolder.getChildren().add(roomListPanel.getRoot()); + + roomTaskListPanel = new RoomTaskListPanel(logic.getFilteredRoomTaskRecords()); + taskListPanelPlaceholder.getChildren().add(roomTaskListPanel.getRoot()); resultDisplay = new ResultDisplay(); resultDisplayPlaceholder.getChildren().add(resultDisplay.getRoot()); - StatusBarFooter statusBarFooter = new StatusBarFooter(logic.getAddressBookFilePath()); - statusbarPlaceholder.getChildren().add(statusBarFooter.getRoot()); + StatusBarFooter statusBarFooter = new StatusBarFooter(logic.getCovigentAppFilePath()); + statusBarPlaceholder.getChildren().add(statusBarFooter.getRoot()); CommandBox commandBox = new CommandBox(this::executeCommand); commandBoxPlaceholder.getChildren().add(commandBox.getRoot()); @@ -163,8 +196,9 @@ private void handleExit() { primaryStage.hide(); } - public PersonListPanel getPersonListPanel() { - return personListPanel; + + public PatientListPanel getPatientListPanel() { + return patientListPanel; } /** diff --git a/src/main/java/seedu/address/ui/PatientCard.java b/src/main/java/seedu/address/ui/PatientCard.java new file mode 100644 index 00000000000..1f4387485c6 --- /dev/null +++ b/src/main/java/seedu/address/ui/PatientCard.java @@ -0,0 +1,63 @@ +package seedu.address.ui; + +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Region; +import seedu.address.model.patient.Patient; + +/** + * An UI component that displays information of a {@code Patient}. + */ +public class PatientCard extends UiPart { + + private static final String FXML = "PatientListCard.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 CovigentApp level 4 + */ + + public final Patient patient; + + @FXML + private HBox cardPane; + @FXML + private Label name; + @FXML + private Label id; + @FXML + private Label temperature; + + /** + * Creates a {@code PatientCard} with the given {@code Patient} and index to display. + */ + public PatientCard(Patient patient, int displayedIndex) { + super(FXML); + this.patient = patient; + id.setText(displayedIndex + ". "); + name.setText(patient.getName().fullName); + temperature.setText("Temperature: " + patient.getTemperature().toString()); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof PatientCard)) { + return false; + } + + // state check + PatientCard card = (PatientCard) other; + return id.getText().equals(card.id.getText()) + && patient.equals(card.patient); + } +} diff --git a/src/main/java/seedu/address/ui/PatientDetailsPanel.java b/src/main/java/seedu/address/ui/PatientDetailsPanel.java new file mode 100644 index 00000000000..cbf3ea7846a --- /dev/null +++ b/src/main/java/seedu/address/ui/PatientDetailsPanel.java @@ -0,0 +1,84 @@ +package seedu.address.ui; + +import java.util.logging.Level; +import java.util.logging.Logger; + +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.layout.Region; +import seedu.address.commons.core.LogsCenter; +import seedu.address.model.patient.Patient; + +//@@author chiamyunqing -reused +//Reused code from RoomDetailsPanel class +/** + * Panel containing the details of the patient. + */ +public class PatientDetailsPanel extends UiPart { + + private static final String FXML = "PatientDetailsPanel.fxml"; + private Image imagePatient = new Image(getClass().getResourceAsStream("/images/patient_icon.png")); + private final Logger logger = LogsCenter.getLogger(PatientDetailsPanel.class); + + @FXML + private Label name; + @FXML + private Label patientHeader; + @FXML + private Label phone; + @FXML + private Label temperature; + @FXML + private Label periodOfStay; + @FXML + private Label age; + @FXML + private Label comment; + + public PatientDetailsPanel() { + super(FXML); + } + + /** + * Sets the details of the patient. + */ + public void setPatientDetails(Patient patient) { + //sets up the image + ImageView imageView = new ImageView(imagePatient); + imageView.setFitHeight(30); + imageView.setFitWidth(30); + name.setGraphic(imageView); + //fill in the details + name.setText(patient.getName().toString()); + patientHeader.setUnderline(true); + temperature.setText("Temperature: " + patient.getTemperature().toString()); + periodOfStay.setText("Period of stay: " + patient.getPeriodOfStay().readerFriendlyString()); + phone.setText("Phone number: " + patient.getPhone().toString()); + age.setText("Age: " + patient.getAge().toString()); + comment.setText("Comment: " + patient.getComment().toString()); + + logger.log(Level.INFO, "Displayed patient details successfully."); + } + + /** + * Sets the details for an empty patient records. + */ + public void setNoPatientDetails() { + //sets up the image + ImageView imageView = new ImageView(imagePatient); + imageView.setFitHeight(30); + imageView.setFitWidth(30); + name.setGraphic(imageView); + //fill in the details + name.setText("NO PATIENT PRESENT"); + patientHeader.setUnderline(true); + temperature.setText("Temperature: -"); + periodOfStay.setText("Period of stay: -"); + phone.setText("Phone number: -"); + age.setText("Age: -"); + comment.setText("Comment: -"); + logger.log(Level.INFO, "Set no patient details successfully."); + } +} diff --git a/src/main/java/seedu/address/ui/PatientListPanel.java b/src/main/java/seedu/address/ui/PatientListPanel.java new file mode 100644 index 00000000000..e27a8d3f4c2 --- /dev/null +++ b/src/main/java/seedu/address/ui/PatientListPanel.java @@ -0,0 +1,115 @@ +package seedu.address.ui; + +import java.util.logging.Level; +import java.util.logging.Logger; + +import javafx.collections.ListChangeListener; +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.scene.control.ListCell; +import javafx.scene.control.ListView; +import javafx.scene.input.MouseEvent; +import javafx.scene.layout.AnchorPane; +import javafx.scene.layout.Region; +import seedu.address.commons.core.LogsCenter; +import seedu.address.model.patient.Patient; + +//@@author chiamyunqing-reused +//Reused from RoomListPanel class +/** + * Panel containing the list of patients. + */ +public class PatientListPanel extends UiPart { + private static final String FXML = "PatientListPanel.fxml"; + private final Logger logger = LogsCenter.getLogger(PatientListPanel.class); + + private PatientDetailsPanel patientDetailsPanel = new PatientDetailsPanel(); + + @FXML + private ListView patientListView; + + @FXML + private AnchorPane patientDetailsPanelPlaceholder; + + /** + * Creates a {@code PatientListPanel} with the given {@code ObservableList}. + */ + public PatientListPanel(ObservableList patientList) { + super(FXML); + setPatientDetailsPanel(patientList); + patientDetailsPanelPlaceholder.getChildren().add(patientDetailsPanel.getRoot()); + updateDetailsIfChanged(patientList); + patientListView.setItems(patientList); + patientListView.setCellFactory(listView -> new PatientListViewCell()); + } + + /** + * Updates a {@code PatientDetailsPanel} with the given {@code ObservableList}. + */ + public void setPatientDetailsPanel(ObservableList patientList) { + if (!patientList.isEmpty()) { + patientDetailsPanel.setPatientDetails(patientList.get(0)); + } else { + patientDetailsPanel.setNoPatientDetails(); + } + } + + /** + * Attach listener to {@code patientList} and update details panel. + * Fixes issue of editPatient changes not immediately reflected. + * + * @param patientList to listen for changes. + */ + private void updateDetailsIfChanged(ObservableList patientList) { + patientList.addListener(new ListChangeListener() { + @Override + public void onChanged(Change change) { + while (change.next()) { + if (change.wasAdded()) { + int indexToChange = change.getFrom(); + Patient patientToDisplay = change.getList().get(indexToChange); + patientListView.scrollTo(indexToChange); + patientListView.getSelectionModel().select(indexToChange); + patientListView.getFocusModel().focus(indexToChange); + patientDetailsPanel.setPatientDetails(patientToDisplay); + } else if (change.wasRemoved()) { + if (patientList.size() > 0) { + patientListView.scrollTo(0); + patientListView.getSelectionModel().select(0); + patientListView.getFocusModel().focus(0); + } + setPatientDetailsPanel(patientList); + } + } + } + }); + } + + /** + * Handles mouse click event on the patient card. + * + * @param mouseEvent created by the user + */ + @FXML + public void handleMouseClick(MouseEvent mouseEvent) { + Patient patientToDisplay = patientListView.getSelectionModel().getSelectedItem(); + patientDetailsPanel.setPatientDetails(patientToDisplay); + logger.log(Level.INFO, "Patient mouse click handled."); + } + + /** + * Custom {@code ListCell} that displays the graphics of a {@code Patient} using a {@code PatientCard}. + */ + class PatientListViewCell extends ListCell { + @Override + protected void updateItem(Patient patient, boolean empty) { + super.updateItem(patient, empty); + if (empty || patient == null) { + setGraphic(null); + setText(null); + } else { + setGraphic(new PatientCard(patient, getIndex() + 1).getRoot()); + } + } + } +} diff --git a/src/main/java/seedu/address/ui/PersonCard.java b/src/main/java/seedu/address/ui/PersonCard.java deleted file mode 100644 index 7fc927bc5d9..00000000000 --- a/src/main/java/seedu/address/ui/PersonCard.java +++ /dev/null @@ -1,77 +0,0 @@ -package seedu.address.ui; - -import java.util.Comparator; - -import javafx.fxml.FXML; -import javafx.scene.control.Label; -import javafx.scene.layout.FlowPane; -import javafx.scene.layout.HBox; -import javafx.scene.layout.Region; -import seedu.address.model.person.Person; - -/** - * An UI component that displays information of a {@code Person}. - */ -public class PersonCard extends UiPart { - - private static final String FXML = "PersonListCard.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 - */ - - public final Person person; - - @FXML - private HBox cardPane; - @FXML - private Label name; - @FXML - private Label id; - @FXML - private Label phone; - @FXML - private Label address; - @FXML - private Label email; - @FXML - private FlowPane tags; - - /** - * Creates a {@code PersonCode} with the given {@code Person} and index to display. - */ - public PersonCard(Person person, int displayedIndex) { - super(FXML); - this.person = person; - id.setText(displayedIndex + ". "); - name.setText(person.getName().fullName); - phone.setText(person.getPhone().value); - address.setText(person.getAddress().value); - email.setText(person.getEmail().value); - person.getTags().stream() - .sorted(Comparator.comparing(tag -> tag.tagName)) - .forEach(tag -> tags.getChildren().add(new Label(tag.tagName))); - } - - @Override - public boolean equals(Object other) { - // short circuit if same object - if (other == this) { - return true; - } - - // instanceof handles nulls - if (!(other instanceof PersonCard)) { - return false; - } - - // state check - PersonCard card = (PersonCard) other; - return id.getText().equals(card.id.getText()) - && person.equals(card.person); - } -} diff --git a/src/main/java/seedu/address/ui/PersonListPanel.java b/src/main/java/seedu/address/ui/PersonListPanel.java deleted file mode 100644 index f4c501a897b..00000000000 --- a/src/main/java/seedu/address/ui/PersonListPanel.java +++ /dev/null @@ -1,49 +0,0 @@ -package seedu.address.ui; - -import java.util.logging.Logger; - -import javafx.collections.ObservableList; -import javafx.fxml.FXML; -import javafx.scene.control.ListCell; -import javafx.scene.control.ListView; -import javafx.scene.layout.Region; -import seedu.address.commons.core.LogsCenter; -import seedu.address.model.person.Person; - -/** - * Panel containing the list of persons. - */ -public class PersonListPanel extends UiPart { - private static final String FXML = "PersonListPanel.fxml"; - private final Logger logger = LogsCenter.getLogger(PersonListPanel.class); - - @FXML - private ListView personListView; - - /** - * Creates a {@code PersonListPanel} with the given {@code ObservableList}. - */ - public PersonListPanel(ObservableList personList) { - super(FXML); - personListView.setItems(personList); - personListView.setCellFactory(listView -> new PersonListViewCell()); - } - - /** - * Custom {@code ListCell} that displays the graphics of a {@code Person} using a {@code PersonCard}. - */ - class PersonListViewCell extends ListCell { - @Override - protected void updateItem(Person person, boolean empty) { - super.updateItem(person, empty); - - if (empty || person == null) { - setGraphic(null); - setText(null); - } else { - setGraphic(new PersonCard(person, getIndex() + 1).getRoot()); - } - } - } - -} diff --git a/src/main/java/seedu/address/ui/RoomCard.java b/src/main/java/seedu/address/ui/RoomCard.java new file mode 100644 index 00000000000..a794bc80dc9 --- /dev/null +++ b/src/main/java/seedu/address/ui/RoomCard.java @@ -0,0 +1,88 @@ +package seedu.address.ui; + +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Region; +import javafx.scene.paint.Color; +import javafx.scene.text.Text; +import seedu.address.model.room.Room; + +//@@author LeeMingDe +/** + * An UI component that displays information of a {@code Room}. + */ +public class RoomCard extends UiPart { + + private static final String FXML = "RoomListCard.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 CovigentApp level 4 + */ + + public final Room room; + private Image unoccupied = new Image(this.getClass().getResourceAsStream("/images/green_circle.png")); + private Image occupied = new Image(this.getClass().getResourceAsStream("/images/red_circle.png")); + + @FXML + private HBox cardPane; + @FXML + private Label roomNumber; + @FXML + private ImageView showOccupancy; + @FXML + private Label occupancy; + /** + * Creates a {@code RoomCode} with the given {@code Room} and index to display. + */ + public RoomCard(Room room) { + super(FXML); + this.room = room; + + //position the + occupancy.setTranslateX(-70); + occupancy.setTranslateY(-20); + roomNumber.setTranslateX(10); + roomNumber.setText(String.format("Room #%d", room.getRoomNumber())); + + setRoomCard(room); + } + + private void setRoomCard(Room room) { + if (room.isOccupied()) { + Text text = new Text("OCCUPIED"); + text.setFill(Color.RED); + occupancy.setGraphic(text); + showOccupancy.setImage(occupied); + } else { + Text text = new Text("UNOCCUPIED"); + text.setFill(Color.GREEN); + occupancy.setGraphic(text); + showOccupancy.setImage(unoccupied); + } + } + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof RoomCard)) { + return false; + } + + // state check + RoomCard card = (RoomCard) other; + return roomNumber.getText().equals(card.roomNumber.getText()) + && room.equals(card.room); + } + //@@author LeeMingDe +} diff --git a/src/main/java/seedu/address/ui/RoomDetailsPanel.java b/src/main/java/seedu/address/ui/RoomDetailsPanel.java new file mode 100644 index 00000000000..704e06b7dc1 --- /dev/null +++ b/src/main/java/seedu/address/ui/RoomDetailsPanel.java @@ -0,0 +1,84 @@ +package seedu.address.ui; + +import java.util.logging.Level; +import java.util.logging.Logger; + +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.layout.Region; +import seedu.address.commons.core.LogsCenter; +import seedu.address.model.room.Room; + +//@@author LeeMingDe +/** + * Panel containing the room details. + */ +public class RoomDetailsPanel extends UiPart { + private static final String FXML = "RoomDetailsPanel.fxml"; + private Image imagePatient = new Image(getClass().getResourceAsStream("/images/patient_icon.png")); + private Image imageTask = new Image(getClass().getResourceAsStream("/images/task_icon.png")); + private final Logger logger = LogsCenter.getLogger(RoomDetailsPanel.class); + + @FXML + private Label patientHeader; + + @FXML + private Label taskHeader; + + @FXML + private Label roomNumber; + + @FXML + private Label patientDetails; + + @FXML + private Label taskDetails; + + /** + * Creates a {@code RoomDetailsPanel} with the given {@code Room}. + */ + public RoomDetailsPanel() { + super(FXML); + setIcons(); + } + + private void setIcons() { + ImageView imageView = new ImageView(imagePatient); + imageView.setFitHeight(20); + imageView.setFitWidth(20); + patientHeader.setGraphic(imageView); + imageView = new ImageView(imageTask); + imageView.setFitHeight(20); + imageView.setFitWidth(20); + taskHeader.setGraphic(imageView); + } + + /** + * Sets the room patient and task details. + * + * @param room To set the details for. + */ + public void setRoomDetails(Room room) { + logger.log(Level.INFO, "Set non-empty room details successfully."); + if (room.getPatient().isPresent()) { + patientDetails.setText(room.getPatient().get().toString()); + } else { + patientDetails.setText("No Patient Present."); + } + roomNumber.setText("Room #" + room.getRoomNumber()); + taskDetails.setText(room.getPrintFriendlyTaskSummary()); + } + + /** + * Sets the details for an empty room. + */ + public void setEmptyRoomDetails() { + logger.log(Level.INFO, "Set empty room details successfully."); + roomNumber.setText("NO ROOM PRESENT"); + patientDetails.setText("-"); + taskDetails.setText("-"); + } + //@@author LeeMingDe +} diff --git a/src/main/java/seedu/address/ui/RoomListPanel.java b/src/main/java/seedu/address/ui/RoomListPanel.java new file mode 100644 index 00000000000..16fcda850a4 --- /dev/null +++ b/src/main/java/seedu/address/ui/RoomListPanel.java @@ -0,0 +1,103 @@ +package seedu.address.ui; + +import java.util.logging.Level; +import java.util.logging.Logger; + +import javafx.collections.ListChangeListener; +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.scene.control.ListCell; +import javafx.scene.control.ListView; +import javafx.scene.input.MouseEvent; +import javafx.scene.layout.AnchorPane; +import javafx.scene.layout.Region; +import seedu.address.commons.core.LogsCenter; +import seedu.address.model.room.Room; + +//@@author LeeMingDe +/** + * Panel containing the list of rooms. + */ +public class RoomListPanel extends UiPart { + private static final String FXML = "RoomListPanel.fxml"; + private final Logger logger = LogsCenter.getLogger(RoomListPanel.class); + + private RoomDetailsPanel roomDetailsPanel = new RoomDetailsPanel(); + + @FXML + private ListView roomListView; + + @FXML + private AnchorPane roomDetailsPanelPlaceholder; + + /** + * Creates a {@code RoomListPanel} with the given {@code ObservableList}. + */ + public RoomListPanel(ObservableList roomList) { + super(FXML); + + if (!roomList.isEmpty()) { + roomDetailsPanel.setRoomDetails(roomList.get(0)); + } else { + roomDetailsPanel.setEmptyRoomDetails(); + } + roomDetailsPanelPlaceholder.getChildren().add(roomDetailsPanel.getRoot()); + updateDetailsIfChanged(roomList); + roomListView.setItems(roomList); + roomListView.setCellFactory(listView -> new RoomListViewCell()); + } + + /** + * Handles mouse click event on the items. + * + * @param mouseEvent Created by the user. + */ + @FXML + public void handleMouseClick(MouseEvent mouseEvent) { + Room roomToDisplay = roomListView.getSelectionModel().getSelectedItem(); + roomDetailsPanel.setRoomDetails(roomToDisplay);; + logger.log(Level.INFO, "MouseClick is fired and handled"); + } + + /** + * Attach listener to {@code roomList} and update details panel. + * + * @param roomList RoomList to attach listener to. + */ + private void updateDetailsIfChanged(ObservableList roomList) { + roomList.addListener(new ListChangeListener() { + @Override + public void onChanged(Change change) { + while (change.next()) { + if (change.wasAdded() || change.wasUpdated()) { + logger.log(Level.INFO, "OnChangeListener is fired"); + int indexToChange = change.getFrom(); + Room roomToDisplay = change.getList().get(indexToChange); + roomListView.scrollTo(indexToChange); + roomListView.getSelectionModel().select(indexToChange); + roomListView.getFocusModel().focus(indexToChange); + roomDetailsPanel.setRoomDetails(roomToDisplay); + } + } + } + }); + } + + /** + * Custom {@code ListCell} that displays the graphics of a {@code Room} using a {@code RoomCard}. + */ + class RoomListViewCell extends ListCell { + @Override + protected void updateItem(Room room, boolean empty) { + super.updateItem(room, empty); + + if (empty || room == null) { + setGraphic(null); + setText(null); + } else { + setGraphic(new RoomCard(room).getRoot()); + } + } + } + //@@author LeeMingDe +} diff --git a/src/main/java/seedu/address/ui/RoomTaskListPanel.java b/src/main/java/seedu/address/ui/RoomTaskListPanel.java new file mode 100644 index 00000000000..2466cca60e5 --- /dev/null +++ b/src/main/java/seedu/address/ui/RoomTaskListPanel.java @@ -0,0 +1,55 @@ +package seedu.address.ui; + +import java.util.logging.Logger; + +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.scene.control.ListCell; +import javafx.scene.control.ListView; +import javafx.scene.layout.Region; +import seedu.address.commons.core.LogsCenter; +import seedu.address.model.room.RoomTaskAssociation; + +/** + * Panel containing the list of room with tasks. + */ +public class RoomTaskListPanel extends UiPart { + + private static final String FXML = "RoomTaskListPanel.fxml"; + + private final Logger logger = LogsCenter.getLogger(RoomTaskListPanel.class); + + @FXML + private ListView roomTaskListView; + + /** + * Creates a {@code RoomTaskListPanel} with the given {@code ObservableList}. + */ + public RoomTaskListPanel(ObservableList roomTaskAssociations) { + super(FXML); + roomTaskListView.setItems(roomTaskAssociations); + roomTaskListView.setCellFactory(listView -> new RoomTaskListViewCell()); + logger.info("RoomTaskListPanel has been initialized with tasks from all rooms."); + } + + /** + * Custom {@code ListCell} that displays the graphics of a {@code RoomTaskAssociation} using a {@code TaskCard}. + */ + class RoomTaskListViewCell extends ListCell { + @Override + protected void updateItem(RoomTaskAssociation roomTaskAssociation, boolean empty) { + super.updateItem(roomTaskAssociation, empty); + + if (empty || roomTaskAssociation == null) { + setGraphic(null); + setText(null); + } else { + int roomNumber = roomTaskAssociation.getRoomNumber(); + int taskIndex = roomTaskAssociation.getTaskIndex(); + int totalNumberOfTasksInRoom = roomTaskAssociation.getTotalTasksInRoom(); + setGraphic(new TaskCard(roomNumber, taskIndex, totalNumberOfTasksInRoom, + roomTaskAssociation.getTask()).getRoot()); + } + } + } +} diff --git a/src/main/java/seedu/address/ui/TaskCard.java b/src/main/java/seedu/address/ui/TaskCard.java new file mode 100644 index 00000000000..04a14a84d79 --- /dev/null +++ b/src/main/java/seedu/address/ui/TaskCard.java @@ -0,0 +1,82 @@ +package seedu.address.ui; + +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.control.TextField; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Region; +import seedu.address.model.task.Task; + +/** + * An UI component that displays information of a {@code Task}. + */ +public class TaskCard extends UiPart { + + private static final String FXML = "TaskListCard.fxml"; + + public final int roomNumber; + public final int taskIndex; + public final int totalNumberOfTasksInRoom; + public final Task task; + + private final String roomIdText = "[Room %1$d]"; + private final String taskIdText = "Task %1$d of %2$d"; + private final String dueAtText = "Due Date: %1$s"; + + @FXML + private HBox cardPane; + @FXML + private Label roomId; + @FXML + private Label taskId; + @FXML + private TextField description; + @FXML + private Label dueAt; + + /** + * Creates a {@code TaskCard} with the given {@code Task}. + * + * @param roomNumber The room number of the room in which the task is found. + * @param task The task. + */ + public TaskCard(int roomNumber, int taskIndex, int totalNumberOfTasksInRoom, Task task) { + super(FXML); + this.roomNumber = roomNumber; + this.taskIndex = taskIndex; + this.totalNumberOfTasksInRoom = totalNumberOfTasksInRoom; + this.task = task; + setTaskCard(task); + } + + /** + * Sets the labels in {@code TaskCard} based on the information in {@code Task}. + * + * @param task The task from which the information is retrieved to set the labels. + */ + private void setTaskCard(Task task) { + roomId.setText(String.format(roomIdText, roomNumber)); + taskId.setText(String.format(taskIdText, taskIndex, totalNumberOfTasksInRoom)); + description.setText(task.getDescription().toString()); + dueAt.setText(String.format(dueAtText, task.getDueAt().toString())); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof TaskCard)) { + return false; + } + + // state check + return roomNumber == ((TaskCard) other).roomNumber + && taskIndex == ((TaskCard) other).taskIndex + && totalNumberOfTasksInRoom == ((TaskCard) other).totalNumberOfTasksInRoom + && task.equals(((TaskCard) other).task); + } +} diff --git a/src/main/java/seedu/address/ui/UiManager.java b/src/main/java/seedu/address/ui/UiManager.java index 882027e4537..49f98bf9336 100644 --- a/src/main/java/seedu/address/ui/UiManager.java +++ b/src/main/java/seedu/address/ui/UiManager.java @@ -20,7 +20,7 @@ public class UiManager implements Ui { public static final String ALERT_DIALOG_PANE_FIELD_ID = "alertDialogPane"; private static final Logger logger = LogsCenter.getLogger(UiManager.class); - private static final String ICON_APPLICATION = "/images/address_book_32.png"; + private static final String ICON_APPLICATION = "/images/appLogo.jpg"; private Logic logic; private MainWindow mainWindow; @@ -39,10 +39,10 @@ public void start(Stage primaryStage) { //Set the application icon. primaryStage.getIcons().add(getImage(ICON_APPLICATION)); - try { mainWindow = new MainWindow(primaryStage, logic); mainWindow.show(); //This should be called before creating other UI parts + mainWindow.displayAppIcon(); mainWindow.fillInnerParts(); } catch (Throwable e) { @@ -66,7 +66,7 @@ void showAlertDialogAndWait(Alert.AlertType type, String title, String headerTex private static void showAlertDialogAndWait(Stage owner, AlertType type, String title, String headerText, String contentText) { final Alert alert = new Alert(type); - alert.getDialogPane().getStylesheets().add("view/DarkTheme.css"); + alert.getDialogPane().getStylesheets().add("css/MainWindow.css"); alert.initOwner(owner); alert.setTitle(title); alert.setHeaderText(headerText); diff --git a/src/main/java/seedu/address/ui/UiPart.java b/src/main/java/seedu/address/ui/UiPart.java index fc820e01a9c..2db1fb70a4f 100644 --- a/src/main/java/seedu/address/ui/UiPart.java +++ b/src/main/java/seedu/address/ui/UiPart.java @@ -29,6 +29,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,6 +46,7 @@ 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) { @@ -60,6 +62,7 @@ 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. */ diff --git a/src/main/resources/css/CommandBox.css b/src/main/resources/css/CommandBox.css new file mode 100644 index 00000000000..967be65017f --- /dev/null +++ b/src/main/resources/css/CommandBox.css @@ -0,0 +1,14 @@ +.command-box { + -fx-background-color: #fff1f1; +} + +#commandTextField { + -fx-background-color: transparent #383838 transparent #383838; + -fx-background-insets: 0; + -fx-border-color: #383838 #383838 #ffffff #383838; + -fx-border-insets: 0; + -fx-border-width: 1px; + -fx-font-family: "Segoe UI Light"; + -fx-font-size: 17px; + -fx-text-fill: black; +} diff --git a/src/main/resources/view/Extensions.css b/src/main/resources/css/Extensions.css similarity index 71% rename from src/main/resources/view/Extensions.css rename to src/main/resources/css/Extensions.css index bfe82a85964..328c3e9de77 100644 --- a/src/main/resources/view/Extensions.css +++ b/src/main/resources/css/Extensions.css @@ -5,14 +5,14 @@ .list-cell:empty { /* Empty cells will not have alternating colours */ - -fx-background: #383838; + -fx-background: #aec6cf; } .tag-selector { - -fx-border-width: 1; + -fx-border-width: 1px; -fx-border-color: white; - -fx-border-radius: 3; - -fx-background-radius: 3; + -fx-border-radius: 3px; + -fx-background-radius: 3px; } .tooltip-text { diff --git a/src/main/resources/css/ListCard.css b/src/main/resources/css/ListCard.css new file mode 100644 index 00000000000..507dff8bc30 --- /dev/null +++ b/src/main/resources/css/ListCard.css @@ -0,0 +1,25 @@ +.card-pane { + -fx-background-color: transparent; + -fx-border-width: 0; +} + +.text-field { + -fx-background-color: transparent; + -fx-background-insets: 0; + -fx-padding: 0; + -fx-font-family: "Segoe UI Semibold"; + -fx-font-size: 16px; + -fx-text-fill: #010504; +} + +.label-big { + -fx-font-family: "Segoe UI Semibold"; + -fx-font-size: 16px; + -fx-text-fill: #010504; +} + +.label-small { + -fx-font-family: "Segoe UI"; + -fx-font-size: 13px; + -fx-text-fill: #010504; +} diff --git a/src/main/resources/css/ListPanel.css b/src/main/resources/css/ListPanel.css new file mode 100644 index 00000000000..264757411f5 --- /dev/null +++ b/src/main/resources/css/ListPanel.css @@ -0,0 +1,32 @@ +.list-view { + -fx-background-insets: 0; + -fx-padding: 0; + -fx-background-color: #aec6cf; +} + +.list-cell { + -fx-label-padding: 0 0 0 0; + -fx-graphic-text-gap : 0; + -fx-padding: 0 0 0 0; +} + +.list-cell:filled:even { + -fx-background-color: #fff1f1; +} + +.list-cell:filled:odd { + -fx-background-color: derive(#fff1f1, 20%); +} + +.list-cell:filled:selected { + -fx-background-color: #fff2cc; +} + +.list-cell:filled:selected .card-pane { + -fx-border-color: #78909c; + -fx-border-width: 3px; +} + +.list-cell .label { + -fx-text-fill: #000000; +} diff --git a/src/main/resources/css/ListPanelNoHighlights.css b/src/main/resources/css/ListPanelNoHighlights.css new file mode 100644 index 00000000000..8422f1288dd --- /dev/null +++ b/src/main/resources/css/ListPanelNoHighlights.css @@ -0,0 +1,23 @@ +.list-view { + -fx-background-insets: 0; + -fx-padding: 0; + -fx-background-color: #aec6cf; +} + +.list-cell { + -fx-label-padding: 0 0 0 0; + -fx-graphic-text-gap : 0; + -fx-padding: 0 0 0 0; +} + +.list-cell:filled:even { + -fx-background-color: #fff1f1; +} + +.list-cell:filled:odd { + -fx-background-color: derive(#fff1f1, 20%); +} + +.list-cell .label { + -fx-text-fill: #000000; +} diff --git a/src/main/resources/css/MainWindow.css b/src/main/resources/css/MainWindow.css new file mode 100644 index 00000000000..082ca7a30ce --- /dev/null +++ b/src/main/resources/css/MainWindow.css @@ -0,0 +1,116 @@ +.background { + -fx-background-color: #aec6cf; + background-color: #aec6cf; /* Used in the default.html file */ +} + +.scroll-bar { + -fx-background-color: #aec6cf; +} + +.scroll-bar .thumb { + -fx-background-color: #fff1f1; + -fx-background-insets: 3px; +} + +.scroll-bar .increment-button, .scroll-bar .decrement-button { + -fx-background-color: transparent; + -fx-padding: 0px; +} + +.scroll-bar .increment-arrow, .scroll-bar .decrement-arrow { + -fx-shape: " "; +} + +.scroll-bar:vertical .increment-arrow, .scroll-bar:vertical .decrement-arrow { + -fx-padding: 1px 8px; +} + +.scroll-bar:horizontal .increment-arrow, .scroll-bar:horizontal .decrement-arrow { + -fx-padding: 8px 1px; +} + +/* + * Metro style Push Button + * Author: Pedro Duque Vieira + * http://pixelduke.wordpress.com/2012/10/23/jmetro-windows-8-controls-on-java/ + */ +.button { + -fx-padding: 5 22 5 22; + -fx-border-color: #e2e2e2; + -fx-border-width: 2; + -fx-background-radius: 0; + -fx-background-color: #1d1d1d; + -fx-font-family: "Segoe UI", Helvetica, Arial, sans-serif; + -fx-font-size: 15px; + -fx-text-fill: #d8d8d8; + -fx-background-insets: 0 0 0 0, 0, 1, 2; +} + +.button:hover { + -fx-background-color: #3a3a3a; +} + +.button:pressed, .button:default:hover:pressed { + -fx-background-color: white; + -fx-text-fill: #1d1d1d; +} + +.button:focused { + -fx-border-color: white, white; + -fx-border-width: 1, 1; + -fx-border-style: solid, segments(1, 1); + -fx-border-radius: 0, 0; + -fx-border-insets: 1 1 1 1, 0; +} + +.button:disabled, .button:default:disabled { + -fx-opacity: 0.4; + -fx-background-color: #1d1d1d; + -fx-text-fill: white; +} + +.button:default { + -fx-background-color: -fx-focus-color; + -fx-text-fill: #ffffff; +} + +.button:default:hover { + -fx-background-color: derive(-fx-focus-color, 30%); +} + +.main-logo-stack-pane { + -fx-background-color: #aec6cf; +} + +.main-tab-pane .tab-header-area { + -fx-padding: 1px; + -fx-min-height: 0; + -fx-max-height: 0; + -fx-text-alignment: center; +} + +.main-tab-pane .tab-header-area .tab-header-background { + -fx-background-color: #aec6cf; +} + +.main-tab-pane .tab { + -fx-rotate: 90; + -fx-pref-width: 90px; + -fx-pref-height: 90px; + -fx-background-color: #fff1f1; +} + +.main-tab-pane .tab:selected { + -fx-background-color: derive(#ff9999, 40%); +} + +.main-pane-with-border { + -fx-padding: 5px 10px; + -fx-background-color: #aec6cf; + -fx-border-color: derive(#aec6cf, 5%); + -fx-border-top-width: 1px; +} + +.main-status-bar { + -fx-background-color: #aec6cf; +} diff --git a/src/main/resources/css/PatientDetailsPanel.css b/src/main/resources/css/PatientDetailsPanel.css new file mode 100644 index 00000000000..6b71038f6e2 --- /dev/null +++ b/src/main/resources/css/PatientDetailsPanel.css @@ -0,0 +1,26 @@ +.patient-details-panel .label { + -fx-font-size: 11pt; + -fx-font-family: "Segoe UI Semibold"; + -fx-text-fill: #555555; + -fx-opacity: 0.9; + +} + +#nameOfPatient { + -fx-font-size: 2.2em; + -fx-font-weight: bold; + -fx-text-fill: rgb(31, 31, 31); + -fx-font-family: "Segoe UI Semibold"; + -fx-opacity: 1; +} + +#detailsHeader { + -fx-font-size: 1.4em; + -fx-font-weight: bold; +} + +.scroll-pane { + -fx-padding: 20px 50px 30px 30px; + -fx-border-insets: 20px 50px 30px 30px; + -fx-background-insets: 20px 50px 30px 30px; +} diff --git a/src/main/resources/css/ResultDisplay.css b/src/main/resources/css/ResultDisplay.css new file mode 100644 index 00000000000..fff6549c745 --- /dev/null +++ b/src/main/resources/css/ResultDisplay.css @@ -0,0 +1,15 @@ +.result-display { + -fx-min-height: 100px; + -fx-font-family: "Segoe UI Light"; + -fx-font-size: 17px; + -fx-text-fill: black; +} + +.result-display .content { + -fx-background-color: #fff1f1; + -fx-background-radius: 0; +} + +.result-display .label { + -fx-text-fill: black !important; +} diff --git a/src/main/resources/css/RoomDetailsPanel.css b/src/main/resources/css/RoomDetailsPanel.css new file mode 100644 index 00000000000..e2f918d1a65 --- /dev/null +++ b/src/main/resources/css/RoomDetailsPanel.css @@ -0,0 +1,28 @@ +.room-details-panel .label { + -fx-font-size: 11pt; + -fx-font-family: "Segoe UI Semibold"; + -fx-text-fill: #555555; + -fx-opacity: 0.9; +} + +.scroll-pane { + -fx-padding: 20px 50px 30px 30px; + -fx-border-insets: 20px 50px 30px 30px; + -fx-background-insets: 20px 50px 30px 30px; +} + +.room-details-panel #room-number { + -fx-font-size: 2.2em; + -fx-font-weight: bold; + -fx-text-fill: #000000; + -fx-font-family: "Segoe UI Semibold"; + -fx-opacity: 1; +} + +.room-details-panel .label-header { + -fx-font-size: 1.5em; + -fx-font-weight: bold; + -fx-text-fill: #555555; + -fx-font-family: "Segoe UI Semibold"; + -fx-opacity: 1; +} diff --git a/src/main/resources/css/StatusBarFooter.css b/src/main/resources/css/StatusBarFooter.css new file mode 100644 index 00000000000..f4cd592608e --- /dev/null +++ b/src/main/resources/css/StatusBarFooter.css @@ -0,0 +1,21 @@ +.status-bar { + -fx-background-color: #aec6cf; +} + +.status-bar .label { + -fx-font-size: 15px; + -fx-font-family: "Segoe UI Light"; + -fx-text-fill: black; + -fx-padding: 2px; + -fx-min-height: 2px; +} + +.status-bar-with-border { + -fx-background-color: #aec6cf; + -fx-border-color: derive(#2b5c7b, 10%); + -fx-border-width: 2px; +} + +.status-bar-with-border .label { + -fx-text-fill: white; +} diff --git a/src/main/resources/images/address_book_32.png b/src/main/resources/images/address_book_32.png deleted file mode 100644 index 29810cf1fd9..00000000000 Binary files a/src/main/resources/images/address_book_32.png and /dev/null differ diff --git a/src/main/resources/images/appLogo.jpg b/src/main/resources/images/appLogo.jpg new file mode 100644 index 00000000000..904bf3b9ed1 Binary files /dev/null and b/src/main/resources/images/appLogo.jpg differ diff --git a/src/main/resources/images/covigent.png b/src/main/resources/images/covigent.png new file mode 100644 index 00000000000..c9857db72db Binary files /dev/null and b/src/main/resources/images/covigent.png differ diff --git a/src/main/resources/images/green_circle.png b/src/main/resources/images/green_circle.png new file mode 100644 index 00000000000..63deeecc9a0 Binary files /dev/null and b/src/main/resources/images/green_circle.png differ diff --git a/src/main/resources/images/logo.png b/src/main/resources/images/logo.png new file mode 100644 index 00000000000..72181b089e8 Binary files /dev/null and b/src/main/resources/images/logo.png differ diff --git a/src/main/resources/images/patient.png b/src/main/resources/images/patient.png new file mode 100644 index 00000000000..f58e5a6ad1f Binary files /dev/null and b/src/main/resources/images/patient.png differ diff --git a/src/main/resources/images/patient_icon.png b/src/main/resources/images/patient_icon.png new file mode 100644 index 00000000000..b7ea4044862 Binary files /dev/null and b/src/main/resources/images/patient_icon.png differ diff --git a/src/main/resources/images/patientlogo.png b/src/main/resources/images/patientlogo.png new file mode 100644 index 00000000000..7234b505e5d Binary files /dev/null and b/src/main/resources/images/patientlogo.png differ diff --git a/src/main/resources/images/red_circle.png b/src/main/resources/images/red_circle.png new file mode 100644 index 00000000000..dcb749a59a5 Binary files /dev/null and b/src/main/resources/images/red_circle.png differ diff --git a/src/main/resources/images/roomlogo.png b/src/main/resources/images/roomlogo.png new file mode 100644 index 00000000000..b624df22d73 Binary files /dev/null and b/src/main/resources/images/roomlogo.png differ diff --git a/src/main/resources/images/rooms.png b/src/main/resources/images/rooms.png new file mode 100644 index 00000000000..fcfff59d23d Binary files /dev/null and b/src/main/resources/images/rooms.png differ diff --git a/src/main/resources/images/task.png b/src/main/resources/images/task.png new file mode 100644 index 00000000000..808001402af Binary files /dev/null and b/src/main/resources/images/task.png differ diff --git a/src/main/resources/images/task_icon.png b/src/main/resources/images/task_icon.png new file mode 100644 index 00000000000..769d230e5ef Binary files /dev/null and b/src/main/resources/images/task_icon.png differ diff --git a/src/main/resources/images/tasklogo.png b/src/main/resources/images/tasklogo.png new file mode 100644 index 00000000000..6e571044fe5 Binary files /dev/null and b/src/main/resources/images/tasklogo.png differ diff --git a/src/main/resources/view/CommandBox.fxml b/src/main/resources/view/CommandBox.fxml index 09f6d6fe9e4..9543d7aa5b4 100644 --- a/src/main/resources/view/CommandBox.fxml +++ b/src/main/resources/view/CommandBox.fxml @@ -3,7 +3,6 @@ - - + + - diff --git a/src/main/resources/view/DarkTheme.css b/src/main/resources/view/DarkTheme.css deleted file mode 100644 index 36e6b001cd8..00000000000 --- a/src/main/resources/view/DarkTheme.css +++ /dev/null @@ -1,352 +0,0 @@ -.background { - -fx-background-color: derive(#1d1d1d, 20%); - background-color: #383838; /* Used in the default.html file */ -} - -.label { - -fx-font-size: 11pt; - -fx-font-family: "Segoe UI Semibold"; - -fx-text-fill: #555555; - -fx-opacity: 0.9; -} - -.label-bright { - -fx-font-size: 11pt; - -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"; - -fx-text-fill: white; - -fx-opacity: 1; -} - -.text-field { - -fx-font-size: 12pt; - -fx-font-family: "Segoe UI Semibold"; -} - -.tab-pane { - -fx-padding: 0 0 0 1; -} - -.tab-pane .tab-header-area { - -fx-padding: 0 0 0 0; - -fx-min-height: 0; - -fx-max-height: 0; -} - -.table-view { - -fx-base: #1d1d1d; - -fx-control-inner-background: #1d1d1d; - -fx-background-color: #1d1d1d; - -fx-table-cell-border-color: transparent; - -fx-table-header-border-color: transparent; - -fx-padding: 5; -} - -.table-view .column-header-background { - -fx-background-color: transparent; -} - -.table-view .column-header, .table-view .filler { - -fx-size: 35; - -fx-border-width: 0 0 1 0; - -fx-background-color: transparent; - -fx-border-color: - transparent - transparent - derive(-fx-base, 80%) - transparent; - -fx-border-insets: 0 10 1 0; -} - -.table-view .column-header .label { - -fx-font-size: 20pt; - -fx-font-family: "Segoe UI Light"; - -fx-text-fill: white; - -fx-alignment: center-left; - -fx-opacity: 1; -} - -.table-view:focused .table-row-cell:filled:focused:selected { - -fx-background-color: -fx-focus-color; -} - -.split-pane:horizontal .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; - -fx-background-color: derive(#1d1d1d, 20%); -} - -.list-view { - -fx-background-insets: 0; - -fx-padding: 0; - -fx-background-color: derive(#1d1d1d, 20%); -} - -.list-cell { - -fx-label-padding: 0 0 0 0; - -fx-graphic-text-gap : 0; - -fx-padding: 0 0 0 0; -} - -.list-cell:filled:even { - -fx-background-color: #3c3e3f; -} - -.list-cell:filled:odd { - -fx-background-color: #515658; -} - -.list-cell:filled:selected { - -fx-background-color: #424d5f; -} - -.list-cell:filled:selected #cardPane { - -fx-border-color: #3e7b91; - -fx-border-width: 1; -} - -.list-cell .label { - -fx-text-fill: white; -} - -.cell_big_label { - -fx-font-family: "Segoe UI Semibold"; - -fx-font-size: 16px; - -fx-text-fill: #010504; -} - -.cell_small_label { - -fx-font-family: "Segoe UI"; - -fx-font-size: 13px; - -fx-text-fill: #010504; -} - -.stack-pane { - -fx-background-color: derive(#1d1d1d, 20%); -} - -.pane-with-border { - -fx-background-color: derive(#1d1d1d, 20%); - -fx-border-color: derive(#1d1d1d, 10%); - -fx-border-top-width: 1px; -} - -.status-bar { - -fx-background-color: derive(#1d1d1d, 30%); -} - -.result-display { - -fx-background-color: transparent; - -fx-font-family: "Segoe UI Light"; - -fx-font-size: 13pt; - -fx-text-fill: white; -} - -.result-display .label { - -fx-text-fill: black !important; -} - -.status-bar .label { - -fx-font-family: "Segoe UI Light"; - -fx-text-fill: white; - -fx-padding: 4px; - -fx-pref-height: 30px; -} - -.status-bar-with-border { - -fx-background-color: derive(#1d1d1d, 30%); - -fx-border-color: derive(#1d1d1d, 25%); - -fx-border-width: 1px; -} - -.status-bar-with-border .label { - -fx-text-fill: white; -} - -.grid-pane { - -fx-background-color: derive(#1d1d1d, 30%); - -fx-border-color: derive(#1d1d1d, 30%); - -fx-border-width: 1px; -} - -.grid-pane .stack-pane { - -fx-background-color: derive(#1d1d1d, 30%); -} - -.context-menu { - -fx-background-color: derive(#1d1d1d, 50%); -} - -.context-menu .label { - -fx-text-fill: white; -} - -.menu-bar { - -fx-background-color: derive(#1d1d1d, 20%); -} - -.menu-bar .label { - -fx-font-size: 14pt; - -fx-font-family: "Segoe UI Light"; - -fx-text-fill: white; - -fx-opacity: 0.9; -} - -.menu .left-container { - -fx-background-color: black; -} - -/* - * Metro style Push Button - * Author: Pedro Duque Vieira - * http://pixelduke.wordpress.com/2012/10/23/jmetro-windows-8-controls-on-java/ - */ -.button { - -fx-padding: 5 22 5 22; - -fx-border-color: #e2e2e2; - -fx-border-width: 2; - -fx-background-radius: 0; - -fx-background-color: #1d1d1d; - -fx-font-family: "Segoe UI", Helvetica, Arial, sans-serif; - -fx-font-size: 11pt; - -fx-text-fill: #d8d8d8; - -fx-background-insets: 0 0 0 0, 0, 1, 2; -} - -.button:hover { - -fx-background-color: #3a3a3a; -} - -.button:pressed, .button:default:hover:pressed { - -fx-background-color: white; - -fx-text-fill: #1d1d1d; -} - -.button:focused { - -fx-border-color: white, white; - -fx-border-width: 1, 1; - -fx-border-style: solid, segments(1, 1); - -fx-border-radius: 0, 0; - -fx-border-insets: 1 1 1 1, 0; -} - -.button:disabled, .button:default:disabled { - -fx-opacity: 0.4; - -fx-background-color: #1d1d1d; - -fx-text-fill: white; -} - -.button:default { - -fx-background-color: -fx-focus-color; - -fx-text-fill: #ffffff; -} - -.button:default:hover { - -fx-background-color: derive(-fx-focus-color, 30%); -} - -.dialog-pane { - -fx-background-color: #1d1d1d; -} - -.dialog-pane > *.button-bar > *.container { - -fx-background-color: #1d1d1d; -} - -.dialog-pane > *.label.content { - -fx-font-size: 14px; - -fx-font-weight: bold; - -fx-text-fill: white; -} - -.dialog-pane:header *.header-panel { - -fx-background-color: derive(#1d1d1d, 25%); -} - -.dialog-pane:header *.header-panel *.label { - -fx-font-size: 18px; - -fx-font-style: italic; - -fx-fill: white; - -fx-text-fill: white; -} - -.scroll-bar { - -fx-background-color: derive(#1d1d1d, 20%); -} - -.scroll-bar .thumb { - -fx-background-color: derive(#1d1d1d, 50%); - -fx-background-insets: 3; -} - -.scroll-bar .increment-button, .scroll-bar .decrement-button { - -fx-background-color: transparent; - -fx-padding: 0 0 0 0; -} - -.scroll-bar .increment-arrow, .scroll-bar .decrement-arrow { - -fx-shape: " "; -} - -.scroll-bar:vertical .increment-arrow, .scroll-bar:vertical .decrement-arrow { - -fx-padding: 1 8 1 8; -} - -.scroll-bar:horizontal .increment-arrow, .scroll-bar:horizontal .decrement-arrow { - -fx-padding: 8 1 8 1; -} - -#cardPane { - -fx-background-color: transparent; - -fx-border-width: 0; -} - -#commandTypeLabel { - -fx-font-size: 11px; - -fx-text-fill: #F70D1A; -} - -#commandTextField { - -fx-background-color: transparent #383838 transparent #383838; - -fx-background-insets: 0; - -fx-border-color: #383838 #383838 #ffffff #383838; - -fx-border-insets: 0; - -fx-border-width: 1; - -fx-font-family: "Segoe UI Light"; - -fx-font-size: 13pt; - -fx-text-fill: white; -} - -#filterField, #personListPanel, #personWebpage { - -fx-effect: innershadow(gaussian, black, 10, 0, 0, 0); -} - -#resultDisplay .content { - -fx-background-color: transparent, #383838, transparent, #383838; - -fx-background-radius: 0; -} - -#tags { - -fx-hgap: 7; - -fx-vgap: 3; -} - -#tags .label { - -fx-text-fill: white; - -fx-background-color: #3e7b91; - -fx-padding: 1 3 1 3; - -fx-border-radius: 2; - -fx-background-radius: 2; - -fx-font-size: 11; -} diff --git a/src/main/resources/view/HelpWindow.fxml b/src/main/resources/view/HelpWindow.fxml index fa0fb54d9f4..9aa2c65fab3 100644 --- a/src/main/resources/view/HelpWindow.fxml +++ b/src/main/resources/view/HelpWindow.fxml @@ -1,5 +1,5 @@ - + @@ -14,26 +14,26 @@ - - - - - - - - - - - - + + + + + + + + + + + + diff --git a/src/main/resources/view/MainWindow.fxml b/src/main/resources/view/MainWindow.fxml index a431648f6c0..5d04fbbfa0e 100644 --- a/src/main/resources/view/MainWindow.fxml +++ b/src/main/resources/view/MainWindow.fxml @@ -1,59 +1,74 @@ + + + + + - - - - - - + + - + - + - + - - + + - - - - - - - - - + + + + + + + + + - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - + - - - - - - - - + + + + + diff --git a/src/main/resources/view/PatientDetailsPanel.fxml b/src/main/resources/view/PatientDetailsPanel.fxml new file mode 100644 index 00000000000..dbcf147d570 --- /dev/null +++ b/src/main/resources/view/PatientDetailsPanel.fxml @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/PersonListCard.fxml b/src/main/resources/view/PatientListCard.fxml similarity index 67% rename from src/main/resources/view/PersonListCard.fxml rename to src/main/resources/view/PatientListCard.fxml index f08ea32ad55..658fbe51c3d 100644 --- a/src/main/resources/view/PersonListCard.fxml +++ b/src/main/resources/view/PatientListCard.fxml @@ -9,7 +9,7 @@ - + @@ -19,18 +19,16 @@ - - diff --git a/src/main/resources/view/PatientListPanel.fxml b/src/main/resources/view/PatientListPanel.fxml new file mode 100644 index 00000000000..0731beb9dc9 --- /dev/null +++ b/src/main/resources/view/PatientListPanel.fxml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/PersonListPanel.fxml b/src/main/resources/view/PersonListPanel.fxml deleted file mode 100644 index 8836d323cc5..00000000000 --- a/src/main/resources/view/PersonListPanel.fxml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/src/main/resources/view/ResultDisplay.fxml b/src/main/resources/view/ResultDisplay.fxml index 58d5ad3dc56..f31c359f188 100644 --- a/src/main/resources/view/ResultDisplay.fxml +++ b/src/main/resources/view/ResultDisplay.fxml @@ -3,7 +3,6 @@ - -