diff --git a/docs/UserGuide.md b/docs/UserGuide.md index 3cdf1f48280..2446aecca90 100644 --- a/docs/UserGuide.md +++ b/docs/UserGuide.md @@ -3,436 +3,712 @@ layout: page title: User Guide --- ![Banner](images/AgentAssistBanner.png) -## AgentAssist -**Transform Your Credit Card Sales with AgentAssist** — the definitive desktop tool for bank agents. Merging the swift efficiency of a Command Line Interface (CLI) with the intuitive accessibility of a Graphical User Interface (GUI), AgentAssist propels your credit card sales into the fast lane. Tailored for the agile bank agent, this application lets you manage contact databases, track sales progress, and execute transactions with unprecedented speed. Maximize your productivity, minimize your response time, and amplify your sales performance. With AgentAssist, you're not just keeping up with the competitive world of credit card sales — _you're setting the pace_. +# Welcome to the AgentAssist User Guide! ---- +The **AgentAssist User Guide** is here to help you unlock the full potential of **AgentAssist** and take your credit card sales to the next level. This guide offers clear, step-by-step instructions and practical examples to help you get the most out of the application. + +In this guide, you'll learn how to: +* **Set Up AgentAssist** +* **Navigate and Use Key Features** like contact management, filtering, and more. +* **Optimize Your Workflow** with shortcuts, data export/import, and automatic saving. -## Table of Contents - -1. [Quick Start](#quick-start) -2. [Using AgentAssist](#using-agentassist) -3. [Features Overview](#features-overview) - - [Save Current Data](#save-current-data) - - [Add New Customer](#add-new-customer) - - [Remove Old Customer](#remove-old-customer) - - [Edit Existing Customer](#edit-existing-customer) - - [Find a Customer by Details](#find-a-customer-by-details) - - [Help](#help) - - [Exit](#exit) -4. [FAQ](#faq) -5. [Known Issues](#known-issues) -6. [Command Summary](#command-summary) +Let’s begin and get you up to speed with AgentAssist! -------------------------------------------------------------------------------------------------------------------- -## Quick start {#quick-start} +# Table of Contents -### 1. Install Java +1. [Introduction](#1-introduction) +2. [Important Prerequisites](#2-important-prerequisites) +3. [Getting Started](#3-getting-started) +
+ Subsections -Make sure you have **Java 17 or above** installed on your computer. You may skip this step if you already have it installed. + 3.1 [Installation](#31-installation) + 3.2 [Graphical User Interface (GUI) Layout](#32-graphical-user-interface-gui-layout) -Installing Java: +
-* Download Java [here](https://www.oracle.com/java/technologies/javase/jdk17-archive-downloads.html). Follow the instructions on that page to install Java. +4. [Understanding Commands in AgentAssist](#4-understanding-commands-in-agentassist) +
+ Subsections -### 2. Download the AgentAssist application + 4.1 [Command Structure Overview](#41-command-structure-overview) + 4.2 [Commands](#42-commands) + 4.3 [Flags](#43-flags) + 4.4 [Arguments](#44-arguments) + 4.5 [Using Commands](#45-using-commands) -Go to this [link](https://github.com/AY2425S1-CS2103T-T14-4/tp/releases) and download the latest version of the `.jar` file. +
-### 3. Choose a Folder +5. [Commands](#5-commands) +
+ Subsections -Find or create a folder on your computer where you want to store the AgentAssist information.
-Move the `.jar` file you just downloaded into this folder. + 5.1 [How to Read Commands](#51-how-to-read-commands) + 5.2 [Data Modification Commands](#52-data-modification-commands) + 5.3 [Data Filtering Commands](#53-data-filtering-commands) + 5.4 [General Commands](#54-general-commands) + 5.5 [Saving Data](#55-saving-data) + 5.6 [Modifying the Data File](#56-modifying-the-data-file) -### 4. Open a Command Terminal +
+ +6. [FAQ](#6-faq) +7. [Known Issues](#7-known-issues) +8. [Command Summary](#8-command-summary) -Now, open a command terminal: -* On Windows, press the **Windows Key** and type **`Command Prompt`** in the search bar. Click on the **Command Prompt** application to open it. -* On macOS, press **Cmd + Space**, type **`Terminal`** into the search bar, and press **Enter**. -* On Linux, open your Terminal application. +# 1. Introduction +## 1.1 What is AgentAssist? -### 5. Run the applicaton +AgentAssist is the **definitive desktop tool for credit card sales agents**. Merging the swift efficiency of a Command Line Interface (CLI) with the intuitive accessibility of a Graphical User Interface (GUI), this application lets you manage contact databases, track sales progress, and execute transactions with unprecedented speed. -Navigate your terminal to the folder where you saved the AgentAssist application: +**Overview of Key Features:** +* **Contact Management**: + * Manage your client details easily. Add, edit, and delete contacts to keep all your client information in one accessible place. +* **Keyboard-centric Navigation**: + * Navigate through the application entirely via keyboard shortcuts, improving workflow efficiency. +* **Multi-Level Filtering**: + * Filter your data by multiple criteria to find exactly who you’re looking for. +* **Auto-Save**: + * Automatically saves your work as you go, ensuring data is updated without manual intervention. +* **Effortless Data Import & Export**: + * Import or export client and sales data in compatible formats for backups or use in other systems. -* Type **`cd `** (with a space after it), then drag and drop the folder where you placed the `.jar` file into the terminal window. The command should look similar to this: `cd '/Users/name/AgentAssistFolder'`. Press Enter.
+Maximize your productivity, minimize your response time, and amplify your sales performance. With AgentAssist, you're not just keeping up with the competitive world of credit card sales — _you're setting the pace_. -Type the following command: **`java -jar agentassist.jar`** and press **Enter**. +-------------------------------------------------------------------------------------------------------------------- -* A window similar to the below image should appear in a few seconds. You will see a graphical user interface with sample contact information already added.
- ![Ui](images/Ui.png) +# 2. Important Prerequisites ---- +Before you start using AgentAssist, there are a few prerequisites to ensure you get the most out of the application: -## Using AgentAssist {#using-agentassist} +### Familiarity with Keyboard Navigation +AgentAssist is designed to enhance speed and efficiency, with a strong focus on **keyboard-based navigation**. While the application includes a Graphical User Interface (GUI), its full potential is unlocked when you use **keyboard commands**. Therefore, it is important to: -AgentAssist's command box is your portal to managing contacts efficiently. Here’s how you can use it to streamline your tasks: +- Familiarize yourself with basic `Command Line Interface (CLI)` commands if you haven't already. This will make it easier to use AgentAssist’s command system effectively. +- Know common keyboard shortcuts (e.g., `Enter`, `Arrow keys`, etc.). -* To get started, simply type a command into the command box and hit **Enter**. +### Basic Understanding of Data Fields +AgentAssist allows you to manage client data like names, phone numbers, emails, and job information. A basic understanding of these data fields will make it easier to add, edit, and filter client information. -Some initial commands to try: +🎉 **By meeting these prerequisites, you'll be ready to make the most of AgentAssist’s fast, keyboard-driven interface and powerful data management features.** 🎉 +# 3. Getting Started +Welcome to AgentAssist. Here’s how to get up and running quickly and easily. -- **Viewing Contacts** - * `list`: This command displays all contacts currently in your database, making it easy to browse through entries. +## 3.1 Installation +### Step 1: Install Java -- **Adding a New Contact** - * `add n/Jane Doe p/87654321 e/jane@example.com a/123 Jane Road j/doctor i/120000`: Adds Jane Doe to your database with detailed contact info, occupation, and income. +Ensure you have **Java 17** installed on your computer. AgentAssist is optimized for **Java 17**, and using other versions may affect performance or functionality. If you already have Java 17 installed, you can skip this step. -- **Editing a Contact** - * `edit 1 p/12345678`: Updates the phone number of the first contact in your list to `12345678`. - * `edit 4 rn/Updated remarks here`: Replaces the remarks of the fourth contact with "Updated remarks here". +To install Java 17: +* Visit the Java download page from [Oracle](https://www.oracle.com/java/technologies/downloads/#java17?er=221886). +* Download the appropriate installer for your operating system (Windows, macOS, or Linux). +* Follow the installation instructions on the website to complete the setup. +* Once installed, verify the installation by [opening your terminal (or command prompt)](#how-to-open-terminal) and typing: + ``` + java -version + ``` +* If you see Java 17 in the output, you’re good to go! -- **Removing a Contact** - * `delete 3`: Removes the third contact from your list. Ensure you have the correct index to avoid deleting the wrong entry. +### Step 2: Download the AgentAssist application -- **Searching for a Contact** - * `filter n/Jane`: Finds all contacts named Jane in your database. It’s a powerful tool for quickly locating entries. +Download the latest version of the `.jar` file from the AgentAssist [repository](https://github.com/AY2425S1-CS2103T-T14-4/tp/releases). -- **Getting Help** - * `help`: Opens a help dialog that provides a summary of all available commands and their usage. +[comment]: # (TODO: Add image of GitHub Releases page with annotations to show user the file to install.) +### Step 3: Choose a Folder -**Pro Tip:** Experiment with combining commands like `filter` followed by `edit` or `delete` to manage your contacts more effectively. For example, use `filter j/doctor` to display all doctors, then `edit 2 a/321 New Address` to update the address for the second listed doctor. +Find or create a folder on your computer where you want to store the AgentAssist application and its data. +Move the .jar file you downloaded into this folder. -The GUI will dynamically update to show the results of your commands, making it easy to see the impact of your actions in real time. +### Step 4: Run the Application -Refer to the [Features](#features-overview) section for more detailed instructions on each command. +1. **Open a command terminal** + - On Windows, press `Windows Key + R`, type `cmd`, and press `Enter`. + - On macOS, press `Command + Space`, type `Terminal`, and press `Enter`. + - On Linux, open your **Terminal** application from the system menu. ---- +2. **Navigate your terminal to the folder where you saved the AgentAssist application:** + - To do this, use the `cd` command followed by the path to your folder.\ + > ℹ️ **Tip:** Follow the guide below to navigate to your folder in the terminal: + > + >
Click here to learn how to navigate to your folder in terminal + > + > - **Windows**: Use the command `cd `. + For example, if **AgentAssist** is stored in the `Downloads` folder: + > ```bash + > cd C:\Users\\Downloads + > ``` + > + > - **macOS/Linux**: Use the command `cd `. + For example, if **AgentAssist** is stored in the `Downloads` folder: + > ```bash + > cd /Users//Downloads + > ``` + > + >
+ +3. Run the application: + - Type the following command: **`java -jar agentassist.jar`** and press **Enter**. + - A window similar to the below image should appear in a few seconds. You will see a graphical user interface with sample contact information already added.

+ ![Ui](images/Ui.png) -## Features Overview {#features-overview} +4. 🎉 **Congratulations! AgentAssist is now up and running!** 🎉 + You're all set to start using AgentAssist to manage your contacts, track your sales, and boost your productivity! -### Feature 1: Ability to Save Current Data {#save-current-data} +## 3.2 Graphical User Interface (GUI) Layout -**Purpose:** -This feature ensures that any details you add to the app are saved automatically. When you close and reopen the app, all your data will still be there. -**How it Works:** -- **Command Format and Example:** Not applicable, as this process is automatic. +To learn more about how to use commands in AgentAssist, proceed to the next section. -#### Parameters -- **Flags and Parameters:** There are no parameters needed for this feature. +-------------------------------------------------------------------------------------------------------------------- -#### What to Expect -- **If Successful:** You can access all the data you've entered previously. -- **If There is an Error:** There's a chance that the data might not be saved due to an error, and you could lose information. +# 4. Understanding Commands in AgentAssist {#using-agentassist} ---- +The true power of **AgentAssist** lies in efficiently using commands. Before diving into specific commands, let’s break down the basic structure of a command. + +## 4.1 Command Structure Overview +Each command in AgentAssist consists of three key components: the **command**, **flag(s)**, and **argument(s)**. + +Let's take a look at the structure in more detail: -### Feature 2: Add New Customer {#add-new-customer} +| **Components** | **Description** | **Example** | +|:----------------|:----------------------------------------------------------------------------------------------------------------------------------------------------------|:-------------------------------| +| **Command** | The action you want AgentAssist to perform. | `add` | +| **Flag(s)** | Modifiers that specify what kind of data is being handled.

Flag(s) are typically 1-2 letters followed by a backslash. | `n/`, `p/`, `r/`, `rn/` | +| **Argument(s)** | The values or inputs the command uses, such as client data or specific details.

This guide may represent it as a placeholder using ``. | `John Doe`, `john@example.com` | -**Purpose:** -This feature allows you to enter and save detailed records for new customers. Each customer's record includes their name, contact number, email, occupation, and income. You can also enter the optional fields for credit card tier and remark here. Otherwise, new users are assigned a default value of "N.A". +Here's an example that uses multiple flags and arguments: +``` +add n/ John Doe e/ john@example.com +``` +* **Command:** `add` instructs AgentAssist to add a new entry. +* **Flags:** `n/` and `e/` specify the information type (name and email). +* **Arguments:** `John Doe` and `john@example.com` are the actual values being input for the respective flags. -**How to Use It:** - - **Command Format:** - ``` - add n/ p/ e/ a/
j/ i/ [t/ ] [rn/ ] - ``` - - **Examples:** - - Add new customer, basic convention: - ``` - add n/ TAN LESHEW p/ 99007766 e/ mrtan@ntu.sg a/ com3 j/ doctor i/ 99999 - ``` - - Add new customer with tier and remark: - ``` - add n/ TAN LESHEW p/ 99007766 e/ mrtan@ntu.sg a/ com3 j/ doctor i/ 99999 t/ gold rn/ got anger issue - ``` - -#### Parameters {#add-command-parameters} +## 4.2 Commands +A command is the action that AgentAssist will perform, such as adding, deleting, or editing a contact. -| Parameter | Expected Format | Explanation | -|-----------|--------------------------------------------------|-------------------------------------------------------------------------------------------------------------------| -| NAME | Alphanumeric, case insensitive | Accepts all names without case sensitivity. Names will be displayed in block letters for clarity and consistency. | -| PHONE | 8-digit number, starts with 8 or 9 | Ensures the contact number is valid in Singapore. | -| EMAIL | Must include "@" and domain, case insensitive | Verifies that the email address is in a standard format. | -| ADDRESS | Any text, case insensitive | Accepts all addresses without case sensitivity. Addresses can have numbers and symbol alike /. | -| JOB | Any text, case insensitive | Accepts all job titles without case sensitivity. | -| INCOME | Non-negative integers | Only positive numbers or zero are valid for income fields. | -| TIER | [optional] String (gold, silver, bronze, reject) | Defines the specific credit card tier to be assigned or updated. | -| REMARK | [optional] Any string | Notes are case-insensitive and can include any textual information. | +Here is a reference table that briefly summarizes available commands: -#### What to Expect -- **If Successful:** - - Message: "New person added: ``; Phone: ``; Email: ``; Address: `
`; Job: ``; Income: ``; Tier: ``; Remark: ``". It's noted that if "Tier" and "Remark" are not added, they will be defined as "N/A." -- **If There is an Error:** - - Message: "Please verify that your input is in the correct format. Include the following details: n/ `` p/ `` e/ `` a/ `
` j/ `` i/ `` [t/ ``] [rn/ ``]." +| **Command** | **Description** | +|-------------|--------------------------------------------------------| +| `add` | Adds a new client to the system. | +| `edit` | Modifies details of an existing client. | +| `delete` | Removes a client from the system. | +| `list` | Displays all clients currently stored in the system. | +| `filter` | Filters clients based on specified criteria | +| `clear` | Deletes all clients from the system. | +| `help` | Displays a list of available commands and their usage. | +| `exit` | Exits the AgentAssist application. | -**Handling Duplicates:** -If a customer with the same name, email, job, and income is already saved, you'll get a message: "This customer is already saved as a contact." +Refer to the [Commands Section](#commands-section) for more comprehensive details of each command. ---- +## 4.3 Flags + +AgentAssist uses flags as a shorthand for different options in commands. Flags help you specify what kind of information you are providing, allowing you to write shorter and more efficient commands, improving your workflow. + +Here’s a reference table of available flags and the type of data they correspond to: + +| **Flag** | **Type of Data** | +|----------|------------------| +| `i/` | `index` | +| `n/` | `name` | +| `p/` | `phone` | +| `e/` | `email` | +| `a/` | `address` | +| `j/` | `job` | +| `i/` | `income` | +| `t/` | `tier` | +| `r/` | `remark` | +| `ra/` | `remark append` | +| `rn/` | `remark new` | + +> 💡 **Pro Tip:** +> +> Flags are typically derived from the first letter of their corresponding data type (e.g., `n/` for `name`), making them easy to remember! + +## 4.4 Arguments + +Arguments are the values that follow each flag in a command. **Arguments cannot be empty**, and each must meet specific parsing and format requirements to ensure proper execution of the command. + +Refer to the table below for more details. + +| **Flag** | **Expected Argument** | **Description** | **Requirements** | **Case Sensitivity** | +|----------|-----------------------|------------------------------------------------|-------------------------------------------------------------------------------------------------|-----------------------| +| `n/` | `` | The client's full name | Any combination of letters, numbers, and spaces (no symbols). | ❌ | +| `p/` | `` | The client's phone number | Valid Singapore phone number:
• 8-digit number
• Starts with 8 or 9 | ❌ | +| `e/` | `` | The client's email address | Valid email format (`username@domain.com`) | ❌ | +| `a/` | `
` | The client's physical address | Any combination of letters, numbers, spaces, and symbols. | ❌ | +| `j/` | `` | The client's job title or profession | Any combination of letters, numbers, spaces, and symbols. | ❌ | +| `i/` | `` | The client's annual income | Positive number or zero
• Cannot include commas and decimal points
• Must be numeric | ❌ | +| `t/` | `` | The client's assigned tier level | Must be one of the predefined tiers:
• Gold, Silver, Bronze, Reject | ✔️ | +| `r/` | `` | General remarks about the client | Any combination of letters, numbers, spaces, and symbols. | ❌ | +| `ra/` | `` | Append information to the existing remark | Any combination of letters, numbers, spaces, and symbols. | ❌ | +| `rn/` | `` | Replaces the existing remark with a new remark | Any combination of letters, numbers, spaces, and symbols. | ❌ | -### Feature 3: Remove Old Customer {#remove-old-customer} +> 💡 **Pro Tip:** +> +> Ensure every flag is followed by a valid argument! +> +> Providing a flag without an accompanying argument will result in an error and prevent the command from executing properly. -**Purpose:** -This feature allows you to remove records of customers who are no longer using your credit card services. +## 4.5 Using Commands +To get started, simply type a command into the command box and hit **Enter**. + +Some initial commands to try: +**Viewing All Clients** +* `list`: This command displays all clients currently in your database, making it easy to browse through entries. + +**Adding a New Client** +* `add n/Jane Doe p/87654321 e/jane@example.com a/123 Jane Road j/doctor i/120000`: Adds Jane Doe to your database with detailed contact information, job title, and income. + +**Editing a Client's Information** +* `edit 1 p/12345678`: Updates the phone number of the first client in your list to `12345678`. +* `edit 4 rn/Updated remarks here`: Replaces the remarks of the fourth client with "Updated remarks here". + +**Removing a Client** +* `delete 3`: Removes the third client from your list. Ensure you have the correct index to avoid deleting the wrong client. + +**Searching for a Client** +* `filter n/Jane`: Finds all clients named Jane in your database. It’s a powerful tool for quickly locating clients or filtering for a specific type of client. + +**Getting Help** +* `help`: Opens a help dialog that provides a summary of all available commands and their usage. + +The GUI will dynamically update to show the results of your commands, making it easy to see the impact of your actions in real time. + +Refer to the [Commands Section](#commands-section) for more comprehensive details of each command. + +> 💡 **Pro Tip:** +> Combine commands like `filter` followed by `edit` or `delete` to manage your contacts more effectively. +> For example, use `filter j/doctor` to display all doctors, then `edit 2 a/321 New Address` to update the address for the second listed doctor. + +-------------------------------------------------------------------------------------------------------------------- -**How to Use It:** -- **Command Format:** +# 5. Commands + +## 5.1 How to Read Commands + +When working with commands in **AgentAssist**, it's important to understand **how the command format is structured**. Commands consist of specific components like **flags** and **arguments**, and some parts of the command can be **optional**. + +If you're unfamiliar with how commands are structured, refer back to the [Command Structure Overview in Section 4.1](#command-structure-overview) for more details on how flags, arguments, and placeholders work together. + +### Command Syntax + +When reading commands, there are certain syntax conventions that help indicate how to use them: + +- **`< >` (Angle Brackets):** + Text enclosed in angle brackets represents a **placeholder** for the actual value you need to provide. For example, `` should be replaced by the client's actual name, such as "John Doe." + +- **`[ ]` (Square Brackets):** + Components enclosed in square brackets are **optional**. You can choose to include them if necessary, but they are not required for the command to execute. For instance, `[t/ ]` means that the credit card tier is optional, and if omitted, a default value will be used. + +### Example Command: +``` +add n/ p/ e/ a/
j/ i/ [t/ ] [rn/ ] +``` +- **Mandatory Components**: Flags such as `n/`, `p/`, `e/`, `a/`, `j/`, and `i/` must be followed by valid arguments like the name, phone number, and job title. +- **Optional Components**: Flags like `t/` and `rn/` are enclosed in square brackets, indicating they are optional. + +## 5.2 Data Modification Commands + +### 5.2.1 Adding a new client {#add-command} + +**Purpose:** Save detailed records of a new client. + +Each client's record includes their name, contact number, email, occupation, and income. You can also enter the optional fields for credit card tier and remark here. Otherwise, new users are assigned a default value of "N.A". + +**Command Format:** ``` - delete + add n/ p/ e/ a/
j/ i/ [t/ ] [rn/ ] ``` -- **Example:** +* Mandatory Fields: `n/`, `p/`, `e/`, `a/`, `j/`, `i/` +* Optional Fields: `t/`, `rn/` + +For detailed explanations of each flag and acceptable arguments, refer to Sections [4.3 Flags](#43-flags) and [4.4 Arguments](#44-arguments) + +**Examples:** +- Add new customer (without optional fields): + ``` + add n/ JOHN DOE p/ 99007766 e/ mrdoe@ntu.sg a/ com3 j/ doctor i/ 99999 ``` - delete 69 +- Add new customer with tier and remark: ``` + add n/ JOHN DOE p/ 99007766 e/ mrdoe@ntu.sg a/ com3 j/ doctor i/ 99999 t/ gold rn/ got anger issue + ``` + +#### What to Expect +- **On Success:** + - Message: + ``` + New client added: Name: , Phone: , Email: , Address:
, Job: , Income: , Tier: , Remark: . + ``` + - If "Tier" and "Remark" are not provided, they will be set to "N.A." and displayed as such in the success message. -#### Parameters +- **On Error** + - Message: + ``` + Please verify that your input is in the correct format. Include the following details: n/ p/ e/ a/
j/ i/ [t/ ] [rn/ ]. + ``` -| Parameter | Expected Format | Explanation | -|-----------|-------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------| -| INDEX | Integer (1 to the last INDEX) | The INDEX must be a valid integer within the registered range (either the original list or any filtered list after using `filter` command). | +> **Note on Duplicates:** +> +> AgentAssist will prevent duplicate entries if a client with the **same name, email and phone number** is already saved. +> When this happens, you will see the following message: +> +> ``` +> This customer is already saved as a contact. +> ``` +> +> **The duplicate contact will not be saved** to prevent redundancy. +> +> If you need to update details for an existing contact, use the `edit` command instead. +> For more information, see Section [5.2.2 Editing a client](#edit-command). -#### What to Expect -- **If Successful:** - - Message: "Customer `` has been deleted." -- **If There is an Error:** - - Invalid index error message: "No customer with `` exists. Please recheck the index." -**Handling Duplicates:** -Since customer INDEX are unique identifiers: -- No two customers can have the same index due to the uniqueness constraint on customer index. -- Even if a customer record appears duplicated due to data file modifications, the system assigns a unique index upon loading the data, preventing actual duplicates in the database. ---- -### Feature 4: Edit the existing customer {#edit-existing-customer} +### 5.2.2 Edit an Existing Client's Information {#edit-command} -**Purpose:** -This feature allows users to update the details of an existing customer in the database. All customer information can be modified, including contact details, address, job information, and other relevant data. Additionally, users can either append to or replace existing remarks and adjust the customer's tier status. +**Purpose:** Update the details of an existing client in the database. -**How to Use It:** -- **Command Format:** - ``` - edit n/ p/ e/ a/
j/ i/ [t/ ] [rn/ ] [ra/ - ] - ``` -**Note that `rn/` and `ra/` cannot be used at the same time.** - - **Examples:** - - Edit only 1 specific field: +All client information, including contact details, address, job information, and other relevant data, can be modified. You can also append to or replace existing remarks and adjust the client's tier status. + +**Command Format:** +``` +edit n/ p/ e/ a/
j/ i/ [t/ ] [rn/ ] [ra/ ] +``` +- Mandatory Field: `` +- Optional Fields: `n/`, `p/`, `e/`, `a/`, `j/`, `i/`, `t/`, `rn/`, `ra/` +- **Note:** `rn/` (new remark) and `ra/` (append remark) cannot be used simultaneously in a single command. + +For detailed explanations of each flag and acceptable arguments, refer to Sections [4.3 Flags](#43-flags) and [4.4 Arguments](#44-arguments) + +**Examples:** +- Edit only 1 specific field: ``` - edit 69 a/ Ridge View Residential College + edit 12 a/ Ridge View Residential College ``` ``` - edit 69 t/ gold + edit 12 t/ gold ``` - - Edit multiple fields at the same time: + +- Edit multiple fields at the same time: ``` - edit 69 p/ 99887766 e/ mrtan_newemail@ntu.sg j/ unemployed i/ 0 t/ reject + edit 12 p/ 99887766 e/ mrtan_newemail@ntu.sg j/ unemployed i/ 0 t/ reject ``` - - Append new remark onto existing one: +- Append new remark onto existing one: ``` - edit 69 ra/ just know his dad is mr moore + edit 12 ra/ Recently received Gordon E. Moore Award ``` - - Replace all remark(s) with a new remark: +- Replace all remark(s) with a new remark: ``` - edit 69 rn/ ran out of money + edit 69 rn/ Do not call, angry about calls ``` -#### Parameters - -| Parameter | Expected Format | Explanation | -|-----------|--------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------| -| INDEX | Integer (1 to the last INDEX) | The index must be a valid integer within the registered range (either the original list or any filtered list after using `filter` command). | -| NAME | Alphanumeric, capitalized | Names are in block letters for clarity and consistency. | -| PHONE | 8-digit number, starts with 8 or 9 | Ensures the contact number is valid in Singapore. | -| EMAIL | Must include "@" and domain, case insensitive | Verifies that the email address is in a standard format. | -| ADDRESS | Any text, case insensitive | Accepts all addresses without case sensitivity. Addresses can have numbers and symbol alike /. | -| JOB | Any text, case insensitive | Accepts all job titles without case sensitivity. | -| INCOME | Non-negative integers | Only positive numbers or zero are valid for income fields. | -| TIER | [optional] String (gold, silver, bronze, reject) | Defines the specific credit card tier to be assigned or updated. | -| REMARK | [optional] Any string | For both `rn/` and `ra/`, remarks are case-insensitive and can include any textual information. | +**What to Expect:** +- **On Success:** + - Message: + ``` + Customer has been updated successfully. + ``` +- **On Error:** + - Message: + ``` + Failed to update customer . + ``` + +> 💡 **Pro Tip:** +> No need to worry about duplicate indexes—AgentAssist guarantees that every customer has a unique index automatically. -#### What to Expect -- **If Successful:** - - Message: "Customer `` has been updated successfully." -- **If There is an Error:** - - Message: "Failed to update customer ``." -**Handling Duplicates:** -- No two customers can have the same index due to the uniqueness constraint on customer index. ---- -### Feature 5: Find a Customer by Details {#find-a-customer-by-details} +### 5.2.3 Delete an Existing Client {#delete-command} -**Purpose:** -This feature allows users to search for customers by specific details such as name, address, email, phone number, job title, or remarks. +**Purpose:** Remove records of customers who are no longer using your credit card services. -**How to Use It:** -To perform a search, use the `filter` command followed by one or more flags (indicating the fields to search) and the corresponding search terms. +**Command Format:** +``` +delete +``` +* Mandatory Field: `` +* Note: The provided `` must be **greater than 0 and less than the total number of customers in the list**. -Searches are **case-insensitive** and use [**substring-matching**](#substring-matching), **except for [Tier](#filtering-by-tier) and [Income](#filtering-by-income)**, which have their own specific matching criteria detailed below. +For detailed explanations of each flag and acceptable arguments, refer to Sections [4.3 Flags](#43-flags) and [4.4 Arguments](#44-arguments) -- **Command Format:** - ``` - filter / [/ ] - ``` -- **Examples:** - - Filter customers by name: +**Examples:** +- Remove a customer with a specific index (e.g. at index 12): ``` - filter n/ TAN LESHEW + delete 12 ``` - - Filter customers by job: - ``` - filter j/ doctor - ``` - - Filter customers by name, job and remark: + +**What to Expect:** +- **On Success:** + - Message: + ``` + Customer has been deleted. + ``` +- **On Error:** + - Invalid index error message: + ``` + No customer with exists. Please recheck the index. + ``` + +> 💡 **Pro Tip:** +> No need to worry about duplicate indexes—AgentAssist guarantees that every customer has a unique index automatically. + + + + +### 5.2.4 Delete All Existing Clients {#clear-command} + +**Purpose:** Delete all clients from the database, effectively resetting the application’s contact list + +**Command Format:** +``` +clear +``` + +**What to Expect:** +- **On Success:** + - Message: ``` - filter n/ Gordon Moore j/ doctor r/ award winner + Address book has been cleared! ``` + The application will remove all client data from the list, effectively resetting the client database. +- **On Error:** + - This command does not typically produce errors but will have no effect if there are no clients in the database to clear. -#### Parameters - -| Parameter | Expected Format | Explanation | -|-------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------| -| FLAG | Refer to the list of supported flags detailed below. | Identifies the field to search.

e.g., `n/` for name, `j/` for job. | | -| SEARCH TERM | Follows the syntax for [each field's expected input](#add-command-parameters).

**Income** requires a numeric value with a comparison operator (`=`, `>`, `<`), while **Tier** allows for partial (prefix) matching. Other fields follow substring matching. | The value to search for in the specified field.

e.g., `doctor` for job, `>5000` for income). | - -#### Supported flags: -- `n/` for Name -- `p/` for Phone Number -- `e/` for Email -- `a/` for Address -- `j/` for Job -- `r/` for Remarks -- `t/` for Tier - -#### Substring Matching: -- Substring matching is used for searches, meaning that the search term must match a part of the field in the same order as it appears in the customer record. -- For instance, if a customer’s name is `Gordon Moore`, the search term `Gordon`, `Moore`, or `Gordon Moore` will match, but `Moore Gordon` will not. - -#### Filtering By Tier -- **Prefix Matching:** Tier searches use **prefix matching**, meaning the search term must match the beginning of the tier exactly. - - If a customer has a tier labeled `Gold`, a search for `t/ G` or `t/ Gold` will match, but `t/ ld` or `t/ Gold Premium` will not. - -#### Filtering By Income -- **Comparison Operators:** Filtering by income allows numeric comparisons using operators `=`, `>`, or `<` to find customers whose income meets certain criteria. -- **Equal to (`=`):** Use `=` to find customers with a specific income. - - `i/ =5000` will match customers with an income of exactly 5000. -- **Greater than (`>`):** Use `>` to find customers with an income higher than the specified threshold. - - `i/ >5000` will match customers with incomes greater than 5000. -- **Less than (`<`):** Use `<` to find customers with an income lower than the specified threshold. - - `i/ <5000` will match customers with incomes below 5000. +> ⛔ **Danger:** +> The `clear` command is **irreversible** and does not provide a confirmation message before clearing all records. Once executed, all client data is **permanently deleted**. +> +> It is highly recommended to **avoid using this command** unless absolutely necessary. -#### What to Expect -- **If Successful:** - - Message: "`x` person listed!", where `x` is the number of matching results. -- **If Unsuccessful (No Matches Found):** - - Message: "0 persons listed!" -- **If There is an Error:** - - No Valid Flags Used: - - Message: - - "filter: Searches for all customers whose specified field contains the given substring (case-insensitive) and displays the results in a numbered list. - - Parameters: `/ ` - - Flags: `n/` (name), `p/` (phone), `e/` (email), `a/` (address), `j/` (job), `r/` (remarks) - - Example: `filter n/ Alice p/ 91112222` + + + +## 5.3 Data Filtering Commands + +### 5.3.1 List All Clients {#list-command} + +**Purpose:** View a list of all clients saved in AgentAssist. + +**Command Format:** +``` +list +``` +* No parameters are required for this command. Any parameter added will be ignored. - This will find all customers whose names contain 'Alice' and has phone number '91112222'." - - Search Term Fails to Meet Requirement (i.e. Phone Number longer than 8 digits): - - The system will display usage hints specific to the first invalid search term. ---- -### Feature 6: Help {#help} +### 5.2.3 Filter Clients by Details / Find a Client {#filter-command} -**Purpose:** -This feature provides users with quick access to the command summary and the user guide for the application, helping them understand how to use various features effectively. +**Purpose:** Search for clients by specific details such as name, address, email, phone number, job title, income, or remarks. -**How to Use It:** -- **Command Format:** +**Command Format:** +``` +filter n/ p/ e/ a/
j/ r/ t/ i/ +``` +- **Mandatory Field**: One or more flags with corresponding search terms. +- **Special Syntax for Income (i/)**: + - When filtering by income, use comparison operators `=`, `>`, or `<` to specify criteria. + - Example: `i/ >5000` will filter customers with an income greater than 5000. + - See [Filtering By Income](#filtering-by-income) for more information. + +For detailed explanations of each flag and acceptable arguments, refer to Sections [4.3 Flags](#43-flags) and [4.4 Arguments](#44-arguments) + +**Examples:** +- Filter customers by name: + ``` + filter n/ John Doe + ``` +- Filter customers by job: ``` - help + filter j/ doctor ``` -- **Example:** +- Filter customers by name, job and remark: ``` - help + filter n/ Gordon Moore j/ doctor r/ award winner ``` +**Matching Criteria & Filter Behavior:** + +- **Substring Matching: (For most fields)** + Searches for most fields use **substring matching**, meaning the search term must match part of the field in the same order as it appears in the customer record. + - **Example:** + If a customer’s name is `Gordon Moore`, the search term `Gordon`, `Moore`, or `Gordon Moore` will match, but `Moore Gordon` will not. + +- **Filtering by Tier (Prefix Matching):** + Tier searches use **prefix matching**, meaning the search term must match the beginning of the tier exactly. + - **Example:** + If a customer has a tier labeled `Gold`, a search for `t/ G` or `t/ Gold` will match, but `t/ ld` or `t/ Gold Premium` will not. + +- **Filtering by Income (Using Comparison Operators):** +Filtering by income allows numeric comparisons using operators `=`, `>`, or `<` to find customers whose income meets certain criteria. + + - **Equal to (`=`):** + Use `=` to find customers with a specific income. + Example: `i/ =5000` will match customers with an income of exactly 5000. + + - **Greater than (`>`):** + Use `>` to find customers with an income higher than the specified threshold. + Example: `i/ >5000` will match customers with incomes greater than 5000. + + - **Less than (`<`):** + Use `<` to find customers with an income lower than the specified threshold. + Example: `i/ <5000` will match customers with incomes below 5000. + +**What to Expect:** +- **On Success:** + - Message: + ``` + x person(s) listed! + ``` + where `x` is the number of matching results. +- **On Error:** + - If no valid flags are used: + ``` + filter: Searches for all customers whose specified field contains the given substring (case-insensitive) and displays the results in a numbered list. -#### Parameters -- **Flag:** N/A - - There are no parameters required for this command. + Parameters: / + + Flags: n/ (name), p/ (phone), e/ (email), a/ (address), j/ (job), r/ (remarks) + + Example: filter n/ Alice p/ 91112222 + ``` + - If a search term fails to meet the requirements (e.g., invalid phone number length), the system will display usage hints specific to the first invalid search term. -#### What to Expect -- **If Successful:** - - No immediate message is displayed in the command interface. - - Opens up a dialog box that provides command summary table with command format and basic example, together with the hyperlink to the user guide markdown file, allowing users to easily access detailed instructions and information. -**Handling Duplicates:** -- N/A as this command does not involve processing or displaying data that could involve duplicates. ---- -### Feature 7: Exit {#exit} -**Purpose:** -Allows users to exit the application through a simple command, eliminating the need to use the window's close button or external controls. +## 5.4 General Commands -**How to Use It:** -- **Command Format:** - ``` - exit - ``` -- **Example:** - ``` - exit - ``` +### 5.4.1 Viewing a Client's Details {#view-command} -#### Parameters -- **Flag:** N/A - - No parameters are needed to execute this command. +**Purpose:** View the full details of a selected client by opening a detailed card with all available information about the client. -#### What to Expect -- **If Successful:** - - The message "Terminating program…" is displayed. - - The program will then exit after a short delay, effectively closing the application. +**Methods to View Client Details:** +- **Method 1 - Using GUI:** + Left-click on a specific client in the list to open the client's card and display their full details. +- **Method 2 - Using CLI:** + 1. Use the `↑` and `↓` arrow keys to navigate through the client list in the Command Line Interface (CLI). + 2. Once the desired client is selected **(highlighted in blue)**, type the following command to open their detailed card: + ``` + view + ``` + - This will display the full details of the currently selected client. -**Handling Duplicates:** -- N/A as this command is unique and does not process data that could involve duplicates. +> 💡 **Pro Tip:** +> Using the CLI with the `↑` and `↓` arrow keys to select a client is a fast, keyboard-friendly way to navigate through the list. Once selected, simply type `view` to instantly display the client's details without needing to use the mouse! --------------------------------------------------------------------------------------------------------------------- -## FAQ {#faq} -**Q**: How do I transfer my data to another Computer?
-**A**: Install the app in the other computer and overwrite the empty data file it creates with the file that contains the data of your previous AddressBook home folder.
-**Q**: How do I change the remarks or credit card tier of an existing customer?
-**A**: Use the [`edit`](#feature-4-edit-the-existing-customer) command, and specify the corresponding `t/` and or `rn/` or `ra/` flag to change these two fields.
+### 5.4.2 Closing a Client's Details {#close-command} + +**Purpose:** Close the detailed view of a client's card and return to the main client list. + +**Command Format:** +``` +close +``` +- If a client's detailed card is currently open, this command will return you to the main list view. +- **Note:** If no client cards are open, the `close` command is ignored. + + + + +### 5.4.3 Help Menu {#help-command} + +**Purpose:** Provides quick access to a command summary and the user guide for AgentAssist. + +**Command Format:** +``` +help +``` +- Opens up a dialog box that provides: + - **Command summary table** with command format and basic examples + - **Hyperlink to the User Guide** + + + + +### 5.4.4 Exiting AgentAssist {#exit} + +**Purpose:** Exit the application directly from the command line, providing a quick and easy way to close the program without using external controls. + +**Command Format:** +``` +exit +``` +- The message `Terminating program…` is displayed. +- After a brief delay, the program will close, effectively exiting the application. + + -**Q**: Why am I getting an error when trying to edit the remark of an existing customer?
-**A**: Besides making sure that the command syntax is correct, please note that the `rn/` and `ra/` flags cannot be used together, as `rn/` is used to provide a new remark that will override any existing remark. Whilst, `ra/` will append a given remark to any existing remark. -**Q**: What do the different tier colors represent in the UI? -**A**: Each credit card tier is visually distinguished in the UI: Gold is marked with a gold banner, Silver with a silver banner, Bronze with a bronze banner, and Reject with a red banner. This makes it easy to see at a glance the tier of each customer. +## 5.5 Saving Data +AgentAssist **automatically saves** all client data to your computer after each command. There's no need to manually save anything. + + + + + +## 5.6 Modifying the Data File +The data in AgentAssist is automatically saved as a [JSON](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Objects/JSON) file as `[JAR file location]/data/addressbook.json`. Advanced users are welcome to update data directly by editing that data file. + +> ⚠️ **Danger:** +> If the data file format becomes invalid, AgentAssist will **discard all data** and start with an empty file on the next run. It's strongly recommended to back up the file before any manual edits. +> +> Incorrect data modifications may also cause unexpected behavior. **Only modify the data file if you're confident in doing so correctly.** + + +-------------------------------------------------------------------------------------------------------------------- + +## 6. FAQ + +### How do I transfer my data to another Computer? +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. + +### How do I change the remarks or credit card tier of an existing customer? +Use the [`edit` command](#feature-4-edit-the-existing-customer), and specify the `t/` flag for the credit card tier, and `rn/` or `ra/` for remarks. + +### Why am I getting an error when trying to edit the remark of an existing customer? +Ensure that the command syntax is correct, and note that the `rn/` and `ra/` flags cannot be used together. The `rn/` flag replaces the existing remark, while `ra/` appends to the current remark. + +### What do the different tier colors represent in the UI? +Each credit card tier is visually distinguished in the UI: Gold is marked with a gold banner, Silver with a silver banner, Bronze with a bronze banner, and Reject with a red banner. This makes it easy to see at a glance the tier of each customer. -------------------------------------------------------------------------------------------------------------------- -## Known issues {#known-issues} +## 7. Known issues 1. **When using multiple screens**, if you move the application to a secondary screen, and later switch to using only the primary screen, the GUI will open off-screen. The remedy is to delete the `preferences.json` file created by the application before running the application again. 2. **If you minimize the Help Window** and then run the `help` command (or use the `Help` menu, or the keyboard shortcut `F1`) again, the original Help Window will remain minimized, and no new Help Window will appear. The remedy is to manually restore the minimized Help Window. -------------------------------------------------------------------------------------------------------------------- -## Command Summary {#command-summary} - -| Action | Command Format | Example | -|--------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------| -| **Save Data Automatically** | *Automatic* | *No command required* | -| **Add New Customer** | `add n/ p/ e/ a/
j/ i/ [t/ ] [rn/ ]` | `add n/ TAN LESHEW p/ 99007766 e/ mrtan@ntu.sg a/ com3 j/ doctor i/ 99999 t/ gold rn/ got anger issue` | -| **Remove Old Customer** | `delete ` | `delete 69` | -| **Edit Existing Customer** | `edit n/ p/ e/ a/
j/ i/ [t/ ] [rn/ ] [ra/ / ` | `filter n/ TAN LESHEW` | -| **Help** | `help` | `help` | -| **Exit** | `exit` | `exit` | +## 8. Command Summary + +| **Action** | **Command Format** | **Example** | +|----------------------------|------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------| +| **Add New Client** | `add n/ p/ e/ a/
j/ i/ [t/] [rn/]` | `add n/ GORDON MOORE p/ 99007766 e/ gmoore@ntu.sg a/ COM3 j/ engineer i/ 99999 t/ gold rn/ remark` | +| **Delete Existing Client** | `delete ` | `delete 69` | +| **Edit Existing Client** | `edit n/ p/ e/ a/
j/ i/ [t/] [rn/] [ra/]` | `edit 69 n/ GORDON MOORE p/ 77337733 e/ gmoore_new@ntu.sg a/ COM3 j/ doctor i/ 1000000000 ra/ added info` | +| **List All Clients** | `list` | `list` | +| **Filter Client List** | `filter [n/] [p/] [e/] [a/
] [j/] [r/] [t/] [i/]` | `filter n/ GORDON MOORE j/ doctor t/ gold` | +| **View Client Details** | `view` | `view` | +| **Close Client Details** | `close` | `close` | +| **View Help** | `help` | `help` | +| **Exit Application** | `exit` | `exit` | +| **Clear All Data** | `clear` | `clear` | + + + diff --git a/src/main/java/seedu/address/logic/LogicManager.java b/src/main/java/seedu/address/logic/LogicManager.java index 5aa3b91c7d0..9e3d984d520 100644 --- a/src/main/java/seedu/address/logic/LogicManager.java +++ b/src/main/java/seedu/address/logic/LogicManager.java @@ -1,5 +1,7 @@ package seedu.address.logic; +import static seedu.address.logic.Messages.MESSAGE_COMMAND_CANCELLED; + import java.io.IOException; import java.nio.file.AccessDeniedException; import java.nio.file.Path; @@ -33,6 +35,10 @@ public class LogicManager implements Logic { private final Storage storage; private final AddressBookParser addressBookParser; + // Boolean to determine if user has confirmed they want to delete command + private boolean awaitingConfirmation = false; + private Command delayedCommand = null; + /** * Constructs a {@code LogicManager} with the given {@code Model} and {@code Storage}. */ @@ -47,8 +53,17 @@ public CommandResult execute(String commandText) throws CommandException, ParseE logger.info("----------------[USER COMMAND][" + commandText + "]"); CommandResult commandResult; - Command command = addressBookParser.parseCommand(commandText); - commandResult = command.execute(model); + if (this.awaitingConfirmation) { + commandResult = handleConfirmation(commandText); + } else { + Command command = addressBookParser.parseCommand(commandText); + commandResult = command.execute(model, this.awaitingConfirmation); + + if (commandResult.isShowConfirmation() && !this.awaitingConfirmation) { + this.awaitingConfirmation = true; + this.delayedCommand = command; + } + } try { storage.saveAddressBook(model.getAddressBook()); @@ -61,6 +76,25 @@ public CommandResult execute(String commandText) throws CommandException, ParseE return commandResult; } + /** + * Processes user input for command confirmation. + * Executes the delayed command if confirmed, or cancels it and returns a cancellation message. + * + * @param commandText The user's confirmation input. + * @return The result of the command if confirmed, or a cancellation message. + * @throws CommandException If an error occurs during command execution. + */ + private CommandResult handleConfirmation(String commandText) throws CommandException { + CommandResult commandResult; + if (addressBookParser.parseConfirmation(commandText)) { + commandResult = this.delayedCommand.execute(model, this.awaitingConfirmation); + } else { + commandResult = new CommandResult(MESSAGE_COMMAND_CANCELLED); + } + this.awaitingConfirmation = false; + return commandResult; + } + @Override public ReadOnlyAddressBook getAddressBook() { return model.getAddressBook(); diff --git a/src/main/java/seedu/address/logic/Messages.java b/src/main/java/seedu/address/logic/Messages.java index 6174a241348..25ea2b5ad5b 100644 --- a/src/main/java/seedu/address/logic/Messages.java +++ b/src/main/java/seedu/address/logic/Messages.java @@ -21,6 +21,9 @@ public class Messages { public static final String MESSAGE_CONCURRENT_RN_RA_FIELDS = "Both remark new and remark append fields are " + "specified, please only use one."; + + public static final String MESSAGE_COMMAND_CANCELLED = "Command has been cancelled."; + /** * Returns an error message indicating the duplicate prefixes. */ diff --git a/src/main/java/seedu/address/logic/commands/AddCommand.java b/src/main/java/seedu/address/logic/commands/AddCommand.java index 668cf152221..d3d0ea6f208 100644 --- a/src/main/java/seedu/address/logic/commands/AddCommand.java +++ b/src/main/java/seedu/address/logic/commands/AddCommand.java @@ -57,7 +57,7 @@ public AddCommand(Person person) { } @Override - public CommandResult execute(Model model) throws CommandException { + protected CommandResult execute(Model model) throws CommandException { requireNonNull(model); if (model.hasPerson(toAdd)) { diff --git a/src/main/java/seedu/address/logic/commands/ClearCommand.java b/src/main/java/seedu/address/logic/commands/ClearCommand.java index 9c86b1fa6e4..637ee425c4a 100644 --- a/src/main/java/seedu/address/logic/commands/ClearCommand.java +++ b/src/main/java/seedu/address/logic/commands/ClearCommand.java @@ -12,12 +12,24 @@ public class ClearCommand extends Command { public static final String COMMAND_WORD = "clear"; public static final String MESSAGE_SUCCESS = "Address book has been cleared!"; + public static final String MESSAGE_CLEAR_CONFIRMATION = "This will permanently clear all contacts. " + + "Are you sure you want to execute this command? (y/n)"; + private static final boolean requiresConfirmation = true; @Override - public CommandResult execute(Model model) { + protected CommandResult execute(Model model) { requireNonNull(model); model.setAddressBook(new AddressBook()); return new CommandResult(MESSAGE_SUCCESS); } + + @Override + public CommandResult execute(Model model, Boolean confirmationReceived) { + if (confirmationReceived.equals(requiresConfirmation)) { + return this.execute(model); + } + return new CommandResult(MESSAGE_CLEAR_CONFIRMATION, false, false, true, null, false); + } + } diff --git a/src/main/java/seedu/address/logic/commands/CloseCommand.java b/src/main/java/seedu/address/logic/commands/CloseCommand.java new file mode 100644 index 00000000000..1871f5b3f0d --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/CloseCommand.java @@ -0,0 +1,36 @@ +package seedu.address.logic.commands; + +import static seedu.address.logic.commands.CommandCommons.EMPTY_PERSON; + +import seedu.address.model.Model; + +/** + * Closes the detail view in the address book. + */ +public class CloseCommand extends Command { + + public static final String COMMAND_WORD = "close"; + + public static final String MESSAGE_SUCCESS = "Detailed view closed."; + + @Override + public CommandResult execute(Model model) { + return new CommandResult( + MESSAGE_SUCCESS, + false, + false, + false, + EMPTY_PERSON, + false + ); + } + @Override + public boolean equals(Object obj) { + return obj == this || obj instanceof CloseCommand; + } + + @Override + public String toString() { + return COMMAND_WORD; + } +} diff --git a/src/main/java/seedu/address/logic/commands/Command.java b/src/main/java/seedu/address/logic/commands/Command.java index 64f18992160..278fca620a2 100644 --- a/src/main/java/seedu/address/logic/commands/Command.java +++ b/src/main/java/seedu/address/logic/commands/Command.java @@ -8,13 +8,30 @@ */ public abstract class Command { + private static final boolean requiresConfirmation = false; + /** * Executes the command and returns the result message. * - * @param model {@code Model} which the command should operate on. + * @param model {@code Model} which the command should operate on and m + * @param confirmationReceived A {@code Boolean} indicating whether user confirmation has been provided, + * if required for executing the command. * @return feedback message of the operation result for display * @throws CommandException If an error occurs during command execution. */ - public abstract CommandResult execute(Model model) throws CommandException; + public CommandResult execute(Model model, Boolean confirmationReceived) throws CommandException { + if (confirmationReceived.equals(requiresConfirmation)) { + return this.execute(model); + } + return null; + } + /** + * Executes the command and returns the result message. + * + * @param model {@code Model} which the command should operate on. + * @return feedback message of the operation result for display + * @throws CommandException If an error occurs during command execution. + */ + protected abstract CommandResult execute(Model model) throws CommandException; } diff --git a/src/main/java/seedu/address/logic/commands/CommandCommons.java b/src/main/java/seedu/address/logic/commands/CommandCommons.java index d0fd738fa37..db7dbc71735 100644 --- a/src/main/java/seedu/address/logic/commands/CommandCommons.java +++ b/src/main/java/seedu/address/logic/commands/CommandCommons.java @@ -1,10 +1,13 @@ package seedu.address.logic.commands; +import seedu.address.model.person.Person; + /** * Contains common values used in different command classes, including default command values. */ public final class CommandCommons { public static final String DEFAULT_TIER = ""; public static final String DEFAULT_REMARK = "NA"; + public static final Person EMPTY_PERSON = null; } diff --git a/src/main/java/seedu/address/logic/commands/CommandResult.java b/src/main/java/seedu/address/logic/commands/CommandResult.java index 249b6072d0d..6f0aaa88cff 100644 --- a/src/main/java/seedu/address/logic/commands/CommandResult.java +++ b/src/main/java/seedu/address/logic/commands/CommandResult.java @@ -5,6 +5,7 @@ import java.util.Objects; import seedu.address.commons.util.ToStringBuilder; +import seedu.address.model.person.Person; /** * Represents the result of a command execution. @@ -19,13 +20,23 @@ public class CommandResult { /** The application should exit. */ private final boolean exit; + /** The application should show a confirmation button */ + private final boolean showConfirmation; + + private final boolean showPerson; + private final Person viewedPerson; + /** * Constructs a {@code CommandResult} with the specified fields. */ - public CommandResult(String feedbackToUser, boolean showHelp, boolean exit) { + public CommandResult(String feedbackToUser, boolean showHelp, boolean exit, + boolean showPerson, Person viewedPerson, boolean showConfirmation) { this.feedbackToUser = requireNonNull(feedbackToUser); this.showHelp = showHelp; this.exit = exit; + this.showPerson = showPerson; + this.viewedPerson = viewedPerson; + this.showConfirmation = showConfirmation; } /** @@ -33,7 +44,7 @@ public CommandResult(String feedbackToUser, boolean showHelp, boolean exit) { * and other fields set to their default value. */ public CommandResult(String feedbackToUser) { - this(feedbackToUser, false, false); + this(feedbackToUser, false, false, false, null, false); } public String getFeedbackToUser() { @@ -48,13 +59,24 @@ public boolean isExit() { return exit; } + public boolean isShowPerson() { + return showPerson; + } + + public Person getViewedPerson() { + return viewedPerson; + } + + public boolean isShowConfirmation() { + return showConfirmation; + } + @Override public boolean equals(Object other) { if (other == this) { return true; } - // instanceof handles nulls if (!(other instanceof CommandResult)) { return false; } @@ -62,12 +84,15 @@ public boolean equals(Object other) { CommandResult otherCommandResult = (CommandResult) other; return feedbackToUser.equals(otherCommandResult.feedbackToUser) && showHelp == otherCommandResult.showHelp - && exit == otherCommandResult.exit; + && exit == otherCommandResult.exit + && showConfirmation == otherCommandResult.showConfirmation + && showPerson == otherCommandResult.showPerson + && Objects.equals(viewedPerson, otherCommandResult.viewedPerson); } @Override public int hashCode() { - return Objects.hash(feedbackToUser, showHelp, exit); + return Objects.hash(feedbackToUser, showHelp, exit, showConfirmation, showPerson, viewedPerson); } @Override diff --git a/src/main/java/seedu/address/logic/commands/DeleteCommand.java b/src/main/java/seedu/address/logic/commands/DeleteCommand.java index 1135ac19b74..649883e3229 100644 --- a/src/main/java/seedu/address/logic/commands/DeleteCommand.java +++ b/src/main/java/seedu/address/logic/commands/DeleteCommand.java @@ -24,6 +24,11 @@ public class DeleteCommand extends Command { + "Example: " + COMMAND_WORD + " 1"; public static final String MESSAGE_DELETE_PERSON_SUCCESS = "Deleted Person: %1$s"; + public static final String MESSAGE_DELETE_CONFIRMATION = "This will permanently delete this contact. " + + "Are you sure you want to execute this command? (y/n)"; + + private static final boolean requiresConfirmation = true; + private final Index targetIndex; @@ -45,6 +50,14 @@ public CommandResult execute(Model model) throws CommandException { return new CommandResult(String.format(MESSAGE_DELETE_PERSON_SUCCESS, Messages.format(personToDelete))); } + @Override + public CommandResult execute(Model model, Boolean confirmationReceived) throws CommandException { + if (confirmationReceived.equals(requiresConfirmation)) { + return this.execute(model); + } + return new CommandResult(MESSAGE_DELETE_CONFIRMATION, false, false, true, null, false); + } + @Override public boolean equals(Object other) { if (other == this) { diff --git a/src/main/java/seedu/address/logic/commands/ExitCommand.java b/src/main/java/seedu/address/logic/commands/ExitCommand.java index 3dd85a8ba90..57e9abdd90d 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, false, null, false); } } diff --git a/src/main/java/seedu/address/logic/commands/FilterCommand.java b/src/main/java/seedu/address/logic/commands/FilterCommand.java index 7c562afac2d..87c62d20b93 100644 --- a/src/main/java/seedu/address/logic/commands/FilterCommand.java +++ b/src/main/java/seedu/address/logic/commands/FilterCommand.java @@ -31,7 +31,7 @@ public FilterCommand(Predicate predicate) { } @Override - public CommandResult execute(Model model) { + protected CommandResult execute(Model model) { requireNonNull(model); model.updateFilteredPersonList(predicate); return new CommandResult( diff --git a/src/main/java/seedu/address/logic/commands/HelpCommand.java b/src/main/java/seedu/address/logic/commands/HelpCommand.java index bf824f91bd0..2ae81820002 100644 --- a/src/main/java/seedu/address/logic/commands/HelpCommand.java +++ b/src/main/java/seedu/address/logic/commands/HelpCommand.java @@ -1,5 +1,7 @@ package seedu.address.logic.commands; +import static seedu.address.logic.commands.CommandCommons.EMPTY_PERSON; + import seedu.address.model.Model; /** @@ -16,6 +18,6 @@ public class HelpCommand extends Command { @Override public CommandResult execute(Model model) { - return new CommandResult(SHOWING_HELP_MESSAGE, true, false); + return new CommandResult(SHOWING_HELP_MESSAGE, true, false, false, EMPTY_PERSON, false); } } diff --git a/src/main/java/seedu/address/logic/commands/ListCommand.java b/src/main/java/seedu/address/logic/commands/ListCommand.java index 84be6ad2596..1320c632340 100644 --- a/src/main/java/seedu/address/logic/commands/ListCommand.java +++ b/src/main/java/seedu/address/logic/commands/ListCommand.java @@ -16,7 +16,7 @@ public class ListCommand extends Command { @Override - public CommandResult execute(Model model) { + protected 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/ViewCommand.java b/src/main/java/seedu/address/logic/commands/ViewCommand.java new file mode 100644 index 00000000000..41495ad2c52 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/ViewCommand.java @@ -0,0 +1,76 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX; + +import java.util.List; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.person.Person; + +/** + * Represents a command to view the details of a person identified by their index in the displayed list. + * This command allows users to see detailed information about a specific person in the address book. + */ +public class ViewCommand extends Command { + + public static final String COMMAND_WORD = "view"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Views 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_VIEW_PERSON_SUCCESS = "Viewed Person: %1$s"; + + private final Index targetIndex; + + /** + * Creates a new ViewCommand to view the person at the specified {@code targetIndex}. + * + * @param targetIndex The index of the person to view in the filtered person list + * @throws NullPointerException if {@code targetIndex} is null + */ + public ViewCommand(Index targetIndex) { + requireNonNull(targetIndex); + this.targetIndex = targetIndex; + } + + /** + * Executes the view command to show the person at the specified index. + * + * @param model The model containing the person data + * @return A CommandResult containing the viewed person's information + * @throws CommandException if the index is invalid or out of bounds + * @throws NullPointerException if {@code model} is null + */ + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + List lastShownList = model.getFilteredPersonList(); + + if (targetIndex.getZeroBased() >= lastShownList.size()) { + throw new CommandException(MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + + Person personToView = lastShownList.get(targetIndex.getZeroBased()); + return new CommandResult(String.format(MESSAGE_VIEW_PERSON_SUCCESS, personToView), + false, false, true, personToView, false); + } + + /** + * Compares this ViewCommand to another object for equality. + * + * @param other The object to compare to + * @return true if the other object is also a ViewCommand targeting the same index + */ + @Override + public boolean equals(Object other) { + return other == this + || (other instanceof ViewCommand + && targetIndex.equals(((ViewCommand) other).targetIndex)); + } +} diff --git a/src/main/java/seedu/address/logic/parser/AddressBookParser.java b/src/main/java/seedu/address/logic/parser/AddressBookParser.java index bc2f04c5ab5..f8840fe74cd 100644 --- a/src/main/java/seedu/address/logic/parser/AddressBookParser.java +++ b/src/main/java/seedu/address/logic/parser/AddressBookParser.java @@ -10,6 +10,7 @@ import seedu.address.commons.core.LogsCenter; import seedu.address.logic.commands.AddCommand; import seedu.address.logic.commands.ClearCommand; +import seedu.address.logic.commands.CloseCommand; import seedu.address.logic.commands.Command; import seedu.address.logic.commands.DeleteCommand; import seedu.address.logic.commands.EditCommand; @@ -17,6 +18,7 @@ import seedu.address.logic.commands.FilterCommand; import seedu.address.logic.commands.HelpCommand; import seedu.address.logic.commands.ListCommand; +import seedu.address.logic.commands.ViewCommand; import seedu.address.logic.parser.exceptions.ParseException; /** @@ -78,10 +80,27 @@ public Command parseCommand(String userInput) throws ParseException { case HelpCommand.COMMAND_WORD: return new HelpCommand(); + case ViewCommand.COMMAND_WORD: + return new ViewCommandParser().parse(arguments); + + case CloseCommand.COMMAND_WORD: + return new CloseCommand(); + default: logger.finer("This user input caused a ParseException: " + userInput); throw new ParseException(MESSAGE_UNKNOWN_COMMAND); } } + /** + * Parses user input into command for execution. + * + * @param userInput full user input string + * @return boolean depending on whether the input is yes/y + */ + public boolean parseConfirmation(String userInput) { + userInput = userInput.replace("\n", "").toLowerCase().trim(); + return userInput.equals("y") || userInput.equals("yes"); + } + } diff --git a/src/main/java/seedu/address/logic/parser/ViewCommandParser.java b/src/main/java/seedu/address/logic/parser/ViewCommandParser.java new file mode 100644 index 00000000000..2d82dbf1ce8 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/ViewCommandParser.java @@ -0,0 +1,28 @@ +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.ViewCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a new ViewCommand object + */ +public class ViewCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the ViewCommand + * and returns a ViewCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public ViewCommand parse(String args) throws ParseException { + try { + Index index = ParserUtil.parseIndex(args); + return new ViewCommand(index); + } catch (ParseException pe) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, ViewCommand.MESSAGE_USAGE), pe); + } + } +} diff --git a/src/main/java/seedu/address/model/Model.java b/src/main/java/seedu/address/model/Model.java index 59747e3d623..b9c7d1ea542 100644 --- a/src/main/java/seedu/address/model/Model.java +++ b/src/main/java/seedu/address/model/Model.java @@ -3,6 +3,7 @@ import java.nio.file.Path; import java.util.function.Predicate; +import javafx.beans.property.ReadOnlyProperty; import javafx.collections.ObservableList; import seedu.address.commons.core.GuiSettings; import seedu.address.model.person.Person; @@ -84,4 +85,21 @@ public interface Model { * @throws NullPointerException if {@code predicates} is null. */ void updateFilteredPersonList(Predicate predicate); + + /** + * Selected person in the filtered person list. + * null if no person is selected. + */ + ReadOnlyProperty selectedPersonProperty(); + + /** + * Returns the selected person in the filtered person list. + * null if no person is selected. + */ + Person getSelectedPerson(); + + /** + * Sets the selected person in the filtered person list. + */ + void setSelectedPerson(Person person); } diff --git a/src/main/java/seedu/address/model/ModelManager.java b/src/main/java/seedu/address/model/ModelManager.java index 57bc563fde6..6908c632603 100644 --- a/src/main/java/seedu/address/model/ModelManager.java +++ b/src/main/java/seedu/address/model/ModelManager.java @@ -7,11 +7,15 @@ import java.util.function.Predicate; import java.util.logging.Logger; +import javafx.beans.property.ReadOnlyProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.collections.ListChangeListener; import javafx.collections.ObservableList; import javafx.collections.transformation.FilteredList; import seedu.address.commons.core.GuiSettings; import seedu.address.commons.core.LogsCenter; import seedu.address.model.person.Person; +import seedu.address.model.person.exceptions.PersonNotFoundException; /** * Represents the in-memory model of the address book data. @@ -22,11 +26,13 @@ public class ModelManager implements Model { private final AddressBook addressBook; private final UserPrefs userPrefs; private final FilteredList filteredPersons; + private final SimpleObjectProperty selectedPerson = new SimpleObjectProperty<>(); /** * Initializes a ModelManager with the given addressBook and userPrefs. */ public ModelManager(ReadOnlyAddressBook addressBook, ReadOnlyUserPrefs userPrefs) { + super(); requireAllNonNull(addressBook, userPrefs); logger.fine("Initializing with address book: " + addressBook + " and user prefs " + userPrefs); @@ -40,6 +46,7 @@ public ModelManager() { this(new AddressBook(), new UserPrefs()); } + //=========== UserPrefs ================================================================================== @Override @@ -111,6 +118,44 @@ public void setPerson(Person target, Person editedPerson) { addressBook.setPerson(target, editedPerson); } + //=========== Selected Person =========================================================================== + + @Override + public ReadOnlyProperty selectedPersonProperty() { + return selectedPerson; + } + + @Override + public Person getSelectedPerson() { + return selectedPerson.getValue(); + } + + @Override + public void setSelectedPerson(Person person) { + requireNonNull(person); + if (!filteredPersons.contains(person)) { + throw new PersonNotFoundException(); + } + selectedPerson.setValue(person); + } + + /** + * Ensures {@code selectedPerson} is a valid person in {@code filteredPersons}. + */ + private void ensureSelectedPersonIsValid(ListChangeListener.Change change) { + while (change.next()) { + if (this.getSelectedPerson() == null) { + return; + } + + boolean wasSelectedPersonRemoved = change.getRemoved().stream() + .anyMatch(removedPerson -> selectedPerson.getValue().isSamePerson(removedPerson)); + if (wasSelectedPersonRemoved) { + this.setSelectedPerson(change.getFrom() > 0 ? filteredPersons.get(change.getFrom() - 1) : null); + } + } + } + //=========== Filtered Person List Accessors ============================================================= /** diff --git a/src/main/java/seedu/address/ui/MainWindow.java b/src/main/java/seedu/address/ui/MainWindow.java index 2d5a689f135..0cb9ecf01cb 100644 --- a/src/main/java/seedu/address/ui/MainWindow.java +++ b/src/main/java/seedu/address/ui/MainWindow.java @@ -5,7 +5,9 @@ import javafx.event.ActionEvent; import javafx.fxml.FXML; import javafx.scene.control.MenuItem; +import javafx.scene.control.SplitPane; import javafx.scene.control.TextInputControl; +import javafx.scene.image.ImageView; import javafx.scene.input.KeyCombination; import javafx.scene.input.KeyEvent; import javafx.scene.layout.StackPane; @@ -16,6 +18,7 @@ import seedu.address.logic.commands.CommandResult; import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.person.Person; /** * The Main Window. Provides the basic application layout containing @@ -30,8 +33,8 @@ public class MainWindow extends UiPart { private Stage primaryStage; private Logic logic; - // Independent Ui parts residing in this Ui container private PersonListPanel personListPanel; + private PersonDetailPanel personDetailPanel; private ResultDisplay resultDisplay; private HelpWindow helpWindow; @@ -41,37 +44,52 @@ public class MainWindow extends UiPart { @FXML private MenuItem helpMenuItem; + @FXML + private SplitPane splitPane; + @FXML private StackPane personListPanelPlaceholder; + @FXML + private StackPane personDetailsPanelPlaceholder; + @FXML private StackPane resultDisplayPlaceholder; @FXML private StackPane statusbarPlaceholder; + @FXML + private ImageView placeholderImage; + /** - * Creates a {@code MainWindow} with the given {@code Stage} and {@code Logic}. + * Creates a new MainWindow with the given Stage and Logic. + * Initializes the window with default settings and accelerators. + * + * @param primaryStage The primary stage for this window + * @param logic The logic manager that handles command execution */ public MainWindow(Stage primaryStage, Logic logic) { super(FXML, primaryStage); - - // Set dependencies this.primaryStage = primaryStage; this.logic = logic; - - // Configure the UI setWindowDefaultSize(logic.getGuiSettings()); - setAccelerators(); - helpWindow = new HelpWindow(); } + /** + * Returns the primary stage of the application. + * + * @return The primary stage + */ public Stage getPrimaryStage() { return primaryStage; } + /** + * Sets up keyboard accelerators for menu items. + */ private void setAccelerators() { setAccelerator(helpMenuItem, KeyCombination.valueOf("F1")); } @@ -83,21 +101,6 @@ private void setAccelerators() { private void setAccelerator(MenuItem menuItem, KeyCombination keyCombination) { menuItem.setAccelerator(keyCombination); - /* - * TODO: the code below can be removed once the bug reported here - * https://bugs.openjdk.java.net/browse/JDK-8131666 - * is fixed in later version of SDK. - * - * According to the bug report, TextInputControl (TextField, TextArea) will - * consume function-key events. Because CommandBox contains a TextField, and - * ResultDisplay contains a TextArea, thus some accelerators (e.g F1) will - * not work when the focus is in them because the key event is consumed by - * the TextInputControl(s). - * - * For now, we add following event filter to capture such key events and open - * help window purposely so to support accelerators even when focus is - * in CommandBox or ResultDisplay. - */ getRoot().addEventFilter(KeyEvent.KEY_PRESSED, event -> { if (event.getTarget() instanceof TextInputControl && keyCombination.match(event)) { menuItem.getOnAction().handle(new ActionEvent()); @@ -113,6 +116,9 @@ void fillInnerParts() { personListPanel = new PersonListPanel(logic.getFilteredPersonList()); personListPanelPlaceholder.getChildren().add(personListPanel.getRoot()); + personDetailPanel = new PersonDetailPanel(); + personDetailsPanelPlaceholder.getChildren().add(personDetailPanel.getRoot()); + resultDisplay = new ResultDisplay(); resultDisplayPlaceholder.getChildren().add(resultDisplay.getRoot()); @@ -121,6 +127,14 @@ void fillInnerParts() { CommandBox commandBox = new CommandBox(this::executeCommand); commandBoxPlaceholder.getChildren().add(commandBox.getRoot()); + + splitPane.setDividerPositions(0.6); + + splitPane.getDividers().get(0).positionProperty().addListener((observable, oldValue, newValue) -> { + splitPane.setDividerPositions(0.6); + }); + + splitPane.getItems().remove(personDetailsPanelPlaceholder); } /** @@ -147,6 +161,9 @@ public void handleHelp() { } } + /** + * Makes the main window visible. + */ void show() { primaryStage.show(); } @@ -163,8 +180,37 @@ private void handleExit() { primaryStage.hide(); } - public PersonListPanel getPersonListPanel() { - return personListPanel; + /** + * Handles the view command by updating the UI to show person details. + * If the details panel is not visible, adds it to the split pane. + * Updates the person details based on the provided index in the command. + * + * @param commandText The full command text containing the view command and index + */ + private void handleViewCommand(String commandText) { + if (!splitPane.getItems().contains(personDetailsPanelPlaceholder)) { + splitPane.getItems().add(personDetailsPanelPlaceholder); + placeholderImage.setVisible(false); + splitPane.setDividerPositions(0.6); + } + + String[] commandParts = commandText.split("\\s+"); + if (commandParts.length > 1) { + try { + int index = Integer.parseInt(commandParts[1]) - 1; // Assuming 1-based indexing in UI + Person personToView = logic.getFilteredPersonList().get(index); + personDetailPanel.setPersonDetails(personToView); + } catch (NumberFormatException | IndexOutOfBoundsException e) { + // Handle invalid index + resultDisplay.setFeedbackToUser("Invalid person index."); + } + } else { + resultDisplay.setFeedbackToUser("Please provide a person index to view."); + } + } + + private void handleCloseCommand() { + splitPane.getItems().remove(personDetailsPanelPlaceholder); } /** @@ -175,10 +221,15 @@ public PersonListPanel getPersonListPanel() { private CommandResult executeCommand(String commandText) throws CommandException, ParseException { try { CommandResult commandResult = logic.execute(commandText); - logger.info("Result: " + commandResult.getFeedbackToUser()); // used to log the result, not actually - // used to display + logger.info("Result: " + commandResult.getFeedbackToUser()); resultDisplay.setFeedbackToUser(commandResult.getFeedbackToUser()); + if (commandResult.isShowPerson()) { + handleViewCommand(commandText); + } else { + handleCloseCommand(); + } + if (commandResult.isShowHelp()) { handleHelp(); } @@ -189,7 +240,7 @@ private CommandResult executeCommand(String commandText) throws CommandException return commandResult; } catch (CommandException | ParseException e) { - logger.info("An error occurred while executing command: " + commandText); + logger.info("Invalid command: " + commandText); resultDisplay.setFeedbackToUser(e.getMessage()); throw e; } diff --git a/src/main/java/seedu/address/ui/PersonCard.java b/src/main/java/seedu/address/ui/PersonCard.java index 2a0fd281d7e..00aea43f39b 100644 --- a/src/main/java/seedu/address/ui/PersonCard.java +++ b/src/main/java/seedu/address/ui/PersonCard.java @@ -65,20 +65,16 @@ private void createFields() { private void createTier() { // Create a label for the tier - Label tierLabel = new Label(person.getTier().toParsableString()); + Label tierLabel = new Label(person.getTier().toParsableString().toUpperCase()); - // Apply a different style class based on the tier value - String tier = person.getTier().toParsableString().toUpperCase(); - switch (tier) { - case "GOLD" -> tierLabel.getStyleClass().add("gold-tier"); - case "SILVER" -> tierLabel.getStyleClass().add("silver-tier"); - case "BRONZE" -> tierLabel.getStyleClass().add("bronze-tier"); - case "REJECT" -> tierLabel.getStyleClass().add("reject-tier"); - default -> tierLabel = null; - } - if (tierLabel != null) { - // Add the label to the FlowPane - assignedTier.getChildren().add(tierLabel); - } + // Apply the existing style classes + tierLabel.getStyleClass().add("label"); + + // Add the tier-specific style class + String tier = person.getTier().toParsableString().toLowerCase(); + tierLabel.getStyleClass().add(tier + "-tier"); + + // Add the label to the FlowPane + assignedTier.getChildren().add(tierLabel); } } diff --git a/src/main/java/seedu/address/ui/PersonDetailPanel.java b/src/main/java/seedu/address/ui/PersonDetailPanel.java new file mode 100644 index 00000000000..bf145725a80 --- /dev/null +++ b/src/main/java/seedu/address/ui/PersonDetailPanel.java @@ -0,0 +1,151 @@ +package seedu.address.ui; + +import java.util.logging.Logger; + +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.layout.FlowPane; +import javafx.scene.layout.Region; +import seedu.address.commons.core.LogsCenter; +import seedu.address.model.person.Person; + +/** + * A UI component that displays detailed information about a {@code Person}. + * Shows all available fields including name, phone, address, email, job, income, tier, and remarks. + * Fields can be shown or hidden based on the presence of a person's data. + */ +public class PersonDetailPanel extends UiPart { + private static final String FXML = "PersonDetailPanel.fxml"; + private final Logger logger = LogsCenter.getLogger(PersonDetailPanel.class); + + @FXML + private Label nameLabel; + @FXML + private Label phoneLabel; + @FXML + private Label addressLabel; + @FXML + private Label emailLabel; + @FXML + private Label jobLabel; + @FXML + private Label incomeLabel; + @FXML + private FlowPane tierPane; + @FXML + private Label remarkLabel; + + /** + * Creates a new PersonDetailPanel. + * This constructor initializes all labels and hides them by default. + */ + public PersonDetailPanel() { + super(FXML); + initializeLabels(); + hideAllFields(); + } + + /** + * Initializes all FXML-injected labels, creating new ones if they are null. + * This ensures all label references are valid for manipulation. + */ + private void initializeLabels() { + nameLabel = nameLabel == null ? new Label() : nameLabel; + phoneLabel = phoneLabel == null ? new Label() : phoneLabel; + addressLabel = addressLabel == null ? new Label() : addressLabel; + emailLabel = emailLabel == null ? new Label() : emailLabel; + jobLabel = jobLabel == null ? new Label() : jobLabel; + incomeLabel = incomeLabel == null ? new Label() : incomeLabel; + remarkLabel = remarkLabel == null ? new Label() : remarkLabel; + } + + /** + * Hides all fields in the detail panel by setting their managed and visible properties to false. + * This is used when no person is selected or when clearing the panel. + */ + private void hideAllFields() { + setManagedAndVisible(nameLabel, false); + setManagedAndVisible(phoneLabel, false); + setManagedAndVisible(addressLabel, false); + setManagedAndVisible(emailLabel, false); + setManagedAndVisible(jobLabel, false); + setManagedAndVisible(incomeLabel, false); + setManagedAndVisible(tierPane, false); + setManagedAndVisible(remarkLabel, false); + } + + /** + * Sets both the managed and visible properties of a JavaFX node. + * + * @param node The JavaFX node to modify + * @param value The boolean value to set for both managed and visible properties + */ + private void setManagedAndVisible(javafx.scene.Node node, boolean value) { + if (node != null) { + node.setManaged(value); + node.setVisible(value); + } + } + + /** + * Updates the detail panel to display information about the given person. + * If person is null, all fields will be hidden. + * + * @param person The person whose details should be displayed, can be null + */ + public void setPersonDetails(Person person) { + if (person != null) { + showAllFields(); + setLabelText(nameLabel, person.getName().fullName); + setLabelText(phoneLabel, person.getPhone().value); + setLabelText(addressLabel, person.getAddress().value); + setLabelText(emailLabel, person.getEmail().value); + setLabelText(jobLabel, person.getJob().value); + setLabelText(incomeLabel, String.valueOf(person.getIncome())); + setTier(person.getTier().toParsableString()); + setLabelText(remarkLabel, person.getRemark().value); + } else { + hideAllFields(); + } + } + + /** + * Sets the text of a label if the label is not null. + * + * @param label The label to update + * @param text The text to set + */ + private void setLabelText(Label label, String text) { + if (label != null) { + label.setText(text); + } + } + + /** + * Sets the tier display in the detail panel. + * Creates a new styled label for the tier and adds it to the tier pane. + * + * @param tier The tier value to display + */ + private void setTier(String tier) { + tierPane.getChildren().clear(); + Label tierLabel = new Label(tier.toUpperCase()); + tierLabel.getStyleClass().addAll("label", "detail-tier-label", tier.toLowerCase() + "-tier"); + tierPane.getChildren().add(tierLabel); + } + + /** + * Shows all fields in the detail panel by setting their managed and visible properties to true. + * This is used when displaying a person's details. + */ + private void showAllFields() { + setManagedAndVisible(nameLabel, true); + setManagedAndVisible(phoneLabel, true); + setManagedAndVisible(addressLabel, true); + setManagedAndVisible(emailLabel, true); + setManagedAndVisible(jobLabel, true); + setManagedAndVisible(incomeLabel, true); + setManagedAndVisible(tierPane, true); + setManagedAndVisible(remarkLabel, true); + } +} diff --git a/src/main/java/seedu/address/ui/PersonListPanel.java b/src/main/java/seedu/address/ui/PersonListPanel.java index f4c501a897b..f52048b99af 100644 --- a/src/main/java/seedu/address/ui/PersonListPanel.java +++ b/src/main/java/seedu/address/ui/PersonListPanel.java @@ -29,6 +29,10 @@ public PersonListPanel(ObservableList personList) { personListView.setCellFactory(listView -> new PersonListViewCell()); } + public ListView getListView() { + return personListView; + } + /** * Custom {@code ListCell} that displays the graphics of a {@code Person} using a {@code PersonCard}. */ diff --git a/src/main/resources/view/DarkTheme.css b/src/main/resources/view/DarkTheme.css index a299de39ecd..e7d9768c499 100644 --- a/src/main/resources/view/DarkTheme.css +++ b/src/main/resources/view/DarkTheme.css @@ -307,6 +307,47 @@ -fx-padding: 8 1 8 1; } +.cell_small_label { + -fx-font-family: "Segoe UI"; + -fx-font-size: 13px; + -fx-text-fill: #a0a0a0; +} + +.cell_big_label { + -fx-font-family: "Segoe UI Bold"; + -fx-font-size: 16px; + -fx-text-fill: white; +} + +.name-label { + -fx-font-weight: bold; +} + +.detail-tier-label { + -fx-text-fill: white; + -fx-padding: 2 5 2 5; + -fx-border-radius: 3; + -fx-background-radius: 3; + -fx-font-size: 12px; +} + +/* Tier-specific styles for the detail panel */ +.detail-tier-label.gold-tier { + -fx-background-color: #B59410; +} + +.detail-tier-label.silver-tier { + -fx-background-color: #71706e; +} + +.detail-tier-label.bronze-tier { + -fx-background-color: #804a00; +} + +.detail-tier-label.reject-tier { + -fx-background-color: #8B0000; +} + #cardPane { -fx-background-color: transparent; -fx-border-width: 0; @@ -370,3 +411,15 @@ -fx-background-radius: 2; -fx-font-size: 11; } + +#personDetailsPanelPlaceholder { + -fx-background-color: #383838; +} + +.scroll-pane { + -fx-background-color: #383838; +} + +.scroll-pane > .viewport { + -fx-background-color: #383838; +} diff --git a/src/main/resources/view/MainWindow.fxml b/src/main/resources/view/MainWindow.fxml index 8f953aa9bdb..7760fa3c805 100644 --- a/src/main/resources/view/MainWindow.fxml +++ b/src/main/resources/view/MainWindow.fxml @@ -8,6 +8,7 @@ + @@ -46,13 +47,22 @@ - - - - - - - + + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/PersonDetailPanel.fxml b/src/main/resources/view/PersonDetailPanel.fxml new file mode 100644 index 00000000000..50e2c59e196 --- /dev/null +++ b/src/main/resources/view/PersonDetailPanel.fxml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/java/seedu/address/logic/LogicManagerTest.java b/src/test/java/seedu/address/logic/LogicManagerTest.java index 617445f4582..aa314c15249 100644 --- a/src/test/java/seedu/address/logic/LogicManagerTest.java +++ b/src/test/java/seedu/address/logic/LogicManagerTest.java @@ -1,7 +1,6 @@ package seedu.address.logic; import static org.junit.jupiter.api.Assertions.assertEquals; -import static seedu.address.logic.Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX; import static seedu.address.logic.Messages.MESSAGE_UNKNOWN_COMMAND; import static seedu.address.logic.commands.CommandTestUtil.ADDRESS_DESC_AMY; import static seedu.address.logic.commands.CommandTestUtil.EMAIL_DESC_AMY; @@ -9,6 +8,7 @@ import static seedu.address.logic.commands.CommandTestUtil.JOB_DESC_AMY; import static seedu.address.logic.commands.CommandTestUtil.NAME_DESC_AMY; import static seedu.address.logic.commands.CommandTestUtil.PHONE_DESC_AMY; +import static seedu.address.logic.commands.DeleteCommand.MESSAGE_DELETE_CONFIRMATION; import static seedu.address.testutil.Assert.assertThrows; import static seedu.address.testutil.TypicalPersons.AMY; @@ -60,11 +60,29 @@ public void execute_invalidCommandFormat_throwsParseException() { } @Test - public void execute_commandExecutionError_throwsCommandException() { + public void execute_deleteCommandRequiresConfirmation_displaysDeleteConfirmationMessage() + throws CommandException, ParseException { String deleteCommand = "delete 9"; - assertCommandException(deleteCommand, MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + assertCommandSuccess(deleteCommand, MESSAGE_DELETE_CONFIRMATION, model); } + @Test + public void execute_cancelDeleteCommand_success() throws CommandException, ParseException { + String[] deleteCommand = {"delete 9", "no"}; + Model expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs()); + assertCommandSuccess(deleteCommand[0], MESSAGE_DELETE_CONFIRMATION, expectedModel); + } + + /* + @Test + public void execute_commandExecutionError_throwsCommandException() throws CommandException, ParseException { + String[] deleteCommand = {"delete 9", "y"}; + assertCommandSuccess(deleteCommand[0], MESSAGE_DELETE_CONFIRMATION, model); + assertThrows(CommandException.class, MESSAGE_INVALID_PERSON_DISPLAYED_INDEX, () -> + logic.execute(deleteCommand[1])); + } + */ + @Test public void execute_validCommand_success() throws Exception { String listCommand = ListCommand.COMMAND_WORD; @@ -88,6 +106,25 @@ public void getFilteredPersonList_modifyList_throwsUnsupportedOperationException assertThrows(UnsupportedOperationException.class, () -> logic.getFilteredPersonList().remove(0)); } + /** + * Executes the command (which requires a confirmation) and confirms that + * - the {@code expectedException} is thrown
+ * - the resulting error message is equal to {@code expectedMessage}
+ * @see #assertCommandSuccess(String, String, Model) + */ + private void assertDeleteCommandFailure(Class expectedException, String expectedMessage, + String... inputCommand) throws CommandException, ParseException { + for (int i = 0; i < inputCommand.length; i++) { + if (i == inputCommand.length - 1) { + assertThrows(expectedException, expectedMessage, () -> + logic.execute(inputCommand[inputCommand.length - 1])); + } else { + logic.execute(inputCommand[i]); + } + } + } + + /** * Executes the command and confirms that * - no exceptions are thrown
@@ -96,12 +133,13 @@ public void getFilteredPersonList_modifyList_throwsUnsupportedOperationException * @see #assertCommandFailure(String, Class, String, Model) */ private void assertCommandSuccess(String inputCommand, String expectedMessage, - Model expectedModel) throws CommandException, ParseException { + Model expectedModel) throws CommandException, ParseException { CommandResult result = logic.execute(inputCommand); assertEquals(expectedMessage, result.getFeedbackToUser()); - assertEquals(expectedModel, model); + assertEquals(expectedModel.getAddressBook(), model.getAddressBook()); + assertEquals(expectedModel.getFilteredPersonList(), model.getFilteredPersonList()); + assertEquals(expectedModel.getSelectedPerson(), model.getSelectedPerson()); } - /** * Executes the command, confirms that a ParseException is thrown and that the result message is correct. * @see #assertCommandFailure(String, Class, String, Model) @@ -173,4 +211,5 @@ public void saveAddressBook(ReadOnlyAddressBook addressBook, Path filePath) expectedModel.addPerson(expectedPerson); assertCommandFailure(addCommand, CommandException.class, expectedMessage, expectedModel); } + } diff --git a/src/test/java/seedu/address/logic/commands/AddCommandTest.java b/src/test/java/seedu/address/logic/commands/AddCommandTest.java index 90e8253f48e..31b3d9438a8 100644 --- a/src/test/java/seedu/address/logic/commands/AddCommandTest.java +++ b/src/test/java/seedu/address/logic/commands/AddCommandTest.java @@ -14,6 +14,7 @@ import org.junit.jupiter.api.Test; +import javafx.beans.property.ReadOnlyProperty; import javafx.collections.ObservableList; import seedu.address.commons.core.GuiSettings; import seedu.address.logic.Messages; @@ -157,6 +158,21 @@ public ObservableList getFilteredPersonList() { public void updateFilteredPersonList(Predicate predicate) { throw new AssertionError("This method should not be called."); } + + @Override + public ReadOnlyProperty selectedPersonProperty() { + throw new AssertionError("This method should not be called."); + } + + @Override + public Person getSelectedPerson() { + throw new AssertionError("This method should not be called."); + } + + @Override + public void setSelectedPerson(Person person) { + throw new AssertionError("This method should not be called."); + } } /** diff --git a/src/test/java/seedu/address/logic/commands/CloseCommandTest.java b/src/test/java/seedu/address/logic/commands/CloseCommandTest.java new file mode 100644 index 00000000000..378d7c35afd --- /dev/null +++ b/src/test/java/seedu/address/logic/commands/CloseCommandTest.java @@ -0,0 +1,47 @@ +package seedu.address.logic.commands; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +import seedu.address.model.Model; +import seedu.address.model.ModelManager; + +public class CloseCommandTest { + + private Model model = new ModelManager(); + @Test + public void execute_close_success() { + CloseCommand closeCommand = new CloseCommand(); + CommandResult commandResult = closeCommand.execute(model); + + assertEquals(CloseCommand.MESSAGE_SUCCESS, commandResult.getFeedbackToUser()); + assertFalse(commandResult.isShowHelp()); + assertFalse(commandResult.isExit()); + } + + @Test + public void equals() { + CloseCommand closeCommand = new CloseCommand(); + + // same object -> returns true + assertTrue(closeCommand.equals(closeCommand)); + + // same type -> returns true + assertTrue(closeCommand.equals(new CloseCommand())); + + // null -> returns false + assertFalse(closeCommand.equals(null)); + + // different types -> returns false + assertFalse(closeCommand.equals(1)); + } + + @Test + public void toStringMethod() { + CloseCommand closeCommand = new CloseCommand(); + assertEquals(CloseCommand.COMMAND_WORD, closeCommand.toString()); + } +} diff --git a/src/test/java/seedu/address/logic/commands/CommandResultTest.java b/src/test/java/seedu/address/logic/commands/CommandResultTest.java index 7b8c7cd4546..f78c6f4f332 100644 --- a/src/test/java/seedu/address/logic/commands/CommandResultTest.java +++ b/src/test/java/seedu/address/logic/commands/CommandResultTest.java @@ -4,6 +4,7 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.logic.commands.CommandCommons.EMPTY_PERSON; import org.junit.jupiter.api.Test; @@ -14,7 +15,7 @@ public void equals() { // same values -> returns true assertTrue(commandResult.equals(new CommandResult("feedback"))); - assertTrue(commandResult.equals(new CommandResult("feedback", false, false))); + assertTrue(commandResult.equals(new CommandResult("feedback", false, false, false, EMPTY_PERSON, false))); // same object -> returns true assertTrue(commandResult.equals(commandResult)); @@ -29,10 +30,12 @@ public void equals() { assertFalse(commandResult.equals(new CommandResult("different"))); // different showHelp value -> returns false - assertFalse(commandResult.equals(new CommandResult("feedback", true, false))); + assertFalse(commandResult.equals(new CommandResult("feedback", + true, false, false, EMPTY_PERSON, false))); // different exit value -> returns false - assertFalse(commandResult.equals(new CommandResult("feedback", false, true))); + assertFalse(commandResult.equals(new CommandResult("feedback", + false, true, false, EMPTY_PERSON, false))); } @Test @@ -46,10 +49,12 @@ public void hashcode() { assertNotEquals(commandResult.hashCode(), new CommandResult("different").hashCode()); // different showHelp value -> returns different hashcode - assertNotEquals(commandResult.hashCode(), new CommandResult("feedback", true, false).hashCode()); + assertNotEquals(commandResult.hashCode(), new CommandResult("feedback", + true, false, false, EMPTY_PERSON, false).hashCode()); // different exit value -> returns different hashcode - assertNotEquals(commandResult.hashCode(), new CommandResult("feedback", false, true).hashCode()); + assertNotEquals(commandResult.hashCode(), new CommandResult("feedback", + false, true, false, EMPTY_PERSON, false).hashCode()); } @Test diff --git a/src/test/java/seedu/address/logic/commands/ExitCommandTest.java b/src/test/java/seedu/address/logic/commands/ExitCommandTest.java index 9533c473875..19c0aa49724 100644 --- a/src/test/java/seedu/address/logic/commands/ExitCommandTest.java +++ b/src/test/java/seedu/address/logic/commands/ExitCommandTest.java @@ -14,7 +14,8 @@ public class ExitCommandTest { @Test public void execute_exit_success() { - CommandResult expectedCommandResult = new CommandResult(MESSAGE_EXIT_ACKNOWLEDGEMENT, false, true); + CommandResult expectedCommandResult = new CommandResult(MESSAGE_EXIT_ACKNOWLEDGEMENT, + false, true, false, null, false); assertCommandSuccess(new ExitCommand(), model, expectedCommandResult, expectedModel); } } diff --git a/src/test/java/seedu/address/logic/commands/HelpCommandTest.java b/src/test/java/seedu/address/logic/commands/HelpCommandTest.java index 4904fc4352e..b9f61ff5f88 100644 --- a/src/test/java/seedu/address/logic/commands/HelpCommandTest.java +++ b/src/test/java/seedu/address/logic/commands/HelpCommandTest.java @@ -1,5 +1,6 @@ package seedu.address.logic.commands; +import static seedu.address.logic.commands.CommandCommons.EMPTY_PERSON; import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; import static seedu.address.logic.commands.HelpCommand.SHOWING_HELP_MESSAGE; @@ -14,7 +15,8 @@ public class HelpCommandTest { @Test public void execute_help_success() { - CommandResult expectedCommandResult = new CommandResult(SHOWING_HELP_MESSAGE, true, false); + CommandResult expectedCommandResult = new CommandResult(SHOWING_HELP_MESSAGE, + true, false, false, EMPTY_PERSON, false); assertCommandSuccess(new HelpCommand(), model, expectedCommandResult, expectedModel); } } diff --git a/src/test/java/seedu/address/logic/commands/ViewCommandTest.java b/src/test/java/seedu/address/logic/commands/ViewCommandTest.java new file mode 100644 index 00000000000..4016872f43b --- /dev/null +++ b/src/test/java/seedu/address/logic/commands/ViewCommandTest.java @@ -0,0 +1,80 @@ +package seedu.address.logic.commands; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.logic.Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX; +import static seedu.address.testutil.Assert.assertThrows; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON; +import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook; + +import org.junit.jupiter.api.Test; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.ModelManager; +import seedu.address.model.UserPrefs; +import seedu.address.model.person.Person; + +class ViewCommandTest { + + private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + + @Test + public void constructor_nullIndex_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> new ViewCommand(null)); + } + + @Test + public void execute_validIndexUnfilteredList_success() throws Exception { + Person personToView = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased()); + ViewCommand viewCommand = new ViewCommand(INDEX_FIRST_PERSON); + + String expectedMessage = String.format(ViewCommand.MESSAGE_VIEW_PERSON_SUCCESS, personToView); + + CommandResult result = viewCommand.execute(model); + + assertEquals(expectedMessage, result.getFeedbackToUser()); + assertTrue(result.isShowPerson()); + assertEquals(personToView, result.getViewedPerson()); + } + + @Test + public void execute_invalidIndexUnfilteredList_throwsCommandException() { + Index outOfBoundIndex = Index.fromOneBased(model.getFilteredPersonList().size() + 1); + ViewCommand viewCommand = new ViewCommand(outOfBoundIndex); + + assertThrows(CommandException.class, MESSAGE_INVALID_PERSON_DISPLAYED_INDEX, () -> + viewCommand.execute(model)); + } + + @Test + public void equals() { + ViewCommand viewFirstCommand = new ViewCommand(INDEX_FIRST_PERSON); + ViewCommand viewSecondCommand = new ViewCommand(Index.fromOneBased(2)); + + // same object -> returns true + assertTrue(viewFirstCommand.equals(viewFirstCommand)); + + // same values -> returns true + ViewCommand viewFirstCommandCopy = new ViewCommand(INDEX_FIRST_PERSON); + assertTrue(viewFirstCommand.equals(viewFirstCommandCopy)); + + // different types -> returns false + assertFalse(viewFirstCommand.equals(1)); + + // null -> returns false + assertFalse(viewFirstCommand.equals(null)); + + // different person -> returns false + assertFalse(viewFirstCommand.equals(viewSecondCommand)); + } + + @Test + public void testCommandUsageMessage() { + assertEquals("view: Views the person identified by the index number used in the displayed person list.\n" + + "Parameters: INDEX (must be a positive integer)\n" + + "Example: view 1", ViewCommand.MESSAGE_USAGE); + } +} diff --git a/src/test/java/seedu/address/logic/parser/ViewCommandParserTest.java b/src/test/java/seedu/address/logic/parser/ViewCommandParserTest.java new file mode 100644 index 00000000000..4a73641e031 --- /dev/null +++ b/src/test/java/seedu/address/logic/parser/ViewCommandParserTest.java @@ -0,0 +1,30 @@ +package seedu.address.logic.parser; + +import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON; + +import org.junit.jupiter.api.Test; + +import seedu.address.logic.commands.ViewCommand; + +public class ViewCommandParserTest { + + private ViewCommandParser parser = new ViewCommandParser(); + + @Test + public void parse_validArgs_returnsViewCommand() { + assertParseSuccess(parser, "1", new ViewCommand(INDEX_FIRST_PERSON)); + } + + @Test + public void parse_invalidArgs_throwsParseException() { + assertParseFailure(parser, "a", String.format(MESSAGE_INVALID_COMMAND_FORMAT, ViewCommand.MESSAGE_USAGE)); + } + + @Test + public void parse_emptyArg_throwsParseException() { + assertParseFailure(parser, "", String.format(MESSAGE_INVALID_COMMAND_FORMAT, ViewCommand.MESSAGE_USAGE)); + } +}