diff --git a/README.md b/README.md index 13f5c77403f..62a66080735 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,17 @@ -[![CI Status](https://github.com/se-edu/addressbook-level3/workflows/Java%20CI/badge.svg)](https://github.com/se-edu/addressbook-level3/actions) +[![CI Status](https://github.com/se-edu/addressbook-level3/workflows/Java%20CI/badge.svg)](https://github.com/se-edu/addressbook-level3/actions) [![codecov](https://codecov.io/gh/AY2324S2-CS2103T-T08-3/tp/graph/badge.svg?token=0RG4SRDBHW)](https://codecov.io/gh/AY2324S2-CS2103T-T08-3/tp) ![Ui](docs/images/Ui.png) -* This is **a sample project for Software Engineering (SE) students**.
+* This is **a project for Computer professional job seekers**.
Example usages: - * as a starting point of a course project (as opposed to writing everything from scratch) - * as a case study -* The project simulates an ongoing software project for a desktop application (called _AddressBook_) used for managing contact details. + * as a tool to manage job applications + * as a tool to manage contacts of recruiters and interviewers +* The project simulates an ongoing software project for a desktop application (called _CareerConnect Bot_) used for managing contact details. * It is **written in OOP fashion**. It provides a **reasonably well-written** code base **bigger** (around 6 KLoC) than what students usually write in beginner-level SE modules, without being overwhelmingly big. * It comes with a **reasonable level of user and developer documentation**. -* It is named `AddressBook Level 3` (`AB3` for short) because it was initially created as a part of a series of `AddressBook` projects (`Level 1`, `Level 2`, `Level 3` ...). +* It is named `CareerConnect Bot` (`CCBot` for short), it was initially created as a part of a series of `AddressBook` projects (`Level 1`, `Level 2`, `Level 3` ...). * For the detailed documentation of this project, see the **[Address Book Product Website](https://se-education.org/addressbook-level3)**. -* This project is a **part of the se-education.org** initiative. If you would like to contribute code to this project, see [se-education.org](https://se-education.org#https://se-education.org/#contributing) for more info. +* This project is based on the AddressBook-Level3 project created by the [SE-EDU initiative](https://se-education.org). + + + diff --git a/build.gradle b/build.gradle index a2951cc709e..4e1ce7b9c71 100644 --- a/build.gradle +++ b/build.gradle @@ -66,7 +66,11 @@ dependencies { } shadowJar { - archiveFileName = 'addressbook.jar' + archiveFileName = 'CCBOT.jar' +} + +run { + enableAssertions = true } defaultTasks 'clean', 'test' diff --git a/docs/.Rhistory b/docs/.Rhistory new file mode 100644 index 00000000000..e69de29bb2d diff --git a/docs/AboutUs.md b/docs/AboutUs.md index 1c9514e966a..851ba29c3f6 100644 --- a/docs/AboutUs.md +++ b/docs/AboutUs.md @@ -9,51 +9,50 @@ You can reach us at the email `seer[at]comp.nus.edu.sg` ## Project team -### John Doe +### Zhang Tianyao - + -[[homepage](http://www.comp.nus.edu.sg/~damithch)] -[[github](https://github.com/johndoe)] -[[portfolio](team/johndoe.md)] +[[homepage](https://github.com/ZHANGTIANYAO1)] +[[github](https://github.com/ZHANGTIANYAO1)] +[[portfolio](team/zhangtianyao1.md)] * Role: Project Advisor -### Jane Doe +### Mahathir Norrahim - + -[[github](http://github.com/johndoe)] -[[portfolio](team/johndoe.md)] +[[github](https://github.com/dabzpengu)] * Role: Team Lead -* Responsibilities: UI +* Responsibilities: UI / Quality Assurance -### Johnny Doe +### Tan Yi Jing - + -[[github](http://github.com/johndoe)] [[portfolio](team/johndoe.md)] +[[github](http://github.com/Lalelulilulela)] [[portfolio](team/lalelulilulela.md)] * Role: Developer * Responsibilities: Data -### Jean Doe +### Ashley Chua Xin Ru - + -[[github](http://github.com/johndoe)] -[[portfolio](team/johndoe.md)] +[[github](http://github.com/ashleyy2444)] +[[portfolio](team/ashley.md)] * Role: Developer * Responsibilities: Dev Ops + Threading -### James Doe +### Dexter Wong - + -[[github](http://github.com/johndoe)] -[[portfolio](team/johndoe.md)] +[[github](http://github.com/Dexter-Wong)] +[[portfolio](team/dexter.md)] * Role: Developer * Responsibilities: UI diff --git a/docs/DeveloperGuide.md b/docs/DeveloperGuide.md index 1b56bb5d31b..7b7fa1b5af9 100644 --- a/docs/DeveloperGuide.md +++ b/docs/DeveloperGuide.md @@ -70,7 +70,7 @@ The sections below give more details of each component. The **API** of this component is specified in [`Ui.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/ui/Ui.java) -![Structure of the UI Component](images/UiClassDiagram.png) +![Structure of the UI Component](images/UiClassDiagram2.png) The UI consists of a `MainWindow` that is made up of parts e.g.`CommandBox`, `ResultDisplay`, `PersonListPanel`, `StatusBarFooter` etc. All these, including the `MainWindow`, inherit from the abstract `UiPart` class which captures the commonalities between classes that represent parts of the visible GUI. @@ -155,178 +155,349 @@ Classes used by multiple components are in the `seedu.addressbook.commons` packa This section describes some noteworthy details on how certain features are implemented. -### \[Proposed\] Undo/redo feature +### Sorting Contact List + +#### Overview of SortCommand +This feature allows users to sort their addressbook based on various information, namely name, company +name, interview time, salary and priority. This feature leverages on the built-in `ObservableList` provided by JavaFX. +The sorting is done by creating classes that implements the Comparator interface. + +#### Comparator Classes +* `PersonCompanyNameComparator.java` +* `PersonInterviewTimeComparator.java` +* `PersonNameComparator.java` +* `PersonPriorityComparator.java` +* `PersonSalaryComparator.java` + +These comparators are referenced in the `SortCommandParser`. In the `SortCommandParser` each comparator will be assigned +a static integer based on the CLI Syntax from the userInput. The string is that parsed and assigned an integer from +**1 - 4** which are pre-assigned to a comparator. + +![job_difficulty_class](images/sortoverview2.png) + +**Process**: +1. User inputs `sort pri/`, triggering the `execute()` function in the `LogicManager` object. + - `pri/` is a CLI syntax added to refer to priority, an attribute added to the `Person` class. +2. `LogicManager` invokes the `parseCommand()` function in the `AddressBookParser`, which interprets the + `sort` command and creates a `SortCommandParser` object. +3. The `SortCommandParser` parses `pri/` and creates the `SortCommand` object. + - The `SortCommand` constructor accepts an **Integer**, with `SortCommandParser` pre-assigning `pri/` to 0. +4. `LogicManager` executes the command. +5. `SortCommand` calls `updateSortedPersonList()` from the `Model` object, which references the + `AddressBook` containing the `UniquePersonList`. The `UniquePersonList` sorts based on the assigned comparator. + +#### Class Structure +- `SortCommand` class: Responsible for executing the sort operation based on the specified attribute. Inherits from the + `Command` class and utilizes multiple comparator classes to sort contacts based on various attributes. + +#### Method Details +- `SortCommand(Integer info, boolean isInverse)`: Constructor that takes an integer representing the attribute index + and a boolean indicating whether to sort in reverse order. +- `execute(Model model)`: Executes the command to sort the list of contacts in the address book based on the specified + attribute. Utilises switch-case statements to select the appropriate comparator and sorts the contacts accordingly. +- `equals(Object other)`: Overrides the equals method to compare if two `SortCommand` objects are equal based on the + attribute index. + +### Job Difficulty Feature +![job_difficulty_class](images/JobDifficultyDiagram.png) + +#### Overview of Job Difficulty Feature +The job difficulty feature calculates a difficulty score for a job based on the company name and salary. It utilises +the local storage of renowned company names and their job difficulty levels. + +#### Class Structure +- `JobDifficulty` class: Responsible for computing the job difficulty score. Utilizes `CompanyName` and `Salary` + classes to fetch the necessary information. + +#### Method Details +- `JobDifficulty(CompanyName companyName, Salary salary)`: Constructor that takes a `CompanyName` object and a `Salary` + object to compute the job difficulty score. +- `getDifficulty()`: Returns the calculated job difficulty score. + +### Filtering of List Feature + +#### Overview of Filtering List Feature +This feature allows users to filter their lists according to the interview time range, tags, salary range +and programming language. + +#### Implementation +- `FilterCommand` class: An abstract class inherited by concrete classes, such as `FilterInterviewTimeCommand`, + `FilterTagCommand`, `FilterSalaryCommand`, and `FilterProgrammingLanguageCommand`. These classes filter the list + according to their respective categories. + +**Process:** +1. `FilterCommandParser` determines a concrete subclass of `FilterCommand` to return. +2. The concrete `FilterCommandClass` checks if their respective categories (interview times, tags, etc.) in the contact list contain the keyword. +3. Returns the list of contacts containing the keyword. + +### Deleting Contacts With Same Tag Feature +![delete_tag_seq_diagram](images/DeleteTagSeqDiagram.png) + +#### Overview of DeletingTagCommand +The `DeleteTagCommand` feature enables users to delete all contacts associated with a specified tag from the address +book. This feature uses the `TagContainsKeywordsPredicate` class to filter and identify contacts with the specified tag for deletion. + +**Process**: +1. `DeleteTagCommandParser` determines a concrete subclass of `DeleteTagCommand` to return. +2. The concrete `DeleteTagCommand` class retrieves the list of tags to be deleted from the + `TagContainsKeywordsPredicate`. +4. Filters the contacts in the address book based on these tags. +5. Deletes the filtered contacts from the address book model. + +#### Class Structure +- `DeleteTagCommand` class: Responsible for executing the delete operation based on the specified tag. Inherits from + the `DeleteAbstractCommand` class and utilizes the `TagContainsKeywordsPredicate` class to filter contacts. + +#### Method Details +- `DeleteTagCommand(TagContainsKeywordsPredicate tagToDelete)`: Constructor that takes a `TagContainsKeywordsPredicate` + object representing the tag to be deleted. +- `execute(Model model)`: Executes the command to delete all contacts with the specified tag from the address book. + Retrieves the list of tags to be deleted, filters the contacts based on these tags, and deletes them from the model. + +### Finding Contact Name by Company Name Feature + +#### Overview of FindCommand +The `FindCommand` feature allows users to search and list all persons in the address book whose name or company name +contains any of the specified keywords. This feature employs the `NameOrCompanyNameContainsKeywordsPredicate` class to +filter and identify contacts based on their names or company names. + +**Process**: +1. `FindCommandParser` determines a concrete subclass of `FindCommand` to return. +2. The concrete `FindCommand` class checks if the names or company names of contacts in the address book contain the +specified keywords. +3. It returns the list of contacts whose names or company names contain the keyword. + +#### Class Structure +- `FindCommand` class: Responsible for executing the find operation based on the specified keywords. Inherits from the + `Command` class and utilizes the `NameOrCompanyNameContainsKeywordsPredicate` class to filter contacts. + +#### Method Details +- `FindCommand(NameOrCompanyNameContainsKeywordsPredicate predicate)`: Constructor that takes a + `NameOrCompanyNameContainsKeywordsPredicate` object representing the keywords to search for. +- `execute(Model model)`: Executes the command to find and list all contacts whose names or company names contain the + specified keywords. Retrieves the list of contacts matching the predicate, updates the filtered contact list in the + model, and returns a command result indicating the number of contacts found. + +### Add resume feature + +The add resume feature allows user to add their own personal and professional details that would be required in a +job search. This feature has its own UI, similar to the `Help` feature. + +#### Implementation +This feature introduces a new `User` **singleton** class. The structure is as shown below. +![UserDiagram.png](images%2FUserDiagram.png) +The `ResumeWindow` controller, that controls the FXML file that displays the resume, has a reference to the +User class to retrieve the values of the user, if any. + +#### Process: +1. User inputs `resume...` into UI. +2. The `UIManager` then calls `LogicManager` +3. `AddressBookParser` is called. +4. `AddressBookParser` resets the User's attributes and re-assigns them to the current inputs +5. `AddResumeCommand` is instantiated and its `execute()` is called to return `CommandResult` +6. `ResumeWindow` is opened or reopened to show the current values. +-------------------------------------------------------------------------------------------------------------------- -#### Proposed Implementation +## **Documentation, logging, testing, configuration, dev-ops** -The proposed undo/redo mechanism is facilitated by `VersionedAddressBook`. It extends `AddressBook` with an undo/redo history, stored internally as an `addressBookStateList` and `currentStatePointer`. Additionally, it implements the following operations: +* [Documentation guide](Documentation.md) +* [Testing guide](Testing.md) +* [Logging guide](Logging.md) +* [Configuration guide](Configuration.md) +* [DevOps guide](DevOps.md) -* `VersionedAddressBook#commit()` — Saves the current address book state in its history. -* `VersionedAddressBook#undo()` — Restores the previous address book state from its history. -* `VersionedAddressBook#redo()` — Restores a previously undone address book state from its history. +-------------------------------------------------------------------------------------------------------------------- -These operations are exposed in the `Model` interface as `Model#commitAddressBook()`, `Model#undoAddressBook()` and `Model#redoAddressBook()` respectively. +## **Appendix: Requirements** -Given below is an example usage scenario and how the undo/redo mechanism behaves at each step. +### Product scope -Step 1. The user launches the application for the first time. The `VersionedAddressBook` will be initialized with the initial address book state, and the `currentStatePointer` pointing to that single address book state. +**Target user profile**: -![UndoRedoState0](images/UndoRedoState0.png) +* computing professionals looking for job openings +* has a need to manage a significant number of company contacts +* prefer desktop apps over other types +* can type fast +* prefers typing to mouse interactions +* is reasonably comfortable using CLI apps -Step 2. The user executes `delete 5` command to delete the 5th person in the address book. The `delete` command calls `Model#commitAddressBook()`, causing the modified state of the address book after the `delete 5` command executes to be saved in the `addressBookStateList`, and the `currentStatePointer` is shifted to the newly inserted address book state. +**Value proposition**: User will be able to manage and schedule interview contacts, timings and job listings from a +centralised location. Also manage contacts faster than a typical mouse/GUI driven app -![UndoRedoState1](images/UndoRedoState1.png) -Step 3. The user executes `add n/David …​` to add a new person. The `add` command also calls `Model#commitAddressBook()`, causing another modified address book state to be saved into the `addressBookStateList`. +### User stories -![UndoRedoState2](images/UndoRedoState2.png) +Priorities: High (must have) - `* * *`, Medium (nice to have) - `* *`, Low (unlikely to have) - `*` -
:information_source: **Note:** If a command fails its execution, it will not call `Model#commitAddressBook()`, so the address book state will not be saved into the `addressBookStateList`. +| Priority | As a …​ | I want to …​ | So that I can…​ | +|----------|-----------------------------|--------------------------------------------------|--------------------------------------------------------------------| +| `* * *` | Computer science job seeker | delete features | delete information where necessary | +| `* * *` | Computer science job seeker | add contact information of interviewer / company | contact the interviewer / company | +| `* * *` | Computer science job seeker | add salary range | check the salary range of the job | +| `* * *` | Computer science job seeker | add company name | check which company the job is from | +| `* * *` | Computer science job seeker | add extra info about the company | recall the extra information about each of the companies | | +| `* * *` | Computer science job seeker | add interview time | check what is the interview time | +| `* *` | Computer science job seeker | add programming language(s) related to the job | identify which programming language(s) is/are required for the job | +| `* *` | Computer science job seeker | add job responsibilities | check what are the job responsibilities for the job | +| `* *` | Computer science job seeker | categorise job postings according to industry | filter information based on the different types of industries | +| `*` | Computer science job seeker | receive notifications when I get a new interview | stay up-to-date with the interview offers and attend them | +| `*` | Computer science job seeker | track the status of my job application | follow up with any actions when necessary | +| `*` | Computer science job seeker | receive reminders for my interview timings | be reminded about the interview | -
-Step 4. The user now decides that adding the person was a mistake, and decides to undo that action by executing the `undo` command. The `undo` command will call `Model#undoAddressBook()`, which will shift the `currentStatePointer` once to the left, pointing it to the previous address book state, and restores the address book to that state. +### Use cases -![UndoRedoState3](images/UndoRedoState3.png) +(For all use cases below, the **System** is the `AddressBook` and the **Actor** is the `user`, unless specified otherwise) -
:information_source: **Note:** If the `currentStatePointer` is at index 0, pointing to the initial AddressBook state, then there are no previous AddressBook states to restore. The `undo` command uses `Model#canUndoAddressBook()` to check if this is the case. If so, it will return an error to the user rather -than attempting to perform the undo. +--- -
+**Use case: Delete a person** -The following sequence diagram shows how an undo operation goes through the `Logic` component: +**MSS** -![UndoSequenceDiagram](images/UndoSequenceDiagram-Logic.png) +1. User requests to list persons +2. AddressBook shows a list of persons +3. User requests to delete a specific person in the list +4. AddressBook deletes the person -
:information_source: **Note:** The lifeline for `UndoCommand` should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram. + Use case ends. -
+**Extensions** -Similarly, how an undo operation goes through the `Model` component is shown below: +* 2a. The list is empty. -![UndoSequenceDiagram](images/UndoSequenceDiagram-Model.png) + Use case ends. -The `redo` command does the opposite — it calls `Model#redoAddressBook()`, which shifts the `currentStatePointer` once to the right, pointing to the previously undone state, and restores the address book to that state. +* 3a. The given index is invalid. -
:information_source: **Note:** If the `currentStatePointer` is at index `addressBookStateList.size() - 1`, pointing to the latest address book state, then there are no undone AddressBook states to restore. The `redo` command uses `Model#canRedoAddressBook()` to check if this is the case. If so, it will return an error to the user rather than attempting to perform the redo. + * 3a1. AddressBook shows an error message. -
+ Use case resumes at step 2. -Step 5. The user then decides to execute the command `list`. Commands that do not modify the address book, such as `list`, will usually not call `Model#commitAddressBook()`, `Model#undoAddressBook()` or `Model#redoAddressBook()`. Thus, the `addressBookStateList` remains unchanged. -![UndoRedoState4](images/UndoRedoState4.png) +--- -Step 6. The user executes `clear`, which calls `Model#commitAddressBook()`. Since the `currentStatePointer` is not pointing at the end of the `addressBookStateList`, all address book states after the `currentStatePointer` will be purged. Reason: It no longer makes sense to redo the `add n/David …​` command. This is the behavior that most modern desktop applications follow. +**Use case: Add a Contact with Detailed Information** -![UndoRedoState5](images/UndoRedoState5.png) +**MSS** -The following activity diagram summarizes what happens when a user executes a new command: +1. The user decides to add a new contact to their address book. +2. The user inputs the add command followed by the contact's details in the format: add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]…. +3. CCBot validates the input details. +4. CCBot adds the new contact to the address book, assigning it a unique identifier within the system. +5. CCBot displays a confirmation message to the user indicating the successful addition of the new contact. - + Use case ends. -#### Design considerations: +**Extensions** -**Aspect: How undo & redo executes:** +* 3a. If the user enters invalid details (e.g., incorrect format, missing mandatory fields like name or phone number): + * 3a1. CCBot shows an error message indicating the validation failure and the correct format of the command. -* **Alternative 1 (current choice):** Saves the entire address book. - * Pros: Easy to implement. - * Cons: May have performance issues in terms of memory usage. + Use case resumes at step 2. +*a. At any time, User chooses to cancel the addition. + *a1. CCBot requests to confirm cancellation + *a2. User confirms the cancellation + Use case ends. +--- -* **Alternative 2:** Individual command knows how to undo/redo by - itself. - * Pros: Will use less memory (e.g. for `delete`, just save the person being deleted). - * Cons: We must ensure that the implementation of each individual command are correct. +**Use case: Add Salary Range to a Contact** -_{more aspects and alternatives to be added}_ +**MSS** -### \[Proposed\] Data archiving +1. The user decides to add a new contact with the salary or salary range info to their address book. +2. User inputs the 'add' command with the salary detail in the correct format. +3. CCBot validates the salary format and range. +4. CCBot adds or updates the salary information for the contact and displays a success message. -_{Explain here how the data archiving feature will be implemented}_ + Use case ends. +**Extensions** --------------------------------------------------------------------------------------------------------------------- +* 3a. If the salary detail is invalid: + * 3a1. CCBot shows an error message indicating the validation failure and the correct format of the command. -## **Documentation, logging, testing, configuration, dev-ops** + Use case resumes at step 2. -* [Documentation guide](Documentation.md) -* [Testing guide](Testing.md) -* [Logging guide](Logging.md) -* [Configuration guide](Configuration.md) -* [DevOps guide](DevOps.md) +--- --------------------------------------------------------------------------------------------------------------------- +**Use case: Add the Company’s Name to a Contact** -## **Appendix: Requirements** +**MSS** -### Product scope +1. The user decides to add a new contact with the company’s name info to their address book. +2. User inputs the 'add' command with the company’s name in the correct format. +3. CCBot validates the salary format and range. +4. CCBot adds or updates the company’s name information for the contact and displays a success message. -**Target user profile**: + Use case ends. -* has a need to manage a significant number of contacts -* prefer desktop apps over other types -* can type fast -* prefers typing to mouse interactions -* is reasonably comfortable using CLI apps +**Extensions** -**Value proposition**: manage contacts faster than a typical mouse/GUI driven app +* 3a. If the company’s name is bigger than 100 characters: + * 3a1. CCBot shows an error message indicating the validation failure and the limit characters number. + Use case resumes at step 2. -### User stories +--- -Priorities: High (must have) - `* * *`, Medium (nice to have) - `* *`, Low (unlikely to have) - `*` +**Use case: Add Programming Language to a Contact** -| Priority | As a …​ | I want to …​ | So that I can…​ | -| -------- | ------------------------------------------ | ------------------------------ | ---------------------------------------------------------------------- | -| `* * *` | new user | see usage instructions | refer to instructions when I forget how to use the App | -| `* * *` | user | add a new person | | -| `* * *` | user | delete a person | remove entries that I no longer need | -| `* * *` | user | find a person by name | locate details of persons without having to go through the entire list | -| `* *` | user | hide private contact details | minimize chance of someone else seeing them by accident | -| `*` | user with many persons in the address book | sort persons by name | locate a person easily | +**MSS** -*{More to be added}* +1. The user decides to add a new contact with the programming language info to their address book. +2. User inputs the 'add' command with the programming language detail in the correct format. +3. CCBot validates the salary format and range. +4. CCBot adds or updates the programming language information for the contact and displays a success message. -### Use cases + Use case ends. -(For all use cases below, the **System** is the `AddressBook` and the **Actor** is the `user`, unless specified otherwise) +**Extensions** -**Use case: Delete a person** +* 3a. If the programming language detail is invalid: + * 3a1. CCBot shows an error message indicating the validation failure and an error message about the format or character limit. -**MSS** + Use case resumes at step 2. -1. User requests to list persons -2. AddressBook shows a list of persons -3. User requests to delete a specific person in the list -4. AddressBook deletes the person +--- - Use case ends. +**Use case: Add extra info about the company to a Contact** -**Extensions** +**MSS** -* 2a. The list is empty. +1. The user decides to add a new contact with extra company info to their address book. +2. User inputs the 'add' command with the extra information detail in the correct format. +3. CCBot validates the input details. +4. CCBot adds or updates the extra information for the contact and displays a success message. - Use case ends. + Use case ends. -* 3a. The given index is invalid. +**Extensions** - * 3a1. AddressBook shows an error message. +* 3a. If the extra information detail is invalid: + * 3a1. CCBot shows an error message indicating the validation failure and an error message about the format or character limit. Use case resumes at step 2. -*{More to be added}* +--- + ### Non-Functional Requirements 1. Should work on any _mainstream OS_ as long as it has Java `11` or above installed. 2. Should be able to hold up to 1000 persons without a noticeable sluggishness in performance for typical usage. 3. A user with above average typing speed for regular English text (i.e. not code, not system admin commands) should be able to accomplish most of the tasks faster using commands than using the mouse. +4. User Interface should be intuitive enough for users to easily add interview dates and salaries. +5. System should be able to cater to various date formats given by users. +6. System should be able to handle a minimum of 100 contacts *{More to be added}* ### Glossary +* **Computer Science job seeker** : Student / Unemployed / Working adult seeking employment opportunities in the field of Computer Science * **Mainstream OS**: Windows, Linux, Unix, MacOS -* **Private contact detail**: A contact detail that is not meant to be shared with others +* **Private contact detail**: A contact detail that is not meant to be shared with others -------------------------------------------------------------------------------------------------------------------- @@ -380,3 +551,75 @@ testers are expected to do more *exploratory* testing. 1. _{explain how to simulate a missing/corrupted file, and the expected behavior}_ 1. _{ more test cases …​ }_ +-------------------------------------------------------------------------------------------------------------------- +## **Appendix: Planned Enhancements** + +Team size: 5 +1. **Have an error message show up when the data file is corrupted on startup**: The current address book simply starts +up with an empty list when the data in the json file is corrupted or has an invalid format. The current implementation +has a logger warning shown in the developer console, but not the app itself. We plan have an error message show up on +results display after startup of the app when the data is corrupted. + +2. **Allow `filter` command to filter the contact list based on a combination of categories**: The current `filter` +command only allows the contact list to be filtered based on one specific category such as tags, salary range etc +and this may not be useful for users with a very huge number of contacts as filtering by only one category may still +return a long list. It is also not convenient for users who want to find contacts more specifically. We plan to +allow `filter` to filter the contact list more specifically. (e.g. `filter t/TAG s/SALARY_RANGE` will return +contacts with matching `TAG` and `SALARY_RANGE` that falls within what is specified) + +3. **Show more specific error messages for our features**: The current `add`, `edit`, `resume`, +`filter`, `find`, `sort`, and `delete` command requires prefixes such as `t/` and `s/` as their parameters. This may +be confusing for users as they may input the wrong prefix for a command (e.g. input `edu/` in `add` command +instead of in the `resume`command). We plan to allow the error message to throw more specific errors: `Invalid +Command Format! edu/ is part of the resume command and not the add command.` + +4. **Improve help command functionality by making it easier for CLI users**: +Makes it so that the `help` command bring up a tab with the link to the user guide automatically highlighted. +This allows the user to simply use the keyboard shortcut `ctrl-c` to copy the link to their clipboard, instead of +having to use the mouse to click the copy button manually. This allows users using the CLI to use the app more +effectively. + +5. **Allow `add` command to add contacts with the same name**: The current version of the application restricts the +addition of contacts with identical names, assuming that each contact should have a unique name. However, it is not +uncommon for people to have the same name, and this restriction can be limiting. We propose to enhance the application +to allow multiple contacts with the same name, while ensuring that each contact is uniquely identifiable by other means +such as company name, email, or phone number. The updated application will allow the addition of contacts with the same +name. To differentiate between contacts with identical names, additional details such as company name, email, or phone +number will be used. This approach will maintain the uniqueness of each contact within the application. Adjust the +contact validation logic to check for uniqueness based on the combination of name and one or more of the additional +identifying details. If a contact with the same name and other identifying details already exists, the application will +prompt the user to confirm the addition of the new contact instead of adding it immediately. + +6. **Allow `find` command to search contact list with partial name/company name matches**: The existing `find` +command +focuses on exact matches for names or company names. This limitation can be restrictive, especially when users are +unsure about the complete name or are looking for contacts with names that have common substrings. To provide a more +flexible and intuitive search experience, we plan to enhance the find command to support partial matching. The improved +find command will enable users to search for contacts using partial names or partial company names. The command will +display a list of contacts where the entered term matches any part of the person's name or company name. (e.g. `find j` +will return contacts with names like "John" and company names like "JPMorgan".) + +7. **Store the resume such that the user can retrieve it later**. The existing implementation of the program +does not store the user's resume but only binds it to the current instance of the program. This can be limiting +as users would have to re-input their resume everytime they open the application. Even thought it is unlikely that +a resume can change as often as a contact in the addressbook. To provide a better user experience, we plan to store the +resume and enable the application to read the stored resume, if any, whenever it starts. + +8. **Allow user to edit the resume**. The existing implementation of the program, does not allow users to edit the +resume. Currently, user would have to call `resume` command and re-input the values if they want to update them. This +can be limiting as it results in unnecessary inconvenience of having to re-input all the values instead of editing just +the intended one. To provide a more intuitive experience, we plan to enhance the `edit` command such that users can +directly access the current resume and edit the values. (e.g `edit resume s/3000` will update the salary on the resume +to 3000) + +9. **Allow user to store multiple versions of the resume**. The existing implementation does not allow users to store +multiple resumes. This can be limiting as users may want to retrieve specific versions of the resume. To make the +experience more intuitive, we plan to create a separate storage system to allow users to store multiple resumes. For +instance, calling the `resume` command will create a separate resume to store, instead of overwriting the current +resume. + +10. **Ask for confirmation from users when `delete` and `clear` command is executed**: The current implementation + allows users to clear and delete contacts from the contact list. Users may accidentally delete important + contacts or misunderstand the function of the commands. Hence, we plan to show a warning to tell them that `delete` + will cause the contact to be deleted permanently and that `clear` will clear the whole list permanently and + thereafter ask for a confirmation to exceute the commands. diff --git a/docs/UserGuide.md b/docs/UserGuide.md index 7abd1984218..2115c0481bf 100644 --- a/docs/UserGuide.md +++ b/docs/UserGuide.md @@ -2,24 +2,47 @@ layout: page title: User Guide --- +### Introduction +Welcome to CCBot and thank you for choosing CCBot! +>_"Connecting lives, Connecting careers" ~CCBot_ -AddressBook Level 3 (AB3) is a **desktop app for managing contacts, optimized for use via a Command Line Interface** (CLI) while still having the benefits of a Graphical User Interface (GUI). If you can type fast, AB3 can get your contact management tasks done faster than traditional GUI apps. +CareerConnectBot (CCBot) is a **desktop app for managing job interview contacts, for you computing professionals!** +**It is optimized for use via a Command Line Interface** (CLI), while still having the **benefits of a Graphical User +Interface (GUI)**. If you can type fast, CCBot can get your contact management tasks done **faster** than traditional +GUI apps. +### Key features +With CCBot, you can keep track and manage your various job applications by: +* **Filtering and sorting your contact list flexibly** +* **Predicting the job difficulty of each of your applied job** +* **Adding additional notes about each of your company contact** +* **Adding interview times, salary, required technical skills, and priority to each of your company contact** + +_All in one app!_ + +For more details on what CCBot has to offer, check out the [Features](#features) section below! + +## About +This user guide serves as a manual with instructions for downloading, installing, setting up and using CCBot. + +Sections of the guide are split into the relevant chapters listed in the [Table Of Contents](#table-of-contents). +If you need to look for how to use a specific command, you may skip to the relevant chapter. + +## Table of Contents * Table of Contents {:toc} -------------------------------------------------------------------------------------------------------------------- - ## Quick start 1. Ensure you have Java `11` or above installed in your Computer. -1. Download the latest `addressbook.jar` from [here](https://github.com/se-edu/addressbook-level3/releases). +1. Download the latest `CCBOT.jar` from [here](https://github.com/AY2324S2-CS2103T-T08-3/tp/releases). -1. Copy the file to the folder you want to use as the _home folder_ for your AddressBook. +1. Copy the file to the folder you want to use as the _home folder_ for your CCBot. -1. Open a command terminal, `cd` into the folder you put the jar file in, and use the `java -jar addressbook.jar` command to run the application.
- A GUI similar to the below should appear in a few seconds. Note how the app contains some sample data.
+1. Open a command terminal, `cd` into the folder you put the jar file in, and use the `java -jar CCBOT.jar` command to run the application.
+ A GUI similar to the picture below should appear in a few seconds. Note how the app contains some sample data.
![Ui](images/Ui.png) 1. Type the command in the command box and press Enter to execute it. e.g. typing **`help`** and pressing Enter will open the help window.
@@ -27,11 +50,15 @@ AddressBook Level 3 (AB3) is a **desktop app for managing contacts, optimized fo * `list` : Lists all contacts. - * `add n/John Doe p/98765432 e/johnd@example.com a/John street, block 123, #01-01` : Adds a contact named `John Doe` to the Address Book. + * `add cn/Google n/John Doe p/98765432 e/johnd@example.com a/311, Clementi Ave 2, #02-25 tt/121220221400 + i/remote work s/5000 pl/Java t/friends t/referral pri/2` : + Adds a contact named `John Doe` to the Address Book. * `delete 3` : Deletes the 3rd contact shown in the current list. - * `clear` : Deletes all contacts. + * `clear` : Deletes all contacts. + * Note that this action is irreversible so use it only after trying the above commands to clear the example + contacts. * `exit` : Exits the app. @@ -63,9 +90,54 @@ AddressBook Level 3 (AB3) is a **desktop app for managing contacts, optimized fo * If you are using a PDF version of this document, be careful when copying and pasting commands that span multiple lines as space characters surrounding line-breaks may be omitted when copied over to the application. +### Priority +>From 0-4 our program has 5 priorities. The lower the value, the higher the priority. + + + +**0 - High Priority** - This is the highest priority. + + + +**1 - Medium Priority** - This is the second highest priority. + + + +**2 - Low Priority** - This is the third highest priority. + + + +**3 - Very Low Priority** - This is the fourth highest priority. + + + +**4 - Lowest Priority** - This is the lowest priority. + +-------------------------------------------------------------------------------------------------------------------- +### Date and Time +> Your CCBot uses date and time to better help you manage your interviews. This section contains all the details on how +> your CCBot interprets date and time. + +* ### Format : `ddMMyyyyHHmm` + * `dd` - refers to the day and the acceptable range is from **01** to **31** + * `MM` - refers to the month and the acceptable range is from **01** to **12** + * `yyyy` - refers to the year and accepts any **4-digit** numbers from **0001** to **9999** + * `HH` - refers to the hour and the acceptable range is from **00** to **23** + * `mm` - refers to the minutes and the acceptable range is from **00** to **59** +* ### Examples: + * `121220221400` is interpreted as **December 12, 2022 4:00 PM** + * `010100010000` is interpreted as **January 01, 0001 12:00 AM** +> #### Handling leap years +> * Your CCBot uses the ISO-8601 calendar system to handle leap years. This means that giving a leap day on a non-leap + > year returns the last valid day of that month. +> * Example: `290220090000` will give the same output as `280220090000` +> * For more information, click [here](https://docs.oracle.com/javase/8/docs/api/java/time/LocalDateTime.html). + +-------------------------------------------------------------------------------------------------------------------- + ### Viewing help : `help` -Shows a message explaning how to access the help page. +Shows a message explaining how to access the help page. ![help message](images/helpMessage.png) @@ -76,64 +148,190 @@ Format: `help` Adds a person to the address book. -Format: `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]…​` +Format: `add cn/COMPANY_NAME n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [tt/INTERVIEW_TIME] [i/INFO] + [s/SALARY] [pl/PROGRAMMING_LANGUAGE]... [t/TAG]... [pri/PRIORITY(0-4)]​`
:bulb: **Tip:** A person can have any number of tags (including 0)
+* Refer to [parameter constraints table](#parameter-constraints) for more details on acceptable inputs. + +Examples: +* `add cn/Google n/John Doe p/98765432 e/johnd@example.com a/311, Clementi Ave 2, #02-25 tt/121220221400 +i/Birthday: 12 May 2001 s/5000 pl/Java t/friends t/owesMoney pri/2` +* `add cn/ Amazon n/Betsy Crowe p/81234567 e/betsycrowe@example.com a/Newgate Prison tt/121220241200 +i/Remote work s/4000 pl/Python t/criminal pri/4` + +### Adding a resume: `resume` + +Adds a resume. + +Format: `resume cn/COMPANY_NAME n/NAME p/PHONE_NUMBER a/ADDRESS e/EMAIL s/SALARY edu/EDUCATION +[pl/PROGRAMMING_LANGUAGE]...` + +* Refer to [parameter constraints table](#parameter-constraints) for more details on acceptable inputs. + Examples: -* `add n/John Doe p/98765432 e/johnd@example.com a/John street, block 123, #01-01` -* `add n/Betsy Crowe t/friend e/betsycrowe@example.com a/Newgate Prison p/1234567 t/criminal` +* `resume cn/Google n/John Doe p/98765432 e/johnd@example.com s/3000 edu/NUS a/311, Clementi Ave 2, #02-25 pl/Java pl/C++` +* `resume cn/Apple n/Amy Birch p/87654321 e/amy@example.com s/3000 edu/NUS a/311, Clementi Ave 2, #02-25` + ### Listing all persons : `list` Shows a list of all persons in the address book. +_Note: Any extra parameters after `list` will be ignored as it displays the full address book._ + Format: `list` +
+**:information_source: Note:** Extraneous parameters for `list` will be ignored. (e.g. `list 1` is interpreted +`list`.) If you want to find a person in the contact list, refer to the `find` or `filter` feature. +
### Editing a person : `edit` Edits an existing person in the address book. -Format: `edit INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [t/TAG]…​` +Format: `edit INDEX [cn/COMPANY_NAME] [n/NAME] [p/PHONE_NUMBER] [e/EMAIL a/ADDRESS] [tt/INTERVIEW_TIME] [i/INFO] +[s/SALARY] [pl/PROGRAMMING_LANGUAGE]... [t/TAG]... [pri/PRIORITY(0-4)]` -* Edits the person at the specified `INDEX`. The index refers to the index number shown in the displayed person list. The index **must be a positive integer** 1, 2, 3, …​ +* Edits the person at the specified `INDEX`. The index refers to the index number shown in the displayed person list. * At least one of the optional fields must be provided. * Existing values will be updated to the input values. -* When editing tags, the existing tags of the person will be removed i.e adding of tags is not cumulative. -* You can remove all the person’s tags by typing `t/` without - specifying any tags after it. +* When editing tags and info, the existing tags and info of the person will be removed i.e adding of tags and info are + not cumulative. + * You can remove all the person’s tags by typing `t/` without specifying any tags after it. + * You can remove all the person’s info by typing `i/` without specifying any info after it. +* Refer to [parameter constraints table](#parameter-constraints) for more details on acceptable inputs. +* Examples: + * `edit 1 p/91234567 e/johndoe@example.com` Edits the phone number and email address of the 1st person to be `91234567` and `johndoe@example.com` respectively. + * `edit 2 n/Betsy Crower t/` Edits the name of the 2nd person to be `Betsy Crower` and clears all existing tags. -Examples: -* `edit 1 p/91234567 e/johndoe@example.com` Edits the phone number and email address of the 1st person to be `91234567` and `johndoe@example.com` respectively. -* `edit 2 n/Betsy Crower t/` Edits the name of the 2nd person to be `Betsy Crower` and clears all existing tags. -### Locating persons by name: `find` -Finds persons whose names contain any of the given keywords. +### Locating persons by name / company name: `find` -Format: `find KEYWORD [MORE_KEYWORDS]` +Finds persons whose names / company names contain any of the given keywords. + +Format: `find KEYWORD [KEYWORDS]` * The search is case-insensitive. e.g `hans` will match `Hans` * The order of the keywords does not matter. e.g. `Hans Bo` will match `Bo Hans` -* Only the name is searched. +* Only name and company name is searched. * Only full words will be matched e.g. `Han` will not match `Hans` * Persons matching at least one keyword will be returned (i.e. `OR` search). e.g. `Hans Bo` will return `Hans Gruber`, `Bo Yang` Examples: -* `find John` returns `john` and `John Doe` +* `find John` returns `John` and `John Doe` +* `find google` returns `google` and `Google` +* `find mary tiktok` returns `Mary Lee` and `Tiktok` * `find alex david` returns `Alex Yeoh`, `David Li`
- ![result for 'find alex david'](images/findAlexDavidResult.png) + ![result for 'find alex david'](images/FindExample.png) + +### Locating persons by various categories: `filter` + +Filters contact list based on tags, interview times, salary range, or programming languages. + +#### Filter by tag: `filter t/` + +Format: `filter t/TAG [TAGS]...` + +* The search is case-sensitive. +* Only the tags of each of the contacts are searched. +* Persons matching at least one tag will be returned. -### Deleting a person : `delete` +Examples: +* `filter t/manager` returns any persons with a tag, `manager` +* `filter t/manager HR` returns any persons with either of the tags, `manager` or `HR`. + +#### Filter by interview times: `filter tt/` + +Format: `filter tt/INTERVIEW_TIME_RANGE [INTERVIEW_TIME_RANGE]...` + +* Only the interview times of each of the contacts are searched. +* Persons with interview times within the range provided will be returned. +* Valid `INTERVIEW_TIME_RANGE` includes: + * `before/INTERVIEW_TIME` + * `after/INTERVIEW_TIME` + * `from/INTERVIEW_TIME-INTERVIEW_TIME` + * Refer to [parameter constraints table](#parameter-constraints) for more details on acceptable inputs for + `INTERVIEW_TIME`. + +Examples: +* `filter tt/before/010120200000 from/010120220000-010120230000` returns persons with interview times before 1 Jan + 2020, 1200am `(010120200000)` or persons with interview times within 1 Jan 2022, 1200am `(010120220000)` and 1 Jan + 2023, 1200am `(010120230000)`. +* `filter tt/after/010120220000` returns persons with interview times after 1 Jan 2022, 1200am `(010120220000)`. + +#### Filter by salaries: `filter s/` + +Format: `filter s/SALARY_RANGE [SALARY_RANGE]...` + +* Only the salaries of each of the contacts are searched. +* Persons with salaries within the range provided will be returned. +* Valid `SALARY_RANGE` includes: + * [Acceptable values for `SALARY`](#parameter-constraints) + * `>=INTEGER` + * `<=INTEGER` + + where `INTEGER` is a number inclusive of and between 0 and 2147483647. + +Examples: +* `filter s/5000` returns persons with salaries of $5000. +* `filter s/2000-5000 >=7000` returns persons with salaries that contain any number from $2000 to $5000 or with + salaries more than or equals to $7000. (e.g. persons with salaries `3000-6000`, `4000`, `8000-20900` are returned). + +#### Filter by programming language: `filter pl/` + +Format `filter pl/PROGRAMMING_LANGUAGE [PROGRAMMING_LANGUAGE]...` + +* The search is case-insensitive. +* Only the programming language of each of the contacts are searched. +* Persons matching at least one programming language will be returned. +* Refer to [parameter constraints table](#parameter-constraints) for more details on acceptable inputs for `PROGRAMMING_LANGUAGE`. + +Examples: +* `filter pl/Java` returns any persons with a programming language, `java`. +* `filter pl/python C` returns any persons with either of the programming languages, `python` or `c`. + +### Sorting the person list : `sort` + +Sorts the person list by the specified field. + +Format: `sort [rev/] KEYWORD` + +* Sorts the person list by the specified `KEYWORD`. +* The `KEYWORD` must be one of the following: `n/` (name), `cn/` (company name),`tt/` (interview time), `s/` (salary), `pri/` (priority). +* The `rev/` prefix can be added to sort in reverse order. (Optional) +* The sorting is not case-insensitive. +* The sort command defaults to alphabetical sorting for names and company names. +* The sort command defaults to chronological sorting for interview times. +* The sort command defaults to sort from largest to smallest for salary. +* The sort command defaults to sort from the highest priority to the lowest priority for priority. + +Examples: +* `sort n/` sorts the person list by name in alphabetical order. +* `sort rev/ s/` sorts the person list by salary in descending order. +* `sort tt/` sorts the person list by interview time in chronological order. +* `sort rev/ pri/` sorts the person list by priority in descending order. +* `sort cn/` sorts the person list by company name in alphabetical order. +* `sort rev/ cn/` sorts the person list by company name in reverse alphabetical order. +* `sort rev/ tt/` sorts the person list by interview time in reverse chronological order. + +### Deleting a person/persons: `delete` + +Deletes the contact list based index or tags. + +#### Delete by index: `delete` Deletes the specified person from the address book. Format: `delete INDEX` * Deletes the person at the specified `INDEX`. + * Refer to [parameter constraints table](#parameter-constraints) for more details on acceptable inputs for `INDEX`. * The index refers to the index number shown in the displayed person list. * The index **must be a positive integer** 1, 2, 3, …​ @@ -141,13 +339,30 @@ Examples: * `list` followed by `delete 2` deletes the 2nd person in the address book. * `find Betsy` followed by `delete 1` deletes the 1st person in the results of the `find` command. +#### Delete by tag: `delete t/` + +Format: `delete t/TAG [TAG]...` + +* The search is case-insensitive. +* Only contacts that have the tag are deleted. +* Persons matching the tag will be deleted. +* Refer to [parameter constraints table](#parameter-constraints) for more details on acceptable inputs for `TAG`. + +Examples: +* `delete t/manager` deletes any persons with a tag, `manager` + ### Clearing all entries : `clear` -Clears all entries from the address book. +Clears **all** entries from the address book. Format: `clear` -### Exiting the program : `exit` +
:exclamation: **Caution:** +Extraneous parameters for `clear` will be ignored. (e.g. `clear 1` is interpreted as `clear`) +If you only want to clear **some** and **not all** entries from the address book, refer to `delete` feature. +
+ +### Exiting the program : `exit` Exits the program. @@ -155,27 +370,38 @@ Format: `exit` ### Saving the data -AddressBook data are saved in the hard disk automatically after any command that changes the data. There is no need to save manually. +CCBot data are saved in the hard disk automatically after any command that changes the data. There is no need to save +manually. + +
:exclamation: **Caution:** +Resumes added will not be saved in the hard disk as of v1.4. +
+ ### Editing the data file -AddressBook data are saved automatically as a JSON file `[JAR file location]/data/addressbook.json`. Advanced users are welcome to update data directly by editing that data file. +CCBot data are saved automatically as a JSON file `[JAR file location]/data/addressbook.json`. Advanced users are +welcome to update data directly by editing that data file.
:exclamation: **Caution:** -If your changes to the data file makes its format invalid, AddressBook will discard all data and start with an empty data file at the next run. Hence, it is recommended to take a backup of the file before editing it.
-Furthermore, certain edits can cause the AddressBook to behave in unexpected ways (e.g., if a value entered is outside of the acceptable range). Therefore, edit the data file only if you are confident that you can update it correctly. +If your changes to the data file makes its format invalid, CCBot will discard all data and start with an empty data +file at the next run. Hence, it is recommended to take a backup of the file before editing it.
+Furthermore, certain edits can cause the CCBot to behave in unexpected ways (e.g., if a value entered is outside of the +acceptable range). Therefore, edit the data file only if you are confident that you can update it correctly.
-### Archiving data files `[coming in v2.0]` +### Job difficulty +The job difficulty is automatically generated by our self-developed algorithm with an evaluation factor ranging from 0% to 100%. A higher value means that the algorithm guesses the difficulty or competition of getting the job. -_Details coming soon ..._ +This value cannot be modified or added by the `edit` or `add` commands. -------------------------------------------------------------------------------------------------------------------- ## 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. +**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 CCBot home folder. -------------------------------------------------------------------------------------------------------------------- @@ -187,12 +413,51 @@ _Details coming soon ..._ ## Command summary -Action | Format, Examples ---------|------------------ -**Add** | `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]…​`
e.g., `add n/James Ho p/22224444 e/jamesho@example.com a/123, Clementi Rd, 1234665 t/friend t/colleague` -**Clear** | `clear` -**Delete** | `delete INDEX`
e.g., `delete 3` -**Edit** | `edit INDEX [n/NAME] [p/PHONE_NUMBER] [e/EMAIL] [a/ADDRESS] [t/TAG]…​`
e.g.,`edit 2 n/James Lee e/jameslee@example.com` -**Find** | `find KEYWORD [MORE_KEYWORDS]`
e.g., `find James Jake` -**List** | `list` -**Help** | `help` +| Action | Format, Examples | +|------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| **Add** | `add cn/COMPANY_NAME n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [tt/INTERVIEW_TIME] [i/INFO] [s/SALARY]
[pl/PROGRAMMING_LANGUAGE]... [t/TAG]... [pri/PRIORITY(0-4)]`
e.g., `add n/James Ho p/22224444 e/jamesho@example.com a/123, Clementi Rd, 1234665 t/friend t/colleague` | +| **Clear** | `clear` | +| **Delete** | `delete INDEX`
e.g., `delete 3`
`delete t/TAG [TAG]...`
e.g., `delete t/HR Manager` | +| **Edit** | `edit INDEX [cn/COMPANY_NAME] [n/NAME] [p/PHONE_NUMBER] [e/EMAIL a/ADDRESS] [tt/INTERVIEW_TIME]

[i/INFO] [s/SALARY] [pl/PROGRAMMING_LANGUAGE]... [t/TAG]... [pri/PRIORITY(0-4)]`
e.g.,`edit 2 n/James Lee e/jameslee@example.com` | +| **Find** | `find KEYWORD [KEYWORD]...`
e.g., `find James Jake` | +| **Filter** | `filter t/TAG [TAG]...`
e.g., `filter t/manager HR`
`filter tt/INTERVIEW_TIME_RANGE [INTERVIEW_TIME_RANGE]...`
e.g., `filter tt/before/010120200000 from/010120220000-010120230000 after/010120220000`
`filter s/SALARY_RANGE [SALARY_RANGE]...`
e.g., `filter s/2000-5000 >=7000`
`filter pl/PROGRAMMING_LANGUAGE [PROGRAMMING_LANGUAGE]...`
e.g., `filter pl/python C` | +| **List** | `list` | +| **Help** | `help` | +| **Sort** | `sort` or `sort rev/ [pri/PRIORITY] [n/NAME] [cn/COMPANY_NAME] [s/SALARY] [tt/INTERVIEW_TIME] [jd/JOB_DIFFICULTY]`
e.g., `sort pri/`
e.g., `sort rev/ tt/` | +| **Resume** | `resume cn/COMPANY_NAME n/NAME p/PHONE e/EMAIL edu/EDUCATION s/SALARY [pl/PROGRAMMING_LANGUAGE]...`
e.g., `resume cn/Google n/John Doe p/98765432 e/johnd@example.com s/3000 edu/NUS a/311, Clementi Ave 2, #02-25 pl/Java pl/C++ ` | + +## CLI Syntax Summary + +| Character | Explaination, Examples | +|-----------|----------------------------------------------| +| **n/** | Name of the contact | +| **cn/** | The company name | +| **tt/** | The time of the interview | +| **s/** | The salary | +| **a/** | The place of residence | +| **e/** | The email address | +| **i/** | Any additional info | +| **pri/** | Priority of the job | +| **edu/** | Education level | +| **pl/** | Programming languages | +| **jd/** | The job difficulty (only for `sort` command) | + +## Parameter Constraints + +| Field | Constraint | +|----------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `COMPANY_NAME` | Should be less than 100 characters, and it should not be blank. | +| `NAME` | Should only contain alphanumeric characters and spaces, and it should not be blank. | +| `PHONE_NUMBER` | Should only contain numbers, and it should be at least 3 digits long. | +| `EMAIL` | Should be a valid email address. | +| `ADDRESS` | Can take any values, and it should not be blank. | +| `SALARY` | Should only contain numbers, with range [0, 2147483647] or two pure digital numbers with '-' in between. Both digital numbers should be within the range [0, 2147483647]. | +| `PROGRAMMING_LANGUAGE` | Should be alphanumeric and may contain some special characters (+ and #), and must be less than 50 characters. | +| `TAG` | Should be a single alphanumerical word that does not contain spaces.


| +| `PRIORITY` | Should be a number between 0 and 4. | +| `INTERVIEW_TIME` | Should be in the format `ddMMyyyyHHmm`.
* `dd` - refers to the day and the acceptable range is from **01** to **31**
* `MM` - refers to the month and the acceptable range is from **01** to **12**
* `yyyy` - refers to the year and accepts any **4-digit** numbers from **0001** to **9999**
* `HH` - refers to the hour and the acceptable range is from **00** to **23**
* `mm` - refers to the minutes and the acceptable range is from **00** to **59** | +| `INFO` | Can take any values, and it should not be blank. | +| `EDUCATION` | Should only contain alphanumeric characters and spaces, and it should not be blank. | +| `INDEX` | The index **must be a positive integer** 1, 2, 3, …
_Note: CCBot does not consider 0 to be a positive integer_ | + +_Navigate back to [Table Of Contents](#table-of-contents)_ diff --git a/docs/_config.yml b/docs/_config.yml index 6bd245d8f4e..c783e49fc00 100644 --- a/docs/_config.yml +++ b/docs/_config.yml @@ -1,4 +1,4 @@ -title: "AB-3" +title: "CCBot" theme: minima header_pages: @@ -8,7 +8,7 @@ header_pages: markdown: kramdown -repository: "se-edu/addressbook-level3" +repository: "nus-cs2103-AY2324S2/tp" github_icon: "images/github-icon.png" plugins: diff --git a/docs/_sass/minima/_base.scss b/docs/_sass/minima/_base.scss index 0d3f6e80ced..f3a62867bdc 100644 --- a/docs/_sass/minima/_base.scss +++ b/docs/_sass/minima/_base.scss @@ -288,7 +288,7 @@ table { text-align: center; } .site-header:before { - content: "AB-3"; + content: "CCBot"; font-size: 32px; } } diff --git a/docs/diagrams/BetterModelClassDiagram.puml b/docs/diagrams/BetterModelClassDiagram.puml index 598474a5c82..da4c9aeb347 100644 --- a/docs/diagrams/BetterModelClassDiagram.puml +++ b/docs/diagrams/BetterModelClassDiagram.puml @@ -13,9 +13,14 @@ UniqueTagList -right-> "*" Tag UniquePersonList -right-> Person Person -up-> "*" Tag +Person -up-> "*" ProgammingLanguage Person *--> Name Person *--> Phone Person *--> Email Person *--> Address +Person *--> CompanyName +Person *--> InterviewTime +Person *-right-> Salary +Person *-right-> Info @enduml diff --git a/docs/diagrams/DeleteTagSequenceDiagram.puml b/docs/diagrams/DeleteTagSequenceDiagram.puml new file mode 100644 index 00000000000..bd5fa31d3ff --- /dev/null +++ b/docs/diagrams/DeleteTagSequenceDiagram.puml @@ -0,0 +1,70 @@ +@startuml +!include style.puml +skinparam ArrowFontStyle plain + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR +participant ":DeleteTagCommandParser" as DeleteTagCommandParser LOGIC_COLOR +participant "d:DeleteTagCommand" as DeleteTagCommand LOGIC_COLOR +participant "r:CommandResult" as CommandResult LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant "m:Model" as Model MODEL_COLOR +end box + +[-> LogicManager : execute("delete t/friend") +activate LogicManager + +LogicManager -> AddressBookParser : parseCommand("delete t/friend") +activate AddressBookParser + +create DeleteTagCommandParser +AddressBookParser -> DeleteTagCommandParser +activate DeleteTagCommandParser + +DeleteTagCommandParser --> AddressBookParser +deactivate DeleteTagCommandParser + +AddressBookParser -> DeleteTagCommandParser : parse("friend") +activate DeleteTagCommandParser + +create DeleteTagCommand +DeleteTagCommandParser -> DeleteTagCommand +activate DeleteTagCommand + +DeleteTagCommand --> DeleteTagCommandParser : +deactivate DeleteTagCommand + +DeleteTagCommandParser --> AddressBookParser : d +deactivate DeleteTagCommandParser +'Hidden arrow to position the destroy marker below the end of the activation bar. +DeleteTagCommandParser -[hidden]-> AddressBookParser +destroy DeleteTagCommandParser + +AddressBookParser --> LogicManager : d +deactivate AddressBookParser + +LogicManager -> DeleteTagCommand : execute(m) +activate DeleteTagCommand + +DeleteTagCommand -> Model : deletePerson(tag) +activate Model + +Model --> DeleteTagCommand +deactivate Model + +create CommandResult +DeleteTagCommand -> CommandResult +activate CommandResult + +CommandResult --> DeleteTagCommand +deactivate CommandResult + +DeleteTagCommand --> LogicManager : r +deactivate DeleteTagCommand + +[<--LogicManager +deactivate LogicManager +@enduml diff --git a/docs/diagrams/JobDifficultyDiagram.puml b/docs/diagrams/JobDifficultyDiagram.puml new file mode 100644 index 00000000000..c4925d751ad --- /dev/null +++ b/docs/diagrams/JobDifficultyDiagram.puml @@ -0,0 +1,17 @@ +@startuml +class JobDifficulty { + -difficulty : int + -- + +JobDifficulty(CompanyName, Salary) : void + +getDifficulty() : int + +toString() : String +} +class CompanyName { + -companyName : String +} +class Salary { + -salary : int +} +JobDifficulty *-- CompanyName +JobDifficulty *-- Salary +@enduml diff --git a/docs/diagrams/LogicClassDiagram.puml b/docs/diagrams/LogicClassDiagram.puml index 58b4f602ce6..c66093078dc 100644 --- a/docs/diagrams/LogicClassDiagram.puml +++ b/docs/diagrams/LogicClassDiagram.puml @@ -11,7 +11,15 @@ package "Parser Classes" as ParserClasses{ Class XYZCommand Class CommandResult Class "{abstract}\nCommand" as Command - +Class AddCommand +Class ClearCommand +Class DeleteCommand +Class EditCommand +Class ExitCommand +Class FindCommand +Class HelpCommand +Class ListCommand +Class SortCommand Class "<>\nLogic" as Logic Class LogicManager @@ -31,7 +39,15 @@ LogicManager .right.|> Logic LogicManager -right->"1" ParserClasses ParserClasses ..> XYZCommand : <> -XYZCommand -up-|> Command +AddCommand -up-|> Command +ClearCommand -up-|> Command +DeleteCommand -up-|> Command +EditCommand -up-|> Command +ExitCommand -up-|> Command +FindCommand -up-|> Command +HelpCommand -up-|> Command +ListCommand -up-|> Command +SortCommand -up-|> Command LogicManager .left.> Command : <> LogicManager --> Model diff --git a/docs/diagrams/ModelClassDiagram.puml b/docs/diagrams/ModelClassDiagram.puml index 0de5673070d..e9d3dafc3e3 100644 --- a/docs/diagrams/ModelClassDiagram.puml +++ b/docs/diagrams/ModelClassDiagram.puml @@ -15,10 +15,17 @@ Class UserPrefs Class UniquePersonList Class Person Class Address +Class CompanyName Class Email +Class Info +Class InterviewTime Class Name Class Phone +Class Salary Class Tag +Class ProgammingLanguage +Class JobDifficulty +Class Priority Class I #FFFFFF } @@ -37,11 +44,18 @@ UserPrefs .up.|> ReadOnlyUserPrefs AddressBook *--> "1" UniquePersonList UniquePersonList --> "~* all" Person -Person *--> Name +Person *-left-> Name Person *--> Phone Person *--> Email Person *--> Address +Person *--> Info +Person *--> CompanyName +Person *--> Salary +Person *--> InterviewTime +Person *--> JobDifficulty +Person *--> Priority Person *--> "*" Tag +Person *-right-> "*" ProgammingLanguage Person -[hidden]up--> I UniquePersonList -[hidden]right-> I diff --git a/docs/diagrams/SortComparatorClass.puml b/docs/diagrams/SortComparatorClass.puml new file mode 100644 index 00000000000..2785292376c --- /dev/null +++ b/docs/diagrams/SortComparatorClass.puml @@ -0,0 +1,14 @@ +@startuml +!include style.puml +skinparam arrowThickness 1.1 +skinparam arrowColor UI_COLOR_T4 +skinparam classBackgroundColor UI_COLOR + +Comparator <|-- PersonCompanyNameComparator +Comparator <|-- NameContainsKeywordsPredicate +Comparator <|-- PersonInterviewTimeComparator +Comparator <|-up- PersonNameComparator +Comparator <|-up- PersonPriorityComparator +Comparator <|-up- PersonSalaryComparator + +@enduml diff --git a/docs/diagrams/SortSequenceDiagram.puml b/docs/diagrams/SortSequenceDiagram.puml new file mode 100644 index 00000000000..4dbf150b6f0 --- /dev/null +++ b/docs/diagrams/SortSequenceDiagram.puml @@ -0,0 +1,46 @@ +@startuml +!include style.puml +skinparam ArrowFontStyle plain + +Actor User as user USER_COLOR +Participant ":UI" as ui UI_COLOR +Participant ":Logic" as logic LOGIC_COLOR +Participant ":Model" as model MODEL_COLOR +Participant "AddressBook" as addressBook MODEL_COLOR +Participant ":Storage" as storage STORAGE_COLOR + +user -[USER_COLOR]> ui : "sort pri/" +activate ui UI_COLOR + +ui -[UI_COLOR]> logic : execute("sort pri/") +activate logic LOGIC_COLOR + +logic -[LOGIC_COLOR]> model : updateSortedPersonList(p) +activate model MODEL_COLOR + +model -[MODEL_COLOR]> addressBook : sortPersons(info) +activate addressBook MODEL_COLOR + +addressBook -[MODEL_COLOR]> model +deactivate addressBook + +model -[MODEL_COLOR]-> logic +deactivate model + +logic -[LOGIC_COLOR]> storage : saveAddressBook(addressBook) +activate storage STORAGE_COLOR + +storage -[STORAGE_COLOR]> storage : Save to file +activate storage STORAGE_COLOR_T1 +storage --[STORAGE_COLOR]> storage +deactivate storage + +storage --[STORAGE_COLOR]> logic +deactivate storage + +logic --[LOGIC_COLOR]> ui +deactivate logic + +ui--[UI_COLOR]> user +deactivate ui +@enduml diff --git a/docs/diagrams/SortSqDiagram.puml b/docs/diagrams/SortSqDiagram.puml new file mode 100644 index 00000000000..bf9e13a1389 --- /dev/null +++ b/docs/diagrams/SortSqDiagram.puml @@ -0,0 +1,77 @@ +@startuml +!include style.puml +skinparam ArrowFontStyle plain + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR +participant ":SortCommandParser" as SortCommandParser LOGIC_COLOR +participant "s:SortCommand" as SortCommand LOGIC_COLOR +participant "r:CommandResult" as CommandResult LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant "m:Model" as Model MODEL_COLOR +Participant "AddressBook" as addressBook MODEL_COLOR +end box + +[-> LogicManager : execute("sort pri/") +activate LogicManager + +LogicManager -> AddressBookParser : parseCommand("sort pri/") +activate AddressBookParser + +create SortCommandParser +AddressBookParser -> SortCommandParser +activate SortCommandParser + +SortCommandParser --> AddressBookParser +deactivate SortCommandParser + +AddressBookParser -> SortCommandParser : parse("pri/") +activate SortCommandParser + +create SortCommand +SortCommandParser -> SortCommand +activate SortCommand + +SortCommand --> SortCommandParser : +deactivate SortCommand + +SortCommandParser --> AddressBookParser : s +deactivate SortCommandParser +'Hidden arrow to position the destroy marker below the end of the activation bar. +SortCommandParser -[hidden]-> AddressBookParser +destroy SortCommandParser + +AddressBookParser --> LogicManager : s +deactivate AddressBookParser + +LogicManager -> SortCommand : execute(m) +activate SortCommand + +SortCommand -> Model : updateSortedPersonList(p) +activate Model + +Model -> addressBook : sortPersons(info) +activate addressBook + +addressBook --> Model +deactivate addressBook + +Model --> SortCommand +deactivate Model + +create CommandResult +SortCommand -> CommandResult +activate CommandResult + +CommandResult --> SortCommand +deactivate CommandResult + +SortCommand --> LogicManager : r +deactivate SortCommand + +[<--LogicManager +deactivate LogicManager +@enduml diff --git a/docs/diagrams/UiClassDiagram.puml b/docs/diagrams/UiClassDiagram.puml index 95473d5aa19..e1ecb2d0853 100644 --- a/docs/diagrams/UiClassDiagram.puml +++ b/docs/diagrams/UiClassDiagram.puml @@ -15,6 +15,7 @@ Class PersonListPanel Class PersonCard Class StatusBarFooter Class CommandBox +Class ResumeWindow } package Model <> { @@ -34,6 +35,7 @@ MainWindow *-down-> "1" CommandBox MainWindow *-down-> "1" ResultDisplay MainWindow *-down-> "1" PersonListPanel MainWindow *-down-> "1" StatusBarFooter +MainWindow *-down-> "0..1" ResumeWindow MainWindow --> "0..1" HelpWindow PersonListPanel -down-> "*" PersonCard @@ -46,6 +48,7 @@ PersonListPanel --|> UiPart PersonCard --|> UiPart StatusBarFooter --|> UiPart HelpWindow --|> UiPart +ResumeWindow -left-|> UiPart PersonCard ..> Model UiManager -right-> Logic diff --git a/docs/diagrams/UserDiagram.puml b/docs/diagrams/UserDiagram.puml new file mode 100644 index 00000000000..bcb91f9c6b8 --- /dev/null +++ b/docs/diagrams/UserDiagram.puml @@ -0,0 +1,43 @@ +@startuml +'https://plantuml.com/class-diagram + +class User + +class Salary { + -salary : int +} + +class CompanyName { + -companyName : String +} + +class Name { + -name : String +} + +class Address { + -address : String +} + +class Education { + -education : String +} + +class Email { + -email : String +} +class Phone { + -phone : String +} +class ProgrammingLanguage { +} + +User *-- "1" CompanyName +User *-- "1" Salary +User *-- "1" Name +User *-- "1" Address +User *-- "1" Education +User *-- "1" Email +User *-- "1" Phone +User *-- "0..1" ProgrammingLanguage +@enduml diff --git a/docs/images/BetterModelClassDiagram.png b/docs/images/BetterModelClassDiagram.png index 02a42e35e76..8cd4e02eb0a 100644 Binary files a/docs/images/BetterModelClassDiagram.png and b/docs/images/BetterModelClassDiagram.png differ diff --git a/docs/images/DeleteTagSeqDiagram.png b/docs/images/DeleteTagSeqDiagram.png new file mode 100644 index 00000000000..37bd9bfc0eb Binary files /dev/null and b/docs/images/DeleteTagSeqDiagram.png differ diff --git a/docs/images/FindExample.png b/docs/images/FindExample.png new file mode 100644 index 00000000000..c18308c2033 Binary files /dev/null and b/docs/images/FindExample.png differ diff --git a/docs/images/JobDifficultyDiagram.png b/docs/images/JobDifficultyDiagram.png new file mode 100644 index 00000000000..d3bb4d53d71 Binary files /dev/null and b/docs/images/JobDifficultyDiagram.png differ diff --git a/docs/images/ModelClassDiagram.png b/docs/images/ModelClassDiagram.png index a19fb1b4ac8..84d98f6e994 100644 Binary files a/docs/images/ModelClassDiagram.png and b/docs/images/ModelClassDiagram.png differ diff --git a/docs/images/SortSequenceDiagram.png b/docs/images/SortSequenceDiagram.png new file mode 100644 index 00000000000..3e8e9469ec4 Binary files /dev/null and b/docs/images/SortSequenceDiagram.png differ diff --git a/docs/images/Ui.png b/docs/images/Ui.png index 5bd77847aa2..c4b20df3273 100644 Binary files a/docs/images/Ui.png and b/docs/images/Ui.png differ diff --git a/docs/images/UiClassDiagram2.png b/docs/images/UiClassDiagram2.png new file mode 100644 index 00000000000..2f9219439fb Binary files /dev/null and b/docs/images/UiClassDiagram2.png differ diff --git a/docs/images/UserDiagram.png b/docs/images/UserDiagram.png new file mode 100644 index 00000000000..284ab836f9d Binary files /dev/null and b/docs/images/UserDiagram.png differ diff --git a/docs/images/ashley1.png b/docs/images/ashley1.png new file mode 100644 index 00000000000..0fb6200ff26 Binary files /dev/null and b/docs/images/ashley1.png differ diff --git a/docs/images/johndoe.png b/docs/images/ashleyy2444.png similarity index 100% rename from docs/images/johndoe.png rename to docs/images/ashleyy2444.png diff --git a/docs/images/dabzpengu.png b/docs/images/dabzpengu.png new file mode 100644 index 00000000000..ed923a840c0 Binary files /dev/null and b/docs/images/dabzpengu.png differ diff --git a/docs/images/dexter-wong.png b/docs/images/dexter-wong.png new file mode 100644 index 00000000000..0f926e7caa2 Binary files /dev/null and b/docs/images/dexter-wong.png differ diff --git a/docs/images/helpMessage.png b/docs/images/helpMessage.png index b1f70470137..0388cd13802 100644 Binary files a/docs/images/helpMessage.png and b/docs/images/helpMessage.png differ diff --git a/docs/images/lalelulilulela.png b/docs/images/lalelulilulela.png new file mode 100644 index 00000000000..1ce7ce16dc8 Binary files /dev/null and b/docs/images/lalelulilulela.png differ diff --git a/docs/images/sortoverview2.png b/docs/images/sortoverview2.png new file mode 100644 index 00000000000..780d67e5cb3 Binary files /dev/null and b/docs/images/sortoverview2.png differ diff --git a/docs/images/zhangtianyao1.png b/docs/images/zhangtianyao1.png new file mode 100644 index 00000000000..69c5fd87b0f Binary files /dev/null and b/docs/images/zhangtianyao1.png differ diff --git a/docs/index.md b/docs/index.md index 7601dbaad0d..b6ffd2b8a61 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,17 +1,18 @@ --- layout: page -title: AddressBook Level-3 +title: CCBOT --- [![CI Status](https://github.com/se-edu/addressbook-level3/workflows/Java%20CI/badge.svg)](https://github.com/se-edu/addressbook-level3/actions) -[![codecov](https://codecov.io/gh/se-edu/addressbook-level3/branch/master/graph/badge.svg)](https://codecov.io/gh/se-edu/addressbook-level3) +[![codecov](https://codecov.io/gh/AY2324S2-CS2103T-T08-3/tp/graph/badge.svg?token=0RG4SRDBHW)](https://codecov.io/gh/AY2324S2-CS2103T-T08-3/tp) + ![Ui](images/Ui.png) -**AddressBook is a desktop application for managing your contact details.** While it has a GUI, most of the user interactions happen using a CLI (Command Line Interface). +**CCBOT is a desktop application for managing your job application details.** While it has a GUI, most of the user interactions happen using a CLI (Command Line Interface). -* If you are interested in using AddressBook, head over to the [_Quick Start_ section of the **User Guide**](UserGuide.html#quick-start). -* If you are interested about developing AddressBook, the [**Developer Guide**](DeveloperGuide.html) is a good place to start. +* If you are interested in using CCBOT, head over to the [_Quick Start_ section of the **User Guide**](UserGuide.html#quick-start). +* If you are interested about developing CCBOT, the [**Developer Guide**](DeveloperGuide.html) is a good place to start. **Acknowledgements** diff --git a/docs/team/ashley.md b/docs/team/ashley.md new file mode 100644 index 00000000000..3b2c22395d4 --- /dev/null +++ b/docs/team/ashley.md @@ -0,0 +1,54 @@ +--- +layout: page +title: Ashley’s Project Portfolio Page +--- + +### Project: CCBOT + +CCBOT is a desktop application for managing your job application details. While it has a GUI, most of the user interactions happen using a CLI (Command Line Interface). + +Given below are my contributions to the project. + +* **New Feature**: Added an additional component, programming language, which is saved in the addressbook. + * What it does: Allow users to input programming languages that are being used in the job that they applied for. + * Justification: This feature improves the product significantly because a user can recognise which specific technical skill is required for that particular role. + * Highlights: This enhancement affects existing commands and commands to be added in future. It required an in-depth analysis of design alternatives. The implementation too was challenging as it required changes to existing commands. + + +* **New Feature**: Allows users to filter by company name. + * What it does: Allows users to find a contact based on the company name. + * Justification: This feature improves the product significantly because users can easily find the person based on the company that they applied for. + * Highlights: This enhancement affects existing commands and commands to be added in future. It required an in-depth analysis of design alternatives. The implementation was not too challenging. It required changes to existing commands. + + +* **New Feature**: Added the ability to filter by programming language. + * What it does: Allow users to filter contacts based on programming language. + * Justification: This feature improves the product significantly because a user can easily find contacts of the jobs that they applied for in which require the specific programming language. + * Highlights: This enhancement affects existing commands and commands to be added in future. It required an in-depth analysis of design alternatives. The implementation too was challenging as it required changes to existing commands. + + +* **New Feature**: Added the ability to delete by tag command. + * What it does: Allows users to delete contacts based on the tags given to each contact. + * Justification: This feature improves the product significantly because users can easily delete the contacts of everyone who has the same tag. + * Highlights: This enhancement affects existing commands and commands to be added in future. It required an in-depth analysis of design alternatives. The implementation too was challenging as it required changes to existing commands. + + +* **Project management**: + * Managed releases v1.2 - v1.4 (4 releases) on GitHub + + +* **Enhancements to existing features**: + * Add find by company name in addition to find by name (Pull Request [\#70]()) + * Add programming language to person card (Pull Request [\#34]()) + + +* **Documentation**: + * User Guide: + * Added documentation for the features find (filter by name and company name), filter programming language and delete by tag (Pull Request [\#89]()) + * Developer Guide: + * Added implementation details for delete by tag feature. (Pull Request [\#191]()) + * Added implementation details for find feature. (Pull Request [\#191]()) + * Added planned enhancement 6. (Pull Request [\#196]()) + * Added planned enhancement 7. (Pull Request [\#193]()) + * Update UML diagram. (Pull Request [\#56]()) + * Add Sequence diagram for delete tag command. (Pull Request [\#181]()) diff --git a/docs/team/dexter.md b/docs/team/dexter.md new file mode 100644 index 00000000000..773a07794e2 --- /dev/null +++ b/docs/team/dexter.md @@ -0,0 +1,46 @@ +--- +layout: page +title: John Doe's Project Portfolio Page +--- + +### Project: AddressBook Level 3 + +AddressBook - Level 3 is a desktop address book application used for teaching Software Engineering principles. The user interacts with it using a CLI, and it has a GUI created with JavaFX. It is written in Java, and has about 10 kLoC. + +Given below are my contributions to the project. + +* **New Feature**: Added the ability to undo/redo previous commands. + * What it does: allows the user to undo all previous commands one at a time. Preceding undo commands can be reversed by using the redo command. + * Justification: This feature improves the product significantly because a user can make mistakes in commands and the app should provide a convenient way to rectify them. + * Highlights: This enhancement affects existing commands and commands to be added in future. It required an in-depth analysis of design alternatives. The implementation too was challenging as it required changes to existing commands. + * Credits: *{mention here if you reused any code/ideas from elsewhere or if a third-party library is heavily used in the feature so that a reader can make a more accurate judgement of how much effort went into the feature}* + +* **New Feature**: Added a history command that allows the user to navigate to previous commands using up/down keys. + +* **Code contributed**: [RepoSense link]() + +* **Project management**: + * Managed releases `v1.3` - `v1.5rc` (3 releases) on GitHub + +* **Enhancements to existing features**: + * Updated the GUI color scheme (Pull requests [\#33](), [\#34]()) + * Wrote additional tests for existing features to increase coverage from 88% to 92% (Pull requests [\#36](), [\#38]()) + +* **Documentation**: + * User Guide: + * Added documentation for the features `delete` and `find` [\#72]() + * Did cosmetic tweaks to existing documentation of features `clear`, `exit`: [\#74]() + * Developer Guide: + * Added implementation details of the `delete` feature. + +* **Community**: + * PRs reviewed (with non-trivial review comments): [\#12](), [\#32](), [\#19](), [\#42]() + * Contributed to forum discussions (examples: [1](), [2](), [3](), [4]()) + * Reported bugs and suggestions for other teams in the class (examples: [1](), [2](), [3]()) + * Some parts of the history feature I added was adopted by several other class mates ([1](), [2]()) + +* **Tools**: + * Integrated a third party library (Natty) to the project ([\#42]()) + * Integrated a new Github plugin (CircleCI) to the team repo + +* _{you can add/remove categories in the list above}_ diff --git a/docs/team/lalelulilulela.md b/docs/team/lalelulilulela.md new file mode 100644 index 00000000000..e69de29bb2d diff --git a/docs/team/zhangtianyao1.md b/docs/team/zhangtianyao1.md new file mode 100644 index 00000000000..1b7400ac188 --- /dev/null +++ b/docs/team/zhangtianyao1.md @@ -0,0 +1,40 @@ +--- +layout: page +title: Zhang Tianyao's Project Portfolio Page +--- + +### Project: CCBOT + +CCBOT is a desktop application for managing your job application details.** While it has a GUI, most of the user interactions happen using a CLI (Command Line Interface). + +Given below are my contributions to the project. + +* **New Feature**: Added the ability to sort person list. + * What it does: allows the user to sort the person list by name, company name, job difficulty, priority, salary, and interview date. + * Justification: This feature improves the product significantly because a user can easily find the person he/she wants to check. + * Highlights: This enhancement affects existing commands and commands to be added in future. It required an in-depth analysis of design alternatives. The implementation too was challenging as it required changes to existing commands. + +* **New Feature**: Added the job difficulty model. + * What it does: allows the program auto calculate the job difficulty based on the job description. + * Justification: This feature improves the product significantly because a user can easily identify the job difficulty. + * Highlights: This enhancement affects existing commands and commands to be added in future. It required an in-depth analysis of design alternatives. The implementation too was challenging as it required changes to existing commands. + +* **Project management**: + * Managed releases `v1.2` - `v1.4` (4 releases) on GitHub + +* **Enhancements to existing features**: + * Add salary range to the person card (Pull requests [\#20]()) + * Fix add command examples (Pull requests [\#35]()) + * Add job difficulty comparator (Pull requests [\#69]()) + * Add priority to person card (Pull requests [\#39]()) + +* **Documentation**: + * User Guide: + * Added documentation for the features `sort` and `job difficulty` [\#93]() [\#86]() + * Added documentation for the constraints of the `add`, `edit` and `add resume` command [\#169]() + * Developer Guide: + * Added implementation details of the `sort` feature. + * Added implementation details of the `job difficulty` feature.[\#54]() + * Update UML diagrams. [\#37]() + * Add UML diagrams for `sort` command. [\#43]() + diff --git a/src/main/java/seedu/address/commons/core/LogsCenter.java b/src/main/java/seedu/address/commons/core/LogsCenter.java index 8cf8e15a0f0..fc3965de91b 100644 --- a/src/main/java/seedu/address/commons/core/LogsCenter.java +++ b/src/main/java/seedu/address/commons/core/LogsCenter.java @@ -20,7 +20,7 @@ public class LogsCenter { private static final int MAX_FILE_COUNT = 5; private static final int MAX_FILE_SIZE_IN_BYTES = (int) (Math.pow(2, 20) * 5); // 5MB - private static final String LOG_FILE = "addressbook.log"; + private static final String LOG_FILE = "ccbot.log"; private static final Logger logger; // logger for this class private static Logger baseLogger; // to be used as the parent of all other loggers created by this class. private static Level currentLogLevel = Level.INFO; @@ -79,7 +79,7 @@ private static void removeHandlers(Logger logger) { * Sets it as the {@code baseLogger}, to be used as the parent logger of all other loggers. */ private static void setBaseLogger() { - baseLogger = Logger.getLogger("ab3"); + baseLogger = Logger.getLogger("ccbot"); baseLogger.setUseParentHandlers(false); removeHandlers(baseLogger); diff --git a/src/main/java/seedu/address/logic/Messages.java b/src/main/java/seedu/address/logic/Messages.java index ecd32c31b53..5813201b42c 100644 --- a/src/main/java/seedu/address/logic/Messages.java +++ b/src/main/java/seedu/address/logic/Messages.java @@ -18,6 +18,7 @@ public class Messages { public static final String MESSAGE_PERSONS_LISTED_OVERVIEW = "%1$d persons listed!"; public static final String MESSAGE_DUPLICATE_FIELDS = "Multiple values specified for the following single-valued field(s): "; + public static final String MESSAGE_INVALID_SORT_COMMAND_INDEX = "Invalid sort command index"; /** * Returns an error message indicating the duplicate prefixes. @@ -36,15 +37,29 @@ public static String getErrorMessageForDuplicatePrefixes(Prefix... duplicatePref */ public static String format(Person person) { final StringBuilder builder = new StringBuilder(); - builder.append(person.getName()) + builder.append("Company name: ") + .append(person.getCompanyName()) + .append("; Name: ") + .append(person.getName()) .append("; Phone: ") .append(person.getPhone()) .append("; Email: ") .append(person.getEmail()) .append("; Address: ") .append(person.getAddress()) + .append("; Interview Date and Time: ") + .append(person.getDateTime()) + .append("; Salary: ") + .append(person.getSalary()) + .append("$") + .append("; Info: ") + .append(person.getInfo()) .append("; Tags: "); person.getTags().forEach(builder::append); + builder.append("; Programming Languages: "); + person.getProgrammingLanguages().forEach(builder::append); + builder.append("; Priority: ") + .append(person.getPriority()); return builder.toString(); } diff --git a/src/main/java/seedu/address/logic/commands/AddCommand.java b/src/main/java/seedu/address/logic/commands/AddCommand.java index 5d7185a9680..6904b43707d 100644 --- a/src/main/java/seedu/address/logic/commands/AddCommand.java +++ b/src/main/java/seedu/address/logic/commands/AddCommand.java @@ -2,9 +2,15 @@ import static java.util.Objects.requireNonNull; import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_COMPANY_NAME; import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; +import static seedu.address.logic.parser.CliSyntax.PREFIX_INFO; +import static seedu.address.logic.parser.CliSyntax.PREFIX_INTERVIEWTIME; import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PRIORITY; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PROGRAMMING_LANGUAGE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_SALARY; import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; import seedu.address.commons.util.ToStringBuilder; @@ -21,23 +27,34 @@ public class AddCommand extends Command { public static final String COMMAND_WORD = "add"; public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a person to the address book. " - + "Parameters: " - + PREFIX_NAME + "NAME " - + PREFIX_PHONE + "PHONE " - + PREFIX_EMAIL + "EMAIL " - + PREFIX_ADDRESS + "ADDRESS " + + "Parameters: \n" + + PREFIX_COMPANY_NAME + "COMPANY NAME \n" + + PREFIX_NAME + "NAME \n" + + PREFIX_PHONE + "PHONE \n" + + PREFIX_EMAIL + "EMAIL \n" + + PREFIX_ADDRESS + "ADDRESS \n" + + PREFIX_INTERVIEWTIME + "INTERVIEW-TIME \n" + + PREFIX_INFO + "INFO \n" + + "[" + PREFIX_SALARY + "SALARY] \n" + + "[" + PREFIX_PROGRAMMING_LANGUAGE + "PROGRAMMING-LANGUAGE]...\n" + "[" + PREFIX_TAG + "TAG]...\n" + + "[" + PREFIX_PRIORITY + "PRIORITY(0-4)]\n" + "Example: " + COMMAND_WORD + " " + + PREFIX_COMPANY_NAME + "Google " + PREFIX_NAME + "John Doe " + PREFIX_PHONE + "98765432 " + PREFIX_EMAIL + "johnd@example.com " + PREFIX_ADDRESS + "311, Clementi Ave 2, #02-25 " + + PREFIX_INTERVIEWTIME + "121220221400 " + + PREFIX_INFO + "Birthday: 12 May 2001 " + + PREFIX_SALARY + "5000 " + + PREFIX_PROGRAMMING_LANGUAGE + "Java " + PREFIX_TAG + "friends " - + PREFIX_TAG + "owesMoney"; + + PREFIX_TAG + "owesMoney " + + PREFIX_PRIORITY + "2"; public static final String MESSAGE_SUCCESS = "New person added: %1$s"; public static final String MESSAGE_DUPLICATE_PERSON = "This person already exists in the address book"; - private final Person toAdd; /** diff --git a/src/main/java/seedu/address/logic/commands/AddResumeCommand.java b/src/main/java/seedu/address/logic/commands/AddResumeCommand.java new file mode 100644 index 00000000000..44643d92f6d --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/AddResumeCommand.java @@ -0,0 +1,58 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_COMPANY_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_EDUCATION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PROGRAMMING_LANGUAGE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_SALARY; + +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.person.user.User; + +/** + * Allows user to add their resume + */ +public class AddResumeCommand extends Command { + + public static final String COMMAND_WORD = "resume"; + public static final String MESSAGE_SUCCESS = "Resume added"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds your resume to the address book. " + + "Parameters: \n" + + PREFIX_COMPANY_NAME + "COMPANY NAME \n" + + PREFIX_NAME + "NAME \n" + + PREFIX_PHONE + "PHONE \n" + + PREFIX_EMAIL + "EMAIL \n" + + PREFIX_ADDRESS + "ADDRESS \n" + + PREFIX_SALARY + "SALARY \n" + + "[" + PREFIX_PROGRAMMING_LANGUAGE + "PROGRAMMING-LANGUAGE]...\n" + + "Example: " + COMMAND_WORD + " " + + PREFIX_COMPANY_NAME + "Google " + + PREFIX_NAME + "John Doe " + + PREFIX_PHONE + "98765432 " + + PREFIX_EMAIL + "johnd@example.com " + + PREFIX_SALARY + "3000 " + + PREFIX_EDUCATION + "NUS " + + PREFIX_ADDRESS + "311, Clementi Ave 2, #02-25 " + + PREFIX_PROGRAMMING_LANGUAGE + "Java " + + PREFIX_PROGRAMMING_LANGUAGE + "C++ "; + private final User user; + + /** + * Creates an AddCommand to add the specified {@code User} + */ + public AddResumeCommand(User user) { + requireNonNull(user); + this.user = user; + } + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + return new CommandResult(MESSAGE_SUCCESS, false, true, false); + } +} diff --git a/src/main/java/seedu/address/logic/commands/CommandResult.java b/src/main/java/seedu/address/logic/commands/CommandResult.java index 249b6072d0d..80d2b78d6c8 100644 --- a/src/main/java/seedu/address/logic/commands/CommandResult.java +++ b/src/main/java/seedu/address/logic/commands/CommandResult.java @@ -16,15 +16,18 @@ public class CommandResult { /** Help information should be shown to the user. */ private final boolean showHelp; + private final boolean showResume; + /** The application should exit. */ private final boolean exit; /** * Constructs a {@code CommandResult} with the specified fields. */ - public CommandResult(String feedbackToUser, boolean showHelp, boolean exit) { + public CommandResult(String feedbackToUser, boolean showHelp, boolean showResume, boolean exit) { this.feedbackToUser = requireNonNull(feedbackToUser); this.showHelp = showHelp; + this.showResume = showResume; this.exit = exit; } @@ -33,7 +36,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); } public String getFeedbackToUser() { @@ -44,6 +47,10 @@ public boolean isShowHelp() { return showHelp; } + public boolean isShowResume() { + return showResume; + } + public boolean isExit() { return exit; } @@ -62,12 +69,13 @@ public boolean equals(Object other) { CommandResult otherCommandResult = (CommandResult) other; return feedbackToUser.equals(otherCommandResult.feedbackToUser) && showHelp == otherCommandResult.showHelp + && showResume == otherCommandResult.showResume && exit == otherCommandResult.exit; } @Override public int hashCode() { - return Objects.hash(feedbackToUser, showHelp, exit); + return Objects.hash(feedbackToUser, showHelp, showResume, exit); } @Override @@ -75,6 +83,7 @@ public String toString() { return new ToStringBuilder(this) .add("feedbackToUser", feedbackToUser) .add("showHelp", showHelp) + .add("showResume", showResume) .add("exit", exit) .toString(); } diff --git a/src/main/java/seedu/address/logic/commands/DeleteAbstractCommand.java b/src/main/java/seedu/address/logic/commands/DeleteAbstractCommand.java new file mode 100644 index 00000000000..d28a7ca0afc --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/DeleteAbstractCommand.java @@ -0,0 +1,22 @@ +package seedu.address.logic.commands; + +import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; + +/** + * Abstract class representing a DeleteCommand for deleting contacts or tags in an address book. + * Subclasses will implement specific delete commands for different types of deletions. + */ +public abstract class DeleteAbstractCommand extends Command { + + public static final String COMMAND_WORD = "delete"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Deletes the contact identified by the index number " + + "used in the displayed contact list or deletes contacts with the specified tag.\n" + + "Parameters:\n" + + "- " + COMMAND_WORD + " INDEX (must be a positive integer)\n" + + "- " + COMMAND_WORD + " " + PREFIX_TAG + "TAG\n" + + "Example: " + COMMAND_WORD + " 1\n" + + "Example: " + COMMAND_WORD + " " + PREFIX_TAG + "friend"; + +} + diff --git a/src/main/java/seedu/address/logic/commands/DeleteCommand.java b/src/main/java/seedu/address/logic/commands/DeleteCommand.java index 1135ac19b74..c265694a490 100644 --- a/src/main/java/seedu/address/logic/commands/DeleteCommand.java +++ b/src/main/java/seedu/address/logic/commands/DeleteCommand.java @@ -14,7 +14,7 @@ /** * Deletes a person identified using it's displayed index from the address book. */ -public class DeleteCommand extends Command { +public class DeleteCommand extends DeleteAbstractCommand { public static final String COMMAND_WORD = "delete"; diff --git a/src/main/java/seedu/address/logic/commands/DeleteTagCommand.java b/src/main/java/seedu/address/logic/commands/DeleteTagCommand.java new file mode 100644 index 00000000000..b68f9f85420 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/DeleteTagCommand.java @@ -0,0 +1,82 @@ +package seedu.address.logic.commands; + +import java.util.List; +import java.util.stream.Collectors; + +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.person.Person; +import seedu.address.model.tag.Tag; +import seedu.address.model.tag.TagContainsKeywordsPredicate; + +/** + * Command to delete all contacts with a specified tag from the address book. + */ +public class DeleteTagCommand extends DeleteAbstractCommand { + public static final String COMMAND_WORD = "delete t/"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Deletes all contacts with the specified tag.\n" + + "Parameters: TAG\n" + + "Example: " + COMMAND_WORD + " friend"; + + public static final String MESSAGE_DELETE_TAG_SUCCESS = "Deleted all contacts with tag: %1$s"; + public static final String MESSAGE_TAG_DOES_NOT_EXIST = "The following tags do not exist: %1$s"; + + private final TagContainsKeywordsPredicate tagToDelete; + + /** + * Constructs a {@code DeleteTagCommand} with the specified tag to delete. + * + * @param tagToDelete The tag to delete. + */ + public DeleteTagCommand(TagContainsKeywordsPredicate tagToDelete) { + this.tagToDelete = tagToDelete; + } + + /** + * Executes the command to delete all contacts with the specified tag from the address book. + * + * @param model The address book model. + * @return The result of the command execution. + * @throws CommandException If an error occurs during command execution. + */ + @Override + public CommandResult execute(Model model) throws CommandException { + List tags = tagToDelete.getTags(); + List allTags = model.getAddressBook().getPersonList().stream() + .flatMap(person -> person.getTags().stream()) + .distinct() + .collect(Collectors.toList()); + + if (!allTags.containsAll(tags)) { + List nonExistentTags = tags.stream() + .filter(tag -> !allTags.contains(tag)) + .collect(Collectors.toList()); + throw new CommandException(String.format(MESSAGE_TAG_DOES_NOT_EXIST, nonExistentTags)); + } + + List personsToDelete = model.getFilteredPersonList().stream() + .filter(person -> tagToDelete.test(person)) + .collect(Collectors.toList()); + + personsToDelete.forEach(model::deletePerson); + + return new CommandResult(String.format(MESSAGE_DELETE_TAG_SUCCESS, tagToDelete)); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof DeleteTagCommand)) { + return false; + } + + DeleteTagCommand otherDeleteTagCommand = (DeleteTagCommand) other; + return tagToDelete.equals(otherDeleteTagCommand.tagToDelete); + } + +} diff --git a/src/main/java/seedu/address/logic/commands/EditCommand.java b/src/main/java/seedu/address/logic/commands/EditCommand.java index 4b581c7331e..d98022abbf9 100644 --- a/src/main/java/seedu/address/logic/commands/EditCommand.java +++ b/src/main/java/seedu/address/logic/commands/EditCommand.java @@ -2,9 +2,15 @@ import static java.util.Objects.requireNonNull; import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_COMPANY_NAME; import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; +import static seedu.address.logic.parser.CliSyntax.PREFIX_INFO; +import static seedu.address.logic.parser.CliSyntax.PREFIX_INTERVIEWTIME; import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PRIORITY; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PROGRAMMING_LANGUAGE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_SALARY; import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; @@ -21,11 +27,16 @@ import seedu.address.logic.Messages; import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.model.Model; +import seedu.address.model.language.ProgrammingLanguage; import seedu.address.model.person.Address; +import seedu.address.model.person.CompanyName; import seedu.address.model.person.Email; +import seedu.address.model.person.Info; +import seedu.address.model.person.InterviewTime; import seedu.address.model.person.Name; import seedu.address.model.person.Person; import seedu.address.model.person.Phone; +import seedu.address.model.person.Salary; import seedu.address.model.tag.Tag; /** @@ -39,11 +50,17 @@ public class EditCommand extends Command { + "by the index number used in the displayed person list. " + "Existing values will be overwritten by the input values.\n" + "Parameters: INDEX (must be a positive integer) " + + "[" + PREFIX_COMPANY_NAME + "COMPANY NAME] " + "[" + PREFIX_NAME + "NAME] " + "[" + PREFIX_PHONE + "PHONE] " + "[" + PREFIX_EMAIL + "EMAIL] " + "[" + PREFIX_ADDRESS + "ADDRESS] " + + "[" + PREFIX_INTERVIEWTIME + "INTERVIEW-TIME] " + + "[" + PREFIX_SALARY + "SALARY] " + + "[" + PREFIX_INFO + "INFO] " + "[" + PREFIX_TAG + "TAG]...\n" + + "[" + PREFIX_PROGRAMMING_LANGUAGE + "PROGRAMMING-LANGUAGE]...\n" + + "[" + PREFIX_PRIORITY + "PRIORITY]\n" + "Example: " + COMMAND_WORD + " 1 " + PREFIX_PHONE + "91234567 " + PREFIX_EMAIL + "johndoe@example.com"; @@ -94,14 +111,22 @@ public CommandResult execute(Model model) throws CommandException { */ private static Person createEditedPerson(Person personToEdit, EditPersonDescriptor editPersonDescriptor) { assert personToEdit != null; - + CompanyName updatedCompanyName = editPersonDescriptor.getCompanyName().orElse(personToEdit.getCompanyName()); Name updatedName = editPersonDescriptor.getName().orElse(personToEdit.getName()); Phone updatedPhone = editPersonDescriptor.getPhone().orElse(personToEdit.getPhone()); Email updatedEmail = editPersonDescriptor.getEmail().orElse(personToEdit.getEmail()); Address updatedAddress = editPersonDescriptor.getAddress().orElse(personToEdit.getAddress()); + InterviewTime updatedDateTime = editPersonDescriptor.getDateTime().orElse(personToEdit.getDateTime()); + Salary updatedSalary = editPersonDescriptor.getSalary().orElse(personToEdit.getSalary()); + Info updatedInfo = editPersonDescriptor.getInfo().orElse(personToEdit.getInfo()); Set updatedTags = editPersonDescriptor.getTags().orElse(personToEdit.getTags()); - - return new Person(updatedName, updatedPhone, updatedEmail, updatedAddress, updatedTags); + Set updatedProgrammingLanguages = editPersonDescriptor.getProgrammingLanguages() + .orElse(personToEdit.getProgrammingLanguages()); + int updatedPriority = editPersonDescriptor.getPriority().orElse(personToEdit.getPriority()); + return new Person( + updatedCompanyName, updatedName, updatedPhone, updatedEmail, + updatedAddress, updatedDateTime, updatedSalary, updatedInfo, + updatedTags, updatedProgrammingLanguages, updatedPriority); } @Override @@ -116,6 +141,12 @@ public boolean equals(Object other) { } EditCommand otherEditCommand = (EditCommand) other; + System.out.println("EditCommand equals"); + System.out.println("index: " + index + " other index: " + ((EditCommand) other).index); + System.out.println("editPersonDescriptor: " + editPersonDescriptor + " other editPersonDescriptor: " + + ((EditCommand) other).editPersonDescriptor); + System.out.println(index.equals(otherEditCommand.index)); + System.out.println(editPersonDescriptor.equals(otherEditCommand.editPersonDescriptor)); return index.equals(otherEditCommand.index) && editPersonDescriptor.equals(otherEditCommand.editPersonDescriptor); } @@ -133,11 +164,17 @@ public String toString() { * corresponding field value of the person. */ public static class EditPersonDescriptor { + private CompanyName companyName; private Name name; private Phone phone; private Email email; private Address address; + private InterviewTime dateTime; + private Salary salary; + private Info info; private Set tags; + private Set programmingLanguages; + private Integer priority; public EditPersonDescriptor() {} @@ -146,23 +183,37 @@ public EditPersonDescriptor() {} * A defensive copy of {@code tags} is used internally. */ public EditPersonDescriptor(EditPersonDescriptor toCopy) { + setCompanyName(toCopy.companyName); setName(toCopy.name); setPhone(toCopy.phone); setEmail(toCopy.email); setAddress(toCopy.address); + setDateTime(toCopy.dateTime); + setSalary(toCopy.salary); + setInfo(toCopy.info); setTags(toCopy.tags); + setProgrammingLanguages(toCopy.programmingLanguages); + setPriority(toCopy.priority); } /** * Returns true if at least one field is edited. */ public boolean isAnyFieldEdited() { - return CollectionUtil.isAnyNonNull(name, phone, email, address, tags); + return CollectionUtil.isAnyNonNull(companyName, name, phone, email, address, dateTime, salary, info, tags, + programmingLanguages, priority); + } + public void setCompanyName(CompanyName companyName) { + this.companyName = companyName; } public void setName(Name name) { this.name = name; } + public Optional getCompanyName() { + return Optional.ofNullable(companyName); + } + public Optional getName() { return Optional.ofNullable(name); @@ -187,11 +238,31 @@ public Optional getEmail() { public void setAddress(Address address) { this.address = address; } + public void setDateTime(InterviewTime dateTime) { + this.dateTime = dateTime; + } + public Optional getDateTime() { + return Optional.ofNullable(dateTime); + } public Optional
getAddress() { return Optional.ofNullable(address); } + public void setSalary(Salary salary) { + this.salary = salary; + } + + public Optional getSalary() { + return Optional.ofNullable(salary); + } + + public void setInfo(Info info) { + this.info = info; } + + public Optional getInfo() { + return Optional.ofNullable(info); } + /** * Sets {@code tags} to this object's {@code tags}. * A defensive copy of {@code tags} is used internally. @@ -209,6 +280,33 @@ public Optional> getTags() { return (tags != null) ? Optional.of(Collections.unmodifiableSet(tags)) : Optional.empty(); } + /** + * Sets {@code programmingLanguages} to this object's {@code programmingLanguages}. + * A defensive copy of {@code programmingLanguages} is used internally. + */ + public void setProgrammingLanguages(Set programmingLanguages) { + this.programmingLanguages = (programmingLanguages != null) ? new HashSet<>(programmingLanguages) : null; + } + + /** + * Returns an unmodifiable programmingLanguages set, which throws {@code UnsupportedOperationException} + * if modification is attempted. + * Returns {@code Optional#empty()} if {@code programmingLanguages} is null. + */ + public Optional> getProgrammingLanguages() { + return (programmingLanguages != null) ? Optional.of(Collections + .unmodifiableSet(programmingLanguages)) : Optional.empty(); + } + + public void setPriority(Integer priority) { + this.priority = priority; + } + + public Optional getPriority() { + return Optional.ofNullable(priority); + } + + @Override public boolean equals(Object other) { if (other == this) { @@ -221,22 +319,36 @@ public boolean equals(Object other) { } EditPersonDescriptor otherEditPersonDescriptor = (EditPersonDescriptor) other; - return Objects.equals(name, otherEditPersonDescriptor.name) + return Objects.equals(companyName, otherEditPersonDescriptor.companyName) + && Objects.equals(name, otherEditPersonDescriptor.name) && Objects.equals(phone, otherEditPersonDescriptor.phone) && Objects.equals(email, otherEditPersonDescriptor.email) && Objects.equals(address, otherEditPersonDescriptor.address) - && Objects.equals(tags, otherEditPersonDescriptor.tags); + && Objects.equals(dateTime, otherEditPersonDescriptor.dateTime) + && Objects.equals(salary, otherEditPersonDescriptor.salary) + && Objects.equals(info, otherEditPersonDescriptor.info) + && Objects.equals(tags, otherEditPersonDescriptor.tags) + && Objects.equals(programmingLanguages, otherEditPersonDescriptor.programmingLanguages) + && Objects.equals(priority, otherEditPersonDescriptor.priority); } @Override public String toString() { return new ToStringBuilder(this) + .add("company name", companyName) .add("name", name) .add("phone", phone) .add("email", email) .add("address", address) + .add("dateTime", dateTime) + .add("salary", salary) + .add("info", info) .add("tags", tags) + .add("programmingLanguages", programmingLanguages) + .add("priority", priority) .toString(); } + + } } diff --git a/src/main/java/seedu/address/logic/commands/ExitCommand.java b/src/main/java/seedu/address/logic/commands/ExitCommand.java index 3dd85a8ba90..eb7560413f9 100644 --- a/src/main/java/seedu/address/logic/commands/ExitCommand.java +++ b/src/main/java/seedu/address/logic/commands/ExitCommand.java @@ -13,7 +13,7 @@ public class ExitCommand extends Command { @Override public CommandResult execute(Model model) { - return new CommandResult(MESSAGE_EXIT_ACKNOWLEDGEMENT, false, true); + return new CommandResult(MESSAGE_EXIT_ACKNOWLEDGEMENT, false, false, true); } } diff --git a/src/main/java/seedu/address/logic/commands/FilterCommand.java b/src/main/java/seedu/address/logic/commands/FilterCommand.java new file mode 100644 index 00000000000..8caffd490dc --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/FilterCommand.java @@ -0,0 +1,26 @@ +package seedu.address.logic.commands; + +import static seedu.address.logic.parser.CliSyntax.PREFIX_INTERVIEWTIME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PROGRAMMING_LANGUAGE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_SALARY; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; + + +/** + * Abstract class representing a FilterCommand for filtering contacts in an address book. + * Subclasses will implement specific filter commands for different categories. + */ +public abstract class FilterCommand extends Command { + + public static final String COMMAND_WORD = "filter"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Filters the contact by various categories and " + + "displays them as a list with index numbers.\n" + + "Parameters:\n" + + "- " + PREFIX_TAG + "TAG [MORE TAG]...\n" + + "- " + PREFIX_SALARY + "SALARY_RANGE [MORE SALARY_RANGE]...\n" + + "- " + PREFIX_INTERVIEWTIME + "INTERVIEW_TIME_RANGE [MORE INTERVIEW_TIME_RANGE]...\n" + + "- " + PREFIX_PROGRAMMING_LANGUAGE + + "PROGRAMMING_LANGUAGE [MORE PROGRAMMING_LANGUAGE]...\n" + + "Example: " + COMMAND_WORD + " s/2000 5000-7000"; +} diff --git a/src/main/java/seedu/address/logic/commands/FilterInterviewTimeCommand.java b/src/main/java/seedu/address/logic/commands/FilterInterviewTimeCommand.java new file mode 100644 index 00000000000..7d76b593ffb --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/FilterInterviewTimeCommand.java @@ -0,0 +1,73 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_INTERVIEWTIME; + +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.logic.Messages; +import seedu.address.model.Model; +import seedu.address.model.person.InterviewTime; +import seedu.address.model.person.InterviewTimeContainsKeywordsPredicate; + + +/** + * Filters and lists all contacts in address book whose InterviewTime is within the interview time range provided. + */ +public class FilterInterviewTimeCommand extends FilterCommand { + public static final String COMMAND_WORD = FilterCommand.COMMAND_WORD + " " + PREFIX_INTERVIEWTIME; + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Filters all companies whose " + + "range of interview times contain any of " + + "the specified salary and displays them as a list with index numbers.\n" + + "Parameters: INTERVIEW_TIME_RANGE [MORE_INTERVIEW_TIME_RANGE]...\n" + + "Valid INTERVIEW_TIME_RANGE includes:\n" + + "- before/INTERVIEW_TIME\n" + + "- after/INTERVIEW_TIME\n" + + "- from/INTERVIEW_TIME-INTERVIEW_TIME\n" + + "Example:\n" + + "- " + COMMAND_WORD + "before/020220241100 after/020220251100 from/030420240800-020520241100"; + public static final String WRONG_INTERVIEW_TIME_RANGE_MESSAGE = "For INTERVIEW_TIME_RANGE starting with from/ :\n" + + " Format: from/INTERVIEW_TIME-INTERVIEW_TIME\n" + + " where:\n" + + " 1) There are no white spaces before and after '-'\n" + + " 2) The first INTERVIEW_TIME provided should be chronologically before or equal to the second " + + "INTERVIEW_TIME\n" + + " 3) INTERVIEW_TIME is in the form: " + InterviewTime.INTERVIEW_TIME_FORMAT + + "\n Example: from/" + InterviewTime.EXAMPLE_INTERVIEW_TIME_FORMAT_1 + "-" + + InterviewTime.EXAMPLE_INTERVIEW_TIME_FORMAT_2; + + private final InterviewTimeContainsKeywordsPredicate predicate; + + public FilterInterviewTimeCommand(InterviewTimeContainsKeywordsPredicate predicate) { + this.predicate = predicate; + } + + @Override + public CommandResult execute(Model model) { + requireNonNull(model); + model.updateFilteredPersonList(predicate); + return new CommandResult( + String.format(Messages.MESSAGE_PERSONS_LISTED_OVERVIEW, model.getFilteredPersonList().size())); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof FilterInterviewTimeCommand)) { + return false; + } + + FilterInterviewTimeCommand otherFilterInterviewTimeCommand = (FilterInterviewTimeCommand) other; + return predicate.equals(otherFilterInterviewTimeCommand.predicate); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("interviewTime", predicate) + .toString(); + } +} diff --git a/src/main/java/seedu/address/logic/commands/FilterProgrammingLanguageCommand.java b/src/main/java/seedu/address/logic/commands/FilterProgrammingLanguageCommand.java new file mode 100644 index 00000000000..d6285157b8e --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/FilterProgrammingLanguageCommand.java @@ -0,0 +1,59 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PROGRAMMING_LANGUAGE; + +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.logic.Messages; +import seedu.address.model.Model; +import seedu.address.model.language.ProgrammingLanguageContainsKeywordsPredicate; + +/** + * Filters and lists all persons in address book whose programming language contains any of the argument keywords. + */ +public class FilterProgrammingLanguageCommand extends FilterCommand { + + public static final String COMMAND_WORD = FilterCommand.COMMAND_WORD + " " + PREFIX_PROGRAMMING_LANGUAGE; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Filters all contacts whose programming language " + + "contain any of the specified tags and displays them as a list with index numbers.\n" + + "Parameters: PROGRAMMING_LANGUAGE [MORE_PROGRAMMING_LANGUAGES]...\n" + + "Example: " + COMMAND_WORD + " Java"; + + private final ProgrammingLanguageContainsKeywordsPredicate predicate; + + public FilterProgrammingLanguageCommand(ProgrammingLanguageContainsKeywordsPredicate predicate) { + this.predicate = predicate; + } + + @Override + + public CommandResult execute(Model model) { + requireNonNull(model); + model.updateFilteredPersonList(predicate); + return new CommandResult( + String.format(Messages.MESSAGE_PERSONS_LISTED_OVERVIEW, model.getFilteredPersonList().size())); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof FilterProgrammingLanguageCommand)) { + return false; + } + + FilterProgrammingLanguageCommand otherFindCommand = (FilterProgrammingLanguageCommand) other; + return predicate.equals(otherFindCommand.predicate); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("programming_language", predicate) + .toString(); + } +} diff --git a/src/main/java/seedu/address/logic/commands/FilterSalaryCommand.java b/src/main/java/seedu/address/logic/commands/FilterSalaryCommand.java new file mode 100644 index 00000000000..c9350391f60 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/FilterSalaryCommand.java @@ -0,0 +1,59 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_SALARY; + +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.logic.Messages; +import seedu.address.model.Model; +import seedu.address.model.person.SalaryContainsKeywordsPredicate; + + + +/** + * Filters and lists all contacts in address book whose Salary is within the salary range provided. + */ +public class FilterSalaryCommand extends FilterCommand { + public static final String COMMAND_WORD = FilterCommand.COMMAND_WORD + " " + PREFIX_SALARY; + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Filters all companies whose " + + "salary range contain any of " + + "the specified salary and displays them as a list with index numbers.\n" + + "Parameters: SALARY_RANGE [MORE_SALARY_RANGE]...\n" + + "Example: " + COMMAND_WORD + "5000-6000 >=8000"; + + private final SalaryContainsKeywordsPredicate predicate; + + public FilterSalaryCommand(SalaryContainsKeywordsPredicate predicate) { + this.predicate = predicate; + } + + @Override + public CommandResult execute(Model model) { + requireNonNull(model); + model.updateFilteredPersonList(predicate); + return new CommandResult( + String.format(Messages.MESSAGE_PERSONS_LISTED_OVERVIEW, model.getFilteredPersonList().size())); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof FilterSalaryCommand)) { + return false; + } + + FilterSalaryCommand otherFindCommand = (FilterSalaryCommand) other; + return predicate.equals(otherFindCommand.predicate); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("salary", predicate) + .toString(); + } +} diff --git a/src/main/java/seedu/address/logic/commands/FilterTagCommand.java b/src/main/java/seedu/address/logic/commands/FilterTagCommand.java new file mode 100644 index 00000000000..89a70417bf8 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/FilterTagCommand.java @@ -0,0 +1,60 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; + +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.logic.Messages; +import seedu.address.model.Model; +import seedu.address.model.tag.TagContainsKeywordsPredicate; + + +/** + * Filters and lists all persons in address book whose tags contains any of the argument keywords. + */ +public class FilterTagCommand extends FilterCommand { + + public static final String COMMAND_WORD = FilterCommand.COMMAND_WORD + " " + PREFIX_TAG; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Filters all contacts whose tags contain any of " + + "the specified tags and displays them as a list with index numbers.\n" + + "Parameters: TAG [MORE_TAGS]...\n" + + "Example: " + COMMAND_WORD + " owes money"; + + private final TagContainsKeywordsPredicate predicate; + + public FilterTagCommand(TagContainsKeywordsPredicate predicate) { + this.predicate = predicate; + } + + @Override + + public CommandResult execute(Model model) { + requireNonNull(model); + model.updateFilteredPersonList(predicate); + return new CommandResult( + String.format(Messages.MESSAGE_PERSONS_LISTED_OVERVIEW, model.getFilteredPersonList().size())); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof FilterTagCommand)) { + return false; + } + + FilterTagCommand otherFindCommand = (FilterTagCommand) other; + return predicate.equals(otherFindCommand.predicate); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("tag", predicate) + .toString(); + } +} diff --git a/src/main/java/seedu/address/logic/commands/FindCommand.java b/src/main/java/seedu/address/logic/commands/FindCommand.java index 72b9eddd3a7..dd34ccc39b2 100644 --- a/src/main/java/seedu/address/logic/commands/FindCommand.java +++ b/src/main/java/seedu/address/logic/commands/FindCommand.java @@ -5,24 +5,24 @@ import seedu.address.commons.util.ToStringBuilder; import seedu.address.logic.Messages; import seedu.address.model.Model; -import seedu.address.model.person.NameContainsKeywordsPredicate; +import seedu.address.model.person.NameOrCompanyNameContainsKeywordsPredicate; /** - * Finds and lists all persons in address book whose name contains any of the argument keywords. - * Keyword matching is case insensitive. + * Finds and lists all persons in address book whose name or company name contains any of the argument keywords. + * Keyword matching is case-insensitive. */ public class FindCommand extends Command { public static final String COMMAND_WORD = "find"; - public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all persons whose names contain any of " - + "the specified keywords (case-insensitive) and displays them as a list with index numbers.\n" + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all persons whose names or company names contain" + + "any of the specified keywords (case-insensitive) and displays them as a list with index numbers.\n" + "Parameters: KEYWORD [MORE_KEYWORDS]...\n" - + "Example: " + COMMAND_WORD + " alice bob charlie"; + + "Example: " + COMMAND_WORD + " alice google"; - private final NameContainsKeywordsPredicate predicate; + private final NameOrCompanyNameContainsKeywordsPredicate predicate; - public FindCommand(NameContainsKeywordsPredicate predicate) { + public FindCommand(NameOrCompanyNameContainsKeywordsPredicate predicate) { this.predicate = predicate; } @@ -36,6 +36,7 @@ public CommandResult execute(Model model) { @Override public boolean equals(Object other) { + assert 1 == 1; if (other == this) { return true; } diff --git a/src/main/java/seedu/address/logic/commands/HelpCommand.java b/src/main/java/seedu/address/logic/commands/HelpCommand.java index bf824f91bd0..07d26e2a23c 100644 --- a/src/main/java/seedu/address/logic/commands/HelpCommand.java +++ b/src/main/java/seedu/address/logic/commands/HelpCommand.java @@ -16,6 +16,6 @@ public class HelpCommand extends Command { @Override public CommandResult execute(Model model) { - return new CommandResult(SHOWING_HELP_MESSAGE, true, false); + return new CommandResult(SHOWING_HELP_MESSAGE, true, false, false); } } diff --git a/src/main/java/seedu/address/logic/commands/SortCommand.java b/src/main/java/seedu/address/logic/commands/SortCommand.java new file mode 100644 index 00000000000..af87a261662 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/SortCommand.java @@ -0,0 +1,86 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_COMPANY_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_INTERVIEWTIME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_SALARY; + +import seedu.address.logic.Messages; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.person.PersonCompanyNameComparator; +import seedu.address.model.person.PersonInterviewTimeComparator; +import seedu.address.model.person.PersonJobDifficultyComparator; +import seedu.address.model.person.PersonNameComparator; +import seedu.address.model.person.PersonPriorityComparator; +import seedu.address.model.person.PersonSalaryComparator; + +/** + * Sorts the list of contacts based on a specified information. + */ +public class SortCommand extends Command { + + public static final String COMMAND_WORD = "sort"; + public static final Object MESSAGE_USAGE = COMMAND_WORD + ": Sorts the current list of contacts " + + "based on a specified information. \n" + + "Optional: [rev/] for reverse order\n" + + "Parameters: PERSON ATTRIBUTE (only hashable types)" + + "[" + PREFIX_COMPANY_NAME + "COMPANY NAME] " + + "[" + PREFIX_NAME + "NAME] " + + "[" + PREFIX_INTERVIEWTIME + "INTERVIEW-TIME] " + + "[" + PREFIX_SALARY + "SALARY] " + + "Example: " + COMMAND_WORD + " " + PREFIX_INTERVIEWTIME; + + public static final String MESSAGE_LIST_SORTED_SUCCESS = "List Sorted"; + private final Integer info; + private final boolean isReverseOrder; + /** + * Creates a SortCommand to sort the list of contacts based on the specified information. + */ + public SortCommand(Integer info, boolean isInverse) { + requireNonNull(info); + this.info = info; + this.isReverseOrder = isInverse; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + switch (info) { + case 0: + model.updateSortedPersonList(new PersonPriorityComparator(isReverseOrder)); + break; + case 1: + model.updateSortedPersonList(new PersonCompanyNameComparator(isReverseOrder)); + break; + case 2: + model.updateSortedPersonList(new PersonNameComparator(isReverseOrder)); + break; + case 3: + model.updateSortedPersonList(new PersonInterviewTimeComparator(isReverseOrder)); + break; + case 4: + model.updateSortedPersonList(new PersonSalaryComparator(isReverseOrder)); + break; + case 5: + model.updateSortedPersonList(new PersonJobDifficultyComparator(isReverseOrder)); + break; + default: + throw new CommandException(Messages.MESSAGE_INVALID_SORT_COMMAND_INDEX); + } + return new CommandResult(MESSAGE_LIST_SORTED_SUCCESS); + } + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + // instanceof handles nulls + if (!(other instanceof SortCommand)) { + return false; + } + SortCommand otherSort = (SortCommand) other; + return info.equals(otherSort.info); + } +} diff --git a/src/main/java/seedu/address/logic/parser/AddCommandParser.java b/src/main/java/seedu/address/logic/parser/AddCommandParser.java index 4ff1a97ed77..e891b171018 100644 --- a/src/main/java/seedu/address/logic/parser/AddCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/AddCommandParser.java @@ -2,9 +2,15 @@ import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_COMPANY_NAME; import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; +import static seedu.address.logic.parser.CliSyntax.PREFIX_INFO; +import static seedu.address.logic.parser.CliSyntax.PREFIX_INTERVIEWTIME; import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PRIORITY; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PROGRAMMING_LANGUAGE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_SALARY; import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; import java.util.Set; @@ -12,11 +18,16 @@ import seedu.address.logic.commands.AddCommand; import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.language.ProgrammingLanguage; import seedu.address.model.person.Address; +import seedu.address.model.person.CompanyName; import seedu.address.model.person.Email; +import seedu.address.model.person.Info; +import seedu.address.model.person.InterviewTime; import seedu.address.model.person.Name; import seedu.address.model.person.Person; import seedu.address.model.person.Phone; +import seedu.address.model.person.Salary; import seedu.address.model.tag.Tag; /** @@ -31,21 +42,35 @@ public class AddCommandParser implements Parser { */ public AddCommand parse(String args) throws ParseException { ArgumentMultimap argMultimap = - ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, PREFIX_TAG); + ArgumentTokenizer.tokenize( + args, PREFIX_COMPANY_NAME, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, + PREFIX_INTERVIEWTIME, PREFIX_TAG, PREFIX_SALARY, + PREFIX_INFO, PREFIX_PROGRAMMING_LANGUAGE, PREFIX_PRIORITY); - if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_ADDRESS, PREFIX_PHONE, PREFIX_EMAIL) - || !argMultimap.getPreamble().isEmpty()) { + + if (!arePrefixesPresent(argMultimap, PREFIX_COMPANY_NAME, PREFIX_NAME, PREFIX_ADDRESS, + PREFIX_PHONE, PREFIX_EMAIL) || !argMultimap.getPreamble().isEmpty()) { throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE)); } - argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS); + argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_COMPANY_NAME, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, + PREFIX_ADDRESS, PREFIX_INTERVIEWTIME, PREFIX_INFO, PREFIX_SALARY, PREFIX_PRIORITY); + CompanyName companyName = ParserUtil.parseCompanyName(argMultimap.getValue(PREFIX_COMPANY_NAME).get()); Name name = ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get()); Phone phone = ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE).get()); Email email = ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL).get()); Address address = ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS).get()); + InterviewTime interviewTime = ParserUtil.parseInterviewTime(argMultimap.getValue(PREFIX_INTERVIEWTIME) + .orElse(null)); + Salary salary = ParserUtil.parseSalary(argMultimap.getValue(PREFIX_SALARY).orElse("0")); + Info info = ParserUtil.parseInfo(argMultimap.getValue(PREFIX_INFO).orElse("")); Set tagList = ParserUtil.parseTags(argMultimap.getAllValues(PREFIX_TAG)); + Set programmingLanguageList = ParserUtil.parseProgrammingLanguages(argMultimap + .getAllValues(PREFIX_PROGRAMMING_LANGUAGE)); + int priority = ParserUtil.parsePriority(argMultimap.getValue(PREFIX_PRIORITY).orElse("2")); - Person person = new Person(name, phone, email, address, tagList); + Person person = new Person(companyName, name, phone, email, address, interviewTime, salary, info, tagList, + programmingLanguageList, priority); return new AddCommand(person); } diff --git a/src/main/java/seedu/address/logic/parser/AddResumeCommandParser.java b/src/main/java/seedu/address/logic/parser/AddResumeCommandParser.java new file mode 100644 index 00000000000..04466110010 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/AddResumeCommandParser.java @@ -0,0 +1,79 @@ +package seedu.address.logic.parser; + +import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_COMPANY_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_EDUCATION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PROGRAMMING_LANGUAGE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_SALARY; + +import java.util.Set; +import java.util.stream.Stream; + +import seedu.address.logic.commands.AddResumeCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.language.ProgrammingLanguage; +import seedu.address.model.person.Address; +import seedu.address.model.person.CompanyName; +import seedu.address.model.person.Email; +import seedu.address.model.person.Name; +import seedu.address.model.person.Phone; +import seedu.address.model.person.Salary; +import seedu.address.model.person.user.Education; +import seedu.address.model.person.user.User; + +/** + * Parses input arguments and creates a new AddResumeCommand object + */ +public class AddResumeCommandParser implements Parser { + /** + * Parses the given {@code String} of arguments in the context of the AddResumeCommand + * and returns an AddResumeCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public AddResumeCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize( + args, PREFIX_COMPANY_NAME, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, + PREFIX_SALARY, PREFIX_EDUCATION, PREFIX_PROGRAMMING_LANGUAGE); + + if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_ADDRESS, PREFIX_PHONE, PREFIX_EMAIL, + PREFIX_COMPANY_NAME, PREFIX_SALARY, PREFIX_EDUCATION) || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddResumeCommand.MESSAGE_USAGE)); + } + argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_COMPANY_NAME, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, + PREFIX_ADDRESS, PREFIX_SALARY, PREFIX_EDUCATION); + CompanyName companyName = ParserUtil.parseCompanyName(argMultimap.getValue(PREFIX_COMPANY_NAME).get()); + Name name = ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get()); + Phone phone = ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE).get()); + Email email = ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL).get()); + Address address = ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS).get()); + Salary salary = ParserUtil.parseSalary(argMultimap.getValue(PREFIX_SALARY).orElse("0")); + Education education = ParserUtil.parseEducation(argMultimap.getValue(PREFIX_EDUCATION).get()); + Set skills = ParserUtil.parseProgrammingLanguages(argMultimap + .getAllValues(PREFIX_PROGRAMMING_LANGUAGE)); + + User user = User.getInstance(); + user.reset(); + user.setCompanyName(companyName); + user.setName(name); + user.setPhone(phone); + user.setEmail(email); + user.setAddress(address); + user.setSalary(salary); + user.setEducation(education); + user.setSkills(skills); + + return new AddResumeCommand(user); + } + /** + * Returns true if none of the prefixes contains empty {@code Optional} values in the given + * {@code ArgumentMultimap}. + */ + private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); + } +} diff --git a/src/main/java/seedu/address/logic/parser/AddressBookParser.java b/src/main/java/seedu/address/logic/parser/AddressBookParser.java index 3149ee07e0b..349da8a0cae 100644 --- a/src/main/java/seedu/address/logic/parser/AddressBookParser.java +++ b/src/main/java/seedu/address/logic/parser/AddressBookParser.java @@ -9,14 +9,17 @@ import seedu.address.commons.core.LogsCenter; import seedu.address.logic.commands.AddCommand; +import seedu.address.logic.commands.AddResumeCommand; import seedu.address.logic.commands.ClearCommand; import seedu.address.logic.commands.Command; -import seedu.address.logic.commands.DeleteCommand; +import seedu.address.logic.commands.DeleteAbstractCommand; import seedu.address.logic.commands.EditCommand; import seedu.address.logic.commands.ExitCommand; +import seedu.address.logic.commands.FilterCommand; import seedu.address.logic.commands.FindCommand; import seedu.address.logic.commands.HelpCommand; import seedu.address.logic.commands.ListCommand; +import seedu.address.logic.commands.SortCommand; import seedu.address.logic.parser.exceptions.ParseException; /** @@ -42,10 +45,8 @@ public Command parseCommand(String userInput) throws ParseException { if (!matcher.matches()) { throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, HelpCommand.MESSAGE_USAGE)); } - final String commandWord = matcher.group("commandWord"); final String arguments = matcher.group("arguments"); - // Note to developers: Change the log level in config.json to enable lower level (i.e., FINE, FINER and lower) // log messages such as the one below. // Lower level log messages are used sparingly to minimize noise in the code. @@ -59,8 +60,8 @@ public Command parseCommand(String userInput) throws ParseException { case EditCommand.COMMAND_WORD: return new EditCommandParser().parse(arguments); - case DeleteCommand.COMMAND_WORD: - return new DeleteCommandParser().parse(arguments); + case DeleteAbstractCommand.COMMAND_WORD: + return new DeleteAbstractCommandParser().parse(arguments); case ClearCommand.COMMAND_WORD: return new ClearCommand(); @@ -68,6 +69,9 @@ public Command parseCommand(String userInput) throws ParseException { case FindCommand.COMMAND_WORD: return new FindCommandParser().parse(arguments); + case FilterCommand.COMMAND_WORD: + return new FilterCommandParser().parse(arguments); + case ListCommand.COMMAND_WORD: return new ListCommand(); @@ -77,6 +81,12 @@ public Command parseCommand(String userInput) throws ParseException { case HelpCommand.COMMAND_WORD: return new HelpCommand(); + case SortCommand.COMMAND_WORD: + return new SortCommandParser().parse(arguments); + + case AddResumeCommand.COMMAND_WORD: + return new AddResumeCommandParser().parse(arguments); + default: logger.finer("This user input caused a ParseException: " + userInput); throw new ParseException(MESSAGE_UNKNOWN_COMMAND); diff --git a/src/main/java/seedu/address/logic/parser/ArgumentMultimap.java b/src/main/java/seedu/address/logic/parser/ArgumentMultimap.java index 21e26887a83..9e535355b7d 100644 --- a/src/main/java/seedu/address/logic/parser/ArgumentMultimap.java +++ b/src/main/java/seedu/address/logic/parser/ArgumentMultimap.java @@ -75,4 +75,8 @@ public void verifyNoDuplicatePrefixesFor(Prefix... prefixes) throws ParseExcepti throw new ParseException(Messages.getErrorMessageForDuplicatePrefixes(duplicatedPrefixes)); } } + + public int getSize() { + return argMultimap.size(); + } } diff --git a/src/main/java/seedu/address/logic/parser/CliSyntax.java b/src/main/java/seedu/address/logic/parser/CliSyntax.java index 75b1a9bf119..062f3530940 100644 --- a/src/main/java/seedu/address/logic/parser/CliSyntax.java +++ b/src/main/java/seedu/address/logic/parser/CliSyntax.java @@ -10,6 +10,15 @@ public class CliSyntax { public static final Prefix PREFIX_PHONE = new Prefix("p/"); public static final Prefix PREFIX_EMAIL = new Prefix("e/"); public static final Prefix PREFIX_ADDRESS = new Prefix("a/"); + public static final Prefix PREFIX_COMPANY_NAME = new Prefix("cn/"); public static final Prefix PREFIX_TAG = new Prefix("t/"); + public static final Prefix PREFIX_INTERVIEWTIME = new Prefix("tt/"); + public static final Prefix PREFIX_SALARY = new Prefix("s/"); + public static final Prefix PREFIX_INFO = new Prefix("i/"); + public static final Prefix PREFIX_PROGRAMMING_LANGUAGE = new Prefix("pl/"); + public static final Prefix PREFIX_PRIORITY = new Prefix("pri/"); + public static final Prefix PREFIX_RESUME = new Prefix("resume/"); + public static final Prefix PREFIX_EDUCATION = new Prefix("edu/"); + public static final Prefix PREFIX_JOB_DIFFICULTY = new Prefix("jd/"); } diff --git a/src/main/java/seedu/address/logic/parser/DeleteAbstractCommandParser.java b/src/main/java/seedu/address/logic/parser/DeleteAbstractCommandParser.java new file mode 100644 index 00000000000..23f9ee9e0aa --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/DeleteAbstractCommandParser.java @@ -0,0 +1,58 @@ +package seedu.address.logic.parser; + +import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; + +import java.util.stream.Stream; + +import seedu.address.logic.commands.DeleteAbstractCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a new DeleteAbstractCommand object + */ +public class DeleteAbstractCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the DeleteAbstractCommand + * and returns a DeleteAbstractCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public DeleteAbstractCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_TAG); + + if (!exactlyOnePrefixPresent(argMultimap, PREFIX_TAG)) { + System.out.println("1"); + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + DeleteAbstractCommand.MESSAGE_USAGE)); + } + + if (argMultimap.getSize() == 1) { + return new DeleteCommandParser().parse(args); + } else if (!argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + DeleteAbstractCommand.MESSAGE_USAGE)); + } + + argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_TAG); + + if (argMultimap.getValue(PREFIX_TAG).isPresent()) { + return new DeleteTagCommandParser().parse(argMultimap.getValue(PREFIX_TAG).get()); + } else { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + DeleteAbstractCommand.MESSAGE_USAGE)); + } + } + + /** + * Returns true if exactly one of the prefixes contains a non-empty {@code Optional} value in the given + * {@code ArgumentMultimap}. + */ + private static boolean exactlyOnePrefixPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + long count = Stream.of(prefixes) + .filter(prefix -> argumentMultimap.getValue(prefix).isPresent()) + .count(); + return count <= 1; + } +} + diff --git a/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java b/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java index 3527fe76a3e..f124973cb3d 100644 --- a/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java @@ -25,5 +25,4 @@ public DeleteCommand parse(String args) throws ParseException { String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteCommand.MESSAGE_USAGE), pe); } } - } diff --git a/src/main/java/seedu/address/logic/parser/DeleteTagCommandParser.java b/src/main/java/seedu/address/logic/parser/DeleteTagCommandParser.java new file mode 100644 index 00000000000..2c0d2564885 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/DeleteTagCommandParser.java @@ -0,0 +1,60 @@ +package seedu.address.logic.parser; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import java.util.ArrayList; +import java.util.List; + +import seedu.address.logic.commands.DeleteTagCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.tag.Tag; +import seedu.address.model.tag.TagContainsKeywordsPredicate; + +/** + * Parses input arguments and creates a new DeleteTagCommand object. + */ +public class DeleteTagCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the DeleteTagCommand + * and returns a DeleteTagCommand object for execution. + * @throws ParseException if the user input does not conform to the expected format + */ + @Override + public DeleteTagCommand parse(String args) throws ParseException { + String trimmedArgs = args.trim(); + if (trimmedArgs.isEmpty()) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteTagCommand.MESSAGE_USAGE)); + } + String[] tagKeywords = trimmedArgs.split("\\s+"); + try { + List tags = parseTags(tagKeywords); + return new DeleteTagCommand(new TagContainsKeywordsPredicate(tags)); + } catch (ParseException pe) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteTagCommand.MESSAGE_USAGE), pe); + } + } + + /** + * Parses {@code Collection tagNames} into a {@code List}. + * + * @param tagNames The collection of tag names to parse. + * @return A list of parsed tags. + * @throws ParseException If any of the tag names are invalid. + */ + public static List parseTags(String... tagNames) throws ParseException { + requireNonNull(tagNames); + List parsedTags = new ArrayList<>(); + for (String tagName : tagNames) { + try { + parsedTags.add(ParserUtil.parseTag(tagName)); + } catch (ParseException pe) { + throw new ParseException(Tag.MESSAGE_CONSTRAINTS); + } + } + return parsedTags; + } +} diff --git a/src/main/java/seedu/address/logic/parser/EditCommandParser.java b/src/main/java/seedu/address/logic/parser/EditCommandParser.java index 46b3309a78b..43b75d8086e 100644 --- a/src/main/java/seedu/address/logic/parser/EditCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/EditCommandParser.java @@ -3,9 +3,15 @@ import static java.util.Objects.requireNonNull; import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_COMPANY_NAME; import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; +import static seedu.address.logic.parser.CliSyntax.PREFIX_INFO; +import static seedu.address.logic.parser.CliSyntax.PREFIX_INTERVIEWTIME; import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PRIORITY; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PROGRAMMING_LANGUAGE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_SALARY; import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; import java.util.Collection; @@ -17,6 +23,7 @@ import seedu.address.logic.commands.EditCommand; import seedu.address.logic.commands.EditCommand.EditPersonDescriptor; import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.language.ProgrammingLanguage; import seedu.address.model.tag.Tag; /** @@ -27,25 +34,33 @@ public class EditCommandParser implements Parser { /** * Parses the given {@code String} of arguments in the context of the EditCommand * and returns an EditCommand object for execution. + * * @throws ParseException if the user input does not conform the expected format */ public EditCommand parse(String args) throws ParseException { requireNonNull(args); ArgumentMultimap argMultimap = - ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, PREFIX_TAG); + ArgumentTokenizer.tokenize( + args, PREFIX_COMPANY_NAME, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, + PREFIX_ADDRESS, PREFIX_INTERVIEWTIME, PREFIX_SALARY, PREFIX_INFO, + PREFIX_TAG, PREFIX_PROGRAMMING_LANGUAGE, PREFIX_PRIORITY); Index index; - try { index = ParserUtil.parseIndex(argMultimap.getPreamble()); } catch (ParseException pe) { throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditCommand.MESSAGE_USAGE), pe); } - argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS); + argMultimap.verifyNoDuplicatePrefixesFor( + PREFIX_COMPANY_NAME, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_SALARY, PREFIX_INFO, + PREFIX_ADDRESS, PREFIX_INTERVIEWTIME, PREFIX_PRIORITY); EditPersonDescriptor editPersonDescriptor = new EditPersonDescriptor(); - + if (argMultimap.getValue(PREFIX_COMPANY_NAME).isPresent()) { + editPersonDescriptor.setCompanyName(ParserUtil.parseCompanyName( + argMultimap.getValue(PREFIX_COMPANY_NAME).get())); + } if (argMultimap.getValue(PREFIX_NAME).isPresent()) { editPersonDescriptor.setName(ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get())); } @@ -58,15 +73,28 @@ public EditCommand parse(String args) throws ParseException { if (argMultimap.getValue(PREFIX_ADDRESS).isPresent()) { editPersonDescriptor.setAddress(ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS).get())); } + if (argMultimap.getValue(PREFIX_INTERVIEWTIME).isPresent()) { + editPersonDescriptor.setDateTime( + ParserUtil.parseInterviewTime(argMultimap.getValue(PREFIX_INTERVIEWTIME).get())); + } + if (argMultimap.getValue(PREFIX_SALARY).isPresent()) { + editPersonDescriptor.setSalary(ParserUtil.parseSalary(argMultimap.getValue(PREFIX_SALARY).get())); + } + if (argMultimap.getValue(PREFIX_INFO).isPresent()) { + editPersonDescriptor.setInfo(ParserUtil.parseInfo(argMultimap.getValue(PREFIX_INFO).get())); + } + if (argMultimap.getValue(PREFIX_PRIORITY).isPresent()) { + editPersonDescriptor.setPriority(ParserUtil.parsePriority(argMultimap.getValue(PREFIX_PRIORITY).get())); + } parseTagsForEdit(argMultimap.getAllValues(PREFIX_TAG)).ifPresent(editPersonDescriptor::setTags); + parseProgrammingLanguagesForEdit(argMultimap.getAllValues(PREFIX_PROGRAMMING_LANGUAGE)) + .ifPresent(editPersonDescriptor::setProgrammingLanguages); if (!editPersonDescriptor.isAnyFieldEdited()) { throw new ParseException(EditCommand.MESSAGE_NOT_EDITED); } - return new EditCommand(index, editPersonDescriptor); } - /** * Parses {@code Collection tags} into a {@code Set} if {@code tags} is non-empty. * If {@code tags} contain only one element which is an empty string, it will be parsed into a @@ -82,4 +110,25 @@ private Optional> parseTagsForEdit(Collection tags) throws Pars return Optional.of(ParserUtil.parseTags(tagSet)); } + /** + * Parses a collection of programming language names into a set of ProgrammingLanguages if the collection is + * non-empty. If the collection contains only one element which is an empty string, it will be parsed into a + * set containing zero programming languages. + * + * @param programmingLanguages A collection of programming language names to be parsed. + * @return An Optional containing set of ProgrammingLanguages if collection is non-empty, otherwise empty Optional. + * @throws ParseException If any of the programming language names is invalid. + */ + private Optional> parseProgrammingLanguagesForEdit(Collection programmingLanguages) + throws ParseException { + assert programmingLanguages != null; + + if (programmingLanguages.isEmpty()) { + return Optional.empty(); + } + Collection languageSet = programmingLanguages.size() == 1 && programmingLanguages.contains("") + ? Collections.emptySet() : programmingLanguages; + return Optional.of(ParserUtil.parseProgrammingLanguages(languageSet)); + } + } diff --git a/src/main/java/seedu/address/logic/parser/FilterCommandParser.java b/src/main/java/seedu/address/logic/parser/FilterCommandParser.java new file mode 100644 index 00000000000..0657867cc39 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/FilterCommandParser.java @@ -0,0 +1,61 @@ +package seedu.address.logic.parser; + +import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_INTERVIEWTIME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PROGRAMMING_LANGUAGE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_SALARY; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; + +import java.util.stream.Stream; + +import seedu.address.logic.commands.FilterCommand; +import seedu.address.logic.parser.exceptions.ParseException; + + +/** + * Parses input arguments and creates a new FilterCommand object + */ +public class FilterCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the FilterCommand + * and returns a FilterCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public FilterCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize( + args, PREFIX_SALARY, PREFIX_TAG, PREFIX_INTERVIEWTIME, PREFIX_PROGRAMMING_LANGUAGE); + if (!exactlyOnePrefixPresent(argMultimap, PREFIX_SALARY, PREFIX_TAG, PREFIX_INTERVIEWTIME, + PREFIX_PROGRAMMING_LANGUAGE) || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, FilterCommand.MESSAGE_USAGE)); + } + + argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_SALARY, PREFIX_TAG, PREFIX_INTERVIEWTIME, + PREFIX_PROGRAMMING_LANGUAGE); + + if (argMultimap.getValue(PREFIX_SALARY).isPresent()) { + return new FilterSalaryCommandParser().parse(argMultimap.getValue(PREFIX_SALARY).get()); + } else if (argMultimap.getValue(PREFIX_TAG).isPresent()) { + return new FilterTagCommandParser().parse(argMultimap.getValue(PREFIX_TAG).get()); + } else if (argMultimap.getValue(PREFIX_PROGRAMMING_LANGUAGE).isPresent()) { + return new FilterProgrammingLanguageCommandParser().parse(argMultimap.getValue(PREFIX_PROGRAMMING_LANGUAGE) + .get()); + } else { + return new FilterInterviewTimeCommandParser().parse(argMultimap.getValue(PREFIX_INTERVIEWTIME).get()); + } + } + + /** + * Returns true if at least one of the prefixes contains empty {@code Optional} values in the given + * {@code ArgumentMultimap}. + */ + private static boolean exactlyOnePrefixPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + long count = Stream.of(prefixes) + .filter(prefix -> argumentMultimap.getValue(prefix).isPresent()) + .count(); + return count == 1; + } + +} + diff --git a/src/main/java/seedu/address/logic/parser/FilterInterviewTimeCommandParser.java b/src/main/java/seedu/address/logic/parser/FilterInterviewTimeCommandParser.java new file mode 100644 index 00000000000..c32bbda7f15 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/FilterInterviewTimeCommandParser.java @@ -0,0 +1,98 @@ +package seedu.address.logic.parser; + +import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import seedu.address.logic.commands.FilterInterviewTimeCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.person.InterviewTime; +import seedu.address.model.person.InterviewTimeContainsKeywordsPredicate; + + +/** + * Parses input arguments and creates a new FilterInterviewCommand object + */ +public class FilterInterviewTimeCommandParser { + private static final Prefix PREFIX_BEFORE_INTERVIEW_TIME = new Prefix("before/"); + private static final Prefix PREFIX_AFTER_INTERVIEW_TIME = new Prefix("after/"); + private static final Prefix PREFIX_FROM_INTERVIEW_TIME = new Prefix("from/"); + private static final String VALIDATION_REGEX_FROM_PREFIX = "^[^-]*-[^-]*$"; + + /** + * Parses the given {@code String} of arguments in the context of the FilterInterviewTimeCommand + * and returns a FilterInterviewTimeCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public FilterInterviewTimeCommand parse(String args) throws ParseException { + String trimmedArgs = args.trim(); + if (trimmedArgs.isEmpty()) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, FilterInterviewTimeCommand.MESSAGE_USAGE)); + } + trimmedArgs = " " + trimmedArgs; + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize( + trimmedArgs, PREFIX_BEFORE_INTERVIEW_TIME, PREFIX_AFTER_INTERVIEW_TIME, + PREFIX_FROM_INTERVIEW_TIME); + if (!argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + FilterInterviewTimeCommand.MESSAGE_USAGE)); + } + List> interviewTimes = new ArrayList<>(); + if (argMultimap.getValue(PREFIX_BEFORE_INTERVIEW_TIME).isPresent()) { + interviewTimes.addAll(handleBeforeInterviewTime(argMultimap)); + } + if (argMultimap.getValue(PREFIX_AFTER_INTERVIEW_TIME).isPresent()) { + interviewTimes.addAll(handleAfterInterviewTime(argMultimap)); + } + if (argMultimap.getValue(PREFIX_FROM_INTERVIEW_TIME).isPresent()) { + interviewTimes.addAll(handleFromInterviewTime(argMultimap)); + } + return new FilterInterviewTimeCommand(new InterviewTimeContainsKeywordsPredicate(interviewTimes)); + } + private static List> handleFromInterviewTime(ArgumentMultimap argMultimap) + throws ParseException { + List rangeInterviewTimes = argMultimap.getAllValues(PREFIX_FROM_INTERVIEW_TIME); + List> interviewTimes = new ArrayList<>(); + for (String rangeInterviewTime : rangeInterviewTimes) { + if (!rangeInterviewTime.matches(VALIDATION_REGEX_FROM_PREFIX)) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + FilterInterviewTimeCommand.WRONG_INTERVIEW_TIME_RANGE_MESSAGE)); + } else { + String[] range = rangeInterviewTime.split("-"); + List rangeTimes = new ArrayList<>(); + for (String interviewTime : range) { + rangeTimes.add(ParserUtil.parseInterviewTime(interviewTime)); + } + if (rangeTimes.get(1).isBefore(rangeTimes.get(0))) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + FilterInterviewTimeCommand.WRONG_INTERVIEW_TIME_RANGE_MESSAGE)); + } + interviewTimes.add(rangeTimes); + } + } + return interviewTimes; + } + private static List> handleBeforeInterviewTime(ArgumentMultimap argMultimap) + throws ParseException { + List beforeInterviewTimes = argMultimap.getAllValues(PREFIX_BEFORE_INTERVIEW_TIME); + List> interviewTimes = new ArrayList<>(); + for (String beforeInterviewTime : beforeInterviewTimes) { + interviewTimes.add(Arrays.asList(null, ParserUtil.parseInterviewTime(beforeInterviewTime))); + } + return interviewTimes; + } + private static List> handleAfterInterviewTime(ArgumentMultimap argMultimap) + throws ParseException { + List afterInterviewTimes = argMultimap.getAllValues(PREFIX_AFTER_INTERVIEW_TIME); + List> interviewTimes = new ArrayList<>(); + for (String afterInterviewTime : afterInterviewTimes) { + interviewTimes.add(Arrays.asList(ParserUtil.parseInterviewTime(afterInterviewTime), null)); + } + return interviewTimes; + } + +} diff --git a/src/main/java/seedu/address/logic/parser/FilterProgrammingLanguageCommandParser.java b/src/main/java/seedu/address/logic/parser/FilterProgrammingLanguageCommandParser.java new file mode 100644 index 00000000000..c4e0c36d452 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/FilterProgrammingLanguageCommandParser.java @@ -0,0 +1,43 @@ +package seedu.address.logic.parser; + +import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import java.util.ArrayList; +import java.util.List; + +import seedu.address.logic.commands.FilterProgrammingLanguageCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.language.ProgrammingLanguage; +import seedu.address.model.language.ProgrammingLanguageContainsKeywordsPredicate; + +/** + * Parses input arguments and creates a new FindCommand object + */ +public class FilterProgrammingLanguageCommandParser implements Parser { + /** + * Parses the given {@code String} of arguments in the context of the FilterProgrammingLanguageCommand + * and returns a FilterProgrammingLanguageCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public FilterProgrammingLanguageCommand parse(String args) throws ParseException { + String trimmedArgs = args.trim(); + if (trimmedArgs.isEmpty()) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, FilterProgrammingLanguageCommand.MESSAGE_USAGE)); + } + String[] languageKeywords = trimmedArgs.split("\\s+"); + return new FilterProgrammingLanguageCommand( + new ProgrammingLanguageContainsKeywordsPredicate(createLanguages(languageKeywords))); + } + + /** + * Parses {@code languageKeywords} into a {@code List}. + */ + public static List createLanguages(String... languageKeywords) throws ParseException { + List languages = new ArrayList<>(); + for (String keyword : languageKeywords) { + languages.add(ParserUtil.parseProgrammingLanguage(keyword.toLowerCase())); // Convert to lowercase + } + return languages; + } +} diff --git a/src/main/java/seedu/address/logic/parser/FilterSalaryCommandParser.java b/src/main/java/seedu/address/logic/parser/FilterSalaryCommandParser.java new file mode 100644 index 00000000000..d1e8e17de34 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/FilterSalaryCommandParser.java @@ -0,0 +1,38 @@ +package seedu.address.logic.parser; + +import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import java.util.Arrays; + +import seedu.address.logic.commands.FilterSalaryCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.person.SalaryContainsKeywordsPredicate; +import seedu.address.model.person.SalaryRange; + + +/** + * Parses input arguments and creates a new FindSalaryCommand object + */ +public class FilterSalaryCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the FilterSalaryCommand + * and returns a FilterSalaryCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public FilterSalaryCommand parse(String args) throws ParseException { + String trimmedArgs = args.trim(); + if (trimmedArgs.isEmpty()) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, FilterSalaryCommand.MESSAGE_USAGE)); + } + + String[] salaryKeywords = trimmedArgs.split("\\s+"); + SalaryRange[] salaries = new SalaryRange[salaryKeywords.length]; + for (int i = 0; i < salaryKeywords.length; i++) { + salaries[i] = ParserUtil.parseSalaryRange(salaryKeywords[i]); + } + return new FilterSalaryCommand(new SalaryContainsKeywordsPredicate(Arrays.asList(salaries))); + } + +} diff --git a/src/main/java/seedu/address/logic/parser/FilterTagCommandParser.java b/src/main/java/seedu/address/logic/parser/FilterTagCommandParser.java new file mode 100644 index 00000000000..da3a5a01bfb --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/FilterTagCommandParser.java @@ -0,0 +1,45 @@ +package seedu.address.logic.parser; + +import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import java.util.ArrayList; +import java.util.List; + +import seedu.address.logic.commands.FilterTagCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.tag.Tag; +import seedu.address.model.tag.TagContainsKeywordsPredicate; + + +/** + * Parses input arguments and creates a new FilterTagCommand object + */ +public class FilterTagCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the FilterTagCommand + * and returns a FilterTagCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public FilterTagCommand parse(String args) throws ParseException { + String trimmedArgs = args.trim(); + if (trimmedArgs.isEmpty()) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, FilterTagCommand.MESSAGE_USAGE)); + } + String[] tagKeywords = trimmedArgs.split("\\s+"); + return new FilterTagCommand(new TagContainsKeywordsPredicate(createTags(tagKeywords))); + } + + /** + * Parses {@code tagKeywords} into a {@code List}. + */ + public static List createTags(String... tagKeywords) throws ParseException { + List tags = new ArrayList<>(); + for (String keyword : tagKeywords) { + tags.add(ParserUtil.parseTag(keyword)); + } + return tags; + } + +} diff --git a/src/main/java/seedu/address/logic/parser/FindCommandParser.java b/src/main/java/seedu/address/logic/parser/FindCommandParser.java index 2867bde857b..a2b71408413 100644 --- a/src/main/java/seedu/address/logic/parser/FindCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/FindCommandParser.java @@ -6,7 +6,7 @@ import seedu.address.logic.commands.FindCommand; import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.person.NameContainsKeywordsPredicate; +import seedu.address.model.person.NameOrCompanyNameContainsKeywordsPredicate; /** * Parses input arguments and creates a new FindCommand object @@ -25,9 +25,10 @@ public FindCommand parse(String args) throws ParseException { String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE)); } - String[] nameKeywords = trimmedArgs.split("\\s+"); + String[] nameOrCompanyNameKeywords = trimmedArgs.split("\\s+"); - return new FindCommand(new NameContainsKeywordsPredicate(Arrays.asList(nameKeywords))); + return new FindCommand(new NameOrCompanyNameContainsKeywordsPredicate( + Arrays.asList(nameOrCompanyNameKeywords))); } } diff --git a/src/main/java/seedu/address/logic/parser/ParserUtil.java b/src/main/java/seedu/address/logic/parser/ParserUtil.java index b117acb9c55..0d4c520219f 100644 --- a/src/main/java/seedu/address/logic/parser/ParserUtil.java +++ b/src/main/java/seedu/address/logic/parser/ParserUtil.java @@ -9,10 +9,17 @@ import seedu.address.commons.core.index.Index; import seedu.address.commons.util.StringUtil; import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.language.ProgrammingLanguage; import seedu.address.model.person.Address; +import seedu.address.model.person.CompanyName; import seedu.address.model.person.Email; +import seedu.address.model.person.Info; +import seedu.address.model.person.InterviewTime; import seedu.address.model.person.Name; import seedu.address.model.person.Phone; +import seedu.address.model.person.Salary; +import seedu.address.model.person.SalaryRange; +import seedu.address.model.person.user.Education; import seedu.address.model.tag.Tag; /** @@ -35,6 +42,36 @@ public static Index parseIndex(String oneBasedIndex) throws ParseException { return Index.fromOneBased(Integer.parseInt(trimmedIndex)); } + /** + * Parses a {@code String name} into a {@code Name}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code name} is invalid. + */ + public static CompanyName parseCompanyName(String companyName) throws ParseException { + requireNonNull(companyName); + String trimmedName = companyName.trim(); + if (!CompanyName.isValidCompanyName(trimmedName)) { + throw new ParseException(CompanyName.MESSAGE_CONSTRAINTS); + } + return new CompanyName(trimmedName); + } + + /** + * Parses a {@code String name} into a {@code Name}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code name} is invalid. + */ + public static Education parseEducation(String education) throws ParseException { + requireNonNull(education); + String trimmedEducation = education.trim(); + if (!Education.isValidEducation(trimmedEducation)) { + throw new ParseException(Education.MESSAGE_CONSTRAINTS); + } + return new Education(education); + } + /** * Parses a {@code String name} into a {@code Name}. * Leading and trailing whitespaces will be trimmed. @@ -110,6 +147,24 @@ public static Tag parseTag(String tag) throws ParseException { return new Tag(trimmedTag); } + /** + * Reads the input for interview-time tag + * @param dateTime given + * @return trimmed output to be stored + * @throws ParseException if invalid format + */ + public static InterviewTime parseInterviewTime(String dateTime) throws ParseException { + if (dateTime == null) { + return new InterviewTime(null); + } else { + String trimmedDateTime = dateTime.trim(); + if (!InterviewTime.isValidInterviewTime(trimmedDateTime)) { + throw new ParseException(InterviewTime.MESSAGE_CONSTRAINTS); + } + return new InterviewTime(trimmedDateTime); + } + } + /** * Parses {@code Collection tags} into a {@code Set}. */ @@ -121,4 +176,107 @@ public static Set parseTags(Collection tags) throws ParseException } return tagSet; } + + /** + * Parses a {@code String salary} into an {@code Salary}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code salary} is invalid. + */ + public static Salary parseSalary(String salary) throws ParseException { + requireNonNull(salary); + String trimmedSalary = salary.trim(); + if (!Salary.isValidSalary(trimmedSalary)) { + throw new ParseException(Salary.MESSAGE_CONSTRAINTS); + } + return new Salary(trimmedSalary); + } + + /** + * Parses a {@code String salary range} into an {@code Salary}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code salary range} is invalid. + */ + public static SalaryRange parseSalaryRange(String salaryRange) throws ParseException { + requireNonNull(salaryRange); + String trimmedSalaryRange = salaryRange.trim(); + if (!SalaryRange.isValidSalaryRange(trimmedSalaryRange)) { + throw new ParseException(SalaryRange.MESSAGE_CONSTRAINTS); + } + return new SalaryRange(trimmedSalaryRange); + } + + /** + * Parses a {@code String info} into an {@code Info}. + * Leading and trailing whitespaces will be trimmed. + */ + public static Info parseInfo(String info) { + String trimmedInfo = info.trim(); + return new Info(trimmedInfo); + } + + /** + * Parses a string representing a programming language into a {@code ProgrammingLanguage} object. + * Leading and trailing whitespaces will be trimmed. + * + * @param programmingLanguageString A string representing a programming language. + * @return A {@code ProgrammingLanguage} object parsed from the input string. + * @throws ParseException if the given {@code programmingLanguageString} is invalid. + */ + public static ProgrammingLanguage parseProgrammingLanguage(String programmingLanguageString) throws ParseException { + requireNonNull(programmingLanguageString); + String trimmedProgrammingLanguage = programmingLanguageString.trim(); + if (!ProgrammingLanguage.isValidLanguageName(trimmedProgrammingLanguage)) { + throw new ParseException(ProgrammingLanguage.MESSAGE_CONSTRAINTS); + } + return new ProgrammingLanguage(trimmedProgrammingLanguage); + } + + /** + * Parses collection of strings representing programming languages into set of {@code ProgrammingLanguage} objects. + * + * @param programmingLanguages A collection of strings representing programming languages. + * @return A set of {@code ProgrammingLanguage} objects parsed from the input collection. + * @throws ParseException if any of the programming language strings are invalid. + */ + public static Set parseProgrammingLanguages(Collection programmingLanguages) + throws ParseException { + requireNonNull(programmingLanguages); + final Set programmingLanguageSet = new HashSet<>(); + for (String programmingLanguage : programmingLanguages) { + programmingLanguageSet.add(parseProgrammingLanguage(programmingLanguage)); + } + return programmingLanguageSet; + } + + /** + * Parses a string representing a priority into an integer. + * Leading and trailing whitespaces will be trimmed. + * + * @param priority A string representing a priority. + * @return An integer parsed from the input string. + * @throws ParseException if the given {@code priority} is invalid. + */ + public static Integer parsePriority(String priority) throws ParseException { + requireNonNull(priority); + String trimmedPriority = priority.trim(); + if (!trimmedPriority.matches("\\d+")) { + throw new ParseException("Priority should be a number between 0 and 4."); + } + int priorityValue = Integer.parseInt(trimmedPriority); + if (priorityValue < 0 || priorityValue > 4) { + throw new ParseException("Priority should be a number between 0 and 4."); + } + return priorityValue; + } + + /** + * Returns true if a given string is a valid priority. + */ + private static boolean isValidPriority(String test) { + String validRegex = "^[0-4]$"; + return test.matches(validRegex); + } + } diff --git a/src/main/java/seedu/address/logic/parser/SortCommandParser.java b/src/main/java/seedu/address/logic/parser/SortCommandParser.java new file mode 100644 index 00000000000..b42d922abe4 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/SortCommandParser.java @@ -0,0 +1,81 @@ +package seedu.address.logic.parser; + +import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_COMPANY_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_INTERVIEWTIME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_JOB_DIFFICULTY; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PRIORITY; +import static seedu.address.logic.parser.CliSyntax.PREFIX_SALARY; + +import java.util.Objects; + +import seedu.address.logic.commands.SortCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a new SortCommand object + */ +public class SortCommandParser implements Parser { + public static final String MESSAGE_INVALID_COMMAND_LENGTH = "Invalid command length for sort command" + + "sort command only accepts 1 arguments"; + public static final String MESSAGE_INVALID_COMMAND_KEYWORD = "Invalid command keyword for sort command"; + public static final String MESSAGE_INVALID_INVERSE_COMMAND_KEYWORD = "Invalid command keyword for sort command, " + + "more than 1 keywords detected or do you mean [rev/] [keyword]?"; + private boolean isReverseOrder = false; + /** + * Parses the given {@code String} of arguments in the context of the SortCommand + * and returns a SortCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public SortCommand parse(String args) throws ParseException { + Integer index = null; + String trimmedArgs = args.trim(); + if (trimmedArgs.isEmpty()) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, SortCommand.MESSAGE_USAGE)); + } + + String[] inputString = trimmedArgs.split("\\s+"); + + if (inputString.length == 1) { + isReverseOrder = false; + } else if (inputString.length == 2) { + if (inputString[0].equalsIgnoreCase("rev/")) { + isReverseOrder = true; + } else { + throw new ParseException( + String.format(MESSAGE_INVALID_INVERSE_COMMAND_KEYWORD, SortCommand.MESSAGE_USAGE)); + } + } else { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_LENGTH, SortCommand.MESSAGE_USAGE)); + } + + String keyword = null; + + if (isReverseOrder) { + keyword = inputString[1]; + } else { + keyword = inputString[0]; + } + + if (Objects.equals(keyword, PREFIX_PRIORITY.getPrefix())) { + index = 0; + } else if (Objects.equals(keyword, PREFIX_COMPANY_NAME.getPrefix())) { + index = 1; + } else if (Objects.equals(keyword, PREFIX_NAME.getPrefix())) { + index = 2; + } else if (Objects.equals(keyword, PREFIX_INTERVIEWTIME.getPrefix())) { + index = 3; + } else if (Objects.equals(keyword, PREFIX_SALARY.getPrefix())) { + index = 4; + } else if (Objects.equals(keyword, PREFIX_JOB_DIFFICULTY.getPrefix())) { + index = 5; + } else { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_KEYWORD, SortCommand.MESSAGE_USAGE)); + } + return new SortCommand(index, isReverseOrder); + } +} diff --git a/src/main/java/seedu/address/model/AddressBook.java b/src/main/java/seedu/address/model/AddressBook.java index 73397161e84..59e123dbc2f 100644 --- a/src/main/java/seedu/address/model/AddressBook.java +++ b/src/main/java/seedu/address/model/AddressBook.java @@ -2,6 +2,7 @@ import static java.util.Objects.requireNonNull; +import java.util.Comparator; import java.util.List; import javafx.collections.ObservableList; @@ -86,6 +87,10 @@ public void setPerson(Person target, Person editedPerson) { persons.setPerson(target, editedPerson); } + public void sortPerosns(Comparator comparator) { + persons.sortPersonsList(comparator); + } + /** * Removes {@code key} from this {@code AddressBook}. * {@code key} must exist in the address book. diff --git a/src/main/java/seedu/address/model/Model.java b/src/main/java/seedu/address/model/Model.java index d54df471c1f..2c07bdf5c48 100644 --- a/src/main/java/seedu/address/model/Model.java +++ b/src/main/java/seedu/address/model/Model.java @@ -1,6 +1,7 @@ package seedu.address.model; import java.nio.file.Path; +import java.util.Comparator; import java.util.function.Predicate; import javafx.collections.ObservableList; @@ -84,4 +85,10 @@ public interface Model { * @throws NullPointerException if {@code predicate} is null. */ void updateFilteredPersonList(Predicate predicate); + + /** + * Updates the filter of the filtered person list to filter by the given {@code Prefix}. + * @throws NullPointerException if {@code Prefix} is null. + */ + void updateSortedPersonList(Comparator comparator); } diff --git a/src/main/java/seedu/address/model/ModelManager.java b/src/main/java/seedu/address/model/ModelManager.java index 57bc563fde6..1142ad48de1 100644 --- a/src/main/java/seedu/address/model/ModelManager.java +++ b/src/main/java/seedu/address/model/ModelManager.java @@ -4,11 +4,13 @@ import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; import java.nio.file.Path; +import java.util.Comparator; import java.util.function.Predicate; import java.util.logging.Logger; import javafx.collections.ObservableList; import javafx.collections.transformation.FilteredList; +import javafx.collections.transformation.SortedList; import seedu.address.commons.core.GuiSettings; import seedu.address.commons.core.LogsCenter; import seedu.address.model.person.Person; @@ -22,6 +24,7 @@ public class ModelManager implements Model { private final AddressBook addressBook; private final UserPrefs userPrefs; private final FilteredList filteredPersons; + private final SortedList sortedPersons; /** * Initializes a ModelManager with the given addressBook and userPrefs. @@ -34,6 +37,7 @@ public ModelManager(ReadOnlyAddressBook addressBook, ReadOnlyUserPrefs userPrefs this.addressBook = new AddressBook(addressBook); this.userPrefs = new UserPrefs(userPrefs); filteredPersons = new FilteredList<>(this.addressBook.getPersonList()); + sortedPersons = new SortedList<>(this.addressBook.getPersonList()); } public ModelManager() { @@ -127,6 +131,12 @@ public void updateFilteredPersonList(Predicate predicate) { requireNonNull(predicate); filteredPersons.setPredicate(predicate); } + //=========== Sorted Person List Accessors ============================================================= + @Override + public void updateSortedPersonList(Comparator info) { + requireAllNonNull(info); + addressBook.sortPerosns(info); + } @Override public boolean equals(Object other) { @@ -142,7 +152,12 @@ public boolean equals(Object other) { ModelManager otherModelManager = (ModelManager) other; return addressBook.equals(otherModelManager.addressBook) && userPrefs.equals(otherModelManager.userPrefs) - && filteredPersons.equals(otherModelManager.filteredPersons); + && filteredPersons.equals(otherModelManager.filteredPersons) + && sortedPersons.equals(otherModelManager.sortedPersons); } + @Override + public String toString() { + return String.format("%s, %s, %s", this.addressBook, this.userPrefs, this.filteredPersons, this.sortedPersons); + } } diff --git a/src/main/java/seedu/address/model/UserPrefs.java b/src/main/java/seedu/address/model/UserPrefs.java index 6be655fb4c7..9052661d549 100644 --- a/src/main/java/seedu/address/model/UserPrefs.java +++ b/src/main/java/seedu/address/model/UserPrefs.java @@ -14,7 +14,7 @@ public class UserPrefs implements ReadOnlyUserPrefs { private GuiSettings guiSettings = new GuiSettings(); - private Path addressBookFilePath = Paths.get("data" , "addressbook.json"); + private Path addressBookFilePath = Paths.get("data" , "ccbot.json"); /** * Creates a {@code UserPrefs} with default values. diff --git a/src/main/java/seedu/address/model/language/ProgrammingLanguage.java b/src/main/java/seedu/address/model/language/ProgrammingLanguage.java new file mode 100644 index 00000000000..f1989718a0a --- /dev/null +++ b/src/main/java/seedu/address/model/language/ProgrammingLanguage.java @@ -0,0 +1,77 @@ +package seedu.address.model.language; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +/** + * Represents a Programming Language in the address book. + * Guarantees: immutable; name is valid as declared in {@link #isValidLanguageName(String)} + */ +public class ProgrammingLanguage { + + public static final String MESSAGE_CONSTRAINTS = + "Programming Languages should be alphanumeric and may contain some special characters (+ and #)" + + ", and must be less than 50 characters"; + public static final String VALIDATION_REGEX = "[\\p{Alnum}+#\\s]{1,50}"; + public final String languageName; + + /** + * Constructs a {@code ProgrammingLanguage}. + * + * @param languageName A valid programming language name. + */ + public ProgrammingLanguage(String languageName) { + requireNonNull(languageName); + checkArgument(isValidLanguageName(languageName), MESSAGE_CONSTRAINTS); + this.languageName = languageName; + } + + /** + * Returns the name of this programming language. + * + * @return The name of this programming language. + */ + public String getLanguageName() { + return languageName; + } + + /** + * Returns true if a given string is a valid programming language name. + * + * @param test The string to test for validity. + * @return {@code true} if the string is a valid programming language name, {@code false} otherwise. + */ + public static boolean isValidLanguageName(String test) { + return test.matches(VALIDATION_REGEX); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof ProgrammingLanguage)) { + return false; + } + + ProgrammingLanguage otherLanguage = (ProgrammingLanguage) other; + return languageName.equals(otherLanguage.languageName); + } + + @Override + public int hashCode() { + return languageName.hashCode(); + } + + /** + * Returns the string representation of this programming language. + * + * @return The string representation of this programming language. + */ + @Override + public String toString() { + return "[" + languageName + "]"; + } +} diff --git a/src/main/java/seedu/address/model/language/ProgrammingLanguageContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/language/ProgrammingLanguageContainsKeywordsPredicate.java new file mode 100644 index 00000000000..e1d6690aecc --- /dev/null +++ b/src/main/java/seedu/address/model/language/ProgrammingLanguageContainsKeywordsPredicate.java @@ -0,0 +1,66 @@ +package seedu.address.model.language; + +import static java.util.Objects.requireNonNull; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.model.person.Person; + +/** + * Tests that a {@code Person}'s {@code Tag} matches any of the keywords given. + */ +public class ProgrammingLanguageContainsKeywordsPredicate implements Predicate { + private final List originalLanguages; + private final List lowercaseLanguageNames; + /** + * Constructs a {@code ProgrammingLanguageContainsKeywordsPredicate} with the given list of programming languages. + * Creates two lists: one to store the original ProgrammingLanguage objects and another to store their lowercase + * string representations. + * + * @param languages The list of programming languages to be used in the predicate. + * @throws NullPointerException if {@code languages} is null. + */ + public ProgrammingLanguageContainsKeywordsPredicate(List languages) { + requireNonNull(languages); + this.originalLanguages = new ArrayList<>(languages); + this.lowercaseLanguageNames = languages.stream() + .map(ProgrammingLanguage::getLanguageName) + .map(String::toLowerCase) + .collect(Collectors.toList()); + } + + @Override + public boolean test(Person person) { + Set personProgrammingLanguages = person.getProgrammingLanguages(); + return personProgrammingLanguages.stream() + .map(ProgrammingLanguage::getLanguageName) + .map(String::toLowerCase) + .anyMatch(lowercaseLanguageNames::contains); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof ProgrammingLanguageContainsKeywordsPredicate)) { + return false; + } + + ProgrammingLanguageContainsKeywordsPredicate otherProgrammingLanguageContainsKeywordsPredicate = + (ProgrammingLanguageContainsKeywordsPredicate) other; + return originalLanguages.equals(otherProgrammingLanguageContainsKeywordsPredicate.originalLanguages); + } + + @Override + public String toString() { + return new ToStringBuilder(this).add("programming_language", originalLanguages).toString(); + } +} diff --git a/src/main/java/seedu/address/model/person/CompanyName.java b/src/main/java/seedu/address/model/person/CompanyName.java new file mode 100644 index 00000000000..2a18f8a5518 --- /dev/null +++ b/src/main/java/seedu/address/model/person/CompanyName.java @@ -0,0 +1,67 @@ +package seedu.address.model.person; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +/** + * Represents a Company's name in the address book. + * Guarantees: immutable; is valid as declared in {@link #isValidCompanyName(String)} + */ +public class CompanyName { + + public static final String MESSAGE_CONSTRAINTS = + "Company names should be less than 100 chracters, and it should not be blank"; + + /* + * The first character of the address must not be a whitespace, + * otherwise " " (a blank string) becomes a valid input. + */ + public static final String VALIDATION_REGEX = "[^\\s].*"; + + public final String companyName; + + /** + * Constructs a {@code CompanyName}. + * + * @param name A valid name. + */ + public CompanyName(String name) { + requireNonNull(name); + checkArgument(isValidCompanyName(name), MESSAGE_CONSTRAINTS); + companyName = name; + } + + /** + * Returns true if a given string is a valid company name. + */ + public static boolean isValidCompanyName(String test) { + return test.length() <= 100 && test.matches(VALIDATION_REGEX); + } + + + @Override + public String toString() { + return companyName; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof CompanyName)) { + return false; + } + + CompanyName otherName = (CompanyName) other; + return companyName.equals(otherName.companyName); + } + + @Override + public int hashCode() { + return companyName.hashCode(); + } + +} diff --git a/src/main/java/seedu/address/model/person/Info.java b/src/main/java/seedu/address/model/person/Info.java new file mode 100644 index 00000000000..cc83b63d050 --- /dev/null +++ b/src/main/java/seedu/address/model/person/Info.java @@ -0,0 +1,65 @@ +package seedu.address.model.person; + +/** + * Represents a Person's info in the address book. + */ +public class Info { + public final String value; + + /** + * Constructs a {@code Info}. + * + * @param info Information about the person in the address book + */ + public Info(String info) { + String curr = info.trim(); + if (curr.length() == 0) { + value = "No additional info"; + } else { + value = curr; + } + } + + + public Info() { + value = "No additional info"; + } + + public static boolean isValidInfo(String test) { + return true; + } + + public String getInfo() { + return value; + } + + @Override + public String toString() { + + if (value.length() == 0) { + return "No additional info"; + } + return value; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof Info)) { + return false; + } + + Info otherInfo = (Info) other; + return value.equals(otherInfo.value); + } + + @Override + public int hashCode() { + return value.hashCode(); + } + +} diff --git a/src/main/java/seedu/address/model/person/InterviewTime.java b/src/main/java/seedu/address/model/person/InterviewTime.java new file mode 100644 index 00000000000..c3a643b04b2 --- /dev/null +++ b/src/main/java/seedu/address/model/person/InterviewTime.java @@ -0,0 +1,124 @@ +package seedu.address.model.person; + +import static seedu.address.commons.util.AppUtil.checkArgument; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; +import java.util.List; +import java.util.Locale; + +/** + * Logic to interpret date and time from user input + */ +public class InterviewTime { + + public static final String MESSAGE_CONSTRAINTS = "Error thrown, date and time format may be wrong. Check UG."; + + public static final String REGEX_YYYY = "\"([1-9]\\\\d{3}|0\\\\d{3})\""; + public static final String REGEX_DD = "(0[1-9]|[1-2][0-9]|3[01])"; + public static final String REGEX_MM = "(0[1-9]|1[0-2])"; + public static final String REGEX_HHMM = "^([0-1][0-9]|2[0-3])[0-5][0-9]$"; + public static final String INTERVIEW_TIME_FORMAT = "ddMMyyyyHHmm"; + public static final String EXAMPLE_INTERVIEW_TIME_FORMAT_1 = "020520240800"; + public static final String EXAMPLE_INTERVIEW_TIME_FORMAT_2 = "030820242300"; + private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern(INTERVIEW_TIME_FORMAT); //set format + + public final LocalDateTime dateTime; + + /** + * Constructs InterviewTime object + * @param dateTime input + */ + public InterviewTime(String dateTime) { + if (dateTime == null) { + this.dateTime = null; + } else { + checkArgument(isValidInterviewTime(dateTime), MESSAGE_CONSTRAINTS); + this.dateTime = LocalDateTime.parse(dateTime, formatter); //set format + } + } + + /** + * Checks if date input is valid + * @param test input + * @return true if correct format, false otherwise + */ + public static boolean isValidInterviewTime(String test) { + if (test == null) { + return true; + } else { + try { + LocalDateTime.parse(test, formatter); + return true; + } catch (DateTimeParseException e) { + return false; + } + } + } + + public LocalDateTime getDateTime() { + return this.dateTime; + } + + public String rawToString() { + return dateTime == null ? null : dateTime.format(formatter); + } + + /** + * Returns true if {@code this.dateTime} is within the range provided. + * @param range A List containing two {@code InterviewTime}. + */ + public boolean isWithinInterviewTimeRange(List range) { + assert range.size() == 2 : "InterviewTime range should be of size 2"; + assert !(range.get(0) == null && range.get(1) == null) + : "InterviewTime range cannot cannot contain more than 1 null Object"; + LocalDateTime after = range.get(0) == null ? null : range.get(0).getDateTime(); + LocalDateTime before = range.get(1) == null ? null : range.get(1).getDateTime(); + if (after == null) { + return this.dateTime.isBefore(before); + } else if (before == null) { + return this.dateTime.isAfter(after); + } else { + return (this.dateTime.isBefore(before) || this.dateTime.isEqual(before)) + && (this.dateTime.isAfter(after) || this.dateTime.isEqual(after)); + } + } + + public boolean isBefore(InterviewTime date) { + return this.dateTime.isBefore(date.getDateTime()); + } + + @Override + public String toString() { + if (dateTime == null) { + return "No Interviews set"; + } else { + DateTimeFormatter beautify = DateTimeFormatter.ofPattern("MMMM dd, yyyy hh:mm a", Locale.ENGLISH); + return dateTime.format(beautify); + } + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof InterviewTime)) { + return false; + } + + InterviewTime otherDateTime = (InterviewTime) other; + return (dateTime == null && otherDateTime.dateTime == null) + || (dateTime != null && dateTime.equals(otherDateTime.dateTime)); + } + + @Override + public int hashCode() { + return dateTime == null ? 0 : dateTime.hashCode(); + } + + +} diff --git a/src/main/java/seedu/address/model/person/InterviewTimeContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/person/InterviewTimeContainsKeywordsPredicate.java new file mode 100644 index 00000000000..435477f6492 --- /dev/null +++ b/src/main/java/seedu/address/model/person/InterviewTimeContainsKeywordsPredicate.java @@ -0,0 +1,47 @@ +package seedu.address.model.person; + +import java.util.List; +import java.util.function.Predicate; + +import seedu.address.commons.util.ToStringBuilder; + + + +/** + * Tests that a {@code Person}'s {@code Name} matches any of the keywords given. + */ +public class InterviewTimeContainsKeywordsPredicate implements Predicate { + private final List> keywords; + + public InterviewTimeContainsKeywordsPredicate(List> keywords) { + this.keywords = keywords; + } + + @Override + public boolean test(Person person) { + return keywords.stream() + .anyMatch(interviewTimeRange -> person.getDateTime().isWithinInterviewTimeRange(interviewTimeRange)); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof InterviewTimeContainsKeywordsPredicate)) { + return false; + } + + InterviewTimeContainsKeywordsPredicate otherSalaryContainsKeywordsPredicate = + (InterviewTimeContainsKeywordsPredicate) other; + return keywords.equals(otherSalaryContainsKeywordsPredicate.keywords); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("interviewTimes", keywords).toString(); + } +} diff --git a/src/main/java/seedu/address/model/person/JobDifficulty.java b/src/main/java/seedu/address/model/person/JobDifficulty.java new file mode 100644 index 00000000000..7112f62c122 --- /dev/null +++ b/src/main/java/seedu/address/model/person/JobDifficulty.java @@ -0,0 +1,53 @@ +package seedu.address.model.person; + +import java.util.Random; + +/** + * Calculate the difficulty of a job in the address book. + */ +public class JobDifficulty { + private int difficulty = 0; + + /** + * Constructs a {@code JobDifficulty}. + * The difficulty is calculated based on the company name and salary. + * + * @param companyName A valid company name. + * @param salary A valid salary. + */ + public JobDifficulty(CompanyName companyName, Salary salary) { + Random random = new Random(); + String companyNameString = companyName.toString().toLowerCase(); + int salaryInt = salary.getSalary1(); + if (JobDifficultyCompanyList.getJobDifficultyCompanyList().containsKey(companyNameString)) { + difficulty += JobDifficultyCompanyList.getJobDifficultyCompanyList().get(companyNameString); + } else { + difficulty += random.nextInt(30); + } + if (salaryInt < 1000) { + difficulty += random.nextInt(5); + } else if (salaryInt <= 1500) { + difficulty += 5 + random.nextInt(15); + } else if (salaryInt <= 3000) { + difficulty += 20 + random.nextInt(10); + } else if (salaryInt <= 4000) { + difficulty += 30 + random.nextInt(10); + } else if (salaryInt <= 5000) { + difficulty += 40 + random.nextInt(20); + } else { + difficulty += 60 + random.nextInt(20); + } + if (difficulty > 100) { + difficulty = 100; + } + } + + public int getDifficulty() { + return difficulty; + } + + @Override + public String toString() { + return Integer.toString(difficulty); + } +} diff --git a/src/main/java/seedu/address/model/person/JobDifficultyCompanyList.java b/src/main/java/seedu/address/model/person/JobDifficultyCompanyList.java new file mode 100644 index 00000000000..166e5d4726f --- /dev/null +++ b/src/main/java/seedu/address/model/person/JobDifficultyCompanyList.java @@ -0,0 +1,127 @@ +package seedu.address.model.person; + +import java.util.HashMap; + +/** + * Represents a list of companies and their corresponding job difficulty. + */ +public class JobDifficultyCompanyList { + /** + * A list of companies and their corresponding job difficulty. + */ + public static final HashMap JOBDIFFCULTYLIST = new HashMap() { + { + put("google", 70); + put("googles", 70); + put("facebook", 70); + put("amazon", 70); + put("apple", 70); + put("apples", 70); + put("microsoft", 70); + put("netflix", 70); + put("twitter", 70); + put("linkedIn", 70); + put("airbnb", 70); + put("uber", 70); + put("grab", 50); + put("shopee", 50); + put("lazada", 50); + put("carousell", 50); + put("tikTok", 60); + put("byteDance", 50); + put("tencent", 60); + put("alibaba", 60); + put("baidu", 60); + put("jd", 60); + put("meituan-dianping", 50); + put("didichuxing", 50); + put("xiaomi", 50); + put("huawei", 50); + put("oppo", 50); + put("vivo", 50); + put("oneplus", 50); + put("realme", 50); + put("lenovo", 55); + put("asus", 55); + put("acer", 55); + put("dell", 55); + put("hp", 55); + put("sony", 65); + put("samsung", 65); + put("lg", 65); + put("nokia", 55); + put("motorola", 55); + put("ibm", 70); + put("oracle", 70); + put("sap", 70); + put("vmware", 70); + put("cisco", 70); + put("gm", 70); + put("accenture", 70); + put("tata consultancy services", 70); + put("tata", 60); + put("wipro", 60); + put("sea", 60); + put("singtel", 60); + put("tiktok", 60); + put("pg", 50); + put("p & g", 50); + put("p&g", 50); + put("sp", 50); + put("ministry of defence", 50); + put("mindef", 60); + put("changi airport group", 50); + put("cag", 60); + put("goto Group", 50); + put("goto", 50); + put("dbs", 65); + put("dbs bank", 65); + put("ocbc", 65); + put("uob", 65); + put("ncs", 55); + put("jtc", 55); + put("jtc corporation", 55); + put("temasek", 70); + put("temasek holdings", 70); + put("st engineering", 55); + put("st", 55); + put("gic", 70); + put("deloitte", 70); + put("bnp paribas", 55); + put("dsta", 60); + put("smrt", 50); + put("micron", 50); + put("micron technology", 50); + put("phillip", 55); + put("phillip securities", 55); + put("singhealth", 50); + put("jp morgan", 70); + put("bank of america", 65); + put("cognizant", 60); + put("pricewaterhouseCoopers", 70); + put("pwc", 70); + put("govtech", 60); + put("meta", 70); + put("cpf", 55); + put("imda", 60); + put("mas", 65); + put("kpmg", 70); + put("ey", 65); + put("ernst & young", 65); + put("tesla", 60); + put("razer", 50); + put("m1", 50); + put("singapore airlines", 55); + put("sia", 55); + put("synapxe", 55); + put("comarch", 55); + put("intel", 55); + put("intel corporation", 55); + put("seagate", 55); + } + }; + + public static HashMap getJobDifficultyCompanyList() { + return JOBDIFFCULTYLIST; + } +} diff --git a/src/main/java/seedu/address/model/person/NameOrCompanyNameContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/person/NameOrCompanyNameContainsKeywordsPredicate.java new file mode 100644 index 00000000000..08639f3001d --- /dev/null +++ b/src/main/java/seedu/address/model/person/NameOrCompanyNameContainsKeywordsPredicate.java @@ -0,0 +1,45 @@ +package seedu.address.model.person; + +import java.util.List; +import java.util.function.Predicate; + +import seedu.address.commons.util.StringUtil; +import seedu.address.commons.util.ToStringBuilder; + +/** + * Tests that a {@code Person}'s {@code Name} or {@code CompanyName} matches any of the keywords given. + */ +public class NameOrCompanyNameContainsKeywordsPredicate implements Predicate { + private final List keywords; + + public NameOrCompanyNameContainsKeywordsPredicate(List keywords) { + this.keywords = keywords; + } + + @Override + public boolean test(Person person) { + return keywords.stream() + .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(person.getName().fullName, keyword) + || StringUtil.containsWordIgnoreCase(person.getCompanyName().companyName, keyword)); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof NameOrCompanyNameContainsKeywordsPredicate)) { + return false; + } + + NameOrCompanyNameContainsKeywordsPredicate otherPredicate = (NameOrCompanyNameContainsKeywordsPredicate) other; + return keywords.equals(otherPredicate.keywords); + } + + @Override + public String toString() { + return new ToStringBuilder(this).add("keywords", keywords).toString(); + } +} diff --git a/src/main/java/seedu/address/model/person/Person.java b/src/main/java/seedu/address/model/person/Person.java index abe8c46b535..d4b55b50d46 100644 --- a/src/main/java/seedu/address/model/person/Person.java +++ b/src/main/java/seedu/address/model/person/Person.java @@ -8,6 +8,7 @@ import java.util.Set; import seedu.address.commons.util.ToStringBuilder; +import seedu.address.model.language.ProgrammingLanguage; import seedu.address.model.tag.Tag; /** @@ -17,26 +18,45 @@ public class Person { // Identity fields + private final CompanyName companyName; private final Name name; private final Phone phone; private final Email email; // Data fields private final Address address; + private final Salary salary; + private final Info info; private final Set tags = new HashSet<>(); - + private final InterviewTime dateTime; + private final Set programmingLanguages = new HashSet<>(); + private final Integer priority; // default priority level + private final JobDifficulty jobDifficulty; /** * Every field must be present and not null. */ - public Person(Name name, Phone phone, Email email, Address address, Set tags) { - requireAllNonNull(name, phone, email, address, tags); + + public Person( + CompanyName companyName, Name name, Phone phone, Email email, Address address, + InterviewTime dateTime, Salary salary, Info info, Set tags, + Set programmingLanguages, Integer priority) { + requireAllNonNull(name, phone, email, address, salary, tags); + this.companyName = companyName; this.name = name; this.phone = phone; this.email = email; this.address = address; + this.dateTime = dateTime; + this.salary = salary; + this.info = info; this.tags.addAll(tags); + this.programmingLanguages.addAll(programmingLanguages); + this.priority = priority; + this.jobDifficulty = new JobDifficulty(companyName, salary); + } + public CompanyName getCompanyName() { + return companyName; } - public Name getName() { return name; } @@ -53,6 +73,30 @@ public Address getAddress() { return address; } + public InterviewTime getDateTime() { + return dateTime; + } + public Salary getSalary() { + return salary; + } + public Info getInfo() { + return info; + } + public Integer getPriority() { + return priority; + } + public JobDifficulty getJobDifficulty() { + return jobDifficulty; + } + + /** + * Returns true if a given string is a valid priority level. + */ + public static boolean isValidPriority(String test) { + String validRegex = "^[0-4]$"; + return test.matches(validRegex); + } + /** * Returns an immutable tag set, which throws {@code UnsupportedOperationException} * if modification is attempted. @@ -61,6 +105,16 @@ public Set getTags() { return Collections.unmodifiableSet(tags); } + /** + * Returns an immutable set of programming languages. + * This set cannot be modified and any attempt to do so will result in an {@code UnsupportedOperationException}. + * + * @return An immutable set of programming languages. + */ + public Set getProgrammingLanguages() { + return Collections.unmodifiableSet(programmingLanguages); + } + /** * Returns true if both persons have the same name. * This defines a weaker notion of equality between two persons. @@ -90,27 +144,35 @@ public boolean equals(Object other) { } Person otherPerson = (Person) other; - return name.equals(otherPerson.name) + return companyName.equals(otherPerson.companyName) + && name.equals(otherPerson.name) && phone.equals(otherPerson.phone) && email.equals(otherPerson.email) && address.equals(otherPerson.address) - && tags.equals(otherPerson.tags); + && tags.equals(otherPerson.tags) + && programmingLanguages.equals(otherPerson.programmingLanguages); } @Override public int hashCode() { // use this method for custom fields hashing instead of implementing your own - return Objects.hash(name, phone, email, address, tags); + return Objects.hash(companyName, name, phone, email, address, tags, dateTime, programmingLanguages); } @Override public String toString() { return new ToStringBuilder(this) + .add("company name", companyName) .add("name", name) .add("phone", phone) .add("email", email) .add("address", address) + .add("interview-time", dateTime) + .add("salary", salary) + .add("info", info) .add("tags", tags) + .add("programming-languages", programmingLanguages) + .add("priority", priority) .toString(); } diff --git a/src/main/java/seedu/address/model/person/PersonCompanyNameComparator.java b/src/main/java/seedu/address/model/person/PersonCompanyNameComparator.java new file mode 100644 index 00000000000..fdc76959cce --- /dev/null +++ b/src/main/java/seedu/address/model/person/PersonCompanyNameComparator.java @@ -0,0 +1,20 @@ +package seedu.address.model.person; + +import java.util.Comparator; + +/** + * Compares two persons based on their company name. + */ +public class PersonCompanyNameComparator implements Comparator { + private boolean isReverseOrder = false; + + // Constructor that accepts a boolean parameter to set the sorting order + public PersonCompanyNameComparator(boolean isReverseOrder) { + this.isReverseOrder = isReverseOrder; + } + @Override + public int compare(Person p1, Person p2) { + int comparisonResult = p1.getCompanyName().companyName.compareTo(p2.getCompanyName().companyName); + return isReverseOrder ? -comparisonResult : comparisonResult; + } +} diff --git a/src/main/java/seedu/address/model/person/PersonInterviewTimeComparator.java b/src/main/java/seedu/address/model/person/PersonInterviewTimeComparator.java new file mode 100644 index 00000000000..f1fe12588e0 --- /dev/null +++ b/src/main/java/seedu/address/model/person/PersonInterviewTimeComparator.java @@ -0,0 +1,29 @@ +package seedu.address.model.person; + +import java.util.Comparator; + +/** + * Compares two persons based on their interview time. + */ +public class PersonInterviewTimeComparator implements Comparator { + private boolean isReverseOrder = false; + + public PersonInterviewTimeComparator(boolean isReverseOrder) { + this.isReverseOrder = isReverseOrder; + } + @Override + public int compare(Person p1, Person p2) { + if (p1.getDateTime().dateTime == null && p2.getDateTime().dateTime == null) { + return 0; // Both dates are null, retain the order + } else if (p1.getDateTime().dateTime == null) { + return isReverseOrder ? -1 : 1; // p1 has null date, so it should come after p2 + } else if (p2.getDateTime().dateTime == null) { + return isReverseOrder ? 1 : -1; // p2 has null date, so it should come after p1 + } else { + // Compare non-null dates + int comparisonResult = p1.getDateTime().dateTime.compareTo(p2.getDateTime().dateTime); + return isReverseOrder ? -comparisonResult : comparisonResult; + } + } +} + diff --git a/src/main/java/seedu/address/model/person/PersonJobDifficultyComparator.java b/src/main/java/seedu/address/model/person/PersonJobDifficultyComparator.java new file mode 100644 index 00000000000..f5c0dce2fee --- /dev/null +++ b/src/main/java/seedu/address/model/person/PersonJobDifficultyComparator.java @@ -0,0 +1,20 @@ +package seedu.address.model.person; + +import java.util.Comparator; + +/** + * Compares two persons based on their job difficulty. + */ +public class PersonJobDifficultyComparator implements Comparator { + private boolean isReverseOrder = false; + + public PersonJobDifficultyComparator(boolean isReverseOrder) { + this.isReverseOrder = isReverseOrder; + } + @Override + public int compare(Person p1, Person p2) { + int comparisonResult = Double.compare(p1.getJobDifficulty().getDifficulty(), + p2.getJobDifficulty().getDifficulty()); + return isReverseOrder ? -comparisonResult : comparisonResult; + } +} diff --git a/src/main/java/seedu/address/model/person/PersonNameComparator.java b/src/main/java/seedu/address/model/person/PersonNameComparator.java new file mode 100644 index 00000000000..8dba67ad080 --- /dev/null +++ b/src/main/java/seedu/address/model/person/PersonNameComparator.java @@ -0,0 +1,19 @@ +package seedu.address.model.person; + +import java.util.Comparator; + +/** + * Compares two persons based on their name. + */ +public class PersonNameComparator implements Comparator { + private boolean isReverseOrder = false; + + public PersonNameComparator(boolean isReverseOrder) { + this.isReverseOrder = isReverseOrder; + } + @Override + public int compare(Person p1, Person p2) { + int comparisonResult = p1.getName().fullName.compareTo(p2.getName().fullName); + return isReverseOrder ? -comparisonResult : comparisonResult; + } +} diff --git a/src/main/java/seedu/address/model/person/PersonPriorityComparator.java b/src/main/java/seedu/address/model/person/PersonPriorityComparator.java new file mode 100644 index 00000000000..20738508b27 --- /dev/null +++ b/src/main/java/seedu/address/model/person/PersonPriorityComparator.java @@ -0,0 +1,19 @@ +package seedu.address.model.person; + +import java.util.Comparator; + +/** + * Compares two persons based on their priority. + */ +public class PersonPriorityComparator implements Comparator { + private boolean isReverseOrder = false; + + public PersonPriorityComparator(boolean isReverseOrder) { + this.isReverseOrder = isReverseOrder; + } + @Override + public int compare(Person p1, Person p2) { + int comparisonResult = Integer.compare(p1.getPriority(), p2.getPriority()); + return isReverseOrder ? -comparisonResult : comparisonResult; + } +} diff --git a/src/main/java/seedu/address/model/person/PersonSalaryComparator.java b/src/main/java/seedu/address/model/person/PersonSalaryComparator.java new file mode 100644 index 00000000000..b100a40c932 --- /dev/null +++ b/src/main/java/seedu/address/model/person/PersonSalaryComparator.java @@ -0,0 +1,19 @@ +package seedu.address.model.person; + +import java.util.Comparator; + +/** + * Compares two persons based on their salary. + */ +public class PersonSalaryComparator implements Comparator { + private boolean isReverseOrder = false; + + public PersonSalaryComparator(boolean isReverseOrder) { + this.isReverseOrder = isReverseOrder; + } + @Override + public int compare(Person p1, Person p2) { + int comparisonResult = Double.compare(p2.getSalary().getSalary1(), p1.getSalary().getSalary1()); + return isReverseOrder ? -comparisonResult : comparisonResult; + } +} diff --git a/src/main/java/seedu/address/model/person/Salary.java b/src/main/java/seedu/address/model/person/Salary.java new file mode 100644 index 00000000000..494911dd345 --- /dev/null +++ b/src/main/java/seedu/address/model/person/Salary.java @@ -0,0 +1,133 @@ +package seedu.address.model.person; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + + +/** + * Represents a Person's salary in the address book. + */ +public class Salary { + public static final String MESSAGE_CONSTRAINTS = "Salaries should only contain numbers, with range [0, 2147483647] " + + "or two pure digital numbers with '-' in between. " + + "There's no space on either side of '-'." + + "Both digital numbers should be within the range [0, 2147483647]"; + public static final String VALIDATION_REGEX = "^(\\d{1,10})(-\\d{1,10})?$"; + public static final int UPPERBOUND = 2147483647; + public static final int LOWERBOUND = 0; + + private int salary1; + private int salary2; + private boolean isRange; + + /** + * Constructs a {@code Salary}. + * + * @param salary A valid salary. + */ + public Salary(String salary) { + requireNonNull(salary); + checkArgument(isValidSalary(salary), MESSAGE_CONSTRAINTS); + parseSalary(salary); + } + + /** + * Constructs a {@code Salary}. + * + * @param salary A valid salary in string format. + */ + public void parseSalary(String salary) { + if (salary.contains("-")) { + String[] salaryRange = salary.split("-"); + int salary1 = Integer.parseInt(salaryRange[0]); + int salary2 = Integer.parseInt(salaryRange[1]); + if (salary1 > salary2) { + this.salary1 = salary2; + this.salary2 = salary1; + } else { + this.salary1 = salary1; + this.salary2 = salary2; + } + this.isRange = true; + } else { + this.salary1 = Integer.parseInt(salary); + this.salary2 = Integer.parseInt(salary); + this.isRange = false; + } + } + + /** + * Returns true if a given string is a valid salary format. + */ + public static boolean isValidSalary(String test) { + if (!test.matches(VALIDATION_REGEX)) { + return false; + } + if (test.contains("-")) { + try { + String[] salaryRange = test.split("-"); + int salary1 = Integer.parseInt(salaryRange[0]); + int salary2 = Integer.parseInt(salaryRange[1]); + if (salary1 < LOWERBOUND || salary2 < LOWERBOUND) { + return false; + } + } catch (NumberFormatException e) { + return false; + } + } else { + try { + int salary = Integer.parseInt(test); + if (salary < LOWERBOUND) { + return false; + } + } catch (NumberFormatException e) { + return false; + } + } + return true; + } + + + @Override + public String toString() { + if (isRange) { + return salary1 + "-" + salary2; + } else { + return String.valueOf(salary1); + } + } + + public int getSalary1() { + return salary1; + } + + public int getSalary2() { + return salary2; + } + + public boolean isRange() { + return isRange; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof Salary)) { + return false; + } + + if (isRange != ((Salary) other).isRange) { + return false; + } + + if (isRange) { + return salary1 == ((Salary) other).salary1 && salary2 == ((Salary) other).salary2; + } else { + return salary1 == ((Salary) other).salary1; + } + } +} diff --git a/src/main/java/seedu/address/model/person/SalaryContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/person/SalaryContainsKeywordsPredicate.java new file mode 100644 index 00000000000..1dc70e42bad --- /dev/null +++ b/src/main/java/seedu/address/model/person/SalaryContainsKeywordsPredicate.java @@ -0,0 +1,44 @@ +package seedu.address.model.person; + +import java.util.List; +import java.util.function.Predicate; + +import seedu.address.commons.util.ToStringBuilder; + + +/** + * Tests that a {@code Person}'s {@code Name} matches any of the keywords given. + */ +public class SalaryContainsKeywordsPredicate implements Predicate { + private final List keywords; + + public SalaryContainsKeywordsPredicate(List keywords) { + this.keywords = keywords; + } + + @Override + public boolean test(Person person) { + return keywords.stream() + .anyMatch(salaryRange -> salaryRange.isWithinSalaryRange(person.getSalary())); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof SalaryContainsKeywordsPredicate)) { + return false; + } + + SalaryContainsKeywordsPredicate otherSalaryContainsKeywordsPredicate = (SalaryContainsKeywordsPredicate) other; + return keywords.equals(otherSalaryContainsKeywordsPredicate.keywords); + } + + @Override + public String toString() { + return new ToStringBuilder(this).add("salaries", keywords).toString(); + } +} diff --git a/src/main/java/seedu/address/model/person/SalaryRange.java b/src/main/java/seedu/address/model/person/SalaryRange.java new file mode 100644 index 00000000000..02a84abb9a8 --- /dev/null +++ b/src/main/java/seedu/address/model/person/SalaryRange.java @@ -0,0 +1,148 @@ +package seedu.address.model.person; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + + +/** + * Represents a Person's salary in the address book. + */ +public class SalaryRange { + public static final String MESSAGE_CONSTRAINTS = "SALARY_RANGE should only contain numbers, with range [0, " + + "4294967295] " + + "or two pure digital numbers with ‘-’ in between or '<=' or '>=' followed by a digital number.\n" + + "Numbers can vary from large to small or from small to large.\n" + + "Both digital numbers should be within the range [0, 2147483647]\n" + + "Examples:\n" + + "2000-4000\n" + + ">=5000\n" + + "<=10000\n"; + public static final String VALIDATION_REGEX = "^(\\d+-\\d+)|((<=|>=)\\d+)|(\\d+)$"; + public static final int UPPERBOUND = 2147483647; + public static final int LOWERBOUND = 0; + + private int maxSalary; + private int minSalary; + private boolean isRange; + private boolean isMax; + private boolean isMin; + + /** + * Constructs a {@code Salary}. + * + * @param salaryRange A valid salary range. + */ + public SalaryRange(String salaryRange) { + requireNonNull(salaryRange); + checkArgument(isValidSalaryRange(salaryRange), MESSAGE_CONSTRAINTS); + parseSalaryRange(salaryRange); + } + + /** + * Constructs a {@code Salary}. + * + * @param salaryRange A valid salary in string format. + */ + public void parseSalaryRange(String salaryRange) { + if (salaryRange.contains("-")) { + String[] range = salaryRange.split("-"); + int minSalary = Integer.parseInt(range[0]); + int maxSalary = Integer.parseInt(range[1]); + this.minSalary = Math.min(minSalary, maxSalary); + this.maxSalary = Math.max(minSalary, maxSalary); + this.isRange = true; + } else if (salaryRange.contains(">=")) { + String[] range = salaryRange.split(">="); + this.minSalary = Integer.parseInt(range[1]); + this.maxSalary = UPPERBOUND; + this.isMin = true; + } else if (salaryRange.contains("<=")) { + String[] range = salaryRange.split("<="); + this.maxSalary = Integer.parseInt(range[1]); + this.minSalary = LOWERBOUND; + this.isMax = true; + } else { + this.minSalary = Integer.parseInt(salaryRange); + this.maxSalary = Integer.parseInt(salaryRange); + this.isRange = false; + } + } + + /** + * Returns true if a given string is a valid salary format. + */ + public static boolean isValidSalaryRange(String test) { + if (!test.matches(VALIDATION_REGEX)) { + return false; + } + if (test.contains(">=")) { + return isValidMin(test); + } else if (test.contains("<=")) { + return isValidMax(test); + } else { + return Salary.isValidSalary(test); + } + } + private static boolean isValidMin(String test) { + try { + String[] salaryRange = test.split(">="); + int minSalary = Integer.parseInt(salaryRange[1]); + if (minSalary < LOWERBOUND) { + return false; + } + } catch (NumberFormatException e) { + return false; + } + return true; + } + private static boolean isValidMax(String test) { + try { + String[] salaryRange = test.split("<="); + int maxSalary = Integer.parseInt(salaryRange[1]); + if (maxSalary < LOWERBOUND) { + return false; + } + } catch (NumberFormatException e) { + return false; + } + return true; + } + + /** + * Returns true if the given Salary is within the salary range + */ + public boolean isWithinSalaryRange(Salary salaryToTest) { + SalaryRange test = new SalaryRange(salaryToTest.toString()); + boolean isMinWithinRange = this.minSalary <= test.maxSalary; + boolean isMaxWithinRange = this.maxSalary >= test.minSalary; + return isMinWithinRange && isMaxWithinRange; + } + + @Override + public String toString() { + if (isRange) { + return this.minSalary + "-" + maxSalary; + } else if (isMax) { + return "<=" + this.maxSalary; + } else if (isMin) { + return ">=" + this.minSalary; + } else { + return String.valueOf(minSalary); + } + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + // instanceof handles nulls + if (!(other instanceof SalaryRange)) { + return false; + } else { + boolean isMinSame = ((SalaryRange) other).minSalary == this.minSalary; + boolean isMaxSame = ((SalaryRange) other).maxSalary == this.maxSalary; + return isMinSame && isMaxSame; + } + } +} diff --git a/src/main/java/seedu/address/model/person/UniquePersonList.java b/src/main/java/seedu/address/model/person/UniquePersonList.java index cc0a68d79f9..448fc4dbc83 100644 --- a/src/main/java/seedu/address/model/person/UniquePersonList.java +++ b/src/main/java/seedu/address/model/person/UniquePersonList.java @@ -3,6 +3,7 @@ import static java.util.Objects.requireNonNull; import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; +import java.util.Comparator; import java.util.Iterator; import java.util.List; @@ -97,6 +98,15 @@ public void setPersons(List persons) { internalList.setAll(persons); } + /** + * Sorts the list by the given comparator. + * + * @param comparator The comparator to sort the list by. + */ + public void sortPersonsList(Comparator comparator) { + FXCollections.sort(internalList, comparator); + } + /** * Returns the backing list as an unmodifiable {@code ObservableList}. */ diff --git a/src/main/java/seedu/address/model/person/user/Education.java b/src/main/java/seedu/address/model/person/user/Education.java new file mode 100644 index 00000000000..a4f2f3ad8e1 --- /dev/null +++ b/src/main/java/seedu/address/model/person/user/Education.java @@ -0,0 +1,60 @@ +package seedu.address.model.person.user; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +/** + * Represents the user's Education, as part of the Resume + */ +public class Education { + + public static final String MESSAGE_CONSTRAINTS = + "Names should only contain alphanumeric characters and spaces, and it should not be blank"; + + /* + * The first character of the address must not be a whitespace, + * otherwise " " (a blank string) becomes a valid input. + */ + public static final String VALIDATION_REGEX = "[\\p{Alnum}][\\p{Alnum} ]*"; + + public final String education; + /** + * Constructs a {@code Education}. + * + * @param education A valid string. + */ + public Education(String education) { + requireNonNull(education); + checkArgument(isValidEducation(education), MESSAGE_CONSTRAINTS); + this.education = education; + } + /** + * Returns true if a given string is a valid name. + */ + public static boolean isValidEducation(String test) { + return test.matches(VALIDATION_REGEX); + } + @Override + public String toString() { + return education; + } + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof Education)) { + return false; + } + + Education otherName = (Education) other; + return education.equals(otherName.education); + } + + @Override + public int hashCode() { + return education.hashCode(); + } +} diff --git a/src/main/java/seedu/address/model/person/user/User.java b/src/main/java/seedu/address/model/person/user/User.java new file mode 100644 index 00000000000..de3ce551bf9 --- /dev/null +++ b/src/main/java/seedu/address/model/person/user/User.java @@ -0,0 +1,106 @@ +package seedu.address.model.person.user; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import seedu.address.model.language.ProgrammingLanguage; +import seedu.address.model.person.Address; +import seedu.address.model.person.CompanyName; +import seedu.address.model.person.Email; +import seedu.address.model.person.Name; +import seedu.address.model.person.Phone; +import seedu.address.model.person.Salary; + +/** + * Represents the user in the addressbook + */ +public class User { + + private static User user; + private CompanyName companyName; + private Name name; + private Phone phone; + private Email email; + + // Data fields + private Address address; + private Salary salary; + private Education education; + private Set skills = new HashSet<>(); + + private User() { + + } + + public static User getInstance() { + if (user == null) { + // Create a new instance if it doesn't exist + user = new User(); + } + return user; + } + + public CompanyName getCompanyName() { + return companyName; + } + public void setCompanyName(CompanyName companyName) { + this.companyName = companyName; + } + public Name getName() { + return name; + } + public void setName(Name name) { + this.name = name; + } + public Phone getPhone() { + return phone; + } + public void setPhone(Phone phone) { + this.phone = phone; + } + public Address getAddress() { + return address; + } + public void setAddress(Address address) { + this.address = address; + } + public Salary getSalary() { + return salary; + } + public void setSalary(Salary salary) { + this.salary = salary; + } + public Email getEmail() { + return email; + } + public void setEmail(Email email) { + this.email = email; + } + public Education getEducation() { + return education; + } + public void setEducation(Education education) { + this.education = education; + } + public Set getSkills() { + return Collections.unmodifiableSet(skills); + } + public void setSkills(Set skills) { + this.skills.addAll(skills); + } + + /** + * Resets the values for User singleton class + */ + public void reset() { + this.companyName = null; + this.name = null; + this.phone = null; + this.email = null; + this.address = null; + this.salary = null; + this.education = null; + this.skills = new HashSet<>(); + } +} diff --git a/src/main/java/seedu/address/model/tag/Tag.java b/src/main/java/seedu/address/model/tag/Tag.java index f1a0d4e233b..9d6c2efb32a 100644 --- a/src/main/java/seedu/address/model/tag/Tag.java +++ b/src/main/java/seedu/address/model/tag/Tag.java @@ -9,7 +9,8 @@ */ public class Tag { - public static final String MESSAGE_CONSTRAINTS = "Tags names should be alphanumeric"; + public static final String MESSAGE_CONSTRAINTS = "Should be a single alphanumerical word that does" + + " not contain spaces."; public static final String VALIDATION_REGEX = "\\p{Alnum}+"; public final String tagName; diff --git a/src/main/java/seedu/address/model/tag/TagContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/tag/TagContainsKeywordsPredicate.java new file mode 100644 index 00000000000..50a95d1c74c --- /dev/null +++ b/src/main/java/seedu/address/model/tag/TagContainsKeywordsPredicate.java @@ -0,0 +1,54 @@ +package seedu.address.model.tag; + +import java.util.List; +import java.util.Set; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +import seedu.address.model.person.Person; + +/** + * Tests that a {@code Person}'s {@code Tag} matches any of the keywords given. + */ +public class TagContainsKeywordsPredicate implements Predicate { + private final List tags; + + public TagContainsKeywordsPredicate(List tags) { + this.tags = tags; + } + + public List getTags() { + return tags; + } + + @Override + public boolean test(Person person) { + Set personTags = person.getTags(); + return tags.stream() + .anyMatch(personTags::contains); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof TagContainsKeywordsPredicate)) { + return false; + } + + TagContainsKeywordsPredicate otherTagContainsKeywordsPredicate = (TagContainsKeywordsPredicate) other; + return tags.equals(otherTagContainsKeywordsPredicate.tags); + } + + @Override + public String toString() { + List tagNames = tags.stream() + .map(Tag::toString) + .collect(Collectors.toList()); + + return String.join(", ", tagNames); + } +} diff --git a/src/main/java/seedu/address/model/util/SampleDataUtil.java b/src/main/java/seedu/address/model/util/SampleDataUtil.java index 1806da4facf..ecf591aafda 100644 --- a/src/main/java/seedu/address/model/util/SampleDataUtil.java +++ b/src/main/java/seedu/address/model/util/SampleDataUtil.java @@ -6,11 +6,16 @@ import seedu.address.model.AddressBook; import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.model.language.ProgrammingLanguage; import seedu.address.model.person.Address; +import seedu.address.model.person.CompanyName; import seedu.address.model.person.Email; +import seedu.address.model.person.Info; +import seedu.address.model.person.InterviewTime; import seedu.address.model.person.Name; import seedu.address.model.person.Person; import seedu.address.model.person.Phone; +import seedu.address.model.person.Salary; import seedu.address.model.tag.Tag; /** @@ -19,24 +24,31 @@ public class SampleDataUtil { public static Person[] getSamplePersons() { return new Person[] { - new Person(new Name("Alex Yeoh"), new Phone("87438807"), new Email("alexyeoh@example.com"), + new Person(new CompanyName("Google"), new Name("Alex Yeoh"), new Phone("87438807"), new Email("alexyeoh" + + "@example.com"), new Address("Blk 30 Geylang Street 29, #06-40"), - getTagSet("friends")), - new Person(new Name("Bernice Yu"), new Phone("99272758"), new Email("berniceyu@example.com"), - new Address("Blk 30 Lorong 3 Serangoon Gardens, #07-18"), - getTagSet("colleagues", "friends")), - new Person(new Name("Charlotte Oliveiro"), new Phone("93210283"), new Email("charlotte@example.com"), - new Address("Blk 11 Ang Mo Kio Street 74, #11-04"), - getTagSet("neighbours")), - new Person(new Name("David Li"), new Phone("91031282"), new Email("lidavid@example.com"), - new Address("Blk 436 Serangoon Gardens Street 26, #16-43"), - getTagSet("family")), - new Person(new Name("Irfan Ibrahim"), new Phone("92492021"), new Email("irfan@example.com"), - new Address("Blk 47 Tampines Street 20, #17-35"), - getTagSet("classmates")), - new Person(new Name("Roy Balakrishnan"), new Phone("92624417"), new Email("royb@example.com"), - new Address("Blk 45 Aljunied Street 85, #11-31"), - getTagSet("colleagues")) + new InterviewTime("121220221400"), new Salary("0"), new Info("Friend of boss"), + getTagSet("friends"), getProgrammingLanguageSet("Java"), 0), + new Person(new CompanyName("Google"), new Name("Bernice Yu"), new Phone("99272758"), + new Email("berniceyu@example.com"), new Address("Blk 30 Lorong 3 Serangoon Gardens, #07-18"), + new InterviewTime("121220221400"), new Salary("0"), new Info("Friend of boss"), + getTagSet("colleagues", "friends"), getProgrammingLanguageSet("Java"), 1), + new Person(new CompanyName("Google"), new Name("Charlotte Oliveiro"), new Phone("93210283"), + new Email("charlotte@example.com"), new Address("Blk 11 Ang Mo Kio Street 74, #11-04"), + new InterviewTime("121220221400"), new Salary("0"), new Info("Friend of boss"), + getTagSet("neighbours"), getProgrammingLanguageSet("Java"), 2), + new Person(new CompanyName("Google"), new Name("David Li"), new Phone("91031282"), + new Email("lidavid@example.com"), new Address("Blk 436 Serangoon Gardens Street 26, #16-43"), + new InterviewTime("121220221400"), new Salary("0"), new Info("Friend of boss"), + getTagSet("family"), getProgrammingLanguageSet("Java"), 3), + new Person(new CompanyName("Google"), new Name("Irfan Ibrahim"), new Phone("92492021"), + new Email("irfan@example.com"), new Address("Blk 47 Tampines Street 20, #17-35"), + new InterviewTime("121220221400"), new Salary("0"), new Info("Friend of boss"), + getTagSet("classmates"), getProgrammingLanguageSet("Java"), 4), + new Person(new CompanyName("Amazon"), new Name("Roy Balakrishnan"), new Phone("92624417"), + new Email("royb@example.com"), new Address("Blk 45 Aljunied Street 85, #11-31"), + new InterviewTime("121220221400"), new Salary("0"), new Info("Friend of boss"), + getTagSet("colleagues"), getProgrammingLanguageSet("Java"), 0), }; } @@ -57,4 +69,13 @@ public static Set getTagSet(String... strings) { .collect(Collectors.toSet()); } + /** + * Returns a programming language set containing the list of strings given. + */ + public static Set getProgrammingLanguageSet(String... strings) { + return Arrays.stream(strings) + .map(ProgrammingLanguage::new) + .collect(Collectors.toSet()); + } + } diff --git a/src/main/java/seedu/address/storage/JsonAdaptedPerson.java b/src/main/java/seedu/address/storage/JsonAdaptedPerson.java index bd1ca0f56c8..33b1e956671 100644 --- a/src/main/java/seedu/address/storage/JsonAdaptedPerson.java +++ b/src/main/java/seedu/address/storage/JsonAdaptedPerson.java @@ -10,11 +10,16 @@ import com.fasterxml.jackson.annotation.JsonProperty; import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.language.ProgrammingLanguage; import seedu.address.model.person.Address; +import seedu.address.model.person.CompanyName; import seedu.address.model.person.Email; +import seedu.address.model.person.Info; +import seedu.address.model.person.InterviewTime; import seedu.address.model.person.Name; import seedu.address.model.person.Person; import seedu.address.model.person.Phone; +import seedu.address.model.person.Salary; import seedu.address.model.tag.Tag; /** @@ -23,40 +28,71 @@ class JsonAdaptedPerson { public static final String MISSING_FIELD_MESSAGE_FORMAT = "Person's %s field is missing!"; + private final String companyName; private final String name; private final String phone; private final String email; private final String address; + private final String dateTime; + private final String salary; + private final String info; private final List tags = new ArrayList<>(); + private final List programmingLanguages = new ArrayList<>(); + private final String priority; /** * Constructs a {@code JsonAdaptedPerson} with the given person details. */ @JsonCreator - public JsonAdaptedPerson(@JsonProperty("name") String name, @JsonProperty("phone") String phone, - @JsonProperty("email") String email, @JsonProperty("address") String address, - @JsonProperty("tags") List tags) { + public JsonAdaptedPerson(@JsonProperty("company name") String companyName, @JsonProperty("name") String name, + @JsonProperty("phone") String phone, @JsonProperty("email") String email, + @JsonProperty("address") String address, @JsonProperty("dateTime") String dateTime, + @JsonProperty("salary") String salary, @JsonProperty("info") String info, + @JsonProperty("tags") List tags, + @JsonProperty("programmingLanguages") List + programmingLanguages, + @JsonProperty("priority") String priority) { + this.companyName = companyName; this.name = name; this.phone = phone; this.email = email; this.address = address; + this.dateTime = dateTime; + this.salary = salary; + this.info = info; if (tags != null) { this.tags.addAll(tags); } + if (programmingLanguages != null) { + this.programmingLanguages.addAll(programmingLanguages); + } + this.priority = priority; } /** * Converts a given {@code Person} into this class for Jackson use. */ public JsonAdaptedPerson(Person source) { + companyName = source.getCompanyName().companyName; name = source.getName().fullName; phone = source.getPhone().value; email = source.getEmail().value; address = source.getAddress().value; + if (source.getDateTime() == null) { + dateTime = null; + } else { + dateTime = source.getDateTime().rawToString(); + } + salary = source.getSalary().toString(); + info = source.getInfo().value; tags.addAll(source.getTags().stream() .map(JsonAdaptedTag::new) .collect(Collectors.toList())); + programmingLanguages.addAll(source.getProgrammingLanguages().stream() + .map(JsonAdaptedProgrammingLanguage::new) + .collect(Collectors.toList())); + priority = Integer.toString(source.getPriority()); } /** @@ -69,6 +105,18 @@ public Person toModelType() throws IllegalValueException { for (JsonAdaptedTag tag : tags) { personTags.add(tag.toModelType()); } + final List personProgrammingLanguages = new ArrayList<>(); + for (JsonAdaptedProgrammingLanguage language : programmingLanguages) { + personProgrammingLanguages.add(language.toModelType()); + } + if (companyName == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + CompanyName.class.getSimpleName())); + } + if (!CompanyName.isValidCompanyName(companyName)) { + throw new IllegalValueException(CompanyName.MESSAGE_CONSTRAINTS); + } + final CompanyName modelCompanyName = new CompanyName(companyName); if (name == null) { throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Name.class.getSimpleName())); @@ -101,9 +149,37 @@ public Person toModelType() throws IllegalValueException { throw new IllegalValueException(Address.MESSAGE_CONSTRAINTS); } final Address modelAddress = new Address(address); + if (!InterviewTime.isValidInterviewTime(dateTime)) { + throw new IllegalValueException(InterviewTime.MESSAGE_CONSTRAINTS); + } + final InterviewTime modelDateTime = new InterviewTime(dateTime); + + if (salary == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Salary.class.getSimpleName())); + } + if (!Salary.isValidSalary(salary)) { + throw new IllegalValueException(Salary.MESSAGE_CONSTRAINTS); + } + + final Salary modelSalary = new Salary(salary); + + final Info modelInfo = new Info(info); final Set modelTags = new HashSet<>(personTags); - return new Person(modelName, modelPhone, modelEmail, modelAddress, modelTags); - } + final Set modelProgrammingLanguages = new HashSet<>(personProgrammingLanguages); + + if (priority == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, "Priority")); + } + + if (!Person.isValidPriority(priority)) { + throw new IllegalValueException("Priority level should be between 1 and 3"); + } + + final int modelPriority = Integer.parseInt(priority); + + return new Person(modelCompanyName, modelName, modelPhone, modelEmail, modelAddress, modelDateTime, + modelSalary, modelInfo, modelTags, modelProgrammingLanguages, modelPriority); + } } diff --git a/src/main/java/seedu/address/storage/JsonAdaptedProgrammingLanguage.java b/src/main/java/seedu/address/storage/JsonAdaptedProgrammingLanguage.java new file mode 100644 index 00000000000..ab0ae28dff4 --- /dev/null +++ b/src/main/java/seedu/address/storage/JsonAdaptedProgrammingLanguage.java @@ -0,0 +1,50 @@ +package seedu.address.storage; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.language.ProgrammingLanguage; + +/** + * Jackson-friendly version of {@link ProgrammingLanguage}. + */ +class JsonAdaptedProgrammingLanguage { + + private final String languageName; + + /** + * Constructs a {@code JsonAdaptedProgrammingLanguage} with the given {@code languageName}. + */ + @JsonCreator + public JsonAdaptedProgrammingLanguage(String languageName) { + this.languageName = languageName; + } + + /** + * Converts a given {@code ProgrammingLanguage} into this class for Jackson use. + */ + public JsonAdaptedProgrammingLanguage(ProgrammingLanguage source) { + languageName = source.languageName; + } + + @JsonValue + public String getLanguageName() { + return languageName; + } + + /** + * Converts this Jackson-friendly adapted programming language object into the model's {@code ProgrammingLanguage} + * object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted programming language. + */ + public ProgrammingLanguage toModelType() throws IllegalValueException { + if (!ProgrammingLanguage.isValidLanguageName(languageName)) { + throw new IllegalValueException(ProgrammingLanguage.MESSAGE_CONSTRAINTS); + } + return new ProgrammingLanguage(languageName); + } + +} + diff --git a/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java b/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java index 5efd834091d..22e58ed46a3 100644 --- a/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java +++ b/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java @@ -16,7 +16,7 @@ /** * An Immutable AddressBook that is serializable to JSON format. */ -@JsonRootName(value = "addressbook") +@JsonRootName(value = "ccbot") class JsonSerializableAddressBook { public static final String MESSAGE_DUPLICATE_PERSON = "Persons list contains duplicate person(s)."; diff --git a/src/main/java/seedu/address/ui/HelpWindow.java b/src/main/java/seedu/address/ui/HelpWindow.java index 3f16b2fcf26..54ea755f4fa 100644 --- a/src/main/java/seedu/address/ui/HelpWindow.java +++ b/src/main/java/seedu/address/ui/HelpWindow.java @@ -15,7 +15,7 @@ */ public class HelpWindow extends UiPart { - public static final String USERGUIDE_URL = "https://se-education.org/addressbook-level3/UserGuide.html"; + public static final String USERGUIDE_URL = "https://ay2324s2-cs2103t-t08-3.github.io/tp/UserGuide.html"; public static final String HELP_MESSAGE = "Refer to the user guide: " + USERGUIDE_URL; private static final Logger logger = LogsCenter.getLogger(HelpWindow.class); diff --git a/src/main/java/seedu/address/ui/MainWindow.java b/src/main/java/seedu/address/ui/MainWindow.java index 79e74ef37c0..c7901f6da4c 100644 --- a/src/main/java/seedu/address/ui/MainWindow.java +++ b/src/main/java/seedu/address/ui/MainWindow.java @@ -34,6 +34,7 @@ public class MainWindow extends UiPart { private PersonListPanel personListPanel; private ResultDisplay resultDisplay; private HelpWindow helpWindow; + private ResumeWindow resumeWindow; @FXML private StackPane commandBoxPlaceholder; @@ -66,6 +67,7 @@ public MainWindow(Stage primaryStage, Logic logic) { setAccelerators(); helpWindow = new HelpWindow(); + resumeWindow = new ResumeWindow(); } public Stage getPrimaryStage() { @@ -160,9 +162,26 @@ private void handleExit() { (int) primaryStage.getX(), (int) primaryStage.getY()); logic.setGuiSettings(guiSettings); helpWindow.hide(); + resumeWindow.hide(); primaryStage.hide(); } + /** + * Opens the resume window + */ + @FXML + public void handleResume() { + if (!resumeWindow.isShowing()) { + resumeWindow = new ResumeWindow(); + resumeWindow.show(); + } else { + resumeWindow.close(); + resumeWindow = new ResumeWindow(); + resumeWindow.show(); + resumeWindow.focus(); + } + } + public PersonListPanel getPersonListPanel() { return personListPanel; } @@ -182,6 +201,10 @@ private CommandResult executeCommand(String commandText) throws CommandException handleHelp(); } + if (commandResult.isShowResume()) { + handleResume(); + } + if (commandResult.isExit()) { handleExit(); } diff --git a/src/main/java/seedu/address/ui/PersonCard.java b/src/main/java/seedu/address/ui/PersonCard.java index 094c42cda82..9b26bd462bc 100644 --- a/src/main/java/seedu/address/ui/PersonCard.java +++ b/src/main/java/seedu/address/ui/PersonCard.java @@ -29,6 +29,8 @@ public class PersonCard extends UiPart { @FXML private HBox cardPane; @FXML + private Label companyName; + @FXML private Label name; @FXML private Label id; @@ -39,8 +41,19 @@ public class PersonCard extends UiPart { @FXML private Label email; @FXML + private Label dateTime; + @FXML + private Label salary; + @FXML + private Label info; + @FXML + private Label jobDifficulty; + @FXML + private Label programmingLanguagesLabel; + @FXML private FlowPane tags; - + @FXML + private FlowPane programmingLanguages; /** * Creates a {@code PersonCode} with the given {@code Person} and index to display. */ @@ -48,12 +61,45 @@ public PersonCard(Person person, int displayedIndex) { super(FXML); this.person = person; id.setText(displayedIndex + ". "); + companyName.setText(person.getCompanyName().companyName); name.setText(person.getName().fullName); phone.setText(person.getPhone().value); address.setText(person.getAddress().value); email.setText(person.getEmail().value); + dateTime.setText("Interview Time: " + person.getDateTime().toString()); + salary.setText("Salary: " + person.getSalary().toString() + "$"); + info.setText(person.getInfo().value); + jobDifficulty.setText("Predict Job Difficulty: " + person.getJobDifficulty().toString() + "%"); person.getTags().stream() .sorted(Comparator.comparing(tag -> tag.tagName)) .forEach(tag -> tags.getChildren().add(new Label(tag.tagName))); + person.getProgrammingLanguages().stream() + .sorted(Comparator.comparing(pl -> pl.languageName)) + .forEach(pl -> programmingLanguages.getChildren().add( + new Label("[" + pl.languageName + "] "))); + updateCardColor(person); + } + private void updateCardColor(Person person) { + String styleClass; + switch (person.getPriority()) { + case 0: + styleClass = "priorityHigh"; + break; + case 1: + styleClass = "priorityMedium"; + break; + case 2: + styleClass = "priorityLow"; + break; + case 3: + styleClass = "priorityLower"; + break; + case 4: + styleClass = "priorityLowest"; + break; + default: + styleClass = "priorityDefault"; + } + cardPane.getStyleClass().add(styleClass); } } diff --git a/src/main/java/seedu/address/ui/ResumeWindow.java b/src/main/java/seedu/address/ui/ResumeWindow.java new file mode 100644 index 00000000000..2a65beee294 --- /dev/null +++ b/src/main/java/seedu/address/ui/ResumeWindow.java @@ -0,0 +1,174 @@ +package seedu.address.ui; + +import java.util.Comparator; +import java.util.logging.Logger; + +import javafx.fxml.FXML; +import javafx.scene.control.Button; +import javafx.scene.control.Label; +import javafx.scene.input.Clipboard; +import javafx.scene.input.ClipboardContent; +import javafx.scene.layout.VBox; +import javafx.stage.Stage; +import seedu.address.commons.core.LogsCenter; +import seedu.address.model.person.user.User; + +/** + * Controller for resume page + */ +public class ResumeWindow extends UiPart { + private static final Logger logger = LogsCenter.getLogger(HelpWindow.class); + private static final String FXML = "ResumeWindow2.fxml"; + private User user = User.getInstance(); + + @FXML + private Label companyName; + @FXML + private Label name; + @FXML + private Label phone; + @FXML + private Label address; + @FXML + private Label email; + @FXML + private Label salary; + @FXML + private Label resume; + @FXML + private Label education; + @FXML + private Label skills; + @FXML + private VBox skillsVbox; + @FXML + private Label contactLabel; + @FXML + private VBox contactBox; + @FXML + private Button copyButton; + + private Stage currentStage; + + /** + * Creates a new ResumeWindow. + * + * @param root Stage to use as the root of the ResumeWindow. + */ + public ResumeWindow(Stage root) { + super(FXML, root); + if (user.getName() != null) { + resume.setText("Resume added"); + companyName.setText("Previous employment: " + user.getCompanyName().companyName); + companyName.setVisible(true); + name.setText("Name: " + user.getName().fullName); + name.setVisible(true); + phone.setText("Phone: " + user.getPhone().value); + phone.setVisible(true); + address.setText("Place of residence: " + user.getAddress().value); + address.setVisible(true); + email.setText("Email: " + user.getEmail().value); + email.setVisible(true); + salary.setText("Previous salary: " + "$" + user.getSalary().toString()); + salary.setVisible(true); + education.setText("Highest Education: " + user.getEducation()); + education.setVisible(true); + skills.setVisible(true); + skillsVbox.setVisible(true); + + //for contact info box + contactLabel.setVisible(true); + contactBox.setVisible(true); + + user.getSkills().stream() + .sorted(Comparator.comparing(pl -> pl.languageName)) + .forEach(pl -> skillsVbox.getChildren().add( + new Label(pl.languageName))); + } else { + resume.setText("No resume added"); + } + } + + /** + * Creates a new ResumeWindow. + */ + public ResumeWindow() { + this(new Stage()); + } + + /** + * Shows the help window. + * @throws IllegalStateException + *
    + *
  • + * if this method is called on a thread other than the JavaFX Application Thread. + *
  • + *
  • + * if this method is called during animation or layout processing. + *
  • + *
  • + * if this method is called on the primary stage. + *
  • + *
  • + * if {@code dialogStage} is already showing. + *
  • + *
+ */ + public void show() { + logger.fine("Showing user's resume."); + getRoot().show(); + getRoot().centerOnScreen(); + } + + public void close() { + getRoot().close(); + } + + /** + * Allow user to generate resume as string to copy + * @return + */ + public String generateResume() { + StringBuilder resumeBuilder = new StringBuilder(); + resumeBuilder.append(companyName.getText()).append("\n"); + resumeBuilder.append(name.getText()).append("\n"); + resumeBuilder.append(phone.getText()).append("\n"); + resumeBuilder.append(email.getText()).append("\n"); + resumeBuilder.append(address.getText()).append("\n"); + resumeBuilder.append(salary.getText()).append("\n"); + resumeBuilder.append("Skills: ").append("\n"); + skillsVbox.getChildren().forEach(label -> resumeBuilder.append("- ").append(((Label) label).getText()) + .append("\n")); + + return resumeBuilder.toString(); + } + + /** + * Returns true if the help window is currently being shown. + */ + public boolean isShowing() { + return getRoot().isShowing(); + } + + /** + * Hides the help window. + */ + public void hide() { + getRoot().hide(); + } + + /** + * Focuses on the help window. + */ + public void focus() { + getRoot().requestFocus(); + } + + @FXML + private void copyResume() { + final Clipboard clipboard = Clipboard.getSystemClipboard(); + final ClipboardContent resume = new ClipboardContent(); + resume.putString(generateResume()); + clipboard.setContent(resume); + } +} diff --git a/src/main/java/seedu/address/ui/UiManager.java b/src/main/java/seedu/address/ui/UiManager.java index fdf024138bc..0565bccbf2d 100644 --- a/src/main/java/seedu/address/ui/UiManager.java +++ b/src/main/java/seedu/address/ui/UiManager.java @@ -20,7 +20,7 @@ public class UiManager implements Ui { public static final String ALERT_DIALOG_PANE_FIELD_ID = "alertDialogPane"; private static final Logger logger = LogsCenter.getLogger(UiManager.class); - private static final String ICON_APPLICATION = "/images/address_book_32.png"; + private static final String ICON_APPLICATION = "/images/ccbot_logo_1.png"; private Logic logic; private MainWindow mainWindow; diff --git a/src/main/resources/images/ccbot_logo.png b/src/main/resources/images/ccbot_logo.png new file mode 100644 index 00000000000..60e90c5bfb2 Binary files /dev/null and b/src/main/resources/images/ccbot_logo.png differ diff --git a/src/main/resources/images/ccbot_logo_1.png b/src/main/resources/images/ccbot_logo_1.png new file mode 100644 index 00000000000..d33b6d1927a Binary files /dev/null and b/src/main/resources/images/ccbot_logo_1.png differ diff --git a/src/main/resources/images/resume_bg.jpg b/src/main/resources/images/resume_bg.jpg new file mode 100644 index 00000000000..3c79ff4b7f7 Binary files /dev/null and b/src/main/resources/images/resume_bg.jpg differ diff --git a/src/main/resources/view/CustomTheme.css b/src/main/resources/view/CustomTheme.css new file mode 100644 index 00000000000..77247e52a8e --- /dev/null +++ b/src/main/resources/view/CustomTheme.css @@ -0,0 +1,376 @@ +.background { + -fx-background-color: derive(#1d1d1d, 20%); + background-color: #8B8B00; /* Used in the default.html file */ +} + +.label { + -fx-font-size: 11pt; + -fx-font-family: "Segoe UI Semibold"; + -fx-text-fill: #555555; + -fx-opacity: 0.9; +} + +.label-bright { + -fx-font-size: 11pt; + -fx-font-family: "Segoe UI Semibold"; + -fx-text-fill: white; + -fx-opacity: 1; +} + +.label-header { + -fx-font-size: 32pt; + -fx-font-family: "Segoe UI Light"; + -fx-text-fill: white; + -fx-opacity: 1; +} + +.text-field { + -fx-font-size: 12pt; + -fx-font-family: "Segoe UI Semibold"; +} + +.tab-pane { + -fx-padding: 0 0 0 1; +} + +.tab-pane .tab-header-area { + -fx-padding: 0 0 0 0; + -fx-min-height: 0; + -fx-max-height: 0; +} + +.table-view { + -fx-base: #1d1d1d; + -fx-control-inner-background: #1d1d1d; + -fx-background-color: #1d1d1d; + -fx-table-cell-border-color: transparent; + -fx-table-header-border-color: transparent; + -fx-padding: 5; +} + +.table-view .column-header-background { + -fx-background-color: transparent; +} + +.table-view .column-header, .table-view .filler { + -fx-size: 35; + -fx-border-width: 0 0 1 0; + -fx-background-color: transparent; + -fx-border-color: + transparent + transparent + derive(-fx-base, 80%) + transparent; + -fx-border-insets: 0 10 1 0; +} + +.table-view .column-header .label { + -fx-font-size: 20pt; + -fx-font-family: "Segoe UI Light"; + -fx-text-fill: white; + -fx-alignment: center-left; + -fx-opacity: 1; +} + +.table-view:focused .table-row-cell:filled:focused:selected { + -fx-background-color: -fx-focus-color; +} + +.split-pane:horizontal .split-pane-divider { + -fx-background-color: derive(#1d1d1d, 20%); + -fx-border-color: transparent transparent transparent #4d4d4d; +} + +.split-pane { + -fx-border-radius: 1; + -fx-border-width: 1; + -fx-background-color: derive(#1d1d1d, 20%); +} + +.list-view { + -fx-background-insets: 0; + -fx-padding: 0; + -fx-background-color: derive(#1d1d1d, 20%); +} + +.list-cell { + -fx-label-padding: 0 0 0 0; + -fx-graphic-text-gap : 0; + -fx-padding: 0 0 0 0; +} + +.list-cell:filled:even { + -fx-background-color: #54707f; +} + +.list-cell:filled:odd { + -fx-background-color: #4c6d7b; +} + +.list-cell:filled:selected { + -fx-background-color: #2f3e54; +} + +.list-cell:filled:selected #cardPane { + -fx-border-color: #040404; + -fx-border-width: 1; +} + +.list-cell .label { + -fx-text-fill: rgb(255, 255, 255); +} + +.cell_big_label { + -fx-font-family: "Segoe UI Semibold"; + -fx-font-size: 16px; + -fx-text-fill: #010504; +} + +.cell_small_label { + -fx-font-family: "Segoe UI"; + -fx-font-size: 13px; + -fx-text-fill: #010504; +} + +.stack-pane { + -fx-background-color: derive(#1d1d1d, 20%); +} + +.pane-with-border { + -fx-background-color: derive(#1d1d1d, 20%); + -fx-border-color: derive(#1d1d1d, 10%); + -fx-border-top-width: 1px; +} + +.status-bar { + -fx-background-color: derive(#1d1d1d, 30%); +} + +.result-display { + -fx-background-color: transparent; + -fx-font-family: "DejaVu Sans Mono"; + -fx-font-size: 13pt; + -fx-text-fill: rgb(188, 188, 188); +} + +.result-display .label { + -fx-text-fill: rgb(0, 0, 0) !important; +} + +.status-bar .label { + -fx-font-family: "Segoe UI Light"; + -fx-text-fill: white; + -fx-padding: 4px; + -fx-pref-height: 30px; +} + +.status-bar-with-border { + -fx-background-color: derive(#1d1d1d, 30%); + -fx-border-color: derive(#1d1d1d, 25%); + -fx-border-width: 1px; +} + +.status-bar-with-border .label { + -fx-text-fill: white; +} + +.grid-pane { + -fx-background-color: derive(#1d1d1d, 30%); + -fx-border-color: derive(#1d1d1d, 30%); + -fx-border-width: 1px; +} + +.grid-pane .stack-pane { + -fx-background-color: derive(#1d1d1d, 30%); +} + +.context-menu { + -fx-background-color: derive(#1d1d1d, 50%); +} + +.context-menu .label { + -fx-text-fill: white; +} + +.menu-bar { + -fx-background-color: derive(#1d1d1d, 20%); +} + +.menu-bar .label { + -fx-font-size: 14pt; + -fx-font-family: "Segoe UI Light"; + -fx-text-fill: white; + -fx-opacity: 0.9; +} + +.menu .left-container { + -fx-background-color: black; +} + +/* + * Metro style Push Button + * Author: Pedro Duque Vieira + * http://pixelduke.wordpress.com/2012/10/23/jmetro-windows-8-controls-on-java/ + */ +.button { + -fx-padding: 5 22 5 22; + -fx-border-color: #e2e2e2; + -fx-border-width: 2; + -fx-background-radius: 0; + -fx-background-color: #1d1d1d; + -fx-font-family: "Segoe UI", Helvetica, Arial, sans-serif; + -fx-font-size: 11pt; + -fx-text-fill: #d8d8d8; + -fx-background-insets: 0 0 0 0, 0, 1, 2; +} + +.button:hover { + -fx-background-color: #3a3a3a; +} + +.button:pressed, .button:default:hover:pressed { + -fx-background-color: white; + -fx-text-fill: #1d1d1d; +} + +.button:focused { + -fx-border-color: white, white; + -fx-border-width: 1, 1; + -fx-border-style: solid, segments(1, 1); + -fx-border-radius: 0, 0; + -fx-border-insets: 1 1 1 1, 0; +} + +.button:disabled, .button:default:disabled { + -fx-opacity: 0.4; + -fx-background-color: #1d1d1d; + -fx-text-fill: white; +} + +.button:default { + -fx-background-color: -fx-focus-color; + -fx-text-fill: #ffffff; +} + +.button:default:hover { + -fx-background-color: derive(-fx-focus-color, 30%); +} + +.dialog-pane { + -fx-background-color: #1d1d1d; +} + +.dialog-pane > *.button-bar > *.container { + -fx-background-color: #1d1d1d; +} + +.dialog-pane > *.label.content { + -fx-font-size: 14px; + -fx-font-weight: bold; + -fx-text-fill: white; +} + +.dialog-pane:header *.header-panel { + -fx-background-color: derive(#1d1d1d, 25%); +} + +.dialog-pane:header *.header-panel *.label { + -fx-font-size: 18px; + -fx-font-style: italic; + -fx-fill: white; + -fx-text-fill: white; +} + +.scroll-bar { + -fx-background-color: derive(#1d1d1d, 20%); +} + +.scroll-bar .thumb { + -fx-background-color: derive(#1d1d1d, 50%); + -fx-background-insets: 3; +} + +.scroll-bar .increment-button, .scroll-bar .decrement-button { + -fx-background-color: transparent; + -fx-padding: 0 0 0 0; +} + +.scroll-bar .increment-arrow, .scroll-bar .decrement-arrow { + -fx-shape: " "; +} + +.scroll-bar:vertical .increment-arrow, .scroll-bar:vertical .decrement-arrow { + -fx-padding: 1 8 1 8; +} + +.scroll-bar:horizontal .increment-arrow, .scroll-bar:horizontal .decrement-arrow { + -fx-padding: 8 1 8 1; +} + +#cardPane { + -fx-background-color: transparent; + -fx-border-width: 0; +} + +#commandTypeLabel { + -fx-font-size: 11px; + -fx-text-fill: #F70D1A; +} + +#commandTextField { + -fx-background-color: transparent #383838 transparent #383838; + -fx-background-insets: 0; + -fx-border-color: #383838 #383838 #ffffff #383838; + -fx-border-insets: 0; + -fx-border-width: 1; + -fx-font-family: "Segoe UI Light"; + -fx-font-size: 13pt; + -fx-text-fill: white; +} + +#filterField, #personListPanel, #personWebpage { + -fx-effect: innershadow(gaussian, black, 10, 0, 0, 0); +} + +#resultDisplay .content { + -fx-background-color: transparent, #383838, transparent, #383838; + -fx-background-radius: 0; +} + +#tags { + -fx-hgap: 7; + -fx-vgap: 3; +} + +#tags .label { + -fx-text-fill: white; + -fx-background-color: #20b038; + -fx-padding: 1 3 1 3; + -fx-border-radius: 2; + -fx-background-radius: 2; + -fx-font-size: 11; +} + +#cardPane.priorityHigh { + -fx-background-color: #E11F1F; +} + +#cardPane.priorityMedium { + -fx-background-color: #E18D1F; +} + +#cardPane.priorityLow { + -fx-background-color: #159584; +} + +#cardPane.priorityLower { + -fx-background-color: #1550C5; +} + +#cardPane.priorityLowest { + -fx-background-color: #8A15C5; +} + +#cardPane.priorityDefault { + -fx-background-color: grey; +} diff --git a/src/main/resources/view/DarkTheme.css b/src/main/resources/view/DarkTheme.css index 36e6b001cd8..45e414a6328 100644 --- a/src/main/resources/view/DarkTheme.css +++ b/src/main/resources/view/DarkTheme.css @@ -149,8 +149,8 @@ .result-display { -fx-background-color: transparent; -fx-font-family: "Segoe UI Light"; - -fx-font-size: 13pt; - -fx-text-fill: white; + -fx-font-size: 20pt; + -fx-text-fill: rgb(81, 255, 0); } .result-display .label { @@ -350,3 +350,19 @@ -fx-background-radius: 2; -fx-font-size: 11; } + +.personListCard.priorityHigh { + -fx-background-color: red; +} + +.personListCard.priorityMedium { + -fx-background-color: yellow; +} + +.personListCard.priorityLow { + -fx-background-color: green; +} + +.personListCard.priorityDefault { + -fx-background-color: grey; +} diff --git a/src/main/resources/view/Extensions.css b/src/main/resources/view/Extensions.css index bfe82a85964..5fafef71f8f 100644 --- a/src/main/resources/view/Extensions.css +++ b/src/main/resources/view/Extensions.css @@ -1,6 +1,6 @@ .error { - -fx-text-fill: #d06651 !important; /* The error class should always override the default text-fill style */ + -fx-text-fill: #e14620 !important; /* The error class should always override the default text-fill style */ } .list-cell:empty { @@ -10,11 +10,37 @@ .tag-selector { -fx-border-width: 1; - -fx-border-color: white; + -fx-border-color: rgb(255, 255, 255); -fx-border-radius: 3; -fx-background-radius: 3; } .tooltip-text { - -fx-text-fill: white; + -fx-text-fill: rgb(255, 255, 255); } + +.personListCard.priorityHigh { + -fx-background-color: red; +} + +.personListCard.priorityMedium { + -fx-background-color: yellow; +} + +.personListCard.priorityLow { + -fx-background-color: green; +} + +.personListCard.priorityLower { + -fx-background-color: yellow; +} + +.personListCard.priorityLowest { + -fx-background-color: gold; +} + +.personListCard.priorityDefault { + -fx-background-color: grey; +} + + diff --git a/src/main/resources/view/MainWindow.fxml b/src/main/resources/view/MainWindow.fxml index 7778f666a0a..e1ac0b9f0de 100644 --- a/src/main/resources/view/MainWindow.fxml +++ b/src/main/resources/view/MainWindow.fxml @@ -6,51 +6,54 @@ - + - + - + - + + - - + + + + + - + - + - + - + - + - + - + diff --git a/src/main/resources/view/PersonListCard.fxml b/src/main/resources/view/PersonListCard.fxml index f5e812e25e6..1b650661748 100644 --- a/src/main/resources/view/PersonListCard.fxml +++ b/src/main/resources/view/PersonListCard.fxml @@ -7,30 +7,45 @@ + - + - + - + - + + + + + + + diff --git a/src/main/resources/view/ResultDisplay.fxml b/src/main/resources/view/ResultDisplay.fxml index 01b691792a9..c6c34bb6504 100644 --- a/src/main/resources/view/ResultDisplay.fxml +++ b/src/main/resources/view/ResultDisplay.fxml @@ -3,7 +3,6 @@ - -