diff --git a/.gitignore b/.gitignore index 823d175eb670..7073634afb4a 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,316 @@ preferences.json ./screenshot*.png classes/ /data/ +/backup/ /bin/ src/main/resources/docs/ out/ +seedu.address/index_SORT_BY_NAME_DESC.html +seedu.address.ui/index_SORT_BY_METHOD.html +seedu.address.ui/index_SORT_BY_LINE_DESC.html +seedu.address.ui/index_SORT_BY_LINE.html +seedu.address.ui/index_SORT_BY_CLASS_DESC.html +seedu.address.ui/index_SORT_BY_CLASS.html +seedu.address.ui/index_SORT_BY_BLOCK_DESC.html +seedu.address.ui/index_SORT_BY_BLOCK.html +seedu.address.ui/index.html +seedu.address.ui/.classes/UiPart.html +seedu.address.ui/.classes/UiManager.html +seedu.address.ui/.classes/Ui.html +seedu.address.ui/.classes/StatusBarFooter.html +seedu.address.ui/.classes/ResultDisplay.html +seedu.address.ui/.classes/PersonListPanel.html +seedu.address.ui/.classes/PersonCard.html +seedu.address.ui/.classes/MainWindow.html +seedu.address.storage/.classes/Storage.html +seedu.address.storage/.classes/JsonUserPrefsStorage.html +seedu.address.storage/.classes/JsonSerializableAddressBook.html +seedu.address.storage/.classes/JsonAddressBookStorage.html +seedu.address.storage/.classes/JsonAdaptedTag.html +seedu.address.storage/.classes/JsonAdaptedPerson.html +seedu.address.storage/.classes/JsonAdaptedPastJob.html +seedu.address.storage/.classes/JsonAdaptedKnownProgLang.html +seedu.address.storage/.classes/JsonAdaptedJobsApply.html +seedu.address.storage/.classes/AddressBookStorage.html +seedu.address.model/index_SORT_BY_NAME_DESC.html +seedu.address.model/index_SORT_BY_METHOD_DESC.html +seedu.address.model/index_SORT_BY_METHOD.html +seedu.address.model/index_SORT_BY_LINE_DESC.html +seedu.address.model/index_SORT_BY_LINE.html +seedu.address.model/index_SORT_BY_CLASS_DESC.html +seedu.address.model.util/index_SORT_BY_CLASS_DESC.html +seedu.address.model.util/index_SORT_BY_CLASS.html +seedu.address.model.util/index_SORT_BY_BLOCK_DESC.html +seedu.address.model.util/index_SORT_BY_BLOCK.html +seedu.address.model.util/index.html +seedu.address.model.util/.classes/SampleDataUtil.html +seedu.address.model.tag/index_SORT_BY_NAME_DESC.html +seedu.address.model.tag/index_SORT_BY_METHOD_DESC.html +seedu.address.model.tag/index_SORT_BY_METHOD.html +seedu.address.model.tag/index_SORT_BY_LINE_DESC.html +seedu.address.model.tag/index_SORT_BY_LINE.html +seedu.address.model.tag/index_SORT_BY_CLASS_DESC.html +seedu.address.model.tag/index_SORT_BY_CLASS.html +seedu.address.model.person/.classes/UniqueNricMap.html +seedu.address.model.person/.classes/School.html +seedu.address.model.person/.classes/Race.html +seedu.address.model.person/.classes/Phone.html +seedu.address.model.person/.classes/Person.html +seedu.address.model.person/.classes/PastJob.html +seedu.address.model.person/.classes/Nric.html +seedu.address.model.person/.classes/Name.html +seedu.address.model.person/.classes/Major.html +seedu.address.model.person/.classes/KnownProgLang.html +seedu.address.model.person/.classes/JobsApply.html +seedu.address.model.person/.classes/InterviewScores.html +seedu.address.model.person/.classes/Grade.html +seedu.address.model.person/.classes/Gender.html +seedu.address.model.person/.classes/Email.html +seedu.address.model.person/.classes/Address.html +seedu.address.model.person.predicate/.classes/EmailContainsKeywordsPredicate.html +seedu.address.model.person.predicate/.classes/AddressContainsKeywordsPredicate.html +seedu.address.model.person.exceptions/index_SORT_BY_NAME_DESC.html +seedu.address.model.person.exceptions/index_SORT_BY_METHOD_DESC.html +seedu.address.model.person.exceptions/index_SORT_BY_METHOD.html +seedu.address.model.person.exceptions/index_SORT_BY_LINE_DESC.html +seedu.address.model.person.exceptions/index_SORT_BY_LINE.html +seedu.address.model.person.exceptions/index_SORT_BY_CLASS_DESC.html +seedu.address.model.person.exceptions/index_SORT_BY_CLASS.html +seedu.address.model.person.exceptions/index_SORT_BY_BLOCK_DESC.html +seedu.address.model.person.exceptions/index_SORT_BY_BLOCK.html +seedu.address.model.person.exceptions/index.html +seedu.address.model.person.exceptions/.classes/PersonNotFoundException.html +seedu.address.model.person.exceptions/.classes/DuplicatePersonException.html +seedu.address.model.job/index_SORT_BY_NAME_DESC.html +seedu.address.model.job/index_SORT_BY_METHOD_DESC.html +seedu.address.logic/index_SORT_BY_LINE.html +seedu.address.logic/index_SORT_BY_CLASS_DESC.html +seedu.address.logic/index_SORT_BY_CLASS.html +seedu.address.logic/index_SORT_BY_BLOCK_DESC.html +seedu.address.logic/index_SORT_BY_BLOCK.html +seedu.address.logic/index.html +seedu.address.logic/.classes/LogicManager.html +seedu.address.logic/.classes/Logic.html +seedu.address.logic/.classes/CommandHistory.html +seedu.address.logic.parser/index_SORT_BY_NAME_DESC.html +seedu.address.logic.parser/index_SORT_BY_METHOD_DESC.html +seedu.address.logic.parser/index_SORT_BY_METHOD.html +seedu.address.logic.parser/index_SORT_BY_LINE_DESC.html +seedu.address.logic.parser/index_SORT_BY_LINE.html +seedu.address.logic.parser/index_SORT_BY_CLASS_DESC.html +seedu.address.logic.parser/index_SORT_BY_CLASS.html +seedu.address.logic.parser.exceptions/index_SORT_BY_BLOCK_DESC.html +seedu.address.logic.parser.exceptions/index_SORT_BY_BLOCK.html +seedu.address.logic.parser.exceptions/index.html +seedu.address.logic.parser.exceptions/.classes/ParseException.html +seedu.address.logic.commands/index_SORT_BY_NAME_DESC.html +seedu.address.logic.commands/index_SORT_BY_METHOD_DESC.html +seedu.address.logic.commands/index_SORT_BY_METHOD.html +seedu.address.logic.commands/index_SORT_BY_LINE_DESC.html +seedu.address.logic.commands/index_SORT_BY_LINE.html +seedu.address.logic.commands/index_SORT_BY_CLASS_DESC.html +seedu.address.logic.commands/index_SORT_BY_CLASS.html +seedu.address.logic.commands/index_SORT_BY_BLOCK_DESC.html +seedu.address.logic.commands/index_SORT_BY_BLOCK.html +seedu.address.logic.commands/index.html +seedu.address.logic.commands/.classes/UndoCommand.html +seedu.address.logic.commands/.classes/SelectCommand.html +seedu.address.logic.commands/.classes/ExitCommand.html +seedu.address.logic.commands/.classes/EditCommand.html +seedu.address.logic.commands/.classes/DeleteCommand.html +seedu.address.logic.commands/.classes/CreateJobCommand.html +seedu.address.logic.commands/.classes/CommandResult.html +seedu.address.logic.commands/.classes/Command.html +seedu.address.logic.commands/.classes/ClearCommand.html +seedu.address.logic.commands/.classes/AddCommand.html +seedu.address.logic.commands.exceptions/index_SORT_BY_NAME_DESC.html +seedu.address.logic.commands.exceptions/index_SORT_BY_METHOD_DESC.html +seedu.address.logic.commands.exceptions/index_SORT_BY_METHOD.html +seedu.address.logic.commands.exceptions/index_SORT_BY_LINE_DESC.html +seedu.address.logic.commands.exceptions/index_SORT_BY_LINE.html +seedu.address.logic.commands.exceptions/index_SORT_BY_CLASS_DESC.html +seedu.address.logic.commands.exceptions/index_SORT_BY_CLASS.html +seedu.address.logic.commands.exceptions/index_SORT_BY_BLOCK_DESC.html +seedu.address.logic.commands.exceptions/index_SORT_BY_BLOCK.html +seedu.address.logic.commands.exceptions/index.html +seedu.address.logic.commands.exceptions/.classes/CommandException.html +seedu.address.commons.util/index_SORT_BY_NAME_DESC.html +seedu.address.commons.util/index_SORT_BY_METHOD_DESC.html +seedu.address.commons.util/index_SORT_BY_METHOD.html +seedu.address.commons.util/index_SORT_BY_LINE_DESC.html +seedu.address.commons.util/index_SORT_BY_LINE.html +seedu.address.commons.util/index_SORT_BY_CLASS_DESC.html +seedu.address.commons.util/index_SORT_BY_CLASS.html +seedu.address.commons.util/index_SORT_BY_BLOCK_DESC.html +seedu.address.commons.util/index_SORT_BY_BLOCK.html +seedu.address.commons.util/index.html +seedu.address.commons.util/.classes/StringUtil.html +seedu.address.commons.util/.classes/JsonUtil.html +seedu.address.commons.util/.classes/InvalidationListenerManager.html +seedu.address.commons.util/.classes/FileUtil.html +seedu.address.commons.util/.classes/ConfigUtil.html +seedu.address.commons.util/.classes/CollectionUtil.html +seedu.address.commons.util/.classes/AppUtil.html +seedu.address.commons.exceptions/index_SORT_BY_NAME_DESC.html +seedu.address.commons.exceptions/index_SORT_BY_METHOD_DESC.html +seedu.address.commons.exceptions/index_SORT_BY_METHOD.html +seedu.address.commons.exceptions/index_SORT_BY_LINE_DESC.html +seedu.address.commons.exceptions/index_SORT_BY_LINE.html +seedu.address.commons.exceptions/index_SORT_BY_CLASS_DESC.html +seedu.address.commons.exceptions/index_SORT_BY_CLASS.html +seedu.address.commons.exceptions/index_SORT_BY_BLOCK_DESC.html +seedu.address.commons.exceptions/index_SORT_BY_BLOCK.html +seedu.address.commons.exceptions/index.html +seedu.address.commons.exceptions/.classes/IllegalValueException.html +seedu.address.commons.exceptions/.classes/DataConversionException.html +seedu.address.commons.core/index_SORT_BY_NAME_DESC.html +seedu.address.commons.core/index_SORT_BY_METHOD_DESC.html +seedu.address.commons.core/index_SORT_BY_METHOD.html +seedu.address.commons.core/index_SORT_BY_LINE_DESC.html +seedu.address.commons.core/index_SORT_BY_LINE.html +seedu.address.commons.core/index_SORT_BY_CLASS_DESC.html +seedu.address.commons.core/index_SORT_BY_CLASS.html +seedu.address.commons.core/index_SORT_BY_BLOCK_DESC.html +seedu.address.commons.core/index_SORT_BY_BLOCK.html +seedu.address.commons.core/index.html +seedu.address.commons.core/.classes/Version.html +seedu.address.commons.core/.classes/Messages.html +seedu.address.commons.core/.classes/LogsCenter.html +seedu.address.commons.core/.classes/GuiSettings.html +seedu.address.commons.core/.classes/Config.html +seedu.address.commons.core.index/index_SORT_BY_NAME_DESC.html +seedu.address.commons.core.index/index_SORT_BY_METHOD_DESC.html +seedu.address.commons.core.index/index_SORT_BY_METHOD.html +seedu.address.commons.core.index/index_SORT_BY_LINE_DESC.html +seedu.address.commons.core.index/index_SORT_BY_LINE.html +seedu.address.commons.core.index/index_SORT_BY_CLASS_DESC.html +seedu.address.commons.core.index/index_SORT_BY_CLASS.html +seedu.address.commons.core.index/index_SORT_BY_BLOCK_DESC.html +seedu.address.commons.core.index/index_SORT_BY_BLOCK.html +seedu.address.commons.core.index/index.html +seedu.address.commons.core.index/.classes/Index.html +.img/arrowUp.gif +.img/arrowDown.gif +.css/coverage.css +seedu.address/index_SORT_BY_METHOD_DESC.html +seedu.address/index_SORT_BY_METHOD.html +seedu.address/index_SORT_BY_LINE_DESC.html +seedu.address/index_SORT_BY_LINE.html +seedu.address/index_SORT_BY_CLASS_DESC.html +seedu.address/index_SORT_BY_CLASS.html +seedu.address/index_SORT_BY_BLOCK_DESC.html +seedu.address/index_SORT_BY_BLOCK.html +seedu.address/index.html +seedu.address/.classes/MainApp.html +seedu.address/.classes/AppParameters.html +seedu.address.ui/index_SORT_BY_NAME_DESC.html +seedu.address.ui/index_SORT_BY_METHOD_DESC.html +seedu.address.ui/.classes/ListElementPointer.html +seedu.address.ui/.classes/HelpWindow.html +seedu.address.ui/.classes/CommandBox.html +seedu.address.ui/.classes/BrowserPanel.html +seedu.address.storage/index_SORT_BY_NAME_DESC.html +seedu.address.storage/index_SORT_BY_METHOD_DESC.html +seedu.address.storage/index_SORT_BY_METHOD.html +seedu.address.storage/index_SORT_BY_LINE_DESC.html +seedu.address.storage/index_SORT_BY_LINE.html +seedu.address.storage/index_SORT_BY_CLASS_DESC.html +seedu.address.storage/index_SORT_BY_CLASS.html +seedu.address.storage/index_SORT_BY_BLOCK_DESC.html +seedu.address.storage/index_SORT_BY_BLOCK.html +seedu.address.storage/index.html +seedu.address.storage/.classes/UserPrefsStorage.html +seedu.address.storage/.classes/StorageManager.html +seedu.address.model/index_SORT_BY_CLASS.html +seedu.address.model/index_SORT_BY_BLOCK_DESC.html +seedu.address.model/index_SORT_BY_BLOCK.html +seedu.address.model/index.html +seedu.address.model/.classes/VersionedAddressBook.html +seedu.address.model/.classes/UserPrefs.html +seedu.address.model/.classes/ReadOnlyUserPrefs.html +seedu.address.model/.classes/ReadOnlyAddressBook.html +seedu.address.model/.classes/ModelManager.html +seedu.address.model/.classes/Model.html +seedu.address.model/.classes/AddressBook.html +seedu.address.model.util/index_SORT_BY_NAME_DESC.html +seedu.address.model.util/index_SORT_BY_METHOD_DESC.html +seedu.address.model.util/index_SORT_BY_METHOD.html +seedu.address.model.util/index_SORT_BY_LINE_DESC.html +seedu.address.model.util/index_SORT_BY_LINE.html +seedu.address.model.tag/index_SORT_BY_BLOCK_DESC.html +seedu.address.model.tag/index_SORT_BY_BLOCK.html +seedu.address.model.tag/index.html +seedu.address.model.tag/.classes/UniqueTagList.html +seedu.address.model.tag/.classes/Tag.html +seedu.address.model.person/index_SORT_BY_NAME_DESC.html +seedu.address.model.person/index_SORT_BY_METHOD_DESC.html +seedu.address.model.person/index_SORT_BY_METHOD.html +seedu.address.model.person/index_SORT_BY_LINE_DESC.html +seedu.address.model.person/index_SORT_BY_LINE.html +seedu.address.model.person/index_SORT_BY_CLASS_DESC.html +seedu.address.model.person/index_SORT_BY_CLASS.html +seedu.address.model.person/index_SORT_BY_BLOCK_DESC.html +seedu.address.model.person/index_SORT_BY_BLOCK.html +seedu.address.model.person/index.html +seedu.address.model.person/.classes/UniquePersonList.html +seedu.address.model.person.predicate/index_SORT_BY_NAME_DESC.html +seedu.address.model.person.predicate/index_SORT_BY_METHOD_DESC.html +seedu.address.model.person.predicate/index_SORT_BY_METHOD.html +seedu.address.model.person.predicate/index_SORT_BY_LINE_DESC.html +seedu.address.model.person.predicate/index_SORT_BY_LINE.html +seedu.address.model.person.predicate/index_SORT_BY_CLASS_DESC.html +seedu.address.model.person.predicate/index_SORT_BY_CLASS.html +seedu.address.model.person.predicate/index_SORT_BY_BLOCK_DESC.html +seedu.address.model.person.predicate/index_SORT_BY_BLOCK.html +seedu.address.model.person.predicate/index.html +seedu.address.model.person.predicate/.classes/SchoolContainsKeywordsPredicate.html +seedu.address.model.person.predicate/.classes/RaceContainsKeywordsPredicate.html +seedu.address.model.person.predicate/.classes/PredicateManager.html +seedu.address.model.person.predicate/.classes/PhoneContainsKeywordsPredicate.html +seedu.address.model.person.predicate/.classes/NameContainsKeywordsPredicate.html +seedu.address.model.person.predicate/.classes/MajorContainsKeywordsPredicate.html +seedu.address.model.job/index_SORT_BY_METHOD.html +seedu.address.model.job/index_SORT_BY_LINE_DESC.html +seedu.address.model.job/index_SORT_BY_LINE.html +seedu.address.model.job/index_SORT_BY_CLASS_DESC.html +seedu.address.model.job/index_SORT_BY_CLASS.html +seedu.address.model.job/index_SORT_BY_BLOCK_DESC.html +seedu.address.model.job/index_SORT_BY_BLOCK.html +seedu.address.model.job/index.html +seedu.address.model.job/.classes/JobName.html +seedu.address.model.job/.classes/Job.html +seedu.address.logic/index_SORT_BY_NAME_DESC.html +seedu.address.logic/index_SORT_BY_METHOD_DESC.html +seedu.address.logic/index_SORT_BY_METHOD.html +seedu.address.logic/index_SORT_BY_LINE_DESC.html +seedu.address.logic.parser/index_SORT_BY_BLOCK_DESC.html +seedu.address.logic.parser/index_SORT_BY_BLOCK.html +seedu.address.logic.parser/index.html +seedu.address.logic.parser/.classes/SelectCommandParser.html +seedu.address.logic.parser/.classes/SearchCommandParser.html +seedu.address.logic.parser/.classes/Prefix.html +seedu.address.logic.parser/.classes/ParserUtil.html +seedu.address.logic.parser/.classes/Parser.html +seedu.address.logic.parser/.classes/FindCommandParser.html +seedu.address.logic.parser/.classes/EditCommandParser.html +seedu.address.logic.parser/.classes/DeleteCommandParser.html +seedu.address.logic.parser/.classes/CreateJobCommandParser.html +seedu.address.logic.parser/.classes/CliSyntax.html +seedu.address.logic.parser/.classes/ArgumentTokenizer.html +seedu.address.logic.parser/.classes/ArgumentMultimap.html +seedu.address.logic.parser/.classes/AddressBookParser.html +seedu.address.logic.parser/.classes/AddCommandParser.html +seedu.address.logic.parser.exceptions/index_SORT_BY_NAME_DESC.html +seedu.address.logic.parser.exceptions/index_SORT_BY_METHOD_DESC.html +seedu.address.logic.parser.exceptions/index_SORT_BY_METHOD.html +seedu.address.logic.parser.exceptions/index_SORT_BY_LINE_DESC.html +seedu.address.logic.parser.exceptions/index_SORT_BY_LINE.html +seedu.address.logic.parser.exceptions/index_SORT_BY_CLASS_DESC.html +seedu.address.logic.parser.exceptions/index_SORT_BY_CLASS.html +seedu.address.logic.commands/.classes/SearchCommand.html +seedu.address.logic.commands/.classes/RedoCommand.html +seedu.address.logic.commands/.classes/ListCommand.html +seedu.address.logic.commands/.classes/ImportResumesCommand.html +seedu.address.logic.commands/.classes/HistoryCommand.html +seedu.address.logic.commands/.classes/HelpCommand.html +seedu.address.logic.commands/.classes/FindCommand.html diff --git a/README.adoc b/README.adoc index b4c8aac388ae..753c421aaccf 100644 --- a/README.adoc +++ b/README.adoc @@ -1,11 +1,10 @@ -= Address Book (Level 4) += slaveFinder() ifdef::env-github,env-browser[:relfileprefix: docs/] -https://travis-ci.org/se-edu/addressbook-level4[image:https://travis-ci.org/se-edu/addressbook-level4.svg?branch=master[Build Status]] -https://ci.appveyor.com/project/damithc/addressbook-level4[image:https://ci.appveyor.com/api/projects/status/3boko2x2vr5cc3w2?svg=true[Build status]] -https://coveralls.io/github/se-edu/addressbook-level4?branch=master[image:https://coveralls.io/repos/github/se-edu/addressbook-level4/badge.svg?branch=master[Coverage Status]] -https://www.codacy.com/app/damith/addressbook-level4?utm_source=github.com&utm_medium=referral&utm_content=se-edu/addressbook-level4&utm_campaign=Badge_Grade[image:https://api.codacy.com/project/badge/Grade/fc0b7775cf7f4fdeaf08776f3d8e364a[Codacy Badge]] -https://gitter.im/se-edu/Lobby[image:https://badges.gitter.im/se-edu/Lobby.svg[Gitter chat]] +https://travis-ci.org/CS2103-AY1819S2-W15-3/main[image:https://travis-ci.org/CS2103-AY1819S2-W15-3/main.svg?branch=master[Build Status]] +https://ci.appveyor.com/project/WeeSooJun/main[image:https://ci.appveyor.com/api/projects/status/6sdm7tsfki1ubrcf?svg=true[Build status]] +https://coveralls.io/github/CS2103-AY1819S2-W15-3/main?branch=master[image:https://coveralls.io/repos/github/CS2103-AY1819S2-W15-3/main/badge.svg?branch=master[Coverage Status]] +image:https://api.codacy.com/project/badge/Grade/98250edf8ec845508bf9e8ef17dd2118["Codacy code quality", link="https://www.codacy.com/app/WeeSooJun/main?utm_source=github.com&utm_medium=referral&utm_content=CS2103-AY1819S2-W15-3/main&utm_campaign=Badge_Grade"] ifdef::env-github[] image::docs/images/Ui.png[width="600"] @@ -15,26 +14,17 @@ ifndef::env-github[] image::images/Ui.png[width="600"] endif::[] -* This is a desktop Address Book application. It has a GUI but most of the user interactions happen using a CLI (Command Line Interface). -* It is a Java sample application intended for students learning Software Engineering while using Java as the main programming language. -* It is *written in OOP fashion*. It provides a *reasonably well-written* code example that is *significantly bigger* (around 6 KLoC)than what students usually write in beginner-level SE modules. -* What's different from https://github.com/se-edu/addressbook-level3[level 3]: -** A more sophisticated GUI that includes a list panel and an in-built Browser. -** More test cases, including automated GUI testing. -** Support for _Build Automation_ using Gradle and for _Continuous Integration_ using Travis CI. +* This is a desktop HR application. It has a GUI but most of the user interactions happen using a CLI (Command Line Interface). == Site Map * <> * <> -* <> * <> * <> == Acknowledgements -* Some parts of this sample application were inspired by the excellent http://code.makery.ch/library/javafx-8-tutorial/[Java FX tutorial] by -_Marco Jakob_. -* Libraries used: https://github.com/TestFX/TestFX[TextFX], https://github.com/FasterXML/jackson[Jackson], https://github.com/google/guava[Guava], https://github.com/junit-team/junit5[JUnit5] +* AddressBook-Level4 project created by SE-EDU initiative at https://github.com/se-edu/ == Licence : link:LICENSE[MIT] diff --git a/_reposense/config.json b/_reposense/config.json new file mode 100644 index 000000000000..f75983f5d1a7 --- /dev/null +++ b/_reposense/config.json @@ -0,0 +1,46 @@ +{ + "authors": [ + { + "githubId": "DanielDSSim", + "emails": [ + "danieldssim@gmail.com" + ], + "displayName": "DanielDSSim", + "authorNames": [ + "DanielDSSim", + "Daniel" + ] + }, + { + "githubId": "chiuyuhua", + "displayName": "chiuyuhua", + "authorNames": [ + "chiuyuhua" + ] + }, + { + "githubId": "WeeSooJun", + "displayName": "WeeSooJun", + "authorNames": [ + "WeeSooJun", + "Wee Soo Jun" + ] + }, + { + "githubId": "CaesarTY", + "displayName": "CaesarTY", + "authorNames": [ + "CaesarTY" + ] + }, + { + "githubId": "COGnitiveAspian", + "displayName": "COGnitiveAspian", + "authorNames": [ + "COGnitiveAspian", + "Ong Suyi", + "Suyi" + ] + } + ] +} diff --git a/build.gradle b/build.gradle index 4f2949b6e774..b3a4612983d7 100644 --- a/build.gradle +++ b/build.gradle @@ -203,8 +203,7 @@ asciidoctor { idseparator: '-', 'site-root': "${sourceDir}", // must be the same as sourceDir, do not modify 'site-name': 'AddressBook-Level4', - 'site-githuburl': 'https://github.com/se-edu/addressbook-level4', - 'site-seedu': true, // delete this line if your project is not a fork (not a SE-EDU project) + 'site-githuburl': 'https://github.com/CS2103-AY1819S2-W15-3/main', ] options['template_dirs'].each { diff --git a/docs/AboutUs.adoc b/docs/AboutUs.adoc index e647ed1e715a..cc5ed7388ba5 100644 --- a/docs/AboutUs.adoc +++ b/docs/AboutUs.adoc @@ -4,53 +4,53 @@ :imagesDir: images :stylesDir: stylesheets -AddressBook - Level 4 was developed by the https://se-edu.github.io/docs/Team.html[se-edu] team. + -_{The dummy content given below serves as a placeholder to be used by future forks of the project.}_ + +slaveFinder() was developed by W15-3. + {empty} + We are a team based in the http://www.comp.nus.edu.sg[School of Computing, National University of Singapore]. == Project Team -=== John Doe -image::damithc.jpg[width="150", align="left"] -{empty}[http://www.comp.nus.edu.sg/~damithch[homepage]] [https://github.com/damithc[github]] [<>] +=== Chiu Yu Hua +image::chiuyuhua.png[width="150", align="left"] +[https://github.com/chiuyuhua[github]] [https://github.com/CS2103-AY1819S2-W15-3/main/blob/master/docs/team/chiuyuhua.adoc[portfolio]] -Role: Project Advisor +Role: Software and User Interface Developer +Responsibilities: Develop Analytics feature and responsible for the UI of the main app and its various interactions. ''' -=== John Roe -image::lejolly.jpg[width="150", align="left"] -{empty}[http://github.com/lejolly[github]] [<>] +=== Daniel Sim +image::danieldssim.png[width="150", align="left"] +{empty}[https://github.com/DanielDSSim[github]] [<>] Role: Team Lead + Responsibilities: UI ''' -=== Johnny Doe -image::yijinl.jpg[width="150", align="left"] -{empty}[http://github.com/yijinl[github]] [<>] +=== Ong Suyi +image::suyi.png[width="150", align="left"] +{empty}[http://github.com/COGnitiveAspian[github]] [<>] Role: Developer + Responsibilities: Data ''' -=== Johnny Roe -image::m133225.jpg[width="150", align="left"] -{empty}[http://github.com/m133225[github]] [<>] +=== Teng Yun +image::caesarty.png[width="150", align="left"] +{empty}[http://github.com/caesarty[github]] [<>] Role: Developer + Responsibilities: Dev Ops + Threading ''' -=== Benson Meier -image::yl_coder.jpg[width="150", align="left"] -{empty}[http://github.com/yl-coder[github]] [<>] +=== Wee Soo Jun +image::weesoojun.png[width="150", align="left"] +{empty}[http://github.com/weesoojun[github]] [<>] Role: Developer + -Responsibilities: UI +Responsibilities: Logic ''' diff --git a/docs/ContactUs.adoc b/docs/ContactUs.adoc index 5de5363abffd..23b7f55be246 100644 --- a/docs/ContactUs.adoc +++ b/docs/ContactUs.adoc @@ -2,6 +2,5 @@ :site-section: ContactUs :stylesDir: stylesheets -* *Bug reports, Suggestions* : Post in our https://github.com/se-edu/addressbook-level4/issues[issue tracker] if you noticed bugs or have suggestions on how to improve. +* *Bug reports, Suggestions* : Post in our https://github.com/CS2103-AY1819S2-W15-3/main/issues[issue tracker] if you noticed bugs or have suggestions on how to improve. * *Contributing* : We welcome pull requests. Follow the process described https://github.com/oss-generic/process[here] -* *Email us* : You can also reach us at `damith [at] comp.nus.edu.sg` diff --git a/docs/DeveloperGuide.adoc b/docs/DeveloperGuide.adoc index 8b92d5fb7e62..f40ad199a016 100644 --- a/docs/DeveloperGuide.adoc +++ b/docs/DeveloperGuide.adoc @@ -1,4 +1,4 @@ -= AddressBook Level 4 - Developer Guide += slaveFinder() - Developer Guide :site-section: DeveloperGuide :toc: :toc-title: @@ -13,9 +13,9 @@ ifdef::env-github[] :warning-caption: :warning: :experimental: endif::[] -:repoURL: https://github.com/se-edu/addressbook-level4/tree/master +:repoURL: https://github.com/CS2103-AY1819S2-W15-3/main -By: `Team SE-EDU`      Since: `Jun 2016`      Licence: `MIT` +By: `W15-3`      Since: `Feb 2019`      Licence: `MIT` == Setting up @@ -157,11 +157,11 @@ The sections below give more details of each component. === UI component .Structure of the UI Component -image::UiClassDiagram.png[width="800"] +image::UiComponentClassDiagram.png[width="800"] *API* : link:{repoURL}/src/main/java/seedu/address/ui/Ui.java[`Ui.java`] -The UI consists of a `MainWindow` that is made up of parts e.g.`CommandBox`, `ResultDisplay`, `PersonListPanel`, `StatusBarFooter`, `BrowserPanel` etc. All these, including the `MainWindow`, inherit from the abstract `UiPart` class. +The UI consists of a `MainWindow` that is made up of parts e.g.`CommandBox`, `ResultDisplay`, `PersonListPanel`, `StatusBarFooter`, `JobListPanel` etc. All these, including the `MainWindow`, inherit from the abstract `UiPart` class. The `UI` component uses JavaFx UI framework. The layout of these UI parts are defined in matching `.fxml` files that are in the `src/main/resources/view` folder. For example, the layout of the link:{repoURL}/src/main/java/seedu/address/ui/MainWindow.java[`MainWindow`] is specified in link:{repoURL}/src/main/resources/view/MainWindow.fxml[`MainWindow.fxml`] @@ -191,6 +191,7 @@ Given below is the Sequence Diagram for interactions within the `Logic` componen .Interactions Inside the Logic Component for the `delete 1` Command image::DeletePersonSdForLogic.png[width="800"] +// tag::model[] [[Design-Model]] === Model component @@ -205,12 +206,22 @@ The `Model`, * stores the Address Book data. * exposes an unmodifiable `ObservableList` that can be 'observed' e.g. the UI can be bound to this list so that the UI automatically updates when the data in the list change. * does not depend on any of the other three components. +* InterviewScores stores the scores of the applicant and is used in considering an applicant for hiring. +* NRIC is used as a unique identifier for an applicant. +* School of the applicant (no restrictions on the input for this). +* Race and Gender can only take specific values as spelled out in the user guide and it is used in the analytics portion of the app. +* JobsApply should always be present when a for a person. +* PastJobs, KnownProgLang and InterviewScores are optional. +* PastJobs, KnownProgLang and Major stores values as strings therefore, "Engineer, Data Scientist" are stored as one PastJobs. + +// end::model[] [NOTE] As a more OOP model, we can store a `Tag` list in `Address Book`, which `Person` can reference. This would allow `Address Book` to only require one `Tag` object per unique `Tag`, instead of each `Person` needing their own `Tag` object. An example of how such a model may look like is given below. + + image:ModelClassBetterOopDiagram.png[width="800"] + [[Design-Storage]] === Storage component @@ -293,7 +304,6 @@ The following activity diagram summarizes what happens when a user executes a ne image::UndoRedoActivityDiagram.png[width="650"] ==== Design Considerations - ===== Aspect: How undo & redo executes * **Alternative 1 (current choice):** Saves the entire address book. @@ -313,6 +323,213 @@ image::UndoRedoActivityDiagram.png[width="650"] ** Cons: Requires dealing with commands that have already been undone: We must remember to skip these commands. Violates Single Responsibility Principle and Separation of Concerns as `HistoryManager` now needs to do two different things. // end::undoredo[] +// tag::jobs[] +=== Job Feature +==== Current Implementation + +The Job feature is facilitated by the Job class and UniqueJobList Class. +UniqueJobList contains Jobs by composition while Jobs contains Person by aggregation. The main Database contains one uniqueJobList which holds all the current job openings. + +image::uniqueJobListCompositeJobAggregatePerson.png[width="650"] + +==== Storage Implementation + +Storage of the Job relies on the JsonAdaptedJob and the JsonAdaptedJobPersonList classes to store and load jobs. Instead of storing a Person in the job, due to the relationship of aggregation, +the Nric is stored instead. At load time, the UniqueJobList is only created after the UniquePersonList has been created and fills the jobs using the data from UniquePersonList. + + +==== Design considerations + +===== How to associate job with the vanilla AB4 structure +* **Alternative 1 (current choice):** Store under addressbook class +** Pros: Can easily adapt methods of add, delete, undo and redo to work with job +** Cons: Violates Single responsibility principle as addressbook class now manages both Job and Person data +* **Alternative 2:** Create an overarching class that contains both addressbook which is a database of Person(s) as well as a Job container class. +** Pros: Reduced coupling +** Cons: Implementation of an overarching class would require a major overhaul of current system especially Logic and UI components + +===== How to store Person in Job in JSON file +* **Alternative 1 (current choice):** Store only one unique person identifier and retrieve person +** Pros: Saves space extremely significantly by avoiding repeat of data due to many jobs having the same person +** Cons: When loading, requires more time and violates Law of Demeter as Job class now has access to UniquePersonList which is a class contained in Addressbook +* **Alternative 2:** Store full person data in Job +** Pros: Faster loading and adheres to Law of Demeter as the Job does not access more that what it is associated with +** Cons: Greater memory usage + +// tag::filter[] +=== Filter Feature + +With the filter feature, users can input specific parameters that act as conditions for slaveFinder() to conditionally update the `UniqueFilterList` and filter the `UniquePeronList`. Using these parameters, slaveFinder() shows applicants contains the specified parameters. Filter is visble and can combine or be deleted. + +Command Format: +**`filter [FILTERLISTNAME] fn/FILTERNAME [/]...` +**`deleteFilter [FILTERLISTNAME] fn/FILTERNAME` +**`clearFilter [FILTERLISTNAME]` + +[NOTE] +* FILTERLISTNAME indicate which Job list this command will used. + +==== Add Filter + +The command format for Adding a Filter is: + +Format: `filter [FILTERLISTNAME] fn/FILTERNAME [n/NAME] pp/PHONE_NUMBER] [nric/NRIC] [e/EMAIL] [a/ADDRESS] [g/GENDER] [r/RACE] [m/MAJOR] [s/SCHOOL] [gr/GRADE] [is1/INTERVIEWSCORESQ1] [is2/INTERVIEWSCORESQ2] [is3/INTERVIEWSCORESQ3] [is4/INTERVIEWSCORESQ4] [is5/INTERVIEWSCORESQ5] [j/JOBS_APPLY]... [kpl/KnowPROGLANG]... [pj/PASTJOB]...` + +[NOTE] +==== +* If multiple fields are provided, this command will filter the `UniquePersonList` by `AND` logic among multiple fields. +* If multiple keywords are provided in one command, this command will filter the `UniquePersonList` by `OR` logic among multiple keywords. +* Example: `filter fn/filter1 m/CS s/NUS` changes the UI to display applicants whose school is NUS *AND* major is CS. `filter fn/filter2 s/NUS NTU` changes the UI to display applicants whose school is NUS *OR* NTU. +==== + + +Upon entering the `filter` command, the `filter` command word is stripped from the input and the argument fields are passed into the `FilterCommandParser` class. The FilterListName will be stripped from the argument and parse to `LISTNAME` object. `FilterCommandParser` tokenizes the other argument string using `ArgumentTokenizer` object, The regular expressions will be checked and mapping each parameter to it's respective prefix in an `ArgumentMultiMap` object. `FilterCommandParser` then creates a `predicatePersonDescriptor` object using the parameter +values in `ArgumentMultiMap` for each filter. If invalid parameters are specified by the user, or if an invalid `FILTERLISTNAME` was be inputed, or there is no filter name is provided, then `FilterCommandParser` throws a `ParseException` and displays an error message to the user. + +If valid inputs are provided, `predicatePersonDescriptor` will be created and `FilterCommandParser` will return a `FilterCommand` with parameters `predicatePersonDescriptor` and `FilterName` and `FilterListName`. `FilterCommand` then creates a Predicate Manager object (implements Java 8’s Predicate interface) using the parameter values in `predicatePersonDescriptor` for each filter condition, and combines them into one single Predicate using the and() function in Predicate interface. After that, `FilterCommand` calls the addPredicate method in Model to set the Predicate List (indicated by FilterListName).In the end `FilterCommand` calls the updateFilteredPersonList method in Model to update applicants using all current undeleted PredicateManager object. UI will change and displaying all undeleted Filter name label and the Person who evaluates the set Predicate to true. If repeated filterName are specified by the user, or if an non-empty `FILTERLISTNAME` was be inputed in All Job Screen mode, or if no `FILTERLISTNAME` was be inputed in JOb Detail Screen mode, then `FilterCommandParser` throws a `CommandException` and displays an error message to the user. + +===== Current Implementation + +The following sequence diagram shows the flow of events when the `filter fn/nus` command is entered by the user: + +image::FilterSD.png[width=800] +Figure: Sequence diagram illustrating the interactions between the +`Logic` and `Model` components when `filter` command is called. + +==== Delete Filter + +The command format for Deleting a filter is: + +Format: `deleteFilter [FILTERLISTNAME] FILTERNAME` + + +Upon entering the `deleteFilter` command, the `deleteFfilter` command word is stripped from the input and the argument fields are passed into the `DeleteFilterCommandParser` class. The FilterListName will be stripped from the argument and parse to `LISTNAME` object. `DeleteFilterCommandParser` tokenizes the FILTERNAME. If an invalid `FILTERLISTNAME` was be inputed, or there is no filter name is provided, then `DeleteFilterCommandParser` throws a `ParseException` and displays an error message to the user. + +If valid inputs are provided, `DeleteFilterCommandParser` will return a `DeleteFilterCommand` with parameters `FilterName` and `FilterListName`. `FilterCommand`. After that, `DeleteFilterCommand` calls the removePredicate method in Model to set the Predicate List (indicated by FilterListName).In the end `FilterCommand` calls the updateFilteredPersonList method in Model to update applicants using all current undeleted PredicateManager object. UI will change and displaying all undeleted Filter name label and the Person who evaluates the set Predicate to true. If no filterName are found, or if an non-empty `FILTERLISTNAME` was be inputed in All Job Screen mode, or if no `FILTERLISTNAME` was be inputed in JOb Detail Screen mode, then `DeleteFilterCommandParser` throws a `CommandException` and displays an error message to the user. + +==== Clear Filter + +The command format for Clearing a filter is: + +Format: `clearFilter [FILTERLISTNAME] ` + + +Upon entering the `clearFilter` command, the `clearFfilter` command word is stripped from the input and the argument fields are passed into the `ClearFilterCommandParser` class. The FilterListName will be stripped from the argument and parse to `LISTNAME` object. `ClearFilterCommandParser` tokenizes the FILTERNAME. If an invalid `FILTERLISTNAME` was be inputed, then `ClearFilterCommandParser` throws a `ParseException` and displays an error message to the user. + +If valid inputs are provided, `ClearFilterCommandParser` will return a `ClearFilterCommand` with parameters `FilterName` and `FilterListName`. `FilterCommand`. After that, `ClearFilterCommand` calls the clearPredicate method in Model to set the Predicate List (indicated by FilterListName).In the end `ClearFilterCommand` calls the updateFilteredPersonList method in Model to update applicants using an always true PredicateManager object. UI will change and displaying an empty filter panel and the all Persons will show. If an non-empty `FILTERLISTNAME` was be inputed in All Job Screen mode, or if no `FILTERLISTNAME` was be inputed in JOb Detail Screen mode, then `ClearFilterCommandParser` throws a `CommandException` and displays an error message to the user. + +==== Design Considerations + +===== Aspect: How to parse parameters in filter command + +* **Alternative 1 (current choice):** `FilterCommandParse` Create a `predicatePersonDescriptor` object and parse it to `FilterCommand` +** Pros: +*** Make filterCommand more comparable. We can compare `predicatePersonDescriptor` to say whether two filter Command is same. +*** Have better contol on a Filtercommand. +** Cons: Logic is indirect. +* **Alternative 2:** `FilterCommandParse` combine all conditions (parameters) in a Predicate and parse it to `FilterCommand` +** Pros: Logic is direct. +** Cons: Predicate interface is incomparable so this make test more difficult. + +===== Aspect: How to design a filter name restricted format + +* **Alternative 1 (current choice):** Filter name can be any String. +** Pros: +*** Make Filter name more flexible. +*** A filter may include many information. But user can only see this filter by it's Filter name label. So it should allow user make a more detailed name for memory and control. +** Cons: Because user can take unpredictable signas their filter name, so it may cause unpredictable bugs. +* **Alternative 2:** Filter name should only be restricted in the specified format +** Pros: Easy to control and handle error. +** Cons: User more need more complicated filter name. + +===== Aspect: How to handle filter grade and interview scores + +* **Alternative 1 (current choice):** Filter parameter become a value range and person's score in this range will be returned. +** Pros: +*** Make more sense on filtering value related field. +*** HR manager like to know peron's score in a range, not exactly in a specified value. +** Cons: May add additional logic and error handling. +* **Alternative 2:** Same logic as other field +** Pros: Easy to implement. +** Cons: HR manager like to know peron's score in a range, not exactly equal to a specified value. + +// end::filter[] + +=== Analytics Feature +==== Current Implementation + +The analytics is facilitated by the Analytics class. Analytics data are generated in real time depending on the specific job currently on display in the software by the user. Hence it will not be saved as states in the `versionedAddressBook`. It pulls required person list to generate data from Model, which consists of lists: `displayedFilter`, `activeJobAllApplicants`, `activeJobKiv`, `activeJobInterview`, `activeJobShortist`. An `Analytics` object will be created by Analytics class, storing the various required data generated, and pass it to Logic and UI for display. + +Given below is the sequence diagram for `analytics`: + +image::AnalyticsDiagram.png[width="790"] + +// tag::interviews[] +=== Interviews Feature +==== Current Implementation + +The interviews and its functionalities are facilitated by the Interviews class. +It is a private field present in the versionedAddressBook, to facilitate the integration of interviews and its functions with undo/redo operation in slaveFinder(). +(Show is not counted as an operation and hence it is not saved as a state). +The reason for using Calendar over LocalDate is that in v2.0, the app can be upgrade to schedule interviews for any time instead of only scheduling by days. +The Interview class acts as an association class between calendar and person. + +image::InterviewsAssociationClass.png[width="650"] + +For the generate command. There are total 2 parameters that determines how the interviews list is generated. +The 2 parameters are: maxInterviewsADay and blockOutDates. +maxInterviewsADay determines the number of interviews that can be scheduled a day and it is saved in the interviews class +blockOutDates are the user's own block out dates which represent unavailable dates that the user is not free and therefore interviews cannot be scheduled. +Another thing to note is that weekends are not considered in the scheduled as the user is assumed to have normal working hours from Monday to Friday. + +Given below is the sequence diagram for generateInterviews: + +image::GenerateInterviewsDiagram.png[width="650"] + +An example of generateInterviews: + +Step 1. User launches the application. The blockOutDates field in the interviews class is currently empty. + +Step 2. User sets the block out dates with setBlockOutDates command. User does not change the max interviews a day and it is set to a default value of 2. + +Step 3. User request for interviews to be generated. + +Step 4. Interviews are generated with the restriction based on the parameters and working hours of a normal working adult. + +Interviews also works with deletePerson, that is, when a person is deleted, he/she is also removed from his/her scheduled interview slots. + +image::InterviewsDeletePersonSequenceDiagram.png[width="650"] + +An example of deleting a person: + +Step 1. User launches the application and generates interviews. + +Step 2. User deletes the person from slaveFinder() + +Step 3. Person is removed from his/her scheduled interview date + +interviews.removePerson(p) is called. A collection of ArrayList of person is taken from the HashMap and since the collection is backed by the map, any changes to the collection will be reflected in the hashmap. +Thus removal of person will be reflected in the hashmap. Therefore this method of removal was used. + +// end::interviews[] + +===== Aspect: How interviews is integrated with delete Person command + +* When a person is removed, naturally he/she should also be removed from the scheduled interviews. A remove person function is implemented in interviews which potentially could be used by the v2.0 reschedule command to remove or reschedule persons for interviews. + +===== Aspect: How interviews is integrated with existing undo/redo + +* **Alternative 1 (current choice):** In order to hinge on the existing undo/redo command, interviews was implemented as a field in AddressBook.java. In the event that undo and redo is called, the data is reset to that of a previous state. +In the current implementation, the data is copied over and could take up a lot of time if there is a large amount of dates to copy over. + +** Pros: Easy to implement. +** Cons: Performance issues may arise in terms of memory usage + +* **Alternative 2:** Implement a function that keeps track of what the user does to interviews and each time a undo is called, the previous command's inverse function is called. + +** Pros: Less memory is used +** Cons: Need to consider all possible commands, when adding new command, need to include an inverse command. + // tag::dataencryption[] === [Proposed] Data Encryption @@ -528,350 +745,239 @@ A project often depends on third-party libraries. For example, Address Book depe . Include those libraries in the repo (this bloats the repo size) . Require developers to download those libraries manually (this creates extra work for developers) -[[GetStartedProgramming]] [appendix] -== Suggested Programming Tasks to Get Started - -Suggested path for new programmers: - -1. First, add small local-impact (i.e. the impact of the change does not go beyond the component) enhancements to one component at a time. Some suggestions are given in <>. - -2. Next, add a feature that touches multiple components to learn how to implement an end-to-end feature across all components. <> explains how to go about adding such a feature. - -[[GetStartedProgramming-EachComponent]] -=== Improving each component - -Each individual exercise in this section is component-based (i.e. you would not need to modify the other components to get it to work). - -[discrete] -==== `Logic` component - -*Scenario:* You are in charge of `logic`. During dog-fooding, your team realize that it is troublesome for the user to type the whole command in order to execute a command. Your team devise some strategies to help cut down the amount of typing necessary, and one of the suggestions was to implement aliases for the command words. Your job is to implement such aliases. - -[TIP] -Do take a look at <> before attempting to modify the `Logic` component. - -. Add a shorthand equivalent alias for each of the individual commands. For example, besides typing `clear`, the user can also type `c` to remove all persons in the list. -+ -**** -* Hints -** Just like we store each individual command word constant `COMMAND_WORD` inside `*Command.java` (e.g. link:{repoURL}/src/main/java/seedu/address/logic/commands/FindCommand.java[`FindCommand#COMMAND_WORD`], link:{repoURL}/src/main/java/seedu/address/logic/commands/DeleteCommand.java[`DeleteCommand#COMMAND_WORD`]), you need a new constant for aliases as well (e.g. `FindCommand#COMMAND_ALIAS`). -** link:{repoURL}/src/main/java/seedu/address/logic/parser/AddressBookParser.java[`AddressBookParser`] is responsible for analyzing command words. -* Solution -** Modify the switch statement in link:{repoURL}/src/main/java/seedu/address/logic/parser/AddressBookParser.java[`AddressBookParser#parseCommand(String)`] such that both the proper command word and alias can be used to execute the same intended command. -** Add new tests for each of the aliases that you have added. -** Update the user guide to document the new aliases. -** See this https://github.com/se-edu/addressbook-level4/pull/785[PR] for the full solution. -**** - -[discrete] -==== `Model` component - -*Scenario:* You are in charge of `model`. One day, the `logic`-in-charge approaches you for help. He wants to implement a command such that the user is able to remove a particular tag from everyone in the address book, but the model API does not support such a functionality at the moment. Your job is to implement an API method, so that your teammate can use your API to implement his command. - -[TIP] -Do take a look at <> before attempting to modify the `Model` component. - -. Add a `removeTag(Tag)` method. The specified tag will be removed from everyone in the address book. -+ -**** -* Hints -** The link:{repoURL}/src/main/java/seedu/address/model/Model.java[`Model`] and the link:{repoURL}/src/main/java/seedu/address/model/AddressBook.java[`AddressBook`] API need to be updated. -** Think about how you can use SLAP to design the method. Where should we place the main logic of deleting tags? -** Find out which of the existing API methods in link:{repoURL}/src/main/java/seedu/address/model/AddressBook.java[`AddressBook`] and link:{repoURL}/src/main/java/seedu/address/model/person/Person.java[`Person`] classes can be used to implement the tag removal logic. link:{repoURL}/src/main/java/seedu/address/model/AddressBook.java[`AddressBook`] allows you to update a person, and link:{repoURL}/src/main/java/seedu/address/model/person/Person.java[`Person`] allows you to update the tags. -* Solution -** Implement a `removeTag(Tag)` method in link:{repoURL}/src/main/java/seedu/address/model/AddressBook.java[`AddressBook`]. Loop through each person, and remove the `tag` from each person. -** Add a new API method `deleteTag(Tag)` in link:{repoURL}/src/main/java/seedu/address/model/ModelManager.java[`ModelManager`]. Your link:{repoURL}/src/main/java/seedu/address/model/ModelManager.java[`ModelManager`] should call `AddressBook#removeTag(Tag)`. -** Add new tests for each of the new public methods that you have added. -** See this https://github.com/se-edu/addressbook-level4/pull/790[PR] for the full solution. -**** - -[discrete] -==== `Ui` component - -*Scenario:* You are in charge of `ui`. During a beta testing session, your team is observing how the users use your address book application. You realize that one of the users occasionally tries to delete non-existent tags from a contact, because the tags all look the same visually, and the user got confused. Another user made a typing mistake in his command, but did not realize he had done so because the error message wasn't prominent enough. A third user keeps scrolling down the list, because he keeps forgetting the index of the last person in the list. Your job is to implement improvements to the UI to solve all these problems. - -[TIP] -Do take a look at <> before attempting to modify the `UI` component. - -. Use different colors for different tags inside person cards. For example, `friends` tags can be all in brown, and `colleagues` tags can be all in yellow. -+ -**Before** -+ -image::getting-started-ui-tag-before.png[width="300"] -+ -**After** -+ -image::getting-started-ui-tag-after.png[width="300"] -+ -**** -* Hints -** The tag labels are created inside link:{repoURL}/src/main/java/seedu/address/ui/PersonCard.java[the `PersonCard` constructor] (`new Label(tag.tagName)`). https://docs.oracle.com/javase/8/javafx/api/javafx/scene/control/Label.html[JavaFX's `Label` class] allows you to modify the style of each Label, such as changing its color. -** Use the .css attribute `-fx-background-color` to add a color. -** You may wish to modify link:{repoURL}/src/main/resources/view/DarkTheme.css[`DarkTheme.css`] to include some pre-defined colors using css, especially if you have experience with web-based css. -* Solution -** You can modify the existing test methods for `PersonCard` 's to include testing the tag's color as well. -** See this https://github.com/se-edu/addressbook-level4/pull/798[PR] for the full solution. -*** The PR uses the hash code of the tag names to generate a color. This is deliberately designed to ensure consistent colors each time the application runs. You may wish to expand on this design to include additional features, such as allowing users to set their own tag colors, and directly saving the colors to storage, so that tags retain their colors even if the hash code algorithm changes. -**** - -. Modify link:{repoURL}/src/main/java/seedu/address/commons/events/ui/NewResultAvailableEvent.java[`NewResultAvailableEvent`] such that link:{repoURL}/src/main/java/seedu/address/ui/ResultDisplay.java[`ResultDisplay`] can show a different style on error (currently it shows the same regardless of errors). -+ -**Before** -+ -image::getting-started-ui-result-before.png[width="200"] -+ -**After** -+ -image::getting-started-ui-result-after.png[width="200"] -+ -**** -* Hints -** link:{repoURL}/src/main/java/seedu/address/commons/events/ui/NewResultAvailableEvent.java[`NewResultAvailableEvent`] is raised by link:{repoURL}/src/main/java/seedu/address/ui/CommandBox.java[`CommandBox`] which also knows whether the result is a success or failure, and is caught by link:{repoURL}/src/main/java/seedu/address/ui/ResultDisplay.java[`ResultDisplay`] which is where we want to change the style to. -** Refer to link:{repoURL}/src/main/java/seedu/address/ui/CommandBox.java[`CommandBox`] for an example on how to display an error. -* Solution -** Modify link:{repoURL}/src/main/java/seedu/address/commons/events/ui/NewResultAvailableEvent.java[`NewResultAvailableEvent`] 's constructor so that users of the event can indicate whether an error has occurred. -** Modify link:{repoURL}/src/main/java/seedu/address/ui/ResultDisplay.java[`ResultDisplay#handleNewResultAvailableEvent(NewResultAvailableEvent)`] to react to this event appropriately. -** You can write two different kinds of tests to ensure that the functionality works: -*** The unit tests for `ResultDisplay` can be modified to include verification of the color. -*** The system tests link:{repoURL}/src/test/java/systemtests/AddressBookSystemTest.java[`AddressBookSystemTest#assertCommandBoxShowsDefaultStyle() and AddressBookSystemTest#assertCommandBoxShowsErrorStyle()`] to include verification for `ResultDisplay` as well. -** See this https://github.com/se-edu/addressbook-level4/pull/799[PR] for the full solution. -*** Do read the commits one at a time if you feel overwhelmed. -**** - -. Modify the link:{repoURL}/src/main/java/seedu/address/ui/StatusBarFooter.java[`StatusBarFooter`] to show the total number of people in the address book. -+ -**Before** -+ -image::getting-started-ui-status-before.png[width="500"] -+ -**After** -+ -image::getting-started-ui-status-after.png[width="500"] -+ -**** -* Hints -** link:{repoURL}/src/main/resources/view/StatusBarFooter.fxml[`StatusBarFooter.fxml`] will need a new `StatusBar`. Be sure to set the `GridPane.columnIndex` properly for each `StatusBar` to avoid misalignment! -** link:{repoURL}/src/main/java/seedu/address/ui/StatusBarFooter.java[`StatusBarFooter`] needs to initialize the status bar on application start, and to update it accordingly whenever the address book is updated. -* Solution -** Modify the constructor of link:{repoURL}/src/main/java/seedu/address/ui/StatusBarFooter.java[`StatusBarFooter`] to take in the number of persons when the application just started. -** Use link:{repoURL}/src/main/java/seedu/address/ui/StatusBarFooter.java[`StatusBarFooter#handleAddressBookChangedEvent(AddressBookChangedEvent)`] to update the number of persons whenever there are new changes to the addressbook. -** For tests, modify link:{repoURL}/src/test/java/guitests/guihandles/StatusBarFooterHandle.java[`StatusBarFooterHandle`] by adding a state-saving functionality for the total number of people status, just like what we did for save location and sync status. -** For system tests, modify link:{repoURL}/src/test/java/systemtests/AddressBookSystemTest.java[`AddressBookSystemTest`] to also verify the new total number of persons status bar. -** See this https://github.com/se-edu/addressbook-level4/pull/803[PR] for the full solution. -**** - -[discrete] -==== `Storage` component - -*Scenario:* You are in charge of `storage`. For your next project milestone, your team plans to implement a new feature of saving the address book to the cloud. However, the current implementation of the application constantly saves the address book after the execution of each command, which is not ideal if the user is working on limited internet connection. Your team decided that the application should instead save the changes to a temporary local backup file first, and only upload to the cloud after the user closes the application. Your job is to implement a backup API for the address book storage. - -[TIP] -Do take a look at <> before attempting to modify the `Storage` component. - -. Add a new method `backupAddressBook(ReadOnlyAddressBook)`, so that the address book can be saved in a fixed temporary location. -+ -**** -* Hint -** Add the API method in link:{repoURL}/src/main/java/seedu/address/storage/AddressBookStorage.java[`AddressBookStorage`] interface. -** Implement the logic in link:{repoURL}/src/main/java/seedu/address/storage/StorageManager.java[`StorageManager`] and link:{repoURL}/src/main/java/seedu/address/storage/JsonAddressBookStorage.java[`JsonAddressBookStorage`] class. -* Solution -** See this https://github.com/se-edu/addressbook-level4/pull/594[PR] for the full solution. -**** - -[[GetStartedProgramming-RemarkCommand]] -=== Creating a new command: `remark` - -By creating this command, you will get a chance to learn how to implement a feature end-to-end, touching all major components of the app. - -*Scenario:* You are a software maintainer for `addressbook`, as the former developer team has moved on to new projects. The current users of your application have a list of new feature requests that they hope the software will eventually have. The most popular request is to allow adding additional comments/notes about a particular contact, by providing a flexible `remark` field for each contact, rather than relying on tags alone. After designing the specification for the `remark` command, you are convinced that this feature is worth implementing. Your job is to implement the `remark` command. - -==== Description -Edits the remark for a person specified in the `INDEX`. + -Format: `remark INDEX r/[REMARK]` - -Examples: +== Product Scope -* `remark 1 r/Likes to drink coffee.` + -Edits the remark for the first person to `Likes to drink coffee.` -* `remark 1 r/` + -Removes the remark for the first person. +*Target user profile*: HR Executive -==== Step-by-step Instructions +* has a need to manage a significant number of job openings and applicants +* prefer desktop apps over other types +* can type fast +* prefers typing over mouse input +* is reasonably comfortable using CLI apps -===== [Step 1] Logic: Teach the app to accept 'remark' which does nothing -Let's start by teaching the application how to parse a `remark` command. We will add the logic of `remark` later. +*Value proposition*: manage contacts faster than a typical mouse/GUI driven app -**Main:** +[appendix] +== User Stories -. Add a `RemarkCommand` that extends link:{repoURL}/src/main/java/seedu/address/logic/commands/Command.java[`Command`]. Upon execution, it should just throw an `Exception`. -. Modify link:{repoURL}/src/main/java/seedu/address/logic/parser/AddressBookParser.java[`AddressBookParser`] to accept a `RemarkCommand`. +Priorities: High (must have) - `* * \*`, Medium (nice to have) - `* \*`, Low (unlikely to have) - `*` -**Tests:** +[width="59%",cols="22%,<23%,<25%,<30%",options="header",] +|======================================================================= +|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 -. Add `RemarkCommandTest` that tests that `execute()` throws an Exception. -. Add new test method to link:{repoURL}/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java[`AddressBookParserTest`], which tests that typing "remark" returns an instance of `RemarkCommand`. +|`* * *` |HR |add a new person | -===== [Step 2] Logic: Teach the app to accept 'remark' arguments -Let's teach the application to parse arguments that our `remark` command will accept. E.g. `1 r/Likes to drink coffee.` +|`* * *` |HR |delete a person |reject applicants not suited for the job -**Main:** +|`* * *` |HR |find a person by personal information |locate details of persons without having to go through the entire list -. Modify `RemarkCommand` to take in an `Index` and `String` and print those two parameters as the error message. -. Add `RemarkCommandParser` that knows how to parse two arguments, one index and one with prefix 'r/'. -. Modify link:{repoURL}/src/main/java/seedu/address/logic/parser/AddressBookParser.java[`AddressBookParser`] to use the newly implemented `RemarkCommandParser`. +|`* * *` |HR |filter persons by some specific requirements |filter out the persons who are not qualified efficiently. -**Tests:** +|`* * *` |HR |create a job posting |have a place to store and keep track of applicants' progress -. Modify `RemarkCommandTest` to test the `RemarkCommand#equals()` method. -. Add `RemarkCommandParserTest` that tests different boundary values -for `RemarkCommandParser`. -. Modify link:{repoURL}/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java[`AddressBookParserTest`] to test that the correct command is generated according to the user input. +|`* * *` |HR |move people to the job posting |start to manage job applications -===== [Step 3] Ui: Add a placeholder for remark in `PersonCard` -Let's add a placeholder on all our link:{repoURL}/src/main/java/seedu/address/ui/PersonCard.java[`PersonCard`] s to display a remark for each person later. +|`* * *` |HR |move only selected people to jobs and application progress lists |more easily manage the progress of applications -**Main:** +|`* * *` |HR |view all applicant progress of job application |know and keep track of who has applied for the job and their progress -. Add a `Label` with any random text inside link:{repoURL}/src/main/resources/view/PersonListCard.fxml[`PersonListCard.fxml`]. -. Add FXML annotation in link:{repoURL}/src/main/java/seedu/address/ui/PersonCard.java[`PersonCard`] to tie the variable to the actual label. +|`* * *` |HR |get the best people for a job |please my boss -**Tests:** +|`* * *` |HR |look at the analytics of the job applicants | to review the quality of job applicants and ensure a better hiring process -. Modify link:{repoURL}/src/test/java/guitests/guihandles/PersonCardHandle.java[`PersonCardHandle`] so that future tests can read the contents of the remark label. +|`* * *` |HR |easily store all txt resumes in slaveFinder() |be more efficient +// tag::interviewsUserStories[] +|`* * *` |HR |arrange interview dates quickly |to be more efficient in finding the right person for the job -===== [Step 4] Model: Add `Remark` class -We have to properly encapsulate the remark in our link:{repoURL}/src/main/java/seedu/address/model/person/Person.java[`Person`] class. Instead of just using a `String`, let's follow the conventional class structure that the codebase already uses by adding a `Remark` class. +|`* * *` |HR |include block out dates in before scheduling interviews |to be more efficient in finding the right person for the job +// end::interviewsUserStories[] -**Main:** +|`* *` |HR |delete a job posting |maintain a cleaner interface -. Add `Remark` to model component (you can copy from link:{repoURL}/src/main/java/seedu/address/model/person/Address.java[`Address`], remove the regex and change the names accordingly). -. Modify `RemarkCommand` to now take in a `Remark` instead of a `String`. +|`* *` |HR |look at the list of applicants I have narrowed down |send them to boss for approval -**Tests:** +|`* *` |HR |hide <> by default |minimize chance of someone else seeing them by accident -. Add test for `Remark`, to test the `Remark#equals()` method. +|`*` |user with many persons in the address book |sort persons by name |locate a person easily -===== [Step 5] Model: Modify `Person` to support a `Remark` field -Now we have the `Remark` class, we need to actually use it inside link:{repoURL}/src/main/java/seedu/address/model/person/Person.java[`Person`]. +|======================================================================= -**Main:** -. Add `getRemark()` in link:{repoURL}/src/main/java/seedu/address/model/person/Person.java[`Person`]. -. You may assume that the user will not be able to use the `add` and `edit` commands to modify the remarks field (i.e. the person will be created without a remark). -. Modify link:{repoURL}/src/main/java/seedu/address/model/util/SampleDataUtil.java/[`SampleDataUtil`] to add remarks for the sample data (delete your `data/addressbook.json` so that the application will load the sample data when you launch it.) +[appendix] +== Use Cases -===== [Step 6] Storage: Add `Remark` field to `JsonAdaptedPerson` class -We now have `Remark` s for `Person` s, but they will be gone when we exit the application. Let's modify link:{repoURL}/src/main/java/seedu/address/storage/JsonAdaptedPerson.java[`JsonAdaptedPerson`] to include a `Remark` field so that it will be saved. +(For all use cases below, the *System* is the `slaveFinder()` and the *Actor* is the `user`, unless specified otherwise) -**Main:** +=== Use case: Delete person -. Add a new JSON field for `Remark`. +*MSS* -**Tests:** +1. User requests to list persons +2. slaveFinder() shows a list of persons +3. User requests to delete a specific person in the list +4. slaveFinder() deletes the person ++ +Use case ends. -. Fix `invalidAndValidPersonAddressBook.json`, `typicalPersonsAddressBook.json`, `validAddressBook.json` etc., such that the JSON tests will not fail due to a missing `remark` field. +*Extensions* -===== [Step 6b] Test: Add withRemark() for `PersonBuilder` -Since `Person` can now have a `Remark`, we should add a helper method to link:{repoURL}/src/test/java/seedu/address/testutil/PersonBuilder.java[`PersonBuilder`], so that users are able to create remarks when building a link:{repoURL}/src/main/java/seedu/address/model/person/Person.java[`Person`]. +[none] +* 2a. The list is empty. ++ +Use case ends. -**Tests:** +* 3a. The given index is invalid. ++ +[none] +** 3a1. AddressBook shows an error message. ++ +Use case resumes at step 2. -. Add a new method `withRemark()` for link:{repoURL}/src/test/java/seedu/address/testutil/PersonBuilder.java[`PersonBuilder`]. This method will create a new `Remark` for the person that it is currently building. -. Try and use the method on any sample `Person` in link:{repoURL}/src/test/java/seedu/address/testutil/TypicalPersons.java[`TypicalPersons`]. +=== Use case: Create and view Job opening -===== [Step 7] Ui: Connect `Remark` field to `PersonCard` -Our remark label in link:{repoURL}/src/main/java/seedu/address/ui/PersonCard.java[`PersonCard`] is still a placeholder. Let's bring it to life by binding it with the actual `remark` field. +*MSS* -**Main:** +1. User requests to create a new job +2. slaveFinder() creates the new job +3. User requests to add people to the new job +4. slaveFinder() shows updated job data +5. User requests to view job information +6. slaveFinder() changes view to job display panel ++ +Use case ends. -. Modify link:{repoURL}/src/main/java/seedu/address/ui/PersonCard.java[`PersonCard`]'s constructor to bind the `Remark` field to the `Person` 's remark. +*Extensions* -**Tests:** +[none] +* 2a. Job with name already exists ++ +Use case ends. -. Modify link:{repoURL}/src/test/java/seedu/address/ui/testutil/GuiTestAssert.java[`GuiTestAssert#assertCardDisplaysPerson(...)`] so that it will compare the now-functioning remark label. +* 3a. No people in database +** 3a1. User adds people to database ++ +Use case resumes at step 3. -===== [Step 8] Logic: Implement `RemarkCommand#execute()` logic -We now have everything set up... but we still can't modify the remarks. Let's finish it up by adding in actual logic for our `remark` command. +=== Use case: Manage people in job opening -**Main:** +*MSS* -. Replace the logic in `RemarkCommand#execute()` (that currently just throws an `Exception`), with the actual logic to modify the remarks of a person. +1. User requests to view job information +2. slaveFinder() changes view to job display panel +3. User selects a few people and requests to move them +4. slaveFinder() moves the people and displays updated information ++ +Use case ends. -**Tests:** +*Extensions* -. Update `RemarkCommandTest` to test that the `execute()` logic works. +[none] +* 2a. Job does not exist ++ +Use case ends. -==== Full Solution +* 3a. No people in database +** 3a1. User adds people to job ++ +Use case resumes at step 3. -See this https://github.com/se-edu/addressbook-level4/pull/599[PR] for the step-by-step solution. +// tag::interviewsUseCases[] -[appendix] -== Product Scope +=== Use case: Generate and show interview dates -*Target user profile*: +*MSS* -* has a need to manage a significant number of contacts -* prefer desktop apps over other types -* can type fast -* prefers typing over mouse input -* is reasonably comfortable using CLI apps +1. User requests to arrange interview dates for applicants in slaveFinder() +2. slaveFinder() assigns to each interviewee a date +3. User request for the interview dates list ++ +Use case ends. -*Value proposition*: manage contacts faster than a typical mouse/GUI driven app +*Extensions* -[appendix] -== User Stories +[none] +* 1a. User wants to set block out dates so no dates are arranged on that day. +** 1a1. Block out dates set using the command. ++ +Use case resumes at step 2. -Priorities: High (must have) - `* * \*`, Medium (nice to have) - `* \*`, Low (unlikely to have) - `*` +* 1b. User wants to set maximum interviews a day +** 1b1. Max interviews a day set by using the command ++ +Use case resumes at step 2. -[width="59%",cols="22%,<23%,<25%,<30%",options="header",] -|======================================================================= -|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 +[none] +* 3a. User is not satisfied with the dates +** 3a1. User request to reassign a person to another date +** 3a2. slaveFinder() reassigns that person +** 3a3. slaveFinder() shows the updated interviews dates ++ +Use case ends. -|`* * *` |user |add a new person | +=== Use case: Clear interview dates -|`* * *` |user |delete a person |remove entries that I no longer need +*MSS* -|`* * *` |user |find a person by name |locate details of persons without having to go through the entire list +1. User request to clear interviews +2. slaveFinder() clears interviews ++ +Use case ends. -|`* *` |user |hide <> by default |minimize chance of someone else seeing them by accident +*Extensions* -|`*` |user with many persons in the address book |sort persons by name |locate a person easily -|======================================================================= +* 3a. User wants to recover the cleared interviews through undo +* 3b. slaveFinder() undos the clear interview operation +* 3c. Previous interview dates are recovered. ++ +Use case ends. -_{More to be added}_ +// end::interviewsUseCases[] -[appendix] -== Use Cases +=== Use case: Search person -(For all use cases below, the *System* is the `AddressBook` and the *Actor* is the `user`, unless specified otherwise) +// tag::filterUseCase[] +=== Use case: Filter person -[discrete] -=== Use case: Delete person *MSS* 1. User requests to list persons -2. AddressBook shows a list of persons -3. User requests to delete a specific person in the list -4. AddressBook deletes the person +2. slaveFinder() shows a list of persons +3. User requests to filter persons fulfill some requirements in the list +4. slaveFinder() shows a list of target persons + Use case ends. *Extensions* +*Extensions* + [none] * 2a. The list is empty. + Use case ends. -* 3a. The given index is invalid. +* 3a. The given command is invalid. + [none] -** 3a1. AddressBook shows an error message. +** 3a1. slaveFinder() shows an error message. + Use case resumes at step 2. +// end::filterUseCase[] + +=== Use case: View Analytics + +*MSS* + +1. User requests to display various lists of applicants from one of the jobs in all job openings lists +2. slaveFinder() shows lists of persons for specific job +3. User requests to view analytics for specific list of persons +4. slaveFinder() shows analytics results ++ +Use case ends. _{More to be added}_ @@ -918,6 +1024,21 @@ Given below are instructions to test the app manually. [NOTE] These instructions only provide a starting point for testers to work on; testers are expected to do more _exploratory_ testing. +// tag::interviewsTestCommands[] +For interviews test commands: + +* setMaxInterviewsADay 3 +* setBlockOutDates 22/04/2019 - 24/04/2019 +* generateInterviews +* showInterviews +* delete 1 (check that interviews works with delete) +* showInterviews +* clearInterviews +* showInterviews +* undo (check that interviews works with undo) +* redo (check that interviews works with redo) +// end::interviewsTestCommands[] + === Launch and Shutdown . Initial launch @@ -946,7 +1067,30 @@ _{ more test cases ... }_ .. Other incorrect delete commands to try: `delete`, `delete x` (where x is larger than the list size) _{give more}_ + Expected: Similar to previous. -_{ more test cases ... }_ +// tag::filterManualTest[] +=== Filtering a list of persons + +. Filtering a list of person while All Job Screen show + +.. Prerequisites: Using `list` command to get to All Job Screen. +.. Test case: `filter fn/CS m/CS` + + Expected: All persons with major CS in All Applicants list will show, a filter named "CS" will show on Filter list panel. +.. Test case: `filter none` + + Expected: List unchangeed. Error details shown in the status message. Status bar remains the same. +.. Other incorrect filter commands to try: `filter a`, `filter a fn/CS`,`filter b fn/CS` + Expected: Similar to previous. + +. Filtering a list of person while Job Detail Screen show + +.. Prerequisites: Create a job object using the `createJob` command. Display job list using the `DisplayJob` command. 4 lists of persons + + will show. +.. Test case: `filter a fn/CS m/CS` + + Expected: All persons with major CS in "Applicant" list will show on the Applicant list, a filter named "CS" will show on Filter list panel. +.. Test case: `filter none` + + Expected: List unchangeed. Error details shown in the status message. Status bar remains the same. +.. Other incorrect filter commands to try: `filter a`, `filter fn/CS`,`filter b fn/CS` + Expected: Similar to previous. +// end::filterManualTest[] === Saving data diff --git a/docs/UserGuide.adoc b/docs/UserGuide.adoc index 7e0070e12f49..b807cd80ebf0 100644 --- a/docs/UserGuide.adoc +++ b/docs/UserGuide.adoc @@ -1,8 +1,9 @@ -= AddressBook Level 4 - User Guide += slaveFinder() - User Guide :site-section: UserGuide :toc: :toc-title: :toc-placement: preamble +:toclevels: 4 :sectnums: :imagesDir: images :stylesDir: stylesheets @@ -12,13 +13,17 @@ ifdef::env-github[] :tip-caption: :bulb: :note-caption: :information_source: endif::[] -:repoURL: https://github.com/se-edu/addressbook-level4 +:repoURL: https://github.com/CS2103-AY1819S2-W15-3/main -By: `Team SE-EDU` Since: `Jun 2016` Licence: `MIT` +By: `Team 15-3` Since: `Feb 2019` Licence: `MIT` == Introduction -AddressBook Level 4 (AB4) is for those who *prefer to use a desktop app for managing contacts*. More importantly, AB4 is *optimized for those who prefer to work with a Command Line Interface* (CLI) while still having the benefits of a Graphical User Interface (GUI). If you can type fast, AB4 can get your contact management tasks done faster than traditional GUI apps. Interested? Jump to the <> to get started. Enjoy! +slaveFinder() is a resume management application that helps recruiters manage most of the hiring process. + + +The application is optimized for those who prefer to work with a Command Line Interface (CLI) while still having the benefits of a Graphical User Interface (GUI). If you can type fast, slaveFinder() can get your resume management tasks done faster than traditional GUI apps. + + +Interested? Jump to the <> to get started or <> for the list of commands. Enjoy! == Quick Start @@ -26,17 +31,26 @@ AddressBook Level 4 (AB4) is for those who *prefer to use a desktop app for mana . Download the latest `addressbook.jar` link:{repoURL}/releases[here]. . Copy the file to the folder you want to use as the home folder for your Address Book. . Double-click the file to start the app. The GUI should appear in a few seconds. +. There are two main Screen for this Software. + +All Jobs Screen: Two main list will show in this screen, one is for all applicants, the other is for all jobs openings. + image::Ui.png[width="790"] + +Job Detail Screen: Four main lists will show in this screen, they are all applicants list and represent four stages of hiring process in a specific job. ++ +image::DisplayJob.png[width="790"] ++ +. The GUI should start with some data preloaded to allow easier trying out of system +. Type the command `clear` to start with an empty addressbook instead. . Type the command in the command box and press kbd:[Enter] to execute it. + e.g. typing *`help`* and pressing kbd:[Enter] will open the help window. . Some example commands you can try: -* *`list`* : lists all contacts -* **`add`**`n/John Doe p/98765432 e/johnd@example.com a/John street, block 123, #01-01` : adds a contact named `John Doe` to the Address Book. -* **`delete`**`3` : deletes the 3rd contact shown in the current list -* *`exit`* : exits the app +* `*add* n/John p/91757536 nric/S8761230Q e/john@example.com a/123 Disneyland g/Male r/Malay m/Psychology s/NUS gr/4.33 j/Manager`: adds a person named `John` to all applicants database. +* `*createJob* jn/Manager`: creates new job opening `Manager`. +* `*addAll* a jn/Manager` : adds all applicants in the database to the `applicants` list of job `Manager`. +* `*displayJob* jn/Manager`: displays the selection process for job opening `Manager` +* `*list*` : Goes back to the list of all applicants and job openings. . Refer to <> for details of each command. @@ -47,54 +61,131 @@ e.g. typing *`help`* and pressing kbd:[Enter] will open the help window. *Command Format* * Words in `UPPER_CASE` are the parameters to be supplied by the user e.g. in `add n/NAME`, `NAME` is a parameter which can be used as `add n/John Doe`. -* Items in square brackets are optional e.g `n/NAME [t/TAG]` can be used as `n/John Doe t/friend` or as `n/John Doe`. -* Items with `…`​ after them can be used multiple times including zero times e.g. `[t/TAG]...` can be used as `{nbsp}` (i.e. 0 times), `t/friend`, `t/friend t/family` etc. +* Items in square brackets are optional e.g `n/NAME [pj/PASTJOB]` can be used as `n/John Doe pj/Software-Engineer` or as `n/John Doe`. +* Items with `…`​ after them can be used multiple times including zero times e.g. `[pj/PASTJOB]...` can be used as `{nbsp}` (i.e. 0 times), `pj/Software-Engineer`, `pj/Software-Engineer pj/Web-Developer` etc. * Parameters can be in any order e.g. if the command specifies `n/NAME p/PHONE_NUMBER`, `p/PHONE_NUMBER n/NAME` is also acceptable. +* But INDEX and FILTERLISTNAME should always be put follow on command word (preamble). INDEX and LISTNAME don't have prefix before. e.g. `n/NAME INDEX`, `n/NAME FILTERLISTNAME` are not allowed ==== -=== Viewing help : `help` +=== Applicants Related Features -Format: `help` +==== Import Resumes to slaveFinder() : `importResumes` + +Given input resume txt files in placed in the specified folder, reads all the resumes and saves them into slaveFinder(). +Format : `importResumes path_to_folder` + +**** +* All the resume documents should be txt files and strictly follow the below format. +``` +Name +Phone +Email +NRIC +Gender +Race +Address +School +Major +Grade +Lang,Lang,Lang +pastJob,pastJob,pastJob +jobsApply,jobsApply,jobsApply +interviewScore +``` +* All fields are to be populated, except for Programming Languages, Past Jobs, and Jobs Applied +** For these fields, specify any number of items (zero or more), separated by commas +* All the resume documents should be stored in one folder. +* If the new added people is a new person to our company, slaveFinder will crawl the data from resume and add him/her as ADD command. +* If the new added people is a person already in our storage, slaveFinder will crawl the data from resume and change his/her data as EDIT command. +* We assume that applicants who want to apply HR's company need to fill in a Resume Form (which format is strict) online. +** *The format will be refined in V2.0*, applicant can provide a link of their real CV (prefer pdf formmat) when they fill in the Resume Form. +**** + +Examples: + +* `importResumes C:\Users\MyName\Desktop\MyResumes` + +Imports all resumes in the given path +* To try this command with 1000 resumes, use the folder `CVFolder` found in the zip file of slaveFinder()'s release. + + +==== Open CV for potential applicants : `openCV` `Coming in v2.0` + +Opens the real CV provided in Resume Form of applicants + +Format: `openCV [FILTERLISTNAME] INDEX ` + +**** +* This command can be used in both All Jobs Screen and Job Detail Screen, when Screen is All JOb Showing Screen, *FILTERLISTNAME should be empty*. +* When Screen is Job Detail Screen (four applicants lists shows), *FILTERLISTNAME is needed*. +* FILTERLISTNAME indicate which Job list this command will be used. +* Opens the CV of applicants 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, ... +* Pontential candidate will be confirmed by FILTERLISTNAME (if any) and index and his/her CV will be opened for reference. +**** + +Examples: -=== Adding a person: `add` +* `openCV 1` + +Opens the CV of the 1st applicants showing on All Allicants List +* `openCV applicant 2` + +Opens the CV of the 2nd applicants showing on Allicants List in Job Detail Screen. + + + +==== Adding a person: `add` Adds a person to the address book + -Format: `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]...` +Format: `add n/NAME p/PHONE_NUMBER nric/NRIC e/EMAIL a/ADDRESS g/GENDER r/RACE m/MAJOR s/SCHOOL gr/GRADE j/JOBS_APPLY` +**** [TIP] -A person can have any number of tags (including 0) +* This command can only be used when All Jobs Screen shows. You can add applicants into All Aplicants list +* `n/`: *Name* should only contain alphanumeric characters and spaces, and should not be empty. +* `a/`: *Address* can take any values, but should not be empty. +* `nric/`: *NRIC* must be unique. It must start with S, followed by exactly 7 numbers, and end with an alpabet in capital letter. It should not be empty. +* `p/`: *Phone* numbers should only contain numbers, and it should be at least 3 digits long, and should not be empty. +* `e/`: *Email* should be of the format local-part@domain, and should not be empty. "E.g. example@gmail.com" +* `g/`: *Gender* should only be "Female", "Male" or "Others", and should not be empty. +* `r/`: *Race* should only be "Chinese", "Malay", "Indian" or "Others", and should not be empty. +* `gr/`: *Grade* should only contain positive numbers, and must be in exactly 2 decimal place. E.g. "4.64" +* `s/`: *School* can take any values, but should not be empty. +* `m/`: *Major* should only contain alphanumeric characters and spaces, and should not be empty. +* `j/`: *Jobs Apply* must only contain one word. If two or more words, have to be connected by a dash. E.g. "Software-Engineer". It should not be empty. It can take more than 1 value. E.g. "j/Manager j/Sweeper" +* `is/`: *Interview scores* field is optional, and must be exactly 5 set of numbers, each seperated by a comma. E.g. "1,2,3,4,5" +* `kpl/`: *Known Programming Language* field is optional. It can take any values, and can take more than 1 value. E.g. "kpl/Java kpl/Python" +* `pj/`: *Past jobs* field is optional, and past job must only contain one word. If two or more words, have to be connected by a dash. E.g. "Software-Engineer". It can take more than 1 value E.g. "pj/Manager pj/Sweeper" +**** 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` +* `add n/John p/91757536 nric/S8761230Q e/john@example.com a/123 Disneyland g/Male r/Malay m/Psychology s/NUS gr/4.33 j/Manager` +* `add n/Betty p/123 nric/S4444455Y e/betty@bet.com a/321 USS g/Female r/Others m/Life Science s/NTU gr/0.44 j/Helper is/1,2,1,10,5 kpl/Java pj/Chief-Executive-Officer` -=== Listing all persons : `list` +==== Listing all persons : `list` -Shows a list of all persons in the address book. + +Shows a list of all job openings and applicants in slaveFinder(). + Format: `list` -=== Editing a person : `edit` +* Useful after using filter/displayJob which shows a subset of the all applicants list. -Edits an existing person in the address book. + -Format: `edit INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [t/TAG]...` +==== Editing a person : `edit` + +Edits an existing person in slaveFinder(). + +Format: `edit INDEX n/NAME p/PHONE_NUMBER nric/NRIC e/EMAIL a/ADDRESS g/GENDER r/RACE m/MAJOR s/SCHOOL gr/GRADE j/JOBS_APPLY` **** +* This command can only be used when All Jobs Screen shows. You can edit applicants in All Aplicants list * Edits the person at the specified `INDEX`. The index refers to the index number shown in the displayed person list. The index *must be a positive integer* 1, 2, 3, ... -* At least one of the optional fields must be provided. +* Editting fields that allows more than 1 value will entirely replace the existing values. * 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. **** 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. +* `edit 2 n/Betsy Crower` pj/Manager + +Edits the name of the 2nd person to be `Betsy Crower` and clears all existing past jobs and replace it with 'Manager". -=== Locating persons by name: `find` +==== Locating persons by name: `find` Finds persons whose names contain any of the given keywords. + Format: `find KEYWORD [MORE_KEYWORDS]` @@ -114,12 +205,13 @@ Returns `john` and `John Doe` * `find Betsy Tim John` + Returns any person having names `Betsy`, `Tim`, or `John` -=== Deleting a person : `delete` +==== Deleting a person : `delete` -Deletes the specified person from the address book. + +Deletes the specified person from slaveFinder(). + Format: `delete INDEX` **** +* This command can only be used when All Jobs Screen shows. You can delete an applicant in All Aplicants list * Deletes the person at the specified `INDEX`. * The index refers to the index number shown in the displayed person list. * The index *must be a positive integer* 1, 2, 3, ... @@ -129,112 +221,376 @@ Examples: * `list` + `delete 2` + -Deletes the 2nd person in the address book. +Deletes the 2nd person in slaveFinder(). * `find Betsy` + `delete 1` + Deletes the 1st person in the results of the `find` command. -=== Selecting a person : `select` +==== Clearing all entries : `clear` -Selects the person identified by the index number used in the displayed person list. + -Format: `select INDEX` +Clears all entries from slaveFinder(). + +Format: `clear` **** -* Selects the person and loads the Google search page 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, ...` +* This command can only be used when All Jobs Screen shows. You can clear all applicants in All Aplicants list +**** + +// tag::jobs[] +=== Job Related Features + + +==== Create a Job Hiring Process: `createJob` + +Create a Job hiring process with four person lists: "Applicants", "KIV", "Interview", "Shortlist". + +Format : `createJob [jn/JOBNAME]` + +**** +* JOBNAME indicate the job name. This name cannot contain spaces. Names with multiple words are separated by '-'. For example: `IOS-Developer`. +* All people in the storage who want to apply this job will automatically be added in "Applied" list. +**** + +==== Delete the Job Hiring Process : `deleteJob` + +Delete a Job Hiring Process and all its information + +Format : `deleteJob [jn/JOBNAME]` + + +==== Displays one of the four persons list in a job : `displayJob` + +Displays a Job + +Format : `displayJob [jn/JOBNAME]` + +**** +* Displays all four lists of a job at once +**** + + +==== Add all shown persons in a list to another list : `addAll` + +Adds all currently shown people in source list to the destination list + +Format : `addAll TO FROM(Optional) [jn/JOBNAME](Optional)` + +**** +* Filter function can be used to modify the displayed list before moving the people in the list +* `FROM` input is optional and if not given, input list will be the displayed list of the entire directory. +* `JOBNAME` input is optional if there is a currently displayed job. If provided, both source and destination will be of the provided job. +* `FORM` and `TO` can only be one of the following `applied, kiv, interview, shortlist` **** Examples: * `list` + -`select 2` + -Selects the 2nd person in the address book. -* `find Betsy` + -`select 1` + -Selects the 1st person in the results of the `find` command. +`createJob jn/Lecturer` + +* `addAll applied jn/Lecturer` + +addAll adds all in database to Lecturer Job +* `addAll kiv applied jn/Lecturer` + +addAll adds all in applied list to kiv list in lecturer + + +==== Adds people using by index to a selected list in a Job : `movePeople` + + Moves a few people specified by index from a specified list to another list in a job.+ +Format : `movePeople TO FROM INDEXES [jn/JOBNAME]` + +**** +* `FROM` input can only be given if a job is displayed, input list will be the displayed list of the entire directory. +* `JOBNAME` input is required only if there isn't a displayed job. Should be omitted otherwise. +* `FORM` and `TO` can only be one of the following `applied, kiv, interview, shortlist` +**** + +Examples: + +* `list` + +`createJob jn/Lecturer` + +* `movePeople applied 1, 2 jn/Lecturer` + +moves persons with index 1 and 2 to applied list in Lecturer +* `displayJob jn/Lecturer` +* `movePeople kiv applied 2` + +moves person 2 in applied list to kiv list in Lecturer + +==== Adds people using by index to a selected list in a Job : `remove` + + Removes people from a specific list in Job+ +Format : `remove FROM INDEXES [jn/JOBNAME]` + +**** +* Only usage if on display job screen +**** + + +==== Select people into "Interview" List: `selectInterview` `Coming in v2.0` + +Select people from display board to the Job Hiring Process's "Interviewed" list + +Format : `selectInterview [INDEX] [INDEX-INDEX] [all]` + +**** +* Edits the person at the specified `INDEX`. The index refers to the index number shown in the displayed person list. The index *must be a positive integer* 1, 2, 3, ... +* At least one of the optional fields must be provided. +* You can add all people on the Person Display List to the "Interview" list by using `all` parameter. +**** + +Examples: + +* `selectInterview 2-10` + +Selects the 2nd person to 10th people to the "Interview" list. +* `selectInterview 2 4` +Selects the 2nd person and 4th people to the "Interview" list. +* `selectInterview all` +Selects all the people on the Person Display List to the "Interview" list. + +==== Select people into "To be sent to boss" List: `selectfinal` `Coming in v2.0` -=== Listing entered commands : `history` +Select people from display board to the Job Hiring Process's "To be sent to boss" List + +Format : `selectInterview [INDEX] [INDEX-INDEX] [all]` + +**** +* Edits the person at the specified `INDEX`. The index refers to the index number shown in the displayed person list. The index *must be a positive integer* 1, 2, 3, ... +* At least one of the optional fields must be provided. +* You can add all people on the Person Display List to the "To be sent to boss" list by using `all` parameter. +**** +==== Generate a final report for the Job Hiring Process : `report` `Coming in v2.0` + +Generate `report.txt` to show 3 categories of applicants for a specific role: +"Applied", "Interview", "To be sent to boss". in a Job Hiring Process. + +Format : `report JOBNAME` + +=== Filter Related Features +==== Filter results : `filter` + +Filter the people displayed on the Person List. Each filer has a name and can be delete, diplay result always base on all filter request. + +Format: `filter [FILTERLISTNAME] fn/FILTERNAME [n/NAME] pp/PHONE_NUMBER] [nric/NRIC] [e/EMAIL] [a/ADDRESS] [g/GENDER] [r/RACE] [m/MAJOR] [s/SCHOOL] [gr/GRADE] [is1/INTERVIEWSCORESQ1] [is2/INTERVIEWSCORESQ2] [is3/INTERVIEWSCORESQ3] [is4/INTERVIEWSCORESQ4] [is5/INTERVIEWSCORESQ5] [j/JOBS_APPLY]... [kpl/KnowPROGLANG]... [pj/PASTJOB]...` + +**** +* This command can be used in both All Jobs Screen and Job Detail Screen, when Screen is All JOb Showing Screen, *FILTERLISTNAME should be empty*. +* When Screen is Job Detail Screen (four applicants lists shows), *FILTERLISTNAME is needed*. +* FILTERLISTNAME indicate which Job list this command will used. +* FILTERLISTNAME can be full name of the job lists such as "Applicant", "KIV", "Interview", "Shortlist". +* FILTERLISTNAME also can be prefix of the job lists such as "a", "k", "i", "s". +* Multiple filters can be added to filter people. All the filter labels will show on the Filter Panel. +* Applicant List always display people base on all undeleted filters. Persons matching all filters will be returned (i.e. `AND`) +* The filter can be an empty filter without any filtering parameter. e.g. `filter fn/empty` All applicants will be matched in empty filter. +* Filter Name can be any valid String except the String cotaining parameter's prefixes +** For example, `^_^`, `Gender: Male, School: NUS`, `naming is difficult!` can all be a valid filter name. +** But `s/nus`, `valid filtername n/` can not be a valid filter name and the string after the prefix will be regraded as corresponded information and parse into System. +* For grade field (Grade and Interview Scores): +** The Interview has five questions and all the value can be filter by `gr/` or `is[num]/` (num = {1,2,3,4,5}) +** The keyword is a range and splitted by `;`. +** The keyword should be in correct format. e.g. `3.2-4.3` `5 - 6` `3 - 1`. +** Persons' grade or scores are exactly equal to the Upper bound or lower bound will return. +** The Upper bound can lower than lower bound, and no applicants will be matched. +** Persons matching at least one keyword (range) will be returned (i.e. `OR` ). e.g. `gr/3-4; 4-5` will match person with grade in range [3,4] and [4,5]. +* For other field: +** The keyword can be any string and splitted by space. +** The match 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 full words will be matched e.g. `Han` will not match `Hans` +** Persons matching at least one keyword will be returned (i.e. `OR` ). e.g. `Hans Bo` will match `Hans Gruber`, `Bo Yang` +**** + +Examples: + +* `filter fn/nus s/nus` + +Shows all persons whose school is NUS in All Applicants List. +* `filter fn/nus s/nus m/CS` + +Shows all persons whose school is NUS and major is CS in All Applicants List. +* `filter fn/nus s/nus` + +`filter fn/CS m/CS` +Shows all persons whose school is NUS and major is CS in All Applicants List. +* `filter fn/grade gr/4.8-5.0;3.0-3.1` + +Shows all persons whose grade in range of [4.8,5.0] or [3.0,3.1] in All Applicants List. +* `filter Interview fn/nus s/nus` + +Shows all persons whose school is NUS in Interview List in Job Detail Screen. + +==== Delete a Filter : `deleteFilter` + +Delete a filter showing on the display board and renew the update display people list. + +Format: `deleteFilter [FILTERLISTNAME] FILTERNAME` + +**** +* This command can be used in both All Jobs Screen and Job Detail Screen, when Screen is All JOb Showing Screen, *FILTERLISTNAME should be empty*. +* When Screen is Job Detail Screen (four applicants lists shows), *FILTERLISTNAME is needed*. +* FILTERLISTNAME indicate which Job list this command will used. +* FILTERLISTNAME can be full name of the job lists such as "Applicant", "KIV", "Interview", "Shortlist". +* FILTERLISTNAME also can be prefix of the job lists such as "a", "k", "i", "s". +* You can only delete one filter perin one command. The filter label you delete will disappear on the Filter Panel. +* Applicants List always display people base on all undeleted filters.Persons matching all filters will be returned (i.e. `AND`) +**** +Examples: + +* `filter fn/nus s/nus` + +Shows all persons whose school is NUS in All Applicants List. +* `deleteFilter nus` +Shows all persons in All Applicants List. +* `filter Interview fn/nus s/nus` + +Shows all persons whose school is NUS in Interview List in Job Detail Screen. +* `deleteFilter Interview fn/nus` + +Shows all persons in Interview List in Job Detail Screen. + +==== Clear a Filter List: `clearFilter` + +Clear a filter showing on the display board and renew the update display people list. + +Format: `clearFilter [FILTERLISTNAME]` + +**** +* This command can be used in both All Jobs Screen and Job Detail Screen, when Screen is All JOb Showing Screen, *FILTERLISTNAME should be empty*. +* When Screen is Job Detail Screen (four applicants lists shows), *FILTERLISTNAME is needed*. +* FILTERLISTNAME indicate which Job list this command will used. +* FILTERLISTNAME can be full name of the job lists such as "Applicant", "KIV", "Interview", "Shortlist". +* FILTERLISTNAME also can be prefix of the job lists such as "a", "k", "i", "s". +**** +Examples: + +* `filter fn/nus s/nus` + +`filter fn/CS m/CS` +Shows all persons whose school is NUS and major is CS in All Applicants List. +* `clearFilter` +Shows all persons in All Applicants List. +* `filter Interview fn/nus s/nus` + +`filter Interview fn/CS m/CS` +Shows all persons whose school is NUS and major is CS in Interview List in Job Detail Screen. +* `clearFilter Interview` + +Shows all persons in Interview List in Job Detail Screen. + +=== Analytic Related Features + +==== Display Analytics : `analytics` + +Display the analytics of applicants for desired job list (applicant, kiv, interview, shortlist) or all applicants. + +Format : `analytics LISTNAME` or `analytics` (for all applicants) + +**** +* LISTNAME indicate which Job list this command will be used. +* LISTNAME can are the names of job lists such as "applicant", "kiv", "interview", "shortlist". +* If no LISTNAME is entered, the analytis of all applicants in the slave system will be shown. +* If have never used the `DisplayJob` command before, using `analytics LISTNAME` will be empty analytics. +* If on all applicants and all jobs page, `analytics LISTNAME` will display analytics on the job last displayed using command `DisplayJob` + +**** + +Examples: + +* `analytics applicant` +* `analytics kiv` +* `analytics` + +// tag::interviews[] +=== Interview Related Features + +==== Generate Interview Dates : `generateInterviews` + +Generate interview dates for all applicants in slaveFinder(). +Interview dates cannot be generated again if they are already present. +Dates generated exclude weekends and block out dates(see below). + +==== Generate Interview Dates for a particular job : `generateInterviews jn/[JOB_NAME]` `Coming in v2.0` + +Generate interviews for applicants of a particular job in slaveFinder(). +Interview dates cannot be generated again if they are already present. +Dates generated exclude weekends and block out dates(see below). + +==== Clear Interview Dates : `clearInterviews` + +Clears the list of generated interview dates. + +==== Clear Interview Dates for a specified job opening : `clearInterviews jn/[JOB_NAME]` `Coming in v2.0` + +Clears the list of generated interview dates for the specified job. + +==== Set maximum number of interviews a day : `setMaxInterviewsADay [NUMBER]` + +Sets the maximum number of interviews to be generated in a day. + +==== Set block out dates for interviews : `setBlockOutDates [DD/MM/YYYY] OR [DD/MM/YYYY - DD/MM/YYYY] OR [DD/MM/YYYY], [DD/MM/YYYY - DD/MM/YYYY]` + +Sets the block out dates(unavailable dates) which the interviewer is not available for interviews to be scheduled. + +==== Show interviews dates : `showInterviews` + +Shows the list of dates which the interviewees in slaveFinder() are assigned. +(Show is not considered an operation in terms of undo/redo, therefore when undo, the previous command before showInterviews is called) + +==== Show interviews dates for a job opening : `showInterviews jn/[JOB_NAME]` `Coming in v2.0` + +Shows the list of dates which the interviewees for the state job in slaveFinder() are assigned to. + +==== Reschedule Person for interview: `move jn/[JOB_NAME] nric/[NRIC] [FROM_DATE(dd/mm/yyyy)] [TO_DATE(dd/mm/yyyy)]` `Coming in v2.0` + +Reschedules a person scheduled to a particular date to another date. If to date is not present, the person is removed from that date. + +==== Save interviews data `Coming in v2.0` + +Scheduled interviews is automatically stored in a json file. + +// end::interviews[] + +=== Other Basic Command + +==== Viewing help : `help` + +Format: `help` + + +==== Listing entered commands : `history` Lists all the commands that you have entered in reverse chronological order. + Format: `history` [NOTE] -==== +===== Pressing the kbd:[↑] and kbd:[↓] arrows will display the previous and next input respectively in the command box. -==== +===== // tag::undoredo[] -=== Undoing previous command : `undo` +==== Undoing previous command : `undo` Restores the address book to the state before the previous _undoable_ command was executed. + Format: `undo` [NOTE] -==== -Undoable commands: those commands that modify the address book's content (`add`, `delete`, `edit` and `clear`). -==== +===== +Undoable commands: those commands that modify the address book's content (`add`, `delete`, `edit`, `clear`, `createJob`, `deleteJob`, generateInterviews, setMaxInterviewsADay, setBlockOutDates, clearInterviews, filter, delete filter ). +===== Examples: -* `delete 1` + +* `edit 1 n/Johnny` + `list` + -`undo` (reverses the `delete 1` command) + +`undo` (reverses the `edit 1 n/Johnny` command) + -* `select 1` + -`list` + -`undo` + -The `undo` command fails as there are no undoable commands executed previously. -* `delete 1` + -`clear` + -`undo` (reverses the `clear` command) + -`undo` (reverses the `delete 1` command) + - -=== Redoing the previously undone command : `redo` +==== Redoing the previously undone command : `redo` Reverses the most recent `undo` command. + Format: `redo` Examples: -* `delete 1` + -`undo` (reverses the `delete 1` command) + -`redo` (reapplies the `delete 1` command) + +* `edit 1 n/Johnny` + +`undo` (reverses the `edit 1 n/Johnny` command) + +`redo` (reapplies the `edit 1 n/Johnny` command) + -* `delete 1` + +* `edit 1 n/Johnny` + `redo` + The `redo` command fails as there are no `undo` commands executed previously. -* `delete 1` + +* `edit 1 n/Johnny` + `clear` + `undo` (reverses the `clear` command) + -`undo` (reverses the `delete 1` command) + -`redo` (reapplies the `delete 1` command) + +`undo` (reverses the `edit 1 n/Johnny` command) + +`redo` (reapplies the `edit 1 n/Johnny` command) + `redo` (reapplies the `clear` command) + // end::undoredo[] -=== Clearing all entries : `clear` - -Clears all entries from the address book. + -Format: `clear` - -=== Exiting the program : `exit` +==== Exiting the program : `exit` Exits the program. + Format: `exit` -=== Saving the data +==== Saving the data Address book data are saved in the hard disk automatically after any command that changes the data. + There is no need to save manually. -// tag::dataencryption[] -=== Encrypting data files `[coming in v2.0]` - -_{explain how the user can enable/disable data encryption}_ -// end::dataencryption[] - == FAQ *Q*: How do I transfer my data to another Computer? + @@ -242,19 +598,40 @@ _{explain how the user can enable/disable data encryption}_ == Command Summary -* *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` +* *Add* `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS g/GENDER r/RACE m/MAJOR s/SCHOOL [pj/PAST_JOB]... ` + +e.g. `add n/James Ho p/22224444 e/jamesho@example.com a/123, Clementi Rd, 1234665 +g/Male r/Chinese m/MATH s/NUS pj/Professor 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]...` + +* *Edit* : `edit INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] +[g/GENDER] [r/RACE] [s/SCHOOL] [pj/PAST_JOBS] ` + 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` -* *Select* : `select INDEX` + -e.g.`select 2` * *History* : `history` * *Undo* : `undo` * *Redo* : `redo` +* *Generate Interviews* : `generateInterviews` +* *Set maximum number of interviews a day* : `setMaxInterviewsADay[MAX_NUM_INTERVIEWS]` +* *Set block out dates for interviews* : `setBlockOutDATES[DD/MM/YYYY OR DD/MM/YYYY - DD/MM/YYYY] +* *Clear interviews dates* : `clearInterviews` +* *Read to slaveFinder()* : `readAll` +* *Get ranked list* : `getRankedList` +* *Filter search results* : `filter [FILTERLISTNAME] [fn/FILterName] [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] +[g/GENDER] [r/RACE] [s/SCHOOL] [pj/PAST_JOBS] ` + +e.g. `filter fn/Chinese r/Chinese` +* *Delete Filter search results* : `deleteFilter [FILTERLISTNAME] [fn/FILterName] ` + +e.g. `deleteFilter fn/Chinese ` +* *Clear Filter search results* : `clearFilter [FILTERLISTNAME] ` + +e.g. `clearFilter ` +* *Display Hiring Process* : `displayProcess` +* *Display Analytics* : `analytics LISTNAME` or `analytics` +* *Create Job* : `createJob [jn/JOBNAME]` +* *Delete Job* : `deleteJob [jn/JOBNAME]` +* *Display Job* : `displayJob [jn/JOBNAME]` +* *Add All* : `addAll TO FROM(OPTIONAL) [jn/JOBNAME](OPTIONAL)` +* *Move People* : `movePeople TO FROM(OPTIONAL) INDEXES [jn/JOBNAME](ONLY ON DEFAULT SCREEN)` +* *Remove* : 'remove FROM INDEXES [jn/JOBNAME]' + + diff --git a/docs/diagrams/AnalyticsDiagram.pptx b/docs/diagrams/AnalyticsDiagram.pptx new file mode 100644 index 000000000000..5a6a6bf6ff2e Binary files /dev/null and b/docs/diagrams/AnalyticsDiagram.pptx differ diff --git a/docs/diagrams/DeletePersonInterviewsDiagram.pptx b/docs/diagrams/DeletePersonInterviewsDiagram.pptx new file mode 100644 index 000000000000..9c9d49d8718c Binary files /dev/null and b/docs/diagrams/DeletePersonInterviewsDiagram.pptx differ diff --git a/docs/diagrams/FilterDiagram.pptx b/docs/diagrams/FilterDiagram.pptx new file mode 100644 index 000000000000..993cdb6084bf Binary files /dev/null and b/docs/diagrams/FilterDiagram.pptx differ diff --git a/docs/diagrams/GenerateInterviewsDiagram.pptx b/docs/diagrams/GenerateInterviewsDiagram.pptx new file mode 100644 index 000000000000..993cdb6084bf Binary files /dev/null and b/docs/diagrams/GenerateInterviewsDiagram.pptx differ diff --git a/docs/diagrams/ModelComponentClassDiagram.pptx b/docs/diagrams/ModelComponentClassDiagram.pptx index dc0e4ac5ea66..6f0d4a66888c 100644 Binary files a/docs/diagrams/ModelComponentClassDiagram.pptx and b/docs/diagrams/ModelComponentClassDiagram.pptx differ diff --git a/docs/diagrams/Thumbs.db b/docs/diagrams/Thumbs.db new file mode 100644 index 000000000000..8e0a5012b424 Binary files /dev/null and b/docs/diagrams/Thumbs.db differ diff --git a/docs/diagrams/UiComponentClassDiagram.pptx b/docs/diagrams/UiComponentClassDiagram.pptx index ab325e889f70..adff4b608174 100644 Binary files a/docs/diagrams/UiComponentClassDiagram.pptx and b/docs/diagrams/UiComponentClassDiagram.pptx differ diff --git a/docs/images/AnalyticsDiagram.png b/docs/images/AnalyticsDiagram.png new file mode 100644 index 000000000000..9f7a25c26536 Binary files /dev/null and b/docs/images/AnalyticsDiagram.png differ diff --git a/docs/images/DisplayJob.png b/docs/images/DisplayJob.png new file mode 100644 index 000000000000..47f57fca2a86 Binary files /dev/null and b/docs/images/DisplayJob.png differ diff --git a/docs/images/FilterSD.png b/docs/images/FilterSD.png new file mode 100644 index 000000000000..debd3ee969c3 Binary files /dev/null and b/docs/images/FilterSD.png differ diff --git a/docs/images/GenerateInterviewsDiagram.png b/docs/images/GenerateInterviewsDiagram.png new file mode 100644 index 000000000000..d18db46a93b5 Binary files /dev/null and b/docs/images/GenerateInterviewsDiagram.png differ diff --git a/docs/images/InterviewsAssociationClass.png b/docs/images/InterviewsAssociationClass.png new file mode 100644 index 000000000000..42679f79b873 Binary files /dev/null and b/docs/images/InterviewsAssociationClass.png differ diff --git a/docs/images/InterviewsDeletePersonSequenceDiagram.png b/docs/images/InterviewsDeletePersonSequenceDiagram.png new file mode 100644 index 000000000000..512b657b649e Binary files /dev/null and b/docs/images/InterviewsDeletePersonSequenceDiagram.png differ diff --git a/docs/images/ModelClassDiagram.png b/docs/images/ModelClassDiagram.png index 4961edd74e76..63a1fb4fe2e4 100644 Binary files a/docs/images/ModelClassDiagram.png and b/docs/images/ModelClassDiagram.png differ diff --git a/docs/images/Thumbs.db b/docs/images/Thumbs.db new file mode 100644 index 000000000000..2d10b4db2f5d Binary files /dev/null and b/docs/images/Thumbs.db differ diff --git a/docs/images/Ui.png b/docs/images/Ui.png index 5ec9c527b49c..8ce5037111bd 100644 Binary files a/docs/images/Ui.png and b/docs/images/Ui.png differ diff --git a/docs/images/UiComponentClassDiagram.png b/docs/images/UiComponentClassDiagram.png new file mode 100644 index 000000000000..bfea6045708d Binary files /dev/null and b/docs/images/UiComponentClassDiagram.png differ diff --git a/docs/images/Ui_old.png b/docs/images/Ui_old.png new file mode 100644 index 000000000000..5ec9c527b49c Binary files /dev/null and b/docs/images/Ui_old.png differ diff --git a/docs/images/analytics.PNG b/docs/images/analytics.PNG new file mode 100644 index 000000000000..d83e8c7a0cb3 Binary files /dev/null and b/docs/images/analytics.PNG differ diff --git a/docs/images/appveyor/Thumbs.db b/docs/images/appveyor/Thumbs.db new file mode 100644 index 000000000000..03180a9fb9bf Binary files /dev/null and b/docs/images/appveyor/Thumbs.db differ diff --git a/docs/images/caesarty.png b/docs/images/caesarty.png new file mode 100644 index 000000000000..b47e3d61ccd6 Binary files /dev/null and b/docs/images/caesarty.png differ diff --git a/docs/images/capture.png b/docs/images/capture.png new file mode 100644 index 000000000000..3cb40afceb8e Binary files /dev/null and b/docs/images/capture.png differ diff --git a/docs/images/chiuyuhua.png b/docs/images/chiuyuhua.png new file mode 100644 index 000000000000..d46d458bd58b Binary files /dev/null and b/docs/images/chiuyuhua.png differ diff --git a/docs/images/coveralls/Thumbs.db b/docs/images/coveralls/Thumbs.db new file mode 100644 index 000000000000..a44fe0b7b643 Binary files /dev/null and b/docs/images/coveralls/Thumbs.db differ diff --git a/docs/images/damithc.jpg b/docs/images/damithc.jpg deleted file mode 100644 index 127543883893..000000000000 Binary files a/docs/images/damithc.jpg and /dev/null differ diff --git a/docs/images/danieldssim.png b/docs/images/danieldssim.png new file mode 100644 index 000000000000..7957ec11919e Binary files /dev/null and b/docs/images/danieldssim.png differ diff --git a/docs/images/filter.png b/docs/images/filter.png new file mode 100644 index 000000000000..ddc860aa98fe Binary files /dev/null and b/docs/images/filter.png differ diff --git a/docs/images/lejolly.jpg b/docs/images/lejolly.jpg deleted file mode 100644 index 2d1d94e0cf5d..000000000000 Binary files a/docs/images/lejolly.jpg and /dev/null differ diff --git a/docs/images/m133225.jpg b/docs/images/m133225.jpg deleted file mode 100644 index fd14fb94593a..000000000000 Binary files a/docs/images/m133225.jpg and /dev/null differ diff --git a/docs/images/netlify/Thumbs.db b/docs/images/netlify/Thumbs.db new file mode 100644 index 000000000000..173b522dfee4 Binary files /dev/null and b/docs/images/netlify/Thumbs.db differ diff --git a/docs/images/oldUi.png b/docs/images/oldUi.png new file mode 100644 index 000000000000..5ec9c527b49c Binary files /dev/null and b/docs/images/oldUi.png differ diff --git a/docs/images/suyi.png b/docs/images/suyi.png new file mode 100644 index 000000000000..159df10d2fba Binary files /dev/null and b/docs/images/suyi.png differ diff --git a/docs/images/uniqueJobListCompositeJobAggregatePerson.png b/docs/images/uniqueJobListCompositeJobAggregatePerson.png new file mode 100644 index 000000000000..33ea9e5842e2 Binary files /dev/null and b/docs/images/uniqueJobListCompositeJobAggregatePerson.png differ diff --git a/docs/images/yijinl.jpg b/docs/images/yijinl.jpg deleted file mode 100644 index adbf62ad9406..000000000000 Binary files a/docs/images/yijinl.jpg and /dev/null differ diff --git a/docs/images/yl_coder.jpg b/docs/images/yl_coder.jpg deleted file mode 100644 index 17b48a732272..000000000000 Binary files a/docs/images/yl_coder.jpg and /dev/null differ diff --git a/docs/team/WeeSooJun.adoc b/docs/team/WeeSooJun.adoc new file mode 100644 index 000000000000..0ce318f0bc32 --- /dev/null +++ b/docs/team/WeeSooJun.adoc @@ -0,0 +1,65 @@ += Wee Soo Jun - Project Portfolio +:site-section: AboutUs +:imagesDir: ../images +:stylesDir: ../stylesheets + +== PROJECT: slaveFinder() + +--- + +== Overview + +slaveFinder() is a desktop address book application used for people in the HR department of a company to help them filter people and schedule interviews. It also helps with analytics of the hiring process in the company. + +== Summary of contributions + +* *Major enhancement*: added *Interview scheduling* +** What it does: allows the user to schedule interviews with block out dates. +** Justification: This feature improves the product significantly because a user can improve his/her productivity as the app can take over the task of scheduling to the app. +** Highlights: +** Credits: + +* *Minor enhancement*: Added a school field which helped in the ranking and filtering of individuals for interviews + +* *Code contributed*: https://nus-cs2103-ay1819s2.github.io/cs2103-dashboard/#search=weesoojun&sort=displayName&since=2019-02-10&until=2019-04-14&timeframe=day&reverse=false&repoSort=true + +* *Other contributions*: + +** Project management: +*** Managed release `v1.3` on GitHub +** Enhancements to existing features: +** Documentation: +*** Updated Model Component image of slaveFinder() +** Community: +*** PRs reviewed (with non-trivial review comments): https://github.com/CS2103-AY1819S2-W15-3/main/pull/44[#44] +** Tools: +*** Integrated TravisCI, Appveyor, Codacy, and Coveralls to team repo + + +== Contributions to the User Guide + + +|=== +|_Given below are sections I contributed to the User Guide. They showcase my ability to write documentation targeting end-users._ +|=== + +include::../UserGuide.adoc[tag=interviews] +include::../UserGuide.adoc[tag=interviewsUserStories] + + +== Contributions to the Developer Guide + +|=== +|_Given below are sections I contributed to the Developer Guide. They showcase my ability to write technical documentation and the technical depth of my contributions to the project._ +|=== + +include::../DeveloperGuide.adoc[tag=model] +include::../DeveloperGuide.adoc[tag=interviews] +include::../DeveloperGuide.adoc[tag=interviewsUserStories] +include::../DeveloperGuide.adoc[tag=interviewsUseCases] +include::../DeveloperGuide.adoc[tag=interviewsTestCommands] + + + + +--- diff --git a/docs/team/caesarty.adoc b/docs/team/caesarty.adoc new file mode 100644 index 000000000000..a1cff53568df --- /dev/null +++ b/docs/team/caesarty.adoc @@ -0,0 +1,339 @@ += Teng Yun - Project Portfolio +:site-section: AboutUs +:imagesDir: ../images +:stylesDir: ../stylesheets + +== PROJECT: slaveFinder() + +--- + +== Overview + +slaveFinder() is a desktop recruiting software tracking all resumes from the company and helps HR managers manage most of their hiring process. It allows HR to easily import resumes and transform applicants' information into concise and organized format. It helps HR Manager to find the best talent by filter almost all information and automaticaly do analytics and statistics. During the hiring processing, this software will always visualize all stages and it also can help you auto schedule the interviews. The user interacts with it using a CLI, and it has a GUI created with JavaFX. It is written in Java and has about 15 kLoC. + +== Summary of contributions + +* *Major enhancement*: added *a convenient and controlable filter system to filter applicants on their information* +** What it does: +***Allows the user to filter base on the various fields data of the applicants. +***Filter is visble and can be combined. +***Filter can be deleted by their name or cleared all in one command +***Each Applicants list has an independent filter list. +** Justification: HR manager always faces a lot of resumes and applicants' information, a powerful filter system can save them a lot of time and find the perfect candidates in different needs. It can also be used combining with analytic and deliver more insights to user. +** Highlights: A visble filter system will be convenient for HR manager to manage the filter process, it makes filter process more controlable. + +image::filter.png[width="790"] + +* *Minor enhancement*: Add Person field: PastJob(https://github.com/CS2103-AY1819S2-W15-3/main/pull/62[#62]) and ListName class. + + +* *Code contributed*: https://nus-cs2103-ay1819s2.github.io/cs2103-dashboard/#=undefined&search=caesarty[Link to RepoSense] + +* *Other contributions*: + +** Project management: +*** Managed releases `v1.2` on GitHub +** Enhancements to existing features: +*** Ensure user cannot using some delete features such as tag and find.(https://github.com/CS2103-AY1819S2-W15-3/main/pull/243[#243]) +*** Refine Message system and add more detailed error handling in commands such as add and filter (https://github.com/CS2103-AY1819S2-W15-3/main/pull/229[#229]) +*** Make sure commands will not be used in unmatched Screen(mode)(https://github.com/CS2103-AY1819S2-W15-3/main/pull/248[#248]) +*** Wrote additional tests for existing features and my own features. Test code(including system test code) coverage over ninety percent on filter related features. +** Documentation: +*** Reformat the User Guide: (https://github.com/CS2103-AY1819S2-W15-3/main/pull/257[#257]) +** Community: +*** PRs reviewed (with non-trivial review comments): https://github.com/CS2103-AY1819S2-W15-3/main/pull/71[#71] +*** Reported bugs and suggestions for other teams in the class (examples: https://github.com/nus-cs2103-AY1819S2/pe-dry-run/issues/881[1], https://github.com/nus-cs2103-AY1819S2/pe-dry-run/issues/798[2], https://github.com/nus-cs2103-AY1819S2/pe-dry-run/issues/676[3], https://github.com/nus-cs2103-AY1819S2/pe-dry-run/issues/417[4], https://github.com/nus-cs2103-AY1819S2/pe-dry-run/issues/220[5]) + + +== Contributions to the User Guide +|=== +|_Given below are sections I contributed to the User Guide. They showcase my ability to write documentation targeting end-users._ +|=== + +=== Applicants Related Features +==== Open CV for potential applicants : `openCV` `Coming in v2.0` + +Opens the real CV provided in Resume Form of applicants + +Format: `openCV [FILTERLISTNAME] INDEX ` + +**** +* This command can be used in both All Jobs Screen and Job Detail Screen, when Screen is All JOb Showing Screen, *FILTERLISTNAME should be empty*. +* When Screen is Job Detail Screen (four applicants lists shows), *FILTERLISTNAME is needed*. +* FILTERLISTNAME indicate which Job list this command will be used. +* Opens the CV of applicants 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, ... +* Pontential candidate will be confirmed by FILTERLISTNAME (if any) and index and his/her CV will be opened for reference. +**** + +Examples: + +* `openCV 1` + +Opens the CV of the 1st applicants showing on All Allicants List +* `openCV applicant 2` + +Opens the CV of the 2nd applicants showing on Allicants List in Job Detail Screen. + +=== Filter Related Features +==== Filter results : `filter` + +Filter the people displayed on the Person List. Each filer has a name and can be delete, diplay result always base on all filter request. + +Format: `filter [FILTERLISTNAME] fn/FILTERNAME [n/NAME] pp/PHONE_NUMBER] [nric/NRIC] [e/EMAIL] [a/ADDRESS] [g/GENDER] [r/RACE] [m/MAJOR] [s/SCHOOL] [gr/GRADE] [is1/INTERVIEWSCORESQ1] [is2/INTERVIEWSCORESQ2] [is3/INTERVIEWSCORESQ3] [is4/INTERVIEWSCORESQ4] [is5/INTERVIEWSCORESQ5] [j/JOBS_APPLY]... [kpl/KnowPROGLANG]... [pj/PASTJOB]...` + +**** +* This command can be used in both All Jobs Screen and Job Detail Screen, when Screen is All JOb Showing Screen, *FILTERLISTNAME should be empty*. +* When Screen is Job Detail Screen (four applicants lists shows), *FILTERLISTNAME is needed*. +* FILTERLISTNAME indicate which Job list this command will used. +* FILTERLISTNAME can be full name of the job lists such as "Applicant", "KIV", "Interview", "Shortlist". +* FILTERLISTNAME also can be prefix of the job lists such as "a", "k", "i", "s". +* Multiple filters can be added to filter people. All the filter labels will show on the Filter Panel. +* Applicant List always display people base on all undeleted filters. Persons matching all filters will be returned (i.e. `AND`) +* The filter can be an empty filter without any filtering parameter. e.g. `filter fn/empty` All applicants will be matched in empty filter. +* Filter Name can be any valid String except the String cotaining parameter's prefixes +** For example, `^_^`, `Gender: Male, School: NUS`, `naming is difficult!` can all be a valid filter name. +** But `s/nus`, `valid filtername n/` can not be a valid filter name and the string after the prefix will be regraded as corresponded information and parse into System. +* For grade field (Grade and Interview Scores): +** The Interview has five questions and all the value can be filter by `gr/` or `is[num]/` (num = {1,2,3,4,5}) +** The keyword is a range and splitted by `;`. +** The keyword should be in correct format. e.g. `3.2-4.3` `5 - 6` `3 - 1`. +** Persons' grade or scores are exactly equal to the Upper bound or lower bound will return. +** The Upper bound can lower than lower bound, and no applicants will be matched. +** Persons matching at least one keyword (range) will be returned (i.e. `OR` ). e.g. `gr/3-4; 4-5` will match person with grade in range [3,4] and [4,5]. +* For other field: +** The keyword can be any string and splitted by space. +** The match 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 full words will be matched e.g. `Han` will not match `Hans` +** Persons matching at least one keyword will be returned (i.e. `OR` ). e.g. `Hans Bo` will match `Hans Gruber`, `Bo Yang` +**** + +Examples: + +* `filter fn/nus s/nus` + +Shows all persons whose school is NUS in All Applicants List. +* `filter fn/nus s/nus m/CS` + +Shows all persons whose school is NUS and major is CS in All Applicants List. +* `filter fn/nus s/nus` + +`filter fn/CS m/CS` +Shows all persons whose school is NUS and major is CS in All Applicants List. +* `filter fn/grade gr/4.8-5.0;3.0-3.1` + +Shows all persons whose grade in range of [4.8,5.0] or [3.0,3.1] in All Applicants List. +* `filter Interview fn/nus s/nus` + +Shows all persons whose school is NUS in Interview List in Job Detail Screen. + +==== Delete a Filter : `deleteFilter` + +Delete a filter showing on the display board and renew the update display people list. + +Format: `deleteFilter [FILTERLISTNAME] FILTERNAME` + +**** +* This command can be used in both All Jobs Screen and Job Detail Screen, when Screen is All JOb Showing Screen, *FILTERLISTNAME should be empty*. +* When Screen is Job Detail Screen (four applicants lists shows), *FILTERLISTNAME is needed*. +* FILTERLISTNAME indicate which Job list this command will used. +* FILTERLISTNAME can be full name of the job lists such as "Applicant", "KIV", "Interview", "Shortlist". +* FILTERLISTNAME also can be prefix of the job lists such as "a", "k", "i", "s". +* You can only delete one filter perin one command. The filter label you delete will disappear on the Filter Panel. +* Applicants List always display people base on all undeleted filters.Persons matching all filters will be returned (i.e. `AND`) +**** +Examples: + +* `filter fn/nus s/nus` + +Shows all persons whose school is NUS in All Applicants List. +* `deleteFilter nus` +Shows all persons in All Applicants List. +* `filter Interview fn/nus s/nus` + +Shows all persons whose school is NUS in Interview List in Job Detail Screen. +* `deleteFilter Interview fn/nus` + +Shows all persons in Interview List in Job Detail Screen. + +==== Clear a Filter List: `clearFilter` + +Clear a filter showing on the display board and renew the update display people list. + +Format: `clearFilter [FILTERLISTNAME]` + +**** +* This command can be used in both All Jobs Screen and Job Detail Screen, when Screen is All JOb Showing Screen, *FILTERLISTNAME should be empty*. +* When Screen is Job Detail Screen (four applicants lists shows), *FILTERLISTNAME is needed*. +* FILTERLISTNAME indicate which Job list this command will used. +* FILTERLISTNAME can be full name of the job lists such as "Applicant", "KIV", "Interview", "Shortlist". +* FILTERLISTNAME also can be prefix of the job lists such as "a", "k", "i", "s". +**** +Examples: + +* `filter fn/nus s/nus` + +`filter fn/CS m/CS` +Shows all persons whose school is NUS and major is CS in All Applicants List. +* `clearFilter` +Shows all persons in All Applicants List. +* `filter Interview fn/nus s/nus` + +`filter Interview fn/CS m/CS` +Shows all persons whose school is NUS and major is CS in Interview List in Job Detail Screen. +* `clearFilter Interview` + +Shows all persons in Interview List in Job Detail Screen. + + +== Contributions to the Developer Guide + +|=== +|_Given below are sections I contributed to the Developer Guide. They showcase my ability to write technical documentation and the technical depth of my contributions to the project._ +|=== + +=== Filter Feature + +With the filter feature, users can input specific parameters that act as conditions for slaveFinder() to conditionally update the `UniqueFilterList` and filter the `UniquePeronList`. Using these parameters, slaveFinder() shows applicants contains the specified parameters. Filter is visble and can combine or be deleted. + +Command Format: +**`filter [FILTERLISTNAME] fn/FILTERNAME [/]...` +**`deleteFilter [FILTERLISTNAME] fn/FILTERNAME` +**`clearFilter [FILTERLISTNAME]` + +[NOTE] +* FILTERLISTNAME indicate which Job list this command will used. + +==== Add Filter + +The command format for Adding a Filter is: + +Format: `filter [FILTERLISTNAME] fn/FILTERNAME [n/NAME] pp/PHONE_NUMBER] [nric/NRIC] [e/EMAIL] [a/ADDRESS] [g/GENDER] [r/RACE] [m/MAJOR] [s/SCHOOL] [gr/GRADE] [is1/INTERVIEWSCORESQ1] [is2/INTERVIEWSCORESQ2] [is3/INTERVIEWSCORESQ3] [is4/INTERVIEWSCORESQ4] [is5/INTERVIEWSCORESQ5] [j/JOBS_APPLY]... [kpl/KnowPROGLANG]... [pj/PASTJOB]...` + +[NOTE] +==== +* If multiple fields are provided, this command will filter the `UniquePersonList` by `AND` logic among multiple fields. +* If multiple keywords are provided in one command, this command will filter the `UniquePersonList` by `OR` logic among multiple keywords. +* Example: `filter fn/filter1 m/CS s/NUS` changes the UI to display applicants whose school is NUS *AND* major is CS. `filter fn/filter2 s/NUS NTU` changes the UI to display applicants whose school is NUS *OR* NTU. +==== + + +Upon entering the `filter` command, the `filter` command word is stripped from the input and the argument fields are passed into the `FilterCommandParser` class. The FilterListName will be stripped from the argument and parse to `LISTNAME` object. `FilterCommandParser` tokenizes the other argument string using `ArgumentTokenizer` object, The regular expressions will be checked and mapping each parameter to it's respective prefix in an `ArgumentMultiMap` object. `FilterCommandParser` then creates a `predicatePersonDescriptor` object using the parameter +values in `ArgumentMultiMap` for each filter. If invalid parameters are specified by the user, or if an invalid `FILTERLISTNAME` was be inputed, or there is no filter name is provided, then `FilterCommandParser` throws a `ParseException` and displays an error message to the user. + +If valid inputs are provided, `predicatePersonDescriptor` will be created and `FilterCommandParser` will return a `FilterCommand` with parameters `predicatePersonDescriptor` and `FilterName` and `FilterListName`. `FilterCommand` then creates a Predicate Manager object (implements Java 8’s Predicate interface) using the parameter values in `predicatePersonDescriptor` for each filter condition, and combines them into one single Predicate using the and() function in Predicate interface. After that, `FilterCommand` calls the addPredicate method in Model to set the Predicate List (indicated by FilterListName).In the end `FilterCommand` calls the updateFilteredPersonList method in Model to update applicants using all current undeleted PredicateManager object. UI will change and displaying all undeleted Filter name label and the Person who evaluates the set Predicate to true. If repeated filterName are specified by the user, or if an non-empty `FILTERLISTNAME` was be inputed in All Job Screen mode, or if no `FILTERLISTNAME` was be inputed in JOb Detail Screen mode, then `FilterCommandParser` throws a `CommandException` and displays an error message to the user. + +===== Current Implementation + +The following sequence diagram shows the flow of events when the `filter fn/nus` command is entered by the user: + +image::FilterSD.png[width=800] +Figure: Sequence diagram illustrating the interactions between the +`Logic` and `Model` components when `filter` command is called. + +==== Delete Filter + +The command format for Deleting a filter is: + +Format: `deleteFilter [FILTERLISTNAME] FILTERNAME` + + +Upon entering the `deleteFilter` command, the `deleteFilter` command word is stripped from the input and the argument fields are passed into the `DeleteFilterCommandParser` class. The FilterListName will be stripped from the argument and parse to `LISTNAME` object. `DeleteFilterCommandParser` tokenizes the FILTERNAME. If an invalid `FILTERLISTNAME` was be inputed, or there is no filter name is provided, then `DeleteFilterCommandParser` throws a `ParseException` and displays an error message to the user. + +If valid inputs are provided, `DeleteFilterCommandParser` will return a `DeleteFilterCommand` with parameters `FilterName` and `FilterListName`. `FilterCommand`. After that, `DeleteFilterCommand` calls the removePredicate method in Model to set the Predicate List (indicated by FilterListName).In the end `FilterCommand` calls the updateFilteredPersonList method in Model to update applicants using all current undeleted PredicateManager object. UI will change and displaying all undeleted Filter name label and the Person who evaluates the set Predicate to true. If no filterName are found, or if an non-empty `FILTERLISTNAME` was be inputed in All Job Screen mode, or if no `FILTERLISTNAME` was be inputed in JOb Detail Screen mode, then `DeleteFilterCommandParser` throws a `CommandException` and displays an error message to the user. + +==== Clear Filter + +The command format for Clearing all filters is: + +Format: `clearFilter [FILTERLISTNAME] ` + + +Upon entering the `clearFilter` command, the `clearFilter` command word is stripped from the input and the argument fields are passed into the `ClearFilterCommandParser` class. The FilterListName will be stripped from the argument and parse to `LISTNAME` object. `ClearFilterCommandParser` tokenizes the FILTERNAME. If an invalid `FILTERLISTNAME` was be inputed, then `ClearFilterCommandParser` throws a `ParseException` and displays an error message to the user. + +If valid inputs are provided, `ClearFilterCommandParser` will return a `ClearFilterCommand` with parameters `FilterName` and `FilterListName`. `FilterCommand`. After that, `ClearFilterCommand` calls the clearPredicate method in Model to set the Predicate List (indicated by FilterListName).In the end `ClearFilterCommand` calls the updateFilteredPersonList method in Model to update applicants using an always true PredicateManager object. UI will change and displaying an empty filter panel and the all Persons will show. If an non-empty `FILTERLISTNAME` was be inputed in All Job Screen mode, or if no `FILTERLISTNAME` was be inputed in JOb Detail Screen mode, then `ClearFilterCommandParser` throws a `CommandException` and displays an error message to the user. + +==== Design Considerations + +===== Aspect: How to parse parameters in filter command + +* **Alternative 1 (current choice):** `FilterCommandParse` Create a `predicatePersonDescriptor` object and parse it to `FilterCommand` +** Pros: +*** Make filterCommand more comparable. We can compare `predicatePersonDescriptor` to say whether two filter Command is same. +*** Have better contol on a Filtercommand. +** Cons: Logic is indirect. +* **Alternative 2:** `FilterCommandParse` combine all conditions (parameters) in a Predicate and parse it to `FilterCommand` +** Pros: Logic is direct. +** Cons: Predicate interface is incomparable so this make test more difficult. + +===== Aspect: How to design a filter name restricted format + +* **Alternative 1 (current choice):** Filter name can be any String. +** Pros: +*** Make Filter name more flexible. +*** A filter may include many information. But user can only see this filter by it's Filter name label. So it should allow user make a more detailed name for memory and control. +** Cons: Because user can take unpredictable signas their filter name, so it may cause unpredictable bugs. +* **Alternative 2:** Filter name should only be restricted in the specified format +** Pros: Easy to control and handle error. +** Cons: User more need more complicated filter name. + +===== Aspect: How to handle filter grade and interview scores + +* **Alternative 1 (current choice):** Filter parameter become a value range and person's score in this range will be returned. +** Pros: +*** Make more sense on filtering value related field. +*** HR manager like to know peron's score in a range, not exactly in a specified value. +** Cons: May add additional logic and error handling. +* **Alternative 2:** Same logic as other field +** Pros: Easy to implement. +** Cons: HR manager like to know peron's score in a range, not exactly equal to a specified value. + +=== User Stories + +Priorities: High (must have) - `* * \*`, Medium (nice to have) - `* \*`, Low (unlikely to have) - `*` + +[width="59%",cols="22%,<23%,<25%,<30%",options="header",] +|======================================================================= +|Priority |As a ... |I want to ... |So that I can... + +|`* * *` |HR |find a person by personal information |locate details of persons without having to go through the entire list + +|`* * *` |HR |filter persons by some specific requirements |filter out the persons who are not qualified efficiently. + +|======================================================================= + +=== Use case: Filter person + +*MSS* + +1. User requests to list persons +2. slaveFinder() shows a list of persons +3. User requests to filter persons fulfill some requirements in the list +4. slaveFinder() shows a list of target persons ++ +Use case ends. + +*Extensions* + +*Extensions* + +[none] +* 2a. The list is empty. ++ +Use case ends. + +* 3a. The given command is invalid. ++ +[none] +** 3a1. slaveFinder() shows an error message. ++ +Use case resumes at step 2. + +=== Filtering a list of persons + +. Filtering a list of person while All Job Screen show + +.. Prerequisites: Using `list` command to get to All Job Screen. +.. Test case: `filter fn/CS m/CS` + + Expected: All persons with major CS in All Applicants list will show, a filter named "CS" will show on Filter list panel. +.. Test case: `filter none` + + Expected: List unchangeed. Error details shown in the status message. Status bar remains the same. +.. Other incorrect filter commands to try: `filter a`, `filter a fn/CS`,`filter b fn/CS` + Expected: Similar to previous. + +. Filtering a list of person while Job Detail Screen show + +.. Prerequisites: Create a job object using the `createJob` command. Display job list using the `DisplayJob` command. 4 lists of persons + + will show. +.. Test case: `filter a fn/CS m/CS` + + Expected: All persons with major CS in "Applicant" list will show on the Applicant list, a filter named "CS" will show on Filter list panel. +.. Test case: `filter none` + + Expected: List unchangeed. Error details shown in the status message. Status bar remains the same. +.. Other incorrect filter commands to try: `filter a`, `filter fn/CS`,`filter b fn/CS` + Expected: Similar to previous. + + + diff --git a/docs/team/chiuyuhua.adoc b/docs/team/chiuyuhua.adoc new file mode 100644 index 000000000000..284d0da6b871 --- /dev/null +++ b/docs/team/chiuyuhua.adoc @@ -0,0 +1,238 @@ += Chiu Yu Hua - Project Portfolio +:site-section: AboutUs +:imagesDir: ../images +:stylesDir: ../stylesheets + +== PROJECT: slaveFinder() + +--- + +== Overview + +slaveFinder() is a resume management application that helps recruiters manage most of the hiring process. It allows recruiters to easily look for quality job applicants through convenient filtering of resumes' various fields, and sort these applicants into different parts of the selection process. Other features include automated scheduling of interviews based on recruiter's availability and analytics of job applicants. + +== Summary of contributions + +* *Major enhancement 1*: added *Analytics* feature end-to-end +** What it does: +*** Consolidates and breaksdown the various fields data of applicants, such as gender breakdown, number of applicants in each school, number of applicants in each major and average grade. +*** Analytics can be done on specific lists in the selection process, such as only on applicants that were interviewed or selected for the job. `analytics applicant`, `analytics kiv`, `analytics interview`, `analytics shortlist`, `analytics` +** Justification: +*** Recruiters often want to know the quality of applicants that apply to their company, and use this data to improve their hiring process or recruit marketing strategies. +*** Furthermore, many companies are concerned about diviersity in their team. Such analytics can inform them if they are getting (or selecting) a sufficiently diversed pool of applicants. +** Highlights: In addition to processing the various fields data in corresponding lists, analytics data is presented in a visually intuitive manner consisting of different charts to suit the nature of the person field. ++ +image::analytics.PNG[width="790"] + + +* *Major enhancement 2*: restructured the original AB4 UI code to suit the context of slaveFinder(), and provide UI component for various commands added by other team members. +** What it does: +*** Consist of two scenes: First scene shows 2 panels consisting of a list of all applicants and a list of all job openings. Second scene shows 4 panels, each representing a specific part in the hiring process for a specific job opening: "Applicants, KIV, Interviewed, Shortlist". +*** Filtering of applicants through field data is also shown visually on software, where filtered keywords are "stored" (and removed when needed) above the corresponding lists. +** Justification: Being able to filter applicants and sort them according to the selection process is the core value add of our software, and hence essential to be visually represented and for user to interact with each list accordingly. +** Highlights: Enable UI for the following commands: +*** `DisplayJob`: Switch UI scene to show 4 panels for specific job +*** `list`: Switch UI scene to show all applicants and all job openings +*** `filter`: Filter keywords stored above corresponding lists +*** `deleteFilter` & `clearFilter`: remove filter keywords stored above corresponding lists +*** `movePerson`, `addPerson`: allows Person to be moved and displayed at various list on the `applicantsPanel`, `kivPanel`, `interviewPanel`, `shortListPanel` +*** `createJob`: allows new job to be added as a `JobCard` on the `JobsPanel` +*** `analytics`: new window that displays analytics data on different charts. + +* *Minor enhancement*: Added Person Fields: Gender, Race, NRIC, Grade, InterviewScores, JobApply, as required by resumes of job applicants (https://github.com/CS2103-AY1819S2-W15-3/main/pull/85[#85]) + +* *Minor enhancement*: Make person field `NRIC` the unique identifier of `Person` and create `UniqueNRICMap` to allow convenient obtaining of `Person` via NRIC. (https://github.com/CS2103-AY1819S2-W15-3/main/pull/85[#85]) + +* *Minor enhancement*: Enhance `list` command to list both jobs and persons. (https://github.com/CS2103-AY1819S2-W15-3/main/pull/200[#200]) + +* *Code contributed*: https://nus-cs2103-ay1819s2.github.io/cs2103-dashboard/#=undefined&search=chiuyuhua[Functional and Tests] + +* *Other contributions*: + +** Enhancements to existing features: +*** Updated the GUI color scheme (https://github.com/CS2103-AY1819S2-W15-3/main/pull/200[#200]) +*** Wrote additional tests to increase coverage by 4% (https://github.com/CS2103-AY1819S2-W15-3/main/pull/247[#247], (https://github.com/CS2103-AY1819S2-W15-3/main/pull/85[#85]) +** Documentation: +*** Update the UI component class diagram and Sequence Diagram in DG and enhance existing contents in UG. (https://github.com/CS2103-AY1819S2-W15-3/main/pull/256[#256], https://github.com/CS2103-AY1819S2-W15-3/main/pull/238[#238], https://github.com/CS2103-AY1819S2-W15-3/main/pull/239[#239], https://github.com/CS2103-AY1819S2-W15-3/main/pull/201[#201], https://github.com/CS2103-AY1819S2-W15-3/main/pull/110[#110]) + +** Community: +*** PRs reviewed (with non-trivial review comments): https://github.com/CS2103-AY1819S2-W15-3/main/pull/84[#84] https://github.com/CS2103-AY1819S2-W15-3/main/pull/124[#124] +*** Reported bugs and suggestions for other teams in class (https://github.com/nus-cs2103-AY1819S2/pe-dry-run/issues/589[1], https://github.com/nus-cs2103-AY1819S2/pe-dry-run/issues/693[2], https://github.com/nus-cs2103-AY1819S2/pe-dry-run/issues/555[3], https://github.com/nus-cs2103-AY1819S2/pe-dry-run/issues/431[4], https://github.com/nus-cs2103-AY1819S2/pe-dry-run/issues/114[5], https://github.com/nus-cs2103-AY1819S2/pe-dry-run/issues/230[6]) + + +== Contributions to the User Guide + + +|=== +|_Given below are sections I contributed to the User Guide. They showcase my ability to write documentation targeting end-users._ +|=== + +==== Display Analytics : `analytics` + +Display the analytics of applicants for desired job list (applicant, kiv, interview, shortlist) or all applicants. + +Format : `analytics LISTNAME` or `analytics` (for all applicants) + +**** +* LISTNAME indicate which Job list this command will be used. +* LISTNAME can are the names of job lists such as "applicant", "kiv", "interview", "shortlist". +* If no LISTNAME is entered, the analytis of all applicants in the slave system will be shown. +* If have never used the `DisplayJob` command before, using `analytics LISTNAME` will be empty analytics. +* If on all applicants and all jobs page, `analytics LISTNAME` will display analytics on the job last displayed using command `DisplayJob` + +**** + +Examples: + +* `analytics applicant` +* `analytics kiv` +* `analytics` + +== Introduction + +slaveFinder() is a resume management application that helps recruiters manage most of the hiring process. + +The application is optimized for those who prefer to work with a Command Line Interface (CLI) while still having the benefits of a Graphical User Interface (GUI). If you can type fast, slaveFinder() can get your resume management tasks done faster than traditional GUI apps. + +Interested? Jump to the <> to get started or <> for the list of commands. Enjoy! + +. Some example commands you can try: + +* `*add* n/John p/91757536 nric/S8761230Q e/john@example.com a/123 Disneyland g/Male r/Malay m/Psychology s/NUS gr/4.33 j/Manager`: adds a person named `John` to all applicants database. +* `*createJob* jn/Manager`: creates new job opening `Manager`. +* `*addAll* a jn/Manager` : adds all applicants in the database to the `applicants` list of job `Manager`. +* `*displayJob* jn/Manager`: displays the selection process for job opening `Manager` +* `*list*` : Goes back to the list of all applicants and job openings. + +==== Adding a person: `add` + +Adds a person to slaveFinder() + +Format: `add n/NAME p/PHONE_NUMBER nric/NRIC e/EMAIL a/ADDRESS g/GENDER r/RACE m/MAJOR s/SCHOOL gr/GRADE j/JOBS_APPLY` + +**** +[TIP] +* `n/`: *Name* should only contain alphanumeric characters and spaces, and should not be empty. +* `a/`: *Address* can take any values, but should not be empty. +* `nric/`: NRIC must be unique. It must start with S, followed by exactly 7 numbers, and end with an alpabet in capital letter. It should not be empty. +* `p/`: *Phone* numbers should only contain numbers, and it should be at least 3 digits long, and should not be empty. +* `e/`: *Email* should be of the format local-part@domain, and should not be empty. "E.g. example@gmail.com" +* `g/`: *Gender* should only be "Female", "Male" or "Others", and should not be empty. +* `r/`: *Race* should only be "Chinese", "Malay", "Indian" or "Others", and should not be empty. +* `gr/`: *Grade* should only contain positive numbers, and must be in exactly 2 decimal place. E.g. "4.64" +* `s/`: *School* can take any values, but should not be empty. +* `m/`: *Major* should only contain alphanumeric characters and spaces, and should not be empty. +* `j/`: *Jobs Apply* must only contain one word. If two or more words, have to be connected by a dash. E.g. "Software-Engineer". It should not be empty. It can take more than 1 value. E.g. "j/Manager j/Sweeper" +* `is/`: *Interview scores* field is optional, and must be exactly 5 set of numbers, each seperated by a comma. E.g. "1,2,3,4,5" +* `kpl/`: *Known Programming Language* field is optional. It can take any values, and can take more than 1 value. E.g. "kpl/Java kpl/Python" +* `pj/`: *Past jobs* field is optional, and past job must only contain one word. If two or more words, have to be connected by a dash. E.g. "Software-Engineer". It can take more than 1 value E.g. "pj/Manager pj/Sweeper" +**** + +Examples: + +* `add n/John p/91757536 nric/S8761230Q e/john@example.com a/123 Disneyland g/Male r/Malay m/Psychology s/NUS gr/4.33 j/Manager` +* `add n/Betty p/123 nric/S4444455Y e/betty@bet.com a/321 USS g/Female r/Others m/Life Science s/NTU gr/0.44 j/Helper is/1,2,1,10,5 kpl/Java pj/Chief-Executive-Officer` + + +// tag::undoredo[] +==== Undoing previous command : `undo` + +Restores slaveFinder() to the state before the previous _undoable_ command was executed. + +Format: `undo` + +[NOTE] +===== +Undoable commands: those commands that modify slaveFinder()'s content (`add`, `delete`, `edit`, `clear`, `createJob`, `deleteJob`, `generateInterviews`, `setMaxInterviewsADay`, `setBlockOutDates`, `clearInterviews`, `filter`, `deleteFilter`). +===== + +Examples: + +* `edit 1 n/Johnny` + +`list` + +`undo` (reverses the `edit 1 n/Johnny` command) + + +==== Redoing the previously undone command : `redo` + +Reverses the most recent `undo` command. + +Format: `redo` + +Examples: + +* `edit 1 n/Johnny` + +`undo` (reverses the `edit 1 n/Johnny` command) + +`redo` (reapplies the `edit 1 n/Johnny` command) + + +* `edit 1 n/Johnny` + +`redo` + +The `redo` command fails as there are no `undo` commands executed previously. + +* `edit 1 n/Johnny` + +`clear` + +`undo` (reverses the `clear` command) + +`undo` (reverses the `edit 1 n/Johnny` command) + +`redo` (reapplies the `edit 1 n/Johnny` command) + +`redo` (reapplies the `clear` command) + +// end::undoredo[] + +==== Editing a person : `edit` + +Edits an existing person in slaveFinder(). + +Format: `edit INDEX n/NAME p/PHONE_NUMBER nric/NRIC e/EMAIL a/ADDRESS g/GENDER r/RACE m/MAJOR s/SCHOOL gr/GRADE j/JOBS_APPLY` + +**** +* 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, ... +* Editting fields that allows more than 1 value will entirely replace the existing values. +* Existing values will be updated to the input values. +**** + +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` pj/Manager + +Edits the name of the 2nd person to be `Betsy Crower` and clears all existing past jobs and replace it with 'Manager". + +== Contributions to the Developer Guide + +|=== +|_Given below are sections I contributed to the Developer Guide. They showcase my ability to write technical documentation and the technical depth of my contributions to the project._ +|=== + +=== Analytics Feature +==== Current Implementation + +The analytics is facilicated by the Analytics class. Analytics data are generated in real time depending on the specific job currently on display in the software by the user. Hence it will not be saved as states in the `versionedAddressBook`. It pulls required person list to generate data from Model, which consists of lists: `displayedFilter`, `activeJobAllApplicants`, `activeJobKiv`, `activeJobInterview`, `activeJobShortist`. An `Analytics` object will be created by Analytics class, storing the various required data generated, and pass it to Logic and UI for display. + +Given below is the sequence diagram for `analytics`: + +image::AnalyticsDiagram.png[width="790"] + +[[Design-Ui]] +=== UI component + +.Structure of the UI Component +image::UiComponentClassDiagram.png[width="800"] + +*API* : link:{repoURL}/src/main/java/seedu/address/ui/Ui.java[`Ui.java`] + +The UI consists of a `MainWindow` that is made up of parts e.g.`CommandBox`, `ResultDisplay`, `PersonListPanel`, `StatusBarFooter`, `JobListPanel` etc. All these, including the `MainWindow`, inherit from the abstract `UiPart` class. + +The `UI` component uses JavaFx UI framework. The layout of these UI parts are defined in matching `.fxml` files that are in the `src/main/resources/view` folder. For example, the layout of the link:{repoURL}/src/main/java/seedu/address/ui/MainWindow.java[`MainWindow`] is specified in link:{repoURL}/src/main/resources/view/MainWindow.fxml[`MainWindow.fxml`] + +The `UI` component, + +* Executes user commands using the `Logic` component. +* Listens for changes to `Model` data so that the UI can be updated with the modified data. + +=== Use case: View Analytics + +*MSS* + +1. User requests to display various lists of applicants from one of the jobs in all job openings lists +2. slaveFinder() shows lists of persons for specific job +3. User requests to view analytics for specific list of persons +4. slaveFinder() shows analytics results ++ +Use case ends. + + + + + +--- diff --git a/docs/team/danieldssim.adoc b/docs/team/danieldssim.adoc new file mode 100644 index 000000000000..a0ed2faf4343 --- /dev/null +++ b/docs/team/danieldssim.adoc @@ -0,0 +1,61 @@ += Daniel Sim - Project Portfolio +:site-section: AboutUs +:imagesDir: ../images +:stylesDir: ../stylesheets + +== PROJECT: slaveFinder() + +--- + +== Overview + +slaveFinder() is a desktop address book application used for people in the HR department of a company to help them filter people and schedule interviews. It also helps with analytics of the hiring process in the company. + +== Summary of contributions + +* *Major enhancement*: added *Job manipulation functionality* +** What it does: Allows users to interact with new Job object by creating and deleting jobs as well as adding, removing and moving people within the lists contained in job. +** Justification: This feature allows the user to contain data for more than one job opening as well as to indicate progress of candidates through the job application process. +** Highlights: Implementation of this class required a modification of the addressbook application across all components. Base addressbook database was enhanced to not only contain a database of people but also a database of jobs. Add command was modified to automatically add people who have applied for an available job to the first list ("applicants") of the job. When a job is created, all applicants for the specific job are automatically added. +** Credits: + +* *Minor enhancement*: Added a major field and known programming language field which helps in the ranking and filtering of individuals for interviews + +* *Code contributed*:https://nus-cs2103-ay1819s2.github.io/cs2103-dashboard/#=undefined&search=danieldssim + +* *Other contributions*: + +** Project management: +*** Managed Issue Tracker by linking pull requests to issues +*** Set-Up milestones 1.1 and 1.2 +** Enhancements to existing features: +** Documentation: +** Community: +** Tools: + + + +== Contributions to the User Guide + + +|=== +|_Given below are sections I contributed to the User Guide. They showcase my ability to write documentation targeting end-users._ +|=== +* Usage related to Job functions +include::../UserGuide.adoc[tag=jobs] + + +== Contributions to the Developer Guide + +|=== +|_Given below are sections I contributed to the Developer Guide. They showcase my ability to write technical documentation and the technical depth of my contributions to the project._ +|=== +* Updated use cases and user stories +* Job Implementation details +include::../DeveloperGuide.adoc[tag=jobs] + + + + + +--- diff --git a/docs/team/johndoe.adoc b/docs/team/johndoe.adoc deleted file mode 100644 index 453c2152ab9d..000000000000 --- a/docs/team/johndoe.adoc +++ /dev/null @@ -1,72 +0,0 @@ -= John Doe - Project Portfolio -:site-section: AboutUs -:imagesDir: ../images -:stylesDir: ../stylesheets - -== PROJECT: AddressBook - Level 4 - ---- - -== Overview - -AddressBook - Level 4 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. - -== Summary of contributions - -* *Major enhancement*: 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}_ - -* *Minor enhancement*: added a history command that allows the user to navigate to previous commands using up/down keys. - -* *Code contributed*: [https://github.com[Functional code]] [https://github.com[Test code]] _{give links to collated code files}_ - -* *Other contributions*: - -** Project management: -*** Managed releases `v1.3` - `v1.5rc` (3 releases) on GitHub -** Enhancements to existing features: -*** Updated the GUI color scheme (Pull requests https://github.com[#33], https://github.com[#34]) -*** Wrote additional tests for existing features to increase coverage from 88% to 92% (Pull requests https://github.com[#36], https://github.com[#38]) -** Documentation: -*** Did cosmetic tweaks to existing contents of the User Guide: https://github.com[#14] -** Community: -*** PRs reviewed (with non-trivial review comments): https://github.com[#12], https://github.com[#32], https://github.com[#19], https://github.com[#42] -*** Contributed to forum discussions (examples: https://github.com[1], https://github.com[2], https://github.com[3], https://github.com[4]) -*** Reported bugs and suggestions for other teams in the class (examples: https://github.com[1], https://github.com[2], https://github.com[3]) -*** Some parts of the history feature I added was adopted by several other class mates (https://github.com[1], https://github.com[2]) -** Tools: -*** Integrated a third party library (Natty) to the project (https://github.com[#42]) -*** Integrated a new Github plugin (CircleCI) to the team repo - -_{you can add/remove categories in the list above}_ - -== Contributions to the User Guide - - -|=== -|_Given below are sections I contributed to the User Guide. They showcase my ability to write documentation targeting end-users._ -|=== - -include::../UserGuide.adoc[tag=undoredo] - -include::../UserGuide.adoc[tag=dataencryption] - -== Contributions to the Developer Guide - -|=== -|_Given below are sections I contributed to the Developer Guide. They showcase my ability to write technical documentation and the technical depth of my contributions to the project._ -|=== - -include::../DeveloperGuide.adoc[tag=undoredo] - -include::../DeveloperGuide.adoc[tag=dataencryption] - - -== PROJECT: PowerPointLabs - ---- - -_{Optionally, you may include other projects in your portfolio.}_ diff --git a/src/main/java/seedu/address/MainApp.java b/src/main/java/seedu/address/MainApp.java index a92d4d5d71f0..de2fa396021b 100644 --- a/src/main/java/seedu/address/MainApp.java +++ b/src/main/java/seedu/address/MainApp.java @@ -36,7 +36,7 @@ */ public class MainApp extends Application { - public static final Version VERSION = new Version(0, 6, 0, true); + public static final Version VERSION = new Version(1, 3, 0, true); private static final Logger logger = LogsCenter.getLogger(MainApp.class); @@ -48,7 +48,7 @@ public class MainApp extends Application { @Override public void init() throws Exception { - logger.info("=============================[ Initializing AddressBook ]==========================="); + logger.info("=============================[ Initializing slaveFinder() ]==========================="); super.init(); AppParameters appParameters = AppParameters.parse(getParameters()); @@ -167,7 +167,7 @@ protected UserPrefs initPrefs(UserPrefsStorage storage) { @Override public void start(Stage primaryStage) { - logger.info("Starting AddressBook " + MainApp.VERSION); + logger.info("Starting slaveFinder() " + MainApp.VERSION); ui.start(primaryStage); } diff --git a/src/main/java/seedu/address/commons/core/Messages.java b/src/main/java/seedu/address/commons/core/Messages.java index 1deb3a1e4695..fdf889641856 100644 --- a/src/main/java/seedu/address/commons/core/Messages.java +++ b/src/main/java/seedu/address/commons/core/Messages.java @@ -7,7 +7,11 @@ public class Messages { public static final String MESSAGE_UNKNOWN_COMMAND = "Unknown command"; public static final String MESSAGE_INVALID_COMMAND_FORMAT = "Invalid command format! \n%1$s"; - public static final String MESSAGE_INVALID_PERSON_DISPLAYED_INDEX = "The person index provided is invalid"; + public static final String MESSAGE_INVALID_PERSON_DISPLAYED_INDEX = "The person index provided is invalid.\n%1$s"; + public static final String MESSAGE_INVALID_PREAMBLE = "The preamble information has wrong format.\n%1$s"; public static final String MESSAGE_PERSONS_LISTED_OVERVIEW = "%1$d persons listed!"; + public static final String MESSAGE_COMMAND_CANNOT_USE = + "This command should not be used in the Jobs Detail Showing Screen"; + } diff --git a/src/main/java/seedu/address/commons/util/StringUtil.java b/src/main/java/seedu/address/commons/util/StringUtil.java index 61cc8c9a1cb8..5389bb8b4692 100644 --- a/src/main/java/seedu/address/commons/util/StringUtil.java +++ b/src/main/java/seedu/address/commons/util/StringUtil.java @@ -6,6 +6,11 @@ import java.io.PrintWriter; import java.io.StringWriter; import java.util.Arrays; +import java.util.Set; + +import seedu.address.model.person.JobsApply; +import seedu.address.model.person.KnownProgLang; +import seedu.address.model.person.PastJob; /** * Helper functions for handling strings. @@ -14,14 +19,15 @@ public class StringUtil { /** * Returns true if the {@code sentence} contains the {@code word}. - * Ignores case, but a full word match is required. - *
examples:
+     * Ignores case, but a full word match is required.
+     * 
examples:
      *       containsWordIgnoreCase("ABc def", "abc") == true
      *       containsWordIgnoreCase("ABc def", "DEF") == true
      *       containsWordIgnoreCase("ABc def", "AB") == false //not a full word match
      *       
+ * * @param sentence cannot be null - * @param word cannot be null, cannot be empty, must be a single word + * @param word cannot be null, cannot be empty, must be a single word */ public static boolean containsWordIgnoreCase(String sentence, String word) { requireNonNull(sentence); @@ -35,7 +41,36 @@ public static boolean containsWordIgnoreCase(String sentence, String word) { String[] wordsInPreppedSentence = preppedSentence.split("\\s+"); return Arrays.stream(wordsInPreppedSentence) - .anyMatch(preppedWord::equalsIgnoreCase); + .anyMatch(preppedWord::equalsIgnoreCase); + } + + /** + * Returns true if the {@code word} in the range of {@code sentence}. + *
examples:
+     *       valueInRange("1.5-2.0", "1.75") == true
+     *       valueInRange("1.5-2.0", "1.5") == true
+     *       valueInRange("1.5-2.0", "2.0") == true
+     *       
+ * + * @param sentence cannot be null + * @param word cannot be null, cannot be empty, must be a single word + */ + public static boolean valueInRange(String sentence, String word) { + requireNonNull(sentence); + requireNonNull(word); + float value = Float.parseFloat(word); + + String preppedSentence = sentence.trim(); + checkArgument(!preppedSentence.isEmpty(), "Range parameter cannot be empty"); + + int rangeParaSize = preppedSentence.split("-").length; + checkArgument(rangeParaSize == 2, "Range parameter format wrong"); + String[] values = preppedSentence.split("-"); + String preppedUpperBound = values[1].trim(); + String preppedLowerBound = values[0].trim(); + Boolean isValueSmallerThanUpper = value <= Float.parseFloat(preppedUpperBound); + Boolean isValueBiggerThanLower = value >= Float.parseFloat(preppedLowerBound); + return isValueSmallerThanUpper && isValueBiggerThanLower; } /** @@ -48,11 +83,35 @@ public static String getDetails(Throwable t) { return t.getMessage() + "\n" + sw.toString(); } + /** + * Returns a detailed message of the t, including the stack trace. + */ + public static String getSetString(Set s) { + requireNonNull(s); + StringBuilder stringBuilder = new StringBuilder(); + for (Object obj : s) { + if (obj instanceof JobsApply) { + String jobsApply = ((JobsApply) obj).value; + stringBuilder.append(jobsApply); + } else if (obj instanceof KnownProgLang) { + String knownProgLang = ((KnownProgLang) obj).value; + stringBuilder.append(knownProgLang); + } else if (obj instanceof PastJob) { + String pastJob = ((PastJob) obj).value; + stringBuilder.append(pastJob); + } + stringBuilder.append(" "); + } + return stringBuilder.toString(); + } + + /** * Returns true if {@code s} represents a non-zero unsigned integer * e.g. 1, 2, 3, ..., {@code Integer.MAX_VALUE}
* Will return false for any other non-null string input * e.g. empty string, "-1", "0", "+1", and " 2 " (untrimmed), "3 0" (contains whitespace), "1 a" (contains letters) + * * @throws NullPointerException if {@code s} is null. */ public static boolean isNonZeroUnsignedInteger(String s) { diff --git a/src/main/java/seedu/address/logic/Logic.java b/src/main/java/seedu/address/logic/Logic.java index 60369e2074e4..9087e51deced 100644 --- a/src/main/java/seedu/address/logic/Logic.java +++ b/src/main/java/seedu/address/logic/Logic.java @@ -9,6 +9,7 @@ import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.logic.parser.exceptions.ParseException; import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.model.job.Job; import seedu.address.model.person.Person; /** @@ -22,7 +23,7 @@ public interface Logic { * @throws CommandException If an error occurs during command execution. * @throws ParseException If an error occurs during parsing. */ - CommandResult execute(String commandText) throws CommandException, ParseException; + CommandResult execute(String commandText, boolean isAllJobScreen) throws CommandException, ParseException; /** * Returns the AddressBook. @@ -34,6 +35,12 @@ public interface Logic { /** Returns an unmodifiable view of the filtered list of persons */ ObservableList getFilteredPersonList(); + /** Returns an unmodifiable view of the filtered list people in job */ + ObservableList getJobsList(int k); + + /** Returns an unmodifiable view of the filtered list of jobs */ + ObservableList getAllJobs(); + /** * Returns an unmodifiable view of the list of commands entered by the user. * The list is ordered from the least recent command to the most recent command. @@ -63,10 +70,28 @@ public interface Logic { */ ReadOnlyProperty selectedPersonProperty(); + /** + * Selected job in the filtered job list. + * null if no job is selected. + * + * @see seedu.address.model.Model#selectedJobProperty() + */ + ReadOnlyProperty selectedJobProperty(); + /** * Sets the selected person in the filtered person list. * * @see seedu.address.model.Model#setSelectedPerson(Person) */ void setSelectedPerson(Person person); + + void setSelectedAll(Person person); + + void setSelectedKiv(Person person); + + void setSelectedInterviewed(Person person); + + void setSelectedSelected(Person person); + + void setSelectedJob(Job job); } diff --git a/src/main/java/seedu/address/logic/LogicManager.java b/src/main/java/seedu/address/logic/LogicManager.java index 5cb24a617beb..2749ed9a6325 100644 --- a/src/main/java/seedu/address/logic/LogicManager.java +++ b/src/main/java/seedu/address/logic/LogicManager.java @@ -15,6 +15,8 @@ import seedu.address.logic.parser.exceptions.ParseException; import seedu.address.model.Model; import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.model.job.Job; +import seedu.address.model.job.JobListName; import seedu.address.model.person.Person; import seedu.address.storage.Storage; @@ -42,13 +44,13 @@ public LogicManager(Model model, Storage storage) { } @Override - public CommandResult execute(String commandText) throws CommandException, ParseException { + public CommandResult execute(String commandText, boolean isAllJobScreen) throws CommandException, ParseException { logger.info("----------------[USER COMMAND][" + commandText + "]"); addressBookModified = false; CommandResult commandResult; try { - Command command = addressBookParser.parseCommand(commandText); + Command command = addressBookParser.parseCommand(commandText, isAllJobScreen); commandResult = command.execute(model, history); } finally { history.add(commandText); @@ -76,6 +78,25 @@ public ObservableList getFilteredPersonList() { return model.getFilteredPersonList(); } + public ObservableList getJobsList(int k) { + switch (k) { + case 0: + return model.getJobsList(JobListName.APPLICANT); + case 1: + return model.getJobsList(JobListName.KIV); + case 2: + return model.getJobsList(JobListName.INTERVIEW); + case 3: + return model.getJobsList(JobListName.SHORTLIST); + default: + return model.getJobsList(JobListName.STUB); + } + } + + public ObservableList getAllJobs() { + return model.getAllJobs(); + } + @Override public ObservableList getHistory() { return history.getHistory(); @@ -101,8 +122,38 @@ public ReadOnlyProperty selectedPersonProperty() { return model.selectedPersonProperty(); } + @Override + public ReadOnlyProperty selectedJobProperty() { + return model.selectedJobProperty(); + } + @Override public void setSelectedPerson(Person person) { model.setSelectedPerson(person); } + + @Override + public void setSelectedAll(Person person) { + model.setSelectedAll(person); + } + + @Override + public void setSelectedKiv(Person person) { + model.setSelectedKiv(person); + } + + @Override + public void setSelectedInterviewed(Person person) { + model.setSelectedInterviewed(person); + } + + @Override + public void setSelectedSelected(Person person) { + model.setSelectedSelected(person); + } + + public void setSelectedJob(Job job) { + model.setSelectedJob(job); + } + } diff --git a/src/main/java/seedu/address/logic/commands/AddCommand.java b/src/main/java/seedu/address/logic/commands/AddCommand.java index d88e831ff1ce..f4bd5a1d7345 100644 --- a/src/main/java/seedu/address/logic/commands/AddCommand.java +++ b/src/main/java/seedu/address/logic/commands/AddCommand.java @@ -1,15 +1,31 @@ package seedu.address.logic.commands; import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_COMMAND_CANNOT_USE; import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; +import static seedu.address.logic.parser.CliSyntax.PREFIX_GENDER; +import static seedu.address.logic.parser.CliSyntax.PREFIX_GRADE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_INTERVIEWSCORES; +import static seedu.address.logic.parser.CliSyntax.PREFIX_JOBSAPPLY; +import static seedu.address.logic.parser.CliSyntax.PREFIX_KNOWNPROGLANG; +import static seedu.address.logic.parser.CliSyntax.PREFIX_MAJOR; import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NRIC; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PASTJOB; import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; -import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; +import static seedu.address.logic.parser.CliSyntax.PREFIX_RACE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_SCHOOL; + +import java.util.Iterator; import seedu.address.logic.CommandHistory; import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.model.Model; +import seedu.address.model.job.Job; +import seedu.address.model.job.JobListName; +import seedu.address.model.job.JobName; +import seedu.address.model.person.JobsApply; import seedu.address.model.person.Person; /** @@ -18,25 +34,58 @@ public class AddCommand extends Command { public static final String COMMAND_WORD = "add"; + public static final String COMMAND_ALIAS = "a"; - public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a person to the address book. " - + "Parameters: " - + PREFIX_NAME + "NAME " - + PREFIX_PHONE + "PHONE " - + PREFIX_EMAIL + "EMAIL " - + PREFIX_ADDRESS + "ADDRESS " - + "[" + PREFIX_TAG + "TAG]...\n" - + "Example: " + COMMAND_WORD + " " - + PREFIX_NAME + "John Doe " - + PREFIX_PHONE + "98765432 " - + PREFIX_EMAIL + "johnd@example.com " - + PREFIX_ADDRESS + "311, Clementi Ave 2, #02-25 " - + PREFIX_TAG + "friends " - + PREFIX_TAG + "owesMoney"; + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a person to the address book.\n" + + "Parameters: " + + PREFIX_NAME + "NAME " + + PREFIX_PHONE + "PHONE " + + PREFIX_EMAIL + "EMAIL " + + PREFIX_NRIC + "NRIC " + + PREFIX_GENDER + "GENDER " + + PREFIX_RACE + "RACE " + + PREFIX_ADDRESS + "ADDRESS " + + PREFIX_SCHOOL + "SCHOOL " + + PREFIX_MAJOR + "MAJOR " + + PREFIX_GRADE + "GRADE " + + PREFIX_INTERVIEWSCORES + "INTERVIEWSCORES " + + PREFIX_JOBSAPPLY + "JOBSAPPLY..." + + "[" + PREFIX_PASTJOB + "PASTJOB]..." + + "[" + PREFIX_KNOWNPROGLANG + "KNOWNPROGLANG]...\n" + + "Example: " + COMMAND_WORD + " " + + PREFIX_NAME + "John Doe " + + PREFIX_PHONE + "98765432 " + + PREFIX_EMAIL + "johnd@example.com " + + PREFIX_NRIC + "S9671597H " + + PREFIX_GENDER + "Male " + + PREFIX_RACE + "Indian " + + PREFIX_ADDRESS + "311, Clementi Ave 2, #02-25 " + + PREFIX_SCHOOL + "NUS " + + PREFIX_MAJOR + "Computer Science " + + PREFIX_GRADE + "4.76 " + + PREFIX_JOBSAPPLY + "Software-Engineer " + + PREFIX_INTERVIEWSCORES + "1,2,3,4,5 " + + PREFIX_KNOWNPROGLANG + "Python " + + PREFIX_PASTJOB + "Software Engineer " + + "The alias \"a\" can be used instead.\n"; + public static final String MESSAGE_LACK_NAME = "Name field should not be empty.\n%1$s"; + public static final String MESSAGE_LACK_ADDRESS = "Address field should not be empty.\n%1$s"; + public static final String MESSAGE_LACK_EMAIL = "Email field should not be empty.\n%1$s"; + public static final String MESSAGE_LACK_GENDER = "Gender field should not be empty.\n%1$s"; + public static final String MESSAGE_LACK_GRADE = "Grade field should not be empty.\n%1$s"; + public static final String MESSAGE_LACK_JOBSAPPLY = "Jobs Apply field should not be empty.\n%1$s"; + public static final String MESSAGE_LACK_MAJOR = "Major field should not be empty.\n%1$s"; + public static final String MESSAGE_LACK_NRIC = "Nric field should not be empty.\n%1$s"; + public static final String MESSAGE_LACK_PHONE = "Phone field should not be empty.\n%1$s"; + public static final String MESSAGE_LACK_RACE = "Race field should not be empty.\n%1$s"; + public static final String MESSAGE_LACK_SCHOOL = "School field should not be empty.\n%1$s"; + public static final String MESSAGE_INFORMATION_WITHOUT_PREFIX = + "All information need a prefix for this command. \n%1$s"; 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; /** @@ -50,12 +99,24 @@ public AddCommand(Person person) { @Override public CommandResult execute(Model model, CommandHistory history) throws CommandException { requireNonNull(model); - + boolean isAllJobsScreen = model.getIsAllJobScreen(); + if (!isAllJobsScreen) { + throw new CommandException(MESSAGE_COMMAND_CANNOT_USE); + } if (model.hasPerson(toAdd)) { throw new CommandException(MESSAGE_DUPLICATE_PERSON); } model.addPerson(toAdd); + Iterator itr = toAdd.getJobsApply().iterator(); + while (itr.hasNext()) { + JobsApply job = itr.next(); + try { + model.addPersonToJob(new Job(new JobName(job.toString())), toAdd, JobListName.APPLICANT); + } catch (Exception e) { + continue; + } + } model.commitAddressBook(); return new CommandResult(String.format(MESSAGE_SUCCESS, toAdd)); } @@ -63,7 +124,7 @@ public CommandResult execute(Model model, CommandHistory history) throws Command @Override public boolean equals(Object other) { return other == this // short circuit if same object - || (other instanceof AddCommand // instanceof handles nulls - && toAdd.equals(((AddCommand) other).toAdd)); + || (other instanceof AddCommand // instanceof handles nulls + && toAdd.equals(((AddCommand) other).toAdd)); } } diff --git a/src/main/java/seedu/address/logic/commands/AddListToJobCommand.java b/src/main/java/seedu/address/logic/commands/AddListToJobCommand.java new file mode 100644 index 000000000000..0099afaff020 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/AddListToJobCommand.java @@ -0,0 +1,94 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_JOBNAME; + +import seedu.address.logic.CommandHistory; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.job.Job; +import seedu.address.model.job.JobListName; +import seedu.address.model.job.JobName; + + +/** + * Adds a person to the address book. + */ +public class AddListToJobCommand extends Command { + + public static final String COMMAND_WORD = "addAll"; + public static final String COMMAND_ALIAS = "aa"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": adds all shown people to job . " + + "Parameters: " + + "DESTINATION-LIST-NAME " + + "SOURCE-LIST-NAME (Optional. Entire database if omitted.)" + + PREFIX_JOBNAME + "JobName (Optional. Uses activeDisplayedJob if omitted.)" + + "Example: " + COMMAND_WORD + " " + + "kiv " + + "applicant " + + PREFIX_JOBNAME + "Helper " + + "The alias \"aa\" can be used instead.\n" + + "Example: " + COMMAND_ALIAS + " " + + "kiv " + + "applicant " + + PREFIX_JOBNAME + "Helper "; + + public static final String MESSAGE_SUCCESS = "All non-duplicated people added to job: %1$s."; + public static final String MESSAGE_MISSING_JOB = "This job does not exist"; + public static final String MESSAGE_NO_ACTIVE_JOB = "No active Job, please provide a JobName with prefix jn/"; + public static final String MESSAGE_NO_DESTINATION = "Please provide a destination list\n"; + public static final String MESSAGE_IDENTICAL_LISTS = "Destination and source lists cannot be the same\n"; + + private final JobListName to; + private final JobListName from; + private JobName toAdd; + + /** + * Creates an AddCommand to add the specified {@code job} + */ + public AddListToJobCommand(JobName name, JobListName toName, JobListName fromName) { + toAdd = name; + to = toName; + from = fromName; + } + + @Override + public CommandResult execute(Model model, CommandHistory history) throws CommandException { + requireNonNull(model); + + if (toAdd == null) { + if (model.getIsAllJobScreen()) { + throw new CommandException(MESSAGE_NO_ACTIVE_JOB); + } + toAdd = model.getActiveJob().getName(); + } + + Job tempJob = new Job(toAdd); + if (!model.hasJob(tempJob)) { + throw new CommandException(MESSAGE_MISSING_JOB); + } + + if (to == from) { + throw new CommandException(MESSAGE_IDENTICAL_LISTS); + } + + model.addFilteredPersonsToJob(toAdd, from, to); + + model.commitAddressBook(); + if (toAdd == null) { + + } + String command = String.format(MESSAGE_SUCCESS, toAdd); + return new CommandResult(command); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof AddListToJobCommand // instanceof handles nulls + && to.equals(((AddListToJobCommand) other).to) + && from.equals(((AddListToJobCommand) other).from) + && (toAdd == null || toAdd.equals(((AddListToJobCommand) other).toAdd))); + } +} diff --git a/src/main/java/seedu/address/logic/commands/ClearCommand.java b/src/main/java/seedu/address/logic/commands/ClearCommand.java index a22219ad76ad..550a364dd90a 100644 --- a/src/main/java/seedu/address/logic/commands/ClearCommand.java +++ b/src/main/java/seedu/address/logic/commands/ClearCommand.java @@ -1,8 +1,10 @@ package seedu.address.logic.commands; import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_COMMAND_CANNOT_USE; import seedu.address.logic.CommandHistory; +import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.model.AddressBook; import seedu.address.model.Model; @@ -12,12 +14,17 @@ public class ClearCommand extends Command { public static final String COMMAND_WORD = "clear"; + public static final String COMMAND_ALIAS = "c"; public static final String MESSAGE_SUCCESS = "Address book has been cleared!"; @Override - public CommandResult execute(Model model, CommandHistory history) { + public CommandResult execute(Model model, CommandHistory history) throws CommandException { requireNonNull(model); + boolean isAllJobsScreen = model.getIsAllJobScreen(); + if (!isAllJobsScreen) { + throw new CommandException(MESSAGE_COMMAND_CANNOT_USE); + } model.setAddressBook(new AddressBook()); model.commitAddressBook(); return new CommandResult(MESSAGE_SUCCESS); diff --git a/src/main/java/seedu/address/logic/commands/ClearFilterCommand.java b/src/main/java/seedu/address/logic/commands/ClearFilterCommand.java new file mode 100644 index 000000000000..2ceb7d40ac88 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/ClearFilterCommand.java @@ -0,0 +1,77 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.model.job.JobListName.EMPTY; + +import seedu.address.logic.CommandHistory; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.job.JobListName; +import seedu.address.model.person.predicate.UniqueFilterList; + +/** + * Clear filter list + */ +public class ClearFilterCommand extends Command { + + public static final String COMMAND_WORD = "clearFilter"; + public static final String COMMAND_ALIAS = "cf"; + + public static final String MESSAGE_USAGE_ALLJOB_SCREEN = COMMAND_WORD + + ": Clears the filter identified by the filter name used in the All Job Showing Screen.\n" + + "Example: " + COMMAND_WORD + " \n" + + "The alias \"d\" can be used instead.\n" + + "Example: " + COMMAND_ALIAS + " \n"; + + public static final String MESSAGE_USAGE_DETAIL_SCREEN = COMMAND_WORD + + ": Clears the filter identified by the filter name used in the All Job Showing Screen.\n" + + "Example: " + COMMAND_WORD + " Applicant \n" + + "The alias \"d\" can be used instead.\n" + + "Example: " + COMMAND_ALIAS + " Applicant \n"; + + public static final String MESSAGE_CLEAR_FILTER_SUCCESS = "Cleard Filter Success"; + public static final String MESSAGE_LACK_LISTNAME = + "Clear Filter Command in Display Job page need indicate job list\n%1$s"; + public static final String MESSAGE_REDUNDANT_LISTNAME = + "Clear Filter Command in All Jobs page no need indicate job list\n%1$s"; + private final JobListName filterListName; + + public ClearFilterCommand(JobListName filterListName) { + this.filterListName = filterListName; + } + + @Override + public CommandResult execute(Model model, CommandHistory history) throws CommandException { + requireNonNull(model); + UniqueFilterList predicateList; + boolean isAllJobScreen = model.getIsAllJobScreen(); + boolean hasListName = filterListName != EMPTY; + checkException(isAllJobScreen, hasListName); + model.clearJobFilteredLists(filterListName); + model.updateFilteredPersonLists(filterListName); + predicateList = model.getPredicateLists(filterListName); + return new CommandResult(MESSAGE_CLEAR_FILTER_SUCCESS, filterListName, predicateList); + } + + /** + * @param isAllJobScreen Indicate the current screen, true if screen on all jobs screen + * @param hasListName Indicate whether command parser parse the List name + * @throws CommandException throw exception and catch by function excute() + */ + private void checkException(boolean isAllJobScreen, boolean hasListName) + throws CommandException { + String showMessage = isAllJobScreen ? MESSAGE_USAGE_ALLJOB_SCREEN : MESSAGE_USAGE_DETAIL_SCREEN; + if (!isAllJobScreen && !hasListName) { + throw new CommandException(String.format(MESSAGE_LACK_LISTNAME, showMessage)); + } else if (isAllJobScreen && hasListName) { + throw new CommandException(String.format(MESSAGE_REDUNDANT_LISTNAME, showMessage)); + } + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof ClearFilterCommand // instanceof handles nulls + && (filterListName.equals(((ClearFilterCommand) other).filterListName))); // state check + } +} diff --git a/src/main/java/seedu/address/logic/commands/ClearInterviewsCommand.java b/src/main/java/seedu/address/logic/commands/ClearInterviewsCommand.java new file mode 100644 index 000000000000..1d7f5b5dbc12 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/ClearInterviewsCommand.java @@ -0,0 +1,24 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import seedu.address.logic.CommandHistory; +import seedu.address.model.Model; + +/** + * Clears Interviews. + */ +public class ClearInterviewsCommand extends Command { + + public static final String COMMAND_WORD = "clearInterviews"; + public static final String MESSAGE_SUCCESS = "Interviews has been cleared!"; + + @Override + public CommandResult execute(Model model, CommandHistory history) { + requireNonNull(model); + model.clearInterviews(); + model.commitAddressBook(); + return new CommandResult(MESSAGE_SUCCESS); + } + +} diff --git a/src/main/java/seedu/address/logic/commands/CommandResult.java b/src/main/java/seedu/address/logic/commands/CommandResult.java index 92f900b7916d..cd8a7f92aa26 100644 --- a/src/main/java/seedu/address/logic/commands/CommandResult.java +++ b/src/main/java/seedu/address/logic/commands/CommandResult.java @@ -4,6 +4,11 @@ import java.util.Objects; +import seedu.address.model.analytics.Analytics; +import seedu.address.model.job.JobListName; +import seedu.address.model.job.JobName; +import seedu.address.model.person.predicate.UniqueFilterList; + /** * Represents the result of a command execution. */ @@ -11,12 +16,34 @@ public class CommandResult { private final String feedbackToUser; - /** Help information should be shown to the user. */ + /** + * Analytics information should be shown to user + */ + + private Analytics analytics; + + private JobName job; + + private String interviews; + + private boolean filter = false; + + private UniqueFilterList filterList; + + private JobListName listName; + + + /** + * Help information should be shown to the user. + */ private final boolean showHelp; - /** The application should exit. */ + /** + * The application should exit. + */ private final boolean exit; + /** * Constructs a {@code CommandResult} with the specified fields. */ @@ -34,6 +61,36 @@ public CommandResult(String feedbackToUser) { this(feedbackToUser, false, false); } + public CommandResult(String feedbackToUser, Analytics results) { + this(feedbackToUser, false, false); + if (isSuccessfulAnalytics()) { + analytics = results; + } + + } + + public CommandResult(String feedbackToUser, JobListName name, UniqueFilterList list) { + this(feedbackToUser, false, false); + filter = true; + listName = name; + filterList = list; + } + + public CommandResult(String feedbackToUser, JobName results) { + this(feedbackToUser, false, false); + if (isSuccessfulDisplayJob()) { + job = results; + } + + } + + public CommandResult(String feedbackToUser, String results) { + this(feedbackToUser, false, false); + if (isSuccessfulInterviews()) { + interviews = results; + } + } + public String getFeedbackToUser() { return feedbackToUser; } @@ -46,6 +103,47 @@ public boolean isExit() { return exit; } + public boolean isSuccessfulAnalytics() { + return feedbackToUser.equals(GenerateAnalyticsCommand.MESSAGE_SUCCESS); + } + + public boolean isSuccessfulInterviews() { + return feedbackToUser.equals(ShowInterviewsCommand.COMMAND_SUCCESS); + } + + public boolean isSuccessfulDisplayJob() { + return feedbackToUser.equals(DisplayJobCommand.MESSAGE_SUCCESS); + } + + public boolean isSuccessfulFilter() { + return filter; + } + + public boolean isList() { + return feedbackToUser.equals(ListCommand.MESSAGE_SUCCESS); + } + + //remember to handle null later + public Analytics getAnalytics() { + return analytics; + } + + public JobListName getJobListName() { + return listName; + } + + public UniqueFilterList getFilterList() { + return filterList; + } + + public JobName getJob() { + return job; + } + + public String getInterviews() { + return interviews; + } + @Override public boolean equals(Object other) { if (other == this) { @@ -59,8 +157,8 @@ public boolean equals(Object other) { CommandResult otherCommandResult = (CommandResult) other; return feedbackToUser.equals(otherCommandResult.feedbackToUser) - && showHelp == otherCommandResult.showHelp - && exit == otherCommandResult.exit; + && showHelp == otherCommandResult.showHelp + && exit == otherCommandResult.exit; } @Override diff --git a/src/main/java/seedu/address/logic/commands/CreateJobCommand.java b/src/main/java/seedu/address/logic/commands/CreateJobCommand.java new file mode 100644 index 000000000000..fc5613e7b98a --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/CreateJobCommand.java @@ -0,0 +1,74 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_JOBNAME; +import static seedu.address.model.job.JobListName.EMPTY; + +import java.util.ArrayList; +import java.util.function.Predicate; + +import seedu.address.logic.CommandHistory; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.job.Job; +import seedu.address.model.job.JobListName; +import seedu.address.model.person.Person; +import seedu.address.model.person.predicate.JobsApplyContainsKeywordsPredicate; + + +/** + * Adds a person to the address book. + */ +public class CreateJobCommand extends Command { + + public static final String COMMAND_WORD = "createJob"; + public static final String COMMAND_ALIAS = "cj"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a new job. " + + "Parameters: " + + PREFIX_JOBNAME + "NAME " + + "Example: " + COMMAND_WORD + " " + + PREFIX_JOBNAME + "Search Engineer " + + "The alias \"cj\" can be used instead.\n" + + "Example: " + COMMAND_ALIAS + " " + + PREFIX_JOBNAME + "Search Engineer "; + + public static final String MESSAGE_SUCCESS = "New job created. All applicants added"; + public static final String MESSAGE_DUPLICATE_JOB = "This Job already exists in the list"; + + private final Job toAdd; + + /** + * Creates an AddCommand to add the specified {@code job} + */ + public CreateJobCommand(Job job) { + requireNonNull(job); + toAdd = job; + } + + @Override + public CommandResult execute(Model model, CommandHistory history) throws CommandException { + requireNonNull(model); + + if (model.hasJob(toAdd)) { + throw new CommandException(MESSAGE_DUPLICATE_JOB); + } + + model.addJob(toAdd); + ArrayList jobNameCollection = new ArrayList<>(); + jobNameCollection.add(toAdd.getName().toString()); + Predicate predicator = new JobsApplyContainsKeywordsPredicate(jobNameCollection); + model.updateFilteredPersonList(predicator); + model.addFilteredPersonsToJob(toAdd.getName(), JobListName.STUB, JobListName.APPLICANT); + model.commitAddressBook(); + model.updateFilteredPersonLists(EMPTY); + return new CommandResult(String.format(MESSAGE_SUCCESS, toAdd)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof CreateJobCommand // instanceof handles nulls + && toAdd.equals(((CreateJobCommand) other).toAdd)); + } +} diff --git a/src/main/java/seedu/address/logic/commands/DeleteCommand.java b/src/main/java/seedu/address/logic/commands/DeleteCommand.java index a20e9d49eac7..4bc900f5b748 100644 --- a/src/main/java/seedu/address/logic/commands/DeleteCommand.java +++ b/src/main/java/seedu/address/logic/commands/DeleteCommand.java @@ -1,6 +1,7 @@ package seedu.address.logic.commands; import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_COMMAND_CANNOT_USE; import java.util.List; @@ -17,11 +18,14 @@ public class DeleteCommand extends Command { public static final String COMMAND_WORD = "delete"; + public static final String COMMAND_ALIAS = "d"; public static final String MESSAGE_USAGE = COMMAND_WORD + ": Deletes the person identified by the index number used in the displayed person list.\n" + "Parameters: INDEX (must be a positive integer)\n" - + "Example: " + COMMAND_WORD + " 1"; + + "Example: " + COMMAND_WORD + " 1\n" + + "The alias \"d\" can be used instead.\n" + + "Example: " + COMMAND_ALIAS + " 1"; public static final String MESSAGE_DELETE_PERSON_SUCCESS = "Deleted Person: %1$s"; @@ -34,12 +38,18 @@ public DeleteCommand(Index targetIndex) { @Override public CommandResult execute(Model model, CommandHistory history) throws CommandException { requireNonNull(model); + boolean isAllJobsScreen = model.getIsAllJobScreen(); + if (!isAllJobsScreen) { + throw new CommandException(MESSAGE_COMMAND_CANNOT_USE); + } List lastShownList = model.getFilteredPersonList(); if (targetIndex.getZeroBased() >= lastShownList.size()) { throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); } + + Person personToDelete = lastShownList.get(targetIndex.getZeroBased()); model.deletePerson(personToDelete); model.commitAddressBook(); diff --git a/src/main/java/seedu/address/logic/commands/DeleteFilterCommand.java b/src/main/java/seedu/address/logic/commands/DeleteFilterCommand.java new file mode 100644 index 000000000000..11a1c6386123 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/DeleteFilterCommand.java @@ -0,0 +1,91 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.model.job.JobListName.EMPTY; + +import seedu.address.logic.CommandHistory; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.job.JobListName; +import seedu.address.model.person.exceptions.FilterNotFoundException; +import seedu.address.model.person.predicate.UniqueFilterList; + +/** + * Deletes a person identified using it's displayed index from the address book. + */ +public class DeleteFilterCommand extends Command { + + public static final String COMMAND_WORD = "deleteFilter"; + public static final String COMMAND_ALIAS = "df"; + + public static final String MESSAGE_USAGE_ALLJOB_SCREEN = COMMAND_WORD + + ": Deletes the filter identified by the filter name used in the All Job Showing Screen.\n" + + "Parameters: NameFilterName \n" + + "Example: " + COMMAND_WORD + " Chinese\n" + + "The alias \"d\" can be used instead.\n" + + "Example: " + COMMAND_ALIAS + " Chinese\n"; + + public static final String MESSAGE_USAGE_DETAIL_SCREEN = COMMAND_WORD + + ": Deletes the filter identified by the filter name used in the displayed person list.\n" + + "Parameters: FilterList NameFilterName \n" + + "Example: " + COMMAND_WORD + " Applicant Chinese\n" + + "The alias \"d\" can be used instead.\n" + + "Example: " + COMMAND_ALIAS + " Applicant Chinese\n"; + + public static final String MESSAGE_LACK_FILTERNAME = "Delete Filter Command need a name\n%1$s"; + public static final String MESSAGE_DELETE_FILTER_SUCCESS = "Deleted Filter: %1$s"; + public static final String MESSAGE_LACK_LISTNAME = + "Delete Filter Command in Display Job page need indicate job list\n%1$s"; + public static final String MESSAGE_REDUNDANT_LISTNAME = + "Delete Filter Command in All Jobs page no need indicate job list\n%1$s"; + public static final String MESSAGE_CANOT_FOUND_TARGET_FILTER = "The filter you want to delete can not found\n%1$s"; + + private final String targetName; + private final JobListName filterListName; + + public DeleteFilterCommand(JobListName filterListName, String targetName) { + this.filterListName = filterListName; + this.targetName = targetName; + } + + @Override + public CommandResult execute(Model model, CommandHistory history) throws CommandException { + requireNonNull(model); + UniqueFilterList predicateList; + boolean isAllJobScreen = model.getIsAllJobScreen(); + boolean hasListName = filterListName != EMPTY; + checkException(isAllJobScreen, hasListName); + try { + model.removePredicate(targetName, filterListName); + } catch (FilterNotFoundException ex) { + throw new CommandException(MESSAGE_CANOT_FOUND_TARGET_FILTER); + } + model.updateFilteredPersonLists(filterListName); + predicateList = model.getPredicateLists(filterListName); + return new CommandResult(String.format(MESSAGE_DELETE_FILTER_SUCCESS, targetName), filterListName, + predicateList); + } + + /** + * @param isAllJobScreen Indicate the current screen, true if screen on all jobs screen + * @param hasListName Indicate whether command parser parse the List name + * @throws CommandException throw exception and catch by function excute() + */ + private void checkException(boolean isAllJobScreen, boolean hasListName) + throws CommandException { + String showMessage = isAllJobScreen ? MESSAGE_USAGE_ALLJOB_SCREEN : MESSAGE_USAGE_DETAIL_SCREEN; + if (!isAllJobScreen && !hasListName) { + throw new CommandException(String.format(MESSAGE_LACK_LISTNAME, showMessage)); + } else if (isAllJobScreen && hasListName) { + throw new CommandException(String.format(MESSAGE_REDUNDANT_LISTNAME, showMessage)); + } + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof DeleteFilterCommand // instanceof handles nulls + && (filterListName.equals(((DeleteFilterCommand) other).filterListName)) + && (targetName.equals(((DeleteFilterCommand) other).targetName))); // state check + } +} diff --git a/src/main/java/seedu/address/logic/commands/DeleteJobCommand.java b/src/main/java/seedu/address/logic/commands/DeleteJobCommand.java new file mode 100644 index 000000000000..7c92fd958408 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/DeleteJobCommand.java @@ -0,0 +1,61 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_JOBNAME; + +import seedu.address.logic.CommandHistory; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.job.Job; + + +/** + * Adds a person to the address book. + */ +public class DeleteJobCommand extends Command { + + public static final String COMMAND_WORD = "deleteJob"; + public static final String COMMAND_ALIAS = "rmj"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a new job. " + + "Parameters: " + + PREFIX_JOBNAME + "NAME " + + "Example: " + COMMAND_WORD + " " + + PREFIX_JOBNAME + "Search Engineer " + + "The alias \"rmj\" can be used instead.\n" + + "Example: " + COMMAND_ALIAS + " " + + PREFIX_JOBNAME + "Search Engineer "; + + public static final String MESSAGE_SUCCESS = "Removed Job: %1$s"; + public static final String MESSAGE_MISSING_JOB = "This Job doesn't exist in the list"; + + private final Job toRm; + + /** + * Creates an AddCommand to add the specified {@code job} + */ + public DeleteJobCommand(Job job) { + requireNonNull(job); + toRm = job; + } + + @Override + public CommandResult execute(Model model, CommandHistory history) throws CommandException { + requireNonNull(model); + + if (!model.hasJob(toRm)) { + throw new CommandException(MESSAGE_MISSING_JOB); + } + + model.deleteJob(toRm); + model.commitAddressBook(); + return new CommandResult(String.format(MESSAGE_SUCCESS, toRm)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof DeleteJobCommand // instanceof handles nulls + && toRm.equals(((DeleteJobCommand) other).toRm)); + } +} diff --git a/src/main/java/seedu/address/logic/commands/DisplayJobCommand.java b/src/main/java/seedu/address/logic/commands/DisplayJobCommand.java new file mode 100644 index 000000000000..218504966dc4 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/DisplayJobCommand.java @@ -0,0 +1,65 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_JOBNAME; + +import seedu.address.logic.CommandHistory; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.job.Job; +import seedu.address.model.job.JobName; + + +/** + * Adds a person to the address book. + */ +public class DisplayJobCommand extends Command { + + public static final String COMMAND_WORD = "displayJob"; + public static final String COMMAND_ALIAS = "dj"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": displays a list from a job. " + + "Parameters: " + + PREFIX_JOBNAME + "JobName " + + "Example: " + COMMAND_WORD + " " + + PREFIX_JOBNAME + "Helper " + + "The alias \"dj\" can be used instead.\n" + + "Example: " + COMMAND_ALIAS + " " + + PREFIX_JOBNAME + "Helper "; + + public static final String MESSAGE_SUCCESS = "Displaying job"; + public static final String MESSAGE_MISSING_JOB = "This job does not exist"; + + private final JobName toAdd; + + /** + * Creates an AddCommand to add the specified {@code job} + */ + public DisplayJobCommand(JobName name) { + requireNonNull(name); + toAdd = name; + } + + @Override + public CommandResult execute(Model model, CommandHistory history) throws CommandException { + requireNonNull(model); + + Job tempJob = new Job(toAdd); + if (!model.hasJob(tempJob)) { + throw new CommandException(MESSAGE_MISSING_JOB); + } + + model.getJob(toAdd); + model.commitAddressBook(); + model.clearJobFilteredLists(); + model.setIsAllJobScreen(false); + return new CommandResult(String.format(MESSAGE_SUCCESS, toAdd), toAdd); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof DisplayJobCommand // instanceof handles nulls + && toAdd.equals(((DisplayJobCommand) other).toAdd)); + } +} diff --git a/src/main/java/seedu/address/logic/commands/EditCommand.java b/src/main/java/seedu/address/logic/commands/EditCommand.java index 952a9e7e7f2b..4ee48f249ea5 100644 --- a/src/main/java/seedu/address/logic/commands/EditCommand.java +++ b/src/main/java/seedu/address/logic/commands/EditCommand.java @@ -1,11 +1,21 @@ package seedu.address.logic.commands; import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_COMMAND_CANNOT_USE; import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; +import static seedu.address.logic.parser.CliSyntax.PREFIX_GENDER; +import static seedu.address.logic.parser.CliSyntax.PREFIX_GRADE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_INTERVIEWSCORES; +import static seedu.address.logic.parser.CliSyntax.PREFIX_JOBSAPPLY; +import static seedu.address.logic.parser.CliSyntax.PREFIX_KNOWNPROGLANG; +import static seedu.address.logic.parser.CliSyntax.PREFIX_MAJOR; import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NRIC; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PASTJOB; import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; -import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; +import static seedu.address.logic.parser.CliSyntax.PREFIX_RACE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_SCHOOL; import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; import java.util.Collections; @@ -22,9 +32,19 @@ import seedu.address.model.Model; import seedu.address.model.person.Address; import seedu.address.model.person.Email; +import seedu.address.model.person.Gender; +import seedu.address.model.person.Grade; +import seedu.address.model.person.InterviewScores; +import seedu.address.model.person.JobsApply; +import seedu.address.model.person.KnownProgLang; +import seedu.address.model.person.Major; import seedu.address.model.person.Name; +import seedu.address.model.person.Nric; +import seedu.address.model.person.PastJob; import seedu.address.model.person.Person; import seedu.address.model.person.Phone; +import seedu.address.model.person.Race; +import seedu.address.model.person.School; import seedu.address.model.tag.Tag; /** @@ -33,19 +53,42 @@ public class EditCommand extends Command { public static final String COMMAND_WORD = "edit"; + public static final String COMMAND_ALIAS = "ed"; public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits the details of the person identified " - + "by the index number used in the displayed person list. " - + "Existing values will be overwritten by the input values.\n" - + "Parameters: INDEX (must be a positive integer) " - + "[" + PREFIX_NAME + "NAME] " - + "[" + PREFIX_PHONE + "PHONE] " - + "[" + PREFIX_EMAIL + "EMAIL] " - + "[" + PREFIX_ADDRESS + "ADDRESS] " - + "[" + PREFIX_TAG + "TAG]...\n" - + "Example: " + COMMAND_WORD + " 1 " - + PREFIX_PHONE + "91234567 " - + PREFIX_EMAIL + "johndoe@example.com"; + + "by the index number used in the displayed person list. " + + "Existing values will be overwritten by the input values.\n" + + "Parameters: INDEX (must be a positive integer) " + + "[" + PREFIX_NAME + "NAME] " + + "[" + PREFIX_PHONE + "PHONE] " + + "[" + PREFIX_EMAIL + "EMAIL] " + + "[" + PREFIX_NRIC + "NRIC] " + + "[" + PREFIX_GENDER + "GENDER] " + + "[" + PREFIX_RACE + "RACE] " + + "[" + PREFIX_ADDRESS + "ADDRESS] " + + "[" + PREFIX_SCHOOL + "SCHOOL] " + + "[" + PREFIX_MAJOR + "MAJOR] " + + "[" + PREFIX_GRADE + "GRADE] " + + "[" + PREFIX_SCHOOL + "SCHOOL] " + + "[" + PREFIX_KNOWNPROGLANG + "KNOWNPROGLANG] " + + "[" + PREFIX_PASTJOB + "PASTJOB] " + + "[" + PREFIX_JOBSAPPLY + "JOBSAPPLY] " + + "[" + PREFIX_INTERVIEWSCORES + "INTERVIEWSCORES] " + + "Example: " + COMMAND_WORD + " 1 " + + PREFIX_PHONE + "91234567 " + + PREFIX_EMAIL + "johndoe@example.com" + + PREFIX_NRIC + "S9671597H " + + PREFIX_GENDER + "Male " + + PREFIX_RACE + "Indian " + + PREFIX_ADDRESS + "311, Clementi Ave 2, #02-25 " + + PREFIX_SCHOOL + "NUS " + + PREFIX_MAJOR + "Computer Science " + + PREFIX_GRADE + "4.76 " + + PREFIX_JOBSAPPLY + "Software Engineer " + + PREFIX_INTERVIEWSCORES + "5,8,2,4,10 " + + PREFIX_KNOWNPROGLANG + "Python " + + PREFIX_PASTJOB + "Software Engineer " + + "The alias \"ed\" can be used instead.\n"; public static final String MESSAGE_EDIT_PERSON_SUCCESS = "Edited Person: %1$s"; public static final String MESSAGE_NOT_EDITED = "At least one field to edit must be provided."; @@ -55,7 +98,7 @@ public class EditCommand extends Command { private final EditPersonDescriptor editPersonDescriptor; /** - * @param index of the person in the filtered person list to edit + * @param index of the person in the filtered person list to edit * @param editPersonDescriptor details to edit the person with */ public EditCommand(Index index, EditPersonDescriptor editPersonDescriptor) { @@ -69,10 +112,14 @@ public EditCommand(Index index, EditPersonDescriptor editPersonDescriptor) { @Override public CommandResult execute(Model model, CommandHistory history) throws CommandException { requireNonNull(model); + boolean isAllJobsScreen = model.getIsAllJobScreen(); + if (!isAllJobsScreen) { + throw new CommandException(MESSAGE_COMMAND_CANNOT_USE); + } List lastShownList = model.getFilteredPersonList(); if (index.getZeroBased() >= lastShownList.size()) { - throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + throw new CommandException(String.format(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX, MESSAGE_USAGE)); } Person personToEdit = lastShownList.get(index.getZeroBased()); @@ -83,7 +130,8 @@ public CommandResult execute(Model model, CommandHistory history) throws Command } model.setPerson(personToEdit, editedPerson); - model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + model.revertList(); + model.updateBaseFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); model.commitAddressBook(); return new CommandResult(String.format(MESSAGE_EDIT_PERSON_SUCCESS, editedPerson)); } @@ -98,10 +146,24 @@ private static Person createEditedPerson(Person personToEdit, EditPersonDescript Name updatedName = editPersonDescriptor.getName().orElse(personToEdit.getName()); Phone updatedPhone = editPersonDescriptor.getPhone().orElse(personToEdit.getPhone()); Email updatedEmail = editPersonDescriptor.getEmail().orElse(personToEdit.getEmail()); + Nric updatedNric = editPersonDescriptor.getNric().orElse(personToEdit.getNric()); + Gender updatedGender = editPersonDescriptor.getGender().orElse(personToEdit.getGender()); + Race updatedRace = editPersonDescriptor.getRace().orElse(personToEdit.getRace()); Address updatedAddress = editPersonDescriptor.getAddress().orElse(personToEdit.getAddress()); + School updatedSchool = editPersonDescriptor.getSchool().orElse(personToEdit.getSchool()); + Major updatedMajor = editPersonDescriptor.getMajor().orElse(personToEdit.getMajor()); + Grade updatedGrade = editPersonDescriptor.getGrade().orElse(personToEdit.getGrade()); + InterviewScores updatedInterviewScores = editPersonDescriptor.getInterviewScores() + .orElse(personToEdit.getInterviewScores()); + Set updatedKnownProgLangs = editPersonDescriptor.getKnownProgLangs() + .orElse(personToEdit.getKnownProgLangs()); + Set updatedPastJobs = editPersonDescriptor.getPastJobs().orElse(personToEdit.getPastJobs()); + Set updatedJobsApply = editPersonDescriptor.getJobsApply().orElse(personToEdit.getJobsApply()); Set updatedTags = editPersonDescriptor.getTags().orElse(personToEdit.getTags()); + return new Person(updatedName, updatedPhone, updatedEmail, updatedNric, updatedGender, updatedRace, + updatedAddress, updatedSchool, updatedMajor, updatedGrade, updatedKnownProgLangs, updatedPastJobs, + updatedJobsApply, updatedInterviewScores, updatedTags); - return new Person(updatedName, updatedPhone, updatedEmail, updatedAddress, updatedTags); } @Override @@ -119,7 +181,7 @@ public boolean equals(Object other) { // state check EditCommand e = (EditCommand) other; return index.equals(e.index) - && editPersonDescriptor.equals(e.editPersonDescriptor); + && editPersonDescriptor.equals(e.editPersonDescriptor); } /** @@ -130,20 +192,42 @@ public static class EditPersonDescriptor { private Name name; private Phone phone; private Email email; + private Nric nric; + private Gender gender; + private Race race; private Address address; + private School school; + private Major major; + private Grade grade; + private InterviewScores interviewScores; + private Set jobsApply; + private Set knownProgLangs; + private Set pastjobs; private Set tags; - public EditPersonDescriptor() {} + public EditPersonDescriptor() { + } /** * Copy constructor. + * * A defensive copy of {@code pastjobs} is used internally. * A defensive copy of {@code tags} is used internally. */ public EditPersonDescriptor(EditPersonDescriptor toCopy) { setName(toCopy.name); setPhone(toCopy.phone); setEmail(toCopy.email); + setNric(toCopy.nric); + setGender(toCopy.gender); + setRace(toCopy.race); setAddress(toCopy.address); + setSchool(toCopy.school); + setMajor(toCopy.major); + setGrade(toCopy.grade); + setKnownProgLangs(toCopy.knownProgLangs); + setPastJobs(toCopy.pastjobs); + setJobsApply(toCopy.jobsApply); + setInterviewScores(toCopy.interviewScores); setTags(toCopy.tags); } @@ -151,7 +235,8 @@ public EditPersonDescriptor(EditPersonDescriptor toCopy) { * Returns true if at least one field is edited. */ public boolean isAnyFieldEdited() { - return CollectionUtil.isAnyNonNull(name, phone, email, address, tags); + return CollectionUtil.isAnyNonNull(name, phone, email, nric, gender, race, address, school, + major, grade, knownProgLangs, pastjobs, jobsApply, interviewScores, tags); } public void setName(Name name) { @@ -178,6 +263,30 @@ public Optional getEmail() { return Optional.ofNullable(email); } + public void setNric(Nric nric) { + this.nric = nric; + } + + public Optional getNric() { + return Optional.ofNullable(nric); + } + + public void setGender(Gender gender) { + this.gender = gender; + } + + public Optional getGender() { + return Optional.ofNullable(gender); + } + + public void setRace(Race race) { + this.race = race; + } + + public Optional getRace() { + return Optional.ofNullable(race); + } + public void setAddress(Address address) { this.address = address; } @@ -186,6 +295,90 @@ public Optional
getAddress() { return Optional.ofNullable(address); } + public void setSchool(School school) { + this.school = school; + } + + public Optional getSchool() { + return Optional.ofNullable(school); + } + + public void setMajor(Major major) { + this.major = major; + } + + public Optional getMajor() { + return Optional.ofNullable(major); + } + + public void setGrade(Grade grade) { + this.grade = grade; + } + + public Optional getGrade() { + return Optional.ofNullable(grade); + } + + public void setInterviewScores(InterviewScores interviewScores) { + this.interviewScores = interviewScores; + } + + public Optional getInterviewScores() { + return Optional.ofNullable(interviewScores); + } + + /** + * Sets {@code knownProgLangs} to this object's {@code knownProgLangs}. + * A defensive copy of {@code knownProgLangs} is used internally. + */ + public void setKnownProgLangs(Set knownProgLangs) { + this.knownProgLangs = (knownProgLangs != null) ? new HashSet<>(knownProgLangs) : null; + } + + /** + * Returns an unmodifiable pastjob set, which throws {@code UnsupportedOperationException} + * if modification is attempted. + * Returns {@code Optional#empty()} if {@code pastjobs} is null. + */ + public Optional> getKnownProgLangs() { + return (knownProgLangs != null) ? Optional.of(Collections + .unmodifiableSet(knownProgLangs)) : Optional.empty(); + } + + /** + * Sets {@code pastjobs} to this object's {@code pastjobs}. + * A defensive copy of {@code pastjobs} is used internally. + */ + public void setPastJobs(Set pastjobs) { + this.pastjobs = (pastjobs != null) ? new HashSet<>(pastjobs) : null; + } + + /** + * Returns an unmodifiable pastjob set, which throws {@code UnsupportedOperationException} + * if modification is attempted. + * Returns {@code Optional#empty()} if {@code pastjobs} is null. + */ + public Optional> getPastJobs() { + return (pastjobs != null) ? Optional.of(Collections.unmodifiableSet(pastjobs)) : Optional.empty(); + } + + /** + * Sets {@code jobsApply} to this object's {@code jobsApply}. + * A defensive copy of {@code jobsApply} is used internally. + */ + public void setJobsApply(Set jobsApply) { + this.jobsApply = (jobsApply != null) ? new HashSet<>(jobsApply) : null; + } + + /** + * Returns an unmodifiable jobsApply set, which throws {@code UnsupportedOperationException} + * if modification is attempted. + * Returns {@code Optional#empty()} if {@code jobsApply} is null. + */ + public Optional> getJobsApply() { + return (jobsApply != null) ? Optional.of(Collections.unmodifiableSet(jobsApply)) : Optional.empty(); + } + /** * Sets {@code tags} to this object's {@code tags}. * A defensive copy of {@code tags} is used internally. @@ -219,10 +412,19 @@ public boolean equals(Object other) { EditPersonDescriptor e = (EditPersonDescriptor) other; return getName().equals(e.getName()) - && getPhone().equals(e.getPhone()) - && getEmail().equals(e.getEmail()) - && getAddress().equals(e.getAddress()) - && getTags().equals(e.getTags()); + && getPhone().equals(e.getPhone()) + && getEmail().equals(e.getEmail()) + && getNric().equals(e.getNric()) + && getGender().equals(e.getGender()) + && getRace().equals(e.getRace()) + && getAddress().equals(e.getAddress()) + && getSchool().equals(e.getSchool()) + && getMajor().equals(e.getMajor()) + && getGrade().equals(e.getGrade()) + && getKnownProgLangs().equals(e.getKnownProgLangs()) + && getPastJobs().equals(e.getPastJobs()) + && getJobsApply().equals(e.getJobsApply()) + && getInterviewScores().equals(e.getInterviewScores()); } } } diff --git a/src/main/java/seedu/address/logic/commands/ExitCommand.java b/src/main/java/seedu/address/logic/commands/ExitCommand.java index 2240a3e4be1f..4c589c778979 100644 --- a/src/main/java/seedu/address/logic/commands/ExitCommand.java +++ b/src/main/java/seedu/address/logic/commands/ExitCommand.java @@ -9,6 +9,7 @@ public class ExitCommand extends Command { public static final String COMMAND_WORD = "exit"; + public static final String COMMAND_ALIAS = "ex"; public static final String MESSAGE_EXIT_ACKNOWLEDGEMENT = "Exiting Address Book as requested ..."; 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 000000000000..dd6737561ff8 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/FilterCommand.java @@ -0,0 +1,651 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; +import static seedu.address.logic.parser.CliSyntax.PREFIX_FILTERNAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_GENDER; +import static seedu.address.logic.parser.CliSyntax.PREFIX_JOBSAPPLY; +import static seedu.address.logic.parser.CliSyntax.PREFIX_KNOWNPROGLANG; +import static seedu.address.logic.parser.CliSyntax.PREFIX_MAJOR; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NRIC; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PASTJOB; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_RACE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_SCHOOL; +import static seedu.address.model.job.JobListName.EMPTY; + +import java.util.ArrayList; +import java.util.Optional; +import java.util.Set; +import java.util.function.Predicate; + +import seedu.address.commons.core.Messages; +import seedu.address.logic.CommandHistory; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.job.JobListName; +import seedu.address.model.person.Person; +import seedu.address.model.person.exceptions.DuplicateFilterException; +import seedu.address.model.person.predicate.AddressContainsKeywordsPredicate; +import seedu.address.model.person.predicate.EmailContainsKeywordsPredicate; +import seedu.address.model.person.predicate.GenderContainsKeywordsPredicate; +import seedu.address.model.person.predicate.GradeContainsKeywordsPredicate; +import seedu.address.model.person.predicate.InterviewScoreContainsKeywordsPredicate; +import seedu.address.model.person.predicate.JobsApplyContainsKeywordsPredicate; +import seedu.address.model.person.predicate.KnownProgLangContainsKeywordsPredicate; +import seedu.address.model.person.predicate.MajorContainsKeywordsPredicate; +import seedu.address.model.person.predicate.NameContainsKeywordsPredicate; +import seedu.address.model.person.predicate.NricContainsKeywordsPredicate; +import seedu.address.model.person.predicate.PastJobContainsKeywordsPredicate; +import seedu.address.model.person.predicate.PhoneContainsKeywordsPredicate; +import seedu.address.model.person.predicate.PredicateManager; +import seedu.address.model.person.predicate.RaceContainsKeywordsPredicate; +import seedu.address.model.person.predicate.SchoolContainsKeywordsPredicate; +import seedu.address.model.person.predicate.UniqueFilterList; + + +/** + * Searches and lists all persons in address book whose information contains any of the argument keywords. + * Keyword matching is case insensitive. + */ +public class FilterCommand extends Command { + + public static final String COMMAND_WORD = "filter"; + public static final String COMMAND_ALIAS = "f"; + public static final String MESSAGE_USAGE_PARAMETERS = + "[" + PREFIX_FILTERNAME + "FILTERNAME] " + + "[" + PREFIX_NAME + "NAME KEYWORD] " + + "[" + PREFIX_PHONE + "PHONE KEYWORD] " + + "[" + PREFIX_EMAIL + "EMAIL KEYWORD] " + + "[" + PREFIX_NRIC + "NRIC KEYWORD] " + + "[" + PREFIX_GENDER + "GENDER KEYWORD] " + + "[" + PREFIX_RACE + "RACE KEYWORD] " + + "[" + PREFIX_ADDRESS + "ADDRESS KEYWORD] " + + "[" + PREFIX_SCHOOL + "SCHOOL KEYWORD] " + + "[" + PREFIX_MAJOR + "MAJOR KEYWORD] " + + "[" + PREFIX_SCHOOL + "SCHOOL KEYWORD] " + + "[" + PREFIX_KNOWNPROGLANG + "KNOWNPROGLANG KEYWORD] " + + "[" + PREFIX_PASTJOB + "PASTJOB KEYWORD] " + + "[" + PREFIX_JOBSAPPLY + "JOBSAPPLY KEYWORD] " + + "Example: " + COMMAND_WORD + + PREFIX_PHONE + "91234567 " + + PREFIX_EMAIL + "johndoe@example.com " + + PREFIX_NRIC + "S9671597H " + + PREFIX_GENDER + "Male " + + PREFIX_RACE + "Indian " + + PREFIX_ADDRESS + "311, Clementi Ave 2, #02-25 " + + PREFIX_SCHOOL + "NUS " + + PREFIX_MAJOR + "Computer Science " + + "The alias \"sh \" can be used instead.\n" + + "Example: " + COMMAND_ALIAS + + PREFIX_PHONE + "91234567 " + + PREFIX_EMAIL + "johndoe@example.com " + + PREFIX_NRIC + "S9671597H " + + PREFIX_GENDER + "Male " + + PREFIX_RACE + "Indian " + + PREFIX_ADDRESS + "311, Clementi Ave 2, #02-25 " + + PREFIX_SCHOOL + "NUS " + + PREFIX_MAJOR + "Computer Science " + + PREFIX_JOBSAPPLY + "Software Engineer \n"; + + public static final String MESSAGE_USAGE_ALLJOB_SCREEN = COMMAND_WORD + ": (In All Jobs shows mode)\n" + + "Filter all persons whose informations contain any of the specified keywords (case-insensitive) " + + "and displays them as a list with index numbers.\n" + + "Parameters: FILTERNAME [KEYWORDS]...\n" + MESSAGE_USAGE_PARAMETERS; + + + public static final String MESSAGE_USAGE_JOB_DETAIL_SCREEN = COMMAND_WORD + ": (In Job Detail mode)\n" + + "Filter indicated job list whose informations contain any of the specified keywords (case-insensitive) " + + "and displays them as a list with index numbers.\n" + + "Parameters: LISTNAME FILTERNAME [KEYWORDS]...\n" + + "LISTNAME " + MESSAGE_USAGE_PARAMETERS; + + public static final String MESSAGE_LACK_FILTERNAME = "Filter Command need a name\n%1$s"; + public static final String MESSAGE_LACK_LISTNAME = + "Filter Command in Display Job page need indicate job list\n%1$s"; + public static final String MESSAGE_REDUNDANT_LISTNAME = + "Filter Command in All Jobs page no need indicate job list\n%1$s"; + public static final String MESSAGE_INVALID_RANGE = + "Not a valid range, the right format should be value-value;value-value..." + "\n" + + "For example: 1.2-1.3; 1.3-1.4"; + public static final String MESSAGE_REDUNDANT_FILTERNAME = "Filter name has already been used." + "\n" + + "Filter Command need a unique name"; + private final Predicate predicate; + private final PredicatePersonDescriptor predicatePersonDescriptor; + private final JobListName listName; + private final String commandName; + + /** + * @param commandName command name + * @param listName which job list to predicate the person with + * @param predicatePersonDescriptor details to predicate the person with + */ + public FilterCommand(String commandName, JobListName listName, + PredicatePersonDescriptor predicatePersonDescriptor) { + requireNonNull(commandName); + requireNonNull(listName); + requireNonNull(predicatePersonDescriptor); + this.predicatePersonDescriptor = new PredicatePersonDescriptor(predicatePersonDescriptor); + this.predicate = this.predicatePersonDescriptor.toPredicate(); + this.listName = listName; + this.commandName = commandName; + } + + @Override + public CommandResult execute(Model model, CommandHistory history) throws CommandException { + requireNonNull(model); + UniqueFilterList predicateList; + boolean isAllJobScreen = model.getIsAllJobScreen(); + boolean hasListName = listName != EMPTY; + checkException(isAllJobScreen, hasListName); + try { + model.addPredicate(commandName, predicate, listName); + } catch (DuplicateFilterException ex) { + throw new CommandException(MESSAGE_REDUNDANT_FILTERNAME); + } + model.updateFilteredPersonLists(listName); + predicateList = model.getPredicateLists(listName); + int size = model.getJobsList(listName).size(); + return new CommandResult( + String.format(Messages.MESSAGE_PERSONS_LISTED_OVERVIEW, size), listName, + predicateList); + } + + /** + * @param isAllJobScreen Indicate the current screen, true if screen on all jobs screen + * @param hasListName Indicate whether command parser parse the List name + * @throws CommandException throw exception and catch by function excute() + */ + private void checkException(boolean isAllJobScreen, boolean hasListName) + throws CommandException { + String showMessage = isAllJobScreen ? MESSAGE_USAGE_ALLJOB_SCREEN : MESSAGE_USAGE_JOB_DETAIL_SCREEN; + if (!isAllJobScreen && !hasListName) { + throw new CommandException(String.format(MESSAGE_LACK_LISTNAME, showMessage)); + } else if (isAllJobScreen && hasListName) { + throw new CommandException(String.format(MESSAGE_REDUNDANT_LISTNAME, showMessage)); + } + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof FilterCommand)) { + return false; + } + + // state check + FilterCommand e = (FilterCommand) other; + return predicatePersonDescriptor.equals(e.predicatePersonDescriptor); + } + + /** + * Stores the details to edit the person with. Each non-empty field value will replace the + * corresponding field value of the person. + */ + public static class PredicatePersonDescriptor { + private Set name; + private Set phone; + private Set email; + private Set nric; + private Set gender; + private Set race; + private Set interviewScoreQ1; + private Set interviewScoreQ2; + private Set interviewScoreQ3; + private Set interviewScoreQ4; + private Set interviewScoreQ5; + private Set grade; + private Set address; + private Set school; + private Set major; + private Set jobsApply; + private Set knownProgLangs; + private Set pastJobs; + + public PredicatePersonDescriptor() { + } + + /** + * Copy constructor. + * A defensive copy + */ + public PredicatePersonDescriptor(PredicatePersonDescriptor toCopy) { + setName(toCopy.name); + setPhone(toCopy.phone); + setEmail(toCopy.email); + setNric(toCopy.nric); + setGender(toCopy.gender); + setRace(toCopy.race); + setInterviewScoreQ1(toCopy.interviewScoreQ1); + setInterviewScoreQ2(toCopy.interviewScoreQ2); + setInterviewScoreQ3(toCopy.interviewScoreQ3); + setInterviewScoreQ4(toCopy.interviewScoreQ4); + setInterviewScoreQ5(toCopy.interviewScoreQ5); + setGrade(toCopy.grade); + setAddress(toCopy.address); + setSchool(toCopy.school); + setMajor(toCopy.major); + setKnownProgLangs(toCopy.knownProgLangs); + setPastJobs(toCopy.pastJobs); + setJobsApply(toCopy.jobsApply); + } + + /** + * Translate and returns a Predicate object for name + */ + private Predicate nameToPredicate() { + Predicate predicator = new PredicateManager(); + if (this.getName().isPresent()) { + predicator = predicator.and(new NameContainsKeywordsPredicate( + new ArrayList<>(this.getName().get()))); + } + return predicator; + } + + /** + * Translate and returns a Predicate object for phone + */ + private Predicate phoneToPredicate() { + Predicate predicator = new PredicateManager(); + if (this.getPhone().isPresent()) { + predicator = predicator.and(new PhoneContainsKeywordsPredicate( + new ArrayList<>(this.getPhone().get()))); + } + return predicator; + } + + /** + * Translate and returns a Predicate object for email + */ + private Predicate emailToPredicate() { + Predicate predicator = new PredicateManager(); + if (this.getEmail().isPresent()) { + predicator = predicator.and(new EmailContainsKeywordsPredicate( + new ArrayList<>(this.getEmail().get()))); + } + return predicator; + } + + /** + * Translate and returns a Predicate object for race + */ + private Predicate raceToPredicate() { + Predicate predicator = new PredicateManager(); + if (this.getRace().isPresent()) { + predicator = predicator.and(new RaceContainsKeywordsPredicate( + new ArrayList<>(this.getRace().get()))); + } + return predicator; + } + + /** + * Translate and returns a Predicate object for address + */ + private Predicate addressToPredicate() { + Predicate predicator = new PredicateManager(); + if (this.getAddress().isPresent()) { + predicator = predicator.and(new AddressContainsKeywordsPredicate( + new ArrayList<>(this.getAddress().get()))); + } + return predicator; + } + + /** + * Translate and returns a Predicate object for school + */ + private Predicate schoolToPredicate() { + Predicate predicator = new PredicateManager(); + if (this.getSchool().isPresent()) { + predicator = predicator.and(new SchoolContainsKeywordsPredicate( + new ArrayList<>(this.getSchool().get()))); + } + return predicator; + } + + /** + * Translate and returns a Predicate object for major + */ + private Predicate majorToPredicate() { + Predicate predicator = new PredicateManager(); + if (this.getMajor().isPresent()) { + predicator = predicator.and(new MajorContainsKeywordsPredicate( + new ArrayList<>(this.getMajor().get()))); + } + return predicator; + } + + /** + * Translate and returns a Predicate object for gender + */ + private Predicate genderToPredicate() { + Predicate predicator = new PredicateManager(); + if (this.getGender().isPresent()) { + predicator = predicator.and(new GenderContainsKeywordsPredicate( + new ArrayList<>(this.getGender().get()))); + } + return predicator; + } + + /** + * Translate and returns a Predicate object for grade + */ + private Predicate gradeToPredicate() { + Predicate predicator = new PredicateManager(); + if (this.getGrade().isPresent()) { + predicator = predicator.and(new GradeContainsKeywordsPredicate( + new ArrayList<>(this.getGrade().get()))); + } + return predicator; + } + + /** + * Translate and returns a Predicate object for iq1 + */ + private Predicate interviewQ1ToPredicate() { + Predicate predicator = new PredicateManager(); + if (this.getInterviewScoreQ1().isPresent()) { + predicator = predicator.and(new InterviewScoreContainsKeywordsPredicate(1, + new ArrayList<>(this.getInterviewScoreQ1().get()))); + } + return predicator; + } + + /** + * Translate and returns a Predicate object for iq2 + */ + private Predicate interviewQ2ToPredicate() { + Predicate predicator = new PredicateManager(); + if (this.getInterviewScoreQ2().isPresent()) { + predicator = predicator.and(new InterviewScoreContainsKeywordsPredicate(2, + new ArrayList<>(this.getInterviewScoreQ2().get()))); + } + return predicator; + } + + /** + * Translate and returns a Predicate object for iq3 + */ + private Predicate interviewQ3ToPredicate() { + Predicate predicator = new PredicateManager(); + if (this.getInterviewScoreQ3().isPresent()) { + predicator = predicator.and(new InterviewScoreContainsKeywordsPredicate(3, + new ArrayList<>(this.getInterviewScoreQ3().get()))); + } + return predicator; + } + + /** + * Translate and returns a Predicate object for iq4 + */ + private Predicate interviewQ4ToPredicate() { + Predicate predicator = new PredicateManager(); + if (this.getInterviewScoreQ4().isPresent()) { + predicator = predicator.and(new InterviewScoreContainsKeywordsPredicate(4, + new ArrayList<>(this.getInterviewScoreQ4().get()))); + } + return predicator; + } + + /** + * Translate and returns a Predicate object for iq5 + */ + private Predicate interviewQ5ToPredicate() { + Predicate predicator = new PredicateManager(); + if (this.getInterviewScoreQ5().isPresent()) { + predicator = predicator.and(new InterviewScoreContainsKeywordsPredicate(5, + new ArrayList<>(this.getInterviewScoreQ5().get()))); + } + return predicator; + } + + /** + * Translate and returns a Predicate object for nric + */ + private Predicate nricToPredicate() { + Predicate predicator = new PredicateManager(); + if (this.getNric().isPresent()) { + predicator = predicator.and(new NricContainsKeywordsPredicate( + new ArrayList<>(this.getNric().get()))); + } + return predicator; + } + + /** + * Translate and returns a Predicate object for pastJob + */ + private Predicate pastJobToPredicate() { + Predicate predicator = new PredicateManager(); + if (this.getPastJobs().isPresent()) { + predicator = predicator.and(new PastJobContainsKeywordsPredicate( + new ArrayList<>(this.getPastJobs().get()))); + } + return predicator; + } + + /** + * Translate and returns a Predicate object for jobsApply + */ + private Predicate jobsApplyToPredicate() { + Predicate predicator = new PredicateManager(); + if (this.getJobsApply().isPresent()) { + predicator = predicator.and(new JobsApplyContainsKeywordsPredicate( + new ArrayList<>(this.getJobsApply().get()))); + } + return predicator; + } + + /** + * Translate and returns a Predicate object for knownProgLang + */ + private Predicate knownProgLangToPredicate() { + Predicate predicator = new PredicateManager(); + if (this.getKnownProgLangs().isPresent()) { + predicator = predicator.and(new KnownProgLangContainsKeywordsPredicate( + new ArrayList<>(this.getKnownProgLangs().get()))); + } + return predicator; + } + + /** + * Translate and returns a Predicate object for search command + */ + public Predicate toPredicate() { + Predicate predicator = new PredicateManager(); + predicator = predicator.and(nameToPredicate().and(phoneToPredicate() + .and(emailToPredicate().and(raceToPredicate().and(addressToPredicate() + .and(majorToPredicate().and(genderToPredicate().and(schoolToPredicate() + .and(gradeToPredicate().and(interviewQ1ToPredicate().and(interviewQ2ToPredicate() + .and(interviewQ3ToPredicate().and(interviewQ4ToPredicate().and(interviewQ5ToPredicate() + .and(nricToPredicate().and(pastJobToPredicate().and(jobsApplyToPredicate() + .and(knownProgLangToPredicate())))))))))))))))))); + return predicator; + } + + public void setName(Set name) { + this.name = name; + } + + public Optional> getName() { + return Optional.ofNullable(name); + } + + public void setPhone(Set phone) { + this.phone = phone; + } + + public Optional> getPhone() { + return Optional.ofNullable(phone); + } + + public void setEmail(Set email) { + this.email = email; + } + + public Optional> getEmail() { + return Optional.ofNullable(email); + } + + public void setNric(Set nric) { + this.nric = nric; + } + + public Optional> getNric() { + return Optional.ofNullable(nric); + } + + public void setGender(Set gender) { + this.gender = gender; + } + + public Optional> getGender() { + return Optional.ofNullable(gender); + } + + public void setRace(Set race) { + this.race = race; + } + + public Optional> getRace() { + return Optional.ofNullable(race); + } + + public void setGrade(Set grade) { + this.grade = grade; + } + + public Optional> getGrade() { + return Optional.ofNullable(grade); + } + + public void setInterviewScoreQ1(Set interviewScoreQ1) { + this.interviewScoreQ1 = interviewScoreQ1; + } + + public Optional> getInterviewScoreQ1() { + return Optional.ofNullable(interviewScoreQ1); + } + + public void setInterviewScoreQ2(Set interviewScoreQ2) { + this.interviewScoreQ2 = interviewScoreQ2; + } + + public Optional> getInterviewScoreQ2() { + return Optional.ofNullable(interviewScoreQ2); + } + + public void setInterviewScoreQ3(Set interviewScoreQ3) { + this.interviewScoreQ3 = interviewScoreQ3; + } + + public Optional> getInterviewScoreQ3() { + return Optional.ofNullable(interviewScoreQ3); + } + + public void setInterviewScoreQ4(Set interviewScoreQ4) { + this.interviewScoreQ4 = interviewScoreQ4; + } + + public Optional> getInterviewScoreQ4() { + return Optional.ofNullable(interviewScoreQ4); + } + + public void setInterviewScoreQ5(Set interviewScoreQ5) { + this.interviewScoreQ5 = interviewScoreQ5; + } + + public Optional> getInterviewScoreQ5() { + return Optional.ofNullable(interviewScoreQ5); + } + + public void setAddress(Set address) { + this.address = address; + } + + public Optional> getAddress() { + return Optional.ofNullable(address); + } + + public void setSchool(Set school) { + this.school = school; + } + + public Optional> getSchool() { + return Optional.ofNullable(school); + } + + public void setMajor(Set major) { + this.major = major; + } + + public Optional> getMajor() { + return Optional.ofNullable(major); + } + + public void setKnownProgLangs(Set knownProgLangs) { + this.knownProgLangs = knownProgLangs; + } + + public Optional> getKnownProgLangs() { + return Optional.ofNullable(knownProgLangs); + } + + public void setPastJobs(Set pastJobs) { + this.pastJobs = pastJobs; + } + + public Optional> getPastJobs() { + return Optional.ofNullable(pastJobs); + } + + public void setJobsApply(Set jobsApply) { + this.jobsApply = jobsApply; + } + + public Optional> getJobsApply() { + return Optional.ofNullable(jobsApply); + } + + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof PredicatePersonDescriptor)) { + return false; + } + + // state check + PredicatePersonDescriptor e = (PredicatePersonDescriptor) other; + + return getName().equals(e.getName()) + && getPhone().equals(e.getPhone()) + && getEmail().equals(e.getEmail()) + && getNric().equals(e.getNric()) + && getGender().equals(e.getGender()) + && getRace().equals(e.getRace()) + && getGrade().equals(e.getGrade()) + && getInterviewScoreQ1().equals(e.getInterviewScoreQ1()) + && getInterviewScoreQ2().equals(e.getInterviewScoreQ2()) + && getInterviewScoreQ3().equals(e.getInterviewScoreQ3()) + && getInterviewScoreQ4().equals(e.getInterviewScoreQ4()) + && getInterviewScoreQ5().equals(e.getInterviewScoreQ5()) + && getAddress().equals(e.getAddress()) + && getSchool().equals(e.getSchool()) + && getMajor().equals(e.getMajor()) + && getKnownProgLangs().equals(e.getKnownProgLangs()) + && getPastJobs().equals(e.getPastJobs()) + && getJobsApply().equals(e.getJobsApply()); + } + } +} diff --git a/src/main/java/seedu/address/logic/commands/FindCommand.java b/src/main/java/seedu/address/logic/commands/FindCommand.java deleted file mode 100644 index beb178e3a3f5..000000000000 --- a/src/main/java/seedu/address/logic/commands/FindCommand.java +++ /dev/null @@ -1,43 +0,0 @@ -package seedu.address.logic.commands; - -import static java.util.Objects.requireNonNull; - -import seedu.address.commons.core.Messages; -import seedu.address.logic.CommandHistory; -import seedu.address.model.Model; -import seedu.address.model.person.NameContainsKeywordsPredicate; - -/** - * Finds and lists all persons in address book whose name contains any of the argument keywords. - * Keyword matching is case insensitive. - */ -public class FindCommand extends Command { - - public static final String COMMAND_WORD = "find"; - - public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all persons whose names contain any of " - + "the specified keywords (case-insensitive) and displays them as a list with index numbers.\n" - + "Parameters: KEYWORD [MORE_KEYWORDS]...\n" - + "Example: " + COMMAND_WORD + " alice bob charlie"; - - private final NameContainsKeywordsPredicate predicate; - - public FindCommand(NameContainsKeywordsPredicate predicate) { - this.predicate = predicate; - } - - @Override - public CommandResult execute(Model model, CommandHistory history) { - requireNonNull(model); - model.updateFilteredPersonList(predicate); - return new CommandResult( - String.format(Messages.MESSAGE_PERSONS_LISTED_OVERVIEW, model.getFilteredPersonList().size())); - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof FindCommand // instanceof handles nulls - && predicate.equals(((FindCommand) other).predicate)); // state check - } -} diff --git a/src/main/java/seedu/address/logic/commands/GenerateAnalyticsCommand.java b/src/main/java/seedu/address/logic/commands/GenerateAnalyticsCommand.java new file mode 100644 index 000000000000..4880ba2a3f01 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/GenerateAnalyticsCommand.java @@ -0,0 +1,66 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.model.job.JobListName.APPLICANT; +import static seedu.address.model.job.JobListName.INTERVIEW; +import static seedu.address.model.job.JobListName.KIV; +import static seedu.address.model.job.JobListName.SHORTLIST; +import static seedu.address.model.job.JobListName.STUB; + +import seedu.address.logic.CommandHistory; +import seedu.address.model.Model; +import seedu.address.model.analytics.Analytics; +import seedu.address.model.job.JobListName; + +/** + * Generates analytics report based on selected list of persons + */ +public class GenerateAnalyticsCommand extends Command { + + public static final String COMMAND_WORD = "analytics"; + public static final String MESSAGE_SUCCESS = "Analytics generated!"; + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Generates analytics of desired list. " + + "Parameters: list name (If no list name provided, analytics of all applicants will be shown)" + + "\n" + "Possible lists are: applicant, kiv, interview, shortlist" + + "\n" + "Example: " + COMMAND_WORD + " " + "kiv"; + + private final JobListName listName; + + public GenerateAnalyticsCommand(JobListName listName) { + requireNonNull(listName); + this.listName = listName; + } + + public GenerateAnalyticsCommand() { + this.listName = STUB; + } + + @Override + public CommandResult execute(Model model, CommandHistory history) { + requireNonNull(model); + Analytics analytics; + switch (listName) { + + case APPLICANT: + analytics = model.generateAnalytics(APPLICANT); + break; + case KIV: + analytics = model.generateAnalytics(KIV); + break; + case INTERVIEW: + analytics = model.generateAnalytics(INTERVIEW); + break; + case SHORTLIST: + analytics = model.generateAnalytics(SHORTLIST); + break; + case STUB: + analytics = model.generateAnalytics(); + break; + default: + analytics = model.generateAnalytics(); + } + return new CommandResult(MESSAGE_SUCCESS, analytics); + } + +} diff --git a/src/main/java/seedu/address/logic/commands/GenerateInterviewsCommand.java b/src/main/java/seedu/address/logic/commands/GenerateInterviewsCommand.java new file mode 100644 index 000000000000..3f31fd23874f --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/GenerateInterviewsCommand.java @@ -0,0 +1,30 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import seedu.address.logic.CommandHistory; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.interviews.exceptions.InterviewsPresentException; + +/** + * Generates an interview date list from existing persons in the addressbook. + */ +public class GenerateInterviewsCommand extends Command { + + public static final String COMMAND_WORD = "generateInterviews"; + public static final String MESSAGE_SUCCESS = "Interviews generated"; + public static final String MESSAGE_PRESENT = "Interviews already present"; + + @Override + public CommandResult execute(Model model, CommandHistory history) throws CommandException { + requireNonNull(model); + try { + model.generateInterviews(); + model.commitAddressBook(); + } catch (InterviewsPresentException e) { + throw new CommandException(MESSAGE_PRESENT); + } + return new CommandResult(MESSAGE_SUCCESS); + } +} diff --git a/src/main/java/seedu/address/logic/commands/HelpCommand.java b/src/main/java/seedu/address/logic/commands/HelpCommand.java index f0ef78dddded..5bda636fca0f 100644 --- a/src/main/java/seedu/address/logic/commands/HelpCommand.java +++ b/src/main/java/seedu/address/logic/commands/HelpCommand.java @@ -9,6 +9,7 @@ public class HelpCommand extends Command { public static final String COMMAND_WORD = "help"; + public static final String COMMAND_ALIAS = "he"; public static final String MESSAGE_USAGE = COMMAND_WORD + ": Shows program usage instructions.\n" + "Example: " + COMMAND_WORD; diff --git a/src/main/java/seedu/address/logic/commands/HistoryCommand.java b/src/main/java/seedu/address/logic/commands/HistoryCommand.java index dc3de1aad55e..004f0fbf47dd 100644 --- a/src/main/java/seedu/address/logic/commands/HistoryCommand.java +++ b/src/main/java/seedu/address/logic/commands/HistoryCommand.java @@ -14,6 +14,7 @@ public class HistoryCommand extends Command { public static final String COMMAND_WORD = "history"; + public static final String COMMAND_ALIAS = "hi"; public static final String MESSAGE_SUCCESS = "Entered commands (from most recent to earliest):\n%1$s"; public static final String MESSAGE_NO_HISTORY = "You have not yet entered any commands."; diff --git a/src/main/java/seedu/address/logic/commands/ImportResumesCommand.java b/src/main/java/seedu/address/logic/commands/ImportResumesCommand.java new file mode 100644 index 000000000000..6cb5ee6344a0 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/ImportResumesCommand.java @@ -0,0 +1,58 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_COMMAND_CANNOT_USE; + +import java.util.Iterator; +import java.util.Set; + +import seedu.address.logic.CommandHistory; +import seedu.address.logic.commands.exceptions.CommandException; + +import seedu.address.model.Model; +import seedu.address.model.person.Person; + +/** + * Imports resume txt files from a given directory into slavefinder(). + */ +public class ImportResumesCommand extends Command { + + public static final String COMMAND_WORD = "importResumes"; + public static final String COMMAND_ALIAS = "ir"; + public static final String MESSAGE_SUCCESS = "Resumes have been imported"; + public static final String MESSAGE_DUPLICATE_PERSON = "There is a person who already exists in the address book"; + + private Set toAdd; + + public ImportResumesCommand(Set people) { + toAdd = people; + } + + @Override + public CommandResult execute(Model model, CommandHistory history) throws CommandException { + requireNonNull(model); + boolean isAllJobsScreen = model.getIsAllJobScreen(); + if (!isAllJobsScreen) { + throw new CommandException(MESSAGE_COMMAND_CANNOT_USE); + } + Iterator setIterator = toAdd.iterator(); + + while (setIterator.hasNext()) { + Person currentPerson = setIterator.next(); + if (model.hasPerson(currentPerson)) { + throw new CommandException(MESSAGE_DUPLICATE_PERSON); + } + model.addPerson(currentPerson); + } + + model.commitAddressBook(); + return new CommandResult(String.format(MESSAGE_SUCCESS, toAdd)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof ImportResumesCommand // instanceof handles nulls + && toAdd.equals(((ImportResumesCommand) other).toAdd)); + } +} diff --git a/src/main/java/seedu/address/logic/commands/ListCommand.java b/src/main/java/seedu/address/logic/commands/ListCommand.java index 6d44824c7d1b..62395d5c0a42 100644 --- a/src/main/java/seedu/address/logic/commands/ListCommand.java +++ b/src/main/java/seedu/address/logic/commands/ListCommand.java @@ -1,7 +1,7 @@ package seedu.address.logic.commands; import static java.util.Objects.requireNonNull; -import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; +import static seedu.address.model.job.JobListName.EMPTY; import seedu.address.logic.CommandHistory; import seedu.address.model.Model; @@ -12,14 +12,18 @@ public class ListCommand extends Command { public static final String COMMAND_WORD = "list"; + public static final String COMMAND_ALIAS = "l"; - public static final String MESSAGE_SUCCESS = "Listed all persons"; + public static final String MESSAGE_SUCCESS = "Listed all persons and jobs"; @Override public CommandResult execute(Model model, CommandHistory history) { requireNonNull(model); - model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + model.revertList(); + model.clearJobFilteredLists(); + model.setIsAllJobScreen(true); + model.updateFilteredPersonLists(EMPTY); return new CommandResult(MESSAGE_SUCCESS); } } diff --git a/src/main/java/seedu/address/logic/commands/MovePeopleCommand.java b/src/main/java/seedu/address/logic/commands/MovePeopleCommand.java new file mode 100644 index 000000000000..63612c3d0c5d --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/MovePeopleCommand.java @@ -0,0 +1,138 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_JOBNAME; + +import java.util.ArrayList; +import java.util.List; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.CommandHistory; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.job.Job; +import seedu.address.model.job.JobListName; +import seedu.address.model.job.JobName; +import seedu.address.model.person.Person; + + +/** + * Adds a person to the address book. + */ +public class MovePeopleCommand extends Command { + + public static final String COMMAND_WORD = "movePeople"; + public static final String COMMAND_ALIAS = "mp"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": copies person to dest list from source list. " + + "Displayed job is used unless optional JobName is provided. \n" + + "Parameters: " + + "DESTINATION_LIST_NAME " + + "SOURCE_LIST_NAME " + + "APPLICANT_INDEXES " + + PREFIX_JOBNAME + "JobName (OPTIONAL)\n" + + "Example: " + COMMAND_WORD + " " + + "applicant " + + "kiv " + + "1, 2, 3 " + + PREFIX_JOBNAME + "King-Of-The-World \n" + + "The alias \"mp\" can be used instead.\n" + + "Example: " + COMMAND_ALIAS + " " + + "applicant " + + "kiv " + + "1, 2, 3 " + + PREFIX_JOBNAME + "High-On-Drugs "; + + public static final String MESSAGE_SUCCESS = "%1$s people added to job after removing duplicate people"; + public static final String MESSAGE_NO_DISPLAYED_JOB = "No job is displayed. " + + "Please enter a jobName with jn/ prefixed\n" + + "Source list can only be provided if there is a job displayed"; + public static final String MESSAGE_DISPLAYING_JOB_ERROR = "Displaying Job. Cannot have jn/ input\n" + + "and SOURCE cannot be empty"; + public static final String MESSAGE_NO_DESTINATION = "Please provide a destination list\n"; + public static final String MESSAGE_NO_SOURCE = "Please provide a source list\n"; + public static final String MESSAGE_NO_INDEX = "Please provide some indexes to move\n"; + public static final String MESSAGE_BAD_INDEX = "One of the indexes is bad\n"; + public static final String MESSAGE_JOB_NOT_FOUND = "Given job does not exist in database\n"; + public static final String MESSAGE_IDENTICAL_LISTS = "Destination and source lists cannot be the same\n"; + + private final JobListName to; + private final JobListName from; + private final ArrayList indexes; + private final JobName toAdd; + private Integer numberAdded; + + /** + * Creates an AddCommand to add the specified {@code job} + */ + public MovePeopleCommand(JobListName to, JobListName from, ArrayList indexes, JobName jobName) { + requireNonNull(to); + requireNonNull(indexes); + this.to = to; + this.from = from; + this.indexes = indexes; + this.toAdd = jobName; + this.numberAdded = indexes.size(); + } + + @Override + public CommandResult execute(Model model, CommandHistory history) throws CommandException { + requireNonNull(model); + + Job tempJob; + + if (model.getIsAllJobScreen()) { + if (toAdd == null || !from.equals(JobListName.STUB)) { + throw new CommandException(MESSAGE_NO_DISPLAYED_JOB); + } + try { + model.getJob(toAdd); + } catch (Exception e) { + throw new CommandException(MESSAGE_JOB_NOT_FOUND); + } + } else { + if (toAdd != null || from.equals(JobListName.STUB)) { + throw new CommandException(MESSAGE_DISPLAYING_JOB_ERROR); + } + } + + if (to == from) { + throw new CommandException(MESSAGE_IDENTICAL_LISTS); + } + + tempJob = model.getActiveJob(); + + List fromList = model.getJobsList(from); + + for (int i = 0; i < indexes.size(); i++) { + if (indexes.get(i).getZeroBased() >= fromList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + } + + for (int i = 0; i < indexes.size(); i++) { + Person toAdd = fromList.get(indexes.get(i).getZeroBased()); + try { + model.addPersonToJob(tempJob, toAdd, to); + } catch (Exception e) { + this.numberAdded--; + } + } + + model.commitAddressBook(); + String command = String.format(MESSAGE_SUCCESS, numberAdded); + return new CommandResult(command); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof MovePeopleCommand // instanceof handles nulls + && to.equals(((MovePeopleCommand) other).to) + && from.equals(((MovePeopleCommand) other).from) + && indexes.equals(((MovePeopleCommand) other).indexes) + && (toAdd == null || toAdd.equals(((MovePeopleCommand) other).toAdd))); + } +} diff --git a/src/main/java/seedu/address/logic/commands/RedoCommand.java b/src/main/java/seedu/address/logic/commands/RedoCommand.java index 227771a4eef6..00cce5a02fad 100644 --- a/src/main/java/seedu/address/logic/commands/RedoCommand.java +++ b/src/main/java/seedu/address/logic/commands/RedoCommand.java @@ -13,6 +13,7 @@ public class RedoCommand extends Command { public static final String COMMAND_WORD = "redo"; + public static final String COMMAND_ALIAS = "r"; public static final String MESSAGE_SUCCESS = "Redo success!"; public static final String MESSAGE_FAILURE = "No more commands to redo!"; @@ -25,7 +26,7 @@ public CommandResult execute(Model model, CommandHistory history) throws Command } model.redoAddressBook(); - model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + model.updateBaseFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); return new CommandResult(MESSAGE_SUCCESS); } } diff --git a/src/main/java/seedu/address/logic/commands/RemoveFromListCommand.java b/src/main/java/seedu/address/logic/commands/RemoveFromListCommand.java new file mode 100644 index 000000000000..89d0791c7053 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/RemoveFromListCommand.java @@ -0,0 +1,74 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import java.util.ArrayList; +import java.util.List; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.CommandHistory; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.job.JobListName; +import seedu.address.model.person.Person; + +/** + * Deletes a person identified using it's displayed index from the address book. + */ +public class RemoveFromListCommand extends Command { + + public static final String COMMAND_WORD = "remove"; + public static final String COMMAND_ALIAS = "rm"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Removes the person identified by the index number used in the specified list.\n" + + "Parameters: LIST_NAME " + + "INDEX (must be a positive integer)\n" + + "Example: " + COMMAND_WORD + " kiv" + " 1\n" + + "The alias \"d\" can be used instead.\n" + + "Example: " + COMMAND_ALIAS + " kiv" + " 1"; + + public static final String MESSAGE_DELETE_PERSON_SUCCESS = "Removed People"; + public static final String MESSAGE_NO_LIST_NAME = "Please specify which list to remove from"; + public static final String MESSAGE_NO_DISPLAYED_JOB = "No job is displayed. " + + "remove command can only be used when there is an active job."; + public static final String MESSAGE_BAD_INDEX = "One of the indexes is bad"; + + private final JobListName targetList; + private final ArrayList indexes; + + public RemoveFromListCommand(JobListName targetList, ArrayList indexes) { + this.targetList = targetList; + this.indexes = indexes; + } + + @Override + public CommandResult execute(Model model, CommandHistory history) throws CommandException { + requireNonNull(model); + + ArrayList peopleToRemove = new ArrayList<>(); + + if (model.getIsAllJobScreen()) { + throw new CommandException(MESSAGE_NO_DISPLAYED_JOB); + } + + List list = model.getJobsList(targetList); + + for (int i = 0; i < indexes.size(); i++) { + if (indexes.get(i).getZeroBased() >= list.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + peopleToRemove.add(list.get(indexes.get(i).getZeroBased())); + } + + for (int i = 0; i < indexes.size(); i++) { + model.deletePersonFromJobList(peopleToRemove.get(i), model.getActiveJob().getName(), targetList); + } + + + model.commitAddressBook(); + return new CommandResult(MESSAGE_DELETE_PERSON_SUCCESS); + } + +} diff --git a/src/main/java/seedu/address/logic/commands/SelectCommand.java b/src/main/java/seedu/address/logic/commands/SelectCommand.java index baa3c1f30bb4..4c3bccd13a88 100644 --- a/src/main/java/seedu/address/logic/commands/SelectCommand.java +++ b/src/main/java/seedu/address/logic/commands/SelectCommand.java @@ -17,11 +17,14 @@ public class SelectCommand extends Command { public static final String COMMAND_WORD = "select"; + public static final String COMMAND_ALIAS = "s"; public static final String MESSAGE_USAGE = COMMAND_WORD + ": Selects the person identified by the index number used in the displayed person list.\n" + "Parameters: INDEX (must be a positive integer)\n" - + "Example: " + COMMAND_WORD + " 1"; + + "Example: " + COMMAND_WORD + " 1\n" + + "The alias \"s\" can be used instead.\n" + + "Example: " + COMMAND_ALIAS + " 1\n"; public static final String MESSAGE_SELECT_PERSON_SUCCESS = "Selected Person: %1$s"; diff --git a/src/main/java/seedu/address/logic/commands/SetBlockOutDatesCommand.java b/src/main/java/seedu/address/logic/commands/SetBlockOutDatesCommand.java new file mode 100644 index 000000000000..61787fe2c5b3 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/SetBlockOutDatesCommand.java @@ -0,0 +1,37 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import java.util.Calendar; +import java.util.List; + +import seedu.address.logic.CommandHistory; +import seedu.address.model.Model; + +/** + * Sets the block out dates for user. + */ +public class SetBlockOutDatesCommand extends Command { + + public static final String COMMAND_WORD = "setBlockOutDates"; + public static final String MESSAGE_SUCCESS = "Block Out Dates set:\n"; + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Sets block out dates.\n" + + "Parameters: dd/mm/yyyy - dd/mm/yyyy or dd/mm/yyyy" + + "Example: " + COMMAND_WORD + " 01/04/2019 - 04/04/2019, 06/04/2019\n"; + + private final List blockOutDates; + + public SetBlockOutDatesCommand(List blockOutDates) { + this.blockOutDates = blockOutDates; + } + + @Override + public CommandResult execute(Model model, CommandHistory history) { + requireNonNull(model); + model.setBlockOutDates(blockOutDates); + model.commitAddressBook(); + return new CommandResult(MESSAGE_SUCCESS); + } + +} diff --git a/src/main/java/seedu/address/logic/commands/SetMaxInterviewsADayCommand.java b/src/main/java/seedu/address/logic/commands/SetMaxInterviewsADayCommand.java new file mode 100644 index 000000000000..f1d26992bbde --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/SetMaxInterviewsADayCommand.java @@ -0,0 +1,34 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import seedu.address.logic.CommandHistory; +import seedu.address.model.Model; + +/** + * Sets the maximum number of interviews that can be held in a day. + */ +public class SetMaxInterviewsADayCommand extends Command { + + public static final String COMMAND_WORD = "setMaxInterviewsADay"; + public static final String MESSAGE_SUCCESS = "Maximum number of interviews per day set"; + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Sets the maximum number of interviews a day.\n" + + "Parameters: INDEX (must be a positive integer)\n" + + "Example: " + COMMAND_WORD + " 1\n"; + + private final int maxInterviewsADay; + + public SetMaxInterviewsADayCommand(int maxInterviewsADay) { + this.maxInterviewsADay = maxInterviewsADay; + } + + @Override + public CommandResult execute(Model model, CommandHistory history) { + requireNonNull(model); + model.setMaxInterviewsADay(maxInterviewsADay); + model.commitAddressBook(); + return new CommandResult(MESSAGE_SUCCESS); + } + +} diff --git a/src/main/java/seedu/address/logic/commands/ShowInterviewsCommand.java b/src/main/java/seedu/address/logic/commands/ShowInterviewsCommand.java new file mode 100644 index 000000000000..57a98ff233fd --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/ShowInterviewsCommand.java @@ -0,0 +1,26 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import seedu.address.logic.CommandHistory; +import seedu.address.model.Model; + +/** + * Shows an interview date list from existing persons in the addressbook. + */ +public class ShowInterviewsCommand extends Command { + + public static final String COMMAND_WORD = "showInterviews"; + public static final String COMMAND_SUCCESS = "Interviews shown"; + public static final String MESSAGE_INTERVIEWS_NOT_PRESENT = "Interviews not present"; + + @Override + public CommandResult execute(Model model, CommandHistory history) { + requireNonNull(model); + String result = model.getInterviews().toString(); + if (result.isEmpty()) { + return new CommandResult(MESSAGE_INTERVIEWS_NOT_PRESENT); + } + return new CommandResult(COMMAND_SUCCESS, result); + } +} diff --git a/src/main/java/seedu/address/logic/commands/UndoCommand.java b/src/main/java/seedu/address/logic/commands/UndoCommand.java index 40441264f346..43c771df1b70 100644 --- a/src/main/java/seedu/address/logic/commands/UndoCommand.java +++ b/src/main/java/seedu/address/logic/commands/UndoCommand.java @@ -13,6 +13,7 @@ public class UndoCommand extends Command { public static final String COMMAND_WORD = "undo"; + public static final String COMMAND_ALIAS = "u"; public static final String MESSAGE_SUCCESS = "Undo success!"; public static final String MESSAGE_FAILURE = "No more commands to undo!"; @@ -25,7 +26,7 @@ public CommandResult execute(Model model, CommandHistory history) throws Command } model.undoAddressBook(); - model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + model.updateBaseFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); return new CommandResult(MESSAGE_SUCCESS); } } diff --git a/src/main/java/seedu/address/logic/parser/AddCommandParser.java b/src/main/java/seedu/address/logic/parser/AddCommandParser.java index 3b8bfa035e83..435d926dd6bd 100644 --- a/src/main/java/seedu/address/logic/parser/AddCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/AddCommandParser.java @@ -1,12 +1,34 @@ package seedu.address.logic.parser; import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.commands.AddCommand.MESSAGE_INFORMATION_WITHOUT_PREFIX; +import static seedu.address.logic.commands.AddCommand.MESSAGE_LACK_ADDRESS; +import static seedu.address.logic.commands.AddCommand.MESSAGE_LACK_EMAIL; +import static seedu.address.logic.commands.AddCommand.MESSAGE_LACK_GENDER; +import static seedu.address.logic.commands.AddCommand.MESSAGE_LACK_GRADE; +import static seedu.address.logic.commands.AddCommand.MESSAGE_LACK_JOBSAPPLY; +import static seedu.address.logic.commands.AddCommand.MESSAGE_LACK_MAJOR; +import static seedu.address.logic.commands.AddCommand.MESSAGE_LACK_NAME; +import static seedu.address.logic.commands.AddCommand.MESSAGE_LACK_NRIC; +import static seedu.address.logic.commands.AddCommand.MESSAGE_LACK_PHONE; +import static seedu.address.logic.commands.AddCommand.MESSAGE_LACK_RACE; +import static seedu.address.logic.commands.AddCommand.MESSAGE_LACK_SCHOOL; import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; +import static seedu.address.logic.parser.CliSyntax.PREFIX_GENDER; +import static seedu.address.logic.parser.CliSyntax.PREFIX_GRADE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_INTERVIEWSCORES; +import static seedu.address.logic.parser.CliSyntax.PREFIX_JOBSAPPLY; +import static seedu.address.logic.parser.CliSyntax.PREFIX_KNOWNPROGLANG; +import static seedu.address.logic.parser.CliSyntax.PREFIX_MAJOR; import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NRIC; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PASTJOB; import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; -import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; +import static seedu.address.logic.parser.CliSyntax.PREFIX_RACE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_SCHOOL; +import java.util.HashSet; import java.util.Set; import java.util.stream.Stream; @@ -14,9 +36,19 @@ import seedu.address.logic.parser.exceptions.ParseException; import seedu.address.model.person.Address; import seedu.address.model.person.Email; +import seedu.address.model.person.Gender; +import seedu.address.model.person.Grade; +import seedu.address.model.person.InterviewScores; +import seedu.address.model.person.JobsApply; +import seedu.address.model.person.KnownProgLang; +import seedu.address.model.person.Major; import seedu.address.model.person.Name; +import seedu.address.model.person.Nric; +import seedu.address.model.person.PastJob; import seedu.address.model.person.Person; import seedu.address.model.person.Phone; +import seedu.address.model.person.Race; +import seedu.address.model.person.School; import seedu.address.model.tag.Tag; /** @@ -27,24 +59,69 @@ public class AddCommandParser implements Parser { /** * Parses the given {@code String} of arguments in the context of the AddCommand * and returns an AddCommand object for execution. + * * @throws ParseException if the user input does not conform the expected format */ public AddCommand parse(String args) throws ParseException { ArgumentMultimap argMultimap = - ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, PREFIX_TAG); + ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_NRIC, + PREFIX_GENDER, PREFIX_RACE, PREFIX_ADDRESS, PREFIX_SCHOOL, PREFIX_MAJOR, PREFIX_GRADE, + PREFIX_KNOWNPROGLANG, PREFIX_PASTJOB, PREFIX_JOBSAPPLY, PREFIX_INTERVIEWSCORES); - if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_ADDRESS, PREFIX_PHONE, PREFIX_EMAIL) + if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_ADDRESS, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_NRIC, + PREFIX_GENDER, PREFIX_RACE, PREFIX_SCHOOL, PREFIX_MAJOR, PREFIX_GRADE, PREFIX_JOBSAPPLY) || !argMultimap.getPreamble().isEmpty()) { throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE)); } - + if (!argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INFORMATION_WITHOUT_PREFIX, AddCommand.MESSAGE_USAGE)); + } else if (!argMultimap.getValue(PREFIX_NAME).isPresent()) { + throw new ParseException(String.format(MESSAGE_LACK_NAME, AddCommand.MESSAGE_USAGE)); + } else if (!argMultimap.getValue(PREFIX_ADDRESS).isPresent()) { + throw new ParseException(String.format(MESSAGE_LACK_ADDRESS, AddCommand.MESSAGE_USAGE)); + } else if (!argMultimap.getValue(PREFIX_PHONE).isPresent()) { + throw new ParseException(String.format(MESSAGE_LACK_PHONE, AddCommand.MESSAGE_USAGE)); + } else if (!argMultimap.getValue(PREFIX_EMAIL).isPresent()) { + throw new ParseException(String.format(MESSAGE_LACK_EMAIL, AddCommand.MESSAGE_USAGE)); + } else if (!argMultimap.getValue(PREFIX_NRIC).isPresent()) { + throw new ParseException(String.format(MESSAGE_LACK_NRIC, AddCommand.MESSAGE_USAGE)); + } else if (!argMultimap.getValue(PREFIX_GENDER).isPresent()) { + throw new ParseException(String.format(MESSAGE_LACK_GENDER, AddCommand.MESSAGE_USAGE)); + } else if (!argMultimap.getValue(PREFIX_RACE).isPresent()) { + throw new ParseException(String.format(MESSAGE_LACK_RACE, AddCommand.MESSAGE_USAGE)); + } else if (!argMultimap.getValue(PREFIX_SCHOOL).isPresent()) { + throw new ParseException(String.format(MESSAGE_LACK_SCHOOL, AddCommand.MESSAGE_USAGE)); + } else if (!argMultimap.getValue(PREFIX_MAJOR).isPresent()) { + throw new ParseException(String.format(MESSAGE_LACK_MAJOR, AddCommand.MESSAGE_USAGE)); + } else if (!argMultimap.getValue(PREFIX_GRADE).isPresent()) { + throw new ParseException(String.format(MESSAGE_LACK_GRADE, AddCommand.MESSAGE_USAGE)); + } else if (!argMultimap.getValue(PREFIX_JOBSAPPLY).isPresent()) { + throw new ParseException(String.format(MESSAGE_LACK_JOBSAPPLY, AddCommand.MESSAGE_USAGE)); + } Name name = ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get()); Phone phone = ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE).get()); Email email = ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL).get()); + Nric nric = ParserUtil.parseNric(argMultimap.getValue(PREFIX_NRIC).get()); + Gender gender = ParserUtil.parseGender(argMultimap.getValue(PREFIX_GENDER).get()); + Race race = ParserUtil.parseRace(argMultimap.getValue(PREFIX_RACE).get()); Address address = ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS).get()); - Set tagList = ParserUtil.parseTags(argMultimap.getAllValues(PREFIX_TAG)); - - Person person = new Person(name, phone, email, address, tagList); + School school = ParserUtil.parseSchool(argMultimap.getValue(PREFIX_SCHOOL).get()); + Major major = ParserUtil.parseMajor(argMultimap.getValue(PREFIX_MAJOR).get()); + Grade grade = ParserUtil.parseGrade(argMultimap.getValue(PREFIX_GRADE).get()); + InterviewScores interviewScores; + if (argMultimap.getValue(PREFIX_INTERVIEWSCORES).isPresent()) { + interviewScores = ParserUtil.parseInterviewScores(argMultimap + .getValue(PREFIX_INTERVIEWSCORES).get()); + } else { + interviewScores = new InterviewScores(InterviewScores.NO_RECORD); + } + Set knownProgLangsList = ParserUtil.parseKnownProgLangs(argMultimap + .getAllValues(PREFIX_KNOWNPROGLANG)); + Set pastjobList = ParserUtil.parsePastJobs(argMultimap.getAllValues(PREFIX_PASTJOB)); + Set jobsApplyList = ParserUtil.parseJobsApply(argMultimap.getAllValues(PREFIX_JOBSAPPLY)); + Set tagList = new HashSet<>(); + Person person = new Person(name, phone, email, nric, gender, race, address, school, major, grade, + knownProgLangsList, pastjobList, jobsApplyList, interviewScores, tagList); return new AddCommand(person); } diff --git a/src/main/java/seedu/address/logic/parser/AddListToJobCommandParser.java b/src/main/java/seedu/address/logic/parser/AddListToJobCommandParser.java new file mode 100644 index 000000000000..9fe3f49a040e --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/AddListToJobCommandParser.java @@ -0,0 +1,50 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_JOBNAME; + +import seedu.address.logic.commands.AddListToJobCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.job.JobListName; +import seedu.address.model.job.JobName; + +/** + * Parses input arguments and creates a new AddCommand object + */ +public class AddListToJobCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the AddPersonToJobCommand + * and returns an AddPersonToJobCommand object for execution. + * + * @throws ParseException if the user input does not conform the expected format + */ + public AddListToJobCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_JOBNAME); + + JobListName toListName; + JobListName fromListName = JobListName.EMPTY; + JobName toAdd; + + toListName = ParserUtil.parseJobListName(args.split("\\b\\s")[0].trim()); + + if (toListName == JobListName.EMPTY) { + throw new ParseException(AddListToJobCommand.MESSAGE_NO_DESTINATION + + String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddListToJobCommand.MESSAGE_USAGE)); + } + + try { + toAdd = ParserUtil.parseJobName(argMultimap.getValue(PREFIX_JOBNAME).get()); + } catch (Exception noJob) { + toAdd = null; + } + + if ((toAdd == null && args.split("\\b\\s").length > 1) || args.split("\\b\\s").length > 2) { + fromListName = ParserUtil.parseJobListName(args.split("\\b\\s")[1].trim()); + } + + return new AddListToJobCommand(toAdd, toListName, fromListName); + } + +} diff --git a/src/main/java/seedu/address/logic/parser/AddressBookParser.java b/src/main/java/seedu/address/logic/parser/AddressBookParser.java index b7d57f5db86a..c6222f527d12 100644 --- a/src/main/java/seedu/address/logic/parser/AddressBookParser.java +++ b/src/main/java/seedu/address/logic/parser/AddressBookParser.java @@ -1,5 +1,6 @@ package seedu.address.logic.parser; +import static seedu.address.commons.core.Messages.MESSAGE_COMMAND_CANNOT_USE; import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; import static seedu.address.commons.core.Messages.MESSAGE_UNKNOWN_COMMAND; @@ -7,17 +8,31 @@ import java.util.regex.Pattern; import seedu.address.logic.commands.AddCommand; +import seedu.address.logic.commands.AddListToJobCommand; import seedu.address.logic.commands.ClearCommand; +import seedu.address.logic.commands.ClearFilterCommand; +import seedu.address.logic.commands.ClearInterviewsCommand; import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CreateJobCommand; import seedu.address.logic.commands.DeleteCommand; +import seedu.address.logic.commands.DeleteFilterCommand; +import seedu.address.logic.commands.DeleteJobCommand; +import seedu.address.logic.commands.DisplayJobCommand; import seedu.address.logic.commands.EditCommand; import seedu.address.logic.commands.ExitCommand; -import seedu.address.logic.commands.FindCommand; +import seedu.address.logic.commands.FilterCommand; +import seedu.address.logic.commands.GenerateAnalyticsCommand; +import seedu.address.logic.commands.GenerateInterviewsCommand; import seedu.address.logic.commands.HelpCommand; import seedu.address.logic.commands.HistoryCommand; +import seedu.address.logic.commands.ImportResumesCommand; import seedu.address.logic.commands.ListCommand; +import seedu.address.logic.commands.MovePeopleCommand; import seedu.address.logic.commands.RedoCommand; -import seedu.address.logic.commands.SelectCommand; +import seedu.address.logic.commands.RemoveFromListCommand; +import seedu.address.logic.commands.SetBlockOutDatesCommand; +import seedu.address.logic.commands.SetMaxInterviewsADayCommand; +import seedu.address.logic.commands.ShowInterviewsCommand; import seedu.address.logic.commands.UndoCommand; import seedu.address.logic.parser.exceptions.ParseException; @@ -38,7 +53,7 @@ public class AddressBookParser { * @return the command based on the user input * @throws ParseException if the user input does not conform the expected format */ - public Command parseCommand(String userInput) throws ParseException { + public Command parseCommand(String userInput, boolean isAllJobScreen) throws ParseException { final Matcher matcher = BASIC_COMMAND_FORMAT.matcher(userInput.trim()); if (!matcher.matches()) { throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, HelpCommand.MESSAGE_USAGE)); @@ -49,41 +64,173 @@ public Command parseCommand(String userInput) throws ParseException { switch (commandWord) { case AddCommand.COMMAND_WORD: + if (!isAllJobScreen) { + throw new ParseException(MESSAGE_COMMAND_CANNOT_USE); + } return new AddCommandParser().parse(arguments); + case AddCommand.COMMAND_ALIAS: + if (!isAllJobScreen) { + throw new ParseException(MESSAGE_COMMAND_CANNOT_USE); + } + return new AddCommandParser().parse(arguments); + + case ImportResumesCommand.COMMAND_WORD: + if (!isAllJobScreen) { + throw new ParseException(MESSAGE_COMMAND_CANNOT_USE); + } + return new ImportResumesCommandParser().parse(arguments); + + case ImportResumesCommand.COMMAND_ALIAS: + if (!isAllJobScreen) { + throw new ParseException(MESSAGE_COMMAND_CANNOT_USE); + } + return new ImportResumesCommandParser().parse(arguments); + + case CreateJobCommand.COMMAND_WORD: + return new CreateJobCommandParser().parse(arguments); + + case CreateJobCommand.COMMAND_ALIAS: + return new CreateJobCommandParser().parse(arguments); + case EditCommand.COMMAND_WORD: + if (!isAllJobScreen) { + throw new ParseException(MESSAGE_COMMAND_CANNOT_USE); + } return new EditCommandParser().parse(arguments); - case SelectCommand.COMMAND_WORD: - return new SelectCommandParser().parse(arguments); + case EditCommand.COMMAND_ALIAS: + if (!isAllJobScreen) { + throw new ParseException(MESSAGE_COMMAND_CANNOT_USE); + } + return new EditCommandParser().parse(arguments); + + case DeleteFilterCommand.COMMAND_WORD: + return new DeleteFilterCommandParser().parse(arguments); + + case DeleteFilterCommand.COMMAND_ALIAS: + return new DeleteFilterCommandParser().parse(arguments); + + case ClearFilterCommand.COMMAND_WORD: + return new ClearFilterCommandParser().parse(arguments); + + case ClearFilterCommand.COMMAND_ALIAS: + return new ClearFilterCommandParser().parse(arguments); case DeleteCommand.COMMAND_WORD: + if (!isAllJobScreen) { + throw new ParseException(MESSAGE_COMMAND_CANNOT_USE); + } + return new DeleteCommandParser().parse(arguments); + + case DeleteCommand.COMMAND_ALIAS: + if (!isAllJobScreen) { + throw new ParseException(MESSAGE_COMMAND_CANNOT_USE); + } return new DeleteCommandParser().parse(arguments); case ClearCommand.COMMAND_WORD: + if (!isAllJobScreen) { + throw new ParseException(MESSAGE_COMMAND_CANNOT_USE); + } + return new ClearCommand(); + + case ClearCommand.COMMAND_ALIAS: + if (!isAllJobScreen) { + throw new ParseException(MESSAGE_COMMAND_CANNOT_USE); + } return new ClearCommand(); - case FindCommand.COMMAND_WORD: - return new FindCommandParser().parse(arguments); + case FilterCommand.COMMAND_WORD: + return new FilterCommandParser().parse(arguments); + + case FilterCommand.COMMAND_ALIAS: + return new FilterCommandParser().parse(arguments); case ListCommand.COMMAND_WORD: return new ListCommand(); + case ListCommand.COMMAND_ALIAS: + return new ListCommand(); + case HistoryCommand.COMMAND_WORD: return new HistoryCommand(); + case HistoryCommand.COMMAND_ALIAS: + return new HistoryCommand(); + case ExitCommand.COMMAND_WORD: return new ExitCommand(); + case ExitCommand.COMMAND_ALIAS: + return new ExitCommand(); + case HelpCommand.COMMAND_WORD: return new HelpCommand(); + case HelpCommand.COMMAND_ALIAS: + return new HelpCommand(); + case UndoCommand.COMMAND_WORD: return new UndoCommand(); + case UndoCommand.COMMAND_ALIAS: + return new UndoCommand(); + case RedoCommand.COMMAND_WORD: return new RedoCommand(); + case RedoCommand.COMMAND_ALIAS: + return new RedoCommand(); + + case GenerateInterviewsCommand.COMMAND_WORD: + return new GenerateInterviewsCommand(); + + case ShowInterviewsCommand.COMMAND_WORD: + return new ShowInterviewsCommand(); + + case SetMaxInterviewsADayCommand.COMMAND_WORD: + return new SetMaxInterviewsADayCommandParser().parse(arguments); + + case ClearInterviewsCommand.COMMAND_WORD: + return new ClearInterviewsCommand(); + + case GenerateAnalyticsCommand.COMMAND_WORD: + return new AnalyticsCommandParser().parse(arguments); + + case SetBlockOutDatesCommand.COMMAND_WORD: + return new SetBlockOutDatesCommandParser().parse(arguments); + + case DisplayJobCommand.COMMAND_WORD: + return new DisplayJobCommandParser().parse(arguments); + + case DisplayJobCommand.COMMAND_ALIAS: + return new DisplayJobCommandParser().parse(arguments); + + case MovePeopleCommand.COMMAND_WORD: + return new MovePeopleCommandParser().parse(arguments); + + case MovePeopleCommand.COMMAND_ALIAS: + return new MovePeopleCommandParser().parse(arguments); + + case DeleteJobCommand.COMMAND_WORD: + return new DeleteJobCommandParser().parse(arguments); + + case DeleteJobCommand.COMMAND_ALIAS: + return new DeleteJobCommandParser().parse(arguments); + + case AddListToJobCommand.COMMAND_WORD: + return new AddListToJobCommandParser().parse(arguments); + + case AddListToJobCommand.COMMAND_ALIAS: + return new AddListToJobCommandParser().parse(arguments); + + case RemoveFromListCommand.COMMAND_WORD: + return new RemoveFromListCommandParser().parse(arguments); + + case RemoveFromListCommand.COMMAND_ALIAS: + return new RemoveFromListCommandParser().parse(arguments); + default: throw new ParseException(MESSAGE_UNKNOWN_COMMAND); } diff --git a/src/main/java/seedu/address/logic/parser/AnalyticsCommandParser.java b/src/main/java/seedu/address/logic/parser/AnalyticsCommandParser.java new file mode 100644 index 000000000000..8bf1733ffc8a --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/AnalyticsCommandParser.java @@ -0,0 +1,37 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.address.logic.commands.GenerateAnalyticsCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.job.JobListName; + +/** + * Parses input arguments and creates a new SearchCommand object + */ +public class AnalyticsCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the GenerateAnalyticsCommand + * and returns an GenerateAnalyticsCommand object for execution. + * + * @throws ParseException if the user input does not conform the expected format + */ + public GenerateAnalyticsCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args); + String trimmedArgs = args.trim(); + if (trimmedArgs.isEmpty()) { + return new GenerateAnalyticsCommand(); + } else { + JobListName listName; + try { + listName = ParserUtil.parseJobListName(argMultimap.getPreamble()); + } catch (ParseException pe) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + GenerateAnalyticsCommand.MESSAGE_USAGE), pe); + } + return new GenerateAnalyticsCommand(listName); + } + } +} diff --git a/src/main/java/seedu/address/logic/parser/ClearFilterCommandParser.java b/src/main/java/seedu/address/logic/parser/ClearFilterCommandParser.java new file mode 100644 index 000000000000..eb33c115ab63 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/ClearFilterCommandParser.java @@ -0,0 +1,30 @@ +package seedu.address.logic.parser; + +import seedu.address.logic.commands.ClearFilterCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.job.JobListName; + +/** + * Parses input arguments and creates a new ClearFilterCommand object + */ +public class ClearFilterCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the ClearFilterCommand + * and returns an ClearFilterCommand object for execution. + * + * @throws ParseException if the user input does not conform the expected format + */ + public ClearFilterCommand parse(String args) throws ParseException { + JobListName listName; + String trimedString = args.trim(); + try { + listName = ParserUtil.parseJobListName(trimedString); + return new ClearFilterCommand(listName); + } catch (ParseException pe) { + throw new ParseException(String.format(pe.getMessage(), + ClearFilterCommand.MESSAGE_USAGE_ALLJOB_SCREEN + ClearFilterCommand.MESSAGE_USAGE_DETAIL_SCREEN), pe); + } + } + +} diff --git a/src/main/java/seedu/address/logic/parser/CliSyntax.java b/src/main/java/seedu/address/logic/parser/CliSyntax.java index 75b1a9bf1190..1b649ec14e26 100644 --- a/src/main/java/seedu/address/logic/parser/CliSyntax.java +++ b/src/main/java/seedu/address/logic/parser/CliSyntax.java @@ -9,7 +9,25 @@ public class CliSyntax { public static final Prefix PREFIX_NAME = new Prefix("n/"); public static final Prefix PREFIX_PHONE = new Prefix("p/"); public static final Prefix PREFIX_EMAIL = new Prefix("e/"); + public static final Prefix PREFIX_NRIC = new Prefix("nric/"); + public static final Prefix PREFIX_GENDER = new Prefix("g/"); + public static final Prefix PREFIX_RACE = new Prefix("r/"); public static final Prefix PREFIX_ADDRESS = new Prefix("a/"); + public static final Prefix PREFIX_MAJOR = new Prefix("m/"); + public static final Prefix PREFIX_GRADE = new Prefix("gr/"); public static final Prefix PREFIX_TAG = new Prefix("t/"); + public static final Prefix PREFIX_KNOWNPROGLANG = new Prefix("kpl/"); + public static final Prefix PREFIX_PASTJOB = new Prefix("pj/"); + public static final Prefix PREFIX_JOBSAPPLY = new Prefix("j/"); + public static final Prefix PREFIX_INTERVIEWSCORES = new Prefix("is/"); + public static final Prefix PREFIX_INTERVIEWSCORESQ1 = new Prefix("is1/"); + public static final Prefix PREFIX_INTERVIEWSCORESQ2 = new Prefix("is2/"); + public static final Prefix PREFIX_INTERVIEWSCORESQ3 = new Prefix("is3/"); + public static final Prefix PREFIX_INTERVIEWSCORESQ4 = new Prefix("is4/"); + public static final Prefix PREFIX_INTERVIEWSCORESQ5 = new Prefix("is5/"); + public static final Prefix PREFIX_SCHOOL = new Prefix("s/"); + public static final Prefix PREFIX_JOBNAME = new Prefix("jn/"); + public static final Prefix PREFIX_FILTERNAME = new Prefix("fn/"); + public static final Prefix PREFIX_LISTNUMBER = new Prefix("ln/"); } diff --git a/src/main/java/seedu/address/logic/parser/CreateJobCommandParser.java b/src/main/java/seedu/address/logic/parser/CreateJobCommandParser.java new file mode 100644 index 000000000000..a8712bfdc88f --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/CreateJobCommandParser.java @@ -0,0 +1,47 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_JOBNAME; + +import java.util.stream.Stream; + +import seedu.address.logic.commands.CreateJobCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.job.Job; +import seedu.address.model.job.JobName; + +/** + * Parses input arguments and creates a new AddCommand object + */ +public class CreateJobCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the CreateJobCommand + * and returns an CreateJobCommand object for execution. + * + * @throws ParseException if the user input does not conform the expected format + */ + public CreateJobCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_JOBNAME); + + if (!arePrefixesPresent(argMultimap, PREFIX_JOBNAME) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, CreateJobCommand.MESSAGE_USAGE)); + } + + JobName name = ParserUtil.parseJobName(argMultimap.getValue(PREFIX_JOBNAME).get()); + Job job = new Job(name); + + return new CreateJobCommand(job); + } + + /** + * 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/DeleteFilterCommandParser.java b/src/main/java/seedu/address/logic/parser/DeleteFilterCommandParser.java new file mode 100644 index 000000000000..9240b6e0d885 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/DeleteFilterCommandParser.java @@ -0,0 +1,45 @@ +package seedu.address.logic.parser; + +import static seedu.address.logic.commands.DeleteFilterCommand.MESSAGE_LACK_FILTERNAME; +import static seedu.address.logic.commands.DeleteFilterCommand.MESSAGE_USAGE_ALLJOB_SCREEN; +import static seedu.address.logic.commands.DeleteFilterCommand.MESSAGE_USAGE_DETAIL_SCREEN; +import static seedu.address.logic.parser.CliSyntax.PREFIX_FILTERNAME; + +import seedu.address.logic.commands.DeleteFilterCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.job.JobListName; + +/** + * Parses input arguments and creates a new DeleteFilterCommand object + */ +public class DeleteFilterCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the DeleteFilterCommand + * and returns an DeleteFilterCommand object for execution. + * + * @throws ParseException if the user input does not conform the expected format + */ + public DeleteFilterCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_FILTERNAME); + JobListName listName; + String commandName; + if (argMultimap.getValue(PREFIX_FILTERNAME).isPresent()) { + commandName = argMultimap.getValue(PREFIX_FILTERNAME).get().trim(); + } else { + throw new ParseException(String.format(MESSAGE_LACK_FILTERNAME, + MESSAGE_USAGE_ALLJOB_SCREEN + MESSAGE_USAGE_DETAIL_SCREEN)); + } + String preambleString = argMultimap.getPreamble(); + String listNameString = preambleString.trim(); + try { + listName = ParserUtil.parseJobListName(listNameString); + return new DeleteFilterCommand(listName, commandName); + } catch (ParseException pe) { + throw new ParseException(String.format(pe.getMessage(), + MESSAGE_USAGE_ALLJOB_SCREEN + MESSAGE_USAGE_DETAIL_SCREEN), pe); + } + } + +} diff --git a/src/main/java/seedu/address/logic/parser/DeleteJobCommandParser.java b/src/main/java/seedu/address/logic/parser/DeleteJobCommandParser.java new file mode 100644 index 000000000000..40a42ef229ee --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/DeleteJobCommandParser.java @@ -0,0 +1,47 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_JOBNAME; + +import java.util.stream.Stream; + +import seedu.address.logic.commands.DeleteJobCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.job.Job; +import seedu.address.model.job.JobName; + +/** + * Parses input arguments and creates a new AddCommand object + */ +public class DeleteJobCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the CreateJobCommand + * and returns an CreateJobCommand object for execution. + * + * @throws ParseException if the user input does not conform the expected format + */ + public DeleteJobCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_JOBNAME); + + if (!arePrefixesPresent(argMultimap, PREFIX_JOBNAME) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteJobCommand.MESSAGE_USAGE)); + } + + JobName name = ParserUtil.parseJobName(argMultimap.getValue(PREFIX_JOBNAME).get()); + Job job = new Job(name); + + return new DeleteJobCommand(job); + } + + /** + * 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/DisplayJobCommandParser.java b/src/main/java/seedu/address/logic/parser/DisplayJobCommandParser.java new file mode 100644 index 000000000000..c7ca3916a9cf --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/DisplayJobCommandParser.java @@ -0,0 +1,46 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_JOBNAME; + +import java.util.stream.Stream; + +import seedu.address.logic.commands.DisplayJobCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.job.JobName; + +/** + * Parses input arguments and creates a new AddCommand object + */ +public class DisplayJobCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the DisplayJobCommand + * and returns an DisplayJobCommand object for execution. + * + * @throws ParseException if the user input does not conform the expected format + */ + public DisplayJobCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_JOBNAME); + + if (!arePrefixesPresent(argMultimap, PREFIX_JOBNAME) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + DisplayJobCommand.MESSAGE_USAGE)); + } + + JobName job = ParserUtil.parseJobName(argMultimap.getValue(PREFIX_JOBNAME).get()); + + return new DisplayJobCommand(job); + } + + /** + * 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/EditCommandParser.java b/src/main/java/seedu/address/logic/parser/EditCommandParser.java index 845644b7dea1..195d34eaf565 100644 --- a/src/main/java/seedu/address/logic/parser/EditCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/EditCommandParser.java @@ -1,12 +1,21 @@ package seedu.address.logic.parser; import static java.util.Objects.requireNonNull; -import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_PREAMBLE; import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; +import static seedu.address.logic.parser.CliSyntax.PREFIX_GENDER; +import static seedu.address.logic.parser.CliSyntax.PREFIX_GRADE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_INTERVIEWSCORES; +import static seedu.address.logic.parser.CliSyntax.PREFIX_JOBSAPPLY; +import static seedu.address.logic.parser.CliSyntax.PREFIX_KNOWNPROGLANG; +import static seedu.address.logic.parser.CliSyntax.PREFIX_MAJOR; import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NRIC; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PASTJOB; import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; -import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; +import static seedu.address.logic.parser.CliSyntax.PREFIX_RACE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_SCHOOL; import java.util.Collection; import java.util.Collections; @@ -17,6 +26,9 @@ import seedu.address.logic.commands.EditCommand; import seedu.address.logic.commands.EditCommand.EditPersonDescriptor; import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.person.JobsApply; +import seedu.address.model.person.KnownProgLang; +import seedu.address.model.person.PastJob; import seedu.address.model.tag.Tag; /** @@ -27,19 +39,22 @@ 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_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_NRIC, PREFIX_GENDER, + PREFIX_RACE, PREFIX_ADDRESS, PREFIX_SCHOOL, PREFIX_MAJOR, PREFIX_GRADE, PREFIX_KNOWNPROGLANG, + PREFIX_PASTJOB, PREFIX_JOBSAPPLY, PREFIX_INTERVIEWSCORES); Index index; try { index = ParserUtil.parseIndex(argMultimap.getPreamble()); } catch (ParseException pe) { - throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditCommand.MESSAGE_USAGE), pe); + throw new ParseException(String.format(MESSAGE_INVALID_PREAMBLE, + EditCommand.MESSAGE_USAGE), pe); } EditPersonDescriptor editPersonDescriptor = new EditPersonDescriptor(); @@ -52,10 +67,35 @@ public EditCommand parse(String args) throws ParseException { if (argMultimap.getValue(PREFIX_EMAIL).isPresent()) { editPersonDescriptor.setEmail(ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL).get())); } + if (argMultimap.getValue(PREFIX_NRIC).isPresent()) { + editPersonDescriptor.setNric(ParserUtil.parseNric(argMultimap.getValue(PREFIX_NRIC).get())); + } + if (argMultimap.getValue(PREFIX_GENDER).isPresent()) { + editPersonDescriptor.setGender(ParserUtil.parseGender(argMultimap.getValue(PREFIX_GENDER).get())); + } + if (argMultimap.getValue(PREFIX_RACE).isPresent()) { + editPersonDescriptor.setRace(ParserUtil.parseRace(argMultimap.getValue(PREFIX_RACE).get())); + } if (argMultimap.getValue(PREFIX_ADDRESS).isPresent()) { editPersonDescriptor.setAddress(ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS).get())); } - parseTagsForEdit(argMultimap.getAllValues(PREFIX_TAG)).ifPresent(editPersonDescriptor::setTags); + if (argMultimap.getValue(PREFIX_SCHOOL).isPresent()) { + editPersonDescriptor.setSchool(ParserUtil.parseSchool(argMultimap.getValue(PREFIX_SCHOOL).get())); + } + if (argMultimap.getValue(PREFIX_MAJOR).isPresent()) { + editPersonDescriptor.setMajor(ParserUtil.parseMajor(argMultimap.getValue(PREFIX_MAJOR).get())); + } + if (argMultimap.getValue(PREFIX_GRADE).isPresent()) { + editPersonDescriptor.setGrade(ParserUtil.parseGrade(argMultimap.getValue(PREFIX_GRADE).get())); + } + if (argMultimap.getValue(PREFIX_INTERVIEWSCORES).isPresent()) { + editPersonDescriptor.setInterviewScores(ParserUtil.parseInterviewScores(argMultimap + .getValue(PREFIX_INTERVIEWSCORES).get())); + } + parseKnownProgLangsForEdit(argMultimap.getAllValues(PREFIX_KNOWNPROGLANG)) + .ifPresent(editPersonDescriptor::setKnownProgLangs); + parsePastJobsForEdit(argMultimap.getAllValues(PREFIX_PASTJOB)).ifPresent(editPersonDescriptor::setPastJobs); + parseJobsApplyForEdit(argMultimap.getAllValues(PREFIX_JOBSAPPLY)).ifPresent(editPersonDescriptor::setJobsApply); if (!editPersonDescriptor.isAnyFieldEdited()) { throw new ParseException(EditCommand.MESSAGE_NOT_EDITED); @@ -64,6 +104,56 @@ public EditCommand parse(String args) throws ParseException { return new EditCommand(index, editPersonDescriptor); } + /** + * Parses {@code Collection pastjobs} into a {@code Set} if {@code pastjobs} is non-empty. + * If {@code pastjobs} contain only one element which is an empty string, it will be parsed into a + * {@code Set} containing zero past jobs. + */ + private Optional> parseKnownProgLangsForEdit(Collection knownProjLang) + throws ParseException { + assert knownProjLang != null; + + if (knownProjLang.isEmpty()) { + return Optional.empty(); + } + Collection knownProgLangSet = knownProjLang.size() == 1 + && knownProjLang.contains("") ? Collections.emptySet() : knownProjLang; + return Optional.of(ParserUtil.parseKnownProgLangs(knownProgLangSet)); + } + + /** + * Parses {@code Collection pastjobs} into a {@code Set} if {@code pastjobs} is non-empty. + * If {@code pastjobs} contain only one element which is an empty string, it will be parsed into a + * {@code Set} containing zero past jobs. + */ + private Optional> parseJobsApplyForEdit(Collection jobsApply) throws ParseException { + assert jobsApply != null; + + if (jobsApply.isEmpty()) { + return Optional.empty(); + } + Collection jobsApplySet = jobsApply.size() == 1 + && jobsApply.contains("") ? Collections.emptySet() : jobsApply; + return Optional.of(ParserUtil.parseJobsApply(jobsApplySet)); + } + + /** + * Parses {@code Collection jobsApply} into a {@code Set} containing zero jobsApply. + */ + private Optional> parsePastJobsForEdit(Collection pastjobs) throws ParseException { + assert pastjobs != null; + + if (pastjobs.isEmpty()) { + return Optional.empty(); + } + Collection pastjobSet = pastjobs.size() == 1 + && pastjobs.contains("") ? Collections.emptySet() : pastjobs; + return Optional.of(ParserUtil.parsePastJobs(pastjobSet)); + } + + /** * 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 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 000000000000..5482db09c7e0 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/FilterCommandParser.java @@ -0,0 +1,402 @@ +package seedu.address.logic.parser; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.commands.FilterCommand.MESSAGE_INVALID_RANGE; +import static seedu.address.logic.commands.FilterCommand.MESSAGE_LACK_FILTERNAME; +import static seedu.address.logic.commands.FilterCommand.MESSAGE_USAGE_ALLJOB_SCREEN; +import static seedu.address.logic.commands.FilterCommand.MESSAGE_USAGE_JOB_DETAIL_SCREEN; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; +import static seedu.address.logic.parser.CliSyntax.PREFIX_FILTERNAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_GENDER; +import static seedu.address.logic.parser.CliSyntax.PREFIX_GRADE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_INTERVIEWSCORESQ1; +import static seedu.address.logic.parser.CliSyntax.PREFIX_INTERVIEWSCORESQ2; +import static seedu.address.logic.parser.CliSyntax.PREFIX_INTERVIEWSCORESQ3; +import static seedu.address.logic.parser.CliSyntax.PREFIX_INTERVIEWSCORESQ4; +import static seedu.address.logic.parser.CliSyntax.PREFIX_INTERVIEWSCORESQ5; +import static seedu.address.logic.parser.CliSyntax.PREFIX_JOBSAPPLY; +import static seedu.address.logic.parser.CliSyntax.PREFIX_KNOWNPROGLANG; +import static seedu.address.logic.parser.CliSyntax.PREFIX_MAJOR; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NRIC; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PASTJOB; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_RACE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_SCHOOL; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; +import static seedu.address.logic.parser.ParserUtil.isValidValueRange; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; + +import seedu.address.logic.commands.FilterCommand; +import seedu.address.logic.commands.FilterCommand.PredicatePersonDescriptor; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.job.JobListName; + +/** + * 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 an FilterCommand object for execution. + * + * @throws ParseException if the user input does not conform the expected format + */ + public FilterCommand parse(String args) throws ParseException { + requireNonNull(args); + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_RACE, PREFIX_ADDRESS, + PREFIX_SCHOOL, PREFIX_MAJOR, PREFIX_PASTJOB, PREFIX_TAG, PREFIX_GENDER, PREFIX_GRADE, + PREFIX_NRIC, PREFIX_JOBSAPPLY, PREFIX_KNOWNPROGLANG, PREFIX_INTERVIEWSCORESQ1, + PREFIX_INTERVIEWSCORESQ2, PREFIX_INTERVIEWSCORESQ3, PREFIX_INTERVIEWSCORESQ4, + PREFIX_INTERVIEWSCORESQ5, PREFIX_FILTERNAME); + JobListName listName; + String commandName; + String preambleString = argMultimap.getPreamble(); + String listNameString = preambleString.trim(); + try { + listName = ParserUtil.parseJobListName(listNameString); + } catch (ParseException pe) { + throw new ParseException(String.format( + pe.getMessage(), MESSAGE_USAGE_ALLJOB_SCREEN + MESSAGE_USAGE_JOB_DETAIL_SCREEN), pe); + } + PredicatePersonDescriptor predicatePersonDescriptor = new PredicatePersonDescriptor(); + if (argMultimap.getValue(PREFIX_FILTERNAME).isPresent()) { + commandName = argMultimap.getValue(PREFIX_FILTERNAME).get(); + } else { + throw new ParseException(String.format(MESSAGE_LACK_FILTERNAME, + MESSAGE_USAGE_ALLJOB_SCREEN + MESSAGE_USAGE_JOB_DETAIL_SCREEN)); + } + parseEmail(predicatePersonDescriptor, argMultimap); + parseAddress(predicatePersonDescriptor, argMultimap); + parseGender(predicatePersonDescriptor, argMultimap); + parseGrade(predicatePersonDescriptor, argMultimap); + parseInterviewQ1(predicatePersonDescriptor, argMultimap); + parseInterviewQ2(predicatePersonDescriptor, argMultimap); + parseInterviewQ3(predicatePersonDescriptor, argMultimap); + parseInterviewQ4(predicatePersonDescriptor, argMultimap); + parseInterviewQ5(predicatePersonDescriptor, argMultimap); + parseJobsApply(predicatePersonDescriptor, argMultimap); + parseKnowParaLang(predicatePersonDescriptor, argMultimap); + parseMajor(predicatePersonDescriptor, argMultimap); + parseName(predicatePersonDescriptor, argMultimap); + parseNric(predicatePersonDescriptor, argMultimap); + parsePastJob(predicatePersonDescriptor, argMultimap); + parsePhone(predicatePersonDescriptor, argMultimap); + parseRace(predicatePersonDescriptor, argMultimap); + parseSchool(predicatePersonDescriptor, argMultimap); + + return new FilterCommand(commandName, listName, predicatePersonDescriptor); + } + + /** + * parse Name field to the Predicate Descriptor if field exist + * + * @param predicatePersonDescriptor the predicate descriptor + * @param argMultimap the argMultimap contains value for each fields + */ + private void parseName(PredicatePersonDescriptor predicatePersonDescriptor, ArgumentMultimap argMultimap) { + requireNonNull(argMultimap); + requireNonNull(predicatePersonDescriptor); + if (argMultimap.getValue(PREFIX_NAME).isPresent()) { + predicatePersonDescriptor.setName(new HashSet<>(( + Arrays.asList(argMultimap.getValue(PREFIX_NAME).get().split("\\s+"))))); + } + } + + /** + * parse Phone field to the Predicate Descriptor if field exist + * + * @param predicatePersonDescriptor the predicate descriptor + * @param argMultimap the argMultimap contains value for each fields + */ + private void parsePhone(PredicatePersonDescriptor predicatePersonDescriptor, ArgumentMultimap argMultimap) { + requireNonNull(argMultimap); + requireNonNull(predicatePersonDescriptor); + if (argMultimap.getValue(PREFIX_PHONE).isPresent()) { + predicatePersonDescriptor.setPhone(new HashSet<>(( + Arrays.asList(argMultimap.getValue(PREFIX_PHONE).get().split("\\s+"))))); + } + } + + /** + * parse Email field to the Predicate Descriptor if field exist + * + * @param predicatePersonDescriptor the predicate descriptor + * @param argMultimap the argMultimap contains value for each fields + */ + private void parseEmail(PredicatePersonDescriptor predicatePersonDescriptor, ArgumentMultimap argMultimap) { + requireNonNull(argMultimap); + requireNonNull(predicatePersonDescriptor); + if (argMultimap.getValue(PREFIX_EMAIL).isPresent()) { + predicatePersonDescriptor.setEmail(new HashSet<>(( + Arrays.asList(argMultimap.getValue(PREFIX_EMAIL).get().split("\\s+"))))); + } + } + + /** + * parse Race field to the Predicate Descriptor if field exist + * + * @param predicatePersonDescriptor the predicate descriptor + * @param argMultimap the argMultimap contains value for each fields + */ + private void parseRace(PredicatePersonDescriptor predicatePersonDescriptor, ArgumentMultimap argMultimap) { + requireNonNull(argMultimap); + requireNonNull(predicatePersonDescriptor); + if (argMultimap.getValue(PREFIX_RACE).isPresent()) { + predicatePersonDescriptor.setRace(new HashSet<>(( + Arrays.asList(argMultimap.getValue(PREFIX_RACE).get().split("\\s+"))))); + } + } + + /** + * parse Address field to the Predicate Descriptor if field exist + * + * @param predicatePersonDescriptor the predicate descriptor + * @param argMultimap the argMultimap contains value for each fields + */ + private void parseAddress(PredicatePersonDescriptor predicatePersonDescriptor, ArgumentMultimap argMultimap) { + requireNonNull(argMultimap); + requireNonNull(predicatePersonDescriptor); + if (argMultimap.getValue(PREFIX_ADDRESS).isPresent()) { + predicatePersonDescriptor.setAddress(new HashSet<>(( + Arrays.asList(argMultimap.getValue(PREFIX_ADDRESS).get().split("\\s+"))))); + } + } + + /** + * parse School field to the Predicate Descriptor if field exist + * + * @param predicatePersonDescriptor the predicate descriptor + * @param argMultimap the argMultimap contains value for each fields + */ + private void parseSchool(PredicatePersonDescriptor predicatePersonDescriptor, ArgumentMultimap argMultimap) { + requireNonNull(argMultimap); + requireNonNull(predicatePersonDescriptor); + if (argMultimap.getValue(PREFIX_SCHOOL).isPresent()) { + predicatePersonDescriptor.setSchool(new HashSet<>(( + Arrays.asList(argMultimap.getValue(PREFIX_SCHOOL).get().split("\\s+"))))); + } + } + + /** + * parse Major field to the Predicate Descriptor if field exist + * + * @param predicatePersonDescriptor the predicate descriptor + * @param argMultimap the argMultimap contains value for each fields + */ + private void parseMajor(PredicatePersonDescriptor predicatePersonDescriptor, ArgumentMultimap argMultimap) { + requireNonNull(argMultimap); + requireNonNull(predicatePersonDescriptor); + if (argMultimap.getValue(PREFIX_MAJOR).isPresent()) { + predicatePersonDescriptor.setMajor(new HashSet<>(( + Arrays.asList(argMultimap.getValue(PREFIX_MAJOR).get().split("\\s+"))))); + } + } + + /** + * parse Gender field to the Predicate Descriptor if field exist + * + * @param predicatePersonDescriptor the predicate descriptor + * @param argMultimap the argMultimap contains value for each fields + */ + private void parseGender(PredicatePersonDescriptor predicatePersonDescriptor, ArgumentMultimap argMultimap) { + requireNonNull(argMultimap); + requireNonNull(predicatePersonDescriptor); + if (argMultimap.getValue(PREFIX_GENDER).isPresent()) { + predicatePersonDescriptor.setGender(new HashSet<>(( + Arrays.asList(argMultimap.getValue(PREFIX_GENDER).get().split("\\s+"))))); + } + } + + /** + * parse Grade field to the Predicate Descriptor if field exist + * + * @param predicatePersonDescriptor the predicate descriptor + * @param argMultimap the argMultimap contains value for each fields + * @throws ParseException the exception throws if parse value is not in format + */ + private void parseGrade(PredicatePersonDescriptor predicatePersonDescriptor, ArgumentMultimap argMultimap) + throws ParseException { + requireNonNull(argMultimap); + requireNonNull(predicatePersonDescriptor); + if (argMultimap.getValue(PREFIX_GRADE).isPresent()) { + List rangeList = Arrays.asList(argMultimap.getValue(PREFIX_GRADE).get().split(";")); + + if (!isValidValueRange(rangeList)) { + throw new ParseException(MESSAGE_INVALID_RANGE); + } + predicatePersonDescriptor.setGrade(new HashSet<>(rangeList)); + } + } + + /** + * parse Interview Q1 field to the Predicate Descriptor if field exist + * + * @param predicatePersonDescriptor the predicate descriptor + * @param argMultimap the argMultimap contains value for each fields + * @throws ParseException the exception throws if parse value is not in format + */ + private void parseInterviewQ1(PredicatePersonDescriptor predicatePersonDescriptor, ArgumentMultimap argMultimap) + throws ParseException { + requireNonNull(argMultimap); + requireNonNull(predicatePersonDescriptor); + if (argMultimap.getValue(PREFIX_INTERVIEWSCORESQ1).isPresent()) { + List rangeList = + Arrays.asList(argMultimap.getValue(PREFIX_INTERVIEWSCORESQ1).get().split(";")); + if (!isValidValueRange(rangeList)) { + throw new ParseException(MESSAGE_INVALID_RANGE); + } + predicatePersonDescriptor.setInterviewScoreQ1(new HashSet<>(rangeList)); + } + } + + /** + * parse Interview Q2 field to the Predicate Descriptor if field exist + * + * @param predicatePersonDescriptor the predicate descriptor + * @param argMultimap the argMultimap contains value for each fields + * @throws ParseException the exception throws if parse value is not in format + */ + private void parseInterviewQ2(PredicatePersonDescriptor predicatePersonDescriptor, ArgumentMultimap argMultimap) + throws ParseException { + requireNonNull(argMultimap); + requireNonNull(predicatePersonDescriptor); + if (argMultimap.getValue(PREFIX_INTERVIEWSCORESQ2).isPresent()) { + List rangeList = + Arrays.asList(argMultimap.getValue(PREFIX_INTERVIEWSCORESQ2).get().split(";")); + if (!isValidValueRange(rangeList)) { + throw new ParseException(MESSAGE_INVALID_RANGE); + } + predicatePersonDescriptor.setInterviewScoreQ2(new HashSet<>(rangeList)); + } + } + + /** + * parse Interview Q3 field to the Predicate Descriptor if field exist + * + * @param predicatePersonDescriptor the predicate descriptor + * @param argMultimap the argMultimap contains value for each fields + * @throws ParseException the exception throws if parse value is not in format + */ + private void parseInterviewQ3(PredicatePersonDescriptor predicatePersonDescriptor, ArgumentMultimap argMultimap) + throws ParseException { + requireNonNull(argMultimap); + requireNonNull(predicatePersonDescriptor); + if (argMultimap.getValue(PREFIX_INTERVIEWSCORESQ3).isPresent()) { + List rangeList = + Arrays.asList(argMultimap.getValue(PREFIX_INTERVIEWSCORESQ3).get().split(";")); + if (!isValidValueRange(rangeList)) { + throw new ParseException(MESSAGE_INVALID_RANGE); + } + predicatePersonDescriptor.setInterviewScoreQ3(new HashSet<>(rangeList)); + } + } + + /** + * parse Interview Q4 field to the Predicate Descriptor if field exist + * + * @param predicatePersonDescriptor the predicate descriptor + * @param argMultimap the argMultimap contains value for each fields + * @throws ParseException the exception throws if parse value is not in format + */ + private void parseInterviewQ4(PredicatePersonDescriptor predicatePersonDescriptor, ArgumentMultimap argMultimap) + throws ParseException { + requireNonNull(argMultimap); + requireNonNull(predicatePersonDescriptor); + if (argMultimap.getValue(PREFIX_INTERVIEWSCORESQ4).isPresent()) { + List rangeList = + Arrays.asList(argMultimap.getValue(PREFIX_INTERVIEWSCORESQ4).get().split(";")); + if (!isValidValueRange(rangeList)) { + throw new ParseException(MESSAGE_INVALID_RANGE); + } + predicatePersonDescriptor.setInterviewScoreQ4(new HashSet<>(rangeList)); + } + } + + /** + * parse Interview Q5 field to the Predicate Descriptor if field exist + * + * @param predicatePersonDescriptor the predicate descriptor + * @param argMultimap the argMultimap contains value for each fields + * @throws ParseException the exception throws if parse value is not in format + */ + private void parseInterviewQ5(PredicatePersonDescriptor predicatePersonDescriptor, ArgumentMultimap argMultimap) + throws ParseException { + requireNonNull(argMultimap); + requireNonNull(predicatePersonDescriptor); + if (argMultimap.getValue(PREFIX_INTERVIEWSCORESQ5).isPresent()) { + List rangeList = + Arrays.asList(argMultimap.getValue(PREFIX_INTERVIEWSCORESQ5).get().split(";")); + if (!isValidValueRange(rangeList)) { + throw new ParseException(MESSAGE_INVALID_RANGE); + } + predicatePersonDescriptor.setInterviewScoreQ5(new HashSet<>(rangeList)); + } + } + + /** + * parse Nric field to the Predicate Descriptor if field exist + * + * @param predicatePersonDescriptor the predicate descriptor + * @param argMultimap the argMultimap contains value for each fields + */ + private void parseNric(PredicatePersonDescriptor predicatePersonDescriptor, ArgumentMultimap argMultimap) { + requireNonNull(argMultimap); + requireNonNull(predicatePersonDescriptor); + if (argMultimap.getValue(PREFIX_NRIC).isPresent()) { + predicatePersonDescriptor.setNric(new HashSet<>(( + Arrays.asList(argMultimap.getValue(PREFIX_NRIC).get().split("\\s+"))))); + } + } + + /** + * parse Past Job field to the Predicate Descriptor if field exist + * + * @param predicatePersonDescriptor the predicate descriptor + * @param argMultimap the argMultimap contains value for each fields + */ + private void parsePastJob(PredicatePersonDescriptor predicatePersonDescriptor, ArgumentMultimap argMultimap) { + requireNonNull(argMultimap); + requireNonNull(predicatePersonDescriptor); + if (argMultimap.getValue(PREFIX_PASTJOB).isPresent()) { + predicatePersonDescriptor.setPastJobs(new HashSet<>(( + Arrays.asList(argMultimap.getValue(PREFIX_PASTJOB).get().split("\\s+"))))); + } + } + + /** + * parse Jobs Apply field to the Predicate Descriptor if field exist + * + * @param predicatePersonDescriptor the predicate descriptor + * @param argMultimap the argMultimap contains value for each fields + */ + private void parseJobsApply(PredicatePersonDescriptor predicatePersonDescriptor, ArgumentMultimap argMultimap) { + requireNonNull(argMultimap); + requireNonNull(predicatePersonDescriptor); + if (argMultimap.getValue(PREFIX_JOBSAPPLY).isPresent()) { + predicatePersonDescriptor.setJobsApply(new HashSet<>(( + Arrays.asList(argMultimap.getValue(PREFIX_JOBSAPPLY).get().split("\\s+"))))); + } + } + + /** + * parse Known Programming Language field to the Predicate Descriptor if field exist + * + * @param predicatePersonDescriptor the predicate descriptor + * @param argMultimap the argMultimap contains value for each fields + */ + private void parseKnowParaLang(PredicatePersonDescriptor predicatePersonDescriptor, ArgumentMultimap argMultimap) { + requireNonNull(argMultimap); + requireNonNull(predicatePersonDescriptor); + if (argMultimap.getValue(PREFIX_KNOWNPROGLANG).isPresent()) { + predicatePersonDescriptor.setKnownProgLangs(new HashSet<>(( + Arrays.asList(argMultimap.getValue(PREFIX_KNOWNPROGLANG).get().split("\\s+"))))); + } + } +} diff --git a/src/main/java/seedu/address/logic/parser/FindCommandParser.java b/src/main/java/seedu/address/logic/parser/FindCommandParser.java deleted file mode 100644 index b186a967cb94..000000000000 --- a/src/main/java/seedu/address/logic/parser/FindCommandParser.java +++ /dev/null @@ -1,33 +0,0 @@ -package seedu.address.logic.parser; - -import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; - -import java.util.Arrays; - -import seedu.address.logic.commands.FindCommand; -import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.person.NameContainsKeywordsPredicate; - -/** - * Parses input arguments and creates a new FindCommand object - */ -public class FindCommandParser implements Parser { - - /** - * Parses the given {@code String} of arguments in the context of the FindCommand - * and returns an FindCommand object for execution. - * @throws ParseException if the user input does not conform the expected format - */ - public FindCommand parse(String args) throws ParseException { - String trimmedArgs = args.trim(); - if (trimmedArgs.isEmpty()) { - throw new ParseException( - String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE)); - } - - String[] nameKeywords = trimmedArgs.split("\\s+"); - - return new FindCommand(new NameContainsKeywordsPredicate(Arrays.asList(nameKeywords))); - } - -} diff --git a/src/main/java/seedu/address/logic/parser/ImportResumesCommandParser.java b/src/main/java/seedu/address/logic/parser/ImportResumesCommandParser.java new file mode 100644 index 000000000000..330137e8f2b1 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/ImportResumesCommandParser.java @@ -0,0 +1,119 @@ +package seedu.address.logic.parser; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; + +import seedu.address.logic.commands.ImportResumesCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.person.Address; +import seedu.address.model.person.Email; +import seedu.address.model.person.Gender; +import seedu.address.model.person.Grade; +import seedu.address.model.person.InterviewScores; +import seedu.address.model.person.JobsApply; +import seedu.address.model.person.KnownProgLang; +import seedu.address.model.person.Major; +import seedu.address.model.person.Name; +import seedu.address.model.person.Nric; +import seedu.address.model.person.PastJob; +import seedu.address.model.person.Person; +import seedu.address.model.person.Phone; +import seedu.address.model.person.Race; +import seedu.address.model.person.School; +import seedu.address.model.tag.Tag; + +/** + * Parses input arguments and creates a new ImportResumesCommand object + */ +public class ImportResumesCommandParser implements Parser { + + public static final String FILE_NOT_FOUND_MESSAGE = "The specified file cannot be found."; + public static final String READER_ERROR_MESSAGE = "There is a problem reading your file"; + public static final String INVALID_DIRECTORY_MESSAGE = "The given path does not specify a folder."; + public static final String EMPTY_DIRECTORY_MESSAGE = "The given path does not contain any files."; + + /** + * Parses the given {@code String} of arguments in the context of the ImportResumesCommand + * and returns an ImportResumesCommand object for execution. + * + * @throws ParseException if the user input does not conform the expected format + */ + public ImportResumesCommand parse(String args) throws ParseException { + File folder = new File(args.trim()); + File[] filesList = folder.listFiles(); + + if (filesList == null) { + throw new ParseException(INVALID_DIRECTORY_MESSAGE); + } else if (filesList.length == 0) { + throw new ParseException(EMPTY_DIRECTORY_MESSAGE); + } + + Set peopleToAdd = new HashSet<>(); + for (int i = 0; i < filesList.length; i++) { + Person currentPerson = parseOnePerson(filesList[i]); + peopleToAdd.add(currentPerson); + } + + return new ImportResumesCommand(peopleToAdd); + } + + /** + * Opens the given (@code File), and parses its contents in the context of the + * ImportResumesCommand. It returns a Person object. + * + * @throws ParseException if the user input does not conform the expected format + */ + private Person parseOnePerson(File resumeFile) throws ParseException { + Person person = null; + try { + BufferedReader reader = new BufferedReader(new FileReader(resumeFile)); + + Name name = ParserUtil.parseName(reader.readLine()); + Phone phone = ParserUtil.parsePhone(reader.readLine()); + Email email = ParserUtil.parseEmail(reader.readLine()); + Nric nric = ParserUtil.parseNric(reader.readLine()); + Gender gender = ParserUtil.parseGender(reader.readLine()); + Race race = ParserUtil.parseRace(reader.readLine()); + Address address = ParserUtil.parseAddress(reader.readLine()); + School school = ParserUtil.parseSchool(reader.readLine()); + Major major = ParserUtil.parseMajor(reader.readLine()); + Grade grade = ParserUtil.parseGrade(reader.readLine()); + + String langString = reader.readLine(); + String[] langArray = langString.split(","); + Collection langsStringList = Arrays.asList(langArray); + Set knownProgLangsList = ParserUtil.parseKnownProgLangs(langsStringList); + + String pastJobsString = reader.readLine(); + String[] jobsArray = pastJobsString.split(","); + Collection jobsStringList = Arrays.asList(jobsArray); + Set pastjobList = ParserUtil.parsePastJobs(jobsStringList); + + String jobsApplyString = reader.readLine(); + String[] jobsApplyArray = jobsApplyString.split(","); + Collection jobsApplyStringList = Arrays.asList(jobsApplyArray); + Set jobsApplyList = ParserUtil.parseJobsApply(jobsApplyStringList); + + InterviewScores interviewScores = ParserUtil.parseInterviewScores(reader.readLine()); + //always empty + Set tagList = new HashSet<>(); + + person = new Person(name, phone, email, nric, gender, race, address, school, major, grade, + knownProgLangsList, pastjobList, jobsApplyList, interviewScores, tagList); + + } catch (FileNotFoundException e) { + throw new ParseException(FILE_NOT_FOUND_MESSAGE); + } catch (IOException e) { + throw new ParseException(READER_ERROR_MESSAGE); + } + + return person; + } +} diff --git a/src/main/java/seedu/address/logic/parser/MovePeopleCommandParser.java b/src/main/java/seedu/address/logic/parser/MovePeopleCommandParser.java new file mode 100644 index 000000000000..4451b87f2ff4 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/MovePeopleCommandParser.java @@ -0,0 +1,88 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_JOBNAME; + +import java.util.ArrayList; +import java.util.Arrays; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.MovePeopleCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.job.JobListName; +import seedu.address.model.job.JobName; + +/** + * Parses input arguments and creates a new AddCommand object + */ +public class MovePeopleCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the AddPersonToJobCommand + * and returns an AddPersonToJobCommand object for execution. + * + * @throws ParseException if the user input does not conform the expected format + */ + public MovePeopleCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_JOBNAME); + + JobListName to; + JobListName from = JobListName.STUB; + ArrayList indexes = new ArrayList<>(); + JobName toAdd; + String fromString; + String indexString; + + to = ParserUtil.parseJobListName(args.split("\\b\\s")[0].trim()); + + if (to == JobListName.EMPTY) { + throw new ParseException(MovePeopleCommand.MESSAGE_NO_DESTINATION + + String.format(MESSAGE_INVALID_COMMAND_FORMAT, MovePeopleCommand.MESSAGE_USAGE)); + } + + try { + fromString = args.split("\\b\\s")[1].trim(); + } catch (Exception e) { + throw new ParseException(MovePeopleCommand.MESSAGE_NO_SOURCE + + String.format(MESSAGE_INVALID_COMMAND_FORMAT, MovePeopleCommand.MESSAGE_USAGE)); + } + + if (fromString.matches("[A-z]+")) { + from = ParserUtil.parseJobListName(fromString); + try { + indexString = args.split("\\b\\s")[2].trim(); + } catch (Exception e) { + throw new ParseException(MovePeopleCommand.MESSAGE_NO_INDEX + + String.format(MESSAGE_INVALID_COMMAND_FORMAT, MovePeopleCommand.MESSAGE_USAGE)); + } + toAdd = null; + } else { + try { + indexString = args.split("\\b\\s")[1].trim(); + } catch (Exception e) { + throw new ParseException(MovePeopleCommand.MESSAGE_NO_INDEX + + String.format(MESSAGE_INVALID_COMMAND_FORMAT, MovePeopleCommand.MESSAGE_USAGE)); + } + try { + toAdd = ParserUtil.parseJobName(argMultimap.getValue(PREFIX_JOBNAME).get()); + } catch (Exception e) { + toAdd = null; + } + } + + ArrayList numbers = new ArrayList<>(Arrays.asList(indexString.split("[,\\s]+"))); + for (int i = 0; i < numbers.size(); i++) { + try { + indexes.add(ParserUtil.parseIndex(numbers.get(i))); + } catch (ParseException pe) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, MovePeopleCommand.MESSAGE_BAD_INDEX), pe); + } + } + + + return new MovePeopleCommand(to, from, indexes, toAdd); + } + +} diff --git a/src/main/java/seedu/address/logic/parser/ParserUtil.java b/src/main/java/seedu/address/logic/parser/ParserUtil.java index b117acb9c55b..f12c0e821a08 100644 --- a/src/main/java/seedu/address/logic/parser/ParserUtil.java +++ b/src/main/java/seedu/address/logic/parser/ParserUtil.java @@ -1,30 +1,64 @@ package seedu.address.logic.parser; import static java.util.Objects.requireNonNull; +import static seedu.address.model.interviews.Interviews.HOUR; +import static seedu.address.model.interviews.Interviews.MILLISECOND; +import static seedu.address.model.interviews.Interviews.MINUTE; +import static seedu.address.model.interviews.Interviews.SECOND; +import static seedu.address.model.job.JobListName.APPLICANT_NAME; +import static seedu.address.model.job.JobListName.APPLICANT_PREFIX; +import static seedu.address.model.job.JobListName.INTERVIEW_NAME; +import static seedu.address.model.job.JobListName.INTERVIEW_PREFIX; +import static seedu.address.model.job.JobListName.KIV_NAME; +import static seedu.address.model.job.JobListName.KIV_PREFIX; +import static seedu.address.model.job.JobListName.SHORTLIST_NAME; +import static seedu.address.model.job.JobListName.SHORTLIST_PREFIX; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Calendar; import java.util.Collection; +import java.util.GregorianCalendar; import java.util.HashSet; +import java.util.List; import java.util.Set; import seedu.address.commons.core.index.Index; import seedu.address.commons.util.StringUtil; import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.job.JobListName; +import seedu.address.model.job.JobName; import seedu.address.model.person.Address; import seedu.address.model.person.Email; +import seedu.address.model.person.Gender; +import seedu.address.model.person.Grade; +import seedu.address.model.person.InterviewScores; +import seedu.address.model.person.JobsApply; +import seedu.address.model.person.KnownProgLang; +import seedu.address.model.person.Major; import seedu.address.model.person.Name; +import seedu.address.model.person.Nric; +import seedu.address.model.person.PastJob; import seedu.address.model.person.Phone; +import seedu.address.model.person.Race; +import seedu.address.model.person.School; import seedu.address.model.tag.Tag; + /** * Contains utility methods used for parsing strings in the various *Parser classes. */ public class ParserUtil { public static final String MESSAGE_INVALID_INDEX = "Index is not a non-zero unsigned integer."; + public static final String MESSAGE_INVALID_MAX_INTERVIEWS_A_DAY = + "Maximum number of interviews a day is not a non-zero unsigned integer."; + public static final String MESSAGE_INVALID_DATE = "Not a valid date."; /** * Parses {@code oneBasedIndex} into an {@code Index} and returns it. Leading and trailing whitespaces will be * trimmed. + * * @throws ParseException if the specified index is invalid (not non-zero unsigned integer). */ public static Index parseIndex(String oneBasedIndex) throws ParseException { @@ -35,6 +69,31 @@ public static Index parseIndex(String oneBasedIndex) throws ParseException { return Index.fromOneBased(Integer.parseInt(trimmedIndex)); } + /** + * Parses a {@code String value} into a {@code JobListName}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code value} is invalid. + */ + public static JobListName parseJobListName(String value) throws ParseException { + requireNonNull(value); + String trimmedName = value.trim().toLowerCase(); + if (!JobListName.isValidJobListName(trimmedName)) { + throw new ParseException(JobListName.MESSAGE_CONSTRAINTS); + } else if (value.equals(APPLICANT_NAME) || value.equals(APPLICANT_PREFIX)) { + return JobListName.APPLICANT; + } else if (value.equals(KIV_NAME) || value.equals(KIV_PREFIX)) { + return JobListName.KIV; + } else if (value.equals(INTERVIEW_NAME) || value.equals(INTERVIEW_PREFIX)) { + return JobListName.INTERVIEW; + } else if (value.equals(SHORTLIST_NAME) || value.equals(SHORTLIST_PREFIX)) { + return JobListName.SHORTLIST; + } else { + return JobListName.EMPTY; + } + + } + /** * Parses a {@code String name} into a {@code Name}. * Leading and trailing whitespaces will be trimmed. @@ -80,6 +139,81 @@ public static Address parseAddress(String address) throws ParseException { return new Address(trimmedAddress); } + /** + * Parses a {@code String gender} into an {@code Gender}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code gender} is invalid. + */ + public static Gender parseGender(String gender) throws ParseException { + requireNonNull(gender); + String trimmedGender = gender.trim(); + if (!Gender.isValidGender(trimmedGender)) { + throw new ParseException(Gender.MESSAGE_CONSTRAINTS); + } + return new Gender(trimmedGender); + } + + /** + * Parses a {@code String grade} into an {@code Grade}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code grade} is invalid. + */ + public static Grade parseGrade(String grade) throws ParseException { + requireNonNull(grade); + String trimmedGrade = grade.trim(); + if (!Grade.isValidGrade(trimmedGrade)) { + throw new ParseException(Grade.MESSAGE_CONSTRAINTS); + } + return new Grade(trimmedGrade); + } + + /** + * Parses a {@code String nric} into an {@code Nric}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code nric} is invalid. + */ + public static Nric parseNric(String nric) throws ParseException { + requireNonNull(nric); + String trimmedNric = nric.trim(); + if (!Nric.isValidNric(trimmedNric)) { + throw new ParseException(Nric.MESSAGE_CONSTRAINTS); + } + return new Nric(trimmedNric); + } + + /** + * Parses a {@code String interviewScores} into an {@code InterviewScores}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code interviewScores} is invalid. + */ + public static InterviewScores parseInterviewScores(String interviewScores) throws ParseException { + requireNonNull(interviewScores); + String trimmedInterviewScores = interviewScores.trim(); + if (!InterviewScores.isValidInterviewScores(trimmedInterviewScores)) { + throw new ParseException(InterviewScores.MESSAGE_CONSTRAINTS); + } + return new InterviewScores(trimmedInterviewScores); + } + + /** + * Parses a {@code String major} into an {@code Major}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code major} is invalid. + */ + public static Major parseMajor(String major) throws ParseException { + requireNonNull(major); + String trimmedMajor = major.trim(); + if (!Major.isValidMajor(trimmedMajor)) { + throw new ParseException(Major.MESSAGE_CONSTRAINTS); + } + return new Major(trimmedMajor); + } + /** * Parses a {@code String email} into an {@code Email}. * Leading and trailing whitespaces will be trimmed. @@ -95,6 +229,118 @@ public static Email parseEmail(String email) throws ParseException { return new Email(trimmedEmail); } + /** + * Parses a {@code String race} into an {@code Race}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code race} is invalid. + */ + public static Race parseRace(String race) throws ParseException { + requireNonNull(race); + String trimmedRace = race.trim(); + if (!Race.isValidRace(trimmedRace)) { + throw new ParseException(Race.MESSAGE_CONSTRAINTS); + } + return new Race(trimmedRace); + } + + /** + * Parses a {@code String school} into an {@code School}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code school} is invalid. + */ + + public static School parseSchool(String school) throws ParseException { + requireNonNull(school); + String trimmedSchool = school.trim(); + if (!School.isValidSchool(trimmedSchool)) { + throw new ParseException(School.MESSAGE_CONSTRAINTS); + } + return new School(trimmedSchool); + } + + /** + * Parses a {@code String knownproglang} into a {@code KnownProgLang}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code knownproglang} is invalid. + */ + public static KnownProgLang parseKnownProgLang(String knownProgLang) throws ParseException { + requireNonNull(knownProgLang); + String trimmedKnownProgLang = knownProgLang.trim(); + if (!KnownProgLang.isValidKnownProgLang(trimmedKnownProgLang)) { + throw new ParseException(KnownProgLang.MESSAGE_CONSTRAINTS); + } + return new KnownProgLang(trimmedKnownProgLang); + } + + /** + * Parses {@code Collection knownproglangs} into a {@code Set}. + */ + public static Set parseKnownProgLangs(Collection knownProgLangs) throws ParseException { + requireNonNull(knownProgLangs); + final Set knownProgLangSet = new HashSet<>(); + for (String knownProgLangName : knownProgLangs) { + knownProgLangSet.add(parseKnownProgLang(knownProgLangName)); + } + return knownProgLangSet; + } + + /** + * Parses a {@code String jobsApply} into a {@code JobsApply}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code jobsApply} is invalid. + */ + public static JobsApply parseJobsApply(String jobsApply) throws ParseException { + requireNonNull(jobsApply); + String trimmedJobsApply = jobsApply.trim(); + if (!JobsApply.isValidJobsApply(trimmedJobsApply)) { + throw new ParseException(JobsApply.MESSAGE_CONSTRAINTS); + } + return new JobsApply(trimmedJobsApply); + } + + /** + * Parses {@code Collection jobsApply} into a {@code Set}. + */ + public static Set parseJobsApply(Collection jobsApply) throws ParseException { + requireNonNull(jobsApply); + final Set jobsApplySet = new HashSet<>(); + for (String jobsApplyName : jobsApply) { + jobsApplySet.add(parseJobsApply(jobsApplyName)); + } + return jobsApplySet; + } + + /** + * Parses a {@code String pastjob} into a {@code PastJob}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code pastjob} is invalid. + */ + public static PastJob parsePastJob(String pastjob) throws ParseException { + requireNonNull(pastjob); + String trimmedPastJob = pastjob.trim(); + if (!PastJob.isValidPastJob(trimmedPastJob)) { + throw new ParseException(PastJob.MESSAGE_CONSTRAINTS); + } + return new PastJob(trimmedPastJob); + } + + /** + * Parses {@code Collection pastjobs} into a {@code Set}. + */ + public static Set parsePastJobs(Collection pastjobs) throws ParseException { + requireNonNull(pastjobs); + final Set pastjobSet = new HashSet<>(); + for (String pastjobName : pastjobs) { + pastjobSet.add(parsePastJob(pastjobName)); + } + return pastjobSet; + } + /** * Parses a {@code String tag} into a {@code Tag}. * Leading and trailing whitespaces will be trimmed. @@ -121,4 +367,167 @@ public static Set parseTags(Collection tags) throws ParseException } return tagSet; } + + /** + * Parses a {@code String name} into a {@code JobName}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code name} is invalid. + */ + public static JobName parseJobName(String name) throws ParseException { + requireNonNull(name); + String trimmedName = name.trim(); + if (!JobName.isValidName(trimmedName)) { + throw new ParseException(JobName.MESSAGE_CONSTRAINTS); + } + return new JobName(trimmedName); + } + + /** + * Parses a {@code String name} into a {@code int}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code maxInterviewsADay} is invalid. + */ + public static int parseMaxInterviewsADay(String maxInterviewsADay) throws ParseException { + requireNonNull(maxInterviewsADay); + String trimmedMaxInterviewsADay = maxInterviewsADay.trim(); + if (!StringUtil.isNonZeroUnsignedInteger(trimmedMaxInterviewsADay)) { + throw new ParseException(MESSAGE_INVALID_MAX_INTERVIEWS_A_DAY); + } + return Integer.parseInt(trimmedMaxInterviewsADay); + } + + /** + * Parses a {@code String name} into a {@code List}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code blockOutDates} is invalid. + */ + public static List parseBlockOutDates(String blockOutDates) throws ParseException { + requireNonNull(blockOutDates); + List result = new ArrayList<>(); + String trimmedBlockOutDates = blockOutDates.trim(); + String[] stringArray = trimmedBlockOutDates.split(","); + for (String date : stringArray) { + + int day = Integer.parseInt(date.substring(0, 2)); + int month = Integer.parseInt(date.substring(3, 5)); + int year = Integer.parseInt(date.substring(6, 10)); + Calendar currentCalendar = new GregorianCalendar(year, month - 1, day, HOUR, MINUTE, SECOND); + currentCalendar.set(Calendar.MILLISECOND, MILLISECOND); + + if (isValidDateRange(date)) { + int endDay = Integer.parseInt(date.substring(13, 15)); + int endMonth = Integer.parseInt(date.substring(16, 18)); + int endYear = Integer.parseInt(date.substring(19, 23)); + Calendar endCalendar = new GregorianCalendar(endYear, endMonth - 1, endDay, HOUR, MINUTE, SECOND); + endCalendar.set(Calendar.MILLISECOND, MILLISECOND); + while (currentCalendar.compareTo(endCalendar) < 0) { + result.add(currentCalendar); + currentCalendar = (Calendar) currentCalendar.clone(); + currentCalendar.add(Calendar.DATE, 1); + } + result.add(endCalendar); + } else if (isValidDate(date)) { + result.add(currentCalendar); + } else { + throw new ParseException(MESSAGE_INVALID_DATE); + } + } + return result; + } + + /** + * Returns a boolean testing for validity of date. + */ + protected static boolean isValidDate(String date) { + if (!date.matches("\\d\\d/\\d\\d/\\d\\d\\d\\d")) { + return false; + } + + int day = Integer.parseInt(date.substring(0, 2)); + int month = Integer.parseInt(date.substring(3, 5)); + int year = Integer.parseInt(date.substring(6, 10)); + + ArrayList monthsWith30Days = new ArrayList<>(Arrays.asList(4, 6, 9, 11)); + + //Leap year check + if (month == 2) { + if (isLeapYear(year)) { + return day <= 29; + } else { + return day <= 28; + } + } + + //Month check first + if (month > 12) { + return false; + } + + if (day < 1 || day > 31) { + return false; + } else if (monthsWith30Days.contains(month) && day > 30) { + return false; + } + + return true; + } + + /** + * Checks if year is a leap year. + */ + protected static boolean isLeapYear(int year) { + if ((year % 4 == 0) && (year % 100 != 0)) { + return true; + } else if ((year % 4 == 0) && (year % 100 == 0) && (year % 400 == 0)) { + return true; + } else { + return false; + } + } + + /** + * Check if the date range provided is valid. + */ + protected static boolean isValidValueRange(String range) { + requireNonNull(range); + String trimedRange = range.trim(); + String[] bounds = trimedRange.split("-"); + if (bounds.length != 2) { + return false; + } + String trimedLower = bounds[0].trim(); + String trimedUpper = bounds[1].trim(); + String floatFarmat = "\\d+" + "." + "\\d+"; + String integerFarmat = "\\d+"; + boolean isValidLower = trimedLower.matches(floatFarmat) || trimedLower.matches(integerFarmat); + boolean isValidUpper = trimedUpper.matches(floatFarmat) || trimedUpper.matches(integerFarmat); + return isValidLower && isValidUpper; + } + + /** + * Check if the value range provided is valid. Takes a list of string + */ + protected static boolean isValidValueRange(List rangeList) { + requireNonNull(rangeList); + for (String range : rangeList) { + if (!isValidValueRange(range)) { + return false; + } + } + return true; + } + + /** + * Check if the date range provided is valid. + */ + protected static boolean isValidDateRange(String dateRange) { + if (!dateRange.matches("\\d\\d/\\d\\d/\\d\\d\\d\\d - \\d\\d/\\d\\d/\\d\\d\\d\\d")) { + return false; + } + return isValidDate(dateRange.substring(0, 10)) && isValidDate(dateRange.substring(13, 23)); + } + } diff --git a/src/main/java/seedu/address/logic/parser/RemoveFromListCommandParser.java b/src/main/java/seedu/address/logic/parser/RemoveFromListCommandParser.java new file mode 100644 index 000000000000..02c5c5f1be48 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/RemoveFromListCommandParser.java @@ -0,0 +1,56 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import java.util.ArrayList; +import java.util.Arrays; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.RemoveFromListCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.job.JobListName; + +/** + * Parses input arguments and creates a new AddCommand object + */ +public class RemoveFromListCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the AddPersonToJobCommand + * and returns an AddPersonToJobCommand object for execution. + * + * @throws ParseException if the user input does not conform the expected format + */ + public RemoveFromListCommand parse(String args) throws ParseException { + + JobListName chosenList; + ArrayList indexes = new ArrayList<>(); + + try { + chosenList = ParserUtil.parseJobListName(args.split("\\b\\s")[0].trim()); + } catch (Exception e) { + throw new ParseException(RemoveFromListCommand.MESSAGE_NO_LIST_NAME + "\n" + + RemoveFromListCommand.MESSAGE_USAGE); + } + + try { + String indexString = args.split("\\b\\s")[1].trim(); + ArrayList numbers = new ArrayList<>(Arrays.asList(indexString.split("[,\\s]+"))); + for (int i = 0; i < numbers.size(); i++) { + try { + indexes.add(ParserUtil.parseIndex(numbers.get(i))); + } catch (ParseException pe) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, RemoveFromListCommand.MESSAGE_BAD_INDEX), pe); + } + } + } catch (Exception e) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, RemoveFromListCommand.MESSAGE_USAGE), e); + } + + + return new RemoveFromListCommand(chosenList, indexes); + } + +} diff --git a/src/main/java/seedu/address/logic/parser/SetBlockOutDatesCommandParser.java b/src/main/java/seedu/address/logic/parser/SetBlockOutDatesCommandParser.java new file mode 100644 index 000000000000..f4345b68bbee --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/SetBlockOutDatesCommandParser.java @@ -0,0 +1,29 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import java.util.Calendar; +import java.util.List; + +import seedu.address.logic.commands.SetBlockOutDatesCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a new SetBlockOutDatesCommand object. + */ +public class SetBlockOutDatesCommandParser implements Parser { + /** + * Parses the given {@code String} of arguments in the context of the SetMaxInterviewsADayCommand + * and returns an SetMaxInterviewsADayCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public SetBlockOutDatesCommand parse(String args) throws ParseException { + try { + List value = ParserUtil.parseBlockOutDates(args); + return new SetBlockOutDatesCommand(value); + } catch (ParseException pe) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, SetBlockOutDatesCommand.MESSAGE_USAGE), pe); + } + } +} diff --git a/src/main/java/seedu/address/logic/parser/SetMaxInterviewsADayCommandParser.java b/src/main/java/seedu/address/logic/parser/SetMaxInterviewsADayCommandParser.java new file mode 100644 index 000000000000..c68105251286 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/SetMaxInterviewsADayCommandParser.java @@ -0,0 +1,27 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.address.logic.commands.SetMaxInterviewsADayCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a new SetMaxInterviewsADayCommand object + */ +public class SetMaxInterviewsADayCommandParser implements Parser { + /** + * Parses the given {@code String} of arguments in the context of the SetMaxInterviewsADayCommand + * and returns an SetMaxInterviewsADayCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public SetMaxInterviewsADayCommand parse(String args) throws ParseException { + try { + int value = ParserUtil.parseMaxInterviewsADay(args); + return new SetMaxInterviewsADayCommand(value); + } catch (ParseException pe) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, SetMaxInterviewsADayCommand.MESSAGE_USAGE), pe); + } + } + +} diff --git a/src/main/java/seedu/address/model/AddressBook.java b/src/main/java/seedu/address/model/AddressBook.java index 30557cf81ee7..c58558321e9d 100644 --- a/src/main/java/seedu/address/model/AddressBook.java +++ b/src/main/java/seedu/address/model/AddressBook.java @@ -2,21 +2,33 @@ import static java.util.Objects.requireNonNull; +import java.util.Calendar; import java.util.List; import javafx.beans.InvalidationListener; import javafx.collections.ObservableList; +import javafx.collections.transformation.FilteredList; import seedu.address.commons.util.InvalidationListenerManager; +import seedu.address.model.interviews.Interviews; +import seedu.address.model.job.Job; +import seedu.address.model.job.JobListName; +import seedu.address.model.job.JobName; +import seedu.address.model.job.UniqueJobList; import seedu.address.model.person.Person; +import seedu.address.model.person.UniqueNricMap; import seedu.address.model.person.UniquePersonList; + /** * Wraps all data at the address-book level * Duplicates are not allowed (by .isSamePerson comparison) */ public class AddressBook implements ReadOnlyAddressBook { + private final UniqueNricMap nrics; private final UniquePersonList persons; + private final UniqueJobList jobs; + private final Interviews interviews; private final InvalidationListenerManager invalidationListenerManager = new InvalidationListenerManager(); /* @@ -28,9 +40,13 @@ public class AddressBook implements ReadOnlyAddressBook { */ { persons = new UniquePersonList(); + nrics = new UniqueNricMap(); + jobs = new UniqueJobList(); + interviews = new Interviews(); } - public AddressBook() {} + public AddressBook() { + } /** * Creates an AddressBook using the Persons in the {@code toBeCopied} @@ -48,9 +64,23 @@ public AddressBook(ReadOnlyAddressBook toBeCopied) { */ public void setPersons(List persons) { this.persons.setPersons(persons); + this.nrics.setNricMap(persons); + indicateModified(); + } + + public void setInterviews(Interviews interviews) { + this.interviews.setInterviews(interviews); + } + + public void setJobs(List jobs) { + this.jobs.setJobs(jobs); indicateModified(); } + public void setBlockOutDates(List blockOutDates) { + this.interviews.setBlockOutDates(blockOutDates); + } + /** * Resets the existing data of this {@code AddressBook} with {@code newData}. */ @@ -58,6 +88,8 @@ public void resetData(ReadOnlyAddressBook newData) { requireNonNull(newData); setPersons(newData.getPersonList()); + setInterviews(newData.getInterviews()); + setJobs(newData.getJobList()); } //// person-level operations @@ -76,9 +108,111 @@ public boolean hasPerson(Person person) { */ public void addPerson(Person p) { persons.add(p); + nrics.add(p.getNric(), p); + indicateModified(); + } + + /** + * Adds a person to the job. + * The person must not already exist in the job. + * Adds to the first list + */ + public void addPersonToJob(Person person, Job toAdd, JobListName list) { + Job job = getJob(toAdd.getName()); + int destination; + switch (list) { + case APPLICANT: + destination = 0; + break; + case KIV: + destination = 1; + break; + case INTERVIEW: + destination = 2; + break; + case SHORTLIST: + destination = 3; + break; + default: + destination = 0; + } + job.add(person, destination); + this.jobs.setJob(job, job); + indicateModified(); + } + + /** + * Adds a person to the job. + * The person must not already exist in the job. + * Adds to the first list + * This version directly adds from job + */ + public void addFilteredListToJob(FilteredList filteredPersons, JobName jobName, JobListName to) { + Job job = jobs.getJob(jobName); + switch(to) { + case APPLICANT: + job.addFilteredList(filteredPersons, 0); + break; + case KIV: + job.addFilteredList(filteredPersons, 1); + break; + case INTERVIEW: + job.addFilteredList(filteredPersons, 2); + break; + case SHORTLIST: + job.addFilteredList(filteredPersons, 3); + break; + default: + job.addFilteredList(filteredPersons, 0); + break; + } + this.jobs.setJob(job, job); indicateModified(); } + /** + * Retrieves a job in the addressbook + */ + public Job getJob(JobName jobName) { + return jobs.getJob(jobName); + } + + /** + * Adds a job to the address book. + * The job must not already exist in the address book. + */ + public void addJob(Job j) { + jobs.add(j); + indicateModified(); + } + + /** + * Deletes a job in the address book. + * The job must exist in the address book. + */ + public void deleteJob(Job j) { + jobs.remove(j); + indicateModified(); + } + + /** + * Returns true if a job with the same identity as {@code job} exists in the address book. + */ + public int movePerson(Job job, Person person, int source, int dest) { + requireNonNull(job); + requireNonNull(person); + + return job.move(person, source, dest); + } + + /** + * Returns true if a job with the same identity as {@code job} exists in the address book. + */ + public boolean hasJob(Job job) { + requireNonNull(job); + return jobs.contains(job); + } + /** * Replaces the given person {@code target} in the list with {@code editedPerson}. * {@code target} must exist in the address book. @@ -88,6 +222,18 @@ public void setPerson(Person target, Person editedPerson) { requireNonNull(editedPerson); persons.setPerson(target, editedPerson); + nrics.setPerson(target, editedPerson); + indicateModified(); + } + + /** + * Replaces the given job {@code target} in the list with {@code editedJob}. + * {@code target} must exist in the address book. + */ + public void setJob(Job target, Job editedJob) { + requireNonNull(editedJob); + + jobs.setJob(target, editedJob); indicateModified(); } @@ -97,9 +243,49 @@ public void setPerson(Person target, Person editedPerson) { */ public void removePerson(Person key) { persons.remove(key); + nrics.remove(key.getNric()); + jobs.removePerson(key); + interviews.removePerson(key); + indicateModified(); + } + + /** + * Removes {@code key} from this {@code job}. + * in {@code list} + * {@code key} must exist in the address book. + */ + public void deletePersonFromJobList(Person toDelete, JobName job, Integer list) { + jobs.removePersonFromJobList(toDelete, job, list); indicateModified(); } + public ObservableList getJobList() { + return jobs.asUnmodifiableObservableList(); + } + + public UniquePersonList getUniquePersonList() { + return persons; + } + + /** + * Generates interviews + */ + public void generateInterviews() { + interviews.generate(getPersonList()); + } + + public Interviews getInterviews() { + return interviews; + } + + public void setMaxInterviewsADay(int maxInterviewsADay) { + interviews.setMaxInterviewsADay(maxInterviewsADay); + } + + public void clearInterviews() { + interviews.clear(); + } + @Override public void addListener(InvalidationListener listener) { invalidationListenerManager.addListener(listener); @@ -130,15 +316,20 @@ public ObservableList getPersonList() { return persons.asUnmodifiableObservableList(); } + public ObservableList getAllJobList() { + return jobs.asUnmodifiableObservableList(); + } + @Override public boolean equals(Object other) { return other == this // short circuit if same object - || (other instanceof AddressBook // instanceof handles nulls - && persons.equals(((AddressBook) other).persons)); + || (other instanceof AddressBook // instanceof handles nulls + && persons.equals(((AddressBook) other).persons)); } @Override public int hashCode() { - return persons.hashCode(); + return nrics.hashCode(); } } + diff --git a/src/main/java/seedu/address/model/Model.java b/src/main/java/seedu/address/model/Model.java index e857533821b6..c68d96e360eb 100644 --- a/src/main/java/seedu/address/model/Model.java +++ b/src/main/java/seedu/address/model/Model.java @@ -1,18 +1,28 @@ package seedu.address.model; import java.nio.file.Path; +import java.util.Calendar; +import java.util.List; import java.util.function.Predicate; import javafx.beans.property.ReadOnlyProperty; import javafx.collections.ObservableList; import seedu.address.commons.core.GuiSettings; +import seedu.address.model.analytics.Analytics; +import seedu.address.model.interviews.Interviews; +import seedu.address.model.job.Job; +import seedu.address.model.job.JobListName; +import seedu.address.model.job.JobName; import seedu.address.model.person.Person; +import seedu.address.model.person.predicate.UniqueFilterList; /** * The API of the Model component. */ public interface Model { - /** {@code Predicate} that always evaluate to true */ + /** + * {@code Predicate} that always evaluate to true + */ Predicate PREDICATE_SHOW_ALL_PERSONS = unused -> true; /** @@ -50,7 +60,9 @@ public interface Model { */ void setAddressBook(ReadOnlyAddressBook addressBook); - /** Returns the AddressBook */ + /** + * Returns the AddressBook + */ ReadOnlyAddressBook getAddressBook(); /** @@ -58,6 +70,21 @@ public interface Model { */ boolean hasPerson(Person person); + /** + * Returns true if a job with the same identity as {@code job} exists in the address book. + */ + boolean hasJob(Job job); + + /** + * adds all persons in filtered personlist to {@code job}. + */ + void addFilteredPersonsToJob(JobName jobName, JobListName from, JobListName to); + + /** + * adds person with {@code nric} to {@code job}. + */ + void addPersonToJob(Job job, Person person, JobListName list); + /** * Deletes the given person. * The person must exist in the address book. @@ -70,6 +97,31 @@ public interface Model { */ void addPerson(Person person); + /** + * Adds the given job. + * {@code job} must not already exist in the address book. + */ + void addJob(Job job); + + /** + * Deletes the given job. + * {@code job} must exist in the address book. + */ + void deleteJob(Job job); + + /** + * Deletes the given job list of a job + * {@code job} must exist in the address book. + */ + void deletePersonFromJobList(Person toRemove, JobName job, JobListName list); + + /** + * Moves Person with {@code nric} in Job with {@code jobName} + * from list {@code source} to list {@code dest} + * {@code job} must exist in the address book. + */ + Integer movePerson(Job job, Person person, Integer source, Integer dest); + /** * Replaces the given person {@code target} with {@code editedPerson}. * {@code target} must exist in the address book. @@ -77,15 +129,99 @@ public interface Model { */ void setPerson(Person target, Person editedPerson); - /** Returns an unmodifiable view of the filtered person list */ - ObservableList getFilteredPersonList(); + /** + * Returns the job and makes it the active job + */ + Job getJob(JobName name); + + /** + * Returns the current active Job. + */ + Job getActiveJob(); + + /** + * Returns an unmodifiable view of the filtered people list in job + * + * @param list indicate the job person list + */ + + ObservableList getJobsList(JobListName list); + + /** + * Returns an unmodifiable view of the filtered job list + */ + ObservableList getAllJobs(); + + + /** + * add Predicate to filterList list + */ + void addPredicate(String predicateName, Predicate predicate, JobListName listname); + + /** + * remove Predicate to filterList list + */ + void removePredicate(String predicateName, JobListName listName); + + + /** + * Updates the filter of the filtered person list to filter by the given {@code predicate}. + * + * @throws NullPointerException if {@code predicate} is null. + */ + void updateBaseFilteredPersonList(Predicate predicate); /** * Updates the filter of the filtered person list to filter by the given {@code predicate}. + * * @throws NullPointerException if {@code predicate} is null. */ void updateFilteredPersonList(Predicate predicate); + /** + * Updates the filter of the JobKiv filtered person list to filter by the given {@code predicate}. + */ + void updateFilteredPersonLists(JobListName listname); + + /** + * Clear four filter list. + */ + void clearJobFilteredLists(); + + /** + * Clear indicated filter list. + */ + void clearJobFilteredLists(JobListName listName); + + /** + * Returns an unmodifiable view of the filtered person list + */ + ObservableList getFilteredPersonList(); + + + /** + * Returns the filtered job list + */ + UniqueFilterList getPredicateLists(JobListName listName); + + + /** + * Revert the display list. + */ + void revertList(); + + /** + * Returns an unmodifiable view of the list of {@code Person} backed by the internal list of + * {@code versionedAddressBook} + */ + ObservableList getFilteredJobList(); + + /** + * Returns an unmodifiable view of the list of {@code Person} backed by the internal list of + * {@code versionedAddressBook} + */ + ObservableList getBaseFilteredPersonList(); + /** * Returns true if the model has previous address book states to restore. */ @@ -117,14 +253,73 @@ public interface Model { */ ReadOnlyProperty selectedPersonProperty(); + /** + * Selected job in the filtered job list. + * null if no job is selected. + */ + ReadOnlyProperty selectedJobProperty(); + /** * Returns the selected person in the filtered person list. * null if no person is selected. */ Person getSelectedPerson(); + /** + * return whether it is at the all jobs screen ordisplay job screen + */ + boolean getIsAllJobScreen(); + + /** + * set whether it is at the all jobs screen ordisplay job screen + */ + void setIsAllJobScreen(boolean staus); + /** * Sets the selected person in the filtered person list. */ void setSelectedPerson(Person person); + + void setSelectedAll(Person person); + + void setSelectedKiv(Person person); + + void setSelectedInterviewed(Person person); + + void setSelectedSelected(Person person); + + void setSelectedJob(Job job); + + /** + * Generates an interview list. + */ + void generateInterviews(); + + /** + * Returns Interviews. + */ + Interviews getInterviews(); + + /** + * Sets the maximum number of interviews a day. + */ + void setMaxInterviewsADay(int maxInterviewsADay); + + /** + * Clears the generated interviews. + */ + void clearInterviews(); + + /** + * Sets Block Out Dates. + */ + void setBlockOutDates(List blockOutDates); + + /** + * Generates analytics. + */ + Analytics generateAnalytics(); + + Analytics generateAnalytics(JobListName listName); + } diff --git a/src/main/java/seedu/address/model/ModelManager.java b/src/main/java/seedu/address/model/ModelManager.java index b56806232814..405f7cb95d1d 100644 --- a/src/main/java/seedu/address/model/ModelManager.java +++ b/src/main/java/seedu/address/model/ModelManager.java @@ -4,6 +4,8 @@ import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; import java.nio.file.Path; +import java.util.Calendar; +import java.util.List; import java.util.Objects; import java.util.function.Predicate; import java.util.logging.Logger; @@ -15,8 +17,17 @@ import javafx.collections.transformation.FilteredList; import seedu.address.commons.core.GuiSettings; import seedu.address.commons.core.LogsCenter; +import seedu.address.model.analytics.Analytics; +import seedu.address.model.interviews.Interviews; +import seedu.address.model.job.Job; +import seedu.address.model.job.JobListName; +import seedu.address.model.job.JobName; import seedu.address.model.person.Person; +import seedu.address.model.person.UniquePersonList; import seedu.address.model.person.exceptions.PersonNotFoundException; +import seedu.address.model.person.predicate.Filter; +import seedu.address.model.person.predicate.PredicateManager; +import seedu.address.model.person.predicate.UniqueFilterList; /** * Represents the in-memory model of the address book data. @@ -26,8 +37,24 @@ public class ModelManager implements Model { private final VersionedAddressBook versionedAddressBook; private final UserPrefs userPrefs; - private final FilteredList filteredPersons; private final SimpleObjectProperty selectedPerson = new SimpleObjectProperty<>(); + private final SimpleObjectProperty selectedJob = new SimpleObjectProperty<>(); + private FilteredList originalFilteredPersons; + private FilteredList displayedFilteredPersons; + private FilteredList filteredJobs; + private Job activeJob; + private boolean isAllJobScreen; + private FilteredList activeJobAllApplicants; + private FilteredList activeJobKiv; + private FilteredList activeJobInterview; + private FilteredList activeJobShortlist; + private UniqueFilterList filterListAllPersons; + private UniqueFilterList filterListJobAllApplicants; + private UniqueFilterList filterListJobKiv; + private UniqueFilterList filterListJobInterview; + private UniqueFilterList filterListJobShortlist; + private FilteredList allJobsList; + /** * Initializes a ModelManager with the given addressBook and userPrefs. @@ -40,8 +67,26 @@ public ModelManager(ReadOnlyAddressBook addressBook, ReadOnlyUserPrefs userPrefs versionedAddressBook = new VersionedAddressBook(addressBook); this.userPrefs = new UserPrefs(userPrefs); - filteredPersons = new FilteredList<>(versionedAddressBook.getPersonList()); - filteredPersons.addListener(this::ensureSelectedPersonIsValid); + originalFilteredPersons = new FilteredList<>(versionedAddressBook.getPersonList()); + filteredJobs = new FilteredList<>(versionedAddressBook.getJobList()); + originalFilteredPersons.addListener(this::ensureSelectedPersonIsValid); + filteredJobs.addListener(this::ensureSelectedJobIsValid); + displayedFilteredPersons = originalFilteredPersons; + isAllJobScreen = true; + + UniquePersonList fakeList = new UniquePersonList(); + activeJobAllApplicants = new FilteredList<>(fakeList.asUnmodifiableObservableList()); + activeJobKiv = new FilteredList<>(fakeList.asUnmodifiableObservableList()); + activeJobShortlist = new FilteredList<>(fakeList.asUnmodifiableObservableList()); + activeJobInterview = new FilteredList<>(fakeList.asUnmodifiableObservableList()); + + filterListJobAllApplicants = new UniqueFilterList(); + filterListJobKiv = new UniqueFilterList(); + filterListJobInterview = new UniqueFilterList(); + filterListJobShortlist = new UniqueFilterList(); + filterListAllPersons = new UniqueFilterList(); + + allJobsList = new FilteredList<>(versionedAddressBook.getAllJobList()); } public ModelManager() { @@ -101,15 +146,100 @@ public boolean hasPerson(Person person) { return versionedAddressBook.hasPerson(person); } + @Override + public boolean hasJob(Job job) { + requireNonNull(job); + return versionedAddressBook.hasJob(job); + } + + @Override + public boolean getIsAllJobScreen() { + return isAllJobScreen; + } + + @Override + public void setIsAllJobScreen(boolean staus) { + this.isAllJobScreen = staus; + } + + @Override + public void addFilteredPersonsToJob(JobName jobName, JobListName from, JobListName to) { + + if (activeJob == null || !activeJob.getName().equals(jobName)) { + this.getJob(jobName); + this.activeJob = null; + } + + switch (from) { + case APPLICANT: + versionedAddressBook.addFilteredListToJob(activeJobAllApplicants, jobName, to); + break; + case KIV: + versionedAddressBook.addFilteredListToJob(activeJobKiv, jobName, to); + break; + case INTERVIEW: + versionedAddressBook.addFilteredListToJob(activeJobInterview, jobName, to); + break; + case SHORTLIST: + versionedAddressBook.addFilteredListToJob(activeJobShortlist, jobName, to); + break; + default: + versionedAddressBook.addFilteredListToJob(displayedFilteredPersons, jobName, to); + } + } + + @Override + public void addPersonToJob(Job job, Person person, JobListName list) { + requireAllNonNull(job, person); + + versionedAddressBook.addPersonToJob(person, job, list); + } + @Override public void deletePerson(Person target) { versionedAddressBook.removePerson(target); } + @Override + public void deletePersonFromJobList(Person toRemove, JobName job, JobListName list) { + switch (list) { + case APPLICANT: + versionedAddressBook.deletePersonFromJobList(toRemove, job, 0); + break; + case KIV: + versionedAddressBook.deletePersonFromJobList(toRemove, job, 1); + break; + case INTERVIEW: + versionedAddressBook.deletePersonFromJobList(toRemove, job, 2); + break; + case SHORTLIST: + versionedAddressBook.deletePersonFromJobList(toRemove, job, 3); + break; + default: + versionedAddressBook.deletePersonFromJobList(toRemove, job, 0); + } + } + @Override public void addPerson(Person person) { versionedAddressBook.addPerson(person); - updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + updateBaseFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + } + + @Override + public void addJob(Job job) { + versionedAddressBook.addJob(job); + } + + @Override + public void deleteJob(Job job) { + versionedAddressBook.deleteJob(job); + revertList(); + } + + @Override + public Integer movePerson(Job job, Person person, Integer source, Integer dest) { + return versionedAddressBook.movePerson(job, person, source, dest); } @Override @@ -119,21 +249,174 @@ public void setPerson(Person target, Person editedPerson) { versionedAddressBook.setPerson(target, editedPerson); } + + public ObservableList getAllJobs() { + return allJobsList; + } + + public ObservableList getJobsList(JobListName list) { + + switch (list) { + case APPLICANT: + return activeJobAllApplicants; + case KIV: + return activeJobKiv; + case INTERVIEW: + return activeJobInterview; + case SHORTLIST: + return activeJobShortlist; + default: + return displayedFilteredPersons; + } + } + + @Override + public Job getJob(JobName name) { + this.activeJob = versionedAddressBook.getJob(name); + this.activeJobAllApplicants = + new FilteredList<>(activeJob.getList(0).asUnmodifiableObservableList()); + this.activeJobKiv = + new FilteredList<>(activeJob.getList(1).asUnmodifiableObservableList()); + this.activeJobInterview = + new FilteredList<>(activeJob.getList(2).asUnmodifiableObservableList()); + this.activeJobShortlist = + new FilteredList<>(activeJob.getList(3).asUnmodifiableObservableList()); + return activeJob; + } + + public Job getActiveJob() { + return activeJob; + } + //=========== Filtered Person List Accessors ============================================================= - /** - * Returns an unmodifiable view of the list of {@code Person} backed by the internal list of - * {@code versionedAddressBook} - */ + @Override public ObservableList getFilteredPersonList() { - return filteredPersons; + return displayedFilteredPersons; + } + + + @Override + public ObservableList getBaseFilteredPersonList() { + return originalFilteredPersons; + } + + + @Override + public void updateBaseFilteredPersonList(Predicate predicate) { + requireNonNull(predicate); + originalFilteredPersons.setPredicate(predicate); + } + + @Override + public void updateFilteredPersonLists(JobListName listname) { + Predicate predicater = new PredicateManager(); + for (Filter filter : getPredicateLists(listname)) { + predicater = predicater.and(filter.getPredicate()); + } + getPersonsLists(listname).setPredicate(predicater); + } + + @Override + public UniqueFilterList getPredicateLists(JobListName listName) { + switch (listName) { + case APPLICANT: + return filterListJobAllApplicants; + case KIV: + return filterListJobKiv; + case INTERVIEW: + return filterListJobInterview; + case SHORTLIST: + return filterListJobShortlist; + default: + return filterListAllPersons; + } + } + + public FilteredList getPersonsLists(JobListName listname) { + + switch (listname) { + case APPLICANT: + return activeJobAllApplicants; + case KIV: + return activeJobKiv; + case INTERVIEW: + return activeJobInterview; + case SHORTLIST: + return activeJobShortlist; + default: + return displayedFilteredPersons; + } } @Override public void updateFilteredPersonList(Predicate predicate) { requireNonNull(predicate); - filteredPersons.setPredicate(predicate); + displayedFilteredPersons.setPredicate(predicate); + } + + @Override + public void clearJobFilteredLists() { + clearJobFilteredLists(JobListName.APPLICANT); + clearJobFilteredLists(JobListName.KIV); + clearJobFilteredLists(JobListName.INTERVIEW); + clearJobFilteredLists(JobListName.SHORTLIST); + clearJobFilteredLists(JobListName.EMPTY); + } + + @Override + public void clearJobFilteredLists(JobListName listName) { + switch (listName) { + case APPLICANT: + filterListJobAllApplicants = new UniqueFilterList(); + break; + case KIV: + filterListJobKiv = new UniqueFilterList(); + break; + case INTERVIEW: + filterListJobInterview = new UniqueFilterList(); + break; + case SHORTLIST: + filterListJobShortlist = new UniqueFilterList(); + break; + default: + filterListAllPersons = new UniqueFilterList(); + break; + } + } + + @Override + public void addPredicate(String predicateName, Predicate predicate, JobListName listName) { + requireNonNull(predicate); + requireNonNull(listName); + requireNonNull(predicateName); + getPredicateLists(listName).add(new Filter(predicateName, predicate)); + } + + + @Override + public void removePredicate(String predicateName, JobListName listName) { + requireNonNull(predicateName); + getPredicateLists(listName).remove(new Filter(predicateName)); + } + + + @Override + public void revertList() { + this.displayedFilteredPersons = originalFilteredPersons; + } + + + //=========== Filtered Job List Accessors ============================================================= + + /** + * Returns an unmodifiable view of the list of {@code Job} backed by the internal list of + * {@code versionedAddressBook} + */ + @Override + public ObservableList getFilteredJobList() { + return filteredJobs; } //=========== Undo/Redo ================================================================================= @@ -170,6 +453,10 @@ public ReadOnlyProperty selectedPersonProperty() { return selectedPerson; } + public ReadOnlyProperty selectedJobProperty() { + return selectedJob; + } + @Override public Person getSelectedPerson() { return selectedPerson.getValue(); @@ -177,12 +464,52 @@ public Person getSelectedPerson() { @Override public void setSelectedPerson(Person person) { - if (person != null && !filteredPersons.contains(person)) { + if (person != null && !displayedFilteredPersons.contains(person)) { throw new PersonNotFoundException(); } selectedPerson.setValue(person); } + @Override + public void setSelectedAll(Person person) { + if (person != null && !activeJobAllApplicants.contains(person)) { + throw new PersonNotFoundException(); + } + selectedPerson.setValue(person); + } + + @Override + public void setSelectedKiv(Person person) { + if (person != null && !activeJobKiv.contains(person)) { + throw new PersonNotFoundException(); + } + selectedPerson.setValue(person); + } + + @Override + public void setSelectedInterviewed(Person person) { + if (person != null && !activeJobInterview.contains(person)) { + throw new PersonNotFoundException(); + } + selectedPerson.setValue(person); + } + + @Override + public void setSelectedSelected(Person person) { + if (person != null && !activeJobShortlist.contains(person)) { + throw new PersonNotFoundException(); + } + selectedPerson.setValue(person); + } + + @Override + public void setSelectedJob(Job job) { + if (job != null && !allJobsList.contains(job)) { + throw new PersonNotFoundException(); + } + selectedJob.setValue(job); + } + /** * Ensures {@code selectedPerson} is a valid person in {@code filteredPersons}. */ @@ -194,7 +521,7 @@ private void ensureSelectedPersonIsValid(ListChangeListener.Change selectedPerson.getValue().isSamePerson(removedPerson)); + .anyMatch(removedPerson -> selectedPerson.getValue().isSamePerson(removedPerson)); if (wasSelectedPersonRemoved) { // Select the person that came before it in the list, // or clear the selection if there is no such person. @@ -212,6 +539,91 @@ private void ensureSelectedPersonIsValid(ListChangeListener.Change change) { + while (change.next()) { + if (selectedJob.getValue() == null) { + // null is always a valid selected job, so we do not need to check that it is valid anymore. + return; + } + + boolean wasSelectedJobChanged = change.wasReplaced() && change.getAddedSize() == change.getRemovedSize() + && change.getRemoved().contains(selectedJob.getValue()); + if (wasSelectedJobChanged) { + // Update selectedJob to its new value. + int index = change.getRemoved().indexOf(selectedJob.getValue()); + selectedJob.setValue(change.getAddedSubList().get(index)); + continue; + } + + boolean wasSelectedJobRemoved = change.getRemoved().stream() + .anyMatch(removedJob -> selectedJob.getValue().isSameJob(removedJob)); + if (wasSelectedJobChanged) { + // Select the job that came before it in the list, + // or clear the selection if there is no such job. + selectedJob.setValue(change.getFrom() > 0 ? change.getList().get(change.getFrom() - 1) : null); + } + } + } + + @Override + public void generateInterviews() { + versionedAddressBook.generateInterviews(); + } + + @Override + public Interviews getInterviews() { + return versionedAddressBook.getInterviews(); + } + + @Override + public void setMaxInterviewsADay(int maxInterviewsADay) { + versionedAddressBook.setMaxInterviewsADay(maxInterviewsADay); + } + + @Override + public void clearInterviews() { + versionedAddressBook.clearInterviews(); + } + + /** + * Obtains current viewed list and generate analytics based on it + */ + @Override + public Analytics generateAnalytics() { + Analytics analytics = new Analytics(getFilteredPersonList()); + return analytics; + } + + @Override + public Analytics generateAnalytics(JobListName listName) { + Analytics analytics; + switch (listName) { + case APPLICANT: + analytics = new Analytics(activeJobAllApplicants); + break; + case KIV: + analytics = new Analytics(activeJobKiv); + break; + case INTERVIEW: + analytics = new Analytics(activeJobInterview); + break; + case SHORTLIST: + analytics = new Analytics(activeJobShortlist); + break; + default: + analytics = new Analytics(getFilteredPersonList()); + } + return analytics; + } + + @Override + public void setBlockOutDates(List blockOutDates) { + versionedAddressBook.setBlockOutDates(blockOutDates); + } + @Override public boolean equals(Object obj) { // short circuit if same object @@ -227,9 +639,9 @@ public boolean equals(Object obj) { // state check ModelManager other = (ModelManager) obj; return versionedAddressBook.equals(other.versionedAddressBook) - && userPrefs.equals(other.userPrefs) - && filteredPersons.equals(other.filteredPersons) - && Objects.equals(selectedPerson.get(), other.selectedPerson.get()); + && userPrefs.equals(other.userPrefs) + && originalFilteredPersons.equals(other.originalFilteredPersons) + && Objects.equals(selectedPerson.get(), other.selectedPerson.get()); } } diff --git a/src/main/java/seedu/address/model/ReadOnlyAddressBook.java b/src/main/java/seedu/address/model/ReadOnlyAddressBook.java index 6a301434b33b..d2ac179b25f0 100644 --- a/src/main/java/seedu/address/model/ReadOnlyAddressBook.java +++ b/src/main/java/seedu/address/model/ReadOnlyAddressBook.java @@ -2,6 +2,8 @@ import javafx.beans.Observable; import javafx.collections.ObservableList; +import seedu.address.model.interviews.Interviews; +import seedu.address.model.job.Job; import seedu.address.model.person.Person; /** @@ -14,5 +16,6 @@ public interface ReadOnlyAddressBook extends Observable { * This list will not contain any duplicate persons. */ ObservableList getPersonList(); - + Interviews getInterviews(); + ObservableList getJobList(); } diff --git a/src/main/java/seedu/address/model/analytics/Analytics.java b/src/main/java/seedu/address/model/analytics/Analytics.java new file mode 100644 index 000000000000..29194a24010c --- /dev/null +++ b/src/main/java/seedu/address/model/analytics/Analytics.java @@ -0,0 +1,312 @@ +package seedu.address.model.analytics; + +import java.text.DecimalFormat; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Vector; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.scene.chart.PieChart; +import javafx.scene.chart.XYChart; +import seedu.address.model.person.JobsApply; +import seedu.address.model.person.PastJob; +import seedu.address.model.person.Person; + +/** + * Provides analytics data on job applicants, based on their data fields obtained from {@code Person} + * */ + +public class Analytics { + private List personList; + private int numPeople; + + public Analytics(List list) { + personList = list; + numPeople = list.size(); + } + + /** + * Generates Barchart data from job application breakdown list + * */ + public ObservableList> generateJobApplicationData() { + return generateBarChartDataFromSet(jobApplicationBreakdown()); + } + + /** + * Generates Barchart data from major breakdown list + * */ + public ObservableList> generateMajorData() { + return generateBarChartDataFromSet(majorBreakdown()); + } + + /** + * Generates Barchart data from school breakdown list + * */ + public ObservableList> generateSchoolData() { + return generateBarChartDataFromSet(schoolBreakdown()); + } + + /** + * Generates Barchart data from past job breakdown list + * */ + public ObservableList> generatePastJobData() { + return generateBarChartDataFromSet(pastJobBreakdown()); + } + + /** + * Return mean grade data + * */ + public String generateMeanGradeData() { + DecimalFormat df = new DecimalFormat("#.00"); + Float meanGrade = meanGrade(); + if (meanGrade.isNaN()) { + return "No Record"; + } + return df.format(meanGrade()); + } + + /** + * Generates Barchart data from interview scores breakdown list + * */ + public ObservableList> generateInterviewScoresData() { + ObservableList> data = FXCollections.observableArrayList(); + ArrayList scores = meanInterviewScores(); + Vector> charts = new Vector<>(); + for (int i = 1; i < 6; i++) { + XYChart.Series question = new XYChart.Series<>(); + question.setName("Q" + i); + question.getData().add(new XYChart.Data<>("", scores.get(i - 1))); + charts.add(question); + } + data.addAll(charts); + return data; + } + + /** + * Generates Piechart data from race breakdown list + * */ + public ObservableList generateRaceData() { + ObservableList data = FXCollections.observableArrayList(); + ArrayList race = raceBreakdown(); + data.add(new PieChart.Data("Chinese", race.get(0))); + data.add(new PieChart.Data("Malay", race.get(1))); + data.add(new PieChart.Data("Indian", race.get(2))); + data.add(new PieChart.Data("Others", race.get(3))); + + return data; + } + + /** + * Generates Piechart data from gender breakdown list + * */ + public ObservableList generateGenderData() { + ObservableList data = FXCollections.observableArrayList(); + ArrayList gender = genderBreakdown(); + data.add(new PieChart.Data("Female", gender.get(0))); + data.add(new PieChart.Data("Male", gender.get(1))); + data.add(new PieChart.Data("Others", gender.get(2))); + + return data; + } + + /** + * Returns the average grade of all applicants in the list + * */ + + private Float meanGrade() { + Float sumGrade = 0F; + + for (int i = 0; i < numPeople; i++) { + sumGrade += Float.valueOf(personList.get(i).getGrade().value); + } + + return sumGrade / numPeople; + } + + /** + * Provides the number of applicants for each open job role + * A {@code Person} with more than one job applied will be counted multiple times, once for each role. + * */ + + private HashMap jobApplicationBreakdown() { + HashMap jobApplicantsCounter = new HashMap<>(); + + for (int i = 0; i < numPeople; i++) { + Iterator itr = personList.get(i).getJobsApply().iterator(); + while (itr.hasNext()) { + JobsApply job = itr.next(); + if (!jobApplicantsCounter.containsKey(job.value)) { + jobApplicantsCounter.put(job.value, 1); + } else { + jobApplicantsCounter.put(job.value, jobApplicantsCounter.get(job.value) + 1); + } + } + } + return jobApplicantsCounter; + } + + /** + * Provides an average score for each interview question from selected list of applicants + * Applicants not required to all have interview scores + * */ + + private ArrayList meanInterviewScores() { + ArrayList averageScores = new ArrayList<>(); + + for (int i = 0; i < 5; i++) { + averageScores.add(0F); + } + int interviewed = numPeople; + + for (int i = 0; i < numPeople; i++) { + Person curr = personList.get(i); + if (curr.getInterviewScores().value.equals("No Record")) { + interviewed--; + } else { + String[] personScores; + personScores = curr.getInterviewScores().value.split(","); + for (int j = 0; j < 5; j++) { + averageScores.set(j, averageScores.get(j) + Integer.valueOf(personScores[j])); + } + } + } + for (int i = 0; i < 5; i++) { + averageScores.set(i, averageScores.get(i) / interviewed); + } + return averageScores; + } + + /** + * Returns the number of applicants for each gender category + * */ + + private ArrayList genderBreakdown() { + ArrayList genderCount = new ArrayList<>(); + + for (int i = 0; i < 3; i++) { + genderCount.add(0); + } + + for (int i = 0; i < 3; i++) { + genderCount.set(i, 0); + } + + for (int i = 0; i < numPeople; i++) { + String currGender = personList.get(i).getGender().value; + + if (currGender.equals("Female")) { + genderCount.set(0, genderCount.get(0) + 1); + } else if (currGender.equals("Male")) { + genderCount.set(1, genderCount.get(1) + 1); + } else { + genderCount.set(2, genderCount.get(2) + 1); + } + } + return genderCount; + } + + /** + * Returns the number of applicants for each race category + * */ + + private ArrayList raceBreakdown() { + ArrayList raceCount = new ArrayList<>(); + + for (int i = 0; i < 4; i++) { + raceCount.add(0); + } + + for (int i = 0; i < numPeople; i++) { + String currRace = personList.get(i).getRace().value; + + if (currRace.equals("Chinese")) { + raceCount.set(0, raceCount.get(0) + 1); + } else if (currRace.equals("Malay")) { + raceCount.set(1, raceCount.get(1) + 1); + } else if (currRace.equals("Indian")) { + raceCount.set(2, raceCount.get(2) + 1); + } else { + raceCount.set(3, raceCount.get(3) + 1); + } + } + return raceCount; + } + + /** + * Provides the number of applicants studying in each major + * */ + + private HashMap majorBreakdown() { + HashMap majorCounter = new HashMap<>(); + + for (int i = 0; i < numPeople; i++) { + String currMajor = personList.get(i).getMajor().value; + if (!majorCounter.containsKey(currMajor)) { + majorCounter.put(currMajor, 1); + } else { + majorCounter.put(currMajor, majorCounter.get(currMajor) + 1); + } + } + return majorCounter; + } + + /** + * Provides the number of applicants studying in each school + * */ + + private HashMap schoolBreakdown() { + HashMap schoolCounter = new HashMap<>(); + + for (int i = 0; i < numPeople; i++) { + String currSchool = personList.get(i).getSchool().value; + if (!schoolCounter.containsKey(currSchool)) { + schoolCounter.put(currSchool, 1); + } else { + schoolCounter.put(currSchool, schoolCounter.get(currSchool) + 1); + } + } + return schoolCounter; + } + + /** + * Provides the number of applicants for each past job + * A {@code Person} with more than one past job applied will be counted multiple times, once for each job. + * */ + + private HashMap pastJobBreakdown() { + HashMap pastJobCounter = new HashMap<>(); + + for (int i = 0; i < numPeople; i++) { + Iterator itr = personList.get(i).getPastJobs().iterator(); + while (itr.hasNext()) { + PastJob job = itr.next(); + if (!pastJobCounter.containsKey(job.value)) { + pastJobCounter.put(job.value, 1); + } else { + pastJobCounter.put(job.value, pastJobCounter.get(job.value) + 1); + } + } + } + return pastJobCounter; + } + + /** + * Generates the dataset to be put into a Barchart from any Hashmap with a String key and Integer value + * */ + + private ObservableList> generateBarChartDataFromSet(HashMap map) { + ObservableList> output = FXCollections.observableArrayList(); + Iterator itr = map.keySet().iterator(); + while (itr.hasNext()) { + String curr = itr.next(); + XYChart.Series series = new XYChart.Series<>(); + series.setName(curr); + series.getData().add(new XYChart.Data<>("", map.get(curr))); + output.add(series); + } + return output; + } +} diff --git a/src/main/java/seedu/address/model/interviews/Interviews.java b/src/main/java/seedu/address/model/interviews/Interviews.java new file mode 100644 index 000000000000..2ffbbbca1ab9 --- /dev/null +++ b/src/main/java/seedu/address/model/interviews/Interviews.java @@ -0,0 +1,160 @@ +package seedu.address.model.interviews; + +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Collection; +import java.util.GregorianCalendar; +import java.util.HashMap; +import java.util.List; +import java.util.PriorityQueue; + +import seedu.address.model.interviews.exceptions.InterviewsPresentException; +import seedu.address.model.person.Person; + +/** + * Represents the association class between person and calendar. + */ +public class Interviews { + + public static final int SECOND = 0; + public static final int MINUTE = 0; + public static final int HOUR = 0; + public static final int MILLISECOND = 0; + + private int maxInterviewsADay = 2; + + private final HashMap> interviewsHashMap; + private final ArrayList blockOutDates; + + public Interviews() { + this.interviewsHashMap = new HashMap<>(); + this.blockOutDates = new ArrayList<>(); + } + + /** + * Helper method for test. + */ + protected Interviews(HashMap> interviewsHashMap) { + this.interviewsHashMap = interviewsHashMap; + this.blockOutDates = new ArrayList<>(); + } + + /** + * Generates a interviews date list where there are multiple interviewees in a day. + */ + public void generate(List persons) throws InterviewsPresentException { + if (!interviewsHashMap.isEmpty()) { + throw new InterviewsPresentException(); + } + Calendar now = Calendar.getInstance(); + int year = now.get(Calendar.YEAR); + int month = now.get(Calendar.MONTH); + int day = now.get(Calendar.DATE); + Calendar calendar = new GregorianCalendar(year, month, day); + calendar = nextAvailableDay(calendar); + interviewsHashMap.put(calendar, new ArrayList<>()); + for (Person person : persons) { + List personList = interviewsHashMap.get(calendar); + if (personList.size() < maxInterviewsADay) { + personList.add(person); + } else { + calendar = nextAvailableDay(calendar); + interviewsHashMap.put(calendar, new ArrayList<>()); + interviewsHashMap.get(calendar).add(person); + } + } + } + + public void setBlockOutDates(List availableDates) { + for (Calendar dates : availableDates) { + this.blockOutDates.add((Calendar) dates.clone()); + } + } + + public void setInterviews(Interviews other) { + this.maxInterviewsADay = other.maxInterviewsADay; + this.interviewsHashMap.clear(); + other.interviewsHashMap.forEach(((calendar, personList) -> + this.interviewsHashMap.put(calendar, new ArrayList<>(personList)))); + this.blockOutDates.clear(); + for (Calendar calendar : other.blockOutDates) { + this.blockOutDates.add(calendar); + } + } + + public void clear() { + interviewsHashMap.clear(); + } + + public void setMaxInterviewsADay(int maxInterviewsADay) { + this.maxInterviewsADay = maxInterviewsADay; + } + + @Override + public String toString() { + PriorityQueue calendarPriorityQueue = new PriorityQueue<>(interviewsHashMap.keySet()); + String result = ""; + while (!calendarPriorityQueue.isEmpty()) { + Calendar currentCalendar = calendarPriorityQueue.poll(); + List currentPersonList = interviewsHashMap.get(currentCalendar); + result += currentCalendar.get(Calendar.DATE) + "/" + (currentCalendar.get(Calendar.MONTH) + 1) + + "/" + currentCalendar.get(Calendar.YEAR) + ": "; + for (Person person : currentPersonList) { + result += person.getName() + ", "; + } + result = result.substring(0, result.length() - 2); + result += "\n\n"; + } + return result.trim(); + } + + /** + * Removes the person from the interviewsHashMap. + * @param person to be removed from interviewsHashMap. + * @return true if person to be removed is present, else returns false. + */ + public boolean removePerson(Person person) { + Collection> listOfPersonList = interviewsHashMap.values(); + for (List personList : listOfPersonList) { + if (personList.remove(person)) { + return true; + } + } + return false; + } + + protected HashMap> getInterviewsHashMap() { + return interviewsHashMap; + } + + /** + * Returns a new instance of the next available day in calendar format. + */ + private Calendar nextAvailableDay(Calendar calendar) { + Calendar result = (Calendar) calendar.clone(); + result.add(Calendar.DATE, 1); + while ((result.get(Calendar.DAY_OF_WEEK) == Calendar.SATURDAY) + || (result.get(Calendar.DAY_OF_WEEK) == Calendar.SUNDAY) + || (containsDate(blockOutDates, result))) { + result.add(Calendar.DATE, 1); + } + return result; + } + + /** + * Checks if the calendarList contains the date. + */ + protected static boolean containsDate(List calendarList, Calendar date) { + int year = date.get(Calendar.YEAR); + int month = date.get(Calendar.MONTH); + int day = date.get(Calendar.DATE); + for (Calendar calendar : calendarList) { + if (calendar.get(Calendar.YEAR) == year + && calendar.get(Calendar.MONTH) == month + && calendar.get(Calendar.DATE) == day) { + return true; + } + } + return false; + } +} diff --git a/src/main/java/seedu/address/model/interviews/exceptions/InterviewsPresentException.java b/src/main/java/seedu/address/model/interviews/exceptions/InterviewsPresentException.java new file mode 100644 index 000000000000..a8c47c8fcb32 --- /dev/null +++ b/src/main/java/seedu/address/model/interviews/exceptions/InterviewsPresentException.java @@ -0,0 +1,6 @@ +package seedu.address.model.interviews.exceptions; + +/** + * Signals that the interview has already been generated + */ +public class InterviewsPresentException extends RuntimeException {} diff --git a/src/main/java/seedu/address/model/job/Job.java b/src/main/java/seedu/address/model/job/Job.java new file mode 100644 index 000000000000..f7534da23270 --- /dev/null +++ b/src/main/java/seedu/address/model/job/Job.java @@ -0,0 +1,244 @@ +package seedu.address.model.job; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; + +import javafx.collections.transformation.FilteredList; +import seedu.address.model.person.Name; +import seedu.address.model.person.Nric; +import seedu.address.model.person.Person; +import seedu.address.model.person.UniquePersonList; +import seedu.address.model.person.exceptions.DuplicatePersonException; +import seedu.address.model.person.exceptions.PersonNotFoundException; +import seedu.address.model.person.predicate.UniqueFilterList; + +/** + * Represents a Person in the address book. + * Guarantees: details are present and not null, field values are validated, immutable. + */ +public class Job { + + private static final int NUMBER_OF_LISTS = 4; + // Identity fields + private final JobName name; + + // Data fields + private UniquePersonList personsInJob = new UniquePersonList(); + private ArrayList personsList = new ArrayList<>(NUMBER_OF_LISTS); + private ArrayList> personsNricList = new ArrayList<>(NUMBER_OF_LISTS); + private ArrayList predicateList = new ArrayList<>(NUMBER_OF_LISTS); + + + /** + * Every field must be present and not null. + */ + public Job(JobName name) { + requireAllNonNull(name); + + this.name = name; + for (int i = 0; i < 4; i++) { + personsList.add(new UniquePersonList()); + personsNricList.add(new HashSet<>()); + } + } + + public Job(JobName name, ArrayList personList, ArrayList> nricList, + UniquePersonList personsInJob) { + requireAllNonNull(name, personList, nricList, personsInJob); + + this.name = name; + this.personsList = personList; + this.personsNricList = nricList; + this.personsInJob = personsInJob; + } + + public JobName getName() { + return name; + } + + /** + * Adds all persons on displayed filter list to first list of job. + * Only adds if not already in job. + */ + public void addFilteredList(FilteredList filteredPersons, Integer to) { + for (int i = 0; i < filteredPersons.size(); i++) { + if (personsList.get(to).contains(filteredPersons.get(i))) { + continue; + } + if (!personsInJob.contains(filteredPersons.get(i))) { + personsInJob.add(filteredPersons.get(i)); + } + add(filteredPersons.get(i), to); + } + } + + /** + * Removes a person from a job + */ + public void remove(Person toRemove) { + requireNonNull(toRemove); + if (!personsInJob.contains(toRemove)) { + throw new PersonNotFoundException(); + } + for (int i = 0; i < 4; i++) { + if (personsList.get(i).contains(toRemove)) { + personsList.get(i).remove(toRemove); + personsNricList.get(i).remove(toRemove.getNric()); + } + } + personsInJob.remove(toRemove); + } + + /** + * Removes a person from a job list + */ + public void removeFromList(Person toRemove, Integer listNumber) { + requireNonNull(toRemove); + boolean shouldStillExist = false; + if (!personsInJob.contains(toRemove)) { + throw new PersonNotFoundException(); + } + for (int i = 0; i < 4; i++) { + if (i == listNumber) { + continue; + } + if (personsList.get(i).contains(toRemove)) { + shouldStillExist = true; + } + } + personsList.get(listNumber).remove(toRemove); + if (!shouldStillExist) { + personsInJob.remove(toRemove); + } + } + + /** + * Adds a person to a job. + * Goes to the first list + */ + public void add(Person person, Integer destination) { + if (personsList.get(destination).contains(person)) { + throw new DuplicatePersonException(); + } + if (!personsInJob.contains(person)) { + personsInJob.add(person); + } + personsList.get(destination).add(person); + personsNricList.get(destination).add(person.getNric()); + } + + /** + * Returns one of the four UniqurePredicateLists + */ + public UniqueFilterList getPredicateList(Integer listNumber) { + return predicateList.get(listNumber); + } + + /** + * Returns one of the four UniqurePersonLists + */ + public UniquePersonList getList(Integer listNumber) { + return personsList.get(listNumber); + } + + + /** + * Moves a person from one list to another + */ + public int move(Person target, Integer source, Integer dest) { + + if (!(personsList.get(source).contains(target))) { + return 0; + } + + if (personsList.get(dest).contains(target)) { + return 1; + } + + personsList.get(dest).add(target); + personsNricList.get(dest).add(target.getNric()); + return 2; + } + + /** + * Returns an UniquePerson list using {@code listNumber} + */ + public final UniquePersonList getPeople(Integer listNumber) { + return personsList.get(listNumber); + } + + /** + * Returns true if Job contains Person + */ + public final boolean contains(Person person) { + return personsInJob.contains(person); + } + + /** + * Returns an immutable known programming language set, which throws {@code UnsupportedOperationException} + * if modification is attempted. + */ + public final Set getPersonsNric(Integer listNumber) { + return Collections.unmodifiableSet(personsNricList.get(listNumber)); + } + + public final ArrayList getPeopleNames(List peopleList) { + ArrayList names = new ArrayList<>(peopleList.size()); + for (int i = 0; i < peopleList.size(); i++) { + names.add(peopleList.get(i).getName()); + } + return names; + } + + /** + * Returns true if both jobs have the same name. + * This defines a weaker notion of equality between two jobs. + */ + public boolean isSameJob(Job otherJob) { + requireNonNull(otherJob); + if (otherJob == this) { + return true; + } + + return ((otherJob.getName()).equals(this.getName())); + } + + /** + * Returns true if both jobs have the same identity and data fields. + * This defines a stronger notion of equality between two jobs. + */ + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof Job)) { + return false; + } + + Job otherJob = (Job) other; + return otherJob.getName().equals(getName()); + } + + @Override + public int hashCode() { + // use this method for custom fields hashing instead of implementing your own + return Objects.hash(name); + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append(getName()); + return builder.toString(); + } + +} diff --git a/src/main/java/seedu/address/model/job/JobListName.java b/src/main/java/seedu/address/model/job/JobListName.java new file mode 100644 index 000000000000..07090ed786f3 --- /dev/null +++ b/src/main/java/seedu/address/model/job/JobListName.java @@ -0,0 +1,44 @@ +package seedu.address.model.job; + +import java.util.Arrays; +import java.util.TreeSet; + +/** + * Represents a JobListName in the address book. + */ +public enum JobListName { + APPLICANT(), KIV(), INTERVIEW(), SHORTLIST(), STUB(), EMPTY(); + + public static final String MESSAGE_CONSTRAINTS = + "Job List Name should either be empty when you are in All Job Showing Screen or be name (prefix)" + + " of four job list when you are in Job Detail Screen.\n" + + "If you are in you are in Job Detail Screen," + + " the valid job list name should be in following set:\n" + + "{Applicant, KIV, Interview, Shortlist, a, k, i, s}(case insensitive)\n%1$s"; + + public static final String APPLICANT_NAME = "applicant"; + public static final String KIV_NAME = "kiv"; + public static final String INTERVIEW_NAME = "interview"; + public static final String SHORTLIST_NAME = "shortlist"; + public static final String APPLICANT_PREFIX = "a"; + public static final String KIV_PREFIX = "k"; + public static final String INTERVIEW_PREFIX = "i"; + public static final String SHORTLIST_PREFIX = "s"; + public static final String STUB_NAME = "stub"; + public static final String EMPTY_STRING = ""; + + private static final String[] POSSIBLE_ListName = {APPLICANT_NAME, KIV_NAME, INTERVIEW_NAME, SHORTLIST_NAME, + APPLICANT_PREFIX, KIV_PREFIX, INTERVIEW_PREFIX, SHORTLIST_PREFIX, STUB_NAME, EMPTY_STRING}; + private static final TreeSet POSSIBLE_ListName_TREE = new TreeSet<>(Arrays.asList(POSSIBLE_ListName)); + + /** + * Returns true if a given string is a valid list name. + */ + public static boolean isValidJobListName(String test) { + if (test == null) { + throw new NullPointerException("Parameter Type cannot be null"); + } + return POSSIBLE_ListName_TREE.contains(test); + } + +} diff --git a/src/main/java/seedu/address/model/job/JobName.java b/src/main/java/seedu/address/model/job/JobName.java new file mode 100644 index 000000000000..a54997571081 --- /dev/null +++ b/src/main/java/seedu/address/model/job/JobName.java @@ -0,0 +1,59 @@ +package seedu.address.model.job; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +/** + * Represents a job's name in the address book. + * Guarantees: immutable; is valid as declared in {@link #isValidName(String)} + */ +public class JobName { + + public static final String MESSAGE_CONSTRAINTS = "Jobs applied should not start with a whitespace char" + + ", and it should not be blank. Multiple word jobs are to be seperated by '-'."; + + /* + * 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 = "\\b\\S+"; + + public final String fullName; + + /** + * Constructs a {@code Name}. + * + * @param name A valid name. + */ + public JobName(String name) { + requireNonNull(name); + checkArgument(isValidName(name), MESSAGE_CONSTRAINTS); + fullName = name; + } + + /** + * Returns true if a given string is a valid name. + */ + public static boolean isValidName(String test) { + return test.matches(VALIDATION_REGEX); + } + + + @Override + public String toString() { + return fullName; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof JobName // instanceof handles nulls + && fullName.equals(((JobName) other).fullName)); // state check + } + + @Override + public int hashCode() { + return fullName.hashCode(); + } + +} diff --git a/src/main/java/seedu/address/model/job/UniqueJobList.java b/src/main/java/seedu/address/model/job/UniqueJobList.java new file mode 100644 index 000000000000..faba1354a2a3 --- /dev/null +++ b/src/main/java/seedu/address/model/job/UniqueJobList.java @@ -0,0 +1,163 @@ +package seedu.address.model.job; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; + +import java.util.Iterator; +import java.util.List; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import seedu.address.model.job.exceptions.DuplicateJobException; +import seedu.address.model.job.exceptions.JobNotFoundException; +import seedu.address.model.person.Person; + +/** + * A list of jobs that enforces uniqueness between its elements and does not allow nulls. + * A job is considered unique by comparing using {@code Job#isSameJob(Job)}. As such, adding and updating of + * jobs uses Job#isSameJob(Job) for equality so as to ensure that the job being added or updated is + * unique in terms of identity in the UniqueJobList. However, the removal of a job uses Job#equals(Object) so + * as to ensure that the job with exactly the same fields will be removed. + * + * Supports a minimal set of list operations. + * + * @see Job#isSameJob(Job) + */ +public class UniqueJobList implements Iterable { + + private final ObservableList internalList = FXCollections.observableArrayList(); + private final ObservableList internalUnmodifiableList = + FXCollections.unmodifiableObservableList(internalList); + + /** + * Returns true if the list contains an equivalent job as the given argument. + */ + public boolean contains(Job toCheck) { + requireNonNull(toCheck); + return internalList.stream().anyMatch(toCheck::isSameJob); + } + + /** + * Adds a job to the list. + * The job must not already exist in the list. + */ + public void add(Job toAdd) { + requireNonNull(toAdd); + if (contains(toAdd)) { + throw new DuplicateJobException(); + } + internalList.add(toAdd); + } + + /** + * Removes Person from all jobs. + */ + public void removePerson(Person toRemove) { + for (int i = 0; i < internalList.size(); i++) { + if (internalList.get(i).contains(toRemove)) { + internalList.get(i).remove(toRemove); + } + } + } + + /** + * Removes Person from all jobs. + */ + public void removePersonFromJobList(Person toRemove, JobName job, Integer listNumber) { + Job toEdit = getJob(job); + toEdit.removeFromList(toRemove, listNumber); + } + + /** + * Removes the equivalent job from the list. + * The job must exist in the list. + */ + public void remove(Job toRemove) { + requireNonNull(toRemove); + if (!internalList.remove(toRemove)) { + throw new JobNotFoundException(); + } + } + + public Job getJob(JobName name) { + requireAllNonNull(name); + Job job = new Job(name); + for (int i = 0; i < internalList.size(); i++) { + if (internalList.get(i).isSameJob(job)) { + return internalList.get(i); + } + } + throw new JobNotFoundException(); + } + + /** + * Replaces the Job {@code target} in the list with {@code editedJob}. + * {@code target} must exist in the list. + * The Job identity of {@code editedJob} must not be the same as another existing Job in the list. + */ + public void setJob(Job target, Job editedJob) { + requireAllNonNull(target, editedJob); + + int index = internalList.indexOf(target); + if (index == -1) { + throw new JobNotFoundException(); + } + + if (!target.isSameJob(editedJob) && contains(editedJob)) { + throw new DuplicateJobException(); + } + + internalList.set(index, editedJob); + } + + /** + * Replaces the contents of this list with {@code jobs}. + * {@code jobs} must not contain duplicate jobs. + */ + public void setJobs(List jobs) { + requireAllNonNull(jobs); + if (!jobsAreUnique(jobs)) { + throw new DuplicateJobException(); + } + + internalList.setAll(jobs); + } + + /** + * Returns the backing list as an unmodifiable {@code ObservableList}. + */ + public ObservableList asUnmodifiableObservableList() { + return internalUnmodifiableList; + } + + @Override + public Iterator iterator() { + return internalList.iterator(); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof UniqueJobList // instanceof handles nulls + && internalList.equals(((UniqueJobList) other).internalList)); + } + + @Override + public int hashCode() { + return internalList.hashCode(); + } + + /** + * Returns true if {@code jobs} contains only unique jobs. + */ + private boolean jobsAreUnique(List jobs) { + for (int i = 0; i < jobs.size() - 1; i++) { + for (int j = i + 1; j < jobs.size(); j++) { + if (jobs.get(i).isSameJob(jobs.get(j))) { + return false; + } + } + } + return true; + } +} diff --git a/src/main/java/seedu/address/model/job/exceptions/DuplicateJobException.java b/src/main/java/seedu/address/model/job/exceptions/DuplicateJobException.java new file mode 100644 index 000000000000..f00ae7bd966e --- /dev/null +++ b/src/main/java/seedu/address/model/job/exceptions/DuplicateJobException.java @@ -0,0 +1,11 @@ +package seedu.address.model.job.exceptions; + +/** + * Signals that the operation will result in duplicate Jobs (Persons are considered duplicates if they have the same + * identity). + */ +public class DuplicateJobException extends RuntimeException { + public DuplicateJobException() { + super("Operation would result in duplicate jobs"); + } +} diff --git a/src/main/java/seedu/address/model/job/exceptions/JobNotFoundException.java b/src/main/java/seedu/address/model/job/exceptions/JobNotFoundException.java new file mode 100644 index 000000000000..1816e11cc0e8 --- /dev/null +++ b/src/main/java/seedu/address/model/job/exceptions/JobNotFoundException.java @@ -0,0 +1,6 @@ +package seedu.address.model.job.exceptions; + +/** + * Signals that the operation is unable to find the specified job. + */ +public class JobNotFoundException extends RuntimeException {} diff --git a/src/main/java/seedu/address/model/person/Gender.java b/src/main/java/seedu/address/model/person/Gender.java new file mode 100644 index 000000000000..b337c439f84b --- /dev/null +++ b/src/main/java/seedu/address/model/person/Gender.java @@ -0,0 +1,60 @@ +package seedu.address.model.person; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +import java.util.Arrays; +import java.util.TreeSet; + +/** + * Represents a Person's gender in the address book. + * Guarantees: immutable; is valid as declared in {@link #isValidGender(String)} + */ +public class Gender { + + + public static final String MESSAGE_CONSTRAINTS = + "Not among list of possible genders: [Female, Male, Others]"; + private static final String[] POSSIBLE_GENDERS = {"Female", "Male", "Others"}; + private static final TreeSet POSSIBLE_GENDERS_TREE = new TreeSet<>(Arrays.asList(POSSIBLE_GENDERS)); + public final String value; + + /** + * Constructs a {@code Gender}. + * + * @param gender A valid gender. + */ + public Gender(String gender) { + requireNonNull(gender); + checkArgument(isValidGender(gender), MESSAGE_CONSTRAINTS); + value = gender; + } + + /** + * Returns true if a given string is a valid gender. + */ + public static boolean isValidGender(String test) { + if (test == null) { + throw new NullPointerException("Parameter Type cannot be null"); + } + return POSSIBLE_GENDERS_TREE.contains(test); + } + + @Override + public String toString() { + return value; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Gender // instanceof handles nulls + && value.equals(((Gender) other).value)); // state check + } + + @Override + public int hashCode() { + return value.hashCode(); + } + +} diff --git a/src/main/java/seedu/address/model/person/Grade.java b/src/main/java/seedu/address/model/person/Grade.java new file mode 100644 index 000000000000..ed63885040b4 --- /dev/null +++ b/src/main/java/seedu/address/model/person/Grade.java @@ -0,0 +1,56 @@ +package seedu.address.model.person; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +/** + * Represents a Person's grade in the address book. + * Guarantees: immutable; is valid as declared in {@link #isValidGrade(String)} + */ +public class Grade { + + + public static final String MESSAGE_CONSTRAINTS = + "Grade should only contain positive numbers equals to or less than 5," + + " and must be in exactly 2 decimal places"; + public static final String VALIDATION_REGEX = "[0-4]" + "." + "\\d{2}"; + public static final String VALIDATION_REGEX_FULL = "5.00"; + public final String value; + + /** + * Constructs a {@code Grade}. + * + * @param grade A valid grade. + */ + + public Grade(String grade) { + requireNonNull(grade); + checkArgument(isValidGrade(grade), MESSAGE_CONSTRAINTS); + value = grade; + } + + /** + * Returns true if a given string is a valid phone number. + */ + public static boolean isValidGrade(String test) { + return test.matches(VALIDATION_REGEX) || test.matches(VALIDATION_REGEX_FULL); + } + + @Override + public String toString() { + return value; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Grade // instanceof handles nulls + && value.equals(((Grade) other).value)); // state check + } + + @Override + public int hashCode() { + return value.hashCode(); + } + +} diff --git a/src/main/java/seedu/address/model/person/InterviewScores.java b/src/main/java/seedu/address/model/person/InterviewScores.java new file mode 100644 index 000000000000..1d21852ceedd --- /dev/null +++ b/src/main/java/seedu/address/model/person/InterviewScores.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 Person's interview scores in the address book. + * Guarantees: immutable; is valid as declared in {@link #isValidInterviewScores(String)} + */ +public class InterviewScores { + + + public static final String MESSAGE_CONSTRAINTS = + "Interview Scores should be exactly 5 set of numbers, each number separated by a comma"; + public static final String VALIDATION_REGEX = "\\d+" + "," + "\\d+" + "," + "\\d+" + "," + "\\d+" + + "," + "\\d+"; + public static final String NO_RECORD = "No Record"; + public final String value; + + /** + * Constructs a {@code InterviewScores}. + * + * @param interviewScores A valid set of interview scores. + */ + public InterviewScores(String interviewScores) { + requireNonNull(interviewScores); + checkArgument(isValidInterviewScores(interviewScores), MESSAGE_CONSTRAINTS); + value = interviewScores; + } + + /** + * Returns true if a given string is a valid set of interview scores. + */ + public static boolean isValidInterviewScores(String test) { + return + test.matches(VALIDATION_REGEX) || test.matches(NO_RECORD); + } + + @Override + public String toString() { + return value; + } + + public boolean hasRecord() { + return !value.matches(NO_RECORD); + } + + public String getInterviewScore(int questionNum) { + assert (hasRecord()); + String[] scores = value.split(","); + String score = scores[questionNum - 1].trim(); + return score; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof InterviewScores // instanceof handles nulls + && value.equals(((InterviewScores) other).value)); // state check + } + + @Override + public int hashCode() { + return value.hashCode(); + } + +} diff --git a/src/main/java/seedu/address/model/person/JobsApply.java b/src/main/java/seedu/address/model/person/JobsApply.java new file mode 100644 index 000000000000..2496dc177dc3 --- /dev/null +++ b/src/main/java/seedu/address/model/person/JobsApply.java @@ -0,0 +1,57 @@ +package seedu.address.model.person; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +/** + * Represents a Person's Jobs Applying For in the address book. + * Guarantees: immutable; is valid as declared in {@link #isValidJobsApply(String)} + */ +public class JobsApply { + + public static final String MESSAGE_CONSTRAINTS = "Jobs applied should not start with a whitespace char" + + ", and it should not be blank. Multiple word jobs are to be seperated by '-'."; + /* + * The first character of the job must not be a whitespace, + * otherwise " " (a blank string) becomes a valid input. + */ + public static final String VALIDATION_REGEX = "\\b\\S+"; + + public final String value; + + /** + * Constructs an {@code JobsApply}. + * + * @param jobsApply A valid job applying for. + */ + public JobsApply(String jobsApply) { + requireNonNull(jobsApply); + checkArgument(isValidJobsApply(jobsApply), MESSAGE_CONSTRAINTS); + value = jobsApply; + } + + /** + * Returns true if a given string is a valid job applying for. + */ + public static boolean isValidJobsApply(String test) { + return test.matches(VALIDATION_REGEX); + } + + @Override + public String toString() { + return value; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof JobsApply // instanceof handles nulls + && value.equals(((JobsApply) other).value)); // state check + } + + @Override + public int hashCode() { + return value.hashCode(); + } + +} diff --git a/src/main/java/seedu/address/model/person/KnownProgLang.java b/src/main/java/seedu/address/model/person/KnownProgLang.java new file mode 100644 index 000000000000..5358c4ff8f29 --- /dev/null +++ b/src/main/java/seedu/address/model/person/KnownProgLang.java @@ -0,0 +1,57 @@ +package seedu.address.model.person; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +/** + * Represents a Person's Known Programming Languages in the address book. + * Guarantees: immutable; is valid as declared in {@link #isValidKnownProgLang(String)} + */ +public class KnownProgLang { + + public static final String MESSAGE_CONSTRAINTS = "Known programming languages can take any values"; + + /* + * The first character of the programming language must not be a whitespace, + * otherwise " " (a blank string) becomes a valid input. + */ + public static final String VALIDATION_REGEX = "[^\\s].*"; + + public final String value; + + /** + * Constructs an {@code KnownProgLang}. + * + * @param progLang A valid programming language. + */ + public KnownProgLang(String progLang) { + requireNonNull(progLang); + checkArgument(isValidKnownProgLang(progLang), MESSAGE_CONSTRAINTS); + value = progLang; + } + + /** + * Returns true if a given string is a valid programming language. + */ + public static boolean isValidKnownProgLang(String test) { + return test.matches(VALIDATION_REGEX); + } + + @Override + public String toString() { + return value; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof KnownProgLang // instanceof handles nulls + && value.equals(((KnownProgLang) other).value)); // state check + } + + @Override + public int hashCode() { + return value.hashCode(); + } + +} diff --git a/src/main/java/seedu/address/model/person/Major.java b/src/main/java/seedu/address/model/person/Major.java new file mode 100644 index 000000000000..45a0c1393867 --- /dev/null +++ b/src/main/java/seedu/address/model/person/Major.java @@ -0,0 +1,58 @@ +package seedu.address.model.person; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +/** + * Represents a Person's major in the address book. + * Guarantees: immutable; is valid as declared in {@link #isValidMajor(String)} + */ +public class Major { + + + public static final String MESSAGE_CONSTRAINTS = + "Major 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 value; + + /** + * Constructs a {@code Major}. + * + * @param major A valid major. + */ + public Major(String major) { + requireNonNull(major); + checkArgument(isValidMajor(major), MESSAGE_CONSTRAINTS); + value = major; + } + + /** + * Returns true if a given string is a valid major. + */ + public static boolean isValidMajor(String test) { + return test.matches(VALIDATION_REGEX); + } + + @Override + public String toString() { + return value; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Major // instanceof handles nulls + && value.equals(((Major) other).value)); // state check + } + + @Override + public int hashCode() { + return value.hashCode(); + } + +} diff --git a/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java deleted file mode 100644 index c9b5868427ca..000000000000 --- a/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java +++ /dev/null @@ -1,31 +0,0 @@ -package seedu.address.model.person; - -import java.util.List; -import java.util.function.Predicate; - -import seedu.address.commons.util.StringUtil; - -/** - * Tests that a {@code Person}'s {@code Name} matches any of the keywords given. - */ -public class NameContainsKeywordsPredicate implements Predicate { - private final List keywords; - - public NameContainsKeywordsPredicate(List keywords) { - this.keywords = keywords; - } - - @Override - public boolean test(Person person) { - return keywords.stream() - .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(person.getName().fullName, keyword)); - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof NameContainsKeywordsPredicate // instanceof handles nulls - && keywords.equals(((NameContainsKeywordsPredicate) other).keywords)); // state check - } - -} diff --git a/src/main/java/seedu/address/model/person/Nric.java b/src/main/java/seedu/address/model/person/Nric.java new file mode 100644 index 000000000000..4d48ddf5fe4f --- /dev/null +++ b/src/main/java/seedu/address/model/person/Nric.java @@ -0,0 +1,55 @@ +package seedu.address.model.person; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +/** + * Represents a Person's Nric in the address book. + * Guarantees: immutable; is valid as declared in {@link #isValidNric(String)} + */ +public class Nric { + + + public static final String MESSAGE_CONSTRAINTS = + "NRIC must be unique. It should start with 'S', followed by exactly 7 numbers, " + + "and end with an alphabet in capital letter"; + public static final String VALIDATION_REGEX = "S" + "\\d{7}" + "[A-Z]"; + public final String value; + + /** + * Constructs a {@code Nric}. + * + * @param nric A valid NRIC. + */ + public Nric(String nric) { + requireNonNull(nric); + checkArgument(isValidNric(nric), MESSAGE_CONSTRAINTS); + value = nric; + } + + /** + * Returns true if a given string is a valid NRIC. + */ + public static boolean isValidNric(String test) { + return test.matches(VALIDATION_REGEX); + } + + + @Override + public String toString() { + return value; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Nric // instanceof handles nulls + && value.equals(((Nric) other).value)); // state check + } + + @Override + public int hashCode() { + return value.hashCode(); + } + +} diff --git a/src/main/java/seedu/address/model/person/PastJob.java b/src/main/java/seedu/address/model/person/PastJob.java new file mode 100644 index 000000000000..6e56d5e17049 --- /dev/null +++ b/src/main/java/seedu/address/model/person/PastJob.java @@ -0,0 +1,57 @@ +package seedu.address.model.person; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +/** + * Represents a Person's past job in the address book. + * Guarantees: immutable; is valid as declared in {@link #isValidPastJob(String)} + */ +public class PastJob { + + public static final String MESSAGE_CONSTRAINTS = "Past jobs can take any values, and it should not be blank"; + + /* + * The first character of the past job must not be a whitespace, + * otherwise " " (a blank string) becomes a valid input. + */ + public static final String VALIDATION_REGEX = "[^\\s].*"; + + public final String value; + + /** + * Constructs an {@code PastJob}. + * + * @param pastJob A valid past job. + */ + public PastJob(String pastJob) { + requireNonNull(pastJob); + checkArgument(isValidPastJob(pastJob), MESSAGE_CONSTRAINTS); + value = pastJob; + } + + /** + * Returns true if a given string is a valid past job. + */ + public static boolean isValidPastJob(String test) { + return test.matches(VALIDATION_REGEX); + } + + @Override + public String toString() { + return value; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof PastJob // instanceof handles nulls + && value.equals(((PastJob) other).value)); // state check + } + + @Override + public int hashCode() { + return value.hashCode(); + } + +} diff --git a/src/main/java/seedu/address/model/person/Person.java b/src/main/java/seedu/address/model/person/Person.java index 557a7a60cd51..bfc1abc1626a 100644 --- a/src/main/java/seedu/address/model/person/Person.java +++ b/src/main/java/seedu/address/model/person/Person.java @@ -19,23 +19,62 @@ public class Person { private final Name name; private final Phone phone; private final Email email; + private final Nric nric; // Data fields + private final Gender gender; + private final Race race; private final Address address; + private final School school; + private final Major major; + private final Grade grade; + private final InterviewScores interviewScores; + private final Set knownProgLangs = new HashSet<>(); + private final Set pastjobs = new HashSet<>(); + private final Set jobsApply = new HashSet<>(); private final Set tags = new HashSet<>(); /** * Every field must be present and not null. */ - public Person(Name name, Phone phone, Email email, Address address, Set tags) { - requireAllNonNull(name, phone, email, address, tags); + public Person(Name name, Phone phone, Email email, Nric nric, Gender gender, Race race, Address address, + School school, Major major, Grade grade, Set knownProgLangs, Set pastjobs, + Set jobsApply, InterviewScores interviewScores, Set tags) { + requireAllNonNull(name, phone, email, nric, gender, race, address, school, major, grade, + knownProgLangs, pastjobs, jobsApply, interviewScores, tags); + this.name = name; this.phone = phone; this.email = email; + this.nric = nric; + this.gender = gender; + this.race = race; this.address = address; + this.school = school; + this.major = major; + this.grade = grade; + this.knownProgLangs.addAll(knownProgLangs); + this.pastjobs.addAll(pastjobs); + this.jobsApply.addAll(jobsApply); + this.interviewScores = interviewScores; this.tags.addAll(tags); } + public Person(Nric nric) { + this.nric = nric; + + this.name = null; + this.phone = null; + this.email = null; + this.gender = null; + this.race = null; + this.address = null; + this.school = null; + this.major = null; + this.grade = null; + this.interviewScores = null; + } + public Name getName() { return name; } @@ -48,10 +87,66 @@ public Email getEmail() { return email; } + public Nric getNric() { + return nric; + } + + public Gender getGender() { + return gender; + } + + public Race getRace() { + return race; + } + public Address getAddress() { return address; } + public School getSchool() { + return school; + } + + public Major getMajor() { + return major; + } + + public Grade getGrade() { + return grade; + } + + public InterviewScores getInterviewScores() { + return interviewScores; + } + + public String getInterviewScores(int questionNum) { + return interviewScores.getInterviewScore(questionNum); + } + + /** + * Returns an immutable known programming language set, which throws {@code UnsupportedOperationException} + * if modification is attempted. + */ + public final Set getKnownProgLangs() { + return Collections.unmodifiableSet(knownProgLangs); + } + + /** + * Returns an immutable past job set, which throws {@code UnsupportedOperationException} + * if modification is attempted. + */ + public final Set getPastJobs() { + return Collections.unmodifiableSet(pastjobs); + } + + /** + * Returns an immutable jobs applying for set, which throws {@code UnsupportedOperationException} + * if modification is attempted. + */ + public final Set getJobsApply() { + return Collections.unmodifiableSet(jobsApply); + } + /** * Returns an immutable tag set, which throws {@code UnsupportedOperationException} * if modification is attempted. @@ -70,8 +165,7 @@ public boolean isSamePerson(Person otherPerson) { } return otherPerson != null - && otherPerson.getName().equals(getName()) - && (otherPerson.getPhone().equals(getPhone()) || otherPerson.getEmail().equals(getEmail())); + && otherPerson.getNric().equals(getNric()); } /** @@ -92,14 +186,24 @@ public boolean equals(Object other) { return otherPerson.getName().equals(getName()) && otherPerson.getPhone().equals(getPhone()) && otherPerson.getEmail().equals(getEmail()) + && otherPerson.getNric().equals(getNric()) + && otherPerson.getGender().equals(getGender()) + && otherPerson.getRace().equals(getRace()) && otherPerson.getAddress().equals(getAddress()) - && otherPerson.getTags().equals(getTags()); + && otherPerson.getSchool().equals(getSchool()) + && otherPerson.getPastJobs().equals(getPastJobs()) + && otherPerson.getKnownProgLangs().equals(getKnownProgLangs()) + && otherPerson.getJobsApply().equals(getJobsApply()) + && otherPerson.getInterviewScores().equals(getInterviewScores()) + && otherPerson.getMajor().equals(getMajor()) + && otherPerson.getGrade().equals(getGrade()); } @Override public int hashCode() { // use this method for custom fields hashing instead of implementing your own - return Objects.hash(name, phone, email, address, tags); + return Objects.hash(name, phone, email, nric, gender, race, address, school, major, grade, + knownProgLangs, pastjobs, jobsApply, interviewScores, tags); } @Override @@ -110,9 +214,29 @@ public String toString() { .append(getPhone()) .append(" Email: ") .append(getEmail()) + .append(" Nric: ") + .append(getNric()) + .append(" Gender: ") + .append(getGender()) + .append(" Race: ") + .append(getRace()) .append(" Address: ") .append(getAddress()) - .append(" Tags: "); + .append(" School: ") + .append(getSchool()) + .append(" Major: ") + .append(getMajor()) + .append(" Grade: ") + .append(getGrade()) + .append(" Interview Scores: ") + .append(getInterviewScores()); + builder.append(" Past jobs: "); + getPastJobs().forEach(builder::append); + builder.append(" Known Programming Language(s): "); + getKnownProgLangs().forEach(builder::append); + builder.append(" Job(s) Applying For: "); + getJobsApply().forEach(builder::append); + builder.append(" Tags: "); getTags().forEach(builder::append); return builder.toString(); } diff --git a/src/main/java/seedu/address/model/person/Race.java b/src/main/java/seedu/address/model/person/Race.java new file mode 100644 index 000000000000..56dfd84aeb09 --- /dev/null +++ b/src/main/java/seedu/address/model/person/Race.java @@ -0,0 +1,60 @@ +package seedu.address.model.person; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +import java.util.Arrays; +import java.util.TreeSet; + +/** + * Represents a Person's race in the address book. + * Guarantees: immutable; is valid as declared in {@link #isValidRace(String)} + */ +public class Race { + + + public static final String MESSAGE_CONSTRAINTS = + "Not among list of possible races: [Chinese, Malay, Indian, Others]"; + private static final String[] POSSIBLE_RACES = {"Chinese", "Malay", "Indian", "Others"}; + private static final TreeSet POSSIBLE_RACES_TREE = new TreeSet<>(Arrays.asList(POSSIBLE_RACES)); + public final String value; + + /** + * Constructs a {@code Race}. + * + * @param race A valid race. + */ + public Race(String race) { + requireNonNull(race); + checkArgument(isValidRace(race), MESSAGE_CONSTRAINTS); + value = race; + } + + /** + * Returns true if a given string is a valid race. + */ + public static boolean isValidRace(String test) { + if (test == null) { + throw new NullPointerException("Parameter Type cannot be null"); + } + return POSSIBLE_RACES_TREE.contains(test); + } + + @Override + public String toString() { + return value; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Race // instanceof handles nulls + && value.equals(((Race) other).value)); // state check + } + + @Override + public int hashCode() { + return value.hashCode(); + } + +} diff --git a/src/main/java/seedu/address/model/person/School.java b/src/main/java/seedu/address/model/person/School.java new file mode 100644 index 000000000000..c088acc8c62e --- /dev/null +++ b/src/main/java/seedu/address/model/person/School.java @@ -0,0 +1,48 @@ +package seedu.address.model.person; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +/** + * Represents a Person's address in the address book. + * Guarantees: immutable; is valid as declared in {@link #isValidSchool(String)} + */ +public class School { + + public static final String MESSAGE_CONSTRAINTS = "Schools can take any values, and it should not be blank"; + + /* + * The first character of the address must not be a whitespace, + * otherwise " " (a blank string) becomes a valid input. + */ + public static final String VALIDATION_REGEX = "[^\\s].*"; + + public final String value; + + public School(String school) { + requireNonNull(school); + checkArgument(isValidSchool(school), MESSAGE_CONSTRAINTS); + value = school; + } + + public static boolean isValidSchool(String test) { + return test.matches(VALIDATION_REGEX); + } + + @Override + public String toString() { + return value; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof School // instanceof handles null + && value.equals(((School) other).value)); // state check + } + + @Override + public int hashCode() { + return value.hashCode(); + } +} diff --git a/src/main/java/seedu/address/model/person/UniqueNricMap.java b/src/main/java/seedu/address/model/person/UniqueNricMap.java new file mode 100644 index 000000000000..af8d3cdf0f6e --- /dev/null +++ b/src/main/java/seedu/address/model/person/UniqueNricMap.java @@ -0,0 +1,148 @@ +package seedu.address.model.person; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; + +import java.util.List; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableMap; +import seedu.address.model.person.exceptions.DuplicatePersonException; +import seedu.address.model.person.exceptions.PersonNotFoundException; + +/** + * A mapping of unique NRIC to person that enforces uniqueness between its elements and does not allow nulls. + * A person is solely defined as unique by it's NRIC, regardless of update of other fields of a person. + * + * Supports a minimal set of list operations. + * + * @see Person#isSamePerson(Person) + */ +public class UniqueNricMap { + + private final ObservableMap internalMap = FXCollections.observableHashMap(); + private final ObservableMap internalUnmodifiableMap = + FXCollections.unmodifiableObservableMap(internalMap); + + /** + * Returns true if the list contains an equivalent NRIC as the given argument. + */ + public boolean contains(Nric toCheck) { + requireNonNull(toCheck); + return internalMap.containsKey(toCheck); + } + + /** + * Adds a NRIC and corresponding person to the list. + * The NRIC must not already exist in the list. + */ + public void add(Nric nric, Person person) { + requireNonNull(nric); + requireNonNull(person); + if (contains(nric)) { + throw new DuplicatePersonException(); + } + internalMap.put(nric, person); + } + + /** + * Replaces the person {@code target} in the list with {@code editedPerson}. + * {@code target} must exist in the list + * Both {@code target} and {@code editedPerson} must have same NRIC. + */ + public void setPerson(Person target, Person editedPerson) { + requireAllNonNull(target, editedPerson); + + Nric targetNric = target.getNric(); + Nric editedPersonNric = editedPerson.getNric(); + if (!contains(targetNric)) { + throw new PersonNotFoundException(); + } + + if (!targetNric.equals(editedPersonNric) && contains(editedPersonNric)) { + throw new DuplicatePersonException(); + } + + if (!targetNric.equals(editedPersonNric)) { + internalMap.remove(targetNric); + } + + internalMap.put(editedPersonNric, editedPerson); + } + + /** + * Removes the NRIC and equivalent person from the list. + * The NRIC must exist in the list. + */ + public void remove(Nric toRemove) { + requireNonNull(toRemove); + if (!contains(toRemove)) { + throw new PersonNotFoundException(); + } else { + internalMap.remove(toRemove); + } + } + + /** + * Returns the backing list as an unmodifiable {@code ObservableList}. + */ + public ObservableMap asUnmodifiableObservableMap() { + return internalUnmodifiableMap; + } + + + public void setNricMap(UniqueNricMap replacement) { + requireNonNull(replacement); + internalMap.clear(); + for (Nric nric : replacement.internalMap.keySet()) { + internalMap.put(nric, replacement.internalMap.get(nric)); + } + } + + /** + * Replaces the contents of this list with {@code persons}. + * {@code persons} must not contain duplicate persons. + */ + public void setNricMap(List persons) { + requireAllNonNull(persons); + if (!personsAreUnique(persons)) { + throw new DuplicatePersonException(); + } + + internalMap.clear(); + + for (int i = 0; i < persons.size(); i++) { + internalMap.put(persons.get(i).getNric(), persons.get(i)); + } + } + + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof UniqueNricMap // instanceof handles nulls + && internalMap.equals(((UniqueNricMap) other).internalMap)); + } + + @Override + public int hashCode() { + return internalMap.hashCode(); + } + + /** + * Returns true if {@code persons} contains only unique persons. + */ + private boolean personsAreUnique(List persons) { + for (int i = 0; i < persons.size() - 1; i++) { + for (int j = i + 1; j < persons.size(); j++) { + if (persons.get(i).isSamePerson(persons.get(j))) { + return false; + } + } + } + return true; + } +} + + + diff --git a/src/main/java/seedu/address/model/person/UniquePersonList.java b/src/main/java/seedu/address/model/person/UniquePersonList.java index 0fee4fe57e6b..12a702fe6eed 100644 --- a/src/main/java/seedu/address/model/person/UniquePersonList.java +++ b/src/main/java/seedu/address/model/person/UniquePersonList.java @@ -36,6 +36,17 @@ public boolean contains(Person toCheck) { return internalList.stream().anyMatch(toCheck::isSamePerson); } + public Person getPerson(Nric nric) { + requireAllNonNull(nric); + Person person = new Person(nric); + for (int i = 0; i < internalList.size(); i++) { + if (internalList.get(i).isSamePerson(person)) { + return internalList.get(i); + } + } + return null; + } + /** * Adds a person to the list. * The person must not already exist in the list. @@ -96,6 +107,12 @@ public void setPersons(List persons) { internalList.setAll(persons); } + /** + * Returns number of people in the list + * */ + public int size() { + return internalList.size(); + } /** * Returns the backing list as an unmodifiable {@code ObservableList}. diff --git a/src/main/java/seedu/address/model/person/exceptions/DuplicateFilterException.java b/src/main/java/seedu/address/model/person/exceptions/DuplicateFilterException.java new file mode 100644 index 000000000000..5720b7f3940b --- /dev/null +++ b/src/main/java/seedu/address/model/person/exceptions/DuplicateFilterException.java @@ -0,0 +1,11 @@ +package seedu.address.model.person.exceptions; + +/** + * Signals that the operation will result in duplicate filter (Filters are considered duplicates if they have the same + * filter name). + */ +public class DuplicateFilterException extends RuntimeException { + public DuplicateFilterException() { + super("Operation would result in duplicate filter"); + } +} diff --git a/src/main/java/seedu/address/model/person/exceptions/FilterNotFoundException.java b/src/main/java/seedu/address/model/person/exceptions/FilterNotFoundException.java new file mode 100644 index 000000000000..bef78241b4f9 --- /dev/null +++ b/src/main/java/seedu/address/model/person/exceptions/FilterNotFoundException.java @@ -0,0 +1,6 @@ +package seedu.address.model.person.exceptions; + +/** + * Signals that the operation is unable to find the specified filter. + */ +public class FilterNotFoundException extends RuntimeException {} diff --git a/src/main/java/seedu/address/model/person/predicate/AddressContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/person/predicate/AddressContainsKeywordsPredicate.java new file mode 100644 index 000000000000..a7e1404730d9 --- /dev/null +++ b/src/main/java/seedu/address/model/person/predicate/AddressContainsKeywordsPredicate.java @@ -0,0 +1,31 @@ +package seedu.address.model.person.predicate; + +import java.util.List; + +import seedu.address.commons.util.StringUtil; +import seedu.address.model.person.Person; + +/** + * Tests that a {@code Person}'s {@code Phone} matches any of the keywords given. + */ +public class AddressContainsKeywordsPredicate extends PredicateManager { + private final List keywords; + + public AddressContainsKeywordsPredicate(List keywords) { + this.keywords = keywords; + } + + @Override + public boolean test(Person person) { + return (keywords == null) || keywords.stream() + .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(person.getAddress().value, keyword)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof AddressContainsKeywordsPredicate // instanceof handles nulls + && keywords.equals(((AddressContainsKeywordsPredicate) other).keywords)); // state check + } + +} diff --git a/src/main/java/seedu/address/model/person/predicate/EmailContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/person/predicate/EmailContainsKeywordsPredicate.java new file mode 100644 index 000000000000..2c9992b6cfa8 --- /dev/null +++ b/src/main/java/seedu/address/model/person/predicate/EmailContainsKeywordsPredicate.java @@ -0,0 +1,31 @@ +package seedu.address.model.person.predicate; + +import java.util.List; + +import seedu.address.commons.util.StringUtil; +import seedu.address.model.person.Person; + +/** + * Tests that a {@code Person}'s {@code Phone} matches any of the keywords given. + */ +public class EmailContainsKeywordsPredicate extends PredicateManager { + private final List keywords; + + public EmailContainsKeywordsPredicate(List keywords) { + this.keywords = keywords; + } + + @Override + public boolean test(Person person) { + return (keywords == null) || keywords.stream() + .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(person.getEmail().value, keyword)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof EmailContainsKeywordsPredicate // instanceof handles nulls + && keywords.equals(((EmailContainsKeywordsPredicate) other).keywords)); // state check + } + +} diff --git a/src/main/java/seedu/address/model/person/predicate/Filter.java b/src/main/java/seedu/address/model/person/predicate/Filter.java new file mode 100644 index 000000000000..dce60115249a --- /dev/null +++ b/src/main/java/seedu/address/model/person/predicate/Filter.java @@ -0,0 +1,61 @@ +package seedu.address.model.person.predicate; + +import java.util.function.Predicate; + +import seedu.address.model.person.Person; + +/** + * Represents a filter in the address book job list. + */ +public class Filter { + private String filterName; + private Predicate predicate; + + /** + * Constructs a {@code Filter}. + * + * @param filterName A valid filter name. + */ + public Filter(String filterName) { + this.filterName = filterName; + this.predicate = null; + } + /** + * Constructs a {@code Filter}. + * + * @param filterName A valid filter name. + * @param predicate A valid filter name. + */ + public Filter(String filterName, Predicate predicate) { + this.filterName = filterName; + this.predicate = predicate; + } + + /** + * Returns true if both filters of the same name. + * This defines a weaker notion of equality between two filters. + */ + public boolean isSameFilter(Filter otherFilter) { + if (otherFilter == this) { + return true; + } + + return otherFilter != null + && otherFilter.getFilterName().equals(this.getFilterName()); + } + + public Predicate getPredicate() { + return predicate; + } + + public String getFilterName() { + return filterName; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Filter // instanceof handles nulls + && filterName.equals(((Filter) other).getFilterName())); // state check + } +} diff --git a/src/main/java/seedu/address/model/person/predicate/GenderContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/person/predicate/GenderContainsKeywordsPredicate.java new file mode 100644 index 000000000000..d21eb236a394 --- /dev/null +++ b/src/main/java/seedu/address/model/person/predicate/GenderContainsKeywordsPredicate.java @@ -0,0 +1,31 @@ +package seedu.address.model.person.predicate; + +import java.util.List; + +import seedu.address.commons.util.StringUtil; +import seedu.address.model.person.Person; + +/** + * Tests that a {@code Person}'s {@code Gender} matches any of the keywords given. + */ +public class GenderContainsKeywordsPredicate extends PredicateManager { + private final List keywords; + + public GenderContainsKeywordsPredicate(List keywords) { + this.keywords = keywords; + } + + @Override + public boolean test(Person person) { + return (keywords == null) || keywords.stream() + .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(person.getGender().value, keyword)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof GenderContainsKeywordsPredicate // instanceof handles nulls + && keywords.equals(((GenderContainsKeywordsPredicate) other).keywords)); // state check + } + +} diff --git a/src/main/java/seedu/address/model/person/predicate/GradeContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/person/predicate/GradeContainsKeywordsPredicate.java new file mode 100644 index 000000000000..f129a2bfbae3 --- /dev/null +++ b/src/main/java/seedu/address/model/person/predicate/GradeContainsKeywordsPredicate.java @@ -0,0 +1,31 @@ +package seedu.address.model.person.predicate; + +import java.util.List; + +import seedu.address.commons.util.StringUtil; +import seedu.address.model.person.Person; + +/** + * Tests that a {@code Person}'s {@code Phone} matches any of the keywords given. + */ +public class GradeContainsKeywordsPredicate extends PredicateManager { + private final List keywords; + + public GradeContainsKeywordsPredicate(List keywords) { + this.keywords = keywords; + } + + @Override + public boolean test(Person person) { + return (keywords == null) || keywords.stream() + .anyMatch(keyword -> StringUtil.valueInRange(keyword, person.getGrade().value)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof GradeContainsKeywordsPredicate // instanceof handles nulls + && keywords.equals(((GradeContainsKeywordsPredicate) other).keywords)); // state check + } + +} diff --git a/src/main/java/seedu/address/model/person/predicate/InterviewScoreContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/person/predicate/InterviewScoreContainsKeywordsPredicate.java new file mode 100644 index 000000000000..d208e001b6d4 --- /dev/null +++ b/src/main/java/seedu/address/model/person/predicate/InterviewScoreContainsKeywordsPredicate.java @@ -0,0 +1,34 @@ +package seedu.address.model.person.predicate; + +import java.util.List; + +import seedu.address.commons.util.StringUtil; +import seedu.address.model.person.Person; + +/** + * Tests that a {@code Person}'s {@code Phone} matches any of the keywords given. + */ +public class InterviewScoreContainsKeywordsPredicate extends PredicateManager { + private final List keywords; + private final int questionNum; + + public InterviewScoreContainsKeywordsPredicate(int questionNum, List keywords) { + this.questionNum = questionNum; + this.keywords = keywords; + } + + @Override + public boolean test(Person person) { + return (keywords == null) || keywords.stream() + .anyMatch(keyword -> StringUtil.valueInRange(keyword, person.getInterviewScores(questionNum))); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof InterviewScoreContainsKeywordsPredicate // instanceof handles nulls + && keywords.equals(((InterviewScoreContainsKeywordsPredicate) other).keywords)) + && questionNum == ((InterviewScoreContainsKeywordsPredicate) other).questionNum; // state check + } + +} diff --git a/src/main/java/seedu/address/model/person/predicate/JobsApplyContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/person/predicate/JobsApplyContainsKeywordsPredicate.java new file mode 100644 index 000000000000..b699530d128b --- /dev/null +++ b/src/main/java/seedu/address/model/person/predicate/JobsApplyContainsKeywordsPredicate.java @@ -0,0 +1,33 @@ +package seedu.address.model.person.predicate; + +import java.util.List; + +import seedu.address.commons.util.StringUtil; +import seedu.address.model.person.Person; + +/** + * Tests that a {@code Person}'s {@code Gender} matches any of the keywords given. + */ +public class JobsApplyContainsKeywordsPredicate extends PredicateManager { + private final List keywords; + + public JobsApplyContainsKeywordsPredicate(List keywords) { + this.keywords = keywords; + } + + @Override + public boolean test(Person person) { + + return (keywords == null) || keywords.stream() + .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(StringUtil.getSetString + (person.getJobsApply()), keyword)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof JobsApplyContainsKeywordsPredicate // instanceof handles nulls + && keywords.equals(((JobsApplyContainsKeywordsPredicate) other).keywords)); // state check + } + +} diff --git a/src/main/java/seedu/address/model/person/predicate/KnownProgLangContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/person/predicate/KnownProgLangContainsKeywordsPredicate.java new file mode 100644 index 000000000000..76ba1b1fb72e --- /dev/null +++ b/src/main/java/seedu/address/model/person/predicate/KnownProgLangContainsKeywordsPredicate.java @@ -0,0 +1,33 @@ +package seedu.address.model.person.predicate; + +import java.util.List; + +import seedu.address.commons.util.StringUtil; +import seedu.address.model.person.Person; + +/** + * Tests that a {@code Person}'s {@code Gender} matches any of the keywords given. + */ +public class KnownProgLangContainsKeywordsPredicate extends PredicateManager { + private final List keywords; + + public KnownProgLangContainsKeywordsPredicate(List keywords) { + this.keywords = keywords; + } + + @Override + public boolean test(Person person) { + + return (keywords == null) || keywords.stream() + .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(StringUtil.getSetString + (person.getKnownProgLangs()), keyword)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof KnownProgLangContainsKeywordsPredicate // instanceof handles nulls + && keywords.equals(((KnownProgLangContainsKeywordsPredicate) other).keywords)); // state check + } + +} diff --git a/src/main/java/seedu/address/model/person/predicate/MajorContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/person/predicate/MajorContainsKeywordsPredicate.java new file mode 100644 index 000000000000..dc2be8eda8fe --- /dev/null +++ b/src/main/java/seedu/address/model/person/predicate/MajorContainsKeywordsPredicate.java @@ -0,0 +1,31 @@ +package seedu.address.model.person.predicate; + +import java.util.List; + +import seedu.address.commons.util.StringUtil; +import seedu.address.model.person.Person; + +/** + * Tests that a {@code Person}'s {@code Major} matches any of the keywords given. + */ +public class MajorContainsKeywordsPredicate extends PredicateManager { + private final List keywords; + + public MajorContainsKeywordsPredicate(List keywords) { + this.keywords = keywords; + } + + @Override + public boolean test(Person person) { + return (keywords == null) || keywords.stream() + .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(person.getMajor().value, keyword)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof MajorContainsKeywordsPredicate // instanceof handles nulls + && keywords.equals(((MajorContainsKeywordsPredicate) other).keywords)); // state check + } + +} diff --git a/src/main/java/seedu/address/model/person/predicate/NameContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/person/predicate/NameContainsKeywordsPredicate.java new file mode 100644 index 000000000000..0b10fe4dd8d5 --- /dev/null +++ b/src/main/java/seedu/address/model/person/predicate/NameContainsKeywordsPredicate.java @@ -0,0 +1,31 @@ +package seedu.address.model.person.predicate; + +import java.util.List; + +import seedu.address.commons.util.StringUtil; +import seedu.address.model.person.Person; + +/** + * Tests that a {@code Person}'s {@code Name} matches any of the keywords given. + */ +public class NameContainsKeywordsPredicate extends PredicateManager { + private final List keywords; + + public NameContainsKeywordsPredicate(List keywords) { + this.keywords = keywords; + } + + @Override + public boolean test(Person person) { + return (keywords == null) || keywords.stream() + .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(person.getName().fullName, keyword)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof NameContainsKeywordsPredicate // instanceof handles nulls + && keywords.equals(((NameContainsKeywordsPredicate) other).keywords)); // state check + } + +} diff --git a/src/main/java/seedu/address/model/person/predicate/NricContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/person/predicate/NricContainsKeywordsPredicate.java new file mode 100644 index 000000000000..f13aa843f8c7 --- /dev/null +++ b/src/main/java/seedu/address/model/person/predicate/NricContainsKeywordsPredicate.java @@ -0,0 +1,31 @@ +package seedu.address.model.person.predicate; + +import java.util.List; + +import seedu.address.commons.util.StringUtil; +import seedu.address.model.person.Person; + +/** + * Tests that a {@code Person}'s {@code Phone} matches any of the keywords given. + */ +public class NricContainsKeywordsPredicate extends PredicateManager { + private final List keywords; + + public NricContainsKeywordsPredicate(List keywords) { + this.keywords = keywords; + } + + @Override + public boolean test(Person person) { + return (keywords == null) || keywords.stream() + .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(person.getNric().value, keyword)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof NricContainsKeywordsPredicate // instanceof handles nulls + && keywords.equals(((NricContainsKeywordsPredicate) other).keywords)); // state check + } + +} diff --git a/src/main/java/seedu/address/model/person/predicate/PastJobContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/person/predicate/PastJobContainsKeywordsPredicate.java new file mode 100644 index 000000000000..2e867648a59f --- /dev/null +++ b/src/main/java/seedu/address/model/person/predicate/PastJobContainsKeywordsPredicate.java @@ -0,0 +1,33 @@ +package seedu.address.model.person.predicate; + +import java.util.List; + +import seedu.address.commons.util.StringUtil; +import seedu.address.model.person.Person; + +/** + * Tests that a {@code Person}'s {@code Gender} matches any of the keywords given. + */ +public class PastJobContainsKeywordsPredicate extends PredicateManager { + private final List keywords; + + public PastJobContainsKeywordsPredicate(List keywords) { + this.keywords = keywords; + } + + @Override + public boolean test(Person person) { + + return (keywords == null) || keywords.stream() + .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(StringUtil.getSetString + (person.getPastJobs()), keyword)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof PastJobContainsKeywordsPredicate // instanceof handles nulls + && keywords.equals(((PastJobContainsKeywordsPredicate) other).keywords)); // state check + } + +} diff --git a/src/main/java/seedu/address/model/person/predicate/PhoneContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/person/predicate/PhoneContainsKeywordsPredicate.java new file mode 100644 index 000000000000..860ee764a6a1 --- /dev/null +++ b/src/main/java/seedu/address/model/person/predicate/PhoneContainsKeywordsPredicate.java @@ -0,0 +1,31 @@ +package seedu.address.model.person.predicate; + +import java.util.List; + +import seedu.address.commons.util.StringUtil; +import seedu.address.model.person.Person; + +/** + * Tests that a {@code Person}'s {@code Phone} matches any of the keywords given. + */ +public class PhoneContainsKeywordsPredicate extends PredicateManager { + private final List keywords; + + public PhoneContainsKeywordsPredicate(List keywords) { + this.keywords = keywords; + } + + @Override + public boolean test(Person person) { + return (keywords == null) || keywords.stream() + .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(person.getPhone().value, keyword)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof PhoneContainsKeywordsPredicate // instanceof handles nulls + && keywords.equals(((PhoneContainsKeywordsPredicate) other).keywords)); // state check + } + +} diff --git a/src/main/java/seedu/address/model/person/predicate/PredicateManager.java b/src/main/java/seedu/address/model/person/predicate/PredicateManager.java new file mode 100644 index 000000000000..f545824a48ff --- /dev/null +++ b/src/main/java/seedu/address/model/person/predicate/PredicateManager.java @@ -0,0 +1,26 @@ +package seedu.address.model.person.predicate; + +import java.util.function.Predicate; + +import seedu.address.model.person.Person; + +/** + * Tests that a {@code Person}'s {@code Phone} matches any of the keywords given. + */ +public class PredicateManager implements Predicate { + + + @Override + public boolean test(Person person) { + return true; + } + + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof PredicateManager); // instanceof handles null + } + + +} diff --git a/src/main/java/seedu/address/model/person/predicate/RaceContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/person/predicate/RaceContainsKeywordsPredicate.java new file mode 100644 index 000000000000..7a298edb5597 --- /dev/null +++ b/src/main/java/seedu/address/model/person/predicate/RaceContainsKeywordsPredicate.java @@ -0,0 +1,31 @@ +package seedu.address.model.person.predicate; + +import java.util.List; + +import seedu.address.commons.util.StringUtil; +import seedu.address.model.person.Person; + +/** + * Tests that a {@code Person}'s {@code Phone} matches any of the keywords given. + */ +public class RaceContainsKeywordsPredicate extends PredicateManager { + private final List keywords; + + public RaceContainsKeywordsPredicate(List keywords) { + this.keywords = keywords; + } + + @Override + public boolean test(Person person) { + return (keywords == null) || keywords.stream() + .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(person.getRace().value, keyword)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof RaceContainsKeywordsPredicate // instanceof handles nulls + && keywords.equals(((RaceContainsKeywordsPredicate) other).keywords)); // state check + } + +} diff --git a/src/main/java/seedu/address/model/person/predicate/SchoolContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/person/predicate/SchoolContainsKeywordsPredicate.java new file mode 100644 index 000000000000..fdcc6558d462 --- /dev/null +++ b/src/main/java/seedu/address/model/person/predicate/SchoolContainsKeywordsPredicate.java @@ -0,0 +1,31 @@ +package seedu.address.model.person.predicate; + +import java.util.List; + +import seedu.address.commons.util.StringUtil; +import seedu.address.model.person.Person; + +/** + * Tests that a {@code Person}'s {@code Phone} matches any of the keywords given. + */ +public class SchoolContainsKeywordsPredicate extends PredicateManager { + private final List keywords; + + public SchoolContainsKeywordsPredicate(List keywords) { + this.keywords = keywords; + } + + @Override + public boolean test(Person person) { + return (keywords == null) || keywords.stream() + .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(person.getSchool().value, keyword)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof SchoolContainsKeywordsPredicate // instanceof handles nulls + && keywords.equals(((SchoolContainsKeywordsPredicate) other).keywords)); // state check + } + +} diff --git a/src/main/java/seedu/address/model/person/predicate/UniqueFilterList.java b/src/main/java/seedu/address/model/person/predicate/UniqueFilterList.java new file mode 100644 index 000000000000..ad69e806c4d5 --- /dev/null +++ b/src/main/java/seedu/address/model/person/predicate/UniqueFilterList.java @@ -0,0 +1,76 @@ +package seedu.address.model.person.predicate; + +import static java.util.Objects.requireNonNull; + +import java.util.Iterator; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import seedu.address.model.person.exceptions.DuplicateFilterException; +import seedu.address.model.person.exceptions.FilterNotFoundException; + +/** + * A list of filters that enforces uniqueness between its elements and does not allow nulls. + * A filter is considered unique by comparing using {@code Filter#isSameFilter(Filter)}. As such, adding and updating of + * filters uses Filter#isSameFilter(Filter) for equality so as to ensure that the filter being added or updated is + * unique in terms of identity in the UniqueFilterList. However, the removal of a filter uses Filter#equals(Object) so + * as to ensure that the filter with exactly the same fields will be removed. + *

+ * Supports a minimal set of list operations. + * + * @see Filter#isSameFilter(Filter) + */ +public class UniqueFilterList implements Iterable { + + private final ObservableList internalList = FXCollections.observableArrayList(); + + /** + * Returns true if the list contains an equivalent person as the given argument. + */ + public boolean contains(Filter toCheck) { + requireNonNull(toCheck); + return internalList.stream().anyMatch(toCheck::isSameFilter); + } + + /** + * Adds a person to the list. + * The person must not already exist in the list. + */ + public void add(Filter toAdd) { + requireNonNull(toAdd); + if (contains(toAdd)) { + throw new DuplicateFilterException(); + } + internalList.add(toAdd); + } + + /** + * Removes the equivalent person from the list. + * The person must exist in the list. + */ + public void remove(Filter toRemove) { + requireNonNull(toRemove); + if (!internalList.remove(toRemove)) { + throw new FilterNotFoundException(); + } + } + + + @Override + public Iterator iterator() { + return internalList.iterator(); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof UniqueFilterList // instanceof handles nulls + && internalList.equals(((UniqueFilterList) other).internalList)); + } + + @Override + public int hashCode() { + return internalList.hashCode(); + } + +} diff --git a/src/main/java/seedu/address/model/tag/Tag.java b/src/main/java/seedu/address/model/tag/Tag.java index b0ea7e7dad7f..b7b6d54f1e82 100644 --- a/src/main/java/seedu/address/model/tag/Tag.java +++ b/src/main/java/seedu/address/model/tag/Tag.java @@ -24,6 +24,12 @@ public Tag(String tagName) { checkArgument(isValidTagName(tagName), MESSAGE_CONSTRAINTS); this.tagName = tagName; } + /** + * Returns tag name + */ + public String getName() { + return tagName; + } /** * Returns true if a given string is a valid tag name. @@ -32,6 +38,7 @@ public static boolean isValidTagName(String test) { return test.matches(VALIDATION_REGEX); } + @Override public boolean equals(Object other) { return other == this // short circuit if same object diff --git a/src/main/java/seedu/address/model/tag/UniqueTagList.java b/src/main/java/seedu/address/model/tag/UniqueTagList.java new file mode 100644 index 000000000000..cabe3a91105a --- /dev/null +++ b/src/main/java/seedu/address/model/tag/UniqueTagList.java @@ -0,0 +1,71 @@ +package seedu.address.model.tag; + +import java.util.Set; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.collections.ObservableMap; +import seedu.address.model.person.Person; + + +/** + * A list keeping track of all Tags, and for each tag, keeps track of every person that has the tag. + */ + +public class UniqueTagList { + private final ObservableMap> tagAndPersonList = FXCollections.observableHashMap(); + private final ObservableMap> unmodifiableTagAndPersonList = + FXCollections.unmodifiableObservableMap(tagAndPersonList); + + /** + * Adds person to the list of every tag that this person is tagged in + */ + public void addPerson(Person toAdd) { + Set personTagList = toAdd.getTags(); + for (Tag i : personTagList) { + if (tagAndPersonList.containsKey(i)) { + tagAndPersonList.get(i).add(toAdd); + } else { + ObservableList newList = FXCollections.observableArrayList(); + newList.add(toAdd); + tagAndPersonList.put(i, newList); + } + } + } + + /** + * Find person in every tag that contains the person and remove the person + */ + public void removePerson(Person toRemove) { + Set personTagList = toRemove.getTags(); + for (Tag i : personTagList) { + if (tagAndPersonList.containsKey(i)) { + tagAndPersonList.get(i).remove(toRemove); + } else { + continue; + } + } + } + + /** + * Removes the entire tag from tag list + */ + public void removeEntireTag(Tag tag) { + tagAndPersonList.remove(tag); + } + + + public ObservableList getListOfPerson(Tag tag) { + return tagAndPersonList.get(tag); + } + + + /** + * Returns the backing list as an unmodifiable {@code ObservableList}. + */ + public ObservableMap> asUnmodifiableObservableMap() { + return unmodifiableTagAndPersonList; + } +} + + diff --git a/src/main/java/seedu/address/model/util/SampleDataUtil.java b/src/main/java/seedu/address/model/util/SampleDataUtil.java index 1806da4facfa..37bac2b50d0d 100644 --- a/src/main/java/seedu/address/model/util/SampleDataUtil.java +++ b/src/main/java/seedu/address/model/util/SampleDataUtil.java @@ -8,9 +8,19 @@ import seedu.address.model.ReadOnlyAddressBook; import seedu.address.model.person.Address; import seedu.address.model.person.Email; +import seedu.address.model.person.Gender; +import seedu.address.model.person.Grade; +import seedu.address.model.person.InterviewScores; +import seedu.address.model.person.JobsApply; +import seedu.address.model.person.KnownProgLang; +import seedu.address.model.person.Major; import seedu.address.model.person.Name; +import seedu.address.model.person.Nric; +import seedu.address.model.person.PastJob; import seedu.address.model.person.Person; import seedu.address.model.person.Phone; +import seedu.address.model.person.Race; +import seedu.address.model.person.School; import seedu.address.model.tag.Tag; /** @@ -18,25 +28,43 @@ */ public class SampleDataUtil { public static Person[] getSamplePersons() { - return new Person[] { + return new Person[]{ new Person(new Name("Alex Yeoh"), new Phone("87438807"), new Email("alexyeoh@example.com"), - new Address("Blk 30 Geylang Street 29, #06-40"), - getTagSet("friends")), + new Nric("S9671597H"), new Gender("Male"), new Race("Chinese"), + new Address("Blk 30 Geylang Street 29, #06-40"), new School("NUS"), new Major("CS"), + new Grade("4.56"), getKnownProgLangSet("python"), getPastJobSet("Professor", "SDE"), + getJobsApplySet("Software-Engineer"), new InterviewScores("5,5,1,1,3"), + 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 Nric("S9412345A"), new Gender("Female"), new Race("Chinese"), + new Address("Blk 30 Lorong 3 Serangoon Gardens, #07-18"), new School("NTU"), new Major("CS"), + new Grade("3.59"), getKnownProgLangSet("python"), getPastJobSet("Lawyer"), + getJobsApplySet("Lawyer-Intern"), new InterviewScores("10,9,8,7,6"), + 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 Nric("S9354321R"), new Gender("Female"), new Race("Others"), + new Address("Blk 11 Ang Mo Kio Street 74, #11-04"), new School("SMU"), new Major("CS"), + new Grade("4.00"), getKnownProgLangSet("python"), getPastJobSet("Doctor"), + getJobsApplySet("Data-Analyst"), new InterviewScores("5,6,7,8,10"), + 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 Nric("S9212345Z"), new Gender("Male"), new Race("Chinese"), + new Address("Blk 436 Serangoon Gardens Street 26, #16-43"), new School("SUSS"), new Major("CS"), + new Grade("5.00"), getKnownProgLangSet("python"), getPastJobSet("Data Scientist"), + getJobsApplySet("Investment-Analyst"), new InterviewScores("10,10,10,10,10"), + 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 Nric("S9123456G"), new Gender("Male"), new Race("Malay"), + new Address("Blk 47 Tampines Street 20, #17-35"), new School("SIT"), new Major("CS"), + new Grade("4.96"), getKnownProgLangSet("python"), getPastJobSet("Software Engineer"), + getJobsApplySet("Software-Engineer"), new InterviewScores("9,8,2,1,10"), + 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 Nric("S9012312T"), new Gender("Male"), new Race("Indian"), + new Address("Blk 45 Aljunied Street 85, #11-31"), new School("SUTD"), new Major("CS"), + new Grade("3.29"), getKnownProgLangSet("python"), getPastJobSet("Professor"), + getJobsApplySet("Security"), new InterviewScores("1,1,1,1,1"), + getTagSet("colleagues")) }; } @@ -48,13 +76,44 @@ public static ReadOnlyAddressBook getSampleAddressBook() { return sampleAb; } + /** + * Returns a past job set containing the list of strings given. + */ + public static Set getKnownProgLangSet(String... strings) { + return Arrays.stream(strings) + .map(KnownProgLang::new) + .collect(Collectors.toSet()); + } + + /** + * Returns a past job set containing the list of strings given. + */ + public static Set getPastJobSet(String... strings) { + return Arrays.stream(strings) + .map(PastJob::new) + .collect(Collectors.toSet()); + + + } + + /** + * Returns a jobs applying for set containing the list of strings given. + */ + public static Set getJobsApplySet(String... strings) { + return Arrays.stream(strings) + .map(JobsApply::new) + .collect(Collectors.toSet()); + + + } + /** * Returns a tag set containing the list of strings given. */ public static Set getTagSet(String... strings) { return Arrays.stream(strings) - .map(Tag::new) - .collect(Collectors.toSet()); + .map(Tag::new) + .collect(Collectors.toSet()); } } diff --git a/src/main/java/seedu/address/storage/JsonAdaptedJob.java b/src/main/java/seedu/address/storage/JsonAdaptedJob.java new file mode 100644 index 000000000000..c47e7e47b382 --- /dev/null +++ b/src/main/java/seedu/address/storage/JsonAdaptedJob.java @@ -0,0 +1,174 @@ +package seedu.address.storage; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.job.Job; +import seedu.address.model.job.JobName; +import seedu.address.model.person.Nric; +import seedu.address.model.person.Person; +import seedu.address.model.person.UniquePersonList; + +/** + * Jackson-friendly version of {@link Job}. + */ +class JsonAdaptedJob { + + public static final String MISSING_FIELD_MESSAGE_FORMAT = "job %s field is missing!"; + + private final String jobName; + private final List list1 = new ArrayList<>(); + private final List list2 = new ArrayList<>(); + private final List list3 = new ArrayList<>(); + private final List list4 = new ArrayList<>(); + + /** + * Constructs a {@code JsonAdaptedPerson} with the given person details. + */ + @JsonCreator + public JsonAdaptedJob(@JsonProperty("jobName") String jobName, + @JsonProperty("list1") List list1, + @JsonProperty("list2") List list2, + @JsonProperty("list3") List list3, + @JsonProperty("list4") List list4) { + + this.jobName = jobName; + if (list1 != null) { + this.list1.addAll(list1); + } + if (list2 != null) { + this.list2.addAll(list2); + } + if (list3 != null) { + this.list3.addAll(list3); + } + if (list4 != null) { + this.list4.addAll(list4); + } + } + + /** + * Converts a given {@code Job} into this class for Jackson use. + */ + public JsonAdaptedJob(Job source) { + jobName = source.getName().fullName; + list1.addAll(source.getPersonsNric(0).stream() + .map(JsonAdaptedJobPersonList::new) + .collect(Collectors.toList())); + list2.addAll(source.getPersonsNric(1).stream() + .map(JsonAdaptedJobPersonList::new) + .collect(Collectors.toList())); + list3.addAll(source.getPersonsNric(2).stream() + .map(JsonAdaptedJobPersonList::new) + .collect(Collectors.toList())); + list4.addAll(source.getPersonsNric(3).stream() + .map(JsonAdaptedJobPersonList::new) + .collect(Collectors.toList())); + } + + /** + * Converts this Jackson-friendly adapted person object into the model's {@code Person} object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted person. + */ + public Job toModelType(UniquePersonList ab) throws IllegalValueException { + final List firstList = new ArrayList<>(); + final List secondList = new ArrayList<>(); + final List thirdList = new ArrayList<>(); + final List fourthList = new ArrayList<>(); + UniquePersonList personsInJob = new UniquePersonList(); + + for (JsonAdaptedJobPersonList nric : list1) { + firstList.add(nric.toModelType()); + } + Set firstNricSet = new HashSet<>(firstList); + UniquePersonList firstPList = new UniquePersonList(); + for (int i = 0; i < firstList.size(); i++) { + Person tempPerson = ab.getPerson(firstList.get(i)); + if (tempPerson == null) { + continue; + } + if (!personsInJob.contains(tempPerson)) { + personsInJob.add(tempPerson); + } + firstPList.add(tempPerson); + } + + for (JsonAdaptedJobPersonList nric : list2) { + secondList.add(nric.toModelType()); + } + Set secondNricSet = new HashSet<>(secondList); + UniquePersonList secondPList = new UniquePersonList(); + for (int i = 0; i < secondList.size(); i++) { + Person tempPerson = ab.getPerson(secondList.get(i)); + if (tempPerson == null) { + continue; + } + if (!personsInJob.contains(tempPerson)) { + personsInJob.add(tempPerson); + } + secondPList.add(tempPerson); + } + + for (JsonAdaptedJobPersonList nric : list3) { + thirdList.add(nric.toModelType()); + } + Set thirdNricSet = new HashSet<>(thirdList); + UniquePersonList thirdPList = new UniquePersonList(); + for (int i = 0; i < thirdList.size(); i++) { + Person tempPerson = ab.getPerson(thirdList.get(i)); + if (tempPerson == null) { + continue; + } + if (!personsInJob.contains(tempPerson)) { + personsInJob.add(tempPerson); + } + thirdPList.add(tempPerson); + } + + for (JsonAdaptedJobPersonList nric : list4) { + fourthList.add(nric.toModelType()); + } + Set fourthNricSet = new HashSet<>(fourthList); + UniquePersonList fourthPList = new UniquePersonList(); + for (int i = 0; i < fourthList.size(); i++) { + Person tempPerson = ab.getPerson(fourthList.get(i)); + if (tempPerson == null) { + continue; + } + if (!personsInJob.contains(tempPerson)) { + personsInJob.add(tempPerson); + } + fourthPList.add(tempPerson); + } + + ArrayList personsHash = new ArrayList<> (4); + personsHash.add(firstPList); + personsHash.add(secondPList); + personsHash.add(thirdPList); + personsHash.add(fourthPList); + ArrayList> personsNricList = new ArrayList<>(4); + personsNricList.add(firstNricSet); + personsNricList.add(secondNricSet); + personsNricList.add(thirdNricSet); + personsNricList.add(fourthNricSet); + + if (jobName == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, JobName.class.getSimpleName())); + } + if (!JobName.isValidName(jobName)) { + throw new IllegalValueException(JobName.MESSAGE_CONSTRAINTS); + } + final JobName modelName = new JobName(jobName); + + return new Job(modelName, personsHash, personsNricList, personsInJob); + } + +} diff --git a/src/main/java/seedu/address/storage/JsonAdaptedJobPersonList.java b/src/main/java/seedu/address/storage/JsonAdaptedJobPersonList.java new file mode 100644 index 000000000000..349f5911d942 --- /dev/null +++ b/src/main/java/seedu/address/storage/JsonAdaptedJobPersonList.java @@ -0,0 +1,48 @@ +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.person.Nric; + +/** + * Jackson-friendly version of {@link Nric}. + */ +class JsonAdaptedJobPersonList { + + private final String nric; + + /** + * Constructs a {@code JsonAdaptedJobPersonList} with the given {@code Nric}. + */ + @JsonCreator + public JsonAdaptedJobPersonList(String nric) { + this.nric = nric; + } + + /** + * Converts a given {@code JsonAdaptedJobPersonList} into this class for Jackson use. + */ + public JsonAdaptedJobPersonList(Nric source) { + nric = source.value; + } + + @JsonValue + public String getNricName() { + return nric; + } + + /** + * Converts this Jackson-friendly adapted nric object into the model's {@code Nric} object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted nric. + */ + public Nric toModelType() throws IllegalValueException { + if (!Nric.isValidNric(nric)) { + throw new IllegalValueException(Nric.MESSAGE_CONSTRAINTS); + } + return new Nric(nric); + } + +} diff --git a/src/main/java/seedu/address/storage/JsonAdaptedJobsApply.java b/src/main/java/seedu/address/storage/JsonAdaptedJobsApply.java new file mode 100644 index 000000000000..e2e8f9ad0eae --- /dev/null +++ b/src/main/java/seedu/address/storage/JsonAdaptedJobsApply.java @@ -0,0 +1,48 @@ +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.person.JobsApply; + +/** + * Jackson-friendly version of {@link JobsApply}. + */ +class JsonAdaptedJobsApply { + + private final String jobsApplyName; + + /** + * Constructs a {@code JsonAdaptedJobsApply} with the given {@code jobsApplyName}. + */ + @JsonCreator + public JsonAdaptedJobsApply(String jobsApplyName) { + this.jobsApplyName = jobsApplyName; + } + + /** + * Converts a given {@code JobsApply} into this class for Jackson use. + */ + public JsonAdaptedJobsApply(JobsApply source) { + jobsApplyName = source.value; + } + + @JsonValue + public String getJobsApplyName() { + return jobsApplyName; + } + + /** + * Converts this Jackson-friendly adapted jobs apply object into the model's {@code JobsApply} object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted jobsApply. + */ + public JobsApply toModelType() throws IllegalValueException { + if (!JobsApply.isValidJobsApply(jobsApplyName)) { + throw new IllegalValueException(JobsApply.MESSAGE_CONSTRAINTS); + } + return new JobsApply(jobsApplyName); + } + +} diff --git a/src/main/java/seedu/address/storage/JsonAdaptedKnownProgLang.java b/src/main/java/seedu/address/storage/JsonAdaptedKnownProgLang.java new file mode 100644 index 000000000000..46d4e2860011 --- /dev/null +++ b/src/main/java/seedu/address/storage/JsonAdaptedKnownProgLang.java @@ -0,0 +1,48 @@ +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.person.KnownProgLang; + +/** + * Jackson-friendly version of {@link KnownProgLang}. + */ +class JsonAdaptedKnownProgLang { + + private final String proglangName; + + /** + * Constructs a {@code JsonAdaptedKnownProgLang} with the given {@code proglangName}. + */ + @JsonCreator + public JsonAdaptedKnownProgLang(String proglangName) { + this.proglangName = proglangName; + } + + /** + * Converts a given {@code KnownProgLang} into this class for Jackson use. + */ + public JsonAdaptedKnownProgLang(KnownProgLang source) { + proglangName = source.value; + } + + @JsonValue + public String getKnownProgLangName() { + return proglangName; + } + + /** + * Converts this Jackson-friendly adapted known prog lang object into the model's {@code KnownProgLang} object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted proglang. + */ + public KnownProgLang toModelType() throws IllegalValueException { + if (!KnownProgLang.isValidKnownProgLang(proglangName)) { + throw new IllegalValueException(KnownProgLang.MESSAGE_CONSTRAINTS); + } + return new KnownProgLang(proglangName); + } + +} diff --git a/src/main/java/seedu/address/storage/JsonAdaptedPastJob.java b/src/main/java/seedu/address/storage/JsonAdaptedPastJob.java new file mode 100644 index 000000000000..64893a90f8f3 --- /dev/null +++ b/src/main/java/seedu/address/storage/JsonAdaptedPastJob.java @@ -0,0 +1,48 @@ +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.person.PastJob; + +/** + * Jackson-friendly version of {@link PastJob}. + */ +class JsonAdaptedPastJob { + + private final String pastjobName; + + /** + * Constructs a {@code JsonAdaptedPastJob} with the given {@code pastjobName}. + */ + @JsonCreator + public JsonAdaptedPastJob(String pastjobName) { + this.pastjobName = pastjobName; + } + + /** + * Converts a given {@code PastJob} into this class for Jackson use. + */ + public JsonAdaptedPastJob(PastJob source) { + pastjobName = source.value; + } + + @JsonValue + public String getPastJobName() { + return pastjobName; + } + + /** + * Converts this Jackson-friendly adapted pastjob object into the model's {@code PastJob} object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted pastjob. + */ + public PastJob toModelType() throws IllegalValueException { + if (!PastJob.isValidPastJob(pastjobName)) { + throw new IllegalValueException(PastJob.MESSAGE_CONSTRAINTS); + } + return new PastJob(pastjobName); + } + +} diff --git a/src/main/java/seedu/address/storage/JsonAdaptedPerson.java b/src/main/java/seedu/address/storage/JsonAdaptedPerson.java index a6321cec2eac..bcb2f00bb1d8 100644 --- a/src/main/java/seedu/address/storage/JsonAdaptedPerson.java +++ b/src/main/java/seedu/address/storage/JsonAdaptedPerson.java @@ -12,9 +12,19 @@ import seedu.address.commons.exceptions.IllegalValueException; import seedu.address.model.person.Address; import seedu.address.model.person.Email; +import seedu.address.model.person.Gender; +import seedu.address.model.person.Grade; +import seedu.address.model.person.InterviewScores; +import seedu.address.model.person.JobsApply; +import seedu.address.model.person.KnownProgLang; +import seedu.address.model.person.Major; import seedu.address.model.person.Name; +import seedu.address.model.person.Nric; +import seedu.address.model.person.PastJob; import seedu.address.model.person.Person; import seedu.address.model.person.Phone; +import seedu.address.model.person.Race; +import seedu.address.model.person.School; import seedu.address.model.tag.Tag; /** @@ -27,7 +37,17 @@ class JsonAdaptedPerson { private final String name; private final String phone; private final String email; + private final String nric; + private final String gender; + private final String race; private final String address; + private final String school; + private final String major; + private final String grade; + private final String interviewScores; + private final List pastjobed = new ArrayList<>(); + private final List knownProgLang = new ArrayList<>(); + private final List jobsApply = new ArrayList<>(); private final List tagged = new ArrayList<>(); /** @@ -35,12 +55,36 @@ class JsonAdaptedPerson { */ @JsonCreator public JsonAdaptedPerson(@JsonProperty("name") String name, @JsonProperty("phone") String phone, - @JsonProperty("email") String email, @JsonProperty("address") String address, - @JsonProperty("tagged") List tagged) { + @JsonProperty("email") String email, @JsonProperty("nric") String nric, + @JsonProperty("gender") String gender, @JsonProperty("race") String race, + @JsonProperty("address") String address, @JsonProperty("school") String school, + @JsonProperty("major") String major, @JsonProperty("grade") String grade, + @JsonProperty("knownProgLang") List knownProgLang, + @JsonProperty("pastjobed") List pastjobed, + @JsonProperty("jobsApply") List jobsApply, + @JsonProperty("interviewScores") String interviewScores, + @JsonProperty("tagged") List tagged) { + this.name = name; this.phone = phone; this.email = email; + this.nric = nric; + this.gender = gender; + this.race = race; this.address = address; + this.school = school; + this.major = major; + this.grade = grade; + this.interviewScores = interviewScores; + if (knownProgLang != null) { + this.knownProgLang.addAll(knownProgLang); + } + if (pastjobed != null) { + this.pastjobed.addAll(pastjobed); + } + if (jobsApply != null) { + this.jobsApply.addAll(jobsApply); + } if (tagged != null) { this.tagged.addAll(tagged); } @@ -53,7 +97,23 @@ public JsonAdaptedPerson(Person source) { name = source.getName().fullName; phone = source.getPhone().value; email = source.getEmail().value; + nric = source.getNric().value; + gender = source.getGender().value; + race = source.getRace().value; address = source.getAddress().value; + school = source.getSchool().value; + major = source.getMajor().value; + grade = source.getGrade().value; + interviewScores = source.getInterviewScores().value; + knownProgLang.addAll(source.getKnownProgLangs().stream() + .map(JsonAdaptedKnownProgLang::new) + .collect(Collectors.toList())); + pastjobed.addAll(source.getPastJobs().stream() + .map(JsonAdaptedPastJob::new) + .collect(Collectors.toList())); + jobsApply.addAll(source.getJobsApply().stream() + .map(JsonAdaptedJobsApply::new) + .collect(Collectors.toList())); tagged.addAll(source.getTags().stream() .map(JsonAdaptedTag::new) .collect(Collectors.toList())); @@ -66,9 +126,22 @@ public JsonAdaptedPerson(Person source) { */ public Person toModelType() throws IllegalValueException { final List personTags = new ArrayList<>(); + final List personPastJobs = new ArrayList<>(); + final List personKnownProgLang = new ArrayList<>(); + final List personJobsApply = new ArrayList<>(); for (JsonAdaptedTag tag : tagged) { personTags.add(tag.toModelType()); } + for (JsonAdaptedPastJob pastjob : pastjobed) { + personPastJobs.add(pastjob.toModelType()); + } + for (JsonAdaptedKnownProgLang knownProgLang : knownProgLang) { + personKnownProgLang.add(knownProgLang.toModelType()); + } + + for (JsonAdaptedJobsApply jobapply : jobsApply) { + personJobsApply.add(jobapply.toModelType()); + } if (name == null) { throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Name.class.getSimpleName())); @@ -94,6 +167,30 @@ public Person toModelType() throws IllegalValueException { } final Email modelEmail = new Email(email); + if (nric == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Nric.class.getSimpleName())); + } + if (!Nric.isValidNric(nric)) { + throw new IllegalValueException(Nric.MESSAGE_CONSTRAINTS); + } + final Nric modelNric = new Nric(nric); + + if (gender == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Gender.class.getSimpleName())); + } + if (!Gender.isValidGender(gender)) { + throw new IllegalValueException(Gender.MESSAGE_CONSTRAINTS); + } + final Gender modelGender = new Gender(gender); + + if (race == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Race.class.getSimpleName())); + } + if (!Race.isValidRace(race)) { + throw new IllegalValueException(Race.MESSAGE_CONSTRAINTS); + } + final Race modelRace = new Race(race); + if (address == null) { throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Address.class.getSimpleName())); } @@ -102,8 +199,46 @@ public Person toModelType() throws IllegalValueException { } final Address modelAddress = new Address(address); + if (school == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, School.class.getSimpleName())); + } + if (!School.isValidSchool(school)) { + throw new IllegalValueException(School.MESSAGE_CONSTRAINTS); + } + final School modelSchool = new School(school); + + if (major == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Major.class.getSimpleName())); + } + if (!Major.isValidMajor(major)) { + throw new IllegalValueException(Major.MESSAGE_CONSTRAINTS); + } + final Major modelMajor = new Major(major); + + if (grade == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Grade.class.getSimpleName())); + } + if (!Grade.isValidGrade(grade)) { + throw new IllegalValueException(Grade.MESSAGE_CONSTRAINTS); + } + final Grade modelGrade = new Grade(grade); + + if (interviewScores == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, InterviewScores + .class.getSimpleName())); + } + if (!InterviewScores.isValidInterviewScores(interviewScores)) { + throw new IllegalValueException(InterviewScores.MESSAGE_CONSTRAINTS); + } + final InterviewScores modelInterviewScores = new InterviewScores(interviewScores); + + final Set modelKnownProgLang = new HashSet<>(personKnownProgLang); final Set modelTags = new HashSet<>(personTags); - return new Person(modelName, modelPhone, modelEmail, modelAddress, modelTags); + final Set modelPastJobs = new HashSet<>(personPastJobs); + final Set modelJobsApply = new HashSet<>(personJobsApply); + return new Person(modelName, modelPhone, modelEmail, modelNric, modelGender, modelRace, modelAddress, + modelSchool, modelMajor, modelGrade, modelKnownProgLang, modelPastJobs, modelJobsApply, + modelInterviewScores, modelTags); } } diff --git a/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java b/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java index 5efd834091d4..a71ac410c95c 100644 --- a/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java +++ b/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java @@ -11,6 +11,7 @@ import seedu.address.commons.exceptions.IllegalValueException; import seedu.address.model.AddressBook; import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.model.job.Job; import seedu.address.model.person.Person; /** @@ -20,8 +21,10 @@ class JsonSerializableAddressBook { public static final String MESSAGE_DUPLICATE_PERSON = "Persons list contains duplicate person(s)."; + public static final String MESSAGE_DUPLICATE_JOB = "Jobs list contains duplicate job(s)."; private final List persons = new ArrayList<>(); + private final List jobs = new ArrayList<>(); /** * Constructs a {@code JsonSerializableAddressBook} with the given persons. @@ -38,6 +41,7 @@ public JsonSerializableAddressBook(@JsonProperty("persons") List { + + private static final Logger logger = LogsCenter.getLogger(AnalyticsWindow.class); + private static final String FXML = "AnalyticsChart.fxml"; + + @FXML + private BarChart jobApplicationsChart; + @FXML + private BarChart interviewScoresChart; + @FXML + private TextArea gradeText; + @FXML + private PieChart genderChart; + @FXML + private PieChart raceChart; + @FXML + private BarChart schoolChart; + @FXML + private BarChart majorChart; + @FXML + private BarChart pastJobsChart; + + + /** + * Creates a new AnalyticsWindow. + * + * @param root Stage to use as the root of the AnalyticsWindow. + */ + public AnalyticsWindow(Stage root) { + super(FXML, root); + } + + /** + * Creates a new AnalyticsWindow. + */ + public AnalyticsWindow() { + this(new Stage()); + } + + /** + * Shows the Analytics 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(Analytics analytics) { + logger.fine("Showing analytics results."); + getRoot().show(); + jobApplicationsChart.setData(analytics.generateJobApplicationData()); + interviewScoresChart.setData(analytics.generateInterviewScoresData()); + gradeText.setText("Mean Grade: " + analytics.generateMeanGradeData()); + genderChart.setData(analytics.generateGenderData()); + raceChart.setData(analytics.generateRaceData()); + schoolChart.setData(analytics.generateSchoolData()); + majorChart.setData(analytics.generateMajorData()); + pastJobsChart.setData(analytics.generatePastJobData()); + } + + /** + * Returns true if the analytics window is currently being shown. + */ + public boolean isShowing() { + return getRoot().isShowing(); + } + + /** + * Hides the analytics window. + */ + public void hide() { + getRoot().hide(); + } + + /** + * Focuses on the analytics window. + */ + public void focus() { + getRoot().requestFocus(); + } +} diff --git a/src/main/java/seedu/address/ui/BrowserPanel.java b/src/main/java/seedu/address/ui/BrowserPanel.java index 1e76124a59e2..53876e01c8d1 100644 --- a/src/main/java/seedu/address/ui/BrowserPanel.java +++ b/src/main/java/seedu/address/ui/BrowserPanel.java @@ -22,7 +22,7 @@ public class BrowserPanel extends UiPart { public static final URL DEFAULT_PAGE = requireNonNull(MainApp.class.getResource(FXML_FILE_FOLDER + "default.html")); - public static final String SEARCH_PAGE_URL = "https://se-edu.github.io/dummy-search-page/?name="; + public static final String SEARCH_PAGE_URL = "https://se-education.org/dummy-search-page/?name="; private static final String FXML = "BrowserPanel.fxml"; diff --git a/src/main/java/seedu/address/ui/InterviewsWindow.java b/src/main/java/seedu/address/ui/InterviewsWindow.java new file mode 100644 index 000000000000..c360e51f4fc4 --- /dev/null +++ b/src/main/java/seedu/address/ui/InterviewsWindow.java @@ -0,0 +1,82 @@ +package seedu.address.ui; + +import java.util.logging.Logger; + +import javafx.fxml.FXML; +import javafx.scene.control.TextArea; +import javafx.stage.Stage; +import seedu.address.commons.core.LogsCenter; + +/** + * Controller for a analytics page + */ +public class InterviewsWindow extends UiPart { + + private static final Logger logger = LogsCenter.getLogger(InterviewsWindow.class); + private static final String FXML = "InterviewsWindow.fxml"; + + @FXML + private TextArea interviewsDisplay; + + /** + * Creates a new AnalyticsWindow. + * + * @param root Stage to use as the root of the AnalyticsWindow. + */ + public InterviewsWindow(Stage root) { + super(FXML, root); + } + + /** + * Creates a new AnalyticsWindow. + */ + public InterviewsWindow() { + this(new Stage()); + } + + /** + * Shows the Analytics 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(String interviews) { + logger.fine("Showing interviews results."); + getRoot().show(); + interviewsDisplay.setText(interviews); + } + + /** + * Returns true if the interviews window is currently being shown. + */ + public boolean isShowing() { + return getRoot().isShowing(); + } + + /** + * Hides the interviews window. + */ + public void hide() { + getRoot().hide(); + } + + /** + * Focuses on the interviews window. + */ + public void focus() { + getRoot().requestFocus(); + } +} + diff --git a/src/main/java/seedu/address/ui/JobCard.java b/src/main/java/seedu/address/ui/JobCard.java new file mode 100644 index 000000000000..67b543f5d159 --- /dev/null +++ b/src/main/java/seedu/address/ui/JobCard.java @@ -0,0 +1,69 @@ +package seedu.address.ui; + +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Region; +import seedu.address.model.job.Job; + +/** + * An UI component that displays information of a {@code Person}. + */ +public class JobCard extends UiPart { + + private static final String FXML = "JobListCard.fxml"; + + /** + * Note: Certain keywords such as "location" and "resources" are reserved keywords in JavaFX. + * As a consequence, UI elements' variable names cannot be set to such keywords + * or an exception will be thrown by JavaFX during runtime. + * + * @see The issue on AddressBook level 4 + */ + + public final Job job; + + @FXML + private HBox cardPane; + @FXML + private Label name; + @FXML + private Label id; + @FXML + private Label applicants; + @FXML + private Label kiv; + @FXML + private Label interviewed; + @FXML + private Label shortlist; + + public JobCard(Job job, int displayedIndex) { + super(FXML); + this.job = job; + id.setText(displayedIndex + ". "); + name.setText(job.getName().toString()); + applicants.setText("Applicants: " + job.getList(0).size()); + kiv.setText("KIV: " + job.getList(1).size()); + interviewed.setText("Interviewed: " + job.getList(2).size()); + shortlist.setText("Shortlist: " + job.getList(3).size()); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof JobCard)) { + return false; + } + + // state check + JobCard card = (JobCard) other; + return id.getText().equals(card.id.getText()) + && job.equals(card.job); + } +} diff --git a/src/main/java/seedu/address/ui/JobListPanel.java b/src/main/java/seedu/address/ui/JobListPanel.java new file mode 100644 index 000000000000..6b6efbcae0d4 --- /dev/null +++ b/src/main/java/seedu/address/ui/JobListPanel.java @@ -0,0 +1,71 @@ +package seedu.address.ui; + +import java.util.Objects; +import java.util.function.Consumer; +import java.util.logging.Logger; + +import javafx.beans.value.ObservableValue; +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.scene.control.ListCell; +import javafx.scene.control.ListView; +import javafx.scene.layout.Region; +import seedu.address.commons.core.LogsCenter; +import seedu.address.model.job.Job; + +/** + * Panel containing the list of persons. + */ +public class JobListPanel extends UiPart { + private static final String FXML = "JobListPanel.fxml"; + private final Logger logger = LogsCenter.getLogger(JobListPanel.class); + + @FXML + private ListView jobListView; + + public JobListPanel(ObservableList jobList, ObservableValue selectedJob, + Consumer onSelectedJobChange) { + super(FXML); + jobListView.setItems(jobList); + jobListView.setCellFactory(listView -> new JobListViewCell()); + jobListView.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> { + logger.fine("Selection in job list panel changed to : '" + newValue + "'"); + onSelectedJobChange.accept(newValue); + }); + selectedJob.addListener((observable, oldValue, newValue) -> { + logger.fine("Selected job changed to: " + newValue); + + // Don't modify selection if we are already selecting the selected job, + // otherwise we would have an infinite loop. + if (Objects.equals(jobListView.getSelectionModel().getSelectedItem(), newValue)) { + return; + } + + if (newValue == null) { + jobListView.getSelectionModel().clearSelection(); + } else { + int index = jobListView.getItems().indexOf(newValue); + jobListView.scrollTo(index); + jobListView.getSelectionModel().clearAndSelect(index); + } + }); + } + + /** + * Custom {@code ListCell} that displays the graphics of a {@code Person} using a {@code PersonCard}. + */ + class JobListViewCell extends ListCell { + @Override + protected void updateItem(Job job, boolean empty) { + super.updateItem(job, empty); + + if (empty || job == null) { + setGraphic(null); + setText(null); + } else { + setGraphic(new JobCard(job, getIndex() + 1).getRoot()); + } + } + } + +} diff --git a/src/main/java/seedu/address/ui/MainWindow.java b/src/main/java/seedu/address/ui/MainWindow.java index ac165736001d..096fc12c6b57 100644 --- a/src/main/java/seedu/address/ui/MainWindow.java +++ b/src/main/java/seedu/address/ui/MainWindow.java @@ -1,13 +1,17 @@ package seedu.address.ui; +import java.io.IOException; import java.util.logging.Logger; import javafx.event.ActionEvent; import javafx.fxml.FXML; +import javafx.fxml.FXMLLoader; +import javafx.scene.control.Label; import javafx.scene.control.MenuItem; import javafx.scene.control.TextInputControl; import javafx.scene.input.KeyCombination; import javafx.scene.input.KeyEvent; +import javafx.scene.layout.FlowPane; import javafx.scene.layout.StackPane; import javafx.stage.Stage; import seedu.address.commons.core.GuiSettings; @@ -16,6 +20,8 @@ import seedu.address.logic.commands.CommandResult; import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.job.JobListName; +import seedu.address.model.person.predicate.UniqueFilterList; /** * The Main Window. Provides the basic application layout containing @@ -23,22 +29,24 @@ */ public class MainWindow extends UiPart { - private static final String FXML = "MainWindow.fxml"; + private static final String FXML = "MainJobWindow.fxml"; private final Logger logger = LogsCenter.getLogger(getClass()); private Stage primaryStage; private Logic logic; + private boolean isAllJobScreen; // Independent Ui parts residing in this Ui container - private BrowserPanel browserPanel; - private PersonListPanel personListPanel; + private PersonListPanel allApplicantsListPanel; + private PersonListPanel kivListPanel; + private PersonListPanel interviewedListPanel; + private PersonListPanel selectedListPanel; + private PersonListPanel allListPanel; + private JobListPanel jobsListPanel; private ResultDisplay resultDisplay; private HelpWindow helpWindow; - @FXML - private StackPane browserPlaceholder; - @FXML private StackPane commandBoxPlaceholder; @@ -46,7 +54,16 @@ public class MainWindow extends UiPart { private MenuItem helpMenuItem; @FXML - private StackPane personListPanelPlaceholder; + private StackPane allApplicantsPlaceholder; + + @FXML + private StackPane kivPlaceholder; + + @FXML + private StackPane interviewedPlaceholder; + + @FXML + private StackPane selectedPlaceholder; @FXML private StackPane resultDisplayPlaceholder; @@ -54,6 +71,28 @@ public class MainWindow extends UiPart { @FXML private StackPane statusbarPlaceholder; + @FXML + private FlowPane allFilter; + + @FXML + private FlowPane kivFilter; + + @FXML + private FlowPane interviewFilter; + + @FXML + private FlowPane shortlistFilter; + + @FXML + private FlowPane allAppFilter; + + @FXML + private StackPane allPlaceholder; + + @FXML + private StackPane jobsPlaceholder; + + public MainWindow(Stage primaryStage, Logic logic) { super(FXML, primaryStage); @@ -79,6 +118,7 @@ private void setAccelerators() { /** * Sets the accelerator of a MenuItem. + * * @param keyCombination the KeyCombination value of the accelerator */ private void setAccelerator(MenuItem menuItem, KeyCombination keyCombination) { @@ -110,13 +150,7 @@ private void setAccelerator(MenuItem menuItem, KeyCombination keyCombination) { /** * Fills up all the placeholders of this window. */ - void fillInnerParts() { - browserPanel = new BrowserPanel(logic.selectedPersonProperty()); - browserPlaceholder.getChildren().add(browserPanel.getRoot()); - - personListPanel = new PersonListPanel(logic.getFilteredPersonList(), logic.selectedPersonProperty(), - logic::setSelectedPerson); - personListPanelPlaceholder.getChildren().add(personListPanel.getRoot()); + private void fillInnerParts() { resultDisplay = new ResultDisplay(); resultDisplayPlaceholder.getChildren().add(resultDisplay.getRoot()); @@ -126,6 +160,111 @@ void fillInnerParts() { CommandBox commandBox = new CommandBox(this::executeCommand, logic.getHistory()); commandBoxPlaceholder.getChildren().add(commandBox.getRoot()); + + } + + /** + * Fills up all the placeholders of this window when in display all jobs and applicants scene. + */ + + void fillAllJobsParts() { + + try { + switchToAllJobsScene(); + } catch (IOException ex) { + ex.printStackTrace(); + } + + allListPanel = new PersonListPanel(logic.getFilteredPersonList(), logic.selectedPersonProperty(), + logic::setSelectedPerson); + allPlaceholder.getChildren().add(allListPanel.getRoot()); + + jobsListPanel = new JobListPanel(logic.getAllJobs(), logic.selectedJobProperty(), + logic::setSelectedJob); + jobsPlaceholder.getChildren().add(jobsListPanel.getRoot()); + + fillInnerParts(); + } + + /** + * Change scene to display all jobs and applicants + */ + + private void switchToAllJobsScene() throws IOException { + isAllJobScreen = true; + FXMLLoader loader = new FXMLLoader(getClass().getResource("/view/displayAllScene.fxml")); + loader.setController(this); + primaryStage.setScene(loader.load()); + } + + /** + * Change scene to display a specific job and its four lists + */ + + private void switchToDisplayJobScene() throws IOException { + isAllJobScreen = false; + FXMLLoader loader = new FXMLLoader(getClass().getResource("/view/displayJobScene.fxml")); + loader.setController(this); + primaryStage.setScene(loader.load()); + } + + /** + * Refreshes the jobPersonListPanel + */ + private void fillDisplayJobParts() { + + try { + switchToDisplayJobScene(); + } catch (IOException ex) { + ex.printStackTrace(); + } + + allApplicantsListPanel = new PersonListPanel(logic.getJobsList(0), logic.selectedPersonProperty(), + logic::setSelectedAll); + allApplicantsPlaceholder.getChildren().add(allApplicantsListPanel.getRoot()); + + kivListPanel = new PersonListPanel(logic.getJobsList(1), logic.selectedPersonProperty(), + logic::setSelectedKiv); + kivPlaceholder.getChildren().add(kivListPanel.getRoot()); + + interviewedListPanel = new PersonListPanel(logic.getJobsList(2), logic.selectedPersonProperty(), + logic::setSelectedInterviewed); + interviewedPlaceholder.getChildren().add(interviewedListPanel.getRoot()); + + selectedListPanel = new PersonListPanel(logic.getJobsList(3), logic.selectedPersonProperty(), + logic::setSelectedSelected); + selectedPlaceholder.getChildren().add(selectedListPanel.getRoot()); + + fillInnerParts(); + } + + /** + * stores and updates the filtered parameters for each list + */ + + private void updateFilterTags(JobListName listName, UniqueFilterList list) { + switch (listName) { + case APPLICANT: + allFilter.getChildren().clear(); + list.forEach(filter -> allFilter.getChildren().add(new Label(filter.getFilterName()))); + break; + case KIV: + kivFilter.getChildren().clear(); + list.forEach(filter -> kivFilter.getChildren().add(new Label(filter.getFilterName()))); + break; + case INTERVIEW: + interviewFilter.getChildren().clear(); + list.forEach(filter -> interviewFilter.getChildren().add(new Label(filter.getFilterName()))); + break; + case SHORTLIST: + shortlistFilter.getChildren().clear(); + list.forEach(filter -> shortlistFilter.getChildren().add(new Label(filter.getFilterName()))); + break; + default: + allAppFilter.getChildren().clear(); + list.forEach(filter -> allAppFilter.getChildren().add(new Label(filter.getFilterName()))); + break; + } } /** @@ -168,10 +307,6 @@ private void handleExit() { primaryStage.hide(); } - public PersonListPanel getPersonListPanel() { - return personListPanel; - } - /** * Executes the command and returns the result. * @@ -179,14 +314,39 @@ public PersonListPanel getPersonListPanel() { */ private CommandResult executeCommand(String commandText) throws CommandException, ParseException { try { - CommandResult commandResult = logic.execute(commandText); + + CommandResult commandResult = logic.execute(commandText, isAllJobScreen); logger.info("Result: " + commandResult.getFeedbackToUser()); resultDisplay.setFeedbackToUser(commandResult.getFeedbackToUser()); + if (commandResult.isSuccessfulAnalytics()) { + AnalyticsWindow analytics = new AnalyticsWindow(); + analytics.show(commandResult.getAnalytics()); + } + + if (commandResult.isSuccessfulFilter()) { + updateFilterTags(commandResult.getJobListName(), commandResult.getFilterList()); + } + + if (commandResult.isSuccessfulDisplayJob()) { + fillDisplayJobParts(); + resultDisplay.setFeedbackToUser(commandResult.getFeedbackToUser()); + } + + if (commandResult.isSuccessfulInterviews()) { + InterviewsWindow interviews = new InterviewsWindow(); + interviews.show(commandResult.getInterviews()); + } + if (commandResult.isShowHelp()) { handleHelp(); } + if (commandResult.isList()) { + fillAllJobsParts(); + resultDisplay.setFeedbackToUser(commandResult.getFeedbackToUser()); + } + 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 f6727ea83abd..e5d04cb3d7b8 100644 --- a/src/main/java/seedu/address/ui/PersonCard.java +++ b/src/main/java/seedu/address/ui/PersonCard.java @@ -33,10 +33,28 @@ public class PersonCard extends UiPart { @FXML private Label phone; @FXML + private Label nric; + @FXML + private Label gender; + @FXML + private Label race; + @FXML private Label address; @FXML private Label email; @FXML + private Label school; + @FXML + private Label major; + @FXML + private Label grade; + @FXML + private FlowPane pastjobs; + @FXML + private FlowPane jobsApply; + @FXML + private Label interviewScores; + @FXML private FlowPane tags; public PersonCard(Person person, int displayedIndex) { @@ -45,9 +63,18 @@ public PersonCard(Person person, int displayedIndex) { id.setText(displayedIndex + ". "); name.setText(person.getName().fullName); phone.setText(person.getPhone().value); - address.setText(person.getAddress().value); + //nric.setText(person.getNric().value); + gender.setText(person.getGender().value); + race.setText(person.getRace().value); + //address.setText(person.getAddress().value); email.setText(person.getEmail().value); - person.getTags().forEach(tag -> tags.getChildren().add(new Label(tag.tagName))); + school.setText(person.getSchool().value); + major.setText(person.getMajor().value); + grade.setText(person.getGrade().value); + interviewScores.setText(person.getInterviewScores().value); + person.getPastJobs().forEach(pastjob -> pastjobs.getChildren().add(new Label(pastjob.value))); + person.getJobsApply().forEach(jobApply -> jobsApply.getChildren().add(new Label(jobApply.value))); + //person.getTags().forEach(tag -> tags.getChildren().add(new Label(tag.tagName))); } @Override diff --git a/src/main/java/seedu/address/ui/UiManager.java b/src/main/java/seedu/address/ui/UiManager.java index 876621d79b94..f22931c2cee1 100644 --- a/src/main/java/seedu/address/ui/UiManager.java +++ b/src/main/java/seedu/address/ui/UiManager.java @@ -40,7 +40,7 @@ public void start(Stage primaryStage) { try { mainWindow = new MainWindow(primaryStage, logic); mainWindow.show(); //This should be called before creating other UI parts - mainWindow.fillInnerParts(); + mainWindow.fillAllJobsParts(); } catch (Throwable e) { logger.severe(StringUtil.getDetails(e)); @@ -60,6 +60,7 @@ void showAlertDialogAndWait(Alert.AlertType type, String title, String headerTex * Shows an alert dialog on {@code owner} with the given parameters. * This method only returns after the user has closed the alert dialog. */ + private static void showAlertDialogAndWait(Stage owner, AlertType type, String title, String headerText, String contentText) { final Alert alert = new Alert(type); diff --git a/src/main/resources/images/Broom.png b/src/main/resources/images/Broom.png new file mode 100644 index 000000000000..0f7bc47a2bb2 Binary files /dev/null and b/src/main/resources/images/Broom.png differ diff --git a/src/main/resources/view/AnalyticsChart.fxml b/src/main/resources/view/AnalyticsChart.fxml new file mode 100644 index 000000000000..049d672e7ee3 --- /dev/null +++ b/src/main/resources/view/AnalyticsChart.fxml @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +