diff --git a/README.md b/README.md
index 13f5c77403f..99cd75a49c2 100644
--- a/README.md
+++ b/README.md
@@ -1,14 +1,27 @@
-[![CI Status](https://github.com/se-edu/addressbook-level3/workflows/Java%20CI/badge.svg)](https://github.com/se-edu/addressbook-level3/actions)
+# ClientConnect [![CI Status](https://github.com/AY2122S2-CS2103-W17-3/tp/workflows/Java%20CI/badge.svg)](https://github.com/AY2122S2-CS2103-W17-3/tp/actions)
+
+---
![Ui](docs/images/Ui.png)
-* This is **a sample project for Software Engineering (SE) students**.
- Example usages:
- * as a starting point of a course project (as opposed to writing everything from scratch)
- * as a case study
-* The project simulates an ongoing software project for a desktop application (called _AddressBook_) used for managing contact details.
- * It is **written in OOP fashion**. It provides a **reasonably well-written** code base **bigger** (around 6 KLoC) than what students usually write in beginner-level SE modules, without being overwhelmingly big.
- * It comes with a **reasonable level of user and developer documentation**.
-* It is named `AddressBook Level 3` (`AB3` for short) because it was initially created as a part of a series of `AddressBook` projects (`Level 1`, `Level 2`, `Level 3` ...).
-* For the detailed documentation of this project, see the **[Address Book Product Website](https://se-education.org/addressbook-level3)**.
-* This project is a **part of the se-education.org** initiative. If you would like to contribute code to this project, see [se-education.org](https://se-education.org#https://se-education.org/#contributing) for more info.
+### Are you an insurance agent?
+
+### Having trouble with all your clients' details?
+
+### Do you want a clean, simple app that stores and manages all of that for you?
+
+### If so, look no further!
+
+
+## Quick Overview
+
+---
+ClientConnect is a simple desktop application that stores all your clients' information for you!
+
+Manage your clients with ease!
+
+Just using the keyboard, you can add clients by including details such as their name, address, etc.
+You can also store your insurance packages and their corresponding details in the application!
+
+Other functionalities, such as editing or deleting existing contacts, or finding contacts by keywords, are also available.
+
diff --git a/bin/main/images/address_book_32.png b/bin/main/images/address_book_32.png
new file mode 100644
index 00000000000..419d6020de6
Binary files /dev/null and b/bin/main/images/address_book_32.png differ
diff --git a/bin/main/images/calendar.png b/bin/main/images/calendar.png
new file mode 100644
index 00000000000..8b2bdf4f1c1
Binary files /dev/null and b/bin/main/images/calendar.png differ
diff --git a/bin/main/images/clock.png b/bin/main/images/clock.png
new file mode 100644
index 00000000000..0807cbf6451
Binary files /dev/null and b/bin/main/images/clock.png differ
diff --git a/bin/main/images/fail.png b/bin/main/images/fail.png
new file mode 100644
index 00000000000..6daf01290dd
Binary files /dev/null and b/bin/main/images/fail.png differ
diff --git a/bin/main/images/help_icon.png b/bin/main/images/help_icon.png
new file mode 100644
index 00000000000..f8e80d6c1c5
Binary files /dev/null and b/bin/main/images/help_icon.png differ
diff --git a/bin/main/images/info_icon.png b/bin/main/images/info_icon.png
new file mode 100644
index 00000000000..f8cef714095
Binary files /dev/null and b/bin/main/images/info_icon.png differ
diff --git a/bin/main/seedu/address/AppParameters.class b/bin/main/seedu/address/AppParameters.class
new file mode 100644
index 00000000000..8f90c25e7e1
Binary files /dev/null and b/bin/main/seedu/address/AppParameters.class differ
diff --git a/bin/main/seedu/address/Main.class b/bin/main/seedu/address/Main.class
new file mode 100644
index 00000000000..9503b8abcd8
Binary files /dev/null and b/bin/main/seedu/address/Main.class differ
diff --git a/bin/main/seedu/address/MainApp.class b/bin/main/seedu/address/MainApp.class
new file mode 100644
index 00000000000..d5b992f5f97
Binary files /dev/null and b/bin/main/seedu/address/MainApp.class differ
diff --git a/bin/main/seedu/address/commons/core/Config.class b/bin/main/seedu/address/commons/core/Config.class
new file mode 100644
index 00000000000..4a915e15d1b
Binary files /dev/null and b/bin/main/seedu/address/commons/core/Config.class differ
diff --git a/bin/main/seedu/address/commons/core/GuiSettings.class b/bin/main/seedu/address/commons/core/GuiSettings.class
new file mode 100644
index 00000000000..d0d1bba4e51
Binary files /dev/null and b/bin/main/seedu/address/commons/core/GuiSettings.class differ
diff --git a/bin/main/seedu/address/commons/core/LogsCenter.class b/bin/main/seedu/address/commons/core/LogsCenter.class
new file mode 100644
index 00000000000..28f9c301e11
Binary files /dev/null and b/bin/main/seedu/address/commons/core/LogsCenter.class differ
diff --git a/bin/main/seedu/address/commons/core/Messages.class b/bin/main/seedu/address/commons/core/Messages.class
new file mode 100644
index 00000000000..5739d3cded0
Binary files /dev/null and b/bin/main/seedu/address/commons/core/Messages.class differ
diff --git a/bin/main/seedu/address/commons/core/Version.class b/bin/main/seedu/address/commons/core/Version.class
new file mode 100644
index 00000000000..09ba1c53635
Binary files /dev/null and b/bin/main/seedu/address/commons/core/Version.class differ
diff --git a/bin/main/seedu/address/commons/core/index/Index.class b/bin/main/seedu/address/commons/core/index/Index.class
new file mode 100644
index 00000000000..fdbc61b3ed2
Binary files /dev/null and b/bin/main/seedu/address/commons/core/index/Index.class differ
diff --git a/bin/main/seedu/address/commons/exceptions/DataConversionException.class b/bin/main/seedu/address/commons/exceptions/DataConversionException.class
new file mode 100644
index 00000000000..375dc4d730f
Binary files /dev/null and b/bin/main/seedu/address/commons/exceptions/DataConversionException.class differ
diff --git a/bin/main/seedu/address/commons/exceptions/IllegalValueException.class b/bin/main/seedu/address/commons/exceptions/IllegalValueException.class
new file mode 100644
index 00000000000..af85ad747a5
Binary files /dev/null and b/bin/main/seedu/address/commons/exceptions/IllegalValueException.class differ
diff --git a/bin/main/seedu/address/commons/util/AppUtil.class b/bin/main/seedu/address/commons/util/AppUtil.class
new file mode 100644
index 00000000000..9d58ca88144
Binary files /dev/null and b/bin/main/seedu/address/commons/util/AppUtil.class differ
diff --git a/bin/main/seedu/address/commons/util/CollectionUtil.class b/bin/main/seedu/address/commons/util/CollectionUtil.class
new file mode 100644
index 00000000000..810ea10f8b0
Binary files /dev/null and b/bin/main/seedu/address/commons/util/CollectionUtil.class differ
diff --git a/bin/main/seedu/address/commons/util/ConfigUtil.class b/bin/main/seedu/address/commons/util/ConfigUtil.class
new file mode 100644
index 00000000000..8f1e04ece2d
Binary files /dev/null and b/bin/main/seedu/address/commons/util/ConfigUtil.class differ
diff --git a/bin/main/seedu/address/commons/util/CsvUtil.class b/bin/main/seedu/address/commons/util/CsvUtil.class
new file mode 100644
index 00000000000..342b06b6594
Binary files /dev/null and b/bin/main/seedu/address/commons/util/CsvUtil.class differ
diff --git a/bin/main/seedu/address/commons/util/FileUtil.class b/bin/main/seedu/address/commons/util/FileUtil.class
new file mode 100644
index 00000000000..bcd9bc038f8
Binary files /dev/null and b/bin/main/seedu/address/commons/util/FileUtil.class differ
diff --git a/bin/main/seedu/address/commons/util/JsonUtil$LevelDeserializer.class b/bin/main/seedu/address/commons/util/JsonUtil$LevelDeserializer.class
new file mode 100644
index 00000000000..ea586c27b2f
Binary files /dev/null and b/bin/main/seedu/address/commons/util/JsonUtil$LevelDeserializer.class differ
diff --git a/bin/main/seedu/address/commons/util/JsonUtil.class b/bin/main/seedu/address/commons/util/JsonUtil.class
new file mode 100644
index 00000000000..ceb3f0b7ed1
Binary files /dev/null and b/bin/main/seedu/address/commons/util/JsonUtil.class differ
diff --git a/bin/main/seedu/address/commons/util/StringUtil.class b/bin/main/seedu/address/commons/util/StringUtil.class
new file mode 100644
index 00000000000..3238f17ab3f
Binary files /dev/null and b/bin/main/seedu/address/commons/util/StringUtil.class differ
diff --git a/bin/main/seedu/address/logic/Logic.class b/bin/main/seedu/address/logic/Logic.class
new file mode 100644
index 00000000000..0eeb6dca5e3
Binary files /dev/null and b/bin/main/seedu/address/logic/Logic.class differ
diff --git a/bin/main/seedu/address/logic/LogicManager.class b/bin/main/seedu/address/logic/LogicManager.class
new file mode 100644
index 00000000000..9025ff01a2a
Binary files /dev/null and b/bin/main/seedu/address/logic/LogicManager.class differ
diff --git a/bin/main/seedu/address/logic/commands/AddCommand.class b/bin/main/seedu/address/logic/commands/AddCommand.class
new file mode 100644
index 00000000000..cbbb136f7fb
Binary files /dev/null and b/bin/main/seedu/address/logic/commands/AddCommand.class differ
diff --git a/bin/main/seedu/address/logic/commands/AddPackageCommand.class b/bin/main/seedu/address/logic/commands/AddPackageCommand.class
new file mode 100644
index 00000000000..0ed7f816722
Binary files /dev/null and b/bin/main/seedu/address/logic/commands/AddPackageCommand.class differ
diff --git a/bin/main/seedu/address/logic/commands/AddTagCommand.class b/bin/main/seedu/address/logic/commands/AddTagCommand.class
new file mode 100644
index 00000000000..bea9b8822ae
Binary files /dev/null and b/bin/main/seedu/address/logic/commands/AddTagCommand.class differ
diff --git a/bin/main/seedu/address/logic/commands/AddToClipboardCommand.class b/bin/main/seedu/address/logic/commands/AddToClipboardCommand.class
new file mode 100644
index 00000000000..9a1804d0d23
Binary files /dev/null and b/bin/main/seedu/address/logic/commands/AddToClipboardCommand.class differ
diff --git a/bin/main/seedu/address/logic/commands/ClearCommand.class b/bin/main/seedu/address/logic/commands/ClearCommand.class
new file mode 100644
index 00000000000..95f834c0094
Binary files /dev/null and b/bin/main/seedu/address/logic/commands/ClearCommand.class differ
diff --git a/bin/main/seedu/address/logic/commands/Command.class b/bin/main/seedu/address/logic/commands/Command.class
new file mode 100644
index 00000000000..e35925db868
Binary files /dev/null and b/bin/main/seedu/address/logic/commands/Command.class differ
diff --git a/bin/main/seedu/address/logic/commands/CommandResult.class b/bin/main/seedu/address/logic/commands/CommandResult.class
new file mode 100644
index 00000000000..f8f3250fd63
Binary files /dev/null and b/bin/main/seedu/address/logic/commands/CommandResult.class differ
diff --git a/bin/main/seedu/address/logic/commands/DeleteCommand.class b/bin/main/seedu/address/logic/commands/DeleteCommand.class
new file mode 100644
index 00000000000..98260ac1423
Binary files /dev/null and b/bin/main/seedu/address/logic/commands/DeleteCommand.class differ
diff --git a/bin/main/seedu/address/logic/commands/DeletePackageCommand.class b/bin/main/seedu/address/logic/commands/DeletePackageCommand.class
new file mode 100644
index 00000000000..155dfeb6d63
Binary files /dev/null and b/bin/main/seedu/address/logic/commands/DeletePackageCommand.class differ
diff --git a/bin/main/seedu/address/logic/commands/DeleteTagCommand.class b/bin/main/seedu/address/logic/commands/DeleteTagCommand.class
new file mode 100644
index 00000000000..f05c68e1095
Binary files /dev/null and b/bin/main/seedu/address/logic/commands/DeleteTagCommand.class differ
diff --git a/bin/main/seedu/address/logic/commands/EditCommand$EditPersonDescriptor.class b/bin/main/seedu/address/logic/commands/EditCommand$EditPersonDescriptor.class
new file mode 100644
index 00000000000..2359c948eb7
Binary files /dev/null and b/bin/main/seedu/address/logic/commands/EditCommand$EditPersonDescriptor.class differ
diff --git a/bin/main/seedu/address/logic/commands/EditCommand.class b/bin/main/seedu/address/logic/commands/EditCommand.class
new file mode 100644
index 00000000000..2daab69d11e
Binary files /dev/null and b/bin/main/seedu/address/logic/commands/EditCommand.class differ
diff --git a/bin/main/seedu/address/logic/commands/EditPackageCommand.class b/bin/main/seedu/address/logic/commands/EditPackageCommand.class
new file mode 100644
index 00000000000..32c3eda6792
Binary files /dev/null and b/bin/main/seedu/address/logic/commands/EditPackageCommand.class differ
diff --git a/bin/main/seedu/address/logic/commands/EditTagCommand.class b/bin/main/seedu/address/logic/commands/EditTagCommand.class
new file mode 100644
index 00000000000..b136a1c49fd
Binary files /dev/null and b/bin/main/seedu/address/logic/commands/EditTagCommand.class differ
diff --git a/bin/main/seedu/address/logic/commands/ExitCommand.class b/bin/main/seedu/address/logic/commands/ExitCommand.class
new file mode 100644
index 00000000000..f4674f524ab
Binary files /dev/null and b/bin/main/seedu/address/logic/commands/ExitCommand.class differ
diff --git a/bin/main/seedu/address/logic/commands/ExportToCsvCommand.class b/bin/main/seedu/address/logic/commands/ExportToCsvCommand.class
new file mode 100644
index 00000000000..b7287cfa9cf
Binary files /dev/null and b/bin/main/seedu/address/logic/commands/ExportToCsvCommand.class differ
diff --git a/bin/main/seedu/address/logic/commands/FindCommand.class b/bin/main/seedu/address/logic/commands/FindCommand.class
new file mode 100644
index 00000000000..2b5a375be5d
Binary files /dev/null and b/bin/main/seedu/address/logic/commands/FindCommand.class differ
diff --git a/bin/main/seedu/address/logic/commands/HelpCommand.class b/bin/main/seedu/address/logic/commands/HelpCommand.class
new file mode 100644
index 00000000000..03f2f2589df
Binary files /dev/null and b/bin/main/seedu/address/logic/commands/HelpCommand.class differ
diff --git a/bin/main/seedu/address/logic/commands/ImportFromCsvCommand.class b/bin/main/seedu/address/logic/commands/ImportFromCsvCommand.class
new file mode 100644
index 00000000000..84b67cfa29f
Binary files /dev/null and b/bin/main/seedu/address/logic/commands/ImportFromCsvCommand.class differ
diff --git a/bin/main/seedu/address/logic/commands/ListCommand.class b/bin/main/seedu/address/logic/commands/ListCommand.class
new file mode 100644
index 00000000000..3bb98fa6afa
Binary files /dev/null and b/bin/main/seedu/address/logic/commands/ListCommand.class differ
diff --git a/bin/main/seedu/address/logic/commands/ListPackageCommand.class b/bin/main/seedu/address/logic/commands/ListPackageCommand.class
new file mode 100644
index 00000000000..c282d446832
Binary files /dev/null and b/bin/main/seedu/address/logic/commands/ListPackageCommand.class differ
diff --git a/bin/main/seedu/address/logic/commands/PriorityListCommand.class b/bin/main/seedu/address/logic/commands/PriorityListCommand.class
new file mode 100644
index 00000000000..75f8c89e615
Binary files /dev/null and b/bin/main/seedu/address/logic/commands/PriorityListCommand.class differ
diff --git a/bin/main/seedu/address/logic/commands/RedoCommand.class b/bin/main/seedu/address/logic/commands/RedoCommand.class
new file mode 100644
index 00000000000..bed31075270
Binary files /dev/null and b/bin/main/seedu/address/logic/commands/RedoCommand.class differ
diff --git a/bin/main/seedu/address/logic/commands/UndoCommand.class b/bin/main/seedu/address/logic/commands/UndoCommand.class
new file mode 100644
index 00000000000..98a51f56d1c
Binary files /dev/null and b/bin/main/seedu/address/logic/commands/UndoCommand.class differ
diff --git a/bin/main/seedu/address/logic/commands/exceptions/CommandException.class b/bin/main/seedu/address/logic/commands/exceptions/CommandException.class
new file mode 100644
index 00000000000..40ff643fea0
Binary files /dev/null and b/bin/main/seedu/address/logic/commands/exceptions/CommandException.class differ
diff --git a/bin/main/seedu/address/logic/parser/AddCommandParser.class b/bin/main/seedu/address/logic/parser/AddCommandParser.class
new file mode 100644
index 00000000000..fe7b2d7c69b
Binary files /dev/null and b/bin/main/seedu/address/logic/parser/AddCommandParser.class differ
diff --git a/bin/main/seedu/address/logic/parser/AddPackageCommandParser.class b/bin/main/seedu/address/logic/parser/AddPackageCommandParser.class
new file mode 100644
index 00000000000..a681caf7f5d
Binary files /dev/null and b/bin/main/seedu/address/logic/parser/AddPackageCommandParser.class differ
diff --git a/bin/main/seedu/address/logic/parser/AddTagCommandParser.class b/bin/main/seedu/address/logic/parser/AddTagCommandParser.class
new file mode 100644
index 00000000000..19d5e64780d
Binary files /dev/null and b/bin/main/seedu/address/logic/parser/AddTagCommandParser.class differ
diff --git a/bin/main/seedu/address/logic/parser/AddToClipboardCommandParser.class b/bin/main/seedu/address/logic/parser/AddToClipboardCommandParser.class
new file mode 100644
index 00000000000..ad20161419d
Binary files /dev/null and b/bin/main/seedu/address/logic/parser/AddToClipboardCommandParser.class differ
diff --git a/bin/main/seedu/address/logic/parser/AddressBookParser.class b/bin/main/seedu/address/logic/parser/AddressBookParser.class
new file mode 100644
index 00000000000..e96cf9e65d3
Binary files /dev/null and b/bin/main/seedu/address/logic/parser/AddressBookParser.class differ
diff --git a/bin/main/seedu/address/logic/parser/ArgumentMultimap.class b/bin/main/seedu/address/logic/parser/ArgumentMultimap.class
new file mode 100644
index 00000000000..69d69728c9a
Binary files /dev/null and b/bin/main/seedu/address/logic/parser/ArgumentMultimap.class differ
diff --git a/bin/main/seedu/address/logic/parser/ArgumentTokenizer$PrefixPosition.class b/bin/main/seedu/address/logic/parser/ArgumentTokenizer$PrefixPosition.class
new file mode 100644
index 00000000000..8762d805bfe
Binary files /dev/null and b/bin/main/seedu/address/logic/parser/ArgumentTokenizer$PrefixPosition.class differ
diff --git a/bin/main/seedu/address/logic/parser/ArgumentTokenizer.class b/bin/main/seedu/address/logic/parser/ArgumentTokenizer.class
new file mode 100644
index 00000000000..3b18d2b59d4
Binary files /dev/null and b/bin/main/seedu/address/logic/parser/ArgumentTokenizer.class differ
diff --git a/bin/main/seedu/address/logic/parser/CliSyntax.class b/bin/main/seedu/address/logic/parser/CliSyntax.class
new file mode 100644
index 00000000000..c33a44f479c
Binary files /dev/null and b/bin/main/seedu/address/logic/parser/CliSyntax.class differ
diff --git a/bin/main/seedu/address/logic/parser/DeleteCommandParser.class b/bin/main/seedu/address/logic/parser/DeleteCommandParser.class
new file mode 100644
index 00000000000..6b4749e0e4b
Binary files /dev/null and b/bin/main/seedu/address/logic/parser/DeleteCommandParser.class differ
diff --git a/bin/main/seedu/address/logic/parser/DeletePackageCommandParser.class b/bin/main/seedu/address/logic/parser/DeletePackageCommandParser.class
new file mode 100644
index 00000000000..932d5949638
Binary files /dev/null and b/bin/main/seedu/address/logic/parser/DeletePackageCommandParser.class differ
diff --git a/bin/main/seedu/address/logic/parser/DeleteTagCommandParser.class b/bin/main/seedu/address/logic/parser/DeleteTagCommandParser.class
new file mode 100644
index 00000000000..76b854329a2
Binary files /dev/null and b/bin/main/seedu/address/logic/parser/DeleteTagCommandParser.class differ
diff --git a/bin/main/seedu/address/logic/parser/EditCommandParser.class b/bin/main/seedu/address/logic/parser/EditCommandParser.class
new file mode 100644
index 00000000000..76073499a93
Binary files /dev/null and b/bin/main/seedu/address/logic/parser/EditCommandParser.class differ
diff --git a/bin/main/seedu/address/logic/parser/EditPackageCommandParser.class b/bin/main/seedu/address/logic/parser/EditPackageCommandParser.class
new file mode 100644
index 00000000000..0e08a3794f2
Binary files /dev/null and b/bin/main/seedu/address/logic/parser/EditPackageCommandParser.class differ
diff --git a/bin/main/seedu/address/logic/parser/EditTagCommandParser.class b/bin/main/seedu/address/logic/parser/EditTagCommandParser.class
new file mode 100644
index 00000000000..c9551cc575f
Binary files /dev/null and b/bin/main/seedu/address/logic/parser/EditTagCommandParser.class differ
diff --git a/bin/main/seedu/address/logic/parser/FindCommandParser.class b/bin/main/seedu/address/logic/parser/FindCommandParser.class
new file mode 100644
index 00000000000..9135af183ad
Binary files /dev/null and b/bin/main/seedu/address/logic/parser/FindCommandParser.class differ
diff --git a/bin/main/seedu/address/logic/parser/Parser.class b/bin/main/seedu/address/logic/parser/Parser.class
new file mode 100644
index 00000000000..982a9364d07
Binary files /dev/null and b/bin/main/seedu/address/logic/parser/Parser.class differ
diff --git a/bin/main/seedu/address/logic/parser/ParserUtil.class b/bin/main/seedu/address/logic/parser/ParserUtil.class
new file mode 100644
index 00000000000..1ac0c09b72b
Binary files /dev/null and b/bin/main/seedu/address/logic/parser/ParserUtil.class differ
diff --git a/bin/main/seedu/address/logic/parser/Prefix.class b/bin/main/seedu/address/logic/parser/Prefix.class
new file mode 100644
index 00000000000..12701c613d2
Binary files /dev/null and b/bin/main/seedu/address/logic/parser/Prefix.class differ
diff --git a/bin/main/seedu/address/logic/parser/exceptions/ParseException.class b/bin/main/seedu/address/logic/parser/exceptions/ParseException.class
new file mode 100644
index 00000000000..747cbb8b53d
Binary files /dev/null and b/bin/main/seedu/address/logic/parser/exceptions/ParseException.class differ
diff --git a/bin/main/seedu/address/logic/parser/exceptions/ParseNoKeywordException.class b/bin/main/seedu/address/logic/parser/exceptions/ParseNoKeywordException.class
new file mode 100644
index 00000000000..fd569318219
Binary files /dev/null and b/bin/main/seedu/address/logic/parser/exceptions/ParseNoKeywordException.class differ
diff --git a/bin/main/seedu/address/logic/parser/exceptions/ParseNoPrefixException.class b/bin/main/seedu/address/logic/parser/exceptions/ParseNoPrefixException.class
new file mode 100644
index 00000000000..597531260c0
Binary files /dev/null and b/bin/main/seedu/address/logic/parser/exceptions/ParseNoPrefixException.class differ
diff --git a/bin/main/seedu/address/model/AddressBook.class b/bin/main/seedu/address/model/AddressBook.class
new file mode 100644
index 00000000000..3250c8ae3b5
Binary files /dev/null and b/bin/main/seedu/address/model/AddressBook.class differ
diff --git a/bin/main/seedu/address/model/InsurancePackagesSet.class b/bin/main/seedu/address/model/InsurancePackagesSet.class
new file mode 100644
index 00000000000..6ff18ad1992
Binary files /dev/null and b/bin/main/seedu/address/model/InsurancePackagesSet.class differ
diff --git a/bin/main/seedu/address/model/Model.class b/bin/main/seedu/address/model/Model.class
new file mode 100644
index 00000000000..a1b63c4c960
Binary files /dev/null and b/bin/main/seedu/address/model/Model.class differ
diff --git a/bin/main/seedu/address/model/ModelManager.class b/bin/main/seedu/address/model/ModelManager.class
new file mode 100644
index 00000000000..3c76cd41def
Binary files /dev/null and b/bin/main/seedu/address/model/ModelManager.class differ
diff --git a/bin/main/seedu/address/model/ReadOnlyAddressBook.class b/bin/main/seedu/address/model/ReadOnlyAddressBook.class
new file mode 100644
index 00000000000..dd6812d84a6
Binary files /dev/null and b/bin/main/seedu/address/model/ReadOnlyAddressBook.class differ
diff --git a/bin/main/seedu/address/model/ReadOnlyUserPrefs.class b/bin/main/seedu/address/model/ReadOnlyUserPrefs.class
new file mode 100644
index 00000000000..7cb7a75a54e
Binary files /dev/null and b/bin/main/seedu/address/model/ReadOnlyUserPrefs.class differ
diff --git a/bin/main/seedu/address/model/UserPrefs.class b/bin/main/seedu/address/model/UserPrefs.class
new file mode 100644
index 00000000000..e89bc75f00b
Binary files /dev/null and b/bin/main/seedu/address/model/UserPrefs.class differ
diff --git a/bin/main/seedu/address/model/person/Address.class b/bin/main/seedu/address/model/person/Address.class
new file mode 100644
index 00000000000..6120a2e8c8b
Binary files /dev/null and b/bin/main/seedu/address/model/person/Address.class differ
diff --git a/bin/main/seedu/address/model/person/Email.class b/bin/main/seedu/address/model/person/Email.class
new file mode 100644
index 00000000000..86d7851ddc1
Binary files /dev/null and b/bin/main/seedu/address/model/person/Email.class differ
diff --git a/bin/main/seedu/address/model/person/InsurancePackage.class b/bin/main/seedu/address/model/person/InsurancePackage.class
new file mode 100644
index 00000000000..690f59e0303
Binary files /dev/null and b/bin/main/seedu/address/model/person/InsurancePackage.class differ
diff --git a/bin/main/seedu/address/model/person/Name.class b/bin/main/seedu/address/model/person/Name.class
new file mode 100644
index 00000000000..54264b11f97
Binary files /dev/null and b/bin/main/seedu/address/model/person/Name.class differ
diff --git a/bin/main/seedu/address/model/person/Person.class b/bin/main/seedu/address/model/person/Person.class
new file mode 100644
index 00000000000..658c8dc6f74
Binary files /dev/null and b/bin/main/seedu/address/model/person/Person.class differ
diff --git a/bin/main/seedu/address/model/person/Phone.class b/bin/main/seedu/address/model/person/Phone.class
new file mode 100644
index 00000000000..51230e67a6b
Binary files /dev/null and b/bin/main/seedu/address/model/person/Phone.class differ
diff --git a/bin/main/seedu/address/model/person/UniquePersonList.class b/bin/main/seedu/address/model/person/UniquePersonList.class
new file mode 100644
index 00000000000..97139bb40e2
Binary files /dev/null and b/bin/main/seedu/address/model/person/UniquePersonList.class differ
diff --git a/bin/main/seedu/address/model/person/comparators/TagPriorityComparator.class b/bin/main/seedu/address/model/person/comparators/TagPriorityComparator.class
new file mode 100644
index 00000000000..f2c55434c39
Binary files /dev/null and b/bin/main/seedu/address/model/person/comparators/TagPriorityComparator.class differ
diff --git a/bin/main/seedu/address/model/person/exceptions/DuplicatePersonException.class b/bin/main/seedu/address/model/person/exceptions/DuplicatePersonException.class
new file mode 100644
index 00000000000..b93c81ba426
Binary files /dev/null and b/bin/main/seedu/address/model/person/exceptions/DuplicatePersonException.class differ
diff --git a/bin/main/seedu/address/model/person/exceptions/PersonNotFoundException.class b/bin/main/seedu/address/model/person/exceptions/PersonNotFoundException.class
new file mode 100644
index 00000000000..305ea567649
Binary files /dev/null and b/bin/main/seedu/address/model/person/exceptions/PersonNotFoundException.class differ
diff --git a/bin/main/seedu/address/model/person/predicates/AddressContainsKeywordsPredicate.class b/bin/main/seedu/address/model/person/predicates/AddressContainsKeywordsPredicate.class
new file mode 100644
index 00000000000..d5c09257f4b
Binary files /dev/null and b/bin/main/seedu/address/model/person/predicates/AddressContainsKeywordsPredicate.class differ
diff --git a/bin/main/seedu/address/model/person/predicates/CombineContainsKeywordsPredicate.class b/bin/main/seedu/address/model/person/predicates/CombineContainsKeywordsPredicate.class
new file mode 100644
index 00000000000..bb04a9877e2
Binary files /dev/null and b/bin/main/seedu/address/model/person/predicates/CombineContainsKeywordsPredicate.class differ
diff --git a/bin/main/seedu/address/model/person/predicates/EmailContainsKeywordsPredicate.class b/bin/main/seedu/address/model/person/predicates/EmailContainsKeywordsPredicate.class
new file mode 100644
index 00000000000..2ad803e4490
Binary files /dev/null and b/bin/main/seedu/address/model/person/predicates/EmailContainsKeywordsPredicate.class differ
diff --git a/bin/main/seedu/address/model/person/predicates/FieldContainsKeywordsPredicate.class b/bin/main/seedu/address/model/person/predicates/FieldContainsKeywordsPredicate.class
new file mode 100644
index 00000000000..a471c85adbb
Binary files /dev/null and b/bin/main/seedu/address/model/person/predicates/FieldContainsKeywordsPredicate.class differ
diff --git a/bin/main/seedu/address/model/person/predicates/InsurancePackageContainsKeywordsPredicate.class b/bin/main/seedu/address/model/person/predicates/InsurancePackageContainsKeywordsPredicate.class
new file mode 100644
index 00000000000..68a8ef5e2dc
Binary files /dev/null and b/bin/main/seedu/address/model/person/predicates/InsurancePackageContainsKeywordsPredicate.class differ
diff --git a/bin/main/seedu/address/model/person/predicates/NameContainsKeywordsPredicate.class b/bin/main/seedu/address/model/person/predicates/NameContainsKeywordsPredicate.class
new file mode 100644
index 00000000000..a6c1f4514e9
Binary files /dev/null and b/bin/main/seedu/address/model/person/predicates/NameContainsKeywordsPredicate.class differ
diff --git a/bin/main/seedu/address/model/person/predicates/NameExistsPredicate.class b/bin/main/seedu/address/model/person/predicates/NameExistsPredicate.class
new file mode 100644
index 00000000000..78526795898
Binary files /dev/null and b/bin/main/seedu/address/model/person/predicates/NameExistsPredicate.class differ
diff --git a/bin/main/seedu/address/model/person/predicates/PhoneContainsKeywordsPredicate.class b/bin/main/seedu/address/model/person/predicates/PhoneContainsKeywordsPredicate.class
new file mode 100644
index 00000000000..be332abe3c3
Binary files /dev/null and b/bin/main/seedu/address/model/person/predicates/PhoneContainsKeywordsPredicate.class differ
diff --git a/bin/main/seedu/address/model/person/predicates/TagsContainsKeywordsPredicate.class b/bin/main/seedu/address/model/person/predicates/TagsContainsKeywordsPredicate.class
new file mode 100644
index 00000000000..16ca4ced852
Binary files /dev/null and b/bin/main/seedu/address/model/person/predicates/TagsContainsKeywordsPredicate.class differ
diff --git a/bin/main/seedu/address/model/tag/Priority.class b/bin/main/seedu/address/model/tag/Priority.class
new file mode 100644
index 00000000000..f304f007984
Binary files /dev/null and b/bin/main/seedu/address/model/tag/Priority.class differ
diff --git a/bin/main/seedu/address/model/tag/Tag.class b/bin/main/seedu/address/model/tag/Tag.class
new file mode 100644
index 00000000000..b9d65231e65
Binary files /dev/null and b/bin/main/seedu/address/model/tag/Tag.class differ
diff --git a/bin/main/seedu/address/model/util/SampleDataUtil.class b/bin/main/seedu/address/model/util/SampleDataUtil.class
new file mode 100644
index 00000000000..80c4fea41d1
Binary files /dev/null and b/bin/main/seedu/address/model/util/SampleDataUtil.class differ
diff --git a/bin/main/seedu/address/storage/AddressBookStorage.class b/bin/main/seedu/address/storage/AddressBookStorage.class
new file mode 100644
index 00000000000..f78aa5a584a
Binary files /dev/null and b/bin/main/seedu/address/storage/AddressBookStorage.class differ
diff --git a/bin/main/seedu/address/storage/CommandStorage.class b/bin/main/seedu/address/storage/CommandStorage.class
new file mode 100644
index 00000000000..9bae9eb1df6
Binary files /dev/null and b/bin/main/seedu/address/storage/CommandStorage.class differ
diff --git a/bin/main/seedu/address/storage/CsvAdaptedInsurancePackage.class b/bin/main/seedu/address/storage/CsvAdaptedInsurancePackage.class
new file mode 100644
index 00000000000..5227b6daff7
Binary files /dev/null and b/bin/main/seedu/address/storage/CsvAdaptedInsurancePackage.class differ
diff --git a/bin/main/seedu/address/storage/CsvAdaptedPerson.class b/bin/main/seedu/address/storage/CsvAdaptedPerson.class
new file mode 100644
index 00000000000..2f271667da8
Binary files /dev/null and b/bin/main/seedu/address/storage/CsvAdaptedPerson.class differ
diff --git a/bin/main/seedu/address/storage/CsvAdaptedTag.class b/bin/main/seedu/address/storage/CsvAdaptedTag.class
new file mode 100644
index 00000000000..0c874c69f46
Binary files /dev/null and b/bin/main/seedu/address/storage/CsvAdaptedTag.class differ
diff --git a/bin/main/seedu/address/storage/CsvAddressBookStorage.class b/bin/main/seedu/address/storage/CsvAddressBookStorage.class
new file mode 100644
index 00000000000..e84d6588ce8
Binary files /dev/null and b/bin/main/seedu/address/storage/CsvAddressBookStorage.class differ
diff --git a/bin/main/seedu/address/storage/CsvInsurancePackagesStorage.class b/bin/main/seedu/address/storage/CsvInsurancePackagesStorage.class
new file mode 100644
index 00000000000..3018785dc39
Binary files /dev/null and b/bin/main/seedu/address/storage/CsvInsurancePackagesStorage.class differ
diff --git a/bin/main/seedu/address/storage/InsurancePackagesStorage.class b/bin/main/seedu/address/storage/InsurancePackagesStorage.class
new file mode 100644
index 00000000000..1f81cd23901
Binary files /dev/null and b/bin/main/seedu/address/storage/InsurancePackagesStorage.class differ
diff --git a/bin/main/seedu/address/storage/JsonAdaptedPerson.class b/bin/main/seedu/address/storage/JsonAdaptedPerson.class
new file mode 100644
index 00000000000..e80559e8faf
Binary files /dev/null and b/bin/main/seedu/address/storage/JsonAdaptedPerson.class differ
diff --git a/bin/main/seedu/address/storage/JsonAdaptedTag.class b/bin/main/seedu/address/storage/JsonAdaptedTag.class
new file mode 100644
index 00000000000..1fb082980fb
Binary files /dev/null and b/bin/main/seedu/address/storage/JsonAdaptedTag.class differ
diff --git a/bin/main/seedu/address/storage/JsonAddressBookStorage.class b/bin/main/seedu/address/storage/JsonAddressBookStorage.class
new file mode 100644
index 00000000000..8def22d7d14
Binary files /dev/null and b/bin/main/seedu/address/storage/JsonAddressBookStorage.class differ
diff --git a/bin/main/seedu/address/storage/JsonSerializableAddressBook.class b/bin/main/seedu/address/storage/JsonSerializableAddressBook.class
new file mode 100644
index 00000000000..2624110a9a9
Binary files /dev/null and b/bin/main/seedu/address/storage/JsonSerializableAddressBook.class differ
diff --git a/bin/main/seedu/address/storage/JsonUserPrefsStorage.class b/bin/main/seedu/address/storage/JsonUserPrefsStorage.class
new file mode 100644
index 00000000000..d4dfe8f101f
Binary files /dev/null and b/bin/main/seedu/address/storage/JsonUserPrefsStorage.class differ
diff --git a/bin/main/seedu/address/storage/Storage.class b/bin/main/seedu/address/storage/Storage.class
new file mode 100644
index 00000000000..e25291e9154
Binary files /dev/null and b/bin/main/seedu/address/storage/Storage.class differ
diff --git a/bin/main/seedu/address/storage/StorageManager.class b/bin/main/seedu/address/storage/StorageManager.class
new file mode 100644
index 00000000000..2311242906f
Binary files /dev/null and b/bin/main/seedu/address/storage/StorageManager.class differ
diff --git a/bin/main/seedu/address/storage/UndoRedoStorage.class b/bin/main/seedu/address/storage/UndoRedoStorage.class
new file mode 100644
index 00000000000..b231c31daa5
Binary files /dev/null and b/bin/main/seedu/address/storage/UndoRedoStorage.class differ
diff --git a/bin/main/seedu/address/storage/UserPrefsStorage.class b/bin/main/seedu/address/storage/UserPrefsStorage.class
new file mode 100644
index 00000000000..8d3d2d7fd8b
Binary files /dev/null and b/bin/main/seedu/address/storage/UserPrefsStorage.class differ
diff --git a/bin/main/seedu/address/ui/CommandBox$CommandExecutor.class b/bin/main/seedu/address/ui/CommandBox$CommandExecutor.class
new file mode 100644
index 00000000000..efc51a94329
Binary files /dev/null and b/bin/main/seedu/address/ui/CommandBox$CommandExecutor.class differ
diff --git a/bin/main/seedu/address/ui/CommandBox$UserInputTracker.class b/bin/main/seedu/address/ui/CommandBox$UserInputTracker.class
new file mode 100644
index 00000000000..d2a788a024c
Binary files /dev/null and b/bin/main/seedu/address/ui/CommandBox$UserInputTracker.class differ
diff --git a/bin/main/seedu/address/ui/CommandBox.class b/bin/main/seedu/address/ui/CommandBox.class
new file mode 100644
index 00000000000..7220aeb01b5
Binary files /dev/null and b/bin/main/seedu/address/ui/CommandBox.class differ
diff --git a/bin/main/seedu/address/ui/HelpWindow.class b/bin/main/seedu/address/ui/HelpWindow.class
new file mode 100644
index 00000000000..80bc4c5dec7
Binary files /dev/null and b/bin/main/seedu/address/ui/HelpWindow.class differ
diff --git a/bin/main/seedu/address/ui/MainWindow.class b/bin/main/seedu/address/ui/MainWindow.class
new file mode 100644
index 00000000000..45a2caa7e32
Binary files /dev/null and b/bin/main/seedu/address/ui/MainWindow.class differ
diff --git a/bin/main/seedu/address/ui/MessageWindow.class b/bin/main/seedu/address/ui/MessageWindow.class
new file mode 100644
index 00000000000..379737d661b
Binary files /dev/null and b/bin/main/seedu/address/ui/MessageWindow.class differ
diff --git a/bin/main/seedu/address/ui/PackageCard.class b/bin/main/seedu/address/ui/PackageCard.class
new file mode 100644
index 00000000000..5c138046f9b
Binary files /dev/null and b/bin/main/seedu/address/ui/PackageCard.class differ
diff --git a/bin/main/seedu/address/ui/PackageListPanel$PackageListViewCell.class b/bin/main/seedu/address/ui/PackageListPanel$PackageListViewCell.class
new file mode 100644
index 00000000000..115ad6efedc
Binary files /dev/null and b/bin/main/seedu/address/ui/PackageListPanel$PackageListViewCell.class differ
diff --git a/bin/main/seedu/address/ui/PackageListPanel.class b/bin/main/seedu/address/ui/PackageListPanel.class
new file mode 100644
index 00000000000..ba20982e0fd
Binary files /dev/null and b/bin/main/seedu/address/ui/PackageListPanel.class differ
diff --git a/bin/main/seedu/address/ui/PackageWindow.class b/bin/main/seedu/address/ui/PackageWindow.class
new file mode 100644
index 00000000000..99d6a1d16fb
Binary files /dev/null and b/bin/main/seedu/address/ui/PackageWindow.class differ
diff --git a/bin/main/seedu/address/ui/PersonCard.class b/bin/main/seedu/address/ui/PersonCard.class
new file mode 100644
index 00000000000..e44e1f87eda
Binary files /dev/null and b/bin/main/seedu/address/ui/PersonCard.class differ
diff --git a/bin/main/seedu/address/ui/PersonListPanel$PersonListViewCell.class b/bin/main/seedu/address/ui/PersonListPanel$PersonListViewCell.class
new file mode 100644
index 00000000000..011b43afc82
Binary files /dev/null and b/bin/main/seedu/address/ui/PersonListPanel$PersonListViewCell.class differ
diff --git a/bin/main/seedu/address/ui/PersonListPanel.class b/bin/main/seedu/address/ui/PersonListPanel.class
new file mode 100644
index 00000000000..62f755bb4f5
Binary files /dev/null and b/bin/main/seedu/address/ui/PersonListPanel.class differ
diff --git a/bin/main/seedu/address/ui/ResultDisplay.class b/bin/main/seedu/address/ui/ResultDisplay.class
new file mode 100644
index 00000000000..a6c347ebb17
Binary files /dev/null and b/bin/main/seedu/address/ui/ResultDisplay.class differ
diff --git a/bin/main/seedu/address/ui/StatusBarFooter.class b/bin/main/seedu/address/ui/StatusBarFooter.class
new file mode 100644
index 00000000000..939a09aa487
Binary files /dev/null and b/bin/main/seedu/address/ui/StatusBarFooter.class differ
diff --git a/bin/main/seedu/address/ui/Ui.class b/bin/main/seedu/address/ui/Ui.class
new file mode 100644
index 00000000000..8778b647196
Binary files /dev/null and b/bin/main/seedu/address/ui/Ui.class differ
diff --git a/bin/main/seedu/address/ui/UiManager.class b/bin/main/seedu/address/ui/UiManager.class
new file mode 100644
index 00000000000..2744d6e912b
Binary files /dev/null and b/bin/main/seedu/address/ui/UiManager.class differ
diff --git a/bin/main/seedu/address/ui/UiPart.class b/bin/main/seedu/address/ui/UiPart.class
new file mode 100644
index 00000000000..394e8f921a9
Binary files /dev/null and b/bin/main/seedu/address/ui/UiPart.class differ
diff --git a/bin/main/view/CommandBox.fxml b/bin/main/view/CommandBox.fxml
new file mode 100644
index 00000000000..19dd3886dc8
--- /dev/null
+++ b/bin/main/view/CommandBox.fxml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/view/Extensions.css b/bin/main/view/DarkExtensions.css
similarity index 100%
rename from src/main/resources/view/Extensions.css
rename to bin/main/view/DarkExtensions.css
diff --git a/bin/main/view/DarkTheme.css b/bin/main/view/DarkTheme.css
new file mode 100644
index 00000000000..36e6b001cd8
--- /dev/null
+++ b/bin/main/view/DarkTheme.css
@@ -0,0 +1,352 @@
+.background {
+ -fx-background-color: derive(#1d1d1d, 20%);
+ background-color: #383838; /* Used in the default.html file */
+}
+
+.label {
+ -fx-font-size: 11pt;
+ -fx-font-family: "Segoe UI Semibold";
+ -fx-text-fill: #555555;
+ -fx-opacity: 0.9;
+}
+
+.label-bright {
+ -fx-font-size: 11pt;
+ -fx-font-family: "Segoe UI Semibold";
+ -fx-text-fill: white;
+ -fx-opacity: 1;
+}
+
+.label-header {
+ -fx-font-size: 32pt;
+ -fx-font-family: "Segoe UI Light";
+ -fx-text-fill: white;
+ -fx-opacity: 1;
+}
+
+.text-field {
+ -fx-font-size: 12pt;
+ -fx-font-family: "Segoe UI Semibold";
+}
+
+.tab-pane {
+ -fx-padding: 0 0 0 1;
+}
+
+.tab-pane .tab-header-area {
+ -fx-padding: 0 0 0 0;
+ -fx-min-height: 0;
+ -fx-max-height: 0;
+}
+
+.table-view {
+ -fx-base: #1d1d1d;
+ -fx-control-inner-background: #1d1d1d;
+ -fx-background-color: #1d1d1d;
+ -fx-table-cell-border-color: transparent;
+ -fx-table-header-border-color: transparent;
+ -fx-padding: 5;
+}
+
+.table-view .column-header-background {
+ -fx-background-color: transparent;
+}
+
+.table-view .column-header, .table-view .filler {
+ -fx-size: 35;
+ -fx-border-width: 0 0 1 0;
+ -fx-background-color: transparent;
+ -fx-border-color:
+ transparent
+ transparent
+ derive(-fx-base, 80%)
+ transparent;
+ -fx-border-insets: 0 10 1 0;
+}
+
+.table-view .column-header .label {
+ -fx-font-size: 20pt;
+ -fx-font-family: "Segoe UI Light";
+ -fx-text-fill: white;
+ -fx-alignment: center-left;
+ -fx-opacity: 1;
+}
+
+.table-view:focused .table-row-cell:filled:focused:selected {
+ -fx-background-color: -fx-focus-color;
+}
+
+.split-pane:horizontal .split-pane-divider {
+ -fx-background-color: derive(#1d1d1d, 20%);
+ -fx-border-color: transparent transparent transparent #4d4d4d;
+}
+
+.split-pane {
+ -fx-border-radius: 1;
+ -fx-border-width: 1;
+ -fx-background-color: derive(#1d1d1d, 20%);
+}
+
+.list-view {
+ -fx-background-insets: 0;
+ -fx-padding: 0;
+ -fx-background-color: derive(#1d1d1d, 20%);
+}
+
+.list-cell {
+ -fx-label-padding: 0 0 0 0;
+ -fx-graphic-text-gap : 0;
+ -fx-padding: 0 0 0 0;
+}
+
+.list-cell:filled:even {
+ -fx-background-color: #3c3e3f;
+}
+
+.list-cell:filled:odd {
+ -fx-background-color: #515658;
+}
+
+.list-cell:filled:selected {
+ -fx-background-color: #424d5f;
+}
+
+.list-cell:filled:selected #cardPane {
+ -fx-border-color: #3e7b91;
+ -fx-border-width: 1;
+}
+
+.list-cell .label {
+ -fx-text-fill: white;
+}
+
+.cell_big_label {
+ -fx-font-family: "Segoe UI Semibold";
+ -fx-font-size: 16px;
+ -fx-text-fill: #010504;
+}
+
+.cell_small_label {
+ -fx-font-family: "Segoe UI";
+ -fx-font-size: 13px;
+ -fx-text-fill: #010504;
+}
+
+.stack-pane {
+ -fx-background-color: derive(#1d1d1d, 20%);
+}
+
+.pane-with-border {
+ -fx-background-color: derive(#1d1d1d, 20%);
+ -fx-border-color: derive(#1d1d1d, 10%);
+ -fx-border-top-width: 1px;
+}
+
+.status-bar {
+ -fx-background-color: derive(#1d1d1d, 30%);
+}
+
+.result-display {
+ -fx-background-color: transparent;
+ -fx-font-family: "Segoe UI Light";
+ -fx-font-size: 13pt;
+ -fx-text-fill: white;
+}
+
+.result-display .label {
+ -fx-text-fill: black !important;
+}
+
+.status-bar .label {
+ -fx-font-family: "Segoe UI Light";
+ -fx-text-fill: white;
+ -fx-padding: 4px;
+ -fx-pref-height: 30px;
+}
+
+.status-bar-with-border {
+ -fx-background-color: derive(#1d1d1d, 30%);
+ -fx-border-color: derive(#1d1d1d, 25%);
+ -fx-border-width: 1px;
+}
+
+.status-bar-with-border .label {
+ -fx-text-fill: white;
+}
+
+.grid-pane {
+ -fx-background-color: derive(#1d1d1d, 30%);
+ -fx-border-color: derive(#1d1d1d, 30%);
+ -fx-border-width: 1px;
+}
+
+.grid-pane .stack-pane {
+ -fx-background-color: derive(#1d1d1d, 30%);
+}
+
+.context-menu {
+ -fx-background-color: derive(#1d1d1d, 50%);
+}
+
+.context-menu .label {
+ -fx-text-fill: white;
+}
+
+.menu-bar {
+ -fx-background-color: derive(#1d1d1d, 20%);
+}
+
+.menu-bar .label {
+ -fx-font-size: 14pt;
+ -fx-font-family: "Segoe UI Light";
+ -fx-text-fill: white;
+ -fx-opacity: 0.9;
+}
+
+.menu .left-container {
+ -fx-background-color: black;
+}
+
+/*
+ * Metro style Push Button
+ * Author: Pedro Duque Vieira
+ * http://pixelduke.wordpress.com/2012/10/23/jmetro-windows-8-controls-on-java/
+ */
+.button {
+ -fx-padding: 5 22 5 22;
+ -fx-border-color: #e2e2e2;
+ -fx-border-width: 2;
+ -fx-background-radius: 0;
+ -fx-background-color: #1d1d1d;
+ -fx-font-family: "Segoe UI", Helvetica, Arial, sans-serif;
+ -fx-font-size: 11pt;
+ -fx-text-fill: #d8d8d8;
+ -fx-background-insets: 0 0 0 0, 0, 1, 2;
+}
+
+.button:hover {
+ -fx-background-color: #3a3a3a;
+}
+
+.button:pressed, .button:default:hover:pressed {
+ -fx-background-color: white;
+ -fx-text-fill: #1d1d1d;
+}
+
+.button:focused {
+ -fx-border-color: white, white;
+ -fx-border-width: 1, 1;
+ -fx-border-style: solid, segments(1, 1);
+ -fx-border-radius: 0, 0;
+ -fx-border-insets: 1 1 1 1, 0;
+}
+
+.button:disabled, .button:default:disabled {
+ -fx-opacity: 0.4;
+ -fx-background-color: #1d1d1d;
+ -fx-text-fill: white;
+}
+
+.button:default {
+ -fx-background-color: -fx-focus-color;
+ -fx-text-fill: #ffffff;
+}
+
+.button:default:hover {
+ -fx-background-color: derive(-fx-focus-color, 30%);
+}
+
+.dialog-pane {
+ -fx-background-color: #1d1d1d;
+}
+
+.dialog-pane > *.button-bar > *.container {
+ -fx-background-color: #1d1d1d;
+}
+
+.dialog-pane > *.label.content {
+ -fx-font-size: 14px;
+ -fx-font-weight: bold;
+ -fx-text-fill: white;
+}
+
+.dialog-pane:header *.header-panel {
+ -fx-background-color: derive(#1d1d1d, 25%);
+}
+
+.dialog-pane:header *.header-panel *.label {
+ -fx-font-size: 18px;
+ -fx-font-style: italic;
+ -fx-fill: white;
+ -fx-text-fill: white;
+}
+
+.scroll-bar {
+ -fx-background-color: derive(#1d1d1d, 20%);
+}
+
+.scroll-bar .thumb {
+ -fx-background-color: derive(#1d1d1d, 50%);
+ -fx-background-insets: 3;
+}
+
+.scroll-bar .increment-button, .scroll-bar .decrement-button {
+ -fx-background-color: transparent;
+ -fx-padding: 0 0 0 0;
+}
+
+.scroll-bar .increment-arrow, .scroll-bar .decrement-arrow {
+ -fx-shape: " ";
+}
+
+.scroll-bar:vertical .increment-arrow, .scroll-bar:vertical .decrement-arrow {
+ -fx-padding: 1 8 1 8;
+}
+
+.scroll-bar:horizontal .increment-arrow, .scroll-bar:horizontal .decrement-arrow {
+ -fx-padding: 8 1 8 1;
+}
+
+#cardPane {
+ -fx-background-color: transparent;
+ -fx-border-width: 0;
+}
+
+#commandTypeLabel {
+ -fx-font-size: 11px;
+ -fx-text-fill: #F70D1A;
+}
+
+#commandTextField {
+ -fx-background-color: transparent #383838 transparent #383838;
+ -fx-background-insets: 0;
+ -fx-border-color: #383838 #383838 #ffffff #383838;
+ -fx-border-insets: 0;
+ -fx-border-width: 1;
+ -fx-font-family: "Segoe UI Light";
+ -fx-font-size: 13pt;
+ -fx-text-fill: white;
+}
+
+#filterField, #personListPanel, #personWebpage {
+ -fx-effect: innershadow(gaussian, black, 10, 0, 0, 0);
+}
+
+#resultDisplay .content {
+ -fx-background-color: transparent, #383838, transparent, #383838;
+ -fx-background-radius: 0;
+}
+
+#tags {
+ -fx-hgap: 7;
+ -fx-vgap: 3;
+}
+
+#tags .label {
+ -fx-text-fill: white;
+ -fx-background-color: #3e7b91;
+ -fx-padding: 1 3 1 3;
+ -fx-border-radius: 2;
+ -fx-background-radius: 2;
+ -fx-font-size: 11;
+}
diff --git a/bin/main/view/HelpWindow.css b/bin/main/view/HelpWindow.css
new file mode 100644
index 00000000000..17e8a8722cd
--- /dev/null
+++ b/bin/main/view/HelpWindow.css
@@ -0,0 +1,19 @@
+#copyButton, #helpMessage {
+ -fx-text-fill: white;
+}
+
+#copyButton {
+ -fx-background-color: dimgray;
+}
+
+#copyButton:hover {
+ -fx-background-color: gray;
+}
+
+#copyButton:armed {
+ -fx-background-color: darkgray;
+}
+
+#helpMessageContainer {
+ -fx-background-color: derive(#1d1d1d, 20%);
+}
diff --git a/bin/main/view/HelpWindow.fxml b/bin/main/view/HelpWindow.fxml
new file mode 100644
index 00000000000..5dea0adef70
--- /dev/null
+++ b/bin/main/view/HelpWindow.fxml
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bin/main/view/LightExtensions.css b/bin/main/view/LightExtensions.css
new file mode 100644
index 00000000000..abb687197fa
--- /dev/null
+++ b/bin/main/view/LightExtensions.css
@@ -0,0 +1,20 @@
+
+.error {
+ -fx-text-fill: #d06651 !important; /* The error class should always override the default text-fill style */
+}
+
+.list-cell:empty {
+ /* Empty cells will not have alternating colours */
+ -fx-background: white;
+}
+
+.tag-selector {
+ -fx-border-width: 1;
+ -fx-border-color: white;
+ -fx-border-radius: 3;
+ -fx-background-radius: 3;
+}
+
+.tooltip-text {
+ -fx-text-fill: white;
+}
diff --git a/bin/main/view/LightTheme.css b/bin/main/view/LightTheme.css
new file mode 100644
index 00000000000..5a9973a4aa6
--- /dev/null
+++ b/bin/main/view/LightTheme.css
@@ -0,0 +1,352 @@
+.background {
+ -fx-background-color: derive(#d3d3d3, 20%);
+ background-color: #d3d3d3; /* Used in the default.html file */
+}
+
+.label {
+ -fx-font-size: 11pt;
+ -fx-font-family: "Segoe UI Semibold";
+ -fx-text-fill: #555555;
+ -fx-opacity: 0.9;
+}
+
+.label-bright {
+ -fx-font-size: 11pt;
+ -fx-font-family: "Segoe UI Semibold";
+ -fx-text-fill: white;
+ -fx-opacity: 1;
+}
+
+.label-header {
+ -fx-font-size: 32pt;
+ -fx-font-family: "Segoe UI Light";
+ -fx-text-fill: #d3d3d3;
+ -fx-opacity: 1;
+}
+
+.text-field {
+ -fx-font-size: 12pt;
+ -fx-font-family: "Segoe UI Semibold";
+}
+
+.tab-pane {
+ -fx-padding: 0 0 0 1;
+}
+
+.tab-pane .tab-header-area {
+ -fx-padding: 0 0 0 0;
+ -fx-min-height: 0;
+ -fx-max-height: 0;
+}
+
+.table-view {
+ -fx-base: #1d1d1d;
+ -fx-control-inner-background: #1d1d1d;
+ -fx-background-color: #1d1d1d;
+ -fx-table-cell-border-color: transparent;
+ -fx-table-header-border-color: transparent;
+ -fx-padding: 5;
+}
+
+.table-view .column-header-background {
+ -fx-background-color: transparent;
+}
+
+.table-view .column-header, .table-view .filler {
+ -fx-size: 35;
+ -fx-border-width: 0 0 1 0;
+ -fx-background-color: transparent;
+ -fx-border-color:
+ transparent
+ transparent
+ derive(-fx-base, 80%)
+ transparent;
+ -fx-border-insets: 0 10 1 0;
+}
+
+.table-view .column-header .label {
+ -fx-font-size: 20pt;
+ -fx-font-family: "Segoe UI Light";
+ -fx-text-fill: white;
+ -fx-alignment: center-left;
+ -fx-opacity: 1;
+}
+
+.table-view:focused .table-row-cell:filled:focused:selected {
+ -fx-background-color: -fx-focus-color;
+}
+
+.split-pane:horizontal .split-pane-divider {
+ -fx-background-color: derive(#1d1d1d, 20%);
+ -fx-border-color: transparent transparent transparent #4d4d4d;
+}
+
+.split-pane {
+ -fx-border-radius: 1;
+ -fx-border-width: 1;
+ -fx-background-color: derive(#1d1d1d, 20%);
+}
+
+.list-view {
+ -fx-background-insets: 0;
+ -fx-padding: 0;
+ -fx-background-color: derive(white, 20%);
+}
+
+.list-cell {
+ -fx-label-padding: 0 0 0 0;
+ -fx-graphic-text-gap : 0;
+ -fx-padding: 0 0 0 0;
+}
+
+.list-cell:filled:even {
+ -fx-background-color: whitesmoke;
+}
+
+.list-cell:filled:odd {
+ -fx-background-color: white;
+}
+
+.list-cell:filled:selected {
+ -fx-background-color: #eaeaea;
+}
+
+.list-cell:filled:selected #cardPane {
+ -fx-border-color: #eaeaea;
+ -fx-border-width: 1;
+}
+
+.list-cell .label {
+ -fx-text-fill: #333333;
+}
+
+.cell_big_label {
+ -fx-font-family: "Segoe UI Semibold";
+ -fx-font-size: 16px;
+ -fx-text-fill: #333333;
+}
+
+.cell_small_label {
+ -fx-font-family: "Segoe UI";
+ -fx-font-size: 13px;
+ -fx-text-fill: #333333;
+}
+
+.stack-pane {
+ -fx-background-color: derive(whitesmoke, 20%);
+}
+
+.pane-with-border {
+ -fx-background-color: derive(white, 20%);
+ -fx-border-color: derive(whitesmoke, 10%);
+ -fx-border-top-width: 1px;
+}
+
+.status-bar {
+ -fx-background-color: derive(whitesmoke, 30%);
+}
+
+.result-display {
+ -fx-background-color: transparent;
+ -fx-font-family: "Segoe UI Light";
+ -fx-font-size: 13pt;
+ -fx-text-fill: #333333;
+}
+
+.result-display .label {
+ -fx-text-fill: #1d1d1d !important;
+}
+
+.status-bar .label {
+ -fx-font-family: "Segoe UI Light";
+ -fx-text-fill: #333333;
+ -fx-padding: 4px;
+ -fx-pref-height: 30px;
+}
+
+.status-bar-with-border {
+ -fx-background-color: derive(#1d1d1d, 30%);
+ -fx-border-color: derive(#1d1d1d, 25%);
+ -fx-border-width: 1px;
+}
+
+.status-bar-with-border .label {
+ -fx-text-fill: white;
+}
+
+.grid-pane {
+ -fx-background-color: derive(#1d1d1d, 30%);
+ -fx-border-color: derive(#1d1d1d, 30%);
+ -fx-border-width: 1px;
+}
+
+.grid-pane .stack-pane {
+ -fx-background-color: derive(#1d1d1d, 30%);
+}
+
+.context-menu {
+ -fx-background-color: derive(white, 50%);
+}
+
+.context-menu .label {
+ -fx-text-fill: #333333;
+}
+
+.menu-bar {
+ -fx-background-color: derive(white, 20%);
+}
+
+.menu-bar .label {
+ -fx-font-size: 14pt;
+ -fx-font-family: "Segoe UI Light";
+ -fx-text-fill: #333333;
+ -fx-opacity: 0.9;
+}
+
+.menu .left-container {
+ -fx-background-color: #333333;
+}
+
+/*
+ * Metro style Push Button
+ * Author: Pedro Duque Vieira
+ * http://pixelduke.wordpress.com/2012/10/23/jmetro-windows-8-controls-on-java/
+ */
+.button {
+ -fx-padding: 5 22 5 22;
+ -fx-border-color: #e2e2e2;
+ -fx-border-width: 2;
+ -fx-background-radius: 0;
+ -fx-background-color: #d3d3d3;
+ -fx-font-family: "Segoe UI", Helvetica, Arial, sans-serif;
+ -fx-font-size: 11pt;
+ -fx-text-fill: #d8d8d8;
+ -fx-background-insets: 0 0 0 0, 0, 1, 2;
+}
+
+.button:hover {
+ -fx-background-color: #d3d3d3;
+}
+
+.button:pressed, .button:default:hover:pressed {
+ -fx-background-color: white;
+ -fx-text-fill: #d3d3d3;
+}
+
+.button:focused {
+ -fx-border-color: white, white;
+ -fx-border-width: 1, 1;
+ -fx-border-style: solid, segments(1, 1);
+ -fx-border-radius: 0, 0;
+ -fx-border-insets: 1 1 1 1, 0;
+}
+
+.button:disabled, .button:default:disabled {
+ -fx-opacity: 0.4;
+ -fx-background-color: #1d1d1d;
+ -fx-text-fill: white;
+}
+
+.button:default {
+ -fx-background-color: -fx-focus-color;
+ -fx-text-fill: #ffffff;
+}
+
+.button:default:hover {
+ -fx-background-color: derive(-fx-focus-color, 30%);
+}
+
+.dialog-pane {
+ -fx-background-color: #1d1d1d;
+}
+
+.dialog-pane > *.button-bar > *.container {
+ -fx-background-color: #1d1d1d;
+}
+
+.dialog-pane > *.label.content {
+ -fx-font-size: 14px;
+ -fx-font-weight: bold;
+ -fx-text-fill: white;
+}
+
+.dialog-pane:header *.header-panel {
+ -fx-background-color: derive(#1d1d1d, 25%);
+}
+
+.dialog-pane:header *.header-panel *.label {
+ -fx-font-size: 18px;
+ -fx-font-style: italic;
+ -fx-fill: white;
+ -fx-text-fill: white;
+}
+
+.scroll-bar {
+ -fx-background-color: derive(white, 20%);
+}
+
+.scroll-bar .thumb {
+ -fx-background-color: derive(#d3d3d3, 50%);
+ -fx-background-insets: 3;
+}
+
+.scroll-bar .increment-button, .scroll-bar .decrement-button {
+ -fx-background-color: transparent;
+ -fx-padding: 0 0 0 0;
+}
+
+.scroll-bar .increment-arrow, .scroll-bar .decrement-arrow {
+ -fx-shape: " ";
+}
+
+.scroll-bar:vertical .increment-arrow, .scroll-bar:vertical .decrement-arrow {
+ -fx-padding: 1 8 1 8;
+}
+
+.scroll-bar:horizontal .increment-arrow, .scroll-bar:horizontal .decrement-arrow {
+ -fx-padding: 8 1 8 1;
+}
+
+#cardPane {
+ -fx-background-color: transparent;
+ -fx-border-width: 0;
+}
+
+#commandTypeLabel {
+ -fx-font-size: 11px;
+ -fx-text-fill: #F70D1A;
+}
+
+#commandTextField {
+ -fx-background-color: white;
+ -fx-background-insets: 0;
+ -fx-border-color: white white white white;
+ -fx-border-insets: 0;
+ -fx-border-width: 1;
+ -fx-font-family: "Segoe UI Light";
+ -fx-font-size: 13pt;
+ -fx-text-fill: #333333;
+}
+
+#filterField, #personListPanel, #personWebpage {
+ -fx-effect: innershadow(gaussian, black, 10, 0, 0, 0);
+}
+
+#resultDisplay .content {
+ -fx-background-color: white;
+ -fx-background-radius: 0;
+}
+
+#tags {
+ -fx-hgap: 7;
+ -fx-vgap: 3;
+}
+
+#tags .label {
+ -fx-text-fill: white;
+ -fx-background-color: #3e7b91;
+ -fx-padding: 1 3 1 3;
+ -fx-border-radius: 2;
+ -fx-background-radius: 2;
+ -fx-font-size: 11;
+}
diff --git a/bin/main/view/MainWindow.fxml b/bin/main/view/MainWindow.fxml
new file mode 100644
index 00000000000..0e0e5acc8b9
--- /dev/null
+++ b/bin/main/view/MainWindow.fxml
@@ -0,0 +1,65 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bin/main/view/MessageWindow.css b/bin/main/view/MessageWindow.css
new file mode 100644
index 00000000000..17e8a8722cd
--- /dev/null
+++ b/bin/main/view/MessageWindow.css
@@ -0,0 +1,19 @@
+#copyButton, #helpMessage {
+ -fx-text-fill: white;
+}
+
+#copyButton {
+ -fx-background-color: dimgray;
+}
+
+#copyButton:hover {
+ -fx-background-color: gray;
+}
+
+#copyButton:armed {
+ -fx-background-color: darkgray;
+}
+
+#helpMessageContainer {
+ -fx-background-color: derive(#1d1d1d, 20%);
+}
diff --git a/bin/main/view/MessageWindow.fxml b/bin/main/view/MessageWindow.fxml
new file mode 100644
index 00000000000..6fc53280ace
--- /dev/null
+++ b/bin/main/view/MessageWindow.fxml
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bin/main/view/PackageListCard.fxml b/bin/main/view/PackageListCard.fxml
new file mode 100644
index 00000000000..a626b6bf1eb
--- /dev/null
+++ b/bin/main/view/PackageListCard.fxml
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bin/main/view/PackageListPanel.fxml b/bin/main/view/PackageListPanel.fxml
new file mode 100644
index 00000000000..bf333ff5e31
--- /dev/null
+++ b/bin/main/view/PackageListPanel.fxml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/bin/main/view/PackageWindow.fxml b/bin/main/view/PackageWindow.fxml
new file mode 100644
index 00000000000..0291c588b66
--- /dev/null
+++ b/bin/main/view/PackageWindow.fxml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bin/main/view/PersonListCard.fxml b/bin/main/view/PersonListCard.fxml
new file mode 100644
index 00000000000..a70f7f1709c
--- /dev/null
+++ b/bin/main/view/PersonListCard.fxml
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bin/main/view/PersonListPanel.fxml b/bin/main/view/PersonListPanel.fxml
new file mode 100644
index 00000000000..8836d323cc5
--- /dev/null
+++ b/bin/main/view/PersonListPanel.fxml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/bin/main/view/ResultDisplay.fxml b/bin/main/view/ResultDisplay.fxml
new file mode 100644
index 00000000000..58d5ad3dc56
--- /dev/null
+++ b/bin/main/view/ResultDisplay.fxml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
diff --git a/bin/main/view/StatusBarFooter.fxml b/bin/main/view/StatusBarFooter.fxml
new file mode 100644
index 00000000000..149f62bd29c
--- /dev/null
+++ b/bin/main/view/StatusBarFooter.fxml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bin/test/seedu/address/AppParametersTest$ParametersStub.class b/bin/test/seedu/address/AppParametersTest$ParametersStub.class
new file mode 100644
index 00000000000..a9609e5024d
Binary files /dev/null and b/bin/test/seedu/address/AppParametersTest$ParametersStub.class differ
diff --git a/bin/test/seedu/address/AppParametersTest.class b/bin/test/seedu/address/AppParametersTest.class
new file mode 100644
index 00000000000..96d817569bc
Binary files /dev/null and b/bin/test/seedu/address/AppParametersTest.class differ
diff --git a/bin/test/seedu/address/commons/core/ConfigTest.class b/bin/test/seedu/address/commons/core/ConfigTest.class
new file mode 100644
index 00000000000..a06a79cfa69
Binary files /dev/null and b/bin/test/seedu/address/commons/core/ConfigTest.class differ
diff --git a/bin/test/seedu/address/commons/core/VersionTest.class b/bin/test/seedu/address/commons/core/VersionTest.class
new file mode 100644
index 00000000000..b7401f47c8f
Binary files /dev/null and b/bin/test/seedu/address/commons/core/VersionTest.class differ
diff --git a/bin/test/seedu/address/commons/core/index/IndexTest.class b/bin/test/seedu/address/commons/core/index/IndexTest.class
new file mode 100644
index 00000000000..75ab7126206
Binary files /dev/null and b/bin/test/seedu/address/commons/core/index/IndexTest.class differ
diff --git a/bin/test/seedu/address/commons/util/AppUtilTest.class b/bin/test/seedu/address/commons/util/AppUtilTest.class
new file mode 100644
index 00000000000..4c30db01656
Binary files /dev/null and b/bin/test/seedu/address/commons/util/AppUtilTest.class differ
diff --git a/bin/test/seedu/address/commons/util/CollectionUtilTest.class b/bin/test/seedu/address/commons/util/CollectionUtilTest.class
new file mode 100644
index 00000000000..1ad31c77cf8
Binary files /dev/null and b/bin/test/seedu/address/commons/util/CollectionUtilTest.class differ
diff --git a/bin/test/seedu/address/commons/util/ConfigUtilTest.class b/bin/test/seedu/address/commons/util/ConfigUtilTest.class
new file mode 100644
index 00000000000..d514e029b26
Binary files /dev/null and b/bin/test/seedu/address/commons/util/ConfigUtilTest.class differ
diff --git a/bin/test/seedu/address/commons/util/CsvUtilTest.class b/bin/test/seedu/address/commons/util/CsvUtilTest.class
new file mode 100644
index 00000000000..58d750d193a
Binary files /dev/null and b/bin/test/seedu/address/commons/util/CsvUtilTest.class differ
diff --git a/bin/test/seedu/address/commons/util/FileUtilTest.class b/bin/test/seedu/address/commons/util/FileUtilTest.class
new file mode 100644
index 00000000000..9b907a04983
Binary files /dev/null and b/bin/test/seedu/address/commons/util/FileUtilTest.class differ
diff --git a/bin/test/seedu/address/commons/util/JsonUtilTest.class b/bin/test/seedu/address/commons/util/JsonUtilTest.class
new file mode 100644
index 00000000000..35c622f570b
Binary files /dev/null and b/bin/test/seedu/address/commons/util/JsonUtilTest.class differ
diff --git a/bin/test/seedu/address/commons/util/StringUtilTest.class b/bin/test/seedu/address/commons/util/StringUtilTest.class
new file mode 100644
index 00000000000..e70f07a6a6a
Binary files /dev/null and b/bin/test/seedu/address/commons/util/StringUtilTest.class differ
diff --git a/bin/test/seedu/address/logic/LogicManagerTest$JsonAddressBookIoExceptionThrowingStub.class b/bin/test/seedu/address/logic/LogicManagerTest$JsonAddressBookIoExceptionThrowingStub.class
new file mode 100644
index 00000000000..12ead02154a
Binary files /dev/null and b/bin/test/seedu/address/logic/LogicManagerTest$JsonAddressBookIoExceptionThrowingStub.class differ
diff --git a/bin/test/seedu/address/logic/LogicManagerTest.class b/bin/test/seedu/address/logic/LogicManagerTest.class
new file mode 100644
index 00000000000..e3fef2b2fe0
Binary files /dev/null and b/bin/test/seedu/address/logic/LogicManagerTest.class differ
diff --git a/bin/test/seedu/address/logic/commands/AddCommandIntegrationTest.class b/bin/test/seedu/address/logic/commands/AddCommandIntegrationTest.class
new file mode 100644
index 00000000000..4c8d7bd2f98
Binary files /dev/null and b/bin/test/seedu/address/logic/commands/AddCommandIntegrationTest.class differ
diff --git a/bin/test/seedu/address/logic/commands/AddCommandTest$ModelStub.class b/bin/test/seedu/address/logic/commands/AddCommandTest$ModelStub.class
new file mode 100644
index 00000000000..d951c77000c
Binary files /dev/null and b/bin/test/seedu/address/logic/commands/AddCommandTest$ModelStub.class differ
diff --git a/bin/test/seedu/address/logic/commands/AddCommandTest$ModelStubAcceptingPersonAdded.class b/bin/test/seedu/address/logic/commands/AddCommandTest$ModelStubAcceptingPersonAdded.class
new file mode 100644
index 00000000000..c8276c8725f
Binary files /dev/null and b/bin/test/seedu/address/logic/commands/AddCommandTest$ModelStubAcceptingPersonAdded.class differ
diff --git a/bin/test/seedu/address/logic/commands/AddCommandTest$ModelStubWithPerson.class b/bin/test/seedu/address/logic/commands/AddCommandTest$ModelStubWithPerson.class
new file mode 100644
index 00000000000..460e448addb
Binary files /dev/null and b/bin/test/seedu/address/logic/commands/AddCommandTest$ModelStubWithPerson.class differ
diff --git a/bin/test/seedu/address/logic/commands/AddCommandTest.class b/bin/test/seedu/address/logic/commands/AddCommandTest.class
new file mode 100644
index 00000000000..2d6c2a9ec08
Binary files /dev/null and b/bin/test/seedu/address/logic/commands/AddCommandTest.class differ
diff --git a/bin/test/seedu/address/logic/commands/AddPackageCommandTest$ModelStub.class b/bin/test/seedu/address/logic/commands/AddPackageCommandTest$ModelStub.class
new file mode 100644
index 00000000000..08c925af907
Binary files /dev/null and b/bin/test/seedu/address/logic/commands/AddPackageCommandTest$ModelStub.class differ
diff --git a/bin/test/seedu/address/logic/commands/AddPackageCommandTest$ModelStubAcceptingPackageAdded.class b/bin/test/seedu/address/logic/commands/AddPackageCommandTest$ModelStubAcceptingPackageAdded.class
new file mode 100644
index 00000000000..7b0053e4f49
Binary files /dev/null and b/bin/test/seedu/address/logic/commands/AddPackageCommandTest$ModelStubAcceptingPackageAdded.class differ
diff --git a/bin/test/seedu/address/logic/commands/AddPackageCommandTest$ModelStubWithPackage.class b/bin/test/seedu/address/logic/commands/AddPackageCommandTest$ModelStubWithPackage.class
new file mode 100644
index 00000000000..ef2173c3844
Binary files /dev/null and b/bin/test/seedu/address/logic/commands/AddPackageCommandTest$ModelStubWithPackage.class differ
diff --git a/bin/test/seedu/address/logic/commands/AddPackageCommandTest.class b/bin/test/seedu/address/logic/commands/AddPackageCommandTest.class
new file mode 100644
index 00000000000..86887affd57
Binary files /dev/null and b/bin/test/seedu/address/logic/commands/AddPackageCommandTest.class differ
diff --git a/bin/test/seedu/address/logic/commands/AddTagCommandTest.class b/bin/test/seedu/address/logic/commands/AddTagCommandTest.class
new file mode 100644
index 00000000000..a98f8b516f8
Binary files /dev/null and b/bin/test/seedu/address/logic/commands/AddTagCommandTest.class differ
diff --git a/bin/test/seedu/address/logic/commands/AddToClipboardCommandTest.class b/bin/test/seedu/address/logic/commands/AddToClipboardCommandTest.class
new file mode 100644
index 00000000000..28fce7edd40
Binary files /dev/null and b/bin/test/seedu/address/logic/commands/AddToClipboardCommandTest.class differ
diff --git a/bin/test/seedu/address/logic/commands/ClearCommandTest.class b/bin/test/seedu/address/logic/commands/ClearCommandTest.class
new file mode 100644
index 00000000000..6a704c8dedb
Binary files /dev/null and b/bin/test/seedu/address/logic/commands/ClearCommandTest.class differ
diff --git a/bin/test/seedu/address/logic/commands/CommandResultTest.class b/bin/test/seedu/address/logic/commands/CommandResultTest.class
new file mode 100644
index 00000000000..aaacf31cbe4
Binary files /dev/null and b/bin/test/seedu/address/logic/commands/CommandResultTest.class differ
diff --git a/bin/test/seedu/address/logic/commands/CommandTestUtil.class b/bin/test/seedu/address/logic/commands/CommandTestUtil.class
new file mode 100644
index 00000000000..8071a26604c
Binary files /dev/null and b/bin/test/seedu/address/logic/commands/CommandTestUtil.class differ
diff --git a/bin/test/seedu/address/logic/commands/DeleteCommandTest.class b/bin/test/seedu/address/logic/commands/DeleteCommandTest.class
new file mode 100644
index 00000000000..e52a9ffdc3a
Binary files /dev/null and b/bin/test/seedu/address/logic/commands/DeleteCommandTest.class differ
diff --git a/bin/test/seedu/address/logic/commands/DeleteTagCommandTest.class b/bin/test/seedu/address/logic/commands/DeleteTagCommandTest.class
new file mode 100644
index 00000000000..3137345c33c
Binary files /dev/null and b/bin/test/seedu/address/logic/commands/DeleteTagCommandTest.class differ
diff --git a/bin/test/seedu/address/logic/commands/EditCommandTest.class b/bin/test/seedu/address/logic/commands/EditCommandTest.class
new file mode 100644
index 00000000000..e425246f801
Binary files /dev/null and b/bin/test/seedu/address/logic/commands/EditCommandTest.class differ
diff --git a/bin/test/seedu/address/logic/commands/EditPackageCommandTest$ModelStub.class b/bin/test/seedu/address/logic/commands/EditPackageCommandTest$ModelStub.class
new file mode 100644
index 00000000000..b445eec98be
Binary files /dev/null and b/bin/test/seedu/address/logic/commands/EditPackageCommandTest$ModelStub.class differ
diff --git a/bin/test/seedu/address/logic/commands/EditPackageCommandTest$ModelStubWithPackage.class b/bin/test/seedu/address/logic/commands/EditPackageCommandTest$ModelStubWithPackage.class
new file mode 100644
index 00000000000..d4984ef3cd5
Binary files /dev/null and b/bin/test/seedu/address/logic/commands/EditPackageCommandTest$ModelStubWithPackage.class differ
diff --git a/bin/test/seedu/address/logic/commands/EditPackageCommandTest.class b/bin/test/seedu/address/logic/commands/EditPackageCommandTest.class
new file mode 100644
index 00000000000..844edf46ab0
Binary files /dev/null and b/bin/test/seedu/address/logic/commands/EditPackageCommandTest.class differ
diff --git a/bin/test/seedu/address/logic/commands/EditPersonDescriptorTest.class b/bin/test/seedu/address/logic/commands/EditPersonDescriptorTest.class
new file mode 100644
index 00000000000..cd9e81c7389
Binary files /dev/null and b/bin/test/seedu/address/logic/commands/EditPersonDescriptorTest.class differ
diff --git a/bin/test/seedu/address/logic/commands/EditTagCommandTest.class b/bin/test/seedu/address/logic/commands/EditTagCommandTest.class
new file mode 100644
index 00000000000..8f0cf974046
Binary files /dev/null and b/bin/test/seedu/address/logic/commands/EditTagCommandTest.class differ
diff --git a/bin/test/seedu/address/logic/commands/ExitCommandTest.class b/bin/test/seedu/address/logic/commands/ExitCommandTest.class
new file mode 100644
index 00000000000..f8200051636
Binary files /dev/null and b/bin/test/seedu/address/logic/commands/ExitCommandTest.class differ
diff --git a/bin/test/seedu/address/logic/commands/ExportToCsvCommandTest.class b/bin/test/seedu/address/logic/commands/ExportToCsvCommandTest.class
new file mode 100644
index 00000000000..7ddbd9118c1
Binary files /dev/null and b/bin/test/seedu/address/logic/commands/ExportToCsvCommandTest.class differ
diff --git a/bin/test/seedu/address/logic/commands/FindCommandTest.class b/bin/test/seedu/address/logic/commands/FindCommandTest.class
new file mode 100644
index 00000000000..69a80293e47
Binary files /dev/null and b/bin/test/seedu/address/logic/commands/FindCommandTest.class differ
diff --git a/bin/test/seedu/address/logic/commands/HelpCommandTest.class b/bin/test/seedu/address/logic/commands/HelpCommandTest.class
new file mode 100644
index 00000000000..a5ad5d5563f
Binary files /dev/null and b/bin/test/seedu/address/logic/commands/HelpCommandTest.class differ
diff --git a/bin/test/seedu/address/logic/commands/ImportFromCsvCommandTest.class b/bin/test/seedu/address/logic/commands/ImportFromCsvCommandTest.class
new file mode 100644
index 00000000000..28726c9ba6b
Binary files /dev/null and b/bin/test/seedu/address/logic/commands/ImportFromCsvCommandTest.class differ
diff --git a/bin/test/seedu/address/logic/commands/ListCommandTest.class b/bin/test/seedu/address/logic/commands/ListCommandTest.class
new file mode 100644
index 00000000000..942dcb762e3
Binary files /dev/null and b/bin/test/seedu/address/logic/commands/ListCommandTest.class differ
diff --git a/bin/test/seedu/address/logic/commands/PriorityListCommandTest.class b/bin/test/seedu/address/logic/commands/PriorityListCommandTest.class
new file mode 100644
index 00000000000..39cd140a91d
Binary files /dev/null and b/bin/test/seedu/address/logic/commands/PriorityListCommandTest.class differ
diff --git a/bin/test/seedu/address/logic/commands/RedoCommandTest$ModelStub.class b/bin/test/seedu/address/logic/commands/RedoCommandTest$ModelStub.class
new file mode 100644
index 00000000000..a88a1dc9a74
Binary files /dev/null and b/bin/test/seedu/address/logic/commands/RedoCommandTest$ModelStub.class differ
diff --git a/bin/test/seedu/address/logic/commands/RedoCommandTest$ModelStubAcceptingPersonAdded.class b/bin/test/seedu/address/logic/commands/RedoCommandTest$ModelStubAcceptingPersonAdded.class
new file mode 100644
index 00000000000..48201b65320
Binary files /dev/null and b/bin/test/seedu/address/logic/commands/RedoCommandTest$ModelStubAcceptingPersonAdded.class differ
diff --git a/bin/test/seedu/address/logic/commands/RedoCommandTest.class b/bin/test/seedu/address/logic/commands/RedoCommandTest.class
new file mode 100644
index 00000000000..00df20e9793
Binary files /dev/null and b/bin/test/seedu/address/logic/commands/RedoCommandTest.class differ
diff --git a/bin/test/seedu/address/logic/commands/UndoCommandTest$ModelStub.class b/bin/test/seedu/address/logic/commands/UndoCommandTest$ModelStub.class
new file mode 100644
index 00000000000..27412d0daa3
Binary files /dev/null and b/bin/test/seedu/address/logic/commands/UndoCommandTest$ModelStub.class differ
diff --git a/bin/test/seedu/address/logic/commands/UndoCommandTest$ModelStubAcceptingPersonAdded.class b/bin/test/seedu/address/logic/commands/UndoCommandTest$ModelStubAcceptingPersonAdded.class
new file mode 100644
index 00000000000..ed903ee4744
Binary files /dev/null and b/bin/test/seedu/address/logic/commands/UndoCommandTest$ModelStubAcceptingPersonAdded.class differ
diff --git a/bin/test/seedu/address/logic/commands/UndoCommandTest.class b/bin/test/seedu/address/logic/commands/UndoCommandTest.class
new file mode 100644
index 00000000000..81aa4311623
Binary files /dev/null and b/bin/test/seedu/address/logic/commands/UndoCommandTest.class differ
diff --git a/bin/test/seedu/address/logic/parser/AddCommandParserTest.class b/bin/test/seedu/address/logic/parser/AddCommandParserTest.class
new file mode 100644
index 00000000000..4d92e1d9ee2
Binary files /dev/null and b/bin/test/seedu/address/logic/parser/AddCommandParserTest.class differ
diff --git a/bin/test/seedu/address/logic/parser/AddTagCommandParserTest.class b/bin/test/seedu/address/logic/parser/AddTagCommandParserTest.class
new file mode 100644
index 00000000000..ddc136c089b
Binary files /dev/null and b/bin/test/seedu/address/logic/parser/AddTagCommandParserTest.class differ
diff --git a/bin/test/seedu/address/logic/parser/AddToClipboardCommandParserTest.class b/bin/test/seedu/address/logic/parser/AddToClipboardCommandParserTest.class
new file mode 100644
index 00000000000..73dab006526
Binary files /dev/null and b/bin/test/seedu/address/logic/parser/AddToClipboardCommandParserTest.class differ
diff --git a/bin/test/seedu/address/logic/parser/AddressBookParserTest.class b/bin/test/seedu/address/logic/parser/AddressBookParserTest.class
new file mode 100644
index 00000000000..e368ea5be44
Binary files /dev/null and b/bin/test/seedu/address/logic/parser/AddressBookParserTest.class differ
diff --git a/bin/test/seedu/address/logic/parser/ArgumentTokenizerTest.class b/bin/test/seedu/address/logic/parser/ArgumentTokenizerTest.class
new file mode 100644
index 00000000000..ca7f286e117
Binary files /dev/null and b/bin/test/seedu/address/logic/parser/ArgumentTokenizerTest.class differ
diff --git a/bin/test/seedu/address/logic/parser/CommandParserTestUtil.class b/bin/test/seedu/address/logic/parser/CommandParserTestUtil.class
new file mode 100644
index 00000000000..aaa0fa5b0bf
Binary files /dev/null and b/bin/test/seedu/address/logic/parser/CommandParserTestUtil.class differ
diff --git a/bin/test/seedu/address/logic/parser/DeleteCommandParserTest.class b/bin/test/seedu/address/logic/parser/DeleteCommandParserTest.class
new file mode 100644
index 00000000000..4a0b0d0ee28
Binary files /dev/null and b/bin/test/seedu/address/logic/parser/DeleteCommandParserTest.class differ
diff --git a/bin/test/seedu/address/logic/parser/DeleteTagCommandParserTest.class b/bin/test/seedu/address/logic/parser/DeleteTagCommandParserTest.class
new file mode 100644
index 00000000000..cd084181c7d
Binary files /dev/null and b/bin/test/seedu/address/logic/parser/DeleteTagCommandParserTest.class differ
diff --git a/bin/test/seedu/address/logic/parser/EditCommandParserTest.class b/bin/test/seedu/address/logic/parser/EditCommandParserTest.class
new file mode 100644
index 00000000000..83df49afa49
Binary files /dev/null and b/bin/test/seedu/address/logic/parser/EditCommandParserTest.class differ
diff --git a/bin/test/seedu/address/logic/parser/EditTagCommandParserTest.class b/bin/test/seedu/address/logic/parser/EditTagCommandParserTest.class
new file mode 100644
index 00000000000..13dd83c0915
Binary files /dev/null and b/bin/test/seedu/address/logic/parser/EditTagCommandParserTest.class differ
diff --git a/bin/test/seedu/address/logic/parser/FindCommandParserTest.class b/bin/test/seedu/address/logic/parser/FindCommandParserTest.class
new file mode 100644
index 00000000000..0b0cac7beba
Binary files /dev/null and b/bin/test/seedu/address/logic/parser/FindCommandParserTest.class differ
diff --git a/bin/test/seedu/address/logic/parser/ParserUtilTest.class b/bin/test/seedu/address/logic/parser/ParserUtilTest.class
new file mode 100644
index 00000000000..f1936f61c4e
Binary files /dev/null and b/bin/test/seedu/address/logic/parser/ParserUtilTest.class differ
diff --git a/bin/test/seedu/address/model/AddressBookTest$AddressBookStub.class b/bin/test/seedu/address/model/AddressBookTest$AddressBookStub.class
new file mode 100644
index 00000000000..5dbb8bc7ea3
Binary files /dev/null and b/bin/test/seedu/address/model/AddressBookTest$AddressBookStub.class differ
diff --git a/bin/test/seedu/address/model/AddressBookTest.class b/bin/test/seedu/address/model/AddressBookTest.class
new file mode 100644
index 00000000000..8a9f8a7979b
Binary files /dev/null and b/bin/test/seedu/address/model/AddressBookTest.class differ
diff --git a/bin/test/seedu/address/model/InsurancePackagesSetTest.class b/bin/test/seedu/address/model/InsurancePackagesSetTest.class
new file mode 100644
index 00000000000..e7cb4ace914
Binary files /dev/null and b/bin/test/seedu/address/model/InsurancePackagesSetTest.class differ
diff --git a/bin/test/seedu/address/model/ModelManagerTest.class b/bin/test/seedu/address/model/ModelManagerTest.class
new file mode 100644
index 00000000000..62db4a35a5b
Binary files /dev/null and b/bin/test/seedu/address/model/ModelManagerTest.class differ
diff --git a/bin/test/seedu/address/model/UserPrefsTest.class b/bin/test/seedu/address/model/UserPrefsTest.class
new file mode 100644
index 00000000000..1d5244d6ce1
Binary files /dev/null and b/bin/test/seedu/address/model/UserPrefsTest.class differ
diff --git a/bin/test/seedu/address/model/person/AddressTest.class b/bin/test/seedu/address/model/person/AddressTest.class
new file mode 100644
index 00000000000..c36daabd027
Binary files /dev/null and b/bin/test/seedu/address/model/person/AddressTest.class differ
diff --git a/bin/test/seedu/address/model/person/EmailTest.class b/bin/test/seedu/address/model/person/EmailTest.class
new file mode 100644
index 00000000000..9250e898e00
Binary files /dev/null and b/bin/test/seedu/address/model/person/EmailTest.class differ
diff --git a/bin/test/seedu/address/model/person/InsurancePackageTest.class b/bin/test/seedu/address/model/person/InsurancePackageTest.class
new file mode 100644
index 00000000000..e97ce1c5fb3
Binary files /dev/null and b/bin/test/seedu/address/model/person/InsurancePackageTest.class differ
diff --git a/bin/test/seedu/address/model/person/NameTest.class b/bin/test/seedu/address/model/person/NameTest.class
new file mode 100644
index 00000000000..b0f7c45b2e2
Binary files /dev/null and b/bin/test/seedu/address/model/person/NameTest.class differ
diff --git a/bin/test/seedu/address/model/person/PersonTest.class b/bin/test/seedu/address/model/person/PersonTest.class
new file mode 100644
index 00000000000..de82ed751ee
Binary files /dev/null and b/bin/test/seedu/address/model/person/PersonTest.class differ
diff --git a/bin/test/seedu/address/model/person/PhoneTest.class b/bin/test/seedu/address/model/person/PhoneTest.class
new file mode 100644
index 00000000000..ca4dda4b493
Binary files /dev/null and b/bin/test/seedu/address/model/person/PhoneTest.class differ
diff --git a/bin/test/seedu/address/model/person/UniquePersonListTest.class b/bin/test/seedu/address/model/person/UniquePersonListTest.class
new file mode 100644
index 00000000000..a145c1932ed
Binary files /dev/null and b/bin/test/seedu/address/model/person/UniquePersonListTest.class differ
diff --git a/bin/test/seedu/address/model/person/comparators/TagPriorityComparatorTest.class b/bin/test/seedu/address/model/person/comparators/TagPriorityComparatorTest.class
new file mode 100644
index 00000000000..8391d0a4446
Binary files /dev/null and b/bin/test/seedu/address/model/person/comparators/TagPriorityComparatorTest.class differ
diff --git a/bin/test/seedu/address/model/person/predicates/AddressContainsKeywordsPredicateTest.class b/bin/test/seedu/address/model/person/predicates/AddressContainsKeywordsPredicateTest.class
new file mode 100644
index 00000000000..c405b4c55a5
Binary files /dev/null and b/bin/test/seedu/address/model/person/predicates/AddressContainsKeywordsPredicateTest.class differ
diff --git a/bin/test/seedu/address/model/person/predicates/CombineContainsKeywordsPredicateTest.class b/bin/test/seedu/address/model/person/predicates/CombineContainsKeywordsPredicateTest.class
new file mode 100644
index 00000000000..61a5fba256f
Binary files /dev/null and b/bin/test/seedu/address/model/person/predicates/CombineContainsKeywordsPredicateTest.class differ
diff --git a/bin/test/seedu/address/model/person/predicates/EmailContainsKeywordsPredicateTest.class b/bin/test/seedu/address/model/person/predicates/EmailContainsKeywordsPredicateTest.class
new file mode 100644
index 00000000000..0312310233d
Binary files /dev/null and b/bin/test/seedu/address/model/person/predicates/EmailContainsKeywordsPredicateTest.class differ
diff --git a/bin/test/seedu/address/model/person/predicates/InsurancePackageContainsKeywordsPredicateTest.class b/bin/test/seedu/address/model/person/predicates/InsurancePackageContainsKeywordsPredicateTest.class
new file mode 100644
index 00000000000..9a6cd284078
Binary files /dev/null and b/bin/test/seedu/address/model/person/predicates/InsurancePackageContainsKeywordsPredicateTest.class differ
diff --git a/bin/test/seedu/address/model/person/predicates/NameContainsKeywordsPredicateTest.class b/bin/test/seedu/address/model/person/predicates/NameContainsKeywordsPredicateTest.class
new file mode 100644
index 00000000000..56be194c52d
Binary files /dev/null and b/bin/test/seedu/address/model/person/predicates/NameContainsKeywordsPredicateTest.class differ
diff --git a/bin/test/seedu/address/model/person/predicates/NameExistsPredicateTest.class b/bin/test/seedu/address/model/person/predicates/NameExistsPredicateTest.class
new file mode 100644
index 00000000000..cf726ffd6f3
Binary files /dev/null and b/bin/test/seedu/address/model/person/predicates/NameExistsPredicateTest.class differ
diff --git a/bin/test/seedu/address/model/person/predicates/PhoneContainsKeywordsPredicateTest.class b/bin/test/seedu/address/model/person/predicates/PhoneContainsKeywordsPredicateTest.class
new file mode 100644
index 00000000000..802f569bf69
Binary files /dev/null and b/bin/test/seedu/address/model/person/predicates/PhoneContainsKeywordsPredicateTest.class differ
diff --git a/bin/test/seedu/address/model/person/predicates/TagsContainsKeywordsPredicateTest.class b/bin/test/seedu/address/model/person/predicates/TagsContainsKeywordsPredicateTest.class
new file mode 100644
index 00000000000..3c204c2eb84
Binary files /dev/null and b/bin/test/seedu/address/model/person/predicates/TagsContainsKeywordsPredicateTest.class differ
diff --git a/bin/test/seedu/address/model/tag/TagTest.class b/bin/test/seedu/address/model/tag/TagTest.class
new file mode 100644
index 00000000000..cc92f1e0397
Binary files /dev/null and b/bin/test/seedu/address/model/tag/TagTest.class differ
diff --git a/bin/test/seedu/address/storage/CommandStorageTest.class b/bin/test/seedu/address/storage/CommandStorageTest.class
new file mode 100644
index 00000000000..39b8360d781
Binary files /dev/null and b/bin/test/seedu/address/storage/CommandStorageTest.class differ
diff --git a/bin/test/seedu/address/storage/CsvAdaptedInsurancePackageTest.class b/bin/test/seedu/address/storage/CsvAdaptedInsurancePackageTest.class
new file mode 100644
index 00000000000..cb294a85505
Binary files /dev/null and b/bin/test/seedu/address/storage/CsvAdaptedInsurancePackageTest.class differ
diff --git a/bin/test/seedu/address/storage/CsvAdaptedPersonTest.class b/bin/test/seedu/address/storage/CsvAdaptedPersonTest.class
new file mode 100644
index 00000000000..054f1b88925
Binary files /dev/null and b/bin/test/seedu/address/storage/CsvAdaptedPersonTest.class differ
diff --git a/bin/test/seedu/address/storage/CsvAddressBookStorageTest.class b/bin/test/seedu/address/storage/CsvAddressBookStorageTest.class
new file mode 100644
index 00000000000..1d92f63dfab
Binary files /dev/null and b/bin/test/seedu/address/storage/CsvAddressBookStorageTest.class differ
diff --git a/bin/test/seedu/address/storage/CsvInsurancePackageStorageTest.class b/bin/test/seedu/address/storage/CsvInsurancePackageStorageTest.class
new file mode 100644
index 00000000000..f3a242ee963
Binary files /dev/null and b/bin/test/seedu/address/storage/CsvInsurancePackageStorageTest.class differ
diff --git a/bin/test/seedu/address/storage/JsonAdaptedPersonTest.class b/bin/test/seedu/address/storage/JsonAdaptedPersonTest.class
new file mode 100644
index 00000000000..40196c0e984
Binary files /dev/null and b/bin/test/seedu/address/storage/JsonAdaptedPersonTest.class differ
diff --git a/bin/test/seedu/address/storage/JsonAddressBookStorageTest.class b/bin/test/seedu/address/storage/JsonAddressBookStorageTest.class
new file mode 100644
index 00000000000..56349db3029
Binary files /dev/null and b/bin/test/seedu/address/storage/JsonAddressBookStorageTest.class differ
diff --git a/bin/test/seedu/address/storage/JsonSerializableAddressBookTest.class b/bin/test/seedu/address/storage/JsonSerializableAddressBookTest.class
new file mode 100644
index 00000000000..a89f73ade41
Binary files /dev/null and b/bin/test/seedu/address/storage/JsonSerializableAddressBookTest.class differ
diff --git a/bin/test/seedu/address/storage/JsonUserPrefsStorageTest.class b/bin/test/seedu/address/storage/JsonUserPrefsStorageTest.class
new file mode 100644
index 00000000000..f3cad6428cc
Binary files /dev/null and b/bin/test/seedu/address/storage/JsonUserPrefsStorageTest.class differ
diff --git a/bin/test/seedu/address/storage/StorageManagerTest.class b/bin/test/seedu/address/storage/StorageManagerTest.class
new file mode 100644
index 00000000000..0908956a446
Binary files /dev/null and b/bin/test/seedu/address/storage/StorageManagerTest.class differ
diff --git a/bin/test/seedu/address/testutil/AddressBookBuilder.class b/bin/test/seedu/address/testutil/AddressBookBuilder.class
new file mode 100644
index 00000000000..89a4abc2755
Binary files /dev/null and b/bin/test/seedu/address/testutil/AddressBookBuilder.class differ
diff --git a/bin/test/seedu/address/testutil/Assert.class b/bin/test/seedu/address/testutil/Assert.class
new file mode 100644
index 00000000000..9eba3381576
Binary files /dev/null and b/bin/test/seedu/address/testutil/Assert.class differ
diff --git a/bin/test/seedu/address/testutil/EditPersonDescriptorBuilder.class b/bin/test/seedu/address/testutil/EditPersonDescriptorBuilder.class
new file mode 100644
index 00000000000..e68dfeb5fba
Binary files /dev/null and b/bin/test/seedu/address/testutil/EditPersonDescriptorBuilder.class differ
diff --git a/bin/test/seedu/address/testutil/PersonBuilder.class b/bin/test/seedu/address/testutil/PersonBuilder.class
new file mode 100644
index 00000000000..905feab9ad9
Binary files /dev/null and b/bin/test/seedu/address/testutil/PersonBuilder.class differ
diff --git a/bin/test/seedu/address/testutil/PersonUtil.class b/bin/test/seedu/address/testutil/PersonUtil.class
new file mode 100644
index 00000000000..010b64f908a
Binary files /dev/null and b/bin/test/seedu/address/testutil/PersonUtil.class differ
diff --git a/bin/test/seedu/address/testutil/PredicatesListBuilder.class b/bin/test/seedu/address/testutil/PredicatesListBuilder.class
new file mode 100644
index 00000000000..058b9e403c8
Binary files /dev/null and b/bin/test/seedu/address/testutil/PredicatesListBuilder.class differ
diff --git a/bin/test/seedu/address/testutil/SerializableTestClass.class b/bin/test/seedu/address/testutil/SerializableTestClass.class
new file mode 100644
index 00000000000..221e212d5d3
Binary files /dev/null and b/bin/test/seedu/address/testutil/SerializableTestClass.class differ
diff --git a/bin/test/seedu/address/testutil/TestUtil.class b/bin/test/seedu/address/testutil/TestUtil.class
new file mode 100644
index 00000000000..148dc750bdc
Binary files /dev/null and b/bin/test/seedu/address/testutil/TestUtil.class differ
diff --git a/bin/test/seedu/address/testutil/TypicalIndexes.class b/bin/test/seedu/address/testutil/TypicalIndexes.class
new file mode 100644
index 00000000000..06cb6767b16
Binary files /dev/null and b/bin/test/seedu/address/testutil/TypicalIndexes.class differ
diff --git a/bin/test/seedu/address/testutil/TypicalInsurancePackages.class b/bin/test/seedu/address/testutil/TypicalInsurancePackages.class
new file mode 100644
index 00000000000..c956301e7bf
Binary files /dev/null and b/bin/test/seedu/address/testutil/TypicalInsurancePackages.class differ
diff --git a/bin/test/seedu/address/testutil/TypicalPersons.class b/bin/test/seedu/address/testutil/TypicalPersons.class
new file mode 100644
index 00000000000..f183696112e
Binary files /dev/null and b/bin/test/seedu/address/testutil/TypicalPersons.class differ
diff --git a/bin/test/seedu/address/ui/TestFxmlObject.class b/bin/test/seedu/address/ui/TestFxmlObject.class
new file mode 100644
index 00000000000..da30b34a08c
Binary files /dev/null and b/bin/test/seedu/address/ui/TestFxmlObject.class differ
diff --git a/bin/test/seedu/address/ui/UiPartTest$TestUiPart.class b/bin/test/seedu/address/ui/UiPartTest$TestUiPart.class
new file mode 100644
index 00000000000..49c70ef126a
Binary files /dev/null and b/bin/test/seedu/address/ui/UiPartTest$TestUiPart.class differ
diff --git a/bin/test/seedu/address/ui/UiPartTest.class b/bin/test/seedu/address/ui/UiPartTest.class
new file mode 100644
index 00000000000..bf406235665
Binary files /dev/null and b/bin/test/seedu/address/ui/UiPartTest.class differ
diff --git a/bin/test/view/UiPartTest/invalidFile.fxml b/bin/test/view/UiPartTest/invalidFile.fxml
new file mode 100644
index 00000000000..67680946732
--- /dev/null
+++ b/bin/test/view/UiPartTest/invalidFile.fxml
@@ -0,0 +1 @@
+Not a valid FXML file
diff --git a/bin/test/view/UiPartTest/validFile.fxml b/bin/test/view/UiPartTest/validFile.fxml
new file mode 100644
index 00000000000..bab836af0db
--- /dev/null
+++ b/bin/test/view/UiPartTest/validFile.fxml
@@ -0,0 +1,4 @@
+
+
+
+Hello World!
diff --git a/bin/test/view/UiPartTest/validFileWithFxRoot.fxml b/bin/test/view/UiPartTest/validFileWithFxRoot.fxml
new file mode 100644
index 00000000000..151e09ce926
--- /dev/null
+++ b/bin/test/view/UiPartTest/validFileWithFxRoot.fxml
@@ -0,0 +1,6 @@
+
+
+
+ Hello World!
+
diff --git a/build.gradle b/build.gradle
index be2d2905dde..ee489e9b8ff 100644
--- a/build.gradle
+++ b/build.gradle
@@ -40,6 +40,10 @@ task coverage(type: JacocoReport) {
}
}
+run {
+ enableAssertions = true
+}
+
dependencies {
String jUnitVersion = '5.4.0'
String javaFxVersion = '11'
@@ -66,7 +70,7 @@ dependencies {
}
shadowJar {
- archiveName = 'addressbook.jar'
+ archiveName = 'ClientConnect.jar'
}
defaultTasks 'clean', 'test'
diff --git a/docs/AboutUs.md b/docs/AboutUs.md
index 1c9514e966a..e0d80528b38 100644
--- a/docs/AboutUs.md
+++ b/docs/AboutUs.md
@@ -9,51 +9,52 @@ You can reach us at the email `seer[at]comp.nus.edu.sg`
## Project team
-### John Doe
+### Tan Jie Wei
-
+
-[[homepage](http://www.comp.nus.edu.sg/~damithch)]
-[[github](https://github.com/johndoe)]
-[[portfolio](team/johndoe.md)]
+[[github](http://github.com/jiewei98)]
+[[portfolio](team/jiewei98.md)]
-* Role: Project Advisor
+* Role: Developer
+* Responsibilities: UI and Documentation
-### Jane Doe
+### Joshua Emmanuel Teo Rui Zhong
-
+
-[[github](http://github.com/johndoe)]
-[[portfolio](team/johndoe.md)]
+[[github](http://github.com/jetrz)]
+[[portfolio](team/jetrz.md)]
-* Role: Team Lead
-* Responsibilities: UI
+* Role: Developer
+* Responsibilities: UI and Documentation
-### Johnny Doe
+### Lee Yi Heng
-
+
-[[github](http://github.com/johndoe)] [[portfolio](team/johndoe.md)]
+[[github](http://github.com/leeyiheng12)]
+[[portfolio](team/leeyiheng12.md)]
* Role: Developer
-* Responsibilities: Data
+* Responsibilities: UI and Documentation
-### Jean Doe
+### Low Chuan Wei, Michael
-
+
-[[github](http://github.com/johndoe)]
-[[portfolio](team/johndoe.md)]
+[[github](http://github.com/michaelseyo)]
+[[portfolio](team/michaelseyo.md)]
* Role: Developer
-* Responsibilities: Dev Ops + Threading
+* Responsibilities: UI and Documentation
-### James Doe
+### Samyukta Sounderraman
-
+
-[[github](http://github.com/johndoe)]
-[[portfolio](team/johndoe.md)]
+[[github](http://github.com/MontyPython28)]
+[[portfolio](team/montypython28.md)]
* Role: Developer
-* Responsibilities: UI
+* Responsibilities: UI and Documentation
diff --git a/docs/DeveloperGuide.md b/docs/DeveloperGuide.md
index 46eae8ee565..e4ba920c65d 100644
--- a/docs/DeveloperGuide.md
+++ b/docs/DeveloperGuide.md
@@ -2,6 +2,7 @@
layout: page
title: Developer Guide
---
+
* Table of Contents
{:toc}
@@ -9,7 +10,9 @@ title: Developer Guide
## **Acknowledgements**
-* {list here sources of all reused/adapted ideas, code, documentation, and third-party libraries -- include links to the original source as well}
+* AddToClipboardCommand.java, lines 92-94, reused from https://stackoverflow.com/questions/6710350/copying-text-to-the-clipboard-using-java
+
+* ArgumentMultimap.java, lines 42-46, reused from author: rgettman https://stackoverflow.com/questions/28288546/how-to-copy-hashmap-not-shallow-copy-in-java
--------------------------------------------------------------------------------------------------------------------
@@ -59,7 +62,7 @@ The *Sequence Diagram* below shows how the components interact with each other f
Each of the four main components (also shown in the diagram above),
* defines its *API* in an `interface` with the same name as the Component.
-* implements its functionality using a concrete `{Component Name}Manager` class (which follows the corresponding API `interface` mentioned in the previous point.
+* implements its functionality using a concrete `{Component Name}Manager` class (which follows the corresponding API `interface` mentioned in the previous point).
For example, the `Logic` component defines its API in the `Logic.java` interface and implements its functionality using the `LogicManager.java` class which follows the `Logic` interface. Other components interact with a given component through its interface rather than the concrete class (reason: to prevent outside component's being coupled to the implementation of a component), as illustrated in the (partial) class diagram below.
@@ -73,7 +76,7 @@ The **API** of this component is specified in [`Ui.java`](https://github.com/se-
![Structure of the UI Component](images/UiClassDiagram.png)
-The UI consists of a `MainWindow` that is made up of parts e.g.`CommandBox`, `ResultDisplay`, `PersonListPanel`, `StatusBarFooter` etc. All these, including the `MainWindow`, inherit from the abstract `UiPart` class which captures the commonalities between classes that represent parts of the visible GUI.
+The UI consists of a `MainWindow` that is made up of parts e.g.`CommandBox`, `ResultDisplay`, `PersonListPanel`, `StatusBarFooter` etc. All these, including the `MainWindow`, inherit from the abstract `UiPart` class which captures the commonalities between classes that represent parts of the visible GUI. `HelpWindow` and `PackageWindow` opens a new pop up window, displaying the link to the User Guide and the details of the `InsurancePackage` object respectively.
The `UI` component uses the JavaFx UI framework. The layout of these UI parts are defined in matching `.fxml` files that are in the `src/main/resources/view` folder. For example, the layout of the [`MainWindow`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/ui/MainWindow.java) is specified in [`MainWindow.fxml`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/resources/view/MainWindow.fxml)
@@ -83,6 +86,7 @@ The `UI` component,
* listens for changes to `Model` data so that the UI can be updated with the modified data.
* keeps a reference to the `Logic` component, because the `UI` relies on the `Logic` to execute commands.
* depends on some classes in the `Model` component, as it displays `Person` object residing in the `Model`.
+* The `PackageCard` component depends on some classes in the `Model` component as it displays an `InsurancePackage` object residing in the `Model`.
### Logic component
@@ -96,7 +100,7 @@ How the `Logic` component works:
1. When `Logic` is called upon to execute a command, it uses the `AddressBookParser` class to parse the user command.
1. This results in a `Command` object (more precisely, an object of one of its subclasses e.g., `AddCommand`) which is executed by the `LogicManager`.
1. The command can communicate with the `Model` when it is executed (e.g. to add a person).
-1. The result of the command execution is encapsulated as a `CommandResult` object which is returned back from `Logic`.
+1. The result of the command execution is encapsulated as a `CommandResult` object which is returned from `Logic`.
The Sequence Diagram below illustrates the interactions within the `Logic` component for the `execute("delete 1")` API call.
@@ -154,42 +158,164 @@ Classes used by multiple components are in the `seedu.addressbook.commons` packa
This section describes some noteworthy details on how certain features are implemented.
-### \[Proposed\] Undo/redo feature
+### Adding Clip feature
+
+The addition of the clip feature is facilitated by the `AddToClipboardCommand`, `AddToClipboardCommandParser`, and `NameExistsPredicate` classes.
+
+`AddToClipboardCommandParser` parses the input to obtain the name/index of the client that the user wishes to clip. It then creates a `NameExistsPredicate`, which it passes to a `AddToClipboardCommand`. When `Commmand#execute` is called, `AddToClipboardCommand` uses the `NameExistsPredicate` to filter out exact matches to the given name via the `Model#getFilteredPersonList()` method.
+
+![Clip Command Sequence Diagram](images/ClipCommandSeqMain.PNG)
+
+If a name is specified:
+![Clip Command Sequence Diagram](images/ClipCommandSeqSD.PNG)
+
+If an index is specified:
+![Clip Command Sequence Diagram](images/ClipCommandSeqSD2.PNG)
+
+### Adding Priority Level feature
+
+The addition of priorities is facilitated by the `Tag` and `Priority` classes. In addition to a `tagName`, the tag now also has a priority level, represented by the `Priority` enum.
+
+![PersonsAndPriorities](images/PersonsAndPriorities.png)
+
+Currently, there are 4 priority levels that a tag can have, as provided by the `Priority` enum. If unspecified, the default `Priority` is `null`. The priority of a tag is set by the `ParserUtil`'s methods- specifically using `parseTag` and `parsePriority`
+
+#### Design considerations:
+
+**Aspect: TBD:**
+
+* **Alternative 1 (current choice):** Give Priority to Tag
+ * Pros: Can go into detail. Tasks the agent has to do can be captured in the tags. For the same person, some tasks may be very urgent and some may not be as urgent. The specifics can be conveyed much better.
+ * Cons: Not very easy to implement. Hard to adapt for future features (e.g. how to sort by priority in the future)
+
+* **Alternative 2:** Give Priority to Person
+ itself.
+ * Pros: Easier to implement, and easy to extend for future features such as `sort`.
+ * Cons: We cannot give so much nuance- if someone has some issues that are urgent, and others that aren't, this cannot be captured.
+
+#### Listing by priority
+The PriorityList() command. facilitated by `PriorityListCommand` and `TagPriorityComparator`, allows users to sort and display their client list by the priority level of their contact's tags.
+A client with multiple tags will have the priority level of the highest priority out of all of his/her tags.
+
+**Aspect: How prioList executes:**
+![PrioListSeq](images/PrioListCommandSeq.PNG)
+
+### Adding more fields for Find
+Having multiple fields for the `FindCommand` is facilitated by the improvement of the `FindCommandParser` and the creation of different types of `Predicate`. The `CombineContainsKeywordsPredicate` is used in order to combine the various `Predicate` for multiple fields searching. It is the main driver for `FindCommand#execute(model)` in updating the filteredPersonsList.
+
+The improved `FindCommandParser` makes use of the `ArgumentMultimap` and the `ArgumentTokenizer` (similar to how it is implemented in the `AddCommandParser`) to retrieve the prefix-argument mapping. The `Parse` command in `FindCommandParser` uses the `ArgumentMultimap` generated by the `ArgumentTokenizer`'s tokenize method to create the relevant `Predicate` related to the field that the user has stated.
+
+The different `Predicate` inherits from the `FieldContainsKeywordsPredicate`. The`CombineContainsKeywordsPredicate` is the combination of the various `Predicate`.
+- Each subclass of `FieldContainsKeywordsPredicate` implements a concrete method `test` relevant to filtering the Person for the particular field.
+- `CombineContainsKeywordsPredicate` makes use of all the subclasses of `FieldContainsKeywordsPredicate` `test` method in order to update the filteredPersonList from the `Model` using all the `FieldContainsKeywordsPredicate` created.
+
+![FieldContainsKeywordsPredicate](images/FieldContainsKeywordsPredicate.png)
+
+#### Design considerations:
+The `FindCommandParser` was implemented this way as there are multiple prefixes in the new multi-field find command. So it requires a mapping of the different prefixes with their respective arguments.
+
+The predicate is implemented in this way since updating the filteredPersonList in the `Model` class requires a single `Predicate`, so a single `CombineContainsKeywordsPredicate` was used, where the `CombineContainsKeywordsPredicate#test(person)` will make use of all the subclasses of `FieldContainsKeywordsPredicate` to update the filteredPersonList.
+
+**Aspect: How FindCommand executes:**
+
+![FindCommandSequence](images/FindSequenceDiagram.png)
-#### Proposed Implementation
+![ParseFindSequence](images/ParseFindSequenceDiagram.png)
-The proposed undo/redo mechanism is facilitated by `VersionedAddressBook`. It extends `AddressBook` with an undo/redo history, stored internally as an `addressBookStateList` and `currentStatePointer`. Additionally, it implements the following operations:
+![FindCommand](images/FindCommand.png)
-* `VersionedAddressBook#commit()` — Saves the current address book state in its history.
-* `VersionedAddressBook#undo()` — Restores the previous address book state from its history.
-* `VersionedAddressBook#redo()` — Restores a previously undone address book state from its history.
+* **Alternative 1 (current choice):** A combined predicate that makes use of the different subclasses of `FieldContainsKeywordsPredicate`
+ * Pros: More modularised as it makes use of the individual predicates for each field.
+ * Cons: May have some redundancy
-These operations are exposed in the `Model` interface as `Model#commitAddressBook()`, `Model#undoAddressBook()` and `Model#redoAddressBook()` respectively.
+* **Alternative 2:** Using just one predicate to encompass all the fields
+ * Pros: Code is cleaner and less redundant
+ * Cons: Implementation and testing might be harder as it is less modularised
+
-Given below is an example usage scenario and how the undo/redo mechanism behaves at each step.
+### Adding the ability to export AddressBook to CSV and back
-Step 1. The user launches the application for the first time. The `VersionedAddressBook` will be initialized with the initial address book state, and the `currentStatePointer` pointing to that single address book state.
+The ability to convert a collection of Persons to a CSV file is facilitated by `CsvUtil` and `CsvAdaptedPerson`.
+
+`CsvAdaptedPerson` is a CSV-friendly version of `Person`, with a method that allows it to be converted to a CSV-friendly string, while `CsvUtil` allows for the writing of a `CsvAdaptedPerson` object to a CSV file.
+
+The implementation of writing the AddressBook to CSV is as follows:
+
+When the user wishes to save the current state of the AddressBook to CSV, either through the `export` command or by clicking on the button in the dropdown, the following events occur:
+
+Step 1: `MainWindow#handleSaveToCsv` is called, which:
+- calls `MainWindow#handleSaveFile` to obtain a destination file location from the user to save to.
+- then calls `LogicManager#saveAddressBookToCsv`
+
+Step 2: `LogicManager#saveAddressBookToCsv`is called, which calls `StorageManager#saveAddressBookToCsv`
+
+Step 3: `StorageManager#saveAddressBookToCsv` is called, which calls `CsvAddressBookStorage#saveAddressBook`
+
+Step 4: `CsvAddressBookStorage#saveAddressBook` is called, which
+- creates a file at the given location, if it does not exist yet
+- generates a `List` of `CsvAdaptedPerson` from the AddressBook
+- calls `CsvUtil#saveCsvFile`
+
+Step 5: `CsvUtil#saveCsvFile` is called, which writes the contents of the AddressBook to the CSV file.
+
+![Save AddressBook to CSV](images/SaveAddressBookToCsv.png)
+
+A similar flow of events occur for the loading of a CSV file to the AddressBook.
+
+#### Design considerations:
+
+The functionality was implemented this way to stick to the existing codebase as much as possible.
+
+There is existing functionality to export the AddressBook to JSON, hence the classes involved with CSV files are organised and structured in a similar way.
+
+### Adding the ability to Undo/Redo certain commands
+
+List of undo-able/redo-able commands: an `add`, `delete`, `edit`, `clear`, `addTag`, `deleteTag` and `editTag`.
+
+The ability to undo/redo is facilitated by `ModelManager` and `UndoRedoStorage`. The
+`ModelManager` creates a copy of the current address book as an `AddressBook` and stores it in `UndoRedoStorage`.
+
+`ModelManager` implements the following operations:
+
+- ModelManager#copyAddressBook() - Makes a copy of the current address book.
+- ModelManager#undoCommand() - Reverts the address book to a copy before the last undo-able command.
+- ModelManager#redoCommand() - Reverts the address book to a copy before the last undone command.
+
+It is supported by `UndoRedoStorage` which implements the following operations:
+
+- UndoRedoStorage#addToUndo() - Adds the given `AddressBook` to the list of `AddressBook` states to be used by `UndoRedoStorage#undo()`.
+- UndoRedoStorage#undo() - Returns the previous `AddressBook` state.
+- UndoRedoStorage#redo() - Returns the previously undone `AddressBook` state.
+- UndoRedoStorage#resetRedoStack() - Resets the stack of previously undone `AddressBook` states.
+
+Given below is an example usage scenario and how the undo mechanism behaves at each step.
+
+Step 1. The user launches the application for the first time. The `AddressBook` will be initialized with the initial address book state. `ModelManager#copyAddressBook()` is called to make a copy of the `AddressBook`, and
+that copy is stored in the `UndoRedoStorage` via `UndoRedoStorage#addToUndo()`.
![UndoRedoState0](images/UndoRedoState0.png)
-Step 2. The user executes `delete 5` command to delete the 5th person in the address book. The `delete` command calls `Model#commitAddressBook()`, causing the modified state of the address book after the `delete 5` command executes to be saved in the `addressBookStateList`, and the `currentStatePointer` is shifted to the newly inserted address book state.
+Step 2. The user executes `delete 5` command to delete the 5th person in the address book. The `delete` command calls `ModelManager#copyAddressBook()` after the person is deleted, making a copy of the new `AddressBook` without the deleted person and
+that copy is stored in the `UndoRedoStorage` via `UndoRedoStorage#addToUndo()`. There are currently two `AddressBook` states inside `UndoRedoStorage`.
![UndoRedoState1](images/UndoRedoState1.png)
-Step 3. The user executes `add n/David …​` to add a new person. The `add` command also calls `Model#commitAddressBook()`, causing another modified address book state to be saved into the `addressBookStateList`.
+Step 3. The user executes `add n/David …` to add a new person. The `add` command also calls `ModelManager#copyAddressBook()` after adding the new person and stores a copy of the new `AddressBook` in `UndoRedoStorage`.
+There are currently three `AddressBook` states inside `UndoRedoStorage`.
-![UndoRedoState2](images/UndoRedoState2.png)
+![UndoRedoState3](images/UndoRedoState2.png)
-
:information_source: **Note:** If a command fails its execution, it will not call `Model#commitAddressBook()`, so the address book state will not be saved into the `addressBookStateList`.
+
:information_source: **Note:** If a command fails its execution, it will not call `ModelManager#copyAddressBook()` and `UndoRedoStorage#addToUndo()`, so the address book state will not be saved into the `UndoRedoStorage`.
-Step 4. The user now decides that adding the person was a mistake, and decides to undo that action by executing the `undo` command. The `undo` command will call `Model#undoAddressBook()`, which will shift the `currentStatePointer` once to the left, pointing it to the previous address book state, and restores the address book to that state.
+Step 3. The user now decides that adding the person was a mistake, and decides to undo that action by executing the `undo` command. The `undo` command will call `Model#undoCommand()`, which will call `UndoRedoStorage#undo()`.
+It adds the current `AddressBook` state to a stack which stores the undone states of the `AddressBook`. It then shifts the pointer to the previous `AddressBook` state and returns that `AddressBook` state to the `Model`.
![UndoRedoState3](images/UndoRedoState3.png)
-
:information_source: **Note:** If the `currentStatePointer` is at index 0, pointing to the initial AddressBook state, then there are no previous AddressBook states to restore. The `undo` command uses `Model#canUndoAddressBook()` to check if this is the case. If so, it will return an error to the user rather
-than attempting to perform the undo.
+
:information_source: **Note:** If the `currentStatePointer` is at index 0, pointing to the initial `AddressBook` state, then there are no previous `AddressBook` states to restore.
+The `undo` command checks the size of the List storing the `AddressBook` states to check if this is the case. If so, it will return an error to the user rather than attempting to perform the undo.
@@ -197,21 +323,14 @@ The following sequence diagram shows how the undo operation works:
![UndoSequenceDiagram](images/UndoSequenceDiagram.png)
-
:information_source: **Note:** The lifeline for `UndoCommand` should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram.
-
-
-
-The `redo` command does the opposite — it calls `Model#redoAddressBook()`, which shifts the `currentStatePointer` once to the right, pointing to the previously undone state, and restores the address book to that state.
+The `redo` command does the opposite — it calls `Model#redoCommand()`, which shifts the `currentStatePointer` once to the right, pointing to the previously undone state, and restores the `AddressBook` to that state.
-
:information_source: **Note:** If the `currentStatePointer` is at index `addressBookStateList.size() - 1`, pointing to the latest address book state, then there are no undone AddressBook states to restore. The `redo` command uses `Model#canRedoAddressBook()` to check if this is the case. If so, it will return an error to the user rather than attempting to perform the redo.
-
-
-
-Step 5. The user then decides to execute the command `list`. Commands that do not modify the address book, such as `list`, will usually not call `Model#commitAddressBook()`, `Model#undoAddressBook()` or `Model#redoAddressBook()`. Thus, the `addressBookStateList` remains unchanged.
+Step 5. The user then decides to execute the command `list`. Commands that do not modify the address book, such as `list`, will usually not call `ModelManager#copyAddressBook()` and `UndoRedoStorage#addToUndo()`. Thus, the list storing the `AddressBook` states in `UndoRedoStorage` remains unchanged.
![UndoRedoState4](images/UndoRedoState4.png)
-Step 6. The user executes `clear`, which calls `Model#commitAddressBook()`. Since the `currentStatePointer` is not pointing at the end of the `addressBookStateList`, all address book states after the `currentStatePointer` will be purged. Reason: It no longer makes sense to redo the `add n/David …​` command. This is the behavior that most modern desktop applications follow.
+Step 6. The user executes `clear`, which calls `ModelManager#copyAddressBook()` and `UndoRedoStorage#addToUndo()`. The `UndoRedoStorage#resetRedoStack()` command is called after the `AddressBook` has been cleared to remove all previously stored undone `AddressBook` states.
+The current `AddressBook` state is then added to `UndoRedoStorage` via `UndoRedoStorage#addToUndo()`. Reason: It no longer makes sense to redo the `add n/David …a` command. This is the behavior that most modern desktop applications follow.
![UndoRedoState5](images/UndoRedoState5.png)
@@ -232,11 +351,16 @@ The following activity diagram summarizes what happens when a user executes a ne
* Pros: Will use less memory (e.g. for `delete`, just save the person being deleted).
* Cons: We must ensure that the implementation of each individual command are correct.
-_{more aspects and alternatives to be added}_
+#### Areas for improvement:
-### \[Proposed\] Data archiving
+**Aspect: Implementing Undo & redo for Packages:**
-_{Explain here how the data archiving feature will be implemented}_
+* **Possible implementation:** `UndoRedoStorage` stores both `AddressBook` state and `InsurancePackagesSet` state.
+ * Pros: Easy to implement. Change existing `UndoRedoStorage` methods to accommodate both data by using a wrapper class to encapsulate both `AddressBook` state and `InsurancePackagesSet` state.
+ * Issues: `Undo` and `Redo` were able to work as intended for commands that alter `AddressBook` states such as `add`, `delete`, `edit` and `clear`.
+ however, it is able to `Undo` and `Redo` commands for commands that alter `InsurancePackagesSet` states such as `addp`, `deletep` and `editp`
+ but at a certain point, the previous `InsurancePackagesSet` states stored in `UndoRedoStorage` becomes mutated which gives rise to erroneous behaviours when executing 'Undo' and 'Redo'.
+ * Fix: `InsurancePackagesSet()` might have to be changed as it is suspected that the method was mutating the date in the `UndoRedoStorage`.
--------------------------------------------------------------------------------------------------------------------
@@ -257,46 +381,72 @@ _{Explain here how the data archiving feature will be implemented}_
**Target user profile**:
-* has a need to manage a significant number of contacts
-* prefer desktop apps over other types
-* can type fast
-* prefers typing to mouse interactions
-* is reasonably comfortable using CLI apps
+A tech savvy insurance agent who:
+* has to manage a significant number of clients and their different profiles
+* prefers quick commands to locate his clients in his address book
+* has to meet up with clients to introduce new packages at different locations
+* is on the move often, prefers an app that can be used efficiently and reliably on the go
-**Value proposition**: manage contacts faster than a typical mouse/GUI driven app
+**Value proposition**:
+* Allow clients to be saved, ordered and filtered by different categories
+* Allow different pieces of data to be saved for different clients
+* Has short commands for address book to be used efficiently and reliably on the go
### User stories
Priorities: High (must have) - `* * *`, Medium (nice to have) - `* *`, Low (unlikely to have) - `*`
-| Priority | As a …​ | I want to …​ | So that I can…​ |
-| -------- | ------------------------------------------ | ------------------------------ | ---------------------------------------------------------------------- |
-| `* * *` | new user | see usage instructions | refer to instructions when I forget how to use the App |
-| `* * *` | user | add a new person | |
-| `* * *` | user | delete a person | remove entries that I no longer need |
-| `* * *` | user | find a person by name | locate details of persons without having to go through the entire list |
-| `* *` | user | hide private contact details | minimize chance of someone else seeing them by accident |
-| `*` | user with many persons in the address book | sort persons by name | locate a person easily |
-
-*{More to be added}*
+| Priority | As a …​ | I want to …​ | So that I can…​ |
+|----------|--------------------------------------------|----------------------------------------------------|------------------------------------------------------------------------------------------------------------|
+| `* * *` | new user | have a quick guide | learn how to use the address book efficiently and effectively |
+| `* * *` | user | add a new client | have a list of my clients |
+| `* * *` | user | delete an old client | remove clients who have stopped working with me |
+| `* * *` | user | update a client's details | keep information of my clients accurate and up-to-date |
+| `* * *` | user | list all clients | have an overview of my clientele |
+| `* * *` | user | have tags to store other client details | store miscellaneous information pertaining to each client |
+| `* * ` | user | have priorities assigned to tags | conveniently recall which tasks have higher priorities than others, without have to only rely on my memory |
+| `* *` | user | sort my clients | see contacts in ascending/descending order w.r.t a certain priority (i.e. next meeting) |
+| `* *` | user on-the-move | have keyboard shortcuts | use the app efficiently and reliably |
+| `* *` | clumsy user | undo my last command | revert if I made a mistake in my previous command |
+| `* *` | clumsy user | have autocorrect for commands | make minor mistyping errors without having to rewrite entire commands |
+| `* *` | user with many clients in the address book | search for clients | obtain information about a client easily |
+| `* *` | user with many clients in the address book | filter clients | obtain information about a group of clients easily |
+| `* *` | user with many clients in the address book | pin certain clients | easily access them |
+| `* *` | anxious user | set reminders | be alerted for upcoming meetings, deadlines etc. |
+| `* *` | user | see relationships between my clients | manage entire families/groups of friends at the same time and tailor packages specifically for them |
+| `* *` | user | be able to conveniently edit individual tags | avoid typing the entire list of tags every time I want to make a small change |
+| `*` | user | export my data into other formats such as Excel | visualise and share information with others |
+| `*` | user | import and view the data of others' using this app | help out my colleagues when needed |
+| `*` | night-owl user | turn on dark mode | strain my eyes less when working at night |
+| `*` | artsy user | artsy mode | enjoy different visuals on the app |
### Use cases
(For all use cases below, the **System** is the `AddressBook` and the **Actor** is the `user`, unless specified otherwise)
+**Use case: Add a person**
+
+*MSS*
+
+1. User requests to add a person
+2. User keys in person's details
+3. AddressBook adds the person
+
+ Use case ends.
+
**Use case: Delete a person**
-**MSS**
+*MSS*
-1. User requests to list persons
-2. AddressBook shows a list of persons
-3. User requests to delete a specific person in the list
-4. AddressBook deletes the person
+1. User requests to list persons
+2. AddressBook shows a list of persons
+3. User requests to delete a specific person in the list
+4. AddressBook deletes the person
Use case ends.
-**Extensions**
+*Extensions*
* 2a. The list is empty.
@@ -308,20 +458,326 @@ Priorities: High (must have) - `* * *`, Medium (nice to have) - `* *`, Low (unli
Use case resumes at step 2.
-*{More to be added}*
+**Use case: Edit a person**
+
+*MSS*
+
+1. User requests to list persons
+2. AddressBook shows a list of persons
+3. User requests to edit a specific person in the list
+4. User keys in edited person's details
+5. AddressBook edits the person
+
+ Use case ends.
+
+*Extensions*
+
+* 2a. The list is empty.
+
+ Use case ends.
+
+* 3a. The given index is invalid.
+
+ * 3a1. AddressBook shows an error message.
+
+ Use case resumes at step 2.
+
+**Use case: Find a person**
+
+*MSS*
+
+1. User requests to list specific persons
+2. User keys in keywords and relevant fields to find
+3. AddressBook shows a list of persons matching the keyword
+
+ Use case ends.
+
+*Extensions*
+
+* 2a. There are no fields and no keywords, just `find` typed.
+
+ * 2a1. AddressBook shows an error message to show how `find` should be used.
+ * Use case resumes at step 2.
+
+* 2b. The fields are given but there are no keywords
+ * 2b1. AddressBook shows an error message telling the user that no keywords are entered.
+ * Use case resumes at step 2.
+
+* 3a. The list is empty.
+
+ Use case ends.
+
+**Use case: Clear address book**
+
+*MSS*
+
+1. User requests to clear all persons
+2. AddressBook shows an empty list
+
+ Use case ends.
+
+**Use case: Undo a command**
+
+*MSS*
+
+1. User requests to undo the previous command
+2. AddressBook undoes previous command
+
+ Use case ends.
+
+*Extensions*
+
+* 1a. The previous command is not un-doable.
+
+ Use case ends.
+
+* 1b. There is no previous command.
+
+ * 1b1. AddressBook shows an error message.
+
+ Use case ends.
+
+**Use case: Redo a command**
+
+*MSS*
+
+1. User requests to undo the previous command
+2. AddressBook undoes previous command
+3. User requests to redo the undone command
+4. AddressBook redoes undone command
+
+ Use case ends.
+
+*Extensions*
+
+* There is no command to redo.
+
+ * 1a1. AddressBook shows an error message.
+
+ Use case ends.
+
+**Use case: Clip a person's information to clipboard**
+
+*MSS*
+
+1. User requests to clip a person
+2. User specifies the person to clip
+3. AddressBook copies that person's information to user's system clipboard
+
+ Use case ends.
+
+*Extensions*
+
+* 2a. The specified person cannot be found.
+
+ * 2a1. AddressBook shows an error message letting the user know what went wrong.
+
+ Use case ends.
+
+**Use case: Display client list by priority level of tags**
+
+*MSS*
+
+1. User requests to sort and display client list by priority level of tags
+2. AddressBook displays sorted list
+
+ Use case ends.
+
+*Extensions*
+
+* 1a. The command was inputted wrongly.
+
+ * 1a1. AddressBook shows an error message letting the user know what went wrong.
+
+ Use case ends.
+
+**Use case: Add a tag**
+
+*MSS*
+
+1. AddressBook shows a list of persons
+2. User requests to add a tag to a specified person
+3. User keys in details of the new tag
+4. AddressBook adds the tag to the person
+
+ Use case ends.
+
+*Extensions*
+
+* 1a. The list is empty.
+
+ Use case ends.
+
+* 2a. The given index is invalid.
+
+ * 2a1. AddressBook shows an error message.
+
+ Use case resumes at step 1.
+
+* 3b. The given tag name is invalid.
+
+ * 3b1. AddressBook shows an error message.
+
+ Use case resumes at step 1.
+
+
+**Use case: Delete a tag**
+
+*MSS*
+
+1. AddressBook shows a list of persons
+2. User requests to delete a tag from a specific person in the list
+3. User specifies the exact tag they want to delete from that person
+4. AddressBook deletes the tag identified
+
+ Use case ends.
+
+*Extensions*
+
+* 1a. The list is empty.
+
+ Use case ends.
+
+* 2a. The given person index is invalid.
+
+ * 2a1. AddressBook shows an error message.
+
+ Use case resumes at step 1.
+
+* 3a. The given tag number is invalid.
+
+ * 3a1. AddressBook shows an error message.
+
+ Use case resumes at step 1.
+
+**Use case: Edit a person**
+
+*MSS*
+
+1. AddressBook shows a list of persons
+2. User requests to edit a tag from a specific person in the list
+3. User specifies the exact tag they want to delete from that person
+4. User specifies what they want the new version of the tag to look like
+5. AddressBook edits the tag identified accordingly
+
+ Use case ends.
+
+*Extensions*
+
+* 1a. The list is empty.
+
+ Use case ends.
+
+* 2a. The given person index is invalid.
+
+ * 2a1. AddressBook shows an error message.
+
+ Use case resumes at step 1.
+
+* 3a. The given tag number is invalid.
+
+ * 3a1. AddressBook shows an error message.
+
+ Use case resumes at step 1.
+
+* 4a. The given tag name is invalid.
+
+ * 4a1. AddressBook shows an error message.
+
+ Use case resumes at step 1.
+
+**Use case: Add insurance package**
+
+*MSS*
+
+1. User requests to add a package.
+
+2. User keys in package name and description.
+
+3. AddressBook adds the package.
+
+ Use case ends.
+
+*Extensions*
+
+* 2a. There already exists a package with the same package name.
+
+ * 2a1. AddressBook shows an error message, saying that the package already exists.
+
+ Use case ends.
+
+* 2b. User did not input the package description field (`/d`).
+
+ * 2b1. AddressBook shows an error message, saying that the command format is invalid.
+
+ Use case ends.
+
+**Use case: Edit insurance package**
+
+*MSS*
+
+1. User requests to edit a package.
+
+2. User keys in package name to be edited, and the new description of the package.
+
+3. AddressBook edits the package.
+
+ Use case ends.
+
+*Extensions*
+
+* 2a. There is no package with the given package name.
+
+ * 2a1. AddressBook shows an error message, saying that the package does not exist.
+
+ Use case ends.
+
+* 2b. User did not input the package description field (`/d`).
+
+ * 2b1. AddressBook shows an error message, saying that the command format is invalid.
+
+ Use case ends.
+
+**Use case: Delete insurance package**
+
+*MSS*
+
+1. User requests to delete a package.
+
+2. User keys in package name to be deleted.
+
+3. AddressBook deleted the package.
+
+ Use case ends.
+
+*Extensions*
+
+* 2a. There is no package with the given package name.
+
+ * 2a1. AddressBook shows an error message, saying that the package does not exist.
+
+ Use case ends.
+
+* 2b. The given package is still in use by a person.
+
+ * 2b1. AddressBook shows an error message, saying that the package is in use.
+
+ Use case ends.
### Non-Functional Requirements
1. Should work on any _mainstream OS_ as long as it has Java `11` or above installed.
2. Should be able to hold up to 1000 persons without a noticeable sluggishness in performance for typical usage.
3. A user with above average typing speed for regular English text (i.e. not code, not system admin commands) should be able to accomplish most of the tasks faster using commands than using the mouse.
-
-*{More to be added}*
+4. A new user should be able to learn how to use all functions of the address book within 10 minutes.
+5. Product should come with a storage system, and should not depend on external servers/Database Management Systems (DBMS)
+6. Product should work without an installer.
+7. Source code is open source.
+8. Features should be easy to test, for both manual and automated testing.
### Glossary
* **Mainstream OS**: Windows, Linux, Unix, OS-X
-* **Private contact detail**: A contact detail that is not meant to be shared with others
--------------------------------------------------------------------------------------------------------------------
@@ -340,38 +796,27 @@ testers are expected to do more *exploratory* testing.
1. Download the jar file and copy into an empty folder
- 1. Double-click the jar file Expected: Shows the GUI with a set of sample contacts. The window size may not be optimum.
+ 2. Double-click the jar file Expected: Shows the GUI with a set of sample contacts. The window size may not be optimum.
-1. Saving window preferences
+2. Saving window preferences
1. Resize the window to an optimum size. Move the window to a different location. Close the window.
- 1. Re-launch the app by double-clicking the jar file.
+ 2. Re-launch the app by double-clicking the jar file.
Expected: The most recent window size and location is retained.
-1. _{ more test cases …​ }_
-
### Deleting a person
1. Deleting a person while all persons are being shown
1. Prerequisites: List all persons using the `list` command. Multiple persons in the list.
- 1. Test case: `delete 1`
+ 2. Test case: `delete 1`
Expected: First contact is deleted from the list. Details of the deleted contact shown in the status message. Timestamp in the status bar is updated.
- 1. Test case: `delete 0`
+ 3. Test case: `delete 0`
Expected: No person is deleted. Error details shown in the status message. Status bar remains the same.
- 1. Other incorrect delete commands to try: `delete`, `delete x`, `...` (where x is larger than the list size)
+ 4. Other incorrect delete commands to try: `delete`, `delete x`, `...` (where x is larger than the list size)
Expected: Similar to previous.
-
-1. _{ more test cases …​ }_
-
-### Saving data
-
-1. Dealing with missing/corrupted data files
-
- 1. _{explain how to simulate a missing/corrupted file, and the expected behavior}_
-
-1. _{ more test cases …​ }_
+
diff --git a/docs/UserGuide.md b/docs/UserGuide.md
index 3716f3ca8a4..f5263c87597 100644
--- a/docs/UserGuide.md
+++ b/docs/UserGuide.md
@@ -3,190 +3,526 @@ layout: page
title: User Guide
---
-AddressBook Level 3 (AB3) is a **desktop app for managing contacts, optimized for use via a Command Line Interface** (CLI) while still having the benefits of a Graphical User Interface (GUI). If you can type fast, AB3 can get your contact management tasks done faster than traditional GUI apps.
-
* Table of Contents
{:toc}
---------------------------------------------------------------------------------------------------------------------
+ClientConnect is a client-management app for tech-savvy insurance agents to **manage clients in the midst of
+massive lists of clients that have various needs and priorities**.
+
+Are you a fast typer? ClientConnect can get your client management tasks done faster than traditional GUI apps.
+Add Tags with varying levels of priorities to keep track of utmost important tasks to utilize your time efficiently,
+sort your clients by priority so you won't miss out urgent tasks to be done, conveniently import and export your client contacts, and easily find the client details you want through our `find` command.
+
+The package tab allows for quick reference of package details in the event that a client has to be contacted
+regarding their package. It contains a list of all packages that have been added before, not necessarily
+only packages that existing people in ClientConnect have.
+
+![result for 'list'](images/listAllClientsUG.png)
## Quick start
1. Ensure you have Java `11` or above installed in your Computer.
-1. Download the latest `addressbook.jar` from [here](https://github.com/se-edu/addressbook-level3/releases).
-1. Copy the file to the folder you want to use as the _home folder_ for your AddressBook.
+2. Download the latest `ClientConnect.jar`
+
+
+3. Copy the file to the folder you want to use as the _home folder_ for your ClientConnect.
+
+
+4. Double-click the file to start the app. The GUI similar to the below should appear in a few seconds. Note how the app contains some sample data.
-1. Double-click the file to start the app. The GUI similar to the below should appear in a few seconds. Note how the app contains some sample data.
- ![Ui](images/Ui.png)
-1. Type the command in the command box and press Enter to execute it. e.g. typing **`help`** and pressing Enter will open the help window.
+5. Type the command in the command box and press Enter to execute it. e.g. typing **`help`** and pressing Enter will open the help window.
+
Some example commands you can try:
* **`list`** : Lists all contacts.
- * **`add`**`n/John Doe p/98765432 e/johnd@example.com a/John street, block 123, #01-01` : Adds a contact named `John Doe` to the Address Book.
+ * **`add`**`n/John Doe p/98765432 e/johnd@example.com a/John street, block 123, #01-01, i/package1`: Adds a client named John Doe who is under the insurance package1 to the Address Book.
- * **`delete`**`3` : Deletes the 3rd contact shown in the current list.
+ * **`delete`**`3` : Deletes the 3rd client shown in the current list.
- * **`clear`** : Deletes all contacts.
+ * **`clear`** : Deletes all clients.
* **`exit`** : Exits the app.
-1. Refer to the [Features](#features) below for details of each command.
---------------------------------------------------------------------------------------------------------------------
+6. Refer to the Features below for details of each command.
-## Features
-
+## Features
-**:information_source: Notes about the command format:**
+**Notes about the command format:**
-* Words in `UPPER_CASE` are the parameters to be supplied by the user.
+* 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 in square brackets are **optional**.
+ e.g. `n/NAME [t/TAG]` can be used as `n/John Doe t/important` 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 ` ` (i.e. 0 times), `t/friend`, `t/friend t/family` etc.
+ e.g. `[t/TAG]…​` can be used as ` ` (i.e. 0 times), `t/important`, `t/important :p2 t/needs help :p1` 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.
+
* If a parameter is expected only once in the command but you specified it multiple times, only the last occurrence of the parameter will be taken.
e.g. if you specify `p/12341234 p/56785678`, only `p/56785678` will be taken.
+
* Extraneous parameters for commands that do not take in parameters (such as `help`, `list`, `exit` and `clear`) will be ignored.
e.g. if the command specifies `help 123`, it will be interpreted as `help`.
-
+**Notes about the tags:**
+* Each tag has two components- a tag name, and a priority level.
+* There are 4 priority levels, with 1 being the highest and 4 being the lowest. However, a tag doesn't necessarily need to have a priority level.
+* The tag's colour depends on the priority level.
+* The priority level of a tag is indicated along with the tag name when you input the `TAG` parameter in commands. To indicate the priority level, simply follow the tag name by typing ` :p[1/2/3/4]` after the tag name.
+* For example, `owes money` would create a tag with tag name "owes money", and no priority level.
+* However, `owes money :p3` would create a tag with tag name "owes money", and priority level 3.
-### Viewing help : `help`
+### **General commands**
-Shows a message explaning how to access the help page.
+#### Viewing help : `help`
-![help message](images/helpMessage.png)
+Shows a message explaining how to access the ClientConnect help page.
+
+![Help button image](images/helpButtonUG.png)
Format: `help`
+Alternatively, click the Help button on the toolbar to view a dropdown window displaying a "Help ... F1" button.
+
+This also shows that you can press F1 on your keyboard to open the help message.
+
+On clicking Help, you will see this as a result:
-### Adding a person: `add`
+![Help window image](images/helpWindowUG.png)
-Adds a person to the address book.
+------------------------------------------------------------------------------------
+#### Adding a person: `add`
-Format: `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]…​`
+Adds a person, as well as any details, to ClientConnect.
-
:bulb: **Tip:**
-A person can have any number of tags (including 0)
-
+![result before 'add n/Damith p/99998888 e/damith@damith.com a/Blk 123 i/package 1'](images/beforeAddDamithUG.png)
+
+Format: `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [i/INSURANCE_PACKAGE] [t/TAG]…​`
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/Damith p/99998888 e/damith@damith.com a/Blk 123 i/package 1`
+* `add n/Jackson p/92341888 e/jackson5@example.com a/laney street, block 123, #01-01, i/package1 t/important`
+
+After adding:
-### Listing all persons : `list`
+![result after 'add n/Damith p/99998888 e/damith@damith.com a/Blk 123 i/package 1'](images/afterAddDamithUG.png)
-Shows a list of all persons in the address book.
+Notes:
+* Any tags are optional.
+* Email domain must contain only **one period** and minimally ends with 2 letters:
+ e.g. jackson5@example.com is acceptable, jackson5@example.com.sg and jackson5@example is not acceptable
+* For the insurance package indicated in the `i/` field, if an existing package with the same name does not
+ exist, a new one will be automatically created for you.
+
+------------------------------------------------------------------------------------
+#### Listing all persons : `list`
+
+Displays all your clients in ClientConnect.
+
+![result for 'list'](images/listAllClientsUG.png)
Format: `list`
-### Editing a person : `edit`
+------------------------------------------------------------------------------------
+#### Editing a person : `edit`
-Edits an existing person in the address book.
+Edits an existing client in ClientConnect, similar to adding a new client.
-Format: `edit INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [t/TAG]…​`
+![result before 'edit 1 p/123456'](images/beforeEditUG.png)
-* Edits the person at the specified `INDEX`. The index refers to the index number shown in the displayed person list. The index **must be a positive integer** 1, 2, 3, …​
-* At least one of the optional fields must be provided.
-* Existing values will be updated to the input values.
-* When editing tags, the existing tags of the person will be removed i.e adding of tags is not cumulative.
-* You can remove all the person’s tags by typing `t/` without
- specifying any tags after it.
+Format: `edit INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [i/INSURANCE_PACKAGE] [t/TAG]…​`
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 1 p/123456 will edit the phone number of the client listed as #1 to 123456`
-### Locating persons by name: `find`
+After editing:
-Finds persons whose names contain any of the given keywords.
+![result after 'edit 1 p/123456'](images/afterEditUG.png)
-Format: `find KEYWORD [MORE_KEYWORDS]`
+Notes:
+* Edits the person at the specified `INDEX`.
+* You can look for a client’s index by using the `list` command.
+* At least one of the fields must be provided.
+* Existing values will be updated to the input values.
+
+------------------------------------------------------------------------------------
+#### Locating persons by field: `find`
+
+Finds clients whose field contains any of the given keywords.
+
+![result for 'find i/undecided'](images/findInsurancePackageUG.png)
-* The search is case-insensitive. e.g `hans` will match `Hans`
+Format: `find FIELD KEYWORD [MORE_KEYWORDS] [MORE_FIELD] [MORE_KEYWORDS]`
+
+Fields:
+* `n/`: name
+* `p/`: phone number
+* `e/`: email
+* `a/`: address
+* `i/`: insurance package
+* `t/`: tags
+
+Notes:
+* The search is case-insensitive. e.g. `hans` will match `Hans`
* The order of the keywords does not matter. e.g. `Hans Bo` will match `Bo Hans`
-* Only the name is searched.
-* Only full words will be matched e.g. `Han` will not match `Hans`
-* Persons matching at least one keyword will be returned (i.e. `OR` search).
- e.g. `Hans Bo` will return `Hans Gruber`, `Bo Yang`
+* The fields described above can be searched
+* It is possible to search for multiple fields, and the order of the fields does not matter.
Examples:
-* `find John` returns `john` and `John Doe`
-* `find alex david` returns `Alex Yeoh`, `David Li`
- ![result for 'find alex david'](images/findAlexDavidResult.png)
+* `find n/John` returns `john` and `John Doe`
+* `find p/91234567` returns the client that is associated to the number
+* `find a/street` returns the client associated with the address
+* `find i/undecided n/david` returns the client with the Undecided insurance package and with name containing David
+
+After a `find` command:
+* `find n/ alex david` returns `Alex Yeoh`, `David Li`
+
+![result for 'find n/alex david'](images/findAlexDavidResultUG.png)
-### Deleting a person : `delete`
+* `find i/undecided n/david` returns `David Li`
-Deletes the specified person from the address book.
+![result for 'find i/undecided n/david'](images/findUndecidedDavid.png)
+
+------------------------------------------------------------------------------------
+#### Deleting a person : `delete`
+Deletes an existing client from ClientConnect.
Format: `delete INDEX`
-* 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, …​
+* Deletes the person at the specified INDEX.
+* You can look for a client’s index by using the `list` command.
+* You can also delete a client after doing a search and using the index in the search (refer to example 2)
Examples:
-* `list` followed by `delete 2` deletes the 2nd person in the address book.
-* `find Betsy` followed by `delete 1` deletes the 1st person in the results of the `find` command.
+1. `delete 1`
+2. `find n/John` followed by `delete 1` deletes the 1st person in the results provided by ClientConnect after finding by the `John` keyword
+
+Before a delete command:
+
+![result before 'delete 2'](images/beforeDeleteUG.png)
+
+After `delete 2`:
+
+![result after 'delete 2'](images/afterDeleteUG.png)
+
+------------------------------------------------------------------------------------
+#### Importing ClientConnect CSV data: `import`
+Opens a window to choose a CSV file to load.
+
+Format: `import`
+
+------------------------------------------------------------------------------------
+#### Exporting ClientConnect data to CSV: `export`
+Opens a window to choose a CSV file to export to.
+
+Format: `export`
+
+------------------------------------------------------------------------------------
+#### Undo previous command: `undo`
+Undoes previous command executed.
+
+Format: `undo`
+
+Before undo (after we `delete 1`):
+
+![result before undo](images/undo1UG.png)
+
+After undo (where we undo command `delete 1`):
+
+![result after undo](images/undo2UG.png)
+
+Note:
+* Undo only works on add, delete, edit, clear, addTag, deleteTag, editTag commands and
+will not work for addp, deletep, editp and prioList commands.
+
+------------------------------------------------------------------------------------
+#### Redo previous command: `redo`
+Redoes undone command.
+
+Format: `redo`
-### Clearing all entries : `clear`
+Before redo (we undo `delete 1`):
+
+![result before redo](images/redo1UG.png)
+
+After redo (where we redo `delete 1`):
+
+![result after redo](images/redo2UG.png)
+
+Note:
+* Redo only works on commands that are undo-able, which are: add, delete, edit, clear, addTag,
+deleteTag, editTag commands.
+
+------------------------------------------------------------------------------------
+#### Clearing all entries : `clear`
Clears all entries from the address book.
+![result after 'clear'](images/clearUG.png)
+
Format: `clear`
-### Exiting the program : `exit`
+------------------------------------------------------------------------------------
+#### Clipping a client's information to clipboard : `clip`
+Copies a client's information onto the system's clipboard.
+
+Format: `clip n/NAME` or `clip INDEX`
+
+* Copies the information of the client with the specified NAME, or at the specified INDEX.
+* You can look for a client’s name or his number by using the `list` command.
+
+Examples:
+1. `clip n/Bernice Yu`
+2. `clip 1`
+
+Before `clip 1`:
+
+![info copied using 'clip 1'](images/beforeClip1UG.png)
+
+
+Information copied using `clip 1`:
+
+![info copied using 'clip 1'](images/afterClip1UG.png)
+
+------------------------------------------------------------------------------------
+#### Listing client list by priority level of their tags: `prioList`
+In order of decreasing priority, priorities have the following color scheme:
+- Priority 1: Red
+- Priority 2: Orange
+- Priority 3: Yellow
+- Priority 4: Peach
+- No Priority: Blue
+
+Before `prioList`:
-Exits the program.
+![before 'prioList'](images/beforePrioList.PNG)
+
+
+After `prioList`:
+
+![after 'prioList'](images/afterPrioList.PNG)
+
+Note that:
+- Clients with multiple tags have the priority level of the highest priority out of all of their tags.
+- Clients without any tags are filtered below all other clients, even those with only 'No Priority' tags.
+
+Format: `prioList`
+
+------------------------------------------------------------------------------------
+#### Exiting the program : `exit`
+Exits the program
Format: `exit`
-### Saving the data
+------------------------------------------------------------------------------------
+### **Tag-related commands**
+
+#### Adding a tag to a person: `addTag`
-AddressBook data are saved in the hard disk automatically after any command that changes the data. There is no need to save manually.
+Adds a tag to the specified person in ClientConnect.
-### Editing the data file
+![result before 'addTag 3 friend of the family'](images/beforeAddTagUG.png)
-AddressBook data are saved as a JSON file `[JAR file location]/data/addressbook.json`. Advanced users are welcome to update data directly by editing that data file.
+Format: `addTag INDEX TAG`
-
:exclamation: **Caution:**
-If your changes to the data file makes its format invalid, AddressBook will discard all data and start with an empty data file at the next run.
-
+Examples:
+* `addTag 3 friend of the family`
+* `addTag 2 owes money :p2`
-### Archiving data files `[coming in v2.0]`
+After adding:
-_Details coming soon ..._
+![result after 'addTag 3 friend of the family'](images/afterAddTagUG.png)
---------------------------------------------------------------------------------------------------------------------
+Notes:
+* Adds tag to the person at the specified `INDEX`.
+* Only one tag can be added at a time.
+* Duplicate tags (same tag name and priority level) cannot be added
+
+------------------------------------------------------------------------------------
+#### Editing a tag of a person: `editTag`
+
+Edits the specified tag of the specified person in ClientConnect.
+
+![result before 'editTag 2 1 owes money :p3'](images/beforeEditTagUG.png)
+
+Format: `editTag INDEX TAG_NUMBER TAG`
+
+Examples:
+* `editTag 3 1 friend of the family`
+* `editTag 2 1 owes money :p3`
+
+After editTag:
+
+![result after 'editTag 2 1 owes money :p3'](images/afterEditTagUG.png)
+
+Notes:
+* Edits person at the specified `INDEX`.
+* Edits person's tag at the specified `TAG_NUMBER`.
+* Only one tag can be edited at a time.
+* The edited tag cannot have a duplicate tag (same tag name and priority level) in the existing tag list.
+
+------------------------------------------------------------------------------------
+#### Deleting a tag of a person: `deleteTag`
+
+Deletes the specified tag of the specified person in ClientConnect.
+
+Format: `deleteTag INDEX TAG_NUMBER`
+
+Examples:
+* `deleteTag 3 1`
+* `deleteTag 2 3`
+
+Before deleteTag command:
+
+![result before 'deleteTag 3 1'](images/beforeDeleteTagUG.png)
+
+After `deleteTag 3 1`:
+
+![result after 'deleteTag 3 1'](images/afterDeleteTagUG.png)
+
+Notes:
+* Deletes person at the specified `INDEX`.
+* Deletes person's tag at the specified `TAG_NUMBER`.
+* Only one tag can be deleted at a time.
+
+------------------------------------------------------------------------------------
+
+### **Package-related commands**
+
+#### Adding an insurance package: `addp`
+
+Adds an insurance package to ClientConnect.
+
+Format: `addp i/PACKAGE_NAME d/PACKAGE_DESC`
+
+Examples:
+* `addp i/Classic Package d/Classic coverages for your needs`
+
+Before addp:
+
+![result before 'addp i/Classic Package d/Classic coverages for your needs'](images/beforeAddInsurancePackage.png)
+
+After `addp i/Classic Package d/Classic coverages for your needs`:
+
+![result1 after 'addp i/Classic Package d/Classic coverages for your needs'](images/afterAddInsurancePackage1UG.png)
+
+
+![result2 after 'addp i/Classic Package d/Classic coverages for your needs'](images/afterAddInsurancePackage2UG.png)
+
+Notes:
+* The description `d/` parameter is compulsory, but you can leave the description blank, and add it in the future.
+
+------------------------------------------------------------------------------------
+#### Editing an insurance package: `editp`
+
+Edits an existing insurance package in ClientConnect.
+
+Format: `editp i/PACKAGE_NAME d/PACKAGE_DESC`
+
+Examples:
+* `editp i/Golden Package d/Lifetime insurance`
+
+Before editp:
+
+![result before 'editp i/Golden Package d/Lifetime insurance'](images/beforeEditInsurancePackageUG.png)
+
+After `editp i/Golden Package d/Lifetime insurance`:
+
+![result1 before 'editp i/Golden Package d/Lifetime insurance'](images/afterEditInsurancePackage1UG.png)
+
+![result2 before 'editp i/Golden Package d/Lifetime insurance'](images/afterEditInsurancePackage2UG.png)
+
+------------------------------------------------------------------------------------
+#### Deleting an insurance package: `deletep`
+
+Deletes an existing insurance package in ClientConnect.
+
+Format: `deletep i/PACKAGE_NAME`
+
+Examples:
+* `deletep i/Golden Package`
+
+Before deletep:
+
+![result before 'deletep i/Golden Package'](images/beforeDeleteInsurancePackageUG.png)
+
+After `deletep i/Golden Package`:
+
+![result1 before 'deletep i/Golden Package'](images/afterDeleteInsurancePackge1UG.png)
+
+![result2 before 'deletep i/Golden Package'](images/afterDeleteInsurancePackage2UG.png)
+
+Notes:
+* If the packages window was open before a package is deleted, you will have to close and reopen the window
+ to view the change.
+* If you try to delete a package that is used by at least one person, it will not be deleted.
+
+------------------------------------------------------------------------------------
+#### Viewing Packages : `listp`
+
+Opens a new window that shows the various insurance packages and their description.
+
+![Package button image](images/packageButtonUG.png)
+
+Click on the Package button to view a dropdown window displaying a "Package" button. Alternatively, you could type in `listp`.
+
+Format: `listp`
+
+On clicking Package or typing `listp`, you will see this as a result:
+
+![result1 after 'listp'](images/listInsurancePackage1UG.png)
-## FAQ
+![result2 after 'listp'](images/listInsurancePackage2UG.png)
-**Q**: How do I transfer my data to another Computer?
-**A**: Install the app in the other computer and overwrite the empty data file it creates with the file that contains the data of your previous AddressBook home folder.
+------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------------------------------------
## Command summary
-Action | Format, Examples
---------|------------------
-**Add** | `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]…​` e.g., `add n/James Ho p/22224444 e/jamesho@example.com a/123, Clementi Rd, 1234665 t/friend t/colleague`
-**Clear** | `clear`
-**Delete** | `delete INDEX` e.g., `delete 3`
-**Edit** | `edit INDEX [n/NAME] [p/PHONE_NUMBER] [e/EMAIL] [a/ADDRESS] [t/TAG]…​` e.g.,`edit 2 n/James Lee e/jameslee@example.com`
-**Find** | `find KEYWORD [MORE_KEYWORDS]` e.g., `find James Jake`
-**List** | `list`
-**Help** | `help`
+| Action | Format, Examples |
+|----------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| **Add Person** | `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS i/INSURANCE_PACKAGE [t/TAG]…​` e.g., `add n/James Ho p/22224444 e/jamesho@example.com a/123, Clementi Rd, 1234665 i/package1 t/friend t/colleague` |
+| **Edit Person** | `edit INDEX [n/NAME] [p/PHONE_NUMBER] [e/EMAIL] [a/ADDRESS] [i/INSURANCE_PACKAGE] [t/TAG]…​` e.g.,`edit 2 n/James Lee e/jameslee@example.com` |
+| **Delete Person** | `delete INDEX` e.g., `delete 3` |
+| **Add Tag** | `addTag INDEX TAG` e.g., `addTag 3 owes money :p2` |
+| **Edit Tag** | `editTag INDEX TAG_NUMBER TAG` e.g.,`edit 2 3 friend of family` |
+| **Delete Tag** | `deleteTag INDEX TAG_NUMBER` e.g.,`deleteTag 3 2` |
+| **Add Package** | `addp i/PACKAGE_NAME d/PACKAGE_DESC` e.g., `addp i/Golden Package d/Covers everything!` |
+| **Edit Package** | `editp i/PACKAGE_NAME d/NEW_PACKAGE_DESC` e.g., `editp i/Golden Package d/Covers everything but death.` |
+| **Delete Package** | `deletep i/PACKAGE_NAME` e.g.,`deletep i/Golden Package` |
+| **Find** | `find FIELD KEYWORD [MORE_KEYWORDS]` e.g., `find n/James Jake` |
+| **Clip** | `clip n/NAME`or `clip INDEX` e.g., `clip n/John Doe` or `clip 1` |
+| **List** | `list` |
+| **List Packages** | `listp` |
+| **Help** | `help` |
+| **Import from CSV** | `import` |
+| **Export to CSV** | `export` |
+| **Clear** | `clear` |
+| **Undo** | `undo` |
+| **Redo** | `redo` |
+| **Sort by priority** | `prioList` |
+| **Exit** | `exit` |
+
+Fields:
+* `n/`: name
+* `p/`: phone number
+* `e/`: email
+* `a/`: address
+* `i/`: insurance package
+* `t/`: tags
+* `d/`: insurance package description
diff --git a/docs/_config.yml b/docs/_config.yml
index 6bd245d8f4e..0c71d1c6350 100644
--- a/docs/_config.yml
+++ b/docs/_config.yml
@@ -1,4 +1,4 @@
-title: "AB-3"
+title: "ClientConnect"
theme: minima
header_pages:
diff --git a/docs/_sass/minima/_base.scss b/docs/_sass/minima/_base.scss
index 0d3f6e80ced..9d14f0dd195 100644
--- a/docs/_sass/minima/_base.scss
+++ b/docs/_sass/minima/_base.scss
@@ -288,7 +288,7 @@ table {
text-align: center;
}
.site-header:before {
- content: "AB-3";
+ content: "ClientConnect";
font-size: 32px;
}
}
diff --git a/docs/diagrams/BetterModelClassDiagram.puml b/docs/diagrams/BetterModelClassDiagram.puml
index 5731f9cbaa1..e247012b952 100644
--- a/docs/diagrams/BetterModelClassDiagram.puml
+++ b/docs/diagrams/BetterModelClassDiagram.puml
@@ -9,7 +9,7 @@ AddressBook *-right-> "1" UniqueTagList
UniqueTagList -[hidden]down- UniquePersonList
UniqueTagList -[hidden]down- UniquePersonList
-UniqueTagList *-right-> "*" Tag
+UniqueTagList *-right-> "*" TagBetterModelClassDiagram.puml
UniquePersonList -right-> Person
Person -up-> "*" Tag
diff --git a/docs/diagrams/FieldContainsKeywordsPredicate.puml b/docs/diagrams/FieldContainsKeywordsPredicate.puml
new file mode 100644
index 00000000000..0bbcea28009
--- /dev/null
+++ b/docs/diagrams/FieldContainsKeywordsPredicate.puml
@@ -0,0 +1,30 @@
+@startuml
+!include style.puml
+skinparam arrowThickness 1.1
+skinparam arrowColor MODEL_COLOR
+skinparam classBackgroundColor MODEL_COLOR
+
+Class "{abstract}\nFieldContainsKeywordsPredicate" as FieldContainsKeywordsPredicate
+Class "<>\nPredicate"
+Class AddressContainsKeywordsPredicate
+Class EmailContainsKeywordsPredicate
+Class InsurancePackageContainsKeywordsPredicate
+Class NameContainsKeywordsPredicate
+Class CombineContainsKeywordsPredicate
+Class PhoneContainsKeywordsPredicate
+Class TagsContainsKeywordsPredicate
+
+FieldContainsKeywordsPredicate .up.|> "<>\nPredicate"
+
+FieldContainsKeywordsPredicate <|-left- AddressContainsKeywordsPredicate
+FieldContainsKeywordsPredicate <|-right- EmailContainsKeywordsPredicate
+FieldContainsKeywordsPredicate <|-- InsurancePackageContainsKeywordsPredicate
+FieldContainsKeywordsPredicate <|-- NameContainsKeywordsPredicate
+FieldContainsKeywordsPredicate <|-- CombineContainsKeywordsPredicate
+FieldContainsKeywordsPredicate <|-- PhoneContainsKeywordsPredicate
+FieldContainsKeywordsPredicate <|-- TagsContainsKeywordsPredicate
+
+CombineContainsKeywordsPredicate --> "1" "List"
+FieldContainsKeywordsPredicate "1..6" <-- "List"
+
+@enduml
diff --git a/docs/diagrams/FindCommand.puml b/docs/diagrams/FindCommand.puml
new file mode 100644
index 00000000000..073b086bb9e
--- /dev/null
+++ b/docs/diagrams/FindCommand.puml
@@ -0,0 +1,25 @@
+@startuml
+!include style.puml
+skinparam arrowThickness 1.1
+skinparam arrowColor MODEL_COLOR
+skinparam classBackgroundColor MODEL_COLOR
+
+class "{abstract}\nCommand" as Command
+class "FindCommand"
+class "CombineContainsPredicateKeywords"
+class "{abstract}\nFieldContainsKeywordsPredicate" as FieldContainsKeywordsPredicate
+class "<>\nPredicate"
+Class CommandResult
+
+package Model{
+Class HiddenModel #FFFFFF
+}
+
+
+Command .right.> Model
+"FindCommand" -up-|> Command
+FieldContainsKeywordsPredicate .up.|> "<>\nPredicate"
+"CombineContainsPredicateKeywords" --|> FieldContainsKeywordsPredicate
+"CombineContainsPredicateKeywords" -left-> "1" "FindCommand"
+Command .up.> CommandResult : produces >
+@enduml
diff --git a/docs/diagrams/FindSequenceDiagram.puml b/docs/diagrams/FindSequenceDiagram.puml
new file mode 100644
index 00000000000..7eef60411a4
--- /dev/null
+++ b/docs/diagrams/FindSequenceDiagram.puml
@@ -0,0 +1,68 @@
+@startuml
+!include style.puml
+
+box FindCommand Logic LOGIC_COLOR_T1
+participant ":LogicManager" as LogicManager LOGIC_COLOR
+participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR
+participant ":FindCommandParser" as FindCommandParser LOGIC_COLOR
+participant "f:FindCommand" as FindCommand LOGIC_COLOR
+participant ":CommandResult" as CommandResult LOGIC_COLOR
+end box
+
+box Model MODEL_COLOR_T1
+participant ":Model" as Model MODEL_COLOR
+end box
+
+[-> LogicManager : execute("find a/Clementi t/friends")
+activate LogicManager
+
+LogicManager -> AddressBookParser : parseCommand("find a/Clementi t/friends")
+activate AddressBookParser
+
+create FindCommandParser
+AddressBookParser -> FindCommandParser
+activate FindCommandParser
+
+FindCommandParser --> AddressBookParser
+deactivate FindCommandParser
+
+ref over AddressBookParser, FindCommandParser : parse find
+
+create FindCommand
+FindCommandParser -> FindCommand : FindCommand(c)
+activate FindCommand
+
+FindCommand --> FindCommandParser : f
+deactivate FindCommand
+
+FindCommandParser --> AddressBookParser : f
+deactivate FindCommandParser
+'Hidden arrow to position the destroy marker below the end of the activation bar.
+FindCommandParser -[hidden]-> AddressBookParser
+destroy FindCommandParser
+
+AddressBookParser --> LogicManager : f
+deactivate AddressBookParser
+
+LogicManager -> FindCommand : execute()
+activate FindCommand
+
+FindCommand -> Model : updateFilteredPersonList(predicate)
+activate Model
+
+Model --> FindCommand
+deactivate Model
+
+create CommandResult
+FindCommand -> CommandResult
+activate CommandResult
+
+CommandResult --> FindCommand
+deactivate CommandResult
+
+FindCommand --> LogicManager : result
+deactivate FindCommand
+
+[<--LogicManager
+deactivate LogicManager
+@enduml
diff --git a/docs/diagrams/ParseFindSequenceDiagram.puml b/docs/diagrams/ParseFindSequenceDiagram.puml
new file mode 100644
index 00000000000..830a8896e88
--- /dev/null
+++ b/docs/diagrams/ParseFindSequenceDiagram.puml
@@ -0,0 +1,54 @@
+@startuml
+!include style.puml
+
+box Parse Find Command Logic LOGIC_COLOR_T1
+participant ":FindCommandParser" as FindCommandParser LOGIC_COLOR
+participant "<>\nParserUtil" as ParserUtil LOGIC_COLOR
+participant ":ArgumentTokenizer" as ArgumentTokenizer LOGIC_COLOR
+participant "a:ArgumentMultiMap" as ArgumentMultimap LOGIC_COLOR
+participant "c:CombineContainsKeywordsPredicate" as CombineContainsKeywordsPredicate LOGIC_COLOR
+end box
+
+[-> FindCommandParser : parse(" a/Clementi t/friends")
+activate FindCommandParser
+
+FindCommandParser -> ArgumentTokenizer : tokenize(" a/Clementi t/friends", ...prefixes)
+activate ArgumentTokenizer
+
+ArgumentTokenizer -> ArgumentTokenizer : findAllPrefixPositions(" a/Clementi t/friends", prefixes)
+activate ArgumentTokenizer
+
+ArgumentTokenizer --> ArgumentTokenizer : positions
+deactivate ArgumentTokenizer
+
+ArgumentTokenizer -> ArgumentTokenizer : extractArguments(" a/Clementi t/friends, positions")
+activate ArgumentTokenizer
+
+create ArgumentMultimap
+ArgumentTokenizer -> ArgumentMultimap
+activate ArgumentMultimap
+
+ArgumentMultimap --> ArgumentTokenizer : a
+deactivate ArgumentMultimap
+
+deactivate ArgumentTokenizer
+deactivate ArgumentTokenizer
+
+ArgumentTokenizer --> FindCommandParser : a
+
+FindCommandParser -> ParserUtil : parseArgMap(a)
+activate ParserUtil
+
+ParserUtil --> FindCommandParser : predicatesList
+deactivate ParserUtil
+
+create CombineContainsKeywordsPredicate
+FindCommandParser -> CombineContainsKeywordsPredicate : CombineContainsKeywordsPredicate(predicatesList)
+activate CombineContainsKeywordsPredicate
+
+CombineContainsKeywordsPredicate --> FindCommandParser : c
+deactivate CombineContainsKeywordsPredicate
+
+FindCommandParser -> : FindCommand(c)
+
+@enduml
diff --git a/docs/diagrams/PersonsAndPriorities.puml b/docs/diagrams/PersonsAndPriorities.puml
new file mode 100644
index 00000000000..c178a53c4a8
--- /dev/null
+++ b/docs/diagrams/PersonsAndPriorities.puml
@@ -0,0 +1,26 @@
+@startuml
+!include style.puml
+skinparam arrowThickness 1.1
+skinparam arrowColor MODEL_COLOR
+skinparam classBackgroundColor MODEL_COLOR
+
+class Priority<> {
+ PRIORITY_1,
+ PRIORITY_2,
+ PRIORITY_3,
+ PRIORITY_4
+}
+
+class Tag {
+ String tagName;
+}
+
+show members
+AddressBook *-right-> "1" UniquePersonList
+UniquePersonList -right-> Person
+Person -up-> "*" TagList
+
+TagList *-left-> "*" Tag
+Tag -left-> Priority
+
+@enduml
diff --git a/docs/diagrams/SaveAddressBookToCsv.puml b/docs/diagrams/SaveAddressBookToCsv.puml
new file mode 100644
index 00000000000..0832f8abb9e
--- /dev/null
+++ b/docs/diagrams/SaveAddressBookToCsv.puml
@@ -0,0 +1,29 @@
+@startuml
+participant MainWindow
+activate MainWindow
+
+MainWindow -> MainWindow: handleSaveFile
+activate MainWindow
+
+MainWindow -> MainWindow: outputCsvFilePath
+deactivate MainWindow
+
+activate Logic
+MainWindow -> Logic: saveAddressBookToCsv
+
+activate Storage
+Logic -> Storage: saveAddressBookToCsv
+
+activate CsvAddressBookStorage
+Storage -> CsvAddressBookStorage: saveAddressBook
+
+activate CsvUtil
+CsvAddressBookStorage -> CsvUtil: saveCsvFile
+
+deactivate CsvUtil
+deactivate CsvAddressBookStorage
+deactivate Storage
+deactivate Logic
+deactivate MainWindow
+
+@enduml
diff --git a/docs/diagrams/UiClassDiagram.puml b/docs/diagrams/UiClassDiagram.puml
index 95473d5aa19..9c57409f246 100644
--- a/docs/diagrams/UiClassDiagram.puml
+++ b/docs/diagrams/UiClassDiagram.puml
@@ -10,6 +10,9 @@ Class "{abstract}\nUiPart" as UiPart
Class UiManager
Class MainWindow
Class HelpWindow
+Class PackageWindow
+Class PackageListPanel
+Class PackageCard
Class ResultDisplay
Class PersonListPanel
Class PersonCard
@@ -34,9 +37,12 @@ MainWindow *-down-> "1" CommandBox
MainWindow *-down-> "1" ResultDisplay
MainWindow *-down-> "1" PersonListPanel
MainWindow *-down-> "1" StatusBarFooter
+MainWindow --> "0..1" PackageWindow
MainWindow --> "0..1" HelpWindow
PersonListPanel -down-> "*" PersonCard
+PackageWindow -down-> "1" PackageListPanel
+PackageListPanel -down-> "*" PackageCard
MainWindow -left-|> UiPart
@@ -46,8 +52,12 @@ PersonListPanel --|> UiPart
PersonCard --|> UiPart
StatusBarFooter --|> UiPart
HelpWindow --|> UiPart
+PackageWindow --|> UiPart
+PackageListPanel --|> UiPart
+PackageCard --|> UiPart
PersonCard ..> Model
+PackageCard ..> Model
UiManager -right-> Logic
MainWindow -left-> Logic
diff --git a/docs/images/ClipCommandSeqMain.PNG b/docs/images/ClipCommandSeqMain.PNG
new file mode 100644
index 00000000000..672a4e3b23e
Binary files /dev/null and b/docs/images/ClipCommandSeqMain.PNG differ
diff --git a/docs/images/ClipCommandSeqSD.PNG b/docs/images/ClipCommandSeqSD.PNG
new file mode 100644
index 00000000000..8514e0be422
Binary files /dev/null and b/docs/images/ClipCommandSeqSD.PNG differ
diff --git a/docs/images/ClipCommandSeqSD2.PNG b/docs/images/ClipCommandSeqSD2.PNG
new file mode 100644
index 00000000000..845569642a6
Binary files /dev/null and b/docs/images/ClipCommandSeqSD2.PNG differ
diff --git a/docs/images/CommitActivityDiagram.png b/docs/images/CommitActivityDiagram.png
index c08c13f5c8b..2067d9fedc6 100644
Binary files a/docs/images/CommitActivityDiagram.png and b/docs/images/CommitActivityDiagram.png differ
diff --git a/docs/images/FieldContainsKeywordsPredicate.png b/docs/images/FieldContainsKeywordsPredicate.png
new file mode 100644
index 00000000000..ce3cbde8b9d
Binary files /dev/null and b/docs/images/FieldContainsKeywordsPredicate.png differ
diff --git a/docs/images/FindCommand.png b/docs/images/FindCommand.png
new file mode 100644
index 00000000000..d0848bb69f5
Binary files /dev/null and b/docs/images/FindCommand.png differ
diff --git a/docs/images/FindSequenceDiagram.png b/docs/images/FindSequenceDiagram.png
new file mode 100644
index 00000000000..f229bd9bf9b
Binary files /dev/null and b/docs/images/FindSequenceDiagram.png differ
diff --git a/docs/images/ParseFindSequenceDiagram.png b/docs/images/ParseFindSequenceDiagram.png
new file mode 100644
index 00000000000..661729429e3
Binary files /dev/null and b/docs/images/ParseFindSequenceDiagram.png differ
diff --git a/docs/images/PersonsAndPriorities.png b/docs/images/PersonsAndPriorities.png
new file mode 100644
index 00000000000..ce91241bb37
Binary files /dev/null and b/docs/images/PersonsAndPriorities.png differ
diff --git a/docs/images/PrioListCommandSeq.PNG b/docs/images/PrioListCommandSeq.PNG
new file mode 100644
index 00000000000..c2ecb56ee2c
Binary files /dev/null and b/docs/images/PrioListCommandSeq.PNG differ
diff --git a/docs/images/SaveAddressBookToCsv.png b/docs/images/SaveAddressBookToCsv.png
new file mode 100644
index 00000000000..1f76c0a4390
Binary files /dev/null and b/docs/images/SaveAddressBookToCsv.png differ
diff --git a/docs/images/Ui.png b/docs/images/Ui.png
index 5bd77847aa2..51568d3ebff 100644
Binary files a/docs/images/Ui.png and b/docs/images/Ui.png differ
diff --git a/docs/images/UiClassDiagram.png b/docs/images/UiClassDiagram.png
index 785e04dbab4..c49e9bac71e 100644
Binary files a/docs/images/UiClassDiagram.png and b/docs/images/UiClassDiagram.png differ
diff --git a/docs/images/UndoSequenceDiagram.png b/docs/images/UndoSequenceDiagram.png
index 6addcd3a8d9..8f84122094e 100644
Binary files a/docs/images/UndoSequenceDiagram.png and b/docs/images/UndoSequenceDiagram.png differ
diff --git a/docs/images/UndoState0.png b/docs/images/UndoState0.png
new file mode 100644
index 00000000000..c11968202c7
Binary files /dev/null and b/docs/images/UndoState0.png differ
diff --git a/docs/images/UndoState1.png b/docs/images/UndoState1.png
new file mode 100644
index 00000000000..16462a400c8
Binary files /dev/null and b/docs/images/UndoState1.png differ
diff --git a/docs/images/UndoState2.png b/docs/images/UndoState2.png
new file mode 100644
index 00000000000..3b59ea7e252
Binary files /dev/null and b/docs/images/UndoState2.png differ
diff --git a/docs/images/afterAddDamithUG.png b/docs/images/afterAddDamithUG.png
new file mode 100644
index 00000000000..d0e4bf62e1e
Binary files /dev/null and b/docs/images/afterAddDamithUG.png differ
diff --git a/docs/images/afterAddInsurancePackage1UG.png b/docs/images/afterAddInsurancePackage1UG.png
new file mode 100644
index 00000000000..23bab0f32ad
Binary files /dev/null and b/docs/images/afterAddInsurancePackage1UG.png differ
diff --git a/docs/images/afterAddInsurancePackage2UG.png b/docs/images/afterAddInsurancePackage2UG.png
new file mode 100644
index 00000000000..8375882f8e1
Binary files /dev/null and b/docs/images/afterAddInsurancePackage2UG.png differ
diff --git a/docs/images/afterAddTagUG.png b/docs/images/afterAddTagUG.png
new file mode 100644
index 00000000000..4f3ad242a40
Binary files /dev/null and b/docs/images/afterAddTagUG.png differ
diff --git a/docs/images/afterClip1UG.png b/docs/images/afterClip1UG.png
new file mode 100644
index 00000000000..d039a7bc6ae
Binary files /dev/null and b/docs/images/afterClip1UG.png differ
diff --git a/docs/images/afterDeleteInsurancePackage2UG.png b/docs/images/afterDeleteInsurancePackage2UG.png
new file mode 100644
index 00000000000..74caa951af2
Binary files /dev/null and b/docs/images/afterDeleteInsurancePackage2UG.png differ
diff --git a/docs/images/afterDeleteInsurancePackge1UG.png b/docs/images/afterDeleteInsurancePackge1UG.png
new file mode 100644
index 00000000000..ab302990a08
Binary files /dev/null and b/docs/images/afterDeleteInsurancePackge1UG.png differ
diff --git a/docs/images/afterDeleteTagUG.png b/docs/images/afterDeleteTagUG.png
new file mode 100644
index 00000000000..f7241a5a4aa
Binary files /dev/null and b/docs/images/afterDeleteTagUG.png differ
diff --git a/docs/images/afterDeleteUG.png b/docs/images/afterDeleteUG.png
new file mode 100644
index 00000000000..1e438f1ffb9
Binary files /dev/null and b/docs/images/afterDeleteUG.png differ
diff --git a/docs/images/afterEditInsurancePackage1UG.png b/docs/images/afterEditInsurancePackage1UG.png
new file mode 100644
index 00000000000..485be4118e8
Binary files /dev/null and b/docs/images/afterEditInsurancePackage1UG.png differ
diff --git a/docs/images/afterEditInsurancePackage2UG.png b/docs/images/afterEditInsurancePackage2UG.png
new file mode 100644
index 00000000000..fb21aa86eef
Binary files /dev/null and b/docs/images/afterEditInsurancePackage2UG.png differ
diff --git a/docs/images/afterEditTagUG.png b/docs/images/afterEditTagUG.png
new file mode 100644
index 00000000000..bbc895d2410
Binary files /dev/null and b/docs/images/afterEditTagUG.png differ
diff --git a/docs/images/afterEditUG.png b/docs/images/afterEditUG.png
new file mode 100644
index 00000000000..80b43a0a4be
Binary files /dev/null and b/docs/images/afterEditUG.png differ
diff --git a/docs/images/afterPrioList.PNG b/docs/images/afterPrioList.PNG
new file mode 100644
index 00000000000..f7016e03472
Binary files /dev/null and b/docs/images/afterPrioList.PNG differ
diff --git a/docs/images/beforeAddDamithUG.png b/docs/images/beforeAddDamithUG.png
new file mode 100644
index 00000000000..a331153f835
Binary files /dev/null and b/docs/images/beforeAddDamithUG.png differ
diff --git a/docs/images/beforeAddInsurancePackage.png b/docs/images/beforeAddInsurancePackage.png
new file mode 100644
index 00000000000..4aa1c89e121
Binary files /dev/null and b/docs/images/beforeAddInsurancePackage.png differ
diff --git a/docs/images/beforeAddTagUG.png b/docs/images/beforeAddTagUG.png
new file mode 100644
index 00000000000..efa3f325447
Binary files /dev/null and b/docs/images/beforeAddTagUG.png differ
diff --git a/docs/images/beforeClip1UG.png b/docs/images/beforeClip1UG.png
new file mode 100644
index 00000000000..6145fd804a9
Binary files /dev/null and b/docs/images/beforeClip1UG.png differ
diff --git a/docs/images/beforeDeleteInsurancePackageUG.png b/docs/images/beforeDeleteInsurancePackageUG.png
new file mode 100644
index 00000000000..2430d5df1cd
Binary files /dev/null and b/docs/images/beforeDeleteInsurancePackageUG.png differ
diff --git a/docs/images/beforeDeleteTagUG.png b/docs/images/beforeDeleteTagUG.png
new file mode 100644
index 00000000000..778986e9327
Binary files /dev/null and b/docs/images/beforeDeleteTagUG.png differ
diff --git a/docs/images/beforeDeleteUG.png b/docs/images/beforeDeleteUG.png
new file mode 100644
index 00000000000..fdfcadaecca
Binary files /dev/null and b/docs/images/beforeDeleteUG.png differ
diff --git a/docs/images/beforeEditInsurancePackageUG.png b/docs/images/beforeEditInsurancePackageUG.png
new file mode 100644
index 00000000000..7f2b3828e45
Binary files /dev/null and b/docs/images/beforeEditInsurancePackageUG.png differ
diff --git a/docs/images/beforeEditTagUG.png b/docs/images/beforeEditTagUG.png
new file mode 100644
index 00000000000..f4b8b2da22b
Binary files /dev/null and b/docs/images/beforeEditTagUG.png differ
diff --git a/docs/images/beforeEditUG.png b/docs/images/beforeEditUG.png
new file mode 100644
index 00000000000..8ffe7d695f0
Binary files /dev/null and b/docs/images/beforeEditUG.png differ
diff --git a/docs/images/beforePrioList.PNG b/docs/images/beforePrioList.PNG
new file mode 100644
index 00000000000..ed35345621c
Binary files /dev/null and b/docs/images/beforePrioList.PNG differ
diff --git a/docs/images/clearUG.png b/docs/images/clearUG.png
new file mode 100644
index 00000000000..9d6b4e85e73
Binary files /dev/null and b/docs/images/clearUG.png differ
diff --git a/docs/images/clipJohnDoe.PNG b/docs/images/clipJohnDoe.PNG
new file mode 100644
index 00000000000..45e5ca9f13b
Binary files /dev/null and b/docs/images/clipJohnDoe.PNG differ
diff --git a/docs/images/findAlexDavidResultUG.png b/docs/images/findAlexDavidResultUG.png
new file mode 100644
index 00000000000..4f6f6f20be0
Binary files /dev/null and b/docs/images/findAlexDavidResultUG.png differ
diff --git a/docs/images/findInsurancePackageUG.png b/docs/images/findInsurancePackageUG.png
new file mode 100644
index 00000000000..46892e8a9ff
Binary files /dev/null and b/docs/images/findInsurancePackageUG.png differ
diff --git a/docs/images/findUndecidedDavid.png b/docs/images/findUndecidedDavid.png
new file mode 100644
index 00000000000..3d1c7561862
Binary files /dev/null and b/docs/images/findUndecidedDavid.png differ
diff --git a/docs/images/helpButtonUG.png b/docs/images/helpButtonUG.png
new file mode 100644
index 00000000000..fa51a29dd8d
Binary files /dev/null and b/docs/images/helpButtonUG.png differ
diff --git a/docs/images/helpWindowUG.png b/docs/images/helpWindowUG.png
new file mode 100644
index 00000000000..05b7103f639
Binary files /dev/null and b/docs/images/helpWindowUG.png differ
diff --git a/docs/images/jetrz.png b/docs/images/jetrz.png
new file mode 100644
index 00000000000..c2956c7f32f
Binary files /dev/null and b/docs/images/jetrz.png differ
diff --git a/docs/images/jiewei98.png b/docs/images/jiewei98.png
new file mode 100644
index 00000000000..0d00c0dafc2
Binary files /dev/null and b/docs/images/jiewei98.png differ
diff --git a/docs/images/leeyiheng12.png b/docs/images/leeyiheng12.png
new file mode 100644
index 00000000000..806fca6d51c
Binary files /dev/null and b/docs/images/leeyiheng12.png differ
diff --git a/docs/images/listAllClientsUG.png b/docs/images/listAllClientsUG.png
new file mode 100644
index 00000000000..8ea450d414a
Binary files /dev/null and b/docs/images/listAllClientsUG.png differ
diff --git a/docs/images/listInsurancePackage1UG.png b/docs/images/listInsurancePackage1UG.png
new file mode 100644
index 00000000000..3d1fde2b9a6
Binary files /dev/null and b/docs/images/listInsurancePackage1UG.png differ
diff --git a/docs/images/listInsurancePackage2UG.png b/docs/images/listInsurancePackage2UG.png
new file mode 100644
index 00000000000..9a2a367eb46
Binary files /dev/null and b/docs/images/listInsurancePackage2UG.png differ
diff --git a/docs/images/michaelseyo.png b/docs/images/michaelseyo.png
new file mode 100644
index 00000000000..1df3a4c5c79
Binary files /dev/null and b/docs/images/michaelseyo.png differ
diff --git a/docs/images/montypython28.png b/docs/images/montypython28.png
new file mode 100644
index 00000000000..5715cffdca9
Binary files /dev/null and b/docs/images/montypython28.png differ
diff --git a/docs/images/packageButtonUG.png b/docs/images/packageButtonUG.png
new file mode 100644
index 00000000000..d81f83b825f
Binary files /dev/null and b/docs/images/packageButtonUG.png differ
diff --git a/docs/images/packageWindowUG.png b/docs/images/packageWindowUG.png
new file mode 100644
index 00000000000..f5eb97b842e
Binary files /dev/null and b/docs/images/packageWindowUG.png differ
diff --git a/docs/images/redo1UG.png b/docs/images/redo1UG.png
new file mode 100644
index 00000000000..c3ed15d32ba
Binary files /dev/null and b/docs/images/redo1UG.png differ
diff --git a/docs/images/redo2UG.png b/docs/images/redo2UG.png
new file mode 100644
index 00000000000..276ec0f972f
Binary files /dev/null and b/docs/images/redo2UG.png differ
diff --git a/docs/images/undo1UG.png b/docs/images/undo1UG.png
new file mode 100644
index 00000000000..55641739e4b
Binary files /dev/null and b/docs/images/undo1UG.png differ
diff --git a/docs/images/undo2UG.png b/docs/images/undo2UG.png
new file mode 100644
index 00000000000..b45aaf42cfc
Binary files /dev/null and b/docs/images/undo2UG.png differ
diff --git a/docs/index.md b/docs/index.md
index 7601dbaad0d..2826e44ff6f 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -1,18 +1,18 @@
---
layout: page
-title: AddressBook Level-3
+title: ClientConnect
---
-[![CI Status](https://github.com/se-edu/addressbook-level3/workflows/Java%20CI/badge.svg)](https://github.com/se-edu/addressbook-level3/actions)
-[![codecov](https://codecov.io/gh/se-edu/addressbook-level3/branch/master/graph/badge.svg)](https://codecov.io/gh/se-edu/addressbook-level3)
+[![CI Status](https://github.com/AY2122S2-CS2103-W17-3/tp/workflows/Java%20CI/badge.svg)](https://github.com/AY2122S2-CS2103-W17-3/tp/actions)
+[![codecov](https://codecov.io/gh/AY2122S2-CS2103-W17-3/tp/branch/master/graph/badge.svg?token=P5HULHGOU5)](https://codecov.io/gh/AY2122S2-CS2103-W17-3/tp)
![Ui](images/Ui.png)
-**AddressBook is a desktop application for managing your contact details.** While it has a GUI, most of the user interactions happen using a CLI (Command Line Interface).
+**ClientConnect is a desktop application for managing your clients' details and insurance packages.** While it has a GUI, most of the user interactions happen using a CLI (Command Line Interface).
-* If you are interested in using AddressBook, head over to the [_Quick Start_ section of the **User Guide**](UserGuide.html#quick-start).
-* If you are interested about developing AddressBook, the [**Developer Guide**](DeveloperGuide.html) is a good place to start.
+Add Tags with varying levels of priorities to keep track of utmost important tasks to utilize your time efficiently, sort your clients by priority so you won't miss out urgent tasks to be done, conveniently import and export your client contacts, and easily find the client details you want through our `find` command.
+Not to mention we have convenience built-in for you: allowing you to undo and redo commands, and go back to what you previously typed just with a click of an up or down key! Many other features available in the User Guide.
**Acknowledgements**
diff --git a/docs/team/jetrz.md b/docs/team/jetrz.md
new file mode 100644
index 00000000000..df680c4d6f6
--- /dev/null
+++ b/docs/team/jetrz.md
@@ -0,0 +1,48 @@
+---
+layout: page
+title: Joshua Teo's Project Portfolio Page
+---
+
+### Project: ClientConnect
+
+ClientConnect is a desktop client address book application for insurance agents to manage and keep track of their clientele seamlessly and efficiently. Custom tailored to suit the needs of insurance agents, ClientConnect surpasses other similar applications by offering features such as grouping of clients by packages, keyboard shortcuts, a gorgeous GUI, and more.
+
+Given below are my contributions to the project.
+
+* **New Feature**:
+ * ***clip*** command ([\#34](https://github.com/AY2122S2-CS2103-W17-3/tp/pull/34), [\#70](https://github.com/AY2122S2-CS2103-W17-3/tp/pull/70))
+ * Functionality: Allows efficient copying of a client's information onto the user's clipboard.
+ * Justification: It is tedious to manually copy out all of a client's details as they can be long at times.
+ * Enhanced in v1.3 to allow users to specify either name or index for the client they wish to copy.
+ * `NameExistsPredicate` class to compare & filter client list with provided `Name`, for facilitation of this command.
+ * Testing for all relevant classes.
+ * ***prioList*** command ([\#64](https://github.com/AY2122S2-CS2103-W17-3/tp/pull/64))
+ * Functionality: Allows user to sort and display their client list by the priority level of their tags.
+ * Justification: Makes it easy for the user to view which clients should be prioritised.
+ * `TagPriorityComparator` class to compare `Persons` and their tag priority level, for facilitation of this command.
+ * Testing for all relevant classes.
+
+* **Enhancements to existing features**:
+ * Enhanced the current `ModelManager` with a method to sort its client list with a given comparator.
+
+* **Code contributed**: [RepoSense link](https://nus-cs2103-ay2122s2.github.io/tp-dashboard/?search=jetrz&sort=groupTitle&sortWithin=title&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=true&checkedFileTypes=docs~functional-code~test-code~other&since=2022-02-18&tabOpen=true&tabType=authorship&tabAuthor=jetrz&tabRepo=AY2122S2-CS2103-W17-3%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=&authorshipIsBinaryFileTypeChecked=false)
+
+* **Documentation**:
+ * User Guide:
+ * Added description and instructions for clip and prioList command, supported with clear visuals. ([\#162](https://github.com/AY2122S2-CS2103-W17-3/tp/pull/162))
+ * Developer Guide:
+ * Under [implementation section](../DeveloperGuide.md#Implementation), added descriptions on how clip and prioList works, supported with sequence diagrams. ([\#54](https://github.com/AY2122S2-CS2103-W17-3/tp/pull/54), [\#162](https://github.com/AY2122S2-CS2103-W17-3/tp/pull/162))
+ * Updated section on use cases. ([\#176](https://github.com/AY2122S2-CS2103-W17-3/tp/pull/176))
+ * Added section on product scope. ([\#16](https://github.com/AY2122S2-CS2103-W17-3/tp/pull/16))
+ * Added section on user stories. ([\#16](https://github.com/AY2122S2-CS2103-W17-3/tp/pull/16))
+ * Added section on non-functional requirements. ([\#16](https://github.com/AY2122S2-CS2103-W17-3/tp/pull/16))
+ * Improved overall styling.
+
+* **Project management**:
+ * Assigned & closed [issues](https://github.com/AY2122S2-CS2103-W17-3/tp/issues?q=is%3Aissue+is%3Aclosed+assignee%3Ajetrz)
+ * Reviewed and merged PRs ([\#32](https://github.com/AY2122S2-CS2103-W17-3/tp/pull/32), [\#65](https://github.com/AY2122S2-CS2103-W17-3/tp/pull/65))
+ * Created and assigned tags
+ * Submitted all assigned deliverables by given milestone deadlines
+
+* **Community**:
+ * Reported [bugs](https://github.com/jetrz/ped/issues) for other teams
diff --git a/docs/team/jiewei98.md b/docs/team/jiewei98.md
new file mode 100644
index 00000000000..6146ef62fed
--- /dev/null
+++ b/docs/team/jiewei98.md
@@ -0,0 +1,40 @@
+---
+layout: page
+title: Tan Jie Wei's Project Portfolio Page
+---
+
+### Project: ClientConnect
+
+ClientConnect is a desktop client address book application for insurance agents to manage and keep track of their clientele seamlessly and efficiently. Custom tailored to suit the needs of insurance agents, ClientConnect surpasses other similar applications by offering features such as grouping of clients by packages, keyboard shortcuts, a gorgeous GUI, and more.
+
+Given below are my contributions to the project.
+
+* **New Feature**:
+ * ***undo/redo*** command ([\#40](https://github.com/AY2122S2-CS2103-W17-3/tp/pull/40), [\#47](https://github.com/AY2122S2-CS2103-W17-3/tp/pull/47), [\#48](https://github.com/AY2122S2-CS2103-W17-3/tp/pull/48), [\#65](https://github.com/AY2122S2-CS2103-W17-3/tp/pull/65), [\#159](https://github.com/AY2122S2-CS2103-W17-3/tp/pull/159), [\#168](https://github.com/AY2122S2-CS2103-W17-3/tp/pull/168), [\#178](https://github.com/AY2122S2-CS2103-W17-3/tp/pull/178))
+ * Functionality: Allows user to undo any accidental changes made to the AddressBook.
+ * Justification: It is tedious to have to add all of a client's details from scratch if the user accidentally deleted a client from the AddressBook. User might not even remember client's details to re-add them back into the AddressBook.
+ * Enhanced in v1.3 to allow users to undo an accidental clearing of the AddressBook and can also undo/redo changes made to tags.
+ * `UndoRedoStorage` class to store AddressBook states, to allow for undo-ing and redo-ing changes made to the AddressBook
+ * Method in `ModelManager` to make a deep copy of AddressBook state for storage in `UndoRedoStorage`.
+ * Testing for all relevant classes.
+
+* **Code contributed**: [RepoSense link](https://nus-cs2103-ay2122s2.github.io/tp-dashboard/?search=jiewei98&breakdown=true&sort=groupTitle&sortWithin=title&since=2022-02-18&timeframe=commit&mergegroup=&groupSelect=groupByRepos&checkedFileTypes=docs~functional-code~test-code~other)
+
+* **Documentation**:
+ * User Guide:
+ * Added description and instructions for undo/redo command, supported with clear visuals. ([\#163](https://github.com/AY2122S2-CS2103-W17-3/tp/pull/163))
+ * Developer Guide:
+ * Under [implementation section](../DeveloperGuide.md#Implementation), added descriptions on how undo/redo works, supported with sequence diagrams. ([\#57](https://github.com/AY2122S2-CS2103-W17-3/tp/pull/57), [\#164](https://github.com/AY2122S2-CS2103-W17-3/tp/pull/164))
+ * Updated section on use cases. ([\#18](https://github.com/AY2122S2-CS2103-W17-3/tp/pull/18))
+ * Added section on product scope.
+ * Added section on user stories.
+ * Added section on non-functional requirements.
+ * Improved overall styling.
+
+* **Project management**:
+ * Assigned & closed issues
+ * Reviewed and merged PRs ([\#16](https://github.com/AY2122S2-CS2103-W17-3/tp/pull/16))
+ * Submitted all assigned deliverables by given milestone deadlines
+
+* **Community**:
+ * Reported [bugs](https://github.com/jiewei98/ped/issues) for other teams
diff --git a/docs/team/johndoe.md b/docs/team/johndoe.md
deleted file mode 100644
index 773a07794e2..00000000000
--- a/docs/team/johndoe.md
+++ /dev/null
@@ -1,46 +0,0 @@
----
-layout: page
-title: John Doe's Project Portfolio Page
----
-
-### Project: AddressBook Level 3
-
-AddressBook - Level 3 is a desktop address book application used for teaching Software Engineering principles. The user interacts with it using a CLI, and it has a GUI created with JavaFX. It is written in Java, and has about 10 kLoC.
-
-Given below are my contributions to the project.
-
-* **New Feature**: Added the ability to undo/redo previous commands.
- * What it does: allows the user to undo all previous commands one at a time. Preceding undo commands can be reversed by using the redo command.
- * Justification: This feature improves the product significantly because a user can make mistakes in commands and the app should provide a convenient way to rectify them.
- * Highlights: This enhancement affects existing commands and commands to be added in future. It required an in-depth analysis of design alternatives. The implementation too was challenging as it required changes to existing commands.
- * Credits: *{mention here if you reused any code/ideas from elsewhere or if a third-party library is heavily used in the feature so that a reader can make a more accurate judgement of how much effort went into the feature}*
-
-* **New Feature**: Added a history command that allows the user to navigate to previous commands using up/down keys.
-
-* **Code contributed**: [RepoSense link]()
-
-* **Project management**:
- * Managed releases `v1.3` - `v1.5rc` (3 releases) on GitHub
-
-* **Enhancements to existing features**:
- * Updated the GUI color scheme (Pull requests [\#33](), [\#34]())
- * Wrote additional tests for existing features to increase coverage from 88% to 92% (Pull requests [\#36](), [\#38]())
-
-* **Documentation**:
- * User Guide:
- * Added documentation for the features `delete` and `find` [\#72]()
- * Did cosmetic tweaks to existing documentation of features `clear`, `exit`: [\#74]()
- * Developer Guide:
- * Added implementation details of the `delete` feature.
-
-* **Community**:
- * PRs reviewed (with non-trivial review comments): [\#12](), [\#32](), [\#19](), [\#42]()
- * Contributed to forum discussions (examples: [1](), [2](), [3](), [4]())
- * Reported bugs and suggestions for other teams in the class (examples: [1](), [2](), [3]())
- * Some parts of the history feature I added was adopted by several other class mates ([1](), [2]())
-
-* **Tools**:
- * Integrated a third party library (Natty) to the project ([\#42]())
- * Integrated a new Github plugin (CircleCI) to the team repo
-
-* _{you can add/remove categories in the list above}_
diff --git a/docs/team/leeyiheng12.md b/docs/team/leeyiheng12.md
new file mode 100644
index 00000000000..de1fa107421
--- /dev/null
+++ b/docs/team/leeyiheng12.md
@@ -0,0 +1,60 @@
+---
+layout: page
+title: Lee Yi Heng's Project Portfolio Page
+---
+
+### Project: ClientConnect
+
+ClientConnect is a desktop client address book application for insurance agents to manage and keep track of their clientele seamlessly and efficiently. Custom tailored to suit the needs of insurance agents, ClientConnect surpasses other similar applications by offering features such as grouping of clients by packages, keyboard shortcuts, a gorgeous GUI, and more.
+
+Given below are my contributions to the project.
+
+* **New Feature**: Added insurance packages as a separate entity, similar to how persons and their details can be stored in the application ([\#69](https://github.com/AY2122S2-CS2103-W17-3/tp/pull/69))
+ * What it includes:
+ * The ability to store insurance packages into the application
+ * The ability to add, edit, and delete insurance packages
+ * The ability to view insurance packages in a new window
+ * Justification:
+ * This allows for the application to be more than just one that stores contact information.
+ * This allows for links between people and packages: for instance, if the user adds a person and puts down the name of an insurance package, if that package is not in the application yet, it will be automatically added
+ * Highlights:
+ * This enhancement required a good understanding of the architecture of the initial AddressBook, as the implementation of the storage and features of the packages in the app is very similar to that of the persons.
+
+* **New Feature**: Added CSV file storage support ([\#32](https://github.com/AY2122S2-CS2103-W17-3/tp/pull/32), [\#35](https://github.com/AY2122S2-CS2103-W17-3/tp/pull/35))
+ * What it includes:
+ * The ability to export clients' details from the application to a CSV file
+ * The ability to import clients' details from an exported CSV file into the application
+ * Justification:
+ * This allows for sharing of contacts between different users, and allows for storing of different copies of contacts, if necessary.
+
+* **New Feature**: Added the storage of past user commands ([\#37](https://github.com/AY2122S2-CS2103-W17-3/tp/pull/37))
+ * What it includes:
+ * The ability to cycle through previous commands entered by the user, using the up and down arrow keys
+ * Credits:
+ * Inspired from a similar functionality on the command line
+
+* **Code contributed**: [RepoSense link](https://nus-cs2103-ay2122s2.github.io/tp-dashboard/?search=leeyiheng12&breakdown=true)
+
+* **Project management**:
+ * Aided in the assignment of features and issues throughout the project.
+
+* **Enhancements to existing features**:
+ * Enhanced the current `ModelManager` to include the logic and storage of insurance packages.
+
+* **Documentation**:
+ * User Guide:
+ * Updated User Guide to include more screenshots of ClientConnect, in the initial drafts of the User Guide
+ * Added documentation for the features I added, such as adding, editing, deleting, and viewing packages,
+ and importing from and exporting to CSV
+
+ * Developer Guide:
+ * Added implementation details, which included UML diagrams, for the feature regarding importing from and exporting to CSV
+
+ * README.md:
+ * Added a basic README for ClientConnect
+
+* **Community**:
+ * Reviewed PRs of other team members, some of which concerned important/major features
+ (e.g. [here](https://github.com/AY2122S2-CS2103-W17-3/tp/pull/38))
+ * Reported bugs for another project (see [this](https://github.com/AY2122S2-CS2103-F09-2/tp/issues/190) for an example)
+ * Closed over 30 issues on Github Issues (such as [this](https://github.com/AY2122S2-CS2103-W17-3/tp/issues/96)) with comments provided
diff --git a/docs/team/michaelseyo.md b/docs/team/michaelseyo.md
new file mode 100644
index 00000000000..05c09dfa701
--- /dev/null
+++ b/docs/team/michaelseyo.md
@@ -0,0 +1,50 @@
+---
+layout: page
+title: Michael Low's Project Portfolio Page
+---
+
+### Project: ClientConnect
+
+ClientConnect is a desktop client address book application for insurance agents to manage and keep track of their clientele seamlessly and efficiently. Custom tailored to suit the needs of insurance agents, ClientConnect surpasses other similar applications by offering features such as grouping of clients by packages, keyboard shortcuts, a gorgeous GUI, and more.
+
+Given below are my contributions to the project.
+
+* **New Feature**:
+ * Multi-field and multi-keyword FindCommand ([#38](https://github.com/AY2122S2-CS2103-W17-3/tp/pull/38))
+ * What it does: Allows the insurance agent to find clients based off a combination of multiple fields and multiple keywords
+ * Justification: This feature improves the product significantly as insurance agents can better fine-tune their findings with more specificity and complexity of find.
+ * Highlights: The enhancement required the creation of `FieldContainsKeywordsPredicate` class which was an abstract class that the other containsKeywordsPredicate inherited from. The `CombineContainsKeywordsPredicate` is the main driving predicate for enabling the multi-field, multi-keyword find.
+ * PackageWindow UI ([#68](https://github.com/AY2122S2-CS2103-W17-3/tp/pull/68), [#71](https://github.com/AY2122S2-CS2103-W17-3/tp/pull/71))
+ * What it does: A new pop-up window for Insurance Agents to view the details of insurance packages.
+ * Justification: Have quick access to information of insurance packages in the event they have to contact the client.
+ * Highlights: Makes use of a PackageListPanel and a PackageCard as sub-components to display the scrollable window of insurance packages.
+
+* **Code contributed**: [RepoSense link](https://nus-cs2103-ay2122s2.github.io/tp-dashboard/?search=michaelseyo&breakdown=true&sort=groupTitle&sortWithin=title&since=2022-02-18&timeframe=commit&mergegroup=&groupSelect=groupByRepos&checkedFileTypes=docs~functional-code~test-code~other)
+
+* **Project management**:
+ * Assigned issues to teammates and closed issues.
+ * Merged and made PRs.
+
+* **Enhancements to existing features**:
+ * Update the color scheme to be light-theme (Pull request [#42](https://github.com/AY2122S2-CS2103-W17-3/tp/pull/42), [#44](https://github.com/AY2122S2-CS2103-W17-3/tp/pull/44))
+
+* **Documentation**:
+ * User Guide:
+ * Improved the introduction of ClientConnect. ([#151](https://github.com/AY2122S2-CS2103-W17-3/tp/pull/151))
+ * Updated documentation for FindCommand with relevant examples and screenshots. ([#53](https://github.com/AY2122S2-CS2103-W17-3/tp/pull/53/commits/a9368134f1575f2b3422aa5e712631a918638b68))
+ * Updated Packages Tab documentation with screenshots. ([#74](https://github.com/AY2122S2-CS2103-W17-3/tp/pull/74/commits/92e06a85b0c6dbc032b01d0890a504ba07e7c4a1))
+ * Cleaned up overall user guide formatting wise. ([#74](https://github.com/AY2122S2-CS2103-W17-3/tp/pull/74/commits/df135aa0d2e05c05159a0ae6a90cc7048ed594e7))
+ * Developer Guide:
+ * Created additional details for the UI class diagram to include PackageWindow and its relevant components. ([#161](https://github.com/AY2122S2-CS2103-W17-3/tp/pull/161/commits/d0e01bae4e10cb802606851c24090a69d84750a6), [#161](https://github.com/AY2122S2-CS2103-W17-3/tp/pull/161/commits/db7c37804d264a3c3f7f907610fa9d30060657fd))
+ * Added further description on PackageWindow and HelpWindow under the UI component. ([#161](https://github.com/AY2122S2-CS2103-W17-3/tp/pull/161/commits/1738f5de632beb1d10deb4f81cb2d86d1f6d69c9))
+ * Created class diagrams of new Predicate created and FindCommand execution. ([#161](https://github.com/AY2122S2-CS2103-W17-3/tp/pull/161/commits/1738f5de632beb1d10deb4f81cb2d86d1f6d69c9))
+ * Created sequence diagrams for FindCommand to demonstrate the execution of the improved implementation. ([#161](https://github.com/AY2122S2-CS2103-W17-3/tp/pull/161/commits/1738f5de632beb1d10deb4f81cb2d86d1f6d69c9), [#169](https://github.com/AY2122S2-CS2103-W17-3/tp/pull/169))
+ * Added more descriptions on the implementation of the improved FindCommand, including the new Predicate classes created. ([#53](https://github.com/AY2122S2-CS2103-W17-3/tp/pull/53/commits/0a532ec87a861f584267a14b590140ae17589834))
+ * Added MSS for `find` to highlight the various scenarios possible. ([#181](https://github.com/AY2122S2-CS2103-W17-3/tp/pull/181))
+
+* **Community**:
+ * Provided reviews for teammates PRs. (Pull request [#35](https://github.com/AY2122S2-CS2103-W17-3/tp/pull/35))
+ * Reported bugs for other teams in class ([#19](https://github.com/michaelseyo/ped/issues/19), [#21](https://github.com/michaelseyo/ped/issues/21), [#12](https://github.com/michaelseyo/ped/issues/12))
+ * Contributed to the User Guide to document the target user profile and ClientConnect's potential appeal to them. ([#151](https://github.com/AY2122S2-CS2103-W17-3/tp/pull/151/commits/8c189c296e0515fe5835d4f75872a72dd3522653))
+ * Standardise the use of "Client" instead of "Person" in the app display messages. ([#151](https://github.com/AY2122S2-CS2103-W17-3/tp/pull/151))
+ * Changed icon of ClientConnect app.
diff --git a/docs/team/montypython28.md b/docs/team/montypython28.md
new file mode 100644
index 00000000000..67104b2e549
--- /dev/null
+++ b/docs/team/montypython28.md
@@ -0,0 +1,48 @@
+---
+layout: page
+title: Samyukta Sounderraman's Project Portfolio Page
+---
+
+### Project: ClientConnect
+
+ClientConnect is a desktop client address book application for insurance agents to manage and keep track of their clientele seamlessly and efficiently. Custom tailored to suit the needs of insurance agents, ClientConnect surpasses other similar applications by offering features such as grouping of clients by packages, keyboard shortcuts, a gorgeous GUI, and more.
+
+Given below are my contributions to the project.
+
+* **New Feature**: Added the ability for a tag to have priority levels
+ * What it does: allows the user to assign a priority to a tag. Tags with different priorities look completely different in the GUI as well (red for highest priority, blue for least).
+ * Justification: users may use the tags feature to store details about their clients, and potentially upcoming tasks (e.g. owes money soon, or hasn't signed contract yet). Not all of these are at the same level. Assigning a tag a priority level serves as a visual reminder of the importance/urgency of the various details specified in the tags.
+ * Highlights: This enhancement paved the way for the `prioList` command to be added later. It required an in-depth analysis of various design implementations. The implementation was challenging as it required changes to existing commands, parsers and storage methods.
+
+
+* **New Feature**: Added the `addTag`, `editTag` and `deleteTag` commands
+ * What it does: allows the user to add a new tag, edit a current tag or delete an existing tag in a convenient manner.
+ * Justification: users may want to have many tags to remember miscellaneous details about their clients. Previously, the only way tags could be changed is with the `edit` command, that requires the entire list of tags to be typed out again. The three new commands give users a much more convenient way to make small changes if they want.
+ * Highlights: This enhancement required the addition of new parsers, in addition to new commands. It also required a change in the implementation on how to store tags, as there was an additional requirement for them to be ordered. This in turn required several changes to existing commands, parsers and test cases.
+
+* **Code contributed**: [RepoSense link](https://nus-cs2103-ay2122s2.github.io/tp-dashboard/?search=montypython28&breakdown=true&sort=groupTitle&sortWithin=title&since=2022-02-18&timeframe=commit&mergegroup=&groupSelect=groupByRepos&checkedFileTypes=docs~functional-code~test-code~other)
+
+* **Project management**:
+ * Set up team organisation and repository.
+ * Took responsibility for opening, maintaining and closing milestones `v1.2` to `v1.4`.
+ * Coordinated generation and management of releases `v1.2` to `v1.4).
+
+* **Enhancements to existing features**:
+ * Added InsurancePackage field to Person class, and updated commands accordingly (Pull Request [#20](https://github.com/AY2122S2-CS2103-W17-3/tp/pull/20))
+ * Made UI changes to the colours of tags, coordinating their colours with their priority levels (Pull Request [#45](https://github.com/AY2122S2-CS2103-W17-3/tp/pull/45))
+
+* **Documentation**:
+ * User Guide:
+ * Updated information about tags and priorities.
+ * Updated information about `addTag`, `editTag` and `deleteTag` commands.
+ * Reorganised structure and order of command descriptions.
+ * Developer Guide:
+ * Updated information about current team members and their responsibilities.
+ * Updated ModelClassDiagram and the relevant section to include changes we made to the Model.
+ * Added to user stories and use cases regarding the manipulation of individual tags.
+
+* **Community**:
+ * PRs reviewed with comments: [#47](https://github.com/AY2122S2-CS2103-W17-3/tp/pull/47), [#54](https://github.com/AY2122S2-CS2103-W17-3/tp/pull/54)
+ * Contributed to forum discussions ([1](https://github.com/nus-cs2103-AY2122S2/forum/issues/110#issuecomment-1030581324), [2](https://github.com/nus-cs2103-AY2122S2/forum/issues/220))
+ * Reported bugs for other teams (e.g. [1](https://github.com/MontyPython28/ped/issues/5), [2](https://github.com/MontyPython28/ped/issues/3), [3](https://github.com/MontyPython28/ped/issues/1))
+
diff --git a/src/main/java/seedu/address/MainApp.java b/src/main/java/seedu/address/MainApp.java
index 4133aaa0151..21b01fd9043 100644
--- a/src/main/java/seedu/address/MainApp.java
+++ b/src/main/java/seedu/address/MainApp.java
@@ -15,14 +15,18 @@
import seedu.address.commons.util.StringUtil;
import seedu.address.logic.Logic;
import seedu.address.logic.LogicManager;
-import seedu.address.model.AddressBook;
+import seedu.address.model.InsurancePackagesSet;
import seedu.address.model.Model;
import seedu.address.model.ModelManager;
import seedu.address.model.ReadOnlyAddressBook;
import seedu.address.model.ReadOnlyUserPrefs;
import seedu.address.model.UserPrefs;
+import seedu.address.model.person.Person;
import seedu.address.model.util.SampleDataUtil;
import seedu.address.storage.AddressBookStorage;
+import seedu.address.storage.CommandStorage;
+import seedu.address.storage.CsvInsurancePackagesStorage;
+import seedu.address.storage.InsurancePackagesStorage;
import seedu.address.storage.JsonAddressBookStorage;
import seedu.address.storage.JsonUserPrefsStorage;
import seedu.address.storage.Storage;
@@ -36,7 +40,7 @@
*/
public class MainApp extends Application {
- public static final Version VERSION = new Version(0, 2, 0, true);
+ public static final Version VERSION = new Version(1, 3, 0, true);
private static final Logger logger = LogsCenter.getLogger(MainApp.class);
@@ -45,6 +49,7 @@ public class MainApp extends Application {
protected Storage storage;
protected Model model;
protected Config config;
+ protected CommandStorage commandStorage;
@Override
public void init() throws Exception {
@@ -57,13 +62,17 @@ public void init() throws Exception {
UserPrefsStorage userPrefsStorage = new JsonUserPrefsStorage(config.getUserPrefsFilePath());
UserPrefs userPrefs = initPrefs(userPrefsStorage);
AddressBookStorage addressBookStorage = new JsonAddressBookStorage(userPrefs.getAddressBookFilePath());
- storage = new StorageManager(addressBookStorage, userPrefsStorage);
+ InsurancePackagesStorage insurancePackagesStorage = new CsvInsurancePackagesStorage(
+ userPrefs.getInsurancePackagesFilePath());
+ storage = new StorageManager(addressBookStorage, userPrefsStorage, insurancePackagesStorage);
+
+ commandStorage = new CommandStorage();
initLogging(config);
model = initModelManager(storage, userPrefs);
- logic = new LogicManager(model, storage);
+ logic = new LogicManager(model, storage, commandStorage);
ui = new UiManager(logic);
}
@@ -74,6 +83,8 @@ public void init() throws Exception {
* or an empty address book will be used instead if errors occur when reading {@code storage}'s address book.
*/
private Model initModelManager(Storage storage, ReadOnlyUserPrefs userPrefs) {
+
+ // initial AddressBook data
Optional addressBookOptional;
ReadOnlyAddressBook initialData;
try {
@@ -83,14 +94,37 @@ private Model initModelManager(Storage storage, ReadOnlyUserPrefs userPrefs) {
}
initialData = addressBookOptional.orElseGet(SampleDataUtil::getSampleAddressBook);
} catch (DataConversionException e) {
- logger.warning("Data file not in the correct format. Will be starting with an empty AddressBook");
- initialData = new AddressBook();
+ logger.warning("Data file not in the correct format. Will be starting with a sample AddressBook");
+ initialData = SampleDataUtil.getSampleAddressBook();
} catch (IOException e) {
- logger.warning("Problem while reading from the file. Will be starting with an empty AddressBook");
- initialData = new AddressBook();
+ logger.warning("Problem while reading from the file. Will be starting with a sample AddressBook");
+ // initialData = new AddressBook();
+ initialData = SampleDataUtil.getSampleAddressBook();
+ }
+
+ // initialise initial InsurancePackages
+ Optional insurancePackagesOptional;
+ InsurancePackagesSet initialPackages;
+ try {
+ insurancePackagesOptional = storage.readInsurancePackages();
+ if (!insurancePackagesOptional.isPresent()) {
+ logger.info("Insurance packages not found. Will be starting with sample packages");
+ }
+ initialPackages = insurancePackagesOptional.orElseGet(SampleDataUtil::getSampleInsurancePackages);
+ } catch (DataConversionException e) {
+ logger.warning("Data file not in the correct format. Will be starting with sample packages");
+ initialPackages = SampleDataUtil.getSampleInsurancePackages();
+ } catch (IOException e) {
+ logger.warning("Problem while reading from the file. Will be starting with sample packages");
+ initialPackages = SampleDataUtil.getSampleInsurancePackages();
+ }
+
+ // for each person in AddressBook, try to save its insurance package
+ for (Person p: initialData.getPersonList()) {
+ initialPackages.addPackage(p.getInsurancePackage());
}
- return new ModelManager(initialData, userPrefs);
+ return new ModelManager(initialData, userPrefs, initialPackages);
}
private void initLogging(Config config) {
diff --git a/src/main/java/seedu/address/commons/core/Messages.java b/src/main/java/seedu/address/commons/core/Messages.java
index 1deb3a1e469..0e288b47a78 100644
--- a/src/main/java/seedu/address/commons/core/Messages.java
+++ b/src/main/java/seedu/address/commons/core/Messages.java
@@ -8,6 +8,7 @@ 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_TAG_NUMBER = "The tag number provided is invalid";
public static final String MESSAGE_PERSONS_LISTED_OVERVIEW = "%1$d persons listed!";
}
diff --git a/src/main/java/seedu/address/commons/util/CsvUtil.java b/src/main/java/seedu/address/commons/util/CsvUtil.java
new file mode 100644
index 00000000000..9c5d609f676
--- /dev/null
+++ b/src/main/java/seedu/address/commons/util/CsvUtil.java
@@ -0,0 +1,185 @@
+package seedu.address.commons.util;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Scanner;
+import java.util.logging.Logger;
+
+import seedu.address.commons.core.LogsCenter;
+import seedu.address.commons.exceptions.DataConversionException;
+import seedu.address.commons.exceptions.IllegalValueException;
+import seedu.address.storage.CsvAdaptedInsurancePackage;
+import seedu.address.storage.CsvAdaptedPerson;
+
+/**
+ * A class that handles the conversion of a list of Person to a CSV file, and vice versa.
+ */
+public class CsvUtil {
+
+ private static final Logger logger = LogsCenter.getLogger(CsvUtil.class);
+ private static FileWriter fw;
+ private static Scanner s;
+
+ /**
+ * The headers to be used for the CSV file for AddressBook.
+ */
+ private static String abHeaders = "Name,Phone Number,Email,Insurance Package,Address,Tags";
+
+ /**
+ * The headers to be used for the CSV file for InsurancePackagesSet.
+ */
+ private static String ipHeaders = "Package Name,Package Details";
+
+ /**
+ * Takes in a List of CsvAdaptedPerson, and the Path of the CSV file to be saved to,
+ * and saves this list to a CSV file.
+ *
+ * @param persons a list of CsvAdaptedPerson to save to CSV to.
+ * @param filePath the given Path to the CSV file.
+ * @throws IOException if there are errors with file handling.
+ */
+ public static void saveAbCsvFile(List persons, Path filePath) throws IOException {
+ writeAbHeaders(filePath);
+ writePeople(persons, filePath);
+ }
+
+ /**
+ * Method that handles the writing of headers of the CSV file for AddressBook.
+ *
+ * @param filePath the given Path to the CSV file.
+ * @throws IOException if there are errors with file handling.
+ */
+ public static void writeAbHeaders(Path filePath) throws IOException {
+ fw = new FileWriter(filePath.toString());
+ fw.write(abHeaders + "\n");
+ fw.close();
+ }
+
+ /**
+ * Method that handles the writing of the List of CsvAdaptedPerson to CSV.
+ *
+ * @param persons a list of CsvAdaptedPerson to save to CSV to.
+ * @param filePath the given Path to the CSV file.
+ * @throws IOException if there are errors with file handling.
+ */
+ public static void writePeople(List persons, Path filePath) throws IOException {
+ fw = new FileWriter(filePath.toString(), true);
+ for (CsvAdaptedPerson p : persons) {
+ fw.write(p.toCsvString() + "\n");
+ }
+ fw.close();
+ }
+
+ /**
+ * Takes in the Path of an existing CSV file, and reads the CSV file to produce a List of CsvAdaptedPerson.
+ * @param filePath the Path to the existing CSV file.
+ * @return a List of CsvAdaptedPerson.
+ * @throws IOException if there are errors with file handling.
+ */
+ public static List loadAbCsvFile(Path filePath) throws DataConversionException {
+
+ ArrayList persons = new ArrayList<>();
+ String personString;
+ CsvAdaptedPerson p;
+
+ try {
+ s = new Scanner(new File(filePath.toString())); // create a Scanner using the File as the source
+ if (!s.hasNext()) {
+ logger.warning("Empty CSV File");
+ throw new IOException("Empty CSV file");
+ }
+ s.nextLine(); // headers
+ while (s.hasNext()) {
+ personString = s.nextLine();
+ p = new CsvAdaptedPerson(personString);
+ persons.add(p);
+ }
+ s.close();
+ return persons;
+ } catch (IOException e) {
+ logger.warning("Error reading from CSV file " + filePath + ": " + e);
+ throw new DataConversionException(e);
+ }
+ }
+
+ /**
+ * Takes in a List of InsurancePackage, and the Path of the CSV file to be saved to,
+ * and saves this list to a CSV file.
+ *
+ * @param packages a list of InsurancePackage to save to CSV to.
+ * @param filePath the given Path to the CSV file.
+ * @throws IOException if there are errors with file handling.
+ */
+ public static void saveIpCsvFile(List packages, Path filePath) throws IOException {
+ writeIpCsvHeaders(filePath);
+ writePackages(packages, filePath);
+ }
+
+ /**
+ * Method that handles the writing of headers of the CSV file for the insurance packages.
+ *
+ * @param filePath the given Path to the CSV file.
+ * @throws IOException if there are errors with file handling.
+ */
+ public static void writeIpCsvHeaders(Path filePath) throws IOException {
+ fw = new FileWriter(filePath.toString());
+ fw.write(ipHeaders + "\n");
+ fw.close();
+ }
+
+ /**
+ * Method that handles the writing of the List of InsurancePackage to CSV.
+ *
+ * @param packages a list of InsurancePackage to save to CSV to.
+ * @param filePath the given Path to the CSV file.
+ * @throws IOException if there are errors with file handling.
+ */
+ public static void writePackages(List packages, Path filePath) throws IOException {
+ fw = new FileWriter(filePath.toString(), true);
+ for (CsvAdaptedInsurancePackage p : packages) {
+ fw.write(p.toCsvString() + "\n");
+ }
+ fw.close();
+ }
+
+ /**
+ * Takes in the Path of an existing CSV file, and reads the CSV file to produce a List of InsurancePackage.
+ * @param filePath the Path to the existing CSV file.
+ * @return a List of InsurancePackage.
+ * @throws IOException if there are errors with file handling.
+ */
+ public static List loadIpCsvFile(Path filePath) throws DataConversionException {
+
+ ArrayList packages = new ArrayList<>();
+ String packageString;
+ CsvAdaptedInsurancePackage p;
+
+ try {
+ s = new Scanner(new File(filePath.toString())); // create a Scanner using the File as the source
+ if (!s.hasNext()) {
+ logger.warning("Empty CSV File");
+ throw new IOException("Empty CSV file");
+ }
+ s.nextLine(); // headers
+ while (s.hasNext()) {
+ packageString = s.nextLine();
+ try {
+ p = new CsvAdaptedInsurancePackage(packageString);
+ packages.add(p);
+ } catch (IllegalValueException ive) { // row does not have 2 values
+ continue; // choose to ignore
+ }
+ }
+ s.close();
+ return packages;
+ } catch (IOException e) {
+ logger.warning("Error reading from CSV file " + filePath + ": " + e);
+ throw new DataConversionException(e);
+ }
+ }
+
+}
diff --git a/src/main/java/seedu/address/logic/Logic.java b/src/main/java/seedu/address/logic/Logic.java
index 92cd8fa605a..5a61228fbea 100644
--- a/src/main/java/seedu/address/logic/Logic.java
+++ b/src/main/java/seedu/address/logic/Logic.java
@@ -7,6 +7,7 @@
import seedu.address.logic.commands.CommandResult;
import seedu.address.logic.commands.exceptions.CommandException;
import seedu.address.logic.parser.exceptions.ParseException;
+import seedu.address.model.InsurancePackagesSet;
import seedu.address.model.ReadOnlyAddressBook;
import seedu.address.model.person.Person;
@@ -47,4 +48,29 @@ public interface Logic {
* Set the user prefs' GUI settings.
*/
void setGuiSettings(GuiSettings guiSettings);
+
+ /**
+ * Saves the AddressBook to CSV.
+ */
+ void saveAddressBookToCsv(Path csvFilePath) throws CommandException;
+
+ /**
+ * Loads the AddressBook from CSV.
+ */
+ void readAddressBookFromCsv(Path csvFilePath) throws CommandException;
+
+ /**
+ * Retrieves the previous command in history.
+ */
+ String getPreviousCommand();
+
+ /**
+ * Retrieves the next command in history.
+ */
+ String getNextCommand();
+
+ /**
+ * Returns the object storing all insurance packages.
+ */
+ InsurancePackagesSet getAllPackages();
}
diff --git a/src/main/java/seedu/address/logic/LogicManager.java b/src/main/java/seedu/address/logic/LogicManager.java
index 9d9c6d15bdc..e9d229533df 100644
--- a/src/main/java/seedu/address/logic/LogicManager.java
+++ b/src/main/java/seedu/address/logic/LogicManager.java
@@ -2,19 +2,23 @@
import java.io.IOException;
import java.nio.file.Path;
+import java.util.Optional;
import java.util.logging.Logger;
import javafx.collections.ObservableList;
import seedu.address.commons.core.GuiSettings;
import seedu.address.commons.core.LogsCenter;
+import seedu.address.commons.exceptions.DataConversionException;
import seedu.address.logic.commands.Command;
import seedu.address.logic.commands.CommandResult;
import seedu.address.logic.commands.exceptions.CommandException;
import seedu.address.logic.parser.AddressBookParser;
import seedu.address.logic.parser.exceptions.ParseException;
+import seedu.address.model.InsurancePackagesSet;
import seedu.address.model.Model;
import seedu.address.model.ReadOnlyAddressBook;
import seedu.address.model.person.Person;
+import seedu.address.storage.CommandStorage;
import seedu.address.storage.Storage;
/**
@@ -26,14 +30,16 @@ public class LogicManager implements Logic {
private final Model model;
private final Storage storage;
+ private final CommandStorage commandStorage;
private final AddressBookParser addressBookParser;
/**
* Constructs a {@code LogicManager} with the given {@code Model} and {@code Storage}.
*/
- public LogicManager(Model model, Storage storage) {
+ public LogicManager(Model model, Storage storage, CommandStorage commandStorage) {
this.model = model;
this.storage = storage;
+ this.commandStorage = commandStorage;
addressBookParser = new AddressBookParser();
}
@@ -41,12 +47,17 @@ public LogicManager(Model model, Storage storage) {
public CommandResult execute(String commandText) throws CommandException, ParseException {
logger.info("----------------[USER COMMAND][" + commandText + "]");
+ commandStorage.addCommand(commandText);
+
CommandResult commandResult;
Command command = addressBookParser.parseCommand(commandText);
commandResult = command.execute(model);
try {
storage.saveAddressBook(model.getAddressBook());
+ logger.info("AddressBook saved!");
+ storage.saveInsurancePackages(model.getInsurancePackagesSet());
+ logger.info("Insurance Packages saved!");
} catch (IOException ioe) {
throw new CommandException(FILE_OPS_ERROR_MESSAGE + ioe, ioe);
}
@@ -78,4 +89,49 @@ public GuiSettings getGuiSettings() {
public void setGuiSettings(GuiSettings guiSettings) {
model.setGuiSettings(guiSettings);
}
+
+ @Override
+ public void saveAddressBookToCsv(Path csvFilePath) throws CommandException {
+ try {
+ storage.saveAddressBookToCsv(model.getAddressBook(), csvFilePath);
+ } catch (IOException ioe) {
+ throw new CommandException(FILE_OPS_ERROR_MESSAGE + ioe, ioe);
+ }
+ }
+
+ @Override
+ public void readAddressBookFromCsv(Path csvFilePath) throws CommandException {
+ try {
+ Optional ab = storage.readAddressBookFromCsv(csvFilePath);
+ if (ab.isPresent()) {
+ ReadOnlyAddressBook readOnlyAb = ab.get();
+ model.setAddressBook(readOnlyAb);
+
+ for (Person p: readOnlyAb.getPersonList()) {
+ model.addInsurancePackage(p.getInsurancePackage());
+ }
+
+ logger.info("Successfully set address book, without saving.");
+ } else {
+ logger.info("No change to address book after attempting to read from CSV.");
+ }
+ } catch (DataConversionException | IOException ioe) {
+ throw new CommandException(FILE_OPS_ERROR_MESSAGE + ioe, ioe);
+ }
+ }
+
+ @Override
+ public String getPreviousCommand() {
+ return commandStorage.getPreviousCommand();
+ }
+
+ @Override
+ public String getNextCommand() {
+ return commandStorage.getNextCommand();
+ }
+
+ @Override
+ public InsurancePackagesSet getAllPackages() {
+ return model.getInsurancePackagesSet();
+ }
}
diff --git a/src/main/java/seedu/address/logic/commands/AddCommand.java b/src/main/java/seedu/address/logic/commands/AddCommand.java
index 71656d7c5c8..d485c92bc4a 100644
--- a/src/main/java/seedu/address/logic/commands/AddCommand.java
+++ b/src/main/java/seedu/address/logic/commands/AddCommand.java
@@ -3,6 +3,7 @@
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_INSURANCE_PACKAGE;
import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME;
import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE;
import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG;
@@ -18,23 +19,25 @@ public class AddCommand extends Command {
public static final String COMMAND_WORD = "add";
- public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a person to the address book. "
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a client to the address book. "
+ "Parameters: "
+ PREFIX_NAME + "NAME "
+ PREFIX_PHONE + "PHONE "
+ PREFIX_EMAIL + "EMAIL "
+ + PREFIX_INSURANCE_PACKAGE + "INSURANCE PACKAGE "
+ PREFIX_ADDRESS + "ADDRESS "
+ "[" + PREFIX_TAG + "TAG]...\n"
+ "Example: " + COMMAND_WORD + " "
+ PREFIX_NAME + "John Doe "
+ PREFIX_PHONE + "98765432 "
+ PREFIX_EMAIL + "johnd@example.com "
+ + PREFIX_INSURANCE_PACKAGE + "Golden Package "
+ PREFIX_ADDRESS + "311, Clementi Ave 2, #02-25 "
+ PREFIX_TAG + "friends "
+ PREFIX_TAG + "owesMoney";
- public static final String MESSAGE_SUCCESS = "New person added: %1$s";
- public static final String MESSAGE_DUPLICATE_PERSON = "This person already exists in the address book";
+ public static final String MESSAGE_SUCCESS = "New client added: %1$s";
+ public static final String MESSAGE_DUPLICATE_PERSON = "This client already exists in the address book";
private final Person toAdd;
@@ -55,9 +58,15 @@ public CommandResult execute(Model model) throws CommandException {
}
model.addPerson(toAdd);
+ model.addInsurancePackage(toAdd.getInsurancePackage());
+
return new CommandResult(String.format(MESSAGE_SUCCESS, toAdd));
}
+ public Person getToAdd() {
+ return toAdd;
+ }
+
@Override
public boolean equals(Object other) {
return other == this // short circuit if same object
diff --git a/src/main/java/seedu/address/logic/commands/AddPackageCommand.java b/src/main/java/seedu/address/logic/commands/AddPackageCommand.java
new file mode 100644
index 00000000000..91ef1c06395
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/AddPackageCommand.java
@@ -0,0 +1,64 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_INSURANCE_PACKAGE;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_PACKAGE_DESC;
+
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+import seedu.address.model.person.InsurancePackage;
+
+/**
+ * Adds an insurance package to the address book.
+ */
+public class AddPackageCommand extends Command {
+
+ public static final String COMMAND_WORD = "addp";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds an insurance package to the address book. "
+ + "Parameters: "
+ + PREFIX_INSURANCE_PACKAGE + "PACKAGE NAME "
+ + PREFIX_PACKAGE_DESC + "PACKAGE DESCRIPTION \n"
+ + "Example: " + COMMAND_WORD + " "
+ + PREFIX_INSURANCE_PACKAGE + "Golden Package "
+ + PREFIX_PACKAGE_DESC + "Lifetime insurance!";
+
+ public static final String MESSAGE_SUCCESS = "New package added: %1$s";
+ public static final String MESSAGE_DUPLICATE_PACKAGE = "This package already exists in the address book";
+
+ private final InsurancePackage toAdd;
+
+ /**
+ * Creates an AddPackageCommand to add the specified insurance package.
+ */
+ public AddPackageCommand(InsurancePackage p) {
+ requireNonNull(p);
+ toAdd = p;
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+
+ if (model.hasInsurancePackage(toAdd)) {
+ throw new CommandException(MESSAGE_DUPLICATE_PACKAGE);
+ }
+
+ model.addInsurancePackage(toAdd);
+ assert model.hasInsurancePackage(toAdd);
+
+ return new CommandResult(String.format(MESSAGE_SUCCESS, toAdd));
+ }
+
+ public InsurancePackage getToAdd() {
+ return toAdd;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof AddPackageCommand // instanceof handles nulls
+ && toAdd.equals(((AddPackageCommand) other).toAdd));
+ }
+
+}
diff --git a/src/main/java/seedu/address/logic/commands/AddTagCommand.java b/src/main/java/seedu/address/logic/commands/AddTagCommand.java
new file mode 100644
index 00000000000..631a8e6633d
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/AddTagCommand.java
@@ -0,0 +1,94 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS;
+
+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.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+import seedu.address.model.person.Person;
+import seedu.address.model.tag.Tag;
+
+/**
+ * Adds tag to specified person in the address book.
+ */
+public class AddTagCommand extends Command {
+
+ public static final String COMMAND_WORD = "addTag";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds tag to the client identified "
+ + "by the index number used in the displayed person list. "
+ + "Only one tag can be added at a time. Duplicates cannot be added. "
+ + "Parameters: INDEX (must be a positive integer) + TAG\n"
+ + "Example: " + COMMAND_WORD + " 1 "
+ + "owes money :p3 ";
+
+ public static final String MESSAGE_SUCCESS = "Added tag(s) to Person: %1$s";
+ public static final String MESSAGE_DUPLICATE_TAG = "The tag you want to add is already present.";
+
+ private final Index index;
+ private final Tag tagToAdd;
+
+ /**
+ * Creates an AddTagCommand to add the specified {@code Tag}.
+ *
+ * @param index of the person in the filtered person list to edit
+ * @param tag to be added to the person identified
+ */
+ public AddTagCommand(Index index, Tag tag) {
+ requireNonNull(index);
+ requireNonNull(tag);
+ this.index = index;
+ tagToAdd = tag;
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+ List lastShownList = model.getFilteredPersonList();
+
+ if (index.getZeroBased() >= lastShownList.size()) {
+ throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX);
+ }
+
+ Person personToEdit = lastShownList.get(index.getZeroBased());
+ Person tagAddedPerson = addTagToPerson(personToEdit, tagToAdd);
+
+ model.setPerson(personToEdit, tagAddedPerson);
+ model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS);
+ return new CommandResult(String.format(MESSAGE_SUCCESS, tagAddedPerson));
+ }
+
+ /**
+ * Adds {@code Tag} to taglist of {@code Person} specified.
+ * @param personToEdit Person to add tag to
+ * @param tagToAdd Tag to be added
+ * @return Person with the tag added
+ * @throws CommandException if there is a duplicate tag already existing
+ */
+ private Person addTagToPerson(Person personToEdit, Tag tagToAdd) throws CommandException {
+ Person newPerson = Person.copyPerson(personToEdit);
+ ArrayList tagList = newPerson.getTags();
+
+ if (!tagList.contains(tagToAdd)) {
+ tagList.add(tagToAdd);
+ } else {
+ throw new CommandException(MESSAGE_DUPLICATE_TAG);
+ }
+
+ newPerson.setTags(tagList);
+ return newPerson;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof AddTagCommand // instanceof handles nulls
+ && index.equals(((AddTagCommand) other).index)
+ && tagToAdd.equals(((AddTagCommand) other).tagToAdd));
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/AddToClipboardCommand.java b/src/main/java/seedu/address/logic/commands/AddToClipboardCommand.java
new file mode 100644
index 00000000000..e0cd0b3f7ea
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/AddToClipboardCommand.java
@@ -0,0 +1,128 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+
+import java.awt.HeadlessException;
+import java.awt.Toolkit;
+import java.awt.datatransfer.Clipboard;
+import java.awt.datatransfer.StringSelection;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javafx.collections.ObservableList;
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+import seedu.address.model.person.Person;
+import seedu.address.model.person.predicates.NameExistsPredicate;
+
+/**
+ * Copies the information of a given contact to the user's clipboard.
+ */
+public class AddToClipboardCommand extends Command {
+ public static final String COMMAND_WORD = "clip";
+
+ public static final String MESSAGE_SUCCESS = "Added the following client's information to clipboard!";
+ public static final String MESSAGE_FAILURE = "No such client found!";
+ public static final String MESSAGE_NO_CLIPBOARD = "Environment has no clipboard!";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD
+ + ": Adds the information of the client identified "
+ + "by index or their full name (case-insensitive) to the user's clipboard. "
+ + "If both an index and name is specified, the index will be used to select the contact to clip.\n"
+ + "Parameters: index OR n/ Full name of intended contact\n"
+ + "Example: " + COMMAND_WORD + " 1 OR "
+ + COMMAND_WORD + " n/ John Doe";
+
+ private static Logger logger = Logger.getLogger("AddToClipboardCommandLogger");
+ private final NameExistsPredicate predicate;
+ private final Index targetIndex;
+
+ /**
+ * Creates an AddToClipboardCommand to copy information of the person specified in {@code NameExistsPredicate}
+ */
+ public AddToClipboardCommand(NameExistsPredicate predicate) {
+ requireNonNull(predicate);
+ this.predicate = predicate;
+ this.targetIndex = null;
+ }
+
+ /**
+ * Creates an AddToClipboardCommand to copy information of the person at the specified {@code targetIndex}.
+ */
+ public AddToClipboardCommand(Index targetIndex) {
+ this.targetIndex = targetIndex;
+ this.predicate = null;
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException, HeadlessException {
+ requireNonNull(model);
+ ObservableList filterResult;
+ NameExistsPredicate predicateToUse = this.predicate;
+
+ if (this.predicate == null) {
+ assert(this.targetIndex != null);
+ //This instance of AddToClipboardCommand was created with a target index
+ ObservableList personList = model.getFilteredPersonList();
+ if (targetIndex.getZeroBased() >= personList.size()) {
+ //No such contact at that index exists
+ logger.log(Level.INFO, "Clip command failed, no contact at that index exists!");
+ return new CommandResult(MESSAGE_FAILURE);
+ }
+
+ Person personToClip = personList.get(targetIndex.getZeroBased());
+ predicateToUse = new NameExistsPredicate(personToClip.getName());
+ }
+
+ model.updateFilteredPersonList(predicateToUse);
+ filterResult = model.getFilteredPersonList();
+
+ if (filterResult.toString().equals("[]")) {
+ //Filter returns nothing, no such contact with the given name exists
+ logger.log(Level.INFO, "Clip command failed, no contact with that name exists!");
+ return new CommandResult(MESSAGE_FAILURE);
+ }
+
+ //@@author jetrz-reused
+ //Reused from https://stackoverflow.com/questions/6710350/copying-text-to-the-clipboard-using-java
+ //with modifications
+ //Filter returns a contact, copy that contact to clipboard
+ try {
+ StringSelection resultToString = new StringSelection(filterResult.toString());
+ Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
+ clipboard.setContents(resultToString, null);
+ } catch (HeadlessException e) {
+ logger.log(Level.INFO, "Clip command failed, environment has no clipboard!");
+ return new CommandResult(MESSAGE_NO_CLIPBOARD);
+ }
+ logger.log(Level.INFO, "Clip command executed successfully!");
+ return new CommandResult(MESSAGE_SUCCESS);
+ //@@author
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ // short circuit if same object
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof AddToClipboardCommand)) {
+ return false;
+ }
+
+ // state check
+ AddToClipboardCommand e = (AddToClipboardCommand) other;
+ assert (predicate == null || targetIndex == null);
+ assert (e.predicate == null || e.targetIndex == null);
+ if ((predicate == null && e.predicate != null) || (targetIndex == null && e.targetIndex != null)) {
+ return false;
+ } else if (predicate != null) {
+ return predicate.equals(e.predicate);
+ } else {
+ return targetIndex.equals(e.targetIndex);
+ }
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/ClearCommand.java b/src/main/java/seedu/address/logic/commands/ClearCommand.java
index 9c86b1fa6e4..42fbd494be0 100644
--- a/src/main/java/seedu/address/logic/commands/ClearCommand.java
+++ b/src/main/java/seedu/address/logic/commands/ClearCommand.java
@@ -2,7 +2,6 @@
import static java.util.Objects.requireNonNull;
-import seedu.address.model.AddressBook;
import seedu.address.model.Model;
/**
@@ -17,7 +16,7 @@ public class ClearCommand extends Command {
@Override
public CommandResult execute(Model model) {
requireNonNull(model);
- model.setAddressBook(new AddressBook());
+ model.resetAddressBook();
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 92f900b7916..601ea9142ea 100644
--- a/src/main/java/seedu/address/logic/commands/CommandResult.java
+++ b/src/main/java/seedu/address/logic/commands/CommandResult.java
@@ -2,6 +2,7 @@
import static java.util.Objects.requireNonNull;
+import java.util.HashMap;
import java.util.Objects;
/**
@@ -12,18 +13,36 @@ public class CommandResult {
private final String feedbackToUser;
/** Help information should be shown to the user. */
- private final boolean showHelp;
+ private final boolean isShowHelp;
/** The application should exit. */
- private final boolean exit;
+ private final boolean isExit;
+
+ /** The application should display the window allowing the user to import a CSV file. */
+ private final boolean isImportFromCsv;
+
+ /** The application should display the window allowing the user to export to a CSV file. */
+ private final boolean isExportToCsv;
+
+ /** The application should display the window showing the insurance packages available */
+ private final boolean isShowPackages;
+
/**
* Constructs a {@code CommandResult} with the specified fields.
+ * The HashMap can have keys for "showHelp", "exit", "importFromCsv", "exportToCsv".
+ * The default values are false.
*/
- public CommandResult(String feedbackToUser, boolean showHelp, boolean exit) {
+ public CommandResult(String feedbackToUser, HashMap settings) {
this.feedbackToUser = requireNonNull(feedbackToUser);
- this.showHelp = showHelp;
- this.exit = exit;
+
+ boolean hasSettings = Objects.nonNull(settings);
+
+ this.isShowHelp = hasSettings && settings.getOrDefault("showHelp", false);
+ this.isExit = hasSettings && settings.getOrDefault("exit", false);
+ this.isImportFromCsv = hasSettings && settings.getOrDefault("importFromCsv", false);
+ this.isExportToCsv = hasSettings && settings.getOrDefault("exportToCsv", false);
+ this.isShowPackages = hasSettings && settings.getOrDefault("showPackages", false);
}
/**
@@ -31,7 +50,7 @@ public CommandResult(String feedbackToUser, boolean showHelp, boolean exit) {
* and other fields set to their default value.
*/
public CommandResult(String feedbackToUser) {
- this(feedbackToUser, false, false);
+ this(feedbackToUser, null);
}
public String getFeedbackToUser() {
@@ -39,11 +58,23 @@ public String getFeedbackToUser() {
}
public boolean isShowHelp() {
- return showHelp;
+ return isShowHelp;
}
public boolean isExit() {
- return exit;
+ return isExit;
+ }
+
+ public boolean isImportFromCsv() {
+ return isImportFromCsv;
+ }
+
+ public boolean isExportToCsv() {
+ return isExportToCsv;
+ }
+
+ public boolean isShowPackages() {
+ return isShowPackages;
}
@Override
@@ -59,13 +90,17 @@ public boolean equals(Object other) {
CommandResult otherCommandResult = (CommandResult) other;
return feedbackToUser.equals(otherCommandResult.feedbackToUser)
- && showHelp == otherCommandResult.showHelp
- && exit == otherCommandResult.exit;
+ && isShowHelp == otherCommandResult.isShowHelp
+ && isExit == otherCommandResult.isExit
+ && isImportFromCsv == otherCommandResult.isImportFromCsv
+ && isExportToCsv == otherCommandResult.isExportToCsv
+ && isShowPackages == otherCommandResult.isShowPackages;
}
@Override
public int hashCode() {
- return Objects.hash(feedbackToUser, showHelp, exit);
+ return Objects.hash(
+ feedbackToUser, isShowHelp, isExit, isImportFromCsv, isExportToCsv, isShowPackages);
}
}
diff --git a/src/main/java/seedu/address/logic/commands/DeleteCommand.java b/src/main/java/seedu/address/logic/commands/DeleteCommand.java
index 02fd256acba..f6d36f3d9c0 100644
--- a/src/main/java/seedu/address/logic/commands/DeleteCommand.java
+++ b/src/main/java/seedu/address/logic/commands/DeleteCommand.java
@@ -18,7 +18,7 @@ public class DeleteCommand extends Command {
public static final String COMMAND_WORD = "delete";
public static final String MESSAGE_USAGE = COMMAND_WORD
- + ": Deletes the person identified by the index number used in the displayed person list.\n"
+ + ": Deletes the client identified by the index number used in the displayed client list.\n"
+ "Parameters: INDEX (must be a positive integer)\n"
+ "Example: " + COMMAND_WORD + " 1";
diff --git a/src/main/java/seedu/address/logic/commands/DeletePackageCommand.java b/src/main/java/seedu/address/logic/commands/DeletePackageCommand.java
new file mode 100644
index 00000000000..ce25b48de5b
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/DeletePackageCommand.java
@@ -0,0 +1,72 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_INSURANCE_PACKAGE;
+
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+import seedu.address.model.person.InsurancePackage;
+import seedu.address.model.person.Person;
+
+/**
+ * Deletes an insurance package from the address book.
+ *
+ * Note that the current implementation does not allow a package to be deleted, if it is in use by someone.
+ */
+public class DeletePackageCommand extends Command {
+
+ public static final String COMMAND_WORD = "deletep";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD
+ + ": Deletes an existing insurance package in the address book. "
+ + "Parameters: "
+ + PREFIX_INSURANCE_PACKAGE + "PACKAGE NAME \n"
+ + "Example: " + COMMAND_WORD + " "
+ + PREFIX_INSURANCE_PACKAGE + "Golden Package ";
+
+ public static final String MESSAGE_DELETE_PACKAGE_SUCCESS = "Deleted Package: %1$s";
+ public static final String MESSAGE_INVALID_PACKAGE = "This package does not exist in the address book.";
+ public static final String MESSAGE_PACKAGE_IN_USE = "This package is in use by someone, "
+ + "perhaps set his/her package as another one before deleting this package.";
+
+ public final String packageName;
+
+ public DeletePackageCommand(String packageName) {
+ this.packageName = packageName;
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+
+ if (!model.hasInsurancePackage(new InsurancePackage(packageName))) {
+ throw new CommandException(MESSAGE_INVALID_PACKAGE);
+ }
+
+ InsurancePackage packageToDelete = new InsurancePackage(packageName);
+
+ boolean canDelete = true;
+
+ for (Person p : model.getAddressBook().getPersonList()) {
+ if (p.getInsurancePackage().equals(packageToDelete)) {
+ canDelete = false;
+ break;
+ }
+ }
+
+ if (canDelete) {
+ model.deleteInsurancePackage(packageToDelete);
+ assert !model.hasInsurancePackage(packageToDelete);
+ return new CommandResult(String.format(MESSAGE_DELETE_PACKAGE_SUCCESS, packageName));
+ } else {
+ return new CommandResult(MESSAGE_PACKAGE_IN_USE);
+ }
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof DeletePackageCommand // instanceof handles nulls
+ && packageName.equals(((DeletePackageCommand) other).packageName)); // state check
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/DeleteTagCommand.java b/src/main/java/seedu/address/logic/commands/DeleteTagCommand.java
new file mode 100644
index 00000000000..7bb8e7f2c3e
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/DeleteTagCommand.java
@@ -0,0 +1,93 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS;
+
+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.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+import seedu.address.model.person.Person;
+import seedu.address.model.tag.Tag;
+
+/**
+ * Deletes a tag from a person in the address book.
+ */
+public class DeleteTagCommand extends Command {
+
+ public static final String COMMAND_WORD = "deleteTag";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Delete a tag "
+ + "(identified by the tag's index number) "
+ + "of the client identified "
+ + "by the index number used in the displayed client list. "
+ + "Only one tag can be deleted at a time. \n"
+ + "Parameters: CLIENT_INDEX (must be a positive integer) "
+ + "TAG_NUMBER (must be a positive integer) \n"
+ + "Example: " + COMMAND_WORD + " 3 " + "2";
+
+ public static final String MESSAGE_SUCCESS = "Deleted tag in Client: %1$s";
+
+ private final Index index;
+ private final int tagNumber;
+
+ /**
+ * Creates an DeleteTagCommand to delete the {@code Tag} specified by the index and tag number
+ *
+ * @param index of the person in the filtered person list to edit
+ * @param tagNumber of the tag to be deleted
+ */
+ public DeleteTagCommand(Index index, int tagNumber) {
+ requireNonNull(index);
+ this.index = index;
+ this.tagNumber = tagNumber;
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+ List lastShownList = model.getFilteredPersonList();
+
+ if (index.getZeroBased() >= lastShownList.size()) {
+ throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX);
+ }
+
+ Person personToEdit = lastShownList.get(index.getZeroBased());
+ Person tagDeletedPerson = deleteTagFromPerson(personToEdit, tagNumber);
+
+ model.setPerson(personToEdit, tagDeletedPerson);
+ model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS);
+ return new CommandResult(String.format(MESSAGE_SUCCESS, tagDeletedPerson));
+ }
+
+ /**
+ * Deletes {@code Tag} from {@code Person}
+ * @param personToEdit person whose tag should be deleted
+ * @param tagNumber serial number of tag to be deleted
+ * @return Person who is identical to personToEdit except for the deleted tag
+ * @throws CommandException if an invalid tag number is specified
+ */
+ private Person deleteTagFromPerson(Person personToEdit, int tagNumber) throws CommandException {
+ Person newPerson = Person.copyPerson(personToEdit);
+ ArrayList tagList = newPerson.getTags();
+ if (tagNumber < 1 || tagNumber > tagList.size()) {
+ throw new CommandException(Messages.MESSAGE_INVALID_TAG_NUMBER);
+ }
+
+ tagList.remove(tagNumber - 1);
+
+ newPerson.setTags(tagList);
+ return newPerson;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof DeleteTagCommand // instanceof handles nulls
+ && index.equals(((DeleteTagCommand) other).index)
+ && tagNumber == ((DeleteTagCommand) other).tagNumber);
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/EditCommand.java b/src/main/java/seedu/address/logic/commands/EditCommand.java
index 7e36114902f..d5a180f41bb 100644
--- a/src/main/java/seedu/address/logic/commands/EditCommand.java
+++ b/src/main/java/seedu/address/logic/commands/EditCommand.java
@@ -3,13 +3,13 @@
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_INSURANCE_PACKAGE;
import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME;
import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE;
import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG;
import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS;
-import java.util.Collections;
-import java.util.HashSet;
+import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.Set;
@@ -21,6 +21,7 @@
import seedu.address.model.Model;
import seedu.address.model.person.Address;
import seedu.address.model.person.Email;
+import seedu.address.model.person.InsurancePackage;
import seedu.address.model.person.Name;
import seedu.address.model.person.Person;
import seedu.address.model.person.Phone;
@@ -33,22 +34,23 @@ public class EditCommand extends Command {
public static final String COMMAND_WORD = "edit";
- public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits the details of the person identified "
- + "by the index number used in the displayed person list. "
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits the details of the client identified "
+ + "by the index number used in the displayed client list. "
+ "Existing values will be overwritten by the input values.\n"
+ "Parameters: INDEX (must be a positive integer) "
+ "[" + PREFIX_NAME + "NAME] "
+ "[" + PREFIX_PHONE + "PHONE] "
+ "[" + PREFIX_EMAIL + "EMAIL] "
+ + "[" + PREFIX_INSURANCE_PACKAGE + "INSURANCE PACKAGE] "
+ "[" + PREFIX_ADDRESS + "ADDRESS] "
+ "[" + PREFIX_TAG + "TAG]...\n"
+ "Example: " + COMMAND_WORD + " 1 "
+ PREFIX_PHONE + "91234567 "
+ PREFIX_EMAIL + "johndoe@example.com";
- public static final String MESSAGE_EDIT_PERSON_SUCCESS = "Edited Person: %1$s";
+ public static final String MESSAGE_EDIT_PERSON_SUCCESS = "Edited Client: %1$s";
public static final String MESSAGE_NOT_EDITED = "At least one field to edit must be provided.";
- public static final String MESSAGE_DUPLICATE_PERSON = "This person already exists in the address book.";
+ public static final String MESSAGE_DUPLICATE_PERSON = "This client already exists in the address book.";
private final Index index;
private final EditPersonDescriptor editPersonDescriptor;
@@ -82,6 +84,10 @@ public CommandResult execute(Model model) throws CommandException {
}
model.setPerson(personToEdit, editedPerson);
+
+ // cannot simply replace the old insurance package, because it might still be in use by another person
+ model.addInsurancePackage(editedPerson.getInsurancePackage());
+
model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS);
return new CommandResult(String.format(MESSAGE_EDIT_PERSON_SUCCESS, editedPerson));
}
@@ -96,10 +102,13 @@ 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());
+ InsurancePackage updatedInsurancePackage = editPersonDescriptor.getInsurancePackage()
+ .orElse(personToEdit.getInsurancePackage());
Address updatedAddress = editPersonDescriptor.getAddress().orElse(personToEdit.getAddress());
- Set updatedTags = editPersonDescriptor.getTags().orElse(personToEdit.getTags());
+ ArrayList updatedTags = editPersonDescriptor.getTags().orElse(personToEdit.getTags());
- return new Person(updatedName, updatedPhone, updatedEmail, updatedAddress, updatedTags);
+ return new Person(updatedName, updatedPhone, updatedEmail, updatedInsurancePackage,
+ updatedAddress, updatedTags);
}
@Override
@@ -128,8 +137,9 @@ public static class EditPersonDescriptor {
private Name name;
private Phone phone;
private Email email;
+ private InsurancePackage insurancePackage;
private Address address;
- private Set tags;
+ private ArrayList tags;
public EditPersonDescriptor() {}
@@ -141,6 +151,7 @@ public EditPersonDescriptor(EditPersonDescriptor toCopy) {
setName(toCopy.name);
setPhone(toCopy.phone);
setEmail(toCopy.email);
+ setInsurancePackage(toCopy.insurancePackage);
setAddress(toCopy.address);
setTags(toCopy.tags);
}
@@ -149,7 +160,7 @@ 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, insurancePackage, address, tags);
}
public void setName(Name name) {
@@ -176,6 +187,14 @@ public Optional getEmail() {
return Optional.ofNullable(email);
}
+ public void setInsurancePackage(InsurancePackage insurancePackage) {
+ this.insurancePackage = insurancePackage;
+ }
+
+ public Optional getInsurancePackage() {
+ return Optional.ofNullable(insurancePackage);
+ }
+
public void setAddress(Address address) {
this.address = address;
}
@@ -188,8 +207,8 @@ public Optional getAddress() {
* Sets {@code tags} to this object's {@code tags}.
* A defensive copy of {@code tags} is used internally.
*/
- public void setTags(Set tags) {
- this.tags = (tags != null) ? new HashSet<>(tags) : null;
+ public void setTags(ArrayList tags) {
+ this.tags = (tags != null) ? new ArrayList<>(tags) : null;
}
/**
@@ -197,8 +216,8 @@ public void setTags(Set tags) {
* if modification is attempted.
* Returns {@code Optional#empty()} if {@code tags} is null.
*/
- public Optional> getTags() {
- return (tags != null) ? Optional.of(Collections.unmodifiableSet(tags)) : Optional.empty();
+ public Optional> getTags() {
+ return (tags != null) ? Optional.of(tags) : Optional.empty();
}
@Override
@@ -219,8 +238,9 @@ public boolean equals(Object other) {
return getName().equals(e.getName())
&& getPhone().equals(e.getPhone())
&& getEmail().equals(e.getEmail())
+ && getInsurancePackage().equals(e.getInsurancePackage())
&& getAddress().equals(e.getAddress())
- && getTags().equals(e.getTags());
+ && Set.of(getTags()).equals(Set.of(e.getTags()));
}
}
}
diff --git a/src/main/java/seedu/address/logic/commands/EditPackageCommand.java b/src/main/java/seedu/address/logic/commands/EditPackageCommand.java
new file mode 100644
index 00000000000..ea4d1015507
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/EditPackageCommand.java
@@ -0,0 +1,61 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_INSURANCE_PACKAGE;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_PACKAGE_DESC;
+
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+import seedu.address.model.person.InsurancePackage;
+
+/**
+ * Edits an existing insurance package in the address book.
+ */
+public class EditPackageCommand extends Command {
+
+ public static final String COMMAND_WORD = "editp";
+ public static final String MESSAGE_USAGE = COMMAND_WORD
+ + ": Edits an existing insurance package in the address book."
+ + "Parameters: "
+ + PREFIX_INSURANCE_PACKAGE + "PACKAGE NAME "
+ + PREFIX_PACKAGE_DESC + "PACKAGE DESCRIPTION \n"
+ + "Example: " + COMMAND_WORD + " "
+ + PREFIX_INSURANCE_PACKAGE + "Golden Package "
+ + PREFIX_PACKAGE_DESC + "Lifetime insurance!";
+
+ public static final String MESSAGE_EDIT_PACKAGE_SUCCESS = "Edited Package: %1$s";
+ public static final String MESSAGE_INVALID_PACKAGE = "This package does not exist in the address book.";
+
+ private final String packageName;
+ private final String newPackageDesc;
+
+ /**
+ * Creates an EditPackageCommand to edit the specified insurance package.
+ */
+ public EditPackageCommand(String packageName, String newPackageDesc) {
+ this.packageName = packageName;
+ this.newPackageDesc = newPackageDesc;
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+
+ if (!model.hasInsurancePackage(new InsurancePackage(packageName))) {
+ throw new CommandException(MESSAGE_INVALID_PACKAGE);
+ }
+
+ model.setInsurancePackage(packageName, newPackageDesc);
+ assert model.hasInsurancePackage(new InsurancePackage(packageName, newPackageDesc));
+
+ return new CommandResult(String.format(MESSAGE_EDIT_PACKAGE_SUCCESS, packageName));
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof EditPackageCommand // instanceof handles nulls
+ && packageName.equals(((EditPackageCommand) other).packageName)
+ && newPackageDesc.equals(((EditPackageCommand) other).newPackageDesc));
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/EditTagCommand.java b/src/main/java/seedu/address/logic/commands/EditTagCommand.java
new file mode 100644
index 00000000000..2e8da26b3c7
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/EditTagCommand.java
@@ -0,0 +1,104 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS;
+
+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.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+import seedu.address.model.person.Person;
+import seedu.address.model.tag.Tag;
+
+/**
+ * Edits the tag of a person in the address book.
+ */
+public class EditTagCommand extends Command {
+
+ public static final String COMMAND_WORD = "editTag";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edit a tag "
+ + "(identified by the tag's index number) "
+ + "of the client identified "
+ + "by the index number used in the displayed person list. "
+ + "Only one tag can edited at a time. \n"
+ + "Parameters: CLIENT_INDEX (must be a positive integer) "
+ + "TAG_NUMBER (must be a positive integer) "
+ + "TAG\n"
+ + "Example: " + COMMAND_WORD + " 1 2 "
+ + "owesMoney :p2";
+
+ public static final String MESSAGE_SUCCESS = "Edited tag in Client: %1$s";
+ public static final String MESSAGE_DUPLICATE_TAG = "New version of tag specified is already present.";
+
+ private final Index index;
+ private final int tagNumber;
+ private final Tag toAdd;
+
+ /**
+ * Creates an EditTagCommand to replace the tag in the index with the specified {@code Tag}
+ *
+ * @param index of the person in the filtered person list to edit
+ * @param tagNumber of the person's tag list to edit
+ * @param tag to be added to the person identified
+ */
+ public EditTagCommand(Index index, int tagNumber, Tag tag) {
+ requireNonNull(index);
+ requireNonNull(tag);
+ this.index = index;
+ this.tagNumber = tagNumber;
+ toAdd = tag;
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+ List lastShownList = model.getFilteredPersonList();
+
+ if (index.getZeroBased() >= lastShownList.size()) {
+ throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX);
+ }
+
+ Person personToEdit = lastShownList.get(index.getZeroBased());
+ Person tagAddedPerson = editTagOfPerson(personToEdit, tagNumber, toAdd);
+
+ model.setPerson(personToEdit, tagAddedPerson);
+ model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS);
+ return new CommandResult(String.format(MESSAGE_SUCCESS, tagAddedPerson));
+ }
+
+ /**
+ * Edits {@code Tag} of {@code Person}
+ * @param personToEdit person whose tag should be edited
+ * @param tagNumber serial number of tag to be edited
+ * @param tag tag to replace original tag
+ * @return Person who is identical to personToEdit except for edited tag
+ * @throws CommandException if the new tag already exists in personToEdit's tag list
+ */
+ private Person editTagOfPerson(Person personToEdit, int tagNumber, Tag tag) throws CommandException {
+ Person newPerson = Person.copyPerson(personToEdit);
+ ArrayList tagList = newPerson.getTags();
+ if (tagNumber < 1 || tagNumber > tagList.size()) {
+ throw new CommandException(Messages.MESSAGE_INVALID_TAG_NUMBER);
+ }
+ if (tagList.contains(tag)) {
+ throw new CommandException(MESSAGE_DUPLICATE_TAG);
+ }
+
+ tagList.set(tagNumber - 1, tag);
+ newPerson.setTags(tagList);
+ return newPerson;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof EditTagCommand // instanceof handles nulls
+ && index.equals(((EditTagCommand) other).index)
+ && tagNumber == ((EditTagCommand) other).tagNumber
+ && toAdd.equals(((EditTagCommand) other).toAdd));
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/ExitCommand.java b/src/main/java/seedu/address/logic/commands/ExitCommand.java
index 3dd85a8ba90..67ccab3456c 100644
--- a/src/main/java/seedu/address/logic/commands/ExitCommand.java
+++ b/src/main/java/seedu/address/logic/commands/ExitCommand.java
@@ -1,5 +1,7 @@
package seedu.address.logic.commands;
+import java.util.HashMap;
+
import seedu.address.model.Model;
/**
@@ -13,7 +15,9 @@ public class ExitCommand extends Command {
@Override
public CommandResult execute(Model model) {
- return new CommandResult(MESSAGE_EXIT_ACKNOWLEDGEMENT, false, true);
+ HashMap settings = new HashMap<>();
+ settings.put("exit", true);
+ return new CommandResult(MESSAGE_EXIT_ACKNOWLEDGEMENT, settings);
}
}
diff --git a/src/main/java/seedu/address/logic/commands/ExportToCsvCommand.java b/src/main/java/seedu/address/logic/commands/ExportToCsvCommand.java
new file mode 100644
index 00000000000..8fbd4ddd95c
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/ExportToCsvCommand.java
@@ -0,0 +1,25 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.HashMap;
+
+import seedu.address.model.Model;
+
+/**
+ * A command to indicate that the window to export to CSV files should be open.
+ */
+public class ExportToCsvCommand extends Command {
+
+ public static final String COMMAND_WORD = "export";
+
+ public static final String MESSAGE_SUCCESS = "Exported to CSV";
+
+ @Override
+ public CommandResult execute(Model model) {
+ requireNonNull(model);
+ HashMap settings = new HashMap<>();
+ settings.put("exportToCsv", true);
+ return new CommandResult(MESSAGE_SUCCESS, settings);
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/FindCommand.java b/src/main/java/seedu/address/logic/commands/FindCommand.java
index d6b19b0a0de..ed3cbba9e87 100644
--- a/src/main/java/seedu/address/logic/commands/FindCommand.java
+++ b/src/main/java/seedu/address/logic/commands/FindCommand.java
@@ -2,26 +2,33 @@
import static java.util.Objects.requireNonNull;
+import java.util.logging.Logger;
+
+import seedu.address.commons.core.LogsCenter;
import seedu.address.commons.core.Messages;
import seedu.address.model.Model;
-import seedu.address.model.person.NameContainsKeywordsPredicate;
+import seedu.address.model.person.predicates.CombineContainsKeywordsPredicate;
/**
* 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 "
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all clients whose fields 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";
+ + "Parameters: find FIELD KEYWORD [MORE_FIELD] [MORE_KEYWORDS]...\n"
+ + "Example: " + COMMAND_WORD + " n/alice bob i/Undecided";
+
+ public static final String MESSAGE_NO_KEYWORD = "Field must contain a keyword. \n"
+ + "Parameters: find FIELD KEYWORD [MORE_FIELD] [MORE_KEYWORDS]...\n"
+ + "Example: " + COMMAND_WORD + " n/alice bob i/Undecided";
- private final NameContainsKeywordsPredicate predicate;
+ private final CombineContainsKeywordsPredicate predicate;
+ private final Logger logger = LogsCenter.getLogger(FindCommand.class);
- public FindCommand(NameContainsKeywordsPredicate predicate) {
+ public FindCommand(CombineContainsKeywordsPredicate predicate) {
this.predicate = predicate;
}
@@ -29,6 +36,7 @@ public FindCommand(NameContainsKeywordsPredicate predicate) {
public CommandResult execute(Model model) {
requireNonNull(model);
model.updateFilteredPersonList(predicate);
+ logger.info("Model updated filteredPersonList with predicate");
return new CommandResult(
String.format(Messages.MESSAGE_PERSONS_LISTED_OVERVIEW, model.getFilteredPersonList().size()));
}
diff --git a/src/main/java/seedu/address/logic/commands/HelpCommand.java b/src/main/java/seedu/address/logic/commands/HelpCommand.java
index bf824f91bd0..d047b78bd2a 100644
--- a/src/main/java/seedu/address/logic/commands/HelpCommand.java
+++ b/src/main/java/seedu/address/logic/commands/HelpCommand.java
@@ -1,5 +1,7 @@
package seedu.address.logic.commands;
+import java.util.HashMap;
+
import seedu.address.model.Model;
/**
@@ -16,6 +18,8 @@ public class HelpCommand extends Command {
@Override
public CommandResult execute(Model model) {
- return new CommandResult(SHOWING_HELP_MESSAGE, true, false);
+ HashMap settings = new HashMap<>();
+ settings.put("showHelp", true);
+ return new CommandResult(SHOWING_HELP_MESSAGE, settings);
}
}
diff --git a/src/main/java/seedu/address/logic/commands/ImportFromCsvCommand.java b/src/main/java/seedu/address/logic/commands/ImportFromCsvCommand.java
new file mode 100644
index 00000000000..01ee7ccdea8
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/ImportFromCsvCommand.java
@@ -0,0 +1,25 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.HashMap;
+
+import seedu.address.model.Model;
+
+/**
+ * A command to indicate that the window to import CSV files from should be open.
+ */
+public class ImportFromCsvCommand extends Command {
+
+ public static final String COMMAND_WORD = "import";
+
+ public static final String MESSAGE_SUCCESS = "Imported CSV";
+
+ @Override
+ public CommandResult execute(Model model) {
+ requireNonNull(model);
+ HashMap settings = new HashMap<>();
+ settings.put("importFromCsv", true);
+ return new CommandResult(MESSAGE_SUCCESS, settings);
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/ListCommand.java b/src/main/java/seedu/address/logic/commands/ListCommand.java
index 84be6ad2596..ad66dd0c058 100644
--- a/src/main/java/seedu/address/logic/commands/ListCommand.java
+++ b/src/main/java/seedu/address/logic/commands/ListCommand.java
@@ -12,7 +12,7 @@ public class ListCommand extends Command {
public static final String COMMAND_WORD = "list";
- public static final String MESSAGE_SUCCESS = "Listed all persons";
+ public static final String MESSAGE_SUCCESS = "Listed all clients";
@Override
diff --git a/src/main/java/seedu/address/logic/commands/ListPackageCommand.java b/src/main/java/seedu/address/logic/commands/ListPackageCommand.java
new file mode 100644
index 00000000000..591dbccc095
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/ListPackageCommand.java
@@ -0,0 +1,25 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.HashMap;
+
+import seedu.address.model.Model;
+
+/**
+ * A command to indicate that the insurance packages should be displayed.
+ */
+public class ListPackageCommand extends Command {
+
+ public static final String COMMAND_WORD = "listp";
+
+ public static final String MESSAGE_SUCCESS = "Listed all packages";
+
+ @Override
+ public CommandResult execute(Model model) {
+ requireNonNull(model);
+ HashMap settings = new HashMap<>();
+ settings.put("showPackages", true);
+ return new CommandResult(MESSAGE_SUCCESS, settings);
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/PriorityListCommand.java b/src/main/java/seedu/address/logic/commands/PriorityListCommand.java
new file mode 100644
index 00000000000..7b3625adc03
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/PriorityListCommand.java
@@ -0,0 +1,30 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import seedu.address.model.Model;
+
+/**
+ * Sorts and displays the list of persons in the address book by the priority level of their tags.
+ */
+public class PriorityListCommand extends Command {
+ public static final String COMMAND_WORD = "prioList";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Sorts and lists contacts by priority "
+ + "level of their tags.";
+
+ public static final String MESSAGE_SUCCESS = "Listed by priority!";
+
+ private static Logger logger = Logger.getLogger("PriorityListCommandLogger");
+
+ @Override
+ public CommandResult execute(Model model) {
+ requireNonNull(model);
+ model.sortByPriority();
+ logger.log(Level.INFO, "Priority list command executed successfully!");
+ return new CommandResult(MESSAGE_SUCCESS);
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/RedoCommand.java b/src/main/java/seedu/address/logic/commands/RedoCommand.java
new file mode 100644
index 00000000000..fd6dbed776e
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/RedoCommand.java
@@ -0,0 +1,29 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.logging.Logger;
+
+import seedu.address.commons.core.LogsCenter;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+
+/**
+ * Lists all persons in the address book to the user.
+ */
+public class RedoCommand extends Command {
+
+ public static final String COMMAND_WORD = "redo";
+
+ public static final String MESSAGE_SUCCESS = "Command redone";
+
+ private final Logger logger = LogsCenter.getLogger(RedoCommand.class);
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+ model.redoCommand();
+ logger.info("AddressBook successfully redone");
+ return new CommandResult(MESSAGE_SUCCESS);
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/UndoCommand.java b/src/main/java/seedu/address/logic/commands/UndoCommand.java
new file mode 100644
index 00000000000..b08aa3a2283
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/UndoCommand.java
@@ -0,0 +1,29 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.logging.Logger;
+
+import seedu.address.commons.core.LogsCenter;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+
+/**
+ * Lists all persons in the address book to the user.
+ */
+public class UndoCommand extends Command {
+
+ public static final String COMMAND_WORD = "undo";
+
+ public static final String MESSAGE_SUCCESS = "Command undone";
+
+ private final Logger logger = LogsCenter.getLogger(UndoCommand.class);
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+ model.undoCommand();
+ logger.info("AddressBook successfully undone");
+ 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 3b8bfa035e8..e6af2971bd3 100644
--- a/src/main/java/seedu/address/logic/parser/AddCommandParser.java
+++ b/src/main/java/seedu/address/logic/parser/AddCommandParser.java
@@ -3,17 +3,19 @@
import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS;
import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_INSURANCE_PACKAGE;
import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME;
import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE;
import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG;
-import java.util.Set;
+import java.util.ArrayList;
import java.util.stream.Stream;
import seedu.address.logic.commands.AddCommand;
import seedu.address.logic.parser.exceptions.ParseException;
import seedu.address.model.person.Address;
import seedu.address.model.person.Email;
+import seedu.address.model.person.InsurancePackage;
import seedu.address.model.person.Name;
import seedu.address.model.person.Person;
import seedu.address.model.person.Phone;
@@ -31,9 +33,11 @@ public class AddCommandParser implements Parser {
*/
public AddCommand parse(String args) throws ParseException {
ArgumentMultimap argMultimap =
- ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, PREFIX_TAG);
+ ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_INSURANCE_PACKAGE,
+ PREFIX_ADDRESS, PREFIX_TAG);
- if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_ADDRESS, PREFIX_PHONE, PREFIX_EMAIL)
+ if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_ADDRESS, PREFIX_PHONE, PREFIX_EMAIL,
+ PREFIX_INSURANCE_PACKAGE)
|| !argMultimap.getPreamble().isEmpty()) {
throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE));
}
@@ -42,9 +46,11 @@ public AddCommand parse(String args) throws ParseException {
Phone phone = ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE).get());
Email email = ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL).get());
Address address = ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS).get());
- Set tagList = ParserUtil.parseTags(argMultimap.getAllValues(PREFIX_TAG));
+ InsurancePackage insurancePackage = ParserUtil.parseInsurancePackage
+ (argMultimap.getValue(PREFIX_INSURANCE_PACKAGE).get());
+ ArrayList tagList = ParserUtil.parseTags(argMultimap.getAllValues(PREFIX_TAG));
- Person person = new Person(name, phone, email, address, tagList);
+ Person person = new Person(name, phone, email, insurancePackage, address, tagList);
return new AddCommand(person);
}
diff --git a/src/main/java/seedu/address/logic/parser/AddPackageCommandParser.java b/src/main/java/seedu/address/logic/parser/AddPackageCommandParser.java
new file mode 100644
index 00000000000..71c16e010e6
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/AddPackageCommandParser.java
@@ -0,0 +1,44 @@
+package seedu.address.logic.parser;
+
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_INSURANCE_PACKAGE;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_PACKAGE_DESC;
+
+import java.util.stream.Stream;
+
+import seedu.address.logic.commands.AddPackageCommand;
+import seedu.address.logic.parser.exceptions.ParseException;
+import seedu.address.model.person.InsurancePackage;
+
+public class AddPackageCommandParser implements Parser {
+
+ /**
+ * Parses the given {@code String} of arguments in the context of the AddPackageCommand
+ * and returns an AddPackageCommand object for execution.
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public AddPackageCommand parse(String args) throws ParseException {
+ System.out.println(args);
+ ArgumentMultimap argMultimap =
+ ArgumentTokenizer.tokenize(args, PREFIX_INSURANCE_PACKAGE, PREFIX_PACKAGE_DESC);
+
+ if (!arePrefixesPresent(argMultimap, PREFIX_INSURANCE_PACKAGE, PREFIX_PACKAGE_DESC)
+ || !argMultimap.getPreamble().isEmpty()) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddPackageCommand.MESSAGE_USAGE));
+ }
+
+ String packageName = ParserUtil.parseInsurancePackageName(argMultimap.getValue(PREFIX_INSURANCE_PACKAGE).get());
+ String packageDesc = ParserUtil.parseInsurancePackageDesc(argMultimap.getValue(PREFIX_PACKAGE_DESC).get());
+ InsurancePackage insurancePackage = new InsurancePackage(packageName, packageDesc);
+ return new AddPackageCommand(insurancePackage);
+ }
+
+ /**
+ * 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/AddTagCommandParser.java b/src/main/java/seedu/address/logic/parser/AddTagCommandParser.java
new file mode 100644
index 00000000000..914bb5a5bbf
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/AddTagCommandParser.java
@@ -0,0 +1,37 @@
+package seedu.address.logic.parser;
+
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG;
+
+import javafx.util.Pair;
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.AddTagCommand;
+import seedu.address.logic.parser.exceptions.ParseException;
+import seedu.address.model.tag.Tag;
+
+/**
+ * Parses input arguments and creates a new AddTagCommand object
+ */
+public class AddTagCommandParser implements Parser {
+
+ /**
+ * Parses the given {@code String} of arguments in the context of the AddTagCommand
+ * and returns an AddTagCommand object for execution.
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public AddTagCommand parse(String args) throws ParseException {
+ ArgumentMultimap argMultimap =
+ ArgumentTokenizer.tokenize(args, PREFIX_TAG);
+ Pair indexAndTag;
+
+ try {
+ indexAndTag = ParserUtil.parseOutIndex(argMultimap.getPreamble());
+ } catch (ParseException pe) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddTagCommand.MESSAGE_USAGE), pe);
+ }
+
+ Index index = indexAndTag.getKey();
+ Tag tag = ParserUtil.parseTag(indexAndTag.getValue());
+ return new AddTagCommand(index, tag);
+ }
+}
diff --git a/src/main/java/seedu/address/logic/parser/AddToClipboardCommandParser.java b/src/main/java/seedu/address/logic/parser/AddToClipboardCommandParser.java
new file mode 100644
index 00000000000..99fbe455387
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/AddToClipboardCommandParser.java
@@ -0,0 +1,43 @@
+package seedu.address.logic.parser;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME;
+
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.AddToClipboardCommand;
+import seedu.address.logic.parser.exceptions.ParseException;
+import seedu.address.model.person.Name;
+import seedu.address.model.person.predicates.NameExistsPredicate;
+
+/**
+ * Parses input arguments and creates a new {@code AddToClipboardCommand} object
+ */
+public class AddToClipboardCommandParser implements Parser {
+
+ /**
+ * Parses the given {@code String} of arguments in the context of the {@code AddToClipboardCommand}
+ * and returns a {@code AddToClipboardCommand} object for execution.
+ * @throws ParseException if the user input does not conform to the expected format
+ */
+ public AddToClipboardCommand parse(String args) throws ParseException {
+ requireNonNull(args);
+ ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_NAME);
+ String preamble = argMultimap.getPreamble();
+
+ if (!preamble.equals("")) {
+ //An index was specified
+ Index index = ParserUtil.parseIndex(preamble);
+ return new AddToClipboardCommand(index);
+ } else if (!argMultimap.getValue(PREFIX_NAME).isEmpty()) {
+ //A name was specified
+ Name name = ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get());
+ return new AddToClipboardCommand(new NameExistsPredicate(name));
+ } else {
+ //Neither an index nor name was specified
+ throw new ParseException(String.format(
+ MESSAGE_INVALID_COMMAND_FORMAT, AddToClipboardCommand.MESSAGE_USAGE));
+ }
+ }
+
+}
diff --git a/src/main/java/seedu/address/logic/parser/AddressBookParser.java b/src/main/java/seedu/address/logic/parser/AddressBookParser.java
index 1e466792b46..3d00a8b0a20 100644
--- a/src/main/java/seedu/address/logic/parser/AddressBookParser.java
+++ b/src/main/java/seedu/address/logic/parser/AddressBookParser.java
@@ -7,14 +7,27 @@
import java.util.regex.Pattern;
import seedu.address.logic.commands.AddCommand;
+import seedu.address.logic.commands.AddPackageCommand;
+import seedu.address.logic.commands.AddTagCommand;
+import seedu.address.logic.commands.AddToClipboardCommand;
import seedu.address.logic.commands.ClearCommand;
import seedu.address.logic.commands.Command;
import seedu.address.logic.commands.DeleteCommand;
+import seedu.address.logic.commands.DeletePackageCommand;
+import seedu.address.logic.commands.DeleteTagCommand;
import seedu.address.logic.commands.EditCommand;
+import seedu.address.logic.commands.EditPackageCommand;
+import seedu.address.logic.commands.EditTagCommand;
import seedu.address.logic.commands.ExitCommand;
+import seedu.address.logic.commands.ExportToCsvCommand;
import seedu.address.logic.commands.FindCommand;
import seedu.address.logic.commands.HelpCommand;
+import seedu.address.logic.commands.ImportFromCsvCommand;
import seedu.address.logic.commands.ListCommand;
+import seedu.address.logic.commands.ListPackageCommand;
+import seedu.address.logic.commands.PriorityListCommand;
+import seedu.address.logic.commands.RedoCommand;
+import seedu.address.logic.commands.UndoCommand;
import seedu.address.logic.parser.exceptions.ParseException;
/**
@@ -47,12 +60,30 @@ public Command parseCommand(String userInput) throws ParseException {
case AddCommand.COMMAND_WORD:
return new AddCommandParser().parse(arguments);
+ case AddPackageCommand.COMMAND_WORD:
+ return new AddPackageCommandParser().parse(arguments);
+
+ case AddTagCommand.COMMAND_WORD:
+ return new AddTagCommandParser().parse(arguments);
+
case EditCommand.COMMAND_WORD:
return new EditCommandParser().parse(arguments);
+ case EditPackageCommand.COMMAND_WORD:
+ return new EditPackageCommandParser().parse(arguments);
+
+ case EditTagCommand.COMMAND_WORD:
+ return new EditTagCommandParser().parse(arguments);
+
case DeleteCommand.COMMAND_WORD:
return new DeleteCommandParser().parse(arguments);
+ case DeletePackageCommand.COMMAND_WORD:
+ return new DeletePackageCommandParser().parse(arguments);
+
+ case DeleteTagCommand.COMMAND_WORD:
+ return new DeleteTagCommandParser().parse(arguments);
+
case ClearCommand.COMMAND_WORD:
return new ClearCommand();
@@ -62,12 +93,33 @@ public Command parseCommand(String userInput) throws ParseException {
case ListCommand.COMMAND_WORD:
return new ListCommand();
+ case ListPackageCommand.COMMAND_WORD:
+ return new ListPackageCommand();
+
+ case PriorityListCommand.COMMAND_WORD:
+ return new PriorityListCommand();
+
case ExitCommand.COMMAND_WORD:
return new ExitCommand();
case HelpCommand.COMMAND_WORD:
return new HelpCommand();
+ case AddToClipboardCommand.COMMAND_WORD:
+ return new AddToClipboardCommandParser().parse(arguments);
+
+ case ImportFromCsvCommand.COMMAND_WORD:
+ return new ImportFromCsvCommand();
+
+ case ExportToCsvCommand.COMMAND_WORD:
+ return new ExportToCsvCommand();
+
+ case UndoCommand.COMMAND_WORD:
+ return new UndoCommand();
+
+ case RedoCommand.COMMAND_WORD:
+ return new RedoCommand();
+
default:
throw new ParseException(MESSAGE_UNKNOWN_COMMAND);
}
diff --git a/src/main/java/seedu/address/logic/parser/ArgumentMultimap.java b/src/main/java/seedu/address/logic/parser/ArgumentMultimap.java
index 954c8e18f8e..ca75ca884dc 100644
--- a/src/main/java/seedu/address/logic/parser/ArgumentMultimap.java
+++ b/src/main/java/seedu/address/logic/parser/ArgumentMultimap.java
@@ -31,6 +31,21 @@ public void put(Prefix prefix, String argValue) {
argMultimap.put(prefix, argValues);
}
+ public Map> getArgMap() {
+ return argMapCopy();
+ }
+
+ /**
+ * Returns a copy of the argMultimap
+ */
+ private Map> argMapCopy() {
+ Map> copy = new HashMap<>();
+ for (Map.Entry> entry : argMultimap.entrySet()) {
+ copy.put(entry.getKey(), new ArrayList<>(entry.getValue()));
+ }
+ return copy;
+ }
+
/**
* Returns the last value of {@code prefix}.
*/
diff --git a/src/main/java/seedu/address/logic/parser/CliSyntax.java b/src/main/java/seedu/address/logic/parser/CliSyntax.java
index 75b1a9bf119..bb920195457 100644
--- a/src/main/java/seedu/address/logic/parser/CliSyntax.java
+++ b/src/main/java/seedu/address/logic/parser/CliSyntax.java
@@ -5,11 +5,19 @@
*/
public class CliSyntax {
- /* Prefix definitions */
+ /* Prefix definitions for Person */
public static final Prefix PREFIX_NAME = new Prefix("n/");
public static final Prefix PREFIX_PHONE = new Prefix("p/");
public static final Prefix PREFIX_EMAIL = new Prefix("e/");
public static final Prefix PREFIX_ADDRESS = new Prefix("a/");
+ public static final Prefix PREFIX_INSURANCE_PACKAGE = new Prefix("i/");
public static final Prefix PREFIX_TAG = new Prefix("t/");
+
+ /* Prefix definitions for Insurance Package */
+
+ // reuse
+ // public static final Prefix PREFIX_INSURANCE_PACKAGE = new Prefix("i/");
+ public static final Prefix PREFIX_PACKAGE_DESC = new Prefix("d/");
+
}
diff --git a/src/main/java/seedu/address/logic/parser/DeletePackageCommandParser.java b/src/main/java/seedu/address/logic/parser/DeletePackageCommandParser.java
new file mode 100644
index 00000000000..b5b48576fd0
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/DeletePackageCommandParser.java
@@ -0,0 +1,37 @@
+package seedu.address.logic.parser;
+
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_INSURANCE_PACKAGE;
+
+import java.util.stream.Stream;
+
+import seedu.address.logic.commands.DeletePackageCommand;
+import seedu.address.logic.parser.exceptions.ParseException;
+
+public class DeletePackageCommandParser {
+ /**
+ * Parses the given {@code String} of arguments in the context of the DeletePackageCommand
+ * and returns a DeletePackageCommand object for execution.
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public DeletePackageCommand parse(String args) throws ParseException {
+ ArgumentMultimap argMultimap =
+ ArgumentTokenizer.tokenize(args, PREFIX_INSURANCE_PACKAGE);
+
+ if (!arePrefixesPresent(argMultimap, PREFIX_INSURANCE_PACKAGE)
+ || !argMultimap.getPreamble().isEmpty()) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeletePackageCommand.MESSAGE_USAGE));
+ }
+
+ String packageName = ParserUtil.parseInsurancePackageName(argMultimap.getValue(PREFIX_INSURANCE_PACKAGE).get());
+ return new DeletePackageCommand(packageName);
+ }
+
+ /**
+ * 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/DeleteTagCommandParser.java b/src/main/java/seedu/address/logic/parser/DeleteTagCommandParser.java
new file mode 100644
index 00000000000..25efb5f4761
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/DeleteTagCommandParser.java
@@ -0,0 +1,42 @@
+package seedu.address.logic.parser;
+
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+
+import javafx.util.Pair;
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.DeleteTagCommand;
+import seedu.address.logic.parser.exceptions.ParseException;
+
+/**
+ * Parses input arguments and creates a new DeleteTagCommand object
+ */
+public class DeleteTagCommandParser implements Parser {
+
+ /**
+ * Parses the given {@code String} of arguments in the context of the DeleteTagCommand
+ * and returns an DeleteTagCommand object for execution.
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public DeleteTagCommand parse(String args) throws ParseException {
+ ArgumentMultimap argMultimap =
+ ArgumentTokenizer.tokenize(args);
+ Pair indexAndTagNumber;
+ Pair tagNumberAsKey;
+
+ try {
+ indexAndTagNumber = ParserUtil.parseOutIndex(argMultimap.getPreamble());
+ tagNumberAsKey = ParserUtil.parseOutNumber(indexAndTagNumber.getValue());
+ } catch (ParseException pe) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteTagCommand.MESSAGE_USAGE), pe);
+ }
+
+ if (tagNumberAsKey.getValue().length() > 0) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteTagCommand.MESSAGE_USAGE));
+ }
+
+ Index index = indexAndTagNumber.getKey();
+ Integer tagNumber = tagNumberAsKey.getKey();
+ return new DeleteTagCommand(index, tagNumber);
+ }
+
+}
diff --git a/src/main/java/seedu/address/logic/parser/EditCommandParser.java b/src/main/java/seedu/address/logic/parser/EditCommandParser.java
index 845644b7dea..e2be8074295 100644
--- a/src/main/java/seedu/address/logic/parser/EditCommandParser.java
+++ b/src/main/java/seedu/address/logic/parser/EditCommandParser.java
@@ -4,14 +4,15 @@
import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS;
import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_INSURANCE_PACKAGE;
import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME;
import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE;
import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG;
+import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Optional;
-import java.util.Set;
import seedu.address.commons.core.index.Index;
import seedu.address.logic.commands.EditCommand;
@@ -32,7 +33,8 @@ public class EditCommandParser implements Parser {
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_INSURANCE_PACKAGE,
+ PREFIX_ADDRESS, PREFIX_TAG);
Index index;
@@ -52,6 +54,10 @@ 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_INSURANCE_PACKAGE).isPresent()) {
+ editPersonDescriptor.setInsurancePackage(ParserUtil
+ .parseInsurancePackage(argMultimap.getValue(PREFIX_INSURANCE_PACKAGE).get()));
+ }
if (argMultimap.getValue(PREFIX_ADDRESS).isPresent()) {
editPersonDescriptor.setAddress(ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS).get()));
}
@@ -69,14 +75,14 @@ public EditCommand parse(String args) throws ParseException {
* If {@code tags} contain only one element which is an empty string, it will be parsed into a
* {@code Set} containing zero tags.
*/
- private Optional> parseTagsForEdit(Collection tags) throws ParseException {
+ private Optional> parseTagsForEdit(Collection tags) throws ParseException {
assert tags != null;
if (tags.isEmpty()) {
return Optional.empty();
}
- Collection tagSet = tags.size() == 1 && tags.contains("") ? Collections.emptySet() : tags;
- return Optional.of(ParserUtil.parseTags(tagSet));
+ Collection tagList = tags.size() == 1 && tags.contains("") ? Collections.emptyList() : tags;
+ return Optional.of(ParserUtil.parseTags(tagList));
}
}
diff --git a/src/main/java/seedu/address/logic/parser/EditPackageCommandParser.java b/src/main/java/seedu/address/logic/parser/EditPackageCommandParser.java
new file mode 100644
index 00000000000..8d3b8d1a9f9
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/EditPackageCommandParser.java
@@ -0,0 +1,41 @@
+package seedu.address.logic.parser;
+
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_INSURANCE_PACKAGE;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_PACKAGE_DESC;
+
+import java.util.stream.Stream;
+
+import seedu.address.logic.commands.EditPackageCommand;
+import seedu.address.logic.parser.exceptions.ParseException;
+
+public class EditPackageCommandParser implements Parser {
+
+ /**
+ * Parses the given {@code String} of arguments in the context of the EditPackageCommand
+ * and returns an EditPackageCommand object for execution.
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public EditPackageCommand parse(String args) throws ParseException {
+ ArgumentMultimap argMultimap =
+ ArgumentTokenizer.tokenize(args, PREFIX_INSURANCE_PACKAGE, PREFIX_PACKAGE_DESC);
+
+ if (!arePrefixesPresent(argMultimap, PREFIX_INSURANCE_PACKAGE, PREFIX_PACKAGE_DESC)
+ || !argMultimap.getPreamble().isEmpty()) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditPackageCommand.MESSAGE_USAGE));
+ }
+
+ String packageName = ParserUtil.parseInsurancePackageName(argMultimap.getValue(PREFIX_INSURANCE_PACKAGE).get());
+ String newPackageDesc = ParserUtil.parseInsurancePackageDesc(argMultimap.getValue(PREFIX_PACKAGE_DESC).get());
+ return new EditPackageCommand(packageName, newPackageDesc);
+ }
+
+ /**
+ * 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/EditTagCommandParser.java b/src/main/java/seedu/address/logic/parser/EditTagCommandParser.java
new file mode 100644
index 00000000000..3ecfa69be24
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/EditTagCommandParser.java
@@ -0,0 +1,40 @@
+package seedu.address.logic.parser;
+
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+
+import javafx.util.Pair;
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.EditTagCommand;
+import seedu.address.logic.parser.exceptions.ParseException;
+import seedu.address.model.tag.Tag;
+
+/**
+ * Parses input arguments and creates a new EditTagCommand object
+ */
+public class EditTagCommandParser implements Parser {
+
+ /**
+ * Parses the given {@code String} of arguments in the context of the EditTagCommand
+ * and returns an EditTagCommand object for execution.
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public EditTagCommand parse(String args) throws ParseException {
+ ArgumentMultimap argMultimap =
+ ArgumentTokenizer.tokenize(args);
+ Pair indexAndRemaining;
+ Pair tagNumberAndNewTag;
+
+ try {
+ indexAndRemaining = ParserUtil.parseOutIndex(argMultimap.getPreamble());
+ tagNumberAndNewTag = ParserUtil.parseOutNumber(indexAndRemaining.getValue());
+ } catch (ParseException pe) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditTagCommand.MESSAGE_USAGE), pe);
+ }
+
+ Index index = indexAndRemaining.getKey();
+ Integer tagNumber = tagNumberAndNewTag.getKey();
+ Tag newTag = ParserUtil.parseTag(tagNumberAndNewTag.getValue());
+ return new EditTagCommand(index, tagNumber, newTag);
+ }
+
+}
diff --git a/src/main/java/seedu/address/logic/parser/FindCommandParser.java b/src/main/java/seedu/address/logic/parser/FindCommandParser.java
index 4fb71f23103..0432793a57a 100644
--- a/src/main/java/seedu/address/logic/parser/FindCommandParser.java
+++ b/src/main/java/seedu/address/logic/parser/FindCommandParser.java
@@ -1,33 +1,76 @@
package seedu.address.logic.parser;
import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_INSURANCE_PACKAGE;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG;
-import java.util.Arrays;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.stream.Stream;
+import seedu.address.commons.core.LogsCenter;
import seedu.address.logic.commands.FindCommand;
import seedu.address.logic.parser.exceptions.ParseException;
-import seedu.address.model.person.NameContainsKeywordsPredicate;
+import seedu.address.logic.parser.exceptions.ParseNoKeywordException;
+import seedu.address.logic.parser.exceptions.ParseNoPrefixException;
+import seedu.address.model.person.predicates.CombineContainsKeywordsPredicate;
+import seedu.address.model.person.predicates.FieldContainsKeywordsPredicate;
/**
* Parses input arguments and creates a new FindCommand object
*/
public class FindCommandParser implements Parser {
-
+ private final Logger logger = LogsCenter.getLogger(FindCommandParser.class);
/**
- * Parses the given {@code String} of arguments in the context of the FindCommand
+ * Parses the given {@code String} of prefixes and arguments in the context of the FindCommand
* and returns a FindCommand object for execution.
* @throws ParseException if the user input does not conform the expected format
*/
public FindCommand parse(String args) throws ParseException {
- String trimmedArgs = args.trim();
- if (trimmedArgs.isEmpty()) {
- throw new ParseException(
- String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE));
+ logger.info("Starting parse find args");
+ ArgumentMultimap argMultimap =
+ ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_INSURANCE_PACKAGE,
+ PREFIX_ADDRESS, PREFIX_TAG);
+ if (!aPrefixPresent(argMultimap, PREFIX_NAME, PREFIX_ADDRESS, PREFIX_PHONE, PREFIX_EMAIL,
+ PREFIX_INSURANCE_PACKAGE, PREFIX_TAG)
+ || !argMultimap.getPreamble().isEmpty()) {
+ logger.log(Level.WARNING, "Find prefix not present");
+ throw new ParseNoPrefixException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE));
+ }
+ if (noKeywordsPresent(argMultimap, PREFIX_NAME, PREFIX_ADDRESS, PREFIX_PHONE, PREFIX_EMAIL,
+ PREFIX_INSURANCE_PACKAGE, PREFIX_TAG)) {
+ logger.log(Level.WARNING, "Find keywords not present");
+ throw new ParseNoKeywordException(String.format(MESSAGE_INVALID_COMMAND_FORMAT,
+ FindCommand.MESSAGE_NO_KEYWORD));
}
- String[] nameKeywords = trimmedArgs.split("\\s+");
+ assert !argMultimap.getArgMap().isEmpty() : "Argument Multimap should not be empty";
+ List predicatesList = ParserUtil.parseArgMap(argMultimap);
+ return new FindCommand(new CombineContainsKeywordsPredicate(predicatesList));
+ }
- return new FindCommand(new NameContainsKeywordsPredicate(Arrays.asList(nameKeywords)));
+ /**
+ * Returns true if one of the prefix contains {@code Optional} values in the given
+ * {@code ArgumentMultimap}.
+ */
+ private static boolean aPrefixPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) {
+ return Stream.of(prefixes).anyMatch(prefix -> argumentMultimap.getValue(prefix).isPresent());
}
+ /**
+ * Returns true if any of the values mapped to the field prefix is empty
+ */
+ private static boolean noKeywordsPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) {
+ return Stream.of(prefixes).anyMatch(prefix -> {
+ if (argumentMultimap.getValue(prefix).isPresent()) {
+ return argumentMultimap.getValue(prefix).get().isBlank();
+ }
+ return false;
+ });
+ }
}
diff --git a/src/main/java/seedu/address/logic/parser/ParserUtil.java b/src/main/java/seedu/address/logic/parser/ParserUtil.java
index b117acb9c55..3c148038104 100644
--- a/src/main/java/seedu/address/logic/parser/ParserUtil.java
+++ b/src/main/java/seedu/address/logic/parser/ParserUtil.java
@@ -1,26 +1,45 @@
package seedu.address.logic.parser;
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_INSURANCE_PACKAGE;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG;
+import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collection;
-import java.util.HashSet;
-import java.util.Set;
+import java.util.List;
+import java.util.logging.Logger;
+import javafx.util.Pair;
+import seedu.address.commons.core.LogsCenter;
import seedu.address.commons.core.index.Index;
import seedu.address.commons.util.StringUtil;
import seedu.address.logic.parser.exceptions.ParseException;
import seedu.address.model.person.Address;
import seedu.address.model.person.Email;
+import seedu.address.model.person.InsurancePackage;
import seedu.address.model.person.Name;
import seedu.address.model.person.Phone;
+import seedu.address.model.person.predicates.AddressContainsKeywordsPredicate;
+import seedu.address.model.person.predicates.EmailContainsKeywordsPredicate;
+import seedu.address.model.person.predicates.FieldContainsKeywordsPredicate;
+import seedu.address.model.person.predicates.InsurancePackageContainsKeywordsPredicate;
+import seedu.address.model.person.predicates.NameContainsKeywordsPredicate;
+import seedu.address.model.person.predicates.PhoneContainsKeywordsPredicate;
+import seedu.address.model.person.predicates.TagsContainsKeywordsPredicate;
+import seedu.address.model.tag.Priority;
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.";
+ private static final Logger logger = LogsCenter.getLogger(ParserUtil.class);
/**
* Parses {@code oneBasedIndex} into an {@code Index} and returns it. Leading and trailing whitespaces will be
@@ -35,6 +54,51 @@ public static Index parseIndex(String oneBasedIndex) throws ParseException {
return Index.fromOneBased(Integer.parseInt(trimmedIndex));
}
+ /**
+ * Parses {@code indexAndWords} into an {@code Pair} and returns it. Leading and trailing
+ * whitespaces will be trimmed. Must contain a number at the beginning and then words.
+ *
+ * @throws ParseException if the specified index is invalid (not non-zero unsigned integer).
+ */
+ public static Pair parseOutIndex(String indexAndWords) throws ParseException {
+ String trimmedInput = indexAndWords.trim();
+
+ // splitIndexAndRemainingString[0] contains index no., splitIndexAndRemainingString[2] contains remaining string
+ String[] splitIndexAndRemainingString;
+ Index index;
+
+ try {
+ splitIndexAndRemainingString = trimmedInput.split(" ", 2);
+ index = parseIndex(splitIndexAndRemainingString[0]);
+ } catch (Exception e) {
+ throw new ParseException(MESSAGE_INVALID_INDEX);
+ }
+ return new Pair<>(index, splitIndexAndRemainingString.length > 1 ? splitIndexAndRemainingString[1].trim() : "");
+ }
+
+ /**
+ * Parses {@code numberAndWords} into an {@code Pair} and returns it. Leading and trailing
+ * whitespaces will be trimmed. Must contain a number at the beginning and then words.
+ *
+ * @throws ParseException if the specified integer is invalid (not non-zero unsigned integer).
+ */
+ public static Pair parseOutNumber(String numberAndWords) throws ParseException {
+ String trimmedInput = numberAndWords.trim();
+
+ // splitIndexAndRemainingString[0] contains number, splitIndexAndRemainingString[2] contains remaining string
+ String[] splitIndexAndRemainingString;
+ Integer number;
+
+ try {
+ splitIndexAndRemainingString = trimmedInput.split(" ", 2);
+ number = Integer.valueOf(splitIndexAndRemainingString[0].trim());
+ } catch (Exception e) {
+ throw new ParseException(MESSAGE_INVALID_INDEX);
+ }
+ return new Pair<>(number, splitIndexAndRemainingString.length > 1
+ ? splitIndexAndRemainingString[1].trim() : "");
+ }
+
/**
* Parses a {@code String name} into a {@code Name}.
* Leading and trailing whitespaces will be trimmed.
@@ -65,6 +129,45 @@ public static Phone parsePhone(String phone) throws ParseException {
return new Phone(trimmedPhone);
}
+ /**
+ * Parses a {@code String insurance package} into an {@code InsurancePackage}.
+ * Leading and trailing whitespaces will be trimmed.
+ *
+ * @throws ParseException if the given {@code address} is invalid.
+ */
+ public static InsurancePackage parseInsurancePackage(String insurancePackage) throws ParseException {
+ requireNonNull(insurancePackage);
+ String trimmedInsurancePackage = insurancePackage.trim();
+ if (!InsurancePackage.isValidInsurancePackage(trimmedInsurancePackage)) {
+ throw new ParseException(InsurancePackage.MESSAGE_CONSTRAINTS);
+ }
+ return new InsurancePackage(trimmedInsurancePackage);
+ }
+
+ /**
+ * Parses a {@code String insurance package name}.
+ * Leading and trailing whitespaces will be trimmed.
+ *
+ * @throws ParseException if the given {@code address} is invalid.
+ */
+ public static String parseInsurancePackageName(String insurancePackage) throws ParseException {
+ requireNonNull(insurancePackage);
+ String trimmedInsurancePackage = insurancePackage.trim();
+ if (!InsurancePackage.isValidInsurancePackage(trimmedInsurancePackage)) {
+ throw new ParseException(InsurancePackage.MESSAGE_CONSTRAINTS);
+ }
+ return trimmedInsurancePackage;
+ }
+
+ /**
+ * Parses a {@code String insurance package description}.
+ * Leading and trailing whitespaces will be trimmed.
+ */
+ public static String parseInsurancePackageDesc(String insurancePackageDesc) {
+ requireNonNull(insurancePackageDesc);
+ return insurancePackageDesc.trim();
+ }
+
/**
* Parses a {@code String address} into an {@code Address}.
* Leading and trailing whitespaces will be trimmed.
@@ -95,6 +198,40 @@ public static Email parseEmail(String email) throws ParseException {
return new Email(trimmedEmail);
}
+ /**
+ * Parses a {@code String tag} into a {@code Pair}.
+ * Separates the priority from the tag entered.
+ */
+ public static Pair parsePriority(String tag) {
+ requireNonNull(tag);
+ Priority priority = null;
+ String tagName = "";
+ String possiblePriority = tag.length() > 4 ? tag.substring(tag.length() - 4).toLowerCase() : "";
+
+ switch (possiblePriority) {
+ case " :p1":
+ tagName = tag.substring(0, tag.length() - 4);
+ priority = Priority.PRIORITY_1;
+ break;
+ case " :p2":
+ tagName = tag.substring(0, tag.length() - 4);
+ priority = Priority.PRIORITY_2;
+ break;
+ case " :p3":
+ tagName = tag.substring(0, tag.length() - 4);
+ priority = Priority.PRIORITY_3;
+ break;
+ case " :p4":
+ tagName = tag.substring(0, tag.length() - 4);
+ priority = Priority.PRIORITY_4;
+ break;
+ default:
+ tagName = tag;
+ priority = null;
+ }
+ return new Pair<>(tagName, priority);
+ }
+
/**
* Parses a {@code String tag} into a {@code Tag}.
* Leading and trailing whitespaces will be trimmed.
@@ -103,22 +240,74 @@ public static Email parseEmail(String email) throws ParseException {
*/
public static Tag parseTag(String tag) throws ParseException {
requireNonNull(tag);
- String trimmedTag = tag.trim();
+ Pair tagAndPriority = parsePriority(tag.trim());
+ String trimmedTag = tagAndPriority.getKey().trim();
+
if (!Tag.isValidTagName(trimmedTag)) {
throw new ParseException(Tag.MESSAGE_CONSTRAINTS);
}
- return new Tag(trimmedTag);
+ return new Tag(trimmedTag, tagAndPriority.getValue());
}
/**
* Parses {@code Collection tags} into a {@code Set}.
*/
- public static Set parseTags(Collection tags) throws ParseException {
+ public static ArrayList parseTags(Collection tags) throws ParseException {
requireNonNull(tags);
- final Set tagSet = new HashSet<>();
+ final ArrayList tagList = new ArrayList<>();
for (String tagName : tags) {
- tagSet.add(parseTag(tagName));
+ tagList.add(parseTag(tagName));
+ }
+ return tagList;
+ }
+
+ /**
+ * Parses the arguments given for the find field into a List of String
+ */
+ public static List parseFindKeywords(String input) {
+ return Arrays.asList(input.split("\\s+"));
+ }
+
+ /**
+ * Parses the arguments created in {@code ArgumentMultimap} to generate a List of
+ * {@code FieldContainsKeywordsPredicate}
+ */
+ public static List parseArgMap(ArgumentMultimap argMultimap) {
+ List predicatesList = new ArrayList<>();
+ if (argMultimap.getValue(PREFIX_NAME).isPresent()) {
+ List nameKeywords = ParserUtil.parseFindKeywords((argMultimap.getValue(PREFIX_NAME).get()));
+ NameContainsKeywordsPredicate namePredicate = new NameContainsKeywordsPredicate(nameKeywords);
+ predicatesList.add(namePredicate);
+ }
+ if (argMultimap.getValue(PREFIX_PHONE).isPresent()) {
+ List phoneKeywords = ParserUtil.parseFindKeywords(argMultimap.getValue(PREFIX_PHONE).get());
+ PhoneContainsKeywordsPredicate phonePredicate = new PhoneContainsKeywordsPredicate(phoneKeywords);
+ predicatesList.add(phonePredicate);
+ }
+ if (argMultimap.getValue(PREFIX_EMAIL).isPresent()) {
+ List emailKeywords = ParserUtil.parseFindKeywords(argMultimap.getValue(PREFIX_EMAIL).get());
+ EmailContainsKeywordsPredicate emailPredicate = new EmailContainsKeywordsPredicate(emailKeywords);
+ predicatesList.add(emailPredicate);
+ }
+ if (argMultimap.getValue(PREFIX_ADDRESS).isPresent()) {
+ List addressKeywords = ParserUtil.parseFindKeywords(argMultimap.getValue(PREFIX_ADDRESS).get());
+ AddressContainsKeywordsPredicate addressPredicate = new AddressContainsKeywordsPredicate(addressKeywords);
+ predicatesList.add(addressPredicate);
+ }
+ if (argMultimap.getValue(PREFIX_INSURANCE_PACKAGE).isPresent()) {
+ List insurancePackageKeywords =
+ ParserUtil.parseFindKeywords(argMultimap.getValue(PREFIX_INSURANCE_PACKAGE).get());
+ InsurancePackageContainsKeywordsPredicate insurancePackagePredicate =
+ new InsurancePackageContainsKeywordsPredicate(insurancePackageKeywords);
+ predicatesList.add(insurancePackagePredicate);
+ }
+ if (!argMultimap.getAllValues(PREFIX_TAG).isEmpty()) {
+ List tagsKeywords =
+ ParserUtil.parseFindKeywords((String.join(" ", argMultimap.getAllValues(PREFIX_TAG))));
+ TagsContainsKeywordsPredicate tagsPredicate = new TagsContainsKeywordsPredicate(tagsKeywords);
+ predicatesList.add(tagsPredicate);
}
- return tagSet;
+ logger.info("predicatesList for FindCommand created");
+ return predicatesList;
}
}
diff --git a/src/main/java/seedu/address/logic/parser/exceptions/ParseNoKeywordException.java b/src/main/java/seedu/address/logic/parser/exceptions/ParseNoKeywordException.java
new file mode 100644
index 00000000000..cca99d315b1
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/exceptions/ParseNoKeywordException.java
@@ -0,0 +1,10 @@
+package seedu.address.logic.parser.exceptions;
+
+/**
+ * Represents a parse error encountered by a parser when attempting to parse no keywords provided by user
+ */
+public class ParseNoKeywordException extends ParseException {
+ public ParseNoKeywordException(String message) {
+ super(message);
+ }
+}
diff --git a/src/main/java/seedu/address/logic/parser/exceptions/ParseNoPrefixException.java b/src/main/java/seedu/address/logic/parser/exceptions/ParseNoPrefixException.java
new file mode 100644
index 00000000000..e58ab91b2c7
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/exceptions/ParseNoPrefixException.java
@@ -0,0 +1,10 @@
+package seedu.address.logic.parser.exceptions;
+
+/**
+ * Represents a parse error encountered by a parser when attempting to parse no prefixes
+ */
+public class ParseNoPrefixException extends ParseException {
+ public ParseNoPrefixException(String message) {
+ super(message);
+ }
+}
diff --git a/src/main/java/seedu/address/model/AddressBook.java b/src/main/java/seedu/address/model/AddressBook.java
index 1a943a0781a..5abe8b61dc6 100644
--- a/src/main/java/seedu/address/model/AddressBook.java
+++ b/src/main/java/seedu/address/model/AddressBook.java
@@ -2,6 +2,7 @@
import static java.util.Objects.requireNonNull;
+import java.util.Comparator;
import java.util.List;
import javafx.collections.ObservableList;
@@ -93,6 +94,13 @@ public void removePerson(Person key) {
persons.remove(key);
}
+ /**
+ * Sorts the list of persons in this address book using {@code comp}.
+ */
+ public void sort(Comparator comp) {
+ persons.sort(comp);
+ }
+
//// util methods
@Override
diff --git a/src/main/java/seedu/address/model/InsurancePackagesSet.java b/src/main/java/seedu/address/model/InsurancePackagesSet.java
new file mode 100644
index 00000000000..70fb7edf8fc
--- /dev/null
+++ b/src/main/java/seedu/address/model/InsurancePackagesSet.java
@@ -0,0 +1,94 @@
+package seedu.address.model;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.commons.util.CollectionUtil.requireAllNonNull;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+
+import seedu.address.model.person.InsurancePackage;
+
+public class InsurancePackagesSet {
+
+ private List allPackages;
+
+ public InsurancePackagesSet() {
+ allPackages = new ArrayList<>();
+ }
+
+ public InsurancePackagesSet(InsurancePackagesSet s) {
+ allPackages = s.allPackages;
+ }
+
+ /**
+ * Replaces the contents of the packages list.
+ */
+ public void setPackages(List allPackages) {
+ this.allPackages = allPackages;
+ }
+
+ //// person-level operations
+
+ /**
+ * Returns true if a insurancePackage with the same identity exists in the set.
+ */
+ public boolean hasPackage(InsurancePackage insurancePackage) {
+ requireNonNull(insurancePackage);
+ return allPackages.contains(insurancePackage);
+ }
+
+ /**
+ * Adds a package to the set of insurance packages, if it does not exist yet.
+ */
+ public void addPackage(InsurancePackage p) {
+ if (!allPackages.contains(p)) {
+ allPackages.add(p);
+ }
+ }
+
+ public List getPackagesList() {
+ return allPackages;
+ }
+
+
+ /**
+ * Replaces the given package in the list, given the package name, with the given package description.
+ * {@code target} must exist in the address book.
+ */
+ public void setPackage(String targetPackageName, String newPackageDesc) {
+ requireAllNonNull(targetPackageName, newPackageDesc);
+ for (InsurancePackage p: allPackages) {
+ if (p.getPackageName().equals(targetPackageName)) {
+ p.setPackageDescription(newPackageDesc);
+ break;
+ }
+ }
+ }
+
+ /**
+ * Removes {@code key} from this {@code InsurancePackagesSet}.
+ * {@code key} must exist in the set.
+ */
+ public void removePackage(InsurancePackage key) {
+ allPackages.remove(key);
+ }
+
+ public int numPackages() {
+ return allPackages.size();
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ if (!(other instanceof InsurancePackagesSet)) {
+ return false;
+ }
+
+ InsurancePackagesSet otherPackage = (InsurancePackagesSet) other;
+ return new HashSet<>(otherPackage.getPackagesList()).equals(new HashSet<>(allPackages));
+ }
+}
diff --git a/src/main/java/seedu/address/model/Model.java b/src/main/java/seedu/address/model/Model.java
index d54df471c1f..5e32d3dde12 100644
--- a/src/main/java/seedu/address/model/Model.java
+++ b/src/main/java/seedu/address/model/Model.java
@@ -5,6 +5,8 @@
import javafx.collections.ObservableList;
import seedu.address.commons.core.GuiSettings;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.person.InsurancePackage;
import seedu.address.model.person.Person;
/**
@@ -44,14 +46,57 @@ public interface Model {
*/
void setAddressBookFilePath(Path addressBookFilePath);
+ /**
+ * Returns the user prefs' insurance packages file path.
+ */
+ Path getInsurancePackagesFilePath();
+
+ /**
+ * Sets the user prefs' insurance packages file path.
+ */
+ void setInsurancePackagesFilePath(Path insurancePackagesFilePath);
+
/**
* Replaces address book data with the data in {@code addressBook}.
*/
void setAddressBook(ReadOnlyAddressBook addressBook);
+ /**
+ * Replaces address book data with an empty {@code addressBook}.
+ */
+ void resetAddressBook();
+
/** Returns the AddressBook */
ReadOnlyAddressBook getAddressBook();
+ /**
+ * Replaces address book data with the data in {@code addressBook}.
+ */
+ void setInsurancePackagesSet(InsurancePackagesSet insurancePackagesSet);
+
+ /** Returns the AddressBook */
+ InsurancePackagesSet getInsurancePackagesSet();
+
+ /**
+ * Adds the given Insurance Package.
+ */
+ void addInsurancePackage(InsurancePackage p);
+
+ /**
+ * Deletes the given Insurance Package.
+ */
+ void deleteInsurancePackage(InsurancePackage p);
+
+ /**
+ * Returns true if an insurance package with the same identity as {@code p} exists in the set of packages.
+ */
+ boolean hasInsurancePackage(InsurancePackage p);
+
+ /**
+ * Sets the description of an insurance package with the given description.
+ */
+ void setInsurancePackage(String targetPackageName, String newPackageDesc);
+
/**
* Returns true if a person with the same identity as {@code person} exists in the address book.
*/
@@ -84,4 +129,13 @@ public interface Model {
* @throws NullPointerException if {@code predicate} is null.
*/
void updateFilteredPersonList(Predicate predicate);
+
+ /**
+ * Sorts the person list by the priority of their tags.
+ */
+ void sortByPriority();
+
+ void undoCommand() throws CommandException;
+
+ void redoCommand() throws CommandException;
}
diff --git a/src/main/java/seedu/address/model/ModelManager.java b/src/main/java/seedu/address/model/ModelManager.java
index 86c1df298d7..1c190b2fe40 100644
--- a/src/main/java/seedu/address/model/ModelManager.java
+++ b/src/main/java/seedu/address/model/ModelManager.java
@@ -11,7 +11,11 @@
import javafx.collections.transformation.FilteredList;
import seedu.address.commons.core.GuiSettings;
import seedu.address.commons.core.LogsCenter;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.person.InsurancePackage;
import seedu.address.model.person.Person;
+import seedu.address.model.person.comparators.TagPriorityComparator;
+import seedu.address.storage.UndoRedoStorage;
/**
* Represents the in-memory model of the address book data.
@@ -21,23 +25,29 @@ public class ModelManager implements Model {
private final AddressBook addressBook;
private final UserPrefs userPrefs;
+ private InsurancePackagesSet insurancePackagesSet;
private final FilteredList filteredPersons;
+ private UndoRedoStorage undoRedoStorage = new UndoRedoStorage();
+
/**
* Initializes a ModelManager with the given addressBook and userPrefs.
*/
- public ModelManager(ReadOnlyAddressBook addressBook, ReadOnlyUserPrefs userPrefs) {
+ public ModelManager(ReadOnlyAddressBook addressBook, ReadOnlyUserPrefs userPrefs,
+ InsurancePackagesSet insurancePackages) {
requireAllNonNull(addressBook, userPrefs);
logger.fine("Initializing with address book: " + addressBook + " and user prefs " + userPrefs);
this.addressBook = new AddressBook(addressBook);
this.userPrefs = new UserPrefs(userPrefs);
+ this.insurancePackagesSet = new InsurancePackagesSet(insurancePackages);
filteredPersons = new FilteredList<>(this.addressBook.getPersonList());
+ undoRedoStorage.addToUndo(copyAddressBook());
}
public ModelManager() {
- this(new AddressBook(), new UserPrefs());
+ this(new AddressBook(), new UserPrefs(), new InsurancePackagesSet());
}
//=========== UserPrefs ==================================================================================
@@ -75,6 +85,49 @@ public void setAddressBookFilePath(Path addressBookFilePath) {
userPrefs.setAddressBookFilePath(addressBookFilePath);
}
+ @Override
+ public Path getInsurancePackagesFilePath() {
+ return userPrefs.getInsurancePackagesFilePath();
+ }
+
+ @Override
+ public void setInsurancePackagesFilePath(Path insurancePackagesFilePath) {
+ requireNonNull(insurancePackagesFilePath);
+ userPrefs.setInsurancePackagesFilePath(insurancePackagesFilePath);
+ }
+
+ //=========== InsurancePackages ==========================================================================
+ @Override
+ public void setInsurancePackagesSet(InsurancePackagesSet insurancePackagesSet) {
+ this.insurancePackagesSet = new InsurancePackagesSet(insurancePackagesSet);
+ }
+
+ @Override
+ public InsurancePackagesSet getInsurancePackagesSet() {
+ return insurancePackagesSet;
+ }
+
+ @Override
+ public void addInsurancePackage(InsurancePackage p) {
+ this.insurancePackagesSet.addPackage(p);
+ }
+
+ @Override
+ public void deleteInsurancePackage(InsurancePackage p) {
+ this.insurancePackagesSet.removePackage(p);
+ }
+
+ @Override
+ public boolean hasInsurancePackage(InsurancePackage p) {
+ requireNonNull(p);
+ return insurancePackagesSet.hasPackage(p);
+ }
+
+ @Override
+ public void setInsurancePackage(String targetPackageName, String newPackageDesc) {
+ insurancePackagesSet.setPackage(targetPackageName, newPackageDesc);
+ }
+
//=========== AddressBook ================================================================================
@Override
@@ -82,6 +135,13 @@ public void setAddressBook(ReadOnlyAddressBook addressBook) {
this.addressBook.resetData(addressBook);
}
+ @Override
+ public void resetAddressBook() {
+ this.addressBook.resetData(new AddressBook());
+ undoRedoStorage.resetRedoStack();
+ undoRedoStorage.addToUndo(copyAddressBook());
+ }
+
@Override
public ReadOnlyAddressBook getAddressBook() {
return addressBook;
@@ -96,19 +156,49 @@ public boolean hasPerson(Person person) {
@Override
public void deletePerson(Person target) {
addressBook.removePerson(target);
+ undoRedoStorage.resetRedoStack();
+ undoRedoStorage.addToUndo(copyAddressBook());
}
@Override
public void addPerson(Person person) {
addressBook.addPerson(person);
+ undoRedoStorage.resetRedoStack();
+ undoRedoStorage.addToUndo(copyAddressBook());
updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS);
}
@Override
public void setPerson(Person target, Person editedPerson) {
requireAllNonNull(target, editedPerson);
-
addressBook.setPerson(target, editedPerson);
+ undoRedoStorage.resetRedoStack();
+ undoRedoStorage.addToUndo(copyAddressBook());
+ }
+
+ @Override
+ public void undoCommand() throws CommandException {
+ AddressBook newAddressBook = undoRedoStorage.undo();
+ setAddressBook(newAddressBook);
+ }
+
+ @Override
+ public void redoCommand() throws CommandException {
+ AddressBook newAddressBook = undoRedoStorage.redo();
+ setAddressBook(newAddressBook);
+ }
+
+ /**
+ * Makes a deep copy of an AddressBook
+ */
+ public AddressBook copyAddressBook() {
+ AddressBook copiedAddressBook = new AddressBook();
+ int currAddressBookSize = addressBook.getPersonList().size();
+ for (int i = 0; i < currAddressBookSize; i++) {
+ Person copiedPerson = Person.copyPerson(addressBook.getPersonList().get(i));
+ copiedAddressBook.addPerson(copiedPerson);
+ }
+ return copiedAddressBook;
}
//=========== Filtered Person List Accessors =============================================================
@@ -122,12 +212,23 @@ public ObservableList getFilteredPersonList() {
return filteredPersons;
}
+ /**
+ * Filters and updates {@code filteredPersons} by {@code predicate}.
+ */
@Override
public void updateFilteredPersonList(Predicate predicate) {
requireNonNull(predicate);
filteredPersons.setPredicate(predicate);
}
+ /**
+ * Sorts the list of {@code Person} in {@code addressBook} by the priority level of their tags.
+ */
+ @Override
+ public void sortByPriority() {
+ addressBook.sort(new TagPriorityComparator());
+ }
+
@Override
public boolean equals(Object obj) {
// short circuit if same object
diff --git a/src/main/java/seedu/address/model/UserPrefs.java b/src/main/java/seedu/address/model/UserPrefs.java
index 25a5fd6eab9..6db99cb85dd 100644
--- a/src/main/java/seedu/address/model/UserPrefs.java
+++ b/src/main/java/seedu/address/model/UserPrefs.java
@@ -15,6 +15,7 @@ public class UserPrefs implements ReadOnlyUserPrefs {
private GuiSettings guiSettings = new GuiSettings();
private Path addressBookFilePath = Paths.get("data" , "addressbook.json");
+ private Path insurancePackagesFilePath = Paths.get("data" , "insurancePackages.csv");
/**
* Creates a {@code UserPrefs} with default values.
@@ -56,6 +57,15 @@ public void setAddressBookFilePath(Path addressBookFilePath) {
this.addressBookFilePath = addressBookFilePath;
}
+ public Path getInsurancePackagesFilePath() {
+ return insurancePackagesFilePath;
+ }
+
+ public void setInsurancePackagesFilePath(Path insurancePackagesFilePath) {
+ requireNonNull(insurancePackagesFilePath);
+ this.insurancePackagesFilePath = insurancePackagesFilePath;
+ }
+
@Override
public boolean equals(Object other) {
if (other == this) {
@@ -80,7 +90,8 @@ public int hashCode() {
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("Gui Settings : " + guiSettings);
- sb.append("\nLocal data file location : " + addressBookFilePath);
+ sb.append("\nClients data file location : " + addressBookFilePath);
+ sb.append("\nInsurance packages data file location : " + insurancePackagesFilePath);
return sb.toString();
}
diff --git a/src/main/java/seedu/address/model/person/Email.java b/src/main/java/seedu/address/model/person/Email.java
index f866e7133de..9546e581122 100644
--- a/src/main/java/seedu/address/model/person/Email.java
+++ b/src/main/java/seedu/address/model/person/Email.java
@@ -10,17 +10,18 @@
public class Email {
private static final String SPECIAL_CHARACTERS = "+_.-";
- public static final String MESSAGE_CONSTRAINTS = "Emails should be of the format local-part@domain "
+ public static final String MESSAGE_CONSTRAINTS = "Emails should be of the format e.g: local-part@domain.com "
+ "and adhere to the following constraints:\n"
+ "1. The local-part should only contain alphanumeric characters and these special characters, excluding "
+ "the parentheses, (" + SPECIAL_CHARACTERS + "). The local-part may not start or end with any special "
+ "characters.\n"
+ "2. This is followed by a '@' and then a domain name. The domain name is made up of domain labels "
- + "separated by periods.\n"
+ + "separated by only one period.\n"
+ "The domain name must:\n"
+ " - end with a domain label at least 2 characters long\n"
+ " - have each domain label start and end with alphanumeric characters\n"
- + " - have each domain label consist of alphanumeric characters, separated only by hyphens, if any.";
+ + " - have each domain label consist of alphanumeric characters, separated only by hyphens, if any.\n"
+ + " - have ONLY 1 period separating the domain labels: e.g @apple.com";
// alphanumeric and special characters
private static final String ALPHANUMERIC_NO_UNDERSCORE = "[^\\W_]+"; // alphanumeric characters except underscore
private static final String LOCAL_PART_REGEX = "^" + ALPHANUMERIC_NO_UNDERSCORE + "([" + SPECIAL_CHARACTERS + "]"
@@ -28,7 +29,7 @@ public class Email {
private static final String DOMAIN_PART_REGEX = ALPHANUMERIC_NO_UNDERSCORE
+ "(-" + ALPHANUMERIC_NO_UNDERSCORE + ")*";
private static final String DOMAIN_LAST_PART_REGEX = "(" + DOMAIN_PART_REGEX + "){2,}$"; // At least two chars
- private static final String DOMAIN_REGEX = "(" + DOMAIN_PART_REGEX + "\\.)*" + DOMAIN_LAST_PART_REGEX;
+ private static final String DOMAIN_REGEX = "(" + DOMAIN_PART_REGEX + "\\.)" + DOMAIN_LAST_PART_REGEX;
public static final String VALIDATION_REGEX = LOCAL_PART_REGEX + "@" + DOMAIN_REGEX;
public final String value;
diff --git a/src/main/java/seedu/address/model/person/InsurancePackage.java b/src/main/java/seedu/address/model/person/InsurancePackage.java
new file mode 100644
index 00000000000..ed8aa40b436
--- /dev/null
+++ b/src/main/java/seedu/address/model/person/InsurancePackage.java
@@ -0,0 +1,80 @@
+package seedu.address.model.person;
+
+import static seedu.address.commons.util.AppUtil.checkArgument;
+import static seedu.address.commons.util.CollectionUtil.requireAllNonNull;
+
+/**
+ * Represents a Person's insurance package in the address book.
+ * Guarantees: mutable; is valid as declared in {@link #isValidInsurancePackage(String)}
+ */
+public class InsurancePackage {
+
+ public static final String MESSAGE_CONSTRAINTS = "Insurance package should not be blank";
+
+ /*
+ * The first character of the insurance package must not be a whitespace,
+ * otherwise " " (a blank string) becomes a valid input.
+ */
+ public static final String VALIDATION_REGEX = "[^\\s].*";
+
+ private final String packageName;
+ private String packageDescription;
+
+ /**
+ * Constructs an {@code InsurancePackage}.
+ *
+ * @param packageName A name for the insurance package
+ */
+ public InsurancePackage(String packageName) {
+ this(packageName, "");
+ }
+
+ /**
+ * Constructs an {@code InsurancePackage} with a package description.
+ *
+ * @param packageName A name for the insurance package
+ * @param packageDescription The description of the insurance package
+ */
+ public InsurancePackage(String packageName, String packageDescription) {
+ requireAllNonNull(packageName);
+ checkArgument(isValidInsurancePackage(packageName), MESSAGE_CONSTRAINTS);
+ this.packageName = packageName;
+ this.packageDescription = packageDescription;
+ }
+
+ /**
+ * Returns true if a given string is a valid insurance package.
+ */
+ public static boolean isValidInsurancePackage(String test) {
+ return test.matches(VALIDATION_REGEX);
+ }
+
+ public String getPackageName() {
+ return packageName;
+ }
+
+ public String getPackageDescription() {
+ return packageDescription;
+ }
+
+ public void setPackageDescription(String s) {
+ packageDescription = s;
+ }
+
+ @Override
+ public String toString() {
+ return packageName;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof InsurancePackage // instanceof handles nulls
+ && packageName.equals(((InsurancePackage) other).packageName)); // state check
+ }
+
+ @Override
+ public int hashCode() {
+ return packageName.hashCode();
+ }
+}
diff --git a/src/main/java/seedu/address/model/person/Name.java b/src/main/java/seedu/address/model/person/Name.java
index 79244d71cf7..d8bc346d63f 100644
--- a/src/main/java/seedu/address/model/person/Name.java
+++ b/src/main/java/seedu/address/model/person/Name.java
@@ -31,6 +31,17 @@ public Name(String name) {
fullName = name;
}
+ /**
+ * Compare two names ignoring their casing.
+ * @param other Name to be compared to.
+ * @return True if both names are identical when casing is ignored, false otherwise.
+ */
+ public boolean equalsIgnoreCasing(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof Name // instanceof handles nulls
+ && fullName.equalsIgnoreCase(((Name) other).fullName)); // state check
+ }
+
/**
* Returns true if a given string is a valid name.
*/
diff --git a/src/main/java/seedu/address/model/person/Person.java b/src/main/java/seedu/address/model/person/Person.java
index 8ff1d83fe89..bfe771c6c2a 100644
--- a/src/main/java/seedu/address/model/person/Person.java
+++ b/src/main/java/seedu/address/model/person/Person.java
@@ -2,10 +2,10 @@
import static seedu.address.commons.util.CollectionUtil.requireAllNonNull;
-import java.util.Collections;
-import java.util.HashSet;
+import java.util.ArrayList;
+import java.util.List;
import java.util.Objects;
-import java.util.Set;
+import java.util.stream.Collectors;
import seedu.address.model.tag.Tag;
@@ -21,21 +21,38 @@ public class Person {
private final Email email;
// Data fields
+ private final InsurancePackage insurancePackage;
private final Address address;
- private final Set tags = new HashSet<>();
+ private ArrayList tags = new ArrayList<>();
/**
* Every field must be present and not null.
*/
- public Person(Name name, Phone phone, Email email, Address address, Set tags) {
+ public Person(Name name, Phone phone, Email email,
+ InsurancePackage insurancePackage, Address address, ArrayList tags) {
requireAllNonNull(name, phone, email, address, tags);
this.name = name;
this.phone = phone;
this.email = email;
+ this.insurancePackage = insurancePackage;
this.address = address;
this.tags.addAll(tags);
}
+ /**
+ * Copies a person object.
+ */
+ public static Person copyPerson(Person personToCopy) {
+ Name copiedName = personToCopy.getName();
+ Phone copiedPhone = personToCopy.getPhone();
+ Email copiedEmail = personToCopy.getEmail();
+ InsurancePackage copiedInsurancePackage = personToCopy.getInsurancePackage();
+ Address copiedAddress = personToCopy.getAddress();
+ ArrayList copiedTags = new ArrayList<>();
+ copiedTags.addAll(personToCopy.getTags());
+ return new Person(copiedName, copiedPhone, copiedEmail, copiedInsurancePackage, copiedAddress, copiedTags);
+ }
+
public Name getName() {
return name;
}
@@ -48,20 +65,35 @@ public Email getEmail() {
return email;
}
+ public InsurancePackage getInsurancePackage() {
+ return insurancePackage;
+ }
+
public Address getAddress() {
return address;
}
+ public void setTags(ArrayList tagList) {
+ assert tagList != null;
+ this.tags = tagList;
+ }
+
+ public ArrayList getTags() {
+ return new ArrayList<>(tags);
+ }
+
/**
- * Returns an immutable tag set, which throws {@code UnsupportedOperationException}
- * if modification is attempted.
+ * Returns a String of tagname separated by spaces after retrieving the tagname from the {@code Tag } object.
+ * @return String of tagnames separated by spaces
*/
- public Set getTags() {
- return Collections.unmodifiableSet(tags);
+ public String getTagsInString() {
+ ArrayList tags = getTags();
+ List tagsListInString = tags.stream().map(tag -> tag.tagName).collect(Collectors.toList());
+ return String.join(" ", tagsListInString);
}
/**
- * Returns true if both persons have the same name.
+ * Returns true if both persons have the same name, and same phone number.
* This defines a weaker notion of equality between two persons.
*/
public boolean isSamePerson(Person otherPerson) {
@@ -70,7 +102,8 @@ public boolean isSamePerson(Person otherPerson) {
}
return otherPerson != null
- && otherPerson.getName().equals(getName());
+ && otherPerson.getName().equals(getName())
+ && otherPerson.getPhone().equals(getPhone());
}
/**
@@ -91,6 +124,7 @@ public boolean equals(Object other) {
return otherPerson.getName().equals(getName())
&& otherPerson.getPhone().equals(getPhone())
&& otherPerson.getEmail().equals(getEmail())
+ && otherPerson.getInsurancePackage().equals(getInsurancePackage())
&& otherPerson.getAddress().equals(getAddress())
&& otherPerson.getTags().equals(getTags());
}
@@ -109,10 +143,12 @@ public String toString() {
.append(getPhone())
.append("; Email: ")
.append(getEmail())
+ .append("; Insurance Package: ")
+ .append(getInsurancePackage())
.append("; Address: ")
.append(getAddress());
- Set tags = getTags();
+ ArrayList tags = getTags();
if (!tags.isEmpty()) {
builder.append("; Tags: ");
tags.forEach(builder::append);
diff --git a/src/main/java/seedu/address/model/person/UniquePersonList.java b/src/main/java/seedu/address/model/person/UniquePersonList.java
index 0fee4fe57e6..08a6dad3d53 100644
--- a/src/main/java/seedu/address/model/person/UniquePersonList.java
+++ b/src/main/java/seedu/address/model/person/UniquePersonList.java
@@ -3,6 +3,7 @@
import static java.util.Objects.requireNonNull;
import static seedu.address.commons.util.CollectionUtil.requireAllNonNull;
+import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
@@ -97,6 +98,13 @@ public void setPersons(List persons) {
internalList.setAll(persons);
}
+ /**
+ * Sorts the contents of this list using {@code comp}.
+ */
+ public void sort(Comparator comp) {
+ FXCollections.sort(internalList, comp);
+ }
+
/**
* Returns the backing list as an unmodifiable {@code ObservableList}.
*/
diff --git a/src/main/java/seedu/address/model/person/comparators/TagPriorityComparator.java b/src/main/java/seedu/address/model/person/comparators/TagPriorityComparator.java
new file mode 100644
index 00000000000..b1d88eb5c22
--- /dev/null
+++ b/src/main/java/seedu/address/model/person/comparators/TagPriorityComparator.java
@@ -0,0 +1,75 @@
+package seedu.address.model.person.comparators;
+
+import java.util.ArrayList;
+import java.util.Comparator;
+
+import seedu.address.model.person.Person;
+import seedu.address.model.tag.Priority;
+import seedu.address.model.tag.Tag;
+
+/**
+ * Compares two {@code Person} based on the priority level of their tags.
+ */
+public class TagPriorityComparator implements Comparator {
+ @Override
+ public int compare(Person p1, Person p2) {
+ ArrayList tags1 = p1.getTags();
+ ArrayList tags2 = p2.getTags();
+
+ if (tags1.isEmpty() && tags2.isEmpty()) {
+ return 0;
+ } else if (tags1.isEmpty()) {
+ return 1;
+ } else if (tags2.isEmpty()) {
+ return -1;
+ }
+
+ //Treats the priority of a person's tags as the highest priority
+ //out of all their tags.
+ Priority maxPrio1 = Priority.PRIORITY_4;
+ boolean hasNonNull1 = false;
+ boolean hasNull1 = false;
+ Priority maxPrio2 = Priority.PRIORITY_4;
+ boolean hasNonNull2 = false;
+ boolean hasNull2 = false;
+
+ for (Tag currTag : tags1) {
+ Priority currPrio = currTag.getPriority();
+ if (currPrio == null) {
+ hasNull1 = true;
+ continue;
+ }
+
+ hasNonNull1 = true;
+ if (maxPrio1.compareTo(currPrio) > 0) {
+ maxPrio1 = currPrio;
+ }
+ }
+
+ for (Tag currTag : tags2) {
+ Priority currPrio = currTag.getPriority();
+ if (currPrio == null) {
+ hasNull2 = true;
+ continue;
+ }
+
+ hasNonNull2 = true;
+ if (maxPrio2.compareTo(currPrio) > 0) {
+ maxPrio2 = currPrio;
+ }
+ }
+
+ boolean isNull1 = hasNull1 && !hasNonNull1;
+ boolean isNull2 = hasNull2 && !hasNonNull2;
+
+ if (isNull1 && isNull2) {
+ return 0;
+ } else if (isNull1) {
+ return 1;
+ } else if (isNull2) {
+ return -1;
+ } else {
+ return maxPrio1.compareTo(maxPrio2);
+ }
+ }
+}
diff --git a/src/main/java/seedu/address/model/person/predicates/AddressContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/person/predicates/AddressContainsKeywordsPredicate.java
new file mode 100644
index 00000000000..dfbb18dee87
--- /dev/null
+++ b/src/main/java/seedu/address/model/person/predicates/AddressContainsKeywordsPredicate.java
@@ -0,0 +1,34 @@
+package seedu.address.model.person.predicates;
+
+import java.util.List;
+
+import seedu.address.commons.util.StringUtil;
+import seedu.address.model.person.Person;
+
+/**
+ * Tests that a {@code Person}'s {@code Address} matches any of the keywords given.
+ */
+public class AddressContainsKeywordsPredicate extends FieldContainsKeywordsPredicate {
+ private final List keywords;
+
+ public AddressContainsKeywordsPredicate(List keywords) {
+ this.keywords = keywords;
+ }
+
+ public List getKeywords() {
+ return keywords;
+ }
+
+ @Override
+ public boolean test(Person person) {
+ return 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/predicates/CombineContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/person/predicates/CombineContainsKeywordsPredicate.java
new file mode 100644
index 00000000000..8e5db0d5a8c
--- /dev/null
+++ b/src/main/java/seedu/address/model/person/predicates/CombineContainsKeywordsPredicate.java
@@ -0,0 +1,48 @@
+package seedu.address.model.person.predicates;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import seedu.address.model.person.Person;
+
+/**
+ * Tests all of a {@code Person}'s fields to match the respective keywords in {@code FieldContainsKeywordsPredicate}
+ * in the predicates list
+ */
+public class CombineContainsKeywordsPredicate extends FieldContainsKeywordsPredicate {
+ private final List predicates;
+
+ public CombineContainsKeywordsPredicate(List predicates) {
+ this.predicates = predicates;
+ }
+
+ @Override
+ public boolean test(Person person) {
+ List result = new ArrayList<>();
+ for (FieldContainsKeywordsPredicate predicate : predicates) {
+ Boolean predicateResult = predicate.test(person);
+ result.add(predicateResult);
+ }
+ return result.stream().allMatch(val -> val);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ // short circuit if same object
+ if (other == this) {
+ return true;
+ }
+ // instanceof handles nulls
+ if (!(other instanceof CombineContainsKeywordsPredicate)) {
+ return false;
+ }
+
+ CombineContainsKeywordsPredicate c = (CombineContainsKeywordsPredicate) other;
+ List result = new ArrayList<>();
+ for (int i = 0; i < c.predicates.size(); i++) {
+ Boolean predicateEqual = predicates.get(i).equals(c.predicates.get(i));
+ result.add(predicateEqual);
+ }
+ return result.stream().allMatch(val -> val);
+ }
+}
diff --git a/src/main/java/seedu/address/model/person/predicates/EmailContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/person/predicates/EmailContainsKeywordsPredicate.java
new file mode 100644
index 00000000000..34b7c8c58a2
--- /dev/null
+++ b/src/main/java/seedu/address/model/person/predicates/EmailContainsKeywordsPredicate.java
@@ -0,0 +1,34 @@
+package seedu.address.model.person.predicates;
+
+import java.util.List;
+
+import seedu.address.commons.util.StringUtil;
+import seedu.address.model.person.Person;
+
+/**
+ * Tests that a {@code Person}'s {@code Email} matches any of the keywords given.
+ */
+public class EmailContainsKeywordsPredicate extends FieldContainsKeywordsPredicate {
+ private final List keywords;
+
+ public EmailContainsKeywordsPredicate(List keywords) {
+ this.keywords = keywords;
+ }
+
+ public List getKeywords() {
+ return keywords;
+ }
+
+ @Override
+ public boolean test(Person person) {
+ return 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/predicates/FieldContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/person/predicates/FieldContainsKeywordsPredicate.java
new file mode 100644
index 00000000000..931b911a830
--- /dev/null
+++ b/src/main/java/seedu/address/model/person/predicates/FieldContainsKeywordsPredicate.java
@@ -0,0 +1,11 @@
+package seedu.address.model.person.predicates;
+
+import java.util.function.Predicate;
+
+import seedu.address.model.person.Person;
+
+/**
+ * Tests that a {@code Person}'s {@code Field} matches any of the keywords given.
+ */
+public abstract class FieldContainsKeywordsPredicate implements Predicate {
+}
diff --git a/src/main/java/seedu/address/model/person/predicates/InsurancePackageContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/person/predicates/InsurancePackageContainsKeywordsPredicate.java
new file mode 100644
index 00000000000..1abd1e5cf68
--- /dev/null
+++ b/src/main/java/seedu/address/model/person/predicates/InsurancePackageContainsKeywordsPredicate.java
@@ -0,0 +1,35 @@
+package seedu.address.model.person.predicates;
+
+import java.util.List;
+
+import seedu.address.commons.util.StringUtil;
+import seedu.address.model.person.Person;
+
+/**
+ * Tests that a {@code Person}'s {@code InsurancePackage} matches any of the keywords given.
+ */
+public class InsurancePackageContainsKeywordsPredicate extends FieldContainsKeywordsPredicate {
+ private final List keywords;
+
+ public InsurancePackageContainsKeywordsPredicate(List keywords) {
+ this.keywords = keywords;
+ }
+
+ public List getKeywords() {
+ return keywords;
+ }
+
+ @Override
+ public boolean test(Person person) {
+ return keywords.stream()
+ .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(person.getInsurancePackage().getPackageName(),
+ keyword));
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof InsurancePackageContainsKeywordsPredicate // instanceof handles nulls
+ && keywords.equals(((InsurancePackageContainsKeywordsPredicate) other).keywords)); // state check
+ }
+}
diff --git a/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/person/predicates/NameContainsKeywordsPredicate.java
similarity index 77%
rename from src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java
rename to src/main/java/seedu/address/model/person/predicates/NameContainsKeywordsPredicate.java
index c9b5868427c..3adbe703b9e 100644
--- a/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java
+++ b/src/main/java/seedu/address/model/person/predicates/NameContainsKeywordsPredicate.java
@@ -1,20 +1,24 @@
-package seedu.address.model.person;
+package seedu.address.model.person.predicates;
import java.util.List;
-import java.util.function.Predicate;
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 implements Predicate {
+public class NameContainsKeywordsPredicate extends FieldContainsKeywordsPredicate {
private final List keywords;
public NameContainsKeywordsPredicate(List keywords) {
this.keywords = keywords;
}
+ public List getKeywords() {
+ return keywords;
+ }
+
@Override
public boolean test(Person person) {
return keywords.stream()
@@ -27,5 +31,4 @@ public boolean equals(Object other) {
|| (other instanceof NameContainsKeywordsPredicate // instanceof handles nulls
&& keywords.equals(((NameContainsKeywordsPredicate) other).keywords)); // state check
}
-
}
diff --git a/src/main/java/seedu/address/model/person/predicates/NameExistsPredicate.java b/src/main/java/seedu/address/model/person/predicates/NameExistsPredicate.java
new file mode 100644
index 00000000000..0b6339f4c0c
--- /dev/null
+++ b/src/main/java/seedu/address/model/person/predicates/NameExistsPredicate.java
@@ -0,0 +1,29 @@
+package seedu.address.model.person.predicates;
+
+import java.util.function.Predicate;
+
+import seedu.address.model.person.Name;
+import seedu.address.model.person.Person;
+
+/**
+ * Tests that a {@code Person}'s {@code Name} matches exactly with the keyword given.
+ */
+public class NameExistsPredicate implements Predicate {
+ private final Name keyword;
+
+ public NameExistsPredicate(Name keyword) {
+ this.keyword = keyword;
+ }
+
+ @Override
+ public boolean test(Person person) {
+ return keyword.equalsIgnoreCasing(person.getName());
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof NameExistsPredicate // instanceof handles nulls
+ && keyword.equals(((NameExistsPredicate) other).keyword)); // state check
+ }
+}
diff --git a/src/main/java/seedu/address/model/person/predicates/PhoneContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/person/predicates/PhoneContainsKeywordsPredicate.java
new file mode 100644
index 00000000000..778f4d7d50a
--- /dev/null
+++ b/src/main/java/seedu/address/model/person/predicates/PhoneContainsKeywordsPredicate.java
@@ -0,0 +1,35 @@
+package seedu.address.model.person.predicates;
+
+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 FieldContainsKeywordsPredicate {
+ private final List keywords;
+
+ public PhoneContainsKeywordsPredicate(List keywords) {
+ this.keywords = keywords;
+ }
+
+ public List getKeywords() {
+ return keywords;
+ }
+
+ @Override
+ public boolean test(Person person) {
+ return 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/predicates/TagsContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/person/predicates/TagsContainsKeywordsPredicate.java
new file mode 100644
index 00000000000..e5cf14022e0
--- /dev/null
+++ b/src/main/java/seedu/address/model/person/predicates/TagsContainsKeywordsPredicate.java
@@ -0,0 +1,34 @@
+package seedu.address.model.person.predicates;
+
+import java.util.List;
+
+import seedu.address.commons.util.StringUtil;
+import seedu.address.model.person.Person;
+
+/**
+ * Tests that a {@code Person}'s {@code Tag} matches any of the keywords given.
+ */
+public class TagsContainsKeywordsPredicate extends FieldContainsKeywordsPredicate {
+ private final List keywords;
+
+ public TagsContainsKeywordsPredicate(List keywords) {
+ this.keywords = keywords;
+ }
+
+ public List getKeywords() {
+ return keywords;
+ }
+
+ @Override
+ public boolean test(Person person) {
+ return keywords.stream()
+ .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(person.getTagsInString(), keyword));
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof TagsContainsKeywordsPredicate // instanceof handles nulls
+ && keywords.equals(((TagsContainsKeywordsPredicate) other).keywords)); // state check
+ }
+}
diff --git a/src/main/java/seedu/address/model/tag/Priority.java b/src/main/java/seedu/address/model/tag/Priority.java
new file mode 100644
index 00000000000..6c02ec38530
--- /dev/null
+++ b/src/main/java/seedu/address/model/tag/Priority.java
@@ -0,0 +1,8 @@
+package seedu.address.model.tag;
+
+public enum Priority {
+ PRIORITY_1,
+ PRIORITY_2,
+ PRIORITY_3,
+ PRIORITY_4
+}
diff --git a/src/main/java/seedu/address/model/tag/Tag.java b/src/main/java/seedu/address/model/tag/Tag.java
index b0ea7e7dad7..40182666297 100644
--- a/src/main/java/seedu/address/model/tag/Tag.java
+++ b/src/main/java/seedu/address/model/tag/Tag.java
@@ -9,20 +9,23 @@
*/
public class Tag {
- public static final String MESSAGE_CONSTRAINTS = "Tags names should be alphanumeric";
- public static final String VALIDATION_REGEX = "\\p{Alnum}+";
+ public static final String MESSAGE_CONSTRAINTS = "Invalid tag name: tag names should only contain "
+ + "numbers, digits or spaces, and cannot be empty";
+ public static final String VALIDATION_REGEX = "[a-zA-Z0-9 ]+";
public final String tagName;
+ public final Priority tagPriority;
/**
* Constructs a {@code Tag}.
*
* @param tagName A valid tag name.
*/
- public Tag(String tagName) {
+ public Tag(String tagName, Priority tagPriority) {
requireNonNull(tagName);
checkArgument(isValidTagName(tagName), MESSAGE_CONSTRAINTS);
this.tagName = tagName;
+ this.tagPriority = tagPriority;
}
/**
@@ -32,11 +35,19 @@ public static boolean isValidTagName(String test) {
return test.matches(VALIDATION_REGEX);
}
+ /**
+ * Returns the priority of the tag.
+ */
+ public Priority getPriority() {
+ return tagPriority;
+ }
+
@Override
public boolean equals(Object other) {
return other == this // short circuit if same object
|| (other instanceof Tag // instanceof handles nulls
- && tagName.equals(((Tag) other).tagName)); // state check
+ && tagName.equals(((Tag) other).tagName))
+ && tagPriority == (((Tag) other).tagPriority); // state check
}
@Override
diff --git a/src/main/java/seedu/address/model/util/SampleDataUtil.java b/src/main/java/seedu/address/model/util/SampleDataUtil.java
index 1806da4facf..a3c4a63d14d 100644
--- a/src/main/java/seedu/address/model/util/SampleDataUtil.java
+++ b/src/main/java/seedu/address/model/util/SampleDataUtil.java
@@ -1,43 +1,84 @@
package seedu.address.model.util;
+import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Set;
+import java.util.List;
import java.util.stream.Collectors;
+import javafx.util.Pair;
import seedu.address.model.AddressBook;
+import seedu.address.model.InsurancePackagesSet;
import seedu.address.model.ReadOnlyAddressBook;
import seedu.address.model.person.Address;
import seedu.address.model.person.Email;
+import seedu.address.model.person.InsurancePackage;
import seedu.address.model.person.Name;
import seedu.address.model.person.Person;
import seedu.address.model.person.Phone;
+import seedu.address.model.tag.Priority;
import seedu.address.model.tag.Tag;
/**
* Contains utility methods for populating {@code AddressBook} with sample data.
*/
public class SampleDataUtil {
+
+ private static final int NUM_SAMPLES = 6;
+
+ private static final List sampleNames = List.of(
+ new Name("Alex Yeoh"), new Name("Bernice Yu"), new Name("Charlotte Oliveiro"),
+ new Name("David Li"), new Name("Irfan Ibrahim"), new Name("Roy Balakrishnan"));
+
+ private static final List samplePhones = List.of(
+ new Phone("87438807"), new Phone("99272758"), new Phone("93210283"),
+ new Phone("91031282"), new Phone("92492021"), new Phone("92624417"));
+
+ private static final List sampleEmails = List.of(
+ new Email("alexyeoh@example.com"), new Email("berniceyu@example.com"),
+ new Email("charlotte@example.com"), new Email("lidavid@example.com"),
+ new Email("irfan@example.com"), new Email("royb@example.com"));
+
+ private static final List samplePackages = List.of(
+ new InsurancePackage("Golden Package", "Luxurious coverage for many aspects"),
+ new InsurancePackage("Silver Package", "Plenty coverage for most aspects"),
+ new InsurancePackage("Bronze Package", "An affordable package to cover essential aspects"),
+ new InsurancePackage("Golden Plus Package", "A VIP Special for those already under the Golden Package"),
+ new InsurancePackage("Basic Package", "Basic insurance package to cover basic needs"),
+ new InsurancePackage("Theft Insurance Package", "For theft protection"));
+
+ private static final List sampleAddresses = List.of(
+ new Address("Blk 30 Geylang Street 29, #06-40"),
+ new Address("Blk 30 Lorong 3 Serangoon Gardens, #07-18"),
+ new Address("Blk 11 Ang Mo Kio Street 74, #11-04"),
+ new Address("Blk 436 Serangoon Gardens Street 26, #16-43"),
+ new Address("Blk 47 Tampines Street 20, #17-35"),
+ new Address("Blk 45 Aljunied Street 85, #11-31"));
+
+ private static final List> sampleTags = List.of(
+ getTagList(new Pair<>("introduce to friends", null)),
+ getTagList(new Pair<>("going to move abroad soon", Priority.PRIORITY_1)),
+ getTagList(new Pair<>("tell about Car insurance updates", Priority.PRIORITY_3)),
+ getTagList(new Pair<>("contact wife if not available", null)),
+ getTagList(),
+ getTagList(new Pair<>("update insurance package", Priority.PRIORITY_1)));
+
+ private static Person[] samplePersons = null;
+
public static Person[] getSamplePersons() {
- return new Person[] {
- new Person(new Name("Alex Yeoh"), new Phone("87438807"), new Email("alexyeoh@example.com"),
- new Address("Blk 30 Geylang Street 29, #06-40"),
- getTagSet("friends")),
- new Person(new Name("Bernice Yu"), new Phone("99272758"), new Email("berniceyu@example.com"),
- new Address("Blk 30 Lorong 3 Serangoon Gardens, #07-18"),
- getTagSet("colleagues", "friends")),
- new Person(new Name("Charlotte Oliveiro"), new Phone("93210283"), new Email("charlotte@example.com"),
- new Address("Blk 11 Ang Mo Kio Street 74, #11-04"),
- getTagSet("neighbours")),
- new Person(new Name("David Li"), new Phone("91031282"), new Email("lidavid@example.com"),
- new Address("Blk 436 Serangoon Gardens Street 26, #16-43"),
- getTagSet("family")),
- new Person(new Name("Irfan Ibrahim"), new Phone("92492021"), new Email("irfan@example.com"),
- new Address("Blk 47 Tampines Street 20, #17-35"),
- getTagSet("classmates")),
- new Person(new Name("Roy Balakrishnan"), new Phone("92624417"), new Email("royb@example.com"),
- new Address("Blk 45 Aljunied Street 85, #11-31"),
- getTagSet("colleagues"))
- };
+ if (samplePersons == null) {
+ samplePersons = new Person[NUM_SAMPLES];
+ for (int i = 0; i < NUM_SAMPLES; i++) {
+ samplePersons[i] = new Person(
+ sampleNames.get(i),
+ samplePhones.get(i),
+ sampleEmails.get(i),
+ samplePackages.get(i),
+ sampleAddresses.get(i),
+ (ArrayList) sampleTags.get(i)
+ );
+ }
+ }
+ return samplePersons;
}
public static ReadOnlyAddressBook getSampleAddressBook() {
@@ -48,13 +89,21 @@ public static ReadOnlyAddressBook getSampleAddressBook() {
return sampleAb;
}
+ public static InsurancePackagesSet getSampleInsurancePackages() {
+ InsurancePackagesSet sampleIp = new InsurancePackagesSet();
+ for (InsurancePackage samplePackage : samplePackages) {
+ sampleIp.addPackage(samplePackage);
+ }
+ return sampleIp;
+ }
+
/**
* 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());
+ public static ArrayList getTagList(Pair... tagPairs) {
+ return new ArrayList<>(Arrays.stream(tagPairs)
+ .map(x -> new Tag(x.getKey(), x.getValue()))
+ .collect(Collectors.toList()));
}
}
diff --git a/src/main/java/seedu/address/storage/CommandStorage.java b/src/main/java/seedu/address/storage/CommandStorage.java
new file mode 100644
index 00000000000..21c73442fcc
--- /dev/null
+++ b/src/main/java/seedu/address/storage/CommandStorage.java
@@ -0,0 +1,73 @@
+package seedu.address.storage;
+
+import java.util.ArrayList;
+
+import seedu.address.commons.exceptions.IllegalValueException;
+
+/**
+ * A class that stores the history of entered commands.
+ */
+public class CommandStorage {
+
+ private ArrayList history;
+ private int currentCommandNum = 0;
+
+ public CommandStorage() {
+ history = new ArrayList<>();
+ }
+
+ /**
+ * Adds an entered command into the saved history.
+ * @param command The command input entered by the user.
+ */
+ public void addCommand(String command) {
+ history.add(command);
+ currentCommandNum = history.size();
+ }
+
+ /**
+ * Gets the previous command, depending on the current state.
+ * @return The right command in history.
+ */
+ public String getPreviousCommand() {
+ if (currentCommandNum == 0) { // at the oldest String
+ return history.get(0);
+ }
+ currentCommandNum--;
+ return history.get(currentCommandNum);
+ }
+ /**
+ * Gets the next command, depending on the current state.
+ * @return The right command in history.
+ */
+ public String getNextCommand() {
+ if (currentCommandNum >= history.size() - 1) { // already at the newest String
+ return history.get(currentCommandNum);
+ }
+ currentCommandNum++;
+ return history.get(currentCommandNum);
+ }
+
+ /**
+ * Retrieves the right command given the command number in history.
+ *
+ * @param commandNum The position of the command to retrieve.
+ * @return The command at that position.
+ * @throws IllegalValueException If the command number is invalid.
+ */
+ public String getCommand(int commandNum) throws IllegalValueException {
+ if (commandNum < 0 || commandNum >= history.size()) {
+ throw new IllegalValueException("Invalid command number!");
+ }
+ return history.get(commandNum);
+ }
+
+ /**
+ * Retrieves the command at a particular point in history.
+ *
+ * @return The command at this point.
+ */
+ public String getCurrentCommand() {
+ return history.get(currentCommandNum);
+ }
+}
diff --git a/src/main/java/seedu/address/storage/CsvAdaptedInsurancePackage.java b/src/main/java/seedu/address/storage/CsvAdaptedInsurancePackage.java
new file mode 100644
index 00000000000..e69ed776455
--- /dev/null
+++ b/src/main/java/seedu/address/storage/CsvAdaptedInsurancePackage.java
@@ -0,0 +1,130 @@
+package seedu.address.storage;
+
+import java.util.Objects;
+
+import seedu.address.commons.exceptions.IllegalValueException;
+import seedu.address.model.person.InsurancePackage;
+
+
+/**
+ * CSV-friendly version of {@link InsurancePackage}.
+ */
+public class CsvAdaptedInsurancePackage {
+
+ public static final String INVALID_CSV_FORMAT_ERROR = "Invalid insurance package in CSV file!";
+ public static final String MISSING_NAME_MESSAGE_FORMAT = "Insurance package name is missing!";
+
+ private final String insurancePackageName;
+ private String insurancePackageDetails;
+
+ /**
+ * Constructs a {@code CsvAdaptedInsurancePackage} with the given package details.
+ */
+ public CsvAdaptedInsurancePackage(String insurancePackageName, String insurancePackageDetails) {
+ this.insurancePackageName = insurancePackageName;
+ this.insurancePackageDetails = insurancePackageDetails;
+ }
+
+ /**
+ * Converts a given {@code InsurancePackage} into this class for CSV use.
+ * @param source The InsurancePackage object to convert from.
+ */
+ public CsvAdaptedInsurancePackage(InsurancePackage source) {
+ insurancePackageName = source.getPackageName();
+ insurancePackageDetails = source.getPackageDescription();
+ }
+
+ /**
+ * A constructor that takes in a comma-separated string to a CsvAdaptedInsurancePackage.
+ * @param s The String representation of this package.
+ */
+ public CsvAdaptedInsurancePackage(String s) throws IllegalValueException {
+
+ String[] packageDetails = s.split(",(?=([^\"]*\"[^\"]*\")*[^\"]*$)");
+
+ if (packageDetails.length != 2) {
+ throw new IllegalValueException(INVALID_CSV_FORMAT_ERROR);
+ }
+ insurancePackageName = cleanup(packageDetails[0]);
+ insurancePackageDetails = cleanup(packageDetails[1]);
+ }
+
+ /**
+ * Converts this CSV-friendly adapted insurance package object into the model's {@code InsurancePackage} object.
+ *
+ * @throws IllegalValueException if there were any data constraints violated in the adapted package.
+ */
+ public InsurancePackage toModelType() throws IllegalValueException {
+
+ if (insurancePackageName == null || insurancePackageName.equals("")) {
+ throw new IllegalValueException(String.format(MISSING_NAME_MESSAGE_FORMAT,
+ InsurancePackage.class.getSimpleName()));
+ }
+
+ if (insurancePackageDetails == null) {
+ insurancePackageDetails = "";
+ }
+
+ return new InsurancePackage(insurancePackageName, insurancePackageDetails);
+ }
+
+
+ /**
+ * Converts this CsvAdaptedInsurancePackage to a comma-separated string.
+ * To be used for storing a CsvAdaptedInsurancePackage into a CSV file.
+ *
+ * @return The String representation of this InsurancePackage.
+ */
+ public String toCsvString() {
+ return addQuotes(insurancePackageName) + "," + addQuotes(insurancePackageDetails);
+ }
+
+ /**
+ * Adds quotes around a field, for the purposes of storing it into CSV.
+ * This is necessary in case the existing field value has commas inside.
+ *
+ * @param s the String to add double quotes around.
+ * @return the CSV-friendly version of the string.
+ */
+ public static String addQuotes(String s) {
+ return '"' + s + '"';
+ }
+
+ /**
+ * Removes quotes around the field read from the CSV file, if exists.
+ *
+ * @param s the raw String read from the CSV file.
+ * @return the actual value of the field.
+ */
+ public static String cleanup(String s) {
+ if (s.length() > 0 // if the string has characters
+ && s.charAt(0) == '"' // if the string starts with "
+ && s.charAt(s.length() - 1) == '"') { // if the string ends with "
+ return s.substring(1, s.length() - 1);
+ } else {
+ return s;
+ }
+ }
+
+ /**
+ * Returns true if both CsvAdaptedInsurancePackage have the same identity and data fields.
+ */
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ if (!(other instanceof CsvAdaptedInsurancePackage)) {
+ return false;
+ }
+
+ CsvAdaptedInsurancePackage otherPackage = (CsvAdaptedInsurancePackage) other;
+ return otherPackage.insurancePackageName.equals(insurancePackageName);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(insurancePackageName);
+ }
+}
diff --git a/src/main/java/seedu/address/storage/CsvAdaptedPerson.java b/src/main/java/seedu/address/storage/CsvAdaptedPerson.java
new file mode 100644
index 00000000000..4838f5f0931
--- /dev/null
+++ b/src/main/java/seedu/address/storage/CsvAdaptedPerson.java
@@ -0,0 +1,238 @@
+package seedu.address.storage;
+
+import static seedu.address.storage.CsvAdaptedTag.STRING_PRIORITY_MAP;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import seedu.address.commons.exceptions.IllegalValueException;
+import seedu.address.model.person.Address;
+import seedu.address.model.person.Email;
+import seedu.address.model.person.InsurancePackage;
+import seedu.address.model.person.Name;
+import seedu.address.model.person.Person;
+import seedu.address.model.person.Phone;
+import seedu.address.model.tag.Priority;
+import seedu.address.model.tag.Tag;
+
+/**
+ * CSV-friendly version of {@link Person}.
+ */
+public class CsvAdaptedPerson {
+
+ public static final String MISSING_FIELD_MESSAGE_FORMAT = "Person's %s field is missing!";
+
+ private final String name;
+ private final String phone;
+ private final String email;
+ private final String insurancePackage;
+ private final String address;
+ private final List tagged = new ArrayList<>();
+
+ /**
+ * Constructs a {@code CsvAdaptedPerson} with the given person details.
+ */
+ public CsvAdaptedPerson(String name, String phone, String email, String insurancePackage, String address,
+ List tagged) {
+ this.name = name;
+ this.phone = phone;
+ this.email = email;
+ this.insurancePackage = insurancePackage;
+ this.address = address;
+ if (tagged != null) {
+ this.tagged.addAll(tagged);
+ }
+ }
+
+ /**
+ * Converts a given {@code Person} into this class for CSV use.
+ * @param source The Person object to convert from.
+ */
+ public CsvAdaptedPerson(Person source) {
+ name = source.getName().fullName;
+ phone = source.getPhone().value;
+ email = source.getEmail().value;
+ insurancePackage = source.getInsurancePackage().getPackageName();
+ address = source.getAddress().value;
+ tagged.addAll(source.getTags().stream()
+ .map(CsvAdaptedTag::new)
+ .collect(Collectors.toList()));
+ }
+
+ /**
+ * A constructor that takes in a comma-separated string to a CsvAdaptedPerson.
+ * @param s The String representation of this person.
+ */
+ public CsvAdaptedPerson(String s) {
+
+ String[] personDetails = s.split(",(?=([^\"]*\"[^\"]*\")*[^\"]*$)");
+
+ name = cleanup(personDetails[0]);
+ phone = cleanup(personDetails[1]);
+ email = cleanup(personDetails[2]);
+ insurancePackage = cleanup(personDetails[3]);
+ address = cleanup(personDetails[4]);
+
+ // if the tags column in the CSV is empty, personDetails.length == 5
+ // checking is needed to ensure that there are tags in the CSV for this person
+ if (personDetails.length > 5 && personDetails[5].length() > 0) {
+ String allTagsString = cleanup(personDetails[5]);
+
+ // tags are internally separated by |
+ String[] tags = allTagsString.split("\\|");
+ for (String tagString : tags) {
+ String possiblePriority = tagString.length() > 4
+ ? tagString.substring(tagString.length() - 4) : "";
+ Priority p = STRING_PRIORITY_MAP.get(possiblePriority);
+ if (p != null) {
+ tagString = tagString.substring(0, tagString.length() - 4);
+ }
+
+ if (tagString.length() > 0) {
+ tagged.add(new CsvAdaptedTag(tagString, p));
+ }
+ }
+ }
+
+ }
+
+ /**
+ * Converts this CSV-friendly adapted person object into the model's {@code Person} object.
+ *
+ * @throws IllegalValueException if there were any data constraints violated in the adapted person.
+ */
+ public Person toModelType() throws IllegalValueException {
+ final List personTags = new ArrayList<>();
+ for (CsvAdaptedTag tag : tagged) { //change here
+ personTags.add(tag.toModelType());
+ }
+
+ if (name == null) {
+ throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Name.class.getSimpleName()));
+ }
+ if (!Name.isValidName(name)) {
+ throw new IllegalValueException(Name.MESSAGE_CONSTRAINTS);
+ }
+ final Name modelName = new Name(name);
+
+ if (phone == null) {
+ throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Phone.class.getSimpleName()));
+ }
+ if (!Phone.isValidPhone(phone)) {
+ throw new IllegalValueException(Phone.MESSAGE_CONSTRAINTS);
+ }
+ final Phone modelPhone = new Phone(phone);
+
+ if (email == null) {
+ throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Email.class.getSimpleName()));
+ }
+ if (!Email.isValidEmail(email)) {
+ throw new IllegalValueException(Email.MESSAGE_CONSTRAINTS);
+ }
+ final Email modelEmail = new Email(email);
+
+ if (insurancePackage == null) {
+ throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT,
+ InsurancePackage.class.getSimpleName()));
+ }
+ if (!InsurancePackage.isValidInsurancePackage(insurancePackage)) {
+ throw new IllegalValueException(InsurancePackage.MESSAGE_CONSTRAINTS);
+ }
+ final InsurancePackage modelInsurancePackage = new InsurancePackage(insurancePackage);
+
+ if (address == null) {
+ throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Address.class.getSimpleName()));
+ }
+ if (!Address.isValidAddress(address)) {
+ throw new IllegalValueException(Address.MESSAGE_CONSTRAINTS);
+ }
+ final Address modelAddress = new Address(address);
+
+ final ArrayList modelTags = new ArrayList<>(personTags);
+ return new Person(modelName, modelPhone, modelEmail, modelInsurancePackage, modelAddress, modelTags);
+ }
+
+
+ /**
+ * Converts this CsvAdaptedPerson to a comma-separated string.
+ * To be used for storing a CsvAdaptedPerson into a CSV file.
+ *
+ * @return The String representation of this person.
+ */
+ public String toCsvString() {
+ String tags = getTagsAsString(tagged);
+ return Stream.of(name, phone, email, insurancePackage, address, tags)
+ .map(CsvAdaptedPerson::addQuotes)
+ .collect(Collectors.joining(","));
+ }
+
+ /**
+ * Converts the List of Tags into a single string, to be stored in the CSV file/
+ *
+ * @param tags the tags to convert to String.
+ * @return a single String representing the tags associated with this CsvAdaptedPerson.
+ */
+ public static String getTagsAsString(List tags) {
+ return tags.stream()
+ .map(CsvAdaptedTag::getTagNameString)
+ .collect(Collectors.joining("|"));
+ }
+
+ /**
+ * Adds quotes around a CsvAdaptedPerson's field, for the purposes of storing it into CSV.
+ * This is necessary in case the existing field value has commas inside.
+ *
+ * @param s the String to add double quotes around.
+ * @return the CSV-friendly version of the string.
+ */
+ public static String addQuotes(String s) {
+ return '"' + s + '"';
+ }
+
+ /**
+ * Removes quotes around the field read from the CSV file, if exists.
+ *
+ * @param s the raw String read from the CSV file.
+ * @return the actual value of the field.
+ */
+ public static String cleanup(String s) {
+ if (s.length() > 0 // if the string has characters
+ && s.charAt(0) == '"' // if the string starts with "
+ && s.charAt(s.length() - 1) == '"') { // if the string ends with "
+ return s.substring(1, s.length() - 1);
+ } else {
+ return s;
+ }
+ }
+
+ /**
+ * Returns true if both CsvAdaptedPersons have the same identity and data fields.
+ */
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ if (!(other instanceof CsvAdaptedPerson)) {
+ return false;
+ }
+
+ CsvAdaptedPerson otherPerson = (CsvAdaptedPerson) other;
+ return otherPerson.name.equals(name)
+ && otherPerson.phone.equals(phone)
+ && otherPerson.email.equals(email)
+ && otherPerson.insurancePackage.equals(insurancePackage)
+ && otherPerson.address.equals(address)
+ && new HashSet<>(otherPerson.tagged).equals(new HashSet<>(tagged));
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(name, phone, email, address, tagged);
+ }
+}
diff --git a/src/main/java/seedu/address/storage/CsvAdaptedTag.java b/src/main/java/seedu/address/storage/CsvAdaptedTag.java
new file mode 100644
index 00000000000..e2ad85e0355
--- /dev/null
+++ b/src/main/java/seedu/address/storage/CsvAdaptedTag.java
@@ -0,0 +1,79 @@
+package seedu.address.storage;
+
+import java.util.Map;
+
+import seedu.address.commons.exceptions.IllegalValueException;
+import seedu.address.model.tag.Priority;
+import seedu.address.model.tag.Tag;
+
+/**
+ * CSV-friendly version of {@link Tag}.
+ */
+class CsvAdaptedTag {
+
+ public static final Map STRING_PRIORITY_MAP = Map.of("[P1]", Priority.PRIORITY_1,
+ "[P2]", Priority.PRIORITY_2, "[P3]", Priority.PRIORITY_3, "[P4]", Priority.PRIORITY_4);
+ private final String tagName;
+ private final Priority tagPriority;
+
+
+ /**
+ * Constructs a {@code CsvAdaptedTag} with the given {@code tagName}.
+ */
+ public CsvAdaptedTag(String tagName, Priority priority) {
+ this.tagName = tagName;
+ this.tagPriority = priority;
+ }
+
+ /**
+ * Converts a given {@code Tag} into this class for CSV use.
+ */
+ public CsvAdaptedTag(Tag source) {
+ tagName = source.tagName;
+ tagPriority = source.tagPriority;
+ }
+
+ public String getTagNameString() {
+ String priorityString = "";
+ if (tagPriority != null) {
+ for (Map.Entry e: STRING_PRIORITY_MAP.entrySet()) {
+ if (e.getValue() == tagPriority) {
+ priorityString = e.getKey();
+ break;
+ }
+ }
+ }
+
+ return tagName + priorityString;
+ }
+
+ /**
+ * Converts this CSV-friendly adapted tag object into the model's {@code Tag} object.
+ *
+ * @throws IllegalValueException if there were any data constraints violated in the adapted tag.
+ */
+ public Tag toModelType() throws IllegalValueException {
+ if (!Tag.isValidTagName(tagName)) {
+ throw new IllegalValueException(Tag.MESSAGE_CONSTRAINTS);
+ }
+ return new Tag(tagName, tagPriority);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof CsvAdaptedTag // instanceof handles nulls
+ && tagName.equals(((CsvAdaptedTag) other).tagName)
+ && tagPriority == ((CsvAdaptedTag) other).tagPriority);
+ }
+
+ @Override
+ public int hashCode() {
+ return tagName.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return tagName + tagPriority;
+ }
+}
diff --git a/src/main/java/seedu/address/storage/CsvAddressBookStorage.java b/src/main/java/seedu/address/storage/CsvAddressBookStorage.java
new file mode 100644
index 00000000000..98003f9ec05
--- /dev/null
+++ b/src/main/java/seedu/address/storage/CsvAddressBookStorage.java
@@ -0,0 +1,113 @@
+package seedu.address.storage;
+
+import static java.util.Objects.requireNonNull;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+import java.util.logging.Logger;
+import java.util.stream.Collectors;
+
+import seedu.address.commons.core.LogsCenter;
+import seedu.address.commons.exceptions.DataConversionException;
+import seedu.address.commons.exceptions.IllegalValueException;
+import seedu.address.commons.util.CsvUtil;
+import seedu.address.commons.util.FileUtil;
+import seedu.address.model.AddressBook;
+import seedu.address.model.ReadOnlyAddressBook;
+import seedu.address.model.person.Person;
+
+/**
+ * A class to access AddressBook data stored as a CSV file on the hard disk.
+ */
+public class CsvAddressBookStorage implements AddressBookStorage {
+
+ public static final String MESSAGE_DUPLICATE_PERSON = "Persons list contains duplicate person(s).";
+
+ private static final Logger logger = LogsCenter.getLogger(CsvAddressBookStorage.class);
+
+ private Path filePath;
+
+ public CsvAddressBookStorage() {
+ this.filePath = null;
+ }
+
+ public CsvAddressBookStorage(Path filePath) {
+ this.filePath = filePath;
+ }
+
+ public void setFilePath(Path p) {
+ this.filePath = p;
+ }
+
+ public Path getAddressBookFilePath() {
+ return filePath;
+ }
+
+ @Override
+ public Optional readAddressBook() throws DataConversionException {
+ return readAddressBook(filePath);
+ }
+
+ /**
+ * Similar to {@link #readAddressBook()}.
+ *
+ * @param filePath location of the data. Cannot be null.
+ * @return Either a ReadOnlyAddressBook, or nothing.
+ * @throws DataConversionException if the file is not in the correct format.
+ */
+ public Optional readAddressBook(Path filePath) throws DataConversionException {
+ requireNonNull(filePath);
+
+ if (!Files.exists(filePath)) {
+ return Optional.empty();
+ }
+
+ // get the people in the addressbook
+ List persons = CsvUtil.loadAbCsvFile(filePath);
+ Person convertedPerson;
+
+ AddressBook addressBook = new AddressBook();
+ for (CsvAdaptedPerson csvPerson : persons) {
+ try {
+ convertedPerson = csvPerson.toModelType();
+ if (addressBook.hasPerson(convertedPerson)) {
+ logger.info(MESSAGE_DUPLICATE_PERSON);
+ throw new IllegalValueException(MESSAGE_DUPLICATE_PERSON);
+ }
+ addressBook.addPerson(convertedPerson);
+ } catch (IllegalValueException ive) {
+ logger.info("Illegal values found in " + filePath + ": " + ive.getMessage());
+ throw new DataConversionException(ive);
+ }
+ }
+ return Optional.of(addressBook);
+ }
+
+ @Override
+ public void saveAddressBook(ReadOnlyAddressBook addressBook) throws IOException {
+ saveAddressBook(addressBook, filePath);
+ }
+
+ /**
+ * Similar to {@link #saveAddressBook(ReadOnlyAddressBook)}, but for CSV.
+ *
+ * @param filePath location of the data. Cannot be null.
+ */
+ public void saveAddressBook(ReadOnlyAddressBook addressBook, Path filePath) throws IOException {
+ requireNonNull(addressBook);
+ requireNonNull(filePath);
+
+ FileUtil.createIfMissing(filePath);
+
+ // get the people in the addressbook
+ List persons = new ArrayList<>();
+ persons.addAll(addressBook.getPersonList().stream().map(CsvAdaptedPerson::new).collect(Collectors.toList()));
+
+ CsvUtil.saveAbCsvFile(persons, filePath);
+ }
+
+}
diff --git a/src/main/java/seedu/address/storage/CsvInsurancePackagesStorage.java b/src/main/java/seedu/address/storage/CsvInsurancePackagesStorage.java
new file mode 100644
index 00000000000..dc3665124bd
--- /dev/null
+++ b/src/main/java/seedu/address/storage/CsvInsurancePackagesStorage.java
@@ -0,0 +1,120 @@
+package seedu.address.storage;
+
+import static java.util.Objects.requireNonNull;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+import java.util.logging.Logger;
+import java.util.stream.Collectors;
+
+import seedu.address.commons.core.LogsCenter;
+import seedu.address.commons.exceptions.DataConversionException;
+import seedu.address.commons.exceptions.IllegalValueException;
+import seedu.address.commons.util.CsvUtil;
+import seedu.address.commons.util.FileUtil;
+import seedu.address.model.InsurancePackagesSet;
+import seedu.address.model.person.InsurancePackage;
+
+/**
+ * A class to access Insurance Packages data stored as a CSV file on the hard disk.
+ */
+public class CsvInsurancePackagesStorage implements InsurancePackagesStorage {
+
+ public static final String MESSAGE_DUPLICATE_PACKAGE = "Package list contains duplicate package(s).";
+
+ private static final Logger logger = LogsCenter.getLogger(CsvInsurancePackagesStorage.class);
+
+ private Path filePath;
+
+ public CsvInsurancePackagesStorage() {
+ this.filePath = null;
+ }
+
+ public CsvInsurancePackagesStorage(Path filePath) {
+ this.filePath = filePath;
+ }
+
+ public void setFilePath(Path p) {
+ this.filePath = p;
+ }
+
+ public Path getInsurancePackagesFilePath() {
+ return filePath;
+ }
+
+ /**
+ * Returns InsurancePackagesStorage data.
+ * Returns {@code Optional.empty()} if storage file is not found.
+ * @throws DataConversionException if the data in storage is not in the expected format.
+ * @throws IOException if there was any problem when reading from the storage.
+ */
+ @Override
+ public Optional readInsurancePackages() throws DataConversionException, IOException {
+ return readInsurancePackages(filePath);
+ }
+
+ /**
+ * @see #getInsurancePackagesFilePath()
+ */
+ @Override
+ public Optional readInsurancePackages(Path filePath)
+ throws DataConversionException, IOException {
+ requireNonNull(filePath);
+
+ if (!Files.exists(filePath)) {
+ return Optional.empty();
+ }
+
+ // get the people in the addressbook
+ List packages = CsvUtil.loadIpCsvFile(filePath);
+ InsurancePackage convertedPackage;
+
+ InsurancePackagesSet packagesSet = new InsurancePackagesSet();
+ for (CsvAdaptedInsurancePackage csvPackage : packages) {
+ try {
+ convertedPackage = csvPackage.toModelType();
+ if (packagesSet.hasPackage(convertedPackage)) {
+ logger.info(MESSAGE_DUPLICATE_PACKAGE);
+ throw new IllegalValueException(MESSAGE_DUPLICATE_PACKAGE);
+ }
+ packagesSet.addPackage(convertedPackage);
+ } catch (IllegalValueException ive) {
+ logger.info("Illegal values found in " + filePath + ": " + ive.getMessage());
+ throw new DataConversionException(ive);
+ }
+ }
+ return Optional.of(packagesSet);
+ }
+
+ /**
+ * Saves the given insurance packages to the storage.
+ * @param packagesSet cannot be null.
+ * @throws IOException if there was any problem writing to the file.
+ */
+ @Override
+ public void saveInsurancePackages(InsurancePackagesSet packagesSet) throws IOException {
+ saveInsurancePackages(packagesSet, filePath);
+ }
+
+ /**
+ * @see #saveInsurancePackages(InsurancePackagesSet)
+ */
+ @Override
+ public void saveInsurancePackages(InsurancePackagesSet packagesSet, Path filePath) throws IOException {
+ requireNonNull(packagesSet);
+ requireNonNull(filePath);
+
+ FileUtil.createIfMissing(filePath);
+
+ // get the packages in the packagesSet
+ List packages = new ArrayList<>();
+ packages.addAll(packagesSet.getPackagesList().stream().map(CsvAdaptedInsurancePackage::new)
+ .collect(Collectors.toList()));
+
+ CsvUtil.saveIpCsvFile(packages, filePath);
+ }
+}
diff --git a/src/main/java/seedu/address/storage/InsurancePackagesStorage.java b/src/main/java/seedu/address/storage/InsurancePackagesStorage.java
new file mode 100644
index 00000000000..364240e3e49
--- /dev/null
+++ b/src/main/java/seedu/address/storage/InsurancePackagesStorage.java
@@ -0,0 +1,44 @@
+package seedu.address.storage;
+
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.Optional;
+
+import seedu.address.commons.exceptions.DataConversionException;
+import seedu.address.model.InsurancePackagesSet;
+
+/**
+ * Represents a storage for {@link seedu.address.model.InsurancePackagesSet}.
+ */
+public interface InsurancePackagesStorage {
+
+ /**
+ * Returns the file path of the data file.
+ */
+ Path getInsurancePackagesFilePath();
+
+ /**
+ * Returns InsurancePackagesStorage data.
+ * Returns {@code Optional.empty()} if storage file is not found.
+ * @throws DataConversionException if the data in storage is not in the expected format.
+ * @throws IOException if there was any problem when reading from the storage.
+ */
+ Optional readInsurancePackages() throws DataConversionException, IOException;
+
+ /**
+ * @see #getInsurancePackagesFilePath()
+ */
+ Optional readInsurancePackages(Path filePath) throws DataConversionException, IOException;
+
+ /**
+ * Saves the given insurance packages to the storage.
+ * @param packagesSet cannot be null.
+ * @throws IOException if there was any problem writing to the file.
+ */
+ void saveInsurancePackages(InsurancePackagesSet packagesSet) throws IOException;
+
+ /**
+ * @see #saveInsurancePackages(InsurancePackagesSet)
+ */
+ void saveInsurancePackages(InsurancePackagesSet packagesSet, Path filePath) throws IOException;
+}
diff --git a/src/main/java/seedu/address/storage/JsonAdaptedPerson.java b/src/main/java/seedu/address/storage/JsonAdaptedPerson.java
index a6321cec2ea..929848caf6f 100644
--- a/src/main/java/seedu/address/storage/JsonAdaptedPerson.java
+++ b/src/main/java/seedu/address/storage/JsonAdaptedPerson.java
@@ -1,9 +1,7 @@
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;
@@ -12,6 +10,7 @@
import seedu.address.commons.exceptions.IllegalValueException;
import seedu.address.model.person.Address;
import seedu.address.model.person.Email;
+import seedu.address.model.person.InsurancePackage;
import seedu.address.model.person.Name;
import seedu.address.model.person.Person;
import seedu.address.model.person.Phone;
@@ -27,6 +26,7 @@ class JsonAdaptedPerson {
private final String name;
private final String phone;
private final String email;
+ private final String insurancePackage;
private final String address;
private final List tagged = new ArrayList<>();
@@ -35,11 +35,12 @@ 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("insurance package") String insurancePackage,
+ @JsonProperty("address") String address, @JsonProperty("tagged") List tagged) {
this.name = name;
this.phone = phone;
this.email = email;
+ this.insurancePackage = insurancePackage;
this.address = address;
if (tagged != null) {
this.tagged.addAll(tagged);
@@ -53,6 +54,7 @@ public JsonAdaptedPerson(Person source) {
name = source.getName().fullName;
phone = source.getPhone().value;
email = source.getEmail().value;
+ insurancePackage = source.getInsurancePackage().getPackageName();
address = source.getAddress().value;
tagged.addAll(source.getTags().stream()
.map(JsonAdaptedTag::new)
@@ -94,6 +96,15 @@ public Person toModelType() throws IllegalValueException {
}
final Email modelEmail = new Email(email);
+ if (insurancePackage == null) {
+ throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT,
+ InsurancePackage.class.getSimpleName()));
+ }
+ if (!InsurancePackage.isValidInsurancePackage(insurancePackage)) {
+ throw new IllegalValueException(InsurancePackage.MESSAGE_CONSTRAINTS);
+ }
+ final InsurancePackage modelInsurancePackage = new InsurancePackage(insurancePackage);
+
if (address == null) {
throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Address.class.getSimpleName()));
}
@@ -102,8 +113,8 @@ public Person toModelType() throws IllegalValueException {
}
final Address modelAddress = new Address(address);
- final Set modelTags = new HashSet<>(personTags);
- return new Person(modelName, modelPhone, modelEmail, modelAddress, modelTags);
+ final ArrayList modelTags = new ArrayList<>(personTags);
+ return new Person(modelName, modelPhone, modelEmail, modelInsurancePackage, modelAddress, modelTags);
}
}
diff --git a/src/main/java/seedu/address/storage/JsonAdaptedTag.java b/src/main/java/seedu/address/storage/JsonAdaptedTag.java
index 0df22bdb754..04c260841ec 100644
--- a/src/main/java/seedu/address/storage/JsonAdaptedTag.java
+++ b/src/main/java/seedu/address/storage/JsonAdaptedTag.java
@@ -1,24 +1,26 @@
package seedu.address.storage;
import com.fasterxml.jackson.annotation.JsonCreator;
-import com.fasterxml.jackson.annotation.JsonValue;
+import com.fasterxml.jackson.annotation.JsonProperty;
import seedu.address.commons.exceptions.IllegalValueException;
+import seedu.address.model.tag.Priority;
import seedu.address.model.tag.Tag;
/**
* Jackson-friendly version of {@link Tag}.
*/
class JsonAdaptedTag {
-
private final String tagName;
+ private final Priority tagPriority;
/**
* Constructs a {@code JsonAdaptedTag} with the given {@code tagName}.
*/
@JsonCreator
- public JsonAdaptedTag(String tagName) {
+ public JsonAdaptedTag(@JsonProperty("tagName") String tagName, @JsonProperty("priority") Priority priority) {
this.tagName = tagName;
+ this.tagPriority = priority;
}
/**
@@ -26,11 +28,7 @@ public JsonAdaptedTag(String tagName) {
*/
public JsonAdaptedTag(Tag source) {
tagName = source.tagName;
- }
-
- @JsonValue
- public String getTagName() {
- return tagName;
+ tagPriority = source.tagPriority;
}
/**
@@ -42,7 +40,25 @@ public Tag toModelType() throws IllegalValueException {
if (!Tag.isValidTagName(tagName)) {
throw new IllegalValueException(Tag.MESSAGE_CONSTRAINTS);
}
- return new Tag(tagName);
+ return new Tag(tagName, tagPriority);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ if (!(other instanceof JsonAdaptedTag)) {
+ return false;
+ }
+
+ JsonAdaptedTag otherTag = (JsonAdaptedTag) other;
+ return tagName.equals(otherTag.tagName) && tagPriority.equals(otherTag.tagPriority);
}
+ @Override
+ public int hashCode() {
+ return tagName.hashCode();
+ }
}
diff --git a/src/main/java/seedu/address/storage/JsonAddressBookStorage.java b/src/main/java/seedu/address/storage/JsonAddressBookStorage.java
index dfab9daaa0d..84101355ff4 100644
--- a/src/main/java/seedu/address/storage/JsonAddressBookStorage.java
+++ b/src/main/java/seedu/address/storage/JsonAddressBookStorage.java
@@ -76,5 +76,4 @@ public void saveAddressBook(ReadOnlyAddressBook addressBook, Path filePath) thro
FileUtil.createIfMissing(filePath);
JsonUtil.saveJsonFile(new JsonSerializableAddressBook(addressBook), filePath);
}
-
}
diff --git a/src/main/java/seedu/address/storage/Storage.java b/src/main/java/seedu/address/storage/Storage.java
index beda8bd9f11..50906238aba 100644
--- a/src/main/java/seedu/address/storage/Storage.java
+++ b/src/main/java/seedu/address/storage/Storage.java
@@ -5,6 +5,7 @@
import java.util.Optional;
import seedu.address.commons.exceptions.DataConversionException;
+import seedu.address.model.InsurancePackagesSet;
import seedu.address.model.ReadOnlyAddressBook;
import seedu.address.model.ReadOnlyUserPrefs;
import seedu.address.model.UserPrefs;
@@ -12,7 +13,7 @@
/**
* API of the Storage component
*/
-public interface Storage extends AddressBookStorage, UserPrefsStorage {
+public interface Storage extends AddressBookStorage, UserPrefsStorage, InsurancePackagesStorage {
@Override
Optional readUserPrefs() throws DataConversionException, IOException;
@@ -26,7 +27,16 @@ public interface Storage extends AddressBookStorage, UserPrefsStorage {
@Override
Optional readAddressBook() throws DataConversionException, IOException;
+ @Override
+ Optional readInsurancePackages() throws DataConversionException, IOException;
+
@Override
void saveAddressBook(ReadOnlyAddressBook addressBook) throws IOException;
+ @Override
+ void saveInsurancePackages(InsurancePackagesSet packages) throws IOException;
+
+ void saveAddressBookToCsv(ReadOnlyAddressBook addressBook, Path filePath) throws IOException;
+
+ Optional readAddressBookFromCsv(Path filePath) throws DataConversionException, IOException;
}
diff --git a/src/main/java/seedu/address/storage/StorageManager.java b/src/main/java/seedu/address/storage/StorageManager.java
index 6cfa0162164..c5118d923dd 100644
--- a/src/main/java/seedu/address/storage/StorageManager.java
+++ b/src/main/java/seedu/address/storage/StorageManager.java
@@ -7,6 +7,7 @@
import seedu.address.commons.core.LogsCenter;
import seedu.address.commons.exceptions.DataConversionException;
+import seedu.address.model.InsurancePackagesSet;
import seedu.address.model.ReadOnlyAddressBook;
import seedu.address.model.ReadOnlyUserPrefs;
import seedu.address.model.UserPrefs;
@@ -19,13 +20,18 @@ public class StorageManager implements Storage {
private static final Logger logger = LogsCenter.getLogger(StorageManager.class);
private AddressBookStorage addressBookStorage;
private UserPrefsStorage userPrefsStorage;
+ private AddressBookStorage csvAddressBookStorage;
+ private InsurancePackagesStorage insurancePackagesStorage;
/**
* Creates a {@code StorageManager} with the given {@code AddressBookStorage} and {@code UserPrefStorage}.
*/
- public StorageManager(AddressBookStorage addressBookStorage, UserPrefsStorage userPrefsStorage) {
+ public StorageManager(AddressBookStorage addressBookStorage, UserPrefsStorage userPrefsStorage,
+ InsurancePackagesStorage insurancePackagesStorage) {
this.addressBookStorage = addressBookStorage;
this.userPrefsStorage = userPrefsStorage;
+ this.csvAddressBookStorage = new CsvAddressBookStorage();
+ this.insurancePackagesStorage = insurancePackagesStorage;
}
// ================ UserPrefs methods ==============================
@@ -60,7 +66,7 @@ public Optional readAddressBook() throws DataConversionExce
@Override
public Optional readAddressBook(Path filePath) throws DataConversionException, IOException {
- logger.fine("Attempting to read data from file: " + filePath);
+ logger.fine("Attempting to read address book data from file: " + filePath);
return addressBookStorage.readAddressBook(filePath);
}
@@ -71,8 +77,51 @@ public void saveAddressBook(ReadOnlyAddressBook addressBook) throws IOException
@Override
public void saveAddressBook(ReadOnlyAddressBook addressBook, Path filePath) throws IOException {
- logger.fine("Attempting to write to data file: " + filePath);
+ logger.fine("Attempting to write address book to data file: " + filePath);
addressBookStorage.saveAddressBook(addressBook, filePath);
}
+ @Override
+ public void saveAddressBookToCsv(ReadOnlyAddressBook addressBook, Path filePath) throws IOException {
+ logger.fine("Attempting to write to CSV file: " + filePath);
+ csvAddressBookStorage.saveAddressBook(addressBook, filePath);
+ }
+
+ @Override
+ public Optional readAddressBookFromCsv(Path filePath)
+ throws DataConversionException, IOException {
+ logger.fine("Attempting to load from CSV file: " + filePath);
+ return csvAddressBookStorage.readAddressBook(filePath);
+ }
+
+ // ================ InsurancePackagesSet methods ==============================
+
+ @Override
+ public Path getInsurancePackagesFilePath() {
+ return insurancePackagesStorage.getInsurancePackagesFilePath();
+ }
+
+ @Override
+ public Optional readInsurancePackages() throws DataConversionException, IOException {
+ return readInsurancePackages(insurancePackagesStorage.getInsurancePackagesFilePath());
+ }
+
+ @Override
+ public Optional readInsurancePackages(Path filePath)
+ throws DataConversionException, IOException {
+ logger.fine("Attempting to read insurance packages from file: " + filePath);
+ return insurancePackagesStorage.readInsurancePackages(filePath);
+ }
+
+ @Override
+ public void saveInsurancePackages(InsurancePackagesSet packages) throws IOException {
+ saveInsurancePackages(packages, insurancePackagesStorage.getInsurancePackagesFilePath());
+ }
+
+ @Override
+ public void saveInsurancePackages(InsurancePackagesSet packages, Path filePath) throws IOException {
+ logger.fine("Attempting to write insurance packages to data file: " + filePath);
+ insurancePackagesStorage.saveInsurancePackages(packages, filePath);
+ }
+
}
diff --git a/src/main/java/seedu/address/storage/UndoRedoStorage.java b/src/main/java/seedu/address/storage/UndoRedoStorage.java
new file mode 100644
index 00000000000..05d345b7d7c
--- /dev/null
+++ b/src/main/java/seedu/address/storage/UndoRedoStorage.java
@@ -0,0 +1,93 @@
+package seedu.address.storage;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Stack;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import seedu.address.commons.core.LogsCenter;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.AddressBook;
+
+public class UndoRedoStorage {
+
+ private static int undoRedoLimit = 5;
+
+ private List undoList;
+ private Stack redoStack;
+
+ private final Logger logger = LogsCenter.getLogger(UndoRedoStorage.class);
+
+ /**
+ * Constructs a {@code UndoRedoStorage}.
+ */
+ public UndoRedoStorage() {
+ undoList = new ArrayList<>();
+ redoStack = new Stack<>();
+ }
+
+ private void resize() {
+ while (undoList.size() > undoRedoLimit) {
+ logger.info("Undo-able list updated");
+ undoList.remove(0);
+ }
+
+ while (redoStack.size() > undoRedoLimit) {
+ logger.info("Redo-able stack updated");
+ redoStack.remove(0);
+ }
+ }
+
+ /**
+ * Resets the stack storing Redo-able AddressBook states.
+ */
+ public void resetRedoStack() {
+ logger.info("Redo-able stack reset");
+ redoStack = new Stack<>();
+ }
+
+ /**
+ * Adds an address book to a list for storage.
+ */
+ public void addToUndo(AddressBook addressBook) {
+ logger.info("New entry in Undo-able list");
+ undoList.add(addressBook);
+ resize();
+ }
+
+ /**
+ * Undoes the previous command and returns the old {@code AddressBook}.
+ */
+ public AddressBook undo() throws CommandException {
+ if (undoList.isEmpty() || undoList.size() == 1) {
+ logger.log(Level.WARNING, "Current AddressBook is not undo-able");
+ throw new CommandException("No previous command to undo");
+ }
+
+ AddressBook currAddressBook = undoList.get(undoList.size() - 1);
+ redoStack.add(currAddressBook);
+ AddressBook prevAddressBook = undoList.get(undoList.size() - 2);
+ undoList.remove(undoList.size() - 1);
+ resize();
+
+ return prevAddressBook;
+ }
+
+ /**
+ * Redoes the previous command and returns the old {@code AddressBook}.
+ */
+ public AddressBook redo() throws CommandException {
+ if (redoStack.isEmpty()) {
+ logger.log(Level.WARNING, "Current AddressBook is not redo-able");
+ throw new CommandException("No previous command to redo");
+ }
+
+ AddressBook addressBook = redoStack.pop();
+ undoList.add(addressBook);
+ resize();
+
+ return addressBook;
+ }
+
+}
diff --git a/src/main/java/seedu/address/ui/CommandBox.java b/src/main/java/seedu/address/ui/CommandBox.java
index 9e75478664b..cd900c3171a 100644
--- a/src/main/java/seedu/address/ui/CommandBox.java
+++ b/src/main/java/seedu/address/ui/CommandBox.java
@@ -3,6 +3,8 @@
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.scene.control.TextField;
+import javafx.scene.input.KeyCode;
+import javafx.scene.input.KeyEvent;
import javafx.scene.layout.Region;
import seedu.address.logic.commands.CommandResult;
import seedu.address.logic.commands.exceptions.CommandException;
@@ -17,6 +19,8 @@ public class CommandBox extends UiPart {
private static final String FXML = "CommandBox.fxml";
private final CommandExecutor commandExecutor;
+ private final UserInputTracker userInputTracker;
+ private String historyMessage;
@FXML
private TextField commandTextField;
@@ -24,17 +28,36 @@ public class CommandBox extends UiPart {
/**
* Creates a {@code CommandBox} with the given {@code CommandExecutor}.
*/
- public CommandBox(CommandExecutor commandExecutor) {
+ public CommandBox(CommandExecutor commandExecutor, UserInputTracker userInputTracker) {
super(FXML);
this.commandExecutor = commandExecutor;
+ this.userInputTracker = userInputTracker;
// calls #setStyleToDefault() whenever there is a change to the text of the command box.
commandTextField.textProperty().addListener((unused1, unused2, unused3) -> setStyleToDefault());
}
/**
- * Handles the Enter button pressed event.
+ * Handles the pressing of keyboard buttons.
*/
@FXML
+ private void handleKeyTyped(KeyEvent event) {
+
+ if (event.getCode() == KeyCode.UP) {
+ handleUpArrow();
+ }
+
+ if (event.getCode() == KeyCode.DOWN) {
+ handleDownArrow();
+ }
+
+ if (event.getCode() == KeyCode.ENTER) {
+ handleCommandEntered();
+ }
+ }
+
+ /**
+ * Handles the Enter button pressed event.
+ */
private void handleCommandEntered() {
String commandText = commandTextField.getText();
if (commandText.equals("")) {
@@ -49,6 +72,26 @@ private void handleCommandEntered() {
}
}
+ /**
+ * Handles the pressing of the UP arrow key.
+ * @return The right command in history.
+ */
+ private void handleUpArrow() {
+ String pastCommand = userInputTracker.getString(true);
+ commandTextField.setText(pastCommand);
+ commandTextField.positionCaret(pastCommand.length());
+ }
+
+ /**
+ * Handles the pressing of the DOWN arrow key.
+ * @return The right command in history.
+ */
+ private void handleDownArrow() {
+ String pastCommand = userInputTracker.getString(false);
+ commandTextField.setText(pastCommand);
+ commandTextField.positionCaret(pastCommand.length());
+ }
+
/**
* Sets the command box style to use the default style.
*/
@@ -82,4 +125,15 @@ public interface CommandExecutor {
CommandResult execute(String commandText) throws CommandException, ParseException;
}
+ /**
+ * Represents a function that can track the history of user input.
+ */
+ @FunctionalInterface
+ public interface UserInputTracker {
+ /**
+ * Evaluates the pressing of UP/DOWN and returns the right String.
+ *
+ */
+ String getString(boolean isUp);
+ }
}
diff --git a/src/main/java/seedu/address/ui/HelpWindow.java b/src/main/java/seedu/address/ui/HelpWindow.java
index 9a665915949..1fb79ae77de 100644
--- a/src/main/java/seedu/address/ui/HelpWindow.java
+++ b/src/main/java/seedu/address/ui/HelpWindow.java
@@ -15,7 +15,7 @@
*/
public class HelpWindow extends UiPart {
- public static final String USERGUIDE_URL = "https://se-education.org/addressbook-level3/UserGuide.html";
+ public static final String USERGUIDE_URL = "https://ay2122s2-cs2103-w17-3.github.io/tp/UserGuide.html";
public static final String HELP_MESSAGE = "Refer to the user guide: " + USERGUIDE_URL;
private static final Logger logger = LogsCenter.getLogger(HelpWindow.class);
diff --git a/src/main/java/seedu/address/ui/MainWindow.java b/src/main/java/seedu/address/ui/MainWindow.java
index 9106c3aa6e5..7d0a8f37378 100644
--- a/src/main/java/seedu/address/ui/MainWindow.java
+++ b/src/main/java/seedu/address/ui/MainWindow.java
@@ -1,5 +1,8 @@
package seedu.address.ui;
+import java.awt.Desktop;
+import java.io.File;
+import java.nio.file.Path;
import java.util.logging.Logger;
import javafx.event.ActionEvent;
@@ -9,6 +12,7 @@
import javafx.scene.input.KeyCombination;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.StackPane;
+import javafx.stage.FileChooser;
import javafx.stage.Stage;
import seedu.address.commons.core.GuiSettings;
import seedu.address.commons.core.LogsCenter;
@@ -34,6 +38,8 @@ public class MainWindow extends UiPart {
private PersonListPanel personListPanel;
private ResultDisplay resultDisplay;
private HelpWindow helpWindow;
+ private MessageWindow messageWindow;
+ private PackageWindow packageWindow;
@FXML
private StackPane commandBoxPlaceholder;
@@ -66,6 +72,8 @@ public MainWindow(Stage primaryStage, Logic logic) {
setAccelerators();
helpWindow = new HelpWindow();
+ messageWindow = new MessageWindow();
+ packageWindow = new PackageWindow(logic);
}
public Stage getPrimaryStage() {
@@ -119,7 +127,7 @@ void fillInnerParts() {
StatusBarFooter statusBarFooter = new StatusBarFooter(logic.getAddressBookFilePath());
statusbarPlaceholder.getChildren().add(statusBarFooter.getRoot());
- CommandBox commandBox = new CommandBox(this::executeCommand);
+ CommandBox commandBox = new CommandBox(this::executeCommand, this::accessHistory);
commandBoxPlaceholder.getChildren().add(commandBox.getRoot());
}
@@ -147,6 +155,118 @@ public void handleHelp() {
}
}
+ /**
+ * Opens the package window or focuses on it if it's already opened.
+ */
+ @FXML
+ public void handleShowPackages() {
+ if (!packageWindow.isShowing()) {
+ packageWindow.show();
+ packageWindow.fillInnerParts();
+ } else {
+ packageWindow.focus();
+ }
+ }
+
+ /**
+ * Handles the tasks associated with allowing the user to load a file from CSV.
+ */
+ @FXML
+ public void handleLoadFromCsv() {
+ if (!Desktop.isDesktopSupported()) {
+ messageWindow.show("Desktop not supported.");
+ return;
+ }
+
+ logger.info("Loading from csv");
+ Path inputCsvFilePath = handleLoadFile();
+
+ if (inputCsvFilePath == null) {
+ return;
+ }
+
+ try {
+ logic.readAddressBookFromCsv(inputCsvFilePath);
+ messageWindow.show(
+ "Loaded successfully. Type a command (e.g. list) to save this permanently.");
+ } catch (CommandException err) {
+ messageWindow.show("Error in loading: " + err.getMessage());
+ }
+ }
+
+ /**
+ * OHandles the tasks associated with allowing the user to save a file to CSV.
+ */
+ @FXML
+ public void handleSaveToCsv() {
+ if (!Desktop.isDesktopSupported()) {
+ messageWindow.show("Desktop not supported.");
+ return;
+ }
+
+ logger.info("Saving to csv");
+ Path outputCsvFilePath = handleSaveFile();
+
+ if (outputCsvFilePath == null) {
+ return;
+ }
+
+ try {
+ logic.saveAddressBookToCsv(outputCsvFilePath);
+ messageWindow.show("Saved successfully.");
+ } catch (CommandException err) {
+ messageWindow.show("Error in saving: " + err.getMessage());
+ }
+ }
+
+ /**
+ * Method that handles the GUI aspect of allowing the user to select a path to load CSV file from.
+ *
+ * @return the Path of the CSV file.
+ */
+ public Path handleLoadFile() {
+
+ // initialise the file chooser
+ FileChooser fileChooser = new FileChooser();
+
+ // current working directory
+ File cwd = new java.io.File(".");
+
+ // chooser settings
+ fileChooser.setInitialDirectory(cwd);
+ fileChooser.setTitle("Select a ClientConnect CSV file...");
+
+ // only allow CSV files
+ fileChooser.getExtensionFilters().add(
+ new FileChooser.ExtensionFilter("ClientConnect CSV files", "*.csv"));
+
+ // opens the dialog
+ File fileChosen = fileChooser.showOpenDialog(primaryStage);
+ return fileChosen == null ? null : fileChosen.toPath();
+
+ }
+
+ /**
+ * Method that handles the GUI aspect of allowing the user to select a path to save a CSV file to.
+ *
+ * @return the Path of the CSV file.
+ */
+ public Path handleSaveFile() {
+
+ // initialise the file chooser
+ FileChooser chooser = new FileChooser();
+
+ // chooser settings
+ chooser.setInitialDirectory(new java.io.File("."));
+ chooser.setTitle("Save CSV file to ...");
+ chooser.setInitialFileName("ClientConnectData.csv");
+ chooser.getExtensionFilters().add(
+ new FileChooser.ExtensionFilter("ClientConnect CSV files", "*.csv"));
+
+ File fileChosen = chooser.showSaveDialog(primaryStage);
+ return fileChosen == null ? null : fileChosen.toPath();
+ }
+
void show() {
primaryStage.show();
}
@@ -160,6 +280,7 @@ private void handleExit() {
(int) primaryStage.getX(), (int) primaryStage.getY());
logic.setGuiSettings(guiSettings);
helpWindow.hide();
+ messageWindow.hide();
primaryStage.hide();
}
@@ -186,6 +307,18 @@ private CommandResult executeCommand(String commandText) throws CommandException
handleExit();
}
+ if (commandResult.isImportFromCsv()) {
+ handleLoadFromCsv();
+ }
+
+ if (commandResult.isExportToCsv()) {
+ handleSaveToCsv();
+ }
+
+ if (commandResult.isShowPackages()) {
+ handleShowPackages();
+ }
+
return commandResult;
} catch (CommandException | ParseException e) {
logger.info("Invalid command: " + commandText);
@@ -193,4 +326,15 @@ private CommandResult executeCommand(String commandText) throws CommandException
throw e;
}
}
+
+ /**
+ * Depending on whether the user pressed UP or DOWN, retrieve the previous or next user input.
+ * This seeks to emulate the command line ability to scroll through the history of typed commands.
+ *
+ * @param isUp True if the button pressed is Up, False if it's Down.
+ * @return The relevant command in history.
+ */
+ private String accessHistory(boolean isUp) {
+ return isUp ? logic.getPreviousCommand() : logic.getNextCommand();
+ }
}
diff --git a/src/main/java/seedu/address/ui/MessageWindow.java b/src/main/java/seedu/address/ui/MessageWindow.java
new file mode 100644
index 00000000000..ea14f0ae307
--- /dev/null
+++ b/src/main/java/seedu/address/ui/MessageWindow.java
@@ -0,0 +1,83 @@
+package seedu.address.ui;
+
+import java.util.logging.Logger;
+
+import javafx.fxml.FXML;
+import javafx.scene.control.Label;
+import javafx.stage.Stage;
+import seedu.address.commons.core.LogsCenter;
+
+/**
+ * Controller for a generic message window
+ */
+public class MessageWindow extends UiPart {
+
+ private static final Logger logger = LogsCenter.getLogger(MessageWindow.class);
+ private static final String FXML = "MessageWindow.fxml";
+
+ @FXML
+ private Label message;
+
+ /**
+ * Creates a new HelpWindow.
+ *
+ * @param root Stage to use as the root of the MessageWindow.
+ */
+ public MessageWindow(Stage root) {
+ super(FXML, root);
+ }
+
+ /**
+ * Creates a new MessageWindow.
+ */
+ public MessageWindow() {
+ this(new Stage());
+ }
+
+ /**
+ * Shows the help window.
+ * @throws IllegalStateException
+ *
+ *
+ * if this method is called on a thread other than the JavaFX Application Thread.
+ *
+ *
+ * if this method is called during animation or layout processing.
+ *
+ *
+ * if this method is called on the primary stage.
+ *
+ *
+ * if {@code dialogStage} is already showing.
+ *
+ *
+ */
+ public void show(String messageToShow) {
+ logger.fine("Showing a message: " + messageToShow);
+ message.setText(messageToShow);
+ getRoot().show();
+ getRoot().centerOnScreen();
+ }
+
+ /**
+ * Returns true if the message window is currently being shown.
+ */
+ public boolean isShowing() {
+ return getRoot().isShowing();
+ }
+
+ /**
+ * Hides the message window.
+ */
+ public void hide() {
+ getRoot().hide();
+ }
+
+ /**
+ * Focuses on the message window.
+ */
+ public void focus() {
+ getRoot().requestFocus();
+ }
+
+}
diff --git a/src/main/java/seedu/address/ui/PackageCard.java b/src/main/java/seedu/address/ui/PackageCard.java
new file mode 100644
index 00000000000..543c58e4ce0
--- /dev/null
+++ b/src/main/java/seedu/address/ui/PackageCard.java
@@ -0,0 +1,59 @@
+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.person.InsurancePackage;
+
+/**
+ * An UI component that displays information of a {@code Person}.
+ */
+public class PackageCard extends UiPart {
+
+ private static final String FXML = "PackageListCard.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 InsurancePackage insurancePackage;
+
+ @FXML
+ private HBox cardPane;
+ @FXML
+ private Label packageName;
+ @FXML
+ private Label packageDesc;
+
+ /**
+ * Creates a {@code PackageCard} with the given {@code InsurancePackage}
+ */
+ public PackageCard(InsurancePackage insurancePackage) {
+ super(FXML);
+ this.insurancePackage = insurancePackage;
+ packageName.setText(insurancePackage.getPackageName());
+ packageDesc.setText(insurancePackage.getPackageDescription());
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ // short circuit if same object
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof PersonCard)) {
+ return false;
+ }
+
+ // state check
+ PackageCard card = (PackageCard) other;
+ return insurancePackage.equals(card.insurancePackage);
+ }
+}
diff --git a/src/main/java/seedu/address/ui/PackageListPanel.java b/src/main/java/seedu/address/ui/PackageListPanel.java
new file mode 100644
index 00000000000..c9cc089adf7
--- /dev/null
+++ b/src/main/java/seedu/address/ui/PackageListPanel.java
@@ -0,0 +1,49 @@
+package seedu.address.ui;
+
+import java.util.logging.Logger;
+
+import javafx.collections.ObservableList;
+import javafx.fxml.FXML;
+import javafx.scene.control.ListCell;
+import javafx.scene.control.ListView;
+import javafx.scene.layout.Region;
+import seedu.address.commons.core.LogsCenter;
+import seedu.address.model.person.InsurancePackage;
+
+/**
+ * Panel containing the list of packages.
+ */
+public class PackageListPanel extends UiPart {
+ private static final String FXML = "PackageListPanel.fxml";
+ private final Logger logger = LogsCenter.getLogger(PackageListPanel.class);
+
+ @FXML
+ private ListView packageListView;
+
+ /**
+ * Creates a {@code PackageListPanel} with the given {@code ObservableList}.
+ */
+ public PackageListPanel(ObservableList packageList) {
+ super(FXML);
+ packageListView.setItems(packageList);
+ packageListView.setCellFactory(listView -> new PackageListViewCell());
+ }
+
+ /**
+ * Custom {@code ListCell} that displays the graphics of a {@code Person} using a {@code PersonCard}.
+ */
+ class PackageListViewCell extends ListCell {
+ @Override
+ protected void updateItem(InsurancePackage insurancePackage, boolean empty) {
+ super.updateItem(insurancePackage, empty);
+
+ if (empty || insurancePackage == null) {
+ setGraphic(null);
+ setText(null);
+ } else {
+ setGraphic(new PackageCard(insurancePackage).getRoot());
+ }
+ }
+ }
+
+}
diff --git a/src/main/java/seedu/address/ui/PackageWindow.java b/src/main/java/seedu/address/ui/PackageWindow.java
new file mode 100644
index 00000000000..a9a77da8dc9
--- /dev/null
+++ b/src/main/java/seedu/address/ui/PackageWindow.java
@@ -0,0 +1,106 @@
+package seedu.address.ui;
+
+import java.util.logging.Logger;
+
+import javafx.collections.FXCollections;
+import javafx.collections.ObservableList;
+import javafx.collections.transformation.FilteredList;
+import javafx.fxml.FXML;
+import javafx.scene.control.Label;
+import javafx.scene.layout.StackPane;
+import javafx.stage.Stage;
+import seedu.address.commons.core.LogsCenter;
+import seedu.address.logic.Logic;
+import seedu.address.model.person.InsurancePackage;
+
+/**
+ * Controller for a package page
+ */
+public class PackageWindow extends UiPart {
+ private static final Logger logger = LogsCenter.getLogger(PackageWindow.class);
+ private static final String FXML = "PackageWindow.fxml";
+ private Logic logic;
+
+ @FXML
+ private StackPane packageListPanelPlaceholder;
+ @FXML
+ private Label helpMessage;
+
+ // Independent Ui parts residing in this Ui container
+ private PackageListPanel packageListPanel;
+
+ /**
+ * Creates a new PackageWindow.
+ *
+ * @param root Stage to use as the root of the PackageWindow.
+ */
+ public PackageWindow(Stage root) {
+ super(FXML, root);
+ }
+
+ /**
+ * Creates a new PackageWindow.
+ */
+ public PackageWindow(Logic logic) {
+ this(new Stage());
+ this.logic = logic;
+ }
+
+ /**
+ * Shows the package window.
+ * @throws IllegalStateException
+ *
+ *
+ * if this method is called on a thread other than the JavaFX Application Thread.
+ *
+ *
+ * if this method is called during animation or layout processing.
+ *
+ *
+ * if this method is called on the primary stage.
+ *
+ *
+ * if {@code dialogStage} is already showing.
+ *
+ *
+ */
+ public void show() {
+ logger.fine("Showing package page about the insurance packages.");
+ getRoot().show();
+ getRoot().centerOnScreen();
+ }
+
+ /**
+ * Fills up all the placeholders of this window.
+ */
+ public void fillInnerParts() {
+ ObservableList internalList =
+ FXCollections.observableArrayList(logic.getAllPackages().getPackagesList());
+ ObservableList internalUnmodifiableList =
+ FXCollections.unmodifiableObservableList(internalList);
+ ObservableList packageList = new FilteredList<>(internalUnmodifiableList);
+ packageListPanel = new PackageListPanel(packageList);
+ packageListPanelPlaceholder.getChildren().add(packageListPanel.getRoot());
+ }
+
+ /**
+ * Returns true if the package window is currently being shown.
+ */
+ public boolean isShowing() {
+ return getRoot().isShowing();
+ }
+
+ /**
+ * Hides the package window.
+ */
+ public void hide() {
+ getRoot().hide();
+ }
+
+ /**
+ * Focuses on the package window.
+ */
+ public void focus() {
+ getRoot().requestFocus();
+ }
+}
diff --git a/src/main/java/seedu/address/ui/PersonCard.java b/src/main/java/seedu/address/ui/PersonCard.java
index 7fc927bc5d9..274af31bf83 100644
--- a/src/main/java/seedu/address/ui/PersonCard.java
+++ b/src/main/java/seedu/address/ui/PersonCard.java
@@ -1,6 +1,6 @@
package seedu.address.ui;
-import java.util.Comparator;
+import java.util.Map;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
@@ -8,6 +8,7 @@
import javafx.scene.layout.HBox;
import javafx.scene.layout.Region;
import seedu.address.model.person.Person;
+import seedu.address.model.tag.Priority;
/**
* An UI component that displays information of a {@code Person}.
@@ -15,6 +16,12 @@
public class PersonCard extends UiPart {
private static final String FXML = "PersonListCard.fxml";
+ private static final Map fxmlColorMapper = Map.of(
+ Priority.PRIORITY_1, "-fx-background-color: red;",
+ Priority.PRIORITY_2, "-fx-background-color: orange;",
+ Priority.PRIORITY_3, "-fx-background-color: gold;",
+ Priority.PRIORITY_4, "-fx-background-color: peachpuff;"
+ );
/**
* Note: Certain keywords such as "location" and "resources" are reserved keywords in JavaFX.
@@ -37,6 +44,8 @@ public class PersonCard extends UiPart {
@FXML
private Label address;
@FXML
+ private Label insurancePackage;
+ @FXML
private Label email;
@FXML
private FlowPane tags;
@@ -50,11 +59,18 @@ public PersonCard(Person person, int displayedIndex) {
id.setText(displayedIndex + ". ");
name.setText(person.getName().fullName);
phone.setText(person.getPhone().value);
+ insurancePackage.setText(person.getInsurancePackage().getPackageName());
address.setText(person.getAddress().value);
email.setText(person.getEmail().value);
person.getTags().stream()
- .sorted(Comparator.comparing(tag -> tag.tagName))
- .forEach(tag -> tags.getChildren().add(new Label(tag.tagName)));
+ // .sorted(Comparator.comparing(tag -> tag.tagName))
+ .forEach(tag -> {
+ Label label = new Label(tag.tagName);
+ if (tag.tagPriority != null) {
+ label.setStyle(fxmlColorMapper.get(tag.tagPriority));
+ }
+ tags.getChildren().add(label);
+ });
}
@Override
diff --git a/src/main/resources/images/address_book_32.png b/src/main/resources/images/address_book_32.png
index 29810cf1fd9..419d6020de6 100644
Binary files a/src/main/resources/images/address_book_32.png and b/src/main/resources/images/address_book_32.png differ
diff --git a/src/main/resources/view/CommandBox.fxml b/src/main/resources/view/CommandBox.fxml
index 09f6d6fe9e4..19dd3886dc8 100644
--- a/src/main/resources/view/CommandBox.fxml
+++ b/src/main/resources/view/CommandBox.fxml
@@ -4,6 +4,6 @@
-
+
diff --git a/src/main/resources/view/DarkExtensions.css b/src/main/resources/view/DarkExtensions.css
new file mode 100644
index 00000000000..bfe82a85964
--- /dev/null
+++ b/src/main/resources/view/DarkExtensions.css
@@ -0,0 +1,20 @@
+
+.error {
+ -fx-text-fill: #d06651 !important; /* The error class should always override the default text-fill style */
+}
+
+.list-cell:empty {
+ /* Empty cells will not have alternating colours */
+ -fx-background: #383838;
+}
+
+.tag-selector {
+ -fx-border-width: 1;
+ -fx-border-color: white;
+ -fx-border-radius: 3;
+ -fx-background-radius: 3;
+}
+
+.tooltip-text {
+ -fx-text-fill: white;
+}
diff --git a/src/main/resources/view/LightExtensions.css b/src/main/resources/view/LightExtensions.css
new file mode 100644
index 00000000000..abb687197fa
--- /dev/null
+++ b/src/main/resources/view/LightExtensions.css
@@ -0,0 +1,20 @@
+
+.error {
+ -fx-text-fill: #d06651 !important; /* The error class should always override the default text-fill style */
+}
+
+.list-cell:empty {
+ /* Empty cells will not have alternating colours */
+ -fx-background: white;
+}
+
+.tag-selector {
+ -fx-border-width: 1;
+ -fx-border-color: white;
+ -fx-border-radius: 3;
+ -fx-background-radius: 3;
+}
+
+.tooltip-text {
+ -fx-text-fill: white;
+}
diff --git a/src/main/resources/view/LightTheme.css b/src/main/resources/view/LightTheme.css
new file mode 100644
index 00000000000..5a9973a4aa6
--- /dev/null
+++ b/src/main/resources/view/LightTheme.css
@@ -0,0 +1,352 @@
+.background {
+ -fx-background-color: derive(#d3d3d3, 20%);
+ background-color: #d3d3d3; /* Used in the default.html file */
+}
+
+.label {
+ -fx-font-size: 11pt;
+ -fx-font-family: "Segoe UI Semibold";
+ -fx-text-fill: #555555;
+ -fx-opacity: 0.9;
+}
+
+.label-bright {
+ -fx-font-size: 11pt;
+ -fx-font-family: "Segoe UI Semibold";
+ -fx-text-fill: white;
+ -fx-opacity: 1;
+}
+
+.label-header {
+ -fx-font-size: 32pt;
+ -fx-font-family: "Segoe UI Light";
+ -fx-text-fill: #d3d3d3;
+ -fx-opacity: 1;
+}
+
+.text-field {
+ -fx-font-size: 12pt;
+ -fx-font-family: "Segoe UI Semibold";
+}
+
+.tab-pane {
+ -fx-padding: 0 0 0 1;
+}
+
+.tab-pane .tab-header-area {
+ -fx-padding: 0 0 0 0;
+ -fx-min-height: 0;
+ -fx-max-height: 0;
+}
+
+.table-view {
+ -fx-base: #1d1d1d;
+ -fx-control-inner-background: #1d1d1d;
+ -fx-background-color: #1d1d1d;
+ -fx-table-cell-border-color: transparent;
+ -fx-table-header-border-color: transparent;
+ -fx-padding: 5;
+}
+
+.table-view .column-header-background {
+ -fx-background-color: transparent;
+}
+
+.table-view .column-header, .table-view .filler {
+ -fx-size: 35;
+ -fx-border-width: 0 0 1 0;
+ -fx-background-color: transparent;
+ -fx-border-color:
+ transparent
+ transparent
+ derive(-fx-base, 80%)
+ transparent;
+ -fx-border-insets: 0 10 1 0;
+}
+
+.table-view .column-header .label {
+ -fx-font-size: 20pt;
+ -fx-font-family: "Segoe UI Light";
+ -fx-text-fill: white;
+ -fx-alignment: center-left;
+ -fx-opacity: 1;
+}
+
+.table-view:focused .table-row-cell:filled:focused:selected {
+ -fx-background-color: -fx-focus-color;
+}
+
+.split-pane:horizontal .split-pane-divider {
+ -fx-background-color: derive(#1d1d1d, 20%);
+ -fx-border-color: transparent transparent transparent #4d4d4d;
+}
+
+.split-pane {
+ -fx-border-radius: 1;
+ -fx-border-width: 1;
+ -fx-background-color: derive(#1d1d1d, 20%);
+}
+
+.list-view {
+ -fx-background-insets: 0;
+ -fx-padding: 0;
+ -fx-background-color: derive(white, 20%);
+}
+
+.list-cell {
+ -fx-label-padding: 0 0 0 0;
+ -fx-graphic-text-gap : 0;
+ -fx-padding: 0 0 0 0;
+}
+
+.list-cell:filled:even {
+ -fx-background-color: whitesmoke;
+}
+
+.list-cell:filled:odd {
+ -fx-background-color: white;
+}
+
+.list-cell:filled:selected {
+ -fx-background-color: #eaeaea;
+}
+
+.list-cell:filled:selected #cardPane {
+ -fx-border-color: #eaeaea;
+ -fx-border-width: 1;
+}
+
+.list-cell .label {
+ -fx-text-fill: #333333;
+}
+
+.cell_big_label {
+ -fx-font-family: "Segoe UI Semibold";
+ -fx-font-size: 16px;
+ -fx-text-fill: #333333;
+}
+
+.cell_small_label {
+ -fx-font-family: "Segoe UI";
+ -fx-font-size: 13px;
+ -fx-text-fill: #333333;
+}
+
+.stack-pane {
+ -fx-background-color: derive(whitesmoke, 20%);
+}
+
+.pane-with-border {
+ -fx-background-color: derive(white, 20%);
+ -fx-border-color: derive(whitesmoke, 10%);
+ -fx-border-top-width: 1px;
+}
+
+.status-bar {
+ -fx-background-color: derive(whitesmoke, 30%);
+}
+
+.result-display {
+ -fx-background-color: transparent;
+ -fx-font-family: "Segoe UI Light";
+ -fx-font-size: 13pt;
+ -fx-text-fill: #333333;
+}
+
+.result-display .label {
+ -fx-text-fill: #1d1d1d !important;
+}
+
+.status-bar .label {
+ -fx-font-family: "Segoe UI Light";
+ -fx-text-fill: #333333;
+ -fx-padding: 4px;
+ -fx-pref-height: 30px;
+}
+
+.status-bar-with-border {
+ -fx-background-color: derive(#1d1d1d, 30%);
+ -fx-border-color: derive(#1d1d1d, 25%);
+ -fx-border-width: 1px;
+}
+
+.status-bar-with-border .label {
+ -fx-text-fill: white;
+}
+
+.grid-pane {
+ -fx-background-color: derive(#1d1d1d, 30%);
+ -fx-border-color: derive(#1d1d1d, 30%);
+ -fx-border-width: 1px;
+}
+
+.grid-pane .stack-pane {
+ -fx-background-color: derive(#1d1d1d, 30%);
+}
+
+.context-menu {
+ -fx-background-color: derive(white, 50%);
+}
+
+.context-menu .label {
+ -fx-text-fill: #333333;
+}
+
+.menu-bar {
+ -fx-background-color: derive(white, 20%);
+}
+
+.menu-bar .label {
+ -fx-font-size: 14pt;
+ -fx-font-family: "Segoe UI Light";
+ -fx-text-fill: #333333;
+ -fx-opacity: 0.9;
+}
+
+.menu .left-container {
+ -fx-background-color: #333333;
+}
+
+/*
+ * Metro style Push Button
+ * Author: Pedro Duque Vieira
+ * http://pixelduke.wordpress.com/2012/10/23/jmetro-windows-8-controls-on-java/
+ */
+.button {
+ -fx-padding: 5 22 5 22;
+ -fx-border-color: #e2e2e2;
+ -fx-border-width: 2;
+ -fx-background-radius: 0;
+ -fx-background-color: #d3d3d3;
+ -fx-font-family: "Segoe UI", Helvetica, Arial, sans-serif;
+ -fx-font-size: 11pt;
+ -fx-text-fill: #d8d8d8;
+ -fx-background-insets: 0 0 0 0, 0, 1, 2;
+}
+
+.button:hover {
+ -fx-background-color: #d3d3d3;
+}
+
+.button:pressed, .button:default:hover:pressed {
+ -fx-background-color: white;
+ -fx-text-fill: #d3d3d3;
+}
+
+.button:focused {
+ -fx-border-color: white, white;
+ -fx-border-width: 1, 1;
+ -fx-border-style: solid, segments(1, 1);
+ -fx-border-radius: 0, 0;
+ -fx-border-insets: 1 1 1 1, 0;
+}
+
+.button:disabled, .button:default:disabled {
+ -fx-opacity: 0.4;
+ -fx-background-color: #1d1d1d;
+ -fx-text-fill: white;
+}
+
+.button:default {
+ -fx-background-color: -fx-focus-color;
+ -fx-text-fill: #ffffff;
+}
+
+.button:default:hover {
+ -fx-background-color: derive(-fx-focus-color, 30%);
+}
+
+.dialog-pane {
+ -fx-background-color: #1d1d1d;
+}
+
+.dialog-pane > *.button-bar > *.container {
+ -fx-background-color: #1d1d1d;
+}
+
+.dialog-pane > *.label.content {
+ -fx-font-size: 14px;
+ -fx-font-weight: bold;
+ -fx-text-fill: white;
+}
+
+.dialog-pane:header *.header-panel {
+ -fx-background-color: derive(#1d1d1d, 25%);
+}
+
+.dialog-pane:header *.header-panel *.label {
+ -fx-font-size: 18px;
+ -fx-font-style: italic;
+ -fx-fill: white;
+ -fx-text-fill: white;
+}
+
+.scroll-bar {
+ -fx-background-color: derive(white, 20%);
+}
+
+.scroll-bar .thumb {
+ -fx-background-color: derive(#d3d3d3, 50%);
+ -fx-background-insets: 3;
+}
+
+.scroll-bar .increment-button, .scroll-bar .decrement-button {
+ -fx-background-color: transparent;
+ -fx-padding: 0 0 0 0;
+}
+
+.scroll-bar .increment-arrow, .scroll-bar .decrement-arrow {
+ -fx-shape: " ";
+}
+
+.scroll-bar:vertical .increment-arrow, .scroll-bar:vertical .decrement-arrow {
+ -fx-padding: 1 8 1 8;
+}
+
+.scroll-bar:horizontal .increment-arrow, .scroll-bar:horizontal .decrement-arrow {
+ -fx-padding: 8 1 8 1;
+}
+
+#cardPane {
+ -fx-background-color: transparent;
+ -fx-border-width: 0;
+}
+
+#commandTypeLabel {
+ -fx-font-size: 11px;
+ -fx-text-fill: #F70D1A;
+}
+
+#commandTextField {
+ -fx-background-color: white;
+ -fx-background-insets: 0;
+ -fx-border-color: white white white white;
+ -fx-border-insets: 0;
+ -fx-border-width: 1;
+ -fx-font-family: "Segoe UI Light";
+ -fx-font-size: 13pt;
+ -fx-text-fill: #333333;
+}
+
+#filterField, #personListPanel, #personWebpage {
+ -fx-effect: innershadow(gaussian, black, 10, 0, 0, 0);
+}
+
+#resultDisplay .content {
+ -fx-background-color: white;
+ -fx-background-radius: 0;
+}
+
+#tags {
+ -fx-hgap: 7;
+ -fx-vgap: 3;
+}
+
+#tags .label {
+ -fx-text-fill: white;
+ -fx-background-color: #3e7b91;
+ -fx-padding: 1 3 1 3;
+ -fx-border-radius: 2;
+ -fx-background-radius: 2;
+ -fx-font-size: 11;
+}
diff --git a/src/main/resources/view/MainWindow.fxml b/src/main/resources/view/MainWindow.fxml
index a431648f6c0..0e0e5acc8b9 100644
--- a/src/main/resources/view/MainWindow.fxml
+++ b/src/main/resources/view/MainWindow.fxml
@@ -12,25 +12,30 @@
+ title="ClientConnect" minWidth="450" minHeight="600" onCloseRequest="#handleExit">
-
-
+
+
+
diff --git a/src/main/resources/view/MessageWindow.css b/src/main/resources/view/MessageWindow.css
new file mode 100644
index 00000000000..17e8a8722cd
--- /dev/null
+++ b/src/main/resources/view/MessageWindow.css
@@ -0,0 +1,19 @@
+#copyButton, #helpMessage {
+ -fx-text-fill: white;
+}
+
+#copyButton {
+ -fx-background-color: dimgray;
+}
+
+#copyButton:hover {
+ -fx-background-color: gray;
+}
+
+#copyButton:armed {
+ -fx-background-color: darkgray;
+}
+
+#helpMessageContainer {
+ -fx-background-color: derive(#1d1d1d, 20%);
+}
diff --git a/src/main/resources/view/MessageWindow.fxml b/src/main/resources/view/MessageWindow.fxml
new file mode 100644
index 00000000000..6fc53280ace
--- /dev/null
+++ b/src/main/resources/view/MessageWindow.fxml
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/view/PackageListCard.fxml b/src/main/resources/view/PackageListCard.fxml
new file mode 100644
index 00000000000..a626b6bf1eb
--- /dev/null
+++ b/src/main/resources/view/PackageListCard.fxml
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/view/PackageListPanel.fxml b/src/main/resources/view/PackageListPanel.fxml
new file mode 100644
index 00000000000..bf333ff5e31
--- /dev/null
+++ b/src/main/resources/view/PackageListPanel.fxml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/view/PackageWindow.fxml b/src/main/resources/view/PackageWindow.fxml
new file mode 100644
index 00000000000..0291c588b66
--- /dev/null
+++ b/src/main/resources/view/PackageWindow.fxml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/view/PersonListCard.fxml b/src/main/resources/view/PersonListCard.fxml
index f08ea32ad55..a70f7f1709c 100644
--- a/src/main/resources/view/PersonListCard.fxml
+++ b/src/main/resources/view/PersonListCard.fxml
@@ -29,6 +29,7 @@
+
diff --git a/src/test/data/CsvAddressBookStorageTest/invalidAndValidPersonAddressBook.csv b/src/test/data/CsvAddressBookStorageTest/invalidAndValidPersonAddressBook.csv
new file mode 100644
index 00000000000..c99f81bc061
--- /dev/null
+++ b/src/test/data/CsvAddressBookStorageTest/invalidAndValidPersonAddressBook.csv
@@ -0,0 +1,3 @@
+Name,Phone Number,Email,Insurance Package,Address,Tags
+"Valid Person",9482424,hans@example.com,Gold,"4th street",
+"Person With Invalid Phone Field",948asdf2424,hans@example.com,Silver,"4th street",
diff --git a/src/test/data/CsvAddressBookStorageTest/invalidPersonAddressBook.csv b/src/test/data/CsvAddressBookStorageTest/invalidPersonAddressBook.csv
new file mode 100644
index 00000000000..894bac4809a
--- /dev/null
+++ b/src/test/data/CsvAddressBookStorageTest/invalidPersonAddressBook.csv
@@ -0,0 +1,2 @@
+Name,Phone Number,Email,Insurance Package,Address,Tags
+"Person with invalid name field: Ha!ns Mu@ster",9482424,hans@example.com,Golden Package,4th street,
diff --git a/src/test/data/CsvInsurancePackageStorageTest/invalidAndValidPackages.csv b/src/test/data/CsvInsurancePackageStorageTest/invalidAndValidPackages.csv
new file mode 100644
index 00000000000..09babf85bf8
--- /dev/null
+++ b/src/test/data/CsvInsurancePackageStorageTest/invalidAndValidPackages.csv
@@ -0,0 +1,3 @@
+Package Name,Package Details
+,Dummy detail
+Valid name,Dummy detail two
diff --git a/src/test/data/CsvInsurancePackageStorageTest/invalidPackages.csv b/src/test/data/CsvInsurancePackageStorageTest/invalidPackages.csv
new file mode 100644
index 00000000000..cc6f00cb27e
--- /dev/null
+++ b/src/test/data/CsvInsurancePackageStorageTest/invalidPackages.csv
@@ -0,0 +1,2 @@
+Package Name,Package Details
+,Dummy detail
diff --git a/src/test/data/JsonAddressBookStorageTest/invalidAndValidPersonAddressBook.json b/src/test/data/JsonAddressBookStorageTest/invalidAndValidPersonAddressBook.json
index 6a4d2b7181c..6382c579b1f 100644
--- a/src/test/data/JsonAddressBookStorageTest/invalidAndValidPersonAddressBook.json
+++ b/src/test/data/JsonAddressBookStorageTest/invalidAndValidPersonAddressBook.json
@@ -3,11 +3,13 @@
"name": "Valid Person",
"phone": "9482424",
"email": "hans@example.com",
+ "insurance package": "Gold",
"address": "4th street"
}, {
"name": "Person With Invalid Phone Field",
"phone": "948asdf2424",
"email": "hans@example.com",
+ "insurance package": "Silver",
"address": "4th street"
} ]
}
diff --git a/src/test/data/JsonAddressBookStorageTest/invalidPersonAddressBook.json b/src/test/data/JsonAddressBookStorageTest/invalidPersonAddressBook.json
index ccd21f7d1a9..d0a1e10d069 100644
--- a/src/test/data/JsonAddressBookStorageTest/invalidPersonAddressBook.json
+++ b/src/test/data/JsonAddressBookStorageTest/invalidPersonAddressBook.json
@@ -3,6 +3,7 @@
"name": "Person with invalid name field: Ha!ns Mu@ster",
"phone": "9482424",
"email": "hans@example.com",
+ "insurance package": "Gold",
"address": "4th street"
} ]
}
diff --git a/src/test/data/JsonSerializableAddressBookTest/duplicatePersonAddressBook.json b/src/test/data/JsonSerializableAddressBookTest/duplicatePersonAddressBook.json
index 48831cc7674..6e7e76de6ca 100644
--- a/src/test/data/JsonSerializableAddressBookTest/duplicatePersonAddressBook.json
+++ b/src/test/data/JsonSerializableAddressBookTest/duplicatePersonAddressBook.json
@@ -3,11 +3,16 @@
"name": "Alice Pauline",
"phone": "94351253",
"email": "alice@example.com",
+ "insurance package": "Gold Plus",
"address": "123, Jurong West Ave 6, #08-111",
- "tagged": [ "friends" ]
+ "tagged": [ {
+ "tagName" : "introduce to friends",
+ "tagPriority" : "PRIORITY_2"
+ } ]
}, {
"name": "Alice Pauline",
"phone": "94351253",
+ "insurance package": "Gold Plus",
"email": "pauline@example.com",
"address": "4th street"
} ]
diff --git a/src/test/data/JsonSerializableAddressBookTest/invalidPersonAddressBook.json b/src/test/data/JsonSerializableAddressBookTest/invalidPersonAddressBook.json
index ad3f135ae42..16bcfe82764 100644
--- a/src/test/data/JsonSerializableAddressBookTest/invalidPersonAddressBook.json
+++ b/src/test/data/JsonSerializableAddressBookTest/invalidPersonAddressBook.json
@@ -3,6 +3,7 @@
"name": "Hans Muster",
"phone": "9482424",
"email": "invalid@email!3e",
+ "insurance package": "Gold Plus",
"address": "4th street"
} ]
}
diff --git a/src/test/data/JsonSerializableAddressBookTest/typicalPersonsAddressBook.json b/src/test/data/JsonSerializableAddressBookTest/typicalPersonsAddressBook.json
index f10eddee12e..8d0779b817a 100644
--- a/src/test/data/JsonSerializableAddressBookTest/typicalPersonsAddressBook.json
+++ b/src/test/data/JsonSerializableAddressBookTest/typicalPersonsAddressBook.json
@@ -4,42 +4,61 @@
"name" : "Alice Pauline",
"phone" : "94351253",
"email" : "alice@example.com",
+ "insurance package": "Females-Only Golden Premium",
"address" : "123, Jurong West Ave 6, #08-111",
- "tagged" : [ "friends" ]
+ "tagged" : [ {
+ "tagName" : "friends with Benson",
+ "tagPriority" : null
+ } ]
}, {
"name" : "Benson Meier",
"phone" : "98765432",
"email" : "johnd@example.com",
+ "insurance package": "Pro Silver Plus",
"address" : "311, Clementi Ave 2, #02-25",
- "tagged" : [ "owesMoney", "friends" ]
+ "tagged" : [ {
+ "tagName" : "owes money",
+ "tagPriority" : "PRIORITY_1"
+ }, {
+ "tagName" : "friends with Alice",
+ "tagPriority" : null
+ } ]
}, {
"name" : "Carl Kurz",
"phone" : "95352563",
"email" : "heinz@example.com",
+ "insurance package": "Red Basic 1",
"address" : "wall street",
"tagged" : [ ]
}, {
"name" : "Daniel Meier",
"phone" : "87652533",
"email" : "cornelia@example.com",
+ "insurance package": "Basic BB",
"address" : "10th street",
- "tagged" : [ "friends" ]
+ "tagged" : [ {
+ "tagName" : "update about new contract",
+ "tagPriority" : "PRIORITY_3"
+ } ]
}, {
"name" : "Elle Meyer",
"phone" : "9482224",
"email" : "werner@example.com",
+ "insurance package": "Life Premium X",
"address" : "michegan ave",
"tagged" : [ ]
}, {
"name" : "Fiona Kunz",
"phone" : "9482427",
"email" : "lydia@example.com",
+ "insurance package": "Lvl 5 Apple Guarantee",
"address" : "little tokyo",
"tagged" : [ ]
}, {
"name" : "George Best",
"phone" : "9482442",
"email" : "anna@example.com",
+ "insurance package": "Poor Man's Fire Insurance",
"address" : "4th street",
"tagged" : [ ]
} ]
diff --git a/src/test/java/seedu/address/commons/util/CsvUtilTest.java b/src/test/java/seedu/address/commons/util/CsvUtilTest.java
new file mode 100644
index 00000000000..a44118fadfd
--- /dev/null
+++ b/src/test/java/seedu/address/commons/util/CsvUtilTest.java
@@ -0,0 +1,56 @@
+package seedu.address.commons.util;
+
+import java.nio.file.Path;
+import java.util.Collections;
+import java.util.List;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import seedu.address.storage.CsvAdaptedInsurancePackage;
+import seedu.address.storage.CsvAdaptedPerson;
+import seedu.address.testutil.TestUtil;
+
+
+/**
+ * Tests CSV Read and Write
+ */
+public class CsvUtilTest {
+
+ private static final CsvAdaptedPerson DUMMY_PERSON =
+ new CsvAdaptedPerson(null, null, null, null, null, null);
+
+ // fake list with only 1 person
+ private static final List DUMMY_PERSON_LIST = Collections.singletonList(DUMMY_PERSON);
+
+ private static final CsvAdaptedInsurancePackage DUMMY_PACKAGE = new CsvAdaptedInsurancePackage("Test", "Test");
+
+ // fake list with only 1 package
+ private static final List DUMMY_PACKAGE_LIST =
+ Collections.singletonList(DUMMY_PACKAGE);
+
+ private static final Path TEST_FILE = TestUtil.getFilePathInSandboxFolder("test.csv");
+
+ @Test
+ public void writeAndReadAbCsvFile() {
+ try {
+ CsvUtil.saveAbCsvFile(DUMMY_PERSON_LIST, TEST_FILE);
+ List results = CsvUtil.loadAbCsvFile(TEST_FILE);
+ Assertions.assertEquals(DUMMY_PERSON_LIST.size(), results.size());
+ } catch (Exception e) {
+ Assertions.fail();
+ }
+ }
+
+ @Test
+ public void writeAndReadIpCsvFile() {
+ try {
+ CsvUtil.saveIpCsvFile(DUMMY_PACKAGE_LIST, TEST_FILE);
+ List results = CsvUtil.loadIpCsvFile(TEST_FILE);
+ Assertions.assertEquals(DUMMY_PACKAGE_LIST.size(), results.size());
+ } catch (Exception e) {
+ Assertions.fail();
+ }
+ }
+
+}
diff --git a/src/test/java/seedu/address/logic/LogicManagerTest.java b/src/test/java/seedu/address/logic/LogicManagerTest.java
index ad923ac249a..64fd1326c25 100644
--- a/src/test/java/seedu/address/logic/LogicManagerTest.java
+++ b/src/test/java/seedu/address/logic/LogicManagerTest.java
@@ -5,8 +5,10 @@
import static seedu.address.commons.core.Messages.MESSAGE_UNKNOWN_COMMAND;
import static seedu.address.logic.commands.CommandTestUtil.ADDRESS_DESC_AMY;
import static seedu.address.logic.commands.CommandTestUtil.EMAIL_DESC_AMY;
+import static seedu.address.logic.commands.CommandTestUtil.INSURANCE_PACKAGE_DESC_AMY;
import static seedu.address.logic.commands.CommandTestUtil.NAME_DESC_AMY;
import static seedu.address.logic.commands.CommandTestUtil.PHONE_DESC_AMY;
+import static seedu.address.logic.commands.CommandTestUtil.TAG_DESC_FRIEND;
import static seedu.address.testutil.Assert.assertThrows;
import static seedu.address.testutil.TypicalPersons.AMY;
@@ -22,11 +24,14 @@
import seedu.address.logic.commands.ListCommand;
import seedu.address.logic.commands.exceptions.CommandException;
import seedu.address.logic.parser.exceptions.ParseException;
+import seedu.address.model.InsurancePackagesSet;
import seedu.address.model.Model;
import seedu.address.model.ModelManager;
import seedu.address.model.ReadOnlyAddressBook;
import seedu.address.model.UserPrefs;
import seedu.address.model.person.Person;
+import seedu.address.storage.CommandStorage;
+import seedu.address.storage.CsvInsurancePackagesStorage;
import seedu.address.storage.JsonAddressBookStorage;
import seedu.address.storage.JsonUserPrefsStorage;
import seedu.address.storage.StorageManager;
@@ -46,8 +51,10 @@ public void setUp() {
JsonAddressBookStorage addressBookStorage =
new JsonAddressBookStorage(temporaryFolder.resolve("addressBook.json"));
JsonUserPrefsStorage userPrefsStorage = new JsonUserPrefsStorage(temporaryFolder.resolve("userPrefs.json"));
- StorageManager storage = new StorageManager(addressBookStorage, userPrefsStorage);
- logic = new LogicManager(model, storage);
+ CsvInsurancePackagesStorage ipStorage = new CsvInsurancePackagesStorage(temporaryFolder.resolve("ip.csv"));
+ StorageManager storage = new StorageManager(addressBookStorage, userPrefsStorage, ipStorage);
+ CommandStorage commandStorage = new CommandStorage();
+ logic = new LogicManager(model, storage, commandStorage);
}
@Test
@@ -75,13 +82,16 @@ public void execute_storageThrowsIoException_throwsCommandException() {
new JsonAddressBookIoExceptionThrowingStub(temporaryFolder.resolve("ioExceptionAddressBook.json"));
JsonUserPrefsStorage userPrefsStorage =
new JsonUserPrefsStorage(temporaryFolder.resolve("ioExceptionUserPrefs.json"));
- StorageManager storage = new StorageManager(addressBookStorage, userPrefsStorage);
- logic = new LogicManager(model, storage);
+ CsvInsurancePackagesStorage ipStorage =
+ new CsvInsurancePackagesStorage(temporaryFolder.resolve("ioExceptionIp.csv"));
+ StorageManager storage = new StorageManager(addressBookStorage, userPrefsStorage, ipStorage);
+ CommandStorage commandStorage = new CommandStorage();
+ logic = new LogicManager(model, storage, commandStorage);
// Execute add command
String addCommand = AddCommand.COMMAND_WORD + NAME_DESC_AMY + PHONE_DESC_AMY + EMAIL_DESC_AMY
- + ADDRESS_DESC_AMY;
- Person expectedPerson = new PersonBuilder(AMY).withTags().build();
+ + INSURANCE_PACKAGE_DESC_AMY + ADDRESS_DESC_AMY + TAG_DESC_FRIEND;
+ Person expectedPerson = new PersonBuilder(AMY).build();
ModelManager expectedModel = new ModelManager();
expectedModel.addPerson(expectedPerson);
String expectedMessage = LogicManager.FILE_OPS_ERROR_MESSAGE + DUMMY_IO_EXCEPTION;
@@ -129,7 +139,7 @@ private void assertCommandException(String inputCommand, String expectedMessage)
*/
private void assertCommandFailure(String inputCommand, Class extends Throwable> expectedException,
String expectedMessage) {
- Model expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs());
+ Model expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs(), new InsurancePackagesSet());
assertCommandFailure(inputCommand, expectedException, expectedMessage, expectedModel);
}
diff --git a/src/test/java/seedu/address/logic/commands/AddCommandIntegrationTest.java b/src/test/java/seedu/address/logic/commands/AddCommandIntegrationTest.java
index cb8714bb055..762b232dee9 100644
--- a/src/test/java/seedu/address/logic/commands/AddCommandIntegrationTest.java
+++ b/src/test/java/seedu/address/logic/commands/AddCommandIntegrationTest.java
@@ -7,6 +7,7 @@
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
+import seedu.address.model.InsurancePackagesSet;
import seedu.address.model.Model;
import seedu.address.model.ModelManager;
import seedu.address.model.UserPrefs;
@@ -22,14 +23,14 @@ public class AddCommandIntegrationTest {
@BeforeEach
public void setUp() {
- model = new ModelManager(getTypicalAddressBook(), new UserPrefs());
+ model = new ModelManager(getTypicalAddressBook(), new UserPrefs(), new InsurancePackagesSet());
}
@Test
public void execute_newPerson_success() {
Person validPerson = new PersonBuilder().build();
- Model expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs());
+ Model expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs(), new InsurancePackagesSet());
expectedModel.addPerson(validPerson);
assertCommandSuccess(new AddCommand(validPerson), model,
diff --git a/src/test/java/seedu/address/logic/commands/AddCommandTest.java b/src/test/java/seedu/address/logic/commands/AddCommandTest.java
index 5865713d5dd..4ccb65b7186 100644
--- a/src/test/java/seedu/address/logic/commands/AddCommandTest.java
+++ b/src/test/java/seedu/address/logic/commands/AddCommandTest.java
@@ -17,9 +17,11 @@
import seedu.address.commons.core.GuiSettings;
import seedu.address.logic.commands.exceptions.CommandException;
import seedu.address.model.AddressBook;
+import seedu.address.model.InsurancePackagesSet;
import seedu.address.model.Model;
import seedu.address.model.ReadOnlyAddressBook;
import seedu.address.model.ReadOnlyUserPrefs;
+import seedu.address.model.person.InsurancePackage;
import seedu.address.model.person.Person;
import seedu.address.testutil.PersonBuilder;
@@ -75,7 +77,7 @@ public void equals() {
}
/**
- * A default model stub that have all of the methods failing.
+ * A default model stub that has all of the methods failing.
*/
private class ModelStub implements Model {
@Override
@@ -108,6 +110,46 @@ public void setAddressBookFilePath(Path addressBookFilePath) {
throw new AssertionError("This method should not be called.");
}
+ @Override
+ public Path getInsurancePackagesFilePath() {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void setInsurancePackagesFilePath(Path insurancePackagesFilePath) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public InsurancePackagesSet getInsurancePackagesSet() {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void setInsurancePackagesSet(InsurancePackagesSet s) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public boolean hasInsurancePackage(InsurancePackage p) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void addInsurancePackage(InsurancePackage p) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void deleteInsurancePackage(InsurancePackage p) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void setInsurancePackage(String targetPackageName, String newPackageDesc) {
+ throw new AssertionError("This method should not be called.");
+ }
+
@Override
public void addPerson(Person person) {
throw new AssertionError("This method should not be called.");
@@ -118,6 +160,11 @@ public void setAddressBook(ReadOnlyAddressBook newData) {
throw new AssertionError("This method should not be called.");
}
+ @Override
+ public void resetAddressBook() {
+ throw new AssertionError("This method should not be called.");
+ }
+
@Override
public ReadOnlyAddressBook getAddressBook() {
throw new AssertionError("This method should not be called.");
@@ -147,6 +194,21 @@ public ObservableList getFilteredPersonList() {
public void updateFilteredPersonList(Predicate predicate) {
throw new AssertionError("This method should not be called.");
}
+
+ @Override
+ public void sortByPriority() {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void undoCommand() {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void redoCommand() {
+ throw new AssertionError("This method should not be called.");
+ }
}
/**
@@ -168,7 +230,7 @@ public boolean hasPerson(Person person) {
}
/**
- * A Model stub that always accept the person being added.
+ * A Model stub that always accepts the person being added.
*/
private class ModelStubAcceptingPersonAdded extends ModelStub {
final ArrayList personsAdded = new ArrayList<>();
@@ -185,6 +247,14 @@ public void addPerson(Person person) {
personsAdded.add(person);
}
+ @Override
+ public void addInsurancePackage(InsurancePackage p) {
+ // do nothing with the package
+ requireNonNull(p);
+
+ // this method is written because the Model now adds an insurance package when a person is added
+ }
+
@Override
public ReadOnlyAddressBook getAddressBook() {
return new AddressBook();
diff --git a/src/test/java/seedu/address/logic/commands/AddPackageCommandTest.java b/src/test/java/seedu/address/logic/commands/AddPackageCommandTest.java
new file mode 100644
index 00000000000..b89f0d5a4eb
--- /dev/null
+++ b/src/test/java/seedu/address/logic/commands/AddPackageCommandTest.java
@@ -0,0 +1,225 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static seedu.address.testutil.Assert.assertThrows;
+
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.function.Predicate;
+
+import org.junit.jupiter.api.Test;
+
+import javafx.collections.ObservableList;
+import seedu.address.commons.core.GuiSettings;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.InsurancePackagesSet;
+import seedu.address.model.Model;
+import seedu.address.model.ReadOnlyAddressBook;
+import seedu.address.model.ReadOnlyUserPrefs;
+import seedu.address.model.person.InsurancePackage;
+import seedu.address.model.person.Person;
+
+public class AddPackageCommandTest {
+
+ @Test
+ public void constructor_nullPerson_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> new AddPackageCommand(null));
+ }
+
+ @Test
+ public void execute_packageAcceptedByModel_addSuccessful() throws Exception {
+ AddPackageCommandTest.ModelStubAcceptingPackageAdded modelStub =
+ new AddPackageCommandTest.ModelStubAcceptingPackageAdded();
+
+ InsurancePackage validPackage = new InsurancePackage("Test", "Test Desc");
+ CommandResult commandResult = new AddPackageCommand(validPackage).execute(modelStub);
+
+ assertEquals(String.format(AddPackageCommand.MESSAGE_SUCCESS, validPackage), commandResult.getFeedbackToUser());
+ assertEquals(Collections.singletonList(validPackage), modelStub.packagesAdded);
+ }
+
+ @Test
+ public void execute_duplicatePackage_throwsCommandException() {
+ InsurancePackage validPackage = new InsurancePackage("Test");
+ AddPackageCommand addPackageCommand = new AddPackageCommand(validPackage);
+ AddPackageCommandTest.ModelStub modelStub = new AddPackageCommandTest.ModelStubWithPackage(validPackage);
+
+ assertThrows(CommandException.class,
+ AddPackageCommand.MESSAGE_DUPLICATE_PACKAGE, () -> addPackageCommand.execute(modelStub));
+ }
+
+ /**
+ * A default model stub that has all of the methods failing.
+ */
+ private class ModelStub implements Model {
+ @Override
+ public void setUserPrefs(ReadOnlyUserPrefs userPrefs) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public ReadOnlyUserPrefs getUserPrefs() {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public GuiSettings getGuiSettings() {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void setGuiSettings(GuiSettings guiSettings) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public Path getAddressBookFilePath() {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void setAddressBookFilePath(Path addressBookFilePath) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public Path getInsurancePackagesFilePath() {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void setInsurancePackagesFilePath(Path insurancePackagesFilePath) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public InsurancePackagesSet getInsurancePackagesSet() {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void setInsurancePackagesSet(InsurancePackagesSet s) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public boolean hasInsurancePackage(InsurancePackage p) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void addInsurancePackage(InsurancePackage p) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void deleteInsurancePackage(InsurancePackage p) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void setInsurancePackage(String targetPackageName, String newPackageDesc) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void addPerson(Person person) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void setAddressBook(ReadOnlyAddressBook newData) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void resetAddressBook() {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public ReadOnlyAddressBook getAddressBook() {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public boolean hasPerson(Person person) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void deletePerson(Person target) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void setPerson(Person target, Person editedPerson) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public ObservableList getFilteredPersonList() {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void updateFilteredPersonList(Predicate predicate) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void sortByPriority() {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void undoCommand() {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void redoCommand() {
+ throw new AssertionError("This method should not be called.");
+ }
+ }
+
+ /**
+ * A Model stub that contains a single insurance package.
+ */
+ private class ModelStubWithPackage extends AddPackageCommandTest.ModelStub {
+ private final InsurancePackage p;
+
+ ModelStubWithPackage(InsurancePackage p) {
+ requireNonNull(p);
+ this.p = p;
+ }
+
+ @Override
+ public boolean hasInsurancePackage(InsurancePackage p) {
+ requireNonNull(p);
+ return this.p.equals(p);
+ }
+ }
+
+ /**
+ * A Model stub that always accepts the package being added.
+ */
+ private class ModelStubAcceptingPackageAdded extends AddPackageCommandTest.ModelStub {
+ final ArrayList packagesAdded = new ArrayList<>();
+
+ @Override
+ public boolean hasInsurancePackage(InsurancePackage p) {
+ requireNonNull(p);
+ return packagesAdded.stream().anyMatch(p::equals);
+ }
+
+ @Override
+ public void addInsurancePackage(InsurancePackage p) {
+ requireNonNull(p);
+ packagesAdded.add(p);
+ }
+ }
+
+}
diff --git a/src/test/java/seedu/address/logic/commands/AddTagCommandTest.java b/src/test/java/seedu/address/logic/commands/AddTagCommandTest.java
new file mode 100644
index 00000000000..cf75aeb9d1d
--- /dev/null
+++ b/src/test/java/seedu/address/logic/commands/AddTagCommandTest.java
@@ -0,0 +1,103 @@
+package seedu.address.logic.commands;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_FRIEND;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND;
+import static seedu.address.testutil.Assert.assertThrows;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.commons.core.Messages;
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.ModelManager;
+import seedu.address.model.person.Person;
+import seedu.address.model.tag.Tag;
+import seedu.address.testutil.AddressBookBuilder;
+import seedu.address.testutil.PersonBuilder;
+
+public class AddTagCommandTest {
+ static final Tag TAG_1 = VALID_TAG_FRIEND.get(0);
+ static final Tag TAG_2 = VALID_TAG_HUSBAND.get(0);
+
+ @Test
+ public void constructor_nullIndex_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> new AddTagCommand(null, TAG_1));
+ }
+
+ @Test
+ public void constructor_nullTag_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> new AddTagCommand(Index.fromOneBased(1), null));
+ }
+
+ @Test
+ public void execute_tagsAddedByPerson_addSuccessful() throws Exception {
+ Person person = new PersonBuilder().build();
+ AddressBookBuilder addressBookBuilder = new AddressBookBuilder().withPerson(person);
+ Index index = Index.fromOneBased(1);
+ Tag tagToBeAdded = TAG_2;
+
+ Person tagAddedPerson = new PersonBuilder().build();
+ ArrayList tagList = tagAddedPerson.getTags();
+ boolean wasAdded = tagList.add(tagToBeAdded);
+ tagAddedPerson.setTags(tagList);
+
+ ModelManager modelManager = new ModelManager();
+ modelManager.setAddressBook(addressBookBuilder.build());
+
+ CommandResult commandResult = new AddTagCommand(index, tagToBeAdded).execute(modelManager);
+
+ assertEquals(String.format(AddTagCommand.MESSAGE_SUCCESS, tagAddedPerson), commandResult.getFeedbackToUser());
+ }
+
+ @Test
+ public void execute_indexOutOfBounds_throwsException() throws Exception {
+ Index index = Index.fromOneBased(100);
+ Tag tagToBeAdded = TAG_1;
+ AddTagCommand addTagCommand = new AddTagCommand(index, tagToBeAdded);
+
+ Person person = new PersonBuilder().build();
+ AddressBookBuilder addressBookBuilder = new AddressBookBuilder().withPerson(person); // one person
+ ModelManager modelManager = new ModelManager();
+ modelManager.setAddressBook(addressBookBuilder.build());
+
+ assertThrows(CommandException.class, Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX, () ->
+ addTagCommand.execute(modelManager));
+ }
+
+ @Test
+ public void execute_duplicateTagAlreadyExists_throwsException() throws Exception {
+ ArrayList personTagList = new ArrayList<>(List.of(TAG_1, TAG_2));
+ Person person = new PersonBuilder().withTags(personTagList).build();
+ AddressBookBuilder addressBookBuilder = new AddressBookBuilder().withPerson(person);
+ Index index = Index.fromOneBased(1);
+
+ Tag alreadyPresentTag = TAG_2;
+
+ ModelManager modelManager = new ModelManager();
+ modelManager.setAddressBook(addressBookBuilder.build());
+
+ AddTagCommand addTagCommand = new AddTagCommand(index, alreadyPresentTag);
+
+ assertThrows(CommandException.class, AddTagCommand.MESSAGE_DUPLICATE_TAG, () ->
+ addTagCommand.execute(modelManager));
+ }
+
+ @Test
+ public void equals() {
+ Index index1 = Index.fromZeroBased(1);
+ Index index2 = Index.fromZeroBased(5);
+
+ assertEquals(new AddTagCommand(index1, TAG_1), new AddTagCommand(index1, TAG_1)); // same values
+
+ assertNotEquals(new AddTagCommand(index1, TAG_1), "1"); //different types
+ assertNotEquals(new AddTagCommand(index1, TAG_1),
+ new AddTagCommand(index2, TAG_1)); //different index
+ assertNotEquals(new AddTagCommand(index1, TAG_1),
+ new AddTagCommand(index1, TAG_2)); //different tag
+ }
+}
diff --git a/src/test/java/seedu/address/logic/commands/AddToClipboardCommandTest.java b/src/test/java/seedu/address/logic/commands/AddToClipboardCommandTest.java
new file mode 100644
index 00000000000..10b6cd126e4
--- /dev/null
+++ b/src/test/java/seedu/address/logic/commands/AddToClipboardCommandTest.java
@@ -0,0 +1,183 @@
+package seedu.address.logic.commands;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess;
+import static seedu.address.testutil.Assert.assertThrows;
+import static seedu.address.testutil.TypicalIndexes.INDEX_FALSE;
+import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON;
+import static seedu.address.testutil.TypicalPersons.ELLE;
+import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook;
+
+import java.awt.HeadlessException;
+import java.awt.Toolkit;
+import java.awt.datatransfer.Clipboard;
+import java.util.Arrays;
+import java.util.Collections;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.model.InsurancePackagesSet;
+import seedu.address.model.Model;
+import seedu.address.model.ModelManager;
+import seedu.address.model.UserPrefs;
+import seedu.address.model.person.Name;
+import seedu.address.model.person.Person;
+import seedu.address.model.person.predicates.NameExistsPredicate;
+
+/**
+ * Contains integration tests (interaction with the Model) for {@code AddToClipboardCommand}.
+ */
+class AddToClipboardCommandTest {
+ private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs(), new InsurancePackagesSet());
+ private Model expectedModel = new ModelManager(getTypicalAddressBook(), new UserPrefs(),
+ new InsurancePackagesSet());
+
+ /**
+ * Test for the equals() command in AddToClipboardCommand.java file.
+ */
+ @Test
+ public void equals() {
+ NameExistsPredicate firstPredicate =
+ new NameExistsPredicate(new Name("first"));
+ NameExistsPredicate secondPredicate =
+ new NameExistsPredicate(new Name("second"));
+
+ AddToClipboardCommand clipFirstCommand = new AddToClipboardCommand(firstPredicate);
+ AddToClipboardCommand clipSecondCommand = new AddToClipboardCommand(secondPredicate);
+
+ // same object -> returns true
+ assertTrue(clipFirstCommand.equals(clipFirstCommand));
+
+ // same values -> returns true
+ AddToClipboardCommand clipFirstCommandCopy = new AddToClipboardCommand(firstPredicate);
+ assertTrue(clipFirstCommand.equals(clipFirstCommandCopy));
+
+ // different types -> returns false
+ assertFalse(clipFirstCommand.equals(1));
+
+ // null -> returns false
+ assertFalse(clipFirstCommand.equals(null));
+
+ // different person -> returns false
+ assertFalse(clipFirstCommand.equals(clipSecondCommand));
+ }
+
+ /**
+ * Test for when an empty name (n/) field is entered.
+ * Should return the error message regarding naming constraints from the Name.java class.
+ */
+ @Test
+ public void execute_emptyNameInput() {
+ String expectedMessage = Name.MESSAGE_CONSTRAINTS;
+
+ assertThrows(IllegalArgumentException.class, expectedMessage, () -> preparePredicate(" "));
+ }
+
+ /**
+ * Test for when an invalid name (n/) field is entered.
+ * Should return the error message regarding naming constraints from the Name.java class.
+ */
+ @Test
+ public void execute_invalidNameInput() {
+ String expectedMessage = Name.MESSAGE_CONSTRAINTS;
+ assertThrows(IllegalArgumentException.class, expectedMessage, () -> preparePredicate("##"));
+ }
+
+ /**
+ * Test for when a person is found matching the given name keyword.
+ * Should return the success message from AddToClipboardCommand.java class.
+ */
+ @Test
+ void execute_personFound() throws HeadlessException {
+ try {
+ //Checks if the environment has a clipboard to copy to. If not, return immediately.
+ //This has to be implemented as automated testing on github does not have a "clipboard" to copy to,
+ //so this test will be skipped.
+ Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
+ } catch (HeadlessException e) {
+ return;
+ }
+
+ String expectedMessage = AddToClipboardCommand.MESSAGE_SUCCESS;
+ NameExistsPredicate predicate = preparePredicate("Elle Meyer");
+ AddToClipboardCommand command = new AddToClipboardCommand(predicate);
+ expectedModel.updateFilteredPersonList(predicate);
+ assertCommandSuccess(command, model, expectedMessage, expectedModel);
+ assertEquals(Arrays.asList(ELLE), model.getFilteredPersonList());
+ }
+
+ /**
+ * Test for when no person is found matching the given name keyword.
+ * Should return the failure message from AddToClipboardCommand.java class.
+ */
+ @Test
+ void execute_personNotFound() throws HeadlessException {
+ try {
+ //Checks if the environment has a clipboard to copy to. If not, return immediately.
+ //This has to be implemented as automated testing on github does not have a "clipboard" to copy to,
+ //so this test will be skipped.
+ Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
+ } catch (HeadlessException e) {
+ return;
+ }
+
+ String expectedMessage = AddToClipboardCommand.MESSAGE_FAILURE;
+ NameExistsPredicate predicate = preparePredicate("Not Elle Meyer");
+ AddToClipboardCommand command = new AddToClipboardCommand(predicate);
+ expectedModel.updateFilteredPersonList(predicate);
+ assertCommandSuccess(command, model, expectedMessage, expectedModel);
+ assertEquals(Collections.emptyList(), model.getFilteredPersonList());
+ }
+
+ /**
+ * Test for when a person is found at the given index.
+ * Should return the success message from AddToClipboardCommand.java class.
+ */
+ @Test
+ void execute_indexFound() throws HeadlessException {
+ try {
+ //Checks if the environment has a clipboard to copy to. If not, return immediately.
+ //This has to be implemented as automated testing on github does not have a "clipboard" to copy to,
+ //so this test will be skipped.
+ Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
+ } catch (HeadlessException e) {
+ return;
+ }
+
+ String expectedMessage = AddToClipboardCommand.MESSAGE_SUCCESS;
+ Person personToClip = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased());
+ AddToClipboardCommand command = new AddToClipboardCommand(INDEX_FIRST_PERSON);
+ expectedModel.updateFilteredPersonList(new NameExistsPredicate(personToClip.getName()));
+ assertCommandSuccess(command, model, expectedMessage, expectedModel);
+ assertEquals(Arrays.asList(personToClip), model.getFilteredPersonList());
+ }
+
+ /**
+ * Test for when a person is not found at the given index.
+ * Should return the failure message from AddToClipboardCommand.java class.
+ */
+ @Test
+ void execute_indexNotFound() throws HeadlessException {
+ try {
+ //Checks if the environment has a clipboard to copy to. If not, return immediately.
+ //This has to be implemented as automated testing on github does not have a "clipboard" to copy to,
+ //so this test will be skipped.
+ Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
+ } catch (HeadlessException e) {
+ return;
+ }
+
+ String expectedMessage = AddToClipboardCommand.MESSAGE_FAILURE;
+ AddToClipboardCommand command = new AddToClipboardCommand(INDEX_FALSE);
+ assertCommandSuccess(command, model, expectedMessage, expectedModel);
+ }
+
+ /**
+ * Parses {@code userInput} into a {@code NameExistsPredicate}.
+ */
+ private NameExistsPredicate preparePredicate(String userInput) {
+ return new NameExistsPredicate(new Name(userInput));
+ }
+}
diff --git a/src/test/java/seedu/address/logic/commands/ClearCommandTest.java b/src/test/java/seedu/address/logic/commands/ClearCommandTest.java
index 80d9110c03a..f82fcbcee57 100644
--- a/src/test/java/seedu/address/logic/commands/ClearCommandTest.java
+++ b/src/test/java/seedu/address/logic/commands/ClearCommandTest.java
@@ -6,6 +6,7 @@
import org.junit.jupiter.api.Test;
import seedu.address.model.AddressBook;
+import seedu.address.model.InsurancePackagesSet;
import seedu.address.model.Model;
import seedu.address.model.ModelManager;
import seedu.address.model.UserPrefs;
@@ -22,8 +23,8 @@ public void execute_emptyAddressBook_success() {
@Test
public void execute_nonEmptyAddressBook_success() {
- Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs());
- Model expectedModel = new ModelManager(getTypicalAddressBook(), new UserPrefs());
+ Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs(), new InsurancePackagesSet());
+ Model expectedModel = new ModelManager(getTypicalAddressBook(), new UserPrefs(), new InsurancePackagesSet());
expectedModel.setAddressBook(new AddressBook());
assertCommandSuccess(new ClearCommand(), model, ClearCommand.MESSAGE_SUCCESS, expectedModel);
diff --git a/src/test/java/seedu/address/logic/commands/CommandResultTest.java b/src/test/java/seedu/address/logic/commands/CommandResultTest.java
index 4f3eb46e9ef..0b810bcf2af 100644
--- a/src/test/java/seedu/address/logic/commands/CommandResultTest.java
+++ b/src/test/java/seedu/address/logic/commands/CommandResultTest.java
@@ -5,16 +5,21 @@
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
+import java.util.HashMap;
+
import org.junit.jupiter.api.Test;
public class CommandResultTest {
+
+ private final HashMap settings = new HashMap<>();
+
@Test
public void equals() {
CommandResult commandResult = new CommandResult("feedback");
// same values -> returns true
assertTrue(commandResult.equals(new CommandResult("feedback")));
- assertTrue(commandResult.equals(new CommandResult("feedback", false, false)));
+ assertTrue(commandResult.equals(new CommandResult("feedback", null)));
// same object -> returns true
assertTrue(commandResult.equals(commandResult));
@@ -29,10 +34,14 @@ public void equals() {
assertFalse(commandResult.equals(new CommandResult("different")));
// different showHelp value -> returns false
- assertFalse(commandResult.equals(new CommandResult("feedback", true, false)));
+ settings.put("showHelp", true);
+ settings.put("exit", false);
+ assertFalse(commandResult.equals(new CommandResult("feedback", settings)));
// different exit value -> returns false
- assertFalse(commandResult.equals(new CommandResult("feedback", false, true)));
+ settings.put("showHelp", false);
+ settings.put("exit", true);
+ assertFalse(commandResult.equals(new CommandResult("feedback", settings)));
}
@Test
@@ -46,9 +55,13 @@ public void hashcode() {
assertNotEquals(commandResult.hashCode(), new CommandResult("different").hashCode());
// different showHelp value -> returns different hashcode
- assertNotEquals(commandResult.hashCode(), new CommandResult("feedback", true, false).hashCode());
+ settings.put("showHelp", true);
+ settings.put("exit", false);
+ assertNotEquals(commandResult.hashCode(), new CommandResult("feedback", settings).hashCode());
// different exit value -> returns different hashcode
- assertNotEquals(commandResult.hashCode(), new CommandResult("feedback", false, true).hashCode());
+ settings.put("showHelp", false);
+ settings.put("exit", true);
+ assertNotEquals(commandResult.hashCode(), new CommandResult("feedback", settings).hashCode());
}
}
diff --git a/src/test/java/seedu/address/logic/commands/CommandTestUtil.java b/src/test/java/seedu/address/logic/commands/CommandTestUtil.java
index 643a1d08069..652c5000b56 100644
--- a/src/test/java/seedu/address/logic/commands/CommandTestUtil.java
+++ b/src/test/java/seedu/address/logic/commands/CommandTestUtil.java
@@ -4,6 +4,7 @@
import static org.junit.jupiter.api.Assertions.assertTrue;
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_INSURANCE_PACKAGE;
import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME;
import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE;
import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG;
@@ -17,8 +18,10 @@
import seedu.address.logic.commands.exceptions.CommandException;
import seedu.address.model.AddressBook;
import seedu.address.model.Model;
-import seedu.address.model.person.NameContainsKeywordsPredicate;
import seedu.address.model.person.Person;
+import seedu.address.model.person.predicates.NameContainsKeywordsPredicate;
+import seedu.address.model.tag.Priority;
+import seedu.address.model.tag.Tag;
import seedu.address.testutil.EditPersonDescriptorBuilder;
/**
@@ -28,14 +31,42 @@ public class CommandTestUtil {
public static final String VALID_NAME_AMY = "Amy Bee";
public static final String VALID_NAME_BOB = "Bob Choo";
+ public static final String VALID_MULTIPLE_NAME = "Alice Bob";
public static final String VALID_PHONE_AMY = "11111111";
public static final String VALID_PHONE_BOB = "22222222";
public static final String VALID_EMAIL_AMY = "amy@example.com";
public static final String VALID_EMAIL_BOB = "bob@example.com";
+ public static final String VALID_INSURANCE_PACKAGE_AMY = "Golden Plus";
+ public static final String VALID_INSURANCE_PACKAGE_BOB = "Silver Pro";
public static final String VALID_ADDRESS_AMY = "Block 312, Amy Street 1";
public static final String VALID_ADDRESS_BOB = "Block 123, Bobby Street 3";
- public static final String VALID_TAG_HUSBAND = "husband";
- public static final String VALID_TAG_FRIEND = "friend";
+
+ public static final ArrayList VALID_TAG_HUSBAND = new ArrayList(Arrays.asList(
+ new Tag("Husband", Priority.PRIORITY_3)));
+ public static final String VALID_TAG_COMMAND_HUSBAND = "Husband :p3"; // change when needed
+ public static final ArrayList VALID_TAG_FRIEND = new ArrayList(Arrays.asList(
+ new Tag("Friend", Priority.PRIORITY_4)));
+ public static final String VALID_TAG_COMMAND_FRIEND = "Friend :p4";
+ public static final ArrayList VALID_MULTIPLE_TAGS = new ArrayList(Arrays.asList(
+ new Tag("Husband", Priority.PRIORITY_3), new Tag("Friend", Priority.PRIORITY_4)));
+ public static final ArrayList VALID_DIFFERENT_PRIO_ONE_TAG = new ArrayList(Arrays.asList(
+ new Tag("Friend", Priority.PRIORITY_2), new Tag("Friend", Priority.PRIORITY_4)));
+ public static final ArrayList VALID_DIFFERENT_PRIO_MULTI_TAG = new ArrayList(Arrays.asList(
+ new Tag("Husband", Priority.PRIORITY_2), new Tag("Husband", Priority.PRIORITY_3),
+ new Tag("Friend", Priority.PRIORITY_2), new Tag("Friend", Priority.PRIORITY_3)));
+ public static final ArrayList VALID_NO_TAGS = new ArrayList();
+ public static final ArrayList VALID_TAG_NULL_PRIO = new ArrayList(Arrays.asList(
+ new Tag("Null Priority", null)));
+
+ public static final String NAME_FIND_ALICE_BOB = " " + PREFIX_NAME + "Alice Bob";
+ public static final String NAME_FIND_ALICE_BOB_WHITESPACE = " " + PREFIX_NAME + " \n Alice \n \t Bob \t";
+ public static final String MULTIPLE_FIELDS_FIND = " " + PREFIX_NAME + "Alice Bob"
+ + " " + PREFIX_ADDRESS + "Jurong Clementi";
+ public static final String MULTIPLE_FIELDS_FIND_WHITESPACE = " " + PREFIX_NAME + "\n Alice \n \t Bob \t"
+ + " " + PREFIX_ADDRESS + " \n Jurong \n \t Clementi \t";
+
+ public static final List VALID_MULTIPLE_NAME_KEYWORDS = Arrays.asList("Alice", "Bob");
+ public static final List VALID_MULTIPLE_ADDRESS_KEYWORDS = Arrays.asList("Jurong", "Clementi");
public static final String NAME_DESC_AMY = " " + PREFIX_NAME + VALID_NAME_AMY;
public static final String NAME_DESC_BOB = " " + PREFIX_NAME + VALID_NAME_BOB;
@@ -43,16 +74,31 @@ public class CommandTestUtil {
public static final String PHONE_DESC_BOB = " " + PREFIX_PHONE + VALID_PHONE_BOB;
public static final String EMAIL_DESC_AMY = " " + PREFIX_EMAIL + VALID_EMAIL_AMY;
public static final String EMAIL_DESC_BOB = " " + PREFIX_EMAIL + VALID_EMAIL_BOB;
+ public static final String INSURANCE_PACKAGE_DESC_AMY =
+ " " + PREFIX_INSURANCE_PACKAGE + VALID_INSURANCE_PACKAGE_AMY;
+ public static final String INSURANCE_PACKAGE_DESC_BOB =
+ " " + PREFIX_INSURANCE_PACKAGE + VALID_INSURANCE_PACKAGE_BOB;
public static final String ADDRESS_DESC_AMY = " " + PREFIX_ADDRESS + VALID_ADDRESS_AMY;
public static final String ADDRESS_DESC_BOB = " " + PREFIX_ADDRESS + VALID_ADDRESS_BOB;
- public static final String TAG_DESC_FRIEND = " " + PREFIX_TAG + VALID_TAG_FRIEND;
- public static final String TAG_DESC_HUSBAND = " " + PREFIX_TAG + VALID_TAG_HUSBAND;
+ public static final String TAG_DESC_FRIEND = " " + PREFIX_TAG + VALID_TAG_COMMAND_FRIEND;
+ public static final String TAG_DESC_HUSBAND = " " + PREFIX_TAG + VALID_TAG_COMMAND_HUSBAND;
public static final String INVALID_NAME_DESC = " " + PREFIX_NAME + "James&"; // '&' not allowed in names
public static final String INVALID_PHONE_DESC = " " + PREFIX_PHONE + "911a"; // 'a' not allowed in phones
public static final String INVALID_EMAIL_DESC = " " + PREFIX_EMAIL + "bob!yahoo"; // missing '@' symbol
+ public static final String INVALID_EMAIL_DOMAIN_NO_PERIOD = " " + PREFIX_EMAIL + "bob@yahoo"; // missing one period
+ public static final String INVALID_EMAIL_DOMAIN_MANY_PERIOD = " "
+ + PREFIX_EMAIL + "bob@yahoo.com.sg"; // more than one period
+
+ public static final String INVALID_INSURANCE_PACKAGE_DESC =
+ " " + PREFIX_INSURANCE_PACKAGE; // empty string not allowed for insurance packages
public static final String INVALID_ADDRESS_DESC = " " + PREFIX_ADDRESS; // empty string not allowed for addresses
- public static final String INVALID_TAG_DESC = " " + PREFIX_TAG + "hubby*"; // '*' not allowed in tags
+ public static final String INVALID_TAG_COMMAND = "hubby*"; // '*' not allowed in tags
+ public static final String INVALID_TAG_DESC = " " + PREFIX_TAG + INVALID_TAG_COMMAND;
+
+ public static final String INVALID_EMPTY_FIND_ARG = " ";
+ public static final String INVALID_SINGLE_EMPTY_FIND_FIELD = " n/";
+ public static final String INVALID_MULTIPLE_EMPTY_FIND_FIELD = " n/ i/ a/";
public static final String PREAMBLE_WHITESPACE = "\t \r \n";
public static final String PREAMBLE_NON_EMPTY = "NonEmptyPreamble";
@@ -62,11 +108,13 @@ public class CommandTestUtil {
static {
DESC_AMY = new EditPersonDescriptorBuilder().withName(VALID_NAME_AMY)
- .withPhone(VALID_PHONE_AMY).withEmail(VALID_EMAIL_AMY).withAddress(VALID_ADDRESS_AMY)
+ .withPhone(VALID_PHONE_AMY).withEmail(VALID_EMAIL_AMY)
+ .withInsurancePackage(VALID_INSURANCE_PACKAGE_AMY).withAddress(VALID_ADDRESS_AMY)
.withTags(VALID_TAG_FRIEND).build();
DESC_BOB = new EditPersonDescriptorBuilder().withName(VALID_NAME_BOB)
- .withPhone(VALID_PHONE_BOB).withEmail(VALID_EMAIL_BOB).withAddress(VALID_ADDRESS_BOB)
- .withTags(VALID_TAG_HUSBAND, VALID_TAG_FRIEND).build();
+ .withPhone(VALID_PHONE_BOB).withEmail(VALID_EMAIL_BOB)
+ .withInsurancePackage(VALID_INSURANCE_PACKAGE_BOB).withAddress(VALID_ADDRESS_BOB)
+ .withTags(VALID_TAG_HUSBAND).build();
}
/**
diff --git a/src/test/java/seedu/address/logic/commands/DeleteCommandTest.java b/src/test/java/seedu/address/logic/commands/DeleteCommandTest.java
index 45a8c910ba1..ef3e617496a 100644
--- a/src/test/java/seedu/address/logic/commands/DeleteCommandTest.java
+++ b/src/test/java/seedu/address/logic/commands/DeleteCommandTest.java
@@ -13,6 +13,7 @@
import seedu.address.commons.core.Messages;
import seedu.address.commons.core.index.Index;
+import seedu.address.model.InsurancePackagesSet;
import seedu.address.model.Model;
import seedu.address.model.ModelManager;
import seedu.address.model.UserPrefs;
@@ -24,7 +25,7 @@
*/
public class DeleteCommandTest {
- private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs());
+ private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs(), new InsurancePackagesSet());
@Test
public void execute_validIndexUnfilteredList_success() {
@@ -33,7 +34,8 @@ public void execute_validIndexUnfilteredList_success() {
String expectedMessage = String.format(DeleteCommand.MESSAGE_DELETE_PERSON_SUCCESS, personToDelete);
- ModelManager expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs());
+ ModelManager expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs(),
+ new InsurancePackagesSet());
expectedModel.deletePerson(personToDelete);
assertCommandSuccess(deleteCommand, model, expectedMessage, expectedModel);
@@ -56,7 +58,7 @@ public void execute_validIndexFilteredList_success() {
String expectedMessage = String.format(DeleteCommand.MESSAGE_DELETE_PERSON_SUCCESS, personToDelete);
- Model expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs());
+ Model expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs(), new InsurancePackagesSet());
expectedModel.deletePerson(personToDelete);
showNoPerson(expectedModel);
diff --git a/src/test/java/seedu/address/logic/commands/DeletePackageCommandTest.java b/src/test/java/seedu/address/logic/commands/DeletePackageCommandTest.java
new file mode 100644
index 00000000000..842b23b0f09
--- /dev/null
+++ b/src/test/java/seedu/address/logic/commands/DeletePackageCommandTest.java
@@ -0,0 +1,41 @@
+package seedu.address.logic.commands;
+
+import static org.junit.jupiter.api.Assertions.fail;
+import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.model.InsurancePackagesSet;
+import seedu.address.model.Model;
+import seedu.address.model.ModelManager;
+import seedu.address.model.UserPrefs;
+import seedu.address.model.person.InsurancePackage;
+
+public class DeletePackageCommandTest {
+
+ private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs(), new InsurancePackagesSet());
+ private InsurancePackage validPackage = new InsurancePackage("TestName", "TestDesc");
+ private InsurancePackage validPackageTwo = new InsurancePackage("TestNameThisWillNotExistInside",
+ "TestDesc");
+
+ @Test
+ public void deleteExistingPackage_success() {
+ model.addInsurancePackage(validPackage);
+ try {
+ model.deleteInsurancePackage(validPackage);
+ assert !model.hasInsurancePackage(validPackage);
+ } catch (Exception err) {
+ fail();
+ }
+ }
+
+ @Test
+ public void deleteNonExistingPackage_noExceptionThrown() {
+ assert !model.hasInsurancePackage(validPackageTwo);
+ try {
+ model.deleteInsurancePackage(validPackageTwo);
+ } catch (Exception err) {
+ fail();
+ }
+ }
+}
diff --git a/src/test/java/seedu/address/logic/commands/DeleteTagCommandTest.java b/src/test/java/seedu/address/logic/commands/DeleteTagCommandTest.java
new file mode 100644
index 00000000000..d204e177da5
--- /dev/null
+++ b/src/test/java/seedu/address/logic/commands/DeleteTagCommandTest.java
@@ -0,0 +1,101 @@
+package seedu.address.logic.commands;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_FRIEND;
+import static seedu.address.testutil.Assert.assertThrows;
+
+import java.util.ArrayList;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.commons.core.Messages;
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.ModelManager;
+import seedu.address.model.person.Person;
+import seedu.address.testutil.AddressBookBuilder;
+import seedu.address.testutil.PersonBuilder;
+
+public class DeleteTagCommandTest {
+
+ @Test
+ public void constructor_nullIndex_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> new DeleteTagCommand(null, 1));
+ }
+
+ @Test
+ public void execute_tagDeletedByPerson_deleteSuccessful() throws Exception {
+ Person person = new PersonBuilder().withTags(VALID_TAG_FRIEND).build();
+ AddressBookBuilder addressBookBuilder = new AddressBookBuilder().withPerson(person);
+ Index index = Index.fromOneBased(1);
+
+ Person tagDeletedPerson = new PersonBuilder().withTags(new ArrayList<>()).build();
+
+ ModelManager modelManager = new ModelManager();
+ modelManager.setAddressBook(addressBookBuilder.build());
+
+ CommandResult commandResult = new DeleteTagCommand(index, 1).execute(modelManager);
+
+ assertEquals(String.format(DeleteTagCommand.MESSAGE_SUCCESS, tagDeletedPerson),
+ commandResult.getFeedbackToUser());
+ }
+
+ @Test
+ public void execute_indexOutOfBounds_throwsException() throws Exception {
+ Index index = Index.fromOneBased(100);
+ DeleteTagCommand deleteTagCommand = new DeleteTagCommand(index, 1);
+
+ Person person = new PersonBuilder().build();
+ AddressBookBuilder addressBookBuilder = new AddressBookBuilder().withPerson(person); // one person
+ ModelManager modelManager = new ModelManager();
+ modelManager.setAddressBook(addressBookBuilder.build());
+
+ assertThrows(CommandException.class, Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX, () ->
+ deleteTagCommand.execute(modelManager));
+ }
+
+ @Test
+ public void execute_tagNumberNotPositive_throwsException() throws Exception {
+ Index index = Index.fromOneBased(1);
+ DeleteTagCommand deleteTagCommand = new DeleteTagCommand(index, 0);
+
+ Person person = new PersonBuilder().build();
+ AddressBookBuilder addressBookBuilder = new AddressBookBuilder().withPerson(person); // one person
+ ModelManager modelManager = new ModelManager();
+ modelManager.setAddressBook(addressBookBuilder.build());
+
+ assertThrows(CommandException.class, Messages.MESSAGE_INVALID_TAG_NUMBER, () ->
+ deleteTagCommand.execute(modelManager));
+ }
+
+ @Test
+ public void execute_tagNumberOutOfBounds_throwsException() throws Exception {
+ Index index = Index.fromOneBased(1);
+ DeleteTagCommand deleteTagCommand = new DeleteTagCommand(index, 100);
+
+ Person person = new PersonBuilder().build();
+ AddressBookBuilder addressBookBuilder = new AddressBookBuilder().withPerson(person); // one person
+ ModelManager modelManager = new ModelManager();
+ modelManager.setAddressBook(addressBookBuilder.build());
+
+ assertThrows(CommandException.class, Messages.MESSAGE_INVALID_TAG_NUMBER, () ->
+ deleteTagCommand.execute(modelManager));
+ }
+
+ @Test
+ public void equals() {
+ Index index1 = Index.fromZeroBased(1);
+ Index index2 = Index.fromZeroBased(5);
+ int tagNumber1 = 3;
+ int tagNumber2 = 5;
+
+ assertEquals(new DeleteTagCommand(index1, tagNumber1), new DeleteTagCommand(index1, tagNumber1)); // same values
+
+ assertNotEquals(new DeleteTagCommand(index1, tagNumber1), "1"); //different types
+ assertNotEquals(new DeleteTagCommand(index1, tagNumber1),
+ new DeleteTagCommand(index2, tagNumber1)); //different index
+ assertNotEquals(new DeleteTagCommand(index1, tagNumber1),
+ new DeleteTagCommand(index1, tagNumber2)); //different tag number
+ }
+}
diff --git a/src/test/java/seedu/address/logic/commands/EditCommandTest.java b/src/test/java/seedu/address/logic/commands/EditCommandTest.java
index 214c6c2507b..964fda6f33f 100644
--- a/src/test/java/seedu/address/logic/commands/EditCommandTest.java
+++ b/src/test/java/seedu/address/logic/commands/EditCommandTest.java
@@ -20,6 +20,7 @@
import seedu.address.commons.core.index.Index;
import seedu.address.logic.commands.EditCommand.EditPersonDescriptor;
import seedu.address.model.AddressBook;
+import seedu.address.model.InsurancePackagesSet;
import seedu.address.model.Model;
import seedu.address.model.ModelManager;
import seedu.address.model.UserPrefs;
@@ -32,7 +33,7 @@
*/
public class EditCommandTest {
- private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs());
+ private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs(), new InsurancePackagesSet());
@Test
public void execute_allFieldsSpecifiedUnfilteredList_success() {
@@ -42,7 +43,8 @@ public void execute_allFieldsSpecifiedUnfilteredList_success() {
String expectedMessage = String.format(EditCommand.MESSAGE_EDIT_PERSON_SUCCESS, editedPerson);
- Model expectedModel = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs());
+ Model expectedModel = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs(),
+ new InsurancePackagesSet());
expectedModel.setPerson(model.getFilteredPersonList().get(0), editedPerson);
assertCommandSuccess(editCommand, model, expectedMessage, expectedModel);
@@ -63,7 +65,8 @@ public void execute_someFieldsSpecifiedUnfilteredList_success() {
String expectedMessage = String.format(EditCommand.MESSAGE_EDIT_PERSON_SUCCESS, editedPerson);
- Model expectedModel = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs());
+ Model expectedModel = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs(),
+ new InsurancePackagesSet());
expectedModel.setPerson(lastPerson, editedPerson);
assertCommandSuccess(editCommand, model, expectedMessage, expectedModel);
@@ -76,7 +79,8 @@ public void execute_noFieldSpecifiedUnfilteredList_success() {
String expectedMessage = String.format(EditCommand.MESSAGE_EDIT_PERSON_SUCCESS, editedPerson);
- Model expectedModel = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs());
+ Model expectedModel = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs(),
+ new InsurancePackagesSet());
assertCommandSuccess(editCommand, model, expectedMessage, expectedModel);
}
@@ -92,7 +96,8 @@ public void execute_filteredList_success() {
String expectedMessage = String.format(EditCommand.MESSAGE_EDIT_PERSON_SUCCESS, editedPerson);
- Model expectedModel = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs());
+ Model expectedModel = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs(),
+ new InsurancePackagesSet());
expectedModel.setPerson(model.getFilteredPersonList().get(0), editedPerson);
assertCommandSuccess(editCommand, model, expectedMessage, expectedModel);
diff --git a/src/test/java/seedu/address/logic/commands/EditPackageCommandTest.java b/src/test/java/seedu/address/logic/commands/EditPackageCommandTest.java
new file mode 100644
index 00000000000..7f1f8900fe3
--- /dev/null
+++ b/src/test/java/seedu/address/logic/commands/EditPackageCommandTest.java
@@ -0,0 +1,211 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.nio.file.Path;
+import java.util.function.Predicate;
+
+import org.junit.jupiter.api.Test;
+
+import javafx.collections.ObservableList;
+import seedu.address.commons.core.GuiSettings;
+import seedu.address.model.InsurancePackagesSet;
+import seedu.address.model.Model;
+import seedu.address.model.ReadOnlyAddressBook;
+import seedu.address.model.ReadOnlyUserPrefs;
+import seedu.address.model.person.InsurancePackage;
+import seedu.address.model.person.Person;
+
+public class EditPackageCommandTest {
+
+ @Test
+ public void execute_packageAcceptedByModel_editSuccessful() throws Exception {
+
+ String packageName = "Test Package Name";
+ String oldDesc = "Test Desc";
+ String newDesc = "Test Desc 2";
+
+ InsurancePackage validPackage = new InsurancePackage(packageName, oldDesc);
+
+ // edit package in model stub
+ EditPackageCommandTest.ModelStubWithPackage modelStub =
+ new EditPackageCommandTest.ModelStubWithPackage(validPackage);
+ modelStub.setInsurancePackage(packageName, newDesc);
+
+ // edit package
+ validPackage.setPackageDescription(newDesc);
+
+ CommandResult commandResult = new EditPackageCommand(packageName, newDesc).execute(modelStub);
+
+ assertEquals(String.format(EditPackageCommand.MESSAGE_EDIT_PACKAGE_SUCCESS, validPackage),
+ commandResult.getFeedbackToUser());
+
+ assertEquals(validPackage, modelStub.p);
+
+ assertEquals(newDesc, modelStub.getPackageDesc());
+ }
+
+ /**
+ * A default model stub that has all of the methods failing.
+ */
+ private class ModelStub implements Model {
+ @Override
+ public void setUserPrefs(ReadOnlyUserPrefs userPrefs) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public ReadOnlyUserPrefs getUserPrefs() {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public GuiSettings getGuiSettings() {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void setGuiSettings(GuiSettings guiSettings) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public Path getAddressBookFilePath() {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void setAddressBookFilePath(Path addressBookFilePath) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public Path getInsurancePackagesFilePath() {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void setInsurancePackagesFilePath(Path insurancePackagesFilePath) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public InsurancePackagesSet getInsurancePackagesSet() {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void setInsurancePackagesSet(InsurancePackagesSet s) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public boolean hasInsurancePackage(InsurancePackage p) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void addInsurancePackage(InsurancePackage p) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void deleteInsurancePackage(InsurancePackage p) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void setInsurancePackage(String targetPackageName, String newPackageDesc) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void addPerson(Person person) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void setAddressBook(ReadOnlyAddressBook newData) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void resetAddressBook() {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public ReadOnlyAddressBook getAddressBook() {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public boolean hasPerson(Person person) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void deletePerson(Person target) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void setPerson(Person target, Person editedPerson) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public ObservableList getFilteredPersonList() {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void updateFilteredPersonList(Predicate predicate) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void sortByPriority() {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void undoCommand() {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void redoCommand() {
+ throw new AssertionError("This method should not be called.");
+ }
+ }
+
+ /**
+ * A Model stub that contains a single insurance package, with description editable.
+ */
+ private class ModelStubWithPackage extends EditPackageCommandTest.ModelStub {
+ private final InsurancePackage p;
+
+ ModelStubWithPackage(InsurancePackage p) {
+ requireNonNull(p);
+ this.p = p;
+ }
+
+ @Override
+ public boolean hasInsurancePackage(InsurancePackage p) {
+ return this.p.equals(p);
+ }
+
+ @Override
+ public void setInsurancePackage(String packageName, String newDesc) {
+ if (p.getPackageName().equals(packageName)) {
+ p.setPackageDescription(newDesc);
+ }
+ }
+
+ public String getPackageDesc() {
+ return p.getPackageDescription();
+ }
+ }
+}
diff --git a/src/test/java/seedu/address/logic/commands/EditTagCommandTest.java b/src/test/java/seedu/address/logic/commands/EditTagCommandTest.java
new file mode 100644
index 00000000000..a5c3dc7ef5f
--- /dev/null
+++ b/src/test/java/seedu/address/logic/commands/EditTagCommandTest.java
@@ -0,0 +1,140 @@
+package seedu.address.logic.commands;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_FRIEND;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND;
+import static seedu.address.testutil.Assert.assertThrows;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.commons.core.Messages;
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.ModelManager;
+import seedu.address.model.person.Person;
+import seedu.address.model.tag.Tag;
+import seedu.address.testutil.AddressBookBuilder;
+import seedu.address.testutil.PersonBuilder;
+
+public class EditTagCommandTest {
+ static final Tag TAG_1 = VALID_TAG_FRIEND.get(0);
+ static final Tag TAG_2 = VALID_TAG_HUSBAND.get(0);
+
+ @Test
+ public void constructor_nullIndex_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> new EditTagCommand(null,
+ 1, TAG_1));
+ }
+
+ @Test
+ public void constructor_nullTag_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> new EditTagCommand(Index.fromOneBased(1), 1, null));
+ }
+
+ @Test
+ public void execute_tagEditedByPerson_editSuccessful() throws Exception {
+ Person person = new PersonBuilder().withTags(new ArrayList<>(
+ List.of(TAG_1))).build();
+ AddressBookBuilder addressBookBuilder = new AddressBookBuilder().withPerson(person);
+ Index index = Index.fromOneBased(1);
+ Tag editedTag = TAG_2;
+
+ Person tagAddedPerson = new PersonBuilder().withTags(new ArrayList<>(
+ List.of(editedTag))).build();
+
+ ModelManager modelManager = new ModelManager();
+ modelManager.setAddressBook(addressBookBuilder.build());
+
+ CommandResult commandResult = new EditTagCommand(index, 1, editedTag).execute(modelManager);
+
+ assertEquals(String.format(EditTagCommand.MESSAGE_SUCCESS, tagAddedPerson), commandResult.getFeedbackToUser());
+ }
+
+ @Test
+ public void execute_indexOutOfBounds_throwsException() throws Exception {
+ Index index = Index.fromOneBased(100);
+ Tag editedTag = TAG_2;
+ EditTagCommand editTagCommand = new EditTagCommand(index, 1, editedTag);
+
+ Person person = new PersonBuilder().build();
+ AddressBookBuilder addressBookBuilder = new AddressBookBuilder().withPerson(person); // one person
+ ModelManager modelManager = new ModelManager();
+ modelManager.setAddressBook(addressBookBuilder.build());
+
+ assertThrows(CommandException.class, Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX, () ->
+ editTagCommand.execute(modelManager));
+ }
+
+ @Test
+ public void execute_tagNumberNotPositive_throwsException() throws Exception {
+ Index index = Index.fromOneBased(1);
+ Tag editedTag = TAG_2;
+ EditTagCommand editTagCommand = new EditTagCommand(index, 0, editedTag);
+
+ Person person = new PersonBuilder().build();
+ AddressBookBuilder addressBookBuilder = new AddressBookBuilder().withPerson(person); // one person
+ ModelManager modelManager = new ModelManager();
+ modelManager.setAddressBook(addressBookBuilder.build());
+
+ assertThrows(CommandException.class, Messages.MESSAGE_INVALID_TAG_NUMBER, () ->
+ editTagCommand.execute(modelManager));
+ }
+
+ @Test
+ public void execute_tagNumberOutOfBounds_throwsException() throws Exception {
+ Index index = Index.fromOneBased(1);
+ Tag editedTag = TAG_1;
+ EditTagCommand editTagCommand = new EditTagCommand(index, 100, editedTag);
+
+ Person person = new PersonBuilder().build();
+ AddressBookBuilder addressBookBuilder = new AddressBookBuilder().withPerson(person); // one person
+ ModelManager modelManager = new ModelManager();
+ modelManager.setAddressBook(addressBookBuilder.build());
+
+ assertThrows(CommandException.class, Messages.MESSAGE_INVALID_TAG_NUMBER, () ->
+ editTagCommand.execute(modelManager));
+ }
+
+ @Test
+ public void execute_duplicateTagAlreadyExists_throwsException() throws Exception {
+ ArrayList personTagList = new ArrayList<>(List.of(TAG_1, TAG_2));
+ Person person = new PersonBuilder().withTags(personTagList).build();
+ AddressBookBuilder addressBookBuilder = new AddressBookBuilder().withPerson(person);
+ Index index = Index.fromOneBased(1);
+
+ ModelManager modelManager = new ModelManager();
+ modelManager.setAddressBook(addressBookBuilder.build());
+
+ Tag alreadyPresentTag = TAG_2;
+ EditTagCommand editTagCommand = new EditTagCommand(index, 1, alreadyPresentTag);
+
+ assertThrows(CommandException.class, EditTagCommand.MESSAGE_DUPLICATE_TAG, () ->
+ editTagCommand.execute(modelManager));
+ }
+
+ @Test
+ public void equals() {
+ Index index1 = Index.fromZeroBased(1);
+ Index index2 = Index.fromZeroBased(5);
+ int tagNumber1 = 3;
+ int tagNumber2 = 5;
+
+ Tag tag1 = this.TAG_1;
+ Tag tag2 = this.TAG_2;
+
+ assertEquals(new EditTagCommand(index1, tagNumber1, tag1),
+ new EditTagCommand(index1, tagNumber1, tag1)); // same values
+
+ assertNotEquals(new EditTagCommand(index1, tagNumber1, tag1), "1"); //different types
+ assertNotEquals(new EditTagCommand(index1, tagNumber1, tag1),
+ new EditTagCommand(index2, tagNumber1, tag1)); //different index
+ assertNotEquals(new EditTagCommand(index1, tagNumber1, tag1),
+ new EditTagCommand(index1, tagNumber2, tag1)); //different tag number
+ assertNotEquals(new EditTagCommand(index1, tagNumber1, tag1),
+ new EditTagCommand(index1, tagNumber1, tag2)); //different tag
+ }
+}
diff --git a/src/test/java/seedu/address/logic/commands/ExitCommandTest.java b/src/test/java/seedu/address/logic/commands/ExitCommandTest.java
index 9533c473875..00227834626 100644
--- a/src/test/java/seedu/address/logic/commands/ExitCommandTest.java
+++ b/src/test/java/seedu/address/logic/commands/ExitCommandTest.java
@@ -3,6 +3,8 @@
import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess;
import static seedu.address.logic.commands.ExitCommand.MESSAGE_EXIT_ACKNOWLEDGEMENT;
+import java.util.HashMap;
+
import org.junit.jupiter.api.Test;
import seedu.address.model.Model;
@@ -11,10 +13,12 @@
public class ExitCommandTest {
private Model model = new ModelManager();
private Model expectedModel = new ModelManager();
+ private final HashMap settings = new HashMap<>();
@Test
public void execute_exit_success() {
- CommandResult expectedCommandResult = new CommandResult(MESSAGE_EXIT_ACKNOWLEDGEMENT, false, true);
+ settings.put("exit", true);
+ CommandResult expectedCommandResult = new CommandResult(MESSAGE_EXIT_ACKNOWLEDGEMENT, settings);
assertCommandSuccess(new ExitCommand(), model, expectedCommandResult, expectedModel);
}
}
diff --git a/src/test/java/seedu/address/logic/commands/ExportToCsvCommandTest.java b/src/test/java/seedu/address/logic/commands/ExportToCsvCommandTest.java
new file mode 100644
index 00000000000..4f83ddffa63
--- /dev/null
+++ b/src/test/java/seedu/address/logic/commands/ExportToCsvCommandTest.java
@@ -0,0 +1,24 @@
+package seedu.address.logic.commands;
+
+import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess;
+import static seedu.address.logic.commands.ExportToCsvCommand.MESSAGE_SUCCESS;
+
+import java.util.HashMap;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.model.Model;
+import seedu.address.model.ModelManager;
+
+public class ExportToCsvCommandTest {
+ private Model model = new ModelManager();
+ private Model expectedModel = new ModelManager();
+ private final HashMap settings = new HashMap<>();
+
+ @Test
+ public void execute_exit_success() {
+ settings.put("exportToCsv", true);
+ CommandResult expectedCommandResult = new CommandResult(MESSAGE_SUCCESS, settings);
+ assertCommandSuccess(new ExportToCsvCommand(), model, expectedCommandResult, expectedModel);
+ }
+}
diff --git a/src/test/java/seedu/address/logic/commands/FindCommandTest.java b/src/test/java/seedu/address/logic/commands/FindCommandTest.java
index 9b15db28bbb..369004b4b0e 100644
--- a/src/test/java/seedu/address/logic/commands/FindCommandTest.java
+++ b/src/test/java/seedu/address/logic/commands/FindCommandTest.java
@@ -12,36 +12,89 @@
import java.util.Arrays;
import java.util.Collections;
+import java.util.List;
import org.junit.jupiter.api.Test;
+import seedu.address.model.InsurancePackagesSet;
import seedu.address.model.Model;
import seedu.address.model.ModelManager;
import seedu.address.model.UserPrefs;
-import seedu.address.model.person.NameContainsKeywordsPredicate;
+import seedu.address.model.person.predicates.CombineContainsKeywordsPredicate;
+import seedu.address.model.person.predicates.FieldContainsKeywordsPredicate;
+import seedu.address.model.person.predicates.NameContainsKeywordsPredicate;
+import seedu.address.model.person.predicates.PhoneContainsKeywordsPredicate;
+import seedu.address.testutil.PredicatesListBuilder;
/**
* Contains integration tests (interaction with the Model) for {@code FindCommand}.
*/
public class FindCommandTest {
- private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs());
- private Model expectedModel = new ModelManager(getTypicalAddressBook(), new UserPrefs());
+ private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs(), new InsurancePackagesSet());
+ private Model expectedModel = new ModelManager(getTypicalAddressBook(), new UserPrefs(),
+ new InsurancePackagesSet());
@Test
- public void equals() {
+ public void oneFieldEquals() {
NameContainsKeywordsPredicate firstPredicate =
new NameContainsKeywordsPredicate(Collections.singletonList("first"));
+ CombineContainsKeywordsPredicate firstCombinePredicate = new CombineContainsKeywordsPredicate(
+ new PredicatesListBuilder().addNamePredicate(firstPredicate).build());
NameContainsKeywordsPredicate secondPredicate =
new NameContainsKeywordsPredicate(Collections.singletonList("second"));
+ CombineContainsKeywordsPredicate secondCombinePredicate = new CombineContainsKeywordsPredicate(
+ new PredicatesListBuilder().addNamePredicate(secondPredicate).build());
- FindCommand findFirstCommand = new FindCommand(firstPredicate);
- FindCommand findSecondCommand = new FindCommand(secondPredicate);
+ FindCommand findFirstCommand = new FindCommand(firstCombinePredicate);
+ FindCommand findSecondCommand = new FindCommand(secondCombinePredicate);
// same object -> returns true
assertTrue(findFirstCommand.equals(findFirstCommand));
// same values -> returns true
- FindCommand findFirstCommandCopy = new FindCommand(firstPredicate);
+ FindCommand findFirstCommandCopy = new FindCommand(firstCombinePredicate);
+ assertTrue(findFirstCommand.equals(findFirstCommandCopy));
+
+ // different types -> returns false
+ assertFalse(findFirstCommand.equals(1));
+
+ // null -> returns false
+ assertFalse(findFirstCommand.equals(null));
+
+ // different person -> returns false
+ assertFalse(findFirstCommand.equals(findSecondCommand));
+ }
+
+ @Test
+ public void multipleFieldsEquals() {
+ NameContainsKeywordsPredicate firstNamePredicate =
+ new NameContainsKeywordsPredicate(Collections.singletonList("first"));
+ PhoneContainsKeywordsPredicate firstPhonePredicate =
+ new PhoneContainsKeywordsPredicate(Collections.singletonList("88888888"));
+ CombineContainsKeywordsPredicate firstCombinePredicate = new CombineContainsKeywordsPredicate(
+ new PredicatesListBuilder()
+ .addNamePredicate(firstNamePredicate)
+ .addPhonePredicate(firstPhonePredicate)
+ .build());
+
+ NameContainsKeywordsPredicate secondNamePredicate =
+ new NameContainsKeywordsPredicate(Collections.singletonList("second"));
+ PhoneContainsKeywordsPredicate secondPhonePredicate =
+ new PhoneContainsKeywordsPredicate(Collections.singletonList("77777777"));
+ CombineContainsKeywordsPredicate secondCombinePredicate = new CombineContainsKeywordsPredicate(
+ new PredicatesListBuilder()
+ .addNamePredicate(secondNamePredicate)
+ .addPhonePredicate(secondPhonePredicate)
+ .build());
+
+ FindCommand findFirstCommand = new FindCommand(firstCombinePredicate);
+ FindCommand findSecondCommand = new FindCommand(secondCombinePredicate);
+
+ // same object -> returns true
+ assertTrue(findFirstCommand.equals(findFirstCommand));
+
+ // same values -> returns true
+ FindCommand findFirstCommandCopy = new FindCommand(firstCombinePredicate);
assertTrue(findFirstCommand.equals(findFirstCommandCopy));
// different types -> returns false
@@ -56,28 +109,111 @@ public void equals() {
@Test
public void execute_zeroKeywords_noPersonFound() {
+ PredicatesListBuilder predicatesListBuilder = new PredicatesListBuilder();
+ String expectedMessage = String.format(MESSAGE_PERSONS_LISTED_OVERVIEW, 0);
+ NameContainsKeywordsPredicate namePredicate = prepareNamePredicate(" ");
+ List predicatesList =
+ predicatesListBuilder.addNamePredicate(namePredicate).build();
+ CombineContainsKeywordsPredicate combinePredicate = new CombineContainsKeywordsPredicate(predicatesList);
+ FindCommand command = new FindCommand(combinePredicate);
+ expectedModel.updateFilteredPersonList(combinePredicate);
+ assertCommandSuccess(command, model, expectedMessage, expectedModel);
+ assertEquals(Collections.emptyList(), model.getFilteredPersonList());
+ }
+
+ @Test
+ public void execute_zeroKeywords_multipleFieldsNoPersonFound() {
+ PredicatesListBuilder predicatesListBuilder = new PredicatesListBuilder();
String expectedMessage = String.format(MESSAGE_PERSONS_LISTED_OVERVIEW, 0);
- NameContainsKeywordsPredicate predicate = preparePredicate(" ");
- FindCommand command = new FindCommand(predicate);
- expectedModel.updateFilteredPersonList(predicate);
+ NameContainsKeywordsPredicate namePredicate = prepareNamePredicate(" ");
+ PhoneContainsKeywordsPredicate phonePredicate = preparePhonePredicate(" ");
+ List predicatesList =
+ predicatesListBuilder
+ .addNamePredicate(namePredicate)
+ .addPhonePredicate(phonePredicate)
+ .build();
+ CombineContainsKeywordsPredicate combinePredicate = new CombineContainsKeywordsPredicate(predicatesList);
+ FindCommand command = new FindCommand(combinePredicate);
+ expectedModel.updateFilteredPersonList(combinePredicate);
assertCommandSuccess(command, model, expectedMessage, expectedModel);
assertEquals(Collections.emptyList(), model.getFilteredPersonList());
}
+ @Test
+ public void execute_oneKeyword_onePersonFound() {
+ PredicatesListBuilder predicatesListBuilder = new PredicatesListBuilder();
+ String expectedMessage = String.format(MESSAGE_PERSONS_LISTED_OVERVIEW, 1);
+ NameContainsKeywordsPredicate namePredicate = prepareNamePredicate("Kurz");
+ List predicatesList =
+ predicatesListBuilder.addNamePredicate(namePredicate).build();
+ CombineContainsKeywordsPredicate combinePredicate = new CombineContainsKeywordsPredicate(predicatesList);
+ FindCommand command = new FindCommand(combinePredicate);
+ expectedModel.updateFilteredPersonList(combinePredicate);
+ assertCommandSuccess(command, model, expectedMessage, expectedModel);
+ assertEquals(Arrays.asList(CARL), model.getFilteredPersonList());
+ }
+
+ @Test
+ public void execute_oneKeyword_multipleFieldsOnePersonFound() {
+ PredicatesListBuilder predicatesListBuilder = new PredicatesListBuilder();
+ String expectedMessage = String.format(MESSAGE_PERSONS_LISTED_OVERVIEW, 1);
+ NameContainsKeywordsPredicate namePredicate = prepareNamePredicate("Kurz");
+ PhoneContainsKeywordsPredicate phonePredicate = preparePhonePredicate("95352563");
+ List predicatesList =
+ predicatesListBuilder
+ .addNamePredicate(namePredicate)
+ .addPhonePredicate(phonePredicate)
+ .build();
+ CombineContainsKeywordsPredicate combinePredicate = new CombineContainsKeywordsPredicate(predicatesList);
+ FindCommand command = new FindCommand(combinePredicate);
+ expectedModel.updateFilteredPersonList(combinePredicate);
+ assertCommandSuccess(command, model, expectedMessage, expectedModel);
+ assertEquals(Arrays.asList(CARL), model.getFilteredPersonList());
+ }
+
@Test
public void execute_multipleKeywords_multiplePersonsFound() {
+ PredicatesListBuilder predicatesListBuilder = new PredicatesListBuilder();
String expectedMessage = String.format(MESSAGE_PERSONS_LISTED_OVERVIEW, 3);
- NameContainsKeywordsPredicate predicate = preparePredicate("Kurz Elle Kunz");
- FindCommand command = new FindCommand(predicate);
- expectedModel.updateFilteredPersonList(predicate);
+ NameContainsKeywordsPredicate namePredicate = prepareNamePredicate("Kurz Elle Kunz");
+ List