diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 6ff220b5196..4198dc42f8d 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -1,12 +1,12 @@ name: Java CI -on: [push, pull_request] +on: [ push, pull_request ] jobs: build: strategy: matrix: - platform: [ubuntu-latest, macos-latest, windows-latest] + platform: [ ubuntu-latest, macos-latest, windows-latest ] runs-on: ${{ matrix.platform }} steps: @@ -23,7 +23,7 @@ jobs: - name: Run repository-wide tests if: runner.os == 'Linux' - working-directory: ${{ github.workspace }}/.github + working-directory: ${{ github.workspace }}/.github run: ./run-checks.sh - name: Validate Gradle Wrapper diff --git a/ClientRoles.txt b/ClientRoles.txt new file mode 100644 index 00000000000..50d851cd64d --- /dev/null +++ b/ClientRoles.txt @@ -0,0 +1,4 @@ +Manager +Developer +HR +Client diff --git a/DeveloperRoles.txt b/DeveloperRoles.txt new file mode 100644 index 00000000000..e66210a9b28 --- /dev/null +++ b/DeveloperRoles.txt @@ -0,0 +1,3 @@ +Frontend Developer +Backend Developer +Developer diff --git a/LICENSE b/LICENSE index 39b3478982c..574ddfb6cda 100644 --- a/LICENSE +++ b/LICENSE @@ -2,11 +2,11 @@ MIT License Copyright (c) 2016 Software Engineering Education - FOSS Resources -Permission is hereby granted, free of charge, to any person obtaining a copy +Permission is hereby granted, free of charge, to any developer obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is +copies of the Software, and to permit developers to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all diff --git a/README.md b/README.md index 13f5c77403f..e80dd6d924d 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,28 @@ -[![CI Status](https://github.com/se-edu/addressbook-level3/workflows/Java%20CI/badge.svg)](https://github.com/se-edu/addressbook-level3/actions) +[![CI Status](https://github.com/AY2324S1-CS2103T-T09-2/tp/workflows/Java%20CI/badge.svg)](https://github.com/AY2324S1-CS2103T-T09-2/tp/actions) ![Ui](docs/images/Ui.png) -* This is **a sample project for Software Engineering (SE) students**.
- Example usages: - * as a starting point of a course project (as opposed to writing everything from scratch) - * as a case study -* The project simulates an ongoing software project for a desktop application (called _AddressBook_) used for managing contact details. - * It is **written in OOP fashion**. It provides a **reasonably well-written** code base **bigger** (around 6 KLoC) than what students usually write in beginner-level SE modules, without being overwhelmingly big. - * It comes with a **reasonable level of user and developer documentation**. -* It is named `AddressBook Level 3` (`AB3` for short) because it was initially created as a part of a series of `AddressBook` projects (`Level 1`, `Level 2`, `Level 3` ...). -* For the detailed documentation of this project, see the **[Address Book Product Website](https://se-education.org/addressbook-level3)**. -* This project is a **part of the se-education.org** initiative. If you would like to contribute code to this project, see [se-education.org](https://se-education.org#https://se-education.org/#contributing) for more info. +**CodeContact is a desktop application for managing your employee details in a software company.** While it has a GUI, +most of the user interactions happen using a CLI (Command Line Interface).
+ +Example usages: + +* An HR of a software company can easily access the contact information of all employees so that he/she can quickly + communicate with them regarding HR-related matters. +* The project manager can use it to maintain a list of project collaborators and their contact information so that + he/she can quickly assemble teams for new projects. +* A basic developer in a software company can view the positions and roles of everyone in the contact list so that + he/she knows who to look for when needing some help. + +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 `CodeContact` (`CC` for short). +* For the detailed documentation of this project, see the * + *[Code Contact Product Website](https://ay2324s1-cs2103t-t09-2.github.io/tp/)**. +* This project is based on the AddressBook-Level3 project created by the [SE-EDU initiative](https://se-education.org). + If you would like to contribute code to this project, + see [se-education.org](https://se-education.org#https://se-education.org/#contributing) for more info. diff --git a/build.gradle b/build.gradle index a2951cc709e..6f45dd25754 100644 --- a/build.gradle +++ b/build.gradle @@ -4,6 +4,7 @@ plugins { id 'com.github.johnrengelman.shadow' version '7.1.2' id 'application' id 'jacoco' + id 'org.openjfx.javafxplugin' version '0.0.13' } mainClassName = 'seedu.address.Main' @@ -16,6 +17,11 @@ repositories { maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' } } +javafx { + version = "17.0.7" + modules = ['javafx.controls', 'javafx.fxml'] +} + checkstyle { toolVersion = '10.2' } @@ -43,7 +49,8 @@ task coverage(type: JacocoReport) { dependencies { String jUnitVersion = '5.4.0' String javaFxVersion = '17.0.7' - + implementation group: 'com.azure.tools', name: 'azure-sdk-build-tool', version: '1.0.0' + implementation group: 'org.controlsfx', name: 'controlsfx', version: '11.1.2' implementation group: 'org.openjfx', name: 'javafx-base', version: javaFxVersion, classifier: 'win' implementation group: 'org.openjfx', name: 'javafx-base', version: javaFxVersion, classifier: 'mac' implementation group: 'org.openjfx', name: 'javafx-base', version: javaFxVersion, classifier: 'linux' @@ -63,10 +70,16 @@ dependencies { testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: jUnitVersion testRuntimeOnly group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: jUnitVersion + // https://mvnrepository.com/artifact/org.controlsfx/controlsfx + } shadowJar { - archiveFileName = 'addressbook.jar' + archiveFileName = 'CodeContact.jar' } defaultTasks 'clean', 'test' + +run { + enableAssertions = true +} diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml index eb761a9b9a7..779583ce17e 100644 --- a/config/checkstyle/checkstyle.xml +++ b/config/checkstyle/checkstyle.xml @@ -1,7 +1,7 @@ + "-//Checkstyle//DTD Checkstyle Configuration 1.3//EN" + "https://checkstyle.org/dtds/configuration_1_3.dtd"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - - - - - - - - + + + - - - - - - - - - - - - - - + + + + - - - - + + - - - - + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - - + + + + + + - - - - + - - - - - + + + + + + + + + + - - - + - if (true) { return 1; } // Not allowed + - else if { - return 1; // else if should always be multi line - } + - if (true) - return 1; // Not allowed - --> - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + - - + + - - - - + + - + + + - - - - - - + - - - - - - - - - - - - - - - - - + + - - - - - - + + - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - + + + + + + + + + + + + - - - - - + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - + + - + + - - + + - - + + - - + + - - + + - - + + + + - - + + + + + + + + + - - - - + - - - - - - - - - + + + + + + - + + - - - - - - - - - - diff --git a/config/checkstyle/suppressions.xml b/config/checkstyle/suppressions.xml index 39efb6e4acb..6423654db1a 100644 --- a/config/checkstyle/suppressions.xml +++ b/config/checkstyle/suppressions.xml @@ -1,10 +1,10 @@ + "-//Checkstyle//DTD SuppressionFilter Configuration 1.2//EN" + "https://checkstyle.org/dtds/suppressions_1_2.dtd"> - - + + diff --git a/docs/2103UserGuide b/docs/2103UserGuide new file mode 100644 index 00000000000..2975e80eb99 --- /dev/null +++ b/docs/2103UserGuide @@ -0,0 +1,1297 @@ +--- +layout: page +title: User Guide +--- + +## Welcome to CodeContact +{: .no_toc} + +***Taking charge of your PROject Management!*** + +Seamlessly integrate contact, client, and project management, simplifying access to coding-related contacts, +facilitating collaboration, and offering command-line efficiency for project managers. + +CodeContact 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, CodeContact can get your contact +management tasks done faster than traditional GUI apps. **CodeContact** aims to put the **P.R.O** in project management by enabling project managers to be: +1. **Productive** +2. **Reliable** +3. **Organised** + +Here’s an overview of how CodeContact can help you streamline your project management processes. +* Store and edit information about developers and clients related to projects +* Store and manage projects and their deadlines +* Assign projects to developers and clients + +If you are familiar with CodeContact, jump to our [**Table of Contents**](#table-of-contents) to find out what you are looking +for! + +If you are new here, you may start with learning [**How to navigate this guide**](#navigating-this-guide) and visit +the [**Quick Start**](#quick-start) guide to onboard onto CodeContact smoothly! +
+ +------------------------------------------------------------------------------------- +## Table of Contents +{: .no_toc} + +* Table of Contents +{:toc} + +------------------------------------------------------------------------------------- +
+ +### How can this guide help me? +{: .no_toc} + +If you are a new user, we hope to first inform you on how you can [get started](#quick-start) using CodeContact. + +As you use CodeContact, you may also have questions on how to perform certain actions within the +application. This guide thus contains a comprehensive list of [Features](#features) offered with CodeContact, as well as +explanations on when and how to use them. + +Further questions are also answered within a [FAQ](#faq) section below. + +Confused about the terms or formatting used in this guide? Learn how to **navigate this guide** [here](#navigating-this-guide). + +Confused about the visual display of CodeContact? Learn how to **navigate the user interface** of CodeContact +[here](#navigating-the-graphical-user-interface-gui). + +------------------------------------------------------------------------------------------ +# **Navigating this guide** + + + +## **Glossary** +------------------------------------------------------------------------------------- +### Definitions + +| Term | Definition | +|---------------|----------------------------------------------------------------------------------------------------------------------------| +| Parameter | Parameters are specific details you would include about the developer/client/project.(eg. name, date joined, description). | +| Command | An input from the user that tells CodeContact to perform an action (i.e. add a client). | +| GUI | Graphical User Interface (GUI) represents the visual display of CodeContact that users can see. | +| GUI Component | A subsection of the Graphical User Interface. For more information on specific GUI components, refer to [this section](). | +| CLI | Command Line Interface (CLI) represents a text-based user interface to interact with the application. | +| Character | Any letter or symbol that is recognized by the computer, and can form a line of text (eg. `a` , `+` , `$` ). | +| JSON | [Javascript Object Notation](https://en.wikipedia.org/wiki/JSON) | +| JAR file | [Java Archive File](https://en.wikipedia.org/wiki/JAR_(file_format)) | +| CSV file | [Comma-separated Values File](https://en.wikipedia.org/wiki/Comma-separated_values) | + +[Scroll back to Table of Contents](#table-of-contents) + +------------------------------------------------------------------------------------- +### Parameter Information + +Within the tables below, you can find out more about the parameters that CodeContact supports. These parameters come in +handy when crafting commands in CodeContact. + +Here are some notes about these parameters. + +* Each parameter comes with **constraints**. These constraints detail the specific formats of text that + each parameter accepts as valid user input. + * Not following these constraints will **result in an error** when entering the command. + * Nonetheless, CodeContact will not stop working. Rather, a message will be provided to you on + how to correct your command. + +#### Common Parameters + +| Parameter | Description | Constraints | Valid Examples | Invalid Examples | +|-----------|----------------------------------|-------------------------------------------------------------------------------------------------------------------------------------|-----------------------------|---------------------------------------------| +| `n/` | name of developer/client/project | alphanumeric characters and spaces, and it should not be blank | Tom Hanks, Elizabeth 2 | 成龍, 潔 いさぎ 世 よ 一 いち, Ganesh s/o Ravichandran | +| `p/` | phone number of developer/client | 8 numeric characters, and it should not be blank | 94566835 | 123, 432, 5678@ | +| `e/` | email of developer/client | alphanumeric characters, contains an @ and it should not be blank | amy@gmail.com | amy!gmail.com, amy$gmail | +| `a/` | address of developer/client | alphanumeric characters and spaces, and it should not be blank | 311, Clementi Ave 2, #02-25 | 成龍, 潔 いさぎ 世 よ 一 いち | +| `r/` | role of developer/client | alphabetical characters and spaces, and it should not be blank | Developer | 成龍, 潔 いさぎ 世 よ 一 いち | +| `pr/` | project name | alphanumeric characters and spaces, and it should not be blank, used in the context or Developer/Client when assigned to project(s) | CS2103T | 成龍, 潔 いさぎ 世 よ 一 いち | + +#### Developer Parameters + +| Parameter | Description | Constraints | Valid Examples | Invalid Examples | +|----------|------------------------------|--------------------------------------------------------------------------------------------------|----------------|----------------------| +| `g/` | github username of developer | alphanumeric characters, follows github username convention and it should not be blank | johng, amy123 | g, y | +| `d/` | date joined of developer | numeric characters in dd-MM-yyyy format, should not be a future date, and it should not be blank | 11-11-2023 | 19-11-2024, 1/1/2023 | +| `s/` | salary of developer | positive integers of at least 4 digits, should not be blank | 5000 | 5000.0, 4321.32.22 | +| `rt/` | rating of developer | numeric characters between 0 to 5 | 5, 3.5 | -0, -1, 6 | + +#### Client Parameters + +| Parameter | Description | Constraints | Valid Examples | Invalid Examples | +|-----------|-----------------------------|---------------------------------------------------------------------------------------------------|----------------|------------------| +| `o/` | organisation name of client | alphanumeric characters and spaces, and it should not be blank | Google | 谷歌 | +| `do/` | document link of client | alphanumeric characters and spaces, and it should not be blank, follows standard hyperlink format | google.com | 谷歌.com | + +#### Project Parameters + +| Parameter | Description | Constraints | Valid Examples | Invalid Examples | +|-----------|------------------------|----------------------------------------------------------------|-------------------------------------------------|------------------------------------| +| `dr/` | description of project | alphanumeric characters and spaces, and it should not be blank | App to allow for different juices to be ordered | 成龍, 潔 いさぎ 世 よ 一 いち | +| `dl/` | deadline of project | alphanumeric characters and spaces, and it should not be blank | 19-12-2023, Design backend, HIGH, 0 | 19.1.2023, 潔 いさぎ 世 よ 一 いち, NONE, 5 | +| `pri/` | priority of deadline | HIGH, MEDIUM, LOW | HIGH, MEDIUM, LOW | 5, 3.5, -1, high, M | + +
+ +------------------------------------------------------------------------------------- +### Format +#### General Formatting +Here are the explanations behind the formatting we use through this guide.
+ +* Words in `grey blocks (like these)` usually represent any of the following: + 1. Text used in commands, such as `add-developer` + 2. Keys on your keyboard like `Enter` + 3. File names such as `CodeContact.jar` +
+
+:bulb: This is a blue box. It can be used for additional tips or more useful information. +
+ +
+:exclamation: This is a warning box. It can be used to give more details on the warnings and limitations of features. +
+ +#### Command Formatting + +| Format | Explanation | Example | +|----------------------------------------------|--------------------------------------------------------------------|------------------------------------| +| words in `[UPPER_CASE]` with square brackets | parameter values that are supplied by the user | `add-developer n/NAME n/PHONE` | +| Items with `…` after them | parameters that can be used multiple times (or omitted completely) | `add-developer [pr/PROJECT]...` | + + +[Scroll back to Table of Contents](#table-of-contents) + +
+ +----------------------------------------------------------------------------------------------- + +## **Navigating the Graphical User Interface (GUI)** +CodeContact comes with a GUI to allow for nice visual feedback for our users. Here is a quick run-through +of the different sections of our GUI, as well as some notes regarding the use of the GUI. +### Quick Orientation +![image](images/UG%20UI%202.png) +

+![image](images/UG%20UI%201.png) + +Here is a quick summary of each GUI component within CodeContact: + +| Name | Description | +|----------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| Menu Bar | Contains dropdown menu options for the CodeContact application. | +| Command Box | Allows users to enter CodeContact commands. | +| Result Display | 1. Provides CLI-based feedback upon a user command.
2. Allows users to see if their command was successful or not.
3. Provides error messages to guide users on how to use CodeContact commands. | +| Tabs | Allows users to see different lists of information on clicking on the relevant tabs. The tabs will also automatically switch to the correct tab corresponding to the command executed. | +| Developers/Clients/Projects card | Displays a list of Developer, Client or Project Cards. This list can be manipulated through commands like `find` and `list`. | +| Deadlines progress | Displays the progress of deadlines for a certain project. | +| Deadline list with details | Displays the details of the deadline in a table form. | +| Re-sort | Click on the respective words, there will be a small black arrow that appears. You can press to resort the data eg. show date from furthest to nearest day. | + +### Notes on GUI +
+If you executed a `find` command in developer tab and switched to the client tab, when you return to the developer tab the find results will be cleared and +the whole list of developers will be shown again. This is the intended behavior of CodeContact as we hope that this can save you the time +from always needing to call the list command. +
+ +-------------------------------------------------------------------------------------------------------------------- +
+ +## **Quick start** + +1. Ensure you have Java `11` or above installed in your Computer. + +2. Download the latest `CodeContact.jar`. + +3. Copy the file to the folder you want to use as the _home folder_ for your CodeContact. + +4. Open a command terminal, `cd` into the folder you put the jar file in, and use the `java -jar CodeContact.jar` + command to run the application.
+ +5. A GUI similar to the below should appear in a few seconds. Note how the app might contain some sample data.
+ ![Ui](images/Ui.png) + +6. For new users, learn to use CodeContact through our [Tutorial](#codecontact-tutorial--for-new-users-). + +7. Refer to the [Features](#features) below for details of each command. + +[Scroll back to Table of Contents](#table-of-contents) + +-------------------------------------------------------------------------------------------------------------------- +
+ +## **CodeContact Tutorial (for new users)** + +This is a tutorial for **first-time** CodeContact users. + +1. Launch CodeContact. You may refer to the instructions [here](#quick-start). + * On launch, CodeContact will not contain any developer or client records. +2. You will be asked to **enter a password** to unlock CodeContact. + * Enter the command `unlock pw/Password123!` in the command box. + +3. You can **change the password** to unlock CodeContact. + * Enter the command `change-password pw/Password123! npw/` in the command box. + +
:bulb: +You can always lock and unlock CodeContact using the `lock` and `unlock` commands. +
+ +1. Let us try **adding a project** to our CodeContact. + * Enter the + command `add-project n/AndroidApp dr/App to allow for different juices to be ordered dl/19-12-2023,Design backend,HIGH,0 dl/25-12-2023,Design frontend,MEDIUM,0 `. + +2. We can then **add a developer** to our CodeContact. + * Enter the + command `add-developer n/John Doe p/98765432 e/johnd@example.com a/311, Clementi Ave 2, #02-25 r/Developer pr/AndroidApp s/4500 d/01-11-2023 g/johng rt/3`. + * Try adding more developers with different details for each parameter! + * Remember to add a new [project](#add-project--add-project) or [role](#add-roles) if you wish to add new developers with other roles and + project. + +3. We can also **add a client** to our CodeContact. + * Enter the + command `add-client n/Amy p/88765423 e/amy@example.com a/31, Clementi Ave 6, #03-12 r/Client pr/AndroidApp o/Google do/google.com`. + * Try adding more clients with different details for each parameter! + * Remember to add a new [project](#add-project--add-project) or [role](#add-roles) if you which to add new client with other roles and project. + +4. Let us try **editing the name** of a developer stored in CodeContact. + * Enter the command `edit-developer 1 n/Jhonny`. + * Try editing other parameters or developers and projects too! + * More details of what you can edit can be found [here](#edit). + +5. We can also easily **find** for information in CodeContact. + * Enter the command `find-developer n/John s/4500`. + * Try looking for other information and search with multiple parameters! + * More details of what you can find can be found [here](#find). + +6. You can always **show the full list** after finding in CodeContact. + * Enter the command `list-developer`, `list-client` or `list-project`. + +7. You can **delete developers, clients or projects** in CodeContact. + * Enter the command `delete-developer 3` to delete the 3rd developer in CodeContact. + * More details on how delete works can be found [here](#delete-developer--delete-developer). + +8. If you realise that you did not delete this developer, you can **undo** this action in CodeContact. + * Enter the command `undo` and it will revert your previous actions. + +9. If you realise you actually want it deleted, you can **redo** this action in CodeContact. + * Enter the command `redo` and it will redo your previous actions. + +Congratulations! You are now ready to use CodeContact! + +To view all our features, you may visit our [Features](#features) section. + +[Scroll back to Table of Contents](#table-of-contents) + +-------------------------------------------------------------------------------------------------------------------- +
+ +# **Features** + +| [Security Features](#security-features) | Locking Access to features | Unlocking Access to features | Changing personal password | +|:--------------------------------------------|:--------------------------:|:----------------------------:|:-----------------------------------------------------:| +| [Password Protection](#password-protection) | [`lock`](#lock-lock) | [`unlock`](#unlock-unlock) | [`change-password`](#change-password-change-password) | + +| [Management Features](#management-features) | Developer | Client | Project | +|:----------------------------------------------------|:-------------------------------------------------------------------------:|:----------------------------------------------------------------:|:------------------------------------------------------------------------------------------------------------------------:| +| [Adding new information](#add) | [`add-developer`](#add-developer--add-developer) | [`add-client`](#add-client--add-client) | [`add-project`](#add-project--add-project) | +| [Deleting information](#delete) | [`delete-developer`](#delete-developer--delete-developer) | [`delete-client`](#delete-client--delete-client) | [`delete-project`](#delete-project--delete-project) | +| [Editing information](#edit) | [`edit-developer`](#edit-developer-details--edit-developer) | [`edit-client`](#edit-client-details--edit-client) | [`edit-project`](#edit-project-details--edit-project) | +| [Importing information](#import-information) | [`import-developer` ](#import-developers-import-developer) | [`import-client`](#import-clients-import-client) | - | +| [Finding information](#find) | [`find-developer`](#find-developer-details) | [`find-client`](#find-client-details) | [`find-project`](#find-project-details), [`find-deadline`](#find-deadlines-find-deadline) | +| [Listing information](#listing-information--list) | `list-developer` | `list-client` | `list-project` | +| [Adding new role](#add-roles) | [`add-developer-role`](#add-developer-roles--add-developer-role) | [`add-client-role`](#add-client-roles--add-client-role) | - | +| [Deleting role](#delete-roles) | [`delete-developer-role`](#delete-developer-roles--delete-developer-role) | [`delete-client-role`](#delete-client-roles--delete-client-role) | - | +| [Marking Project Deadlies](#mark-project-deadlines) | - | - | [`mark-deadline`](#mark-deadline-as-done--mark-deadline), [`unmark-deadline`](#mark-deadline-as-undone--unmark-deadline) | + +| [Additional Features](#additional-features) | Finding Help | Clearing data | Exiting program | +|:--------------------------------------------------|:---------------------------------------:|:-----------------------------------------------------:|:-------------------------------------------------:| +| [Miscellaneous Features](#miscellaneous-features) | [View Help `help`](#viewing-help--help) | [Clear entries `clear`](#clearing-all-entries--clear) | [Exit program `exit`](#exiting-the-program--exit) | + +-------------------------------------------------------------------------------------------------------------------- +
+ +## **Security Features** + +-------------------------------------------------------------------------------------------------------------------- +### Password Protection +Project Managers have the ability to grant themselves exclusive access by denying access by others to CodeContact with a personal and customizable password. +#### Lock : `lock` + +Locks the system by hiding all the information in the tabs on the GUI. It also disables parsing of commands +except `unlock`, `help`, and `delete`. + +Format: `lock` + +When command succeeds, CLI shows: + +``` +Locked all data +``` + +[Scroll back to Table of Contents](#table-of-contents) + +-------------------------------------------------------------------------------------------------------------------- +#### Unlock : `unlock` + +Unlocks the system by making all the information visible and allows all commands to be parsed. + +Format: `unlock pw/Password123!` + +* Default password is `Password123!`. +* You are highly recommended to change to a different password. + +When command succeeds, CLI shows: + +``` +Unlocked all data +``` + +[Scroll back to Table of Contents](#table-of-contents) + +-------------------------------------------------------------------------------------------------------------------- +#### Change password : `change-password` + +Allows for password to be changed, given the current password and new password matches criteria. + +Format: `change-password pw/CURRENT_PASSWORD npw/NEW_PASSWORD` + +* Password must be at least 8 characters long and contain at least one digit, one lowercase letter, + one uppercase letter, and one special character. + +Example of usage: `change-password pw/Password123! npw/NewPass987!` + +When command succeeds, CLI shows: + +``` +Password changed successfully. +``` + +[Scroll back to Table of Contents](#table-of-contents) + +-------------------------------------------------------------------------------------------------------------------- +
+ +## **Management Features** + +-------------------------------------------------------------------------------------------------------------------- +### **Add** + +-------------------------------------------------------------------------------------------------------------------- +#### Add developer : `add-developer` + +Adds a new developer to the address book. + +Format: `add-developer n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [d/DATE_JOINED] r/ROLE s/SALARY [pr/PROJECT_NAME]... g/GITHUB_ID rt/RATING` + +* Adds the developer with the given details to the address book. +* Not specifying any `PROJECT_NAME` will add the developer without assigning them to any projects. +* Not specifying the `DATE_JOINED` will automatically use today's date as the date joined. +* `NAME` cannot be the same as another existing developer's name in the address book. Checks are case-insensitive. +* If specified, `PROJECT_NAME` should be the exact name of an existing project. +* Note that for `RATING` values that are not in increments of 0.5 (eg. 3.2 or 3.8 instead of 3.0 or 3.5), the decimal portion + may not be reflected as clearly in the coloured stars. + +Example of usage: `add-developer n/John Doe p/98765432 e/johnd@example.com a/311, Clementi Ave 2, #02-25 r/Developer pr/AndroidApp pr/CustomWebsite s/4500 d/11-11-2023 g/johng rt/3` + +* Adds a new developer `John Doe` with the respective details, who is assigned to the projects `AndroidApp` and `CustomWebsite`, + given that they already exist in the address book. + +When command succeeds, CLI shows: + +``` +New developer added: John Doe; +Phone: 98765432; +Email: johnd@example.com; +Address: 311, Clementi Ave 2, #02-25; +Date Joined: 11-11-2023; +Role: Developer; +Salary: 4500; +Projects: CustomWebsite AndroidApp +``` + +
:bulb: +Entered details of a developer incorrectly? You can always undo the action with the [`undo`](#undo) command! +
+ +[Scroll back to Table of Contents](#table-of-contents) + +-------------------------------------------------------------------------------------------------------------------- +#### Add client : `add-client` + +Adds a new client to the address book. + +Format: `add-client n/NAME p/PHONE e/EMAIL a/ADDRESS r/ROLE [pr/PROJECT]... o/ORGANISATION do/DOCUMENT` + +* Adds the client with the given details to the address book. +* Not specifying any `PROJECT_NAME` will add the client without assigning them to any projects. +* `NAME` cannot be the same as another existing client's name in the address book. Checks are case-insensitive. +* If specified, `PROJECT_NAME` should be the exact name of an existing project. + +Example of usage: `add-client n/Jack Doe p/98765432 e/jackd@example.com a/311, Clementi Ave 2, #02-25 r/Developer pr/AndroidApp pr/CustomWebsite o/Google do/google.com` + +* Adds a new client `Jack Doe` with the respective details, who is assigned to the projects `AndroidApp` and `CustomWebsite`, + given that they already exist in the address book. + +When command succeeds, CLI shows: + +``` +New client added: Jack Doe; +Phone: 98765432; +Email: jackd@example.com; +Address: 311, Clementi Ave 2, #02-25; +Organisation: Google; +Role: Developer; +Document: google.com; +Projects: CustomWebsite AndroidApp +``` + +
:bulb: +Entered details of a client incorrectly? You can always undo the action with the [`undo`](#undo) command! +
+ +[Scroll back to Table of Contents](#table-of-contents) + +-------------------------------------------------------------------------------------------------------------------- +#### Add project : `add-project` + +Adds a new project to the address book. + +Format: `add-project n/NAME dr/DESCRIPTION [dl/DEADLINE_DATE,DEADLINE_DESCRIPTION,PRIORITY,IS_DONE]...` + +* Adds the project with the given details to the address book. +* Not specifying any deadline will add the project without assigning deadlines to it. +* `NAME` cannot be the same as another existing project's name in the address book. Checks are case-insensitive. + +Example of usage: `add-project n/JuiceApp dr/App to allow for different juices to be ordered dl/19-12-2023,Design backend,HIGH,0 dl/25-12-2023,Design frontend,MEDIUM,0` + +* Adds a new project `JuiceApp` with the respective details. + +When command succeeds, CLI shows: + +``` +New project added: JuiceApp; +Description: App to allow for different juices to be ordered; +Deadlines: +1. Design backend by: 19-12-2023, priority: HIGH (undone) +2. Design frontend by: 25-12-2023, priority: MEDIUM (undone) +``` + +
:bulb: +Entered details of a project incorrectly? You can always undo the action with the [`undo`](#undo) command! +
+ +[Scroll back to Table of Contents](#table-of-contents) + +-------------------------------------------------------------------------------------------------------------------- +
+ +### **Delete** + +-------------------------------------------------------------------------------------------------------------------- +#### Delete developer : `delete-developer` + +Deletes developer in the address book. + +Format: `delete-developer INDEX` + +Example of usage: `delete-developer 2` + +* Deletes second developer in the developer list from the developer list and from the address book. + +When command succeeds, CLI shows: + +``` +Deleted Developer: Bernice Yu; +Phone: 99272758; +Email: berniceyu@example.com; +Address: Blk 30 Lorong 3 Serangoon Gardens, #07-18; +Date Joined: 16-11-2020; +Role: Developer; +Salary: 6000; +Projects: Appollo Orbital +``` +if second developer in the list was Bernice Yu. + +
:bulb: +Deleted a wrong developer? You can always undo the action with the [`undo`](#undo) command! +
+ +-------------------------------------------------------------------------------------------------------------------- +#### Delete client : `delete-client` + +Deletes client in the address book. + +Format: `delete-client INDEX` + +Example of usage: `delete-client 3` + +* Deletes third client in the client list from the client list and from the address book. + +When command succeeds, CLI shows: +``` +Deleted Client: George Lim; +Phone: 76543210; +Email: george@example.com; +Address: Blk 789 Woodlands Ave 6, #03-03; +Organisation: MNO Company; +Role: Developer; +Document: https://www.mno.com/; +Projects: CodeContact +``` +if second client in the list was George Lim. + +
:bulb: +Deleted a wrong client? You can always undo the action with the [`undo`](#undo) command! +
+ +-------------------------------------------------------------------------------------------------------------------- +#### Delete project : `delete-project` + +Deletes the details of an existing project in the address book and updates developer and client project details accordingly. + +Format: `delete-project INDEX` + +Example of usage: `delete-project 2` + +* Deletes second project in the project list from the project list and from the address book. +* Deletes project from developers' and clients' project lists if they were assigned to this project. + +When command succeeds, CLI shows: +``` +Deleted Project: TeamTrekker; +Description: A team collaboration tool; +Deadlines: +1. Phase 2 by: 13-11-2021, priority: MEDIUM (undone) +``` +if second project in the list was TeamTrekker. + +
:bulb: +Deleted a wrong project? You can always undo the action with the [`undo`](#undo) command! +
+ +[Scroll back to Table of Contents](#table-of-contents) + +-------------------------------------------------------------------------------------------------------------------- +
+ +### **Edit** + +-------------------------------------------------------------------------------------------------------------------- +#### Edit developer details : `edit-developer` + +Edits the details of an existing developer in the address book. + +Format: `edit-developer INDEX [n/NAME] [p/PHONE_NUMBER] [e/EMAIL] [a/ADDRESS] [d/DATE_JOINED] [r/ROLE] [s/SALARY] [pr/PROJECT_NAME]... [g/GITHUB_ID] [rt/RATING]` + +* Edits the developer at the specified `INDEX` in the currently displayed developer list. +* At least one of the optional fields must be provided. +* Existing values will be updated to the input values. +* When editing projects, the existing assigned projects of the developer will be removed i/e. adding of projects is not + cumulative. +* You can remove all the developer's projects by typing `pr/` without specifying any project name after it. +* `NAME` cannot be the same as another existing developer's name in the address book. Checks are case-insensitive. +* You can, however, edit the casing of an existing developer's `NAME`. +* `PROJECT_NAME` should be the exact name of an existing project. +* Note that for `RATING` values that not in increments of 0.5 (eg. 3.2 or 3.8 instead of 3.0 or 3.5), the decimal portion + may not be reflected as clearly in the coloured stars. + +Example of usage: `edit-developer 2 p/98989898 pr/Project2 pr/Project3` + +* Edits `Amy`'s phone number to `98989898` and changes the projects assigned to her to `Project2` and `Project3`. + +When command succeeds, CLI shows: + +``` +Edited Developer: Amy +Phone: 98989898 +Email: amy@u.nus.edu +Address: NUS UTOWN +Date Joined: 06-09-2023 +Role: Developer +Salary: 6999 +Projects: Project3 Project2 +``` + +
:bulb: +You can always undo the action with the [`undo`](#undo) command! +
+ +[Scroll back to Table of Contents](#table-of-contents) + +-------------------------------------------------------------------------------------------------------------------- +#### Edit client details : `edit-client` + +Edits the details of an existing client in the address book. + +Format: `edit-client INDEX [n/NAME] [p/PHONE_NUMBER] [e/EMAIL] [a/ADDRESS] [pr/PROJECT_NAME]... [o/ORGANISATION]` + +* Edits the client at the specified `INDEX` in the currently displayed client list. +* At least one of the optional fields must be provided. +* Existing values will be updated to the input values. +* When editing projects, the existing assigned projects of the client will be removed i/e. adding of projects is not + cumulative. +* You can remove all the client's projects by typing `pr/` without specifying any project name after it. +* `NAME` cannot be the same as another existing client's name in the address book. Checks are case-insensitive. +* You can, however, edit the casing of an existing client's `NAME`. +* `PROJECT_NAME` should be the exact name of an existing project. + +Example of usage: `edit-client 3 e/bob@gmail.com` + +* Edits `Bob`'s email to `bob@gmail.com`. + +When command succeeds, CLI shows: + +``` +Edited Client: Bob; +Phone: 87654321; +Email: bob@gmail.com; +Address: Blk 123 Banana Road; +Organisation: Google; +Document: google.com +Projects: ProjectA +``` +
:bulb: +You can always undo the action with the [`undo`](#undo) command! +
+ +[Scroll back to Table of Contents](#table-of-contents) + +-------------------------------------------------------------------------------------------------------------------- +#### Edit project details : `edit-project` + +Edits the details of an existing project in the address book. + +Format: `edit-project INDEX [dr/DESCRIPTION] [dl/DEADLINE]...` + +* Edits the project at the specified `INDEX` in the currently displayed project list. +* At least one of the optional fields must be provided. +* Existing values will be updated to the input values. +* When editing deadlines, the existing deadlines will be removed ie. adding of projects is not cumulative. +* You can remove all the current deadlines by typing `dl/` without specifying any deadline. +* The name of a project cannot be edited. + +Example of usage: `edit-project 1 dl/19-12-2023,Design backend,HIGH,0` + +* Deletes existing project deadlines and adds new deadline `Design backend by: 19-12-2023, priority: HIGH (undone)`. + +When command succeeds, CLI shows: + +``` +Edited Project: JuiceApp; +Description: Juice ordering app; +Deadlines: +1. Design backend by: 19-12-2023, priority: HIGH (undone) +``` +
:bulb: +You can always undo the action with the [`undo`](#undo) command! +
+ +[Scroll back to Table of Contents](#table-of-contents) + +-------------------------------------------------------------------------------------------------------------------- +
+ +### **Import** + +-------------------------------------------------------------------------------------------------------------------- +#### Import developers' details : `import-developer` + +Takes in a CSV file and populates the internal list of developers if the file is formatted correctly. + +Format: `import-developer [FILENAME]` + +* Note that the CSV file has to be in the same folder as the JAR file for the command to function correctly. +* The CSV file has to strictly follow the column header names and order for the import to function appropriately. +* The command will abort if any of the rows have invalid data format. +* Example of valid CSV: + +``` +Name, Contact Number, Email, Address, Date Joined, Role, Salary, GithubId, Rating, Projects,, +faiz,87654321,faiz@u.com,utown,12-12-2020,Developer,3333,Faizgit,5,AndroidApp,ProjectB, +John,123456789,john@email.com,123 Main St,01-01-2021,Developer,4000,JohnDesigns,4,AndroidApp,ProjectB, +Sarah,987654321,sarah@email.com,456 Elm St,05-10-2019,Developer,6000,SarahManager,5,AndroidApp,ProjectB,ProjectC +Alex,555555555,alex@email.com,789 Oak St,03-01-2022,Developer,5500,AlexDev,4,AndroidApp,ProjectB, +Emily,111111111,emily@email.com,321 Pine St,08-10-2018,Developer,4800,EmilyAnalyst,4,AndroidApp,ProjectB, +Michael,999999999,michael@email.com,567 Birch St,06-03-2020,Developer,7000,MichaelEngineer,5,AndroidApp,ProjectB, +``` + +Example of usage: `import-developer developers.csv` + +* Imports `developers.csv` and adds a new developer for each row of data. + +When command succeeds, CLI shows: + +``` +New developer added: faiz; +Phone: 87654321; +Email: faiz@u.com; +Address: utown; +Date Joined: 12-12-2020; +Role: Developer; +Salary: 3333; +Projects: ProjectB AndroidApp +``` + +for each developer successfully added. + +[Scroll back to Table of Contents](#table-of-contents) + +-------------------------------------------------------------------------------------------------------------------- +#### Import clients' details : `import-client` + +Takes in a CSV file and populates the internal list of clients if the file is formatted correctly. + +Format: `import-client [FILENAME]` + +* Note that the CSV file has to be in the same folder as the JAR file for the command to function correctly. +* The CSV file has to strictly follow the column header names and order for the import to function appropriately. +* The command will abort if any of the rows have invalid data format +* Example of valid CSV: + +``` +Name, Contact Number, Email, Address, Role, Organisation, Document, Projects, +Mahi,87554321,mahi@u.com,utown,HR,Google,docs.google.com/abd,AndroidApp,ProjectB +Jane,654321876,jane@email.com,456 Oak St,HR,Acme Corp,acme.com/docs,AndroidApp +Robert,987123456,robert@email.com,789 Elm St,HR,Tech Solutions,techdocs.com/123 +Maria,321987654,maria@email.com,123 Maple St,HR,Innovate Inc,innovate.com/docs +Chris,876543219,chris@email.com,567 Pine St,HR,Data Insights,datainsights.com/docs +Laura,888555555,laura@email.com,101 Birch St,HR,Software Systems,software.com/docs +``` + +Example of usage: `import-client clients.csv` + +* Imports `clients.csv` and adds a new client for each row of data. + +When command succeeds, CLI shows: + +``` +New client added: Mahi; +Phone: 87554321; +Email: mahi@u.com; +Address: utown; +Organisation: Google; +Role: HR; +Document: docs.google.com/abd; +Projects: ProjectB AndroidApp +``` + +for each client successfully added. + +[Scroll back to Table of Contents](#table-of-contents) + +-------------------------------------------------------------------------------------------------------------------- +
+ +### **Find** + +-------------------------------------------------------------------------------------------------------------------- +#### Find developer details : `find-developer` + +Finds the details of an existing developer in the address book. + +Format: `find-developer [n/NAME] [p/PHONE_NUMBER] [e/EMAIL] [a/ADDRESS] [d/DATE_JOINED] [r/ROLE] [s/SALARY] [pr/PROJECT_NAME] [g/GITHUB_ID] [rt/RATING]` + +* Finds for developers based on the attributes provided. +* At least one of the optional fields must be provided. +* Existing values will be compared to the input values, and the results will include any items that match the provided + criteria. +* You can combine multiple attributes for a more specific search. +* The search is case-insensitive, so you can use any case for the search criteria. + +Example of usage: `find-developer pr/2103T rt/5.0` + +* Prints developers in 2103/T project with a 5-star rating. + +When command succeeds, CLI shows: + +``` +This is the one developer with matching information. +``` + +followed by the matching developer's details in the GUI. + +[Scroll back to Table of Contents](#table-of-contents) + +-------------------------------------------------------------------------------------------------------------------- +#### Find client details : `find-client` + +Finds the details of an existing client in the address book. + +Format: `find-client [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [o/ORGANISATION] [pr/PROJECT] [d/DOCUMENT]` + +* Finds for clients based on the attributes provided. +* At least one of the optional fields must be provided. +* Existing values will be compared to the input values, and the results will include any items that match the provided + criteria. +* You can combine multiple attributes for a more specific search. +* The search is case-insensitive, so you can use any case for the search criteria. + +Example of usage: `find-client o/Google r/Senior developer` + +* Prints clients from Google with the Senior developer role. + +When command succeeds, CLI shows: + +``` +These are the 2 clients with matching information. +``` + +followed by the matching clients' details in the GUI. + +[Scroll back to Table of Contents](#table-of-contents) + +-------------------------------------------------------------------------------------------------------------------- +#### Find project details : `find-project` + +Finds the details of an existing project in the address book. + +Format: `find-project [pr/PROJECT_NAME] [dr/DESCRIPTION] [dl/DEADLINE]` + +* Finds for projects based on the attributes provided. +* At least one of the optional fields must be provided. +* Existing values will be compared to the input values, and the results will include any items that match the provided + criteria. +* You can combine multiple attributes for a more specific search. +* The search is case-insensitive, so you can use any case for the search criteria. + +Example of usage: `find-project pr/JuiceApp` + +* Print projects with the name JuiceApp. + +When command succeeds, CLI shows: + +``` +This is the one project with matching information. +``` +followed by the matching project's details in the GUI. + +[Scroll back to Table of Contents](#table-of-contents) + +-------------------------------------------------------------------------------------------------------------------- +#### Find deadlines : `find-deadline` + +Finds deadlines in project tab based on date and/or priority. + +Format: `find-deadline [d/DATE] [pri/PRIORITY]` + +* When finding deadlines based on `DATE`, the project tab displays deadlines due before or on the specified date. +* When finding deadlines based on `PRIORITY`, only that priority (`HIGH`,`MEDIUM`, `LOW`) deadlines are shown. + +Example of usage: `find-deadline d/20-11-2023 pri/MEDIUM` + +* Shows deadlines due before or on `20-11-2023` and with `MEDIUM` priority. + +When command succeeds, CLI shows: + +``` +These are the 3 projects with matching information. +``` + +[Scroll back to Table of Contents](#table-of-contents) + +-------------------------------------------------------------------------------------------------------------------- + +### **List** + +-------------------------------------------------------------------------------------------------------------------- +#### List information : `list` + +Shows a list of all developers in the address book. + +Format: `list-TYPE` + +* Lists the specific type of thing you are asking. + +Example of usage:`list-developer` + +* Lists all the developers. + +Acceptable parameters: + +* `developer` to list the developers +* `client` to list the clients +* `project` to list the projects + +When command succeeds, CLI shows: + +``` +Listed all developers +``` + +[Scroll back to Table of Contents](#table-of-contents) + +-------------------------------------------------------------------------------------------------------------------- + +
+ +### **Add roles** + +-------------------------------------------------------------------------------------------------------------------- +#### Add developer roles : `add-developer-role` + +Adds new developer roles into the system. + +Format: `add-developer-role ROLE_NAME` + +* Adds the ROLE_NAME to list of developer roles. +* There are 3 preset roles in the list of roles: `Frontend Developer`,`Backend Developer`,`Developer`. +* You will not be able to add a developer to a role that does not exist in this list of developer roles. +
+:exclamation: **Note** :This command is **not** case-sensitive, even if `Developer` is a role, `developer` can still be added. +
+ +
:bulb: +If you wish to check what roles are there, you can key in `delete-developer-role `, `` should not be an existing role. +
+ +Example of usage: `add-developer-role Tester` + +* Adds the Tester role to list of developer roles. +* You can now add developers with Tester as their roles. + +When command succeeds, CLI shows: + +``` +New role for developer added: Tester +``` + +[Scroll back to Table of Contents](#table-of-contents) + +-------------------------------------------------------------------------------------------------------------------- +#### Add client roles : `add-client-role` + +Adds new client roles into the system. + +Format: `add-client-role ROLE_NAME` + +* Adds the ROLE_NAME to list of client roles. +* There are 4 preset roles in the list of roles: `HR`,`Manager`,`Developer`,`Client`. +* You will not be able to add a client to a role that does not exist in this list of client roles. +
+:exclamation: **Note** :This command is **not** case-sensitive, even if `HR` is a role, `hr` can still be added. +
+ +
:bulb: +If you wish to check what roles are there, you can key in `delete-client-role `, `` should not be an existing role. +
+ +Example of usage: `add-client-role Boss` + +* Adds the Boss role to list of developer roles. +* You can now add clients with Boss as their roles. + +When command succeeds, CLI shows: + +``` +New role for client added: Boss +``` + +[Scroll back to Table of Contents](#table-of-contents) + +-------------------------------------------------------------------------------------------------------------------- + +### **Delete roles** + +-------------------------------------------------------------------------------------------------------------------- +#### Delete developer roles : `delete-developer-role` + +Delete developer roles from the system. + +Format: `delete-developer-role ROLE_NAME` + +* Deletes the ROLE_NAME to list of developer roles. +* There are 3 preset roles in the list of roles: `Frontend Developer`,`Backend Developer`,`Developer`. These roles + cannot be deleted. +* You will not be able to delete a developer role if there are developers in the list with that role. + +Example of usage: `delete-developer-role UI Manager` + +* Deletes the UI Manager from the list of developer roles. +* You can no longer add developers with UIDesigner as their roles. + +When command succeeds, CLI shows: + +``` +Role for developers deleted: UIDesigner +``` + +[Scroll back to Table of Contents](#table-of-contents) + +-------------------------------------------------------------------------------------------------------------------- +#### Delete client roles : `delete-client-role` + +Delete client roles from the system. + +Format: `delete-client-role ROLE_NAME` + +* Deletes the ROLE_NAME to list of developer roles. +* There are 4 preset roles in the list of roles: `HR`,`Manager`,`Developer`,`Client`. These roles cannot be deleted. +* You will not be able to delete a client role if there are clients in the list with that role. + +Example of usage: `delete-client-role Boss` + +* Deletes the Boss from the list of developer roles. +* You can no longer add clients with Boss as their roles. + +When command succeeds, CLI shows: + +``` +Role for clients deleted: Boss +``` + +[Scroll back to Table of Contents](#table-of-contents) + +-------------------------------------------------------------------------------------------------------------------- + +
+ +### **Mark** + +-------------------------------------------------------------------------------------------------------------------- +#### Mark project deadline as done : `mark-deadline` + +Marks the indicated deadline for the project as done. + +Format: `mark-deadline PROJECT_INDEX DEADLINE_INDEX` + +* `PROJECT_INDEX` and `DEADLINE_INDEX` must be valid indexes of existing projects and deadlines. + +Example of usage: `mark-deadline 2 1` + +* Marks the 1st deadline of the 2nd project in the currently displayed project list as done. + +When command succeeds, CLI shows: + +``` +The deadline has been marked as completed! +``` + +[Scroll back to Table of Contents](#table-of-contents) + +-------------------------------------------------------------------------------------------------------------------- +#### Mark project deadline as undone : `unmark-deadline` + +Marks the indicated deadline for the project as undone. + +Format: `unmark-deadline PROJECT_INDEX DEADLINE_INDEX` + +* `PROJECT_INDEX` and `DEADLINE_INDEX` must be valid indexes of existing projects and deadlines. + +Example of usage: `unmark-deadline 2 1` + +* Marks the 1st deadline of the 2nd project in the currently displayed project list as undone. + +When command succeeds, CLI shows: + +``` +The deadline has been marked as undone! +``` + +[Scroll back to Table of Contents](#table-of-contents) + +-------------------------------------------------------------------------------------------------------------------- + +### **Undo** +-------------------------------------------------------------------------------------------------------------------- + +#### Undo : `undo` + +Undo the previous command you entered. + +Format: `undo` + +* Each time you type undo, you move back one stage. +* If you made 5 changes, and you wish to undo, you can enter the command `undo` 5 times. The system will remind you when + you cannot undo anymore. +* `undo` works for all `edit`, `add-TYPE` and `delete` commands. + +Example of usage: `undo` + +* You just deleted a new developer, and you wish to `undo`. + +When command succeeds, CLI shows: + +``` +Undo successful! The change below has been undone: +Deleted Developer: Amy; +Phone: 83566674; +Email: amy@example.com; +Address: 42, Clementi Ave 7, #02-2; +Date Joined: 23-11-2023; +Role: Frontend Developer; +Salary: 5000; +Projects: CustomWebsite AndroidApp +``` + +[Scroll back to Table of Contents](#table-of-contents) + +-------------------------------------------------------------------------------------------------------------------- +#### Redo : `redo` + +Redo the previous command you undid. + +Format: `redo` + +* Each time you type redo, you move forward one stage. +* You can only `redo` if you have `undo` before. +* If you undid 5 changes, and you wish to redo, you can enter the command `redo` 5 times. The system will remind you when + you cannot redo anymore. +* `redo` works for all `edit`, `add-TYPE` and `delete` commands. + +Example of usage: + +* You just `undo` delete developer, and you wish to `redo` to add it back. + When command succeeds, CLI shows: + +``` +Redo successful! The change below has been redone: +Deleted Developer: Amy; +Phone: 83566674; +Email: amy@example.com; +Address: 42, Clementi Ave 7, #02-2; +Date Joined: 23-11-2023; +Role: Frontend Developer; +Salary: 5000; +Projects: CustomWebsite AndroidApp +``` + +[Scroll back to Table of Contents](#table-of-contents) + +-------------------------------------------------------------------------------------------------------------------- +
+ +## **Additional Features** + +-------------------------------------------------------------------------------------------------------------------- +### **Miscellaneous Features** +Listed below are some miscellaneous features that are available in CodeContact to improve your user experience. + +-------------------------------------------------------------------------------------------------------------------- +#### Viewing help : `help` + +Shows a message explaining how to access the help page. + +Format: `help` + +[Scroll back to Table of Contents](#table-of-contents) + +-------------------------------------------------------------------------------------------------------------------- +#### Clearing all entries : `clear` + +Clears all entries from the address book. + +Format: `clear` + +[Scroll back to Table of Contents](#table-of-contents) + +-------------------------------------------------------------------------------------------------------------------- +#### Exiting the program : `exit` + +Exits the program. + +Format: `exit` + +[Scroll back to Table of Contents](#table-of-contents) + +-------------------------------------------------------------------------------------------------------------------- +### Feedback System `[coming in v2.0]` + +-------------------------------------------------------------------------------------------------------------------- +
+ +# **FAQ** + +-------------------------------------------------------------------------------------------------------------------- + +**Q**: How do I transfer my data to another Computer?
+**A**: Install the app in the other computer and overwrite the empty data file it creates with the file that contains +the data of your previous CodeContact home folder. + +-------------------------------------------------------------------------------------------------------------------- + +**Q**: How can I launch CodeContact if the clicking on the JAR file does not work?
+**A**: There are two possible methods to launch CodeContact. +
+ +* Method 1: For users familiar with the command prompt + +1. Open the command prompt. +2. Navigate to the directory where the JAR file is located using cd [JAR file location]. +3. Type `java -jar CodeContact.jar` and press enter. +4. CodeContact should launch. +

+ +* Method 2: For users that wish to create a script to launch CodeContact (Recommended) + +1. Create a new text file. +2. Type the following into the text file: + `java -jar [JAR file location]/CodeContact.jar`. +3. Save the text file as CodeContact.bat (Windows) or CodeContact.sh (MacOS/Linux). +4. Change the admin settings of the script to allow it to run as a program: + * Windows: Right-click on the script and select Properties. Under General , check + the box that says Allow this file to run as a program. + * MacOS/Linux: Open the terminal and navigate to the directory where the script is + located. Type `chmod +x [script name]` and press enter (`chmod +x` changes + permissions of the script to allow it to be executed). +5. Double-click on the script to launch CodeContact. +6. CodeContact should launch. + +-------------------------------------------------------------------------------------------------------------------- + +**Q**: How can I check my java version?
+**A**: Open a command prompt and type `java -version`. If you do not have Java installed, you +can download it [here](https://www.oracle.com/java/technologies/downloads/#java11). + +[Scroll back to Table of Contents](#table-of-contents) + +-------------------------------------------------------------------------------------------------------------------- +
+ +# **Known issues** + +1. **When using multiple screens**, if you move the application to a secondary screen, and later switch to using only + the primary screen, the GUI will open off-screen. The remedy is to delete the `preferences.json` file created by the + application before running the application again. +2. **When tampering with the storage files**, if you tamper with the storage files leading to incorrect format, the application will not be able to + read the data and will throw an error. The remedy is to delete the `addressbook.json` file created by the application before + running the application again. + +[Scroll back to Table of Contents](#table-of-contents) + +-------------------------------------------------------------------------------------------------------------------- +
+ +# **Command summary** + +| Action | Format, Examples | +|---------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| **lock** | `lock` | +| **unlock** | Format:
`unlock pw/PASSWORD`
Example:
`unlock pw/Password123!` | +| **change password** | Format:
`change-password pw/CURRENT_PASSWORD npw/NEW_PASSWORD`
Example:
`change-password pw/Password123! npw/Password123!` | +| **add developers** | Format:
`add-developer n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [d/DATE_JOINED] r/ROLE s/SALARY [pr/PROJECT_NAME]... g/GITHUB_ID rt/RATING`
Example:
`add-developer n/John Doe p/98765432 e/johnd@example.com a/311, Clementi Ave 2, #02-25 r/Developer pr/AndroidApp pr/CustomWebsite s/4500 d/11-11-2023 g/johng rt/3`
| +| **add clients** | Format:
`add-client n/NAME p/PHONE e/EMAIL a/ADDRESS r/ROLE [pr/PROJECT]... o/ORGANISATION do/DOCUMENT`
Example:
`add-client n/Jack Doe p/98765432 e/jackd@example.com a/311, Clementi Ave 2, #02-25 r/Developer pr/AndroidApp pr/CustomWebsite o/Google do/google.com`
| +| **add projects** | Format:
`add-project n/NAME dr/DESCRIPTION [dl/DEADLINE_DATE,DEADLINE_DESCRIPTION,PRIORITY,IS_DONE]...`
Example:
`add-project n/JuiceApp dr/App to allow for different juices to be ordered dl/19-12-2023,Design backend,HIGH,0 dl/25-12-2023,Design frontend,MEDIUM,0`
| +| **edit developers** | Format:
`edit-developer INDEX [n/NAME] [p/PHONE_NUMBER] [e/EMAIL] [a/ADDRESS] [d/DATE_JOINED] [r/ROLE] [s/SALARY] [pr/PROJECT_NAME]... [g/GITHUB_ID] [rt/RATING]`
Example:
`edit-developer 2 p/98989898 pr/Project2 pr/Project3`
| +| **edit clients** | Format:
`edit-client INDEX [n/NAME] [p/PHONE_NUMBER] [e/EMAIL] [a/ADDRESS] [d/DATE_JOINED] [r/ROLE] [s/SALARY] [pr/PROJECT_NAME]... [g/GITHUB_ID] [rt/RATING]`
Example:
`edit-client 3 p/bob@gmail.com`
| +| **edit projects** | Format:
`edit-project INDEX [dr/DESCRIPTION] [dl/DEADLINE]...`
Example:
`edit-project 1 dl/Finish Feature-A by: 09-09-2023`
| +| **find developers** | Format:
`find-developer pr/`
Example:
`find-developer pr/2103/T`
| +| **find clients** | Format:
`find-client n/`
Example:
`find-client n/Amy`
| +| **find projects** | Format:
`find-project dr/description`
Example:
`find-project dr/school semester project`
| +| **find deadlines** | Format:
`find-deadline [d/DATE] [pri/PRIORITY]`
Example:
`find-deadline d/20-11-2023 pri/MEDIUM`
| +| **delete developer** | Format:
`delete-developer INDEX`
Example:
`delete-developer 1`
| +| **delete client** | Format:
`delete-client INDEX`
Example:
`delete-client 1`
| +| **delete project** | Format:
`delete-project INDEX`
Example:
`delete-project 1`
| +| **import developer** | Format:
`import-developer [FILENAME]`
Example:
`import-developer developers.csv`
| +| **import client** | Format:
`import-client [FILENAME]`
Example:
`import-client clients.csv`
| +| **add developer role** | Format:
`add-developer-role ROLE_NAME`
Example:
`add-developer-role UIDesigner`
| +| **add client role** | Format:
`add-client-role ROLE_NAME`
Example:
`add-client-role Boss`
| +| **delete developer role** | Format:
`delete-developer-role ROLE_NAME`
Example:
`delete-developer-role UIDesigner`
| +| **delete client role** | Format:
`delete-client-role ROLE_NAME`
Example:
`delete-client-role Boss`
| +| **list** | Format:
`list-developer`
`list-project`
`list-client` | +| **mark deadline** | Format:
`mark-deadline PROJECT_INDEX DEADLINE_INDEX`
Example:
`mark-deadline 2 1`
| +| **unmark deadline** | Format:
`unmark-deadline PROJECT_INDEX DEADLINE_INDEX`
Example:
`unmark-deadline 2 1`
| +| **undo** | `undo` | +| **redo** | `redo` | +| **clear** | `clear` | +| **exit** | `exit` | +| **help** | `help` | + +[Scroll back to Table of Contents](#table-of-contents) diff --git a/docs/AboutUs.md b/docs/AboutUs.md index 1c9514e966a..61ce4a3a287 100644 --- a/docs/AboutUs.md +++ b/docs/AboutUs.md @@ -2,58 +2,53 @@ 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` - -## Project team +You can reach us at the email `mahidharah@gmail.com` -### John Doe +## Project team T09-02 - +### C Mahidharah Rajendran -[[homepage](http://www.comp.nus.edu.sg/~damithch)] -[[github](https://github.com/johndoe)] -[[portfolio](team/johndoe.md)] + -* Role: Project Advisor +[[portfolio](team/mahidharah.md)] +[[github](https://github.com/Mahidharah)] +[[Linkedin](https://www.linkedin.com/in/mahidharah/)] -### Jane Doe +* Role: Developer - +### Elizabeth Mao Zhi Min -[[github](http://github.com/johndoe)] -[[portfolio](team/johndoe.md)] + -* Role: Team Lead -* Responsibilities: UI +[[github](http://github.com/emzm2023)] +[[portfolio](team/emzm2023.md)] -### Johnny Doe +### Wan Mingyu - + -[[github](http://github.com/johndoe)] [[portfolio](team/johndoe.md)] +[[github](http://github.com/mingyu-wan)] +[[portfolio](team/mingyu-wan.md)] * Role: Developer -* Responsibilities: Data -### Jean Doe +### Mathan Chidambaranathan - + -[[github](http://github.com/johndoe)] -[[portfolio](team/johndoe.md)] +[[github](http://github.com/ncmathan)] +[[portfolio](team/ncmathan.md)] * Role: Developer -* Responsibilities: Dev Ops + Threading -### James Doe +### Mohammed Waseem Malik - + -[[github](http://github.com/johndoe)] -[[portfolio](team/johndoe.md)] +[[github](http://github.com/waseemingly)] [[portfolio](team/waseemingly.md)] * Role: Developer -* Responsibilities: UI +* Responsibilities: Deliverables and deadlines + diff --git a/docs/Configuration.md b/docs/Configuration.md index 13cf0faea16..fe118c75b24 100644 --- a/docs/Configuration.md +++ b/docs/Configuration.md @@ -3,4 +3,5 @@ layout: page title: Configuration guide --- -Certain properties of the application can be controlled (e.g user preferences file location, logging level) through the configuration file (default: `config.json`). +Certain properties of the application can be controlled (e.g user preferences file location, logging level) through the +configuration file (default: `config.json`). diff --git a/docs/DevOps.md b/docs/DevOps.md index d2fd91a6001..a51254c2264 100644 --- a/docs/DevOps.md +++ b/docs/DevOps.md @@ -4,22 +4,22 @@ title: DevOps guide --- * Table of Contents -{:toc} + {:toc} -------------------------------------------------------------------------------------------------------------------- ## Build automation -This project uses Gradle for **build automation and dependency management**. **You are recommended to read [this Gradle Tutorial from the se-edu/guides](https://se-education.org/guides/tutorials/gradle.html)**. - +This project uses Gradle for **build automation and dependency management**. **You are recommended to +read [this Gradle Tutorial from the se-edu/guides](https://se-education.org/guides/tutorials/gradle.html)**. 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 creat 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.
@@ -29,28 +29,39 @@ Given below are how to use Gradle for some important project tasks. **`checkstyleTest`**: Runs the code style check for the test code base. * **`test`**: Runs all tests. - * `./gradlew test` — Runs all tests - * `./gradlew clean test` — Cleans the project and runs tests + * `./gradlew test`— Runs all tests + * `./gradlew clean test`— Cleans the project and runs tests -------------------------------------------------------------------------------------------------------------------- ## Continuous integration (CI) -This project uses GitHub Actions for CI. The project comes with the necessary GitHub Actions configurations files (in the `.github/workflows` folder). No further setting up required. +This project uses GitHub Actions for CI. The project comes with the necessary GitHub Actions configurations files (in +the `.github/workflows` folder). No further setting up required. ### Code coverage -As part of CI, this project uses Codecov to generate coverage reports. When CI runs, it will generate code coverage data (based on the tests run by CI) and upload that data to the CodeCov website, which in turn can provide you more info about the coverage of your tests. +As part of CI, this project uses Codecov to generate coverage reports. When CI runs, it will generate code coverage +data (based on the tests run by CI) and upload that data to the CodeCov website, which in turn can provide you more info +about the coverage of your tests. -However, because Codecov is known to run into intermittent problems (e.g., report upload fails) due to issues on the Codecov service side, the CI is configured to pass even if the Codecov task failed. Therefore, developers are advised to check the code coverage levels periodically and take corrective actions if the coverage level falls below desired levels. +However, because Codecov is known to run into intermittent problems (e.g., report upload fails) due to issues on the +Codecov service side, the CI is configured to pass even if the Codecov task failed. Therefore, developers are advised to +check the code coverage levels periodically and take corrective actions if the coverage level falls below desired +levels. -To enable Codecov for forks of this project, follow the steps given in [this se-edu guide](https://se-education.org/guides/tutorials/codecov.html). +To enable Codecov for forks of this project, follow the steps given +in [this se-edu guide](https://se-education.org/guides/tutorials/codecov.html). ### Repository-wide checks -In addition to running Gradle checks, CI includes some repository-wide checks. Unlike the Gradle checks which only cover files used in the build process, these repository-wide checks cover all files in the repository. They check for repository rules which are hard to enforce on development machines such as line ending requirements. +In addition to running Gradle checks, CI includes some repository-wide checks. Unlike the Gradle checks which only cover +files used in the build process, these repository-wide checks cover all files in the repository. They check for +repository rules which are hard to enforce on development machines such as line ending requirements. -These checks are implemented as POSIX shell scripts, and thus can only be run on POSIX-compliant operating systems such as macOS and Linux. To run all checks locally on these operating systems, execute the following in the repository root directory: +These checks are implemented as POSIX shell scripts, and thus can only be run on POSIX-compliant operating systems such +as macOS and Linux. To run all checks locally on these operating systems, execute the following in the repository root +directory: `./config/travis/run-checks.sh` @@ -58,12 +69,14 @@ Any warnings or errors will be printed out to the console. **If adding new checks:** -* Checks are implemented as executable `check-*` scripts within the `.github` directory. The `run-checks.sh` script will automatically pick up and run files named as such. That is, you can add more such files if you need and the CI will do the rest. +* Checks are implemented as executable `check-*` scripts within the `.github` directory. The `run-checks.sh` script will + automatically pick up and run files named as such. That is, you can add more such files if you need and the CI will do + the rest. * Check scripts should print out errors in the format `SEVERITY:FILENAME:LINE: MESSAGE` - * SEVERITY is either ERROR or WARN. - * FILENAME is the path to the file relative to the current directory. - * LINE is the line of the file where the error occurred and MESSAGE is the message explaining the error. + * SEVERITY is either ERROR or WARN. + * FILENAME is the path to the file relative to the current directory. + * LINE is the line of the file where the error occurred and MESSAGE is the message explaining the error. * Check scripts must exit with a non-zero exit code if any errors occur. @@ -73,7 +86,9 @@ Any warnings or errors will be printed out to the console. Here are the steps to create a new release. -1. Update the version number in [`MainApp.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/MainApp.java). +1. Update the version number + in [`MainApp.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/MainApp.java). 1. Generate a fat JAR file using Gradle (i.e., `gradlew shadowJar`). 1. Tag the repo with the version number. e.g. `v0.1` -1. [Create a new release using GitHub](https://help.github.com/articles/creating-releases/). Upload the JAR file you created. +1. [Create a new release using GitHub](https://help.github.com/articles/creating-releases/). Upload the JAR file you + created. diff --git a/docs/DeveloperGuide.md b/docs/DeveloperGuide.md index 8a861859bfd..d783dea34cb 100644 --- a/docs/DeveloperGuide.md +++ b/docs/DeveloperGuide.md @@ -2,22 +2,56 @@ layout: page title: Developer Guide --- -* Table of Contents -{:toc} +## Table of Contents +* [Acknowledgements](#acknowledgements) +* [Setting up and getting started](#setting-up-and-getting-started) +* [Design](#design) + * [Architecture](#architecture) + * [UI Component](#ui-component) + * [Logic Component](#logic-component) + * [Model Component](#model-component) + * [Storage component](#storage-component) + * [Common Classes](#common-classes) +* [Implementation](#implementation) + * [Add Features (`add-developer`, `add-client`, `add-project`)](#add-features-add-developer-add-client-add-project) + * [Delete Features (`delete-developer`, `delete-client`)](#delete-features-delete-developer-delete-client) + * [Import Features (`import-developer`, `import-client`)](#import-feature-import-developer-import-client) + * [Find Features (`find-developer`, `find-client`, `find-project`)](#find-feature-find-developer-find-client-find-project) + * [Edit Features (`edit-developer`, `edit-client`, `edit-project`)](#edit-feature-edit-developer-edit-client-edit-project) + * [List Features (`list-developer`, `list-client`, `list-project`)](#list-feature-list-developer-list-client-list-project) + * [Undo/redo Features (`undo`, `redo`)](#undoredo-feature-undo-redo) + * [Add-role Feature (`add-developer-role`, `add-client-role`)](#add-role-feature-add-developer-role-add-client-role) + * [Delete-role Feature (`delete-developer-role`, `delete-client-role`)](#delete-role-feature-delete-developer-role-delete-client-role) + * [GUI Feature](#gui-feature) +* [Documentation, logging, testing, configuration, dev-ops](#documentation-logging-testing-configuration-dev-ops) +* [Appendix: Requirements](#appendix-requirements) + * [Product Scope](#project-scope) + * [User Stories](#user-stories) + * [Use Cases](#use-cases) + * [Non Functional Requirements](#non-functional-requirements) + * [Glossary](#glossary) +* [Appendix: Manual Testing](#appendix-manual-testing) +* [Appendix: Planned Enhancements](#appendix-planned-enhancements) +* [Appendix: Effort](#appendix-effort) -------------------------------------------------------------------------------------------------------------------- ## **Acknowledgements** +* Adapted from [AB3](https://se-education.org/addressbook-level3/) +* This project uses Ratings Bar from [ControlsFX](https://controlsfx.github.io/) -* {list here sources of all reused/adapted ideas, code, documentation, and third-party libraries -- include links to the original source as well} +[Scroll back to Table of Contents](#table-of-contents) -------------------------------------------------------------------------------------------------------------------- -## **Setting up, getting started** +## **Setting up and getting started** -Refer to the guide [_Setting up and getting started_](SettingUp.md). +* Refer to the guide [Setting up and getting started](https://ay2324s1-cs2103t-t09-2.github.io/tp/SettingUp.html). + +[Scroll back to Table of Contents](#table-of-contents) -------------------------------------------------------------------------------------------------------------------- +
## **Design** @@ -27,38 +61,35 @@ Refer to the guide [_Setting up and getting started_](SettingUp.md). ### Architecture - -The ***Architecture Diagram*** given above explains the high-level design of the App. +The **Architecture Diagram** given above explains the high-level design of the App. -Given below is a quick overview of main components and how they interact with each other. +Given below is a quick overview of main components and how they interact with ea **Main components of the architecture** **`Main`** (consisting of classes [`Main`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/Main.java) and [`MainApp`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/MainApp.java)) is in charge of the app launch and shut down. * At app launch, it initializes the other components in the correct sequence, and connects them up with each other. * At shut down, it shuts down the other components and invokes cleanup methods where necessary. - -The bulk of the app's work is done by the following 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. +* The bulk of the app's work is done by the following 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. [**`Commons`**](#common-classes) represents a collection of classes used by multiple other components. **How the architecture components interact with each other** -The *Sequence Diagram* below shows how the components interact with each other for the scenario where the user issues the command `delete 1`. +The *Sequence Diagram* below shows how the components interact with each other for the scenario where the user issues the command `delete-developer 1`. Each of the four main components (also shown in the diagram above), * defines its *API* in an `interface` with the same name as the Component. -* implements its functionality using a concrete `{Component Name}Manager` class (which follows the corresponding API `interface` mentioned in the previous point. +* implements its functionality using a concrete `{Component Name}Manager` class (which follows the corresponding API `interface` mentioned in the previous point). For example, the `Logic` component defines its API in the `Logic.java` interface and implements its functionality using the `LogicManager.java` class which follows the `Logic` interface. Other components interact with a given component through its interface rather than the concrete class (reason: to prevent outside component's being coupled to the implementation of a component), as illustrated in the (partial) class diagram below. @@ -66,22 +97,42 @@ For example, the `Logic` component defines its API in the `Logic.java` interface The sections below give more details of each component. +[Scroll back to Table of Contents](#table-of-contents) ### UI component The **API** of this component is specified in [`Ui.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/ui/Ui.java) ![Structure of the UI Component](images/UiClassDiagram.png) -The UI consists of a `MainWindow` that is made up of parts e.g.`CommandBox`, `ResultDisplay`, `PersonListPanel`, `StatusBarFooter` etc. All these, including the `MainWindow`, inherit from the abstract `UiPart` class which captures the commonalities between classes that represent parts of the visible GUI. +The UI consists of a `MainWindow` that is made up of parts e.g.`CommandBox`, `ResultDisplay`, `DeveloperListPanel`, `ClientListPanel`, `ProjectListPanel`, `StatusBarFooter` etc. All these, including the `MainWindow`, inherit from the abstract `UiPart` class which captures the commonalities between classes that represent parts of the visible GUI. The `UI` component uses the JavaFx UI framework. The layout of these UI parts are defined in matching `.fxml` files that are in the `src/main/resources/view` folder. For example, the layout of the [`MainWindow`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/ui/MainWindow.java) is specified in [`MainWindow.fxml`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/resources/view/MainWindow.fxml) The `UI` component, +The UI component, +* executes user commands using the Logic component. +* listens for changes to Model data so that the UI can be updated with the modified data. +* keeps a reference to the Logic component, because the UI relies on the Logic to execute commands. +* depends on some classes in the Model component, as it displays `Developer`, `Client`, `Project` objects residing in the Model. + +#### Main Window -* executes user commands using the `Logic` component. -* listens for changes to `Model` data so that the UI can be updated with the modified data. -* keeps a reference to the `Logic` component, because the `UI` relies on the `Logic` to execute commands. -* depends on some classes in the `Model` component, as it displays `Person` object residing in the `Model`. +The MainWindow houses all the components that make up the visual display of CodeContact. Its primary function is to listen to user input through the CommandBox, initiate the execution of the command, and display the result through the ResultDisplay and/or tabPane. +The tabPane houses the DeveloperListPanel, ClientListPanel and ProjectListPanel, which are responsible for displaying the list of developers, clients and projects respectively. The MainWindow also houses the StatusBarFooter, which displays the current status of the application. + +Here is a table containing a brief description of the purpose of the smaller components within MainWindow + +| Component | Description | +|-----------|-------------| +| `CommandBox` | The CommandBox is where the user enters commands to be executed. | +| `ResultDisplay` | The ResultDisplay displays the result of the command execution. | +| `DeveloperListPanel`| The DeveloperListPanel displays the list of developers. | +| `ClientListPanel` | The ClientListPanel displays the list of clients. | +| `ProjectListPanel` | The ProjectListPanel displays the list of projects. | +| `StatusBarFooter` | The StatusBarFooter displays the current status of the application. | +| `HelpWindow` | Displays a help window containing a link to the User Guide. | + +[Scroll back to Table of Contents](#table-of-contents) ### Logic component @@ -91,47 +142,50 @@ Here's a (partial) class diagram of the `Logic` component: -The sequence diagram below illustrates the interactions within the `Logic` component, taking `execute("delete 1")` API call as an example. +The sequence diagram below illustrates the interactions within the `Logic` component, taking `execute("delete-client 1")` API call as an example. -![Interactions Inside the Logic Component for the `delete 1` Command](images/DeleteSequenceDiagram.png) +![Interactions Inside the Logic Component for the `delete-client 1` Command](images/DeleteClientSequenceDiagram.png)
:information_source: **Note:** The lifeline for `DeleteCommandParser` should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram.
How the `Logic` component works: -1. When `Logic` is called upon to execute a command, it is passed to an `AddressBookParser` object which in turn creates a parser that matches the command (e.g., `DeleteCommandParser`) and uses it to parse the command. -1. This results in a `Command` object (more precisely, an object of one of its subclasses e.g., `DeleteCommand`) which is executed by the `LogicManager`. -1. The command can communicate with the `Model` when it is executed (e.g. to delete a person). -1. The result of the command execution is encapsulated as a `CommandResult` object which is returned back from `Logic`. +1. When `Logic` is called upon to execute a command, it is passed to an `AddressBookParser` object which in turn creates a parser that matches the command (e.g., `DeleteDeveloperCommandParser`) and uses it to parse the command. +2. This results in a `Command` object (more precisely, an object of one of its subclasses e.g., `DeleteDeveloperCommand`) which is executed by the `LogicManager`. +3. The command can communicate with the `Model` when it is executed (e.g. to delete a developer). +4. The result of the command execution is encapsulated as a `CommandResult` object which is returned back from `Logic`. Here are the other classes in `Logic` (omitted from the class diagram above) that are used for parsing a user command: - How the parsing works: -* When called upon to parse a user command, the `AddressBookParser` class creates an `XYZCommandParser` (`XYZ` is a placeholder for the specific command name e.g., `AddCommandParser`) which uses the other classes shown above to parse the user command and create a `XYZCommand` object (e.g., `AddCommand`) which the `AddressBookParser` returns back as a `Command` object. -* All `XYZCommandParser` classes (e.g., `AddCommandParser`, `DeleteCommandParser`, ...) inherit from the `Parser` interface so that they can be treated similarly where possible e.g, during testing. +* When called upon to parse a user command, the `AddressBookParser` class creates an `XYZCommandParser` (`XYZ` is a placeholder for the specific command name e.g., `AddCommandParser`) which uses the other classes shown above to parse the user command and create a `XYZCommand` object (e.g., `AddProjectCommand`) which the `AddressBookParser` returns back as a `Command` object. +* All `XYZCommandParser` classes (e.g., `AddDeveloperCommandParser`, `DeleteClientCommandParser`, ...) inherit from the `Parser` interface so that they can be treated similarly where possible e.g, during testing. + +
### Model component **API** : [`Model.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/model/Model.java) + The `Model` component, -* stores the address book data i.e., all `Person` objects (which are contained in a `UniquePersonList` object). -* stores the currently 'selected' `Person` objects (e.g., results of a search query) as a separate _filtered_ list which is exposed to outsiders as an unmodifiable `ObservableList` that can be 'observed' e.g. the UI can be bound to this list so that the UI automatically updates when the data in the list change. +* stores the address book data i.e., all `Developer` objects (which are contained in a `UniqueDeveloperList` object), and similarly so for `Client` and `Project` objects. +* stores the currently 'selected' `Developer` objects (e.g., results of a search query) as a separate _filtered_ list which is exposed to outsiders as an unmodifiable `ObservableList` that can be 'observed' e.g. the UI can be bound to this list so that the UI automatically updates when the data in the list change. This is similar for `Client` and `Project` objects. * stores a `UserPref` object that represents the user’s preferences. This is exposed to the outside as a `ReadOnlyUserPref` objects. * does not depend on any of the other three components (as the `Model` represents data entities of the domain, they should make sense on their own without depending on other components) -
:information_source: **Note:** An alternative (arguably, a more OOP) model is given below. It has a `Tag` list in the `AddressBook`, which `Person` references. This allows `AddressBook` to only require one `Tag` object per unique tag, instead of each `Person` needing their own `Tag` objects.
- +
:information_source: Note: An alternative (arguably, a more OOP) model is given below. It has a `Project` list in the `AddressBook`, which `Developer`/`Client` references. This allows CodeContact to only require one `Project` object per unique `Project`, instead of each `Developer`/`Client` needing their own set of Strings holding the `Project` names.
+
+ ### Storage component @@ -146,19 +200,297 @@ The `Storage` component, ### Common classes -Classes used by multiple components are in the `seedu.addressbook.commons` package. +Classes used by multiple components are in the `seedu.address.commons` package. + +This section describes some noteworthy details on how certain features are implemented. -------------------------------------------------------------------------------------------------------------------- +
-## **Implementation** +## Implementation -This section describes some noteworthy details on how certain features are implemented. +### Add Features (`add-developer`, `add-client`, `add-project`) +Example Uses: + +`add-developer n/Mahidharah p/81256788 e/aunus@nus.com a/Blk 88 Lorong 8 Serangoon Gardens, #08-88 r/Developer pr/Appollo pr/Orbital s/8880 d/20-10-2020 g/mahidharah88 rt/5.0` + +`add-client n/Mahidharah p/81256788 e/aunus@nus.com a/Blk 88 Lorong 8 Serangoon Gardens, #08-88 r/HR pr/Appollo pr/Orbital o/Google do/google.com` + +`add-project n/Tp dr/Team Project dl/19-12-2023,Design backend,HIGH,0 dl/21-12-2023,Design frontend,LOW,1` + + +#### Intended Result +Adds a developer, client or project to the address book, and adds them to the bottom of the list of exisitng developers, clients or projects respectively. The newly added developer, client or project will be displayed in the list of developers, clients or projects respectively. +If the developer, client or project already exists in the address book, the command will not be allowed and an error will be thrown to alert user. + +#### Implementation +Upon entry of the add-developer command for instance, an `AddDeveloperCommand` class is created. The `AddDeveloperCommand` class extends the abstract `Command` class and implements the `execute()` method. Upon execution of this method, a `Developer` object is added to the model’s list of developers if all the attributes provided are valid and a duplicate instance does not exist. +A similar implementation is done for the add-client and add-project commands, where a `AddClientCommand` and `AddProjectCommand` class (which similarly extend the `Command` class) is created respectively to add a `Client` or `Project` object to the model’s list of clients or projects. + +Given below is an example usage scenario of how the add developer is executed step by step. + +**Step 1.** User launches the application and unlocks the application with the correct password. + +**Step 2.** User executes an add-developer command by entering `add-developer n/Mahidharah p/81256788 e/aunus@nus.com a/Blk 88 Lorong 8 Serangoon Gardens, #08-88 r/Developer pr/Appollo pr/Orbital s/8880 d/20-10-2020 g/mahidharah88 rt/5.0` + +**Step 3.** The developer is added to the model’s list of developers if valid. + +add-client and add-project commands are executed in a similar manner. + +The following sequence diagram illustrates how the add developer operation works + +![image](images/AddDeveloperSequenceDiagram.png) + +[Scroll back to Table of Contents](#table-of-contents) + +### Delete Features (`delete-developer`, `delete-client`) +#### Intended Result +Deletes a developer or client at the specified **one-based index** of list of currently existing/found developers or clients respectively, depending on the command. Users are able to delete any developers or clients in their respective lists. If an index larger than or equal to the size of the respective lists provided, the command will not be allowed and an error will be thrown to alert user. + +Example Uses: + +`delete-developer 3` + +`delete-client 2` +
+ +#### Implementation +Upon entry of the delete developer command for instance, a `DeleteDeveloperCommand` class is created. The `DeleteDeveloperCommand` class extends the abstract `Command` class and implements the `execute()` method. Upon execution of this method, the developer at specified **one-based index** is removed if the index provided is valid. + +Given below is an example usage scenario of how the delete developer command behaves at each step. + +**Step 1.** User launches the application and unlocks the application with the correct password. + +**Step 2.** User executes a delete-developer command by entering `delete-develoepr 1` to delete the developer at index 1 (one-based indexing) + +**Step 3.** The developer at this index is removed if the index provided is valid. + +A similar implementation is done for the delete-client command, where a `DeleteClientCommand`class (which similarly extend the `Command` class) is created respectively to delete a `Client` object from the model’s list of clients. + +The following sequence diagram illustrates how the delete developer operation works: + +![image](images/DeleteClientSequenceDiagram.png) + + +[Scroll back to Table of Contents](#table-of-contents) + +
+ +### Delete Project Feature (`delete-project`) +#### Intended Result +Deletes a project at the specified **one-based index** of list of currently existing/found projects. Users are able to delete any project in project lists. If an index larger than or equal to the size of the project list provided, the command will not be allowed and an error will be thrown to alert user. +Additionally, if the project is assigned to a developer or client, the project will be removed from the developer or client's set of projects. + +Example Uses: + +`delete-project 2` + +#### Implementation +Upon entry of the delete project command, a `DeleteProjectCommand` class is created. The `DeleteProjectCommand` class extends the abstract `Command` class and implements the `execute()` method. Upon execution of this method, the project at specified **one-based index** is removed if the index provided is valid. + +Given below is an example usage scenario of how the delete developer command behaves at each step. + +**Step 1.** User launches the application and unlocks the application with the correct password. + +**Step 2.** User executes a delete-project command by entering `delete-project 1` to delete the project at index 1 (one-based indexing) + +**Step 3.** The project at this index is removed if the index provided is valid. Developer and Client lists are iterated through and the project is removed from the respective project sets if the project is assigned to them. + +This is similar to delete-developer and delete-client commands, except that in the delete project method is called in the model, the project is also removed from the respective developer and client's project sets. +The following sequence diagram illustrates how the delete-project operation works: + +![image](images/DeleteProjectSequenceDiagram.png) + +#### Design considerations + 1. **Alternative 1:** Make the delete-project command call edit-developer and edit-client commands, both to update the project sets of the respective developers and clients. + * Pros: Easy to implement. + * Cons: May be less efficient as the edit-developer and edit-client commands will have to be called for each developer and client respectively. + * Edit command will have to retrieve client and developer project sets, iterate through each set to edit them accordingly and then pass the command, which will be retrieving information from the model to the logic component, which will complicate and potentially break abstractions originally in place. + 2. **Alternative 2:** Do not edit developer and client project sets + * Pros: No extra logic needed to be implemented. + * Cons: Information integrity is compromised as the project will still be assigned to the developer and client even after it is deleted, and this will affect other features such as find, and potentially future features. + 3. **Alternative 3 (current choice):** The delete project method in the model will iterate through developers and clients to make necessary changes to their project sets. + * Pros: Information integrity is maintained. Abstractions in place are not broken. Complies with implemented Validation checks. + * Cons: Implementation is hidden in the model component, and might not be intuitive in a glance in the logic component. + + +[Scroll back to Table of Contents](#table-of-contents) + +
+ +### Import Feature (`import-developer`, `import-client`) +This feature will allow project managers to import existing spreadsheets of developer and client data in the specified format in CSV +#### Implementation + +Upon entry of the import developer command, an `ImportDeveloperCommand` class is created. The `ImportDeveloperCommand` class extends the abstract `Command` class and implements the `execute()` method. Upon execution of this method, a list of `Developer` objects are added to the model’s list of developers if all the attributes provided are valid and a duplicate instance does not exist. + +Given below is an example usage scenario of how the import developer is executed step by step. + +**Step 1.** User launches the application + +**Step 2.** User inputs `import-developer developers.csv` to indicate path and filename of where the spreadsheet of developers is located. + +**Step 3.** The developers are added to the model’s list of developers if valid the column names are valid and each row of input is valid. + +The implementation follows likewise for clients. + +The following sequence diagram illustrates how the import developer operation works: +![sequence diagram](images/ImportDeveloperSequenceDiagram.png) + +[Scroll back to Table of Contents](#table-of-contents) + +### Edit Feature (`edit-developer`, `edit-client`, `edit-project`) +#### Implementation +The original edit feature from AB-3 has been extended to account for the editing of projects and specific people - developers +and clients. The edit command will be parsed to return 1 of 3 different commands, depending on the +object to be edited. + +The `AddressBookParser` will return the respective parser for the command depending on the user input in accordance to the +respective command words defined in `CliSyntax`. Namely, +* `edit-developer` will return an `EditDeveloperCommandParser` that parses the user input and creates an `EditDeveloperCommand` +* `edit-client` will return an `EditClientCommandParser` that parses the user input and creates an `EditClientCommand` +* `edit-project` will return an `EditProjectCommandParser` that parses the user input and creates an `EditProjectCommand` + +Each instance of `EditDeveloperCommand`, `EditClientCommand`, and `EditProjectCommand` objects have 2 private fields: +1. an instance of `Index` containing the index of the target object to edit in the currently displayed list, and +2. an instance of `EditDeveloperDescriptor`, `EditClientDescriptor`, or `EditProjectDescriptor` respectively, which + contains the edited fields to update the target object with. + +Executing the command will replace the existing object in the current `model` with the new object with the edited fields. + +Other than extending the commands, parsers, and descriptors to account for `Developer`, `Client`, and `Project` separately, +some changes to the sequence of interactions between the `Logic` and `Model` components were also made. When +`EditDeveloperCommand` and `EditClientCommand` is executed with edits made to a `Project` assigned to a `Developer` or +`Client`, it calls `Model#areProjectsValid()` to check whether there is an existing `Project` with that name. + +Given below is an example usage scenario where the user edits the projects assigned to a `Developer` using the `edit-developer` +command. + +**Step 1.** User launches the application and unlocks the application with the correct password. + +**Step 2.** User executes an edit developer command by entering `edit-developer 1 pr/AppleApp` to edit the projects assigned +to the developer at index 1 (one-based indexing) in the currently displayed developer list. + +**Step 3.** The developer at index 1 is edited to be assigned to the project `AppleApp` given that there is an existing +project with the name `AppleApp` in the address book. -### \[Proposed\] Undo/redo feature +The sequence diagram below illustrates key interactions taking place in the `Logic` component when the command +`edit-developer 1 pr/AppleApp` is called. A significant modification to take note off is the call to the +`Model#areProjectsValid()` method. The sequence diagram below reflects a successful command execution. -#### Proposed Implementation +![Interactions inside the Logic component for the `edit-developer 1 pr/AppleApp` Command](images/EditDeveloperSequenceDiagram.png) -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 `edit-client` and `edit-project` commands are executed similarly, except project validation checks using the +`Model#areProjectsValid()` method are not conducted for the latter. + +#### Design considerations +**Aspect: Command syntax** +* **Alternative 1 (current choice)**: Have separate commands for each `Developer`, `Client`, and `Project`. Executing the command +automatically switches user to the respective tab. + * Pros: More specific and straightforward, allowed parameters in command are easier to navigate for users. More flexible + as do not need to be in respective tab to edit. + * Cons: More classes to create, user needs to type more. +* **Alternative 2**: Have one general `edit` command. The edit will be made based on the current tab displayed. + * Pros: User as can be less specific when typing command. + * Cons: User needs to ensure that intended tab is open. Allowed parameters are less clearly defined, can lead to + confusion and mistakes. + +[Scroll back to Table of Contents](#table-of-contents) + + +### Find Feature (`find-developer`, `find-client`, `find-project`) + +#### Implementation + +The find feature is facilitated by a map-based strategy, associating specific prefixes (e.g., "find-developer n/" or "find-client r/") with corresponding predicates, allowing dynamic generation of filtering criteria based on user input. + +
+ +Implemented operations include: +- `FindCommandParser#parse()`: Interprets the user's input and generates the appropriate predicate to filter the list of developers or clients. +- `Model#updateFilteredPersonList()`: Updates the list displayed in the UI based on the provided predicate. + +Given below is an example usage scenario and how the find mechanism behaves at each step: + +**Step 1.** The user launches the application. The list of developers and clients are displayed. + +**Step 2.** To filter developers by name, the user executes the command `find-developer n/ alice bob`. The application recognizes the "developer n/" prefix and uses the `NameContainsKeywordsPredicate` to generate a filtering criteria. The list in the UI is updated to only display developers named Alice or Bob. + +**Step 3.** Next, the user wants to find clients from a specific organisation. They use the command `find-client o/Google`. The "find-client o/" prefix maps to the `OrganisationContainsKeywordsPredicate` and filters clients associated with Google. + +**Step 4.** If the user provides an unrecognized prefix, e.g., `find-developer z/ alice`, an error message is displayed informing them of the correct command format. + +> :information_source: **Note:** The following sequence diagram provides an overview of how the find operation is executed: + +The following sequence diagram provides an overview of how the find operation is executed + + +![Interactions Inside the Logic Component for the `find-developer n/alice` Command](images/FindDeveloperSequenceDiagram.png) + +
+ +#### Design considerations +**Aspect:** Implementation of the predicate map:
+**Alternative 1:** +- Use a long chain of `if-else` conditions for each attribute. + - **Pros:** Explicit parsing logic for each attribute. + - **Cons:** Code becomes lengthy and hard to maintain. Adding a new attribute involves modifying the parsing logic, increasing the risk of errors. + +**Alternative 2 (current choice):** +- Use a map linking prefixes to their corresponding predicate constructors. + - **Pros:** Simplifies the parsing process. Adding a new attribute to search becomes as simple as adding a new entry in the map. + - **Cons:** A potential mismatch between the prefix and its predicate can lead to wrong results. + +Given the benefits of a more maintainable and scalable codebase, we've decided to go with the first alternative. Future enhancements might include fuzzy search. + + +[Scroll back to Table of Contents](#table-of-contents) + +### List Feature (`list-developer`, `list-client`, `list-project`) + +#### Implementation +The list command employs a structured approach where specific commands, such as `list-developer` or `list-client` +are associated with corresponding functionalities. This allows users to efficiently retrieve +information about developers, clients or projects by specifying the relevant prefix, streamlining the process of +generating targeted lists based on user input. + +Implemented operations include: +* `` here refers to developer, client or project +* `AddressBookParser`: Interprets the user's input and calls the appropriate `ListCommand#execute()` to print the + relevant lists of data +* `Model#updateFilteredList`: Update the list displayed in the UI to print all the existing developers + ,clients or projects. + +Given below is an example usage scenario and how the `list` mechanism behaves at each step: + +**Step 1.** The user used the `find` feature to search for something and the UI is only displaying some +developers + +**Step 2.** To list all the developers, the user executes the command `list-developer`. `AddressBookParser` +recognizes the `list-developer` command and calls the `ListDeveloperCommand`. + +**Step 3.** Next, the `ListDeveloperCommand#execute()` method that overides the abstract method `Command#execute()` gets +activated.This `execute()` method calls the `Model#updateFilteredDeveloperList`, passing in the `Predicate` +that has been set to true. + +**Step 4.** `Model#updateFilteredDeveloperList` then updates the list in the UI to print all the existing developers. + +The following sequence diagram provides an overview of how the find operation is executed + +![sequence diagram](images/ListDeveloperSequenceDiagram.png) + + +[Scroll back to Table of Contents](#table-of-contents) + +### Undo/redo Feature (`undo`, `redo`) + +#### Implementation - undo + +The proposed undo/redo mechanism is facilitated by `VersionedAddressBook`. It extends `AddressBook` with an undo/redo +history, stored internally as an `addressBookStateList` and `currentStatePointer`. Customised to the functions of CodeContact to +have a more helpful message and automatic tab switch, the history for messages and tabs are also stored internally as +`successfulCommandMessages` and `tabIndex`. +Additionally, it implements the following operations: * `VersionedAddressBook#commit()` — Saves the current address book state in its history. * `VersionedAddressBook#undo()` — Restores the previous address book state from its history. @@ -166,212 +498,1108 @@ The proposed undo/redo mechanism is facilitated by `VersionedAddressBook`. It ex These operations are exposed in the `Model` interface as `Model#commitAddressBook()`, `Model#undoAddressBook()` and `Model#redoAddressBook()` respectively. -Given below is an example usage scenario and how the undo/redo mechanism behaves at each step. +Given below is an example usage scenario and how the `undo`/`redo` mechanism behaves at each step. -Step 1. The user launches the application for the first time. The `VersionedAddressBook` will be initialized with the initial address book state, and the `currentStatePointer` pointing to that single address book state. +**Step 1.** The user launches the application for the first time. The `VersionedAddressBook` will be initialized with the initial address book state, and the `currentStatePointer` pointing to that single address book state. ![UndoRedoState0](images/UndoRedoState0.png) -Step 2. The user executes `delete 5` command to delete the 5th person in the address book. The `delete` command calls `Model#commitAddressBook()`, causing the modified state of the address book after the `delete 5` command executes to be saved in the `addressBookStateList`, and the `currentStatePointer` is shifted to the newly inserted address book state. +**Step 2.** The user executes `delete-developer 5` command to delete the 5th developer in the address book. The `delete` +command calls `Model#commitAddressBook()`, causing the modified state of the address book after the `delete-developer 5` +command executes to be saved in the `addressBookStateList`. The successful command message and tab it switched to +also saved into `successfulCommandMessages` and `tabIndex` respectively. The `currentStatePointer` is then shifted to the newly +inserted address book state. ![UndoRedoState1](images/UndoRedoState1.png) -Step 3. The user executes `add n/David …​` to add a new person. The `add` command also calls `Model#commitAddressBook()`, causing another modified address book state to be saved into the `addressBookStateList`. +**Step 3.** The user executes `add-developer n/David …​` to add a new developer. The `add` command also calls +`Model#commitAddressBook()`, causing another modified address book state to be saved into the `addressBookStateList`, +and another successful command message and tab switched saved into `successfulCommandMessages` and `tabIndex`. ![UndoRedoState2](images/UndoRedoState2.png)
:information_source: **Note:** If a command fails its execution, it will not call `Model#commitAddressBook()`, so the address book state will not be saved into the `addressBookStateList`. -
-Step 4. The user now decides that adding the person was a mistake, and decides to undo that action by executing the `undo` command. The `undo` command will call `Model#undoAddressBook()`, which will shift the `currentStatePointer` once to the left, pointing it to the previous address book state, and restores the address book to that state. +**Step 4.** The user now decides that adding the developer was a mistake, and decides to undo that action by executing the `undo` command. The `undo` command will call `Model#undoAddressBook()`, which will shift the `currentStatePointer` once to the left, pointing it to the previous address book state, and restores the address book to that state. ![UndoRedoState3](images/UndoRedoState3.png) -
:information_source: **Note:** If the `currentStatePointer` is at index 0, pointing to the initial AddressBook state, then there are no previous AddressBook states to restore. The `undo` command uses `Model#canUndoAddressBook()` to check if this is the case. If so, it will return an error to the user rather -than attempting to perform the undo. - -
- The following sequence diagram shows how the undo operation works: +![UndoRedoState3](images/UndoSequenceDiagram.png) -![UndoSequenceDiagram](images/UndoSequenceDiagram.png) - -
:information_source: **Note:** The lifeline for `UndoCommand` should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram. - -
+[Scroll back to Table of Contents](#table-of-contents) +#### Implementation - redo 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.
: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 5. The user then decides to execute the command `list`. Commands that do not modify the address book, such as `list`, will usually not call `Model#commitAddressBook()`, `Model#undoAddressBook()` or `Model#redoAddressBook()`. Thus, the `addressBookStateList` remains unchanged. +**Step 5.** The user then decides to execute the command `list`. Commands that do not modify the address book, such as `list`, will usually not call `Model#commitAddressBook()`, `Model#undoAddressBook()` or `Model#redoAddressBook()`. Thus, the `addressBookStateList` remains unchanged. ![UndoRedoState4](images/UndoRedoState4.png) -Step 6. The user executes `clear`, which calls `Model#commitAddressBook()`. Since the `currentStatePointer` is not pointing at the end of the `addressBookStateList`, all address book states after the `currentStatePointer` will be purged. Reason: It no longer makes sense to redo the `add n/David …​` command. This is the behavior that most modern desktop applications follow. +#### Design considerations +**Aspect: How undo & redo executes:** -![UndoRedoState5](images/UndoRedoState5.png) +* **Alternative 1 (current choice):** Saves the entire address book. + * Pros: Easy to implement. + * Cons: May have performance issues in terms of memory usage. -The following activity diagram summarizes what happens when a user executes a new command: +* **Alternative 2 (part of current choice):** Individual command knows how to undo/redo by itself. + * Pros: Will use less memory (e.g. for `delete`, just save the developer being deleted). + * Cons: We must ensure that the implementation of each individual command are correct. + * Due to a special parameter `role` having its own add and delete functions which are separated from the + model and not stored in address book, it uses alternative 2 whereby `undo` an `add-developer-role` will just + execute the `delete-developer-role function`. The process of this implementation had to be very careful just like + what the cons mentioned, a slight validation error can change the whole `undo` and `redo` feature. - -#### Design considerations: +[Scroll back to Table of Contents](#table-of-contents) -**Aspect: How undo & redo executes:** +### Add-role Feature (`add-developer-role`, `add-client-role`) -* **Alternative 1 (current choice):** Saves the entire address book. +#### Implementation +The add role command employs a structured approach where specific commands, such as `add-developer-role` or +`add-client-role`are associated with corresponding functionalities. This allows users to efficiently add +information about developers and clients. One of the validation checks when adding a developer or client is that this role +added must exist. This is where users will need this command to add in the respective roles. This feature is facilitated +with the `DeveloperRoles` and `ClientRoles` class which implements the following operations: + +* `DeveloperRoles#addDeveloperRole()`  —  Adds a developer role into the list of roles stored +* `DeveloperRoles#saveDeveloperRoles()`  —  Saves the updated list of developer roles +* `DeveloperRoles#loadDeveloperRoles()`  —  Loads the existing list of developer roles from file +* `DeveloperRoles#isValidRole()`  —  Checks if this role being added exists already + +The classes are similar for `ClientRoles` but just that they are associated with the clients. + +Given below is an example usage scenario and how the `add-developer-role` mechanism behaves at each step: + +**Step 1.** The user launches the application. The list roles for developers and clients are loaded into a list or roles. + +**Step 2.** The user executes the command `add-developer-role Tester`. The application recognizes the `add-developer-role` +and calls `AddDeveloperRoleCommandParser#parse()`. + +**Step 3.** The parser checks if the argument is an empty blank space and trims the input given, in this case ` Tester` is +trimmed to `Tester` and calls `AddDeveloperRoleCommand`. + +**Step 4.** `AddDeveloperRoleCommand#execute()` checks if there is an existing role with the same name and creates +a new developer role if there is no such role. + +
+ +
:exclamation: **Note:** +Although no changes is made to the address book, this stage is still committed so that the success command +message and tab index switched to can be changed, the currentPointer can also note that there is an action done here.
+ +
:exclamation: **Note:** +This is not a case-sensitive feature. The command allows users to add `developer` even if `Developer` exists.
+ +The following sequence diagram shows how the Add-role operation works: +![SequenceDiagram](images/AddDeveloperRoleSequenceDiagram.png) + +#### Design considerations +**Aspect: How add-role executes:** +* **Alternative 1 (current choice):** Treat role as a variable, has a separate load and unload list + * Pros: Role can just be treated as an attributed that needs more validation checks + * Cons: Have to load and unload each time system relaunches, quite time and memory consuming + +* **Alternative 2:** Treating role like another person or project, adding roles are like adding projects * Pros: Easy to implement. - * Cons: May have performance issues in terms of memory usage. + * Cons: Roles would have to be implemented on the same level as developers, clients and projects which should not be case. + +[Scroll back to Table of Contents](#table-of-contents) + +
-* **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. +### Delete-role Feature (`delete-developer-role`, `delete-client-role`) + +#### Implementation +The add role command employs a structured approach where specific commands, such as `delete-developer-role` or +`delete-client-role`are associated with corresponding functionalities. This allows users to efficiently delete +information about developers and clients that they no longer need. The system helps you to check if there are any +developers or clients in the list using this feature. If there is, the command will not be successfully executed. +This feature is facilitated with the `DeveloperRoles` and `ClientRoles` class which implements the following operations: + +* `DeveloperRoles#deleteDeveloperRole()`  —  Deletes a developer role into the list of roles stored +* `DeveloperRoles#saveDeveloperRoles()`  —  Saves the updated list of developer roles +* `DeveloperRoles#loadDeveloperRoles()`  —  Loads the existing list of developer roles from file +* `DeveloperRoles#isRemovableRole()`  —  Checks if this role can be deleted; It should not be a pre-determined role, + it should not be a non-existing role, it should not be a role that is in use. + +The classes are similar for `ClientRoles` but just that they are associated with the clients. + +Given below is an example usage scenario and how the `delete-developer-role` mechanism behaves at each step: + +**Step 1.** The user launches the application. The list roles for developers and clients are loaded into a list or roles. + +**Step 2.** The user executes the command `delete-developer-role Tester`. The application recognizes the `delete-developer-role` +and calls `DeleteDeveloperRoleCommandParser#parse()`. + +**Step 3.** The parser checks if the argument is an empty blank space and trims the input given, in this case ` Tester` is +trimmed to `Tester` and calls `DeleteDeveloperRoleCommand`. + +**Step 4.** `DeleteDeveloperRoleCommand#execute()` checks if this is a removable role and removes it from the list of roles +if `DeveloperRoles#isRemovableRole()` returns true. + +
-_{more aspects and alternatives to be added}_ +
:exclamation: **Note:** +Although no changes is made to the address book, this stage is still committed so that the success command +message and tab index switched to can be changed, the currentPointer can also note that there is an action done here.
-### \[Proposed\] Data archiving +The following sequence diagram shows how the Delete-role operation works: +![SequenceDiagram](images/DeleteDeveloperRoleSequenceDiagram.png) -_{Explain here how the data archiving feature will be implemented}_ +The following activity diagram shows how the validation check `isRemovableRole()` works:
+Activity Diagram + + +[Scroll back to Table of Contents](#table-of-contents) + +### Mark/unmark deadline Feature (`mark-deadline`, `unmark-deadline`) +#### Implementation +The mark and unmark deadline features are implemented using a secondary call to the `edit-project` command. As with +the other commands, `mark-deadline` and `unmark-deadline` commands are first parsed to return `MarkDeadlineCommandParser` +and `UnmarkDeadlineCommandParser` respectively. The parses similarly implement the `MarkDeadlineCommandParser#parse()` +and `UnmarkDeadlineCommandParser#parse()` methods which return `MarkDeadlineCommand` and `UnmarkDeadlineCommand` objects +respectively. However, in the execution of `MarkDeadlineCommand` and `UnmarkDeadlineCommand`, a new +`EditProjectCommand` is created and subsequently executed, to get the `CommandResult`. + +Consequently, calling `mark-deadline` and `unmark-deadline` on a deadline of project is synonymous to editing the +project to update the `isDone` status of the deadline. + +This is facilitated by the following methods: +* `MarkDeadlineCommand#editProjectArgs()`  —  Formats each deadline in a list of String representations into a + String that will be used as the arguments parsed by an EditProjectCommandParser. +* `Project#markDeadlineStringRep()`  —  Returns a list with each element being the String representation of the + respective deadline, with the deadline at the given index marked as done. +* `Project#unmarkDeadlineStringRep()`  —  Returns a list with each element being the String representation of the + respective deadline, with the deadline at the given index marked as undone. + +Relevant checks are conducted at the `MarkDeadlineCommand#execute()` and `UnmarkDeadlineCommand#execute()` stages to +ensure the index of the project and the edited deadline passed to the `EditProjectCommandParser` as arguments for the +`EditProjectCommandParser#parse()` as arguments are valid. + +The following sequence diagram illustrates the interactions taking place in the `Logic` component when the command +`mark-deadline 2 1` is called. The sequence reflects a successful command execution, assuming that the current state of +the displayed project list has a project with the index `2` with at least `1` deadline. + +![SequenceDiagram](images/MarkDeadlineSequenceDiagram.png) + +#### Design considerations +**Aspect: Execution of command** +* **Alternative 1**: Implement methods in `ModelManager` class that can directly change the `isDone` status of the deadlines + of a project based on the given project index and deadline index. + * Pros: + * More aligned with OOP principles. + * Mirrors sequence flow of other commands and can be implemented using current code architecture. + * Cons: + * Due to container structure of `Project` and `Deadline`, changing the status of deadlines needs to be done + through projects, so more methods need to be added to achieve this. + * Given the GUI display of project deadlines in a Javafx TableView, makes it more complicated for changes in + deadline status to be automatically reflected in the list of projects and deadlines displayed to the user. +* **Alternative 2 (current choice)**: Implement execution by creating an `EditProjectCommandParser` and `EditProjectCommand` + that will replace the existing project entirely with a new one with the updated deadline being marked/unmarked. + * Pros: + * Fewer methods to implement, allows for more reuse. + * Editing the project with new deadlines will ensure that upon execution of the command, the updated project with + marked/unmarked deadline is displayed to the user on the app. + * Cons: + * Slightly more disorganised interactions within `Logic` component since have to go from parsing a command to + executing it, then parsing another command again and executing that command. + +[Scroll back to Table of Contents](#table-of-contents) + +### GUI Feature + +#### Switching tabs automatically + +When certain commands are executed, the UI will automatically switch to the relevant tab. For example, when the user +executes `list-developer`, the UI will automatically switch to the `Developer` tab. This is implemented by specifying +the `TabIndex` in the `CommandResult` returned in the `execute()` method of the relevant command. + +#### Clicking to switch tabs + +When the user clicks on a different tab, the `list` command will be executed to display the relevant list of information +for the tab clicked. This is implemented by adding a listener to the `TabPane` in the `MainWindow` class. When the +selected tab changes, the `list` command will be executed. + +
:exclamation: **Note:** +When the user clicks away from the tab showing the command result and then switches back to the tab, the tab will +be updated to show the full list of information for that tab again. +
+ +[Scroll back to Table of Contents](#table-of-contents) -------------------------------------------------------------------------------------------------------------------- +
+ ## **Documentation, logging, testing, configuration, dev-ops** +* [Documentation guide](https://ay2324s1-cs2103t-t09-2.github.io/tp/Documentation.html) +* [Testing guide](https://ay2324s1-cs2103t-t09-2.github.io/tp/Testing.html) +* [Logging guide](https://ay2324s1-cs2103t-t09-2.github.io/tp/Logging.html) +* [Configuration guide](https://ay2324s1-cs2103t-t09-2.github.io/tp/Configuration.html) +* [DevOps guide](https://ay2324s1-cs2103t-t09-2.github.io/tp/DevOps.html) -* [Documentation guide](Documentation.md) -* [Testing guide](Testing.md) -* [Logging guide](Logging.md) -* [Configuration guide](Configuration.md) -* [DevOps guide](DevOps.md) +[Scroll back to Table of Contents](#table-of-contents) -------------------------------------------------------------------------------------------------------------------- +
## **Appendix: Requirements** -### Product scope - +### Project Scope **Target user profile**: -* has a need to manage a significant number of contacts +* has a need to manage a significant number of colleague contacts internally * prefer desktop apps over other types * can type fast * prefers typing to mouse interactions * is reasonably comfortable using CLI apps +* a project manager or someone with similar needs working within a software company -**Value proposition**: manage contacts faster than a typical mouse/GUI driven app - +**Value proposition**: CodeContact aims to seamlessly integrate contact, client, and project management, simplifying +access to coding-related contacts, facilitating collaboration, and offering command-line efficiency for project +managers. ### User stories +Priorities: High (must have) - `* * *`, Medium (nice to have) - `* *`, Low (unlikely) - `*` + +| Priority | As a …​ | I want to …​ | So that I can…​ | +|----------|-----------------|------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------| +| `* * *` | project manager | add a list of Developers and their contact information | access contact details easily and quickly assemble teams for new projects | +| `* * *` | project manager | add a list of Clients and their contact information | access client details easily and know who is related to what project. | +| `* * *` | project manager | add a list of Projects and their details | access project details easily and see who is related to the project | +| `* * *` | project manager | delete information about a Client or Developer and the project details will update accordingly | don't repeat deleting several time | +| `* * *` | project manager | edit the the details of the Developers added in | constantly update the contact book | +| `* * *` | project manager | edit the the details of the Clients added in | constantly update the contact book | +| `* * *` | project manager | edit the the details of the Projects added in | constantly update any changes to the project | +| `* * *` | project manager | find the the Developers according to any details they have | source for information related to developers easily | +| `* * *` | project manager | find the the Clients according to any details they have | source for information related to clients easily | +| `* * *` | project manager | find the the Projects according to any details they have | source for information related to projects easily | +| `* * *` | project manager | list different groups of people according to the different commands | view projects, clients and developers can be as different lists | +| `* * *` | project manager | switch between tabs for Developers, Clients and Projects | intuitively view the different data lists | +| `* * *` | project manager | import data from a csv | transfer data from what i am using right now | +| `* *` | project manager | add roles | only add people to existing roles in the system so that i do not assign people to random roles | +| `* *` | project manager | delete roles | remove roles that i think are not useful anymore | +| `* *` | project manager | undo actions | undo any mistakes i made | +| `* *` | project manager | redo actions | redo anything i accidentally undid | +| `* *` | project manager | lock all the data | keep all these data safe | +| `* *` | project manager | unlock all the data | see all these data after locking it | +| `* *` | project manager | change password | access these data without worrying data might be stolen | +| `* *` | project manager | find by deadline | sort out information by deadline due | +| `* *` | project manager | mark deadline | label out completed deadlines as done | +| `* *` | project manager | unmark deadline | label out incomplete or reactivated deadlines as undone | +| `* ` | project manager | send reminder emails to the developers when deadlines are nearing | it can remind them that work is due soon | +| `* ` | project manager | give developers feedback directly from CodeContact | reviews can be autogenerated trough my ratings and i need to write a new review each time | + + +[Scroll back to Table of Contents](#table-of-contents) + +
-Priorities: High (must have) - `* * *`, Medium (nice to have) - `* *`, Low (unlikely to have) - `*` +### Use cases -| 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 | +For all use cases below, we assume the following unless specified otherwise -*{More to be added}* +- The **System** is `CodeContact` +- The **Actor** is the `user` +- The following preconditions + - The `user` has launched the `CodeContact` application. -### Use cases +Furthermore, a lot of **use cases are similar when manipulating +developers, clients and projects**. Therefore, to keep the developer guide concise, the +use cases elaborated upon below are only detailed for developers. Nonetheless, they +can be extrapolated for clients and projects too, without changes to the major details within +the use case. Such associated pairs of use cases are listed in the table below. + +| **Use Case** | **Developer command** | **Client command** | **Project command** | +|--------------|:----------------------|:------------------:|:-------------------:| +| UC3 | Add a developer | Add a client | Add a project | +| UC4 | Delete a developer | Delete a client | Delete a project | +| UC5 | Edit a developer | Edit a client | Edit a project | +| UC6 | Find a developer | Find a client | Find a project | +| UC7 | Add developer role | Add client role | - | +| UC8 | Delete developer role | Delete client role | - | -(For all use cases below, the **System** is the `AddressBook` and the **Actor** is the `user`, unless specified otherwise) +#### **Use case:** UC1 - Unlock System -**Use case: Delete a person** +**Preconditions:** +1. User is not logged in + +**Guarantees:** +1. The system will be unlocked and all data will be shown. **MSS** -1. User requests to list persons -2. AddressBook shows a list of persons -3. User requests to delete a specific person in the list -4. AddressBook deletes the person +1. User requests unlock system with the password. +2. System validates if user's inputs are correct. +3. System logs user in. Use case ends. **Extensions** -* 2a. The list is empty. +* 2a. The password entered is incorrect. + * 2a1. System informs user password entered is incorrect and shows the default password. + * 2a2. User enters password +
Steps 2a1-2a2 are repeated until the correct password is entered. +
Use case resumes from step 3. - Use case ends. +[Scroll back to Table of Contents](#table-of-contents) -* 3a. The given index is invalid. +#### **Use case:** UC2 - Change Password - * 3a1. AddressBook shows an error message. +**Preconditions:** +1. User is logged in - Use case resumes at step 2. +**Guarantees:** +1. The system password will be changed. -*{More to be added}* +**MSS** -### Non-Functional Requirements +1. User requests to change password with current password and new password as inputs. +2. System validates if user's new password fulfils the password criteria are correct. +3. System changes user's password. -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. +Use case ends. -*{More to be added}* +**Extensions** -### Glossary +* 2a. The new password entered is blank or does not follow the format. + * 2a1. System informs user password entered does not follow the format. + * 2a2. User enters new password. +
Steps 2a1-2a2 are repeated until the correct password is entered. +
Use case resumes from step 3. +* 2b. The current password entered is incorrect. + * 2b1. System informs current password entered is incorrect. + * 2b2. User enters new password. +
Steps 2b1-2b2 are repeated until the correct password is entered. +
Use case resumes from step 3. +* 2c. The current password is same as new password. + * 2c1. System informs current password is same as new password. + * 2c2. User enters new password. +
Steps 2c1-2c2 are repeated until the new password is different. +
Use case resumes from step 3. -* **Mainstream OS**: Windows, Linux, Unix, OS-X -* **Private contact detail**: A contact detail that is not meant to be shared with others +[Scroll back to Table of Contents](#table-of-contents) --------------------------------------------------------------------------------------------------------------------- +#### **Use case:** UC3 - Add a developer -## **Appendix: Instructions for manual testing** +
:exclamation: **Note:** +Adding developers, clients and projects have very similar use cases, hence it will not be repeated
-Given below are instructions to test the app manually. +**Preconditions:** User is logged in -
:information_source: **Note:** These instructions only provide a starting point for testers to work on; -testers are expected to do more *exploratory* testing. +**Guarantees:** +1. A new developer will be added to the system after every successful addition -
+**MSS** + +1. User requests to add a developer. +2. System requests for the details of the developer. +3. User enters the requested details in the required format. +4. System validates if the user's inputs are valid. +6. System adds the developer to the data. + + Use case ends. + +**Extensions** + +* 4a. The given details are invalid or in an invalid format. + * 4a1. System informs users specifically which data is wrong. + * 4a2. User enters new data. +
Steps 4a1-4a2 are repeated until the data entered are correct. +
Use case resumes from step 5. + +[Scroll back to Table of Contents](#table-of-contents) + +#### **Use case:** UC4 - Delete a developer +
:exclamation: **Note:** +Deleting developers, clients and projects have very similar use cases, hence it will not be repeated
-### Launch and shutdown +**Preconditions:** User is logged in +
-1. Initial launch +**Guarantees:** +1. A developer deleted from the system after every successful delete - 1. Download the jar file and copy into an empty folder +**MSS** + +1. User requests to delete a developer. +2. System requests for the index of the developer. +3. User enters the index of the developer. +4. System validates if the index is valid. +5. System deletes the developer to the data. + + Use case ends. + +**Extensions** + +* 4a. The index entered is an invalid number or is not a number. + * 4a1. System requests for the correct index + * 4a2. User enters new index +
Steps 4a1-4a2 are repeated until the data entered are correct. +
Use case resumes from step 5. + +[Scroll back to Table of Contents](#table-of-contents) + +#### **Use case:** UC5 - Edit a developer +
:exclamation: **Note:** +Editing developer, client and project have very similar use case, hence it will not be repeated
+ +**Preconditions:** User is logged in + +**Guarantees:** +1. A developer's details are edit from the system after every successful edit + +**MSS** + +1. User requests to edit a developer. +2. System requests for the the index of the developer and details of what they wish to change. +3. User enters the index of the developer and changed details. +4. System validates if index is valid and checks if details changed is valid. +5. System edits the developer's data. + + Use case ends. + +
+ +**Extensions** - 1. Double-click the jar file Expected: Shows the GUI with a set of sample contacts. The window size may not be optimum. +* 4a. The index entered is an invalid number or is not a number. + * 4a1. System requests for the correct index. + * 4a2. User enters new index. +
Steps 4a1-4a2 are repeated until the data entered are correct. +
Use case resumes from step 5. -1. Saving window preferences +* 4b. The details entered are the same as the existing details. + * 4a1. System informs users the details the same. + * 4a2. User enters new details. +
Steps 4b1-4b2 are repeated until the data entered are correct. +
Use case resumes from step 5. - 1. Resize the window to an optimum size. Move the window to a different location. Close the window. +* 4c. The details violate certain validation check and is not allowed to change to these details. + * 4a1. System informs users why the details cannot be changed. + * 4a2. User enters new details. +
Steps 4c1-4c2 are repeated until the data entered are correct. +
Use case resumes from step 5. - 1. Re-launch the app by double-clicking the jar file.
- Expected: The most recent window size and location is retained. +[Scroll back to Table of Contents](#table-of-contents) -1. _{ more test cases …​ }_ +#### **Use case:** UC6 - Find a developer +
:exclamation: **Note:** +Finding developer, client and project have very similar use case, hence it will not be repeated
-### Deleting a person +**Preconditions:** User is logged in + +**Guarantees:** +1. A developer is found according to the details entered. + +**MSS** + +1. User requests to find a developer. +2. System requests for the details user wishes to find by. +3. User enters the details they wish to find by. +4. System searches for developers that matches the details. +5. System shows the relevant developer's data. + + Use case ends. + +**Extensions** + +* 3a. There are no details entered. + * 3a1. System informs users input cannot be empty. + * 3a2. User enters new details. +
Steps 3a1-3a2 are repeated until the data entered are correct. +
Use case resumes from step 4. + +* 3b. The details entered are incorrect (eg. entering alphabets when finding by phone). + * Use case just continues to step 4 but will show that no developers are found in step 5. + +[Scroll back to Table of Contents](#table-of-contents) + +#### **Use case:** UC7 - Undo + +**Preconditions:** User is logged in + +**Guarantees:** +1. The previous command will be undone. + +**MSS** + +1. User requests undo. +2. System checks if there were any changes before. +3. System reverts the change. +
Use case ends. + +**Extensions** -1. Deleting a person while all persons are being shown +* 3a. There are no changes before + * 3a1. System informs users they have reached the first step +
Use case ends + +[Scroll back to Table of Contents](#table-of-contents) + +#### **Use case:** UC8 - Redo + +**Preconditions:** User is logged in + +**Guarantees:** +1. The previous command undone will be redone. + +**MSS** + +1. User requests redo. +2. System checks if there were anything undone changes before. +3. System redoes the change. +
Use case ends. + +
+ +**Extensions** + +* 3a. There are no undone changes before + * 3a1. System inform users they have reached the last step +
Use case ends + +[Scroll back to Table of Contents](#table-of-contents) + +#### **Use case:** UC9 - Add developer role +
:exclamation: **Note:** +Adding developer roles and client roles works the same way, hence add client role use case will not be repeated
+ +**Preconditions:** User is logged in + +**Guarantees:** +1. A new developer role will be added. + +**MSS** + +1. User requests to add a developer role. +2. User enters the role they want to add. +3. System validates if user's inputs are valid. +4. System validates if user's inputs can be added. +5. System adds the developer to the data. + +**Extensions** + +* 3a. User enters an empty role + * 3a1. System informs users role cannot be empty and requests for the correct role + * 3a2. User enters new role +
Steps 3a1-3a2 are repeated until the data entered is not empty +
Use case resumes from step 4. + +* 4a. User enters an existing role + * 4a1. System informs users this role exists + * 4a2. User enters new role +
Steps 4a1-4a2 are repeated until the data entered is not empty +
Use case resumes from step 5. + +[Scroll back to Table of Contents](#table-of-contents) + +
+ +#### **Use case:** UC10 - Delete developer role +
:exclamation: **Note:** +Deleting developer roles and client roles works the same way, hence deleting client role use case will not be repeated
+ +**Preconditions:** User is logged in + +**Guarantees:** +1. A developer role will be deleted. + +**MSS** + +1. User requests to delete a developer role. +2. User enters the role they want to delete. +3. System validates if user's inputs are valid. +4. System validates if user's input can be deleted. +5. System adds the developer to the data. + +**Extensions** + +* 3a. User enters an empty role + * 3a1. System informs users role cannot be empty and requests for the correct role + * 3a2. User enters correct role +
Steps 3a1-3a2 are repeated until the data entered is not empty +
Use case resumes from step 4. + +* 4a. User enters a role that some developer is still using + * 4a1. System informs users this role cannot be deleted as there are developers using + * 4a2. User enters correct role +
Steps 4a1-4a2 are repeated until the data entered is can be deleted +
Use case resumes from step 5. + +* 4b. User enters a role that doesn't exist + * 4b1. System informs users this role don't exist + * 4b2. User enters correct role +
Steps 4a1-4a2 are repeated until the data entered is can be deleted +
Use case resumes from step 5. + +* 4b. User enters a role that cannot be deleted + * 4b1. System informs users this role cannot be deleted as it is a pre-defined role + * 4b2. User enters correct role +
Steps 4a1-4a2 are repeated until the data entered can be deleted +
Use case resumes from step 5. + +[Scroll back to Table of Contents](#table-of-contents) + +
+ +#### **Use case:** UC11 - Mark Deadline + +**Preconditions:** User is logged in + +**Guarantees:** +1. A project deadline will be marked. + +**MSS** + +1. User requests to mark the deadline of a project. +2. System requests for the index of the project and the index of the deadline. +3. User enters relevant indexes. +4. System validates if user's inputs exists. +5. System marks the deadline as done. + +**Extensions** + +* 3a. User enters an empty index + * 3a1. System informs users indexes should not be empty and requests for the correct role + * 3a2. User enters new indexes +
Steps 3a1-3a2 are repeated until the data entered is not empty +
Use case resumes from step 4. + +* 4a. User enters an invalid index (i.e no project with such index or no deadline in project with this index) + * 4a1. System informs users indexes are invalid + * 4a2. User enters new indexes +
Steps 4a1-4a2 are repeated until the data entered is not empty +
Use case resumes from step 5. + [Scroll back to Table of Contents](#table-of-contents) + +#### **Use case:** UC12 - Unmark Deadline + +**Preconditions:** User is logged in + +**Guarantees:** +1. A project deadline will be unmarked. + +**MSS** + +1. User requests to unmark the deadline of a project. +2. System requests for the index of the project and the index of the deadline. +3. User enters relevant indexes. +4. System validates if user's inputs exists. +5. System marks the deadline as undone. + +**Extensions** + +* 3a. User enters an empty index + * 3a1. System informs users indexes should not be empty and requests for the correct role + * 3a2. User enters new indexes +
Steps 3a1-3a2 are repeated until the data entered is not empty +
Use case resumes from step 4. + +* 4a. User enters an invalid index (i.e no project with such index or no deadline in project with this index) + * 4a1. System informs users indexes are invalid + * 4a2. User enters new indexes +
Steps 4a1-4a2 are repeated until the data entered is not empty +
Use case resumes from step 5. + [Scroll back to Table of Contents](#table-of-contents) + +
+ +### Non-Functional Requirements + +#### System/Performance Requirements +* Should work on any _mainstream OS_ as long as it has Java `11` or above installed.
+#### Reliability Requirements +* Should be able to handle failures and show relevant error messages (hardware/network failures) +* Should ensure that data is protected from corruption or loss +* Should be able to recover immediately after inaccurate/invalid commands
+#### Usability Requirements +* Should follow specific code design and usability guidelines +* The user interface shall follow a consistent design pattern and layout throughout the application. +* There shall be clear and intuitive pathways for accomplishing common tasks. +* Users shall receive informative feedback on their actions (e.g., success messages, error messages) + in a clear and user-friendly manner. +* Context-sensitive help and tooltips shall be available to assist users in understanding complex features. +* A comprehensive user manual or online documentation shall be provided to explain how to use the application.
+#### Process Requirements +* The project is expected to adhere to a schedule that completes a milestone set every two weeks. +* The project shall follow an iterative breadth-first development methodology +* Automated testing suits shall be maintained and run for each build +* Code review shall be conducted for all new code contribution, with at least one team member + reviewing each piece of code before it is merged +* All project source code shall be stored in a version control system (e.g., Git), + and commits should follow a consistent naming convention. +* Coding standards and style guidelines shall be defined and followed consistently by all development team members.
+ +[Scroll back to Table of Contents](#table-of-contents) + +### Glossary + +| Term | Definition | +|----------------------|-----------------------------------------------------------------------------------------------------------| +| AddressBook-Level3 | A project created by the SE-EDU initiative, which this project is based on. | +| App | An application created by the project. | +| API | An application programming interface. | +| Architecture Diagram | A diagram that explains the high-level design of the App. | +| Class diagram | A diagram that shows the classes of the App and the relationships between them. | +| CLI | Command Line Interface, for users to perform actions through typing commands. | +| CommandBox | A component of the UI that allows users to enter commands. | +| Commons | A collection of classes used by multiple other components. | +| Component | A modular part of the App that serves a specific function. | +| ContactDisplay | A component of the UI that displays contact information. | +| GUI | Graphical User Interface, a visual way for users to interact with a software program | +| Logic component | The component responsible for executing user commands. | +| Main | A class with two subclasses, Main and MainApp, that initializes and shuts down the components of the App. | +| MainWindow | The main window of the UI that houses all the UI components. | +| Model | The data model used by the App. | +| Navigability | A concept referring to instances of one class holding a reference to instances of another class. | +| PlantUML | A tool used to create diagrams. | +| ResultDisplay | A component of the UI that displays the results of commands. | +| Sequence Diagram | A diagram that shows the sequence of events between components. | +| UML | [Unified Modeling Language](https://en.wikipedia.org/wiki/Unified_Modeling_Language) | + + +[Scroll back to Table of Contents](#table-of-contents) + +-------------------------------------------------------------------------------------------------------------------- +
+ +## **Appendix: Manual Testing** +Given below are some instructions to test the app manually. + +
:information_source: **Note:** These instructions are meant to provide a +starting point for testers to work with, testers should do more *exploratory* testing. +
+ +### Launching the app +#### Initial launch +1. Download the jar file and copy into an empty folder. +2. Double-click the jar file.
+ Expected: Shows the GUI with a message prompting user to unlock to continue. +3. Enter the command `unlock pw/Password123!` in the command box.
+ Expected: Shows the unlocked GUI. + +### Lock +1. Test case: `lock`
+ Expected: All the information in the GUI has been hidden. The execution of all commands except `unlock`, `help`, and + `delete` have also been disabled. + +### Unlock +1. Test case: `unlock pw/Password123!`
+ Expected: Shows the unlocked GUI. +2. Test case: `unlock pw/abc`
+ Expected: GUI remains locked. Error details shown in the status message. + +
+ + +### Change password +1. Test case: `change-password pw/Password123! npw/Password321!`
+ Expected: Password is changed successfully. Command success status message shown. +2. Test case: `change-password pw/Password123! npw/abc`
+ Expected: Password is not changed. Error details shown in the status message. + +### Adding +#### Adding projects +1. Test case: `add-project n/JuiceApp dr/App to allow for different juices to be ordered + dl/19-12-2023,Design backend,HIGH,0 dl/25-12-2023,Design frontend,MEDIUM,0`
+ Expected: New project with the name JuiceApp is created, provided there is no existing project with that name. + Command success status message shown. +2. Test case: `add-project n/JuiceApp dr/App to allow for different juices to be ordered + dl/invaliddeadline`
+ Expected: No project is added. Error details shown in the status message. + +#### Adding developers +1. Test case: `add-developer n/John Doe p/98765432 e/johnd@example.com a/311, Clementi Ave 2, #02-25 r/Developer + s/4500 d/11-11-2023 g/johng rt/3`
+ Expected: New developer with the name John Doe is created, provided there is no existing developer with that name. + Command success status message shown. +2. Test case: `add-developer n/John Does p/98765432 e/johnd@example.com a/311, Clementi Ave 2, #02-25 r/Developer + s/4500 d/11-11-2023 g/johng rt/6`
+ Expected: No developer is added. Error details shown in the status message. + +#### Adding clients +1. Prerequisites: Add a project with the name `AndroidApp` and another project with the name `CustomWebsite` before + testing. +2. Test case: `add-client n/Jack Doe p/98765432 e/jackd@example.com a/311, Clementi Ave 2, #02-25 r/Developer + pr/AndroidApp pr/CustomWebsite o/Google do/google.com`
+ Expected: New client with the name Jack Doe is created, provided there is no existing client with that name. + Command success status message shown. +3. Test case: `add-developer n/John Does p/98765432 e/johnd@example.com a/311, Clementi Ave 2, #02-25 r/Developer + pr/AndroidApp pr/CustomWebsite s/4500 d/11-11-2023 g/johng rt/6`
+ Expected: No developer is added. Error details shown in the status message. + +### Listing +1. Test case: `list-developer`
+ Expected: Lists all the developers. +2. Test case: `list-client`
+ Expected: Lists all the clients. +3. Test case: `list-project`
+ Expected: Lists all the projects. +4. Test case: `lists-project`
+ Expected: No change in GUI. Error details shown in the status message. + +### Deleting +#### Deleting projects +1. Prerequisites: List all projects using the `list-project` command. Ensure there is at least 1 project in the list. +2. Test case: `delete-project 1`
+ Expected: First project is deleted from the list. Command success status message shown. +3. Test case: `delete-project x` where `x` is an integer larger than the number of projects listed. + Expected: No change. Error details shown in the status message. + +#### Deleting developers +1. Prerequisites: List all developers using the `list-developer` command. Ensure there is at least 1 developer in the + list. +2. Test case: `delete-developer 1`
+ Expected: First developer is deleted from the list. Command success status message shown. +3. Test case: `delete-developer x` where `x` is an integer larger than the number of developers listed. + Expected: No change. Error details shown in the status message. + +#### Deleting clients +1. Prerequisites: List all clients using the `list-client` command. Ensure there is at least 1 client in the list. +2. Test case: `delete-client 1`
+ Expected: First client is deleted from the list. Command success status message shown. +3. Test case: `delete-client x` where `x` is an integer larger than the number of client listed. + Expected: No change. Error details shown in the status message. + +### Editing +#### Editing projects +1. Prerequisites: List all projects using the `list-project` command. Ensure there are at least 2 projects in the list. +2. Test case: `edit-project 1 dl/01-12-2023,Design backend,HIGH,0 dl/19-12-2023,Design frontend,HIGH,0`
+ Expected: First project in the list is successfully updated. Command success status message shown. +3. Test case: `edit-project 1 dr/update desc` + Expected: First project in the list is successfully updated. Command success status message shown. +4. Test case: `edit-project 2 dl/invaliddeadline` + Expected: Edit to the second project in the list is unsuccessful. Error details shown in the status message. + +#### Editing developers +1. Prerequisites: List all developers using the `list-developer` command. Ensure there are at least 2 clients in the + list. +2. Test case: `edit-developer 2 p/98989898`
+ Expected: Second developer in the list is successfully updated. Command success status message shown. +3. Test case: `edit-developer 1 s/-200`
+ Expected: Edit to the first developer in the list is unsuccessful. Error details shown in the status message. + +#### Editing clients +1. Prerequisites: List all clients using the `list-client` command. Ensure there is at least 1 client in the list. +2. Test case: `edit-client 1 p/98989898`
+ Expected: First client in the list is successfully updated. Command success status message shown. +3. Test case: `edit-developer 1 p/10`
+ Expected: No edit is made. Error details shown in the status message. + +### Importing information +#### Importing developers +1. Prerequisites: Create a CSV file populated with developer details in the correct format. Add the CSV file to the same + folder as JAR file of this app. +2. Test case: `import-developer developers.csv`
+ Expected: All developers with their details specified in the CSV are added, assuming the data in the file is in the + correct format. Command success status message shown. +3. Test case: `import-developer`
+ Expected: No developer is added. Error details shown in the status message. + +#### Importing clients +1. Prerequisites: Create a CSV file populated with client details in the correct format. Add the CSV file to the same + folder as JAR file of this app. +2. Test case: `import-client clients.csv`
+ Expected: All clients with their details specified in the CSV are added, assuming the data in the file is in the + correct format. Command success status message shown. +3. Test case: `import-client`
+ Expected: No client is added. Error details shown in the status message. + +### Undoing commands +For these tests, each test case has respective prerequisites that must be met before executing the test. +1. Prerequisites: Relaunch the app and unlock it with your password. Do NOT execute any other command after `unlock`. + 2. Test case: `undo`
+ Expected: Nothing is undone since no command has been executed yet. Error details shown in the status message. + +1. Prerequisites: Execute either an edit or delete command after unlocking the app. + 2. Test case: `undo`
+ Expected: The most recent command executed is undone. Command success status message shown. + +### Redoing commands +For these tests, each test case has respective prerequisites that must be met before executing the test. +1. Prerequisites: Relaunch the app and unlock it with your password. Do NOT execute any other command after `unlock`. +2. Test case: `redo`
+ Expected: Nothing is redone since no command has been executed yet. Error details shown in the status message. + +1. Prerequisites: Execute either an edit or delete command after unlocking the app, then execute the `undo` command. +2. Test case: `redo`
+ Expected: The changes from the recent `undo` command executed are reverted. Command success status message shown. + +### Adding roles +#### Adding Developer Roles +1. Test case: `add-developer-role UIDesigner`
+Expected: UIDesigner added as a role. Command status success message shown. +2. Test case: `add-developer-role Developer`
+Expected: No role added. Error details shows role cannot be added as it exists. +3. **Test Case 1 must be completed** `add-developer-role UIDesigner`
+Expected: No role added. Error details shows role cannot be added as it exists. + +#### Adding Client Roles +1. Test case: `add-client-role Tester`
+ Expected: Tester added as a role. Command status success message shown. +2. Test case: `add-client-role HR`
+ Expected: No role added. Error details shows role cannot be added as it exists. +3. **Test Case 1 must be completed** `add-developer-role Tester`
+ Expected: No role added. Error details shows role cannot be added as it exists. + +
+ +### Delete roles +#### Deleting Developer Roles +1. Prerequisite: role `UIDesigner` has been added in already (i.e. add in this role if it is after any of the test case) +2. Test case: `delete-developer-role UIDesigner`
+ Expected: UIDesigner deleted as a role. Command status success message shown. +3. Test case: assign the `UIDesigner` role to any developer, then execute `delete-developer-role UIDesigner`
+ Expected: No role deleted. Error details shows role cannot be deleted as there are developers using it. +4. Test case: `delete-developer-role Developer`
+ Expected: No role deleted. Error details shows role cannot be deleted as this is a pre-declared role. +5. Test case: **Test Case 1 must be completed** then execute `delete-developer-role UIDesigner`
+ Expected: No role deleted. Error details shows role cannot be deleted as it doesn't exist. + +#### Deleting Client Roles +1. Prerequisite: role `Tester` has been added in already (i.e. add in this role if it is after any of the test case) +2. Test case: `delete-client-role Tester`
+ Expected: Tester deleted as a role. Command status success message shown. +3. Test case: assign the `Tester` role to any client, then execute `delete-client-role Tester`
+ Expected: No role deleted. Error details shows role cannot be deleted as there are clients using it. +4. Test case: `delete-client-role HR`
+ Expected: No role deleted. Error details shows role cannot be deleted as this is a pre-declared role. +5. Test case: **Test Case 1 must be completed** then execute `delete-developer-role Tester`
+ Expected: No role deleted. Error details shows role cannot be deleted as it doesn't exist. + +
+ +### Finding +#### Finding projects +1. Prerequisites: List all projects using the `list-project` command. Multiple projects in the list. +2. Test case: `find-project pr/Laundry App`
+ Expected: Projects with the name, Laundry App, are shown on the list. Command success status message shown. +3. Test case: `find-developer Laundry App`
+ Expected: No search result due to error in format. No prefix provided before project name. Error details shown in the status message. + +#### Finding developers +1. Prerequisites: List all developers using the `list-developer` command. Multiple developers in the list. +2. Test case: `find-developer n/Alice`
+ Expected: Developers with the name, Alice, are shown on the list. Command success status message shown. +3. Test case: `find-developer Alice`
+ Expected: No search result due to error in format. No prefix provided before name. Error details shown in the status message. + +#### Finding clients +1. Prerequisites: List all clients using the `list-client` command. Multiple clients in the list. +2. Test case: `find-client o/Google`
+ Expected: Clients from the organisation, Google, are shown on the list. Command success status message shown. +3. Test case: `find-client Google`
+ Expected: No search result due to error in format. No prefix provided before organisation. Error details shown in the status message. + +
+ +### Marking project deadlines +#### Mark deadline as done +1. Prerequisites: List all projects using the `list-project` command. Multiple projects in the list. +2. Test case: `mark-deadline 1 2`
+ Expected: The second deadline of the first project in the currently displayed project list is marked as done. Command success status message shown. +3. Test case: `mark-deadline 1 x` where `x` is an integer larger than the number of deadlines for the project specified.
+ Expected: No change. Error details shown in the status message. + +#### Mark deadline as undone +1. Prerequisites: List all projects using the `list-project` command. Multiple projects in the list. +2. Test case: `unmark-deadline 1 2`
+ Expected: The second deadline of the first project in the currently displayed project list is marked as undone. Command success status message shown. +3. Test case: `unmark-deadline 1 x` where `x` is an integer larger than the number of deadlines for the project specified. + Expected: No change. Error details shown in the status message. + +### Exiting the app +1. After executing some commands, use the `exit` command to exit the app. +2. You can re-launch the app by double-clicking the jar file.
+ Expected: The application should load with any previous changes made during the previous running of the app. + +[Scroll back to Table of Contents](#table-of-contents) + +-------------------------------------------------------------------------------------------------------------------- +
+ +## **Appendix: Planned Enhancements** + +### Validation checks for duplicate fields in edit commands +**Current Behavior:** Two developers can have the same details (eg. address, phone number, email) as long as their name +is not the same. This program behaviour also exists for clients. +
+**Enhanced Behavior:** Validation checks should be conducted for email, contact number and address when adding or +editing developers or clients to make sure that no two developers or clients have repeated details since this is +unrealistic. + +### Validation checks for unedited fields in edit commands +**Current Behavior:** A developer, client, or project can be edited to have the exact same details as it currently has. +
+**Enhanced Behavior:** Validation checks should be conducted which notifies the user when they try to edit an existing +developer, client, or project, to have the exact same details as it currently has. This makes it more user-friendly as +if such an occurrence happens, it is likely that it was a mistake or typo in the command. + +### Case-sensitive validation checks for adding roles +**Current Behavior:** Two similar roles with different cases can both be added. `Developer` and `developer` can exist at the +same time.
+**Enhanced Behavior:** Two same words that are only differentiated by case should not be able to be added in as a role. + +### Deadlines cannot be any date +**Current Behavior:** Deadlines for projects can be any date such at year 0001.
+**Enhanced Behavior:** There should be a limit to the deadline line dates like 10-20 years before and after the current date. + +### Password Recovery +**Current Behavior:** If you forgot your password, there is no way to retrieve it
+**Enhanced Behavior:** Links the system to email or have verifications that allows users to reset their password. + +### Additional menu item for `import-developer` and `import-client` +**Current Behavior:** The import function is only available in CLI.
+**Enhanced Behavior:** A new menu item will be added under File called `Import developers` and `Import clients` +Clicking it will lead to a window to select the location of the respective file in csv format. +The backend implementation of logic follows the CLI implementation by creating a `ImportDeveloperCommand` or `ImportClientCommand` + +### Exporting data +**Current Behavior:** There is only import csv function and no options for export
+**Enhanced Behavior:** A new menu item will be added under File called `Export data` and clicking it will lead to a window +where users can select the location to save the files for Developers, Clients and Projects. The file will be saved in csv format. + +### Find Autocomplete +**Current Behavior:** While searching, users are not prompted for autocomplete suggestions. +**Enhanced Behavior:** Implement autocomplete suggestions as users type their search queries. This can help users avoid +typos and provide quick access to commonly used search terms. + +### Sort Results +**Current Behavior:** After each command, the list of contacts shown are based on the order the contact was added.
+**Enhanced Behavior:** Allow users to sort the search results based on different criteria such as name, date, or +priority. This provides users with more flexibility in organizing and viewing the search results. + +[Scroll back to Table of Contents](#table-of-contents) + +-------------------------------------------------------------------------------------------------------------------- - 1. Prerequisites: List all persons using the `list` command. Multiple persons in the list. +## **Appendix: Effort** - 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. +| Feature | AB3 | CodeContact | +|:---------------|:-----:|:-------------:| +| Effort | 10 | 30 | +| Lines of Code | 6k | +18k | - 1. Test case: `delete 0`
- Expected: No person is deleted. Error details shown in the status message. Status bar remains the same. +The CodeContact project involved a significant effort to adapt the AddressBook-Level3 (AB3) application to a new domain of Developers, Clients and Projects in a Software Engineering setting. One of the major changes was the addition of three new models, Developer, Client and Project, which required modifying the existing data model and the associated logic components. Another significant change was the complete redesign of the user interface using JavaFX, which required a significant amount of time and effort to learn and implement. - 1. Other incorrect delete commands to try: `delete`, `delete x`, `...` (where x is larger than the list size)
- Expected: Similar to previous. +Firstly, although code was reused from AB3, a lot of it had to be refactored to fit our use case. We now had to deal with 3 entity types, Developer, Client and Project, which meant that the way we stored the information also had to change. Moreover, with more attributes for both `Developer` and `Client` classes, it also meant more effort in validating the inputs and writing high quality test cases for each attribute, an extremely time-consuming process. -1. _{ more test cases …​ }_ +The most significant challenge was implementing the logic for tabs within the user interface. Not only was it challenging to implement the tabs at the UI level, the integration of switching tabs based on the command executed was challenging as well. Careful and deliberate decisions were made for the action to take when switching tabs. For example, when switching to the `Developer` tab, the `list-developer` command is executed to show the list of developers. -### Saving data +Other challenges include the implementations of features that seem trivial on the surface level. Something that started as a validation check for valid developer and client roles, evolved to 4 commands allowing users to add and delete roles, and these roles had to be stored in separate files. This feature also affected the undo and redo commands, which had to explicitly check for commands involving roles as they are stored outside of `addressbook.json`. -1. Dealing with missing/corrupted data files +Due to limitations in the storage format in `addressbook.json`, we had to get creative in the way we store the associations between Developers, Clients and the projects they have been assigned to. We decided to store the associations in the `Developer` and `Client` classes themselves, which meant that we had to update the logic components to update the associations when a project is deleted. This was a significant change from AB3, which only had to deal with a single entity type. Other constraints had to be included such as not allowing names of Projects to be edited, and not allowing Developers and Clients to be assigned to Projects that do not exist. - 1. _{explain how to simulate a missing/corrupted file, and the expected behavior}_ +The effort required to implement the features in CodeContact was significantly more than AB3, which is reflected in the number of lines of code. However, the effort was well worth it as we were able to create a useful application that can be used by project managers to manage their projects and teams. -1. _{ more test cases …​ }_ +[Scroll back to Table of Contents](#table-of-contents) diff --git a/docs/Documentation.md b/docs/Documentation.md index 3e68ea364e7..0e33867d565 100644 --- a/docs/Documentation.md +++ b/docs/Documentation.md @@ -18,7 +18,7 @@ title: Documentation guide * Follow the [**_Google developer documentation style guide_**](https://developers.google.com/style). -* Also relevant is the [_[se-edu/guides] **Markdown coding standard**_](https://se-education.org/guides/conventions/markdown.html) +* Also, relevant is the [_[se-edu/guides] **Markdown coding standard**_](https://se-education.org/guides/conventions/markdown.html) **Diagrams:** diff --git a/docs/Logging.md b/docs/Logging.md index 5e4fb9bc217..26cbbb7d981 100644 --- a/docs/Logging.md +++ b/docs/Logging.md @@ -5,7 +5,10 @@ title: Logging guide * We are using `java.util.logging` package for logging. * The `LogsCenter` class is used to manage the logging levels and logging destinations. -* 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 the console and to a `.log` file. -* The output logging level can be controlled using the `logLevel` setting in the configuration file (See the [Configuration guide](Configuration.md) section). -* **When choosing a level for a log message**, follow the conventions given in [_[se-edu/guides] Java: Logging conventions_](https://se-education.org/guides/conventions/java/logging.html). +* 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 the console and to a `.log` file. +* The output logging level can be controlled using the `logLevel` setting in the configuration file (See + the [Configuration guide](Configuration.md) section). +* **When choosing a level for a log message**, follow the conventions given in [_[se-edu/guides] Java: Logging + conventions_](https://se-education.org/guides/conventions/java/logging.html). diff --git a/docs/SettingUp.md b/docs/SettingUp.md index 275445bd551..2e8025d784a 100644 --- a/docs/SettingUp.md +++ b/docs/SettingUp.md @@ -4,8 +4,7 @@ title: Setting up and getting started --- * Table of Contents -{:toc} - + {:toc} -------------------------------------------------------------------------------------------------------------------- @@ -19,12 +18,17 @@ Follow the steps in the following guide precisely. Things will not work out if y First, **fork** this repo, and **clone** the fork into your computer. If you plan to use Intellij IDEA (highly recommended): -1. **Configure the JDK**: Follow the guide [_[se-edu/guides] IDEA: Configuring the JDK_](https://se-education.org/guides/tutorials/intellijJdk.html) to to ensure Intellij is configured to use **JDK 11**. -1. **Import the project as a Gradle project**: Follow the guide [_[se-edu/guides] IDEA: Importing a Gradle project_](https://se-education.org/guides/tutorials/intellijImportGradleProject.html) to import the project into IDEA.
- :exclamation: Note: Importing a Gradle project is slightly different from importing a normal Java project. + +1. **Configure the JDK**: Follow the guide [_[se-edu/guides] IDEA: Configuring the + JDK_](https://se-education.org/guides/tutorials/intellijJdk.html) to to ensure Intellij is configured to use **JDK 11 + **. +1. **Import the project as a Gradle project**: Follow the guide [_[se-edu/guides] IDEA: Importing a Gradle + project_](https://se-education.org/guides/tutorials/intellijImportGradleProject.html) to import the project into + IDEA.
+ :exclamation: Note: Importing a Gradle project is slightly different from importing a normal Java project. 1. **Verify the setup**: - 1. Run the `seedu.address.Main` and try a few commands. - 1. [Run the tests](Testing.md) to ensure they all pass. + 1. Run the `seedu.address.Main` and try a few commands. + 1. [Run the tests](Testing.md) to ensure they all pass. -------------------------------------------------------------------------------------------------------------------- @@ -32,24 +36,31 @@ If you plan to use Intellij IDEA (highly recommended): 1. **Configure the coding style** - If using IDEA, follow the guide [_[se-edu/guides] IDEA: Configuring the code style_](https://se-education.org/guides/tutorials/intellijCodeStyle.html) to set up IDEA's coding style to match ours. + If using IDEA, follow the guide [_[se-edu/guides] IDEA: Configuring the code + style_](https://se-education.org/guides/tutorials/intellijCodeStyle.html) to set up IDEA's coding style to match + ours.
:bulb: **Tip:** - Optionally, you can follow the guide [_[se-edu/guides] Using Checkstyle_](https://se-education.org/guides/tutorials/checkstyle.html) to find how to use the CheckStyle within IDEA e.g., to report problems _as_ you write code. + Optionally, you can follow the guide [_[se-edu/guides] Using + Checkstyle_](https://se-education.org/guides/tutorials/checkstyle.html) to find how to use the CheckStyle within IDEA + e.g., to report problems _as_ you write code.
1. **Set up CI** - This project comes with a GitHub Actions config files (in `.github/workflows` folder). When GitHub detects those files, it will run the CI for your project automatically at each push to the `master` branch or to any PR. No set up required. + This project comes with a GitHub Actions config files (in `.github/workflows` folder). When GitHub detects those + files, it will run the CI for your project automatically at each push to the `master` branch or to any PR. No set up + required. 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). + 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) - * [Adding a new command](tutorials/AddRemark.md) - * [Removing fields](tutorials/RemovingFields.md) + * [Tracing code](tutorials/TracingCode.md) + * [Adding a new command](tutorials/AddRemark.md) + * [Removing fields](tutorials/RemovingFields.md) diff --git a/docs/Testing.md b/docs/Testing.md index 8a99e82438a..96e849b4b4d 100644 --- a/docs/Testing.md +++ b/docs/Testing.md @@ -4,7 +4,7 @@ title: Testing guide --- * Table of Contents -{:toc} + {:toc} -------------------------------------------------------------------------------------------------------------------- @@ -13,11 +13,11 @@ title: Testing guide There are two ways to run tests. * **Method 1: Using IntelliJ JUnit test runner** - * To run all tests, right-click on the `src/test/java` folder and choose `Run 'All Tests'` - * To run a subset of tests, you can right-click on a test package, - test class, or a test and choose `Run 'ABC'` + * To run all tests, right-click on the `src/test/java` folder and choose `Run 'All Tests'` + * To run a subset of tests, you can right-click on a test package, + test class, or a test and choose `Run 'ABC'` * **Method 2: Using Gradle** - * Open a console and run the command `gradlew clean test` (Mac/Linux: `./gradlew clean test`) + * Open a console and run the command `gradlew clean test` (Mac/Linux: `./gradlew clean test`)
:link: **Link**: Read [this Gradle Tutorial from the se-edu/guides](https://se-education.org/guides/tutorials/gradle.html) to learn more about using Gradle.
@@ -30,7 +30,9 @@ This project has three types of tests: 1. *Unit tests* targeting the lowest level methods/classes.
e.g. `seedu.address.commons.StringUtilTest` -1. *Integration tests* that are checking the integration of multiple code units (those code units are assumed to be working).
+1. *Integration tests* that are checking the integration of multiple code units (those code units are assumed to be + working).
e.g. `seedu.address.storage.StorageManagerTest` -1. Hybrids of unit and integration tests. These test are checking multiple code units as well as how the are connected together.
+1. Hybrids of unit and integration tests. These test are checking multiple code units as well as how the are connected + together.
e.g. `seedu.address.logic.LogicManagerTest` diff --git a/docs/UserGuide.md b/docs/UserGuide.md index 57437026c7b..0741c92bd34 100644 --- a/docs/UserGuide.md +++ b/docs/UserGuide.md @@ -1,197 +1,1459 @@ --- layout: page -title: User Guide +title: CodeContact User Guide --- +------------------------------------------------------------------------------------- +## Welcome to CodeContact +{: .no_toc} -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. +***Taking charge of your PROject Management!*** + +Seamlessly integrate contact, client, and project management, simplifying access to coding-related contacts, +facilitating collaboration, and offering command-line efficiency for project managers. + +CodeContact 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, CodeContact can get your contact +management tasks done faster than traditional GUI apps. **CodeContact** aims to put the **P.R.O** in project management by enabling project managers to be: + > **1. Productive** + > **2. Reliable** + > **3. Organised** + +Here’s an overview of how CodeContact can help you streamline your project management processes. +* Store and edit information about developers and clients related to projects. +* Store and manage projects and their deadlines. +* Assign projects to developers and clients. + +If you are familiar with CodeContact, jump to our [**Table of Contents**](#table-of-contents) to find out what you are looking +for! + +If you are new here, you may start with learning [**How to navigate this guide**](#navigating-this-guide) and visit +the [**Quick Start**](#quick-start) guide to onboard onto CodeContact smoothly! + +------------------------------------------------------------------------------------- +
+ +------------------------------------------------------------------------------------- +### How can this guide help me? +{: .no_toc} + +If you are a new user, we hope to first inform you on how you can [get started](#quick-start) using CodeContact. + +As you use CodeContact, you may also have questions on how to perform certain actions within the +application. This guide thus contains a comprehensive list of [Features](#features) offered with CodeContact, as well as +explanations on when and how to use them. + +Further questions are also answered within a [FAQ](#faq) section below. + +Confused about the terms or formatting used in this guide? Learn how to **navigate this guide** [here](#navigating-this-guide). + +Confused about the visual display of CodeContact? Learn how to **navigate the user interface** of CodeContact +[here](#navigating-the-graphical-user-interface-gui). + +------------------------------------------------------------------------------------- + +
+ +## Table of Contents +{: .no_toc} * Table of Contents {:toc} + +
+ +# **Navigating this guide** + + +## ***Glossary*** +------------------------------------------------------------------------------------- +### **Definitions** + +| Term | Definition | +|---------------|----------------------------------------------------------------------------------------------------------------------------| +| Parameter | Parameters are specific details you would include about the developer/client/project.(eg. name, date joined, description). | +| Command | An input from the user that tells CodeContact to perform an action (i.e. add a client). | +| GUI | Graphical User Interface (GUI) represents the visual display of CodeContact that users can see. | +| GUI Component | A subsection of the Graphical User Interface. For more information on specific GUI components, refer to [this section](). | +| CLI | Command Line Interface (CLI) represents a text-based user interface to interact with the application. | +| Character | Any letter or symbol that is recognized by the computer, and can form a line of text (eg. `a` , `+` , `$` ). | +| JSON | [Javascript Object Notation](https://en.wikipedia.org/wiki/JSON) | +| JAR file | [Java Archive File](https://en.wikipedia.org/wiki/JAR_(file_format)) | +| CSV file | [Comma-separated Values File](https://en.wikipedia.org/wiki/Comma-separated_values) | + +[Scroll back to Table of Contents](#table-of-contents) + +------------------------------------------------------------------------------------- +### **Parameter Information** + +Within the tables below, you can find out more about the parameters that CodeContact supports. These parameters come in +handy when crafting commands in CodeContact. + +Here are some notes about these parameters. + +* Each parameter comes with **constraints**. These constraints detail the specific formats of text that + each parameter accepts as valid user input. + * Not following these constraints will **result in an error** when entering the command. + * Nonetheless, CodeContact will not stop working. Rather, a message will be provided to you on + how to correct your command. + +#### Common Parameters for Developers and Clients + +| Parameter | Description | Constraints | Valid Examples | Invalid Examples | +|-----------|----------------------------------|-------------------------------------------------------------------------------------------------------------------------------------|-----------------------------|---------------------------------------------| +| `n/` | name of developer/client | alphanumeric characters and spaces, and it should not be blank | Tom Hanks, Elizabeth 2 | 成龍, 潔 いさぎ 世 よ 一 いち, Ganesh s/o Ravichandran | +| `p/` | phone number of developer/client | 8 numeric characters, and it should not be blank | 94566835 | 123, 432, 5678@ | +| `e/` | email of developer/client | alphanumeric characters, contains an @ and it should not be blank | amy@gmail.com | amy!gmail.com, amy$gmail | +| `a/` | address of developer/client | alphanumeric characters and spaces, and it should not be blank | 311, Clementi Ave 2, #02-25 | 成龍, 潔 いさぎ 世 よ 一 いち | +| `r/` | role of developer/client | alphabetical characters and spaces, and it should not be blank | Developer | 成龍, 潔 いさぎ 世 よ 一 いち | +| `pr/` | project name | alphanumeric characters and spaces, and it should not be blank, used in the context or Developer/Client when assigned to project(s) | CS2103T | 成龍, 潔 いさぎ 世 よ 一 いち | + +#### Developer Specific Parameters + +| Parameter | Description | Constraints | Valid Examples | Invalid Examples | +|----------|------------------------------|--------------------------------------------------------------------------------------------------|----------------|----------------------| +| `g/` | github username of developer | alphanumeric characters, follows github username convention and it should not be blank | johng, amy123 | g, y | +| `d/` | date joined of developer | numeric characters in dd-MM-yyyy format, should not be a future date, and it should not be blank | 11-11-2023 | 19-11-2024, 1/1/2023 | +| `s/` | salary of developer | positive integers of at least 4 digits, should not be blank | 5000 | 5000.0, 4321.32.22 | +| `rt/` | rating of developer | numeric characters between 0 to 5 | 5, 3.5 | -0, -1, 6 | + +
+ +#### Client Specific Parameters + +| Parameter | Description | Constraints | Valid Examples | Invalid Examples | +|-----------|-----------------------------|---------------------------------------------------------------------------------------------------|----------------|------------------| +| `o/` | organisation name of client | alphanumeric characters and spaces, and it should not be blank | Google | 谷歌 | +| `do/` | document link of client | alphanumeric characters and spaces, and it should not be blank, follows standard hyperlink format | google.com | 谷歌.com | + +#### Project Parameters + +| Parameter | Description | Constraints | Valid Examples | Invalid Examples | +|-----------|------------------------|----------------------------------------------------------------|-------------------------------------------------|------------------------------------| +| `n/` | name of project | alphanumeric characters and spaces, and it should not be blank | CodeContact, oribtal23, Team Trekker | 成龍, 潔 いさぎ 世 よ 一 いち | +| `dr/` | description of project | alphanumeric characters and spaces, and it should not be blank | App to allow for different juices to be ordered | 成龍, 潔 いさぎ 世 よ 一 いち | +| `dl/` | deadline of project | alphanumeric characters and spaces, and it should not be blank | 19-12-2023, Design backend, HIGH, 0 | 19.1.2023, 潔 いさぎ 世 よ 一 いち, NONE, 5 | +| `pri/` | priority of deadline | HIGH, MEDIUM, LOW | HIGH, MEDIUM, LOW | 5, 3.5, -1, high, M | + + +### **Format** +------------------------------------------------------------------------------------- +#### General Formatting +Here are the explanations behind the formatting we use through this guide.
+ +* Words in `grey blocks (like these)` usually represent any of the following: + 1. Text used in commands, such as `add-developer`. + 2. Keys on your keyboard like `Enter`. + 3. File names such as `CodeContact.jar`. +
+
+:bulb: This is a blue box. It can be used for additional tips or more useful information. +
+ +
+:exclamation: This is a warning box. It can be used to give more details on the warnings and limitations of features. +
+ +#### Command Formatting + +| Format | Explanation | Example | +|----------------------------------------------|--------------------------------------------------------------------|------------------------------------| +| words in `UPPER_CASE` | **compulsory** parameter values that are supplied by the user | `add-developer n/NAME p/PHONE` | +| words in `[UPPER_CASE]` with square brackets | **optional** parameter values that are supplied by the user | `add-developer n/NAME p/PHONE [d/DATE_JOINED]` | +| Items with `…` after them | parameters that can be used **multiple times** | `add-developer [pr/PROJECT]...` | + + +[Scroll back to Table of Contents](#table-of-contents) + +----------------------------------------------------------------------------------------------- +
+ +## ***Navigating the Graphical User Interface (GUI)*** +CodeContact comes with a GUI to allow for nice visual feedback for our users. Here is a quick run-through +of the different sections of our GUI, as well as some notes regarding the use of the GUI. +### Quick Orientation +![image](images/UG%20UI%202.png) +

+![image](images/UG%20UI%201.png) + +**Here is a quick summary of each GUI component within CodeContact:** + +| Name | Description | +|----------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| Menu Bar | Contains dropdown menu options for the CodeContact application. | +| Command Box | Allows users to enter CodeContact commands. | +| Result Display | 1. Provides CLI-based feedback upon a user command.
2. Allows users to see if their command was successful or not.
3. Provides error messages to guide users on how to use CodeContact commands. | +| Tabs | Allows users to see different lists of information on clicking on the relevant tabs. The tabs will also automatically switch to the correct tab corresponding to the command executed. | +| Developers/Clients/Projects card | Displays a list of Developer, Client or Project Cards. This list can be manipulated through commands like `find` and `list`. | +| Deadlines progress | Displays the progress of deadlines for a certain project. | +| Deadline list with details | Displays the details of the deadline in a table form. | +| Re-sort | Click on the respective words, there will be a small black arrow that appears. You can press to resort the data eg. show date from furthest to nearest day. | + +### Notes on GUI +
+If you executed a `find` command in developer tab and switched to the client tab, when you return to the developer tab the find results will be cleared and +the whole list of developers will be shown again. This is the intended behavior of CodeContact as we hope that this can save you the time +from always needing to call the list command. +
+ -------------------------------------------------------------------------------------------------------------------- +
-## Quick start +## ***Quick start*** 1. Ensure you have Java `11` or above installed in your Computer. -1. Download the latest `addressbook.jar` from [here](https://github.com/se-edu/addressbook-level3/releases). -1. Copy the file to the folder you want to use as the _home folder_ for your AddressBook. +2. Download the latest `CodeContact.jar`. + + +3. Copy the file to the folder you want to use as the _home folder_ for your CodeContact. + + +4. Open a command terminal, `cd` into the folder you put the jar file in, and use the `java -jar CodeContact.jar` + command to run the application.
+ -1. Open a command terminal, `cd` into the folder you put the jar file in, and use the `java -jar addressbook.jar` command to run the application.
- A GUI similar to the below should appear in a few seconds. Note how the app contains some sample data.
+5. A GUI similar to the below should appear in a few seconds. Note how the app might contain some sample data.
![Ui](images/Ui.png) -1. Type the command in the command box and press Enter to execute it. e.g. typing **`help`** and pressing Enter will open the help window.
- Some example commands you can try: - * `list` : Lists all contacts. +6. For new users, learn to use CodeContact through our [Tutorial](#codecontact-tutorial--for-new-users-). + + +7. Refer to the [Features](#features) below for details of each command. + + +[Scroll back to Table of Contents](#table-of-contents) + +-------------------------------------------------------------------------------------------------------------------- - * `add n/John Doe p/98765432 e/johnd@example.com a/John street, block 123, #01-01` : Adds a contact named `John Doe` to the Address Book. - * `delete 3` : Deletes the 3rd contact shown in the current list. - * `clear` : Deletes all contacts. +## ***CodeContact Tutorial (for new users)*** - * `exit` : Exits the app. +This is a tutorial for **first-time** CodeContact users. -1. Refer to the [Features](#features) below for details of each command. +1. Launch CodeContact. You may refer to the instructions [here](#quick-start). + * On launch, CodeContact will not contain any developer or client records. +
+ + +2. You will be asked to **enter a password** to unlock CodeContact. + * Enter the command `unlock pw/Password123!` in the command box. +
+ + +3. You can **change the password** to unlock CodeContact. + * Enter the command `change-password pw/Password123! npw/` in the command box. + +
:bulb: +You can always lock and unlock CodeContact using the `lock` and `unlock` commands. +
+ +1. Let us try **adding a project** to our CodeContact. + * Enter the + command `add-project n/AndroidApp dr/App to allow for different juices to be ordered dl/19-12-2023,Design backend,HIGH,0 dl/25-12-2023,Design frontend,MEDIUM,0 `. +
+ + +2. We can then **add a developer** to our CodeContact. + * Enter the + command `add-developer n/John Doe p/98765432 e/johnd@example.com a/311, Clementi Ave 2, #02-25 r/Developer pr/AndroidApp s/4500 d/01-11-2023 g/johng rt/3`. + * Try adding more developers with different details for each parameter! + * Remember to add a new [project](#add-project--add-project) or [role](#add-roles) if you wish to add new developers with other roles and + project. +
+ + +3. We can also **add a client** to our CodeContact. + * Enter the + command `add-client n/Amy p/88765423 e/amy@example.com a/31, Clementi Ave 6, #03-12 r/Client pr/AndroidApp o/Google do/google.com`. + * Try adding more clients with different details for each parameter! + * Remember to add a new [project](#add-project--add-project) or [role](#add-roles) if you which to add new client with other roles and project. +
+ + +4. Let us try **editing the name** of a developer stored in CodeContact. + * Enter the command `edit-developer 1 n/Jhonny`. + * Try editing other parameters or developers and projects too! + * More details of what you can edit can be found [here](#edit). +
+ + +5. We can also easily **find** for information in CodeContact. + * Enter the command `find-developer n/John s/4500`. + * Try looking for other information and search with multiple parameters! + * More details of what you can find can be found [here](#find). +
+ + +6. You can always **show the full list** after finding in CodeContact. + * Enter the command `list-developer`, `list-client` or `list-project`. +
+ + +7. You can **delete developers, clients or projects** in CodeContact. + * Enter the command `delete-developer 3` to delete the 3rd developer in CodeContact. + * More details on how delete works can be found [here](#delete-developer--delete-developer). +
+ + +8. If you realise that you did not delete this developer, you can **undo** this action in CodeContact. + * Enter the command `undo` and it will revert your previous actions. +
+ + +9. If you realise you actually want it deleted, you can **redo** this action in CodeContact. + * Enter the command `redo` and it will redo your previous actions. +
+ + +Congratulations! You are now ready to use CodeContact! + +To view all our features, you may visit our [Features](#features) section. + +[Scroll back to Table of Contents](#table-of-contents) -------------------------------------------------------------------------------------------------------------------- +
-## Features +# **Features** + + +| [Security Features](#security-features) | Locking Access to features | Unlocking Access to features | Changing personal password | +|:--------------------------------------------|:--------------------------:|:----------------------------:|:-----------------------------------------------------:| +| [Password Protection](#password-protection) | [`lock`](#lock-lock) | [`unlock`](#unlock-unlock) | [`change-password`](#change-password-change-password) | + +| [Management Features](#management-features) | Developer | Client | Project | +|:----------------------------------------------------|:-------------------------------------------------------------------------:|:----------------------------------------------------------------:|:------------------------------------------------------------------------------------------------------------------------:| +| [Adding new information](#add) | [`add-developer`](#add-developer--add-developer) | [`add-client`](#add-client--add-client) | [`add-project`](#add-project--add-project) | +| [Deleting information](#delete) | [`delete-developer`](#delete-developer--delete-developer) | [`delete-client`](#delete-client--delete-client) | [`delete-project`](#delete-project--delete-project) | +| [Editing information](#edit) | [`edit-developer`](#edit-developer-details--edit-developer) | [`edit-client`](#edit-client-details--edit-client) | [`edit-project`](#edit-project-details--edit-project) | +| [Importing information](#import-information) | [`import-developer` ](#import-developers-import-developer) | [`import-client`](#import-clients-import-client) | - | +| [Finding information](#find) | [`find-developer`](#find-developer-details) | [`find-client`](#find-client-details) | [`find-project`](#find-project-details), [`find-deadline`](#find-deadlines-find-deadline) | +| [Listing information](#listing-information--list) | `list-developer` | `list-client` | `list-project` | +| [Adding new role](#add-roles) | [`add-developer-role`](#add-developer-roles--add-developer-role) | [`add-client-role`](#add-client-roles--add-client-role) | - | +| [Deleting role](#delete-roles) | [`delete-developer-role`](#delete-developer-roles--delete-developer-role) | [`delete-client-role`](#delete-client-roles--delete-client-role) | - | +| [Marking Project Deadlies](#mark-project-deadlines) | - | - | [`mark-deadline`](#mark-deadline-as-done--mark-deadline), [`unmark-deadline`](#mark-deadline-as-undone--unmark-deadline) | + +| [Additional Features](#additional-features) | Finding Help | Clearing data | Exiting program | +|:--------------------------------------------------|:---------------------------------------:|:-----------------------------------------------------:|:-------------------------------------------------:| +| [Miscellaneous Features](#miscellaneous-features) | [View Help `help`](#viewing-help--help) | [Clear entries `clear`](#clearing-all-entries--clear) | [Exit program `exit`](#exiting-the-program--exit) | + +-------------------------------------------------------------------------------------------------------------------- +
+ +## ***Security Features*** + +-------------------------------------------------------------------------------------------------------------------- +### **Password Protection** + +> **Grant yourself exclusive access and deny access by others to CodeContact with a personal and +> customizable password.** + + +#### Lock : `lock` + +> Locks the system and hides all the information, denying access to all information and commands +> except `unlock`, `help`, and `delete`. + +**Format:** `lock` + +**Example of usage:** `lock` + +When command succeeds, CLI shows: + +``` +Locked all data +``` + +[Scroll back to Table of Contents](#table-of-contents) + +-------------------------------------------------------------------------------------------------------------------- +#### Unlock : `unlock` + +> Unlocks the system by granting access to all information and commands. + +**Format:** `unlock pw/CURRENT_PASSWORD` + +**Constraints:** +1. Only unlocks if password exactly matches the current password (which is the last set password). + * Note: The default password is `Password123!`. It is highly recommended to change to a different password with the [`change-password` command](#change-password--change-password). + +**Example of usage:** `unlock pw/Password123!` + +When command succeeds, CLI shows: +``` +Unlocked all data +``` + +[Scroll back to Table of Contents](#table-of-contents) + +-------------------------------------------------------------------------------------------------------------------- +#### Change password : `change-password` + +> Changes the current password so that the password is kept personal. + +**Format:** `change-password pw/CURRENT_PASSWORD npw/NEW_PASSWORD` + +**Constraints:** +1. `CURRENT_PASSWORD` entered must exactly match the current password. + * Note: The default password is `Password123!`. + +2. `NEW_PASSWORD` must be at least 8 characters long and contain at least one digit, one lowercase letter, + one uppercase letter, and one special character. + +**Example of usage:** `change-password pw/Password123! npw/NewPass987!` + +When command succeeds, CLI shows: +``` +Password changed successfully. +``` + +[Scroll back to Table of Contents](#table-of-contents) + +-------------------------------------------------------------------------------------------------------------------- +
+ +## ***Management Features*** + +-------------------------------------------------------------------------------------------------------------------- +### **Add** +> **Your accessible command to easily add relevant project details into CodeContact.** -
-**:information_source: Notes about the command format:**
+#### Add developer : `add-developer` -* 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`. +> Adds a new developer to the address book with the entered details. -* 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`. +**Format:** +`add-developer n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [d/DATE_JOINED] r/ROLE s/SALARY [pr/PROJECT_NAME]... g/GITHUB_ID rt/RATING` -* 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. +**Constraints:** +1. `NAME` cannot be the same as another existing developer's name in the address book. Checks are case-insensitive. +2. `PROJECT_NAME` should be the exact name of an existing project if specified. +If project to be assigned does not exist, consider [adding the project](#add-project--add-project) first! -* Parameters can be in any order.
- e.g. if the command specifies `n/NAME p/PHONE_NUMBER`, `p/PHONE_NUMBER n/NAME` is also acceptable. -* Extraneous parameters for commands that do not take in parameters (such as `help`, `list`, `exit` and `clear`) will be ignored.
- e.g. if the command specifies `help 123`, it will be interpreted as `help`. +**Optional Fields:** + 1. `PROJECT_NAME` - if prefix pr/ is missing in the command, the added developer will not be assigned to any projects. -* If you are using a PDF version of this document, be careful when copying and pasting commands that span multiple lines as space characters surrounding line-breaks may be omitted when copied over to the application. + 2. `DATE_JOINED` - if prefix d/ is missing in the command, the added developer's date joined field will automatically reflect today's date. + + + +* Note that for `RATING` values that are not in increments of 0.5 (eg. 3.2 or 3.8 instead of 3.0 or 3.5), the decimal portion + may not be reflected as clearly in the coloured stars. + + +**Example of usage:** +`add-developer n/John Doe p/98765432 e/johnd@example.com a/311, Clementi Ave 2, #02-25 r/Developer +pr/AndroidApp pr/CustomWebsite s/4500 d/11-11-2023 g/johng rt/3` + + +* A new developer, `John Doe`, is added with the respective details. +* He is assigned to the projects `AndroidApp` and `CustomWebsite` (provided that these projects already exist in the address book). + + +When command succeeds, CLI shows: +``` +New developer added: John Doe; +Phone: 98765432; +Email: johnd@example.com; +Address: 311, Clementi Ave 2, #02-25; +Date Joined: 11-11-2023; +Role: Developer; +Salary: 4500; +Projects: CustomWebsite AndroidApp +``` + +
:bulb: +Entered details of a developer incorrectly? You can always undo the action with the [`undo`](#undo) command!
-### Viewing help : `help` +[Scroll back to Table of Contents](#table-of-contents) + +-------------------------------------------------------------------------------------------------------------------- +#### Add client : `add-client` + +> Adds a new client to the address book, with the entered details. + +**Format:** +`add-client n/NAME p/PHONE e/EMAIL a/ADDRESS r/ROLE [pr/PROJECT]... o/ORGANISATION do/DOCUMENT` -Shows a message explaning how to access the help page. +**Constraints:** +1. `NAME` cannot be the same as another existing client's name in the address book. Checks are case-insensitive. -![help message](images/helpMessage.png) +2. `PROJECT_NAME` should be the exact name of an existing project if specified. +If project to be assigned does not exist, consider [adding the project](#add-project--add-project) first! -Format: `help` +**Optional Fields:** +1. `PROJECT_NAME` - if prefix pr/ is missing in the command, the added developer will not be assigned to any projects. +**Example of usage:** +`add-client n/Jack Doe p/98765432 e/jackd@example.com a/311, Clementi Ave 2, #02-25 r/Developer pr/AndroidApp pr/CustomWebsite o/Google do/google.com` -### Adding a person: `add` -Adds a person to the address book. +* A new client, `Jack Doe`, is added with the respective details +* He is assigned to the projects `AndroidApp` and `CustomWebsite` (provided that these projects already exist in the address book). -Format: `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]…​` -
:bulb: **Tip:** -A person can have any number of tags (including 0) +When command succeeds, CLI shows: + +``` +New client added: Jack Doe; +Phone: 98765432; +Email: jackd@example.com; +Address: 311, Clementi Ave 2, #02-25; +Organisation: Google; +Role: Developer; +Document: google.com; +Projects: CustomWebsite AndroidApp +``` + +
:bulb: +Entered details of a client incorrectly? You can always undo the action with the [`undo`](#undo) command!
-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` +[Scroll back to Table of Contents](#table-of-contents) -### Listing all persons : `list` +-------------------------------------------------------------------------------------------------------------------- +#### Add project : `add-project` -Shows a list of all persons in the address book. +> Adds a new project and its relevant deadlines to the address book, with the entered details. -Format: `list` +**Format:** +`add-project n/NAME dr/DESCRIPTION [dl/DEADLINE_DATE,DEADLINE_DESCRIPTION,PRIORITY,IS_DONE]...` -### Editing a person : `edit` +**Constraints:** +1. `NAME` cannot be the same as another existing project's name in the address book. Checks are case-insensitive. -Edits an existing person in the address book. +**Optional Fields:** +1. `DEADLINE` - if prefix dl/ is missing in the command, the added project will not have any deadlines assigned to it. -Format: `edit INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [t/TAG]…​` +**Example of usage:** +`add-project n/JuiceApp dr/App to allow for different juices to be ordered dl/19-12-2023,Design backend,HIGH,0 dl/25-12-2023,Design frontend,MEDIUM,0` -* 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. + +* A new project, `JuiceApp`, is added with the respective details. +* Deadlines with the descriptions `Design backend` and `Design frontend` are created and assigned to the newly added project. + + +When command succeeds, CLI shows: + +``` +New project added: JuiceApp; +Description: App to allow for different juices to be ordered; +Deadlines: +1. Design backend by: 19-12-2023, priority: HIGH (undone) +2. Design frontend by: 25-12-2023, priority: MEDIUM (undone) +``` + +
:bulb: +Entered details of a project incorrectly? You can always undo the action with the [`undo`](#undo) command! +
+ +[Scroll back to Table of Contents](#table-of-contents) + +-------------------------------------------------------------------------------------------------------------------- +
+ +### **Delete** +> **Your quick command to delete project details effectively into CodeContact.** + + +#### Delete developer : `delete-developer` + +> Deletes developer in the address book and their respective list. + +**Format:** `delete-developer INDEX` + +**Constraints:** +1. `INDEX` cannot be greater than the number of developers in the list. + +**Example of usage:** `delete-developer 2` + +When command succeeds, CLI shows: + +``` +Deleted Developer: Bernice Yu; +Phone: 99272758; +Email: berniceyu@example.com; +Address: Blk 30 Lorong 3 Serangoon Gardens, #07-18; +Date Joined: 16-11-2020; +Role: Developer; +Salary: 6000; +Projects: Appollo Orbital +``` +if second developer in the list was Bernice Yu. + +
:bulb: +Deleted a wrong developer? You can always undo the action with the [`undo`](#undo) command! +
+ +[Scroll back to Table of Contents](#table-of-contents) + +-------------------------------------------------------------------------------------------------------------------- +
+ +#### Delete client : `delete-client` + +> Deletes client in the address book and their respective list. + +**Format:** `delete-client INDEX` + +**Constraints:** +1. `INDEX` cannot be greater than the number of clients in the list. + +**Example of usage:** `delete-client 3` + +When command succeeds, CLI shows: +``` +Deleted Client: George Lim; +Phone: 76543210; +Email: george@example.com; +Address: Blk 789 Woodlands Ave 6, #03-03; +Organisation: MNO Company; +Role: Developer; +Document: https://www.mno.com/; +Projects: CodeContact +``` +if second client in the list was George Lim. + +
:bulb: +Deleted a wrong client? You can always undo the action with the [`undo`](#undo) command! +
+ +[Scroll back to Table of Contents](#table-of-contents) + +-------------------------------------------------------------------------------------------------------------------- +
+ +#### Delete project : `delete-project` + +> Deletes the details of an existing project in the address book and their respective list. +> Updates developer and client project details accordingly. + +**Format:** `delete-project INDEX` + +**Constraints:** +1. `INDEX` cannot be greater than the number of projects in the list. + +**Example of usage:** `delete-project 2` + + +* Deletes second project in the project list from the project list and from the address book. +* Deletes project from developers' and clients' project lists if they were assigned to this project. + + +When command succeeds, CLI shows: +``` +Deleted Project: TeamTrekker; +Description: A team collaboration tool; +Deadlines: +1. Phase 2 by: 13-11-2021, priority: MEDIUM (undone) +``` +if second project in the list was TeamTrekker. + +
+:exclamation: **Note**: This command deletes **project from developers' and clients' project lists** if they were assigned to this project. +
+ + +
:bulb: +Deleted a wrong project? You can always undo the action with the [`undo`](#undo) command! +
+ +[Scroll back to Table of Contents](#table-of-contents) + +-------------------------------------------------------------------------------------------------------------------- +
+ +### **Edit** +> **Your quick and accessible CodeContact command to edit your ever changing project details** + + +#### Edit developer details : `edit-developer` + +> Edits the details of an existing developer in the address book. + +**Format:** +`edit-developer INDEX [n/NAME] [p/PHONE_NUMBER] [e/EMAIL] [a/ADDRESS] [d/DATE_JOINED] [r/ROLE] [s/SALARY] [pr/PROJECT_NAME]... [g/GITHUB_ID] [rt/RATING]` + +**Constraints:** +1. `NAME` cannot be the same as another existing developer's name in the address book. Checks are case-insensitive. + You can, however, edit the casing of an existing developer's `NAME`. +2. `PROJECT_NAME` should be the exact name of an existing project. +3. At least one of the optional fields must be provided. + +**Additional Notes:** * 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. +* When editing projects, the existing assigned projects of the developer will be removed i/e. adding of projects is not + cumulative. +* You can remove all the developer's projects by typing `pr/` without specifying any project name after it. +* `RATING` values that not in increments of 0.5 (eg. 3.2 or 3.8 instead of 3.0 or 3.5), the decimal portion + may not be reflected as clearly in the coloured stars. + +**Example of usage:** +`edit-developer 2 p/98989898 pr/Project2 pr/Project3` + +* Edits `Amy`'s phone number to `98989898` and changes the projects assigned to her to `Project2` and `Project3`. + +When command succeeds, CLI shows: + +``` +Edited Developer: Amy +Phone: 98989898 +Email: amy@u.nus.edu +Address: NUS UTOWN +Date Joined: 06-09-2023 +Role: Developer +Salary: 6999 +Projects: Project3 Project2 +``` + +
:bulb: +You can always undo the action with the [`undo`](#undo) command! +
+ +[Scroll back to Table of Contents](#table-of-contents) + +-------------------------------------------------------------------------------------------------------------------- +#### Edit client details : `edit-client` + +> Edits the details of an existing client in the address book. + +**Format:** +`edit-client INDEX [n/NAME] [p/PHONE_NUMBER] [e/EMAIL] [a/ADDRESS] [pr/PROJECT_NAME]... [o/ORGANISATION]` + +**Constraints:** +1. `NAME` cannot be the same as another existing client's name in the address book. Checks are case-insensitive. + You can, however, edit the casing of an existing client's `NAME`. +2. `PROJECT_NAME` should be the exact name of an existing project. +3. At least one of the optional fields must be provided. + +**Additional Notes:** +* Existing values will be updated to the input values. +* When editing projects, the existing assigned projects of the client will be removed i/e. adding of projects is not + cumulative. +* You can remove all the client's projects by typing `pr/` without specifying any project name after it. +* `RATING` values that not in increments of 0.5 (eg. 3.2 or 3.8 instead of 3.0 or 3.5), the decimal portion + may not be reflected as clearly in the coloured stars. + +**Example of usage:** +`edit-client 3 e/bob@gmail.com` + +* Edits `Bob`'s email to `bob@gmail.com`. + +When command succeeds, CLI shows: + +``` +Edited Client: Bob; +Phone: 87654321; +Email: bob@gmail.com; +Address: Blk 123 Banana Road; +Organisation: Google; +Document: google.com +Projects: ProjectA +``` +
:bulb: +You can always undo the action with the [`undo`](#undo) command! +
+ +[Scroll back to Table of Contents](#table-of-contents) + +-------------------------------------------------------------------------------------------------------------------- +#### Edit project details : `edit-project` + +> Edits the details of an existing project in the address book. + +**Format:** +`edit-project INDEX [dr/DESCRIPTION] [dl/DEADLINE]...` + +**Constraints:** +1. The name of a project cannot be edited. +2At least one of the optional fields must be provided. + +**Additional Notes:** +* Existing values will be updated to the input values. +* When editing deadlines, the existing deadlines will be removed ie. adding of projects is not cumulative. +* You can remove all the current deadlines by typing `dl/` without specifying any deadline. + +**Example of usage:** +`edit-project 1 dl/19-12-2023,Design backend,HIGH,0` -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. +* Deletes existing project deadlines and adds new deadline `Design backend by: 19-12-2023, priority: HIGH (undone)`. -### Locating persons by name: `find` +When command succeeds, CLI shows: -Finds persons whose names contain any of the given keywords. +``` +Edited Project: JuiceApp; +Description: Juice ordering app; +Deadlines: +1. Design backend by: 19-12-2023, priority: HIGH (undone) +``` +
:bulb: +You can always undo the action with the [`undo`](#undo) command! +
+ +[Scroll back to Table of Contents](#table-of-contents) + +-------------------------------------------------------------------------------------------------------------------- +
+ +### **Import** +> **Your lightning-fast and reliable way of porting over existing project details into CodeContact.** + + +#### Import developers' details : `import-developer` + +> Reads a CSV file and populates the addressbook with the developers provided. + +**Command Format:** `import-developer FILENAME` + +**Column Header Format (for CSV file):** `Name`, `Contact Number`, `Email`, `Address`, `Date Joined`, `Role`, `Salary`, `GithubId`, `Rating`, `Projects` + +**Constraints:** +1. The CSV file has to be in the same folder as the JAR file for the command to function correctly. + +2. The CSV file has to strictly follow the column header names and order (given in the example). + The entire command will abort if any of column headers do not follow the given format. + +3. `NAME` and `PROJECT_NAME` fields for developers to be added via the import feature have the same constraints as the [`add-developer` command](#add-developer--add-developer) constraints. + The entire command will abort if any of the rows have invalid values that do not comply with given constraints. + +**Optional Fields:** +While `PROJECT` and `DATE JOINED` column headers are compulsory, they are optional fields, where values (corresponding to the developer to be added) under these columns can be empty. -Format: `find KEYWORD [MORE_KEYWORDS]` +If values are empty, corresponding developers will be added with `DATE JOINED` automatically set to today and/or with no projects assigned to them, like the [`add-developer` command](#add-developer--add-developer). -* 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` -Examples: -* `find John` returns `john` and `John Doe` -* `find alex david` returns `Alex Yeoh`, `David Li`
- ![result for 'find alex david'](images/findAlexDavidResult.png) +Example of valid CSV: -### Deleting a person : `delete` +``` +Name, Contact Number, Email, Address, Date Joined, Role, Salary, GithubId, Rating, Projects,, +faiz,87654321,faiz@u.com,utown,12-12-2020,Developer,3333,Faizgit,5,AndroidApp,ProjectB, +John,123456789,john@email.com,123 Main St,01-01-2021,Developer,4000,JohnDesigns,4,AndroidApp,ProjectB, +Sarah,987654321,sarah@email.com,456 Elm St,05-10-2019,Developer,6000,SarahManager,5,AndroidApp,ProjectB,ProjectC +Alex,555555555,alex@email.com,789 Oak St,03-01-2022,Developer,5500,AlexDev,4,AndroidApp,ProjectB, +Emily,111111111,emily@email.com,321 Pine St,08-10-2018,Developer,4800,EmilyAnalyst,4,AndroidApp,ProjectB, +Michael,999999999,michael@email.com,567 Birch St,06-03-2020,Developer,7000,MichaelEngineer,5,AndroidApp,ProjectB, +``` -Deletes the specified person from the address book. +**Example of usage:** `import-developer developers.csv` -Format: `delete INDEX` +Reads `developers.csv` and adds a new developer for each row of data. -* 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, …​ +When command succeeds, CLI shows: +``` +New developer added: faiz; +Phone: 87654321; +Email: faiz@u.com; +Address: utown; +Date Joined: 12-12-2020; +Role: Developer; +Salary: 3333; +Projects: ProjectB AndroidApp +``` +for each developer successfully added. -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. +[Scroll back to Table of Contents](#table-of-contents) -### Clearing all entries : `clear` +-------------------------------------------------------------------------------------------------------------------- +#### Import clients' details : `import-client` + +Reads a CSV file and populates the addressbook with the clients provided. + +**Command Format:** `import-client FILENAME` + +**Column Header Format (for CSV file):** `Name`, `Contact Number`, `Email`, `Address`, `Role`, `Organisation`, `Document`, `Projects` + + +**Constraints:** +1. The CSV file has to be in the same folder as the JAR file for the command to function correctly. + +2. The CSV file has to strictly follow the column header names and order (given in the example). + The entire command will abort if any of column headers do not follow the given format. -Clears all entries from the address book. +3. `NAME` and `PROJECT_NAME` fields for clients to be added via the import feature have the same constraints as the [`add-client` command](#add-client--add-client) constraints. The entire command will abort if any of the rows have invalid values that do not comply with given constraints. -Format: `clear` +**Optional Fields:** +While the `PROJECT` column header is compulsory, it is an optional fields, where values (corresponding to the client to be added) under this column can be empty. -### Exiting the program : `exit` +If values under the `PROJECT` column are empty, corresponding clients will be added with no projects assigned to them, like the [`add-client` command](#add-client--add-client). -Exits the program. -Format: `exit` +Example of valid CSV: +``` +Name, Contact Number, Email, Address, Role, Organisation, Document, Projects, +Mahi,87554321,mahi@u.com,utown,HR,Google,docs.google.com/abd,AndroidApp,ProjectB +Jane,654321876,jane@email.com,456 Oak St,HR,Acme Corp,acme.com/docs,AndroidApp +Robert,987123456,robert@email.com,789 Elm St,HR,Tech Solutions,techdocs.com/123 +Maria,321987654,maria@email.com,123 Maple St,HR,Innovate Inc,innovate.com/docs +Chris,876543219,chris@email.com,567 Pine St,HR,Data Insights,datainsights.com/docs +Laura,888555555,laura@email.com,101 Birch St,HR,Software Systems,software.com/docs +``` -### Saving the data +**Example of usage:** `import-client clients.csv` + +Reads `clients.csv` and adds a new client for each row of data. + +When command succeeds, CLI shows: + +``` +New client added: Mahi; +Phone: 87554321; +Email: mahi@u.com; +Address: utown; +Organisation: Google; +Role: HR; +Document: docs.google.com/abd; +Projects: ProjectB AndroidApp +``` + +for each client successfully added. + +[Scroll back to Table of Contents](#table-of-contents) + +-------------------------------------------------------------------------------------------------------------------- +
-AddressBook data are saved in the hard disk automatically after any command that changes the data. There is no need to save manually. +### **Find** + +> **Your go-to command to quickly access precise information in CodeContact.** + + +#### Find developer details : `find-developer` + +> Finds the details of an existing developer in the address book. + +**Format:** +`find-developer [n/NAME] [p/PHONE_NUMBER] [e/EMAIL] [a/ADDRESS] [d/DATE_JOINED] +[r/ROLE] [s/SALARY] [pr/PROJECT_NAME] [g/GITHUB_ID] [rt/RATING]` + +**Additional notes:** +* Finds for developers based on the attributes provided. +* At least one of the optional fields must be provided. +* Existing values will be compared to the input values, and the results will include any items that match the provided + criteria. +* You can combine multiple attributes for a more specific search. +* The search is case-insensitive, so you can use any case for the search criteria. + +**Example of usage:** +`find-developer pr/2103T rt/5.0` + +* Prints developers in 2103/T project with a 5-star rating. + +When command succeeds, CLI shows: + +``` +This is the one developer with matching information. +``` + + +[Scroll back to Table of Contents](#table-of-contents) + +-------------------------------------------------------------------------------------------------------------------- +#### Find client details : `find-client` + +> Finds the details of an existing client in the address book. + +**Format:** +`find-client [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [o/ORGANISATION] [pr/PROJECT] [d/DOCUMENT]` + +**Additional notes:** +* Finds for clients based on the attributes provided. +* At least one of the optional fields must be provided. +* Existing values will be compared to the input values, and the results will include any items that match the provided + criteria. +* You can combine multiple attributes for a more specific search. +* The search is case-insensitive, so you can use any case for the search criteria. + +**Example of usage:** +`find-client o/Google r/Senior developer` + +* Prints clients from Google with the Senior developer role. + +When command succeeds, CLI shows: + +``` +These are the 2 clients with matching information. +``` + + +[Scroll back to Table of Contents](#table-of-contents) + +-------------------------------------------------------------------------------------------------------------------- +#### Find project details : `find-project` -### Editing the data file +> Finds the details of an existing project in the address book. -AddressBook data are saved automatically as a JSON file `[JAR file location]/data/addressbook.json`. Advanced users are welcome to update data directly by editing that data file. +**Format:** `find-project [pr/PROJECT_NAME] [dr/DESCRIPTION] [dl/DEADLINE]` -
:exclamation: **Caution:** -If your changes to the data file makes its format invalid, AddressBook will discard all data and start with an empty data file at the next run. Hence, it is recommended to take a backup of the file before editing it. +**Additional notes:** +* Finds for projects based on the attributes provided. +* At least one of the optional fields must be provided. +* Existing values will be compared to the input values, and the results will include any items that match the provided + criteria. +* You can combine multiple attributes for a more specific search. +* The search is case-insensitive, so you can use any case for the search criteria. + +**Example of usage:** `find-project pr/JuiceApp` + +* Print projects with the name JuiceApp. + +When command succeeds, CLI shows: + +``` +This is the one project with matching information. +``` + +[Scroll back to Table of Contents](#table-of-contents) + +-------------------------------------------------------------------------------------------------------------------- +#### Find deadlines : `find-deadline` + +> Finds deadlines in project tab based on date and/or priority. + +**Format:** `find-deadline [d/DATE] [pri/PRIORITY]` + +**Additional notes:** +* When finding deadlines based on `DATE`, the project tab displays deadlines due before or on the specified date. +* When finding deadlines based on `PRIORITY`, only that priority (`HIGH`,`MEDIUM`, `LOW`) deadlines are shown. + +**Example of usage:** `find-deadline d/20-11-2023 pri/MEDIUM` + +* Shows deadlines due before or on `20-11-2023` and with `MEDIUM` priority. + +When command succeeds, CLI shows: + +``` +These are the 3 projects with matching information. +``` + +[Scroll back to Table of Contents](#table-of-contents) + +-------------------------------------------------------------------------------------------------------------------- +
+ +### **List** +> **Your command to view all relevant details in and your keyboard alternative to switching through tabs in CodeContact** + + +#### List information : `list` + +> Shows a list of all developers in the address book. + +**Format:** `list-TYPE` + +* Lists the specific type of thing you are asking. + +**Example of usage:** `list-developer` + +* Lists all the developers. + +**Acceptable parameters**: + +* `developer` to list the developers +* `client` to list the clients +* `project` to list the projects + +When command succeeds, CLI shows: + +``` +Listed all developers +``` + +[Scroll back to Table of Contents](#table-of-contents) + +-------------------------------------------------------------------------------------------------------------------- + +
+ +### **Add roles** +> **Your command to take control of the type of roles available in CodeContact.** + + +#### Add developer roles : `add-developer-role` + +> Adds new developer roles into the system. + +**Format:** `add-developer-role ROLE_NAME` + +**Additional notes:** +* Adds the ROLE_NAME to list of developer roles. +* There are 3 preset roles in the list of roles: `Frontend Developer`,`Backend Developer`,`Developer`. +* You will not be able to add a developer to a role that does not exist in this list of developer roles. +
+:exclamation: **Note** :This command is **not** case-sensitive, even if `Developer` is a role, `developer` can still be added.
-### Archiving data files `[coming in v2.0]` +
:bulb: +If you wish to check what roles are there, you can key in `delete-developer-role `, `` should not be an existing role. +
-_Details coming soon ..._ +**Example of usage:** `add-developer-role Tester` + +* Adds the Tester role to list of developer roles. +* You can now add developers with Tester as their roles. + +When command succeeds, CLI shows: + +``` +New role for developer added: Tester +``` + +[Scroll back to Table of Contents](#table-of-contents) -------------------------------------------------------------------------------------------------------------------- +#### Add client roles : `add-client-role` -## FAQ +> Adds new client roles into the system. -**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. +**Format:** `add-client-role ROLE_NAME` + +**Additional notes:** +* Adds the ROLE_NAME to list of client roles. +* There are 4 preset roles in the list of roles: `HR`,`Manager`,`Developer`,`Client`. +* You will not be able to add a client to a role that does not exist in this list of client roles. +
+:exclamation: **Note** :This command is **not** case-sensitive, even if `HR` is a role, `hr` can still be added. +
+ +
:bulb: +If you wish to check what roles are there, you can key in `delete-client-role `, `` should not be an existing role. +
+ +**Example of usage:** `add-client-role Boss` + +* Adds the Boss role to list of developer roles. +* You can now add clients with Boss as their roles. + +When command succeeds, CLI shows: + +``` +New role for client added: Boss +``` + +[Scroll back to Table of Contents](#table-of-contents) + +-------------------------------------------------------------------------------------------------------------------- + +### **Delete roles** + +> **Your command to remove unnecessary roles in CodeContact.** + + +#### Delete developer roles : `delete-developer-role` + +> Delete developer roles from the system. + +**Format:** `delete-developer-role ROLE_NAME` + +**Additional notes:** +* Deletes the ROLE_NAME to list of developer roles. +* There are 3 preset roles in the list of roles: `Frontend Developer`,`Backend Developer`,`Developer`. These roles + cannot be deleted. +* You will not be able to delete a developer role if there are developers in the list with that role. + +**Example of usage:** `delete-developer-role UI Manager` + +* Deletes the UI Manager from the list of developer roles. +* You can no longer add developers with UIDesigner as their roles. + +When command succeeds, CLI shows: + +``` +Role for developers deleted: UIDesigner +``` + +[Scroll back to Table of Contents](#table-of-contents) -------------------------------------------------------------------------------------------------------------------- +#### Delete client roles : `delete-client-role` + +> Delete client roles from the system. + +**Format:** `delete-client-role ROLE_NAME` + +**Additional notes:** +* Deletes the ROLE_NAME to list of developer roles. +* There are 4 preset roles in the list of roles: `HR`,`Manager`,`Developer`,`Client`. These roles cannot be deleted. +* You will not be able to delete a client role if there are clients in the list with that role. + +**Example of usage:** `delete-client-role Boss` + +* Deletes the Boss from the list of developer roles. +* You can no longer add clients with Boss as their roles. + +When command succeeds, CLI shows: -## Known issues +``` +Role for clients deleted: Boss +``` -1. **When using multiple screens**, if you move the application to a secondary screen, and later switch to using only the primary screen, the GUI will open off-screen. The remedy is to delete the `preferences.json` file created by the application before running the application again. +[Scroll back to Table of Contents](#table-of-contents) -------------------------------------------------------------------------------------------------------------------- -## Command summary +
+ +### **Mark** + +> **Your quick and reliable command to keep on track with your project deadlines in CodeContact.** + + +#### Mark project deadline as done : `mark-deadline` + +> Marks the indicated deadline for the project as done. + +**Format:** `mark-deadline PROJECT_INDEX DEADLINE_INDEX` + +**Additional notes:** +* `PROJECT_INDEX` and `DEADLINE_INDEX` must be valid indexes of existing projects and deadlines. + +**Example of usage:** `mark-deadline 2 1` + +* Marks the 1st deadline of the 2nd project in the currently displayed project list as done. + +When command succeeds, CLI shows: + +``` +The deadline has been marked as completed! +``` + +[Scroll back to Table of Contents](#table-of-contents) + +-------------------------------------------------------------------------------------------------------------------- +#### Mark project deadline as undone : `unmark-deadline` + +> Marks the indicated deadline for the project as undone. + +**Format:** `unmark-deadline PROJECT_INDEX DEADLINE_INDEX` + +**Additional notes:** +* `PROJECT_INDEX` and `DEADLINE_INDEX` must be valid indexes of existing projects and deadlines. + +**Example of usage:** `unmark-deadline 2 1` + +* Marks the 1st deadline of the 2nd project in the currently displayed project list as undone. + +When command succeeds, CLI shows: + +``` +The deadline has been marked as undone! +``` + +[Scroll back to Table of Contents](#table-of-contents) + +-------------------------------------------------------------------------------------------------------------------- + +### **Undo** + +> **Your quick and hassle free command to revert changes in CodeContact.** + + +#### Undo : `undo` + +> Undo the previous command you entered. + +**Format:** `undo` + +**Additional notes:** +* Each time you type undo, you move back one stage. +* If you made 5 changes, and you wish to undo, you can enter the command `undo` 5 times. The system will remind you when + you cannot undo anymore. +* `undo` works for all `edit`, `add-TYPE` and `delete` commands. + +**Example of usage:** `undo` + +* You just deleted a new developer, and you wish to `undo`. + +When command succeeds, CLI shows: -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` +``` +Undo successful! The change below has been undone: +Deleted Developer: Amy; +Phone: 83566674; +Email: amy@example.com; +Address: 42, Clementi Ave 7, #02-2; +Date Joined: 23-11-2023; +Role: Frontend Developer; +Salary: 5000; +Projects: CustomWebsite AndroidApp +``` + +[Scroll back to Table of Contents](#table-of-contents) + +-------------------------------------------------------------------------------------------------------------------- +#### Redo : `redo` + +> Redo the previous command you undid. + +**Format:** `redo` + +**Additional notes:** +* Each time you type redo, you move forward one stage. +* You can only `redo` if you have `undo` before. +* If you undid 5 changes, and you wish to redo, you can enter the command `redo` 5 times. The system will remind you when + you cannot redo anymore. +* `redo` works for all `edit`, `add-TYPE` and `delete` commands. + +**Example of usage:** + +* You just `undo` delete developer, and you wish to `redo` to add it back. + When command succeeds, CLI shows: + +``` +Redo successful! The change below has been redone: +Deleted Developer: Amy; +Phone: 83566674; +Email: amy@example.com; +Address: 42, Clementi Ave 7, #02-2; +Date Joined: 23-11-2023; +Role: Frontend Developer; +Salary: 5000; +Projects: CustomWebsite AndroidApp +``` + +[Scroll back to Table of Contents](#table-of-contents) + +-------------------------------------------------------------------------------------------------------------------- +
+ +## ***Additional Features*** + +-------------------------------------------------------------------------------------------------------------------- +### **Miscellaneous Features** +> **Accessible features for you to navigate smoothly through CodeContact.** + + +#### Viewing help : `help` + +> Shows a message explaining how to access the help page. + +**Format:** `help` + +[Scroll back to Table of Contents](#table-of-contents) + +-------------------------------------------------------------------------------------------------------------------- +#### Clearing all entries : `clear` + +> Clears all entries from the address book. + +**Format:** `clear` + +[Scroll back to Table of Contents](#table-of-contents) + +-------------------------------------------------------------------------------------------------------------------- +#### Exiting the program : `exit` + +> Exits the program. + +**Format:** `exit` + +[Scroll back to Table of Contents](#table-of-contents) + +-------------------------------------------------------------------------------------------------------------------- + +
+ +# **FAQ** + +-------------------------------------------------------------------------------------------------------------------- + +**Q**: How do I transfer my data to another Computer?
+**A**: Install the app in the other computer and overwrite the empty data file it creates with the file that contains +the data of your previous CodeContact home folder. + + +**Q**: How can I launch CodeContact if the clicking on the JAR file does not work?
+**A**: There are two possible methods to launch CodeContact. +
+ +* Method 1: For users familiar with the command prompt + 1. Open the command prompt. + 2. Navigate to the directory where the JAR file is located using cd [JAR file location]. + 3. Type `java -jar CodeContact.jar` and press enter. + 4. CodeContact should launch. +

+ +* Method 2: For users that wish to create a script to launch CodeContact (Recommended) + + 1. Create a new text file. + 2. Type the following into the text file: + `java -jar [JAR file location]/CodeContact.jar`. + 3. Save the text file as CodeContact.bat (Windows) or CodeContact.sh (MacOS/Linux). + 4. Change the admin settings of the script to allow it to run as a program: + * Windows: Right-click on the script and select Properties. Under General , check + the box that says Allow this file to run as a program. + * MacOS/Linux: Open the terminal and navigate to the directory where the script is + located. Type `chmod +x [script name]` and press enter (`chmod +x` changes + permissions of the script to allow it to be executed). + 5. Double-click on the script to launch CodeContact. + 6. CodeContact should launch. + + +**Q**: How can I check my java version?
+**A**: Open a command prompt and type `java -version`. If you do not have Java installed, you +can download it [here](https://www.oracle.com/java/technologies/downloads/#java11). + +[Scroll back to Table of Contents](#table-of-contents) + +-------------------------------------------------------------------------------------------------------------------- +
+ +# **Known issues** +-------------------------------------------------------------------------------------------------------------------- + +1. **When using multiple screens**, if you move the application to a secondary screen, and later switch to using only + the primary screen, the GUI will open off-screen. The remedy is to delete the `preferences.json` file created by the + application before running the application again. +2. **When tampering with the storage files**, if you tamper with the storage files leading to incorrect format, the application will not be able to + read the data and will throw an error. The remedy is to delete the `addressbook.json` file created by the application before + running the application again. + +[Scroll back to Table of Contents](#table-of-contents) + +-------------------------------------------------------------------------------------------------------------------- +
+ +# **Command summary** + +| Action | Format, Examples | +|---------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| **lock** | `lock` | +| **unlock** | Format:
`unlock pw/PASSWORD`
Example:
`unlock pw/Password123!` | +| **change password** | Format:
`change-password pw/CURRENT_PASSWORD npw/NEW_PASSWORD`
Example:
`change-password pw/Password123! npw/Password123!` | +| **add developers** | Format:
`add-developer n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [d/DATE_JOINED] r/ROLE s/SALARY [pr/PROJECT_NAME]... g/GITHUB_ID rt/RATING`
Example:
`add-developer n/John Doe p/98765432 e/johnd@example.com a/311, Clementi Ave 2, #02-25 r/Developer pr/AndroidApp pr/CustomWebsite s/4500 d/11-11-2023 g/johng rt/3`
| +| **add clients** | Format:
`add-client n/NAME p/PHONE e/EMAIL a/ADDRESS r/ROLE [pr/PROJECT]... o/ORGANISATION do/DOCUMENT`
Example:
`add-client n/Jack Doe p/98765432 e/jackd@example.com a/311, Clementi Ave 2, #02-25 r/Developer pr/AndroidApp pr/CustomWebsite o/Google do/google.com`
| +| **add projects** | Format:
`add-project n/NAME dr/DESCRIPTION [dl/DEADLINE_DATE,DEADLINE_DESCRIPTION,PRIORITY,IS_DONE]...`
Example:
`add-project n/JuiceApp dr/App to allow for different juices to be ordered dl/19-12-2023,Design backend,HIGH,0 dl/25-12-2023,Design frontend,MEDIUM,0`
| +| **edit developers** | Format:
`edit-developer INDEX [n/NAME] [p/PHONE_NUMBER] [e/EMAIL] [a/ADDRESS] [d/DATE_JOINED] [r/ROLE] [s/SALARY] [pr/PROJECT_NAME]... [g/GITHUB_ID] [rt/RATING]`
Example:
`edit-developer 2 p/98989898 pr/Project2 pr/Project3`
| +| **edit clients** | Format:
`edit-client INDEX [n/NAME] [p/PHONE_NUMBER] [e/EMAIL] [a/ADDRESS] [d/DATE_JOINED] [r/ROLE] [s/SALARY] [pr/PROJECT_NAME]... [g/GITHUB_ID] [rt/RATING]`
Example:
`edit-client 3 p/bob@gmail.com`
| +| **edit projects** | Format:
`edit-project INDEX [dr/DESCRIPTION] [dl/DEADLINE]...`
Example:
`edit-project 1 dl/Finish Feature-A by: 09-09-2023`
| +| **find developers** | Format:
`find-developer pr/`
Example:
`find-developer pr/2103/T`
| +| **find clients** | Format:
`find-client n/`
Example:
`find-client n/Amy`
| +| **find projects** | Format:
`find-project dr/description`
Example:
`find-project dr/school semester project`
| +| **find deadlines** | Format:
`find-deadline [d/DATE] [pri/PRIORITY]`
Example:
`find-deadline d/20-11-2023 pri/MEDIUM`
| +| **delete developer** | Format:
`delete-developer INDEX`
Example:
`delete-developer 1`
| +| **delete client** | Format:
`delete-client INDEX`
Example:
`delete-client 1`
| +| **delete project** | Format:
`delete-project INDEX`
Example:
`delete-project 1`
| +| **import developer** | Format:
`import-developer [FILENAME]`
Example:
`import-developer developers.csv`
| +| **import client** | Format:
`import-client [FILENAME]`
Example:
`import-client clients.csv`
| +| **add developer role** | Format:
`add-developer-role ROLE_NAME`
Example:
`add-developer-role UIDesigner`
| +| **add client role** | Format:
`add-client-role ROLE_NAME`
Example:
`add-client-role Boss`
| +| **delete developer role** | Format:
`delete-developer-role ROLE_NAME`
Example:
`delete-developer-role UIDesigner`
| +| **delete client role** | Format:
`delete-client-role ROLE_NAME`
Example:
`delete-client-role Boss`
| +| **list** | Format:
`list-developer`
`list-project`
`list-client` | +| **mark deadline** | Format:
`mark-deadline PROJECT_INDEX DEADLINE_INDEX`
Example:
`mark-deadline 2 1`
| +| **unmark deadline** | Format:
`unmark-deadline PROJECT_INDEX DEADLINE_INDEX`
Example:
`unmark-deadline 2 1`
| +| **undo** | `undo` | +| **redo** | `redo` | +| **clear** | `clear` | +| **exit** | `exit` | +| **help** | `help` | + +[Scroll back to Table of Contents](#table-of-contents) diff --git a/docs/_config.yml b/docs/_config.yml index 6bd245d8f4e..fa18176cd0d 100644 --- a/docs/_config.yml +++ b/docs/_config.yml @@ -1,4 +1,4 @@ -title: "AB-3" +title: "CodeContact" theme: minima header_pages: @@ -8,7 +8,7 @@ header_pages: markdown: kramdown -repository: "se-edu/addressbook-level3" +repository: "AY2324S1-CS2103T-T09-2" github_icon: "images/github-icon.png" plugins: diff --git a/docs/_includes/custom-head.html b/docs/_includes/custom-head.html index 8559a67ffad..fb531a17de3 100644 --- a/docs/_includes/custom-head.html +++ b/docs/_includes/custom-head.html @@ -1,6 +1,6 @@ {% comment %} - Placeholder to allow defining custom head, in principle, you can add anything here, e.g. favicons: +Placeholder to allow defining custom head, in principle, you can add anything here, e.g. favicons: - 1. Head over to https://realfavicongenerator.net/ to add your own favicons. - 2. Customize default _includes/custom-head.html in your source directory and insert the given code snippet. +1. Head over to https://realfavicongenerator.net/ to add your own favicons. +2. Customize default _includes/custom-head.html in your source directory and insert the given code snippet. {% endcomment %} diff --git a/docs/_includes/head.html b/docs/_includes/head.html index 83ac5326933..314465c01c9 100644 --- a/docs/_includes/head.html +++ b/docs/_includes/head.html @@ -1,12 +1,12 @@ - - - + + + - + - {%- include custom-head.html -%} + {%- include custom-head.html -%} - {{page.title}} + {{page.title}} diff --git a/docs/_includes/header.html b/docs/_includes/header.html index 33badcd4f99..7c91b6d8795 100644 --- a/docs/_includes/header.html +++ b/docs/_includes/header.html @@ -1,36 +1,36 @@ diff --git a/docs/_layouts/default.html b/docs/_layouts/default.html index e092cd572e0..bd6d117a6e8 100644 --- a/docs/_layouts/default.html +++ b/docs/_layouts/default.html @@ -1,18 +1,18 @@ - {%- include head.html -%} +{%- include head.html -%} - + - {%- include header.html -%} +{%- include header.html -%} -
-
+
+
{{ content }} -
-
+
+
- + diff --git a/docs/_sass/minima/_base.scss b/docs/_sass/minima/_base.scss index 0d3f6e80ced..7e040aa935b 100644 --- a/docs/_sass/minima/_base.scss +++ b/docs/_sass/minima/_base.scss @@ -288,7 +288,7 @@ table { text-align: center; } .site-header:before { - content: "AB-3"; + content: "CC"; font-size: 32px; } } diff --git a/docs/diagrams/AddDeveloperRoleSequenceDiagram.puml b/docs/diagrams/AddDeveloperRoleSequenceDiagram.puml new file mode 100644 index 00000000000..861294bf0c8 --- /dev/null +++ b/docs/diagrams/AddDeveloperRoleSequenceDiagram.puml @@ -0,0 +1,87 @@ +@startuml +'https://plantuml.com/sequence-diagram + + !include style.puml + skinparam ArrowFontStyle plain + + box Logic LOGIC_COLOR_T1 + participant ":LogicManager" as LogicManager LOGIC_COLOR + participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR + participant "d :AddDeveloperRoleCommandParser" as AddDeveloperRoleCommandParser LOGIC_COLOR + participant "d :AddDeveloperRoleCommand" as AddDeveloperRoleCommand LOGIC_COLOR + participant ":CommandResult" as CommandResult LOGIC_COLOR + end box + + box Model MODEL_COLOR_T1 + participant ":Model" as Model MODEL_COLOR + participant ":VersionedAddressBook" as VersionedAddressBook MODEL_COLOR + participant ":DeveloperRoles" as DeveloperRoles MODEL_COLOR + end box + + [-> LogicManager : execute("add-developer-role Tester") + activate LogicManager + + LogicManager -> AddressBookParser : parseCommand("add-developer-role Tester") + activate AddressBookParser + + create AddDeveloperRoleCommandParser + AddressBookParser -> AddDeveloperRoleCommandParser + activate AddDeveloperRoleCommandParser + + AddDeveloperRoleCommandParser --> AddressBookParser + deactivate AddDeveloperRoleCommandParser + + AddressBookParser -> AddDeveloperRoleCommandParser : parse("Tester") + activate AddDeveloperRoleCommandParser + + create AddDeveloperRoleCommand + AddDeveloperRoleCommandParser -> AddDeveloperRoleCommand + activate AddDeveloperRoleCommand + + AddDeveloperRoleCommand --> AddDeveloperRoleCommandParser + deactivate AddDeveloperRoleCommand + + AddDeveloperRoleCommandParser --> AddressBookParser + deactivate AddDeveloperRoleCommandParser + + AddressBookParser --> LogicManager + deactivate AddressBookParser + + LogicManager -> AddDeveloperRoleCommand : execute() + activate AddDeveloperRoleCommand + + AddDeveloperRoleCommand -> DeveloperRoles : isValidRole("Tester") + activate DeveloperRoles + + DeveloperRoles --> AddDeveloperRoleCommand : boolean true + deactivate DeveloperRoles + + AddDeveloperRoleCommand -> DeveloperRoles : addDeveloperRole(new DeveloperRoles("Tester)) + activate DeveloperRoles + + DeveloperRoles -> DeveloperRoles : saveDeveloperRoles() + DeveloperRoles --> AddDeveloperRoleCommand + + AddDeveloperRoleCommand -> Model : commitAddressBook(model, successMessage, TabIndex) + activate Model + + Model -> VersionedAddressBook : commit() + activate VersionedAddressBook + + VersionedAddressBook --> Model + deactivate VersionedAddressBook + + Model --> AddDeveloperRoleCommand + deactivate Model + + create CommandResult + AddDeveloperRoleCommand -> CommandResult + activate CommandResult + + CommandResult --> AddDeveloperRoleCommand + deactivate CommandResult + + AddDeveloperRoleCommand --> LogicManager : result + deactivate AddDeveloperRoleCommand + +@enduml diff --git a/docs/diagrams/AddDeveloperSequenceDiagram.puml b/docs/diagrams/AddDeveloperSequenceDiagram.puml new file mode 100644 index 00000000000..a4cf4f1b07d --- /dev/null +++ b/docs/diagrams/AddDeveloperSequenceDiagram.puml @@ -0,0 +1,77 @@ +@startuml +!include style.puml + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR +participant ":AddDeveloperCommandParser" as AddDeveloperCommandParser LOGIC_COLOR +participant "a:AddDeveloperCommand" as AddDeveloperCommand LOGIC_COLOR +participant ":CommandResult" as CommandResult LOGIC_COLOR +participant ":Developer" as Developer USER_COLOR +end box + +box Model MODEL_COLOR_T1 +participant ":Model" as Model MODEL_COLOR +end box + +[-> LogicManager : execute("add-developer args") +activate LogicManager + +LogicManager -> AddressBookParser : parseCommand("add-developer args") +activate AddressBookParser + +create AddDeveloperCommandParser +AddressBookParser -> AddDeveloperCommandParser +activate AddDeveloperCommandParser + +AddDeveloperCommandParser --> AddressBookParser +deactivate AddDeveloperCommandParser + +AddressBookParser -> AddDeveloperCommandParser : parse("args") +activate AddDeveloperCommandParser + +create AddDeveloperCommand +AddDeveloperCommandParser -> AddDeveloperCommand +activate AddDeveloperCommand + +AddDeveloperCommand --> AddDeveloperCommandParser : a +deactivate AddDeveloperCommand + +AddDeveloperCommandParser --> AddressBookParser : a +deactivate AddDeveloperCommandParser +'Hidden arrow to position the destroy marker below the end of the activation bar. +AddDeveloperCommandParser -[hidden]-> AddressBookParser +destroy AddDeveloperCommandParser + +AddressBookParser --> LogicManager : a +deactivate AddressBookParser + +LogicManager -> AddDeveloperCommand : execute() +activate AddDeveloperCommand + +create Developer +AddDeveloperCommand -> Developer: new +activate Developer + +Developer --> AddDeveloperCommand : developer +deactivate Developer + +AddDeveloperCommand -> Model : addDeveloper(devleoper) +activate Model + +Model --> AddDeveloperCommand +deactivate Model + +create CommandResult +AddDeveloperCommand -> CommandResult +activate CommandResult + +CommandResult --> AddDeveloperCommand +deactivate CommandResult + +AddDeveloperCommand --> LogicManager : result +deactivate AddDeveloperCommand + +[<--LogicManager +deactivate LogicManager +@enduml diff --git a/docs/diagrams/ArchitectureSequenceDiagram.puml b/docs/diagrams/ArchitectureSequenceDiagram.puml index 48b6cc4333c..d0ee59f4c70 100644 --- a/docs/diagrams/ArchitectureSequenceDiagram.puml +++ b/docs/diagrams/ArchitectureSequenceDiagram.puml @@ -8,13 +8,13 @@ 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 : "delete-developer 1" activate ui UI_COLOR -ui -[UI_COLOR]> logic : execute("delete 1") +ui -[UI_COLOR]> logic : execute("delete-developer 1") activate logic LOGIC_COLOR -logic -[LOGIC_COLOR]> model : deletePerson(p) +logic -[LOGIC_COLOR]> model : deleteDeveloper(p) activate model MODEL_COLOR model -[MODEL_COLOR]-> logic diff --git a/docs/diagrams/BetterModelClassDiagram.puml b/docs/diagrams/BetterModelClassDiagram.puml index 598474a5c82..1220d1b8136 100644 --- a/docs/diagrams/BetterModelClassDiagram.puml +++ b/docs/diagrams/BetterModelClassDiagram.puml @@ -4,18 +4,19 @@ skinparam arrowThickness 1.1 skinparam arrowColor MODEL_COLOR skinparam classBackgroundColor MODEL_COLOR -AddressBook *-right-> "1" UniquePersonList -AddressBook *-right-> "1" UniqueTagList -UniqueTagList -[hidden]down- UniquePersonList -UniqueTagList -[hidden]down- UniquePersonList +AddressBook *-right-> "1" UniqueDeveloperList +AddressBook *-right-> "1" UniqueProjectList -UniqueTagList -right-> "*" Tag -UniquePersonList -right-> Person +UniqueProjectList -[hidden]down- UniqueDeveloperList +UniqueProjectList -[hidden]down- UniqueDeveloperList -Person -up-> "*" Tag +UniqueProjectList -right-> "*" Project +UniqueDeveloperList -right-> Developer -Person *--> Name -Person *--> Phone -Person *--> Email -Person *--> Address +Developer -up-> "*" Project + +Developer *--> Name +Developer *--> Phone +Developer *--> Email +Developer *--> Address @enduml diff --git a/docs/diagrams/DeleteClientSequenceDiagram.puml b/docs/diagrams/DeleteClientSequenceDiagram.puml new file mode 100644 index 00000000000..21abf717ec8 --- /dev/null +++ b/docs/diagrams/DeleteClientSequenceDiagram.puml @@ -0,0 +1,69 @@ +@startuml +!include Style.puml + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR +participant ":DeleteClientCommandParser" as DeleteClientCommandParser LOGIC_COLOR +participant "d:DeleteClientCommand" as DeleteClientCommand 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-client 1") +activate LogicManager + +LogicManager -> AddressBookParser : parseCommand("delete-client 1") +activate AddressBookParser + +create DeleteClientCommandParser +AddressBookParser -> DeleteClientCommandParser +activate DeleteClientCommandParser + +DeleteClientCommandParser --> AddressBookParser +deactivate DeleteClientCommandParser + +AddressBookParser -> DeleteClientCommandParser : parse("1") +activate DeleteClientCommandParser + +create DeleteClientCommand +DeleteClientCommandParser -> DeleteClientCommand +activate DeleteClientCommand + +DeleteClientCommand --> DeleteClientCommandParser : c +deactivate DeleteClientCommand + +DeleteClientCommandParser --> AddressBookParser : c +deactivate DeleteClientCommandParser +'Hidden arrow to position the destroy marker below the end of the activation bar. +DeleteClientCommandParser -[hidden]-> AddressBookParser +destroy DeleteClientCommandParser + +AddressBookParser --> LogicManager : c +deactivate AddressBookParser + +LogicManager -> DeleteClientCommand : execute() +activate DeleteClientCommand + +DeleteClientCommand -> Model : deleteClient(1) +activate Model + +Model --> DeleteClientCommand +deactivate Model + +create CommandResult +DeleteClientCommand -> CommandResult +activate CommandResult + +CommandResult --> DeleteClientCommand +deactivate CommandResult + +DeleteClientCommand --> LogicManager : result +deactivate DeleteClientCommand + +[<--LogicManager +deactivate LogicManager +@enduml diff --git a/docs/diagrams/DeleteDeveloperRoleSequenceDiagram.puml b/docs/diagrams/DeleteDeveloperRoleSequenceDiagram.puml new file mode 100644 index 00000000000..0319e46d543 --- /dev/null +++ b/docs/diagrams/DeleteDeveloperRoleSequenceDiagram.puml @@ -0,0 +1,87 @@ +@startuml +'https://plantuml.com/sequence-diagram + + !include style.puml + skinparam ArrowFontStyle plain + + box Logic LOGIC_COLOR_T1 + participant ":LogicManager" as LogicManager LOGIC_COLOR + participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR + participant "d :DeleteDeveloperRoleCommandParser" as DeleteDeveloperRoleCommandParser LOGIC_COLOR + participant "d :DeleteDeveloperRoleCommand" as DeleteDeveloperRoleCommand LOGIC_COLOR + participant ":CommandResult" as CommandResult LOGIC_COLOR + end box + + box Model MODEL_COLOR_T1 + participant ":Model" as Model MODEL_COLOR + participant ":VersionedAddressBook" as VersionedAddressBook MODEL_COLOR + participant ":DeveloperRoles" as DeveloperRoles MODEL_COLOR + end box + + [-> LogicManager : execute("delete-developer-role Tester") + activate LogicManager + + LogicManager -> AddressBookParser : parseCommand("delete-developer-role Tester") + activate AddressBookParser + + create DeleteDeveloperRoleCommandParser + AddressBookParser -> DeleteDeveloperRoleCommandParser + activate DeleteDeveloperRoleCommandParser + + DeleteDeveloperRoleCommandParser --> AddressBookParser + deactivate DeleteDeveloperRoleCommandParser + + AddressBookParser -> DeleteDeveloperRoleCommandParser : parse("Tester") + activate DeleteDeveloperRoleCommandParser + + create DeleteDeveloperRoleCommand + DeleteDeveloperRoleCommandParser -> DeleteDeveloperRoleCommand + activate DeleteDeveloperRoleCommand + + DeleteDeveloperRoleCommand --> DeleteDeveloperRoleCommandParser + deactivate DeleteDeveloperRoleCommand + + DeleteDeveloperRoleCommandParser --> AddressBookParser + deactivate DeleteDeveloperRoleCommandParser + + AddressBookParser --> LogicManager + deactivate AddressBookParser + + LogicManager -> DeleteDeveloperRoleCommand : execute() + activate DeleteDeveloperRoleCommand + + DeleteDeveloperRoleCommand -> DeveloperRoles : isRemovableRole("Tester") + activate DeveloperRoles + + DeveloperRoles --> DeleteDeveloperRoleCommand : boolean true + deactivate DeveloperRoles + + DeleteDeveloperRoleCommand -> DeveloperRoles : deleteDeveloperRole(new DeveloperRoles("Tester)) + activate DeveloperRoles + + DeveloperRoles -> DeveloperRoles : saveDeveloperRoles() + DeveloperRoles --> DeleteDeveloperRoleCommand + + DeleteDeveloperRoleCommand -> Model : commitAddressBook(model, successMessage, TabIndex) + activate Model + + Model -> VersionedAddressBook : commit() + activate VersionedAddressBook + + VersionedAddressBook --> Model + deactivate VersionedAddressBook + + Model --> DeleteDeveloperRoleCommand + deactivate Model + + create CommandResult + DeleteDeveloperRoleCommand -> CommandResult + activate CommandResult + + CommandResult --> DeleteDeveloperRoleCommand + deactivate CommandResult + + DeleteDeveloperRoleCommand --> LogicManager : result + deactivate DeleteDeveloperRoleCommand + +@enduml diff --git a/docs/diagrams/DeleteProjectSequenceDiagram.puml b/docs/diagrams/DeleteProjectSequenceDiagram.puml new file mode 100644 index 00000000000..a064002e606 --- /dev/null +++ b/docs/diagrams/DeleteProjectSequenceDiagram.puml @@ -0,0 +1,92 @@ +@startuml +!include Style.puml + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR +participant ":DeleteProjectCommandParser" as DeleteProjectCommandParser LOGIC_COLOR +participant "d:DeleteProjectCommand" as DeleteProjectCommand LOGIC_COLOR +participant ":CommandResult" as CommandResult LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant ":ModelManager" as Model MODEL_COLOR +participant ":AddressBook" as AddressBook MODEL_COLOR +participant ":UniqueProjectList" as UniqueProjectList MODEL_COLOR +participant ":UniqueClientList" as UniqueClientList MODEL_COLOR +participant ":UniqueDeveloperList" as UniqueDeveloperList MODEL_COLOR +end box + +[-> LogicManager : execute("delete-project 2") +activate LogicManager + +LogicManager -> AddressBookParser : parseCommand("delete-project 2") +activate AddressBookParser + +create DeleteProjectCommandParser +AddressBookParser -> DeleteProjectCommandParser +activate DeleteProjectCommandParser + +DeleteProjectCommandParser --> AddressBookParser +deactivate DeleteProjectCommandParser + +AddressBookParser -> DeleteProjectCommandParser : parse("2") +activate DeleteProjectCommandParser + +create DeleteProjectCommand +DeleteProjectCommandParser -> DeleteProjectCommand +activate DeleteProjectCommand + +DeleteProjectCommand --> DeleteProjectCommandParser : d +deactivate DeleteProjectCommand + +DeleteProjectCommandParser --> AddressBookParser : d +deactivate DeleteProjectCommandParser +'Hidden arrow to position the destroy marker below the end of the activation bar. +DeleteProjectCommandParser -[hidden]-> AddressBookParser +destroy DeleteProjectCommandParser + +AddressBookParser --> LogicManager : d +deactivate AddressBookParser + +LogicManager -> DeleteProjectCommand : execute() +activate DeleteProjectCommand + +DeleteProjectCommand -> Model : deleteProject(2) +activate Model + +Model -> AddressBook : removeProject(2) +activate AddressBook + +AddressBook -> UniqueProjectList : remove(2) +activate UniqueProjectList +UniqueProjectList --> AddressBook +deactivate UniqueProjectList +AddressBook -> UniqueClientList : updateClientProjects("2") +activate UniqueClientList +UniqueClientList --> AddressBook +deactivate UniqueClientList +AddressBook -> UniqueDeveloperList : updateDeveloperProjects("2") +activate UniqueDeveloperList +UniqueDeveloperList --> AddressBook +deactivate UniqueDeveloperList +AddressBook --> Model +deactivate AddressBook + + +Model --> DeleteProjectCommand +deactivate Model + +create CommandResult +DeleteProjectCommand -> CommandResult +activate CommandResult + +CommandResult --> DeleteProjectCommand +deactivate CommandResult + +DeleteProjectCommand --> LogicManager : result +deactivate DeleteProjectCommand + +[<--LogicManager +deactivate LogicManager +@enduml diff --git a/docs/diagrams/EditDeveloperSequenceDiagram.puml b/docs/diagrams/EditDeveloperSequenceDiagram.puml new file mode 100644 index 00000000000..5b279d820a5 --- /dev/null +++ b/docs/diagrams/EditDeveloperSequenceDiagram.puml @@ -0,0 +1,76 @@ +@startuml +!include style.puml +skinparam ArrowFontStyle plain + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR +participant ":EditDeveloperCommandParser" as EditDeveloperCommandParser LOGIC_COLOR +participant "d:EditDeveloperCommand" as EditDeveloperCommand 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("edit-developer 1 pr/AppleApp") +activate LogicManager + +LogicManager -> AddressBookParser : parseCommand("edit-developer 1 pr/AppleApp") +activate AddressBookParser + +create EditDeveloperCommandParser +AddressBookParser -> EditDeveloperCommandParser +activate EditDeveloperCommandParser + +EditDeveloperCommandParser --> AddressBookParser +deactivate EditDeveloperCommandParser + +AddressBookParser -> EditDeveloperCommandParser : parse("1 pr/AppleApp") +activate EditDeveloperCommandParser + +create EditDeveloperCommand +EditDeveloperCommandParser -> EditDeveloperCommand +activate EditDeveloperCommand + +EditDeveloperCommand --> EditDeveloperCommandParser : d +deactivate EditDeveloperCommand + +EditDeveloperCommandParser --> AddressBookParser : d +deactivate EditDeveloperCommandParser +'Hidden arrow to position the destroy marker below the end of the activation bar. +EditDeveloperCommandParser -[hidden]-> AddressBookParser +destroy EditDeveloperCommandParser + +AddressBookParser --> LogicManager : d +deactivate AddressBookParser + +LogicManager -> EditDeveloperCommand : execute() +activate EditDeveloperCommand + +EditDeveloperCommand -> Model : areProjectsValid(editedDeveloper) +activate Model + +Model --> EditDeveloperCommand +deactivate Model + +EditDeveloperCommand -> Model : setDeveloper(developerToEdit, editedDeveloper) +activate Model + +Model --> EditDeveloperCommand +deactivate Model + +create CommandResult +EditDeveloperCommand -> CommandResult +activate CommandResult + +CommandResult --> EditDeveloperCommand +deactivate CommandResult + +EditDeveloperCommand --> LogicManager : result +deactivate EditDeveloperCommand + +[<--LogicManager +deactivate LogicManager +@enduml diff --git a/docs/diagrams/FindDeveloperSequenceDiagram.puml b/docs/diagrams/FindDeveloperSequenceDiagram.puml new file mode 100644 index 00000000000..f5e9dcd8735 --- /dev/null +++ b/docs/diagrams/FindDeveloperSequenceDiagram.puml @@ -0,0 +1,70 @@ +@startuml +!include style.puml +skinparam ArrowFontStyle plain + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR +participant ":FindCommandParser" as FindCommandParser LOGIC_COLOR +participant "d:FindCommand" as FindCommand 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("find-developer n/Alice") +activate LogicManager + +LogicManager -> AddressBookParser : parseCommand("find-developer n/Alice") +activate AddressBookParser + +create FindCommandParser +AddressBookParser -> FindCommandParser +activate FindCommandParser + +FindCommandParser --> AddressBookParser +deactivate FindCommandParser + +AddressBookParser -> FindCommandParser : parse("Alice") +activate FindCommandParser + +create FindCommand +FindCommandParser -> FindCommand +activate FindCommand + +FindCommand --> FindCommandParser : d +deactivate FindCommand + +FindCommandParser --> AddressBookParser : d +deactivate FindCommandParser +'Hidden arrow to position the destroy marker below the end of the activation bar. +FindCommandParser -[hidden]-> AddressBookParser +destroy FindCommandParser + +AddressBookParser --> LogicManager : d +deactivate AddressBookParser + +LogicManager -> FindCommand : execute() +activate FindCommand + +FindCommand -> Model : FindDeveloper(1) +activate Model + +Model --> FindCommand +deactivate Model + +create CommandResult +FindCommand -> CommandResult +activate CommandResult + +CommandResult --> FindCommand +deactivate CommandResult + +FindCommand --> LogicManager : result +deactivate FindCommand + +[<--LogicManager +deactivate LogicManager +@enduml diff --git a/docs/diagrams/ImportDeveloperSequenceDiagram.puml b/docs/diagrams/ImportDeveloperSequenceDiagram.puml new file mode 100644 index 00000000000..1c05d959616 --- /dev/null +++ b/docs/diagrams/ImportDeveloperSequenceDiagram.puml @@ -0,0 +1,84 @@ +@startuml +!include style.puml +skinparam ArrowFontStyle plain + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR +participant ":ImportDeveloperCommandParser" as ImportDeveloperCommandParser LOGIC_COLOR +participant "d:ImportDeveloperCommand" as ImportDeveloperCommand LOGIC_COLOR +participant ":AddDeveloperCommand" as AddDeveloperCommand 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\n("import-developer\n developers.csv") +activate LogicManager + +LogicManager -> AddressBookParser : parseCommand\n("import-developer\n developers.csv") +activate AddressBookParser + +create ImportDeveloperCommandParser +AddressBookParser -> ImportDeveloperCommandParser +activate ImportDeveloperCommandParser + +ImportDeveloperCommandParser --> AddressBookParser +deactivate ImportDeveloperCommandParser + +AddressBookParser -> ImportDeveloperCommandParser : parse\n("Developers.csv") +activate ImportDeveloperCommandParser + +create ImportDeveloperCommand +ImportDeveloperCommandParser -> ImportDeveloperCommand +activate ImportDeveloperCommand + +ImportDeveloperCommand --> ImportDeveloperCommandParser : d +deactivate ImportDeveloperCommand + +ImportDeveloperCommandParser --> AddressBookParser : d +deactivate ImportDeveloperCommandParser +'Hidden arrow to position the destroy marker below the end of the activation bar. +ImportDeveloperCommandParser -[hidden]-> AddressBookParser +destroy ImportDeveloperCommandParser + +AddressBookParser --> LogicManager : d +deactivate AddressBookParser + +LogicManager -> ImportDeveloperCommand : execute() +activate ImportDeveloperCommand +loop for each row in csv + create AddDeveloperCommand + ImportDeveloperCommand -> AddDeveloperCommand + activate AddDeveloperCommand + AddDeveloperCommand --> ImportDeveloperCommand + deactivate AddDeveloperCommand + ImportDeveloperCommand -> AddDeveloperCommand : execute() + activate AddDeveloperCommand + AddDeveloperCommand -> Model : addDeveloper() + activate Model + Model --> AddDeveloperCommand + deactivate Model + create CommandResult + AddDeveloperCommand -> CommandResult + activate CommandResult + CommandResult --> AddDeveloperCommand + deactivate CommandResult + AddDeveloperCommand --> ImportDeveloperCommand : result + deactivate AddDeveloperCommand +end + +create CommandResult + ImportDeveloperCommand -> CommandResult + activate CommandResult + CommandResult --> ImportDeveloperCommand + deactivate CommandResult + +ImportDeveloperCommand --> LogicManager : result +deactivate ImportDeveloperCommand + +[<--LogicManager +deactivate LogicManager +@enduml diff --git a/docs/diagrams/ListDeveloperCommand.puml b/docs/diagrams/ListDeveloperCommand.puml new file mode 100644 index 00000000000..3c32aec9cd1 --- /dev/null +++ b/docs/diagrams/ListDeveloperCommand.puml @@ -0,0 +1,56 @@ + + @startuml + !include style.puml + skinparam ArrowFontStyle plain + + box Logic LOGIC_COLOR_T1 + participant ":LogicManager" as LogicManager LOGIC_COLOR + participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR + participant "d :ListDeveloperCommand" as ListDeveloperCommand LOGIC_COLOR + participant ":CommandResult" as CommandResult LOGIC_COLOR + end box + + box Model MODEL_COLOR_T1 + participant ":Model" as Model MODEL_COLOR + end box + + [-> LogicManager : execute("list-developer") + activate LogicManager + + LogicManager -> AddressBookParser : parseCommand("list-developer") + activate AddressBookParser + + create ListDeveloperCommand + AddressBookParser -> ListDeveloperCommand + activate ListDeveloperCommand + + ListDeveloperCommand --> AddressBookParser : d + deactivate ListDeveloperCommand + + AddressBookParser --> LogicManager : d + deactivate AddressBookParser + + LogicManager -> ListDeveloperCommand : execute() + activate ListDeveloperCommand + + ListDeveloperCommand -> Model : updateFilteredDeveloperList(PREDICATE_SHOW_ALL_DEVELOPERS) + activate Model + + Model --> ListDeveloperCommand + deactivate Model + + create CommandResult + ListDeveloperCommand -> CommandResult + activate CommandResult + + CommandResult --> ListDeveloperCommand + deactivate CommandResult + + ListDeveloperCommand --> LogicManager : result + deactivate ListDeveloperCommand + + [<--LogicManager + deactivate LogicManager + + @enduml + diff --git a/docs/diagrams/MarkDeadlineSequenceDiagram.puml b/docs/diagrams/MarkDeadlineSequenceDiagram.puml new file mode 100644 index 00000000000..4af6f64ed20 --- /dev/null +++ b/docs/diagrams/MarkDeadlineSequenceDiagram.puml @@ -0,0 +1,105 @@ +@startuml +!include style.puml +skinparam ArrowFontStyle plain + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR +participant ":MarkDeadlineCommandParser" as MarkDeadlineCommandParser LOGIC_COLOR +participant "d:MarkDeadlineCommand" as MarkDeadlineCommand LOGIC_COLOR +participant ":EditProjectCommandParser" as EditProjectCommandParser LOGIC_COLOR +participant "d:EditProjectCommand" as EditProjectCommand 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("mark-deadline 1 2") +activate LogicManager + +LogicManager -> AddressBookParser : parseCommand("mark-deadline 1 2") +activate AddressBookParser + +create MarkDeadlineCommandParser +AddressBookParser -> MarkDeadlineCommandParser +activate MarkDeadlineCommandParser + +MarkDeadlineCommandParser --> AddressBookParser +deactivate MarkDeadlineCommandParser + +AddressBookParser -> MarkDeadlineCommandParser : parse("1 2") +activate MarkDeadlineCommandParser + +create MarkDeadlineCommand +MarkDeadlineCommandParser -> MarkDeadlineCommand +activate MarkDeadlineCommand + +MarkDeadlineCommand --> MarkDeadlineCommandParser : d +deactivate MarkDeadlineCommand + +MarkDeadlineCommandParser --> AddressBookParser : d +deactivate MarkDeadlineCommandParser +'Hidden arrow to position the destroy marker below the end of the activation bar. +MarkDeadlineCommandParser -[hidden]-> AddressBookParser +destroy MarkDeadlineCommandParser + +AddressBookParser --> LogicManager : d +deactivate AddressBookParser + +LogicManager -> MarkDeadlineCommand : execute() +activate MarkDeadlineCommand + +create EditProjectCommandParser +MarkDeadlineCommand -> EditProjectCommandParser +activate EditProjectCommandParser + +EditProjectCommandParser --> MarkDeadlineCommand +deactivate EditProjectCommandParser + +MarkDeadlineCommand -> EditProjectCommandParser : parse("1 dl/19-12-2023,Design backend,HIGH,1") +activate EditProjectCommandParser + +create EditProjectCommand +EditProjectCommandParser -> EditProjectCommand +activate EditProjectCommand + +EditProjectCommand --> EditProjectCommandParser : c +deactivate EditProjectCommand + +EditProjectCommandParser --> MarkDeadlineCommand : c +deactivate EditProjectCommandParser +'Hidden arrow to position the destroy marker below the end of the activation bar. +EditProjectCommandParser -[hidden]-> MarkDeadlineCommand +destroy EditProjectCommandParser + +MarkDeadlineCommand -> EditProjectCommand : execute() +activate EditProjectCommand + +EditProjectCommand -> Model : setProject(projectToEdit, editedProject) +activate Model + +Model --> EditProjectCommand +deactivate Model + +EditProjectCommand --> MarkDeadlineCommand +deactivate EditProjectCommand +'Hidden arrow to position the destroy marker below the end of the activation bar. +EditProjectCommand -[hidden]-> MarkDeadlineCommand +destroy EditProjectCommand + +create CommandResult +MarkDeadlineCommand -> CommandResult +activate CommandResult + +CommandResult --> MarkDeadlineCommand +deactivate CommandResult + +MarkDeadlineCommand --> LogicManager : result +deactivate MarkDeadlineCommand + +[<--LogicManager +deactivate LogicManager + +@enduml diff --git a/docs/diagrams/ModelClassDiagram.puml b/docs/diagrams/ModelClassDiagram.puml index 0de5673070d..e1f9474b08c 100644 --- a/docs/diagrams/ModelClassDiagram.puml +++ b/docs/diagrams/ModelClassDiagram.puml @@ -11,18 +11,31 @@ Class "<>\nModel" as Model Class AddressBook Class ModelManager Class UserPrefs - -Class UniquePersonList -Class Person -Class Address -Class Email -Class Name -Class Phone -Class Tag +Package Person as PersonPackage <>{ +Package Developer as DeveloperPackage <>{ +Class UniqueDeveloperList +Class Developer +} +Package Client as ClientPackage <>{ +Class UniqueClientList +Class Client +} +} +Package Project as ProjectPackage <>{ +Class UniqueProjectList +Class Project +} +'Class Address +''Class Email +'Class Name +'Class Phone +'Class Tag Class I #FFFFFF } + + Class HiddenOutside #FFFFFF HiddenOutside ..> Model @@ -35,20 +48,27 @@ ModelManager -left-> "1" AddressBook ModelManager -right-> "1" UserPrefs UserPrefs .up.|> ReadOnlyUserPrefs -AddressBook *--> "1" UniquePersonList -UniquePersonList --> "~* all" Person -Person *--> Name -Person *--> Phone -Person *--> Email -Person *--> Address -Person *--> "*" Tag +AddressBook *--> "1" UniqueDeveloperList +UniqueDeveloperList --> "~* all" Developer +AddressBook *--> "1" UniqueClientList +UniqueClientList --> "~* all" Client +AddressBook *--> "1" UniqueProjectList +UniqueProjectList --> "~* all" Project +'Person *--> Name +'Person *--> Phone +'Person *--> Email +'Person *--> Address +'Person *--> "*" Tag -Person -[hidden]up--> I -UniquePersonList -[hidden]right-> I +Developer -[hidden]up--> I +UniqueDeveloperList -[hidden]right-> I +UniqueProjectList -[hidden]up-> UserPrefs -Name -[hidden]right-> Phone -Phone -[hidden]right-> Address -Address -[hidden]right-> Email +'Name -[hidden]right-> Phone +'Phone -[hidden]right-> Address +'Address -[hidden]right-> Email -ModelManager --> "~* filtered" Person +ModelManager --> "~* filtered Developers" Developer +ModelManager --> "~* filtered Clients" Client +ModelManager --> "~* filtered Projects" Project @enduml diff --git a/docs/diagrams/ModelClassDiagram2.puml b/docs/diagrams/ModelClassDiagram2.puml new file mode 100644 index 00000000000..383e778d12d --- /dev/null +++ b/docs/diagrams/ModelClassDiagram2.puml @@ -0,0 +1,74 @@ +@startuml +!include style.puml +skinparam arrowThickness 1.1 +skinparam arrowColor MODEL_COLOR +skinparam classBackgroundColor MODEL_COLOR + +Package Person as PersonPackage <>{ +Class Person + + +Class Name +Class Phone +Class Email +Class Address +Class Projects + +Package Developer as DeveloperPackage <>{ +Class Developer + +Class Salary +Class DateJoined +Class GithubId +Class Rating +Class DeveloperRoles +} +Package Client as ClientPackage <>{ +Class Client + +Class Organisation +Class Document +Class ClientRoles +} +} + +Package Project as ProjectPackage <>{ +Class Project + +Class ProjectName +Class ProjectDeadline +Class ProjectDescription +} + +Person *--> Name +Person *--> Phone +Person *--> Email +Person *--> Address +Person *-->"*"Projects + +Client -up-|> Person +Developer -up-|> Person +Developer -[hidden]up->Name +Client -[hidden]up->Name + +Developer *-->Salary +Developer *-->DateJoined +Developer *-->GithubId +Developer *-->Rating +Developer *-->"*"DeveloperRoles + +Client *-->Organisation +Client *-->Document +Client *-->"*"ClientRoles + +Project -[hidden]up->Rating +Project -[hidden]up->Document +Project *-->ProjectName +Project *-->ProjectDeadline +Project *-->ProjectDescription + +'Name -[hidden]right-> Phone +'Phone -[hidden]right-> Address +'Address -[hidden]right-> Email + +@enduml diff --git a/docs/diagrams/StorageClassDiagram.puml b/docs/diagrams/StorageClassDiagram.puml index a821e06458c..4825beefc7e 100644 --- a/docs/diagrams/StorageClassDiagram.puml +++ b/docs/diagrams/StorageClassDiagram.puml @@ -18,8 +18,9 @@ package "AddressBook Storage" #F4F6F6{ Class "<>\nAddressBookStorage" as AddressBookStorage Class JsonAddressBookStorage Class JsonSerializableAddressBook -Class JsonAdaptedPerson -Class JsonAdaptedTag +Class JsonAdaptedDeveloper +Class JsonAdaptedClient +Class JsonAdaptedProject } } @@ -37,7 +38,8 @@ Storage -right-|> AddressBookStorage JsonUserPrefsStorage .up.|> UserPrefsStorage JsonAddressBookStorage .up.|> AddressBookStorage JsonAddressBookStorage ..> JsonSerializableAddressBook -JsonSerializableAddressBook --> "*" JsonAdaptedPerson -JsonAdaptedPerson --> "*" JsonAdaptedTag +JsonSerializableAddressBook --> "*" JsonAdaptedDeveloper +JsonSerializableAddressBook --> "*" JsonAdaptedClient +JsonSerializableAddressBook --> "*" JsonAdaptedProject @enduml diff --git a/docs/diagrams/UiClassDiagram.puml b/docs/diagrams/UiClassDiagram.puml index 95473d5aa19..66371dea5cc 100644 --- a/docs/diagrams/UiClassDiagram.puml +++ b/docs/diagrams/UiClassDiagram.puml @@ -11,8 +11,12 @@ Class UiManager Class MainWindow Class HelpWindow Class ResultDisplay -Class PersonListPanel -Class PersonCard +Class DeveloperListPanel +Class DeveloperCard +Class ClientListPanel +Class ClientCard +Class ProjectListPanel +Class ProjectCard Class StatusBarFooter Class CommandBox } @@ -32,26 +36,39 @@ UiManager .left.|> Ui UiManager -down-> "1" MainWindow MainWindow *-down-> "1" CommandBox MainWindow *-down-> "1" ResultDisplay -MainWindow *-down-> "1" PersonListPanel +MainWindow *-down-> "1" DeveloperListPanel +MainWindow *-down-> "1" ClientListPanel +MainWindow *-down-> "1" ProjectListPanel MainWindow *-down-> "1" StatusBarFooter MainWindow --> "0..1" HelpWindow -PersonListPanel -down-> "*" PersonCard + +DeveloperListPanel -down-> "*" DeveloperCard +ClientListPanel -down-> "*" ClientCard +ProjectListPanel -down-> "*" ProjectCard MainWindow -left-|> UiPart ResultDisplay --|> UiPart CommandBox --|> UiPart -PersonListPanel --|> UiPart -PersonCard --|> UiPart +DeveloperListPanel --|> UiPart +DeveloperCard --|> UiPart +ClientListPanel --|> UiPart +ClientCard --|> UiPart +ProjectListPanel --|> UiPart +ProjectCard --|> UiPart StatusBarFooter --|> UiPart HelpWindow --|> UiPart -PersonCard ..> Model +DeveloperCard ..> Model +ClientCard ..> Model +ProjectCard ..> Model UiManager -right-> Logic MainWindow -left-> Logic -PersonListPanel -[hidden]left- HelpWindow +ProjectListPanel -[hidden]left- ClientListPanel +ClientListPanel -[hidden]left- DeveloperListPanel +DeveloperListPanel -[hidden]left- HelpWindow HelpWindow -[hidden]left- CommandBox CommandBox -[hidden]left- ResultDisplay ResultDisplay -[hidden]left- StatusBarFooter diff --git a/docs/diagrams/UndoRedoActivityDiagram.puml b/docs/diagrams/UndoRedoActivityDiagram.puml new file mode 100644 index 00000000000..3ec83d497b3 --- /dev/null +++ b/docs/diagrams/UndoRedoActivityDiagram.puml @@ -0,0 +1,37 @@ +@startuml +'https://plantuml.com/activity-diagram-beta + +start +:undo +:new page; +if (Page.onSecurityCheck) then (true) + :Page.onInit(); + if (isForward?) then (no) + :Process controls; + if (continue processing?) then (no) + stop + endif + + if (isPost?) then (yes) + :Page.onPost(); + else (no) + :Page.onGet(); + endif + :Page.onRender(); + endif +else (false) +endif + +if (do redirect?) then (yes) + :redirect process; +else + if (do forward?) then (yes) + :Forward request; + else (no) + :Render page template; + endif +endif + +stop + +@enduml diff --git a/docs/diagrams/UndoSequenceDiagram.puml b/docs/diagrams/UndoSequenceDiagram.puml index 87ff3e9237e..2f8a4cd0d56 100644 --- a/docs/diagrams/UndoSequenceDiagram.puml +++ b/docs/diagrams/UndoSequenceDiagram.puml @@ -37,7 +37,7 @@ activate Model Model -> VersionedAddressBook : undo() activate VersionedAddressBook -VersionedAddressBook -> VersionedAddressBook :resetData(ReadOnlyAddressBook) +VersionedAddressBook -> VersionedAddressBook :changeAddressBook(model) VersionedAddressBook --> Model : deactivate VersionedAddressBook diff --git a/docs/diagrams/isRemovableRole.puml b/docs/diagrams/isRemovableRole.puml new file mode 100644 index 00000000000..042c49335fc --- /dev/null +++ b/docs/diagrams/isRemovableRole.puml @@ -0,0 +1,23 @@ +@startuml +'https://plantuml.com/activity-diagram-beta + +start +:get the full developer list; + +if (check is any developer using this role) then (there are developers using this role) + :throw developer still using error; + stop + endif + if (check is this role one of the pre-defined roles) then (is pre-defined role) + :throw cannot be deleted error; + stop + endif + if (check is this role in the list of existing roles) then (role does not exist) + :throw role does not exist error; + stop + endif + :delete role from existing list; + stop + + +@enduml diff --git a/docs/images/AddDeveloperRoleSequenceDiagram.png b/docs/images/AddDeveloperRoleSequenceDiagram.png new file mode 100644 index 00000000000..b984caf89bd Binary files /dev/null and b/docs/images/AddDeveloperRoleSequenceDiagram.png differ diff --git a/docs/images/AddDeveloperSequenceDiagram.png b/docs/images/AddDeveloperSequenceDiagram.png new file mode 100644 index 00000000000..67abbb4b76c Binary files /dev/null and b/docs/images/AddDeveloperSequenceDiagram.png differ diff --git a/docs/images/ArchitectureSequenceDiagram.png b/docs/images/ArchitectureSequenceDiagram.png index 37ad06a2803..cd6d43395f6 100644 Binary files a/docs/images/ArchitectureSequenceDiagram.png and b/docs/images/ArchitectureSequenceDiagram.png differ diff --git a/docs/images/BetterModelClassDiagram.png b/docs/images/BetterModelClassDiagram.png index 02a42e35e76..5e8a8259b59 100644 Binary files a/docs/images/BetterModelClassDiagram.png and b/docs/images/BetterModelClassDiagram.png differ diff --git a/docs/images/DeleteClientSequenceDiagram.png b/docs/images/DeleteClientSequenceDiagram.png new file mode 100644 index 00000000000..be013156949 Binary files /dev/null and b/docs/images/DeleteClientSequenceDiagram.png differ diff --git a/docs/images/DeleteDeveloperRoleSequenceDiagram.png b/docs/images/DeleteDeveloperRoleSequenceDiagram.png new file mode 100644 index 00000000000..0b2209fc66a Binary files /dev/null and b/docs/images/DeleteDeveloperRoleSequenceDiagram.png differ diff --git a/docs/images/DeleteProjectSequenceDiagram.png b/docs/images/DeleteProjectSequenceDiagram.png new file mode 100644 index 00000000000..397b8bf9c97 Binary files /dev/null and b/docs/images/DeleteProjectSequenceDiagram.png differ diff --git a/docs/images/EditDeveloperSequenceDiagram.png b/docs/images/EditDeveloperSequenceDiagram.png new file mode 100644 index 00000000000..baf790e3b57 Binary files /dev/null and b/docs/images/EditDeveloperSequenceDiagram.png differ diff --git a/docs/images/FindDeveloperSequenceDiagram.png b/docs/images/FindDeveloperSequenceDiagram.png new file mode 100644 index 00000000000..41b2db7d865 Binary files /dev/null and b/docs/images/FindDeveloperSequenceDiagram.png differ diff --git a/docs/images/ImportDeveloperSequenceDiagram.png b/docs/images/ImportDeveloperSequenceDiagram.png new file mode 100644 index 00000000000..747b30b89f0 Binary files /dev/null and b/docs/images/ImportDeveloperSequenceDiagram.png differ diff --git a/docs/images/ListDeveloperSequenceDiagram.png b/docs/images/ListDeveloperSequenceDiagram.png new file mode 100644 index 00000000000..2fea1533c48 Binary files /dev/null and b/docs/images/ListDeveloperSequenceDiagram.png differ diff --git a/docs/images/MarkDeadlineSequenceDiagram.png b/docs/images/MarkDeadlineSequenceDiagram.png new file mode 100644 index 00000000000..2fa29113637 Binary files /dev/null and b/docs/images/MarkDeadlineSequenceDiagram.png differ diff --git a/docs/images/ModelClassDiagram.png b/docs/images/ModelClassDiagram.png index a19fb1b4ac8..d6fcf8ca888 100644 Binary files a/docs/images/ModelClassDiagram.png and b/docs/images/ModelClassDiagram.png differ diff --git a/docs/images/ModelClassDiagramB.png b/docs/images/ModelClassDiagramB.png new file mode 100644 index 00000000000..71293661291 Binary files /dev/null and b/docs/images/ModelClassDiagramB.png differ diff --git a/docs/images/StorageClassDiagram.png b/docs/images/StorageClassDiagram.png index 18fa4d0d51f..fdb697a23f4 100644 Binary files a/docs/images/StorageClassDiagram.png and b/docs/images/StorageClassDiagram.png differ diff --git a/docs/images/UG UI 1.png b/docs/images/UG UI 1.png new file mode 100644 index 00000000000..cdd6eaba6fb Binary files /dev/null and b/docs/images/UG UI 1.png differ diff --git a/docs/images/UG UI 2.png b/docs/images/UG UI 2.png new file mode 100644 index 00000000000..6d42b393f34 Binary files /dev/null and b/docs/images/UG UI 2.png differ diff --git a/docs/images/Ui.png b/docs/images/Ui.png index 5bd77847aa2..966ce0f4b22 100644 Binary files a/docs/images/Ui.png and b/docs/images/Ui.png differ diff --git a/docs/images/UiClassDiagram.png b/docs/images/UiClassDiagram.png index 11f06d68671..1df9e2c7fed 100644 Binary files a/docs/images/UiClassDiagram.png and b/docs/images/UiClassDiagram.png differ diff --git a/docs/images/UndoSequenceDiagram.png b/docs/images/UndoSequenceDiagram.png index c7a7e637266..4e23d7f95d6 100644 Binary files a/docs/images/UndoSequenceDiagram.png and b/docs/images/UndoSequenceDiagram.png differ diff --git a/docs/images/addClientRole.png b/docs/images/addClientRole.png new file mode 100644 index 00000000000..634a176cae5 Binary files /dev/null and b/docs/images/addClientRole.png differ diff --git a/docs/images/addDeveloperRole.png b/docs/images/addDeveloperRole.png new file mode 100644 index 00000000000..454ea666510 Binary files /dev/null and b/docs/images/addDeveloperRole.png differ diff --git a/docs/images/companyProfile.png b/docs/images/companyProfile.png new file mode 100644 index 00000000000..26108db205b Binary files /dev/null and b/docs/images/companyProfile.png differ diff --git a/docs/images/deleteClientRole.png b/docs/images/deleteClientRole.png new file mode 100644 index 00000000000..e830a31b0c6 Binary files /dev/null and b/docs/images/deleteClientRole.png differ diff --git a/docs/images/deleteClientRoleErr.png b/docs/images/deleteClientRoleErr.png new file mode 100644 index 00000000000..df523a5407a Binary files /dev/null and b/docs/images/deleteClientRoleErr.png differ diff --git a/docs/images/deleteDeveloperRole.png b/docs/images/deleteDeveloperRole.png new file mode 100644 index 00000000000..fde53ef6e66 Binary files /dev/null and b/docs/images/deleteDeveloperRole.png differ diff --git a/docs/images/deleteDeveloperRoleErr.png b/docs/images/deleteDeveloperRoleErr.png new file mode 100644 index 00000000000..84f4c57f71d Binary files /dev/null and b/docs/images/deleteDeveloperRoleErr.png differ diff --git a/docs/images/emzm2023.png b/docs/images/emzm2023.png new file mode 100644 index 00000000000..b382803ed6a Binary files /dev/null and b/docs/images/emzm2023.png differ diff --git a/docs/images/isRemovableRole.png b/docs/images/isRemovableRole.png new file mode 100644 index 00000000000..03640755a81 Binary files /dev/null and b/docs/images/isRemovableRole.png differ diff --git a/docs/images/mahidharah.png b/docs/images/mahidharah.png new file mode 100644 index 00000000000..f878cbc1d1e Binary files /dev/null and b/docs/images/mahidharah.png differ diff --git a/docs/images/mingyu-wan.png b/docs/images/mingyu-wan.png new file mode 100644 index 00000000000..324eda9e630 Binary files /dev/null and b/docs/images/mingyu-wan.png differ diff --git a/docs/images/ncmathan.png b/docs/images/ncmathan.png new file mode 100644 index 00000000000..f6b7bffaf66 Binary files /dev/null and b/docs/images/ncmathan.png differ diff --git a/docs/images/newEmployee.png b/docs/images/newEmployee.png new file mode 100644 index 00000000000..60eed74a676 Binary files /dev/null and b/docs/images/newEmployee.png differ diff --git a/docs/images/redo.png b/docs/images/redo.png new file mode 100644 index 00000000000..629501e159e Binary files /dev/null and b/docs/images/redo.png differ diff --git a/docs/images/searchPicture.png b/docs/images/searchPicture.png new file mode 100644 index 00000000000..36ab5782dc7 Binary files /dev/null and b/docs/images/searchPicture.png differ diff --git a/docs/images/undo.png b/docs/images/undo.png new file mode 100644 index 00000000000..f9094df86bf Binary files /dev/null and b/docs/images/undo.png differ diff --git a/docs/images/updateDetails.png b/docs/images/updateDetails.png new file mode 100644 index 00000000000..349ffff5d64 Binary files /dev/null and b/docs/images/updateDetails.png differ diff --git a/docs/images/waseemingly.png b/docs/images/waseemingly.png new file mode 100644 index 00000000000..3f8a586b7de Binary files /dev/null and b/docs/images/waseemingly.png differ diff --git a/docs/index.md b/docs/index.md index 7601dbaad0d..9e15e89b118 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,17 +1,17 @@ --- layout: page -title: AddressBook Level-3 +title: CodeContact --- -[![CI Status](https://github.com/se-edu/addressbook-level3/workflows/Java%20CI/badge.svg)](https://github.com/se-edu/addressbook-level3/actions) -[![codecov](https://codecov.io/gh/se-edu/addressbook-level3/branch/master/graph/badge.svg)](https://codecov.io/gh/se-edu/addressbook-level3) +[![CI Status](https://github.com/AY2324S1-CS2103T-T09-2/tp/workflows/Java%20CI/badge.svg)](https://github.com/AY2324S1-CS2103T-T09-2/tp/actions) +[![codecov](https://app.codecov.io/gh/AY2324S1-CS2103T-T09-2/branch/master/graph/badge.svg)](https://codecov.io/gh/AY2324S1-CS2103T-T09-2/tp/tree/master) ![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). +**CodeContact is a desktop application for managing your employee details in a software company.** While it has a GUI, most of the user interactions happen using a CLI (Command Line Interface). -* If you are interested in using AddressBook, head over to the [_Quick Start_ section of the **User Guide**](UserGuide.html#quick-start). -* If you are interested about developing AddressBook, the [**Developer Guide**](DeveloperGuide.html) is a good place to start. +* If you are interested in using CodeContact, head over to the [_Quick Start_ section of the **User Guide**](UserGuide.html#quick-start). +* If you are interested about developing CodeContact, the [**Developer Guide**](DeveloperGuide.html) is a good place to start. **Acknowledgements** diff --git a/docs/team/emzm2023.md b/docs/team/emzm2023.md new file mode 100644 index 00000000000..4a4928a3c3a --- /dev/null +++ b/docs/team/emzm2023.md @@ -0,0 +1,85 @@ +--- +layout: page +title: Elizabeth's Project Portfolio Page +--- +### Project: CodeContact + +CodeContact is a desktop address book solution for Software Engineering companies. 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**: `mark-deadline` and `unmark-deadline` + * What it does: Gives users the ability to mark project deadlines as done or undone. + * Justification: One basic job of project managers is to manage deadlines, so having a mark and unmark feature + allows them to update the status of the project and project deadlines accordingly. + * Highlights: Deadline fields had to be updated to ensure that an index is saved when a deadline for a project is + created. Further methods also had to be implemented to toggle the `isDone` status of a deadline between `true` and + `false`. Additional changes to the existing GUI were made to reflect deadline numbers to allow users to mark and + unmark deadlines according to the index. This involved adding a new column to the table of deadlines that displayed + deadline indexes. +
+ + +* **Enhancement to existing features**: `edit-developer`, `edit-client`, `edit-project` + * What it does: Allows users to edit the fields of existing developers, clients, and projects. + * Justification: This gives project managers the flexibility and freedom to change and update the details if + necessary. Aside from keeping contact details accurate, this is also especially important as it allows for the + reassignment of developers to new projects and the updating of project deadlines after milestones. + * Highlights: Edits are done according to the object's index in the displayed list in the relevant tab. Making + edits to objects automatically switches the tab on the GUI to the relevant one. Various code changes to the existing + `edit` function had to be made to account for the different fields of the object to edit, as well as various + implemented restrictions on fields that could and could not be changed. The respective Parser and Command classes + were extended to account for each additional field, and thorough checks were implemented to ensure that users are + unable to access prefixes that are meant for other objects. +
+ +* **Enhancements to existing features**: + * Updated the GUI project table columns and CSS (Pull request #142) +
+ +* **Other significant code contributions**: + * Initially implemented edit command with behaviour varying in response to access rights, but undid changes due to + shift in project direction (Pull requests #59, #66) + * Implemented Project and Project relevant classes (eg. Deadline, Priority, Description) for use by team (Pull + request #73) + * Implemented project validation checks to ensure assigned projects exist in the address book (Pull request #159) + * Wrote additional tests for features to increase coverage (Pull request #249) +
+ +* **Code contributed**: [RepoSense link](https://nus-cs2103-ay2324s1.github.io/tp-dashboard/?search=emzm2023&breakdown=true) +
+ +* **Project management (team-based tasks contributions)**: + * Set up project schedule tracking with milestones and issues for `v1.1` + * Updated Developer Guide with project notes for `v1.1` + * Add screenshots of UI and managed the collaboration group document for tutorial checks + * Create and release JAR file for `v1.3` and `v1.4` + * Assign labels and assignees to bug issues in post-PED +
+ + +* **Documentation Contributions**: + * User Guide Contributions: + * Added documentation for the features add, edit and mark/unmark deadline features + (Pull requests #104, #151, #163, #263) + * Created template format for other team members to follow for their portion of the User Guide (Pull request + #151) + * Did extensive cosmetic tweaks for standardisation and fixed bugs across entire UG (Pull requests #252, #279) + * Developer Guide Contributions: + * Added all user stories and several use cases for initial iteration, but undid changes due to shift in project + direction (Pull request #35) + * Added implementation details of the features `edit-developer`, `edit-client`, `edit-project`, `mark-deadline` + , and `unmark-deadline` (Pull requests #109, #263) + * Added instructions for manual testing in Appendix (Pull request #263) + * Added planned enhancements in Appendix (Pull request #263) + * Did extensive cosmetic tweaks for standardisation and fixed bugs across entire DG (Pull request #279) +
+ +* **Community (Review/mentoring contributions)**: + * Helped to modify add command parsers with part of my implementation for edit command parsers to ensure more + thorough and bug-free parsing of user inputs (Pull request #127) + * Conducted pull request reviews for team members (Pull request #238) + +* **Contributions beyond the project team**: + * Reported bugs and suggestions for other teams in the cohort diff --git a/docs/team/johndoe.md b/docs/team/johndoe.md deleted file mode 100644 index 773a07794e2..00000000000 --- a/docs/team/johndoe.md +++ /dev/null @@ -1,46 +0,0 @@ ---- -layout: page -title: John Doe's Project Portfolio Page ---- - -### Project: AddressBook Level 3 - -AddressBook - Level 3 is a desktop address book application used for teaching Software Engineering principles. The user interacts with it using a CLI, and it has a GUI created with JavaFX. It is written in Java, and has about 10 kLoC. - -Given below are my contributions to the project. - -* **New Feature**: Added the ability to undo/redo previous commands. - * What it does: allows the user to undo all previous commands one at a time. Preceding undo commands can be reversed by using the redo command. - * Justification: This feature improves the product significantly because a user can make mistakes in commands and the app should provide a convenient way to rectify them. - * Highlights: This enhancement affects existing commands and commands to be added in future. It required an in-depth analysis of design alternatives. The implementation too was challenging as it required changes to existing commands. - * Credits: *{mention here if you reused any code/ideas from elsewhere or if a third-party library is heavily used in the feature so that a reader can make a more accurate judgement of how much effort went into the feature}* - -* **New Feature**: Added a history command that allows the user to navigate to previous commands using up/down keys. - -* **Code contributed**: [RepoSense link]() - -* **Project management**: - * Managed releases `v1.3` - `v1.5rc` (3 releases) on GitHub - -* **Enhancements to existing features**: - * Updated the GUI color scheme (Pull requests [\#33](), [\#34]()) - * Wrote additional tests for existing features to increase coverage from 88% to 92% (Pull requests [\#36](), [\#38]()) - -* **Documentation**: - * User Guide: - * Added documentation for the features `delete` and `find` [\#72]() - * Did cosmetic tweaks to existing documentation of features `clear`, `exit`: [\#74]() - * Developer Guide: - * Added implementation details of the `delete` feature. - -* **Community**: - * PRs reviewed (with non-trivial review comments): [\#12](), [\#32](), [\#19](), [\#42]() - * Contributed to forum discussions (examples: [1](), [2](), [3](), [4]()) - * Reported bugs and suggestions for other teams in the class (examples: [1](), [2](), [3]()) - * Some parts of the history feature I added was adopted by several other class mates ([1](), [2]()) - -* **Tools**: - * Integrated a third party library (Natty) to the project ([\#42]()) - * Integrated a new Github plugin (CircleCI) to the team repo - -* _{you can add/remove categories in the list above}_ diff --git a/docs/team/mahidharah.md b/docs/team/mahidharah.md new file mode 100644 index 00000000000..bff12035e8a --- /dev/null +++ b/docs/team/mahidharah.md @@ -0,0 +1,74 @@ +--- +layout: page +title: Mahidharah's Project Portfolio Page +--- + +### Project: CodeContact + +CodeContact 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, CodeContact can get your contact management tasks done faster than traditional GUI apps. + +Our product helps seamlessly integrate contact, client, and project management, simplifying access to coding-related contacts, facilitating collaboration, and offering command-line efficiency for project managers. + +Given below are my contributions to the project. + + +* **New Feature 1**: `add-developer`, `add-client` & `add-project` + * What it does: Adds a developer/client/project to the address book, making sure input parameters are valid and potential duplicates are found, highlighted to the user and rejected. + * Justification: Decided to separate these as different commands instead of having a single `add` command to allow for more flexibility with parameters changes to these features in the future (from basic OOP principles). This also allows for more specific error messages to be displayed to users when they input invalid parameters, utilising polymorphism. + * Highlights: Allows specific optional fields (i.e Project deadline). Automatically switches tabs according to command. + + + +* **New Feature 2**: `delete-developer` & `delete-client` + * What it does: Deletes a developer/client from the address book. + * Justification: Decided to separate these as different commands instead of having a single `delete` command to allow for more flexibility with parameters changes to these features in the future (from basic OOP principles). This also allows for more specific error messages to be displayed to users when they input invalid parameters, utilising polymorphism. + * Highlights: Automatically switches tabs according to command. + +
+ +* **New Feature 3**: `delete-project` + * What it does: Deletes a project from the address book and accordingly removes it from the set of projects for all developers and clients. + * Justification: I decided to implement the feature such that it triggers updates on developers and clients with hopes that this feature will be a major quality of life improvement for project managers, enabling them to remove projects easily without having to edit the information for relevant clients and developers to maintain integrity of the data. + * Highlights: Automatically switches tabs according to command. This feature uses the iterator java class and its for each remaining method effectively to simplify code and ensure efficiency. + + +* **Enhancements to existing features**: + * Contributed to the GUI component of CodeContact by creating relevant classes and FXML files for developer and client cards (Pull request [\#79](https://github.com/AY2324S1-CS2103T-T09-2/tp/pull/79)) + * Ensured functional improvements use developer and client classes instead of person class (Pull request [\#79](https://github.com/AY2324S1-CS2103T-T09-2/tp/pull/79)) + * Edited SampleData to include sample developers, clients and projects for easy testing for teammates (Pull request [\#79](https://github.com/AY2324S1-CS2103T-T09-2/tp/pull/79)) + + +* **Testing**: + * Enabled test files to work for other team members and contributed greatly to testcode + * Wrote testcases for model, logic and storage, and edited necessary test utility files to account for functional developments of CodeContact, speciafically developer and client features (Pull request [\#216](https://github.com/AY2324S1-CS2103T-T09-2/tp/pull/216), [\#222](https://github.com/AY2324S1-CS2103T-T09-2/tp/pull/222), [\#236](https://github.com/AY2324S1-CS2103T-T09-2/tp/pull/236)) + * Wrote testcases for my functional contributions to Codecontact. (Pull request [\#216](https://github.com/AY2324S1-CS2103T-T09-2/tp/pull/216), [\#236](https://github.com/AY2324S1-CS2103T-T09-2/tp/pull/222), [\#236](https://github.com/AY2324S1-CS2103T-T09-2/tp/pull/236)) + + +* **Code contributed**: [RepoSense link](https://nus-cs2103-ay2324s1.github.io/tp-dashboard/?search=&sort=groupTitle&sortWithin=title&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=true&checkedFileTypes=docs~functional-code~test-code&since=2023-09-22&tabOpen=true&tabType=authorship&zFR=false&tabAuthor=Mahidharah&tabRepo=AY2324S1-CS2103T-T09-2%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code&authorshipIsBinaryFileTypeChecked=false&authorshipIsIgnoredFilesChecked=false) + + +* **Project management(team-based tasks contributions)**: + * Responsible for the timely pivot of our team project in v1.2 after checking in with tutors and professors + * Spearheaded the pivot by suggesting a new idea and directing the team to a new project which effectively carried over previous functional contributions + * Actively contributed during team meetings and reminded team members of upcoming deadlines + +
+ +* **Documentation Contributions**: + * User Guide Contributions: + * Added documentation for the features `delete-developer`, `delete-client`, `delete-project`, `add-developer`, `add-client`, `add-project` + * Did cosmetic tweaks to existing documentation of feature list table + * Developer Guide Contributions: + * Added implementation details of the `delete` and `add` features + * Added PUML sequence diagrams for these implementations + + +* **Community (Review/mentoring contributions)**: + * PRs reviewed (with non-trivial review comments): [\#155](https://github.com/AY2324S1-CS2103T-T09-2/tp/pull/155), [\##219](https://github.com/AY2324S1-CS2103T-T09-2/tp/pull/219) + * Reported bugs and suggested ideal implementations for features for teammates + * Some parts of the delete-project feature I added was adopted by team members and classmates, for instance the following implementation of validation checks for projects + + +* **Tools**: + * Capitalised on GitHub copilot autocomplete features in creating testcases, enabling me to cover the most amount of functional code in testcases diff --git a/docs/team/mingyu-wan.md b/docs/team/mingyu-wan.md new file mode 100644 index 00000000000..91bbc1ec046 --- /dev/null +++ b/docs/team/mingyu-wan.md @@ -0,0 +1,76 @@ +--- +layout: page +title: Mingyu's Project Portfolio Page +--- + +### Project: CodeContact + +CodeContact is a desktop address book solution for Software Engineering companies. 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 1**: `Undo` n `Redo` + * What it does:
Allows users to undo and redo their command easily. + * Justification:
This feature enhances user experience by allowing effortless reversal of previous actions, + providing convenience in scenarios where unintended changes are made. + * Highlights:
The undo and redo feature is unlimited, allowing users to revert all the way to their first or last command using + the simple `undo` or `redo` command, ensuring ease of use. + * Credits:
Inspired by the AB-3 developer guide, this feature employs a pointer to change the address book at each undo or redo command. + Additional validation checks were added for CodeContact's complexity. +
+ +* **New Feature 2**: `Adding roles ` + * What it does:
Allows users to add different roles. + * Justification:
This feature minimizes risks associated with incorrect role assignments by allowing explicit +definition and addition of developer or client roles. It ensures a standardized set of role names, +reducing the chance of mislabeling and improving searchability within CodeContact. + * Highlights:
The feature provides separate commands (add-developer-role and add-client-role) for adding developers and clients, +adding clarity to the role assignment process. A pre-added list of roles for clients and developers facilitates user adoption, and validation checks prevent the addition of repeated roles. + * Credits: N.A. +
+ +* **New Feature 3**: `Deleting roles ` + * What it does:
Allows users to delete different roles. + * Justification:
This CodeContact feature is crucial for user data management, enabling users to refine and update the list of roles. + Deleting roles is essential for maintaining a clean and relevant role structure, empowering users to adapt roles to evolving needs and ensuring accuracy in the address book. + * Highlights:
Separate commands (delete-developer-role and delete-client-role) cater to the unique needs of deleting developers and clients. + To prevent accidental deletion when roles are in use, the feature checks for active developers and clients before deletion. Users cannot delete pre-added roles from CodeContact. + * Credits: N.A. +
+ +* **Enhancements to existing features**: + * Modified list feature to work with our new tab and fields (Pull requests [\#113](https://github.com/AY2324S1-CS2103T-T09-2/tp/pull/113)) + * Fixed parts of the test cases to pass again (Pull requests [\#225](https://github.com/AY2324S1-CS2103T-T09-2/tp/pull/225)) + * Increased code coverage from `32.28%` to `38%` and from `48.9%` to `54.39%`.(Pull requests [\#250](https://github.com/AY2324S1-CS2103T-T09-2/tp/pull/250) + [\#241](https://github.com/AY2324S1-CS2103T-T09-2/tp/pull/241)) +
+ +* **Code contributed**: [RepoSense link](https://nus-cs2103-ay2324s1.github.io/tp-dashboard/?search=mingyu&sort=groupTitle&sortWithin=title&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=true&checkedFileTypes=docs~functional-code~test-code&since=2023-09-22&tabOpen=true&tabType=zoom&zA=mingyu-wan&zR=AY2324S1-CS2103T-T09-2%2Ftp%5Bmaster%5D&zACS=241.3&zS=2023-09-22&zFS=mingyu&zU=2023-11-10&zMG=false&zFTF=commit&zFGS=groupByRepos&zFR=false) +
+ +* **Project management(team-based tasks contributions)**: + * Managed releases `v1.1` - `v1.4` (4 releases) on GitHub + * Managed issues creating and some assigning throughout the milestones + * Managed the collaboration document and product demo submissions for tutorial +
+ +* **Documentation Contributions**: + * User Guide Contributions: + * Removed traces of AB-3: [\#50](https://github.com/AY2324S1-CS2103T-T09-2/tp/pull/50) + * Added documentation for the features `undo`,`redo`,`add-role` and `delete-role`: [\#160](https://github.com/AY2324S1-CS2103T-T09-2/tp/pull/160) + * Did cosmetic tweaks to existing documentation of features `list`: [\#160](https://github.com/AY2324S1-CS2103T-T09-2/tp/pull/160) + * Wrote the content for `tutorial`: [\#162](https://github.com/AY2324S1-CS2103T-T09-2/tp/pull/162) + * Modified and content and UI pictures for `Quick Start`: [\#162](https://github.com/AY2324S1-CS2103T-T09-2/tp/pull/162) + * Wrote the content for `Navigating the GUI` and `Format`: [\#242](https://github.com/AY2324S1-CS2103T-T09-2/tp/pull/242) + * Developer Guide Contributions: + * Added use case (abandoned due to a shift in project): [\#43](https://github.com/AY2324S1-CS2103T-T09-2/tp/pull/43) + * Reformatted entire DG format: [\#244](https://github.com/AY2324S1-CS2103T-T09-2/tp/pull/244) + * Wrote user stories and use case: [\#246](https://github.com/AY2324S1-CS2103T-T09-2/tp/pull/246) [\#247](https://github.com/AY2324S1-CS2103T-T09-2/tp/pull/247) + * Wrote implementations, sequence and activity diagrams for features i implemented: [\#246](https://github.com/AY2324S1-CS2103T-T09-2/tp/pull/246) + * Wrote in appendix: planned enhancements: [\#247](https://github.com/AY2324S1-CS2103T-T09-2/tp/pull/246) +
+ +* **Community (Review/mentoring contributions)**: + * Reported bugs and suggestions for other teams in the cohort +
diff --git a/docs/team/ncmathan.md b/docs/team/ncmathan.md new file mode 100644 index 00000000000..dc3af462895 --- /dev/null +++ b/docs/team/ncmathan.md @@ -0,0 +1,83 @@ +--- +layout: page +title: Mathan's Project Portfolio Page +--- + +### Project: CodeContact + +CodeContact is a desktop address book solution for Software Engineering companies. 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 1**: `import-developer` and `import-client` + * What it does:
Gives users the ability to import developers and clients from a CSV file. + * Justification:
Allows users to easily import a large number of developers and clients into the address book. This + is especially useful for companies that have a large number of employees and those who are new to CodeContact porting over from other address book applications. + * Highlights:
The feature is able to handle a large number of developers and clients, and is able to handle + errors such as invalid CSV files and invalid data in the CSV file. + + +* **New Feature 2**: `lock` and `unlock` + * What it does:
Gives users the ability to lock and unlock the application. + * Justification:
Allows users to lock the application when they are away from their computer, preventing + unauthorised access to the application. This is especially useful since the information stored on the application contains personal data of developers and clients, that has to be kept secure. + * Highlights:
The password is stored in a hashed format, preventing anyone from accessing the password file to view it directly. The application is also locked when the user closes the application. + + +* **New Feature 3**: `change-password` + * What it does:
Gives users the ability to change their password. + * Justification:
Allows users to change their password when they feel that their password has been compromised. This is especially useful since the information stored on the application contains personal data of developers and clients, that has to be kept secure. + * Highlights:
The password is only changed if the current password is correct, new password follows the password requirements and the new password is different from the current password. + * Credits:[Password requirements] (https://support.kaspersky.com/KPC/1.0/en-US/183862.htm) + +* **New Feature 4**: `find-deadline` + * What it does:
Gives users the ability to filter deadlines by date and priority. + * Justification:
Allows users to easily find deadlines that are due before or on a certain date, or deadlines that are of a certain priority. This is especially useful for project managers who need to keep track of deadlines. + * Highlights:
The feature is able to handle a large number of deadlines, and is able to handle errors such as invalid dates and invalid priority. + * Credits: Followed a similar structure as storing filtered lists in `ModelManager` but one level lower at the individual project level to store filtered deadlines. + + + + +* **Enhancements to existing features**: + * Update Logic and Model for Person to Developer, Client and Project (Pull request [\#92](https://github.com/AY2324S1-CS2103T-T09-2/tp/pull/92)) + * Implemented Tab Functionality (Pull request [\#96](https://github.com/AY2324S1-CS2103T-T09-2/tp/pull/96)) + * Updated the GUI color scheme from Dark Theme to Light Theme(Pull requests [\#110](https://github.com/AY2324S1-CS2103T-T09-2/tp/pull/110)) + * Updated User Guide (Pull request [\#144](https://github.com/AY2324S1-CS2103T-T09-2/tp/pull/144), [\#161](https://github.com/AY2324S1-CS2103T-T09-2/tp/pull/161), [\#243](https://github.com/AY2324S1-CS2103T-T09-2/tp/pull/243), [\#254](https://github.com/AY2324S1-CS2103T-T09-2/tp/pull/254), [\#278](https://github.com/AY2324S1-CS2103T-T09-2/tp/pull/278)) + * Updated Developer Guide (Pull Request [\#254](https://github.com/AY2324S1-CS2103T-T09-2/tp/pull/254), [\#261](https://github.com/AY2324S1-CS2103T-T09-2/tp/pull/261), [\#278](https://github.com/AY2324S1-CS2103T-T09-2/tp/pull/278)) + * Add javadocs and fixed checkstyle errors (Pull request [\#219](https://github.com/AY2324S1-CS2103T-T09-2/tp/pull/219)) + * Fixed bugs (Pull request [\#226](https://github.com/AY2324S1-CS2103T-T09-2/tp/pull/226), [\#243](https://github.com/AY2324S1-CS2103T-T09-2/tp/pull/243)) + * Wrote additional tests for existing features to increase coverage from 41% to 43% (Pull requests [\#243](https://github.com/AY2324S1-CS2103T-T09-2/tp/pull/243)) + + +* **Code contributed**: [RepoSense link](https://nus-cs2103-ay2324s1.github.io/tp-dashboard/?search=&sort=groupTitle&sortWithin=title&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=true&checkedFileTypes=docs~functional-code~test-code&since=2023-09-22&tabOpen=true&tabType=authorship&tabAuthor=ncmathan&tabRepo=AY2324S1-CS2103T-T09-2%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code&authorshipIsBinaryFileTypeChecked=false&authorshipIsIgnoredFilesChecked=false) + + +* **Project management(team-based tasks contributions)**: + * Managed releases `v1.2`-`v1.3` (2 JAR releases) on GitHub + * Managed issues creating and some assigning throughout the milestones + * Managed the collaboration document and product demo submissions for tutorial + * Sorted the PE-D bugs into relevant catgegories + + +* **Documentation Contributions**: + * User Guide Contributions: + * Added documentation for the features `import-developer`, `import-client`, `lock`, `unlock`, `change-password` and `find-deadline` + * Did cosmetic tweaks to existing documentation including adding a Table of Contents for features + * Developer Guide Contributions: + * Added implementation details of the `import`, `GUI` features. + * Updated the architecture diagrams and sequence diagrams in the design section to reflect changes in CodeContact + * Added Glossary section + * Added Effort section + + +* **Community (Review/mentoring contributions)**: + * PRs reviewed (with non-trivial review comments): [\#219](https://github.com/AY2324S1-CS2103T-T09-2/tp/pull/219), [\#216](https://github.com/AY2324S1-CS2103T-T09-2/tp/pull/216)) + +* **Contributions beyond the project team:**: + * Provided suggestions to other teams on how to improve their project + + +* **Tools**: + * Integrated a third party library (ControlsFX) to the project diff --git a/docs/team/waseemingly.md b/docs/team/waseemingly.md new file mode 100644 index 00000000000..75ab43df92b --- /dev/null +++ b/docs/team/waseemingly.md @@ -0,0 +1,244 @@ +--- +layout: page +title: Waseem's Project Portfolio Page +--- + +### Project: CodeContact + +CodeContact is a desktop address book solution for Software Engineering companies. 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**: Find + * What it does: Project managers can find for developers, clients and projects based on the specified attributes. + * Justification: This feature improves the product significantly as project managers might want to look out for + certain attributes in a developer before adding them to a new project, or sort clients based on their + organisation or even group projects according to their deadline. + * Highlights: This implementation was challenging as it required creating new predicates for each of the attributes + of the developers, clients and projects. The feature also allows for multi-level searching as project managers + can find based on multiple attributes at the same time. This allows for even more detailed searching. The find + feature then displayed the updated filtered list on the GUI. In addition, the find implementation was utilised + in the implementation of the delete feature as well. + +* **Enhancements to existing features**: + * Wrote additional tests for existing features to increase coverage. + + +* **Code contributed + **: [RepoSense link](https://nus-cs2103-ay2324s1.github.io/tp-dashboard/?search=waseemingly&breakdown=false&sort=groupTitle%20dsc&sortWithin=title&since=2023-09-22&timeframe=commit&mergegroup=&groupSelect=groupByRepos) + + +* **Project management(team-based tasks contributions)**: + * Set up GitHub team repository and organisation and added team members to the developers team. + * Set up the project website + * Set up CodeCov to generate code coverage data and provides more information about coverage of our tests. + * Created and maintained issue tracker. + * Update AboutUs and Index pages. + + +* **Documentation Contributions**: + * User Guide Contributions: + * Added documentation for the features `find` [\#164]() + * Added the table for list of acceptable parameters. + * Added UI screenshots of the app to display examples of certain features. + * Developer Guide Contributions: + * Added implementation details of the `find` feature. + * Added planned enhancements for filter results and autocorrect search. + * Added manual testing cases for `find` and `mark project deadlines` features. + * README Contributions: + * Maintained README page of repository + + +* **Community (Review/mentoring contributions)**: + * PRs reviewed (with non-trivial review comments): [\#12](), [\#32](), [\#19](), [\#42]() + +* **Contributions beyond the project team:**: + * Reported bugs and suggestions for other teams in the cohort. + +* **Contributions to the Developer Guide (Extracts)**: + +### Find Feature + +#### Implementation + +The find feature is facilitated by a map-based strategy, associating specific prefixes (e.g., "find-developer n/" or " +find-client r/") with corresponding predicates, allowing dynamic generation of filtering criteria based on user input. + +Implemented operations include: + +- `FindCommandParser#parse()`: Interprets the user's input and generates the appropriate predicate to filter the list of + developers or clients. +- `Model#updateFilteredPersonList()`: Updates the list displayed in the UI based on the provided predicate. + +Given below is an example usage scenario and how the find mechanism behaves at each step: + +**Step 1.** The user launches the application. The list of developers and clients are displayed. + +**Step 2.** To filter developers by name, the user executes the command `find-developer n/ alice bob`. The application +recognizes the "developer n/" prefix and uses the `NameContainsKeywordsPredicate` to generate a filtering criteria. The +list in the UI is updated to only display developers named Alice or Bob. + +**Step 3.** Next, the user wants to find clients from a specific organisation. They use the +command `find-client o/ Google`. The "find-client o/" prefix maps to the `OrganisationContainsKeywordsPredicate` and +filters clients associated with Google. + +**Step 4.** If the user provides an unrecognized prefix, e.g., `find-developer z/ alice`, an error message is displayed +informing them of the correct command format. + +> :information_source: **Note:** While the user can search by multiple keywords, each keyword maps to an entire word in +> the attributes. For example, searching for "Ali" will not return "Alice". + +The following sequence diagram provides an overview of how the find operation is executed: + +[Diagram would be inserted here illustrating the parsing of the command, identification of the appropriate predicate, and subsequent filtering of the list.] + +### Design Considerations: + +**Aspect:** Implementation of the predicate map: + +**Alternative 1 (current choice):** + +- Use a long chain of `if-else` conditions for each attribute. + - **Pros:** Explicit parsing logic for each attribute. + - **Cons:** Code becomes lengthy and hard to maintain. Adding a new attribute involves modifying the parsing logic, + increasing the risk of errors. + +**Alternative 2:** + +- Use a map linking prefixes to their corresponding predicate constructors. + - **Pros:** Simplifies the parsing process. Adding a new attribute to search becomes as simple as adding a new entry + in the map. + - **Cons:** A potential mismatch between the prefix and its predicate can lead to wrong results. + +Given the benefits of a more maintainable and scalable codebase, we've decided to go with the first alternative. Future +enhancements might include fuzzy search. + + +* **Contributions to the User Guide (Extracts)**: + +### Parameter Information + +Within the tables below, you can find out more about the parameters that CodeContact supports. These parameters come in +handy when crafting commands in CodeContact. + +Here are some notes about these parameters. + +* Each parameter comes with **constraints**. These constraints detail the specific formats of text that + each parameter accepts as valid user input. + * Not following these constraints will **result in an error** when entering the command. + *Nonetheless, CodeContact will not stop working. Rather, a message will be provided to you on + how to correct your command. + +#### Common Parameters + +| Parameter | Description | Constraints | Valid Examples | Invalid Examples | +|-----------|------------------------------------------|----------------------------------------------------------------|-----------------------------|---------------------------------------------| +| `n/` | name of developer/client/project | alphanumeric characters and spaces, and it should not be blank | Tom Hanks, Elizabeth 2 | 成龍, 潔 いさぎ 世 よ 一 いち, Ganesh s/o Ravichandran | +| `p/` | phone number of developer/client/project | numeric characters, and it should not be blank | 94566835 | | +| `e/` | email of developer/client/project | alphanumeric characters, and it should not be blank | amy@gmail.com | | +| `a/` | address of developer/client/project | alphanumeric characters and spaces, and it should not be blank | 311, Clementi Ave 2, #02-25 | | +| `r/` | role of developer/client/project | alphabetical characters and spaces, and it should not be blank | Developer | | +| `pr/` | project name of developer/client/project | alphanumeric characters and spaces, and it should not be blank | CS2103T | | + +#### Developer Parameters + +| Parameter | Description | Constraints | Valid Examples | Invalid Examples | +|-----------|------------------------------|--------------------------------------------------------------------------------|----------------|----------------------| +| `g/` | github username of developer | alphanumeric characters, and it should not be blank | johng, amy123 | | +| `d/` | date joined of developer | numeric characters in DD-MM-YYYY format, and it should not be blank | 19-11-2023 | 19/11/2023, 1/1/2023 | +| `s/` | salary of developer | numeric characters, and it should not be blank | 5000 | | +| `rt/` | rating of developer | numeric characters with 1 decimal place and spaces, and it should not be blank | 5.0, 3.5 | 5,3 | + +#### Client Parameters + +| Parameter | Description | Constraints | Valid Examples | Invalid Examples | +|-----------|-----------------------------|----------------------------------------------------------------|----------------|------------------| +| `o/` | organisation name of client | alphanumeric characters and spaces, and it should not be blank | Google | | +| `do/` | document name of client | alphanumeric characters and spaces, and it should not be blank | google.com | | + +#### Project Parameters + +| Parameter | Description | Constraints | Valid Examples | Invalid Examples | +|-----------|------------------------|---|---|--| +| `dr/` | description of project | alphanumeric characters and spaces, and it should not be blank | App to allow for +different juices to be ordered | | +| `dl/` | deadline of project | alphanumeric characters and spaces, and it should not be blank| 19-12-2023,Design +backend,HIGH,0 | | + + +----------------------------------------------------------------------------------------------- + +### Find + +#### Find developer details + +Finds the details of an existing developer in the address book. + +Format: `find-developer [n/NAME] [p/PHONE_NUMBER] [e/EMAIL] [a/ADDRESS] [d/DATE_JOINED] [r/ROLE] [s/SALARY] [pr/PROJECT_NAME] [gh/GITHUB_ID] [ra/RATING]` + +* Finds for developers based on the attributes provided. +* At least one of the optional fields must be provided. +* Existing values will be compared to the input values, and the results will include any items that match the provided + criteria. +* You can combine multiple attributes for a more specific search. +* The search is case-insensitive, so you can use any case for the search criteria. + +Example of usage: `find-developer pr/2103T rt/5.0` + +* Prints developers in 2103/T project with a 5-star rating. + +When command succeeds, CLI shows: + +``` +This is the one developer with matching information +``` +followed by the matching developer's details in the GUI. + +#### Find client details + +Finds the details of an existing client in the address book. + +Format: `find-client [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [o/ORGANISATION] [pr/PROJECT] [d/DOCUMENT]` + +* Finds for clients based on the attributes provided. +* At least one of the optional fields must be provided. +* Existing values will be compared to the input values, and the results will include any items that match the provided + criteria. +* You can combine multiple attributes for a more specific search. +* The search is case-insensitive, so you can use any case for the search criteria. + +Example of usage: `find-client o/Google r/Senior developer` + +* Prints clients from Google of the senior developer role + +When command succeeds, CLI shows: + +``` +These are the 2 clients with matching information +``` +followed by the matching clients' details in the GUI. + +#### Find project details + +Finds the details of an existing project in the address book. + +Format: `find-project [pr/PROJECT_NAME] [d/DESCRIPTION] [dl/DEADLINE]` + +* Finds for projects based on the attributes provided. +* At least one of the optional fields must be provided. +* Existing values will be compared to the input values, and the results will include any items that match the provided + criteria. +* You can combine multiple attributes for a more specific search. +* The search is case-insensitive, so you can use any case for the search criteria. + +Example of usage: `find-project pr/JuiceApp` + +* Print projects with the name 2103T project. + +When command succeeds, CLI shows: + +``` +This is the one project with matching information +``` +followed by the matching project's details in the GUI. diff --git a/docs/tutorials/AddRemark.md b/docs/tutorials/AddRemark.md index d98f38982e7..42067a48d91 100644 --- a/docs/tutorials/AddRemark.md +++ b/docs/tutorials/AddRemark.md @@ -3,22 +3,26 @@ layout: page title: "Tutorial: Adding a command" --- -Let's walk you through the implementation of a new command — `remark`. +Let's walk you through the implementation of a new command —`remark`. -This command allows users of the AddressBook application to add optional remarks to people in their address book and edit it if required. The command should have the following format: +This command allows users of the AddressBook application to add optional remarks to people in their address book and +edit it if required. The command should have the following format: `remark INDEX r/REMARK` (e.g., `remark 2 r/Likes baseball`) We’ll assume that you have already set up the development environment as outlined in the Developer’s Guide. - ## Create a new `remark` command -Looking in the `logic.command` package, you will notice that each existing command have their own class. All the commands inherit from the abstract class `Command` which means that they must override `execute()`. Each `Command` returns an instance of `CommandResult` upon success and `CommandResult#feedbackToUser` is printed to the `ResultDisplay`. +Looking in the `logic.command` package, you will notice that each existing command have their own class. All the +commands inherit from the abstract class `Command` which means that they must override `execute()`. Each `Command` +returns an instance of `CommandResult` upon success and `CommandResult#feedbackToUser` is printed to +the `ResultDisplay`. Let’s start by creating a new `RemarkCommand` class in the `src/main/java/seedu/address/logic/command` directory. -For now, let’s keep `RemarkCommand` as simple as possible and print some output. We accomplish that by returning a `CommandResult` with an accompanying message. +For now, let’s keep `RemarkCommand` as simple as possible and print some output. We accomplish that by returning +a `CommandResult` with an accompanying message. **`RemarkCommand.java`:** @@ -28,7 +32,7 @@ package seedu.address.logic.commands; import seedu.address.model.Model; /** - * Changes the remark of an existing person in the address book. + * Changes the remark of an existing developer in the address book. */ public class RemarkCommand extends Command { @@ -43,9 +47,12 @@ public class RemarkCommand extends Command { ### Hook `RemarkCommand` into the application -Now that we have our `RemarkCommand` ready to be executed, we need to update `AddressBookParser#parseCommand()` to recognize the `remark` keyword. Add the new command to the `switch` block by creating a new `case` that returns a new instance of `RemarkCommand`. +Now that we have our `RemarkCommand` ready to be executed, we need to update `AddressBookParser#parseCommand()` to +recognize the `remark` keyword. Add the new command to the `switch` block by creating a new `case` that returns a new +instance of `RemarkCommand`. -You can refer to the changes in this [diff](https://github.com/se-edu/addressbook-level3/commit/35eb7286f18a029d39cb7a29df8f172a001e4fd8#diff-399c284cb892c20b7c04a69116fcff6ccc0666c5230a1db8e4a9145def8fa4ee). +You can refer to the changes in +this [diff](https://github.com/se-edu/addressbook-level3/commit/35eb7286f18a029d39cb7a29df8f172a001e4fd8#diff-399c284cb892c20b7c04a69116fcff6ccc0666c5230a1db8e4a9145def8fa4ee). ### Run the application @@ -55,7 +62,9 @@ Run `Main#main` and try out your new `RemarkCommand`. If everything went well, y ## Change `RemarkCommand` to throw an exception -While we have successfully printed a message to `ResultDisplay`, the command does not do what it is supposed to do. Let’s change the command to throw a `CommandException` to accurately reflect that our command is still a work in progress. +While we have successfully printed a message to `ResultDisplay`, the command does not do what it is supposed to do. +Let’s change the command to throw a `CommandException` to accurately reflect that our command is still a work in +progress. ![The relationship between RemarkCommand and Command](../images/add-remark/RemarkCommandClass.png) @@ -65,8 +74,8 @@ Following the convention in other commands, we add relevant messages as constant ``` java public static final String MESSAGE_USAGE = COMMAND_WORD - + ": Edits the remark of the person identified " - + "by the index number used in the last person listing. " + + ": Edits the remark of the developer identified " + + "by the index number used in the last developer listing. " + "Existing remark will be overwritten by the input.\n" + "Parameters: INDEX (must be a positive integer) " + "r/ [REMARK]\n" @@ -88,7 +97,9 @@ Let’s change `RemarkCommand` to parse input from the user. ### Make the command accept parameters -We start by modifying the constructor of `RemarkCommand` to accept an `Index` and a `String`. While we are at it, let’s change the error message to echo the values. While this is not a replacement for tests, it is an obvious way to tell if our code is functioning as intended. +We start by modifying the constructor of `RemarkCommand` to accept an `Index` and a `String`. While we are at it, let’s +change the error message to echo the values. While this is not a replacement for tests, it is an obvious way to tell if +our code is functioning as intended. ``` java import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; @@ -101,8 +112,8 @@ public class RemarkCommand extends Command { private final String remark; /** - * @param index of the person in the filtered person list to edit the remark - * @param remark of the person to be updated to + * @param index of the developer in the filtered developer list to edit the remark + * @param remark of the developer to be updated to */ public RemarkCommand(Index index, String remark) { requireAllNonNull(index, remark); @@ -134,17 +145,21 @@ public class RemarkCommand extends Command { } ``` -Your code should look something like [this](https://github.com/se-edu/addressbook-level3/commit/dc6d5139d08f6403da0ec624ea32bd79a2ae0cbf#diff-a8e35af8f9c251525063fae36c9852922a7e7195763018eacec60f3a4d87c594) after you are done. +Your code should look something +like [this](https://github.com/se-edu/addressbook-level3/commit/dc6d5139d08f6403da0ec624ea32bd79a2ae0cbf#diff-a8e35af8f9c251525063fae36c9852922a7e7195763018eacec60f3a4d87c594) +after you are done. ### Parse user input Now let’s move on to writing a parser that will extract the index and remark from the input provided by the user. -Create a `RemarkCommandParser` class in the `seedu.address.logic.parser` package. The class must extend the `Parser` interface. +Create a `RemarkCommandParser` class in the `seedu.address.logic.parser` package. The class must extend the `Parser` +interface. ![The relationship between Parser and RemarkCommandParser](../images/add-remark/RemarkCommandParserClass.png) -Thankfully, `ArgumentTokenizer#tokenize()` makes it trivial to parse user input. Let’s take a look at the JavaDoc provided for the function to understand what it does. +Thankfully, `ArgumentTokenizer#tokenize()` makes it trivial to parse user input. Let’s take a look at the JavaDoc +provided for the function to understand what it does. **`ArgumentTokenizer.java`:** @@ -162,7 +177,9 @@ Thankfully, `ArgumentTokenizer#tokenize()` makes it trivial to parse user input. */ ``` -We can tell `ArgumentTokenizer#tokenize()` to look out for our new prefix `r/` and it will return us an instance of `ArgumentMultimap`. Now let’s find out what we need to do in order to obtain the Index and String that we need. Let’s look through `ArgumentMultimap` : +We can tell `ArgumentTokenizer#tokenize()` to look out for our new prefix `r/` and it will return us an instance +of `ArgumentMultimap`. Now let’s find out what we need to do in order to obtain the Index and String that we need. Let’s +look through `ArgumentMultimap` : **`ArgumentMultimap.java`:** @@ -177,7 +194,8 @@ public Optional getValue(Prefix prefix) { } ``` -This appears to be what we need to get a String of the remark. But what about the Index? Let's take a quick peek at existing `Command` that uses an index to see how it is done. +This appears to be what we need to get a String of the remark. But what about the Index? Let's take a quick peek at +existing `Command` that uses an index to see how it is done. **`DeleteCommandParser.java`:** @@ -188,7 +206,8 @@ return new DeleteCommand(index); There appears to be another utility class that obtains an `Index` from the input provided by the user. -Now that we have the know-how to extract the data that we need from the user’s input, we can parse the user command and create a new instance of `RemarkCommand`, as given below. +Now that we have the know-how to extract the data that we need from the user’s input, we can parse the user command and +create a new instance of `RemarkCommand`, as given below. **`RemarkCommandParser.java`:** @@ -223,24 +242,33 @@ If you are stuck, check out the sample ## Add `Remark` to the model -Now that we have all the information that we need, let’s lay the groundwork for propagating the remarks added into the in-memory storage of person data. We achieve that by working with the `Person` model. Each field in a Person is implemented as a separate class (e.g. a `Name` object represents the person’s name). That means we should add a `Remark` class so that we can use a `Remark` object to represent a remark given to a person. +Now that we have all the information that we need, let’s lay the groundwork for propagating the remarks added into the +in-memory storage of developer data. We achieve that by working with the `Person` model. Each field in a Person is +implemented as a separate class (e.g. a `Name` object represents the developer’s name). That means we should add +a `Remark` class so that we can use a `Remark` object to represent a remark given to a developer. ### Add a new `Remark` class -Create a new `Remark` in `seedu.address.model.person`. Since a `Remark` is a field that is similar to `Address`, we can reuse a significant bit of code. +Create a new `Remark` in `seedu.address.model.developer`. Since a `Remark` is a field that is similar to `Address`, we +can reuse a significant bit of code. -A copy-paste and search-replace later, you should have something like [this](https://github.com/se-edu/addressbook-level3/commit/4516e099699baa9e2d51801bd26f016d812dedcc#diff-41bb13c581e280c686198251ad6cc337cd5e27032772f06ed9bf7f1440995ece). Note how `Remark` has no constrains and thus does not require input +A copy-paste and search-replace later, you should have something +like [this](https://github.com/se-edu/addressbook-level3/commit/4516e099699baa9e2d51801bd26f016d812dedcc#diff-41bb13c581e280c686198251ad6cc337cd5e27032772f06ed9bf7f1440995ece). +Note how `Remark` has no constrains and thus does not require input validation. ### Make use of `Remark` -Let’s change `RemarkCommand` and `RemarkCommandParser` to use the new `Remark` class instead of plain `String`. These should be relatively simple changes. +Let’s change `RemarkCommand` and `RemarkCommandParser` to use the new `Remark` class instead of plain `String`. These +should be relatively simple changes. ## Add a placeholder element for remark to the UI -Without getting too deep into `fxml`, let’s go on a 5 minute adventure to get some placeholder text to show up for each person. +Without getting too deep into `fxml`, let’s go on a 5 minute adventure to get some placeholder text to show up for each +developer. -Simply add the following to [`seedu.address.ui.PersonCard`](https://github.com/se-edu/addressbook-level3/commit/850b78879582f38accb05dd20c245963c65ea599#diff-639834f1e05afe2276a86372adf0fe5f69314642c2d93cfa543d614ce5a76688). +Simply add the following +to [`seedu.address.ui.DeveloperCard`](https://github.com/se-edu/addressbook-level3/commit/850b78879582f38accb05dd20c245963c65ea599#diff-639834f1e05afe2276a86372adf0fe5f69314642c2d93cfa543d614ce5a76688). **`PersonCard.java`:** @@ -249,10 +277,11 @@ Simply add the following to [`seedu.address.ui.PersonCard`](https://github.com/s private Label remark; ``` +`@FXML` is an annotation that marks a private or protected field and makes it accessible to FXML. It might sound like +Greek to you right now, don’t worry — we will get back to it later. -`@FXML` is an annotation that marks a private or protected field and makes it accessible to FXML. It might sound like Greek to you right now, don’t worry — we will get back to it later. - -Then insert the following into [`main/resources/view/PersonListCard.fxml`](https://github.com/se-edu/addressbook-level3/commit/850b78879582f38accb05dd20c245963c65ea599#diff-d44c4f51c24f6253c277a2bb9bc440b8064d9c15ad7cb7ceda280bca032efce9). +Then insert the following +into [`main/resources/view/PersonListCard.fxml`](https://github.com/se-edu/addressbook-level3/commit/850b78879582f38accb05dd20c245963c65ea599#diff-d44c4f51c24f6253c277a2bb9bc440b8064d9c15ad7cb7ceda280bca032efce9). **`PersonListCard.fxml`:** @@ -270,11 +299,13 @@ Since `PersonCard` displays data from a `Person`, we need to update `Person` to ### Modify `Person` -We change the constructor of `Person` to take a `Remark`. We will also need to define new fields and accessors accordingly to store our new addition. +We change the constructor of `Person` to take a `Remark`. We will also need to define new fields and accessors +accordingly to store our new addition. ### Update other usages of `Person` -Unfortunately, a change to `Person` will cause other commands to break, you will have to modify these commands to use the updated `Person`! +Unfortunately, a change to `Person` will cause other commands to break, you will have to modify these commands to use +the updated `Person`!
@@ -282,18 +313,20 @@ Unfortunately, a change to `Person` will cause other commands to break, you will
-Refer to [this commit](https://github.com/se-edu/addressbook-level3/commit/ce998c37e65b92d35c91d28c7822cd139c2c0a5c) and check that you have got everything in order! - +Refer to [this commit](https://github.com/se-edu/addressbook-level3/commit/ce998c37e65b92d35c91d28c7822cd139c2c0a5c) and +check that you have got everything in order! ## Updating Storage -AddressBook stores data by serializing `JsonAdaptedPerson` into `json` with the help of an external library — Jackson. Let’s update `JsonAdaptedPerson` to work with our new `Person`! +AddressBook stores data by serializing `JsonAdaptedPerson` into `json` with the help of an external library — Jackson. +Let’s update `JsonAdaptedPerson` to work with our new `Person`! While the changes to code may be minimal, the test data will have to be updated as well.
-:exclamation: You must delete AddressBook’s storage file located at `/data/addressbook.json` before running it! Not doing so will cause AddressBook to default to an empty address book! +:exclamation: You must delete AddressBook’s storage file located at `/data/addressbook.json` before running it! Not +doing so will cause AddressBook to default to an empty address book!
@@ -304,14 +337,15 @@ to see what the changes entail. Now that we have finalized the `Person` class and its dependencies, we can now bind the `Remark` field to the UI. -Just add [this one line of code!](https://github.com/se-edu/addressbook-level3/commit/5b98fee11b6b3f5749b6b943c4f3bd3aa049b692) +Just +add [this one line of code!](https://github.com/se-edu/addressbook-level3/commit/5b98fee11b6b3f5749b6b943c4f3bd3aa049b692) **`PersonCard.java`:** ``` java -public PersonCard(Person person, int displayedIndex) { +public PersonCard(Person developer, int displayedIndex) { //... - remark.setText(person.getRemark().value); + remark.setText(developer.getRemark().value); } ``` @@ -319,11 +353,14 @@ public PersonCard(Person person, int displayedIndex) { ## Putting everything together -After the previous step, we notice a peculiar regression — we went from displaying something to nothing at all. However, this is expected behavior as we are yet to update the `RemarkCommand` to make use of the code we've been adding in the last few steps. +After the previous step, we notice a peculiar regression — we went from displaying something to nothing at all. However, +this is expected behavior as we are yet to update the `RemarkCommand` to make use of the code we've been adding in the +last few steps. ### Update `RemarkCommand` and `RemarkCommandParser` -In this last step, we modify `RemarkCommand#execute()` to change the `Remark` of a `Person`. Since all fields in a `Person` are immutable, we create a new instance of a `Person` with the values that we want and +In this last step, we modify `RemarkCommand#execute()` to change the `Remark` of a `Person`. Since all fields in +a `Person` are immutable, we create a new instance of a `Person` with the values that we want and save it with `Model#setPerson()`. **`RemarkCommand.java`:** @@ -341,25 +378,25 @@ save it with `Model#setPerson()`. throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); } - Person personToEdit = lastShownList.get(index.getZeroBased()); - Person editedPerson = new Person( - personToEdit.getName(), personToEdit.getPhone(), personToEdit.getEmail(), - personToEdit.getAddress(), remark, personToEdit.getTags()); + Person developerToEdit = lastShownList.get(index.getZeroBased()); + Person editedDeveloper = new Person( + developerToEdit.getName(), developerToEdit.getPhone(), developerToEdit.getEmail(), + developerToEdit.getAddress(), remark, developerToEdit.getTags()); - model.setPerson(personToEdit, editedPerson); + model.setPerson(developerToEdit, editedDeveloper); model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); - return new CommandResult(generateSuccessMessage(editedPerson)); + return new CommandResult(generateSuccessMessage(editedDeveloper)); } /** * Generates a command execution success message based on whether * the remark is added to or removed from - * {@code personToEdit}. + * {@code developerToEdit}. */ - private String generateSuccessMessage(Person personToEdit) { + private String generateSuccessMessage(Person developerToEdit) { String message = !remark.value.isEmpty() ? MESSAGE_ADD_REMARK_SUCCESS : MESSAGE_DELETE_REMARK_SUCCESS; - return String.format(message, personToEdit); + return String.format(message, developerToEdit); } ``` @@ -367,11 +404,16 @@ save it with `Model#setPerson()`. ## Writing tests -Tests are crucial to ensuring that bugs don’t slip into the codebase unnoticed. This is especially true for large code bases where a change might lead to unintended behavior. +Tests are crucial to ensuring that bugs don’t slip into the codebase unnoticed. This is especially true for large code +bases where a change might lead to unintended behavior. Let’s verify the correctness of our code by writing some tests! -Of course you can simply add the test cases manually, like you've been doing all along this tutorial. The result would be like the test cases in [here](https://github.com/se-edu/addressbook-level3/commit/fac8f3fd855d55831ca0cc73313b5943d49d4d6e#diff-ff58f7c10338b34f76645df49b71ecb2bafaf7611b20e7ff59ebc98475538a01). Alternatively, you can get the help of IntelliJ to generate the skeletons of the test cases, as explained in the next section. +Of course you can simply add the test cases manually, like you've been doing all along this tutorial. The result would +be like the test cases +in [here](https://github.com/se-edu/addressbook-level3/commit/fac8f3fd855d55831ca0cc73313b5943d49d4d6e#diff-ff58f7c10338b34f76645df49b71ecb2bafaf7611b20e7ff59ebc98475538a01). +Alternatively, you can get the help of IntelliJ to generate the skeletons of the test cases, as explained in the next +section. ### Automatically generating tests @@ -380,7 +422,8 @@ The goal is to write effective and efficient tests to ensure that `RemarkCommand The convention for test names is `methodName_testScenario_expectedResult`. An example would be `execute_filteredList_success`. -Let’s create a test for `RemarkCommand#execute()` to test that adding a remark works. On `IntelliJ IDEA` you can bring up the context menu and choose to `Go To` \> `Test` or use the appropriate keyboard shortcut. +Let’s create a test for `RemarkCommand#execute()` to test that adding a remark works. On `IntelliJ IDEA` you can bring +up the context menu and choose to `Go To` \> `Test` or use the appropriate keyboard shortcut. ![Using the context menu to jump to tests](../images/add-remark/ContextMenu.png) @@ -390,9 +433,12 @@ Then, create a test for the `execute` method. Following convention, let’s change the name of the generated method to `execute_addRemarkUnfilteredList_success`. -Let’s use the utility functions provided in `CommandTestUtil`. The functions ensure that commands produce the expected `CommandResult` and output the correct message. In this case, `CommandTestUtil#assertCommandSuccess` is the best fit as we are testing that a `RemarkCommand` will successfully add a `Remark`. +Let’s use the utility functions provided in `CommandTestUtil`. The functions ensure that commands produce the +expected `CommandResult` and output the correct message. In this case, `CommandTestUtil#assertCommandSuccess` is the +best fit as we are testing that a `RemarkCommand` will successfully add a `Remark`. -You should end up with a test that looks something like [this](https://github.com/se-edu/addressbook-level3/commit/fac8f3fd855d55831ca0cc73313b5943d49d4d6e#diff-ff58f7c10338b34f76645df49b71ecb2bafaf7611b20e7ff59ebc98475538a01R36-R49). +You should end up with a test that looks something +like [this](https://github.com/se-edu/addressbook-level3/commit/fac8f3fd855d55831ca0cc73313b5943d49d4d6e#diff-ff58f7c10338b34f76645df49b71ecb2bafaf7611b20e7ff59ebc98475538a01R36-R49). ## Conclusion diff --git a/docs/tutorials/RemovingFields.md b/docs/tutorials/RemovingFields.md index f29169bc924..e4b39e0e39f 100644 --- a/docs/tutorials/RemovingFields.md +++ b/docs/tutorials/RemovingFields.md @@ -5,39 +5,52 @@ title: "Tutorial: Removing Fields" > Perfection is achieved, not when there is nothing more to add, but when there is nothing left to take away. > -> — Antoine de Saint-Exupery +> — Antoine de Saint-Exupery When working on an existing code base, you will most likely find that some features that are no longer necessary. -This tutorial aims to give you some practice on such a code 'removal' activity by removing the `address` field from `Person` class. +This tutorial aims to give you some practice on such a code 'removal' activity by removing the `address` field +from `Person` class.
-**If you have done the [Add `remark` command tutorial](AddRemark.html) already**, you should know where the code had to be updated to add the field `remark`. From that experience, you can deduce where the code needs to be changed to _remove_ that field too. The removing of the `address` field can be done similarly. +**If you have done the [Add `remark` command tutorial](AddRemark.html) already**, you should know where the code had to +be updated to add the field `remark`. From that experience, you can deduce where the code needs to be changed to +_remove_ that field too. The removing of the `address` field can be done similarly.

-However, if you have no such prior knowledge, removing a field can take a quite a bit of detective work. This tutorial takes you through that process. **At least have a read even if you don't actually do the steps yourself.** +However, if you have no such prior knowledge, removing a field can take a quite a bit of detective work. This tutorial +takes you through that process. **At least have a read even if you don't actually do the steps yourself.**
- * Table of Contents -{:toc} + {:toc} ## Safely deleting `Address` -IntelliJ IDEA provides a refactoring tool that can identify *most* parts of a removal easily. Let’s try to use it as much as we can. +IntelliJ IDEA provides a refactoring tool that can identify *most* parts of a removal easily. Let’s try to use it as +much as we can. ### Assisted refactoring -The `address` field in `Person` is actually an instance of the `seedu.address.model.person.Address` class. Since removing the `Address` class will break the application, we start by identifying `Address`'s usages. This allows us to see code that depends on `Address` to function properly and edit them on a case-by-case basis. Right-click the `Address` class and select `Refactor` \> `Safe Delete` through the menu. -* :bulb: To make things simpler, you can unselect the options `Search in comments and strings` and `Search for text occurrences` +The `address` field in `Person` is actually an instance of the `seedu.address.model.developer.Address` class. Since +removing the `Address` class will break the application, we start by identifying `Address`'s usages. This allows us to +see code that depends on `Address` to function properly and edit them on a case-by-case basis. Right-click the `Address` +class and select `Refactor` \> `Safe Delete` through the menu. + +* :bulb: To make things simpler, you can unselect the options `Search in comments and strings` + and `Search for text occurrences` ![Usages detected](../images/remove/UnsafeDelete.png) -Choose to `View Usages` and you should be presented with a list of `Safe Delete Conflicts`. These conflicts describe locations in which the `Address` class is used. +Choose to `View Usages` and you should be presented with a list of `Safe Delete Conflicts`. These conflicts describe +locations in which the `Address` class is used. ![List of conflicts](../images/remove/SafeDeleteConflicts.png) -Remove usages of `Address` by performing `Safe Delete`s on each entry i.e., double-click on the entry (which takes you to the code in concern, right-click on that entity, and choose `Refactor` -> `Safe delete` as before). You will need to exercise discretion when removing usages of `Address`. Functions like `ParserUtil#parseAddress()` can be safely removed but its usages must be removed as well. Other usages like in `EditPersonDescriptor` may require more careful inspection. +Remove usages of `Address` by performing `Safe Delete`s on each entry i.e., double-click on the entry (which takes you +to the code in concern, right-click on that entity, and choose `Refactor` -> `Safe delete` as before). You will need to +exercise discretion when removing usages of `Address`. Functions like `ParserUtil#parseAddress()` can be safely removed +but its usages must be removed as well. Other usages like in `EditPersonDescriptor` may require more careful inspection. Let’s try removing references to `Address` in `EditPersonDescriptor`. @@ -52,7 +65,8 @@ Let’s try removing references to `Address` in `EditPersonDescriptor`.
- :bulb: **Tip:** Removing usages may result in errors. Exercise discretion and fix them. For example, removing the `address` field from the `Person` class will require you to modify its constructor. + :bulb: **Tip:** Removing usages may result in errors. Exercise discretion and fix them. For example, removing + the `address` field from the `Person` class will require you to modify its constructor.
1. Repeat the steps for the remaining usages of `Address` @@ -61,13 +75,17 @@ After you are done, verify that the application still works by compiling and run ### Manual refactoring -Unfortunately, there are usages of `Address` that IntelliJ IDEA cannot identify. You can find them by searching for instances of the word `address` in your code (`Edit` \> `Find` \> `Find in path`). +Unfortunately, there are usages of `Address` that IntelliJ IDEA cannot identify. You can find them by searching for +instances of the word `address` in your code (`Edit` \> `Find` \> `Find in path`). -Places of interest to look out for would be resources used by the application. `main/resources` contains images and `fxml` files used by the application and `test/resources` contains test data. For example, there is a `$address` in each `PersonCard` that has not been removed nor identified. +Places of interest to look out for would be resources used by the application. `main/resources` contains images +and `fxml` files used by the application and `test/resources` contains test data. For example, there is a `$address` in +each `PersonCard` that has not been removed nor identified. ![$address](../images/remove/$address.png) -A quick look at the `PersonCard` class and its `fxml` file quickly reveals why it slipped past the automated refactoring. +A quick look at the `PersonCard` class and its `fxml` file quickly reveals why it slipped past the automated +refactoring. **`PersonCard.java`** @@ -88,24 +106,29 @@ private Label address; ... ``` -After removing the `Label`, we can proceed to formally test our code. If everything went well, you should have most of your tests pass. Fix any remaining errors until the tests all pass. +After removing the `Label`, we can proceed to formally test our code. If everything went well, you should have most of +your tests pass. Fix any remaining errors until the tests all pass. ## Tidying up -At this point, your application is working as intended and all your tests are passing. What’s left to do is to clean up references to `Address` in test data and documentation. +At this point, your application is working as intended and all your tests are passing. What’s left to do is to clean up +references to `Address` in test data and documentation. -In `src/test/data/`, data meant for testing purposes are stored. While keeping the `address` field in the json files does not cause the tests to fail, it is not good practice to let cruft from old features accumulate. +In `src/test/data/`, data meant for testing purposes are stored. While keeping the `address` field in the json files +does not cause the tests to fail, it is not good practice to let cruft from old features accumulate. **`invalidPersonAddressBook.json`:** ```json { - "persons": [ { - "name": "Person with invalid name field: Ha!ns Mu@ster", - "phone": "9482424", - "email": "hans@example.com", - "address": "4th street" - } ] + "developers": [ + { + "name": "Person with invalid name field: Ha!ns Mu@ster", + "phone": "9482424", + "email": "hans@example.com", + "address": "4th street" + } + ] } ``` diff --git a/docs/tutorials/TracingCode.md b/docs/tutorials/TracingCode.md index 4fb62a83ef6..3485a78b8f1 100644 --- a/docs/tutorials/TracingCode.md +++ b/docs/tutorials/TracingCode.md @@ -3,18 +3,23 @@ layout: page title: "Tutorial: Tracing code" --- -> Indeed, the ratio of time spent reading versus writing is well over 10 to 1. We are constantly reading old code as part of the effort to write new code. …​\[Therefore,\] making it easy to read makes it easier to write. +> Indeed, the ratio of time spent reading versus writing is well over 10 to 1. We are constantly reading old code as +> part of the effort to write new code. …​\[Therefore,\] making it easy to read makes it easier to write. > -> — Robert C. Martin Clean Code: A Handbook of Agile Software Craftsmanship +> — Robert C. Martin Clean Code: A Handbook of Agile Software Craftsmanship -When trying to understand an unfamiliar code base, one common strategy used is to trace some representative execution path through the code base. One easy way to trace an execution path is to use a debugger to step through the code. In this tutorial, you will be using the IntelliJ IDEA’s debugger to trace the execution path of a specific user command. +When trying to understand an unfamiliar code base, one common strategy used is to trace some representative execution +path through the code base. One easy way to trace an execution path is to use a debugger to step through the code. In +this tutorial, you will be using the IntelliJ IDEA’s debugger to trace the execution path of a specific user command. * Table of Contents -{:toc} + {:toc} ## Before we start -Before we jump into the code, it is useful to get an idea of the overall structure and the high-level behavior of the application. This is provided in the 'Architecture' section of the developer guide. In particular, the architecture diagram (reproduced below), tells us that the App consists of several components. +Before we jump into the code, it is useful to get an idea of the overall structure and the high-level behavior of the +application. This is provided in the 'Architecture' section of the developer guide. In particular, the architecture +diagram (reproduced below), tells us that the App consists of several components. ![ArchitectureDiagram](../images/ArchitectureDiagram.png) @@ -22,33 +27,49 @@ It also has a sequence diagram (reproduced below) that tells us how a command pr -Note how the diagram shows only the execution flows _between_ the main components. That is, it does not show details of the execution path *inside* each component. By hiding those details, the diagram aims to inform the reader about the overall execution path of a command without overwhelming the reader with too much details. In this tutorial, you aim to find those omitted details so that you get a more in-depth understanding of how the code works. +Note how the diagram shows only the execution flows _between_ the main components. That is, it does not show details of +the execution path *inside* each component. By hiding those details, the diagram aims to inform the reader about the +overall execution path of a command without overwhelming the reader with too much details. In this tutorial, you aim to +find those omitted details so that you get a more in-depth understanding of how the code works. Before we proceed, ensure that you have done the following: + 1. Read the [*Architecture* section of the DG](../DeveloperGuide.md#architecture) 1. Set up the project in Intellij IDEA 1. Learn basic debugging features of Intellij IDEA - * If you are using a different IDE, we'll leave it to you to figure out the equivalent feature to use in your IDE. - * If you are not using an IDE, we'll let you figure out how to achieve the same using your coding toolchain. + * If you are using a different IDE, we'll leave it to you to figure out the equivalent feature to use in your IDE. + * If you are not using an IDE, we'll let you figure out how to achieve the same using your coding toolchain. ## Setting a breakpoint -As you know, the first step of debugging is to put in a breakpoint where you want the debugger to pause the execution. For example, if you are trying to understand how the App starts up, you would put a breakpoint in the first statement of the `main` method. +As you know, the first step of debugging is to put in a breakpoint where you want the debugger to pause the execution. +For example, if you are trying to understand how the App starts up, you would put a breakpoint in the first statement of +the `main` method. -In our case, we would want to begin the tracing at the very point where the App start processing user input (i.e., somewhere in the UI component), and then trace through how the execution proceeds through the UI component. However, the execution path through a GUI is often somewhat obscure due to various *event-driven mechanisms* used by GUI frameworks, which happens to be the case here too. Therefore, let us put the breakpoint where the `UI` transfers control to the `Logic` component. +In our case, we would want to begin the tracing at the very point where the App start processing user input (i.e., +somewhere in the UI component), and then trace through how the execution proceeds through the UI component. However, the +execution path through a GUI is often somewhat obscure due to various *event-driven mechanisms* used by GUI frameworks, +which happens to be the case here too. Therefore, let us put the breakpoint where the `UI` transfers control to +the `Logic` component. -According to the sequence diagram you saw earlier (and repeated above for reference), the `UI` component yields control to the `Logic` component through a method named `execute`. Searching through the code base for an `execute()` method that belongs to the `Logic` component yields a promising candidate in `seedu.address.logic.Logic`. +According to the sequence diagram you saw earlier (and repeated above for reference), the `UI` component yields control +to the `Logic` component through a method named `execute`. Searching through the code base for an `execute()` method +that belongs to the `Logic` component yields a promising candidate in `seedu.address.logic.Logic`.
-:bulb: **Intellij Tip:** The ['**Search Everywhere**' feature](https://www.jetbrains.com/help/idea/searching-everywhere.html) can be used here. In particular, the '**Find Symbol**' ('Symbol' here refers to methods, variables, classes etc.) variant of that feature is quite useful here as we are looking for a _method_ named `execute`, not simply the text `execute`. +:bulb: **Intellij Tip:** The ['**Search Everywhere +**' feature](https://www.jetbrains.com/help/idea/searching-everywhere.html) can be used here. In particular, the '**Find +Symbol**' ('Symbol' here refers to methods, variables, classes etc.) variant of that feature is quite useful here as we +are looking for a _method_ named `execute`, not simply the text `execute`.
-A quick look at the `seedu.address.logic.Logic` (an extract given below) confirms that this indeed might be what we’re looking for. +A quick look at the `seedu.address.logic.Logic` (an extract given below) confirms that this indeed might be what we’re +looking for. ```java public interface Logic { @@ -65,31 +86,39 @@ public interface Logic { ``` But apparently, this is an interface, not a concrete implementation. -That should be fine because the [Architecture section of the Developer Guide](../DeveloperGuide.html#architecture) tells us that components interact through interfaces. Here's the relevant diagram: +That should be fine because the [Architecture section of the Developer Guide](../DeveloperGuide.html#architecture) tells +us that components interact through interfaces. Here's the relevant diagram: -Next, let's find out which statement(s) in the `UI` code is calling this method, thus transferring control from the `UI` to the `Logic`. +Next, let's find out which statement(s) in the `UI` code is calling this method, thus transferring control from the `UI` +to the `Logic`.
-:bulb: **Intellij Tip:** The ['**Find Usages**' feature](https://www.jetbrains.com/help/idea/find-highlight-usages.html#find-usages) can find from which parts of the code a class/method/variable is being used. +:bulb: **Intellij Tip:** The ['**Find Usages +**' feature](https://www.jetbrains.com/help/idea/find-highlight-usages.html#find-usages) can find from which parts of +the code a class/method/variable is being used.
![`Find Usages` tool window. `Edit` \> `Find` \> `Find Usages`.](../images/tracing/FindUsages.png) Bingo\! `MainWindow#executeCommand()` seems to be exactly what we’re looking for\! -Now let’s set the breakpoint. First, double-click the item to reach the corresponding code. Once there, click on the left gutter to set a breakpoint, as shown below. - ![LeftGutter](../images/tracing/LeftGutter.png) +Now let’s set the breakpoint. First, double-click the item to reach the corresponding code. Once there, click on the +left gutter to set a breakpoint, as shown below. +![LeftGutter](../images/tracing/LeftGutter.png) ## Tracing the execution path -Recall from the User Guide that the `edit` command has the format: `edit INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [t/TAG]…​` For this tutorial we will be issuing the command `edit 1 n/Alice Yeoh`. +Recall from the User Guide that the `edit` command has the +format: `edit INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [t/TAG]…​` For this tutorial we will be issuing the +command `edit 1 n/Alice Yeoh`.
-:bulb: **Tip:** Over the course of the debugging session, you will encounter every major component in the application. Try to keep track of what happens inside the component and where the execution transfers to another component. +:bulb: **Tip:** Over the course of the debugging session, you will encounter every major component in the application. +Try to keep track of what happens inside the component and where the execution transfers to another component.
1. To start the debugging session, simply `Run` \> `Debug Main` @@ -101,12 +130,16 @@ Recall from the User Guide that the `edit` command has the format: `edit INDEX [ 1. Use the _Show execution point_ feature to jump to the line of code that we stopped at:
![ShowExecutionPoint](../images/tracing/ShowExecutionPoint.png)
- `CommandResult commandResult = logic.execute(commandText);` is the line that you end up at (i.e., the place where we put the breakpoint). + `CommandResult commandResult = logic.execute(commandText);` is the line that you end up at (i.e., the place where we + put the breakpoint). -1. We are interested in the `logic.execute(commandText)` portion of that line so let’s _Step in_ into that method call:
- ![StepInto](../images/tracing/StepInto.png) +1. We are interested in the `logic.execute(commandText)` portion of that line so let’s _Step in_ into that method + call:
+ ![StepInto](../images/tracing/StepInto.png) -1. We end up in `LogicManager#execute()` (not `Logic#execute` -- but this is expected because we know the `execute()` method in the `Logic` interface is actually implemented by the `LogicManager` class). Let’s take a look at the body of the method. Given below is the same code, with additional explanatory comments. +1. We end up in `LogicManager#execute()` (not `Logic#execute` -- but this is expected because we know the `execute()` + method in the `Logic` interface is actually implemented by the `LogicManager` class). Let’s take a look at the body + of the method. Given below is the same code, with additional explanatory comments. **LogicManager\#execute().** @@ -136,12 +169,14 @@ Recall from the User Guide that the `edit` command has the format: `edit INDEX [ } ``` -1. `LogicManager#execute()` appears to delegate most of the heavy lifting to other components. Let’s take a closer look at each one. +1. `LogicManager#execute()` appears to delegate most of the heavy lifting to other components. Let’s take a closer look + at each one. 1. _Step over_ the logging code since it is of no interest to us now. ![StepOver](../images/tracing/StepOver.png) -1. _Step into_ the line where user input in parsed from a String to a Command, which should bring you to the `AddressBookParser#parseCommand()` method (partial code given below): +1. _Step into_ the line where user input in parsed from a String to a Command, which should bring you to + the `AddressBookParser#parseCommand()` method (partial code given below): ``` java public Command parseCommand(String userInput) throws ParseException { ... @@ -150,12 +185,14 @@ Recall from the User Guide that the `edit` command has the format: `edit INDEX [ ... ``` -1. _Step over_ the statements in that method until you reach the `switch` statement. The 'Variables' window now shows the value of both `commandWord` and `arguments`:
- ![Variables](../images/tracing/Variables.png) +1. _Step over_ the statements in that method until you reach the `switch` statement. The 'Variables' window now shows + the value of both `commandWord` and `arguments`:
+ ![Variables](../images/tracing/Variables.png) 1. We see that the value of `commandWord` is now `edit` but `arguments` is still not processed in any meaningful way. -1. Stepping through the `switch` block, we end up at a call to `EditCommandParser().parse()` as expected (because the command we typed is an edit command). +1. Stepping through the `switch` block, we end up at a call to `EditCommandParser().parse()` as expected (because the + command we typed is an edit command). ``` java ... @@ -164,93 +201,121 @@ Recall from the User Guide that the `edit` command has the format: `edit INDEX [ ... ``` -1. Let’s see what `EditCommandParser#parse()` does by stepping into it. You might have to click the 'step into' button multiple times here because there are two method calls in that statement: `EditCommandParser()` and `parse()`. +1. Let’s see what `EditCommandParser#parse()` does by stepping into it. You might have to click the 'step into' button + multiple times here because there are two method calls in that statement: `EditCommandParser()` and `parse()`.
:bulb: **Intellij Tip:** Sometimes, you might end up stepping into functions that are not of interest. Simply use the `step out` button to get out of them!
-1. Stepping through the method shows that it calls `ArgumentTokenizer#tokenize()` and `ParserUtil#parseIndex()` to obtain the arguments and index required. +1. Stepping through the method shows that it calls `ArgumentTokenizer#tokenize()` and `ParserUtil#parseIndex()` to + obtain the arguments and index required. -1. The rest of the method seems to exhaustively check for the existence of each possible parameter of the `edit` command and store any possible changes in an `EditPersonDescriptor`. Recall that we can verify the contents of `editPersonDesciptor` through the 'Variables' window.
+1. The rest of the method seems to exhaustively check for the existence of each possible parameter of the `edit` command + and store any possible changes in an `EditPersonDescriptor`. Recall that we can verify the contents + of `editPersonDesciptor` through the 'Variables' window.
![EditCommand](../images/tracing/EditCommand.png) -1. As you just traced through some code involved in parsing a command, you can take a look at this class diagram to see where the various parsing-related classes you encountered fit into the design of the `Logic` component. +1. As you just traced through some code involved in parsing a command, you can take a look at this class diagram to see + where the various parsing-related classes you encountered fit into the design of the `Logic` component. 1. Let’s continue stepping through until we return to `LogicManager#execute()`. - The sequence diagram below shows the details of the execution path through the Logic component. Does the execution path you traced in the code so far match the diagram?
- ![Tracing an `edit` command through the Logic component](../images/tracing/LogicSequenceDiagram.png) + The sequence diagram below shows the details of the execution path through the Logic component. Does the execution + path you traced in the code so far match the diagram?
+ ![Tracing an `edit` command through the Logic component](../images/tracing/LogicSequenceDiagram.png) -1. Now, step over until you read the statement that calls the `execute()` method of the `EditCommand` object received, and step into that `execute()` method (partial code given below): +1. Now, step over until you read the statement that calls the `execute()` method of the `EditCommand` object received, + and step into that `execute()` method (partial code given below): **`EditCommand#execute()`:** ``` java @Override public CommandResult execute(Model model) throws CommandException { ... - Person personToEdit = lastShownList.get(index.getZeroBased()); - Person editedPerson = createEditedPerson(personToEdit, editPersonDescriptor); - if (!personToEdit.isSamePerson(editedPerson) && model.hasPerson(editedPerson)) { + Person developerToEdit = lastShownList.get(index.getZeroBased()); + Person editedDeveloper = createEditedPerson(developerToEdit, editPersonDescriptor); + if (!developerToEdit.isSamePerson(editedDeveloper) && model.hasPerson(editedDeveloper)) { throw new CommandException(MESSAGE_DUPLICATE_PERSON); } - model.setPerson(personToEdit, editedPerson); + model.setPerson(developerToEdit, editedDeveloper); model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); - return new CommandResult(String.format(MESSAGE_EDIT_PERSON_SUCCESS, editedPerson)); + return new CommandResult(String.format(MESSAGE_EDIT_PERSON_SUCCESS, editedDeveloper)); } ``` 1. As suspected, `command#execute()` does indeed make changes to the `model` object. Specifically, - * it uses the `setPerson()` method (defined in the interface `Model` and implemented in `ModelManager` as per the usual pattern) to update the person data. - * it uses the `updateFilteredPersonList` method to ask the `Model` to populate the 'filtered list' with _all_ persons.
- FYI, The 'filtered list' is the list of persons resulting from the most recent operation that will be shown to the user immediately after. For the `edit` command, we populate it with all the persons so that the user can see the edited person along with all other persons. If this was a `find` command, we would be setting that list to contain the search results instead.
- To provide some context, given below is the class diagram of the `Model` component. See if you can figure out where the 'filtered list' of persons is being tracked. -
- * :bulb: This may be a good time to read through the [`Model` component section of the DG](../DeveloperGuide.html#model-component) - -1. As you step through the rest of the statements in the `EditCommand#execute()` method, you'll see that it creates a `CommandResult` object (containing information about the result of the execution) and returns it.
- Advancing the debugger by one more step should take you back to the middle of the `LogicManager#execute()` method.
- -1. Given that you have already seen quite a few classes in the `Logic` component in action, see if you can identify in this partial class diagram some of the classes you've encountered so far, and see how they fit into the class structure of the `Logic` component: - - * :bulb: This may be a good time to read through the [`Logic` component section of the DG](../DeveloperGuide.html#logic-component) - -1. Similar to before, you can step over/into statements in the `LogicManager#execute()` method to examine how the control is transferred to the `Storage` component and what happens inside that component. + * it uses the `setPerson()` method (defined in the interface `Model` and implemented in `ModelManager` as per the + usual pattern) to update the developer data. + * it uses the `updateFilteredPersonList` method to ask the `Model` to populate the 'filtered list' with _all_ + developers.
+ FYI, The 'filtered list' is the list of developers resulting from the most recent operation that will be shown to + the user immediately after. For the `edit` command, we populate it with all the developers so that the user can + see the edited developer along with all other developers. If this was a `find` command, we would be setting that + list to contain the search results instead.
+ To provide some context, given below is the class diagram of the `Model` component. See if you can figure out + where the 'filtered list' of developers is being tracked. +
+ * :bulb: This may be a good time to read through + the [`Model` component section of the DG](../DeveloperGuide.html#model-component) + +1. As you step through the rest of the statements in the `EditCommand#execute()` method, you'll see that it creates + a `CommandResult` object (containing information about the result of the execution) and returns it.
+ Advancing the debugger by one more step should take you back to the middle of the `LogicManager#execute()` + method.
+ +1. Given that you have already seen quite a few classes in the `Logic` component in action, see if you can identify in + this partial class diagram some of the classes you've encountered so far, and see how they fit into the class + structure of the `Logic` component: + + * :bulb: This may be a good time to read through + the [`Logic` component section of the DG](../DeveloperGuide.html#logic-component) + +1. Similar to before, you can step over/into statements in the `LogicManager#execute()` method to examine how the + control is transferred to the `Storage` component and what happens inside that component.
:bulb: **Intellij Tip:** When trying to step into a statement such as `storage.saveAddressBook(model.getAddressBook())` which contains multiple method calls, Intellij will let you choose (by clicking) which one you want to step into.
-1. As you step through the code inside the `Storage` component, you will eventually arrive at the `JsonAddressBook#saveAddressBook()` method which calls the `JsonSerializableAddressBook` constructor, to create an object that can be _serialized_ (i.e., stored in storage medium) in JSON format. That constructor is given below (with added line breaks for easier readability): +1. As you step through the code inside the `Storage` component, you will eventually arrive at + the `JsonAddressBook#saveAddressBook()` method which calls the `JsonSerializableAddressBook` constructor, to create + an object that can be _serialized_ (i.e., stored in storage medium) in JSON format. That constructor is given below ( + with added line breaks for easier readability): - **`JsonSerializableAddressBook` constructor:** - ``` java - /** - * 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())); - } - ``` + **`JsonSerializableAddressBook` constructor:** + ``` java + /** + * 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) { + developers.addAll( + source.getPersonList() + .stream() + .map(JsonAdaptedPerson::new) + .collect(Collectors.toList())); + } + ``` -1. It appears that a `JsonAdaptedPerson` is created for each `Person` and then added to the `JsonSerializableAddressBook`. - This is because regular Java objects need to go through an _adaptation_ for them to be suitable to be saved in JSON format. +1. It appears that a `JsonAdaptedPerson` is created for each `Person` and then added to + the `JsonSerializableAddressBook`. + This is because regular Java objects need to go through an _adaptation_ for them to be suitable to be saved in JSON + format. -1. While you are stepping through the classes in the `Storage` component, here is the component's class diagram to help you understand how those classes fit into the structure of the component.
+1. While you are stepping through the classes in the `Storage` component, here is the component's class diagram to help + you understand how those classes fit into the structure of the component.
- * :bulb: This may be a good time to read through the [`Storage` component section of the DG](../DeveloperGuide.html#storage-component) + * :bulb: This may be a good time to read through + the [`Storage` component section of the DG](../DeveloperGuide.html#storage-component) -1. We can continue to step through until you reach the end of the `LogicManager#execute()` method and return to the `MainWindow#executeCommand()` method (the place where we put the original breakpoint). +1. We can continue to step through until you reach the end of the `LogicManager#execute()` method and return to + the `MainWindow#executeCommand()` method (the place where we put the original breakpoint). 1. Stepping into `resultDisplay.setFeedbackToUser(commandResult.getFeedbackToUser());`, we end up in: - **`ResultDisplay#setFeedbackToUser()`** + **`ResultDisplay#setFeedbackToUser()`** ``` java public void setFeedbackToUser(String feedbackToUser) { requireNonNull(feedbackToUser); @@ -259,43 +324,45 @@ Recall from the User Guide that the `edit` command has the format: `edit INDEX [ ``` 1. Finally, you can step through until you reach the end of`MainWindow#executeCommand()`.
- :bulb: This may be a good time to read through the [`UI` component section of the DG](../DeveloperGuide.html#ui-component) - + :bulb: This may be a good time to read through + the [`UI` component section of the DG](../DeveloperGuide.html#ui-component) ## Conclusion -In this tutorial, we traced a valid edit command from raw user input to the result being displayed to the user. From this tutorial, you learned more about how the various components work together to produce a response to a user command. +In this tutorial, we traced a valid edit command from raw user input to the result being displayed to the user. From +this tutorial, you learned more about how the various components work together to produce a response to a user command. -Here are some quick questions you can try to answer based on your execution path tracing. In some cases, you can do further tracing for the given commands to find exactly what happens. +Here are some quick questions you can try to answer based on your execution path tracing. In some cases, you can do +further tracing for the given commands to find exactly what happens. -1. In this tutorial, we traced the "happy path" (i.e., no errors). What - do you think will happen if we traced the following commands - instead? What exceptions do you think will be thrown (if any), where - will the exceptions be thrown and where will they be handled? +1. In this tutorial, we traced the "happy path" (i.e., no errors). What + do you think will happen if we traced the following commands + instead? What exceptions do you think will be thrown (if any), where + will the exceptions be thrown and where will they be handled? - 1. `redit 1 n/Alice Yu` + 1. `redit 1 n/Alice Yu` - 2. `edit 0 n/Alice Yu` + 2. `edit 0 n/Alice Yu` - 3. `edit 1 n/Alex Yeoh` + 3. `edit 1 n/Alex Yeoh` - 4. `edit 1` + 4. `edit 1` - 5. `edit 1 n/アリス ユー` + 5. `edit 1 n/アリス ユー` - 6. `edit 1 t/one t/two t/three t/one` + 6. `edit 1 t/one t/two t/three t/one` -2. What components will you have to modify to perform the following - enhancements to the application? +2. What components will you have to modify to perform the following + enhancements to the application? - 1. Make command words case-insensitive + 1. Make command words case-insensitive - 2. Allow `delete` to remove more than one index at a time + 2. Allow `delete` to remove more than one index at a time - 3. Save the address book in the CSV format instead + 3. Save the address book in the CSV format instead - 4. Add a new command + 4. Add a new command - 5. Add a new field to `Person` + 5. Add a new field to `Person` - 6. Add a new entity to the address book + 6. Add a new entity to the address book diff --git a/gradle.properties b/gradle.properties index 40764dc1791..c75a9af3404 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,5 @@ org.gradle.parallel=false org.gradle.jvmargs=-XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError -Xmx1024m -Dfile.encoding=utf-8 - # TODO: This is a workaround for a JDK11 bug which causes test coverage upload to fail. # Remove it when https://bugs.openjdk.java.net/browse/JDK-8221253 is fixed. systemProp.jdk.tls.client.protocols="TLSv1,TLSv1.1,TLSv1.2" diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index e708b1c023e..41d9927a4d4 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/src/main/java/seedu/address/AppParameters.java b/src/main/java/seedu/address/AppParameters.java index 3d603622d4e..0fc21bcdfb4 100644 --- a/src/main/java/seedu/address/AppParameters.java +++ b/src/main/java/seedu/address/AppParameters.java @@ -19,14 +19,6 @@ public class AppParameters { private Path configPath; - public Path getConfigPath() { - return configPath; - } - - public void setConfigPath(Path configPath) { - this.configPath = configPath; - } - /** * Parses the application command-line parameters. */ @@ -44,6 +36,14 @@ public static AppParameters parse(Application.Parameters parameters) { return appParameters; } + public Path getConfigPath() { + return configPath; + } + + public void setConfigPath(Path configPath) { + this.configPath = configPath; + } + @Override public boolean equals(Object other) { if (other == this) { diff --git a/src/main/java/seedu/address/Main.java b/src/main/java/seedu/address/Main.java index ec1b7958746..f14473af111 100644 --- a/src/main/java/seedu/address/Main.java +++ b/src/main/java/seedu/address/Main.java @@ -7,17 +7,17 @@ /** * The main entry point to the application. - * + *

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

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

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

* By having a separate main class (Main) that doesn't extend Application * to be the entry point of the application, we avoid this issue. */ diff --git a/src/main/java/seedu/address/MainApp.java b/src/main/java/seedu/address/MainApp.java index 3d6bd06d5af..77a90798a34 100644 --- a/src/main/java/seedu/address/MainApp.java +++ b/src/main/java/seedu/address/MainApp.java @@ -36,7 +36,7 @@ */ public class MainApp extends Application { - public static final Version VERSION = new Version(0, 2, 2, true); + public static final Version VERSION = new Version(1, 4, 0, true); private static final Logger logger = LogsCenter.getLogger(MainApp.class); diff --git a/src/main/java/seedu/address/commons/core/LogsCenter.java b/src/main/java/seedu/address/commons/core/LogsCenter.java index 8cf8e15a0f0..b3cd9b0e3de 100644 --- a/src/main/java/seedu/address/commons/core/LogsCenter.java +++ b/src/main/java/seedu/address/commons/core/LogsCenter.java @@ -14,8 +14,8 @@ * Configures and manages loggers and handlers, including their logging level * Named {@link Logger}s can be obtained from this class
* These loggers have been configured to output messages to the console and a {@code .log} file by default, - * at the {@code INFO} level. A new {@code .log} file with a new numbering will be created after the log - * file reaches 5MB big, up to a maximum of 5 files.
+ * at the {@code INFO} level. A new {@code .log} file with a new numbering will be created after the log + * file reaches 5MB big, up to a maximum of 5 files.
*/ public class LogsCenter { private static final int MAX_FILE_COUNT = 5; diff --git a/src/main/java/seedu/address/commons/core/Version.java b/src/main/java/seedu/address/commons/core/Version.java index 491d24559b4..1e3c2882da9 100644 --- a/src/main/java/seedu/address/commons/core/Version.java +++ b/src/main/java/seedu/address/commons/core/Version.java @@ -32,24 +32,9 @@ public Version(int major, int minor, int patch, boolean isEarlyAccess) { this.isEarlyAccess = isEarlyAccess; } - public int getMajor() { - return major; - } - - public int getMinor() { - return minor; - } - - public int getPatch() { - return patch; - } - - public boolean isEarlyAccess() { - return isEarlyAccess; - } - /** * Parses a version number string in the format V1.2.3. + * * @param versionString version number string * @return a Version object */ @@ -67,6 +52,22 @@ public static Version fromString(String versionString) throws IllegalArgumentExc versionMatcher.group(4) == null ? false : true); } + public int getMajor() { + return major; + } + + public int getMinor() { + return minor; + } + + public int getPatch() { + return patch; + } + + public boolean isEarlyAccess() { + return isEarlyAccess; + } + @JsonValue public String toString() { return String.format("V%d.%d.%d%s", major, minor, patch, isEarlyAccess ? "ea" : ""); diff --git a/src/main/java/seedu/address/commons/core/index/Index.java b/src/main/java/seedu/address/commons/core/index/Index.java index dd170d8b68d..6b0d846b4c9 100644 --- a/src/main/java/seedu/address/commons/core/index/Index.java +++ b/src/main/java/seedu/address/commons/core/index/Index.java @@ -4,7 +4,7 @@ /** * Represents a zero-based or one-based index. - * + *

* {@code Index} should be used right from the start (when parsing in a new user input), so that if the current * component wants to communicate with another component, it can send an {@code Index} to avoid having to know what * base the other component is using for its index. However, after receiving the {@code Index}, that component can @@ -25,14 +25,6 @@ private Index(int zeroBasedIndex) { this.zeroBasedIndex = zeroBasedIndex; } - public int getZeroBased() { - return zeroBasedIndex; - } - - public int getOneBased() { - return zeroBasedIndex + 1; - } - /** * Creates a new {@code Index} using a zero-based index. */ @@ -47,6 +39,14 @@ public static Index fromOneBased(int oneBasedIndex) { return new Index(oneBasedIndex - 1); } + public int getZeroBased() { + return zeroBasedIndex; + } + + public int getOneBased() { + return zeroBasedIndex + 1; + } + @Override public boolean equals(Object other) { if (other == this) { diff --git a/src/main/java/seedu/address/commons/exceptions/IllegalValueException.java b/src/main/java/seedu/address/commons/exceptions/IllegalValueException.java index 19124db485c..651ce290208 100644 --- a/src/main/java/seedu/address/commons/exceptions/IllegalValueException.java +++ b/src/main/java/seedu/address/commons/exceptions/IllegalValueException.java @@ -13,7 +13,7 @@ public IllegalValueException(String message) { /** * @param message should contain relevant information on the failed constraint(s) - * @param cause of the main exception + * @param cause of the main exception */ public IllegalValueException(String message, Throwable cause) { super(message, cause); diff --git a/src/main/java/seedu/address/commons/util/CollectionUtil.java b/src/main/java/seedu/address/commons/util/CollectionUtil.java index eafe4dfd681..942edda97d7 100644 --- a/src/main/java/seedu/address/commons/util/CollectionUtil.java +++ b/src/main/java/seedu/address/commons/util/CollectionUtil.java @@ -12,7 +12,9 @@ */ public class CollectionUtil { - /** @see #requireAllNonNull(Collection) */ + /** + * @see #requireAllNonNull(Collection) + */ public static void requireAllNonNull(Object... items) { requireNonNull(items); Stream.of(items).forEach(Objects::requireNonNull); diff --git a/src/main/java/seedu/address/commons/util/FileUtil.java b/src/main/java/seedu/address/commons/util/FileUtil.java index b1e2767cdd9..f811215474c 100644 --- a/src/main/java/seedu/address/commons/util/FileUtil.java +++ b/src/main/java/seedu/address/commons/util/FileUtil.java @@ -20,6 +20,7 @@ public static boolean isFileExists(Path file) { /** * Returns true if {@code path} can be converted into a {@code Path} via {@link Paths#get(String)}, * otherwise returns false. + * * @param path A string representing the file path. Cannot be null. */ public static boolean isValidPath(String path) { @@ -33,6 +34,7 @@ public static boolean isValidPath(String path) { /** * Creates a file if it does not exist along with its missing parent directories. + * * @throws IOException if the file or directory cannot be created. */ public static void createIfMissing(Path file) throws IOException { diff --git a/src/main/java/seedu/address/commons/util/JsonUtil.java b/src/main/java/seedu/address/commons/util/JsonUtil.java index 100cb16c395..cc2d698adb0 100644 --- a/src/main/java/seedu/address/commons/util/JsonUtil.java +++ b/src/main/java/seedu/address/commons/util/JsonUtil.java @@ -52,7 +52,7 @@ static T deserializeObjectFromJsonFile(Path jsonFile, Class classOfObject * Returns the JSON object from the given file or {@code Optional.empty()} object if the file is not found. * If any values are missing from the file, default values will be used, as long as the file is a valid JSON file. * - * @param filePath cannot be null. + * @param filePath cannot be null. * @param classOfObjectToDeserialize JSON file has to correspond to the structure in the class given here. * @throws DataLoadingException if loading of the JSON file failed. */ @@ -80,6 +80,7 @@ public static Optional readJsonFile( /** * Saves the Json object to the specified file. * Overwrites existing file if it exists, creates a new file if it doesn't. + * * @param jsonFile cannot be null * @param filePath cannot be null * @throws IOException if there was an error during writing to the file @@ -94,6 +95,7 @@ public static void saveJsonFile(T jsonFile, Path filePath) throws IOExceptio /** * Converts a given string representation of a JSON data to instance of a class + * * @param The generic type to create an instance of * @return The instance of T with the specified values in the JSON string */ @@ -103,8 +105,9 @@ public static T fromJsonString(String json, Class instanceClass) throws I /** * Converts a given instance of a class into its JSON data string representation + * * @param instance The T object to be converted into the JSON string - * @param The generic type to create an instance of + * @param The generic type to create an instance of * @return JSON data representation of the given class instance, in string */ public static String toJsonString(T instance) throws JsonProcessingException { @@ -129,7 +132,6 @@ protected Level _deserialize(String value, DeserializationContext ctxt) { * Gets the logging level that matches loggingLevelString *

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

+     * Ignores case, but a full word match is required.
+     * 
examples:
      *       containsWordIgnoreCase("ABc def", "abc") == true
      *       containsWordIgnoreCase("ABc def", "DEF") == true
      *       containsWordIgnoreCase("ABc def", "AB") == false //not a full word match
      *       
+ * * @param sentence cannot be null - * @param word cannot be null, cannot be empty, must be a single word + * @param word cannot be null, cannot be empty, must be a single word */ public static boolean containsWordIgnoreCase(String sentence, String word) { requireNonNull(sentence); @@ -38,6 +39,32 @@ public static boolean containsWordIgnoreCase(String sentence, String word) { .anyMatch(preppedWord::equalsIgnoreCase); } + /** + * Returns true if the {@code sentence} contains the {@code word} as a partial word match. + * Ignores case, and it matches the word even if it's only a part of a larger word. + *
examples:
+     * containsPartialWordIgnoreCase("ABc def", "abc") == true
+     * containsPartialWordIgnoreCase("ABc def", "DEF") == true
+     * containsPartialWordIgnoreCase("ABc def", "AB") == true
+     * 
+ * + * @param sentence cannot be null + * @param word cannot be null, cannot be empty + */ + public static boolean containsPartialWordIgnoreCase(String sentence, String word) { + requireNonNull(sentence); + requireNonNull(word); + + String preppedWord = word.trim(); + checkArgument(!preppedWord.isEmpty(), "Word parameter cannot be empty"); + + String preppedSentence = sentence; + String[] wordsInPreppedSentence = preppedSentence.split("\\s+"); + + return Arrays.stream(wordsInPreppedSentence) + .anyMatch(wordInSentence -> wordInSentence.toLowerCase().contains(preppedWord.toLowerCase())); + } + /** * Returns a detailed message of the t, including the stack trace. */ @@ -53,6 +80,7 @@ public static String getDetails(Throwable t) { * e.g. 1, 2, 3, ..., {@code Integer.MAX_VALUE}
* Will return false for any other non-null string input * e.g. empty string, "-1", "0", "+1", and " 2 " (untrimmed), "3 0" (contains whitespace), "1 a" (contains letters) + * * @throws NullPointerException if {@code s} is null. */ public static boolean isNonZeroUnsignedInteger(String s) { diff --git a/src/main/java/seedu/address/commons/util/ToStringBuilder.java b/src/main/java/seedu/address/commons/util/ToStringBuilder.java index d979b926734..9abdc755f39 100644 --- a/src/main/java/seedu/address/commons/util/ToStringBuilder.java +++ b/src/main/java/seedu/address/commons/util/ToStringBuilder.java @@ -30,7 +30,7 @@ public ToStringBuilder(Object object) { /** * Adds a field name/value pair to the output string. * - * @param fieldName The name of the field. + * @param fieldName The name of the field. * @param fieldValue The value of the field. * @return A reference to this {@code ToStringBuilder} object, allowing method calls to be chained. */ diff --git a/src/main/java/seedu/address/logic/Logic.java b/src/main/java/seedu/address/logic/Logic.java index 92cd8fa605a..743149b250a 100644 --- a/src/main/java/seedu/address/logic/Logic.java +++ b/src/main/java/seedu/address/logic/Logic.java @@ -8,7 +8,8 @@ import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.logic.parser.exceptions.ParseException; import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.person.Person; +import seedu.address.model.client.Client; +import seedu.address.model.developer.Developer; /** * API of the Logic component @@ -16,10 +17,11 @@ public interface Logic { /** * Executes the command and returns the result. + * * @param commandText The command as entered by the user. * @return the result of the command execution. * @throws CommandException If an error occurs during command execution. - * @throws ParseException If an error occurs during parsing. + * @throws ParseException If an error occurs during parsing. */ CommandResult execute(String commandText) throws CommandException, ParseException; @@ -30,8 +32,14 @@ public interface Logic { */ ReadOnlyAddressBook getAddressBook(); - /** Returns an unmodifiable view of the filtered list of persons */ - ObservableList getFilteredPersonList(); + /** + * Returns an unmodifiable view of the filtered list of developers + */ + ObservableList getFilteredDeveloperList(); + + ObservableList getFilteredClientList(); + + ObservableList getFilteredProjectList(); /** * Returns the user prefs' address book file path. diff --git a/src/main/java/seedu/address/logic/LogicManager.java b/src/main/java/seedu/address/logic/LogicManager.java index 5aa3b91c7d0..427777c6b0d 100644 --- a/src/main/java/seedu/address/logic/LogicManager.java +++ b/src/main/java/seedu/address/logic/LogicManager.java @@ -10,12 +10,14 @@ import seedu.address.commons.core.LogsCenter; import seedu.address.logic.commands.Command; import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.LockCommand; import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.logic.parser.AddressBookParser; 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.client.Client; +import seedu.address.model.developer.Developer; import seedu.address.storage.Storage; /** @@ -40,6 +42,7 @@ public LogicManager(Model model, Storage storage) { this.model = model; this.storage = storage; addressBookParser = new AddressBookParser(); + new LockCommand().execute(model); } @Override @@ -67,8 +70,18 @@ public ReadOnlyAddressBook getAddressBook() { } @Override - public ObservableList getFilteredPersonList() { - return model.getFilteredPersonList(); + public ObservableList getFilteredDeveloperList() { + return model.getFilteredDeveloperList(); + } + + @Override + public ObservableList getFilteredClientList() { + return model.getFilteredClientList(); + } + + @Override + public ObservableList getFilteredProjectList() { + return model.getFilteredProjectList(); } @Override diff --git a/src/main/java/seedu/address/logic/Messages.java b/src/main/java/seedu/address/logic/Messages.java index ecd32c31b53..58e6479abbb 100644 --- a/src/main/java/seedu/address/logic/Messages.java +++ b/src/main/java/seedu/address/logic/Messages.java @@ -5,19 +5,73 @@ import java.util.stream.Stream; import seedu.address.logic.parser.Prefix; -import seedu.address.model.person.Person; +import seedu.address.model.client.Client; +import seedu.address.model.developer.Developer; /** * Container for user visible messages. */ public class Messages { - public static final String MESSAGE_UNKNOWN_COMMAND = "Unknown command"; + public static final String MESSAGE_UNKNOWN_COMMAND = + "Unknown command!\n" + + "Please provide your command in the following format: -.\n" + + "Example: list-developer, find-client n/John, delete-project 1"; + + public static final String MESSAGE_VALID_LOCKED_COMMANDS = "Valid commands are: \n unlock, help, exit"; + public static final String MESSAGE_VALID_UNLOCKED_COMMANDS = "Type \"help\" to see the list of valid commands in" + + " User Guide!"; public static final String MESSAGE_INVALID_COMMAND_FORMAT = "Invalid command format! \n%1$s"; - public static final String MESSAGE_INVALID_PERSON_DISPLAYED_INDEX = "The person index provided is invalid"; - public static final String MESSAGE_PERSONS_LISTED_OVERVIEW = "%1$d persons listed!"; + public static final String MESSAGE_INVALID_FILE = "File does not exist!\n"; + public static final String MESSAGE_INVALID_DEVELOPER_DISPLAYED_INDEX = "The developer index provided is invalid!"; + public static final String MESSAGE_INVALID_CLIENT_DISPLAYED_INDEX = "The client index provided is invalid!"; + public static final String MESSAGE_INVALID_PROJECT_DISPLAYED_INDEX = "The project index provided is invalid!"; + public static final String MESSAGE_INVALID_DEADLINE_DISPLAYED_INDEX = "The deadline index provided is invalid!"; + public static final String MESSAGE_INAPPLICABLE_PREFIX_USED = + "You tried to edit an inapplicable field! Please check " + + "the prefixes used and try again. \n%1$s"; + public static final String MESSAGE_NONEXISTENT_PROJECT = "There is no existing Project with the name: %1$s!"; public static final String MESSAGE_DUPLICATE_FIELDS = - "Multiple values specified for the following single-valued field(s): "; + "Multiple values specified for the following single-valued field(s): "; + + public static String getMessageDevelopersListedOverview(int count) { + if (count == 0) { + return "There are no developers with matching information."; + } else if (count == 1) { + return "This is the 1 developer with matching information."; + } else { + return String.format("These are the %d developers with matching information.", count); + } + } + + public static String getMessageClientsListedOverview(int count) { + if (count == 0) { + return "There are no clients with matching information."; + } else if (count == 1) { + return "This is the 1 client with matching information."; + } else { + return String.format("These are the %d clients with matching information.", count); + } + } + + public static String getMessageProjectsListedOverview(int count) { + if (count == 0) { + return "There are no projects with matching information."; + } else if (count == 1) { + return "This is the 1 project with matching information."; + } else { + return String.format("These are the %d projects with matching information.", count); + } + } + public static String getMessageDeadlinesListedOverview(int count) { + if (count == 0) { + return "There are no deadlines with matching information."; + } else if (count == 1) { + return "This is the 1 deadline with matching information."; + } else { + return String.format("These are the %d deadlines with matching information.", count); + } + } /** * Returns an error message indicating the duplicate prefixes. @@ -32,19 +86,79 @@ public static String getErrorMessageForDuplicatePrefixes(Prefix... duplicatePref } /** - * Formats the {@code person} for display to the user. + * Formats the {@code developer} for display to the user. + */ + public static String format(Developer developer) { + final StringBuilder builder = new StringBuilder(); + builder.append(developer.getName()) + .append("; \nPhone: ") + .append(developer.getPhone()) + .append("; \nEmail: ") + .append(developer.getEmail()) + .append("; \nAddress: ") + .append(developer.getAddress()) + .append("; \nDate Joined: ") + .append(developer.getDateJoined()) + .append("; \nRole: ") + .append(developer.getRole()) + .append("; \nSalary: ") + .append(developer.getSalary()) + .append("; \nProjects: "); + developer.getProjects().forEach(t -> builder.append(t).append(" ")); + return builder.toString(); + } + + /** + * Formats a client's information into a string. + * + * @param client The client to format. + * @return A string representing the formatted client information. + */ + public static String format(Client client) { + final StringBuilder builder = new StringBuilder(); + builder.append(client.getName()) + .append("; \nPhone: ") + .append(client.getPhone()) + .append("; \nEmail: ") + .append(client.getEmail()) + .append("; \nAddress: ") + .append(client.getAddress()) + .append("; \nOrganisation: ") + .append(client.getOrganisation()) + .append("; \nRole: ") + .append(client.getRole()) + .append("; \nDocument: ") + .append(client.getDocument()) + .append("; \nProjects: "); + client.getProjects().forEach(t -> builder.append(t).append(" ")); + return builder.toString(); + } + + /** + * Formats a project's information into a string. + * + * @param project The project to format. + * @return A string representing the formatted project information. + */ + public static Object format(seedu.address.model.project.Project project) { + final StringBuilder builder = new StringBuilder(); + builder.append(project.getName()) + .append(";\nDescription: ") + .append(project.getProjectDescription()) + .append(";\nDeadlines:\n"); + project.getProjectDeadlines().forEach(t -> builder.append(t.getPrintedStringRepresentation()).append("\n")); + return builder.toString(); + } + + /** + * Formats a role into a string. + * + * @param role The role to format. + * @return A string representing the formatted role. */ - public static String format(Person person) { + public static String format(String role) { final StringBuilder builder = new StringBuilder(); - builder.append(person.getName()) - .append("; Phone: ") - .append(person.getPhone()) - .append("; Email: ") - .append(person.getEmail()) - .append("; Address: ") - .append(person.getAddress()) - .append("; Tags: "); - person.getTags().forEach(builder::append); + builder.append(role); return builder.toString(); } diff --git a/src/main/java/seedu/address/logic/commands/AddCommand.java b/src/main/java/seedu/address/logic/commands/AddCommand.java deleted file mode 100644 index 5d7185a9680..00000000000 --- a/src/main/java/seedu/address/logic/commands/AddCommand.java +++ /dev/null @@ -1,84 +0,0 @@ -package seedu.address.logic.commands; - -import static java.util.Objects.requireNonNull; -import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; -import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; -import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; -import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; -import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; - -import seedu.address.commons.util.ToStringBuilder; -import seedu.address.logic.Messages; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.model.Model; -import seedu.address.model.person.Person; - -/** - * Adds a person to the address book. - */ -public class AddCommand extends Command { - - public static final String COMMAND_WORD = "add"; - - public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a person to the address book. " - + "Parameters: " - + PREFIX_NAME + "NAME " - + PREFIX_PHONE + "PHONE " - + PREFIX_EMAIL + "EMAIL " - + PREFIX_ADDRESS + "ADDRESS " - + "[" + PREFIX_TAG + "TAG]...\n" - + "Example: " + COMMAND_WORD + " " - + PREFIX_NAME + "John Doe " - + PREFIX_PHONE + "98765432 " - + PREFIX_EMAIL + "johnd@example.com " - + PREFIX_ADDRESS + "311, Clementi Ave 2, #02-25 " - + PREFIX_TAG + "friends " - + PREFIX_TAG + "owesMoney"; - - public static final String MESSAGE_SUCCESS = "New person added: %1$s"; - public static final String MESSAGE_DUPLICATE_PERSON = "This person already exists in the address book"; - - private final Person toAdd; - - /** - * Creates an AddCommand to add the specified {@code Person} - */ - public AddCommand(Person person) { - requireNonNull(person); - toAdd = person; - } - - @Override - public CommandResult execute(Model model) throws CommandException { - requireNonNull(model); - - if (model.hasPerson(toAdd)) { - throw new CommandException(MESSAGE_DUPLICATE_PERSON); - } - - model.addPerson(toAdd); - return new CommandResult(String.format(MESSAGE_SUCCESS, Messages.format(toAdd))); - } - - @Override - public boolean equals(Object other) { - if (other == this) { - return true; - } - - // instanceof handles nulls - if (!(other instanceof AddCommand)) { - return false; - } - - AddCommand otherAddCommand = (AddCommand) other; - return toAdd.equals(otherAddCommand.toAdd); - } - - @Override - public String toString() { - return new ToStringBuilder(this) - .add("toAdd", toAdd) - .toString(); - } -} diff --git a/src/main/java/seedu/address/logic/commands/ChangePasswordCommand.java b/src/main/java/seedu/address/logic/commands/ChangePasswordCommand.java new file mode 100644 index 00000000000..0e7512a680d --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/ChangePasswordCommand.java @@ -0,0 +1,43 @@ +/** + * Command to change the user's password. + */ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import seedu.address.model.Model; +import seedu.address.model.Password; + +/** + * Changes the user's password to a new one. + */ +public class ChangePasswordCommand extends Command { + public static final String COMMAND_WORD = "change-password"; + + // Usage message for the command + public static final Object MESSAGE_USAGE = COMMAND_WORD + ": Please change the password to the correct format\n" + + Password.MESSAGE_CONSTRAINTS + "\n" + + "Example: " + COMMAND_WORD + " pw/Password123! npw/NewPass987!"; + + private String currentPw; + private String newPw; + + /** + * Constructs a {@code ChangePasswordCommand} with the current password and the new password. + * + * @param currentPw The current password. + * @param newPw The new password to replace the current password. + */ + public ChangePasswordCommand(String currentPw, String newPw) { + this.currentPw = currentPw; + this.newPw = newPw; + } + + @Override + public CommandResult execute(Model model) { + assert model != null : "Model cannot be null"; + requireNonNull(model); + String result = Password.changePassword(currentPw, newPw); + return new CommandResult(result, TabIndex.Developer); + } +} diff --git a/src/main/java/seedu/address/logic/commands/ClearCommand.java b/src/main/java/seedu/address/logic/commands/ClearCommand.java index 9c86b1fa6e4..04405f09c86 100644 --- a/src/main/java/seedu/address/logic/commands/ClearCommand.java +++ b/src/main/java/seedu/address/logic/commands/ClearCommand.java @@ -18,6 +18,7 @@ public class ClearCommand extends Command { public CommandResult execute(Model model) { requireNonNull(model); model.setAddressBook(new AddressBook()); - return new CommandResult(MESSAGE_SUCCESS); + model.commitAddressBook(model, MESSAGE_SUCCESS, TabIndex.Developer); + return new CommandResult(MESSAGE_SUCCESS, TabIndex.Developer); } } diff --git a/src/main/java/seedu/address/logic/commands/CommandResult.java b/src/main/java/seedu/address/logic/commands/CommandResult.java index 249b6072d0d..d4bbbdb85cd 100644 --- a/src/main/java/seedu/address/logic/commands/CommandResult.java +++ b/src/main/java/seedu/address/logic/commands/CommandResult.java @@ -13,27 +13,34 @@ public class CommandResult { private final String feedbackToUser; - /** Help information should be shown to the user. */ + /** + * Help information should be shown to the user. + */ private final boolean showHelp; - /** The application should exit. */ + /** + * The application should exit. + */ private final boolean exit; + private final TabIndex index; + /** * Constructs a {@code CommandResult} with the specified fields. */ - public CommandResult(String feedbackToUser, boolean showHelp, boolean exit) { + public CommandResult(String feedbackToUser, boolean showHelp, boolean exit, TabIndex index) { this.feedbackToUser = requireNonNull(feedbackToUser); this.showHelp = showHelp; this.exit = exit; + this.index = index; } /** * Constructs a {@code CommandResult} with the specified {@code feedbackToUser}, * and other fields set to their default value. */ - public CommandResult(String feedbackToUser) { - this(feedbackToUser, false, false); + public CommandResult(String feedbackToUser, TabIndex index) { + this(feedbackToUser, false, false, index); } public String getFeedbackToUser() { @@ -62,12 +69,13 @@ public boolean equals(Object other) { CommandResult otherCommandResult = (CommandResult) other; return feedbackToUser.equals(otherCommandResult.feedbackToUser) && showHelp == otherCommandResult.showHelp - && exit == otherCommandResult.exit; + && exit == otherCommandResult.exit + && index == otherCommandResult.index; } @Override public int hashCode() { - return Objects.hash(feedbackToUser, showHelp, exit); + return Objects.hash(feedbackToUser, showHelp, exit, index); } @Override @@ -76,7 +84,11 @@ public String toString() { .add("feedbackToUser", feedbackToUser) .add("showHelp", showHelp) .add("exit", exit) + .add("index", index.toString()) .toString(); } + public int getIndex() { + return index.ordinal(); + } } diff --git a/src/main/java/seedu/address/logic/commands/DeleteCommand.java b/src/main/java/seedu/address/logic/commands/DeleteCommand.java deleted file mode 100644 index 1135ac19b74..00000000000 --- a/src/main/java/seedu/address/logic/commands/DeleteCommand.java +++ /dev/null @@ -1,69 +0,0 @@ -package seedu.address.logic.commands; - -import static java.util.Objects.requireNonNull; - -import java.util.List; - -import seedu.address.commons.core.index.Index; -import seedu.address.commons.util.ToStringBuilder; -import seedu.address.logic.Messages; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.model.Model; -import seedu.address.model.person.Person; - -/** - * Deletes a person identified using it's displayed index from the address book. - */ -public class DeleteCommand extends Command { - - public static final String COMMAND_WORD = "delete"; - - public static final String MESSAGE_USAGE = COMMAND_WORD - + ": Deletes the person identified by the index number used in the displayed person list.\n" - + "Parameters: INDEX (must be a positive integer)\n" - + "Example: " + COMMAND_WORD + " 1"; - - public static final String MESSAGE_DELETE_PERSON_SUCCESS = "Deleted Person: %1$s"; - - private final Index targetIndex; - - public DeleteCommand(Index targetIndex) { - this.targetIndex = targetIndex; - } - - @Override - public CommandResult execute(Model model) throws CommandException { - requireNonNull(model); - List lastShownList = model.getFilteredPersonList(); - - if (targetIndex.getZeroBased() >= lastShownList.size()) { - throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); - } - - Person personToDelete = lastShownList.get(targetIndex.getZeroBased()); - model.deletePerson(personToDelete); - return new CommandResult(String.format(MESSAGE_DELETE_PERSON_SUCCESS, Messages.format(personToDelete))); - } - - @Override - public boolean equals(Object other) { - if (other == this) { - return true; - } - - // instanceof handles nulls - if (!(other instanceof DeleteCommand)) { - return false; - } - - DeleteCommand otherDeleteCommand = (DeleteCommand) other; - return targetIndex.equals(otherDeleteCommand.targetIndex); - } - - @Override - public String toString() { - return new ToStringBuilder(this) - .add("targetIndex", targetIndex) - .toString(); - } -} diff --git a/src/main/java/seedu/address/logic/commands/EditCommand.java b/src/main/java/seedu/address/logic/commands/EditCommand.java deleted file mode 100644 index 4b581c7331e..00000000000 --- a/src/main/java/seedu/address/logic/commands/EditCommand.java +++ /dev/null @@ -1,242 +0,0 @@ -package seedu.address.logic.commands; - -import static java.util.Objects.requireNonNull; -import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; -import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; -import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; -import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; -import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; -import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; - -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; - -import seedu.address.commons.core.index.Index; -import seedu.address.commons.util.CollectionUtil; -import seedu.address.commons.util.ToStringBuilder; -import seedu.address.logic.Messages; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.model.Model; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Person; -import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; - -/** - * Edits the details of an existing person in the address book. - */ -public class EditCommand extends Command { - - public static final String COMMAND_WORD = "edit"; - - public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits the details of the person identified " - + "by the index number used in the displayed person list. " - + "Existing values will be overwritten by the input values.\n" - + "Parameters: INDEX (must be a positive integer) " - + "[" + PREFIX_NAME + "NAME] " - + "[" + PREFIX_PHONE + "PHONE] " - + "[" + PREFIX_EMAIL + "EMAIL] " - + "[" + PREFIX_ADDRESS + "ADDRESS] " - + "[" + PREFIX_TAG + "TAG]...\n" - + "Example: " + COMMAND_WORD + " 1 " - + PREFIX_PHONE + "91234567 " - + PREFIX_EMAIL + "johndoe@example.com"; - - public static final String MESSAGE_EDIT_PERSON_SUCCESS = "Edited Person: %1$s"; - public static final String MESSAGE_NOT_EDITED = "At least one field to edit must be provided."; - public static final String MESSAGE_DUPLICATE_PERSON = "This person already exists in the address book."; - - private final Index index; - private final EditPersonDescriptor editPersonDescriptor; - - /** - * @param index of the person in the filtered person list to edit - * @param editPersonDescriptor details to edit the person with - */ - public EditCommand(Index index, EditPersonDescriptor editPersonDescriptor) { - requireNonNull(index); - requireNonNull(editPersonDescriptor); - - this.index = index; - this.editPersonDescriptor = new EditPersonDescriptor(editPersonDescriptor); - } - - @Override - public CommandResult execute(Model model) throws CommandException { - requireNonNull(model); - List lastShownList = model.getFilteredPersonList(); - - if (index.getZeroBased() >= lastShownList.size()) { - throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); - } - - Person personToEdit = lastShownList.get(index.getZeroBased()); - Person editedPerson = createEditedPerson(personToEdit, editPersonDescriptor); - - if (!personToEdit.isSamePerson(editedPerson) && model.hasPerson(editedPerson)) { - throw new CommandException(MESSAGE_DUPLICATE_PERSON); - } - - model.setPerson(personToEdit, editedPerson); - model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); - return new CommandResult(String.format(MESSAGE_EDIT_PERSON_SUCCESS, Messages.format(editedPerson))); - } - - /** - * Creates and returns a {@code Person} with the details of {@code personToEdit} - * edited with {@code editPersonDescriptor}. - */ - private static Person createEditedPerson(Person personToEdit, EditPersonDescriptor editPersonDescriptor) { - assert personToEdit != null; - - Name updatedName = editPersonDescriptor.getName().orElse(personToEdit.getName()); - Phone updatedPhone = editPersonDescriptor.getPhone().orElse(personToEdit.getPhone()); - Email updatedEmail = editPersonDescriptor.getEmail().orElse(personToEdit.getEmail()); - Address updatedAddress = editPersonDescriptor.getAddress().orElse(personToEdit.getAddress()); - Set updatedTags = editPersonDescriptor.getTags().orElse(personToEdit.getTags()); - - return new Person(updatedName, updatedPhone, updatedEmail, updatedAddress, updatedTags); - } - - @Override - public boolean equals(Object other) { - if (other == this) { - return true; - } - - // instanceof handles nulls - if (!(other instanceof EditCommand)) { - return false; - } - - EditCommand otherEditCommand = (EditCommand) other; - return index.equals(otherEditCommand.index) - && editPersonDescriptor.equals(otherEditCommand.editPersonDescriptor); - } - - @Override - public String toString() { - return new ToStringBuilder(this) - .add("index", index) - .add("editPersonDescriptor", editPersonDescriptor) - .toString(); - } - - /** - * Stores the details to edit the person with. Each non-empty field value will replace the - * corresponding field value of the person. - */ - public static class EditPersonDescriptor { - private Name name; - private Phone phone; - private Email email; - private Address address; - private Set tags; - - public EditPersonDescriptor() {} - - /** - * Copy constructor. - * A defensive copy of {@code tags} is used internally. - */ - public EditPersonDescriptor(EditPersonDescriptor toCopy) { - setName(toCopy.name); - setPhone(toCopy.phone); - setEmail(toCopy.email); - setAddress(toCopy.address); - setTags(toCopy.tags); - } - - /** - * Returns true if at least one field is edited. - */ - public boolean isAnyFieldEdited() { - return CollectionUtil.isAnyNonNull(name, phone, email, address, tags); - } - - public void setName(Name name) { - this.name = name; - } - - public Optional getName() { - return Optional.ofNullable(name); - } - - public void setPhone(Phone phone) { - this.phone = phone; - } - - public Optional getPhone() { - return Optional.ofNullable(phone); - } - - public void setEmail(Email email) { - this.email = email; - } - - public Optional getEmail() { - return Optional.ofNullable(email); - } - - public void setAddress(Address address) { - this.address = address; - } - - public Optional
getAddress() { - return Optional.ofNullable(address); - } - - /** - * Sets {@code tags} to this object's {@code tags}. - * A defensive copy of {@code tags} is used internally. - */ - public void setTags(Set tags) { - this.tags = (tags != null) ? new HashSet<>(tags) : null; - } - - /** - * Returns an unmodifiable tag set, which throws {@code UnsupportedOperationException} - * if modification is attempted. - * Returns {@code Optional#empty()} if {@code tags} is null. - */ - public Optional> getTags() { - return (tags != null) ? Optional.of(Collections.unmodifiableSet(tags)) : Optional.empty(); - } - - @Override - public boolean equals(Object other) { - if (other == this) { - return true; - } - - // instanceof handles nulls - if (!(other instanceof EditPersonDescriptor)) { - return false; - } - - EditPersonDescriptor otherEditPersonDescriptor = (EditPersonDescriptor) other; - return Objects.equals(name, otherEditPersonDescriptor.name) - && Objects.equals(phone, otherEditPersonDescriptor.phone) - && Objects.equals(email, otherEditPersonDescriptor.email) - && Objects.equals(address, otherEditPersonDescriptor.address) - && Objects.equals(tags, otherEditPersonDescriptor.tags); - } - - @Override - public String toString() { - return new ToStringBuilder(this) - .add("name", name) - .add("phone", phone) - .add("email", email) - .add("address", address) - .add("tags", tags) - .toString(); - } - } -} diff --git a/src/main/java/seedu/address/logic/commands/ExitCommand.java b/src/main/java/seedu/address/logic/commands/ExitCommand.java index 3dd85a8ba90..dfe2b088274 100644 --- a/src/main/java/seedu/address/logic/commands/ExitCommand.java +++ b/src/main/java/seedu/address/logic/commands/ExitCommand.java @@ -13,7 +13,7 @@ public class ExitCommand extends Command { @Override public CommandResult execute(Model model) { - return new CommandResult(MESSAGE_EXIT_ACKNOWLEDGEMENT, false, true); + return new CommandResult(MESSAGE_EXIT_ACKNOWLEDGEMENT, false, true, TabIndex.Developer); } } 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 72b9eddd3a7..00000000000 --- a/src/main/java/seedu/address/logic/commands/FindCommand.java +++ /dev/null @@ -1,58 +0,0 @@ -package seedu.address.logic.commands; - -import static java.util.Objects.requireNonNull; - -import seedu.address.commons.util.ToStringBuilder; -import seedu.address.logic.Messages; -import seedu.address.model.Model; -import seedu.address.model.person.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) { - if (other == this) { - return true; - } - - // instanceof handles nulls - if (!(other instanceof FindCommand)) { - return false; - } - - FindCommand otherFindCommand = (FindCommand) other; - return predicate.equals(otherFindCommand.predicate); - } - - @Override - public String toString() { - return new ToStringBuilder(this) - .add("predicate", predicate) - .toString(); - } -} diff --git a/src/main/java/seedu/address/logic/commands/HelpCommand.java b/src/main/java/seedu/address/logic/commands/HelpCommand.java index bf824f91bd0..7c03d3a25b1 100644 --- a/src/main/java/seedu/address/logic/commands/HelpCommand.java +++ b/src/main/java/seedu/address/logic/commands/HelpCommand.java @@ -16,6 +16,6 @@ public class HelpCommand extends Command { @Override public CommandResult execute(Model model) { - return new CommandResult(SHOWING_HELP_MESSAGE, true, false); + return new CommandResult(SHOWING_HELP_MESSAGE, true, false, TabIndex.Developer); } } 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/LockCommand.java b/src/main/java/seedu/address/logic/commands/LockCommand.java new file mode 100644 index 00000000000..a69748f5c3e --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/LockCommand.java @@ -0,0 +1,30 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.model.Model.PREDICATE_SHOW_NO_CLIENT; +import static seedu.address.model.Model.PREDICATE_SHOW_NO_DEVELOPER; +import static seedu.address.model.Model.PREDICATE_SHOW_NO_PROJECT; + +import seedu.address.logic.parser.AddressBookParser; +import seedu.address.model.Model; + +/** + * Command to lock the application. + */ +public class LockCommand extends Command { + public static final String COMMAND_WORD = "lock"; + + public static final String MESSAGE_SUCCESS = "Locked all data"; + + + @Override + public CommandResult execute(Model model) { + assert model != null : "Model cannot be null"; + requireNonNull(model); + model.updateFilteredClientList(PREDICATE_SHOW_NO_CLIENT); + model.updateFilteredDeveloperList(PREDICATE_SHOW_NO_DEVELOPER); + model.updateFilteredProjectList(PREDICATE_SHOW_NO_PROJECT); + AddressBookParser.lock(); + return new CommandResult(MESSAGE_SUCCESS, TabIndex.Developer); + } +} diff --git a/src/main/java/seedu/address/logic/commands/RedoCommand.java b/src/main/java/seedu/address/logic/commands/RedoCommand.java new file mode 100644 index 00000000000..17efa4b90e6 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/RedoCommand.java @@ -0,0 +1,39 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.client.ClientRoles; +import seedu.address.model.developer.DeveloperRoles; + +/** + * Represents a command to redo the most recent undone command in the address book. + * It restores the state of the address book to the state before the last undo operation. + */ +public class RedoCommand extends Command { + public static final String COMMAND_WORD = "redo"; + public static final String MESSAGE_SUCCESS = "Redo successful! The change below has been redone:"; + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + model.redoAddressBook(model); + String previousCommand = model.getPreviousCommandForRedo(); + TabIndex index = model.getPreviousTabIndexForRedo(); + handleRoleRedo(previousCommand); + return new CommandResult(MESSAGE_SUCCESS + "\n" + previousCommand, index); + } + + private void handleRoleRedo(String previousCommand) { + if (previousCommand.contains("New role for client added: ")) { + ClientRoles.addClientRole(new ClientRoles(previousCommand.substring(27))); + } else if (previousCommand.contains("New role for developer added: ")) { + DeveloperRoles.addDeveloperRole(new DeveloperRoles(previousCommand.substring(30))); + } else if (previousCommand.contains("Role for clients deleted: ")) { + ClientRoles.deleteClientRole(new ClientRoles(previousCommand.substring(26))); + } else if (previousCommand.contains("Role for developers deleted: ")) { + DeveloperRoles.deleteDeveloperRole(new DeveloperRoles(previousCommand.substring(29))); + } + } +} diff --git a/src/main/java/seedu/address/logic/commands/TabIndex.java b/src/main/java/seedu/address/logic/commands/TabIndex.java new file mode 100644 index 00000000000..444c6167e71 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/TabIndex.java @@ -0,0 +1,22 @@ +package seedu.address.logic.commands; + +/** + * Enumerates the different tabs available in the application's user interface. + * These tabs correspond to different categories or views within the application. + */ +public enum TabIndex { + /** + * Represents the "Developer" tab, which is used to display and manage developers. + */ + Developer, + + /** + * Represents the "Client" tab, which is used to display and manage clients. + */ + Client, + + /** + * Represents the "Project" tab, which is used to display and manage projects. + */ + Project +} diff --git a/src/main/java/seedu/address/logic/commands/UndoCommand.java b/src/main/java/seedu/address/logic/commands/UndoCommand.java new file mode 100644 index 00000000000..faffb39a91a --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/UndoCommand.java @@ -0,0 +1,49 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.client.ClientRoles; +import seedu.address.model.developer.DeveloperRoles; + +/** + * Represents a command to undo the most recent change in the application. + * This allows users to revert the effects of the previous command. + */ +public class UndoCommand extends Command { + public static final String COMMAND_WORD = "undo"; + public static final String MESSAGE_SUCCESS = "Undo successful! The change below has been undone: "; + + /** + * Executes the undo operation by restoring the previous state of the model and + * retrieving information about the undone command. + * + * @param model The model from which to undo the most recent change. + * @return A CommandResult indicating the success of the undo operation and the + * details of the undone command. + * @throws CommandException If an error occurs during the undo operation. + */ + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + model.undoAddressBook(model); + String previousCommand = model.getPreviousCommandForUndo(); + TabIndex index = model.getPreviousTabIndex(); + // check if it is any of the role commands + handleRoleUndo(previousCommand); + return new CommandResult(MESSAGE_SUCCESS + "\n" + previousCommand, index); + } + + private void handleRoleUndo(String previousCommand) { + if (previousCommand.contains("New role for client added: ")) { + ClientRoles.deleteClientRole(new ClientRoles(previousCommand.substring(27))); + } else if (previousCommand.contains("New role for developer added: ")) { + DeveloperRoles.deleteDeveloperRole(new DeveloperRoles(previousCommand.substring(30))); + } else if (previousCommand.contains("Role for clients deleted: ")) { + ClientRoles.addClientRole(new ClientRoles(previousCommand.substring(26))); + } else if (previousCommand.contains("Role for developers deleted: ")) { + DeveloperRoles.addDeveloperRole(new DeveloperRoles(previousCommand.substring(29))); + } + } +} diff --git a/src/main/java/seedu/address/logic/commands/UnlockCommand.java b/src/main/java/seedu/address/logic/commands/UnlockCommand.java new file mode 100644 index 00000000000..393e398cdd3 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/UnlockCommand.java @@ -0,0 +1,54 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_CLIENTS; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_DEVELOPERS; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PROJECTS; + +import seedu.address.logic.parser.AddressBookParser; +import seedu.address.model.Model; +import seedu.address.model.Password; + +/** + * Represents a command to unlock access to all data within the application + * using a password. Unlocking provides access to restricted data. + */ +public class UnlockCommand extends Command { + public static final String COMMAND_WORD = "unlock"; + + public static final String MESSAGE_SUCCESS = "Unlocked all data"; + public static final String MESSAGE_FAILURE = "Incorrect Password!\n"; + public static final Object MESSAGE_USAGE = COMMAND_WORD + ": Please unlock using correct format\n" + + "Example: " + COMMAND_WORD + " pw/Password123!"; + private String input; + + /** + * Creates an UnlockCommand to unlock data with the given password. + * + * @param input The password input provided by the user. + */ + public UnlockCommand(String input) { + this.input = input; + } + + /** + * Executes the unlock operation, which grants access to restricted data + * when the provided password is correct. + * + * @param model The model containing the data to be unlocked. + * @return A CommandResult indicating the success or failure of the unlock operation. + */ + @Override + public CommandResult execute(Model model) { + assert model != null : "Model cannot be null"; + requireNonNull(model); + if (Password.verifyPassword(input)) { + model.updateFilteredClientList(PREDICATE_SHOW_ALL_CLIENTS); + model.updateFilteredDeveloperList(PREDICATE_SHOW_ALL_DEVELOPERS); + model.updateFilteredProjectList(PREDICATE_SHOW_ALL_PROJECTS); + AddressBookParser.unlock(); + return new CommandResult(MESSAGE_SUCCESS, TabIndex.Developer); + } + return new CommandResult(String.format(MESSAGE_FAILURE, Password.MESSAGE_CONSTRAINTS), TabIndex.Developer); + } +} diff --git a/src/main/java/seedu/address/logic/commands/add/AddClientCommand.java b/src/main/java/seedu/address/logic/commands/add/AddClientCommand.java new file mode 100644 index 00000000000..9dd91bb0d94 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/add/AddClientCommand.java @@ -0,0 +1,106 @@ +package seedu.address.logic.commands.add; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DOCUMENT; +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_ORGANISATION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PROJECT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ROLE; + +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.logic.Messages; +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.TabIndex; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.client.Client; + +/** + * Adds a client to the address book. + */ +public class AddClientCommand extends Command { + + public static final String COMMAND_WORD = "add-client"; + //Name name, Phone phone, Email email, Address address, Role role, Set projects, + // Name organisation, Document document + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a client to the address book. " + + "\n Parameters: " + + PREFIX_NAME + "NAME " + + PREFIX_PHONE + "PHONE " + + PREFIX_EMAIL + "EMAIL " + + PREFIX_ADDRESS + "ADDRESS " + + PREFIX_ROLE + "ROLE " + + "[" + PREFIX_PROJECT + "PROJECT]...\n" + + PREFIX_ORGANISATION + "ORGANISATION " + + PREFIX_DOCUMENT + "DOCUMENT \n" + + "Example: \n" + COMMAND_WORD + " " + + PREFIX_NAME + "John Doe " + + PREFIX_PHONE + "98765432 " + + PREFIX_EMAIL + "johnd@example.com " + + PREFIX_ADDRESS + "311, Clementi Ave 2, #02-25 " + + PREFIX_ROLE + "Developer " + + PREFIX_PROJECT + "AndroidApp " + + PREFIX_PROJECT + "CustomWebsite " + + PREFIX_ORGANISATION + "Google " + + PREFIX_DOCUMENT + "google.com "; + + public static final String MESSAGE_SUCCESS = "New client added: %1$s"; + public static final String MESSAGE_DUPLICATE_CLIENT = "This client already exists in the address book!"; + + private final Client toAdd; + + /** + * Creates an AddDeveloperCommand to add the specified {@code Developer} + */ + public AddClientCommand(Client client) { + requireNonNull(client); + toAdd = client; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + if (model.hasClient(toAdd)) { + throw new CommandException(MESSAGE_DUPLICATE_CLIENT); + } + String res = model.areProjectsValid(toAdd); + if (res != null) { + throw new CommandException(String.format(Messages.MESSAGE_NONEXISTENT_PROJECT, res)); + } + + String successMessage = String.format(MESSAGE_SUCCESS, Messages.format(toAdd)); + TabIndex index = TabIndex.Client; + + model.addClient(toAdd); + model.commitAddressBook(model, successMessage, index); + return new CommandResult(successMessage, index); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof AddClientCommand)) { + return false; + } + + AddClientCommand otherAddClientCommand = (AddClientCommand) other; + return toAdd.equals(otherAddClientCommand.toAdd); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("toAdd", toAdd) + .toString(); + } +} diff --git a/src/main/java/seedu/address/logic/commands/add/AddDeveloperCommand.java b/src/main/java/seedu/address/logic/commands/add/AddDeveloperCommand.java new file mode 100644 index 00000000000..d728417fe30 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/add/AddDeveloperCommand.java @@ -0,0 +1,110 @@ +package seedu.address.logic.commands.add; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DATEJOINED; +import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; +import static seedu.address.logic.parser.CliSyntax.PREFIX_GITHUBID; +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_PROJECT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_RATING; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ROLE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_SALARY; + +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.logic.Messages; +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.TabIndex; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.developer.Developer; + +/** + * Adds a developer to the address book. + */ +public class AddDeveloperCommand extends Command { + + public static final String COMMAND_WORD = "add-developer"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a developer to the address book. " + + "\n Parameters: " + + PREFIX_NAME + "NAME " + + PREFIX_PHONE + "PHONE " + + PREFIX_EMAIL + "EMAIL " + + PREFIX_ADDRESS + "ADDRESS " + + PREFIX_ROLE + "ROLE " + + "[" + PREFIX_PROJECT + "PROJECT]...\n" + + PREFIX_SALARY + "SALARY " + + "[" + PREFIX_DATEJOINED + "DATE JOINED] " + + PREFIX_GITHUBID + "GITHUBID " + + PREFIX_RATING + "RATING \n" + + "Example: \n" + COMMAND_WORD + " " + + PREFIX_NAME + "John Doe " + + PREFIX_PHONE + "98765432 " + + PREFIX_EMAIL + "johnd@example.com " + + PREFIX_ADDRESS + "311, Clementi Ave 2, #02-25 " + + PREFIX_ROLE + "Developer " + + PREFIX_PROJECT + "AndroidApp " + + PREFIX_PROJECT + "CustomWebsite " + + PREFIX_SALARY + "4500 " + + PREFIX_DATEJOINED + "11-11-2023 " + + PREFIX_GITHUBID + "johng " + + PREFIX_RATING + "3"; + + public static final String MESSAGE_SUCCESS = "New developer added: %1$s"; + public static final String MESSAGE_DUPLICATE_DEVELOPER = "This developer already exists in the address book!"; + + private final Developer toAdd; + + /** + * Creates an AddDeveloperCommand to add the specified {@code Developer} + */ + public AddDeveloperCommand(Developer developer) { + requireNonNull(developer); + toAdd = developer; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + if (model.hasDeveloper(toAdd)) { + throw new CommandException(MESSAGE_DUPLICATE_DEVELOPER); + } + String res = model.areProjectsValid(toAdd); + if (res != null) { + throw new CommandException(String.format(Messages.MESSAGE_NONEXISTENT_PROJECT, res)); + } + + String successMessage = String.format(MESSAGE_SUCCESS, Messages.format(toAdd)); + TabIndex index = TabIndex.Developer; + + model.addDeveloper(toAdd); + model.commitAddressBook(model, successMessage, index); + return new CommandResult(successMessage, index); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof AddDeveloperCommand)) { + return false; + } + + AddDeveloperCommand otherAddDeveloperCommand = (AddDeveloperCommand) other; + return toAdd.equals(otherAddDeveloperCommand.toAdd); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("toAdd", toAdd) + .toString(); + } +} diff --git a/src/main/java/seedu/address/logic/commands/add/AddProjectCommand.java b/src/main/java/seedu/address/logic/commands/add/AddProjectCommand.java new file mode 100644 index 00000000000..fc95413d369 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/add/AddProjectCommand.java @@ -0,0 +1,86 @@ +package seedu.address.logic.commands.add; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DEADLINE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DESCRIPTION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; + +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.logic.Messages; +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.TabIndex; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.project.Deadline; +import seedu.address.model.project.Project; + +/** + * Adds a project to the address book. + */ +public class AddProjectCommand extends Command { + + public static final String COMMAND_WORD = "add-project"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a project to the address book.\n" + + Deadline.MESSAGE_CONSTRAINTS + "\n" + + "Parameters: " + + PREFIX_NAME + "NAME " + + PREFIX_DESCRIPTION + "DESCRIPTION " + + "[" + PREFIX_DEADLINE + "DEADLINE_DATE,DEADLINE_DESCRIPTION,PRIORITY,IS_DONE]...\n" + + "Example: \n" + COMMAND_WORD + " " + + PREFIX_NAME + "JuiceApp " + + PREFIX_DESCRIPTION + "App to allow for different juices to be ordered " + + PREFIX_DEADLINE + "19-12-2023,Design backend,HIGH,0 " + + PREFIX_DEADLINE + "25-12-2023,Design frontend,MEDIUM,0 "; + + public static final String MESSAGE_SUCCESS = "New project added: %1$s"; + public static final String MESSAGE_DUPLICATE_PROJECT = "This project already exists in the address book!"; + + private final Project toAdd; + + /** + * Creates an AddProjectCommand to add the specified {@code Developer} + */ + public AddProjectCommand(Project project) { + requireNonNull(project); + toAdd = project; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + if (model.hasProject(toAdd)) { + throw new CommandException(MESSAGE_DUPLICATE_PROJECT); + } + + String successMessage = String.format(MESSAGE_SUCCESS, Messages.format(toAdd)); + TabIndex index = TabIndex.Project; + + model.addProject(toAdd); + model.commitAddressBook(model, successMessage, index); + return new CommandResult(successMessage, index); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof AddProjectCommand)) { + return false; + } + + AddProjectCommand otherAddProjectCommand = (AddProjectCommand) other; + return toAdd.equals(otherAddProjectCommand.toAdd); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("toAdd", toAdd) + .toString(); + } +} diff --git a/src/main/java/seedu/address/logic/commands/addroles/AddClientRoleCommand.java b/src/main/java/seedu/address/logic/commands/addroles/AddClientRoleCommand.java new file mode 100644 index 00000000000..8ed119d0467 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/addroles/AddClientRoleCommand.java @@ -0,0 +1,96 @@ +package seedu.address.logic.commands.addroles; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ROLE; + +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.logic.Messages; +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.TabIndex; +import seedu.address.logic.commands.add.AddDeveloperCommand; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.client.ClientRoles; +/** + * Represents a command to add a new role for clients to the address book. + * This allows users to define custom roles for clients. + */ +public class AddClientRoleCommand extends Command { + public static final String COMMAND_WORD = "add-client-role"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a role for clients to the address book. " + + "Parameters: " + PREFIX_ROLE + "ROLE " + + "Example: " + PREFIX_ROLE + "Developer "; + + public static final String MESSAGE_SUCCESS = "New role for client added: %1$s"; + public static final String MESSAGE_DUPLICATE_ROLE = "This client role already exists in the address book!"; + private final String toAdd; + + /** + * Creates an AddClientRoleCommand to add a new client role with the specified name. + * + * @param role The name of the new client role to be added. + */ + public AddClientRoleCommand(String role) { + requireNonNull(role); + toAdd = role; + } + + /** + * Executes the operation to add a new client role to the address book. + * + * @param model The model that contains the address book data. + * @return A CommandResult indicating the success or failure of the operation. + * @throws CommandException If an error occurs during the execution. + */ + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + if (ClientRoles.isValidRole(toAdd)) { + throw new CommandException(MESSAGE_DUPLICATE_ROLE); + } + + String successMessage = String.format(MESSAGE_SUCCESS, Messages.format(toAdd)); + TabIndex index = TabIndex.Client; + + ClientRoles newRole = new ClientRoles(toAdd.toString()); + ClientRoles.addClientRole(newRole); + model.commitAddressBook(model, successMessage, index); + return new CommandResult(successMessage, index); + } + + /** + * Checks if this AddClientRoleCommand is equal to another object. + * + * @param other The object to compare with this AddClientRoleCommand. + * @return True if the objects are equal, false otherwise. + */ + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof AddDeveloperCommand)) { + return false; + } + + AddClientRoleCommand otherAddClientRoleCommand = (AddClientRoleCommand) other; + return toAdd.equals(otherAddClientRoleCommand.toAdd); + } + + /** + * Generates a string representation of this AddClientRoleCommand. + * + * @return A string representation of this object. + */ + @Override + public String toString() { + return new ToStringBuilder(this) + .add("toAdd", toAdd) + .toString(); + } +} diff --git a/src/main/java/seedu/address/logic/commands/addroles/AddDeveloperRoleCommand.java b/src/main/java/seedu/address/logic/commands/addroles/AddDeveloperRoleCommand.java new file mode 100644 index 00000000000..a0e6d2b1a98 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/addroles/AddDeveloperRoleCommand.java @@ -0,0 +1,97 @@ +package seedu.address.logic.commands.addroles; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ROLE; + +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.logic.Messages; +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.TabIndex; +import seedu.address.logic.commands.add.AddDeveloperCommand; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.developer.DeveloperRoles; + +/** + * Represents a command to add a new role for developers to the address book. + * This allows users to define custom roles for developers. + */ +public class AddDeveloperRoleCommand extends Command { + public static final String COMMAND_WORD = "add-developer-role"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a role for developers to the address book. " + + "Parameters: " + PREFIX_ROLE + "ROLE " + + "Example: " + PREFIX_ROLE + "Developer "; + + public static final String MESSAGE_SUCCESS = "New role for developer added: %1$s"; + public static final String MESSAGE_DUPLICATE_ROLE = "This developer role already exists in the address book!"; + private final String toAdd; + + /** + * Creates an AddDeveloperRoleCommand to add a new developer role with the specified name. + * + * @param role The name of the new developer role to be added. + */ + public AddDeveloperRoleCommand(String role) { + requireNonNull(role); + toAdd = role; + } + + /** + * Executes the operation to add a new developer role to the address book. + * + * @param model The model that contains the address book data. + * @return A CommandResult indicating the success or failure of the operation. + * @throws CommandException If an error occurs during the execution. + */ + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + if (DeveloperRoles.isValidRole(toAdd)) { + throw new CommandException(MESSAGE_DUPLICATE_ROLE); + } + + String successMessage = String.format(MESSAGE_SUCCESS, Messages.format(toAdd)); + TabIndex index = TabIndex.Developer; + + DeveloperRoles newRole = new DeveloperRoles(toAdd.toString()); + DeveloperRoles.addDeveloperRole(newRole); + model.commitAddressBook(model, successMessage, index); + return new CommandResult(successMessage, index); + } + + /** + * Checks if this AddDeveloperRoleCommand is equal to another object. + * + * @param other The object to compare with this AddDeveloperRoleCommand. + * @return True if the objects are equal, false otherwise. + */ + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof AddDeveloperCommand)) { + return false; + } + + AddDeveloperRoleCommand otherAddDeveloperRoleCommand = (AddDeveloperRoleCommand) other; + return toAdd.equals(otherAddDeveloperRoleCommand.toAdd); + } + + /** + * Generates a string representation of this AddDeveloperRoleCommand. + * + * @return A string representation of this object. + */ + @Override + public String toString() { + return new ToStringBuilder(this) + .add("toAdd", toAdd) + .toString(); + } +} diff --git a/src/main/java/seedu/address/logic/commands/delete/DeleteClientCommand.java b/src/main/java/seedu/address/logic/commands/delete/DeleteClientCommand.java new file mode 100644 index 00000000000..bc18b4a29f8 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/delete/DeleteClientCommand.java @@ -0,0 +1,76 @@ +package seedu.address.logic.commands.delete; + +import static java.util.Objects.requireNonNull; + +import java.util.List; + +import seedu.address.commons.core.index.Index; +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.logic.Messages; +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.TabIndex; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.client.Client; + +/** + * Deletes a developer identified using it's displayed index from the address book. + */ +public class DeleteClientCommand extends Command { + + public static final String COMMAND_WORD = "delete-client"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Deletes the client identified by the index number used in the displayed client list.\n" + + "Parameters: INDEX (must be a positive integer)\n" + + "Example: " + COMMAND_WORD + " 1"; + + public static final String MESSAGE_DELETE_CLIENT_SUCCESS = "Deleted Client: %1$s"; + + private final Index targetIndex; + + public DeleteClientCommand(Index targetIndex) { + this.targetIndex = targetIndex; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + List lastShownList = model.getFilteredClientList(); + + if (targetIndex.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_CLIENT_DISPLAYED_INDEX); + } + + Client clientToDelete = lastShownList.get(targetIndex.getZeroBased()); + String successMessage = String.format(MESSAGE_DELETE_CLIENT_SUCCESS, Messages.format(clientToDelete)); + TabIndex index = TabIndex.Client; + + model.deleteClient(clientToDelete); + model.commitAddressBook(model, successMessage, index); + return new CommandResult(successMessage, index); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof DeleteClientCommand)) { + return false; + } + + DeleteClientCommand otherDeleteClientCommand = (DeleteClientCommand) other; + return targetIndex.equals(otherDeleteClientCommand.targetIndex); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("targetIndex", targetIndex) + .toString(); + } +} diff --git a/src/main/java/seedu/address/logic/commands/delete/DeleteDeveloperCommand.java b/src/main/java/seedu/address/logic/commands/delete/DeleteDeveloperCommand.java new file mode 100644 index 00000000000..c78f310b526 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/delete/DeleteDeveloperCommand.java @@ -0,0 +1,76 @@ +package seedu.address.logic.commands.delete; + +import static java.util.Objects.requireNonNull; + +import java.util.List; + +import seedu.address.commons.core.index.Index; +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.logic.Messages; +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.TabIndex; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.developer.Developer; + +/** + * Deletes a developer identified using it's displayed index from the address book. + */ +public class DeleteDeveloperCommand extends Command { + + public static final String COMMAND_WORD = "delete-developer"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Deletes the developer identified by the index number used in the displayed developer list.\n" + + "Parameters: INDEX (must be a positive integer)\n" + + "Example: " + COMMAND_WORD + " 1"; + + public static final String MESSAGE_DELETE_DEVELOPER_SUCCESS = "Deleted Developer: %1$s"; + + private final Index targetIndex; + + public DeleteDeveloperCommand(Index targetIndex) { + this.targetIndex = targetIndex; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + List lastShownList = model.getFilteredDeveloperList(); + + if (targetIndex.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_DEVELOPER_DISPLAYED_INDEX); + } + + Developer developerToDelete = lastShownList.get(targetIndex.getZeroBased()); + String successMessage = String.format(MESSAGE_DELETE_DEVELOPER_SUCCESS, Messages.format(developerToDelete)); + TabIndex index = TabIndex.Developer; + + model.deleteDeveloper(developerToDelete); + model.commitAddressBook(model, successMessage, index); + return new CommandResult(successMessage, index); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof DeleteDeveloperCommand)) { + return false; + } + + DeleteDeveloperCommand otherDeleteDeveloperCommand = (DeleteDeveloperCommand) other; + return targetIndex.equals(otherDeleteDeveloperCommand.targetIndex); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("targetIndex", targetIndex) + .toString(); + } +} diff --git a/src/main/java/seedu/address/logic/commands/delete/DeleteProjectCommand.java b/src/main/java/seedu/address/logic/commands/delete/DeleteProjectCommand.java new file mode 100644 index 00000000000..6bc0dca3ebd --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/delete/DeleteProjectCommand.java @@ -0,0 +1,75 @@ +package seedu.address.logic.commands.delete; + +import static java.util.Objects.requireNonNull; + +import java.util.List; + +import seedu.address.commons.core.index.Index; +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.logic.Messages; +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.TabIndex; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.project.Project; + +/** + * Deletes a project identified using it's displayed index from the address book. + */ +public class DeleteProjectCommand extends Command { + + public static final String COMMAND_WORD = "delete-project"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Deletes the project identified by the index number used in the displayed project list.\n" + + "Parameters: INDEX (must be a positive integer)\n" + + "Example: " + COMMAND_WORD + " 1"; + + public static final String MESSAGE_DELETE_PROJECT_SUCCESS = "Deleted Project: %1$s"; + + private final Index targetIndex; + + public DeleteProjectCommand(Index targetIndex) { + this.targetIndex = targetIndex; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + List lastShownList = model.getFilteredProjectList(); + + if (targetIndex.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_DEVELOPER_DISPLAYED_INDEX); + } + + Project projectToDelete = lastShownList.get(targetIndex.getZeroBased()); + String successMessage = String.format(MESSAGE_DELETE_PROJECT_SUCCESS, Messages.format(projectToDelete)); + TabIndex index = TabIndex.Project; + model.deleteProject(projectToDelete); + model.commitAddressBook(model, successMessage, index); + return new CommandResult(successMessage, index); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof DeleteProjectCommand)) { + return false; + } + + DeleteProjectCommand otherDeleteDeveloperCommand = (DeleteProjectCommand) other; + return targetIndex.equals(otherDeleteDeveloperCommand.targetIndex); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("targetIndex", targetIndex) + .toString(); + } +} diff --git a/src/main/java/seedu/address/logic/commands/deleteroles/DeleteClientRoleCommand.java b/src/main/java/seedu/address/logic/commands/deleteroles/DeleteClientRoleCommand.java new file mode 100644 index 00000000000..08bee5ea90f --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/deleteroles/DeleteClientRoleCommand.java @@ -0,0 +1,109 @@ +package seedu.address.logic.commands.deleteroles; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ROLE; + +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.logic.Messages; +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.TabIndex; +import seedu.address.logic.commands.delete.DeleteClientCommand; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.client.ClientRoles; + +/** + * Represents a command to delete a role for clients from the address book. + * This allows users to remove custom roles for clients. + */ +public class DeleteClientRoleCommand extends Command { + public static final String COMMAND_WORD = "delete-client-role"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Delete a role for clients in the address book. " + + "Parameters: " + PREFIX_ROLE + "ROLE " + + "Example: " + PREFIX_ROLE + "Developer "; + + public static final String MESSAGE_SUCCESS = "Role for clients deleted: %1$s"; + public static final String MESSAGE_CANNOT_DELETE_REPEAT = "This client role cannot be deleted " + + "as there are clients of this role"; + public static final String MESSAGE_CANNOT_DELETE_PREXISTS = "You are not allowed to delete this client role."; + public static final String MESSAGE_CANNOT_DELETE_NONEXISTING = "This client role does not exist. "; + public static final String MESSAGE_EXISTING_CLIENT_ROLES = "These are the existing client roles: \n"; + + private final String toAdd; + + /** + * Creates a DeleteClientRoleCommand to delete a specific client role by its name. + * + * @param role The name of the client role to be deleted. + */ + public DeleteClientRoleCommand(String role) { + requireNonNull(role); + toAdd = role; + } + + /** + * Executes the operation to delete a client role from the address book. + * + * @param model The model that contains the address book data. + * @return A CommandResult indicating the success or failure of the operation. + * @throws CommandException If an error occurs during the execution. + */ + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + if (ClientRoles.isRemovableRole(model, toAdd)) { //if when you search for the role result != 1 + if (ClientRoles.isNotInList()) { + throw new CommandException(MESSAGE_CANNOT_DELETE_NONEXISTING + MESSAGE_EXISTING_CLIENT_ROLES + + ClientRoles.printRoles()); + } else if (!ClientRoles.isNotDefault()) { + throw new CommandException(MESSAGE_CANNOT_DELETE_PREXISTS); + } else if (!ClientRoles.isNoRepeat()) { + throw new CommandException(MESSAGE_CANNOT_DELETE_REPEAT); + } + } + + String successMessage = String.format(MESSAGE_SUCCESS, Messages.format(toAdd)); + TabIndex index = TabIndex.Client; + + ClientRoles newRole = new ClientRoles(toAdd.toString()); + ClientRoles.deleteClientRole(newRole); + model.commitAddressBook(model, successMessage, index); + return new CommandResult(successMessage, index); + } + + /** + * Checks if this DeleteClientRoleCommand is equal to another object. + * + * @param other The object to compare with this DeleteClientRoleCommand. + * @return True if the objects are equal, false otherwise. + */ + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof DeleteClientCommand)) { + return false; + } + + DeleteClientRoleCommand otherDeleteClientRoleCommand = (DeleteClientRoleCommand) other; + return toAdd.equals(otherDeleteClientRoleCommand.toAdd); + } + + /** + * Generates a string representation of this DeleteClientRoleCommand. + * + * @return A string representation of this object. + */ + @Override + public String toString() { + return new ToStringBuilder(this) + .add("toAdd", toAdd) + .toString(); + } +} diff --git a/src/main/java/seedu/address/logic/commands/deleteroles/DeleteDeveloperRoleCommand.java b/src/main/java/seedu/address/logic/commands/deleteroles/DeleteDeveloperRoleCommand.java new file mode 100644 index 00000000000..782ff350312 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/deleteroles/DeleteDeveloperRoleCommand.java @@ -0,0 +1,109 @@ +package seedu.address.logic.commands.deleteroles; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ROLE; + +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.logic.Messages; +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.TabIndex; +import seedu.address.logic.commands.delete.DeleteClientCommand; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.developer.DeveloperRoles; + +/** + * Represents a command to delete a role for developers from the address book. + * This allows users to remove custom roles for developers. + */ +public class DeleteDeveloperRoleCommand extends Command { + public static final String COMMAND_WORD = "delete-developer-role"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Delete a role for developers in the address book. " + + "Parameters: " + PREFIX_ROLE + "ROLE " + + "Example: " + PREFIX_ROLE + "Developer "; + + public static final String MESSAGE_SUCCESS = "Role for developers deleted: %1$s"; + public static final String MESSAGE_CANNOT_DELETE_REPEAT = "This developer role cannot be deleted " + + "as there are developers of this role"; + public static final String MESSAGE_CANNOT_DELETE_PREXISTS = "You are not allowed to delete this developer role."; + public static final String MESSAGE_CANNOT_DELETE_NONEXISTING = "This developer role does not exist. "; + public static final String MESSAGE_EXISTING_DEVELOPERS_ROLES = "These are the existing developer roles: \n"; + + private final String toAdd; + + /** + * Creates a DeleteDeveloperRoleCommand to delete a specific developer role by its name. + * + * @param role The name of the developer role to be deleted. + */ + public DeleteDeveloperRoleCommand(String role) { + requireNonNull(role); + toAdd = role; + } + + /** + * Executes the operation to delete a developer role from the address book. + * + * @param model The model that contains the address book data. + * @return A CommandResult indicating the success or failure of the operation. + * @throws CommandException If an error occurs during the execution. + */ + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + if (DeveloperRoles.isRemovableRole(model, toAdd)) { + if (DeveloperRoles.isNotInList()) { + throw new CommandException(MESSAGE_CANNOT_DELETE_NONEXISTING + MESSAGE_EXISTING_DEVELOPERS_ROLES + + DeveloperRoles.printRoles()); + } else if (!DeveloperRoles.isNotDefault()) { + throw new CommandException(MESSAGE_CANNOT_DELETE_PREXISTS); + } else if (!DeveloperRoles.isNoRepeat()) { + throw new CommandException(MESSAGE_CANNOT_DELETE_REPEAT); + } + } + + String successMessage = String.format(MESSAGE_SUCCESS, Messages.format(toAdd)); + TabIndex index = TabIndex.Developer; + + DeveloperRoles newRole = new DeveloperRoles(toAdd.toString()); + DeveloperRoles.deleteDeveloperRole(newRole); + model.commitAddressBook(model, successMessage, index); + return new CommandResult(successMessage, index); + } + + /** + * Checks if this DeleteDeveloperRoleCommand is equal to another object. + * + * @param other The object to compare with this DeleteDeveloperRoleCommand. + * @return True if the objects are equal, false otherwise. + */ + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof DeleteClientCommand)) { + return false; + } + + DeleteDeveloperRoleCommand otherDeleteDeveloperRoleCommand = (DeleteDeveloperRoleCommand) other; + return toAdd.equals(otherDeleteDeveloperRoleCommand.toAdd); + } + + /** + * Generates a string representation of this DeleteDeveloperRoleCommand. + * + * @return A string representation of this object. + */ + @Override + public String toString() { + return new ToStringBuilder(this) + .add("toAdd", toAdd) + .toString(); + } +} diff --git a/src/main/java/seedu/address/logic/commands/edit/EditClientCommand.java b/src/main/java/seedu/address/logic/commands/edit/EditClientCommand.java new file mode 100644 index 00000000000..6369e959475 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/edit/EditClientCommand.java @@ -0,0 +1,295 @@ +package seedu.address.logic.commands.edit; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DOCUMENT; +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_ORGANISATION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PROJECT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ROLE; + +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; + +import seedu.address.commons.core.index.Index; +import seedu.address.commons.util.CollectionUtil; +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.logic.Messages; +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.TabIndex; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.client.Client; +import seedu.address.model.client.ClientRoles; +import seedu.address.model.client.Document; +import seedu.address.model.commons.Name; +import seedu.address.model.person.Address; +import seedu.address.model.person.Email; +import seedu.address.model.person.Phone; + +/** + * Edits the details of an existing client in the address book. + */ +public class EditClientCommand extends Command { + + public static final String COMMAND_WORD = "edit-client"; + public static final String MESSAGE_EDIT_CLIENT_SUCCESS = "Edited Client: %1$s"; + public static final String MESSAGE_NOT_EDITED = "At least one field to edit must be provided."; + public static final String MESSAGE_DUPLICATE_CLIENT = + "There is already a client with that name!"; + public static final String MESSAGE_UNEDITED_CLIENT = + "The details of the client to edit are already as such!"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits the details of the client identified " + + "by the index number used in the displayed client 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_ROLE + "ROLE] " + + "[" + PREFIX_PROJECT + "PROJECT]... " + + "[" + PREFIX_ORGANISATION + "ORGANISATION] " + + "[" + PREFIX_DOCUMENT + "DOCUMENT] \n" + + "Example: \n" + COMMAND_WORD + " 1 " + + PREFIX_PHONE + "91234567 " + + PREFIX_EMAIL + "johndoe@example.com"; + + private final Index index; + + private final EditClientDescriptor editClientDescriptor; + + + /** + * @param index of the client in the filtered client list to edit + * @param editClientDescriptor details to edit the client with + */ + public EditClientCommand(Index index, EditClientDescriptor editClientDescriptor) { + requireNonNull(index); + requireNonNull(editClientDescriptor); + + this.index = index; + this.editClientDescriptor = new EditClientDescriptor(editClientDescriptor); + } + + /** + * Creates and returns a {@code Client} with the details of {@code clientToEdit} + * edited with {@code editClientDescriptor}. + */ + static Client createEditedClient(Client clientToEdit, EditClientDescriptor editClientDescriptor) { + assert clientToEdit != null; + + Name updatedName = editClientDescriptor.getName().orElse(clientToEdit.getName()); + Phone updatedPhone = editClientDescriptor.getPhone().orElse(clientToEdit.getPhone()); + Email updatedEmail = editClientDescriptor.getEmail().orElse(clientToEdit.getEmail()); + Address updatedAddress = editClientDescriptor.getAddress().orElse(clientToEdit.getAddress()); + ClientRoles updatedRole = editClientDescriptor.getRole().orElse(clientToEdit.getRole()); + Set updatedProjects = editClientDescriptor.getProjects().orElse(clientToEdit.getProjects()); + Name updatedOrganisation = editClientDescriptor.getOrganisation().orElse(clientToEdit.getOrganisation()); + Document updatedDocument = editClientDescriptor.getDocument().orElse(clientToEdit.getDocument()); + + return new Client(updatedName, updatedPhone, updatedEmail, updatedAddress, + updatedRole, updatedProjects, updatedOrganisation, updatedDocument); + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + List lastShownList = model.getFilteredClientList(); + if (index.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_CLIENT_DISPLAYED_INDEX); + } + + Client clientToEdit = lastShownList.get(index.getZeroBased()); + Client editedClient = createEditedClient(clientToEdit, editClientDescriptor); + + if (!clientToEdit.isSameClient(editedClient) && model.hasClient(editedClient)) { + throw new CommandException(MESSAGE_DUPLICATE_CLIENT); + } + String res = model.areProjectsValid(editedClient); + if (res != null) { + throw new CommandException(String.format(Messages.MESSAGE_NONEXISTENT_PROJECT, res)); + } + + String successMessage = String.format(MESSAGE_EDIT_CLIENT_SUCCESS, Messages.format(editedClient)); + TabIndex index = TabIndex.Client; + + model.setClient(clientToEdit, editedClient); + model.updateFilteredClientList(Model.PREDICATE_SHOW_ALL_CLIENTS); + model.commitAddressBook(model, successMessage, index); + return new CommandResult(successMessage, index); + } + + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof EditClientCommand)) { + return false; + } + + EditClientCommand otherEditClientCommand = (EditClientCommand) other; + return index.equals(otherEditClientCommand.index) + && editClientDescriptor.equals(otherEditClientCommand.editClientDescriptor); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("index", index) + .add("editClientDescriptor", editClientDescriptor) + .toString(); + } + + /** + * Stores the details to edit the client with. Each non-empty field value will replace the + * corresponding field value of the client. + */ + public static class EditClientDescriptor { + private Name name; + private Phone phone; + private Email email; + private Address address; + private Set projects; + private ClientRoles role; + private Name organisation; + private Document document; + + public EditClientDescriptor() { + } + + /** + * Copy constructor. + * A defensive copy of {@code tags} is used internally. + */ + public EditClientDescriptor(EditClientDescriptor toCopy) { + setName(toCopy.name); + setPhone(toCopy.phone); + setEmail(toCopy.email); + setAddress(toCopy.address); + setProjects(toCopy.projects); + setRole(toCopy.role); + setOrganisation(toCopy.organisation); + setDocument(toCopy.document); + } + + /** + * Returns true if at least one field is edited. + */ + public boolean isAnyFieldEdited() { + return CollectionUtil.isAnyNonNull( + name, phone, email, address, projects, role, organisation, document); + } + + public Optional getName() { + return Optional.ofNullable(name); + } + + public void setName(Name name) { + this.name = name; + } + + public Optional getPhone() { + return Optional.ofNullable(phone); + } + + public void setPhone(Phone phone) { + this.phone = phone; + } + + public Optional getEmail() { + return Optional.ofNullable(email); + } + + public void setEmail(Email email) { + this.email = email; + } + + public Optional
getAddress() { + return Optional.ofNullable(address); + } + + public void setAddress(Address address) { + this.address = address; + } + + public Optional> getProjects() { + return (projects != null) ? Optional.of(Collections.unmodifiableSet(projects)) : Optional.empty(); + } + + public void setProjects(Set projects) { + this.projects = (projects != null) ? new HashSet<>(projects) : null; + } + + public Optional getOrganisation() { + return Optional.ofNullable(organisation); + } + + public void setOrganisation(Name organisation) { + this.organisation = organisation; + } + + public Optional getDocument() { + return Optional.ofNullable(document); + } + + public void setDocument(Document document) { + this.document = document; + } + + public Optional getRole() { + return Optional.ofNullable(role); + } + + public void setRole(ClientRoles role) { + this.role = role; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof EditClientDescriptor)) { + return false; + } + + EditClientDescriptor otherEditClientDescriptor = (EditClientDescriptor) other; + return Objects.equals(name, otherEditClientDescriptor.name) + && Objects.equals(phone, otherEditClientDescriptor.phone) + && Objects.equals(email, otherEditClientDescriptor.email) + && Objects.equals(address, otherEditClientDescriptor.address) + && Objects.equals(projects, otherEditClientDescriptor.projects) + && Objects.equals(role, otherEditClientDescriptor.role) + && Objects.equals(organisation, otherEditClientDescriptor.organisation) + && Objects.equals(document, otherEditClientDescriptor.document); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("name", name) + .add("phone", phone) + .add("email", email) + .add("address", address) + .add("projects", projects) + .add("role", role) + .add("organisation", organisation) + .add("document", document) + .toString(); + } + } +} diff --git a/src/main/java/seedu/address/logic/commands/edit/EditDeveloperCommand.java b/src/main/java/seedu/address/logic/commands/edit/EditDeveloperCommand.java new file mode 100644 index 00000000000..0bcbf4819b5 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/edit/EditDeveloperCommand.java @@ -0,0 +1,326 @@ +package seedu.address.logic.commands.edit; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DATEJOINED; +import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; +import static seedu.address.logic.parser.CliSyntax.PREFIX_GITHUBID; +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_PROJECT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_RATING; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ROLE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_SALARY; + +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; + +import seedu.address.commons.core.index.Index; +import seedu.address.commons.util.CollectionUtil; +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.logic.Messages; +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.TabIndex; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.commons.Date; +import seedu.address.model.commons.Name; +import seedu.address.model.developer.Developer; +import seedu.address.model.developer.DeveloperRoles; +import seedu.address.model.developer.GithubId; +import seedu.address.model.developer.Rating; +import seedu.address.model.developer.Salary; +import seedu.address.model.person.Address; +import seedu.address.model.person.Email; +import seedu.address.model.person.Phone; + +/** + * Edits the details of an existing developer in the address book. + */ +public class EditDeveloperCommand extends Command { + + public static final String COMMAND_WORD = "edit-developer"; + public static final String MESSAGE_EDIT_DEVELOPER_SUCCESS = "Edited Developer: %1$s"; + public static final String MESSAGE_NOT_EDITED = "At least one field to edit must be provided."; + public static final String MESSAGE_DUPLICATE_DEVELOPER = + "There is already a developer with that name!"; + public static final String MESSAGE_UNEDITED_DEVELOPER = + "The details of the developer to edit are already as such!"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits the details of the developer identified " + + "by the index number used in the displayed developer 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_ROLE + "ROLE] " + + "[" + PREFIX_PROJECT + "PROJECT]... " + + "[" + PREFIX_SALARY + "SALARY] " + + "[" + PREFIX_DATEJOINED + "DATE JOINED] " + + "[" + PREFIX_GITHUBID + "GITHUBID] " + + "[" + PREFIX_RATING + "RATING] \n" + + "Example: \n" + COMMAND_WORD + " 1 " + + PREFIX_PHONE + "91234567 " + + PREFIX_EMAIL + "johndoe@example.com"; + + private final Index index; + + private final EditDeveloperDescriptor editDeveloperDescriptor; + + + /** + * @param index of the developer in the filtered developer list to edit + * @param editDeveloperDescriptor details to edit the developer with + */ + public EditDeveloperCommand(Index index, EditDeveloperDescriptor editDeveloperDescriptor) { + requireNonNull(index); + requireNonNull(editDeveloperDescriptor); + + this.index = index; + this.editDeveloperDescriptor = new EditDeveloperDescriptor(editDeveloperDescriptor); + } + + /** + * Creates and returns a {@code Developer} with the details of {@code developerToEdit} + * edited with {@code editDeveloperDescriptor}. + */ + static Developer createEditedDeveloper(Developer developerToEdit, EditDeveloperDescriptor editDeveloperDescriptor) { + assert developerToEdit != null; + + Name updatedName = editDeveloperDescriptor.getName().orElse(developerToEdit.getName()); + Phone updatedPhone = editDeveloperDescriptor.getPhone().orElse(developerToEdit.getPhone()); + Email updatedEmail = editDeveloperDescriptor.getEmail().orElse(developerToEdit.getEmail()); + Address updatedAddress = editDeveloperDescriptor.getAddress().orElse(developerToEdit.getAddress()); + Date updatedDateJoined = editDeveloperDescriptor.getDateJoined().orElse(developerToEdit.getDateJoined()); + DeveloperRoles updatedRole = editDeveloperDescriptor.getRole().orElse(developerToEdit.getRole()); + Salary updatedSalary = editDeveloperDescriptor.getSalary().orElse(developerToEdit.getSalary()); + Set updatedProjects = editDeveloperDescriptor.getProjects().orElse(developerToEdit.getProjects()); + GithubId updatedGithubId = editDeveloperDescriptor.getGithubId().orElse(developerToEdit.getGithubId()); + Rating updatedRating = editDeveloperDescriptor.getRating().orElse(developerToEdit.getRating()); + + return new Developer(updatedName, updatedPhone, updatedEmail, updatedAddress, + updatedRole, updatedProjects, updatedSalary, updatedDateJoined, updatedGithubId, updatedRating); + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + List lastShownList = model.getFilteredDeveloperList(); + if (index.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_DEVELOPER_DISPLAYED_INDEX); + } + + Developer developerToEdit = lastShownList.get(index.getZeroBased()); + Developer editedDeveloper = createEditedDeveloper(developerToEdit, editDeveloperDescriptor); + + if (!developerToEdit.isSameDeveloper(editedDeveloper) && model.hasDeveloper(editedDeveloper)) { + throw new CommandException(MESSAGE_DUPLICATE_DEVELOPER); + } + String res = model.areProjectsValid(editedDeveloper); + if (res != null) { + throw new CommandException(String.format(Messages.MESSAGE_NONEXISTENT_PROJECT, res)); + } + + String successMessage = String.format(MESSAGE_EDIT_DEVELOPER_SUCCESS, Messages.format(editedDeveloper)); + TabIndex index = TabIndex.Developer; + + model.setDeveloper(developerToEdit, editedDeveloper); + model.updateFilteredDeveloperList(Model.PREDICATE_SHOW_ALL_DEVELOPERS); + model.commitAddressBook(model, successMessage, index); + return new CommandResult(successMessage, index); + } + + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof EditDeveloperCommand)) { + return false; + } + + EditDeveloperCommand otherEditDeveloperCommand = (EditDeveloperCommand) other; + return index.equals(otherEditDeveloperCommand.index) + && editDeveloperDescriptor.equals(otherEditDeveloperCommand.editDeveloperDescriptor); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("index", index) + .add("editDeveloperDescriptor", editDeveloperDescriptor) + .toString(); + } + + /** + * Stores the details to edit the developer with. Each non-empty field value will replace the + * corresponding field value of the developer. + */ + public static class EditDeveloperDescriptor { + private Name name; + private Phone phone; + private Email email; + private Address address; + private Set projects; + private Date dateJoined; + private DeveloperRoles role; + private Salary salary; + private GithubId githubId; + private Rating rating; + + public EditDeveloperDescriptor() { + } + + /** + * Copy constructor. + * A defensive copy of {@code tags} is used internally. + */ + public EditDeveloperDescriptor(EditDeveloperDescriptor toCopy) { + setName(toCopy.name); + setPhone(toCopy.phone); + setEmail(toCopy.email); + setAddress(toCopy.address); + setProjects(toCopy.projects); + setDateJoined(toCopy.dateJoined); + setRole(toCopy.role); + setSalary(toCopy.salary); + setGithubId(toCopy.githubId); + setRating(toCopy.rating); + } + + /** + * Returns true if at least one field is edited. + */ + public boolean isAnyFieldEdited() { + return CollectionUtil.isAnyNonNull( + name, phone, email, address, projects, dateJoined, role, salary, githubId, rating); + } + + public Optional getName() { + return Optional.ofNullable(name); + } + + public void setName(Name name) { + this.name = name; + } + + public Optional getPhone() { + return Optional.ofNullable(phone); + } + + public void setPhone(Phone phone) { + this.phone = phone; + } + + public Optional getEmail() { + return Optional.ofNullable(email); + } + + public void setEmail(Email email) { + this.email = email; + } + + public Optional
getAddress() { + return Optional.ofNullable(address); + } + + public void setAddress(Address address) { + this.address = address; + } + + public Optional> getProjects() { + return (projects != null) ? Optional.of(Collections.unmodifiableSet(projects)) : Optional.empty(); + } + + public void setProjects(Set projects) { + this.projects = (projects != null) ? new HashSet<>(projects) : null; + } + + public Optional getDateJoined() { + return Optional.ofNullable(dateJoined); + } + + public void setDateJoined(Date dateJoined) { + this.dateJoined = dateJoined; + } + + public Optional getRole() { + return Optional.ofNullable(role); + } + + public void setRole(DeveloperRoles role) { + this.role = role; + } + + public Optional getSalary() { + return Optional.ofNullable(salary); + } + + public void setSalary(Salary salary) { + this.salary = salary; + } + + public Optional getGithubId() { + return Optional.ofNullable(githubId); + } + + public void setGithubId(GithubId githubId) { + this.githubId = githubId; + } + + public Optional getRating() { + return Optional.ofNullable(rating); + } + + public void setRating(Rating rating) { + this.rating = rating; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof EditDeveloperDescriptor)) { + return false; + } + + EditDeveloperDescriptor otherEditDeveloperDescriptor = (EditDeveloperDescriptor) other; + return Objects.equals(name, otherEditDeveloperDescriptor.name) + && Objects.equals(phone, otherEditDeveloperDescriptor.phone) + && Objects.equals(email, otherEditDeveloperDescriptor.email) + && Objects.equals(address, otherEditDeveloperDescriptor.address) + && Objects.equals(projects, otherEditDeveloperDescriptor.projects) + && Objects.equals(dateJoined, otherEditDeveloperDescriptor.dateJoined) + && Objects.equals(role, otherEditDeveloperDescriptor.role) + && Objects.equals(salary, otherEditDeveloperDescriptor.salary); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("name", name) + .add("phone", phone) + .add("email", email) + .add("address", address) + .add("projects", projects) + .add("dateJoined", dateJoined) + .add("role", role) + .add("salary", salary) + .add("githubId", githubId) + .add("rating", rating) + .toString(); + } + } +} diff --git a/src/main/java/seedu/address/logic/commands/edit/EditProjectCommand.java b/src/main/java/seedu/address/logic/commands/edit/EditProjectCommand.java new file mode 100644 index 00000000000..f05dd605ba8 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/edit/EditProjectCommand.java @@ -0,0 +1,201 @@ +package seedu.address.logic.commands.edit; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DEADLINE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DESCRIPTION; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.Optional; + +import seedu.address.commons.core.index.Index; +import seedu.address.commons.util.CollectionUtil; +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.logic.Messages; +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.TabIndex; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.commons.Name; +import seedu.address.model.project.Deadline; +import seedu.address.model.project.Description; +import seedu.address.model.project.Project; + +/** + * Edits the details of an existing project in the address book. + */ +public class EditProjectCommand extends Command { + + public static final String COMMAND_WORD = "edit-project"; + public static final String MESSAGE_EDIT_PROJECT_SUCCESS = "Edited Project: %1$s"; + public static final String MESSAGE_NOT_EDITED = "At least one field to edit must be provided."; + public static final String MESSAGE_DUPLICATE_PROJECT = + "There is already a project with that name!"; + public static final String MESSAGE_UNEDITED_PROJECT = + "The details of the project to edit are already as such!"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits the details of the project identified " + + "by the index number used in the displayed project list. " + + "Existing values will be overwritten by the input values.\n" + + Deadline.MESSAGE_CONSTRAINTS + "\n" + + "Parameters: INDEX (must be a positive integer) " + + "[" + PREFIX_DESCRIPTION + "DESCRIPTION] " + + "[" + PREFIX_DEADLINE + "DEADLINE_DATE,DEADLINE_DESCRIPTION,PRIORITY,IS_DONE]...\n" + + "Example: \n" + COMMAND_WORD + " 1 " + + PREFIX_DEADLINE + "31-12-2019,Develop front end interface,HIGH,0 " + + PREFIX_DEADLINE + "01-02-2020,Develop back end,HIGH,0"; + + private final Index index; + + private final EditProjectDescriptor editProjectDescriptor; + + + /** + * @param index of the project in the filtered project list to edit + * @param editProjectDescriptor details to edit the project with + */ + public EditProjectCommand(Index index, EditProjectDescriptor editProjectDescriptor) { + requireNonNull(index); + requireNonNull(editProjectDescriptor); + + this.index = index; + this.editProjectDescriptor = new EditProjectDescriptor(editProjectDescriptor); + } + + /** + * Creates and returns a {@code Project} with the details of {@code projectToEdit} + * edited with {@code editProjectDescriptor}. + */ + static seedu.address.model.project.Project createEditedProject(seedu.address.model.project.Project projectToEdit, + EditProjectDescriptor editProjectDescriptor) { + assert projectToEdit != null; + + Name name = projectToEdit.getProjectName(); + Description updatedDescription = editProjectDescriptor.getDescription() + .orElse(projectToEdit.getProjectDescription()); + List updatedDeadlines = editProjectDescriptor.getDeadlines() + .orElse(projectToEdit.getProjectDeadlines()); + + return new seedu.address.model.project.Project(name, updatedDescription, updatedDeadlines); + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + List lastShownList = model.getFilteredProjectList(); + if (index.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_PROJECT_DISPLAYED_INDEX); + } + + Project projectToEdit = lastShownList.get(index.getZeroBased()); + Project editedProject = createEditedProject(projectToEdit, editProjectDescriptor); + + if (!projectToEdit.isSameProject(editedProject) && model.hasProject(editedProject)) { + throw new CommandException(MESSAGE_DUPLICATE_PROJECT); + } + + String successMessage = String.format(MESSAGE_EDIT_PROJECT_SUCCESS, Messages.format(editedProject)); + TabIndex index = TabIndex.Project; + + model.setProject(projectToEdit, editedProject); + model.updateFilteredProjectList(Model.PREDICATE_SHOW_ALL_PROJECTS); + model.commitAddressBook(model, successMessage, index); + return new CommandResult(successMessage, index); + } + + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof EditProjectCommand)) { + return false; + } + + EditProjectCommand otherEditProjectCommand = (EditProjectCommand) other; + return index.equals(otherEditProjectCommand.index) + && editProjectDescriptor.equals(otherEditProjectCommand.editProjectDescriptor); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("index", index) + .add("editProjectDescriptor", editProjectDescriptor) + .toString(); + } + + /** + * Stores the details to edit the project with. Each non-empty field value will replace the + * corresponding field value of the project. + */ + public static class EditProjectDescriptor { + private Description desc; + private List deadlines; + + public EditProjectDescriptor() { + } + + /** + * Copy constructor. + * A defensive copy of {@code tags} is used internally. + */ + public EditProjectDescriptor(EditProjectDescriptor toCopy) { + setDescription(toCopy.desc); + setDeadlines(toCopy.deadlines); + } + + /** + * Returns true if at least one field is edited. + */ + public boolean isAnyFieldEdited() { + return CollectionUtil.isAnyNonNull( + desc, deadlines); + } + + public Optional getDescription() { + return Optional.ofNullable(desc); + } + + public void setDescription(Description desc) { + this.desc = desc; + } + + public Optional> getDeadlines() { + return (deadlines != null) ? Optional.of(Collections.unmodifiableList(deadlines)) : Optional.empty(); + } + + public void setDeadlines(List deadlines) { + this.deadlines = (deadlines != null) ? new ArrayList<>(deadlines) : null; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof EditProjectDescriptor)) { + return false; + } + + EditProjectDescriptor otherEditProjectDescriptor = (EditProjectDescriptor) other; + return Objects.equals(desc, otherEditProjectDescriptor.desc) + && Objects.equals(deadlines, otherEditProjectDescriptor.deadlines); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("description", desc) + .add("deadlines", deadlines) + .toString(); + } + } +} diff --git a/src/main/java/seedu/address/logic/commands/find/FindClientCommand.java b/src/main/java/seedu/address/logic/commands/find/FindClientCommand.java new file mode 100644 index 00000000000..860f6230dfa --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/find/FindClientCommand.java @@ -0,0 +1,100 @@ +package seedu.address.logic.commands.find; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.Messages.getMessageClientsListedOverview; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DOCUMENT; +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_ORGANISATION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PROJECT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ROLE; + +import java.util.function.Predicate; + +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.TabIndex; +import seedu.address.model.Model; +import seedu.address.model.client.Client; + +/** + * Represents a command to find clients based on various attributes. + * This command allows users to search for clients by specifying keywords for different client attributes. + */ +public class FindClientCommand extends Command { + public static final String COMMAND_WORD = "find-client"; + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Find clients based on various attributes.\n" + + "Parameters: " + + "[" + PREFIX_NAME + "NAME_KEYWORDS] " + + "[" + PREFIX_ROLE + "ROLE_KEYWORDS] " + + "[" + PREFIX_ADDRESS + "ADDRESS_KEYWORDS] " + + "[" + PREFIX_EMAIL + "EMAIL_KEYWORDS] " + + "[" + PREFIX_PHONE + "PHONE_KEYWORDS] " + + "[" + PREFIX_PROJECT + "PROJECT_KEYWORDS] " + + "[" + PREFIX_DOCUMENT + "DOCUMENT_KEYWORDS] " + + "[" + PREFIX_ORGANISATION + "ORGANISATION_KEYWORDS]\n" + + "Example: " + COMMAND_WORD + " n/John r/client\n"; + + private Predicate predicate; + + /** + * Creates a FindClientCommand with the specified predicate. + * + * @param predicate The predicate used to filter the list of clients. + */ + public FindClientCommand(Predicate predicate) { + this.predicate = predicate; + } + + /** + * Executes the find operation by updating the filtered client list based on the given predicate. + * + * @param model The model to execute the command on. + * @return A CommandResult indicating the results of the find operation. + */ + @Override + public CommandResult execute(Model model) { + requireNonNull(model); + model.updateFilteredClientList(predicate); + + int resultCount = model.getFilteredClientList().size(); + String message = getMessageClientsListedOverview(resultCount); + + return new CommandResult(message, TabIndex.Client); + } + + /** + * Checks if this FindClientCommand is equal to another object. + * + * @param other The object to compare with this FindClientCommand. + * @return True if the objects are equal, false otherwise. + */ + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof FindClientCommand)) { + return false; + } + + FindClientCommand otherFindClientCommand = (FindClientCommand) other; + return predicate.equals(otherFindClientCommand.predicate); + } + + /** + * Generates a string representation of this FindClientCommand. + * + * @return A string representation of this object. + */ + @Override + public String toString() { + return new ToStringBuilder(this) + .add("predicate", predicate) + .toString(); + } +} diff --git a/src/main/java/seedu/address/logic/commands/find/FindDeadlineCommand.java b/src/main/java/seedu/address/logic/commands/find/FindDeadlineCommand.java new file mode 100644 index 00000000000..517e0345235 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/find/FindDeadlineCommand.java @@ -0,0 +1,93 @@ +package seedu.address.logic.commands.find; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.Messages.getMessageDeadlinesListedOverview; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DATEJOINED; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PRIORITY; + +import java.util.function.Predicate; + +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.TabIndex; +import seedu.address.model.Model; +import seedu.address.model.project.Deadline; + +/** + * Represents a command to filter deadlines within projects based on specified criteria. + * This command allows users to search for deadlines based on dates and priority levels. + */ +public class FindDeadlineCommand extends Command { + public static final String COMMAND_WORD = "find-deadline"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Filter deadlines within projects.\n" + + "Parameters: " + + PREFIX_DATEJOINED + "DATE (Shows deadlines before this date) \n" + + "[" + PREFIX_PRIORITY + "[HIGH/MEDIUM/LOW] (Shows deadlines based on priority)\n" + + "Example: " + COMMAND_WORD + " d/28-12-2021 pri/HIGH\n"; + + private final Predicate predicate; + + /** + * Creates a FindDeadlineCommand with the specified predicate for filtering deadlines. + * + * @param predicate The predicate used to filter deadlines within projects. + */ + public FindDeadlineCommand(Predicate predicate) { + this.predicate = predicate; + } + + /** + * Executes the find operation by updating the filtered project deadline list based on the given predicate. + * + * @param model The model to execute the command on. + * @return A CommandResult indicating the results of the find operation. + */ + @Override + public CommandResult execute(Model model) { + requireNonNull(model); + + model.updateFilteredProjectDeadlineList(predicate); + + int resultCount = + model.getFilteredProjectList().stream() + .mapToInt(project -> project.getProjectFilteredDeadlines().size()).filter(x -> x > 0).sum(); + + String message = getMessageDeadlinesListedOverview(resultCount); + + return new CommandResult(message, TabIndex.Project); + } + + /** + * Checks if this FindDeadlineCommand is equal to another object. + * + * @param other The object to compare with this FindDeadlineCommand. + * @return True if the objects are equal, false otherwise. + */ + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof FindDeadlineCommand)) { + return false; + } + + FindDeadlineCommand otherFindDeadlineCommand = (FindDeadlineCommand) other; + return predicate.equals(otherFindDeadlineCommand.predicate); + } + + /** + * Generates a string representation of this FindDeadlineCommand. + * + * @return A string representation of this object. + */ + @Override + public String toString() { + return new ToStringBuilder(this) + .add("predicate", predicate) + .toString(); + } +} diff --git a/src/main/java/seedu/address/logic/commands/find/FindDeveloperCommand.java b/src/main/java/seedu/address/logic/commands/find/FindDeveloperCommand.java new file mode 100644 index 00000000000..595cc56b9c4 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/find/FindDeveloperCommand.java @@ -0,0 +1,106 @@ +package seedu.address.logic.commands.find; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.Messages.getMessageDevelopersListedOverview; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DATEJOINED; +import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; +import static seedu.address.logic.parser.CliSyntax.PREFIX_GITHUBID; +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_PROJECT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_RATING; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ROLE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_SALARY; + +import java.util.function.Predicate; + +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.TabIndex; +import seedu.address.model.Model; +import seedu.address.model.developer.Developer; + +/** + * Represents a command to find developers in the address book based on various attributes. + * This command allows users to search for developers using their names, roles, addresses, dates joined, emails, + * phone numbers, associated projects, salaries, ratings, or GitHub IDs. + */ +public class FindDeveloperCommand extends Command { + + public static final String COMMAND_WORD = "find-developer"; + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Find developers based on various attributes.\n" + + "Parameters: " + + "[" + PREFIX_NAME + "NAME_KEYWORDS] " + + "[" + PREFIX_ROLE + "ROLE_KEYWORDS] " + + "[" + PREFIX_ADDRESS + "ADDRESS_KEYWORDS] " + + "[" + PREFIX_DATEJOINED + "DATE_JOINED_KEYWORDS] " + + "[" + PREFIX_EMAIL + "EMAIL_KEYWORDS] " + + "[" + PREFIX_PHONE + "PHONE_KEYWORDS] " + + "[" + PREFIX_PROJECT + "PROJECT_KEYWORDS] " + + "[" + PREFIX_SALARY + "SALARY_KEYWORDS] " + + "[" + PREFIX_RATING + "RATING_KEYWORDS] " + + "[" + PREFIX_GITHUBID + "GITHUBID_KEYWORDS]\n" + + "Example: " + COMMAND_WORD + " n/John r/developer\n"; + + private Predicate predicate; + + /** + * Creates a FindDeveloperCommand with the specified predicate for filtering developers. + * + * @param predicate The predicate used to filter developers based on attributes. + */ + public FindDeveloperCommand(Predicate predicate) { + this.predicate = predicate; + } + + /** + * Executes the find operation by updating the filtered developer list based on the given predicate. + * + * @param model The model to execute the command on. + * @return A CommandResult indicating the results of the find operation. + */ + @Override + public CommandResult execute(Model model) { + requireNonNull(model); + model.updateFilteredDeveloperList(predicate); + + int resultCount = model.getFilteredDeveloperList().size(); + String message = getMessageDevelopersListedOverview(resultCount); + + return new CommandResult(message, TabIndex.Developer); + } + + /** + * Checks if this FindDeveloperCommand is equal to another object. + * + * @param other The object to compare with this FindDeveloperCommand. + * @return True if the objects are equal, false otherwise. + */ + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof FindDeveloperCommand)) { + return false; + } + + FindDeveloperCommand otherFindDeveloperCommand = (FindDeveloperCommand) other; + return predicate.equals(otherFindDeveloperCommand.predicate); + } + + /** + * Generates a string representation of this FindDeveloperCommand. + * + * @return A string representation of this object. + */ + @Override + public String toString() { + return new ToStringBuilder(this) + .add("predicate", predicate) + .toString(); + } +} diff --git a/src/main/java/seedu/address/logic/commands/find/FindProjectCommand.java b/src/main/java/seedu/address/logic/commands/find/FindProjectCommand.java new file mode 100644 index 00000000000..148dd78445c --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/find/FindProjectCommand.java @@ -0,0 +1,70 @@ +package seedu.address.logic.commands.find; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.Messages.getMessageProjectsListedOverview; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DEADLINE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DESCRIPTION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PROJECT; + +import java.util.function.Predicate; + +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.TabIndex; +import seedu.address.model.Model; +import seedu.address.model.project.Project; + +/** + * Finds and lists all persons in address book whose name contains any of the argument keywords. + * Keyword matching is case-insensitive. + */ +public class FindProjectCommand extends Command { + + public static final String COMMAND_WORD = "find-project"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Find projects based on various attributes.\n" + + "Parameters: " + + "[" + PREFIX_PROJECT + "PROJECT_NAME_KEYWORDS] " + + "[" + PREFIX_DESCRIPTION + "DESCRIPTION_KEYWORDS] " + + "[" + PREFIX_DEADLINE + "DEADLINE_KEYWORDS]\n" + + "Example: " + COMMAND_WORD + " pr/MyProject\n"; + + private Predicate predicate; + + public FindProjectCommand(Predicate predicate) { + this.predicate = predicate; + } + + @Override + public CommandResult execute(Model model) { + requireNonNull(model); + model.updateFilteredProjectList(predicate); + + int resultCount = model.getFilteredProjectList().size(); + String message = getMessageProjectsListedOverview(resultCount); + + return new CommandResult(message, TabIndex.Project); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof FindProjectCommand)) { + return false; + } + + FindProjectCommand otherFindProjectCommand = (FindProjectCommand) other; + return predicate.equals(otherFindProjectCommand.predicate); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("predicate", predicate) + .toString(); + } +} diff --git a/src/main/java/seedu/address/logic/commands/imports/ImportClientCommand.java b/src/main/java/seedu/address/logic/commands/imports/ImportClientCommand.java new file mode 100644 index 00000000000..8892e82eaff --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/imports/ImportClientCommand.java @@ -0,0 +1,91 @@ +package seedu.address.logic.commands.imports; + +import static java.util.Objects.requireNonNull; + +import java.util.ArrayList; + +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.TabIndex; +import seedu.address.logic.commands.add.AddClientCommand; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.client.Client; + +/** + * Represents a command to import clients from a CSV file into the address book. + * This command allows users to add multiple clients from a CSV file with specific column titles. + */ +public class ImportClientCommand extends Command { + public static final String COMMAND_WORD = "import-client"; + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Imports clients from a CSV file.\n" + + "Column titles should follow this format strictly:\n" + + "Name, Contact Number, Email, Address, Role, Organisation, Document, Projects"; + public static final String MESSAGE_SUCCESS = "New client added: %1$s"; + + private final ArrayList toAddList; + + /** + * Creates an ImportClientCommand to import the specified list of clients. + * + * @param clientList The list of clients to be imported. + */ + public ImportClientCommand(ArrayList clientList) { + requireNonNull(clientList); + for (Client i : clientList) { + requireNonNull(i); + } + toAddList = clientList; + } + + /** + * Executes the import operation by adding clients from the CSV file to the address book. + * + * @param model The model in which to import the clients. + * @return A CommandResult indicating the success of the import operation. + * @throws CommandException If an error occurs during the import operation. + */ + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + String output = ""; + for (Client toAdd : toAddList) { + CommandResult result = new AddClientCommand(toAdd).execute(model); + output += result.getFeedbackToUser() + "\n"; + } + return new CommandResult(output, TabIndex.Client); + } + + /** + * Checks if this ImportClientCommand is equal to another object. + * + * @param other The object to compare with this ImportClientCommand. + * @return True if the objects are equal, false otherwise. + */ + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof ImportClientCommand)) { + return false; + } + + ImportClientCommand otherImportCommand = (ImportClientCommand) other; + return toAddList.equals(otherImportCommand.toAddList); + } + + /** + * Generates a string representation of this ImportClientCommand. + * + * @return A string representation of this object. + */ + @Override + public String toString() { + return new ToStringBuilder(this) + .add("toAddList", toAddList) + .toString(); + } +} diff --git a/src/main/java/seedu/address/logic/commands/imports/ImportDeveloperCommand.java b/src/main/java/seedu/address/logic/commands/imports/ImportDeveloperCommand.java new file mode 100644 index 00000000000..378503e76ba --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/imports/ImportDeveloperCommand.java @@ -0,0 +1,90 @@ +package seedu.address.logic.commands.imports; + +import static java.util.Objects.requireNonNull; + +import java.util.ArrayList; + +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.TabIndex; +import seedu.address.logic.commands.add.AddDeveloperCommand; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.developer.Developer; +/** + * Represents a command to import developers from a CSV file into the address book. + * This command allows users to add multiple developers from a CSV file with specific column titles. + */ +public class ImportDeveloperCommand extends Command { + public static final String COMMAND_WORD = "import-developer"; + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Imports developers from a CSV file.\n" + + "Column titles should follow this format strictly:\n" + + "Name, Contact Number, Email, Address, Date Joined, Role, Salary, GithubId, Rating, Projects"; + public static final String MESSAGE_SUCCESS = "New developer added: %1$s"; + + private final ArrayList toAddList; + + /** + * Creates an ImportDeveloperCommand to import the specified list of developers. + * + * @param developerList The list of developers to be imported. + */ + public ImportDeveloperCommand(ArrayList developerList) { + requireNonNull(developerList); + for (Developer i : developerList) { + requireNonNull(i); + } + toAddList = developerList; + } + + /** + * Executes the import operation by adding developers from the CSV file to the address book. + * + * @param model The model in which to import the developers. + * @return A CommandResult indicating the success of the import operation. + * @throws CommandException If an error occurs during the import operation. + */ + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + String output = ""; + for (Developer toAdd : toAddList) { + CommandResult result = new AddDeveloperCommand(toAdd).execute(model); + output += result.getFeedbackToUser() + "\n"; + } + return new CommandResult(output, TabIndex.Developer); + } + + /** + * Checks if this ImportDeveloperCommand is equal to another object. + * + * @param other The object to compare with this ImportDeveloperCommand. + * @return True if the objects are equal, false otherwise. + */ + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof ImportDeveloperCommand)) { + return false; + } + + ImportDeveloperCommand otherImportCommand = (ImportDeveloperCommand) other; + return toAddList.equals(otherImportCommand.toAddList); + } + + /** + * Generates a string representation of this ImportDeveloperCommand. + * + * @return A string representation of this object. + */ + @Override + public String toString() { + return new ToStringBuilder(this) + .add("toAddList", toAddList) + .toString(); + } +} diff --git a/src/main/java/seedu/address/logic/commands/list/ListClientCommand.java b/src/main/java/seedu/address/logic/commands/list/ListClientCommand.java new file mode 100644 index 00000000000..2351487cf9d --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/list/ListClientCommand.java @@ -0,0 +1,28 @@ +package seedu.address.logic.commands.list; + +import static java.util.Objects.requireNonNull; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_CLIENTS; + +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.TabIndex; +import seedu.address.model.Model; + +/** + * Lists all persons in the address book to the user. + */ +public class ListClientCommand extends Command { + + public static final String COMMAND_WORD = "list-client"; + + public static final String MESSAGE_SUCCESS = "Listed all clients"; + + + @Override + public CommandResult execute(Model model) { + assert model != null : "Model cannot be null"; + requireNonNull(model); + model.updateFilteredClientList(PREDICATE_SHOW_ALL_CLIENTS); + return new CommandResult(MESSAGE_SUCCESS, TabIndex.Client); + } +} diff --git a/src/main/java/seedu/address/logic/commands/list/ListDeveloperCommand.java b/src/main/java/seedu/address/logic/commands/list/ListDeveloperCommand.java new file mode 100644 index 00000000000..9ffa6d489dc --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/list/ListDeveloperCommand.java @@ -0,0 +1,28 @@ +package seedu.address.logic.commands.list; + +import static java.util.Objects.requireNonNull; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_DEVELOPERS; + +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.TabIndex; +import seedu.address.model.Model; + +/** + * Lists all persons in the address book to the user. + */ +public class ListDeveloperCommand extends Command { + + public static final String COMMAND_WORD = "list-developer"; + + public static final String MESSAGE_SUCCESS = "Listed all developers"; + + + @Override + public CommandResult execute(Model model) { + assert model != null : "Model cannot be null"; + requireNonNull(model); + model.updateFilteredDeveloperList(PREDICATE_SHOW_ALL_DEVELOPERS); + return new CommandResult(MESSAGE_SUCCESS, TabIndex.Developer); + } +} diff --git a/src/main/java/seedu/address/logic/commands/list/ListProjectCommand.java b/src/main/java/seedu/address/logic/commands/list/ListProjectCommand.java new file mode 100644 index 00000000000..8b8346371b4 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/list/ListProjectCommand.java @@ -0,0 +1,29 @@ +package seedu.address.logic.commands.list; + +import static java.util.Objects.requireNonNull; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PROJECTS; + +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.TabIndex; +import seedu.address.model.Model; + +/** + * Lists all persons in the address book to the user. + */ +public class ListProjectCommand extends Command { + + public static final String COMMAND_WORD = "list-project"; + + public static final String MESSAGE_SUCCESS = "Listed all projects"; + + + @Override + public CommandResult execute(Model model) { + assert model != null : "Model cannot be null"; + requireNonNull(model); + model.updateFilteredProjectList(PREDICATE_SHOW_ALL_PROJECTS); + model.updateFilteredProjectDeadlineList(unused -> true); + return new CommandResult(MESSAGE_SUCCESS, TabIndex.Project); + } +} diff --git a/src/main/java/seedu/address/logic/commands/mark/MarkDeadlineCommand.java b/src/main/java/seedu/address/logic/commands/mark/MarkDeadlineCommand.java new file mode 100644 index 00000000000..0de839c9f5d --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/mark/MarkDeadlineCommand.java @@ -0,0 +1,136 @@ +package seedu.address.logic.commands.mark; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DEADLINE; + +import java.util.List; + +import seedu.address.commons.core.index.Index; +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.logic.Messages; +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.TabIndex; +import seedu.address.logic.commands.edit.EditProjectCommand; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.logic.parser.edit.EditProjectCommandParser; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.Model; +import seedu.address.model.project.Project; + +/** + * Represents a command to mark a specific deadline within a project as completed. + * This command allows users to mark deadlines as done within a specified project. + */ +public class MarkDeadlineCommand extends Command { + public static final String COMMAND_WORD = "mark-deadline"; + public static final String MESSAGE_SUCCESS = "The deadline has been marked as completed!"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Marks the specified deadline of the specified project " + + "as done. \n" + + "DEADLINE_INDEX must be a positive integer representing the index of the deadline in the displayed " + + "deadline table, and PROJECT_INDEX must be a positive integer which is the project's index number in the " + + "displayed project list. \n" + + "Parameters: " + + "PROJECT_INDEX DEADLINE_INDEX \n" + + "Example: " + COMMAND_WORD + " 1 2"; + + private final Index deadlineIndex; + private final Index projIndex; + + /** + * Creates a MarkDeadlineCommand to mark a specific deadline as completed within a project. + * + * @param projIndex The index of the project containing the deadline to be marked. + * @param deadlineIndex The index of the deadline to be marked. + */ + public MarkDeadlineCommand(Index projIndex, Index deadlineIndex) { + this.deadlineIndex = deadlineIndex; + this.projIndex = projIndex; + } + + /** + * Executes the mark deadline operation by marking the specified deadline as completed within the project. + * + * @param model The model in which to mark the deadline as completed. + * @return A CommandResult indicating the success of the mark deadline operation. + * @throws CommandException If an error occurs during the execution. + */ + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + List lastShownList = model.getFilteredProjectList(); + if (projIndex.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_PROJECT_DISPLAYED_INDEX); + } + + Project projectToEdit = lastShownList.get(projIndex.getZeroBased()); + + if (deadlineIndex.getZeroBased() >= projectToEdit.deadlineListSize()) { + throw new CommandException(Messages.MESSAGE_INVALID_DEADLINE_DISPLAYED_INDEX); + } + + EditProjectCommand edit; + + try { + edit = new EditProjectCommandParser().parse(editProjectArgs( + projectToEdit.markDeadlineStringRep(deadlineIndex.getZeroBased()), + projIndex.getOneBased())); + } catch (ParseException pe) { + throw new CommandException(pe.getMessage()); + } + + edit.execute(model); + return new CommandResult(MESSAGE_SUCCESS, TabIndex.Project); + } + + /** + * Formats each element in a list of String representations into a String that will be used as the arguments + * parsed by an EditProjectCommandParser. + * + * @param stringRep The list containing the string representations of the Deadlines to be passed into the parser. + * @param index The index of the Project to edit. + * @return A String containing the index of the projects and the deadlines including the marked deadline. + */ + private String editProjectArgs(List stringRep, int index) { + String res = "" + index; + for (String s : stringRep) { + res += " " + PREFIX_DEADLINE + s; + } + return res; + } + + /** + * Checks if this MarkDeadlineCommand is equal to another object. + * + * @param other The object to compare with this MarkDeadlineCommand. + * @return True if the objects are equal, false otherwise. + */ + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof MarkDeadlineCommand)) { + return false; + } + + MarkDeadlineCommand otherMarkDeadlineCommand = (MarkDeadlineCommand) other; + return deadlineIndex == otherMarkDeadlineCommand.deadlineIndex + && projIndex.equals(otherMarkDeadlineCommand.projIndex); + } + + /** + * Generates a string representation of this MarkDeadlineCommand. + * + * @return A string representation of this object. + */ + @Override + public String toString() { + return new ToStringBuilder(this) + .add("deadlineIndex", deadlineIndex) + .add("projIndex", projIndex) + .toString(); + } +} diff --git a/src/main/java/seedu/address/logic/commands/mark/UnmarkDeadlineCommand.java b/src/main/java/seedu/address/logic/commands/mark/UnmarkDeadlineCommand.java new file mode 100644 index 00000000000..c384e689625 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/mark/UnmarkDeadlineCommand.java @@ -0,0 +1,124 @@ +package seedu.address.logic.commands.mark; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DEADLINE; + +import java.util.List; + +import seedu.address.commons.core.index.Index; +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.logic.Messages; +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.TabIndex; +import seedu.address.logic.commands.edit.EditProjectCommand; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.logic.parser.edit.EditProjectCommandParser; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.Model; +import seedu.address.model.project.Project; + +/** + * Marks the specified deadline of the specified project as undone. + */ +public class UnmarkDeadlineCommand extends Command { + public static final String COMMAND_WORD = "unmark-deadline"; + public static final String MESSAGE_SUCCESS = "The deadline has been marked as undone!"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Marks the specified deadline of the specified project " + + "as undone. \n" + + "DEADLINE_INDEX must be a positive integer representing the index of the deadline in the displayed " + + "deadline table, and PROJECT_INDEX must be a positive integer which is the project's index number in the " + + "displayed project list. \n" + + "Parameters: " + + "PROJECT_INDEX DEADLINE_INDEX \n" + + "Example: " + COMMAND_WORD + " 1 2"; + + private final Index deadlineIndex; + private final Index projIndex; + + /** + * Creates an UnmarkDeadlineCommand to unmark the specified deadline of the specified project. + * + * @param projIndex The index of the project containing the deadline to be unmarked. + * @param deadlineIndex The index of the deadline to be unmarked within the project. + */ + public UnmarkDeadlineCommand(Index projIndex, Index deadlineIndex) { + this.deadlineIndex = deadlineIndex; + this.projIndex = projIndex; + } + /** + * Executes the operation to unmark a project deadline as undone. + * + * @param model The model that contains the project data. + * @return A CommandResult indicating the success of the operation. + * @throws CommandException If an error occurs during the execution. + */ + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + List lastShownList = model.getFilteredProjectList(); + if (projIndex.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_PROJECT_DISPLAYED_INDEX); + } + + Project projectToEdit = lastShownList.get(projIndex.getZeroBased()); + + if (deadlineIndex.getZeroBased() >= projectToEdit.deadlineListSize()) { + throw new CommandException(Messages.MESSAGE_INVALID_DEADLINE_DISPLAYED_INDEX); + } + + EditProjectCommand edit; + + try { + edit = new EditProjectCommandParser().parse(editProjectArgs( + projectToEdit.unmarkDeadlineStringRep(deadlineIndex.getZeroBased()), + projIndex.getOneBased())); + } catch (ParseException pe) { + throw new CommandException(pe.getMessage()); + } + + edit.execute(model); + return new CommandResult(MESSAGE_SUCCESS, TabIndex.Project); + } + + /** + * Formats each element in a list of String representations into a String that will be used as the arguments + * parsed by an EditProjectCommandParser. + * + * @param stringRep The list containing the string representations of the Deadlines to be passed into the parser. + * @param index The index of the Project to edit. + * @return A String containing the index of the projects and the deadlines including the unmarked deadline. + */ + private String editProjectArgs(List stringRep, int index) { + String res = "" + index; + for (String s : stringRep) { + res += " " + PREFIX_DEADLINE + s; + } + return res; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof UnmarkDeadlineCommand)) { + return false; + } + + UnmarkDeadlineCommand otherUnmarkDeadlineCommand = (UnmarkDeadlineCommand) other; + return deadlineIndex == otherUnmarkDeadlineCommand.deadlineIndex + && projIndex.equals(otherUnmarkDeadlineCommand.projIndex); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("deadlineIndex", deadlineIndex) + .add("projIndex", projIndex) + .toString(); + } +} 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 4ff1a97ed77..00000000000 --- a/src/main/java/seedu/address/logic/parser/AddCommandParser.java +++ /dev/null @@ -1,61 +0,0 @@ -package seedu.address.logic.parser; - -import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; -import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; -import static seedu.address.logic.parser.CliSyntax.PREFIX_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)); - } - - argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS); - 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 index 3149ee07e0b..a7561a06a8c 100644 --- a/src/main/java/seedu/address/logic/parser/AddressBookParser.java +++ b/src/main/java/seedu/address/logic/parser/AddressBookParser.java @@ -2,22 +2,69 @@ import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; import static seedu.address.logic.Messages.MESSAGE_UNKNOWN_COMMAND; +import static seedu.address.logic.Messages.MESSAGE_VALID_LOCKED_COMMANDS; +import static seedu.address.logic.Messages.MESSAGE_VALID_UNLOCKED_COMMANDS; import java.util.logging.Logger; import java.util.regex.Matcher; import java.util.regex.Pattern; import seedu.address.commons.core.LogsCenter; -import seedu.address.logic.commands.AddCommand; +import seedu.address.logic.commands.ChangePasswordCommand; import seedu.address.logic.commands.ClearCommand; import seedu.address.logic.commands.Command; -import seedu.address.logic.commands.DeleteCommand; -import seedu.address.logic.commands.EditCommand; import seedu.address.logic.commands.ExitCommand; -import seedu.address.logic.commands.FindCommand; import seedu.address.logic.commands.HelpCommand; -import seedu.address.logic.commands.ListCommand; +import seedu.address.logic.commands.LockCommand; +import seedu.address.logic.commands.RedoCommand; +import seedu.address.logic.commands.UndoCommand; +import seedu.address.logic.commands.UnlockCommand; +import seedu.address.logic.commands.add.AddClientCommand; +import seedu.address.logic.commands.add.AddDeveloperCommand; +import seedu.address.logic.commands.add.AddProjectCommand; +import seedu.address.logic.commands.addroles.AddClientRoleCommand; +import seedu.address.logic.commands.addroles.AddDeveloperRoleCommand; +import seedu.address.logic.commands.delete.DeleteClientCommand; +import seedu.address.logic.commands.delete.DeleteDeveloperCommand; +import seedu.address.logic.commands.delete.DeleteProjectCommand; +import seedu.address.logic.commands.deleteroles.DeleteClientRoleCommand; +import seedu.address.logic.commands.deleteroles.DeleteDeveloperRoleCommand; +import seedu.address.logic.commands.edit.EditClientCommand; +import seedu.address.logic.commands.edit.EditDeveloperCommand; +import seedu.address.logic.commands.edit.EditProjectCommand; +import seedu.address.logic.commands.find.FindClientCommand; +import seedu.address.logic.commands.find.FindDeadlineCommand; +import seedu.address.logic.commands.find.FindDeveloperCommand; +import seedu.address.logic.commands.find.FindProjectCommand; +import seedu.address.logic.commands.imports.ImportClientCommand; +import seedu.address.logic.commands.imports.ImportDeveloperCommand; +import seedu.address.logic.commands.list.ListClientCommand; +import seedu.address.logic.commands.list.ListDeveloperCommand; +import seedu.address.logic.commands.list.ListProjectCommand; +import seedu.address.logic.commands.mark.MarkDeadlineCommand; +import seedu.address.logic.commands.mark.UnmarkDeadlineCommand; +import seedu.address.logic.parser.add.AddClientCommandParser; +import seedu.address.logic.parser.add.AddDeveloperCommandParser; +import seedu.address.logic.parser.add.AddProjectCommandParser; +import seedu.address.logic.parser.addroles.AddClientRoleCommandParser; +import seedu.address.logic.parser.addroles.AddDeveloperRoleCommandParser; +import seedu.address.logic.parser.delete.DeleteClientCommandParser; +import seedu.address.logic.parser.delete.DeleteDeveloperCommandParser; +import seedu.address.logic.parser.delete.DeleteProjectCommandParser; +import seedu.address.logic.parser.deleteroles.DeleteClientRoleCommandParser; +import seedu.address.logic.parser.deleteroles.DeleteDeveloperRoleCommandParser; +import seedu.address.logic.parser.edit.EditClientCommandParser; +import seedu.address.logic.parser.edit.EditDeveloperCommandParser; +import seedu.address.logic.parser.edit.EditProjectCommandParser; import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.logic.parser.find.FindClientCommandParser; +import seedu.address.logic.parser.find.FindDeadlineCommandParser; +import seedu.address.logic.parser.find.FindDeveloperCommandParser; +import seedu.address.logic.parser.find.FindProjectCommandParser; +import seedu.address.logic.parser.imports.ImportClientCommandParser; +import seedu.address.logic.parser.imports.ImportDeveloperCommandParser; +import seedu.address.logic.parser.mark.MarkDeadlineCommandParser; +import seedu.address.logic.parser.mark.UnmarkDeadlineCommandParser; /** * Parses user input. @@ -29,6 +76,15 @@ public class AddressBookParser { */ private static final Pattern BASIC_COMMAND_FORMAT = Pattern.compile("(?\\S+)(?.*)"); private static final Logger logger = LogsCenter.getLogger(AddressBookParser.class); + private static boolean isLocked = true; + + public static void lock() { + isLocked = true; + } + + public static void unlock() { + isLocked = false; + } /** * Parses user input into command for execution. @@ -50,37 +106,118 @@ public Command parseCommand(String userInput) throws ParseException { // log messages such as the one below. // Lower level log messages are used sparingly to minimize noise in the code. logger.fine("Command word: " + commandWord + "; Arguments: " + arguments); + if (isLocked == false) { + switch (commandWord) { + case AddDeveloperCommand.COMMAND_WORD: + return new AddDeveloperCommandParser().parse(arguments); + + case AddClientCommand.COMMAND_WORD: + return new AddClientCommandParser().parse(arguments); + + case AddProjectCommand.COMMAND_WORD: + return new AddProjectCommandParser().parse(arguments); + + case AddDeveloperRoleCommand.COMMAND_WORD: + return new AddDeveloperRoleCommandParser().parse(arguments); + + case AddClientRoleCommand.COMMAND_WORD: + return new AddClientRoleCommandParser().parse(arguments); + + case ImportDeveloperCommand.COMMAND_WORD: + return new ImportDeveloperCommandParser().parse(arguments); + + case ImportClientCommand.COMMAND_WORD: + return new ImportClientCommandParser().parse(arguments); + case FindDeadlineCommand.COMMAND_WORD: + return new FindDeadlineCommandParser().parse(arguments); + + case EditDeveloperCommand.COMMAND_WORD: + return new EditDeveloperCommandParser().parse(arguments); + + case EditClientCommand.COMMAND_WORD: + return new EditClientCommandParser().parse(arguments); + + case EditProjectCommand.COMMAND_WORD: + return new EditProjectCommandParser().parse(arguments); + + case DeleteDeveloperCommand.COMMAND_WORD: + return new DeleteDeveloperCommandParser().parse(arguments); - switch (commandWord) { + case DeleteClientCommand.COMMAND_WORD: + return new DeleteClientCommandParser().parse(arguments); - case AddCommand.COMMAND_WORD: - return new AddCommandParser().parse(arguments); + case DeleteProjectCommand.COMMAND_WORD: + return new DeleteProjectCommandParser().parse(arguments); - case EditCommand.COMMAND_WORD: - return new EditCommandParser().parse(arguments); + case DeleteDeveloperRoleCommand.COMMAND_WORD: + return new DeleteDeveloperRoleCommandParser().parse(arguments); - case DeleteCommand.COMMAND_WORD: - return new DeleteCommandParser().parse(arguments); + case DeleteClientRoleCommand.COMMAND_WORD: + return new DeleteClientRoleCommandParser().parse(arguments); - case ClearCommand.COMMAND_WORD: - return new ClearCommand(); + case ClearCommand.COMMAND_WORD: + return new ClearCommand(); - case FindCommand.COMMAND_WORD: - return new FindCommandParser().parse(arguments); + case FindDeveloperCommand.COMMAND_WORD: + return new FindDeveloperCommandParser().parse(arguments); - case ListCommand.COMMAND_WORD: - return new ListCommand(); + case FindClientCommand.COMMAND_WORD: + return new FindClientCommandParser().parse(arguments); - case ExitCommand.COMMAND_WORD: - return new ExitCommand(); + case FindProjectCommand.COMMAND_WORD: + return new FindProjectCommandParser().parse(arguments); - case HelpCommand.COMMAND_WORD: - return new HelpCommand(); + case ListClientCommand.COMMAND_WORD: + return new ListClientCommand(); - default: - logger.finer("This user input caused a ParseException: " + userInput); - throw new ParseException(MESSAGE_UNKNOWN_COMMAND); + case ListDeveloperCommand.COMMAND_WORD: + return new ListDeveloperCommand(); + + case ListProjectCommand.COMMAND_WORD: + return new ListProjectCommand(); + + case UndoCommand.COMMAND_WORD: + return new UndoCommand(); + + case RedoCommand.COMMAND_WORD: + return new RedoCommand(); + + case ExitCommand.COMMAND_WORD: + return new ExitCommand(); + + case HelpCommand.COMMAND_WORD: + return new HelpCommand(); + + case MarkDeadlineCommand.COMMAND_WORD: + return new MarkDeadlineCommandParser().parse(arguments); + + case UnmarkDeadlineCommand.COMMAND_WORD: + return new UnmarkDeadlineCommandParser().parse(arguments); + + case LockCommand.COMMAND_WORD: + return new LockCommand(); + + case ChangePasswordCommand.COMMAND_WORD: + return new ChangePasswordCommandParser().parse(arguments); + + default: + logger.finer("This user input caused a ParseException: " + userInput); + throw new ParseException(MESSAGE_UNKNOWN_COMMAND + "\n" + MESSAGE_VALID_UNLOCKED_COMMANDS); + } + } else { + switch (commandWord) { + case UnlockCommand.COMMAND_WORD: + return new UnlockCommandParser().parse(arguments); + case ExitCommand.COMMAND_WORD: + return new ExitCommand(); + + case HelpCommand.COMMAND_WORD: + return new HelpCommand(); + + default: + logger.finer("This user input caused a ParseException: " + userInput); + throw new ParseException(MESSAGE_UNKNOWN_COMMAND + "\n" + MESSAGE_VALID_LOCKED_COMMANDS); + } } } - } diff --git a/src/main/java/seedu/address/logic/parser/ArgumentMultimap.java b/src/main/java/seedu/address/logic/parser/ArgumentMultimap.java index 21e26887a83..3bb07af2dee 100644 --- a/src/main/java/seedu/address/logic/parser/ArgumentMultimap.java +++ b/src/main/java/seedu/address/logic/parser/ArgumentMultimap.java @@ -19,7 +19,9 @@ */ public class ArgumentMultimap { - /** Prefixes mapped to their respective arguments**/ + /** + * Prefixes mapped to their respective arguments + **/ private final Map> argMultimap = new HashMap<>(); /** @@ -75,4 +77,11 @@ public void verifyNoDuplicatePrefixesFor(Prefix... prefixes) throws ParseExcepti throw new ParseException(Messages.getErrorMessageForDuplicatePrefixes(duplicatedPrefixes)); } } + + /** + * Returns a boolean representing whether there are any prefix mappings in the ArgumentMultimap. + */ + public boolean hasMappings() { + return !argMultimap.isEmpty(); + } } diff --git a/src/main/java/seedu/address/logic/parser/ArgumentTokenizer.java b/src/main/java/seedu/address/logic/parser/ArgumentTokenizer.java index 5c9aebfa488..3be8e89059d 100644 --- a/src/main/java/seedu/address/logic/parser/ArgumentTokenizer.java +++ b/src/main/java/seedu/address/logic/parser/ArgumentTokenizer.java @@ -7,11 +7,11 @@ /** * Tokenizes arguments string of the form: {@code preamble value value ...}
- * e.g. {@code some preamble text t/ 11.00 t/12.00 k/ m/ July} where prefixes are {@code t/ k/ m/}.
+ * e.g. {@code some preamble text t/ 11.00 t/12.00 k/ m/ July} where prefixes are {@code t/ k/ m/}.
* 1. An argument's value can be an empty string e.g. the value of {@code k/} in the above example.
* 2. Leading and trailing whitespaces of an argument value will be discarded.
* 3. An argument may be repeated and all its values will be accumulated e.g. the value of {@code t/} - * in the above example.
+ * in the above example.
*/ public class ArgumentTokenizer { @@ -21,7 +21,7 @@ public class ArgumentTokenizer { * * @param argsString Arguments string of the form: {@code preamble value value ...} * @param prefixes Prefixes to tokenize the arguments string with - * @return ArgumentMultimap object that maps prefixes to their arguments + * @return ArgumentMultimap object that maps prefixes to their arguments */ public static ArgumentMultimap tokenize(String argsString, Prefix... prefixes) { List positions = findAllPrefixPositions(argsString, prefixes); @@ -33,7 +33,7 @@ public static ArgumentMultimap tokenize(String argsString, Prefix... prefixes) { * * @param argsString Arguments string of the form: {@code preamble value value ...} * @param prefixes Prefixes to find in the arguments string - * @return List of zero-based prefix positions in the given arguments string + * @return List of zero-based prefix positions in the given arguments string */ private static List findAllPrefixPositions(String argsString, Prefix... prefixes) { return Arrays.stream(prefixes) @@ -62,7 +62,7 @@ private static List findPrefixPositions(String argsString, Prefi * {@code argsString} starting from index {@code fromIndex}. An occurrence * is valid if there is a whitespace before {@code prefix}. Returns -1 if no * such occurrence can be found. - * + *

* E.g if {@code argsString} = "e/hip/900", {@code prefix} = "p/" and * {@code fromIndex} = 0, this method returns -1 as there are no valid * occurrences of "p/" with whitespace before it. However, if @@ -82,7 +82,7 @@ private static int findPrefixPosition(String argsString, String prefix, int from * * @param argsString Arguments string of the form: {@code preamble value value ...} * @param prefixPositions Zero-based positions of all prefixes in {@code argsString} - * @return ArgumentMultimap object that maps prefixes to their arguments + * @return ArgumentMultimap object that maps prefixes to their arguments */ private static ArgumentMultimap extractArguments(String argsString, List prefixPositions) { @@ -114,8 +114,8 @@ private static ArgumentMultimap extractArguments(String argsString, List { + /** + * Parses user input and creates a {@link ChangePasswordCommand} based on the provided passwords. + * + * @param args User input arguments. + * @return A {@link ChangePasswordCommand} for changing the password. + * @throws ParseException If the user input does not conform to the expected format. + */ + @Override + public ChangePasswordCommand parse(String args) throws ParseException { + requireNonNull(args); + ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_PASSWORD, PREFIX_NEW_PASSWORD); + if (argMultimap.getValue(PREFIX_PASSWORD).isPresent() + && argMultimap.getValue(PREFIX_NEW_PASSWORD).isPresent()) { + String currentPw = ParserUtil.parsePassword(argMultimap.getValue(PREFIX_PASSWORD).get()); + String newPw = ParserUtil.parsePassword(argMultimap.getValue(PREFIX_NEW_PASSWORD).get()); + return new ChangePasswordCommand(currentPw, newPw); + } + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, ChangePasswordCommand.MESSAGE_USAGE)); + } +} diff --git a/src/main/java/seedu/address/logic/parser/CliSyntax.java b/src/main/java/seedu/address/logic/parser/CliSyntax.java index 75b1a9bf119..fb3977ae230 100644 --- a/src/main/java/seedu/address/logic/parser/CliSyntax.java +++ b/src/main/java/seedu/address/logic/parser/CliSyntax.java @@ -10,6 +10,19 @@ public class CliSyntax { public static final Prefix PREFIX_PHONE = new Prefix("p/"); public static final Prefix PREFIX_EMAIL = new Prefix("e/"); public static final Prefix PREFIX_ADDRESS = new Prefix("a/"); - public static final Prefix PREFIX_TAG = new Prefix("t/"); + public static final Prefix PREFIX_PROJECT = new Prefix("pr/"); + public static final Prefix PREFIX_DATEJOINED = new Prefix("d/"); + public static final Prefix PREFIX_ROLE = new Prefix("r/"); + public static final Prefix PREFIX_SALARY = new Prefix("s/"); + public static final Prefix PREFIX_GITHUBID = new Prefix("g/"); + public static final Prefix PREFIX_RATING = new Prefix("rt/"); + public static final Prefix PREFIX_ORGANISATION = new Prefix("o/"); + public static final Prefix PREFIX_DOCUMENT = new Prefix("do/"); + public static final Prefix PREFIX_DESCRIPTION = new Prefix("dr/"); + public static final Prefix PREFIX_DEADLINE = new Prefix("dl/"); + public static final Prefix PREFIX_PASSWORD = new Prefix("pw/"); + public static final Prefix PREFIX_NEW_PASSWORD = new Prefix("npw/"); + public static final Prefix PREFIX_PRIORITY = new Prefix("pri/"); + } 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 3527fe76a3e..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.logic.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 46b3309a78b..00000000000 --- a/src/main/java/seedu/address/logic/parser/EditCommandParser.java +++ /dev/null @@ -1,85 +0,0 @@ -package seedu.address.logic.parser; - -import static java.util.Objects.requireNonNull; -import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; -import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; -import static seedu.address.logic.parser.CliSyntax.PREFIX_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); - } - - argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS); - - 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 2867bde857b..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.logic.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/Parser.java b/src/main/java/seedu/address/logic/parser/Parser.java index d6551ad8e3f..ce644a9c6fd 100644 --- a/src/main/java/seedu/address/logic/parser/Parser.java +++ b/src/main/java/seedu/address/logic/parser/Parser.java @@ -10,6 +10,7 @@ public interface Parser { /** * Parses {@code userInput} into a command and returns it. + * * @throws ParseException if {@code userInput} does not conform the expected format */ T parse(String userInput) throws ParseException; diff --git a/src/main/java/seedu/address/logic/parser/ParserUtil.java b/src/main/java/seedu/address/logic/parser/ParserUtil.java index b117acb9c55..723ef907d9b 100644 --- a/src/main/java/seedu/address/logic/parser/ParserUtil.java +++ b/src/main/java/seedu/address/logic/parser/ParserUtil.java @@ -2,18 +2,32 @@ import static java.util.Objects.requireNonNull; +import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; +import java.util.List; import java.util.Set; import seedu.address.commons.core.index.Index; import seedu.address.commons.util.StringUtil; +import seedu.address.logic.Messages; import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.Password; +import seedu.address.model.client.ClientRoles; +import seedu.address.model.client.Document; +import seedu.address.model.commons.Date; +import seedu.address.model.commons.Name; +import seedu.address.model.developer.DeveloperRoles; +import seedu.address.model.developer.GithubId; +import seedu.address.model.developer.Rating; +import seedu.address.model.developer.Salary; 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.model.project.Deadline; +import seedu.address.model.project.Description; +import seedu.address.model.project.Priority; +import seedu.address.model.project.Project; /** * Contains utility methods used for parsing strings in the various *Parser classes. @@ -25,6 +39,7 @@ public class ParserUtil { /** * Parses {@code oneBasedIndex} into an {@code Index} and returns it. Leading and trailing whitespaces will be * trimmed. + * * @throws ParseException if the specified index is invalid (not non-zero unsigned integer). */ public static Index parseIndex(String oneBasedIndex) throws ParseException { @@ -96,29 +111,237 @@ public static Email parseEmail(String email) throws ParseException { } /** - * Parses a {@code String tag} into a {@code Tag}. + * Parses a {@code String project} into a {@code Project}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code Project} is invalid. + */ + public static Project parseProject(String project) throws ParseException { + requireNonNull(project); + String trimmedProject = project.trim(); + if (!Name.isValidName(trimmedProject)) { + throw new ParseException(Name.MESSAGE_CONSTRAINTS); + } + return new Project(new Name(trimmedProject), new Description(""), new ArrayList()); + } + + /** + * Parses {@code Collection projects} into a {@code Set}. + */ + public static Set parseProjects(Collection projects) throws ParseException { + requireNonNull(projects); + final Set projectSet = new HashSet<>(); + for (String projectName : projects) { + projectSet.add(parseProject(projectName)); + } + return projectSet; + } + + /** + * Parses {@code Collection projects} into a {@code Set}. + * + * @param projects The Collection of projects to parse. + * @returns A HashSet of String. + */ + public static Set parseProjectsToSet(Collection projects) { + requireNonNull(projects); + final Set projectSet = new HashSet<>(); + + for (String p : projects) { + projectSet.add(p); + } + return projectSet; + } + + /** + * Parses {@code Collection deadlines} into a {@code List}. + * + * @param deadlines The Collection of deadlines to parse. + * @throws ParseException if format is invalid. + * @returns An ArrayList of Deadlines if parsing is successful. + */ + public static List parseDeadlines(Collection deadlines) throws ParseException { + requireNonNull(deadlines); + final List deadlineSet = new ArrayList<>(); + for (String str : deadlines) { + if (!Deadline.isValidDeadline(str)) { + throw new ParseException(String.format(Messages.MESSAGE_INVALID_COMMAND_FORMAT, + Deadline.MESSAGE_CONSTRAINTS)); + } else { + deadlineSet.add(new Deadline(str, deadlineSet.size() + 1)); + } + } + return deadlineSet; + } + + /** + * Parses a {@code String dateJoined} into a {@code DateJoined}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code DateJoined} is invalid. + */ + public static Date parseDateJoined(String dateJoined) throws ParseException { + requireNonNull(dateJoined); + String trimmedDateJoined = dateJoined.trim(); + if (!Date.isValidDate(trimmedDateJoined, false)) { + throw new ParseException(Date.MESSAGE_CONSTRAINTS); + } + return new Date(trimmedDateJoined, false); + } + + /** + * Parses a {@code String dateDeadline} into a {@code DateDeadline}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code DateDeadline} is invalid. + */ + public static Date parseDateDeadline(String dateJoined) throws ParseException { + requireNonNull(dateJoined); + String trimmedDateJoined = dateJoined.trim(); + if (!Date.isValidDate(trimmedDateJoined, true)) { + throw new ParseException(Date.MESSAGE_CONSTRAINTS); + } + return new Date(trimmedDateJoined, true); + } + + + /** + * Parses a {@code String role} into a {@code Role}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code Role} is invalid. + */ + public static DeveloperRoles parseDeveloperRole(String role) throws ParseException { + requireNonNull(role); + String trimmedRole = role.trim(); + if (!DeveloperRoles.isValidRole(trimmedRole)) { + throw new ParseException(DeveloperRoles.NO_SUCH_DEVELOPER_ROLE); + } + return new DeveloperRoles(trimmedRole); + } + + /** + * Parses a client role string and returns a `ClientRoles` object. + * + * @param role The client role string to be parsed. + * @return A `ClientRoles` object representing the parsed client role. + * @throws ParseException If the input string is not a valid client role. + */ + public static ClientRoles parseClientRole(String role) throws ParseException { + requireNonNull(role); + String trimmedRole = role.trim(); + if (!ClientRoles.isValidRole(trimmedRole)) { + throw new ParseException(ClientRoles.NO_SUCH_CLIENT_ROLE); + } + return new ClientRoles(trimmedRole); + } + + /** + * Parses a {@code String salary} into a {@code Salary}. * Leading and trailing whitespaces will be trimmed. * - * @throws ParseException if the given {@code tag} is invalid. + * @throws ParseException if the given {@code Salary} is invalid. */ - public static Tag parseTag(String tag) throws ParseException { - requireNonNull(tag); - String trimmedTag = tag.trim(); - if (!Tag.isValidTagName(trimmedTag)) { - throw new ParseException(Tag.MESSAGE_CONSTRAINTS); + public static Salary parseSalary(String salary) throws ParseException { + requireNonNull(salary); + String trimmedSalary = salary.trim(); + if (!Salary.isValidSalary(trimmedSalary)) { + throw new ParseException(Salary.MESSAGE_CONSTRAINTS); } - return new Tag(trimmedTag); + return new Salary(trimmedSalary); } /** - * Parses {@code Collection tags} into a {@code Set}. + * Parses a GitHub ID string and returns a `GithubId` object. + * + * @param githubid The GitHub ID string to be parsed. + * @return A `GithubId` object representing the parsed GitHub ID. + * @throws ParseException If the input string is not a valid GitHub ID. + */ + public static GithubId parseGithubId(String githubid) throws ParseException { + requireNonNull(githubid); + String trimmedGithubId = githubid.trim(); + if (!GithubId.isValidGithubId(trimmedGithubId)) { + throw new ParseException(GithubId.MESSAGE_CONSTRAINTS); + } + return new GithubId(trimmedGithubId); + } + + /** + * Parses a rating string and returns a `Rating` object. + * + * @param rating The rating string to be parsed. + * @return A `Rating` object representing the parsed rating. + * @throws ParseException If the input string is not a valid rating. + */ + public static Rating parseRating(String rating) throws ParseException { + requireNonNull(rating); + String trimmedRating = rating.trim(); + if (!Rating.isValidRating(trimmedRating)) { + throw new ParseException(Rating.MESSAGE_CONSTRAINTS); + } + return new Rating(trimmedRating); + } + + /** + * Parses a document URL string and returns a `Document` object. + * + * @param doc The document URL string to be parsed. + * @return A `Document` object representing the parsed document URL. + * @throws ParseException If the input string is not a valid document URL. + */ + public static Document parseDocument(String doc) throws ParseException { + requireNonNull(doc); + String trimmedDoc = doc.trim(); + if (!Document.isValidUrl(trimmedDoc)) { + throw new ParseException(Document.MESSAGE_CONSTRAINTS); + } + return new Document(trimmedDoc); + } + + /** + * Parses a description string and returns a `Description` object. + * + * @param description The description string to be parsed. + * @return A `Description` object representing the parsed description. + * @throws ParseException If the input string is not a valid description. + */ + 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 password string and returns the trimmed password. + * + * @param pw The password string to be parsed. + * @return The trimmed password. + * @throws ParseException If the input string is not a valid password. + */ + public static String parsePassword(String pw) throws ParseException { + requireNonNull(pw); + String trimmedPw = pw.trim(); + if (!Password.isValidPassword(trimmedPw)) { + throw new ParseException(Password.MESSAGE_CONSTRAINTS); + } + return trimmedPw; + } + + /** + * Parses a priority string and returns the corresponding Priority enum. + * + * @param priorityKeywords The priority string to be parsed. + * @return The corresponding Priority enum. + * @throws ParseException If the input string is not a valid priority. */ - public static Set parseTags(Collection tags) throws ParseException { - requireNonNull(tags); - final Set tagSet = new HashSet<>(); - for (String tagName : tags) { - tagSet.add(parseTag(tagName)); + public static Priority parsePriority(String priorityKeywords) throws ParseException { + if (!(priorityKeywords.equals("HIGH") || priorityKeywords.equals("MEDIUM") || priorityKeywords.equals("LOW"))) { + throw new ParseException("Priority has to be HIGH, MEDIUM or LOW"); } - return tagSet; + return Priority.valueOf(priorityKeywords); } } diff --git a/src/main/java/seedu/address/logic/parser/UnlockCommandParser.java b/src/main/java/seedu/address/logic/parser/UnlockCommandParser.java new file mode 100644 index 00000000000..bcc990b0efa --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/UnlockCommandParser.java @@ -0,0 +1,33 @@ +package seedu.address.logic.parser; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PASSWORD; + +import seedu.address.logic.commands.UnlockCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Parses the user input to create an UnlockCommand. + */ +public class UnlockCommandParser implements Parser { + /** + * Parses the given {@code args} and returns an UnlockCommand. + * + * @param args User input arguments. + * @return An UnlockCommand for unlocking the application. + * @throws ParseException If the user input does not conform to the expected format. + */ + @Override + public UnlockCommand parse(String args) throws ParseException { + requireNonNull(args); + ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_PASSWORD); + + if (argMultimap.getValue(PREFIX_PASSWORD).isPresent()) { + String input = ParserUtil.parsePassword(argMultimap.getValue(PREFIX_PASSWORD).get()); + return new UnlockCommand(input); + } + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, UnlockCommand.MESSAGE_USAGE)); + } +} diff --git a/src/main/java/seedu/address/logic/parser/add/AddClientCommandParser.java b/src/main/java/seedu/address/logic/parser/add/AddClientCommandParser.java new file mode 100644 index 00000000000..0c1166cbf3c --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/add/AddClientCommandParser.java @@ -0,0 +1,94 @@ +package seedu.address.logic.parser.add; + +import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DATEJOINED; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DEADLINE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DESCRIPTION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DOCUMENT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; +import static seedu.address.logic.parser.CliSyntax.PREFIX_GITHUBID; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ORGANISATION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PROJECT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_RATING; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ROLE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_SALARY; + +import java.util.HashSet; +import java.util.Set; +import java.util.stream.Stream; + +import seedu.address.logic.Messages; +import seedu.address.logic.commands.add.AddClientCommand; +import seedu.address.logic.parser.ArgumentMultimap; +import seedu.address.logic.parser.ArgumentTokenizer; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.ParserUtil; +import seedu.address.logic.parser.Prefix; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.client.Client; +import seedu.address.model.client.ClientRoles; +import seedu.address.model.client.Document; +import seedu.address.model.commons.Name; +import seedu.address.model.person.Address; +import seedu.address.model.person.Email; +import seedu.address.model.person.Phone; + +/** + * Parses input arguments and creates a new AddClientCommand object + */ +public class AddClientCommandParser implements Parser { + //Name name, Phone phone, Email email, Address address, Role role, Set projects, + // Name organisation, Document document + + /** + * Returns true if none of the prefixes contains empty {@code Optional} values in the given + * {@code ArgumentMultimap}. + */ + private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); + } + + /** + * Parses user input into an AddClientCommand. + * + * @param args User input arguments. + * @return An AddClientCommand to add a new client. + * @throws ParseException If the user input does not conform to the expected format. + */ + public AddClientCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, + PREFIX_ADDRESS, PREFIX_PROJECT, PREFIX_DATEJOINED, PREFIX_ROLE, PREFIX_SALARY, PREFIX_GITHUBID, + PREFIX_RATING, PREFIX_ORGANISATION, PREFIX_DOCUMENT, PREFIX_DESCRIPTION, PREFIX_DEADLINE); + + + if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_ADDRESS, PREFIX_PHONE, PREFIX_EMAIL, + PREFIX_ROLE, PREFIX_ORGANISATION, PREFIX_DOCUMENT) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddClientCommand.MESSAGE_USAGE)); + } + + for (Prefix p : Client.UNUSED_PREFIXES) { + if (argMultimap.getValue(p).isPresent()) { + throw new ParseException(String.format(Messages.MESSAGE_INAPPLICABLE_PREFIX_USED, + AddClientCommand.MESSAGE_USAGE)); + } + } + + argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS); + 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()); + ClientRoles role = ParserUtil.parseClientRole(argMultimap.getValue(PREFIX_ROLE).get()); + Set projectList = new HashSet<>(argMultimap.getAllValues(PREFIX_PROJECT)); + Name organisation = ParserUtil.parseName(argMultimap.getValue(PREFIX_ORGANISATION).get()); + Document document = ParserUtil.parseDocument(argMultimap.getValue(PREFIX_DOCUMENT).get()); + + Client client = new Client(name, phone, email, address, role, projectList, organisation, document); + + return new AddClientCommand(client); + } +} diff --git a/src/main/java/seedu/address/logic/parser/add/AddDeveloperCommandParser.java b/src/main/java/seedu/address/logic/parser/add/AddDeveloperCommandParser.java new file mode 100644 index 00000000000..9cbb7c3bb1f --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/add/AddDeveloperCommandParser.java @@ -0,0 +1,98 @@ +package seedu.address.logic.parser.add; + +import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DATEJOINED; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DEADLINE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DESCRIPTION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DOCUMENT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; +import static seedu.address.logic.parser.CliSyntax.PREFIX_GITHUBID; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ORGANISATION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PROJECT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_RATING; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ROLE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_SALARY; + +import java.text.SimpleDateFormat; +import java.util.Set; +import java.util.stream.Stream; + +import seedu.address.logic.Messages; +import seedu.address.logic.commands.add.AddDeveloperCommand; +import seedu.address.logic.parser.ArgumentMultimap; +import seedu.address.logic.parser.ArgumentTokenizer; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.ParserUtil; +import seedu.address.logic.parser.Prefix; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.commons.Date; +import seedu.address.model.commons.Name; +import seedu.address.model.developer.Developer; +import seedu.address.model.developer.DeveloperRoles; +import seedu.address.model.developer.GithubId; +import seedu.address.model.developer.Rating; +import seedu.address.model.developer.Salary; +import seedu.address.model.person.Address; +import seedu.address.model.person.Email; +import seedu.address.model.person.Phone; + +/** + * Parses input arguments and creates a new AddDeveloperCommand object + */ +public class AddDeveloperCommandParser implements Parser { + + /** + * Returns true if none of the prefixes contains empty {@code Optional} values in the given + * {@code ArgumentMultimap}. + */ + private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); + } + + /** + * Parses the given {@code String} of arguments in the context of the AddDeveloperCommand + * and returns an AddDeveloperCommand object for execution. + * + * @throws ParseException if the user input does not conform the expected format + */ + public AddDeveloperCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, + PREFIX_ADDRESS, PREFIX_PROJECT, PREFIX_DATEJOINED, PREFIX_ROLE, PREFIX_SALARY, PREFIX_GITHUBID, + PREFIX_RATING, PREFIX_ORGANISATION, PREFIX_DOCUMENT, PREFIX_DESCRIPTION, PREFIX_DEADLINE); + + if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_ADDRESS, PREFIX_PHONE, PREFIX_EMAIL, + PREFIX_SALARY, PREFIX_ROLE) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddDeveloperCommand.MESSAGE_USAGE)); + } + + for (Prefix p : Developer.UNUSED_PREFIXES) { + if (argMultimap.getValue(p).isPresent()) { + throw new ParseException(String.format(Messages.MESSAGE_INAPPLICABLE_PREFIX_USED, + AddDeveloperCommand.MESSAGE_USAGE)); + } + } + + argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS); + 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()); + Date dateJoined = ParserUtil.parseDateJoined(argMultimap.getValue(PREFIX_DATEJOINED) + .orElse(new SimpleDateFormat("dd-MM-yyyy").format(new java.util.Date()))); + DeveloperRoles role = ParserUtil.parseDeveloperRole(argMultimap.getValue(PREFIX_ROLE).get()); + Salary salary = ParserUtil.parseSalary(argMultimap.getValue(PREFIX_SALARY).get()); + Set projectList = ParserUtil.parseProjectsToSet(argMultimap.getAllValues(PREFIX_PROJECT)); + GithubId githubId = ParserUtil.parseGithubId(argMultimap.getValue(PREFIX_GITHUBID).orElse("")); + Rating rating = ParserUtil.parseRating(argMultimap.getValue(PREFIX_RATING).orElse("0")); + + Developer developer = new Developer(name, phone, email, address, role, projectList, salary, + dateJoined, githubId, rating); + + return new AddDeveloperCommand(developer); + } + +} diff --git a/src/main/java/seedu/address/logic/parser/add/AddProjectCommandParser.java b/src/main/java/seedu/address/logic/parser/add/AddProjectCommandParser.java new file mode 100644 index 00000000000..68f4e817ac9 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/add/AddProjectCommandParser.java @@ -0,0 +1,81 @@ +package seedu.address.logic.parser.add; + +import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DATEJOINED; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DEADLINE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DESCRIPTION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DOCUMENT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; +import static seedu.address.logic.parser.CliSyntax.PREFIX_GITHUBID; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ORGANISATION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PROJECT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_RATING; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ROLE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_SALARY; + +import java.util.List; +import java.util.stream.Stream; + +import seedu.address.logic.Messages; +import seedu.address.logic.commands.add.AddProjectCommand; +import seedu.address.logic.parser.ArgumentMultimap; +import seedu.address.logic.parser.ArgumentTokenizer; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.ParserUtil; +import seedu.address.logic.parser.Prefix; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.commons.Name; +import seedu.address.model.project.Deadline; +import seedu.address.model.project.Description; +import seedu.address.model.project.Project; + +/** + * Parses input arguments and creates a new AddProjectCommand object + */ +public class AddProjectCommandParser implements Parser { + + /** + * Returns true if none of the prefixes contains empty {@code Optional} values in the given + * {@code ArgumentMultimap}. + */ + private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); + } + + /** + * Parses user input into an AddProjectCommand. + * + * @param args User input arguments. + * @return An AddProjectCommand to add a new project. + * @throws ParseException If the user input does not conform to the expected format. + */ + public AddProjectCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, + PREFIX_ADDRESS, PREFIX_PROJECT, PREFIX_DATEJOINED, PREFIX_ROLE, PREFIX_SALARY, PREFIX_GITHUBID, + PREFIX_RATING, PREFIX_ORGANISATION, PREFIX_DOCUMENT, PREFIX_DESCRIPTION, PREFIX_DEADLINE); + + if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_DESCRIPTION) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddProjectCommand.MESSAGE_USAGE)); + } + + for (Prefix p : Project.UNUSED_PREFIXES) { + if (argMultimap.getValue(p).isPresent()) { + throw new ParseException(String.format(Messages.MESSAGE_INAPPLICABLE_PREFIX_USED, + AddProjectCommand.MESSAGE_USAGE)); + } + } + + argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_NAME, PREFIX_DESCRIPTION); + Name name = ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get()); + Description desc = ParserUtil.parseDescription(argMultimap.getValue(PREFIX_DESCRIPTION).get()); + List deadlineList = argMultimap.getAllValues(PREFIX_DEADLINE); + List deadlines = ParserUtil.parseDeadlines(deadlineList); + + Project project = new Project(name, desc, deadlines); + return new AddProjectCommand(project); + } +} diff --git a/src/main/java/seedu/address/logic/parser/addroles/AddClientRoleCommandParser.java b/src/main/java/seedu/address/logic/parser/addroles/AddClientRoleCommandParser.java new file mode 100644 index 00000000000..fa736202d78 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/addroles/AddClientRoleCommandParser.java @@ -0,0 +1,30 @@ +package seedu.address.logic.parser.addroles; + +import static java.util.Objects.requireNonNull; + +import seedu.address.logic.commands.addroles.AddClientRoleCommand; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Parses user input to create an AddClientRoleCommand. + */ +public class AddClientRoleCommandParser implements Parser { + /** + * Parses the given {@code args} and returns an AddClientRoleCommand. + * + * @param args User input arguments. + * @return An AddClientRoleCommand for adding a client role. + * @throws ParseException If the user input does not conform to the expected format. + */ + @Override + public AddClientRoleCommand parse(String args) throws ParseException { + requireNonNull(args); + if (!args.isBlank()) { + String role = args.substring(1).trim(); + return new AddClientRoleCommand(role); + } else { + throw new ParseException("Role cannot be empty!"); + } + } +} diff --git a/src/main/java/seedu/address/logic/parser/addroles/AddDeveloperRoleCommandParser.java b/src/main/java/seedu/address/logic/parser/addroles/AddDeveloperRoleCommandParser.java new file mode 100644 index 00000000000..5eb1f537a86 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/addroles/AddDeveloperRoleCommandParser.java @@ -0,0 +1,30 @@ +package seedu.address.logic.parser.addroles; + +import static java.util.Objects.requireNonNull; + +import seedu.address.logic.commands.addroles.AddDeveloperRoleCommand; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Parses user input to create an AddDeveloperRoleCommand. + */ +public class AddDeveloperRoleCommandParser implements Parser { + /** + * Parses the given {@code args} and returns an AddDeveloperRoleCommand. + * + * @param args User input arguments. + * @return An AddDeveloperRoleCommand for adding a developer role. + * @throws ParseException If the user input does not conform to the expected format. + */ + @Override + public AddDeveloperRoleCommand parse(String args) throws ParseException { + requireNonNull(args); + if (!args.isBlank()) { + String role = args.substring(1).trim(); + return new AddDeveloperRoleCommand(role); + } else { + throw new ParseException("Role cannot be empty!"); + } + } +} diff --git a/src/main/java/seedu/address/logic/parser/delete/DeleteClientCommandParser.java b/src/main/java/seedu/address/logic/parser/delete/DeleteClientCommandParser.java new file mode 100644 index 00000000000..beb710e7fd7 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/delete/DeleteClientCommandParser.java @@ -0,0 +1,32 @@ +package seedu.address.logic.parser.delete; + +import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.delete.DeleteClientCommand; +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 DeleteDeveloperCommand object + */ +public class DeleteClientCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the DeleteClientCommand + * and returns a DeleteClientCommand object for execution. + * + * @throws ParseException if the user input does not conform the expected format + */ + public DeleteClientCommand parse(String args) throws ParseException { + try { + Index index = ParserUtil.parseIndex(args); + return new DeleteClientCommand(index); + } catch (ParseException pe) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteClientCommand.MESSAGE_USAGE), pe); + } + } + +} diff --git a/src/main/java/seedu/address/logic/parser/delete/DeleteDeveloperCommandParser.java b/src/main/java/seedu/address/logic/parser/delete/DeleteDeveloperCommandParser.java new file mode 100644 index 00000000000..1c4f732e626 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/delete/DeleteDeveloperCommandParser.java @@ -0,0 +1,32 @@ +package seedu.address.logic.parser.delete; + +import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.delete.DeleteDeveloperCommand; +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 DeleteDeveloperCommand object + */ +public class DeleteDeveloperCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the DeleteDeveloperCommand + * and returns a DeleteDeveloperCommand object for execution. + * + * @throws ParseException if the user input does not conform the expected format + */ + public DeleteDeveloperCommand parse(String args) throws ParseException { + try { + Index index = ParserUtil.parseIndex(args); + return new DeleteDeveloperCommand(index); + } catch (ParseException pe) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteDeveloperCommand.MESSAGE_USAGE), pe); + } + } + +} diff --git a/src/main/java/seedu/address/logic/parser/delete/DeleteProjectCommandParser.java b/src/main/java/seedu/address/logic/parser/delete/DeleteProjectCommandParser.java new file mode 100644 index 00000000000..cc149c00105 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/delete/DeleteProjectCommandParser.java @@ -0,0 +1,32 @@ +package seedu.address.logic.parser.delete; + +import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.delete.DeleteProjectCommand; +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 DeleteDeveloperCommand object + */ +public class DeleteProjectCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the DeleteDeveloperCommand + * and returns a DeleteDeveloperCommand object for execution. + * + * @throws ParseException if the user input does not conform the expected format + */ + public DeleteProjectCommand parse(String args) throws ParseException { + try { + Index index = ParserUtil.parseIndex(args); + return new DeleteProjectCommand(index); + } catch (ParseException pe) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteProjectCommand.MESSAGE_USAGE), pe); + } + } + +} diff --git a/src/main/java/seedu/address/logic/parser/deleteroles/DeleteClientRoleCommandParser.java b/src/main/java/seedu/address/logic/parser/deleteroles/DeleteClientRoleCommandParser.java new file mode 100644 index 00000000000..49441a2ca8d --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/deleteroles/DeleteClientRoleCommandParser.java @@ -0,0 +1,32 @@ +package seedu.address.logic.parser.deleteroles; + +import static java.util.Objects.requireNonNull; + +import seedu.address.logic.commands.deleteroles.DeleteClientRoleCommand; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Parser to handle user input and create a {@link DeleteClientRoleCommand}. + * This command is used to delete a client role by providing the role name as an argument. + * The role name should be a single word that follows the command. + */ +public class DeleteClientRoleCommandParser implements Parser { + /** + * Parses user input into a DeleteClientRoleCommand. + * + * @param args User input arguments. + * @return A DeleteClientRoleCommand to delete a client role. + * @throws ParseException If the user input does not conform to the expected format. + */ + @Override + public DeleteClientRoleCommand parse(String args) throws ParseException { + requireNonNull(args); + if (!args.isBlank()) { + String role = args.substring(1).trim(); + return new DeleteClientRoleCommand(role); + } else { + throw new ParseException("Role cannot be empty!"); + } + } +} diff --git a/src/main/java/seedu/address/logic/parser/deleteroles/DeleteDeveloperRoleCommandParser.java b/src/main/java/seedu/address/logic/parser/deleteroles/DeleteDeveloperRoleCommandParser.java new file mode 100644 index 00000000000..20689a63f5c --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/deleteroles/DeleteDeveloperRoleCommandParser.java @@ -0,0 +1,32 @@ +package seedu.address.logic.parser.deleteroles; + +import static java.util.Objects.requireNonNull; + +import seedu.address.logic.commands.deleteroles.DeleteDeveloperRoleCommand; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Parser to handle user input and create a {@link DeleteDeveloperRoleCommand}. + * This command is used to delete a developer role by providing the role name as an argument. + * The role name should be a single word that follows the command. + */ +public class DeleteDeveloperRoleCommandParser implements Parser { + /** + * Parses user input and creates a {@link DeleteDeveloperRoleCommand} based on the provided role name. + * + * @param args User input arguments. + * @return A {@link DeleteDeveloperRoleCommand} for deleting a developer role. + * @throws ParseException If the user input does not conform to the expected format. + */ + @Override + public DeleteDeveloperRoleCommand parse(String args) throws ParseException { + requireNonNull(args); + if (!args.isBlank()) { + String role = args.substring(1).trim(); + return new DeleteDeveloperRoleCommand(role); + } else { + throw new ParseException("Role cannot be empty!"); + } + } +} diff --git a/src/main/java/seedu/address/logic/parser/edit/EditClientCommandParser.java b/src/main/java/seedu/address/logic/parser/edit/EditClientCommandParser.java new file mode 100644 index 00000000000..ea916c1e94c --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/edit/EditClientCommandParser.java @@ -0,0 +1,124 @@ +package seedu.address.logic.parser.edit; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DATEJOINED; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DEADLINE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DESCRIPTION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DOCUMENT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; +import static seedu.address.logic.parser.CliSyntax.PREFIX_GITHUBID; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ORGANISATION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PROJECT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_RATING; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ROLE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_SALARY; + +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.Messages; +import seedu.address.logic.commands.edit.EditClientCommand; +import seedu.address.logic.parser.ArgumentMultimap; +import seedu.address.logic.parser.ArgumentTokenizer; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.ParserUtil; +import seedu.address.logic.parser.Prefix; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.client.Client; + +/** + * Parses input arguments and creates a new EditClientCommand object + */ +public class EditClientCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the EditClientCommand + * and returns an EditClientCommand object for execution. + * + * @throws ParseException if the user input does not conform to the expected format + */ + + public EditClientCommand parse(String args) throws ParseException { + requireNonNull(args); + ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, + PREFIX_ADDRESS, PREFIX_PROJECT, PREFIX_DATEJOINED, PREFIX_ROLE, PREFIX_SALARY, PREFIX_GITHUBID, + PREFIX_RATING, PREFIX_ORGANISATION, PREFIX_DOCUMENT, PREFIX_DESCRIPTION, PREFIX_DEADLINE); + + Index index; + + try { + index = ParserUtil.parseIndex(argMultimap.getPreamble()); + } catch (ParseException pe) { + throw new ParseException(String.format(Messages.MESSAGE_INVALID_COMMAND_FORMAT, + EditClientCommand.MESSAGE_USAGE), pe); + } + + if (!argMultimap.hasMappings()) { + throw new ParseException(EditClientCommand.MESSAGE_NOT_EDITED); + } + + for (Prefix p : Client.UNUSED_PREFIXES) { + if (argMultimap.getValue(p).isPresent()) { + throw new ParseException(String.format(Messages.MESSAGE_INAPPLICABLE_PREFIX_USED, + EditClientCommand.MESSAGE_USAGE)); + } + } + + argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, + PREFIX_ORGANISATION, PREFIX_DOCUMENT, PREFIX_ROLE); + + EditClientCommand.EditClientDescriptor editClientDescriptor = new EditClientCommand.EditClientDescriptor(); + + if (argMultimap.getValue(PREFIX_NAME).isPresent()) { + editClientDescriptor.setName(ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get())); + } + if (argMultimap.getValue(PREFIX_PHONE).isPresent()) { + editClientDescriptor.setPhone(ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE).get())); + } + if (argMultimap.getValue(PREFIX_EMAIL).isPresent()) { + editClientDescriptor.setEmail(ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL).get())); + } + if (argMultimap.getValue(PREFIX_ADDRESS).isPresent()) { + editClientDescriptor.setAddress(ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS).get())); + } + if (argMultimap.getValue(PREFIX_ROLE).isPresent()) { + editClientDescriptor.setRole(ParserUtil.parseClientRole(argMultimap.getValue(PREFIX_ROLE).get())); + } + if (argMultimap.getValue(PREFIX_ORGANISATION).isPresent()) { + editClientDescriptor.setOrganisation(ParserUtil.parseName(argMultimap.getValue(PREFIX_ORGANISATION).get())); + } + if (argMultimap.getValue(PREFIX_DOCUMENT).isPresent()) { + editClientDescriptor.setDocument(ParserUtil.parseDocument(argMultimap.getValue(PREFIX_DOCUMENT).get())); + } + //editClientDescriptor.setProjects(ParserUtil.parseProjectsWithCheck(argMultimap.getAllValues(PREFIX_PROJECT))); + parseProjectsForEdit(argMultimap.getAllValues(PREFIX_PROJECT)).ifPresent(editClientDescriptor::setProjects); + + if (!editClientDescriptor.isAnyFieldEdited()) { + throw new ParseException(EditClientCommand.MESSAGE_NOT_EDITED); + } + + return new EditClientCommand(index, editClientDescriptor); + } + + /** + * Parses {@code Collection tags} into a {@code Set} if {@code projects} is non-empty. + * If {@code projects} contain only one element which is an empty string, it will be parsed into a + * {@code Set} containing zero tags. + */ + private Optional> parseProjectsForEdit(Collection projects) throws ParseException { + assert projects != null; + + if (projects.isEmpty()) { + return Optional.empty(); + } + Collection projectSet = + projects.size() == 1 && projects.contains("") ? Collections.emptySet() : projects; + return Optional.of(ParserUtil.parseProjectsToSet(projectSet)); + } +} diff --git a/src/main/java/seedu/address/logic/parser/edit/EditDeveloperCommandParser.java b/src/main/java/seedu/address/logic/parser/edit/EditDeveloperCommandParser.java new file mode 100644 index 00000000000..60a06b8a044 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/edit/EditDeveloperCommandParser.java @@ -0,0 +1,130 @@ +package seedu.address.logic.parser.edit; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DATEJOINED; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DEADLINE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DESCRIPTION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DOCUMENT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; +import static seedu.address.logic.parser.CliSyntax.PREFIX_GITHUBID; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ORGANISATION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PROJECT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_RATING; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ROLE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_SALARY; + +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.Messages; +import seedu.address.logic.commands.edit.EditDeveloperCommand; +import seedu.address.logic.parser.ArgumentMultimap; +import seedu.address.logic.parser.ArgumentTokenizer; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.ParserUtil; +import seedu.address.logic.parser.Prefix; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.developer.Developer; + +/** + * Parses input arguments and creates a new EditDeveloperCommand object + */ +public class EditDeveloperCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the EditDeveloperCommand + * and returns an EditDeveloperCommand object for execution. + * + * @throws ParseException if the user input does not conform to the expected format + */ + + public EditDeveloperCommand parse(String args) throws ParseException { + requireNonNull(args); + ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, + PREFIX_ADDRESS, PREFIX_PROJECT, PREFIX_DATEJOINED, PREFIX_ROLE, PREFIX_SALARY, PREFIX_GITHUBID, + PREFIX_RATING, PREFIX_ORGANISATION, PREFIX_DOCUMENT, PREFIX_DESCRIPTION, PREFIX_DEADLINE); + + Index index; + + try { + index = ParserUtil.parseIndex(argMultimap.getPreamble()); + } catch (ParseException pe) { + throw new ParseException(String.format(Messages.MESSAGE_INVALID_COMMAND_FORMAT, + EditDeveloperCommand.MESSAGE_USAGE), pe); + } + + if (!argMultimap.hasMappings()) { + throw new ParseException(EditDeveloperCommand.MESSAGE_NOT_EDITED); + } + for (Prefix p : Developer.UNUSED_PREFIXES) { + if (argMultimap.getValue(p).isPresent()) { + throw new ParseException(String.format(Messages.MESSAGE_INAPPLICABLE_PREFIX_USED, + EditDeveloperCommand.MESSAGE_USAGE)); + } + } + + argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, + PREFIX_DATEJOINED, PREFIX_ROLE, PREFIX_SALARY, PREFIX_GITHUBID, PREFIX_RATING); + + EditDeveloperCommand.EditDeveloperDescriptor editDeveloperDescriptor = + new EditDeveloperCommand.EditDeveloperDescriptor(); + + if (argMultimap.getValue(PREFIX_NAME).isPresent()) { + editDeveloperDescriptor.setName(ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get())); + } + if (argMultimap.getValue(PREFIX_PHONE).isPresent()) { + editDeveloperDescriptor.setPhone(ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE).get())); + } + if (argMultimap.getValue(PREFIX_EMAIL).isPresent()) { + editDeveloperDescriptor.setEmail(ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL).get())); + } + if (argMultimap.getValue(PREFIX_ADDRESS).isPresent()) { + editDeveloperDescriptor.setAddress(ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS).get())); + } + if (argMultimap.getValue(PREFIX_DATEJOINED).isPresent()) { + editDeveloperDescriptor + .setDateJoined(ParserUtil.parseDateJoined(argMultimap.getValue(PREFIX_DATEJOINED).get())); + } + if (argMultimap.getValue(PREFIX_ROLE).isPresent()) { + editDeveloperDescriptor.setRole(ParserUtil.parseDeveloperRole(argMultimap.getValue(PREFIX_ROLE).get())); + } + if (argMultimap.getValue(PREFIX_SALARY).isPresent()) { + editDeveloperDescriptor.setSalary(ParserUtil.parseSalary(argMultimap.getValue(PREFIX_SALARY).get())); + } + if (argMultimap.getValue(PREFIX_GITHUBID).isPresent()) { + editDeveloperDescriptor.setGithubId(ParserUtil.parseGithubId(argMultimap.getValue(PREFIX_GITHUBID).get())); + } + if (argMultimap.getValue(PREFIX_RATING).isPresent()) { + editDeveloperDescriptor.setRating(ParserUtil.parseRating(argMultimap.getValue(PREFIX_RATING).get())); + } + parseProjectsForEdit(argMultimap.getAllValues(PREFIX_PROJECT)).ifPresent(editDeveloperDescriptor::setProjects); + + if (!editDeveloperDescriptor.isAnyFieldEdited()) { + throw new ParseException(EditDeveloperCommand.MESSAGE_NOT_EDITED); + } + + return new EditDeveloperCommand(index, editDeveloperDescriptor); + } + + /** + * Parses {@code Collection tags} into a {@code Set} if {@code projects} is non-empty. + * If {@code projects} contain only one element which is an empty string, it will be parsed into a + * {@code Set} containing zero tags. + */ + private Optional> parseProjectsForEdit(Collection projects) throws ParseException { + assert projects != null; + + if (projects.isEmpty()) { + return Optional.empty(); + } + Collection projectSet = + projects.size() == 1 && projects.contains("") ? Collections.emptySet() : projects; + return Optional.of(ParserUtil.parseProjectsToSet(projectSet)); + } +} diff --git a/src/main/java/seedu/address/logic/parser/edit/EditProjectCommandParser.java b/src/main/java/seedu/address/logic/parser/edit/EditProjectCommandParser.java new file mode 100644 index 00000000000..964ed18eff1 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/edit/EditProjectCommandParser.java @@ -0,0 +1,106 @@ +package seedu.address.logic.parser.edit; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DATEJOINED; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DEADLINE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DESCRIPTION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DOCUMENT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; +import static seedu.address.logic.parser.CliSyntax.PREFIX_GITHUBID; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ORGANISATION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PROJECT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_RATING; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ROLE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_SALARY; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.Messages; +import seedu.address.logic.commands.edit.EditProjectCommand; +import seedu.address.logic.parser.ArgumentMultimap; +import seedu.address.logic.parser.ArgumentTokenizer; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.ParserUtil; +import seedu.address.logic.parser.Prefix; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.project.Deadline; +import seedu.address.model.project.Project; + +/** + * Parses input arguments and creates a new EditProjectCommand object + */ +public class EditProjectCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the EditProjectCommand + * and returns an EditProjectCommand object for execution. + * + * @throws ParseException if the user input does not conform to the expected format + */ + + public EditProjectCommand parse(String args) throws ParseException { + requireNonNull(args); + ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, + PREFIX_ADDRESS, PREFIX_PROJECT, PREFIX_DATEJOINED, PREFIX_ROLE, PREFIX_SALARY, PREFIX_GITHUBID, + PREFIX_RATING, PREFIX_ORGANISATION, PREFIX_DOCUMENT, PREFIX_DESCRIPTION, PREFIX_DEADLINE); + + Index index; + + try { + index = ParserUtil.parseIndex(argMultimap.getPreamble()); + } catch (ParseException pe) { + throw new ParseException(String.format(Messages.MESSAGE_INVALID_COMMAND_FORMAT, + EditProjectCommand.MESSAGE_USAGE), pe); + } + + if (!argMultimap.hasMappings()) { + throw new ParseException(EditProjectCommand.MESSAGE_NOT_EDITED); + } + + for (Prefix p : Project.UNUSED_PREFIXES_FOR_EDIT) { + if (argMultimap.getValue(p).isPresent()) { + throw new ParseException(String.format(Messages.MESSAGE_INAPPLICABLE_PREFIX_USED, + EditProjectCommand.MESSAGE_USAGE)); + } + } + + argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_DESCRIPTION); + + EditProjectCommand.EditProjectDescriptor editProjectDescriptor = new EditProjectCommand.EditProjectDescriptor(); + + if (argMultimap.getValue(PREFIX_DESCRIPTION).isPresent()) { + editProjectDescriptor + .setDescription(ParserUtil.parseDescription(argMultimap.getValue(PREFIX_DESCRIPTION).get())); + } + parseDeadlinesForEdit(argMultimap.getAllValues(PREFIX_DEADLINE)).ifPresent(editProjectDescriptor::setDeadlines); + + if (!editProjectDescriptor.isAnyFieldEdited()) { + throw new ParseException(EditProjectCommand.MESSAGE_NOT_EDITED); + } + + return new EditProjectCommand(index, editProjectDescriptor); + } + + /** + * Parses {@code Collection tags} into a {@code List} if {@code deadlines} is non-empty. + * If {@code deadlines} contain only one element which is an empty string, it will be parsed into a + * {@code List} containing zero tags. + */ + private Optional> parseDeadlinesForEdit(Collection deadlines) throws ParseException { + assert deadlines != null; + + if (deadlines.isEmpty()) { + return Optional.empty(); + } + Collection deadlineSet = + deadlines.size() == 1 && deadlines.contains("") ? Collections.emptySet() : deadlines; + return Optional.of(ParserUtil.parseDeadlines(deadlineSet)); + } +} diff --git a/src/main/java/seedu/address/logic/parser/find/FindClientCommandParser.java b/src/main/java/seedu/address/logic/parser/find/FindClientCommandParser.java new file mode 100644 index 00000000000..4929098e1a8 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/find/FindClientCommandParser.java @@ -0,0 +1,117 @@ +package seedu.address.logic.parser.find; + +import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DOCUMENT; +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_ORGANISATION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PROJECT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ROLE; + +import java.util.Arrays; +import java.util.function.Predicate; + +import seedu.address.logic.commands.find.FindClientCommand; +import seedu.address.logic.parser.ArgumentMultimap; +import seedu.address.logic.parser.ArgumentTokenizer; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.Prefix; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.client.AddressClientContainsKeywordsPredicate; +import seedu.address.model.client.Client; +import seedu.address.model.client.DocumentContainsKeywordsPredicate; +import seedu.address.model.client.EmailClientContainsKeywordsPredicate; +import seedu.address.model.client.NameClientContainsKeywordsPredicate; +import seedu.address.model.client.OrganisationContainsKeywordsPredicate; +import seedu.address.model.client.PhoneClientContainsKeywordsPredicate; +import seedu.address.model.client.ProjectClientContainsKeywordsPredicate; +import seedu.address.model.client.RoleClientContainsKeywordsPredicate; + +/** + * Parses input arguments and creates a new FindClientCommand object + */ +public class FindClientCommandParser implements Parser { + /** + * Parses the given {@code String} of arguments in the context of the FindClientCommand + * and returns a FindClientCommand object for execution. + * + * @throws ParseException if the user input does not conform the expected format + */ + public FindClientCommand parse(String args) throws ParseException { + String trimmedArgs = args.trim(); + if (trimmedArgs.isEmpty()) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindClientCommand.MESSAGE_USAGE)); + } + + ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_ROLE, PREFIX_ADDRESS, + PREFIX_EMAIL, PREFIX_PHONE, PREFIX_PROJECT, PREFIX_DOCUMENT, PREFIX_ORGANISATION); + + for (Prefix prefix : Arrays.asList(PREFIX_NAME, PREFIX_ROLE, PREFIX_ADDRESS, + PREFIX_EMAIL, PREFIX_PHONE, PREFIX_PROJECT, PREFIX_DOCUMENT, PREFIX_ORGANISATION)) { + if (argMultimap.getValue(prefix).isPresent() && argMultimap.getValue(prefix).get().isEmpty()) { + throw new ParseException("Please input a value after the prefix."); + } + } + + if (!argMultimap.getPreamble().isEmpty()) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindClientCommand.MESSAGE_USAGE)); + } + + Predicate finalPredicate = buildPredicate(argMultimap); + + return new FindClientCommand(finalPredicate); + } + + private Predicate buildPredicate(ArgumentMultimap argMultimap) { + Predicate finalPredicate = client -> true; + + if (argMultimap.getValue(PREFIX_NAME).isPresent()) { + String[] nameKeywords = argMultimap.getValue(PREFIX_NAME).get().split("\\s+"); + finalPredicate = finalPredicate.and(new NameClientContainsKeywordsPredicate(Arrays.asList(nameKeywords))); + } + + if (argMultimap.getValue(PREFIX_ROLE).isPresent()) { + String[] roleKeywords = argMultimap.getValue(PREFIX_ROLE).get().split("\\s+"); + finalPredicate = finalPredicate.and(new RoleClientContainsKeywordsPredicate(Arrays.asList(roleKeywords))); + } + + if (argMultimap.getValue(PREFIX_ADDRESS).isPresent()) { + String[] addressKeywords = argMultimap.getValue(PREFIX_ADDRESS).get().split("\\s+"); + finalPredicate = + finalPredicate.and(new AddressClientContainsKeywordsPredicate(Arrays.asList(addressKeywords))); + } + + if (argMultimap.getValue(PREFIX_EMAIL).isPresent()) { + String[] emailKeywords = argMultimap.getValue(PREFIX_EMAIL).get().split("\\s+"); + finalPredicate = finalPredicate.and(new EmailClientContainsKeywordsPredicate(Arrays.asList(emailKeywords))); + } + + if (argMultimap.getValue(PREFIX_PHONE).isPresent()) { + String[] phoneKeywords = argMultimap.getValue(PREFIX_PHONE).get().split("\\s+"); + finalPredicate = finalPredicate.and(new PhoneClientContainsKeywordsPredicate(Arrays.asList(phoneKeywords))); + } + + if (argMultimap.getValue(PREFIX_PROJECT).isPresent()) { + String[] projectKeywords = argMultimap.getValue(PREFIX_PROJECT).get().split("\\s+"); + finalPredicate = + finalPredicate.and(new ProjectClientContainsKeywordsPredicate(Arrays.asList(projectKeywords))); + } + + if (argMultimap.getValue(PREFIX_DOCUMENT).isPresent()) { + String[] documentKeywords = argMultimap.getValue(PREFIX_DOCUMENT).get().split("\\s+"); + finalPredicate = + finalPredicate.and(new DocumentContainsKeywordsPredicate(Arrays.asList(documentKeywords))); + } + + if (argMultimap.getValue(PREFIX_ORGANISATION).isPresent()) { + String[] organisationKeywords = argMultimap.getValue(PREFIX_ORGANISATION).get().split("\\s+"); + finalPredicate = + finalPredicate.and(new OrganisationContainsKeywordsPredicate(Arrays.asList(organisationKeywords))); + } + return finalPredicate; + } +} diff --git a/src/main/java/seedu/address/logic/parser/find/FindDeadlineCommandParser.java b/src/main/java/seedu/address/logic/parser/find/FindDeadlineCommandParser.java new file mode 100644 index 00000000000..c54ff932e0a --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/find/FindDeadlineCommandParser.java @@ -0,0 +1,85 @@ +package seedu.address.logic.parser.find; + +import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DATEJOINED; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PRIORITY; + +import java.util.Arrays; +import java.util.function.Predicate; + +import seedu.address.logic.commands.find.FindDeadlineCommand; +import seedu.address.logic.parser.ArgumentMultimap; +import seedu.address.logic.parser.ArgumentTokenizer; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.ParserUtil; +import seedu.address.logic.parser.Prefix; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.commons.Date; +import seedu.address.model.project.Deadline; +import seedu.address.model.project.Priority; + +/** + * Parser to handle user input and create a {@link FindDeadlineCommand}. + * This command is used to search and filter deadlines within projects based on specific criteria. + * The supported criteria include filtering by date joined and priority level. + */ +public class FindDeadlineCommandParser implements Parser { + /** + * Parses user input and creates a {@link FindDeadlineCommand} based on the provided criteria. + * + * @param args User input arguments, which may include criteria for filtering deadlines. + * @return A {@link FindDeadlineCommand} for searching and filtering deadlines. + * @throws ParseException If the user input does not conform to the expected format. + */ + public FindDeadlineCommand parse(String args) throws ParseException { + String trimmedArgs = args.trim(); + if (trimmedArgs.isEmpty()) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindDeadlineCommand.MESSAGE_USAGE)); + } + + ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_DATEJOINED, PREFIX_PRIORITY); + + for (Prefix prefix : Arrays.asList(PREFIX_DATEJOINED, PREFIX_PRIORITY)) { + if (argMultimap.getValue(prefix).isPresent() && argMultimap.getValue(prefix).get().isEmpty()) { + throw new ParseException("Please input a value after the prefix."); + } + } + + if (!argMultimap.getPreamble().isEmpty()) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindDeadlineCommand.MESSAGE_USAGE)); + } + + Predicate finalPredicate = buildPredicate(argMultimap); + + return new FindDeadlineCommand(finalPredicate); + } + + /** + * Builds a predicate for filtering deadlines based on the criteria provided in the user input. + * + * @param argMultimap Argument multimap containing user input arguments. + * @return A predicate for filtering deadlines. + */ + private Predicate buildPredicate(ArgumentMultimap argMultimap) throws ParseException { + Predicate finalPredicate = deadline -> true; + + if (argMultimap.getValue(PREFIX_DATEJOINED).isPresent()) { + String dateKeywords = argMultimap.getValue(PREFIX_DATEJOINED).get(); + Date input = ParserUtil.parseDateDeadline(dateKeywords); + finalPredicate = + finalPredicate.and(d -> !d.getDate().value.after(input.value)); + // Replace with your DateJoinedPredicate + } + + if (argMultimap.getValue(PREFIX_PRIORITY).isPresent()) { + String priorityKeywords = argMultimap.getValue(PREFIX_PRIORITY).get(); + Priority input = ParserUtil.parsePriority(priorityKeywords); + finalPredicate = finalPredicate.and(d -> d.getPriority().equals(input)); + // Replace with your PriorityPredicate + } + + return finalPredicate; + } +} diff --git a/src/main/java/seedu/address/logic/parser/find/FindDeveloperCommandParser.java b/src/main/java/seedu/address/logic/parser/find/FindDeveloperCommandParser.java new file mode 100644 index 00000000000..d3f325c40a1 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/find/FindDeveloperCommandParser.java @@ -0,0 +1,150 @@ +package seedu.address.logic.parser.find; + +import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DATEJOINED; +import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; +import static seedu.address.logic.parser.CliSyntax.PREFIX_GITHUBID; +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_PROJECT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_RATING; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ROLE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_SALARY; + +import java.util.Arrays; +import java.util.function.Predicate; + +import seedu.address.logic.commands.find.FindDeveloperCommand; +import seedu.address.logic.parser.ArgumentMultimap; +import seedu.address.logic.parser.ArgumentTokenizer; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.Prefix; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.developer.AddressDeveloperContainsKeywordsPredicate; +import seedu.address.model.developer.DateJoinedContainsKeywordsPredicate; +import seedu.address.model.developer.Developer; +import seedu.address.model.developer.EmailDeveloperContainsKeywordsPredicate; +import seedu.address.model.developer.GithubIdContainsKeywordsPredicate; +import seedu.address.model.developer.NameDeveloperContainsKeywordsPredicate; +import seedu.address.model.developer.PhoneDeveloperContainsKeywordsPredicate; +import seedu.address.model.developer.ProjectDeveloperContainsKeywordsPredicate; +import seedu.address.model.developer.RatingContainsKeywordsPredicate; +import seedu.address.model.developer.RoleDeveloperContainsKeywordsPredicate; +import seedu.address.model.developer.SalaryContainsKeywordsPredicate; + +/** + * Parser to handle user input and create a {@link FindDeveloperCommand}. + * This command is used to search and filter developers based on specific criteria. + * The supported criteria include filtering by name, role, address, email, GitHub ID, date joined, + * projects, phone number, salary, rating, and GitHub ID. + */ +public class FindDeveloperCommandParser implements Parser { + /** + * Parses user input and creates a {@link FindDeveloperCommand} based on the provided criteria. + * + * @param args User input arguments, which may include criteria for filtering developers. + * @return A {@link FindDeveloperCommand} for searching and filtering developers. + * @throws ParseException If the user input does not conform to the expected format. + */ + public FindDeveloperCommand parse(String args) throws ParseException { + String trimmedArgs = args.trim(); + if (trimmedArgs.isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + FindDeveloperCommand.MESSAGE_USAGE)); + } + + ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_ROLE, PREFIX_ADDRESS, + PREFIX_EMAIL, PREFIX_GITHUBID, PREFIX_DATEJOINED, + PREFIX_PROJECT, PREFIX_PHONE, PREFIX_SALARY, PREFIX_RATING); + + for (Prefix prefix : Arrays.asList(PREFIX_NAME, PREFIX_ROLE, PREFIX_ADDRESS, + PREFIX_EMAIL, PREFIX_GITHUBID, PREFIX_DATEJOINED, + PREFIX_PROJECT, PREFIX_PHONE, PREFIX_SALARY, PREFIX_RATING)) { + if (argMultimap.getValue(prefix).isPresent() && argMultimap.getValue(prefix).get().isEmpty()) { + throw new ParseException("Please input a value after the prefix."); + } + } + + if (!argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + FindDeveloperCommand.MESSAGE_USAGE)); + } + + Predicate finalPredicate = buildPredicate(argMultimap); + + return new FindDeveloperCommand(finalPredicate); + } + + /** + * Builds a predicate for filtering developers based on the criteria provided in the user input. + * + * @param argMultimap Argument multimap containing user input arguments. + * @return A predicate for filtering developers. + */ + private Predicate buildPredicate(ArgumentMultimap argMultimap) { + Predicate finalPredicate = developer -> true; + + if (argMultimap.getValue(PREFIX_NAME).isPresent()) { + String[] nameKeywords = argMultimap.getValue(PREFIX_NAME).get().split("\\s+"); + finalPredicate = + finalPredicate.and(new NameDeveloperContainsKeywordsPredicate(Arrays.asList(nameKeywords))); + } + + if (argMultimap.getValue(PREFIX_ROLE).isPresent()) { + String[] roleKeywords = argMultimap.getValue(PREFIX_ROLE).get().split("\\s+"); + finalPredicate = + finalPredicate.and(new RoleDeveloperContainsKeywordsPredicate(Arrays.asList(roleKeywords))); + } + + if (argMultimap.getValue(PREFIX_ADDRESS).isPresent()) { + String[] addressKeywords = argMultimap.getValue(PREFIX_ADDRESS).get().split("\\s+"); + finalPredicate = + finalPredicate.and(new AddressDeveloperContainsKeywordsPredicate(Arrays.asList(addressKeywords))); + } + + if (argMultimap.getValue(PREFIX_DATEJOINED).isPresent()) { + String[] dateJoinedKeywords = argMultimap.getValue(PREFIX_DATEJOINED).get().split("\\s+"); + finalPredicate = + finalPredicate.and(new DateJoinedContainsKeywordsPredicate(Arrays.asList(dateJoinedKeywords))); + } + + if (argMultimap.getValue(PREFIX_EMAIL).isPresent()) { + String[] emailKeywords = argMultimap.getValue(PREFIX_EMAIL).get().split("\\s+"); + finalPredicate = + finalPredicate.and(new EmailDeveloperContainsKeywordsPredicate(Arrays.asList(emailKeywords))); + } + + if (argMultimap.getValue(PREFIX_PHONE).isPresent()) { + String[] phoneKeywords = argMultimap.getValue(PREFIX_PHONE).get().split("\\s+"); + finalPredicate = + finalPredicate.and(new PhoneDeveloperContainsKeywordsPredicate(Arrays.asList(phoneKeywords))); + } + + if (argMultimap.getValue(PREFIX_PROJECT).isPresent()) { + String[] projectKeywords = argMultimap.getValue(PREFIX_PROJECT).get().split("\\s+"); + finalPredicate = + finalPredicate.and(new ProjectDeveloperContainsKeywordsPredicate(Arrays.asList(projectKeywords))); + } + + if (argMultimap.getValue(PREFIX_SALARY).isPresent()) { + String[] salaryKeywords = argMultimap.getValue(PREFIX_SALARY).get().split("\\s+"); + finalPredicate = + finalPredicate.and(new SalaryContainsKeywordsPredicate(Arrays.asList(salaryKeywords))); + } + + if (argMultimap.getValue(PREFIX_RATING).isPresent()) { + String[] ratingKeywords = argMultimap.getValue(PREFIX_RATING).get().split("\\s+"); + finalPredicate = + finalPredicate.and(new RatingContainsKeywordsPredicate(Arrays.asList(ratingKeywords))); + } + + if (argMultimap.getValue(PREFIX_GITHUBID).isPresent()) { + String[] githubKeywords = argMultimap.getValue(PREFIX_GITHUBID).get().split("\\s+"); + finalPredicate = + finalPredicate.and(new GithubIdContainsKeywordsPredicate(Arrays.asList(githubKeywords))); + } + + return finalPredicate; + } +} diff --git a/src/main/java/seedu/address/logic/parser/find/FindProjectCommandParser.java b/src/main/java/seedu/address/logic/parser/find/FindProjectCommandParser.java new file mode 100644 index 00000000000..cf555f66bf7 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/find/FindProjectCommandParser.java @@ -0,0 +1,91 @@ +package seedu.address.logic.parser.find; + +import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DEADLINE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DESCRIPTION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PROJECT; + +import java.util.Arrays; +import java.util.function.Predicate; + +import seedu.address.logic.commands.find.FindProjectCommand; +import seedu.address.logic.parser.ArgumentMultimap; +import seedu.address.logic.parser.ArgumentTokenizer; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.Prefix; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.project.DeadlineContainsKeywordsPredicate; +import seedu.address.model.project.DescriptionContainsKeywordsPredicate; +import seedu.address.model.project.Project; +import seedu.address.model.project.ProjectNameContainsKeywordsPredicate; + +/** + * Parser to handle user input and create a {@link FindProjectCommand}. + * This command is used to search and filter projects based on specific criteria. + * The supported criteria include filtering by project name, description, and deadlines. + */ +public class FindProjectCommandParser implements Parser { + + /** + * Parses user input and creates a {@link FindProjectCommand} based on the provided criteria. + * + * @param args User input arguments, which may include criteria for filtering projects. + * @return A {@link FindProjectCommand} for searching and filtering projects. + * @throws ParseException If the user input does not conform to the expected format. + */ + public FindProjectCommand parse(String args) throws ParseException { + String trimmedArgs = args.trim(); + if (trimmedArgs.isEmpty()) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindProjectCommand.MESSAGE_USAGE)); + } + + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_DEADLINE, PREFIX_DESCRIPTION, PREFIX_PROJECT); + + for (Prefix prefix : Arrays.asList(PREFIX_DEADLINE, PREFIX_DESCRIPTION, PREFIX_PROJECT)) { + if (argMultimap.getValue(prefix).isPresent() && argMultimap.getValue(prefix).get().isEmpty()) { + throw new ParseException("Please input a value after the prefix."); + } + } + + if (!argMultimap.getPreamble().isEmpty()) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindProjectCommand.MESSAGE_USAGE)); + } + + Predicate finalPredicate = buildPredicate(argMultimap); + + return new FindProjectCommand(finalPredicate); + } + + /** + * Builds a predicate for filtering projects based on the criteria provided in the user input. + * + * @param argMultimap Argument multimap containing user input arguments. + * @return A predicate for filtering projects. + */ + private Predicate buildPredicate(ArgumentMultimap argMultimap) { + Predicate finalPredicate = project -> true; + + if (argMultimap.getValue(PREFIX_PROJECT).isPresent()) { + String[] projectNameKeywords = argMultimap.getValue(PREFIX_PROJECT).get().split("\\s+"); + finalPredicate = + finalPredicate.and(new ProjectNameContainsKeywordsPredicate(Arrays.asList(projectNameKeywords))); + } + + if (argMultimap.getValue(PREFIX_DESCRIPTION).isPresent()) { + String[] descriptionKeywords = argMultimap.getValue(PREFIX_DESCRIPTION).get().split("\\s+"); + finalPredicate = + finalPredicate.and(new DescriptionContainsKeywordsPredicate(Arrays.asList(descriptionKeywords))); + } + + if (argMultimap.getValue(PREFIX_DEADLINE).isPresent()) { + String[] deadlineKeywords = argMultimap.getValue(PREFIX_DEADLINE).get().split("\\s+"); + finalPredicate = + finalPredicate.and(new DeadlineContainsKeywordsPredicate(Arrays.asList(deadlineKeywords))); + } + + return finalPredicate; + } +} diff --git a/src/main/java/seedu/address/logic/parser/imports/ImportClientCommandParser.java b/src/main/java/seedu/address/logic/parser/imports/ImportClientCommandParser.java new file mode 100644 index 00000000000..63c5d7b6640 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/imports/ImportClientCommandParser.java @@ -0,0 +1,100 @@ +package seedu.address.logic.parser.imports; + +import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.Messages.MESSAGE_INVALID_FILE; + +import java.io.BufferedReader; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Set; + +import seedu.address.logic.commands.imports.ImportClientCommand; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.ParserUtil; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.client.Client; +import seedu.address.model.client.ClientRoles; +import seedu.address.model.client.Document; +import seedu.address.model.commons.Name; +import seedu.address.model.person.Address; +import seedu.address.model.person.Email; +import seedu.address.model.person.Phone; + +/** + * Parses input arguments and creates a + * new {@link ImportClientCommand} object for importing client data from a CSV file. + * The CSV file should contain columns with specific column names and data in a certain format. + */ +public class ImportClientCommandParser implements Parser { + + /** + * Parses the provided file name to import client data from a CSV file. + * + * @param fileName The name of the CSV file to import client data from. + * @return A {@link ImportClientCommand} for importing client data. + * @throws ParseException If there are issues with file handling or if the file format is invalid. + */ + @Override + public ImportClientCommand parse(String fileName) throws ParseException { + try { + FileWriter myWriter = new FileWriter("filename.txt"); + myWriter.write("Files in Java might be tricky, but it is fun enough!"); + myWriter.close(); + fileName = fileName.trim(); + BufferedReader br = new BufferedReader(new FileReader(fileName)); + String line = ""; + String splitBy = ","; + + // Check if the CSV file contains valid column names + boolean isValid = checkColumnNames(br.readLine()); + if (!isValid) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + ImportClientCommand.MESSAGE_USAGE)); + } + + ArrayList toAddList = new ArrayList<>(); + while ((line = br.readLine()) != null) { + String[] clientData = line.split(splitBy); + + // Parse client data from CSV columns + Name name = ParserUtil.parseName(clientData[0]); + Phone phone = ParserUtil.parsePhone(clientData[1]); + Email email = ParserUtil.parseEmail(clientData[2]); + Address address = ParserUtil.parseAddress(clientData[3]); + ClientRoles role = ParserUtil.parseClientRole(clientData[4]); + Name organisation = ParserUtil.parseName(clientData[5]); + Document document = ParserUtil.parseDocument(clientData[6]); + ArrayList projects = new ArrayList<>(); + for (int i = 7; i < clientData.length; i++) { + projects.add(clientData[i]); + } + Set projectList = ParserUtil.parseProjectsToSet(projects); + + // Create a Client object + Client client = new Client(name, phone, email, address, role, projectList, organisation, document); + + // Add the client to the list + toAddList.add(client); + } + + return new ImportClientCommand(toAddList); + } catch (FileNotFoundException ex) { + throw new ParseException(MESSAGE_INVALID_FILE); + } catch (IOException e) { + throw new ParseException("Error reading line from file " + fileName); + } + } + + // Define a method to check if the CSV file contains valid column names + private boolean checkColumnNames(String line) { + String[] columnNames = line.split(","); + return columnNames.length == 8 && columnNames[0].contains("Name") + && columnNames[1].contains("Contact Number") && columnNames[2].contains("Email") + && columnNames[3].contains("Address") && columnNames[4].contains("Role") + && columnNames[5].contains("Organisation") && columnNames[6].contains("Document") + && columnNames[7].contains("Projects"); + } +} diff --git a/src/main/java/seedu/address/logic/parser/imports/ImportDeveloperCommandParser.java b/src/main/java/seedu/address/logic/parser/imports/ImportDeveloperCommandParser.java new file mode 100644 index 00000000000..6704b89ffdc --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/imports/ImportDeveloperCommandParser.java @@ -0,0 +1,126 @@ +package seedu.address.logic.parser.imports; + +import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.Messages.MESSAGE_INVALID_FILE; + +import java.io.BufferedReader; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Set; + +import seedu.address.logic.commands.imports.ImportDeveloperCommand; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.ParserUtil; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.commons.Date; +import seedu.address.model.commons.Name; +import seedu.address.model.developer.Developer; +import seedu.address.model.developer.DeveloperRoles; +import seedu.address.model.developer.GithubId; +import seedu.address.model.developer.Rating; +import seedu.address.model.developer.Salary; +import seedu.address.model.person.Address; +import seedu.address.model.person.Email; +import seedu.address.model.person.Phone; + +/** + * Parses input arguments and + * creates a new {@link ImportDeveloperCommand} object for importing developer data from a CSV file. + * The CSV file should contain columns with specific column names and data in a certain format. + */ +public class ImportDeveloperCommandParser implements Parser { + + /** + * Parses the provided file name to import developer data from a CSV file. + * + * @param fileName The name of the CSV file to import developer data from. + * @return A {@link ImportDeveloperCommand} for importing developer data. + * @throws ParseException If there are issues with file handling or if the file format is invalid. + */ + @Override + public ImportDeveloperCommand parse(String fileName) throws ParseException { + try { + fileName = fileName.trim(); + BufferedReader br = new BufferedReader(new FileReader(fileName)); + String line = ""; + String splitBy = ","; + boolean isValid = checkColumnNames(br.readLine()); + if (!isValid) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + ImportDeveloperCommand.MESSAGE_USAGE)); + } + ArrayList toAddList = new ArrayList<>(); + while ((line = br.readLine()) != null) { + String[] employee = line.split(splitBy); // use comma as separator + Name name = ParserUtil.parseName(employee[0]); + Phone phone = ParserUtil.parsePhone(employee[1]); + Email email = ParserUtil.parseEmail(employee[2]); + Address address = ParserUtil.parseAddress(employee[3]); + Date dateJoined = ParserUtil.parseDateJoined(employee[4]); + DeveloperRoles role = ParserUtil.parseDeveloperRole(employee[5]); + Salary salary = ParserUtil.parseSalary(employee[6]); + GithubId githubId = ParserUtil.parseGithubId(employee[7]); + Rating rating = ParserUtil.parseRating(employee[8]); + ArrayList projects = new ArrayList<>(); + for (int i = 9; i < employee.length; i++) { + projects.add(employee[i]); + } + Set projectList = ParserUtil.parseProjectsToSet(projects); + + Developer developer = + new Developer(name, phone, email, + address, role, projectList, salary, dateJoined, githubId, rating); + toAddList.add(developer); + + } + return new ImportDeveloperCommand(toAddList); + } catch (FileNotFoundException ex) { + throw new ParseException(MESSAGE_INVALID_FILE); + } catch (IOException e) { + throw new ParseException("Error reading line from file " + fileName); + } + } + + /** + * Checks if the CSV file contains valid column names based on expected format. + * + * @param line The header line of the CSV file. + * @return True if the column names match the expected format, false otherwise. + */ + private boolean checkColumnNames(String line) { + String[] columnNames = line.split(","); + if (!columnNames[0].contains("Name")) { + return false; + } + if (!columnNames[1].contains("Contact Number")) { + return false; + } + if (!columnNames[2].contains("Email")) { + return false; + } + if (!columnNames[3].contains("Address")) { + return false; + } + if (!columnNames[4].contains("Date Joined")) { + return false; + } + if (!columnNames[5].contains("Role")) { + return false; + } + if (!columnNames[6].contains("Salary")) { + return false; + } + if (!columnNames[7].contains("GithubId")) { + return false; + } + if (!columnNames[8].contains("Rating")) { + return false; + } + if (!columnNames[9].contains("Projects")) { + return false; + } + return true; + } +} diff --git a/src/main/java/seedu/address/logic/parser/mark/MarkDeadlineCommandParser.java b/src/main/java/seedu/address/logic/parser/mark/MarkDeadlineCommandParser.java new file mode 100644 index 00000000000..ef7ad5345dc --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/mark/MarkDeadlineCommandParser.java @@ -0,0 +1,44 @@ +package seedu.address.logic.parser.mark; + +import static java.util.Objects.requireNonNull; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.Messages; +import seedu.address.logic.commands.mark.MarkDeadlineCommand; +import seedu.address.logic.parser.ParserUtil; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a new MarkDeadlineCommand object. + */ +public class MarkDeadlineCommandParser { + /** + * Parses the given {@code String} of arguments in the context of the MarkDeadlineCommand + * and returns a MarkDeadlineCommand object for execution. + * + * @throws ParseException if the user input does not conform to the expected format. + */ + public MarkDeadlineCommand parse(String args) throws ParseException { + requireNonNull(args); + String[] input = args.trim().split(" "); + + if (input.length != 2) { + throw new ParseException(String.format(Messages.MESSAGE_INVALID_COMMAND_FORMAT, + MarkDeadlineCommand.MESSAGE_USAGE)); + } + + Index projIndex; + Index deadlineIndex; + + try { + projIndex = ParserUtil.parseIndex(input[0]); + deadlineIndex = ParserUtil.parseIndex(input[1]); + } catch (NumberFormatException e) { + throw new ParseException(String.format(Messages.MESSAGE_INVALID_COMMAND_FORMAT, + MarkDeadlineCommand.MESSAGE_USAGE)); + } + + return new MarkDeadlineCommand(projIndex, deadlineIndex); + } + +} diff --git a/src/main/java/seedu/address/logic/parser/mark/UnmarkDeadlineCommandParser.java b/src/main/java/seedu/address/logic/parser/mark/UnmarkDeadlineCommandParser.java new file mode 100644 index 00000000000..7808fb7bab3 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/mark/UnmarkDeadlineCommandParser.java @@ -0,0 +1,43 @@ +package seedu.address.logic.parser.mark; + +import static java.util.Objects.requireNonNull; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.Messages; +import seedu.address.logic.commands.mark.UnmarkDeadlineCommand; +import seedu.address.logic.parser.ParserUtil; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a new UnmarkDeadlineCommand object. + */ +public class UnmarkDeadlineCommandParser { + /** + * Parses the given {@code String} of arguments in the context of the UnmarkDeadlineCommand + * and returns a UnmarkDeadlineCommand object for execution. + * + * @throws ParseException if the user input does not conform to the expected format. + */ + public UnmarkDeadlineCommand parse(String args) throws ParseException { + requireNonNull(args); + String[] input = args.trim().split(" "); + + if (input.length != 2) { + throw new ParseException(String.format(Messages.MESSAGE_INVALID_COMMAND_FORMAT, + UnmarkDeadlineCommand.MESSAGE_USAGE)); + } + + Index projIndex; + Index deadlineIndex; + + try { + projIndex = ParserUtil.parseIndex(input[0]); + deadlineIndex = ParserUtil.parseIndex(input[1]); + } catch (NumberFormatException e) { + throw new ParseException(String.format(Messages.MESSAGE_INVALID_COMMAND_FORMAT, + UnmarkDeadlineCommand.MESSAGE_USAGE)); + } + + return new UnmarkDeadlineCommand(projIndex, deadlineIndex); + } +} diff --git a/src/main/java/seedu/address/model/AddressBook.java b/src/main/java/seedu/address/model/AddressBook.java index 73397161e84..2c0716a91c7 100644 --- a/src/main/java/seedu/address/model/AddressBook.java +++ b/src/main/java/seedu/address/model/AddressBook.java @@ -3,109 +3,291 @@ import static java.util.Objects.requireNonNull; import java.util.List; +import java.util.Objects; +import java.util.Set; import javafx.collections.ObservableList; import seedu.address.commons.util.ToStringBuilder; +import seedu.address.model.client.Client; +import seedu.address.model.client.UniqueClientList; +import seedu.address.model.developer.Developer; +import seedu.address.model.developer.UniqueDeveloperList; import seedu.address.model.person.Person; -import seedu.address.model.person.UniquePersonList; +import seedu.address.model.project.Project; +import seedu.address.model.project.UniqueProjectList; /** - * Wraps all data at the address-book level - * Duplicates are not allowed (by .isSamePerson comparison) + * Represents the entire address book. Contains the data of the developers, clients, and projects. + * Duplicates are not allowed (by .isSamePerson comparison). */ public class AddressBook implements ReadOnlyAddressBook { - private final UniquePersonList persons; + private final UniqueDeveloperList developers; + private final UniqueClientList clients; + private final UniqueProjectList projects; - /* - * 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(); + developers = new UniqueDeveloperList(); + clients = new UniqueClientList(); + projects = new UniqueProjectList(); } - public AddressBook() {} + /** + * Creates an empty AddressBook. + */ + public AddressBook() { + } /** - * Creates an AddressBook using the Persons in the {@code toBeCopied} + * Creates an AddressBook using the data in the given ReadOnlyAddressBook. + * + * @param toBeCopied The ReadOnlyAddressBook containing data to be copied into this AddressBook. */ public AddressBook(ReadOnlyAddressBook toBeCopied) { this(); resetData(toBeCopied); } - //// list overwrite operations + /// Project Validation + + /** + * Checks if the projects assigned to a person are valid. + * + * @param person The person to check for valid projects. + * @return The name of the first invalid project if any, else returns null. + */ + public String areProjectsValid(Person person) { + Set projects = person.getProjects(); + for (String p : projects) { + if (!this.hasProject(p)) { + return p; + } + } + return null; + } + + // List Overwrite Operations + + /** + * Sets the list of developers in the AddressBook with the provided list. + * + * @param developers The list of developers to set. + */ + public void setDevelopers(List developers) { + this.developers.setDevelopers(developers); + } + + /** + * Sets the list of clients in the AddressBook with the provided list. + * + * @param clients The list of clients to set. + */ + public void setClients(List clients) { + this.clients.setClients(clients); + } /** - * Replaces the contents of the person list with {@code persons}. - * {@code persons} must not contain duplicate persons. + * Sets the list of projects in the AddressBook with the provided list. + * + * @param projects The list of projects to set. */ - public void setPersons(List persons) { - this.persons.setPersons(persons); + public void setProjects(List projects) { + this.projects.setProjects(projects); } /** - * Resets the existing data of this {@code AddressBook} with {@code newData}. + * Resets the data of this AddressBook with data from another ReadOnlyAddressBook. + * + * @param newData The ReadOnlyAddressBook containing data to reset this AddressBook with. */ public void resetData(ReadOnlyAddressBook newData) { requireNonNull(newData); + setDevelopers(newData.getDeveloperList()); + setClients(newData.getClientList()); + setProjects(newData.getProjectList()); + } - setPersons(newData.getPersonList()); + // Developer-level Operations + + /** + * Checks if a developer exists in this AddressBook. + * + * @param developer The developer to check. + * @return True if the developer exists, false otherwise. + */ + public boolean hasDeveloper(Developer developer) { + requireNonNull(developer); + return developers.contains(developer); } - //// person-level operations + /** + * Adds a developer to this AddressBook. + * + * @param developer The developer to add. + */ + public void addDeveloper(Developer developer) { + developers.add(developer); + } /** - * Returns true if a person with the same identity as {@code person} exists in the address book. + * Sets an existing developer in this AddressBook with an edited developer. + * + * @param target The developer to be replaced. + * @param editedDeveloper The edited developer. */ - public boolean hasPerson(Person person) { - requireNonNull(person); - return persons.contains(person); + public void setDeveloper(Developer target, Developer editedDeveloper) { + requireNonNull(editedDeveloper); + developers.setDeveloper(target, editedDeveloper); } /** - * Adds a person to the address book. - * The person must not already exist in the address book. + * Removes a developer from this AddressBook. + * + * @param key The developer to remove. */ - public void addPerson(Person p) { - persons.add(p); + public void removeDeveloper(Developer key) { + developers.remove(key); } + // Client-level Operations + /** - * 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. + * Checks if a client exists in this AddressBook. + * + * @param client The client to check. + * @return True if the client exists, false otherwise. */ - public void setPerson(Person target, Person editedPerson) { - requireNonNull(editedPerson); + public boolean hasClient(Client client) { + requireNonNull(client); + return clients.contains(client); + } - persons.setPerson(target, editedPerson); + /** + * Adds a client to this AddressBook. + * + * @param client The client to add. + */ + public void addClient(Client client) { + clients.add(client); } /** - * Removes {@code key} from this {@code AddressBook}. - * {@code key} must exist in the address book. + * Sets an existing client in this AddressBook with an edited client. + * + * @param target The client to be replaced. + * @param editedClient The edited client. */ - public void removePerson(Person key) { - persons.remove(key); + public void setClient(Client target, Client editedClient) { + requireNonNull(editedClient); + clients.setClient(target, editedClient); } - //// util methods + /** + * Removes a client from this AddressBook. + * + * @param key The client to remove. + */ + public void removeClient(Client key) { + clients.remove(key); + } + + // Project-level Operations + + /** + * Checks if a project exists in this AddressBook. + * + * @param project The project to check. + * @return True if the project exists, false otherwise. + */ + public boolean hasProject(Project project) { + requireNonNull(project); + return projects.contains(project); + } + + /** + * Checks if a project with a given name exists in this AddressBook. + * + * @param project The name of the project to check. + * @return True if the project exists, false otherwise. + */ + public boolean hasProject(String project) { + requireNonNull(project); + return projects.contains(project); + } + /** + * Adds a project to this AddressBook. + * + * @param project The project to add. + */ + public void addProject(Project project) { + projects.add(project); + } + + /** + * Sets an existing project in this AddressBook with an edited project. + * + * @param target The project to be replaced. + * @param editedProject The edited project. + */ + public void setProject(Project target, Project editedProject) { + requireNonNull(editedProject); + projects.setProject(target, editedProject); + } + + /** + * Removes a project from this AddressBook. Also updates clients and developers to remove the project assignment. + * + * @param key The project to remove. + */ + public void removeProject(Project key) { + projects.remove(key); + clients.updateClientProjects(key.getName()); + developers.updateDeveloperProjects(key.getName()); + } + + /** + * Returns a string representation of this AddressBook. + * + * @return A string representation of this AddressBook. + */ @Override public String toString() { return new ToStringBuilder(this) - .add("persons", persons) + .add("developers", developers) + .add("clients", clients) + .add("projects", projects) .toString(); } + /** + * Returns the list of developers as an observable list. + * + * @return The list of developers as an observable list. + */ + @Override + public ObservableList getDeveloperList() { + return developers.asUnmodifiableObservableList(); + } + + /** + * Returns the list of clients as an observable list. + * + * @return The list of clients as an observable list. + */ + @Override + public ObservableList getClientList() { + return clients.asUnmodifiableObservableList(); + } + + /** + * Returns the list of projects as an observable list. + * + * @return The list of projects as an observable list. + */ @Override - public ObservableList getPersonList() { - return persons.asUnmodifiableObservableList(); + public ObservableList getProjectList() { + return projects.asUnmodifiableObservableList(); } @Override @@ -114,17 +296,18 @@ public boolean equals(Object other) { return true; } - // instanceof handles nulls if (!(other instanceof AddressBook)) { return false; } AddressBook otherAddressBook = (AddressBook) other; - return persons.equals(otherAddressBook.persons); + return developers.equals(otherAddressBook.developers) + && clients.equals(otherAddressBook.clients) + && projects.equals(otherAddressBook.projects); } @Override public int hashCode() { - return persons.hashCode(); + return Objects.hash(developers, clients, projects); } } diff --git a/src/main/java/seedu/address/model/Model.java b/src/main/java/seedu/address/model/Model.java index d54df471c1f..3e8206f2b6c 100644 --- a/src/main/java/seedu/address/model/Model.java +++ b/src/main/java/seedu/address/model/Model.java @@ -5,25 +5,38 @@ import javafx.collections.ObservableList; import seedu.address.commons.core.GuiSettings; +import seedu.address.logic.commands.TabIndex; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.client.Client; +import seedu.address.model.developer.Developer; import seedu.address.model.person.Person; +import seedu.address.model.project.Deadline; +import seedu.address.model.project.Project; /** * The API of the Model component. */ public interface Model { - /** {@code Predicate} that always evaluate to true */ - Predicate PREDICATE_SHOW_ALL_PERSONS = unused -> true; - /** - * Replaces user prefs data with the data in {@code userPrefs}. + * {@code Predicate} that always evaluate to true */ - void setUserPrefs(ReadOnlyUserPrefs userPrefs); + Predicate PREDICATE_SHOW_ALL_DEVELOPERS = unused -> true; + Predicate PREDICATE_SHOW_ALL_CLIENTS = unused -> true; + Predicate PREDICATE_SHOW_ALL_PROJECTS = unused -> true; + Predicate PREDICATE_SHOW_NO_DEVELOPER = unused -> false; + Predicate PREDICATE_SHOW_NO_CLIENT = unused -> false; + Predicate PREDICATE_SHOW_NO_PROJECT = unused -> false; /** * Returns the user prefs. */ ReadOnlyUserPrefs getUserPrefs(); + /** + * Replaces user prefs data with the data in {@code userPrefs}. + */ + void setUserPrefs(ReadOnlyUserPrefs userPrefs); + /** * Returns the user prefs' GUI settings. */ @@ -44,44 +57,98 @@ public interface Model { */ void setAddressBookFilePath(Path addressBookFilePath); + /** + * Returns the AddressBook + */ + ReadOnlyAddressBook getAddressBook(); + /** * Replaces address book data with the data in {@code addressBook}. */ void setAddressBook(ReadOnlyAddressBook addressBook); - /** Returns the AddressBook */ - ReadOnlyAddressBook getAddressBook(); - /** * Returns true if a person with the same identity as {@code person} exists in the address book. */ - boolean hasPerson(Person person); + boolean hasDeveloper(Developer person); + + boolean hasClient(Client client); + + boolean hasProject(seedu.address.model.project.Project project); + + /** + * Returns null if the projects assigned to a person exist and are valid, + * returns the invalid project name otherwise. + * + * @param person The person to check. + * @returns The String of the invalid project name, or null if all projects are valid. + */ + String areProjectsValid(Person person); /** * Deletes the given person. * The person must exist in the address book. */ - void deletePerson(Person target); + void deleteDeveloper(Developer target); + + void deleteClient(Client target); + + void deleteProject(seedu.address.model.project.Project target); /** * Adds the given person. * {@code person} must not already exist in the address book. */ - void addPerson(Person person); + void addDeveloper(Developer person); + + void addClient(Client person); + + void addProject(seedu.address.model.project.Project person); /** * 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. */ - void setPerson(Person target, Person editedPerson); + void setDeveloper(Developer target, Developer editedDeveloper); + + void setClient(Client target, Client editedClient); + + void setProject(seedu.address.model.project.Project target, seedu.address.model.project.Project editedProject); + + /** + * Returns an unmodifiable view of the filtered person list + */ + ObservableList getFilteredDeveloperList(); + + ObservableList getFilteredClientList(); + + ObservableList getFilteredProjectList(); - /** Returns an unmodifiable view of the filtered person list */ - ObservableList getFilteredPersonList(); /** * Updates the filter of the filtered person list to filter by the given {@code predicate}. + * * @throws NullPointerException if {@code predicate} is null. */ - void updateFilteredPersonList(Predicate predicate); + void updateFilteredDeveloperList(Predicate predicate); + + void updateFilteredClientList(Predicate predicate); + + void updateFilteredProjectList(Predicate predicate); + + void updateFilteredProjectDeadlineList(Predicate predicate); + + void commitAddressBook(Model model, String message, TabIndex index); + + void undoAddressBook(Model model) throws CommandException; + + void redoAddressBook(Model model) throws CommandException; + + String getPreviousCommandForRedo() throws CommandException; + String getPreviousCommandForUndo() throws CommandException; + + TabIndex getPreviousTabIndex(); + + TabIndex getPreviousTabIndexForRedo(); } diff --git a/src/main/java/seedu/address/model/ModelManager.java b/src/main/java/seedu/address/model/ModelManager.java index 57bc563fde6..d8f882a5328 100644 --- a/src/main/java/seedu/address/model/ModelManager.java +++ b/src/main/java/seedu/address/model/ModelManager.java @@ -11,7 +11,12 @@ import javafx.collections.transformation.FilteredList; import seedu.address.commons.core.GuiSettings; import seedu.address.commons.core.LogsCenter; +import seedu.address.logic.commands.TabIndex; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.client.Client; +import seedu.address.model.developer.Developer; import seedu.address.model.person.Person; +import seedu.address.model.project.Deadline; /** * Represents the in-memory model of the address book data. @@ -21,7 +26,10 @@ public class ModelManager implements Model { private final AddressBook addressBook; private final UserPrefs userPrefs; - private final FilteredList filteredPersons; + private final FilteredList filteredDevelopers; + private final FilteredList filteredClients; + private final FilteredList filteredProjects; + private final VersionedAddressBook versionedAddressBook; /** * Initializes a ModelManager with the given addressBook and userPrefs. @@ -33,7 +41,11 @@ public ModelManager(ReadOnlyAddressBook addressBook, ReadOnlyUserPrefs userPrefs this.addressBook = new AddressBook(addressBook); this.userPrefs = new UserPrefs(userPrefs); - filteredPersons = new FilteredList<>(this.addressBook.getPersonList()); + filteredDevelopers = new FilteredList<>(this.addressBook.getDeveloperList()); + filteredClients = new FilteredList<>(this.addressBook.getClientList()); + filteredProjects = new FilteredList<>(this.addressBook.getProjectList()); + versionedAddressBook = new VersionedAddressBook(this.addressBook); + } public ModelManager() { @@ -43,14 +55,14 @@ public ModelManager() { //=========== UserPrefs ================================================================================== @Override - public void setUserPrefs(ReadOnlyUserPrefs userPrefs) { - requireNonNull(userPrefs); - this.userPrefs.resetData(userPrefs); + public ReadOnlyUserPrefs getUserPrefs() { + return userPrefs; } @Override - public ReadOnlyUserPrefs getUserPrefs() { - return userPrefs; + public void setUserPrefs(ReadOnlyUserPrefs userPrefs) { + requireNonNull(userPrefs); + this.userPrefs.resetData(userPrefs); } @Override @@ -77,55 +89,194 @@ public void setAddressBookFilePath(Path addressBookFilePath) { //=========== AddressBook ================================================================================ + @Override + public ReadOnlyAddressBook getAddressBook() { + return addressBook; + } + @Override public void setAddressBook(ReadOnlyAddressBook addressBook) { this.addressBook.resetData(addressBook); } @Override - public ReadOnlyAddressBook getAddressBook() { - return addressBook; + public boolean hasDeveloper(Developer developer) { + requireNonNull(developer); + return addressBook.hasDeveloper(developer); + } + + @Override + public void deleteDeveloper(Developer target) { + addressBook.removeDeveloper(target); + } + + @Override + public void addDeveloper(Developer developer) { + addressBook.addDeveloper(developer); + updateFilteredDeveloperList(PREDICATE_SHOW_ALL_DEVELOPERS); } @Override - public boolean hasPerson(Person person) { + public void setDeveloper(Developer target, Developer editedDeveloper) { + requireAllNonNull(target, editedDeveloper); + + addressBook.setDeveloper(target, editedDeveloper); + } + + // Similarly, create methods for Client and Project + + //=========== Filtered Developer List Accessors ============================================================= + + /** + * Returns an unmodifiable view of the list of {@code Developer} backed by the internal list of + * {@code versionedAddressBook} + */ + @Override + public ObservableList getFilteredDeveloperList() { + return filteredDevelopers; + } + + @Override + public void updateFilteredDeveloperList(Predicate predicate) { + requireNonNull(predicate); + filteredDevelopers.setPredicate(predicate); + } + + @Override + public boolean hasClient(Client client) { + requireNonNull(client); + return addressBook.hasClient(client); + } + + + /** + * Checks if the projects assigned to a person are valid. + * + * @param person The person to check for valid projects. + * @return The name of the first invalid project if any, else returns null. + */ + public String areProjectsValid(Person person) { requireNonNull(person); - return addressBook.hasPerson(person); + return addressBook.areProjectsValid(person); } @Override - public void deletePerson(Person target) { - addressBook.removePerson(target); + public void deleteClient(Client target) { + addressBook.removeClient(target); } @Override - public void addPerson(Person person) { - addressBook.addPerson(person); - updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + public void addClient(Client client) { + addressBook.addClient(client); + updateFilteredClientList(PREDICATE_SHOW_ALL_CLIENTS); } @Override - public void setPerson(Person target, Person editedPerson) { - requireAllNonNull(target, editedPerson); + public void setClient(Client target, Client editedClient) { + requireAllNonNull(target, editedClient); + addressBook.setClient(target, editedClient); + } + + //=========== Filtered Client List Accessors ============================================================= - addressBook.setPerson(target, editedPerson); + /** + * Returns an unmodifiable view of the list of {@code Client} backed by the internal list of + * {@code versionedAddressBook} + */ + @Override + public ObservableList getFilteredClientList() { + return filteredClients; + } + + @Override + public void updateFilteredClientList(Predicate predicate) { + requireNonNull(predicate); + filteredClients.setPredicate(predicate); } - //=========== Filtered Person List Accessors ============================================================= + @Override + public boolean hasProject(seedu.address.model.project.Project project) { + requireNonNull(project); + return addressBook.hasProject(project); + } + + @Override + public void deleteProject(seedu.address.model.project.Project target) { + addressBook.removeProject(target); + } + + @Override + public void addProject(seedu.address.model.project.Project project) { + addressBook.addProject(project); + updateFilteredProjectList(PREDICATE_SHOW_ALL_PROJECTS); + } + + @Override + public void setProject(seedu.address.model.project.Project target, + seedu.address.model.project.Project editedProject) { + requireAllNonNull(target, editedProject); + + addressBook.setProject(target, editedProject); + } + + //=========== Filtered Project List Accessors ============================================================= /** - * Returns an unmodifiable view of the list of {@code Person} backed by the internal list of + * Returns an unmodifiable view of the list of {@code Project} backed by the internal list of * {@code versionedAddressBook} */ @Override - public ObservableList getFilteredPersonList() { - return filteredPersons; + public ObservableList getFilteredProjectList() { + return filteredProjects; + } + + @Override + public void updateFilteredProjectList(Predicate predicate) { + requireNonNull(predicate); + filteredProjects.setPredicate(predicate); + filteredProjects.forEach(e -> e.setPredicate(u -> true)); } @Override - public void updateFilteredPersonList(Predicate predicate) { + public void updateFilteredProjectDeadlineList(Predicate predicate) { requireNonNull(predicate); - filteredPersons.setPredicate(predicate); + filteredProjects.forEach(e -> e.setPredicate(predicate)); + } + + //=========== Undo/Redo Accessors ============================================================= + @Override + public void commitAddressBook(Model model, String message, TabIndex index) { + versionedAddressBook.commit(model, message, index); + } + + @Override + public void undoAddressBook(Model model) throws CommandException { + versionedAddressBook.undo(model); + } + + @Override + public void redoAddressBook(Model model) throws CommandException { + versionedAddressBook.redo(model); + } + + @Override + public String getPreviousCommandForUndo() { + return versionedAddressBook.getPreviousMessage(); + } + + @Override + public String getPreviousCommandForRedo() { + return versionedAddressBook.getPreviousMessageForRedo(); + } + + @Override + public TabIndex getPreviousTabIndex() { + return versionedAddressBook.getPreviousTabIndex(); + } + + @Override + public TabIndex getPreviousTabIndexForRedo() { + return versionedAddressBook.getPreviousTabIndexForRedo(); } @Override @@ -142,7 +293,10 @@ public boolean equals(Object other) { ModelManager otherModelManager = (ModelManager) other; return addressBook.equals(otherModelManager.addressBook) && userPrefs.equals(otherModelManager.userPrefs) - && filteredPersons.equals(otherModelManager.filteredPersons); + && filteredDevelopers.equals(otherModelManager.filteredDevelopers) + && filteredClients.equals(otherModelManager.filteredClients) + && filteredProjects.equals(otherModelManager.filteredProjects) + && versionedAddressBook.equals(otherModelManager.versionedAddressBook); } } diff --git a/src/main/java/seedu/address/model/Password.java b/src/main/java/seedu/address/model/Password.java new file mode 100644 index 00000000000..d2f225b071b --- /dev/null +++ b/src/main/java/seedu/address/model/Password.java @@ -0,0 +1,189 @@ +package seedu.address.model; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +/** + * Represents a User's password. The password must meet certain constraints to be considered valid. + * Guarantees: immutable; meets specified constraints. + */ +public class Password { + public static final String DEFAULT_PASSWORD = "Password123!"; + public static final String MESSAGE_CONSTRAINTS = + "Password must be at least 8 characters long and contain at least one digit, one lowercase letter," + + " one uppercase letter, and one special character.\n" + + "Default password: " + DEFAULT_PASSWORD; + public static final String VALIDATION_REGEX = "^(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z])(?=.*[!@#$%^&+=])(?=\\S+$).{8,}$"; + public static final String FILENAME = "pword.txt"; + private static final String SALT = "YourFixedSaltHere"; + public final String password; + + /** + * Constructs a `Password` with a valid password. + * + * @param password A valid password that meets the specified constraints. + */ + public Password(String password) { + requireNonNull(password); + checkArgument(isValidPassword(password), MESSAGE_CONSTRAINTS); + this.password = password; + } + + /** + * Returns true if a given string is a valid password. + * + * @param test The string to be tested for password validity. + * @return True if the string is a valid password; false otherwise. + */ + public static boolean isValidPassword(String test) { + return test.matches(VALIDATION_REGEX); + } + + /** + * Hashes a password. + * + * @param password The password to be hashed. + * @return The hashed password. + */ + public static String hashPassword(String password) { + return password.hashCode() + ""; + } + + /** + * Saves a hashed password to a file. + * + * @param hashedPassword The hashed password to be saved. + * @param filename The name of the file where the password will be saved. + */ + public static void savePasswordToFile(String hashedPassword, String filename) { + try { + Files.write(Path.of(filename), hashedPassword.getBytes()); + } catch (IOException e) { + e.printStackTrace(); + } + } + + /** + * Reads a hashed password from a file. + * + * @param filename The name of the file from which to read the password. + * @return The hashed password read from the file. + */ + public static String readPasswordFromFile(String filename) { + try { + byte[] bytes = Files.readAllBytes(Path.of(filename)); + return new String(bytes); + } catch (IOException e) { + e.printStackTrace(); + } + return null; + } + + /** + * Verifies a password entered by the user. + * + * @param enteredPassword The entered password to be verified. + * @return True if the entered password matches the stored hashed password; false otherwise. + */ + public static boolean verifyPassword(String enteredPassword) { + System.out.println(enteredPassword); + System.out.println(enteredPassword.equals(DEFAULT_PASSWORD)); + String storedHashedPassword = readPasswordFromFile(FILENAME); + System.out.println(storedHashedPassword); + if (storedHashedPassword == null) { + storedHashedPassword = hashPassword(DEFAULT_PASSWORD); + savePasswordToFile(storedHashedPassword, FILENAME); + } + + String enteredHashedPassword = hashPassword(enteredPassword); + System.out.println(enteredHashedPassword); + System.out.println(storedHashedPassword.equals(enteredHashedPassword)); + return storedHashedPassword.equals(enteredHashedPassword); + } + + /** + * Changes the user's password. + * + * @param currentPassword The current password. + * @param newPassword The new password to set. + * @return A message indicating the result of the password change operation. + */ + public static String changePassword(String currentPassword, String newPassword) { + if (verifyPassword(currentPassword)) { + // Verify that the new password meets the constraints + if (isValidPassword(newPassword)) { + if (currentPassword.equals(newPassword)) { + return "New password cannot be the same as the current password."; + } + // Hash the new password + String newHashedPassword = hashPassword(newPassword); + // Save the new hashed password to the file + savePasswordToFile(newHashedPassword, FILENAME); + return "Password changed successfully."; + } else { + return "New password doesn't meet the constraints.\n" + MESSAGE_CONSTRAINTS; + } + } else { + return "Current password is incorrect.\n" + MESSAGE_CONSTRAINTS; + } + } + + /** + * Converts an array of bytes to its hexadecimal representation. + * + * @param bytes The array of bytes to be converted. + * @return The hexadecimal representation of the byte array. + */ + public static String bytesToHex(byte[] bytes) { + StringBuilder sb = new StringBuilder(); + for (byte b : bytes) { + sb.append(String.format("%02x", b)); + } + return sb.toString(); + } + + /** + * Returns the password as a string. + * + * @return The password as a string. + */ + @Override + public String toString() { + return password; + } + + /** + * Checks if this `Password` is equal to another object. + * + * @param other The object to compare with. + * @return True if the objects are equal; false otherwise. + */ + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof Password)) { + return false; + } + + Password otherPassword = (Password) other; + return password.equals(otherPassword.password); + } + + /** + * Returns the hash code of this `Password`. + * + * @return The hash code of this `Password`. + */ + @Override + public int hashCode() { + return password.hashCode(); + } +} diff --git a/src/main/java/seedu/address/model/ReadOnlyAddressBook.java b/src/main/java/seedu/address/model/ReadOnlyAddressBook.java index 6ddc2cd9a29..cb0cb84d5f6 100644 --- a/src/main/java/seedu/address/model/ReadOnlyAddressBook.java +++ b/src/main/java/seedu/address/model/ReadOnlyAddressBook.java @@ -1,7 +1,8 @@ package seedu.address.model; import javafx.collections.ObservableList; -import seedu.address.model.person.Person; +import seedu.address.model.client.Client; +import seedu.address.model.developer.Developer; /** * Unmodifiable view of an address book @@ -12,6 +13,11 @@ public interface ReadOnlyAddressBook { * Returns an unmodifiable view of the persons list. * This list will not contain any duplicate persons. */ - ObservableList getPersonList(); + ObservableList getDeveloperList(); + + ObservableList getClientList(); + + ObservableList getProjectList(); + } diff --git a/src/main/java/seedu/address/model/UserPrefs.java b/src/main/java/seedu/address/model/UserPrefs.java index 6be655fb4c7..a09f61dbf4d 100644 --- a/src/main/java/seedu/address/model/UserPrefs.java +++ b/src/main/java/seedu/address/model/UserPrefs.java @@ -14,12 +14,13 @@ public class UserPrefs implements ReadOnlyUserPrefs { private GuiSettings guiSettings = new GuiSettings(); - private Path addressBookFilePath = Paths.get("data" , "addressbook.json"); + private Path addressBookFilePath = Paths.get("data", "addressbook.json"); /** * Creates a {@code UserPrefs} with default values. */ - public UserPrefs() {} + public UserPrefs() { + } /** * Creates a {@code UserPrefs} with the prefs in {@code userPrefs}. diff --git a/src/main/java/seedu/address/model/VersionedAddressBook.java b/src/main/java/seedu/address/model/VersionedAddressBook.java new file mode 100644 index 00000000000..996ab17e9ed --- /dev/null +++ b/src/main/java/seedu/address/model/VersionedAddressBook.java @@ -0,0 +1,167 @@ +package seedu.address.model; + +import java.util.ArrayList; +import java.util.List; + +import seedu.address.logic.commands.TabIndex; +import seedu.address.logic.commands.exceptions.CommandException; + +/** + * Represents a versioned history of the address book. + * Keeps track of the address book states and allows undoing and redoing changes. + */ +public class VersionedAddressBook extends AddressBook { + public static final String INVALID_REDO_COMMAND = "You have reached the last step, unable to redo"; + public static final String INVALID_UNDO_COMMAND = "You have reached the first step, unable to undo"; + private List addressBookStateList; + private List successfulCommandMessages; + private List tabIndex; + private int currentStatePointer; + + /** + * Constructs a `VersionedAddressBook` object with an initial address book state. + * + * @param initialAddressBook The initial state of the address book. + */ + public VersionedAddressBook(ReadOnlyAddressBook initialAddressBook) { + addressBookStateList = new ArrayList<>(); + addressBookStateList.add(new AddressBook(initialAddressBook)); + successfulCommandMessages = new ArrayList<>(); + tabIndex = new ArrayList<>(); + currentStatePointer = 0; + } + + /** + * Commits the current model state along with a success message and the tab index. + * + * @param model The current model. + * @param successMessage The success message to be stored. + * @param index The tab index to be stored. + */ + public void commit(Model model, String successMessage, TabIndex index) { + if (currentStatePointer < addressBookStateList.size() - 1) { + // If we're not at the latest state, remove the states after the current one + addressBookStateList.subList(currentStatePointer + 1, addressBookStateList.size()).clear(); + successfulCommandMessages.subList(currentStatePointer, successfulCommandMessages.size()).clear(); + tabIndex.subList(currentStatePointer, tabIndex.size()).clear(); + } + + // Add the current state to the history + AddressBook updatedAddressBook = new AddressBook(model.getAddressBook()); + addressBookStateList.add(updatedAddressBook); + successfulCommandMessages.add(successMessage); + tabIndex.add(index); + currentStatePointer++; + } + + /** + * Undoes the last change by restoring the previous state. + * + * @param model The current model. + * @throws CommandException If the undo operation is not valid (e.g., when there are no more states to undo). + */ + public void undo(Model model) throws CommandException { + if (canUndo()) { + currentStatePointer--; + changeAddressBook(model); + } else { + throw new CommandException(INVALID_UNDO_COMMAND); + } + } + + /** + * Redoes the last undone change by restoring the next state. + * + * @param model The current model. + * @throws CommandException If the redo operation is not valid (e.g., when there are no more states to redo). + */ + public void redo(Model model) throws CommandException { + if (canRedo()) { + currentStatePointer++; + changeAddressBook(model); + } else { + throw new CommandException(INVALID_REDO_COMMAND); + } + } + + /** + * Checks if there are states that can be undone. + * + * @return `true` if there are states to undo, `false` otherwise. + */ + public boolean canUndo() { + return currentStatePointer > 0; + } + + /** + * Checks if there are states that can be redone. + * + * @return `true` if there are states to redo, `false` otherwise. + */ + public boolean canRedo() { + return currentStatePointer < addressBookStateList.size() - 1; + } + + /** + * Gets the success message associated with the previous command. + * + * @return The success message from the previous command. + */ + public String getPreviousMessage() { + return successfulCommandMessages.get(currentStatePointer); + } + /** + * Gets the success message associated with the redone command. + * + * @return The success message from the previous command. + */ + public String getPreviousMessageForRedo() { + return successfulCommandMessages.get(currentStatePointer - 1); + } + + /** + * Gets the tab index associated with the previous command. + * + * @return The tab index from the previous command. + */ + public TabIndex getPreviousTabIndex() { + return tabIndex.get(currentStatePointer); + } + + /** + * Gets the tab index associated with the redone command. + * + * @return The tab index from the previous command. + */ + public TabIndex getPreviousTabIndexForRedo() { + return tabIndex.get(currentStatePointer - 1); + } + + /** + * Gets the current addressbook. + * + * @return The tab index from the previous command. + */ + public ReadOnlyAddressBook getCurrentState(int currentStatePointer) { + return addressBookStateList.get(currentStatePointer); + } + + /** + * Gets the current current state pointer. + * + * @return The tab index from the previous command. + */ + public int getCurrentStatePointer() { + return currentStatePointer; + } + + /** + * Changes the current model's address book to match the current state. + * + * @param model The current model. + */ + public void changeAddressBook(Model model) { + AddressBook newAddressBook = addressBookStateList.get(currentStatePointer); + model.setAddressBook(newAddressBook); + } +} diff --git a/src/main/java/seedu/address/model/client/AddressClientContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/client/AddressClientContainsKeywordsPredicate.java new file mode 100644 index 00000000000..c43b86ecf4d --- /dev/null +++ b/src/main/java/seedu/address/model/client/AddressClientContainsKeywordsPredicate.java @@ -0,0 +1,44 @@ +package seedu.address.model.client; + +import java.util.List; + +import seedu.address.commons.util.StringUtil; +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.model.person.KeywordPredicate; + +/** + * Tests that a {@code client}'s {@code Address} matches any of the keywords given. + */ +public class AddressClientContainsKeywordsPredicate implements KeywordPredicate { + private final List keywords; + + public AddressClientContainsKeywordsPredicate(List keywords) { + this.keywords = keywords; + } + + @Override + public boolean test(Client client) { + return keywords.stream() + .anyMatch(keyword -> StringUtil.containsPartialWordIgnoreCase(client.getAddress().value, keyword)); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof AddressClientContainsKeywordsPredicate)) { + return false; + } + + AddressClientContainsKeywordsPredicate otherPredicate = (AddressClientContainsKeywordsPredicate) other; + return keywords.equals(otherPredicate.keywords); + } + + @Override + public String toString() { + return new ToStringBuilder(this).add("keywords", keywords).toString(); + } +} diff --git a/src/main/java/seedu/address/model/client/Client.java b/src/main/java/seedu/address/model/client/Client.java new file mode 100644 index 00000000000..67cfad3bae0 --- /dev/null +++ b/src/main/java/seedu/address/model/client/Client.java @@ -0,0 +1,96 @@ +package seedu.address.model.client; + +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DATEJOINED; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DEADLINE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DESCRIPTION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_GITHUBID; +import static seedu.address.logic.parser.CliSyntax.PREFIX_RATING; +import static seedu.address.logic.parser.CliSyntax.PREFIX_SALARY; + +import java.util.Objects; +import java.util.Set; + +import seedu.address.logic.parser.Prefix; +import seedu.address.model.commons.Name; +import seedu.address.model.person.Address; +import seedu.address.model.person.Email; +import seedu.address.model.person.Person; +import seedu.address.model.person.Phone; + +/** + * Represents a Client in the address book, extending the Developer class. + * Guarantees: details are present and not null, field values are validated, immutable. + */ +public class Client extends Person { + public static final Prefix[] UNUSED_PREFIXES = new Prefix[]{PREFIX_DATEJOINED, PREFIX_SALARY, PREFIX_RATING, + PREFIX_GITHUBID, PREFIX_DESCRIPTION, PREFIX_DEADLINE}; + private final Name organisation; + private final Document document; + private final ClientRoles role; + + /** + * Every field must be present and not null. + */ + public Client(Name name, Phone phone, Email email, Address address, ClientRoles role, Set projects, + Name organisation, Document document) { + super(name, phone, email, address, projects); + requireAllNonNull(organisation, document); + this.organisation = organisation; + this.document = document; + this.role = role; + } + + public Name getOrganisation() { + return organisation; + } + + public Document getDocument() { + return document; + } + + public ClientRoles getRole() { + return role; + } + + /** + * Returns true if the current client has the same name as the provided {@code otherClient}. + * This is used for checking if two clients are the same, ignoring their other attributes. + * + * @param otherClient The other client to compare with. + * @return `true` if the current client has the same name as the provided `otherClient`, `false` otherwise. + */ + public boolean isSameClient(Client otherClient) { + if (otherClient == this) { + return true; + } + + return otherClient != null + && otherClient.getName().fullName.toLowerCase().equals(getName().fullName.toLowerCase()); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof Client)) { + return false; + } + + Client otherClient = (Client) other; + return super.equals(otherClient) && organisation.equals(otherClient.organisation) + && document.equals(otherClient.document) && role.equals(otherClient.role); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), organisation, document); + } + + @Override + public String toString() { + return super.toString() + " Organisation: " + organisation + " Document: " + document; + } +} diff --git a/src/main/java/seedu/address/model/client/ClientRoles.java b/src/main/java/seedu/address/model/client/ClientRoles.java new file mode 100644 index 00000000000..c7c549292a4 --- /dev/null +++ b/src/main/java/seedu/address/model/client/ClientRoles.java @@ -0,0 +1,249 @@ +package seedu.address.model.client; + +import static java.util.Objects.requireNonNull; + +import java.io.BufferedReader; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +import javafx.collections.ObservableList; +import seedu.address.model.Model; + +/** + * Represents a Developer's role in the company. + * Guarantees: immutable; is valid as declared in {@link #isValidRole(String)} + */ +public class ClientRoles { + public static final String NO_SUCH_CLIENT_ROLE = "There is no such client role, " + + "please create role before proceeding!"; + private static List roles = new ArrayList<>(); + private static boolean noRepeat; + private static boolean notDefault; + private static boolean notInList; + private static String listOfRoles; + + static { + roles.add(new ClientRoles("Manager")); + roles.add(new ClientRoles("Developer")); + roles.add(new ClientRoles("HR")); + roles.add(new ClientRoles("Client")); + loadClientRoles(); + } + + public final String role; + + /** + * Constructs a {@code Role}. + * + * @param role A valid role. + */ + public ClientRoles(String role) { + requireNonNull(role); + this.role = role; + } + + /** + * Adds a client role to the list of client roles. + * + * @param role The client role to be added. + */ + public static void addClientRole(ClientRoles role) { + roles.add(role); + saveClientRoles(); + } + + /** + * Deletes a client role from the list of client roles. + * + * @param role The client role to be deleted. + */ + public static void deleteClientRole(ClientRoles role) { + roles.remove(role); + saveClientRoles(); + } + + /** + * Returns true if a given string is a valid role. + * + * @param role The role to check for validity. + * @return {@code true} if the role is valid, {@code false} otherwise. + */ + public static boolean isValidRole(String role) { + boolean roleExists = false; + for (ClientRoles cliRoles : roles) { + if (cliRoles.toString().equals(role)) { + roleExists = true; + break; // You can break early once a match is found + } + } + return roleExists; + } + + /** + * Returns a string representation of the list of client roles. + * + * @return A string representation of the client roles. + */ + public static String printRoles() { + listOfRoles = roles.toString(); + return listOfRoles; + } + + /** + * Checks if a role can be removed based on specific criteria. + * + * @param model The model for checking if the role is removable. + * @param role The role to check for removal. + * @return {@code true} if the role can be removed, {@code false} otherwise. + */ + public static boolean isRemovableRole(Model model, String role) { + // check if anyone is using this role + ObservableList clientsList = model.getAddressBook().getClientList(); + + Predicate rolePredicate = client -> client.getRole().toString().equals(role); + + List clientsWithRole = clientsList.stream() + .filter(rolePredicate) + .collect(Collectors.toList()); + + if (clientsWithRole.isEmpty()) { + noRepeat = true; + } else { + noRepeat = false; + } + + // check if this role is one of the defaults + if (role.equals("Manager") + || role.equals("Developer") + || role.equals("HR") + || role.equals("Client")) { + notDefault = false; + } else { + notDefault = true; + } + + // check if role is in the list + boolean roleExists = isValidRole(role); + + if (roleExists) { + notInList = false; + } else { + notInList = true; + } + + if (noRepeat && notDefault && !notInList) { + return false; + } else { + return true; + } + } + + /** + * Saves the list of client roles to a text file. + */ + public static void saveClientRoles() { + try { + // Save roles to a text file + try (FileWriter writer = new FileWriter("ClientRoles.txt")) { + for (ClientRoles role : roles) { + writer.write(role.toString() + "\n"); + } + } + } catch (IOException e) { + e.printStackTrace(); + } + } + + /** + * Loads the list of client roles from a text file. + */ + public static void loadClientRoles() { + try (BufferedReader reader = new BufferedReader(new FileReader("ClientRoles.txt"))) { + String line; + int lineCount = 1; + + while ((line = reader.readLine()) != null) { + if (lineCount >= 5) { + roles.add(new ClientRoles(line)); + } + lineCount++; + } + } catch (IOException e) { + saveClientRoles(); + } + + } + + /** + * Checks if the role is not one of the default roles. + * + * @return {@code true} if the role is not a default role, {@code false} otherwise. + */ + public static boolean isNotDefault() { + return notDefault; + } + + /** + * Checks if the role is not repeated. + * + * @return {@code true} if the role is not repeated, {@code false} otherwise. + */ + public static boolean isNoRepeat() { + return noRepeat; + } + + /** + * Checks if the role is not in the list of client roles. + * + * @return {@code true} if the role is not in the list, {@code false} otherwise. + */ + public static boolean isNotInList() { + return notInList; + } + + /** + * Returns a string representation of this client role. + * + * @return A string representation of this client role. + */ + @Override + public String toString() { + return role; + } + + /** + * Checks if this client role is equal to another object. + * + * @param other The object to compare to. + * @return {@code true} if the client roles are equal, {@code false} otherwise. + */ + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof ClientRoles)) { + return false; + } + + ClientRoles otherRole = (ClientRoles) other; + return role.equals(otherRole.role); + } + + /** + * Returns the hash code for this client role. + * + * @return The hash code for this client role. + */ + @Override + public int hashCode() { + return role.hashCode(); + } +} diff --git a/src/main/java/seedu/address/model/client/Document.java b/src/main/java/seedu/address/model/client/Document.java new file mode 100644 index 00000000000..516ed5fb56f --- /dev/null +++ b/src/main/java/seedu/address/model/client/Document.java @@ -0,0 +1,57 @@ +package seedu.address.model.client; + +/** + * Represents a document with a URL, ensuring it is a valid URL. + * Guarantees: immutable; is valid as declared in {@link #isValidUrl(String)} + */ +public class Document { + + public static final String MESSAGE_CONSTRAINTS = "Invalid URL format!"; + + // Regular expression to match a valid URL + public static final String VALIDATION_REGEX = + "(https:\\/\\/www\\.|http:\\/\\/www\\.|https:\\/\\/|http:\\/\\/)" + + "?[a-zA-Z0-9\\/]{2,}(\\.[a-zA-Z0-9\\/]{2,})(\\.[a-zA-Z0-9\\/]{2,})?"; + + private final String url; + + /** + * Constructs a {@code Document} with the specified URL. + * + * @param url The URL of the document. + */ + public Document(String url) { + this.url = url; + } + + /** + * Returns true if a given string is a valid URL. + */ + public static boolean isValidUrl(String test) { + return test.matches(VALIDATION_REGEX); + } + + @Override + public String toString() { + return url; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof Document)) { + return false; + } + + Document otherDocument = (Document) other; + return url.equals(otherDocument.url); + } + + @Override + public int hashCode() { + return url.hashCode(); + } +} diff --git a/src/main/java/seedu/address/model/client/DocumentContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/client/DocumentContainsKeywordsPredicate.java new file mode 100644 index 00000000000..8505c5729b4 --- /dev/null +++ b/src/main/java/seedu/address/model/client/DocumentContainsKeywordsPredicate.java @@ -0,0 +1,46 @@ +package seedu.address.model.client; + +import java.util.List; + +import seedu.address.commons.util.StringUtil; +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.model.person.KeywordPredicate; + +/** + * Tests that a {@code Developer}'s {@code Document} matches any of the keywords given. + */ +public class DocumentContainsKeywordsPredicate implements KeywordPredicate { + private final List keywords; + + public DocumentContainsKeywordsPredicate(List keywords) { + this.keywords = keywords; + } + + @Override + public boolean test(Client client) { + return keywords.stream() + .anyMatch(keyword -> + StringUtil.containsPartialWordIgnoreCase(client.getDocument().toString(), keyword)); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof DocumentContainsKeywordsPredicate)) { + return false; + } + + DocumentContainsKeywordsPredicate otherDocumentContainsKeywordsPredicate = + (DocumentContainsKeywordsPredicate) other; + return keywords.equals(otherDocumentContainsKeywordsPredicate.keywords); + } + + @Override + public String toString() { + return new ToStringBuilder(this).add("keywords", keywords).toString(); + } +} diff --git a/src/main/java/seedu/address/model/client/EmailClientContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/client/EmailClientContainsKeywordsPredicate.java new file mode 100644 index 00000000000..a6debdcf245 --- /dev/null +++ b/src/main/java/seedu/address/model/client/EmailClientContainsKeywordsPredicate.java @@ -0,0 +1,45 @@ +package seedu.address.model.client; + +import java.util.List; + +import seedu.address.commons.util.StringUtil; +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.model.person.KeywordPredicate; + +/** + * Tests that a {@code Client}'s {@code Email} matches any of the keywords given. + */ +public class EmailClientContainsKeywordsPredicate implements KeywordPredicate { + private final List keywords; + + public EmailClientContainsKeywordsPredicate(List keywords) { + this.keywords = keywords; + } + + @Override + public boolean test(Client client) { + return keywords.stream() + .anyMatch(keyword -> StringUtil.containsPartialWordIgnoreCase(client.getEmail().value, keyword)); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof seedu.address.model.client.EmailClientContainsKeywordsPredicate)) { + return false; + } + + seedu.address.model.client.EmailClientContainsKeywordsPredicate otherPredicate = + (seedu.address.model.client.EmailClientContainsKeywordsPredicate) other; + return keywords.equals(otherPredicate.keywords); + } + + @Override + public String toString() { + return new ToStringBuilder(this).add("keywords", keywords).toString(); + } +} diff --git a/src/main/java/seedu/address/model/client/NameClientContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/client/NameClientContainsKeywordsPredicate.java new file mode 100644 index 00000000000..7d812840dfb --- /dev/null +++ b/src/main/java/seedu/address/model/client/NameClientContainsKeywordsPredicate.java @@ -0,0 +1,45 @@ +package seedu.address.model.client; + +import java.util.List; + +import seedu.address.commons.util.StringUtil; +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.model.person.KeywordPredicate; + +/** + * Tests that a {@code Client}'s {@code Name} matches any of the keywords given. + */ +public class NameClientContainsKeywordsPredicate implements KeywordPredicate { + private final List keywords; + + public NameClientContainsKeywordsPredicate(List keywords) { + this.keywords = keywords; + } + + @Override + public boolean test(Client client) { + return keywords.stream() + .anyMatch(keyword -> StringUtil.containsPartialWordIgnoreCase(client.getName().fullName, keyword)); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof seedu.address.model.client.NameClientContainsKeywordsPredicate)) { + return false; + } + + seedu.address.model.client.NameClientContainsKeywordsPredicate otherNameContainsKeywordsPredicate = + (seedu.address.model.client.NameClientContainsKeywordsPredicate) other; + return keywords.equals(otherNameContainsKeywordsPredicate.keywords); + } + + @Override + public String toString() { + return new ToStringBuilder(this).add("keywords", keywords).toString(); + } +} diff --git a/src/main/java/seedu/address/model/client/OrganisationContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/client/OrganisationContainsKeywordsPredicate.java new file mode 100644 index 00000000000..f84c2aaa3bc --- /dev/null +++ b/src/main/java/seedu/address/model/client/OrganisationContainsKeywordsPredicate.java @@ -0,0 +1,46 @@ +package seedu.address.model.client; + +import java.util.List; + +import seedu.address.commons.util.StringUtil; +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.model.person.KeywordPredicate; + +/** + * Tests that a {@code Developer}'s {@code Organisation} matches any of the keywords given. + */ +public class OrganisationContainsKeywordsPredicate implements KeywordPredicate { + private final List keywords; + + public OrganisationContainsKeywordsPredicate(List keywords) { + this.keywords = keywords; + } + + @Override + public boolean test(Client client) { + return keywords.stream() + .anyMatch(keyword -> + StringUtil.containsPartialWordIgnoreCase(client.getOrganisation().toString(), keyword)); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof OrganisationContainsKeywordsPredicate)) { + return false; + } + + OrganisationContainsKeywordsPredicate otherOrganisationContainsKeywordsPredicate = + (OrganisationContainsKeywordsPredicate) other; + return keywords.equals(otherOrganisationContainsKeywordsPredicate.keywords); + } + + @Override + public String toString() { + return new ToStringBuilder(this).add("keywords", keywords).toString(); + } +} diff --git a/src/main/java/seedu/address/model/client/PhoneClientContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/client/PhoneClientContainsKeywordsPredicate.java new file mode 100644 index 00000000000..dad21ebb016 --- /dev/null +++ b/src/main/java/seedu/address/model/client/PhoneClientContainsKeywordsPredicate.java @@ -0,0 +1,45 @@ +package seedu.address.model.client; + +import java.util.List; + +import seedu.address.commons.util.StringUtil; +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.model.person.KeywordPredicate; + +/** + * Tests that a {@code Client}'s {@code Phone} matches any of the keywords given. + */ +public class PhoneClientContainsKeywordsPredicate implements KeywordPredicate { + private final List keywords; + + public PhoneClientContainsKeywordsPredicate(List keywords) { + this.keywords = keywords; + } + + @Override + public boolean test(Client client) { + return keywords.stream() + .anyMatch(keyword -> StringUtil.containsPartialWordIgnoreCase(client.getPhone().value, keyword)); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof seedu.address.model.client.PhoneClientContainsKeywordsPredicate)) { + return false; + } + + seedu.address.model.client.PhoneClientContainsKeywordsPredicate otherPredicate = + (seedu.address.model.client.PhoneClientContainsKeywordsPredicate) other; + return keywords.equals(otherPredicate.keywords); + } + + @Override + public String toString() { + return new ToStringBuilder(this).add("keywords", keywords).toString(); + } +} diff --git a/src/main/java/seedu/address/model/client/ProjectClientContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/client/ProjectClientContainsKeywordsPredicate.java new file mode 100644 index 00000000000..87cb8abf912 --- /dev/null +++ b/src/main/java/seedu/address/model/client/ProjectClientContainsKeywordsPredicate.java @@ -0,0 +1,45 @@ +package seedu.address.model.client; + +import java.util.List; + +import seedu.address.commons.util.StringUtil; +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.model.person.KeywordPredicate; + +/** + * Tests that a {@code Client}'s associated {@code Project} names match any of the keywords given. + */ +public class ProjectClientContainsKeywordsPredicate implements KeywordPredicate { + private final List keywords; + + public ProjectClientContainsKeywordsPredicate(List keywords) { + this.keywords = keywords; + } + + @Override + public boolean test(Client client) { + return keywords.stream().allMatch(keyword -> + StringUtil.containsPartialWordIgnoreCase(client.getProjects().toString(), keyword)); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof seedu.address.model.client.ProjectClientContainsKeywordsPredicate)) { + return false; + } + + seedu.address.model.client.ProjectClientContainsKeywordsPredicate otherPredicate = + (seedu.address.model.client.ProjectClientContainsKeywordsPredicate) other; + return keywords.equals(otherPredicate.keywords); + } + + @Override + public String toString() { + return new ToStringBuilder(this).add("keywords", keywords).toString(); + } +} diff --git a/src/main/java/seedu/address/model/client/RoleClientContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/client/RoleClientContainsKeywordsPredicate.java new file mode 100644 index 00000000000..2d381935aa0 --- /dev/null +++ b/src/main/java/seedu/address/model/client/RoleClientContainsKeywordsPredicate.java @@ -0,0 +1,45 @@ +package seedu.address.model.client; + +import java.util.List; + +import seedu.address.commons.util.StringUtil; +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.model.person.KeywordPredicate; + +/** + * Tests that a {@code Client}'s {@code Role} matches any of the keywords given. + */ +public class RoleClientContainsKeywordsPredicate implements KeywordPredicate { + private final List keywords; + + public RoleClientContainsKeywordsPredicate(List keywords) { + this.keywords = keywords; + } + + @Override + public boolean test(Client client) { + return keywords.stream() + .anyMatch(keyword -> StringUtil.containsPartialWordIgnoreCase(client.getRole().toString(), keyword)); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof RoleClientContainsKeywordsPredicate)) { + return false; + } + + RoleClientContainsKeywordsPredicate otherRoleContainsKeywordsPredicate = + (RoleClientContainsKeywordsPredicate) other; + return keywords.equals(otherRoleContainsKeywordsPredicate.keywords); + } + + @Override + public String toString() { + return new ToStringBuilder(this).add("keywords", keywords).toString(); + } +} diff --git a/src/main/java/seedu/address/model/client/UniqueClientList.java b/src/main/java/seedu/address/model/client/UniqueClientList.java new file mode 100644 index 00000000000..f823ffcd98a --- /dev/null +++ b/src/main/java/seedu/address/model/client/UniqueClientList.java @@ -0,0 +1,170 @@ +package seedu.address.model.client; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; + +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import seedu.address.model.person.exceptions.ClientNotFoundException; +import seedu.address.model.person.exceptions.DuplicateClientException; + +/** + * A list of clients that enforces uniqueness between its elements and does not allow nulls. + * A client is considered unique by comparing using {@code Client#isSameClient(Client)}. + * As such, adding and updating of clients use Client#isSameClient(Client) + * for equality to ensure that the client being added or updated + * is unique in terms of identity in the UniqueClientList. + * However, the removal of a client uses Client#equals(Object) to + * ensure that the client with exactly the same fields will be removed. + *

+ * Supports a minimal set of list operations. + * + * @see Client#isSameClient(Client) + */ +public class UniqueClientList implements Iterable { + + private final ObservableList internalList = FXCollections.observableArrayList(); + private final ObservableList internalUnmodifiableList = + FXCollections.unmodifiableObservableList(internalList); + + /** + * Returns true if the list contains an equivalent client as the given argument. + */ + public boolean contains(Client toCheck) { + requireNonNull(toCheck); + return internalList.stream().anyMatch(toCheck::isSameClient); + } + + /** + * Adds a client to the list. + * The client must not already exist in the list. + */ + public void add(Client toAdd) { + requireNonNull(toAdd); + if (contains(toAdd)) { + throw new DuplicateClientException(); + } + internalList.add(toAdd); + } + + /** + * Replaces the client {@code target} in the list with {@code editedClient}. + * {@code target} must exist in the list. + * The client identity of {@code editedClient} must not be the same as another existing client in the list. + */ + public void setClient(Client target, Client editedClient) { + requireAllNonNull(target, editedClient); + + int index = internalList.indexOf(target); + if (index == -1) { + throw new ClientNotFoundException(); + } + + if (!target.isSameClient(editedClient) && contains(editedClient)) { + throw new DuplicateClientException(); + } + + internalList.set(index, editedClient); + } + + /** + * Removes the equivalent client from the list. + * The client must exist in the list. + */ + public void remove(Client toRemove) { + requireNonNull(toRemove); + if (!internalList.remove(toRemove)) { + throw new ClientNotFoundException(); + } + } + + public void setClients(UniqueClientList replacement) { + requireNonNull(replacement); + internalList.setAll(replacement.internalList); + } + + /** + * Replaces the contents of this list with {@code clients}. + * {@code clients} must not contain duplicate clients. + */ + public void setClients(List clients) { + requireAllNonNull(clients); + if (!clientsAreUnique(clients)) { + throw new DuplicateClientException(); + } + + internalList.setAll(clients); + } + + /** + * Returns the backing list as an unmodifiable {@code ObservableList}. + */ + public ObservableList asUnmodifiableObservableList() { + return internalUnmodifiableList; + } + + /** + * Updates the projects of all clients by removing a specific project. + * + * @param project The project to be removed from the clients' projects. + */ + public void updateClientProjects(String project) { + iterator().forEachRemaining( + client -> { + Set newprojectset = new HashSet<>(client.getProjects()); + newprojectset.remove(project); + setClient(client, new Client(client.getName(), client.getPhone(), client.getEmail(), + client.getAddress(), client.getRole(), newprojectset, + client.getOrganisation(), client.getDocument())); + }); + } + + @Override + public Iterator iterator() { + return internalList.iterator(); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof UniqueClientList)) { + return false; + } + + UniqueClientList otherUniqueClientList = (UniqueClientList) other; + return internalList.equals(otherUniqueClientList.internalList); + } + + @Override + public int hashCode() { + return internalList.hashCode(); + } + + @Override + public String toString() { + return internalList.toString(); + } + + /** + * Returns true if {@code clients} contains only unique clients. + */ + private boolean clientsAreUnique(List clients) { + for (int i = 0; i < clients.size() - 1; i++) { + for (int j = i + 1; j < clients.size(); j++) { + if (clients.get(i).isSameClient(clients.get(j))) { + return false; + } + } + } + return true; + } +} diff --git a/src/main/java/seedu/address/model/commons/Date.java b/src/main/java/seedu/address/model/commons/Date.java new file mode 100644 index 00000000000..bb8acc312c6 --- /dev/null +++ b/src/main/java/seedu/address/model/commons/Date.java @@ -0,0 +1,82 @@ +package seedu.address.model.commons; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +import java.text.ParseException; +import java.text.SimpleDateFormat; + +/** + * Represents the date a developer Joined. In the format: dd-MM-YYYY + */ +public class Date { + public static final String MESSAGE_CONSTRAINTS = + "Date should be of the format dd-MM-yyyy! Eg: 31-12-2019\n" + + "For Developers: Date joined should not be in the future."; + public static final String VALIDATION_REGEX = "[0-3]\\d-[01]\\d-\\d{4}"; + public final java.util.Date value; + public final boolean allowFuture; + + /** + * Constructs a {@code DateJoined}. + * + * @param date A valid date string. + */ + public Date(String date, boolean allowFuture) { + requireNonNull(date); + this.allowFuture = allowFuture; + checkArgument(isValidDate(date, allowFuture), MESSAGE_CONSTRAINTS); + try { + value = new SimpleDateFormat("dd-MM-yyyy").parse(date); + } catch (ParseException e) { + throw new RuntimeException(e); + } + + } + + /** + * Returns true if a given string is a valid date. + */ + public static boolean isValidDate(String text, boolean allowFuture) { + if (text == null || !text.matches(VALIDATION_REGEX)) { + return false; + } + SimpleDateFormat df = new SimpleDateFormat("dd-MM-yyyy"); + df.setLenient(false); + try { + java.util.Date date = df.parse(text); + if (date.after(new java.util.Date()) && !allowFuture) { + return false; + } + return true; + } catch (ParseException ex) { + return false; + } + } + + @Override + public String toString() { + return new SimpleDateFormat("dd-MM-yyyy").format(value); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof Date)) { + return false; + } + + Date otherDate = (Date) other; + return value.equals(otherDate.value); + } + + @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/commons/Name.java similarity index 92% rename from src/main/java/seedu/address/model/person/Name.java rename to src/main/java/seedu/address/model/commons/Name.java index 173f15b9b00..bba418dd3bd 100644 --- a/src/main/java/seedu/address/model/person/Name.java +++ b/src/main/java/seedu/address/model/commons/Name.java @@ -1,16 +1,16 @@ -package seedu.address.model.person; +package seedu.address.model.commons; 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 Developer's name in the address book. * Guarantees: immutable; is valid as declared in {@link #isValidName(String)} */ public class Name { public static final String MESSAGE_CONSTRAINTS = - "Names should only contain alphanumeric characters and spaces, and it should not be blank"; + "Names should only contain alphanumeric characters and spaces, and it should not be blank!"; /* * The first character of the address must not be a whitespace, diff --git a/src/main/java/seedu/address/model/developer/AddressDeveloperContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/developer/AddressDeveloperContainsKeywordsPredicate.java new file mode 100644 index 00000000000..f8300eecc5e --- /dev/null +++ b/src/main/java/seedu/address/model/developer/AddressDeveloperContainsKeywordsPredicate.java @@ -0,0 +1,44 @@ +package seedu.address.model.developer; + +import java.util.List; + +import seedu.address.commons.util.StringUtil; +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.model.person.KeywordPredicate; + +/** + * Tests that a {@code Developer}'s {@code Address} matches any of the keywords given. + */ +public class AddressDeveloperContainsKeywordsPredicate implements KeywordPredicate { + private final List keywords; + + public AddressDeveloperContainsKeywordsPredicate(List keywords) { + this.keywords = keywords; + } + + @Override + public boolean test(Developer developer) { + return keywords.stream() + .anyMatch(keyword -> StringUtil.containsPartialWordIgnoreCase(developer.getAddress().value, keyword)); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof AddressDeveloperContainsKeywordsPredicate)) { + return false; + } + + AddressDeveloperContainsKeywordsPredicate otherPredicate = (AddressDeveloperContainsKeywordsPredicate) other; + return keywords.equals(otherPredicate.keywords); + } + + @Override + public String toString() { + return new ToStringBuilder(this).add("keywords", keywords).toString(); + } +} diff --git a/src/main/java/seedu/address/model/developer/CombinedDeveloperPredicate.java b/src/main/java/seedu/address/model/developer/CombinedDeveloperPredicate.java new file mode 100644 index 00000000000..9c896cf9452 --- /dev/null +++ b/src/main/java/seedu/address/model/developer/CombinedDeveloperPredicate.java @@ -0,0 +1,78 @@ +package seedu.address.model.developer; + +import java.util.function.Predicate; + +/** + * A combined predicate for filtering developers based on multiple criteria. + * This class combines individual predicates for filtering by name, role, address, date joined, email, phone, + * projects, salary, rating, and GitHub ID. + * It tests whether a developer satisfies all the specified criteria. + */ +public class CombinedDeveloperPredicate implements Predicate { + private final Predicate namePredicate; + private final Predicate rolePredicate; + private final Predicate addressPredicate; + private final Predicate dateJoinedPredicate; + private final Predicate emailPredicate; + private final Predicate phonePredicate; + private final Predicate projectPredicate; + private final Predicate salaryPredicate; + private final Predicate ratingPredicate; + private final Predicate githubIdPredicate; + + /** + * Constructs a CombinedDeveloperPredicate with individual predicates for each filtering criterion. + * + * @param namePredicate Predicate for filtering by name. + * @param rolePredicate Predicate for filtering by role. + * @param addressPredicate Predicate for filtering by address. + * @param dateJoinedPredicate Predicate for filtering by date joined. + * @param emailPredicate Predicate for filtering by email. + * @param phonePredicate Predicate for filtering by phone. + * @param projectPredicate Predicate for filtering by projects. + * @param salaryPredicate Predicate for filtering by salary. + * @param ratingPredicate Predicate for filtering by rating. + * @param githubIdPredicate Predicate for filtering by GitHub ID. + */ + public CombinedDeveloperPredicate(Predicate namePredicate, + Predicate rolePredicate, + Predicate addressPredicate, + Predicate dateJoinedPredicate, + Predicate emailPredicate, + Predicate phonePredicate, + Predicate projectPredicate, + Predicate salaryPredicate, + Predicate ratingPredicate, + Predicate githubIdPredicate) { + this.namePredicate = namePredicate; + this.rolePredicate = rolePredicate; + this.addressPredicate = addressPredicate; + this.dateJoinedPredicate = dateJoinedPredicate; + this.emailPredicate = emailPredicate; + this.phonePredicate = phonePredicate; + this.projectPredicate = projectPredicate; + this.salaryPredicate = salaryPredicate; + this.ratingPredicate = ratingPredicate; + this.githubIdPredicate = githubIdPredicate; + } + + /** + * Tests whether a developer satisfies all the specified criteria. + * + * @param developer The developer to be tested. + * @return True if the developer satisfies all the specified criteria, false otherwise. + */ + @Override + public boolean test(Developer developer) { + return namePredicate.test(developer) + && rolePredicate.test(developer) + && addressPredicate.test(developer) + && dateJoinedPredicate.test(developer) + && emailPredicate.test(developer) + && phonePredicate.test(developer) + && projectPredicate.test(developer) + && salaryPredicate.test(developer) + && ratingPredicate.test(developer) + && githubIdPredicate.test(developer); + } +} diff --git a/src/main/java/seedu/address/model/developer/DateJoinedContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/developer/DateJoinedContainsKeywordsPredicate.java new file mode 100644 index 00000000000..02516010a32 --- /dev/null +++ b/src/main/java/seedu/address/model/developer/DateJoinedContainsKeywordsPredicate.java @@ -0,0 +1,45 @@ +package seedu.address.model.developer; + +import java.util.List; + +import seedu.address.commons.util.StringUtil; +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.model.person.KeywordPredicate; + +/** + * Tests that a {@code Developer}'s {@code DateJoined} matches any of the keywords given. + */ +public class DateJoinedContainsKeywordsPredicate implements KeywordPredicate { + private final List keywords; + + public DateJoinedContainsKeywordsPredicate(List keywords) { + this.keywords = keywords; + } + + @Override + public boolean test(Developer developer) { + return keywords.stream() + .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(developer.getDateJoined().toString(), keyword)); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof DateJoinedContainsKeywordsPredicate)) { + return false; + } + + DateJoinedContainsKeywordsPredicate otherDateJoinedContainsKeywordsPredicate = + (DateJoinedContainsKeywordsPredicate) other; + return keywords.equals(otherDateJoinedContainsKeywordsPredicate.keywords); + } + + @Override + public String toString() { + return new ToStringBuilder(this).add("keywords", keywords).toString(); + } +} diff --git a/src/main/java/seedu/address/model/developer/Developer.java b/src/main/java/seedu/address/model/developer/Developer.java new file mode 100644 index 00000000000..711f412146f --- /dev/null +++ b/src/main/java/seedu/address/model/developer/Developer.java @@ -0,0 +1,112 @@ +package seedu.address.model.developer; + + +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DEADLINE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DESCRIPTION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DOCUMENT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ORGANISATION; + +import java.util.Objects; +import java.util.Set; + +import seedu.address.logic.parser.Prefix; +import seedu.address.model.commons.Date; +import seedu.address.model.commons.Name; +import seedu.address.model.person.Address; +import seedu.address.model.person.Email; +import seedu.address.model.person.Person; +import seedu.address.model.person.Phone; + +/** + * Represents a Developer in the address book, extending the Developer class. + * Guarantees: details are present and not null, field values are validated, immutable. + */ +public class Developer extends Person { + public static final Prefix[] UNUSED_PREFIXES = new Prefix[]{PREFIX_ORGANISATION, PREFIX_DOCUMENT, + PREFIX_DESCRIPTION, PREFIX_DEADLINE}; + private final Salary salary; + private final Date dateJoined; + private final GithubId githubId; + private final Rating rating; + private final DeveloperRoles role; + + /** + * Every field must be present and not null. + */ + public Developer(Name name, Phone phone, Email email, Address address, DeveloperRoles role, Set projects, + Salary salary, Date dateJoined, GithubId githubId, Rating rating) { + super(name, phone, email, address, projects); + requireAllNonNull(salary, dateJoined, githubId, rating); + this.salary = salary; + this.dateJoined = dateJoined; + this.githubId = githubId; + this.rating = rating; + this.role = role; + } + + public Salary getSalary() { + return salary; + } + + public Date getDateJoined() { + return dateJoined; + } + + /** + * Checks if this developer is the same as another developer. + * Developers are considered the same if they have the same name. + * + * @param otherDeveloper The other developer to compare with. + * @return True if the developers are the same, false otherwise. + */ + public boolean isSameDeveloper(Developer otherDeveloper) { + if (otherDeveloper == this) { + return true; + } + + return otherDeveloper != null + && otherDeveloper.getName().fullName.toLowerCase().equals(getName().fullName.toLowerCase()); + } + + public GithubId getGithubId() { + return githubId; + } + + public Rating getRating() { + return rating; + } + + public DeveloperRoles getRole() { + return role; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof Developer)) { + return false; + } + + Developer otherDeveloper = (Developer) other; + return super.equals(otherDeveloper) && salary.equals(otherDeveloper.salary) + && dateJoined.equals(otherDeveloper.dateJoined) + && githubId.equals(otherDeveloper.githubId) + && rating.equals(otherDeveloper.rating) + && role.equals(otherDeveloper.role); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), salary, dateJoined, githubId, rating); + } + + @Override + public String toString() { + return super.toString() + " Salary: " + salary + " Date Joined: " + dateJoined + + " Github ID: " + githubId + " Rating: " + rating; + } +} diff --git a/src/main/java/seedu/address/model/developer/DeveloperRoles.java b/src/main/java/seedu/address/model/developer/DeveloperRoles.java new file mode 100644 index 00000000000..f3c1b990f87 --- /dev/null +++ b/src/main/java/seedu/address/model/developer/DeveloperRoles.java @@ -0,0 +1,216 @@ +package seedu.address.model.developer; + +import static java.util.Objects.requireNonNull; + +import java.io.BufferedReader; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +import javafx.collections.ObservableList; +import seedu.address.model.Model; + +/** + * Represents the roles a Developer can have in the company. + * Guarantees: immutable; is valid as declared in {@link #isValidRole(String)} + */ +public class DeveloperRoles { + public static final String NO_SUCH_DEVELOPER_ROLE = "There is no such developer role, " + + "please create role before proceeding!"; + private static List roles = new ArrayList<>(); + private static boolean noRepeat; + private static boolean notDefault; + private static boolean notInList; + private static String listOfRoles; + + static { + roles.add(new DeveloperRoles("Frontend Developer")); + roles.add(new DeveloperRoles("Backend Developer")); + roles.add(new DeveloperRoles("Developer")); + loadDeveloperRoles(); + } + + public final String role; + + /** + * Constructs a {@code DeveloperRoles}. + * + * @param role A valid role. + */ + public DeveloperRoles(String role) { + requireNonNull(role); + this.role = role; + } + + /** + * Adds a developer role to the list of developer roles. + * + * @param role The developer role to be added. + */ + public static void addDeveloperRole(DeveloperRoles role) { + roles.add(role); + saveDeveloperRoles(); + } + + /** + * Deletes a developer role from the list of developer roles. + * + * @param role The developer role to be deleted. + */ + public static void deleteDeveloperRole(DeveloperRoles role) { + roles.remove(role); + saveDeveloperRoles(); + } + + /** + * Returns true if a given string is a valid role. + * + * @param role The role to be checked. + * @return True if the role is valid, false otherwise. + */ + public static boolean isValidRole(String role) { + boolean roleExists = false; + for (DeveloperRoles devRoles : roles) { + if (devRoles.toString().equals(role)) { + roleExists = true; + break; // You can break early once a match is found + } + } + return roleExists; + } + + /** + * Retrieves a string representation of all developer roles. + * + * @return A string representing all developer roles. + */ + public static String printRoles() { + listOfRoles = roles.toString(); + return listOfRoles; + } + + /** + * Checks if a developer role can be removed. + * + * @param model The model to check against. + * @param role The role to be checked. + * @return True if the role can be removed, false otherwise. + */ + public static boolean isRemovableRole(Model model, String role) { + + ObservableList developerList = model.getAddressBook().getDeveloperList(); + + Predicate rolePredicate = developer -> developer.getRole().toString().equals(role); + + List developersWithRole = developerList.stream() + .filter(rolePredicate) + .collect(Collectors.toList()); + + if (developersWithRole.isEmpty()) { + noRepeat = true; + } else { + noRepeat = false; + } + + // check if this role is one of the defaults + if (role.equals("Frontend Developer") + || role.equals("Backend Developer") + || role.equals("Developer")) { + notDefault = false; + } else { + notDefault = true; + } + + // check if role is in the list + + boolean roleExists = isValidRole(role); + + if (roleExists) { + notInList = false; + } else { + notInList = true; + } + + if (noRepeat && notDefault && !notInList) { + return false; + } else { + return true; + } + } + + /** + * Saves developer roles to a text file. + */ + public static void saveDeveloperRoles() { + try { + // Save roles to a text file + try (FileWriter writer = new FileWriter("DeveloperRoles.txt")) { + for (DeveloperRoles role : roles) { + writer.write(role.toString() + "\n"); + } + } + } catch (IOException e) { + e.printStackTrace(); + } + } + + /** + * Loads developer roles from a file on initialization. + */ + public static void loadDeveloperRoles() { + try (BufferedReader reader = new BufferedReader(new FileReader("DeveloperRoles.txt"))) { + String line; + int lineCount = 1; + + while ((line = reader.readLine()) != null) { + if (lineCount >= 4) { + roles.add(new DeveloperRoles(line)); + } + lineCount++; + } + } catch (IOException e) { + e.printStackTrace(); + } + } + + public static boolean isNotDefault() { + return notDefault; + } + + public static boolean isNoRepeat() { + return noRepeat; + } + + public static boolean isNotInList() { + return notInList; + } + + @Override + public String toString() { + return role; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof DeveloperRoles)) { + return false; + } + + DeveloperRoles otherRole = (DeveloperRoles) other; + return role.equals(otherRole.role); + } + + @Override + public int hashCode() { + return role.hashCode(); + } +} diff --git a/src/main/java/seedu/address/model/developer/EmailDeveloperContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/developer/EmailDeveloperContainsKeywordsPredicate.java new file mode 100644 index 00000000000..ee67101feb0 --- /dev/null +++ b/src/main/java/seedu/address/model/developer/EmailDeveloperContainsKeywordsPredicate.java @@ -0,0 +1,44 @@ +package seedu.address.model.developer; + +import java.util.List; + +import seedu.address.commons.util.StringUtil; +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.model.person.KeywordPredicate; + +/** + * Tests that a {@code Developer}'s {@code Email} matches any of the keywords given. + */ +public class EmailDeveloperContainsKeywordsPredicate implements KeywordPredicate { + private final List keywords; + + public EmailDeveloperContainsKeywordsPredicate(List keywords) { + this.keywords = keywords; + } + + @Override + public boolean test(Developer developer) { + return keywords.stream() + .anyMatch(keyword -> StringUtil.containsPartialWordIgnoreCase(developer.getEmail().value, keyword)); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof EmailDeveloperContainsKeywordsPredicate)) { + return false; + } + + EmailDeveloperContainsKeywordsPredicate otherPredicate = (EmailDeveloperContainsKeywordsPredicate) other; + return keywords.equals(otherPredicate.keywords); + } + + @Override + public String toString() { + return new ToStringBuilder(this).add("keywords", keywords).toString(); + } +} diff --git a/src/main/java/seedu/address/model/developer/GithubId.java b/src/main/java/seedu/address/model/developer/GithubId.java new file mode 100644 index 00000000000..e14d01dc685 --- /dev/null +++ b/src/main/java/seedu/address/model/developer/GithubId.java @@ -0,0 +1,61 @@ +package seedu.address.model.developer; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +/** + * Represents a GitHub username with specific constraints. + * Guarantees: immutable; is valid as declared in {@link #isValidGithubId(String)} + */ +public class GithubId { + + public static final String MESSAGE_CONSTRAINTS = + "GitHub usernames should only contain alphanumeric characters or hyphens," + + " and should not violate length constraints!"; + + public static final String VALIDATION_REGEX = "^(?!-)[a-zA-Z0-9-]{1,38}[a-zA-Z0-9]$"; + + public final String username; + + /** + * Constructs a {@code GithubId}. + * + * @param username A valid GitHub username. + */ + public GithubId(String username) { + requireNonNull(username); + checkArgument(isValidGithubId(username), MESSAGE_CONSTRAINTS); + this.username = username; + } + + /** + * Returns true if a given string is a valid GitHub username. + */ + public static boolean isValidGithubId(String test) { + return test.matches(VALIDATION_REGEX); + } + + @Override + public String toString() { + return username; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof GithubId)) { + return false; + } + + GithubId otherId = (GithubId) other; + return username.equals(otherId.username); + } + + @Override + public int hashCode() { + return username.hashCode(); + } +} diff --git a/src/main/java/seedu/address/model/developer/GithubIdContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/developer/GithubIdContainsKeywordsPredicate.java new file mode 100644 index 00000000000..68414839553 --- /dev/null +++ b/src/main/java/seedu/address/model/developer/GithubIdContainsKeywordsPredicate.java @@ -0,0 +1,47 @@ +package seedu.address.model.developer; + +import java.util.List; + +import seedu.address.commons.util.StringUtil; +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.model.person.KeywordPredicate; + +/** + * Tests that a {@code Developer}'s {@code GithubId} matches any of the keywords given. + */ +public class GithubIdContainsKeywordsPredicate implements KeywordPredicate { + private final List keywords; + + public GithubIdContainsKeywordsPredicate(List keywords) { + this.keywords = keywords; + } + + @Override + public boolean test(Developer developer) { + // Assuming Developer has a getGithubId() method that returns the Github ID as a String. + return keywords.stream() + .anyMatch(keyword -> + StringUtil.containsPartialWordIgnoreCase(developer.getGithubId().toString(), keyword)); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof GithubIdContainsKeywordsPredicate)) { + return false; + } + + GithubIdContainsKeywordsPredicate otherGithubIdContainsKeywordsPredicate = + (GithubIdContainsKeywordsPredicate) other; + return keywords.equals(otherGithubIdContainsKeywordsPredicate.keywords); + } + + @Override + public String toString() { + return new ToStringBuilder(this).add("keywords", keywords).toString(); + } +} diff --git a/src/main/java/seedu/address/model/developer/NameDeveloperContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/developer/NameDeveloperContainsKeywordsPredicate.java new file mode 100644 index 00000000000..eff2f084726 --- /dev/null +++ b/src/main/java/seedu/address/model/developer/NameDeveloperContainsKeywordsPredicate.java @@ -0,0 +1,45 @@ +package seedu.address.model.developer; + +import java.util.List; + +import seedu.address.commons.util.StringUtil; +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.model.person.KeywordPredicate; + +/** + * Tests that a {@code Developer}'s {@code Name} matches any of the keywords given. + */ +public class NameDeveloperContainsKeywordsPredicate implements KeywordPredicate { + private final List keywords; + + public NameDeveloperContainsKeywordsPredicate(List keywords) { + this.keywords = keywords; + } + + @Override + public boolean test(Developer developer) { + return keywords.stream() + .anyMatch(keyword -> StringUtil.containsPartialWordIgnoreCase(developer.getName().fullName, keyword)); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof NameDeveloperContainsKeywordsPredicate)) { + return false; + } + + NameDeveloperContainsKeywordsPredicate otherNameContainsKeywordsPredicate = + (NameDeveloperContainsKeywordsPredicate) other; + return keywords.equals(otherNameContainsKeywordsPredicate.keywords); + } + + @Override + public String toString() { + return new ToStringBuilder(this).add("keywords", keywords).toString(); + } +} diff --git a/src/main/java/seedu/address/model/developer/PhoneDeveloperContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/developer/PhoneDeveloperContainsKeywordsPredicate.java new file mode 100644 index 00000000000..0266359c52f --- /dev/null +++ b/src/main/java/seedu/address/model/developer/PhoneDeveloperContainsKeywordsPredicate.java @@ -0,0 +1,44 @@ +package seedu.address.model.developer; + +import java.util.List; + +import seedu.address.commons.util.StringUtil; +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.model.person.KeywordPredicate; + +/** + * Tests that a {@code Developer}'s {@code Phone} matches any of the keywords given. + */ +public class PhoneDeveloperContainsKeywordsPredicate implements KeywordPredicate { + private final List keywords; + + public PhoneDeveloperContainsKeywordsPredicate(List keywords) { + this.keywords = keywords; + } + + @Override + public boolean test(Developer developer) { + return keywords.stream() + .anyMatch(keyword -> StringUtil.containsPartialWordIgnoreCase(developer.getPhone().value, keyword)); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof PhoneDeveloperContainsKeywordsPredicate)) { + return false; + } + + PhoneDeveloperContainsKeywordsPredicate otherPredicate = (PhoneDeveloperContainsKeywordsPredicate) other; + return keywords.equals(otherPredicate.keywords); + } + + @Override + public String toString() { + return new ToStringBuilder(this).add("keywords", keywords).toString(); + } +} diff --git a/src/main/java/seedu/address/model/developer/ProjectDeveloperContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/developer/ProjectDeveloperContainsKeywordsPredicate.java new file mode 100644 index 00000000000..fb12f49f441 --- /dev/null +++ b/src/main/java/seedu/address/model/developer/ProjectDeveloperContainsKeywordsPredicate.java @@ -0,0 +1,45 @@ +package seedu.address.model.developer; + +import java.util.List; + +import seedu.address.commons.util.StringUtil; +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.model.person.KeywordPredicate; + +/** + * Tests that a {@code Developer}'s associated {@code Project} names match any of the keywords given. + */ +public class ProjectDeveloperContainsKeywordsPredicate implements KeywordPredicate { + private final List keywords; + + public ProjectDeveloperContainsKeywordsPredicate(List keywords) { + this.keywords = keywords; + } + + @Override + public boolean test(Developer developer) { + return keywords.stream() + .allMatch(keyword -> + StringUtil.containsPartialWordIgnoreCase(developer.getProjects().toString(), keyword)); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof ProjectDeveloperContainsKeywordsPredicate)) { + return false; + } + + ProjectDeveloperContainsKeywordsPredicate otherPredicate = (ProjectDeveloperContainsKeywordsPredicate) other; + return keywords.equals(otherPredicate.keywords); + } + + @Override + public String toString() { + return new ToStringBuilder(this).add("keywords", keywords).toString(); + } +} diff --git a/src/main/java/seedu/address/model/developer/Rating.java b/src/main/java/seedu/address/model/developer/Rating.java new file mode 100644 index 00000000000..f71cff91824 --- /dev/null +++ b/src/main/java/seedu/address/model/developer/Rating.java @@ -0,0 +1,58 @@ +package seedu.address.model.developer; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +/** + * Represents a Developer's rating in the company. + * Guarantees: immutable; is valid as declared in {@link #isValidRating(String)} + */ +public class Rating { + public static final String MESSAGE_CONSTRAINTS = + "Rating should be between 0 and 5!"; + public static final String VALIDATION_REGEX = "[0-5](\\.[0-9]*)?"; + public final double rating; + + /** + * Constructs a {@code Rating}. + * + * @param rating A valid rating. + */ + public Rating(String rating) { + requireNonNull(rating); + checkArgument(isValidRating(rating), MESSAGE_CONSTRAINTS); + this.rating = Double.parseDouble(rating); + } + + /** + * Returns true if a given string is a valid salary. + */ + public static boolean isValidRating(String test) { + return test.matches(VALIDATION_REGEX) && Double.valueOf(test) <= 5 && Double.valueOf(test) >= 0; + } + + @Override + public String toString() { + return rating + ""; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof Rating)) { + return false; + } + + Rating otherRating = (Rating) other; + return rating == otherRating.rating; + } + + @Override + public int hashCode() { + return new Double(rating).hashCode(); + } +} diff --git a/src/main/java/seedu/address/model/developer/RatingContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/developer/RatingContainsKeywordsPredicate.java new file mode 100644 index 00000000000..fc0ae8f7dd7 --- /dev/null +++ b/src/main/java/seedu/address/model/developer/RatingContainsKeywordsPredicate.java @@ -0,0 +1,46 @@ +package seedu.address.model.developer; + +import java.util.List; + +import seedu.address.commons.util.StringUtil; +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.model.person.KeywordPredicate; + +/** + * Tests that a {@code Developer}'s {@code Rating} matches any of the keywords given. + */ +public class RatingContainsKeywordsPredicate implements KeywordPredicate { + private final List keywords; + + public RatingContainsKeywordsPredicate(List keywords) { + this.keywords = keywords; + } + + @Override + public boolean test(Developer developer) { + // Assuming Developer has a getRating() method that returns a Rating object + // and the Rating object has a toString() method that returns its string representation. + return keywords.stream() + .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(developer.getRating().toString(), keyword)); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof RatingContainsKeywordsPredicate)) { + return false; + } + + RatingContainsKeywordsPredicate otherRatingContainsKeywordsPredicate = (RatingContainsKeywordsPredicate) other; + return keywords.equals(otherRatingContainsKeywordsPredicate.keywords); + } + + @Override + public String toString() { + return new ToStringBuilder(this).add("keywords", keywords).toString(); + } +} diff --git a/src/main/java/seedu/address/model/developer/RoleDeveloperContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/developer/RoleDeveloperContainsKeywordsPredicate.java new file mode 100644 index 00000000000..aa19c80e0fb --- /dev/null +++ b/src/main/java/seedu/address/model/developer/RoleDeveloperContainsKeywordsPredicate.java @@ -0,0 +1,45 @@ +package seedu.address.model.developer; + +import java.util.List; + +import seedu.address.commons.util.StringUtil; +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.model.person.KeywordPredicate; + +/** + * Tests that a {@code Developer}'s {@code Role} matches any of the keywords given. + */ +public class RoleDeveloperContainsKeywordsPredicate implements KeywordPredicate { + private final List keywords; + + public RoleDeveloperContainsKeywordsPredicate(List keywords) { + this.keywords = keywords; + } + + @Override + public boolean test(Developer developer) { + return keywords.stream() + .anyMatch(keyword -> StringUtil.containsPartialWordIgnoreCase(developer.getRole().toString(), keyword)); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof RoleDeveloperContainsKeywordsPredicate)) { + return false; + } + + RoleDeveloperContainsKeywordsPredicate otherRoleContainsKeywordsPredicate = + (RoleDeveloperContainsKeywordsPredicate) other; + return keywords.equals(otherRoleContainsKeywordsPredicate.keywords); + } + + @Override + public String toString() { + return new ToStringBuilder(this).add("keywords", keywords).toString(); + } +} diff --git a/src/main/java/seedu/address/model/developer/Salary.java b/src/main/java/seedu/address/model/developer/Salary.java new file mode 100644 index 00000000000..8b68c0d67cf --- /dev/null +++ b/src/main/java/seedu/address/model/developer/Salary.java @@ -0,0 +1,61 @@ +package seedu.address.model.developer; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +/** + * Represents a Developer's salary in the company. + * Guarantees: immutable; is valid as declared in {@link #isValidSalary(String)} + */ +public class Salary { + + + public static final String MESSAGE_CONSTRAINTS = + "Salary must be a positive integer and should be at least 4 digits!"; + public static final String VALIDATION_REGEX = "\\d{4,}"; + public final int salary; + + /** + * Constructs a {@code Salary}. + * + * @param salary A valid salary. + */ + public Salary(String salary) { + requireNonNull(salary); + checkArgument(isValidSalary(salary), MESSAGE_CONSTRAINTS); + this.salary = Integer.parseInt(salary); + } + + /** + * Returns true if a given string is a valid salary. + */ + public static boolean isValidSalary(String test) { + return test.matches(VALIDATION_REGEX); + } + + @Override + public String toString() { + return salary + ""; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof Salary)) { + return false; + } + + Salary otherSalary = (Salary) other; + return salary == otherSalary.salary; + } + + @Override + public int hashCode() { + return new Integer(salary).hashCode(); + } + +} diff --git a/src/main/java/seedu/address/model/developer/SalaryContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/developer/SalaryContainsKeywordsPredicate.java new file mode 100644 index 00000000000..2a9e1a599a8 --- /dev/null +++ b/src/main/java/seedu/address/model/developer/SalaryContainsKeywordsPredicate.java @@ -0,0 +1,44 @@ +package seedu.address.model.developer; + +import java.util.List; + +import seedu.address.commons.util.StringUtil; +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.model.person.KeywordPredicate; + +/** + * Tests that a {@code Developer}'s {@code Salary} matches any of the keywords given. + */ +public class SalaryContainsKeywordsPredicate implements KeywordPredicate { + private final List keywords; + + public SalaryContainsKeywordsPredicate(List keywords) { + this.keywords = keywords; + } + + @Override + public boolean test(Developer developer) { + return keywords.stream() + .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(developer.getSalary().toString(), keyword)); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof SalaryContainsKeywordsPredicate)) { + return false; + } + + SalaryContainsKeywordsPredicate otherSalaryContainsKeywordsPredicate = (SalaryContainsKeywordsPredicate) other; + return keywords.equals(otherSalaryContainsKeywordsPredicate.keywords); + } + + @Override + public String toString() { + return new ToStringBuilder(this).add("keywords", keywords).toString(); + } +} diff --git a/src/main/java/seedu/address/model/developer/UniqueDeveloperList.java b/src/main/java/seedu/address/model/developer/UniqueDeveloperList.java new file mode 100644 index 00000000000..491b752f14c --- /dev/null +++ b/src/main/java/seedu/address/model/developer/UniqueDeveloperList.java @@ -0,0 +1,173 @@ +package seedu.address.model.developer; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; + +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import seedu.address.model.person.exceptions.DeveloperNotFoundException; +import seedu.address.model.person.exceptions.DuplicateDeveloperException; + +/** + * A list of developers that enforces uniqueness between its elements and does not allow nulls. + * A developer is considered unique by comparing using {@code Developer#isSameDeveloper(Developer)}. + * As such, adding and updating of developers use Developer#isSameDeveloper(Developer) + * for equality to ensure that the developer being added or updated is + * unique in terms of identity in the UniqueDeveloperList. + * However, the removal of a developer uses Developer#equals(Object) + * to ensure that the developer with exactly the same fields will be removed. + *

+ * Supports a minimal set of list operations. + * + * @see Developer#isSameDeveloper(Developer) + */ +public class UniqueDeveloperList implements Iterable { + + private final ObservableList internalList = FXCollections.observableArrayList(); + private final ObservableList internalUnmodifiableList = + FXCollections.unmodifiableObservableList(internalList); + + /** + * Returns true if the list contains an equivalent developer as the given argument. + */ + public boolean contains(Developer toCheck) { + requireNonNull(toCheck); + return internalList.stream().anyMatch(toCheck::isSameDeveloper); + } + + /** + * Adds a developer to the list. + * The developer must not already exist in the list. + */ + public void add(Developer toAdd) { + requireNonNull(toAdd); + if (contains(toAdd)) { + throw new DuplicateDeveloperException(); + } + internalList.add(toAdd); + } + + /** + * Replaces the developer {@code target} in the list with {@code editedDeveloper}. + * {@code target} must exist in the list. + * The developer identity of {@code editedDeveloper} must + * not be the same as another existing developer in the list. + */ + public void setDeveloper(Developer target, Developer editedDeveloper) { + requireAllNonNull(target, editedDeveloper); + + int index = internalList.indexOf(target); + if (index == -1) { + throw new DeveloperNotFoundException(); + } + + if (!target.isSameDeveloper(editedDeveloper) && contains(editedDeveloper)) { + throw new DuplicateDeveloperException(); + } + + internalList.set(index, editedDeveloper); + } + + /** + * Removes the equivalent developer from the list. + * The developer must exist in the list. + */ + public void remove(Developer toRemove) { + requireNonNull(toRemove); + if (!internalList.remove(toRemove)) { + throw new DeveloperNotFoundException(); + } + } + + public void setDevelopers(UniqueDeveloperList replacement) { + requireNonNull(replacement); + internalList.setAll(replacement.internalList); + } + + /** + * Replaces the contents of this list with {@code developers}. + * {@code developers} must not contain duplicate developers. + */ + public void setDevelopers(List developers) { + requireAllNonNull(developers); + if (!developersAreUnique(developers)) { + throw new DuplicateDeveloperException(); + } + + internalList.setAll(developers); + } + + /** + * Updates the projects of all developers by removing a specified project. + * + * @param project The project to be removed from all developers. + */ + public void updateDeveloperProjects(String project) { + iterator().forEachRemaining( + developer -> { + Set newprojectset = new HashSet<>(developer.getProjects()); + newprojectset.remove(project); + setDeveloper(developer, new Developer(developer.getName(), + developer.getPhone(), developer.getEmail(), + developer.getAddress(), developer.getRole(), newprojectset, + developer.getSalary(), developer.getDateJoined(), + developer.getGithubId(), developer.getRating())); + }); + } + + /** + * 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) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof UniqueDeveloperList)) { + return false; + } + + UniqueDeveloperList otherUniqueDeveloperList = (UniqueDeveloperList) other; + return internalList.equals(otherUniqueDeveloperList.internalList); + } + + @Override + public int hashCode() { + return internalList.hashCode(); + } + + @Override + public String toString() { + return internalList.toString(); + } + + /** + * Returns true if {@code developers} contains only unique developers. + */ + private boolean developersAreUnique(List developers) { + for (int i = 0; i < developers.size() - 1; i++) { + for (int j = i + 1; j < developers.size(); j++) { + if (developers.get(i).isSameDeveloper(developers.get(j))) { + return false; + } + } + } + return true; + } +} diff --git a/src/main/java/seedu/address/model/person/Address.java b/src/main/java/seedu/address/model/person/Address.java index 469a2cc9a1e..b6b9c68ea16 100644 --- a/src/main/java/seedu/address/model/person/Address.java +++ b/src/main/java/seedu/address/model/person/Address.java @@ -4,12 +4,12 @@ import static seedu.address.commons.util.AppUtil.checkArgument; /** - * Represents a Person's address in the address book. + * Represents a Developer'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"; + 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, diff --git a/src/main/java/seedu/address/model/person/Email.java b/src/main/java/seedu/address/model/person/Email.java index c62e512bc29..17eb3752842 100644 --- a/src/main/java/seedu/address/model/person/Email.java +++ b/src/main/java/seedu/address/model/person/Email.java @@ -4,7 +4,7 @@ import static seedu.address.commons.util.AppUtil.checkArgument; /** - * Represents a Person's email in the address book. + * Represents a Developer's email in the address book. * Guarantees: immutable; is valid as declared in {@link #isValidEmail(String)} */ public class Email { diff --git a/src/main/java/seedu/address/model/person/KeywordPredicate.java b/src/main/java/seedu/address/model/person/KeywordPredicate.java new file mode 100644 index 00000000000..2026e7cb62d --- /dev/null +++ b/src/main/java/seedu/address/model/person/KeywordPredicate.java @@ -0,0 +1,14 @@ +package seedu.address.model.person; + +import java.util.function.Predicate; + +/** + * An interface representing a predicate that filters objects based on keywords. + * + * @param The type of objects that this predicate filters. + */ +public interface KeywordPredicate extends Predicate { + + // You can define common methods if needed. + // For now, this interface will be empty and serve mainly as a type. +} diff --git a/src/main/java/seedu/address/model/person/KeywordPredicateFactory.java b/src/main/java/seedu/address/model/person/KeywordPredicateFactory.java new file mode 100644 index 00000000000..c8fd2143f76 --- /dev/null +++ b/src/main/java/seedu/address/model/person/KeywordPredicateFactory.java @@ -0,0 +1,20 @@ +package seedu.address.model.person; + +import java.util.List; + +/** + * Represents a factory for creating keyword predicates of a specified type. + * + * @param The type of objects that the generated keyword predicate will filter. + */ +@FunctionalInterface +public interface KeywordPredicateFactory { + + /** + * Creates a keyword predicate for filtering objects based on a list of keywords. + * + * @param keywords A list of keywords to be used for filtering. + * @return A keyword predicate for filtering objects based on the given keywords. + */ + KeywordPredicate createPredicate(List keywords); +} diff --git a/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java deleted file mode 100644 index 62d19be2977..00000000000 --- a/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java +++ /dev/null @@ -1,44 +0,0 @@ -package seedu.address.model.person; - -import java.util.List; -import java.util.function.Predicate; - -import seedu.address.commons.util.StringUtil; -import seedu.address.commons.util.ToStringBuilder; - -/** - * Tests that a {@code Person}'s {@code Name} matches any of the keywords given. - */ -public class NameContainsKeywordsPredicate implements Predicate { - private final List keywords; - - public NameContainsKeywordsPredicate(List keywords) { - this.keywords = keywords; - } - - @Override - public boolean test(Person person) { - return keywords.stream() - .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(person.getName().fullName, keyword)); - } - - @Override - public boolean equals(Object other) { - if (other == this) { - return true; - } - - // instanceof handles nulls - if (!(other instanceof NameContainsKeywordsPredicate)) { - return false; - } - - NameContainsKeywordsPredicate otherNameContainsKeywordsPredicate = (NameContainsKeywordsPredicate) other; - return keywords.equals(otherNameContainsKeywordsPredicate.keywords); - } - - @Override - public String toString() { - return new ToStringBuilder(this).add("keywords", keywords).toString(); - } -} diff --git a/src/main/java/seedu/address/model/person/Person.java b/src/main/java/seedu/address/model/person/Person.java index abe8c46b535..eeeca4707ce 100644 --- a/src/main/java/seedu/address/model/person/Person.java +++ b/src/main/java/seedu/address/model/person/Person.java @@ -8,7 +8,7 @@ import java.util.Set; import seedu.address.commons.util.ToStringBuilder; -import seedu.address.model.tag.Tag; +import seedu.address.model.commons.Name; /** * Represents a Person in the address book. @@ -23,18 +23,18 @@ public class Person { // Data fields private final Address address; - private final Set tags = new HashSet<>(); + private final Set projects = 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); + public Person(Name name, Phone phone, Email email, Address address, Set projects) { + requireAllNonNull(name, phone, email, address, projects); this.name = name; this.phone = phone; this.email = email; this.address = address; - this.tags.addAll(tags); + this.projects.addAll(projects); } public Name getName() { @@ -53,12 +53,13 @@ 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); + public Set getProjects() { + return Collections.unmodifiableSet(projects); } /** @@ -94,13 +95,13 @@ public boolean equals(Object other) { && phone.equals(otherPerson.phone) && email.equals(otherPerson.email) && address.equals(otherPerson.address) - && tags.equals(otherPerson.tags); + && projects.equals(otherPerson.projects); } @Override public int hashCode() { // use this method for custom fields hashing instead of implementing your own - return Objects.hash(name, phone, email, address, tags); + return Objects.hash(name, phone, email, address, projects); } @Override @@ -110,7 +111,7 @@ public String toString() { .add("phone", phone) .add("email", email) .add("address", address) - .add("tags", tags) + .add("projects", projects) .toString(); } diff --git a/src/main/java/seedu/address/model/person/Phone.java b/src/main/java/seedu/address/model/person/Phone.java index d733f63d739..801d6268044 100644 --- a/src/main/java/seedu/address/model/person/Phone.java +++ b/src/main/java/seedu/address/model/person/Phone.java @@ -4,15 +4,15 @@ import static seedu.address.commons.util.AppUtil.checkArgument; /** - * Represents a Person's phone number in the address book. + * Represents a Developer's phone number in the address book. * 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"; - public static final String VALIDATION_REGEX = "\\d{3,}"; + "Phone numbers should only contain numbers, and it should be at least 8 digits long!"; + public static final String VALIDATION_REGEX = "\\d{8,}"; public final String value; /** 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 cc0a68d79f9..00000000000 --- a/src/main/java/seedu/address/model/person/UniquePersonList.java +++ /dev/null @@ -1,150 +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) { - if (other == this) { - return true; - } - - // instanceof handles nulls - if (!(other instanceof UniquePersonList)) { - return false; - } - - UniquePersonList otherUniquePersonList = (UniquePersonList) other; - return internalList.equals(otherUniquePersonList.internalList); - } - - @Override - public int hashCode() { - return internalList.hashCode(); - } - - @Override - public String toString() { - return internalList.toString(); - } - - /** - * 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/ClientNotFoundException.java b/src/main/java/seedu/address/model/person/exceptions/ClientNotFoundException.java new file mode 100644 index 00000000000..b58aad6cb1a --- /dev/null +++ b/src/main/java/seedu/address/model/person/exceptions/ClientNotFoundException.java @@ -0,0 +1,15 @@ +package seedu.address.model.person.exceptions; + +/** + * Exception thrown when a client cannot be found in a specific operation or context. + */ +public class ClientNotFoundException extends RuntimeException { + + /** + * Constructs a `ClientNotFoundException` with a default error message. + * The message indicates that a client cannot be found in the operation or context. + */ + public ClientNotFoundException() { + super("Unable to find client in operation"); + } +} diff --git a/src/main/java/seedu/address/model/person/exceptions/DeveloperNotFoundException.java b/src/main/java/seedu/address/model/person/exceptions/DeveloperNotFoundException.java new file mode 100644 index 00000000000..135f2e6b11e --- /dev/null +++ b/src/main/java/seedu/address/model/person/exceptions/DeveloperNotFoundException.java @@ -0,0 +1,10 @@ +package seedu.address.model.person.exceptions; + +/** + * Signals that the operation is unable to find the specified person. + */ +public class DeveloperNotFoundException extends RuntimeException { + public DeveloperNotFoundException() { + super("Unable to find developer in operation"); + } +} diff --git a/src/main/java/seedu/address/model/person/exceptions/DuplicateClientException.java b/src/main/java/seedu/address/model/person/exceptions/DuplicateClientException.java new file mode 100644 index 00000000000..a4251d5f5b9 --- /dev/null +++ b/src/main/java/seedu/address/model/person/exceptions/DuplicateClientException.java @@ -0,0 +1,11 @@ +package seedu.address.model.person.exceptions; + +/** + * Signals that the operation will result in duplicate Clients (Clients are considered duplicates if they have the same + * identity). + */ +public class DuplicateClientException extends RuntimeException { + public DuplicateClientException() { + super("Operation would result in duplicate clients"); + } +} diff --git a/src/main/java/seedu/address/model/person/exceptions/DuplicateDeveloperException.java b/src/main/java/seedu/address/model/person/exceptions/DuplicateDeveloperException.java new file mode 100644 index 00000000000..306f0f9ebdb --- /dev/null +++ b/src/main/java/seedu/address/model/person/exceptions/DuplicateDeveloperException.java @@ -0,0 +1,12 @@ +package seedu.address.model.person.exceptions; + +/** + * Signals that the operation will result in duplicate Developers (Developers are considered duplicates + * if they have the same + * identity). + */ +public class DuplicateDeveloperException extends RuntimeException { + public DuplicateDeveloperException() { + super("Operation would result in duplicate developers"); + } +} 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/project/Deadline.java b/src/main/java/seedu/address/model/project/Deadline.java new file mode 100644 index 00000000000..bafe7666d4a --- /dev/null +++ b/src/main/java/seedu/address/model/project/Deadline.java @@ -0,0 +1,134 @@ +package seedu.address.model.project; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +import java.util.Objects; + +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.model.commons.Date; + +/** + * Represents a Project's deadline in the address book. + * Guarantees: immutable; fields are validated. + */ +public class Deadline { + public static final String MESSAGE_CONSTRAINTS = + "Deadline should be of the format dd-MM-yyyy,,,<0|1>\n" + + "Eg: 31-12-2019,Develop front end interface,HIGH,0"; + public static final String VALIDATION_REGEX = "^[0-3]\\d-[01]\\d-\\d{4},[^,]+,(HIGH|MEDIUM|LOW),(0|1)$"; + private final Date date; + private final Description desc; + private final Priority priority; + + private final boolean isDone; + private final int num; + + /** + * Every field must be present and not null. + */ + public Deadline(String str, int num) { + requireNonNull(str); + checkArgument(isValidDeadline(str), MESSAGE_CONSTRAINTS); + String[] output = str.split(","); + this.date = new Date(output[0].trim(), true); + this.desc = new Description(output[1].trim()); + this.priority = Priority.valueOf(output[2].trim()); + this.isDone = output[3].trim().contains("1"); + this.num = num; + } + + /** + * Returns true if a given string is a valid date. + */ + public static boolean isValidDeadline(String text) { + if (text == null || !text.matches(VALIDATION_REGEX)) { + return false; + } + String[] output = text.split(","); + if (!Date.isValidDate(output[0].trim(), true)) { + return false; + } + + return true; + } + + public Date getDate() { + return date; + } + + public Description getDescription() { + return desc; + } + + public Priority getPriority() { + return priority; + } + + public int getNum() { + return num; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof Deadline)) { + return false; + } + + Deadline otherDeadline = (Deadline) other; + return date.equals(otherDeadline.date) + && desc.equals(otherDeadline.desc) + && priority.equals(otherDeadline.priority); + } + + public String getPrintedStringRepresentation() { + return "" + num + ". " + desc.toString() + " by: " + date.toString() + ", priority: " + priority.toString() + + " (" + (isDone ? "done)" : "undone)"); + } + + public String getStringRepresentation() { + return date.toString() + "," + desc.toString() + "," + priority.toString() + "," + (isDone ? "1" : "0"); + } + + /** + * Returns a String representation of the Deadline such that it is marked as completed. + * + * @return A String representing the Deadline if it was completed. + */ + public String getDoneStringRepresentation() { + return date.toString() + "," + desc.toString() + "," + priority.toString() + "," + ("1"); + } + + /** + * Returns a String representation of the Deadline such that it is marked as incomplete. + * + * @return A String representing the Deadline if it was incomplete. + */ + public String getUndoneStringRepresentation() { + return date.toString() + "," + desc.toString() + "," + priority.toString() + "," + ("0"); + } + + @Override + public int hashCode() { + return Objects.hash(date, desc, priority); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("index", num) + .add("date", date) + .add("description", desc) + .add("priority", priority) + .toString(); + } + + public boolean getIsDone() { + return isDone; + } +} diff --git a/src/main/java/seedu/address/model/project/DeadlineContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/project/DeadlineContainsKeywordsPredicate.java new file mode 100644 index 00000000000..9a5e1e9190c --- /dev/null +++ b/src/main/java/seedu/address/model/project/DeadlineContainsKeywordsPredicate.java @@ -0,0 +1,46 @@ +package seedu.address.model.project; + +import java.util.List; + +import seedu.address.commons.util.StringUtil; +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.model.person.KeywordPredicate; + +/** + * Tests that a {@code Developer}'s {@code Name} matches any of the keywords given. + */ +public class DeadlineContainsKeywordsPredicate implements KeywordPredicate { + private final List keywords; + + public DeadlineContainsKeywordsPredicate(List keywords) { + this.keywords = keywords; + } + + @Override + public boolean test(Project project) { + return keywords.stream() + .anyMatch(keyword -> StringUtil.containsPartialWordIgnoreCase(project.getProjectDeadlines().toString(), + keyword)); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof DeadlineContainsKeywordsPredicate)) { + return false; + } + + DeadlineContainsKeywordsPredicate otherNameContainsKeywordsPredicate = + (DeadlineContainsKeywordsPredicate) other; + return keywords.equals(otherNameContainsKeywordsPredicate.keywords); + } + + @Override + public String toString() { + return new ToStringBuilder(this).add("keywords", keywords).toString(); + } +} diff --git a/src/main/java/seedu/address/model/project/Description.java b/src/main/java/seedu/address/model/project/Description.java new file mode 100644 index 00000000000..daa3e56f101 --- /dev/null +++ b/src/main/java/seedu/address/model/project/Description.java @@ -0,0 +1,63 @@ +package seedu.address.model.project; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +/** + * Represents a Project's description in the address book. + * Guarantees: immutable; is validated. + */ +public class Description { + public static final String MESSAGE_CONSTRAINTS = + "Description should only contain alphanumeric characters and spaces, and it should not be blank!"; + + + public static final String VALIDATION_REGEX = "^(?!\\\\s*$)[a-zA-Z0-9 ]+$"; + + public final String desc; + + /** + * Constructs a {@code Description}. + * + * @param desc The String description. + */ + public Description(String desc) { + requireNonNull(desc); + checkArgument(isValidDescription(desc), MESSAGE_CONSTRAINTS); + this.desc = desc; + } + + /** + * Returns true if a given string is a valid description. + */ + public static boolean isValidDescription(String test) { + return test.matches(VALIDATION_REGEX); + } + + + @Override + public String toString() { + return desc; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof Description)) { + return false; + } + + Description otherDesc = (Description) other; + return desc.equals(otherDesc.desc); + } + + @Override + public int hashCode() { + return desc.hashCode(); + } + +} diff --git a/src/main/java/seedu/address/model/project/DescriptionContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/project/DescriptionContainsKeywordsPredicate.java new file mode 100644 index 00000000000..6432edae745 --- /dev/null +++ b/src/main/java/seedu/address/model/project/DescriptionContainsKeywordsPredicate.java @@ -0,0 +1,45 @@ +package seedu.address.model.project; + +import java.util.List; + +import seedu.address.commons.util.StringUtil; +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.model.person.KeywordPredicate; + +/** + * Tests that a {@code Developer}'s {@code Name} matches any of the keywords given. + */ +public class DescriptionContainsKeywordsPredicate implements KeywordPredicate { + private final List keywords; + + public DescriptionContainsKeywordsPredicate(List keywords) { + this.keywords = keywords; + } + + @Override + public boolean test(Project project) { + return keywords.stream().anyMatch(keyword -> + StringUtil.containsPartialWordIgnoreCase(project.getProjectDescription().toString(), keyword)); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof DescriptionContainsKeywordsPredicate)) { + return false; + } + + DescriptionContainsKeywordsPredicate otherNameContainsKeywordsPredicate = + (DescriptionContainsKeywordsPredicate) other; + return keywords.equals(otherNameContainsKeywordsPredicate.keywords); + } + + @Override + public String toString() { + return new ToStringBuilder(this).add("keywords", keywords).toString(); + } +} diff --git a/src/main/java/seedu/address/model/project/Priority.java b/src/main/java/seedu/address/model/project/Priority.java new file mode 100644 index 00000000000..37c9fca97ce --- /dev/null +++ b/src/main/java/seedu/address/model/project/Priority.java @@ -0,0 +1,22 @@ +package seedu.address.model.project; + +/** + * Enumerates the priority levels for a task or item, typically used to indicate the importance or urgency. + * It provides three priority levels: HIGH, MEDIUM, and LOW. + */ +public enum Priority { + /** + * Represents the highest priority level, indicating a task or item of utmost importance and urgency. + */ + HIGH, + + /** + * Represents a medium priority level, indicating a task or item with moderate importance and urgency. + */ + MEDIUM, + + /** + * Represents the lowest priority level, indicating a task or item with minimal importance and urgency. + */ + LOW +} diff --git a/src/main/java/seedu/address/model/project/Project.java b/src/main/java/seedu/address/model/project/Project.java new file mode 100644 index 00000000000..6aae8ae9e4e --- /dev/null +++ b/src/main/java/seedu/address/model/project/Project.java @@ -0,0 +1,187 @@ +package seedu.address.model.project; + +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DATEJOINED; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DOCUMENT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; +import static seedu.address.logic.parser.CliSyntax.PREFIX_GITHUBID; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ORGANISATION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PROJECT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_RATING; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ROLE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_SALARY; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.function.Predicate; + +import javafx.collections.FXCollections; +import javafx.collections.transformation.FilteredList; +import seedu.address.logic.parser.Prefix; +import seedu.address.model.commons.Name; + +/** + * Represents a Project in the address book. + * Guarantees: immutable; fields are validated. + */ +public class Project { + + public static final Prefix[] UNUSED_PREFIXES = new Prefix[]{PREFIX_DATEJOINED, PREFIX_SALARY, PREFIX_RATING, + PREFIX_GITHUBID, PREFIX_ADDRESS, PREFIX_DOCUMENT, PREFIX_EMAIL, PREFIX_ORGANISATION, PREFIX_PHONE, + PREFIX_PROJECT, PREFIX_ROLE}; + public static final Prefix[] UNUSED_PREFIXES_FOR_EDIT = new Prefix[]{PREFIX_DATEJOINED, PREFIX_SALARY, + PREFIX_RATING, + PREFIX_GITHUBID, PREFIX_ADDRESS, PREFIX_DOCUMENT, PREFIX_EMAIL, PREFIX_ORGANISATION, PREFIX_PHONE, + PREFIX_PROJECT, PREFIX_ROLE, PREFIX_NAME}; + private final Name projectName; + private final Description description; + private final List deadlines; + private final FilteredList filteredDeadlines; + + /** + * Constructs a {@code Tag}. + * + * @param projectName A valid project name. + */ + public Project(Name projectName, Description desc, List deadlines) { + requireAllNonNull(projectName, desc, deadlines); + this.projectName = projectName; + this.description = desc; + this.deadlines = deadlines; + this.filteredDeadlines = new FilteredList<>(FXCollections.observableList(deadlines)); + } + + public Project(String projectName) { + this(new Name(projectName), new Description(""), new ArrayList<>()); + } + + public void setPredicate(Predicate predicate) { + filteredDeadlines.setPredicate(predicate); + } + + public String getName() { + return projectName.fullName; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof Project)) { + return false; + } + + Project otherTag = (Project) other; + return projectName.equals(otherTag.projectName) + && description.equals(otherTag.description) + && deadlines.equals(otherTag.deadlines); + } + + @Override + public int hashCode() { + return Objects.hash(projectName, description, deadlines); + } + + /** + * Returns the name of the Project. + */ + @Override + public String toString() { + return projectName.toString(); + } + + public Name getProjectName() { + return projectName; + } + + public Description getProjectDescription() { + return description; + } + + /** + * Returns a list with each element being the String representation of the respective deadline. + * The element at the given index is the String representation of the respective deadline such that it is completed. + * + * @param index The index of the deadline to mark as completed. + * @return A list containing String representations of deadlines. + */ + public List markDeadlineStringRep(int index) { + List res = new ArrayList<>(); + for (int i = 0; i < deadlines.size(); i++) { + if (i == index) { + res.add(deadlines.get(i).getDoneStringRepresentation()); + } else { + res.add(deadlines.get(i).getStringRepresentation()); + } + } + return res; + } + + /** + * Returns a list with each element being the String representation of the respective deadline. + * The element at the given index is the String representation of + * the respective deadline such that it is incomplete. + * + * @param index The index of the deadline to mark as incomplete. + * @return A list containing String representations of deadlines. + */ + public List unmarkDeadlineStringRep(int index) { + List res = new ArrayList<>(); + for (int i = 0; i < deadlines.size(); i++) { + if (i == index) { + res.add(deadlines.get(i).getUndoneStringRepresentation()); + } else { + res.add(deadlines.get(i).getStringRepresentation()); + } + } + return res; + } + + /** + * Returns the size of the deadlines list. + * + * @return An integer representing the size of the deadlines list. + */ + public int deadlineListSize() { + return deadlines.size(); + } + + /** + * Returns an immutable list, which throws {@code UnsupportedOperationException} + * if modification is attempted. + */ + public List getProjectDeadlines() { + return Collections.unmodifiableList(deadlines); + } + + public FilteredList getProjectFilteredDeadlines() { + return filteredDeadlines; + } + + /** + * Checks if this project is the same as another project based on their names. + * + * @param otherProject The other project to compare with. + * @return True if the projects have the same name, or both are the same object reference; false otherwise. + */ + public boolean isSameProject(Project otherProject) { + if (otherProject == this) { + return true; + } + + return otherProject != null + && otherProject.getName().toLowerCase().equals(getName().toLowerCase()); + } + + public boolean isSameProject(String projectName) { + return projectName.equals(getName()); + } +} diff --git a/src/main/java/seedu/address/model/project/ProjectNameContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/project/ProjectNameContainsKeywordsPredicate.java new file mode 100644 index 00000000000..2dc0dfa2132 --- /dev/null +++ b/src/main/java/seedu/address/model/project/ProjectNameContainsKeywordsPredicate.java @@ -0,0 +1,45 @@ +package seedu.address.model.project; + +import java.util.List; + +import seedu.address.commons.util.StringUtil; +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.model.person.KeywordPredicate; + +/** + * Tests that a {@code Developer}'s {@code Name} matches any of the keywords given. + */ +public class ProjectNameContainsKeywordsPredicate implements KeywordPredicate { + private final List keywords; + + public ProjectNameContainsKeywordsPredicate(List keywords) { + this.keywords = keywords; + } + + @Override + public boolean test(Project project) { + return keywords.stream() + .anyMatch(keyword -> StringUtil.containsPartialWordIgnoreCase(project.getName(), keyword)); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof ProjectNameContainsKeywordsPredicate)) { + return false; + } + + ProjectNameContainsKeywordsPredicate otherNameContainsKeywordsPredicate = + (ProjectNameContainsKeywordsPredicate) other; + return keywords.equals(otherNameContainsKeywordsPredicate.keywords); + } + + @Override + public String toString() { + return new ToStringBuilder(this).add("keywords", keywords).toString(); + } +} diff --git a/src/main/java/seedu/address/model/project/UniqueProjectList.java b/src/main/java/seedu/address/model/project/UniqueProjectList.java new file mode 100644 index 00000000000..dbacd5436c8 --- /dev/null +++ b/src/main/java/seedu/address/model/project/UniqueProjectList.java @@ -0,0 +1,168 @@ +package seedu.address.model.project; + +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.project.exceptions.DuplicateProjectException; +import seedu.address.model.project.exceptions.ProjectNotFoundException; + +/** + * A list of projects that enforces uniqueness between its elements and does not allow nulls. + * A project is considered unique by comparing using {@code Project#isSameProject(Project)}. + * As such, adding and updating of + * projects use Project#isSameProject(Project) for equality so as to ensure that the project being added or updated is + * unique in terms of identity in the UniqueProjectList. However, the removal of a project uses + * Project#equals(Object) to ensure that the project with exactly the same fields will be removed. + *

+ * Supports a minimal set of list operations. + * + * @see Project#isSameProject(Project) + */ +public class UniqueProjectList implements Iterable { + + private final ObservableList internalList = FXCollections.observableArrayList(); + private final ObservableList internalUnmodifiableList = + FXCollections.unmodifiableObservableList(internalList); + + /** + * Returns true if the list contains an equivalent project as the given argument. + */ + public boolean contains(Project toCheck) { + requireNonNull(toCheck); + return internalList.stream().anyMatch(toCheck::isSameProject); + } + + /** + * Checks if the project list contains a project with the same name as the specified string. + * + * @param toCheck The string representing the name of the project to search for. + * @return True if a project with the same name is found in the list, false otherwise. + * @throws NullPointerException If the specified string is null. + */ + public boolean contains(String toCheck) { + requireNonNull(toCheck); + for (Project p : internalList) { + if (p.isSameProject(toCheck)) { + return true; + } + } + return false; + } + + /** + * Adds a project to the list. + * The project must not already exist in the list. + */ + public void add(Project toAdd) { + requireNonNull(toAdd); + if (contains(toAdd)) { + throw new DuplicateProjectException(); + } + internalList.add(toAdd); + } + + /** + * Replaces the project {@code target} in the list with {@code editedProject}. + * {@code target} must exist in the list. + * The project identity of {@code editedProject} must not be the same as another existing project in the list. + */ + public void setProject(Project target, Project editedProject) { + requireAllNonNull(target, editedProject); + + int index = internalList.indexOf(target); + if (index == -1) { + throw new ProjectNotFoundException(); + } + + if (!target.isSameProject(editedProject) && contains(editedProject)) { + throw new DuplicateProjectException(); + } + + internalList.set(index, editedProject); + } + + /** + * Removes the equivalent project from the list. + * The project must exist in the list. + */ + public void remove(Project toRemove) { + requireNonNull(toRemove); + if (!internalList.remove(toRemove)) { + throw new ProjectNotFoundException(); + } + } + + public void setProjects(UniqueProjectList replacement) { + requireNonNull(replacement); + internalList.setAll(replacement.internalList); + } + + /** + * Replaces the contents of this list with {@code projects}. + * {@code projects} must not contain duplicate projects. + */ + public void setProjects(List projects) { + requireAllNonNull(projects); + if (!projectsAreUnique(projects)) { + throw new DuplicateProjectException(); + } + + internalList.setAll(projects); + } + + /** + * 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) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof UniqueProjectList)) { + return false; + } + + UniqueProjectList otherUniqueProjectList = (UniqueProjectList) other; + return internalList.equals(otherUniqueProjectList.internalList); + } + + @Override + public int hashCode() { + return internalList.hashCode(); + } + + @Override + public String toString() { + return internalList.toString(); + } + + /** + * Returns true if {@code projects} contains only unique projects. + */ + private boolean projectsAreUnique(List projects) { + for (int i = 0; i < projects.size() - 1; i++) { + for (int j = i + 1; j < projects.size(); j++) { + if (projects.get(i).isSameProject(projects.get(j))) { + return false; + } + } + } + return true; + } +} diff --git a/src/main/java/seedu/address/model/project/exceptions/DuplicateProjectException.java b/src/main/java/seedu/address/model/project/exceptions/DuplicateProjectException.java new file mode 100644 index 00000000000..63a028244da --- /dev/null +++ b/src/main/java/seedu/address/model/project/exceptions/DuplicateProjectException.java @@ -0,0 +1,12 @@ +package seedu.address.model.project.exceptions; + +/** + * Signals that the operation will result in duplicate Projects + * (Projects are considered duplicates if they have the same + * name). + */ +public class DuplicateProjectException extends RuntimeException { + public DuplicateProjectException() { + super("Operation would result in duplicate projects"); + } +} diff --git a/src/main/java/seedu/address/model/project/exceptions/ProjectNotFoundException.java b/src/main/java/seedu/address/model/project/exceptions/ProjectNotFoundException.java new file mode 100644 index 00000000000..629586b75f0 --- /dev/null +++ b/src/main/java/seedu/address/model/project/exceptions/ProjectNotFoundException.java @@ -0,0 +1,14 @@ +package seedu.address.model.project.exceptions; + +/** + * Exception thrown when a project is not found in a specific operation. + */ +public class ProjectNotFoundException extends RuntimeException { + + /** + * Constructs a ProjectNotFoundException with a default error message. + */ + public ProjectNotFoundException() { + super("Unable to find project in operation"); + } +} diff --git a/src/main/java/seedu/address/model/tag/Tag.java b/src/main/java/seedu/address/model/tag/Tag.java deleted file mode 100644 index f1a0d4e233b..00000000000 --- a/src/main/java/seedu/address/model/tag/Tag.java +++ /dev/null @@ -1,62 +0,0 @@ -package seedu.address.model.tag; - -import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.AppUtil.checkArgument; - -/** - * Represents a Tag in the address book. - * Guarantees: immutable; name is valid as declared in {@link #isValidTagName(String)} - */ -public class Tag { - - public static final String MESSAGE_CONSTRAINTS = "Tags names should be alphanumeric"; - public static final String VALIDATION_REGEX = "\\p{Alnum}+"; - - public final String tagName; - - /** - * Constructs a {@code Tag}. - * - * @param tagName A valid tag name. - */ - public Tag(String tagName) { - requireNonNull(tagName); - checkArgument(isValidTagName(tagName), MESSAGE_CONSTRAINTS); - this.tagName = tagName; - } - - /** - * Returns true if a given string is a valid tag name. - */ - public static boolean isValidTagName(String test) { - return test.matches(VALIDATION_REGEX); - } - - @Override - public boolean equals(Object other) { - if (other == this) { - return true; - } - - // instanceof handles nulls - if (!(other instanceof Tag)) { - return false; - } - - Tag otherTag = (Tag) other; - return tagName.equals(otherTag.tagName); - } - - @Override - public int hashCode() { - return tagName.hashCode(); - } - - /** - * Format state as text for viewing. - */ - public String toString() { - return '[' + tagName + ']'; - } - -} diff --git a/src/main/java/seedu/address/model/util/SampleDataUtil.java b/src/main/java/seedu/address/model/util/SampleDataUtil.java index 1806da4facf..af6862d40ae 100644 --- a/src/main/java/seedu/address/model/util/SampleDataUtil.java +++ b/src/main/java/seedu/address/model/util/SampleDataUtil.java @@ -1,49 +1,104 @@ package seedu.address.model.util; +import java.util.ArrayList; import java.util.Arrays; +import java.util.List; import java.util.Set; import java.util.stream.Collectors; import seedu.address.model.AddressBook; import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.model.client.Client; +import seedu.address.model.client.ClientRoles; +import seedu.address.model.client.Document; +import seedu.address.model.commons.Date; +import seedu.address.model.commons.Name; +import seedu.address.model.developer.Developer; +import seedu.address.model.developer.DeveloperRoles; +import seedu.address.model.developer.GithubId; +import seedu.address.model.developer.Rating; +import seedu.address.model.developer.Salary; 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.project.Deadline; +import seedu.address.model.project.Description; +import seedu.address.model.project.Project; /** * Contains utility methods for populating {@code AddressBook} with sample data. */ public class SampleDataUtil { - public static Person[] getSamplePersons() { - return new Person[] { - new Person(new Name("Alex Yeoh"), new Phone("87438807"), new Email("alexyeoh@example.com"), - new Address("Blk 30 Geylang Street 29, #06-40"), - getTagSet("friends")), - new Person(new Name("Bernice Yu"), new Phone("99272758"), new Email("berniceyu@example.com"), - new Address("Blk 30 Lorong 3 Serangoon Gardens, #07-18"), - getTagSet("colleagues", "friends")), - new Person(new Name("Charlotte Oliveiro"), new Phone("93210283"), new Email("charlotte@example.com"), - new Address("Blk 11 Ang Mo Kio Street 74, #11-04"), - getTagSet("neighbours")), - new Person(new Name("David Li"), new Phone("91031282"), new Email("lidavid@example.com"), - new Address("Blk 436 Serangoon Gardens Street 26, #16-43"), - getTagSet("family")), - new Person(new Name("Irfan Ibrahim"), new Phone("92492021"), new Email("irfan@example.com"), - new Address("Blk 47 Tampines Street 20, #17-35"), - getTagSet("classmates")), - new Person(new Name("Roy Balakrishnan"), new Phone("92624417"), new Email("royb@example.com"), - new Address("Blk 45 Aljunied Street 85, #11-31"), - getTagSet("colleagues")) + public static Developer[] getSampleDevelopers() { + Set projectSet1 = getProjectSet("CodeContact", "TeamTrekker"); + Set projectSet2 = getProjectSet("Orbital", "Appollo"); + Set projectSet3 = getProjectSet("CodeContact"); + Set projectSet4 = getProjectSet("TeamTrekker", "Appollo"); + //Fix date constructors below + return new Developer[]{ + new Developer(new Name("Alex Yeoh"), new Phone("87438807"), new Email("alexyeoh@example.com"), + new Address("Blk 30 Geylang Street 29, #06-40"), new DeveloperRoles("Developer"), projectSet1, + new Salary("5000"), new Date("15-12-2019", false), new GithubId("mahidharah"), new Rating("5.0")), + new Developer(new Name("Bernice Yu"), new Phone("99272758"), new Email("berniceyu@example.com"), + new Address("Blk 30 Lorong 3 Serangoon Gardens, #07-18"), new DeveloperRoles("Developer"), + projectSet2, + new Salary("6000"), new Date("16-11-2020", false), new GithubId("mahidharah1"), new Rating("5.0")), + new Developer(new Name("Charlotte Oliveiro"), new Phone("93210283"), new Email("charlotte@example.com"), + new Address("Blk 11 Ang Mo Kio Street 74, #11-04"), new DeveloperRoles("Developer"), projectSet3, + new Salary("4500"), new Date("20-10-2020", false), new GithubId("mahidharah2"), + new Rating("5.0")), + new Developer(new Name("David Li"), new Phone("91031282"), new Email("lidavid@example.com"), + new Address("Blk 436 Serangoon Gardens Street 26, #16-43"), new DeveloperRoles("Developer"), + projectSet4, + new Salary("5500"), new Date("23-09-2021", false), new GithubId("mahidharah3"), new Rating("5.0")) + }; + } + + public static Client[] getSampleClients() { + Set projectSet1 = getProjectSet("CodeContact", "TeamTrekker"); + Set projectSet2 = getProjectSet("Orbital", "Appollo"); + Set projectSet3 = getProjectSet("CodeContact"); + Set projectSet4 = getProjectSet("TeamTrekker", "Appollo"); + + return new Client[]{ + new Client(new Name("Eva Tang"), new Phone("98765432"), new Email("eva@example.com"), + new Address("Blk 123 Bukit Batok Street 11, #01-01"), new ClientRoles("HR"), projectSet1, + new Name("XYZ Corp"), new Document("https://www.xyzcorp.com/")), + new Client(new Name("Fiona Goh"), new Phone("87654321"), new Email("fiona@example.com"), + new Address("Blk 456 Jurong East Street 33, #02-02"), new ClientRoles("Manager"), projectSet2, + new Name("ABC Pte Ltd"), new Document("https://www.abc.com/")), + new Client(new Name("George Lim"), new Phone("76543210"), new Email("george@example.com"), + new Address("Blk 789 Woodlands Ave 6, #03-03"), new ClientRoles("Developer"), projectSet3, + new Name("MNO Company"), new Document("https://www.mno.com/")), + new Client(new Name("Helen Tan"), new Phone("65432109"), new Email("helen@example.com"), + new Address("Blk 321 Tampines Street 33, #04-04"), new ClientRoles("HR"), projectSet4, + new Name("PQR LLC"), new Document("https://www.pqr.com/")) + }; + } + + public static Project[] getSampleProjects() { + return new Project[]{ + new Project(new Name("CodeContact"), new Description("A contact management system"), + getDeadlineList("13-10-2021,Phase 1,HIGH,0")), + new Project(new Name("TeamTrekker"), new Description("A team collaboration tool"), + getDeadlineList("13-11-2021,Phase 2,MEDIUM,0")), + new Project(new Name("Orbital"), new Description("A satellite tracking system"), + getDeadlineList("13-12-2021,Phase 3,LOW,0")), + new Project(new Name("Appollo"), new Description("A mobile app development platform"), + getDeadlineList("13-01-2022,Launch,HIGH,0")) }; } public static ReadOnlyAddressBook getSampleAddressBook() { AddressBook sampleAb = new AddressBook(); - for (Person samplePerson : getSamplePersons()) { - sampleAb.addPerson(samplePerson); + for (Developer sampleDeveloper : getSampleDevelopers()) { + sampleAb.addDeveloper(sampleDeveloper); + } + for (Client sampleClient : getSampleClients()) { + sampleAb.addClient(sampleClient); + } + for (Project sampleProject : getSampleProjects()) { + sampleAb.addProject(sampleProject); } return sampleAb; } @@ -51,10 +106,16 @@ public static ReadOnlyAddressBook getSampleAddressBook() { /** * Returns a tag set containing the list of strings given. */ - public static Set getTagSet(String... strings) { + public static Set getProjectSet(String... strings) { return Arrays.stream(strings) - .map(Tag::new) .collect(Collectors.toSet()); } + public static List getDeadlineList(String... deadlines) { + List deadlineList = new ArrayList<>(); + for (String s : deadlines) { + deadlineList.add(new Deadline(s, deadlineList.size() + 1)); + } + return deadlineList; + } } diff --git a/src/main/java/seedu/address/storage/AddressBookStorage.java b/src/main/java/seedu/address/storage/AddressBookStorage.java index f2e015105ae..be4e3d8a47f 100644 --- a/src/main/java/seedu/address/storage/AddressBookStorage.java +++ b/src/main/java/seedu/address/storage/AddressBookStorage.java @@ -32,6 +32,7 @@ public interface AddressBookStorage { /** * Saves the given {@link ReadOnlyAddressBook} to the storage. + * * @param addressBook cannot be null. * @throws IOException if there was any problem writing to the file. */ diff --git a/src/main/java/seedu/address/storage/JsonAdaptedClient.java b/src/main/java/seedu/address/storage/JsonAdaptedClient.java new file mode 100644 index 00000000000..1cd6125ddd4 --- /dev/null +++ b/src/main/java/seedu/address/storage/JsonAdaptedClient.java @@ -0,0 +1,153 @@ +package seedu.address.storage; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.client.Client; +import seedu.address.model.client.ClientRoles; +import seedu.address.model.client.Document; +import seedu.address.model.commons.Name; +import seedu.address.model.person.Address; +import seedu.address.model.person.Email; +import seedu.address.model.person.Phone; + +/** + * Jackson-friendly version of {@link seedu.address.model.client.Client}. + */ +public class JsonAdaptedClient { + + public static final String MISSING_FIELD_MESSAGE_FORMAT = "Client's %s field is missing!"; + + private final String name; + private final String phone; + private final String email; + private final String address; + private final String role; + private final List projects = new ArrayList<>(); + private final String organisation; + private final String document; + + /** + * Constructs a {@code JsonAdaptedClient} with the given client details. + * + * @param name Name of the client. + * @param phone Phone number of the client. + * @param email Email address of the client. + * @param address Address of the client. + * @param role Role of the client. + * @param projects List of projects associated with the client. + * @param organisation Name of the client's organization. + * @param document Document URL associated with the client. + */ + @JsonCreator + public JsonAdaptedClient(@JsonProperty("name") String name, @JsonProperty("phone") String phone, + @JsonProperty("email") String email, @JsonProperty("address") String address, + @JsonProperty("role") String role, @JsonProperty("projects") List projects, + @JsonProperty("organisation") String organisation, + @JsonProperty("document") String document) { + this.name = name; + this.phone = phone; + this.email = email; + this.address = address; + this.role = role; + this.projects.addAll(projects); + this.organisation = organisation; + this.document = document; + } + + /** + * Constructs a {@code JsonAdaptedClient} with data from the given {@code Client}. + * + * @param source The client object from which to extract data. + */ + public JsonAdaptedClient(Client source) { + name = source.getName().fullName; + phone = source.getPhone().value; + email = source.getEmail().value; + address = source.getAddress().value; + role = source.getRole().role; + projects.addAll(source.getProjects()); + organisation = source.getOrganisation().fullName; + document = source.getDocument().toString(); + } + + /** + * Converts this Jackson-friendly adapted client object into the model's {@code Client} object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted client. + */ + public Client toModelType() throws IllegalValueException { + final List clientProjects = new ArrayList<>(); + for (String project : projects) { + clientProjects.add(project); + } + + 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); + + if (role == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + ClientRoles.class.getSimpleName())); + } + if (!ClientRoles.isValidRole(role)) { + throw new IllegalValueException(ClientRoles.NO_SUCH_CLIENT_ROLE); + } + final ClientRoles modelRole = new ClientRoles(role); + + if (organisation == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Name.class.getSimpleName())); + } + if (!Name.isValidName(organisation)) { + throw new IllegalValueException(Name.MESSAGE_CONSTRAINTS); + } + final Name modelOrganisation = new Name(organisation); + + if (document == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + Document.class.getSimpleName())); + } + if (!Document.isValidUrl(document)) { + throw new IllegalValueException(Document.MESSAGE_CONSTRAINTS); + } + final Document modelDocument = new Document(document); + + final Set modelProjects = new HashSet<>(clientProjects); + return new Client(modelName, modelPhone, modelEmail, modelAddress, modelRole, modelProjects, + modelOrganisation, modelDocument); + } +} diff --git a/src/main/java/seedu/address/storage/JsonAdaptedDeveloper.java b/src/main/java/seedu/address/storage/JsonAdaptedDeveloper.java new file mode 100644 index 00000000000..94ae0cb42f3 --- /dev/null +++ b/src/main/java/seedu/address/storage/JsonAdaptedDeveloper.java @@ -0,0 +1,189 @@ +package seedu.address.storage; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.commons.Date; +import seedu.address.model.commons.Name; +import seedu.address.model.developer.Developer; +import seedu.address.model.developer.DeveloperRoles; +import seedu.address.model.developer.GithubId; +import seedu.address.model.developer.Rating; +import seedu.address.model.developer.Salary; +import seedu.address.model.person.Address; +import seedu.address.model.person.Email; +import seedu.address.model.person.Person; +import seedu.address.model.person.Phone; + +/** + * Jackson-friendly version of {@link Person}. + */ +class JsonAdaptedDeveloper { + + public static final String MISSING_FIELD_MESSAGE_FORMAT = "Developer's %s field is missing!"; + + private final String name; + private final String phone; + private final String email; + private final String address; + + private final String role; + + private final List projects = new ArrayList<>(); + private final String salary; + private final String dateJoined; + private final String githubId; + private final String rating; + + /** + * Constructs a {@code JsonAdaptedDeveloper} with the given data. + * + * @param name The name of the developer. + * @param phone The phone number of the developer. + * @param email The email address of the developer. + * @param address The address of the developer. + * @param dateJoined The date when the developer joined the company. + * @param role The role of the developer. + * @param salary The salary of the developer. + * @param projects The list of project names associated with the developer. + * @param githubId The GitHub username of the developer. + * @param rating The rating of the developer. + */ + @JsonCreator + public JsonAdaptedDeveloper(@JsonProperty("name") String name, @JsonProperty("phone") String phone, + @JsonProperty("email") String email, @JsonProperty("address") String address, + @JsonProperty("dateJoined") String dateJoined, @JsonProperty("role") String role, + @JsonProperty("salary") String salary, + @JsonProperty("projects") List projects, + @JsonProperty("githubId") String githubId, + @JsonProperty("rating") String rating) { + this.name = name; + this.phone = phone; + this.email = email; + this.address = address; + this.dateJoined = dateJoined; + this.role = role; + this.salary = salary; + if (projects != null) { + this.projects.addAll(projects); + } + this.githubId = githubId; + this.rating = rating; + } + + /** + * Constructs a {@code JsonAdaptedDeveloper} with data from the given {@code Developer}. + * + * @param source The developer object from which to extract data. + */ + public JsonAdaptedDeveloper(Developer source) { + name = source.getName().fullName; + phone = source.getPhone().value; + email = source.getEmail().value; + address = source.getAddress().value; + dateJoined = source.getDateJoined().toString(); + role = source.getRole().role; + salary = source.getSalary().toString(); + projects.addAll(source.getProjects()); + rating = source.getRating().toString(); + githubId = source.getGithubId().username; + } + + /** + * Converts this Jackson-friendly adapted developer object into the model's {@code Developer} object. + * + * @return A {@code Developer} object with the data from this adapted developer. + * @throws IllegalValueException If there were any data constraints violated in the adapted developer. + */ + public Developer toModelType() throws IllegalValueException { + final List personProjects = new ArrayList<>(); + for (String projects : projects) { + personProjects.add(projects); + } + + 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); + + if (dateJoined == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Date.class.getSimpleName())); + } + if (!Date.isValidDate(dateJoined, false)) { + throw new IllegalValueException(Date.MESSAGE_CONSTRAINTS); + } + final Date modelDateJoined = new Date(dateJoined, false); + + + if (role == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + DeveloperRoles.class.getSimpleName())); + } + if (!DeveloperRoles.isValidRole(role)) { + throw new IllegalValueException(DeveloperRoles.NO_SUCH_DEVELOPER_ROLE); + } + final DeveloperRoles modelRole = new DeveloperRoles(role); + + if (salary == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Salary.class.getSimpleName())); + } + if (!Salary.isValidSalary(salary)) { + throw new IllegalValueException(Salary.MESSAGE_CONSTRAINTS); + } + final Salary modelSalary = new Salary(salary); + if (githubId == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + GithubId.class.getSimpleName())); + } + if (!GithubId.isValidGithubId(githubId)) { + throw new IllegalValueException(GithubId.MESSAGE_CONSTRAINTS); + } + final GithubId modelGithubId = new GithubId(githubId); + if (rating == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Rating.class.getSimpleName())); + } + if (!Rating.isValidRating(rating)) { + throw new IllegalValueException(Rating.MESSAGE_CONSTRAINTS); + } + final Rating modelRating = new Rating(rating); + + final Set modelProjects = new HashSet<>(personProjects); + return new Developer(modelName, modelPhone, modelEmail, + modelAddress, modelRole, modelProjects, modelSalary, modelDateJoined, + modelGithubId, modelRating); + } + +} diff --git a/src/main/java/seedu/address/storage/JsonAdaptedDeveloperRoles.java b/src/main/java/seedu/address/storage/JsonAdaptedDeveloperRoles.java new file mode 100644 index 00000000000..89e9019ad79 --- /dev/null +++ b/src/main/java/seedu/address/storage/JsonAdaptedDeveloperRoles.java @@ -0,0 +1,11 @@ +package seedu.address.storage; + +/** + * Represents a JSON-serializable version of DeveloperRoles. + * This class is used for JSON serialization and deserialization of DeveloperRoles. + * Instances of this class contain the necessary information to recreate a DeveloperRoles object + * when reading from or writing to a JSON file. + */ +public class JsonAdaptedDeveloperRoles { + // Class implementation goes here +} 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 bd1ca0f56c8..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 tags = 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("tags") List tags) { - this.name = name; - this.phone = phone; - this.email = email; - this.address = address; - if (tags != null) { - this.tags.addAll(tags); - } - } - - /** - * 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; - tags.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 : tags) { - 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/JsonAdaptedProject.java b/src/main/java/seedu/address/storage/JsonAdaptedProject.java new file mode 100644 index 00000000000..da7d66b929c --- /dev/null +++ b/src/main/java/seedu/address/storage/JsonAdaptedProject.java @@ -0,0 +1,94 @@ +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.commons.Name; +import seedu.address.model.project.Deadline; +import seedu.address.model.project.Description; +import seedu.address.model.project.Project; + +/** + * Jackson-friendly version of {@link Project}. + */ +class JsonAdaptedProject { + public static final String MISSING_FIELD_MESSAGE_FORMAT = "Project's %s field is missing!"; + + private final String projectName; + private final String description; + private final List deadlines = new ArrayList<>(); + + /** + * Constructs a {@code JsonAdaptedProject} with the given {@code projectName}. + */ + @JsonCreator + public JsonAdaptedProject(@JsonProperty("projectName") String projectName, + @JsonProperty("description") String description, + @JsonProperty("deadlines") List deadlines) { + + this.projectName = projectName; + this.description = description; + this.deadlines.clear(); + if (deadlines != null) { + this.deadlines.addAll(deadlines); + } + } + + /** + * Converts a given {@code Tag} into this class for Jackson use. + */ + public JsonAdaptedProject(Project source) { + + projectName = source.getProjectName().fullName; + description = source.getProjectDescription().desc; + deadlines.addAll(source.getProjectDeadlines().stream() + .map(Deadline::getStringRepresentation).collect(Collectors.toList())); + } + + + /** + * 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 Project toModelType() throws IllegalValueException { + if (projectName == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Name.class.getSimpleName())); + } + if (!Name.isValidName(projectName)) { + throw new IllegalValueException(Name.MESSAGE_CONSTRAINTS); + } + final Name modelName = new Name(projectName); + + if (description == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + Description.class.getSimpleName())); + } + if (!Description.isValidDescription(description)) { + throw new IllegalValueException(Description.MESSAGE_CONSTRAINTS); + } + final Description modelDescription = new Description(description); + + if (deadlines == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + Deadline.class.getSimpleName())); + } + + // Parse and validate the set of deadlines + final List modelDeadlines = new ArrayList<>(); + for (String deadline : deadlines) { + if (!Deadline.isValidDeadline(deadline)) { + throw new IllegalValueException(Deadline.MESSAGE_CONSTRAINTS); + } + modelDeadlines.add(new Deadline(deadline, modelDeadlines.size() + 1)); + } + + return new Project(modelName, modelDescription, modelDeadlines); + } + +} 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/JsonSerializableAddressBook.java b/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java index 5efd834091d..81ad5e5ee32 100644 --- a/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java +++ b/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java @@ -11,24 +11,33 @@ import seedu.address.commons.exceptions.IllegalValueException; import seedu.address.model.AddressBook; import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.person.Person; +import seedu.address.model.client.Client; +import seedu.address.model.developer.Developer; /** * An Immutable AddressBook that is serializable to JSON format. */ @JsonRootName(value = "addressbook") -class JsonSerializableAddressBook { +public class JsonSerializableAddressBook { - public static final String MESSAGE_DUPLICATE_PERSON = "Persons list contains duplicate person(s)."; + public static final String MESSAGE_DUPLICATE_DEVELOPER = "Persons list contains duplicate developer(s)."; + public static final String MESSAGE_DUPLICATE_CLIENT = "Persons list contains duplicate client(s)."; + public static final String MESSAGE_DUPLICATE_PROJECT = "Persons list contains duplicate project(s)."; - private final List persons = new ArrayList<>(); + private final List developers = new ArrayList<>(); + private final List clients = new ArrayList<>(); + private final List projects = new ArrayList<>(); /** * Constructs a {@code JsonSerializableAddressBook} with the given persons. */ @JsonCreator - public JsonSerializableAddressBook(@JsonProperty("persons") List persons) { - this.persons.addAll(persons); + public JsonSerializableAddressBook(@JsonProperty("developers") List developers, + @JsonProperty("clients") List clients, + @JsonProperty("projects") List projects) { + this.developers.addAll(developers); + this.clients.addAll(clients); + this.projects.addAll(projects); } /** @@ -37,7 +46,10 @@ public JsonSerializableAddressBook(@JsonProperty("persons") List readUserPrefs() throws DataLoadingException { /** * Similar to {@link #readUserPrefs()} + * * @param prefsFilePath location of the data. Cannot be null. * @throws DataLoadingException if the file format is not as expected. */ diff --git a/src/main/java/seedu/address/storage/UserPrefsStorage.java b/src/main/java/seedu/address/storage/UserPrefsStorage.java index e94ca422ea8..39a0baac068 100644 --- a/src/main/java/seedu/address/storage/UserPrefsStorage.java +++ b/src/main/java/seedu/address/storage/UserPrefsStorage.java @@ -28,6 +28,7 @@ public interface UserPrefsStorage { /** * Saves the given {@link seedu.address.model.ReadOnlyUserPrefs} to the storage. + * * @param userPrefs cannot be null. * @throws IOException if there was any problem writing to the file. */ diff --git a/src/main/java/seedu/address/ui/PersonCard.java b/src/main/java/seedu/address/ui/ClientCard.java similarity index 53% rename from src/main/java/seedu/address/ui/PersonCard.java rename to src/main/java/seedu/address/ui/ClientCard.java index 094c42cda82..32fd2fc07c9 100644 --- a/src/main/java/seedu/address/ui/PersonCard.java +++ b/src/main/java/seedu/address/ui/ClientCard.java @@ -1,20 +1,19 @@ 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; +import seedu.address.model.client.Client; + /** - * An UI component that displays information of a {@code Person}. + * A UI component that displays information of a {@code Client}. */ -public class PersonCard extends UiPart { +public class ClientCard extends UiPart { - private static final String FXML = "PersonListCard.fxml"; + private static final String FXML = "ClientListCard.fxml"; /** * Note: Certain keywords such as "location" and "resources" are reserved keywords in JavaFX. @@ -24,7 +23,7 @@ public class PersonCard extends UiPart { * @see The issue on AddressBook level 4 */ - public final Person person; + public final Client client; @FXML private HBox cardPane; @@ -35,25 +34,36 @@ public class PersonCard extends UiPart { @FXML private Label phone; @FXML + private Label email; + @FXML + private Label role; + @FXML private Label address; @FXML - private Label email; + private Label organisation; + @FXML + private Label document; + + @FXML private FlowPane tags; /** * Creates a {@code PersonCode} with the given {@code Person} and index to display. */ - public PersonCard(Person person, int displayedIndex) { + public ClientCard(Client client, int displayedIndex) { super(FXML); - this.person = person; + this.client = client; 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))); + name.setText(client.getName().fullName); + phone.setText("Contact: " + client.getPhone().value); + email.setText("Email: " + client.getEmail().value); + role.setText("Role: " + client.getRole().role); + organisation.setText("Company: " + client.getOrganisation().fullName); + document.setText("Document: " + client.getDocument().toString()); + + address.setText("Address: " + client.getAddress().value); + client.getProjects().stream() + .forEach(tag -> tags.getChildren().add(new Label(tag))); } } diff --git a/src/main/java/seedu/address/ui/PersonListPanel.java b/src/main/java/seedu/address/ui/ClientListPanel.java similarity index 55% rename from src/main/java/seedu/address/ui/PersonListPanel.java rename to src/main/java/seedu/address/ui/ClientListPanel.java index f4c501a897b..6991af22771 100644 --- a/src/main/java/seedu/address/ui/PersonListPanel.java +++ b/src/main/java/seedu/address/ui/ClientListPanel.java @@ -8,40 +8,40 @@ import javafx.scene.control.ListView; import javafx.scene.layout.Region; import seedu.address.commons.core.LogsCenter; -import seedu.address.model.person.Person; +import seedu.address.model.client.Client; /** * 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); +public class ClientListPanel extends UiPart { + private static final String FXML = "ClientListPanel.fxml"; + private final Logger logger = LogsCenter.getLogger(ClientListPanel.class); @FXML - private ListView personListView; + private ListView clientListView; /** * Creates a {@code PersonListPanel} with the given {@code ObservableList}. */ - public PersonListPanel(ObservableList personList) { + public ClientListPanel(ObservableList clientList) { super(FXML); - personListView.setItems(personList); - personListView.setCellFactory(listView -> new PersonListViewCell()); + clientListView.setItems(clientList); + clientListView.setCellFactory(listView -> new ClientListViewCell()); } /** - * Custom {@code ListCell} that displays the graphics of a {@code Person} using a {@code PersonCard}. + * Custom {@code ListCell} that displays the graphics of a {@code Person} using a {@code DeveloperCard}. */ - class PersonListViewCell extends ListCell { + class ClientListViewCell extends ListCell { @Override - protected void updateItem(Person person, boolean empty) { + protected void updateItem(Client person, boolean empty) { super.updateItem(person, empty); if (empty || person == null) { setGraphic(null); setText(null); } else { - setGraphic(new PersonCard(person, getIndex() + 1).getRoot()); + setGraphic(new ClientCard(person, getIndex() + 1).getRoot()); } } } diff --git a/src/main/java/seedu/address/ui/DeveloperCard.java b/src/main/java/seedu/address/ui/DeveloperCard.java new file mode 100644 index 00000000000..74364dd71bb --- /dev/null +++ b/src/main/java/seedu/address/ui/DeveloperCard.java @@ -0,0 +1,108 @@ +package seedu.address.ui; + +import java.text.SimpleDateFormat; +import java.time.Period; +import java.time.ZoneId; +import java.util.Comparator; +import java.util.Date; + +import org.controlsfx.control.Rating; + +import javafx.event.EventHandler; +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.input.MouseEvent; +import javafx.scene.layout.FlowPane; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Region; +import seedu.address.model.developer.Developer; + + +/** + * A UI component that displays information of a {@code Developer}. + */ +public class DeveloperCard extends UiPart { + + private static final String FXML = "DeveloperListCard.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 Developer developer; + + @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 Label dateJoined; + @FXML + private Label role; + @FXML + private Label salary; + + @FXML + private Label githubId; + @FXML + private Rating rating; + @FXML + private FlowPane tags; + + /** + * Creates a {@code PersonCode} with the given {@code Developer} and index to display. + */ + public DeveloperCard(seedu.address.model.developer.Developer developer, int displayedIndex) { + super(FXML); + this.developer = developer; + id.setText(displayedIndex + ". "); + name.setText(developer.getName().fullName); + phone.setText("Contact: " + developer.getPhone().value); + email.setText("Email: " + developer.getEmail().value); + role.setText("Role: " + developer.getRole().role); + githubId.setText("GitHub ID: " + developer.getGithubId().username); + rating.setPartialRating(true); + rating.setUpdateOnHover(false); + rating.setOnMousePressed(null); + rating.setOnMouseEntered(null); + //rating.setDisable(true); + rating.setOnMouseClicked(new EventHandler() { + @Override + public void handle(MouseEvent event) { + + } + }); + rating.setMax(5); + rating.autosize(); + rating.setDisable(true); + rating.setRating(developer.getRating().rating); + address.setText("Address: " + developer.getAddress().value); + SimpleDateFormat dateFormat = new SimpleDateFormat("dd-MM-yyyy"); + Date joined = developer.getDateJoined().value; + // Calculate the period between the two dates + Period period = Period.between(joined.toInstant().atZone(ZoneId.systemDefault()).toLocalDate(), + new Date().toInstant().atZone(ZoneId.systemDefault()).toLocalDate()); + + // Extract years and months from the period + int years = period.getYears(); + int months = period.getMonths(); + dateJoined.setText("Date Joined: " + dateFormat.format(joined) + "\n(" + years + " years " + months + + " months)"); + salary.setText("Salary: $" + developer.getSalary().salary); + developer.getProjects().stream() + .sorted(Comparator.comparing(tag -> tag)) + .forEach(tag -> tags.getChildren().add(new Label(tag))); + } +} diff --git a/src/main/java/seedu/address/ui/DeveloperListPanel.java b/src/main/java/seedu/address/ui/DeveloperListPanel.java new file mode 100644 index 00000000000..b604057db56 --- /dev/null +++ b/src/main/java/seedu/address/ui/DeveloperListPanel.java @@ -0,0 +1,49 @@ +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.developer.Developer; + +/** + * Panel containing the list of persons. + */ +public class DeveloperListPanel extends UiPart { + private static final String FXML = "DeveloperListPanel.fxml"; + private final Logger logger = LogsCenter.getLogger(DeveloperListPanel.class); + + @FXML + private ListView developerListView; + + /** + * Creates a {@code PersonListPanel} with the given {@code ObservableList}. + */ + public DeveloperListPanel(ObservableList developerList) { + super(FXML); + developerListView.setItems(developerList); + developerListView.setCellFactory(listView -> new DeveloperListViewCell()); + } + + /** + * Custom {@code ListCell} that displays the graphics of a {@code Person} using a {@code DeveloperCard}. + */ + class DeveloperListViewCell extends ListCell { + @Override + protected void updateItem(Developer person, boolean empty) { + super.updateItem(person, empty); + + if (empty || person == null) { + setGraphic(null); + setText(null); + } else { + setGraphic(new DeveloperCard(person, getIndex() + 1).getRoot()); + } + } + } + +} diff --git a/src/main/java/seedu/address/ui/HelpWindow.java b/src/main/java/seedu/address/ui/HelpWindow.java index 3f16b2fcf26..2819226f004 100644 --- a/src/main/java/seedu/address/ui/HelpWindow.java +++ b/src/main/java/seedu/address/ui/HelpWindow.java @@ -15,7 +15,7 @@ */ public class HelpWindow extends UiPart { - public static final String USERGUIDE_URL = "https://se-education.org/addressbook-level3/UserGuide.html"; + public static final String USERGUIDE_URL = "https://ay2324s1-cs2103t-t09-2.github.io/tp/UserGuide.html"; public static final String HELP_MESSAGE = "Refer to the user guide: " + USERGUIDE_URL; private static final Logger logger = LogsCenter.getLogger(HelpWindow.class); @@ -46,21 +46,21 @@ public HelpWindow() { /** * Shows the help window. - * @throws IllegalStateException - *

    - *
  • - * if this method is called on a thread other than the JavaFX Application Thread. - *
  • - *
  • - * if this method is called during animation or layout processing. - *
  • - *
  • - * if this method is called on the primary stage. - *
  • - *
  • - * if {@code dialogStage} is already showing. - *
  • - *
+ * + * @throws IllegalStateException
    + *
  • + * if this method is called on a thread other than the JavaFX Application Thread. + *
  • + *
  • + * if this method is called during animation or layout processing. + *
  • + *
  • + * if this method is called on the primary stage. + *
  • + *
  • + * if {@code dialogStage} is already showing. + *
  • + *
*/ public void show() { logger.fine("Showing help page about the application."); diff --git a/src/main/java/seedu/address/ui/MainWindow.java b/src/main/java/seedu/address/ui/MainWindow.java index 79e74ef37c0..cb2c7c4c4ac 100644 --- a/src/main/java/seedu/address/ui/MainWindow.java +++ b/src/main/java/seedu/address/ui/MainWindow.java @@ -5,6 +5,8 @@ import javafx.event.ActionEvent; import javafx.fxml.FXML; import javafx.scene.control.MenuItem; +import javafx.scene.control.Tab; +import javafx.scene.control.TabPane; import javafx.scene.control.TextInputControl; import javafx.scene.input.KeyCombination; import javafx.scene.input.KeyEvent; @@ -31,9 +33,12 @@ public class MainWindow extends UiPart { private Logic logic; // Independent Ui parts residing in this Ui container - private PersonListPanel personListPanel; + private DeveloperListPanel developerListPanel; + private ClientListPanel clientListPanel; + private ProjectListPanel projectListPanel; private ResultDisplay resultDisplay; private HelpWindow helpWindow; + private int resultTabIndex; @FXML private StackPane commandBoxPlaceholder; @@ -42,7 +47,23 @@ public class MainWindow extends UiPart { private MenuItem helpMenuItem; @FXML - private StackPane personListPanelPlaceholder; + private StackPane developerListPanelPlaceholder; + @FXML + private StackPane clientListPanelPlaceholder; + @FXML + private StackPane projectListPanelPlaceholder; + + @FXML + private TabPane tabPane; + + @FXML + private Tab developerTab; + + @FXML + private Tab clientTab; + + @FXML + private Tab projectTab; @FXML private StackPane resultDisplayPlaceholder; @@ -50,6 +71,7 @@ public class MainWindow extends UiPart { @FXML private StackPane statusbarPlaceholder; + /** * Creates a {@code MainWindow} with the given {@code Stage} and {@code Logic}. */ @@ -78,6 +100,7 @@ private void setAccelerators() { /** * Sets the accelerator of a MenuItem. + * * @param keyCombination the KeyCombination value of the accelerator */ private void setAccelerator(MenuItem menuItem, KeyCombination keyCombination) { @@ -110,10 +133,47 @@ private void setAccelerator(MenuItem menuItem, KeyCombination keyCombination) { * Fills up all the placeholders of this window. */ void fillInnerParts() { - personListPanel = new PersonListPanel(logic.getFilteredPersonList()); - personListPanelPlaceholder.getChildren().add(personListPanel.getRoot()); + //tabPane = new TabPane(); + // Create tabs + /*developerTab = new Tab("Developer"); + clientTab = new Tab("Client"); + projectTab = new Tab("Proct");*/ + + + developerListPanel = new DeveloperListPanel(logic.getFilteredDeveloperList()); + developerListPanelPlaceholder.getChildren().add(developerListPanel.getRoot()); + + clientListPanel = new ClientListPanel(logic.getFilteredClientList()); + clientListPanelPlaceholder.getChildren().add(clientListPanel.getRoot()); + + projectListPanel = new ProjectListPanel(logic.getFilteredProjectList()); + projectListPanelPlaceholder.getChildren().add(projectListPanel.getRoot()); + + // Add content to the tabs (you can add any JavaFX Node) + developerTab.setContent(developerListPanelPlaceholder); + clientTab.setContent(clientListPanelPlaceholder); + projectTab.setContent(projectListPanelPlaceholder); + + // Add tabs to the TabPane + tabPane.getTabs().addAll(developerTab, clientTab, projectTab); + tabPane.getSelectionModel().selectedIndexProperty().addListener((observable, oldValue, newValue) -> { + if (newValue.intValue() != resultTabIndex) { + try { + if (newValue.intValue() == 0) { + executeCommand("list-developer"); + } else if (newValue.intValue() == 1) { + executeCommand("list-client"); + } else if (newValue.intValue() == 2) { + executeCommand("list-project"); + } + } catch (CommandException | ParseException e) { + e.printStackTrace(); + } + } + }); resultDisplay = new ResultDisplay(); + resultDisplay.setFeedbackToUser("Welcome to CodeContact!\nUnlock to continue."); resultDisplayPlaceholder.getChildren().add(resultDisplay.getRoot()); StatusBarFooter statusBarFooter = new StatusBarFooter(logic.getAddressBookFilePath()); @@ -163,8 +223,16 @@ private void handleExit() { primaryStage.hide(); } - public PersonListPanel getPersonListPanel() { - return personListPanel; + public DeveloperListPanel getDeveloperListPanel() { + return developerListPanel; + } + + public ClientListPanel getClientListPanel() { + return clientListPanel; + } + + public ProjectListPanel getProjectListPanel() { + return projectListPanel; } /** @@ -177,7 +245,9 @@ private CommandResult executeCommand(String commandText) throws CommandException CommandResult commandResult = logic.execute(commandText); logger.info("Result: " + commandResult.getFeedbackToUser()); resultDisplay.setFeedbackToUser(commandResult.getFeedbackToUser()); - + resultTabIndex = commandResult.getIndex(); + System.out.println("resultTabIndex: " + resultTabIndex); + tabPane.getSelectionModel().select(resultTabIndex); if (commandResult.isShowHelp()) { handleHelp(); } diff --git a/src/main/java/seedu/address/ui/ProjectCard.java b/src/main/java/seedu/address/ui/ProjectCard.java new file mode 100644 index 00000000000..d71a7d54a58 --- /dev/null +++ b/src/main/java/seedu/address/ui/ProjectCard.java @@ -0,0 +1,230 @@ +package seedu.address.ui; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.stream.Collectors; + +import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.property.SimpleIntegerProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.beans.property.SimpleStringProperty; +import javafx.collections.FXCollections; +import javafx.collections.ListChangeListener; +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.control.ProgressBar; +import javafx.scene.control.TableCell; +import javafx.scene.control.TableColumn; +import javafx.scene.control.TableView; +import javafx.scene.control.cell.PropertyValueFactory; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Region; +import seedu.address.MainApp; +import seedu.address.model.commons.Date; +import seedu.address.model.project.Deadline; +import seedu.address.model.project.Priority; +import seedu.address.model.project.Project; + + +/** + * A UI component that displays information of a {@code Project}. + */ +public class ProjectCard extends UiPart { + + private static final String FXML = "ProjectListCard.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 Project project; + + @FXML + private HBox cardPane; + @FXML + private Label name; + + @FXML + private ProgressBar progress; + @FXML + private Label id; + @FXML + private Label description; + @FXML + private TableView table; + + + /** + * Creates a {@code PersonCode} with the given {@code Person} and index to display. + */ + public ProjectCard(Project project, int displayedIndex) { + super(FXML); + this.project = project; + id.setText(displayedIndex + ". "); + name.setText(project.getName()); + description.setText(project.getProjectDescription().desc); + TableColumn dateCol = new TableColumn("Date"); + dateCol.setMinWidth(95); + dateCol.setCellValueFactory( + new PropertyValueFactory<>("date")); + + TableColumn descriptionCol = new TableColumn("Description"); + descriptionCol.setMinWidth(100); + descriptionCol.setCellValueFactory( + new PropertyValueFactory<>("description")); + + TableColumn priorityCol = new TableColumn("Priority"); + priorityCol.setMinWidth(95); + priorityCol.setCellValueFactory( + new PropertyValueFactory<>("priority")); + priorityCol.setCellFactory(column -> new TableCell() { + @Override + protected void updateItem(String item, boolean empty) { + super.updateItem(item, empty); + + if (empty || item == null) { + // For empty cells, set no text and no background color + setText(""); + setStyle(""); // Clear any previous styling + } else { + setText(item); // Set the text + + // Customize cell color based on priority + if ("HIGH".equals(item)) { + // Set the cell background color for HIGH priority + setStyle("-fx-background-color: #FFC0CB;"); + } else if ("MEDIUM".equals(item)) { + // Set the cell background color for MEDIUM priority + setStyle("-fx-background-color: #FFFFE0;"); + } else if ("LOW".equals(item)) { + // Set the cell background color for LOW priority + setStyle("-fx-background-color: #90EE90;"); + } else { + // Handle any other cases or priority values + setStyle(""); // Clear any previous styling + } + } + } + }); + TableColumn doneCol = new TableColumn("Done"); + doneCol.setMinWidth(95); + Image imageTrue = new Image(MainApp.class.getResourceAsStream("/images/tick.png")); + Image imageFalse = new Image(MainApp.class.getResourceAsStream("/images/cross.png")); + doneCol.setCellFactory(col -> new TableCell() { + + private final ImageView imageView = new ImageView(); + + { + // initialize ImageView + set as graphic + imageView.setFitWidth(20); + imageView.setFitHeight(20); + setGraphic(imageView); + } + + @Override + protected void updateItem(Boolean item, boolean empty) { + if (empty || item == null) { + // no image for empty cells + imageView.setImage(null); + } else { + // set image for non-empty cell + imageView.setImage(item ? imageTrue : imageFalse); + } + } + + }); + doneCol.setCellValueFactory( + new PropertyValueFactory<>("isDone")); + TableColumn indexCol = new TableColumn("#"); + indexCol.setMinWidth(20); + indexCol.setCellValueFactory( + new PropertyValueFactory<>("index")); + table.getColumns().addAll(indexCol, doneCol, priorityCol, dateCol, descriptionCol); + ObservableList data = FXCollections.observableList(project.getProjectFilteredDeadlines().stream() + .map(deadline -> new Data(deadline)).collect(Collectors.toList())); + project.getProjectFilteredDeadlines().addListener(new ListChangeListener() { + @Override + public void onChanged(Change c) { + ObservableList data = FXCollections.observableList(project.getProjectFilteredDeadlines().stream() + .map(deadline -> new Data(deadline)).collect(Collectors.toList())); + progress.setProgress(data.stream().filter(t -> t.getIsDone()).count() * 1.0 / data.size()); + table.setItems(data); + } + }); + + progress.setProgress(data.stream().filter(t -> t.getIsDone()).count() * 1.0 / data.size()); + table.setItems(data); + } + /** + * A class to represent data related to project deadlines. + */ + public static class Data { + private final SimpleObjectProperty date; + private final SimpleStringProperty description; + private final SimpleObjectProperty priority; + + private final SimpleBooleanProperty isDone; + private final SimpleIntegerProperty index; + private final SimpleDateFormat format = new SimpleDateFormat("dd-MM-yyyy"); + + private Data(Deadline deadline) { + date = new SimpleObjectProperty(deadline.getDate()); + description = new SimpleStringProperty(deadline.getDescription().desc); + priority = new SimpleObjectProperty(deadline.getPriority()); + isDone = new SimpleBooleanProperty(deadline.getIsDone()); + index = new SimpleIntegerProperty(deadline.getNum()); + } + + public int getIndex() { + return index.get(); + } + + public void setIndex(int num) { + this.index.set(num); + } + + public String getDate() { + return date.get().toString(); + } + + public void setDate(String date) throws ParseException { + this.date.set(new seedu.address.model.commons.Date(date, true)); + } + + public Date getDateObject() { + return date.get(); + } + + public boolean getIsDone() { + return isDone.get(); + } + + public void setIsDone(boolean isDone) { + this.isDone.set(isDone); + } + + public String getDescription() { + return description.get(); + } + + public void setDescription(String description) { + this.description.set(description); + } + + public String getPriority() { + return priority.get().toString(); + } + + public void setPriority(String priority) { + this.priority.set(Priority.valueOf(priority)); + } + + } +} diff --git a/src/main/java/seedu/address/ui/ProjectListPanel.java b/src/main/java/seedu/address/ui/ProjectListPanel.java new file mode 100644 index 00000000000..cf0ff7f8c97 --- /dev/null +++ b/src/main/java/seedu/address/ui/ProjectListPanel.java @@ -0,0 +1,49 @@ +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.project.Project; + +/** + * Panel containing the list of persons. + */ +public class ProjectListPanel extends UiPart { + private static final String FXML = "ProjectListPanel.fxml"; + private final Logger logger = LogsCenter.getLogger(ProjectListPanel.class); + + @FXML + private ListView projectListView; + + /** + * Creates a {@code PersonListPanel} with the given {@code ObservableList}. + */ + public ProjectListPanel(ObservableList projectList) { + super(FXML); + projectListView.setItems(projectList); + projectListView.setCellFactory(listView -> new ProjectListViewCell()); + } + + /** + * Custom {@code ListCell} that displays the graphics of a {@code Person} using a {@code DeveloperCard}. + */ + class ProjectListViewCell extends ListCell { + @Override + protected void updateItem(Project person, boolean empty) { + super.updateItem(person, empty); + + if (empty || person == null) { + setGraphic(null); + setText(null); + } else { + setGraphic(new ProjectCard(person, getIndex() + 1).getRoot()); + } + } + } + +} diff --git a/src/main/java/seedu/address/ui/Ui.java b/src/main/java/seedu/address/ui/Ui.java index 17aa0b494fe..e28dae7e990 100644 --- a/src/main/java/seedu/address/ui/Ui.java +++ b/src/main/java/seedu/address/ui/Ui.java @@ -7,7 +7,9 @@ */ public interface Ui { - /** Starts the UI (and the App). */ + /** + * Starts the UI (and the App). + */ void start(Stage primaryStage); } diff --git a/src/main/java/seedu/address/ui/UiManager.java b/src/main/java/seedu/address/ui/UiManager.java index fdf024138bc..f55f157abd6 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/logo.png"; private Logic logic; private MainWindow mainWindow; @@ -32,47 +32,51 @@ public UiManager(Logic logic) { this.logic = logic; } + /** + * Shows an alert dialog on {@code owner} with the given parameters. + * This method only returns after the user has closed the alert dialog. + */ + 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/LightTheme.css"); + alert.initOwner(owner); + alert.setTitle(title); + alert.setHeaderText(headerText); + alert.setContentText(contentText); + alert.getDialogPane().setId(ALERT_DIALOG_PANE_FIELD_ID); + alert.showAndWait(); + } + void showAlertDialogAndWait(Alert.AlertType type, String title, String headerText, String contentText) { + showAlertDialogAndWait(mainWindow.getPrimaryStage(), type, title, headerText, contentText); + } + @Override public void start(Stage primaryStage) { logger.info("Starting UI..."); //Set the application icon. primaryStage.getIcons().add(getImage(ICON_APPLICATION)); + primaryStage.setTitle("CodeContact"); try { mainWindow = new MainWindow(primaryStage, logic); mainWindow.show(); //This should be called before creating other UI parts mainWindow.fillInnerParts(); + } catch (Throwable e) { logger.severe(StringUtil.getDetails(e)); showFatalErrorDialogAndShutdown("Fatal error during initializing", e); } } + private Image getImage(String imagePath) { return new Image(MainApp.class.getResourceAsStream(imagePath)); } - void showAlertDialogAndWait(Alert.AlertType type, String title, String headerText, String contentText) { - showAlertDialogAndWait(mainWindow.getPrimaryStage(), type, title, headerText, contentText); - } - /** - * Shows an alert dialog on {@code owner} with the given parameters. - * This method only returns after the user has closed the alert dialog. - */ - 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.initOwner(owner); - alert.setTitle(title); - alert.setHeaderText(headerText); - alert.setContentText(contentText); - alert.getDialogPane().setId(ALERT_DIALOG_PANE_FIELD_ID); - alert.showAndWait(); - } /** * Shows an error alert dialog with {@code title} and error message, {@code e}, diff --git a/src/main/java/seedu/address/ui/UiPart.java b/src/main/java/seedu/address/ui/UiPart.java index fc820e01a9c..017725f04db 100644 --- a/src/main/java/seedu/address/ui/UiPart.java +++ b/src/main/java/seedu/address/ui/UiPart.java @@ -14,7 +14,9 @@ */ public abstract class UiPart { - /** Resource folder where FXML files are stored. */ + /** + * Resource folder where FXML files are stored. + */ public static final String FXML_FILE_FOLDER = "/view/"; private final FXMLLoader fxmlLoader = new FXMLLoader(); @@ -29,6 +31,7 @@ public UiPart(URL fxmlFileUrl) { /** * Constructs a UiPart using the specified FXML file within {@link #FXML_FILE_FOLDER}. + * * @see #UiPart(URL) */ public UiPart(String fxmlFileName) { @@ -45,12 +48,23 @@ public UiPart(URL fxmlFileUrl, T root) { /** * Constructs a UiPart with the specified FXML file within {@link #FXML_FILE_FOLDER} and root object. + * * @see #UiPart(URL, T) */ public UiPart(String fxmlFileName, T root) { this(getFxmlFileUrl(fxmlFileName), root); } + /** + * Returns the FXML file URL for the specified FXML file name within {@link #FXML_FILE_FOLDER}. + */ + private static URL getFxmlFileUrl(String fxmlFileName) { + requireNonNull(fxmlFileName); + String fxmlFileNameWithFolder = FXML_FILE_FOLDER + fxmlFileName; + URL fxmlFileUrl = MainApp.class.getResource(fxmlFileNameWithFolder); + return requireNonNull(fxmlFileUrl); + } + /** * Returns the root object of the scene graph of this UiPart. */ @@ -60,8 +74,9 @@ public T getRoot() { /** * Loads the object hierarchy from a FXML document. + * * @param location Location of the FXML document. - * @param root Specifies the root of the object hierarchy. + * @param root Specifies the root of the object hierarchy. */ private void loadFxmlFile(URL location, T root) { requireNonNull(location); @@ -75,14 +90,4 @@ private void loadFxmlFile(URL location, T root) { } } - /** - * Returns the FXML file URL for the specified FXML file name within {@link #FXML_FILE_FOLDER}. - */ - private static URL getFxmlFileUrl(String fxmlFileName) { - requireNonNull(fxmlFileName); - String fxmlFileNameWithFolder = FXML_FILE_FOLDER + fxmlFileName; - URL fxmlFileUrl = MainApp.class.getResource(fxmlFileNameWithFolder); - return requireNonNull(fxmlFileUrl); - } - } diff --git a/src/main/resources/images/cross.png b/src/main/resources/images/cross.png new file mode 100644 index 00000000000..f0c0937616e Binary files /dev/null and b/src/main/resources/images/cross.png differ diff --git a/src/main/resources/images/logo.png b/src/main/resources/images/logo.png new file mode 100644 index 00000000000..53ead2691e1 Binary files /dev/null and b/src/main/resources/images/logo.png differ diff --git a/src/main/resources/images/tick.png b/src/main/resources/images/tick.png new file mode 100644 index 00000000000..21289a3191a Binary files /dev/null and b/src/main/resources/images/tick.png differ diff --git a/src/main/resources/view/ClientListCard.fxml b/src/main/resources/view/ClientListCard.fxml new file mode 100644 index 00000000000..f371dfdecc2 --- /dev/null +++ b/src/main/resources/view/ClientListCard.fxml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/ClientListPanel.fxml b/src/main/resources/view/ClientListPanel.fxml new file mode 100644 index 00000000000..bbc34d1f816 --- /dev/null +++ b/src/main/resources/view/ClientListPanel.fxml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/main/resources/view/CommandBox.fxml b/src/main/resources/view/CommandBox.fxml index 124283a392e..3d7a93d0998 100644 --- a/src/main/resources/view/CommandBox.fxml +++ b/src/main/resources/view/CommandBox.fxml @@ -2,8 +2,7 @@ - - - + + 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/DeveloperListCard.fxml b/src/main/resources/view/DeveloperListCard.fxml new file mode 100644 index 00000000000..740313466c6 --- /dev/null +++ b/src/main/resources/view/DeveloperListCard.fxml @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/DeveloperListPanel.fxml b/src/main/resources/view/DeveloperListPanel.fxml new file mode 100644 index 00000000000..6d7da279478 --- /dev/null +++ b/src/main/resources/view/DeveloperListPanel.fxml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/main/resources/view/Extensions.css b/src/main/resources/view/Extensions.css index bfe82a85964..fb42383e155 100644 --- a/src/main/resources/view/Extensions.css +++ b/src/main/resources/view/Extensions.css @@ -3,10 +3,7 @@ -fx-text-fill: #d06651 !important; /* The error class should always override the default text-fill style */ } -.list-cell:empty { - /* Empty cells will not have alternating colours */ - -fx-background: #383838; -} + .tag-selector { -fx-border-width: 1; diff --git a/src/main/resources/view/HelpWindow.fxml b/src/main/resources/view/HelpWindow.fxml index e01f330de33..f237e789fc9 100644 --- a/src/main/resources/view/HelpWindow.fxml +++ b/src/main/resources/view/HelpWindow.fxml @@ -2,43 +2,43 @@ - + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + diff --git a/src/main/resources/view/LightTheme.css b/src/main/resources/view/LightTheme.css new file mode 100644 index 00000000000..c5e5439430e --- /dev/null +++ b/src/main/resources/view/LightTheme.css @@ -0,0 +1,547 @@ +/* Light Theme for Contact Manager Application */ + +/* General Styles */ +.background { + -fx-background-color: #ffffff; +} + +.label { + -fx-font-size: 11pt; + -fx-font-family: "Segoe UI Semibold"; + -fx-text-fill: #333333; /* Dark gray text color */ +} + +.label-bright { + -fx-font-size: 11pt; + -fx-font-family: "Segoe UI Semibold"; + -fx-text-fill: #0078d4; /* Blue text color for bright labels */ +} + +/* Tab Styles */ +* { + -fx-primary-color: #dddddd; /* Your primary color */ + -fx-secondary-color: #f3f3f3; /* Your secondary color */ + + -fx-focus-color: -fx-secondary-color; +} + +.tab-header-area .tab-header-background { + -fx-background-color: -fx-primary-color; +} + +.tab-pane .tab:selected { + -fx-background-color: -fx-secondary-color; + -fx-background-insets: 0; + -fx-background-radius: 0; +} + +.tab .tab-label { + -fx-font-size: 13pt; + -fx-alignment: CENTER; + -fx-text-fill: black; /* Change font color to black */ + -fx-font-weight: bold; + -fx-padding: 0 5; +} + +.tab-pane:top *.tab-header-area { + -fx-size: 35; + -fx-padding: 0 0; /* Updated padding for top tab pane */ +} + +.tab-pane { + -fx-background-color: transparent; + -fx-tab-min-width: 212px; /* Adjusted minimum tab width */ + -fx-tab-min-height: 33px; + -fx-tab-max-height: 33px; +} + +.tab-pane .tab { + -fx-background-color: transparent; +} + +.tab-pane:focused .tab-header-area .headers-region .tab:selected .focus-indicator { + -fx-border-width: 0; + -fx-border-insets: 0; + -fx-border-radius: 0; +} + +/* TableView Styles */ +.table-view { + -fx-base: #ffffff; + -fx-control-inner-background: #ffffff; + -fx-background-color: #ffffff; + -fx-table-cell-border-color: transparent; + -fx-table-header-border-color: #e2e2e2; /* Light gray border color for table headers */ + -fx-padding: 5; +} + +.table-view .column-header-background { + -fx-background-color: #f3f3f3; +} + +.table-view .column-header, .table-view .filler { + -fx-size: 35; + -fx-border-width: 0 0 1 0; + -fx-background-color: #f3f3f3; + -fx-border-color: transparent transparent #e2e2e2 transparent; /* Light gray border color for table columns */ + -fx-border-insets: 0 10 1 0; +} + +.table-view .column-header .label { + -fx-font-size: 12pt; + -fx-font-family: "Segoe UI Light"; + -fx-text-fill: #333333; /* Dark gray text color for column headers */ +} + +.table-view:focused .table-row-cell:filled:focused:selected { + -fx-background-color: #0078d4; /* Blue background color for selected rows */ +} + +/* TabPane Styles */ +.tab-pane { + -fx-padding: 0 0 0 1; + -fx-background-color: #ffffff; +} + +.tab-pane .tab-header-area { + -fx-padding: 0 0 0 0; + -fx-min-height: 0; + -fx-max-height: 0; + -fx-background-color: #ffffff; +} + +/* List Styles */ +.list-view { + -fx-background-insets: 0; + -fx-padding: 0; + -fx-background-color: #f3f3f3; /* Light gray background color for lists */ +} + +.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: #e3e3e3; /* Slightly darker background color for even list cells */ +} + +.list-cell:filled:odd { + -fx-background-color: #f3f3f3; /* Lighter background color for odd list cells */ +} + +.list-cell:filled:selected { + -fx-background-color: #0078d4; /* Blue background color for selected list cells */ +} + +.list-cell:filled:selected #cardPane { + -fx-border-color: #003d72; /* Dark blue border color for selected cells */ + -fx-border-width: 1; +} + +.list-cell .label { + -fx-text-fill: #333333; /* Dark gray text color for list labels */ +} + +.cell_big_label { + -fx-font-family: "Segoe UI Semibold"; + -fx-font-size: 16pt; + -fx-text-fill: #010504; +} + +.cell_small_label { + -fx-font-family: "Segoe UI"; + -fx-font-size: 13pt; + -fx-text-fill: #010504; +} + +/* StackPane Styles */ +.stack-pane { + -fx-background-color: #ffffff; +} + +/* Pane with Border Styles */ +.pane-with-border { + -fx-background-color: #ffffff; + -fx-border-color: #e2e2e2; /* Light gray border color */ + -fx-border-top-width: 1px; +} + +/* Status Bar Styles */ +.status-bar { + -fx-background-color: #f3f3f3; /* Light gray background color for the status bar */ +} + +.result-display { + -fx-background-color: transparent; + -fx-font-family: "Segoe UI Light"; + -fx-font-size: 13pt; + -fx-text-fill: #333333; /* Dark gray text color for the result display */ +} + +.result-display .label { + -fx-text-fill: black !important; +} + +.status-bar .label { + -fx-font-family: "Segoe UI Light"; + -fx-text-fill: #333333; /* Dark gray text color for status bar labels */ + -fx-padding: 4px; + -fx-pref-height: 30px; +} + +.status-bar-with-border { + -fx-background-color: #f3f3f3; /* Light gray background color for status bar with border */ + -fx-border-color: #e2e2e2; /* Light gray border color */ + -fx-border-width: 1px; +} + +.status-bar-with-border .label { + -fx-text-fill: #333333; /* Dark gray text color for status bar with border labels */ +} + +/* Grid Pane Styles */ +.grid-pane { + -fx-background-color: #f3f3f3; /* Light gray background color for grid panes */ + -fx-border-color: #e2e2e2; /* Light gray border color */ + -fx-border-width: 1px; +} + +.grid-pane .stack-pane { + -fx-background-color: #f3f3f3; /* Light gray background color for stack panes */ +} + +/* Context Menu Styles */ +.context-menu { + -fx-background-color: #ffffff; +} + +.context-menu .label { + -fx-text-fill: #333333; /* Dark gray text color for context menu labels */ +} + +/* Menu Bar Styles */ +.menu-bar { + -fx-background-color: #ffffff; +} + +.menu-bar .label { + -fx-font-size: 14pt; + -fx-font-family: "Segoe UI Light"; + -fx-text-fill: #333333; /* Dark gray text color for menu bar labels */ + -fx-opacity: 0.9; +} + +/* Button Styles */ +.button { + -fx-padding: 5 22 5 22; + -fx-border-color: #e2e2e2; /* Light gray border color for buttons */ + -fx-border-width: 2; + -fx-background-radius: 0; + -fx-background-color: #ffffff; /* White background for buttons */ + -fx-font-family: "Segoe UI", Helvetica, Arial, sans-serif; + -fx-font-size: 11pt; + -fx-text-fill: #333333; /* Dark gray text color for buttons */ + -fx-background-insets: 0 0 0 0 +} +/* StackPane Styles */ +.stack-pane { + -fx-background-color: #ffffff; +} + +/* Pane with Border Styles */ +.pane-with-border { + -fx-background-color: #ffffff; + -fx-border-color: #e2e2e2; /* Light gray border color */ + -fx-border-top-width: 1px; +} + +/* Status Bar Styles */ +.status-bar { + -fx-background-color: #f3f3f3; /* Light gray background color for the status bar */ +} + +.result-display { + -fx-background-color: transparent; + -fx-font-family: "Segoe UI Light"; + -fx-font-size: 13pt; + -fx-text-fill: #333333; /* Dark gray text color for the result display */ +} + +.result-display .label { + -fx-text-fill: black !important; +} + +.status-bar .label { + -fx-font-family: "Segoe UI Light"; + -fx-text-fill: #333333; /* Dark gray text color for status bar labels */ + -fx-padding: 4px; + -fx-pref-height: 30px; +} + +/* Status Bar with Border Styles */ +.status-bar-with-border { + -fx-background-color: #f3f3f3; /* Light gray background color for status bar with border */ + -fx-border-color: #e2e2e2; /* Light gray border color */ + -fx-border-width: 1px; +} + +.status-bar-with-border .label { + -fx-text-fill: #333333; /* Dark gray text color for status bar with border labels */ +} + +/* Grid Pane Styles */ +.grid-pane { + -fx-background-color: #f3f3f3; /* Light gray background color for grid panes */ + -fx-border-color: #e2e2e2; /* Light gray border color */ + -fx-border-width: 1px; +} + +.grid-pane .stack-pane { + -fx-background-color: #f3f3f3; /* Light gray background color for stack panes */ +} + +/* Context Menu Styles */ +.context-menu { + -fx-background-color: #ffffff; +} + +.context-menu .label { + -fx-text-fill: #333333; /* Dark gray text color for context menu labels */ +} + +/* Menu Bar Styles */ +.menu-bar { + -fx-background-color: #ffffff; +} + +.menu-bar .label { + -fx-font-size: 14pt; + -fx-font-family: "Segoe UI Light"; + -fx-text-fill: #333333; /* Dark gray text color for menu bar labels */ + -fx-opacity: 0.9; +} + +/* Button Styles */ +.button { + -fx-padding: 5 22 5 22; + -fx-border-color: #e2e2e2; /* Light gray border color for buttons */ + -fx-border-width: 2; + -fx-background-radius: 0; + -fx-background-color: #ffffff; /* White background for buttons */ + -fx-font-family: "Segoe UI", Helvetica, Arial, sans-serif; + -fx-font-size: 11pt; + -fx-text-fill: #333333; /* Dark gray text color for buttons */ + -fx-background-insets: 0 0 0 0; +} + +.button:hover { + -fx-background-color: #f3f3f3; /* Light gray background color on hover */ +} + +.button:pressed, .button:default:hover:pressed { + -fx-background-color: #d3d3d3; /* Slightly darker background color when pressed */ + -fx-text-fill: #0078d4; /* Blue text color when pressed */ +} + +.button:focused { + -fx-border-color: #333333, #333333; /* Dark gray border color on focus */ + -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: #ffffff; /* White background for disabled buttons */ + -fx-text-fill: #333333; /* Dark gray text color for disabled buttons */ +} + +.button:default { + -fx-background-color: #0078d4; /* Blue background color for default buttons */ + -fx-text-fill: #ffffff; /* White text color for default buttons */ +} + +.button:default:hover { + -fx-background-color: #005ba2; /* Darker blue background color for default buttons on hover */ +} + +/* Dialog Pane Styles */ +.dialog-pane { + -fx-background-color: #ffffff; +} + +.dialog-pane > *.button-bar > *.container { + -fx-background-color: #ffffff; +} + +.dialog-pane > *.label.content { + -fx-font-size: 14px; + -fx-font-weight: bold; + -fx-text-fill: #333333; /* Dark gray text color for dialog content */ +} + +.dialog-pane:header *.header-panel { + -fx-background-color: #f3f3f3; /* Light gray background for dialog header panel */ +} + +.dialog-pane:header *.header-panel *.label { + -fx-font-size: 18px; + -fx-font-style: italic; + -fx-fill: #333333; + -fx-text-fill: #333333; /* Dark gray text color for dialog header labels */ +} + +/* Scroll Bar Styles */ +.scroll-bar { + -fx-background-color: #f3f3f3; /* Light gray background color for scroll bars */ +} + +.scroll-bar .thumb { + -fx-background-color: #d3d3d3; /* Slightly darker background color for the scroll thumb */ + -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 Styles */ +#cardPane { + -fx-background-color: #ffffff; + -fx-border-width: 0; +} + +/* Command Label Styles */ +#commandTypeLabel { + -fx-font-size: 11px; + -fx-text-fill: #F70D1A; /* Red text color for the command label */ +} + +/* Command TextField Styles */ +#commandTextField { + -fx-background-color: #ffffff #f3f3f3 #ffffff #f3f3f3; + -fx-background-insets: 0; + -fx-border-color: #f3f3f3 #f3f3f3 #333333 #f3f3f3; + -fx-border-insets: 0; + -fx-border-width: 1; + -fx-font-family: "Segoe UI Light"; + -fx-font-size: 13pt; + -fx-text-fill: #333333; /* Dark gray text color for the command text field */ +} + +/* Filter Field, Person List Panel, Person Webpage Styles */ +#filterField, #personListPanel, #personWebpage { + -fx-effect: innershadow(gaussian, black, 10, 0, 0, 0); +} + +/* Result Display Styles */ +#resultDisplay .content { + -fx-background-color: transparent, #f3f3f3, transparent, #f3f3f3; + -fx-background-radius: 0; +} + +/* Tags Styles */ +#tags { + -fx-hgap: 7; + -fx-vgap: 3; +} + +#tags .label { + -fx-text-fill: #ffffff; + -fx-background-color: #0078d4; /* Blue background color for tags */ + -fx-padding: 1 3 1 3; + -fx-border-radius: 2; + -fx-background-radius: 2; + -fx-font-size: 11; +} +/* Rating Styles */ +.rating > .container .button { + -fx-pref-width: 20; + -fx-pref-height: 20; + -fx-background-size: cover; + -fx-padding: 0; + -fx-border-style: none; +} + +.rating:disabled { + -fx-background-color: transparent; + -fx-background-radius: 0; + -fx-border-style: none; + -fx-opacity: 1.0; /* Full opacity */ +} + +/* TabPane Styles */ +.tab-pane { + -fx-padding: 0 0 0 1; + -fx-background-color: #ffffff; +} + +.tab-pane .tab-header-area { + -fx-padding: 0 0 0 0; + -fx-min-height: 0; + -fx-max-height: 0; + -fx-background-color: #ffffff; +} + +/* TableView Styles */ +.table-view { + -fx-base: #ffffff; + -fx-control-inner-background: #ffffff; + -fx-background-color: #ffffff; + -fx-table-cell-border-color: transparent; + -fx-table-header-border-color: #e2e2e2; /* Light gray border color for table headers */ + -fx-padding: 5; +} + +.table-view .column-header-background { + -fx-background-color: #f3f3f3; +} + +.table-view .column-header, .table-view .filler { + -fx-size: 35; + -fx-border-width: 0 0 1 0; + -fx-background-color: #f3f3f3; + -fx-border-color: transparent transparent #e2e2e2 transparent; /* Light gray border color for table columns */ + -fx-border-insets: 0 10 1 0; +} + +.table-view .column-header .label { + -fx-font-size: 14pt; + -fx-font-family: "Segoe UI Light"; + -fx-text-fill: #333333; /* Dark gray text color for column headers */ +} + +.table-view:focused .table-row-cell:filled:focused:selected { + -fx-background-color: #0078d4; /* Blue background color for selected rows */ +} + +/* TableColumn Styles */ +.table-column .label { + -fx-font-size: 14pt; + -fx-font-family: "Segoe UI Semibold"; + -fx-text-fill: #333333; /* Dark gray text color for table column labels */ +} +/* VBox Styles */ +.vbox { + -fx-background-color: #ffffff; + -fx-spacing: 10; /* Adjust the vertical spacing between children */ + -fx-padding: 20; /* Adjust the padding around the VBox */ + -fx-border-color: #e2e2e2; /* Light gray border color */ + -fx-border-width: 1px; /* Border width */ +} diff --git a/src/main/resources/view/MainWindow.fxml b/src/main/resources/view/MainWindow.fxml index 7778f666a0a..f3e5f8349a9 100644 --- a/src/main/resources/view/MainWindow.fxml +++ b/src/main/resources/view/MainWindow.fxml @@ -6,55 +6,75 @@ - + + + - - - - - - - - - - + + + + + + - - - - - - - - - + + + + + + + + + - - - - - + + + + + - - - - - + + + + + - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - + + + + + + + + diff --git a/src/main/resources/view/PersonListCard.fxml b/src/main/resources/view/PersonListCard.fxml index f5e812e25e6..2ffbf23a9ce 100644 --- a/src/main/resources/view/PersonListCard.fxml +++ b/src/main/resources/view/PersonListCard.fxml @@ -8,29 +8,33 @@ - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/PersonListPanel.fxml b/src/main/resources/view/PersonListPanel.fxml deleted file mode 100644 index a1bb6bbace8..00000000000 --- a/src/main/resources/view/PersonListPanel.fxml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/src/main/resources/view/ProjectListCard.fxml b/src/main/resources/view/ProjectListCard.fxml new file mode 100644 index 00000000000..ad96435fdfc --- /dev/null +++ b/src/main/resources/view/ProjectListCard.fxml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/ProjectListPanel.fxml b/src/main/resources/view/ProjectListPanel.fxml new file mode 100644 index 00000000000..79f4e623737 --- /dev/null +++ b/src/main/resources/view/ProjectListPanel.fxml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/main/resources/view/ResultDisplay.fxml b/src/main/resources/view/ResultDisplay.fxml index 01b691792a9..cd1eb7d92a0 100644 --- a/src/main/resources/view/ResultDisplay.fxml +++ b/src/main/resources/view/ResultDisplay.fxml @@ -3,7 +3,6 @@ - -