diff --git a/README.adoc b/README.adoc index 03eff3a4d191..74e812ff3eab 100644 --- a/README.adoc +++ b/README.adoc @@ -1,11 +1,9 @@ -= Address Book (Level 4) += TuitionCor ifdef::env-github,env-browser[:relfileprefix: docs/] -https://travis-ci.org/se-edu/addressbook-level4[image:https://travis-ci.org/se-edu/addressbook-level4.svg?branch=master[Build Status]] -https://ci.appveyor.com/project/damithc/addressbook-level4[image:https://ci.appveyor.com/api/projects/status/3boko2x2vr5cc3w2?svg=true[Build status]] -https://coveralls.io/github/se-edu/addressbook-level4?branch=master[image:https://coveralls.io/repos/github/se-edu/addressbook-level4/badge.svg?branch=master[Coverage Status]] -https://www.codacy.com/app/damith/addressbook-level4?utm_source=github.com&utm_medium=referral&utm_content=se-edu/addressbook-level4&utm_campaign=Badge_Grade[image:https://api.codacy.com/project/badge/Grade/fc0b7775cf7f4fdeaf08776f3d8e364a[Codacy Badge]] -https://gitter.im/se-edu/Lobby[image:https://badges.gitter.im/se-edu/Lobby.svg[Gitter chat]] + +https://travis-ci.org/CS2103JAN2018-F11-B2/main[image:https://travis-ci.org/CS2103JAN2018-F11-B2/main.svg?branch=master[Build Status]] + ifdef::env-github[] image::docs/images/Ui.png[width="600"] @@ -15,19 +13,22 @@ ifndef::env-github[] image::images/Ui.png[width="600"] endif::[] -* This is a desktop Address Book application. It has a GUI but most of the user interactions happen using a CLI (Command Line Interface). -* It is a Java sample application intended for students learning Software Engineering while using Java as the main programming language. -* It is *written in OOP fashion*. It provides a *reasonably well-written* code example that is *significantly bigger* (around 6 KLoC)than what students usually write in beginner-level SE modules. -* What's different from https://github.com/se-edu/addressbook-level3[level 3]: -** A more sophisticated GUI that includes a list panel and an in-built Browser. -** More test cases, including automated GUI testing. -** Support for _Build Automation_ using Gradle and for _Continuous Integration_ using Travis CI. +* This is a desktop TuitionCor application. It has a GUI but most of the user interactions happen using a CLI (Command Line Interface). +* TuitionCor is targeted at tuition coordinators who have to manage a large amount of contacts. +* The daily job-scope of a tuition coordinator involves the need to manage large amount of contacts and match the students to tutors according to their credentials, needs and location. +* Therefore, TuitionCor aims to facilitate this process and make the job of a tuition coordinator easier. +* In addition, users are also able to +** Add clients +** Match students to tutors or vice versa +** Sort clients based on a particular aspect +** Find clients based on keywords +** Close assigned clients +** Restore closed clients == Site Map * <> * <> -* <> * <> * <> diff --git a/collated/functional/Zhu-Jiahui.md b/collated/functional/Zhu-Jiahui.md new file mode 100644 index 000000000000..b6e6f694882a --- /dev/null +++ b/collated/functional/Zhu-Jiahui.md @@ -0,0 +1,339 @@ +# Zhu-Jiahui +###### \java\seedu\address\logic\commands\FindCommand.java +``` java + @Override + public CommandResult execute() { + if (ListPanelController.isCurrentDisplayActiveList()) { + model.updateFilteredStudentList(predicate); + model.updateFilteredTutorList(predicate); + return new CommandResult(getMessageForClientListShownSummary( + model.getFilteredStudentList().size(), model.getFilteredTutorList().size())); + } else { + model.updateFilteredClosedStudentList(predicate); + model.updateFilteredClosedTutorList(predicate); + return new CommandResult(getMessageForClientListShownSummary( + model.getFilteredClosedStudentList().size(), model.getFilteredClosedTutorList().size())); + } + } +``` +###### \java\seedu\address\logic\commands\MatchCommand.java +``` java +/** + * Match the entered client and lists all clients in address book that has similar attributes to the matched client. + */ +public class MatchCommand extends Command { + + public static final String COMMAND_WORD = "match"; + public static final String COMMAND_ALIAS = "m"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Finds all clients that match all the fields listed by the person entered.\n" + + "Example: " + COMMAND_WORD + " 1" + " c/t"; + + private final Index targetIndex; + private final Category category; + + private Client clientToMatch; + + public MatchCommand(Index index, Category category) { + this.targetIndex = index; + this.category = category; + } + + @Override + public CommandResult execute() throws CommandException { + if (!ListPanelController.isCurrentDisplayActiveList()) { + throw new CommandNotAvailableInClosedViewException(); + } + List lastShownList; + + if (category.isStudent()) { + lastShownList = model.getFilteredStudentList(); + } else { + lastShownList = model.getFilteredTutorList(); + } + + if (targetIndex.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + + clientToMatch = lastShownList.get(targetIndex.getZeroBased()); + + MatchContainsKeywordsPredicate predicate = new MatchContainsKeywordsPredicate(clientToMatch); + if (category.isStudent()) { + model.updateFilteredTutorList(predicate); + model.updateFilteredStudentList(new MatchContainsPersonsPredicate(clientToMatch)); + model.updateRankedTutorList(); + + } else { + model.updateFilteredStudentList(predicate); + model.updateFilteredTutorList(new MatchContainsPersonsPredicate(clientToMatch)); + model.updateRankedStudentList(); + + } + + return new CommandResult(getMessageForClientListShownSummary( + model.getFilteredStudentList().size(), model.getFilteredTutorList().size())); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof MatchCommand // instanceof handles nulls + && this.targetIndex.equals(((MatchCommand) other).targetIndex)) + && this.category.equals(((MatchCommand) other).category); // state check + } +} +``` +###### \java\seedu\address\logic\parser\MatchCommandParser.java +``` java +/** + * Parses input arguments and creates a new MatchCommand object + */ +public class MatchCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the MatchCommand + * and returns an MatchCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public MatchCommand parse(String args) throws ParseException { + requireNonNull(args); + ArgumentMultimap argumentMultimap = ArgumentTokenizer.tokenize(args, PREFIX_CATEGORY); + + if (!arePrefixesPresent(argumentMultimap, PREFIX_CATEGORY) + || argumentMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, MatchCommand.MESSAGE_USAGE)); + } + + Index index; + Category category; + + try { + index = ParserUtil.parseIndex(argumentMultimap.getPreamble()); + category = ParserUtil.parseCategory(argumentMultimap.getValue(PREFIX_CATEGORY)).get(); + return new MatchCommand(index, category); + } catch (IllegalValueException ive) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, MatchCommand.MESSAGE_USAGE)); + } + } + + /** + * 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()); + } +} +``` +###### \java\seedu\address\model\ModelManager.java +``` java + /** + * Returns an unmodifiable view of the list of {@code Client} backed by the internal list of + * {@code addressBook} + */ + + @Override + public void updateRankedStudentList() { + Comparator rankStudent = new RankComparator(); + sortedFilteredStudents.setComparator(rankStudent); + } + + /** + * Returns an unmodifiable view of the list of {@code Client} backed by the internal list of + * {@code addressBook} + */ + + @Override + public void updateRankedTutorList() { + Comparator rankTutor = new RankComparator(); + sortedFilteredTutors.setComparator(rankTutor); + } + + /** + * Reset {@code rank}, {@code MatchedGrade}, {@code MatchedLocation} and {@code MatchedSubject} in all + * Clientlist to default value + */ + + @Override + public void resetHighLight() { + for (Client client : filteredTutors) { + client.setRank(0); + client.setMatchedLocation(false); + client.setMatchedGrade(false); + client.setMatchedSubject(false); + } + for (Client client : filteredStudents) { + client.setRank(0); + client.setMatchedLocation(false); + client.setMatchedGrade(false); + client.setMatchedSubject(false); + } + + for (Client client : sortedFilteredStudents) { + client.setRank(0); + client.setMatchedLocation(false); + client.setMatchedGrade(false); + client.setMatchedSubject(false); + } + + for (Client client : sortedFilteredTutors) { + client.setRank(0); + client.setMatchedLocation(false); + client.setMatchedGrade(false); + client.setMatchedSubject(false); + } + + } +``` +###### \java\seedu\address\model\person\MatchContainsKeywordsPredicate.java +``` java +/** + * Tests that a {@code Client}'s {@code Location, Grade and Subject} matches the entered {@code Client}'s + * {@code Location, Grade and Subject}. + */ +public class MatchContainsKeywordsPredicate implements Predicate { + private final Client client; + + public MatchContainsKeywordsPredicate(Client client) { + this.client = client; + } + + @Override + public boolean test(Client other) { + boolean isMatch = false; + int rank = 0; + + if (StringUtil.containsWordIgnoreCase(other.getLocation().toString(), client.getLocation().toString())) { + isMatch = true; + other.setMatchedLocation(isMatch); + rank++; + } + if (GradeUtil.containsGradeIgnoreCase(other.getGrade().value, client.getGrade().toString() + .split("\\s+")[0])) { + isMatch = true; + other.setMatchedGrade(isMatch); + rank++; + } + if (StringUtil.containsWordIgnoreCase(other.getSubject().value, client.getSubject().toString() + .split("\\s+")[0])) { + isMatch = true; + other.setMatchedSubject(isMatch); + rank++; + } + other.setRank(rank); + return isMatch; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof MatchContainsKeywordsPredicate // instanceof handles nulls + && this.client.equals(((MatchContainsKeywordsPredicate) other).client)); // state check + } + +} +``` +###### \java\seedu\address\model\person\MatchContainsPersonsPredicate.java +``` java +/** + * Tests that a {@code Client}'s attributes matches all of the attributes of the entered {@code Client}'s. + */ +public class MatchContainsPersonsPredicate implements Predicate { + private final Client client; + + public MatchContainsPersonsPredicate(Client client) { + this.client = client; + } + + @Override + public boolean test(Client other) { + return other.toString().equals(client.toString()); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof MatchContainsPersonsPredicate // instanceof handles nulls + && this.client.equals(((MatchContainsPersonsPredicate) other).client)); // state check + } + +} +``` +###### \java\seedu\address\model\person\SearchContainsKeywordsPredicate.java +``` java + @Override + public boolean test(Client client) { + return keywords.stream() + .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(client.getName().fullName, keyword)) + || keywords.stream() + .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(client.getEmail().value, keyword)) + || keywords.stream() + .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(client.getAddress().value, keyword)) + || keywords.stream() + .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(client.getPhone().value, keyword)) + || keywords.stream() + .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(client.getLocation().value, keyword)) + || keywords.stream() + .anyMatch(keyword -> GradeUtil.containsGradeIgnoreCase(client.getGrade().value, keyword)) + || keywords.stream() + .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(client.getSubject().value, keyword)) + || keywords.stream() + .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(client.getCategory().value, keyword)); + } +``` +###### \java\seedu\address\ui\ClientCard.java +``` java + /** + * Initialises Location + * If Location is matched with the client, Location field will be highlighted. + * @param client + */ + + private void intplaces(Client client) { + + places.setText(client.getLocation().value); + + if (client.getMatchedLocation() == true) { + places.getStyleClass().add(MATCH_COLOUR_STYLE); + } else { + places.getStyleClass().add(UNMATCH_COLOUR_STYLE); + } + } + + /** + * Initialises Grade + * If Grade is matched with the client, Grade field will be highlighted. + * @param client + */ + + private void intGrades(Client client) { + + grades.setText(client.getGrade().value); + + if (client.getMatchedGrade() == true) { + grades.getStyleClass().add(MATCH_COLOUR_STYLE); + } else { + grades.getStyleClass().add(UNMATCH_COLOUR_STYLE); + } + } + + /** + *@author Zhu-Jiahui + * Initialises Subject + * If Subject is matched with the client, Subject field will be highlighted. + * @param client + */ + + private void intSubjects(Client client) { + subjects.setText(client.getSubject().value); + + if (client.getMatchedSubject() == true) { + subjects.getStyleClass().add(MATCH_COLOUR_STYLE); + } else { + subjects.getStyleClass().add(UNMATCH_COLOUR_STYLE); + } + } +``` diff --git a/collated/functional/olimhc.md b/collated/functional/olimhc.md new file mode 100644 index 000000000000..f57b18188ae0 --- /dev/null +++ b/collated/functional/olimhc.md @@ -0,0 +1,854 @@ +# olimhc +###### \java\seedu\address\commons\events\ui\ClientListSwitchEvent.java +``` java +/** + * Represents an event when the user wants to switch the list + */ +public class ClientListSwitchEvent extends BaseEvent { + + @Override + public String toString() { + return this.getClass().getSimpleName(); + } +} +``` +###### \java\seedu\address\logic\commands\CloseCommand.java +``` java +/** + * Deletes a person from the active list and add it to the closed list + */ +public class CloseCommand extends UndoableCommand { + public static final String COMMAND_WORD = "close"; + public static final String COMMAND_ALIAS = "cs"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Close an active tutor or student and store them in " + + "a closed student or tutor list. \n" + + "Parameters: " + COMMAND_WORD + " " + "INDEX" + " " + PREFIX_CATEGORY + "CATEGORY \n" + + "INDEX should be non-zero and non-negative and " + + "CATEGORY can only be either 's' or 't', where 's' represents students and 't' represents tutor).\n" + + "Example: " + COMMAND_WORD + " " + "1" + " " + PREFIX_CATEGORY + "t\n"; + + public static final String MESSAGE_CLOSE_STUDENT_SUCCESS = "Student closed: %1$s"; + public static final String MESSAGE_CLOSE_TUTOR_SUCCESS = "Tutor closed: %1$s"; + + private final Index targetIndex; + private final Category category; + + private Client clientToClose; + + public CloseCommand(Index targetIndex, Category category) { + this.targetIndex = targetIndex; + this.category = category; + } + + @Override + public CommandResult executeUndoableCommand() { + requireNonNull(clientToClose); + try { + model.deleteClient(clientToClose, category); + } catch (PersonNotFoundException pnfe) { + throw new AssertionError("The target client cannot be missing"); + } + + try { + if (category.isStudent()) { + model.addClosedStudent(clientToClose); + } else { + model.addClosedTutor(clientToClose); + } + } catch (DuplicatePersonException e) { + throw new AssertionError("The client should not be duplicated"); + } + + if (category.isStudent()) { + return new CommandResult(String.format(MESSAGE_CLOSE_STUDENT_SUCCESS, clientToClose)); + } else { + return new CommandResult(String.format(MESSAGE_CLOSE_TUTOR_SUCCESS, clientToClose)); + } + } + + @Override + protected void preprocessUndoableCommand() throws CommandException { + if (!ListPanelController.isCurrentDisplayActiveList()) { + throw new CommandNotAvailableInClosedViewException(); + } + + List lastShownList; + + if (category.isStudent()) { + lastShownList = model.getFilteredStudentList(); + } else { + lastShownList = model.getFilteredTutorList(); + } + + if (targetIndex.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + + clientToClose = lastShownList.get(targetIndex.getZeroBased()); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof CloseCommand // instanceof handles nulls + && this.targetIndex.equals(((CloseCommand) other).targetIndex) // state check + && Objects.equals(this.clientToClose, ((CloseCommand) other).clientToClose)); + } +} +``` +###### \java\seedu\address\logic\commands\exceptions\CommandNotAvailableInActiveViewException.java +``` java +/** + * Signals that the command is not available in active list view. + */ +public class CommandNotAvailableInActiveViewException extends CommandException { + public CommandNotAvailableInActiveViewException() { + super("Command is not available in active list view." + + " Please switch back to closed list view with the command word: switch\n"); + } +} +``` +###### \java\seedu\address\logic\commands\exceptions\CommandNotAvailableInClosedViewException.java +``` java +/** + * Signals that the command is not available in closed list view. + */ +public class CommandNotAvailableInClosedViewException extends CommandException { + public CommandNotAvailableInClosedViewException() { + super("Command is not available in closed list view." + + " Please switch back to active list view with the command word: switch\n"); + } +} +``` +###### \java\seedu\address\logic\commands\RestoreCommand.java +``` java +/** + * Delete a person from the closed list and add it back to the active list + */ +public class RestoreCommand extends UndoableCommand { + public static final String COMMAND_WORD = "restore"; + public static final String COMMAND_ALIAS = "res"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Restore a closed tutor or student to the active " + + "tutor or student list. \n" + + "Parameters: " + COMMAND_WORD + " " + "INDEX" + " " + PREFIX_CATEGORY + "CATEGORY " + + "(CATEGORY can only be either 's' or 't', where 's' represents students and 't' represents tutor).\n" + + "Example: " + COMMAND_WORD + " " + "1" + " " + PREFIX_CATEGORY + "t\n"; + + public static final String MESSAGE_RESTORE_STUDENT_SUCCESS = "Student restored: %1$s"; + public static final String MESSAGE_RESTORE_TUTOR_SUCCESS = "Tutor restored: %1$s"; + + private final Index targetIndex; + private final Category category; + + private Client clientToRestore; + + public RestoreCommand(Index targetIndex, Category category) { + this.targetIndex = targetIndex; + this.category = category; + } + + @Override + public CommandResult executeUndoableCommand() { + requireNonNull(clientToRestore); + try { + model.deleteClosedClient(clientToRestore, category); + } catch (PersonNotFoundException pnfe) { + throw new AssertionError("The target client cannot be missing"); + } + + try { + if (category.isStudent()) { + model.addStudent(clientToRestore); + } else { + model.addTutor(clientToRestore); + } + } catch (DuplicatePersonException e) { + throw new AssertionError("The client should not be duplicated"); + } + + if (category.isStudent()) { + return new CommandResult(String.format(MESSAGE_RESTORE_STUDENT_SUCCESS, clientToRestore)); + } else { + return new CommandResult(String.format(MESSAGE_RESTORE_TUTOR_SUCCESS, clientToRestore)); + } + } + + @Override + protected void preprocessUndoableCommand() throws CommandException { + if (ListPanelController.isCurrentDisplayActiveList()) { + throw new CommandNotAvailableInActiveViewException(); + } + + List lastShownList; + + if (category.isStudent()) { + lastShownList = model.getFilteredClosedStudentList(); + } else { + lastShownList = model.getFilteredClosedTutorList(); + } + + if (targetIndex.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + + clientToRestore = lastShownList.get(targetIndex.getZeroBased()); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof RestoreCommand // instanceof handles nulls + && this.targetIndex.equals(((RestoreCommand) other).targetIndex) // state check + && Objects.equals(this.clientToRestore, ((RestoreCommand) other).clientToRestore)); + } +} +``` +###### \java\seedu\address\logic\commands\SortByGradeCommand.java +``` java +/** + *Sort the selected list according to their grade in ascending order + */ +public class SortByGradeCommand extends SortCommand { + + public static final String MESSAGE_SORT_DESC = " their grade in ascending order."; + + private Category category; + + public SortByGradeCommand(Category category) { + this.category = category; + } + + @Override + public CommandResult execute() throws CommandException { + if (!ListPanelController.isCurrentDisplayActiveList()) { + throw new CommandNotAvailableInClosedViewException(); + } + + if (category.isTutor()) { + model.sortByGradeFilteredClientTutorList(); + return new CommandResult(MESSAGE_SUCCESS_TUTOR + MESSAGE_SORT_DESC); + } else { + model.sortByGradeFilteredClientStudentList(); + return new CommandResult(MESSAGE_SUCCESS_STUDENT + MESSAGE_SORT_DESC); + } + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof SortByGradeCommand // instanceof handles nulls + & this.category.equals(((SortByGradeCommand) other).category)); // state check // state check + } +} +``` +###### \java\seedu\address\logic\commands\SortByLocationCommand.java +``` java +/** + *Sort the selected list according to their location in alphabetical order + */ +public class SortByLocationCommand extends SortCommand { + + public static final String MESSAGE_SORT_DESC = " their location in alphabetical order."; + + private Category category; + + public SortByLocationCommand(Category category) { + this.category = category; + } + + @Override + public CommandResult execute() throws CommandException { + if (!ListPanelController.isCurrentDisplayActiveList()) { + throw new CommandNotAvailableInClosedViewException(); + } + + if (category.isTutor()) { + model.sortByLocationFilteredClientTutorList(); + return new CommandResult(MESSAGE_SUCCESS_TUTOR + MESSAGE_SORT_DESC); + } else { + model.sortByLocationFilteredClientStudentList(); + return new CommandResult(MESSAGE_SUCCESS_STUDENT + MESSAGE_SORT_DESC); + } + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof SortByLocationCommand // instanceof handles nulls + & this.category.equals(((SortByLocationCommand) other).category)); // state check // state check + } +} +``` +###### \java\seedu\address\logic\commands\SortByNameCommand.java +``` java +/** + *Sort the selected list according to their name in alphabetical order + */ +public class SortByNameCommand extends SortCommand { + + public static final String MESSAGE_SORT_DESC = " their name in alphabetical order."; + + private Category category; + + public SortByNameCommand(Category category) { + this.category = category; + } + + @Override + public CommandResult execute() throws CommandException { + if (!ListPanelController.isCurrentDisplayActiveList()) { + throw new CommandNotAvailableInClosedViewException(); + } + + if (category.isTutor()) { + model.sortByNameFilteredClientTutorList(); + return new CommandResult(MESSAGE_SUCCESS_TUTOR + MESSAGE_SORT_DESC); + } else { + model.sortByNameFilteredClientStudentList(); + return new CommandResult(MESSAGE_SUCCESS_STUDENT + MESSAGE_SORT_DESC); + } + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof SortByNameCommand // instanceof handles nulls + & this.category.equals(((SortByNameCommand) other).category)); // state check // state check + } +} +``` +###### \java\seedu\address\logic\commands\SortBySubjectCommand.java +``` java +/** + *Sort the selected list according to their subject in alphabetical order + */ +public class SortBySubjectCommand extends SortCommand { + + public static final String MESSAGE_SORT_DESC = " their subject in alphabetical order."; + + private Category category; + + + public SortBySubjectCommand(Category category) { + this.category = category; + } + + @Override + public CommandResult execute() throws CommandException { + if (!ListPanelController.isCurrentDisplayActiveList()) { + throw new CommandNotAvailableInClosedViewException(); + } + + if (category.isTutor()) { + model.sortBySubjectFilteredClientTutorList(); + return new CommandResult(MESSAGE_SUCCESS_TUTOR + MESSAGE_SORT_DESC); + } else { + model.sortBySubjectFilteredClientStudentList(); + return new CommandResult(MESSAGE_SUCCESS_STUDENT + MESSAGE_SORT_DESC); + } + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof SortBySubjectCommand // instanceof handles nulls + & this.category.equals(((SortBySubjectCommand) other).category)); // state check // state check + } +} +``` +###### \java\seedu\address\logic\commands\SortCommand.java +``` java +/** + * Represents a sort command + */ +public abstract class SortCommand extends Command { + public static final String COMMAND_WORD = "sort"; + public static final String COMMAND_ALIAS = "so"; + + public static final String COMMAND_WORD_NAME = "n"; + public static final String COMMAND_WORD_LOCATION = "l"; + public static final String COMMAND_WORD_SUBJECT = "s"; + public static final String COMMAND_WORD_GRADE = "g"; + public static final String COMMAND_WORD_TUTOR = "t"; + public static final String COMMAND_WORD_STUDENT = "s"; + + public static final String MESSAGE_SUCCESS_TUTOR = "Sorted tutor's list according to"; + public static final String MESSAGE_SUCCESS_STUDENT = "Sorted student's list according to"; + + private static final String USAGE_MESSAGE_LIST = " " + + COMMAND_WORD + " " + COMMAND_WORD_NAME + " " + PREFIX_CATEGORY + COMMAND_WORD_TUTOR + ", " + + COMMAND_WORD + " " + COMMAND_WORD_LOCATION + " " + PREFIX_CATEGORY + COMMAND_WORD_TUTOR + ", " + + COMMAND_WORD + " " + COMMAND_WORD_SUBJECT + " " + PREFIX_CATEGORY + COMMAND_WORD_TUTOR + ", " + + COMMAND_WORD + " " + COMMAND_WORD_GRADE + " " + PREFIX_CATEGORY + COMMAND_WORD_TUTOR + ", " + + "to sort Tutor's list base on name, location, subject and level respectively.\n" + + "Parameters: " + + COMMAND_WORD + " " + COMMAND_WORD_NAME + " " + PREFIX_CATEGORY + COMMAND_WORD_STUDENT + ", " + + COMMAND_WORD + " " + COMMAND_WORD_LOCATION + " " + PREFIX_CATEGORY + COMMAND_WORD_STUDENT + ", " + + COMMAND_WORD + " " + COMMAND_WORD_SUBJECT + " " + PREFIX_CATEGORY + COMMAND_WORD_STUDENT + ", " + + COMMAND_WORD + " " + COMMAND_WORD_GRADE + " " + PREFIX_CATEGORY + COMMAND_WORD_STUDENT + ", " + + "to sort Student's list base on name, location, subject and level respectively.\n" + + "Example: " + + COMMAND_WORD + " " + COMMAND_WORD_LOCATION + " " + PREFIX_CATEGORY + COMMAND_WORD_STUDENT + "\n"; + + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Sort selected list according to user choice.\n" + + "Parameters:" + USAGE_MESSAGE_LIST; + + @Override + public abstract CommandResult execute() throws CommandException; +} +``` +###### \java\seedu\address\logic\commands\SwitchCommand.java +``` java +/** + * Represents a switch command to enable user to switch between closed and active client list + * All active students and tutors or closed students and tutors will be shown after switching + */ +public class SwitchCommand extends Command { + + public static final String COMMAND_WORD = "switch"; + public static final String COMMAND_ALIAS = "sw"; + + public static final String MESSAGE_SUCCESS = "Switched to "; + public static final String MESSAGE_CLOSED_DISPLAY_LIST = "closed client list.\n"; + public static final String MESSAGE_ACTIVE_DISPLAY_LIST = "active client list.\n"; + + + @Override + public CommandResult execute() { + EventsCenter.getInstance().post(new ClientListSwitchEvent()); + listPanelController.switchDisplay(); + if (!ListPanelController.isCurrentDisplayActiveList()) { + model.updateFilteredClosedTutorList(PREDICATE_SHOW_ALL_CLOSED_TUTORS); + model.updateFilteredClosedStudentList(PREDICATE_SHOW_ALL_CLOSED_STUDENTS); + return new CommandResult(MESSAGE_SUCCESS + MESSAGE_CLOSED_DISPLAY_LIST); + } else { + model.updateFilteredTutorList(PREDICATE_SHOW_ALL_TUTORS); + model.updateFilteredStudentList(PREDICATE_SHOW_ALL_STUDENTS); + return new CommandResult(MESSAGE_SUCCESS + MESSAGE_ACTIVE_DISPLAY_LIST); + } + } +} +``` +###### \java\seedu\address\logic\commands\util\GradeUtil.java +``` java +/** + * Helper function for handling different format of grade + */ +public class GradeUtil { + + /** + * Returns true if the {@code value} matches the {@code word} given that word is a valid grade + *
examples:
+     *       A p3 grade should match primary3.
+     *       A client with P3 P4 grades should match a p4 grade
+     *       
+ * @param value cannot be null, can be a string of multiple grades or just a grade + * @param word cannot be null, cannot be empty, must be a single word + */ + public static boolean containsGradeIgnoreCase(String value, String word) { + requireNonNull(value); + requireNonNull(word); + + if (!isValidGrade(word)) { + return false; + } + + String preppedWord = word.trim(); + checkArgument(!preppedWord.isEmpty(), "Word parameter cannot be empty"); + checkArgument(preppedWord.split("\\s+").length == 1, "Word parameter should be a single word"); + + int preppedWordValueWeightage = getGradeIndex(preppedWord); + int[] getAllGradeWeightage = getAllGradeWeightage(value); + + for (int i : getAllGradeWeightage) { + if (i == preppedWordValueWeightage) { + return true; + } + } + + return false; + } +} +``` +###### \java\seedu\address\logic\commands\util\SortByGradeComparator.java +``` java +/** + * Comparator to sort by int base on valueWeight + */ +public class SortByGradeComparator + implements Comparator { + + @Override + public int compare(Client o1, Client o2) { + return o1.getGrade().valueWeightage - o2.getGrade().valueWeightage; + } +} +``` +###### \java\seedu\address\logic\parser\CloseCommandParser.java +``` java +/** + * Parses an input and create a new CloseCommand object + */ +public class CloseCommandParser implements Parser { + /** + * Parses the given {@code String} of arguments in the context of the CloseCommand + * and returns a Close Command object for execution. + * + * @throws ParseException if the user input does not conform the expected format + */ + public CloseCommand parse(String args) throws ParseException { + requireNonNull(args); + ArgumentMultimap argumentMultimap = ArgumentTokenizer.tokenize(args, PREFIX_CATEGORY); + + if (!arePrefixesPresent(argumentMultimap, PREFIX_CATEGORY) + || argumentMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, CloseCommand.MESSAGE_USAGE)); + } + + Index index; + Category category; + + try { + index = ParserUtil.parseIndex(argumentMultimap.getPreamble()); + category = ParserUtil.parseCategory(argumentMultimap.getValue(PREFIX_CATEGORY)).get(); + return new CloseCommand(index, category); + } catch (IllegalValueException ive) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, CloseCommand.MESSAGE_USAGE)); + } + } + + /** + * 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()); + } +} +``` +###### \java\seedu\address\logic\parser\RestoreCommandParser.java +``` java +/** + * Parse an input and create a new RestoreCommand object + */ +public class RestoreCommandParser implements Parser { + /** + * Parses the given {@code String} of arguments in the context of the RestoreCommand + * and returns a Restore Command object for execution. + * + * @throws ParseException if the user input does not conform the expected format + */ + public RestoreCommand parse(String args) throws ParseException { + requireNonNull(args); + ArgumentMultimap argumentMultimap = ArgumentTokenizer.tokenize(args, PREFIX_CATEGORY); + + if (!arePrefixesPresent(argumentMultimap, PREFIX_CATEGORY) + || argumentMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, RestoreCommand.MESSAGE_USAGE)); + } + + Index index; + Category category; + + try { + index = ParserUtil.parseIndex(argumentMultimap.getPreamble()); + category = ParserUtil.parseCategory(argumentMultimap.getValue(PREFIX_CATEGORY)).get(); + return new RestoreCommand(index, category); + } catch (IllegalValueException ive) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, RestoreCommand.MESSAGE_USAGE)); + } + } + + /** + * 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()); + } +} +``` +###### \java\seedu\address\logic\parser\SortCommandParser.java +``` java +/** + * Parses input arguments and creates a new subclass object of SortCommand + */ +public class SortCommandParser implements Parser { + + /** + * Parse the given {@code String} of arguments in the context of SortCommand + * @return either SortByGradeCommand, SortByNameCommand, SortByGradeCommand, SortBySubjectCommand + * object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public SortCommand parse(String args) throws ParseException { + requireNonNull(args); + ArgumentMultimap argumentMultimap = ArgumentTokenizer.tokenize(args, PREFIX_CATEGORY); + + if (!arePrefixesPresent(argumentMultimap, PREFIX_CATEGORY) + || argumentMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, SortCommand.MESSAGE_USAGE)); + } + + String sortType; + Category category; + + try { + sortType = argumentMultimap.getPreamble(); + category = ParserUtil.parseCategory(argumentMultimap.getValue(PREFIX_CATEGORY)).get(); + return getSortCommandType(category, sortType); + } catch (IllegalValueException ive) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, SortCommand.MESSAGE_USAGE)); + } + } + + /** + * @return the respective sort command based on the category parsed + * @throws ParseException + */ + private SortCommand getSortCommandType(Category category, String sortType) throws ParseException { + + switch (sortType) { + case SortCommand.COMMAND_WORD_NAME: + return new SortByNameCommand(category); + + case SortCommand.COMMAND_WORD_SUBJECT: + return new SortBySubjectCommand(category); + + case SortCommand.COMMAND_WORD_LOCATION: + return new SortByLocationCommand(category); + + case SortCommand.COMMAND_WORD_GRADE: + return new SortByGradeCommand(category); + + default: + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, SortCommand.MESSAGE_USAGE)); + } + } + + /** + * 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()); + } +} +``` +###### \java\seedu\address\model\ModelManager.java +``` java + /** + * Returns an unmodifiable view of the list of {@code Tutor} backed by the internal list of + * {@code addressBook} + */ + + @Override + public void sortByNameFilteredClientTutorList() { + Comparator sortByName = (tutor1, tutor2)-> (tutor1.getName().fullName) + .compareToIgnoreCase(tutor2.getName().fullName); + sortedFilteredTutors.setComparator(sortByName); + } + + @Override + public void sortByNameFilteredClientStudentList() { + Comparator sortByName = (student1, student2)-> (student1.getName().fullName) + .compareToIgnoreCase(student2.getName().fullName); + sortedFilteredStudents.setComparator(sortByName); + } + + @Override + public void sortByLocationFilteredClientTutorList() { + Comparator sortByLocation = (tutor1, tutor2)-> (tutor1.getLocation().value) + .compareToIgnoreCase(tutor2.getLocation().value); + sortedFilteredTutors.setComparator(sortByLocation); + } + + @Override + public void sortByLocationFilteredClientStudentList() { + Comparator sortByLocation = (student1, student2)-> (student1.getLocation().value) + .compareToIgnoreCase(student2.getLocation().value); + sortedFilteredStudents.setComparator(sortByLocation); + } + + + @Override + public void sortByGradeFilteredClientTutorList() { + Comparator sortByGrade = new SortByGradeComparator(); + sortedFilteredTutors.setComparator(sortByGrade); + } + + @Override + public void sortByGradeFilteredClientStudentList() { + Comparator sortByGrade = new SortByGradeComparator(); + sortedFilteredStudents.setComparator(sortByGrade); + } + + @Override + public void sortBySubjectFilteredClientTutorList() { + Comparator sortBySubject = (tutor1, tutor2)-> (tutor1.getSubject().value) + .compareToIgnoreCase(tutor2.getSubject().value); + sortedFilteredTutors.setComparator(sortBySubject); + } + + @Override + public void sortBySubjectFilteredClientStudentList() { + Comparator sortBySubject = (student1, student2)-> (student1.getSubject().value) + .compareToIgnoreCase(student2.getSubject().value); + sortedFilteredStudents.setComparator(sortBySubject); + } +``` +###### \java\seedu\address\ui\StatusBarFooter.java +``` java + @Subscribe + private void handleClientListSwitchEvent(ClientListSwitchEvent event) { + logger.info(LogsCenter.getEventHandlingLogMessage(event)); + if (this.displayStatus.getText().equals(SYNC_STATUS_ACTIVE_LIST)) { + setDisplayStatus(SYNC_STATUS_CLOSED_LIST); + } else { + setDisplayStatus(SYNC_STATUS_ACTIVE_LIST); + } + } +``` +###### \java\seedu\address\ui\StudentListPanel.java +``` java + /** + * Switch the displayed student's list + */ + private void switchListDisplay() { + ListPanelController listPanelController = ListPanelController.getInstance(); + switch (listPanelController.getCurrentListDisplayed()) { + case activeList: + setConnectionsForClosedStudents(); + break; + + case closedList: + setConnectionsForStudents(); + break; + + default: + throw new AssertionError("This should not be possible."); + } + } +``` +###### \java\seedu\address\ui\StudentListPanel.java +``` java + @Subscribe + private void handleClientListSwitchEvent(ClientListSwitchEvent event) { + logger.info(LogsCenter.getEventHandlingLogMessage(event)); + switchListDisplay(); + } +``` +###### \java\seedu\address\ui\TutorListPanel.java +``` java + /** + * Switch the displayed tutor's list + */ + private void switchListDisplay() { + ListPanelController listPanelController = ListPanelController.getInstance(); + switch (listPanelController.getCurrentListDisplayed()) { + case activeList: + setConnectionsForClosedTutors(); + break; + + case closedList: + setConnectionsForTutors(); + break; + + default: + throw new AssertionError("This should not be possible."); + } + } +``` +###### \java\seedu\address\ui\TutorListPanel.java +``` java + @Subscribe + private void handleClientListSwitchEvent(ClientListSwitchEvent event) { + logger.info(LogsCenter.getEventHandlingLogMessage(event)); + switchListDisplay(); + } +``` +###### \java\seedu\address\ui\util\ListPanelController.java +``` java +/** + * Stores the type of list being displayed + */ +public class ListPanelController { + private static final Logger logger = LogsCenter.getLogger(ListPanelController.class); + private static ListPanelController instance = null; + + /** + * An enum to store which the type of list displayed + */ + public enum DisplayType { + closedList, activeList + } + + /** + * Ensure that the active client list is always shown first + */ + private static DisplayType currentlyDisplayed = DisplayType.activeList; + + public DisplayType getCurrentListDisplayed() { + return currentlyDisplayed; + } + + /** + * Switch the current display + */ + public void switchDisplay() { + switch (currentlyDisplayed) { + case activeList: + currentlyDisplayed = DisplayType.closedList; + logger.fine("Switching display to closed client list."); + break; + + case closedList: + currentlyDisplayed = DisplayType.activeList; + logger.fine("Switching display to active client list."); + break; + + default: + throw new AssertionError("This should not be possible."); + } + } + + /** + * Reset the display to its default mode showing active client list. + */ + public void setDefault() { + if (!isCurrentDisplayActiveList()) { + switchDisplay(); + } + } + + /** + * @return true if displayed list is active list + */ + public static boolean isCurrentDisplayActiveList() { + if (currentlyDisplayed == DisplayType.activeList) { + return true; + } else { + return false; + } + } + + /** + * Ensure that only one instance of ListPanelController is created + * @return instance + */ + public static ListPanelController getInstance() { + if (instance == null) { + instance = new ListPanelController(); + } + + return instance; + } +} +``` diff --git a/collated/functional/shookshire.md b/collated/functional/shookshire.md new file mode 100644 index 000000000000..515ca8f79921 --- /dev/null +++ b/collated/functional/shookshire.md @@ -0,0 +1,1580 @@ +# shookshire +###### \java\seedu\address\logic\commands\AddClientCommand.java +``` java +/** + * Adds a tutor to TuitionCor. + */ +public class AddClientCommand extends UndoableCommand { + + public static final String COMMAND_WORD = "addclient"; + public static final String COMMAND_ALIAS = "ac"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a client to TuitionCor. \n" + + "Parameters: " + + PREFIX_NAME + "NAME " + + PREFIX_CATEGORY + "CATEGORY " + + PREFIX_PHONE + "PHONE " + + PREFIX_EMAIL + "EMAIL " + + PREFIX_ADDRESS + "ADDRESS " + + "[" + PREFIX_TAG + "TAG]... " + + PREFIX_LOCATION + "LOCATION " + + PREFIX_GRADE + "GRADE " + + PREFIX_SUBJECT + "SUBJECT\n" + + "Example: " + COMMAND_WORD + " " + + PREFIX_NAME + "John Doe " + + PREFIX_CATEGORY + "t " + + PREFIX_PHONE + "98765432 " + + PREFIX_EMAIL + "johnd@example.com " + + PREFIX_ADDRESS + "311, Clementi Ave 2, #02-25 " + + PREFIX_TAG + "friends " + + PREFIX_TAG + "owesMoney " + + PREFIX_LOCATION + "east " + + PREFIX_GRADE + "p6 " + + PREFIX_SUBJECT + "physics"; + + public static final String MESSAGE_SUCCESS_STUDENT = "New student added: %1$s"; + public static final String MESSAGE_SUCCESS_TUTOR = "New tutor added: %1$s"; + public static final String MESSAGE_DUPLICATE_PERSON = "This student/tutor already exists in the address book"; + + private final Client toAdd; + + /** + * Creates an AddClientCommand to add the specified {@code Client} + */ + public AddClientCommand(Client client) { + requireNonNull(client); + toAdd = client; + } + + @Override + public CommandResult executeUndoableCommand() throws CommandException { + requireNonNull(model); + + if (!ListPanelController.isCurrentDisplayActiveList()) { + throw new CommandNotAvailableInClosedViewException(); + } + + try { + if (toAdd.getCategory().isStudent()) { + model.addStudent(toAdd); + return new CommandResult(String.format(MESSAGE_SUCCESS_STUDENT, toAdd)); + } else { + model.addTutor(toAdd); + return new CommandResult(String.format(MESSAGE_SUCCESS_TUTOR, toAdd)); + } + + } catch (DuplicatePersonException e) { + throw new CommandException(MESSAGE_DUPLICATE_PERSON); + } + + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof AddClientCommand // instanceof handles nulls + && toAdd.equals(((AddClientCommand) other).toAdd)); + } +} +``` +###### \java\seedu\address\logic\commands\RemoveCommand.java +``` java +/** + * Remove the specified details of an existing client in the address book. + */ +public class RemoveCommand extends UndoableCommand { + + public static final String COMMAND_WORD = "remove"; + public static final String COMMAND_ALIAS = "re"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Removes the subject from the client identified " + + "by the index number used in the last client listing. " + + "If the specified subject exists it would be removed from the client. " + + "Input subject should be a single word without space.\n" + + "Parameters: INDEX (must be a positive integer) " + + "[" + PREFIX_CATEGORY + "CATEGORY] " + + "[" + PREFIX_SUBJECT + "SUBJECT]\n" + + "Example: " + COMMAND_WORD + " 1 " + + PREFIX_CATEGORY + "s " + + PREFIX_SUBJECT + "math"; + + public static final String MESSAGE_REMOVE_CLIENT_SUCCESS = "Edited Person: %1$s"; + public static final String MESSAGE_DUPLICATE_PERSON = "This person already exists in the address book."; + public static final String MESSAGE_VALUE_DONT_EXIST = "The inputted subject does not exist"; + public static final String MESSAGE_LAST_VALUE = "The last subject cannot be removed.\n" + + "Recommended to delete or close client instead"; + + public static final String REMOVE_VALIDATION_REGEX = "[a-zA-Z]+"; + + private final Index index; + private final Category category; + private final Subject toRemove; + + private Client personToEdit; + private Client editedPerson; + + + /** + * @param index of the person in the filtered person list to edit + * @param toRemove the subject to be removed + */ + public RemoveCommand(Index index, Subject toRemove, Category category) { + requireNonNull(index); + requireNonNull(toRemove); + requireNonNull(category); + + this.category = category; + this.index = index; + this.toRemove = toRemove; + } + + @Override + public CommandResult executeUndoableCommand() throws CommandException { + try { + model.updateClient(personToEdit, editedPerson, category); + } catch (DuplicatePersonException dpe) { + throw new CommandException(MESSAGE_DUPLICATE_PERSON); + } catch (PersonNotFoundException pnfe) { + throw new AssertionError("The target person cannot be missing"); + } + model.updateFilteredStudentList(PREDICATE_SHOW_ALL_STUDENTS); + model.updateFilteredTutorList(PREDICATE_SHOW_ALL_TUTORS); + return new CommandResult(String.format(MESSAGE_REMOVE_CLIENT_SUCCESS, editedPerson)); + } + + @Override + protected void preprocessUndoableCommand() throws CommandException { + if (!ListPanelController.isCurrentDisplayActiveList()) { + throw new CommandNotAvailableInClosedViewException(); + } + + List lastShownList; + + if (category.isStudent()) { + lastShownList = model.getFilteredStudentList(); + } else { + lastShownList = model.getFilteredTutorList(); + } + + if (index.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + + personToEdit = lastShownList.get(index.getZeroBased()); + + if (!isValidRemovableSubject(toRemove)) { + throw new CommandException(MESSAGE_USAGE); + } + + if (!containsSubject(personToEdit, toRemove)) { + throw new CommandException(MESSAGE_VALUE_DONT_EXIST); + } + + if (!isMoreThanOne(personToEdit.getSubject())) { + throw new CommandException(MESSAGE_LAST_VALUE); + } + + editedPerson = createEditedPerson(personToEdit, toRemove); + } + + /** + * @param personToEdit the client that we wish to remove a subject from + * @param toRemove the subject to be removed + * Returns true if the subject exists + */ + private static boolean containsSubject(Client personToEdit, Subject toRemove) { + String originalSubject = personToEdit.getSubject().toString(); + ArrayList originalSubjectArrayList = new ArrayList<>(Arrays.asList(originalSubject.split(" "))); + + return originalSubjectArrayList.contains(toRemove.value); + } + + private static boolean isValidRemovableSubject(Subject test) { + return test.value.matches(REMOVE_VALIDATION_REGEX); + } + + private static boolean isMoreThanOne(Subject test) { + String[] testArray = test.value.split(" "); + return testArray.length > 1; + } + + /** + * Creates and returns a {@code Client} with the details of {@code personToEdit} + * edited with {@code editPersonDescriptor}. + */ + private static Client createEditedPerson(Client personToEdit, Subject toRemove) { + assert personToEdit != null; + + String editSubjects = personToEdit.getSubject().value; + ArrayList editSubjectArray = new ArrayList<>(Arrays.asList(editSubjects.split(" "))); + editSubjectArray.remove(toRemove.value); + + StringBuilder sb = new StringBuilder(); + + for (int i = 0; i < editSubjectArray.size(); i++) { + sb.append(editSubjectArray.get(i)); + sb.append(" "); + } + + return new Client(personToEdit.getName(), personToEdit.getPhone(), personToEdit.getEmail(), + personToEdit.getAddress(), personToEdit.getTags(), personToEdit.getLocation(), + personToEdit.getGrade(), new Subject(sb.toString()), personToEdit.getCategory()); + } + + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof RemoveCommand)) { + return false; + } + + // state check + RemoveCommand r = (RemoveCommand) other; + return index.equals(r.index) + && Objects.equals(editedPerson, r.editedPerson) + && Objects.equals(personToEdit, r.personToEdit); + } + +} + +``` +###### \java\seedu\address\logic\parser\AddClientCommandParser.java +``` java +/** + * Parses input arguments and creates a new AddClientCommand object + */ +public class AddClientCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the AddClientCommand + * and returns an AddClientCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public AddClientCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, + PREFIX_TAG, PREFIX_LOCATION, PREFIX_GRADE, PREFIX_SUBJECT, PREFIX_CATEGORY); + + if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_ADDRESS, PREFIX_PHONE, PREFIX_EMAIL, + PREFIX_LOCATION, PREFIX_GRADE, PREFIX_SUBJECT, PREFIX_CATEGORY) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddClientCommand.MESSAGE_USAGE)); + } + + try { + Name name = ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME)).get(); + Phone phone = ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE)).get(); + Email email = ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL)).get(); + Address address = ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS)).get(); + Set tagList = ParserUtil.parseTags(argMultimap.getAllValues(PREFIX_TAG)); + Location location = ParserUtil.parseLocation(argMultimap.getValue(PREFIX_LOCATION)).get(); + Grade grade = ParserUtil.parseGrade(argMultimap.getValue(PREFIX_GRADE)).get(); + Subject subject = ParserUtil.parseSubject(argMultimap.getValue(PREFIX_SUBJECT)).get(); + Category category = ParserUtil.parseCategory(argMultimap.getValue(PREFIX_CATEGORY)).get(); + + Client client = new Client(name, phone, email, address, tagList, location, grade, subject, category); + + return new AddClientCommand(client); + } catch (IllegalValueException ive) { + throw new ParseException(ive.getMessage(), ive); + } + } + + /** + * 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()); + } + +} +``` +###### \java\seedu\address\logic\parser\RemoveCommandParser.java +``` java +/** + * Parses input arguments and creates a new RemoveCommand object + */ +public class RemoveCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the RemoveCommand + * and returns an RemoveCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public RemoveCommand parse(String args) throws ParseException { + requireNonNull(args); + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_SUBJECT, PREFIX_CATEGORY); + + if (!arePrefixesPresent(argMultimap, PREFIX_CATEGORY, PREFIX_SUBJECT) + || argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, RemoveCommand.MESSAGE_USAGE)); + } + + Index index; + Category category; + Subject subject; + + try { + index = ParserUtil.parseIndex(argMultimap.getPreamble()); + category = ParserUtil.parseCategory(argMultimap.getValue(PREFIX_CATEGORY)).get(); + subject = ParserUtil.parseSubject(argMultimap.getValue(PREFIX_SUBJECT)).get(); + } catch (IllegalValueException ive) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, RemoveCommand.MESSAGE_USAGE)); + } + + return new RemoveCommand(index, subject, category); + } + + /** + * 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()); + } +} +``` +###### \java\seedu\address\model\AddressBook.java +``` java +/** + * Wraps all data at the address-book level + * Duplicates are not allowed (by .equals comparison) + */ +public class AddressBook implements ReadOnlyAddressBook { + private final UniqueClientList students; + private final UniqueClientList tutors; + private final UniqueClientList closedStudents; + private final UniqueClientList closedTutors; + private final UniqueTagList tags; + + /* + * The 'unusual' code block below is an non-static initialization block, sometimes used to avoid duplication + * between constructors. See https://docs.oracle.com/javase/tutorial/java/javaOO/initial.html + * + * Note that non-static init blocks are not recommended to use. There are other ways to avoid duplication + * among constructors. + */ + { + students = new UniqueClientList(); + tutors = new UniqueClientList(); + closedTutors = new UniqueClientList(); + closedStudents = new UniqueClientList(); + tags = new UniqueTagList(); + } + +``` +###### \java\seedu\address\model\AddressBook.java +``` java + public void setStudents(List students) throws DuplicatePersonException { + this.students.setClients(students); + } + + public void setTutors(List tutors) throws DuplicatePersonException { + this.tutors.setClients(tutors); + } + + public void setClosedStudents(List closedStudents) throws DuplicatePersonException { + this.closedStudents.setClients(closedStudents); + } + + public void setClosedTutors(List closedTutors) throws DuplicatePersonException { + this.closedTutors.setClients(closedTutors); + } + +``` +###### \java\seedu\address\model\AddressBook.java +``` java + /** + * Adds a tutor to TuitionCor. + * Also checks the new tutor's tags and updates {@link #tags} with any new tags found, + * and updates the Tag objects in the tutor to point to those in {@link #tags}. + * + * @throws DuplicatePersonException if an equivalent person already exists. + */ + public void addTutor(Client t) throws DuplicatePersonException { + Client tutor = syncWithMasterTagList(t); + // TODO: the tags master list will be updated even though the below line fails. + // This can cause the tags master list to have additional tags that are not tagged to any person + // in the person list. + tutors.add(tutor, closedTutors); + } + + /** + * Adds a student to TuitionCor. + * Also checks the new student's tags and updates {@link #tags} with any new tags found, + * and updates the Tag objects in the student to point to those in {@link #tags}. + * + * @throws DuplicatePersonException if an equivalent person already exists. + */ + public void addStudent(Client t) throws DuplicatePersonException { + Client student = syncWithMasterTagList(t); + // TODO: the tags master list will be updated even though the below line fails. + // This can cause the tags master list to have additional tags that are not tagged to any person + // in the person list. + students.add(student, closedStudents); + } + + /** + * Adds a student to closed student's list. + * Also checks the closed student's tags and updates {@link #tags} with any new tags found, + * and updates the Tag objects in the closed student to point to those in {@link #tags}. + * + * @throws AssertionError if an equivalent person already exists. + */ + public void addClosedStudent(Client t) throws AssertionError { + Client closedStudent = syncWithMasterTagList(t); + // TODO: the tags master list will be updated even though the below line fails. + // This can cause the tags master list to have additional tags that are not tagged to any person + // in the person list. + closedStudents.add(closedStudent); + } + + /** + * Adds a tutor to closed tutor's list. + * Also checks the closed tutor's tags and updates {@link #tags} with any new tags found, + * and updates the Tag objects in the closed tutor to point to those in {@link #tags}. + * + * @throws AssertionError if an equivalent person already exists. + */ + public void addClosedTutor(Client t) throws AssertionError { + Client closedTutor = syncWithMasterTagList(t); + // TODO: the tags master list will be updated even though the below line fails. + // This can cause the tags master list to have additional tags that are not tagged to any person + // in the person list. + closedTutors.add(closedTutor); + } + + /** + * For test cases use and when adding sample data + * Adds a closed client to TuitionCor. + * Also checks the new student's tags and updates {@link #tags} with any new tags found, + * and updates the Tag objects in the tutor to point to those in {@link #tags}. + * + */ + public void addClosedClient(Client t) { + if (t.getCategory().isStudent()) { + Client closedStudent = syncWithMasterTagList(t); + closedStudents.add(closedStudent); + } else { + Client closedTutor = syncWithMasterTagList(t); + closedTutors.add(closedTutor); + } + } + + /** + * For test cases use and when adding sample data + * Adds a client to TuitionCor + * Also checks the new student's tags and updates {@link #tags} with any new tags found, + * and updates the Tag objects in the tutor to point to those in {@link #tags}. + * + * @throws DuplicatePersonException if an equivalent person already exists. + */ + public void addClient(Client t) throws DuplicatePersonException { + if (t.getCategory().isStudent()) { + Client student = syncWithMasterTagList(t); + students.add(student); + } else { + Client tutor = syncWithMasterTagList(t); + tutors.add(tutor); + } + } + + /** + * Replaces the given client {@code target} in the list with {@code editedClient}. + * {@code AddressBook}'s tag list will be updated with the tags of {@code editedClient}. + * Either closedStudents or closedTutors will be pass in for duplication check when editing the client in active + * list. + * @throws DuplicatePersonException if updating the client's details causes the client to be equivalent to + * another existing client in the list. + * @throws PersonNotFoundException if {@code target} could not be found in the list. + * + * @see #syncWithMasterTagList(Client) + */ + public void updatePerson(Client target, Client editedClient, Category category) + throws DuplicatePersonException, PersonNotFoundException { + requireNonNull(editedClient); + + Client syncedEditedPerson = syncWithMasterTagList(editedClient); + // TODO: the tags master list will be updated even though the below line fails. + // This can cause the tags master list to have additional tags that are not tagged to any person + // in the person list. + if (category.isStudent()) { + students.setClient(target, syncedEditedPerson, closedStudents); + } else { + tutors.setClient(target, syncedEditedPerson, closedTutors); + } + } + +``` +###### \java\seedu\address\model\AddressBook.java +``` java + /** + * Removes {@code key} from the active client list in this {@code AddressBook}. + * @throws PersonNotFoundException if the {@code key} is not in this {@code AddressBook}. + */ + public boolean removeClient(Client key, Category category) throws PersonNotFoundException { + Boolean isSuccess; + + if (category.isStudent()) { + isSuccess = students.remove(key); + } else { + isSuccess = tutors.remove(key); + } + + if (isSuccess) { + return true; + } else { + throw new PersonNotFoundException(); + } + } + + /** + * Removes {@code key} from the closed client list in this {@code AddressBook}. + * @throws PersonNotFoundException if the {@code key} is not in this {@code AddressBook}. + */ + public boolean removeClosedClient(Client key, Category category) throws PersonNotFoundException { + Boolean isSuccess; + + if (category.isStudent()) { + isSuccess = closedStudents.remove(key); + } else { + isSuccess = closedTutors.remove(key); + } + + if (isSuccess) { + return true; + } else { + throw new PersonNotFoundException(); + } + } + +``` +###### \java\seedu\address\model\AddressBook.java +``` java + @Override + public ObservableList getStudentList() { + return students.asObservableList(); + } + + @Override + public ObservableList getTutorList() { + return tutors.asObservableList(); + } + +``` +###### \java\seedu\address\model\ModelManager.java +``` java + @Override + public synchronized void deleteClosedClient(Client target, Category category) throws PersonNotFoundException { + addressBook.removeClosedClient(target, category); + indicateAddressBookChanged(); + } + + @Override + public void updateClient(Client target, Client editedPerson, Category category) + throws DuplicatePersonException, PersonNotFoundException { + requireAllNonNull(target, editedPerson, category); + addressBook.updatePerson(target, editedPerson, category); + indicateAddressBookChanged(); + } + + @Override + public synchronized void addTutor(Client tutor) throws DuplicatePersonException { + addressBook.addTutor(tutor); + updateFilteredTutorList(PREDICATE_SHOW_ALL_TUTORS); + indicateAddressBookChanged(); + } + + @Override + public synchronized void addStudent(Client student) throws DuplicatePersonException { + addressBook.addStudent(student); + updateFilteredStudentList(PREDICATE_SHOW_ALL_STUDENTS); + indicateAddressBookChanged(); + } + + @Override + public synchronized void addClosedTutor(Client closedTutor) throws DuplicatePersonException { + addressBook.addClosedTutor(closedTutor); + updateFilteredClosedTutorList(PREDICATE_SHOW_ALL_CLOSED_TUTORS); + indicateAddressBookChanged(); + } + + @Override + public synchronized void addClosedStudent(Client closedStudent) throws DuplicatePersonException { + addressBook.addClosedStudent(closedStudent); + updateFilteredClosedStudentList(PREDICATE_SHOW_ALL_CLOSED_STUDENTS); + indicateAddressBookChanged(); + } + +``` +###### \java\seedu\address\model\ModelManager.java +``` java + /** + * Returns an unmodifiable view of the list of {@code Client} backed by the internal list of + * {@code addressBook} + */ + @Override + public ObservableList getFilteredStudentList() { + return FXCollections.unmodifiableObservableList(sortedFilteredStudents); + } + + @Override + public void updateFilteredStudentList(Predicate predicate) { + requireNonNull(predicate); + filteredStudents.setPredicate(predicate); + indicateAddressBookChanged(); + } + + /** + * Returns an unmodifiable view of the list of {@code Client} backed by the internal list of + * {@code addressBook} + */ + @Override + public ObservableList getFilteredTutorList() { + return FXCollections.unmodifiableObservableList(sortedFilteredTutors); + } + + @Override + public void updateFilteredTutorList(Predicate predicate) { + requireNonNull(predicate); + filteredTutors.setPredicate(predicate); + indicateAddressBookChanged(); + } +``` +###### \java\seedu\address\model\person\Category.java +``` java +/** + * Represents if a Client is a student or tutor in TuitionCor. + * Guarantees: immutable; is valid as declared in {@link #isValidCategory(String)} + */ +public class Category { + + public static final String MESSAGE_CATEGORY_CONSTRAINTS = + "Client Category can only be s or t, representing student or tutor respectively"; + + /* + * Must be either s or t + */ + public static final String ADDRESS_VALIDATION_REGEX = "[st]"; + + public final String value; + + /** + * Constructs an {@code Category}. + * + * @param category A valid category. + */ + public Category(String category) { + requireNonNull(category); + checkArgument(isValidCategory(category), MESSAGE_CATEGORY_CONSTRAINTS); + this.value = category; + } + + /** + * Returns true if a given string is a valid client category. + */ + public static boolean isValidCategory(String test) { + return test.matches(ADDRESS_VALIDATION_REGEX); + } + + public boolean isStudent() { + return value.equals("s"); + } + + public boolean isTutor() { + return value.equals("t"); + } + + @Override + public String toString() { + return value; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Category // instanceof handles nulls + && this.value.equals(((Category) other).value)); // state check + } + + @Override + public int hashCode() { + return value.hashCode(); + } + +} +``` +###### \java\seedu\address\model\person\Client.java +``` java +/** + * Represents a Client in tuitionCor. + * Guarantees: details are present and not null, field values are validated, immutable. + */ +public class Client extends Person { + + private final Location location; + private final Grade grade; + private final Subject subject; + private final Category category; + private int rank = 0; + private boolean matchedGrade = false; + private boolean matchedSubject = false; + private boolean matchedLocation = false; + + /** + * Every field must be present and not null. + */ + public Client(Name name, Phone phone, Email email, Address address, Set tags, Location location, + Grade grade, Subject subject, Category category) { + super(name, phone, email, address, tags); + requireAllNonNull(location, grade, subject); + this.location = location; + this.grade = grade; + this.subject = subject; + this.category = category; + } + + public Location getLocation() { + return location; + } + + public Grade getGrade() { + return grade; + } + + public Subject getSubject() { + return subject; + } + + public Category getCategory() { + return category; + } + + public int getRank() { + return rank; + } + + public void setRank(int value) { + this.rank = value; + } + + public boolean getMatchedGrade() { + return matchedGrade; + } + + public void setMatchedGrade(boolean isMatch) { + this.matchedGrade = isMatch; + } + + public boolean getMatchedSubject() { + return matchedSubject; + } + + public void setMatchedSubject(boolean isMatch) { + this.matchedSubject = isMatch; + } + + public boolean getMatchedLocation() { + return matchedLocation; + } + + public void setMatchedLocation(boolean isMatch) { + this.matchedLocation = isMatch; + } + + @Override + public int hashCode() { + // use this method for custom fields hashing instead of implementing your own + return Objects.hash(this.getName(), this.getPhone(), this.getEmail(), this.getAddress(), + this.getTags(), location, grade, subject, category); + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append(getName()) + .append(" Phone: ") + .append(getPhone()) + .append(" Email: ") + .append(getEmail()) + .append(" Address: ") + .append(getAddress()) + .append(" Tags: "); + getTags().forEach(builder::append); + builder.append(" Location: ") + .append(getLocation()) + .append(" Grade: ") + .append(getGrade()) + .append(" Subject: ") + .append(getSubject()); + return builder.toString(); + } +} +``` +###### \java\seedu\address\model\person\Grade.java +``` java +/** + * Represents a Client's related Grade (the year of study eg. Primary 4, Secondary 3) in the TuitionCor. + * Guarantees: immutable; is valid as declared in {@link #isValidGrade(String)} + */ +public class Grade { + public static final String MESSAGE_WORD_KINDERGARTEN = "Kindergarten"; + public static final String MESSAGE_WORD_KINDERGARTEN_ALIAS = "K"; + public static final String MESSAGE_WORD_PRIMARY = "Primary"; + public static final String MESSAGE_WORD_PRIMARY_ALIAS = "P"; + public static final String MESSAGE_WORD_SECONDARY = "Secondary"; + public static final String MESSAGE_WORD_SECONDARY_ALIAS = "S"; + public static final String MESSAGE_WORD_TERTIARY = "Tertiary"; + public static final String MESSAGE_WORD_TERTIARY_ALIAS = "J"; + public static final String MESSAGE_WORD_UNIVERSITY = "University"; + public static final String MESSAGE_WORD_UNIVERSITY_ALIAS = "U"; + + private static final String KINDERGARTEN_REGEX = "kindergarten"; + private static final String KINDERGARTEN_ALIAS_REGEX = "k"; + private static final String PRIMARY_REGEX = "primary"; + private static final String PRIMARY_ALIAS_REGEX = "p"; + private static final String SECONDARY_REGEX = "secondary"; + private static final String SECONDARY_ALIAS_REGEX = "s"; + private static final String TERTIARY_REGEX = "tertiary"; + private static final String TERTIARY_ALIAS_REGEX = "j"; + private static final String UNIVERSITY_REGEX = "university"; + private static final String UNIVERSITY_ALIAS_REGEX = "u"; + + private static final String GRADE_VALIDATION_REGEX_KINDERGARTEN_FULL = "(?i)" + + MESSAGE_WORD_KINDERGARTEN + "[1-3]"; + private static final String GRADE_VALIDATION_REGEX_KINDERGARTEN_ALIAS = "(?i)" + + MESSAGE_WORD_KINDERGARTEN_ALIAS + "[1-3]"; + private static final String GRADE_VALIDATION_REGEX_PRIMARY_FULL = "(?i)" + + MESSAGE_WORD_PRIMARY + "[1-6]"; + private static final String GRADE_VALIDATION_REGEX_PRIMARY_ALIAS = "(?i)" + + MESSAGE_WORD_PRIMARY_ALIAS + "[1-6]"; + private static final String GRADE_VALIDATION_REGEX_SECONDARY_FULL = "(?i)" + + MESSAGE_WORD_SECONDARY + "[1-5]"; + private static final String GRADE_VALIDATION_REGEX_SECONDARY_ALIAS = "(?i)" + + MESSAGE_WORD_SECONDARY_ALIAS + "[1-5]"; + private static final String GRADE_VALIDATION_REGEX_TERTIARY_FULL = "(?i)" + + MESSAGE_WORD_TERTIARY + "[1-2]"; + private static final String GRADE_VALIDATION_REGEX_TERTIARY_ALIAS = "(?i)" + + MESSAGE_WORD_TERTIARY_ALIAS + "[1-2]"; + private static final String GRADE_VALIDATION_REGEX_UNIVERSITY_FULL = "(?i)" + + MESSAGE_WORD_UNIVERSITY + "[1-4]"; + private static final String GRADE_VALIDATION_REGEX_UNIVERSITY_ALIAS = "(?i)" + + MESSAGE_WORD_UNIVERSITY_ALIAS + "[1-4]"; + + public static final String MESSAGE_GRADE_CONSTRAINTS = + "Grades accepted are " + MESSAGE_WORD_KINDERGARTEN + " (1-3)" + + ", " + MESSAGE_WORD_PRIMARY + " (1-6)" + + ", " + MESSAGE_WORD_SECONDARY + " (1-5)" + + ", " + MESSAGE_WORD_TERTIARY + " (1-2)" + + ", " + MESSAGE_WORD_UNIVERSITY + " (1-4).\n" + + "Alias acceptable are " + MESSAGE_WORD_KINDERGARTEN_ALIAS + " for " + MESSAGE_WORD_KINDERGARTEN + + ", " + MESSAGE_WORD_PRIMARY_ALIAS + " for " + MESSAGE_WORD_PRIMARY + + ", " + MESSAGE_WORD_SECONDARY_ALIAS + " for " + MESSAGE_WORD_SECONDARY + + ", " + MESSAGE_WORD_TERTIARY_ALIAS + " for " + MESSAGE_WORD_TERTIARY + + ", " + MESSAGE_WORD_UNIVERSITY_ALIAS + " for " + MESSAGE_WORD_UNIVERSITY + ".\n" + + "Examples of valid input for grade: " + MESSAGE_WORD_KINDERGARTEN + "1" + + " or " + MESSAGE_WORD_KINDERGARTEN_ALIAS + "1" + ", " + + MESSAGE_WORD_TERTIARY + "2" + + " or " + MESSAGE_WORD_TERTIARY_ALIAS + "2.\n" + + "multiple grades should be typed with a single space between them " + + "in decreasing order of preferences with no repetitions"; + + private static final int levelIndex = 0; + private static final int yearIndex = 1; + + public final String value; + + public final int valueWeightage; //Stores the int value weightage of only the first grade in the list + + /** + * Constructs an {@code Grade}. + * + * @param grade A valid grade. + */ + public Grade(String grade) { + requireNonNull(grade); + checkArgument(isValidGrade(grade), MESSAGE_GRADE_CONSTRAINTS); + this.value = grade.trim().replaceAll(" +", " "); + this.valueWeightage = getGradeIndex(this.value); + } + + /** + * @return an int value base on the grade or the first grade in a string of multiple grades + */ + public static int getGradeIndex(String value) { + final String levelField = getGradeFields(value)[levelIndex].toLowerCase(); + + int tempIndex = 0; + + switch (levelField) { + case KINDERGARTEN_REGEX: + tempIndex += 1; + break; + + case KINDERGARTEN_ALIAS_REGEX: + tempIndex += 1; + break; + + case PRIMARY_REGEX: + tempIndex += 4; + break; + + case PRIMARY_ALIAS_REGEX: + tempIndex += 4; + break; + + case SECONDARY_REGEX: + tempIndex += 10; + break; + + case SECONDARY_ALIAS_REGEX: + tempIndex += 10; + break; + + case TERTIARY_REGEX: + tempIndex += 15; + break; + + case TERTIARY_ALIAS_REGEX: + tempIndex += 15; + break; + + case UNIVERSITY_REGEX: + tempIndex += 17; + break; + + case UNIVERSITY_ALIAS_REGEX: + tempIndex += 17; + break; + + default: + throw new AssertionError("It should not be possible to reach here"); + } + + tempIndex += (Integer.parseInt(getGradeFields(value)[yearIndex]) - 1); + + return tempIndex; + } + + /** + * Returns true if a given string is a valid client Grade. + */ + public static boolean isValidGradeRegex(String test) { + return test.matches(GRADE_VALIDATION_REGEX_KINDERGARTEN_ALIAS) + || test.matches(GRADE_VALIDATION_REGEX_KINDERGARTEN_FULL) + || test.matches(GRADE_VALIDATION_REGEX_PRIMARY_ALIAS) + || test.matches(GRADE_VALIDATION_REGEX_PRIMARY_FULL) + || test.matches(GRADE_VALIDATION_REGEX_SECONDARY_ALIAS) + || test.matches(GRADE_VALIDATION_REGEX_SECONDARY_FULL) + || test.matches(GRADE_VALIDATION_REGEX_TERTIARY_ALIAS) + || test.matches(GRADE_VALIDATION_REGEX_TERTIARY_FULL) + || test.matches(GRADE_VALIDATION_REGEX_UNIVERSITY_ALIAS) + || test.matches(GRADE_VALIDATION_REGEX_UNIVERSITY_FULL); + + } + + /** + * Returns true if all of the given string is a valid client Grade. + */ + public static boolean isValidGrade(String test) { + if (test.matches("\\s+")) { + return false; + } + String[] splitGrade = test.split("\\s+"); + Set isUnique = new HashSet<>(); + Set isUniqueWeightage = new HashSet<>(); + + boolean isValid = true; + for (String ss : splitGrade) { + if (isValid) { + isValid = isValidGradeRegex(ss); + isUnique.add(ss); + } + + if (isValid) { + isUniqueWeightage.add(getGradeIndex(ss)); + } + } + if (isUnique.size() != splitGrade.length || isUniqueWeightage.size() != splitGrade.length) { + isValid = false; + } + return isValid; + } + + /** + * @return gradeFields of only the first Grade in the string in terms of an array containing + * Level(Primary,Secondary..) and Year(1,2... + */ + private static String[] getGradeFields(String value) { + String[] allGrades = value.split("\\s+"); + String[] gradeFields = allGrades[0].split("(?=[\\d])"); + + checkArgument(gradeFields.length == 2, "Error in grade fields format."); + + String temp = gradeFields[levelIndex]; + gradeFields[levelIndex] = temp.trim(); + gradeFields[yearIndex].trim(); + + return gradeFields; + } + + /** + * @return an array containing all the grade weightage of the individual grades + */ + public static int[] getAllGradeWeightage(String value) { + String[] allGrades = value.split("\\s+"); + int[] allGradeWeightage = new int[allGrades.length]; + + for (int i = 0; i < allGrades.length; i++) { + allGradeWeightage[i] = getGradeIndex(allGrades[i]); + } + return allGradeWeightage; + } + + @Override + public String toString() { + return value; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Grade // instanceof handles nulls + && this.value.equals(((Grade) other).value)); // state check + } + + @Override + public int hashCode() { + return value.hashCode(); + } +} +``` +###### \java\seedu\address\model\person\Location.java +``` java +/** + * Represents a Person's available Location in the TuitionCor. + * Guarantees: immutable; is valid as declared in {@link #isValidLocation(String)} + */ +public class Location { + + public static final String MESSAGE_LOCATION_CONSTRAINTS = + "Location should only be north, south, east, west and central in decreasing order of preference without " + + "any repetitions"; + + private static final String LOCATION_VALIDATION_REGEX_NORTH = "(?i)North"; + private static final String LOCATION_VALIDATION_REGEX_SOUTH = "(?i)South"; + private static final String LOCATION_VALIDATION_REGEX_EAST = "(?i)East"; + private static final String LOCATION_VALIDATION_REGEX_WEST = "(?i)West"; + private static final String LOCATION_VALIDATION_REGEX_CENTRAL = "(?i)Central"; + + public final String value; + + /** + * Constructs a {@code Location}. + * + * @param location A valid location. + */ + public Location(String location) { + requireNonNull(location); + checkArgument(isValidLocation(location), MESSAGE_LOCATION_CONSTRAINTS); + this.value = location.trim().replaceAll(" +", " "); + } + + /** + * Returns true if a given string is a valid client Location. + */ + public static boolean isValidLocationRegex(String test) { + return test.matches(LOCATION_VALIDATION_REGEX_NORTH) || test.matches(LOCATION_VALIDATION_REGEX_EAST) + || test.matches(LOCATION_VALIDATION_REGEX_SOUTH) + || test.matches(LOCATION_VALIDATION_REGEX_WEST) + || test.matches(LOCATION_VALIDATION_REGEX_CENTRAL); + } + + /** + * Returns true if all of the given string is a valid client Location. + */ + public static boolean isValidLocation(String test) { + if (test.matches("\\s+")) { + return false; + } + String[] splitLocation = test.split("\\s+"); + Set isUnique = new HashSet<>(); + boolean isValid = true; + for (String ss : splitLocation) { + if (isValid) { + isValid = isValidLocationRegex(ss); + isUnique.add(ss); + } + } + if (isUnique.size() != splitLocation.length) { + isValid = false; + } + return isValid; + } + + @Override + public String toString() { + return value; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Location // instanceof handles nulls + && this.value.equals(((Location) other).value)); // state check + } + + @Override + public int hashCode() { + return value.hashCode(); + } + +} +``` +###### \java\seedu\address\model\person\Subject.java +``` java +/** + * Represents a Person's related Subject in the TuitionCor. + * Guarantees: immutable; is valid as declared in {@link #isValidSubject(String)} + */ +public class Subject { + + public static final String MESSAGE_SUBJECT_CONSTRAINTS = + "Subjects can take any value and should not be blank"; + + /* + * The first character of the Subject must not be a whitespace, + * otherwise " " (a blank string) becomes a valid input. + */ + public static final String ADDRESS_VALIDATION_REGEX = "[^\\s].*"; + + public final String value; + + /** + * Constructs an {@code Subject}. + * + * @param subject A valid subject. + */ + public Subject(String subject) { + requireNonNull(subject); + checkArgument(isValidSubject(subject), MESSAGE_SUBJECT_CONSTRAINTS); + this.value = subject.trim().replaceAll(" +", " "); + } + + /** + * Returns true if a given string is a valid person Subject. + */ + public static boolean isValidSubject(String test) { + return test.matches(ADDRESS_VALIDATION_REGEX); + } + + @Override + public String toString() { + return value; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Subject // instanceof handles nulls + && this.value.equals(((Subject) other).value)); // state check + } + + @Override + public int hashCode() { + return value.hashCode(); + } +} +``` +###### \java\seedu\address\model\person\UniqueClientList.java +``` java +/** + * A list of client that enforces uniqueness between its elements and does not allow nulls. + * + * Supports a minimal set of list operations. + * + * @see Client#equals(Object) + * @see CollectionUtil#elementsAreUnique(Collection) + */ +public class UniqueClientList implements Iterable { + + private final ObservableList internalList = FXCollections.observableArrayList(); + + /** + * Returns true if the list contains an equivalent client as the given argument. + */ + public boolean contains(Client toCheck) { + requireNonNull(toCheck); + return internalList.contains(toCheck); + } + + /** + * Adds a client to the active list. + * + * @throws DuplicatePersonException if the client to add is a duplicate of an existing client in the list. + */ + public void add(Client toAdd, UniqueClientList closedList) throws DuplicatePersonException { + requireNonNull(toAdd); + if (contains(toAdd) || closedList.contains(toAdd)) { + throw new DuplicatePersonException(); + } + internalList.add(toAdd); + } + + /** + * Adds a client to TuitionCor. + * + * @throws AssertionError as it's impossible to have a duplicate given that we have checked for duplication + * before adding it into active list. + */ + public void add(Client toAdd) throws AssertionError { + requireNonNull(toAdd); + if (contains(toAdd)) { + throw new AssertionError("It's impossible to have a duplicate person here"); + } + internalList.add(toAdd); + } + + /** + * Replaces the client {@code target} in the list with {@code editedClient}. + * + * @throws DuplicatePersonException if the replacement is equivalent to another existing client in the list. + * @throws PersonNotFoundException if {@code target} could not be found in the list. + * + * Returns true if success. + */ + public Boolean setClient(Client target, Client editedClient, UniqueClientList closedList) + throws DuplicatePersonException, PersonNotFoundException { + requireNonNull(editedClient); + + int index = internalList.indexOf(target); + if (index == -1) { + throw new PersonNotFoundException(); + } + + if (!target.equals(editedClient) && (internalList.contains(editedClient) + || closedList.contains(editedClient))) { + throw new DuplicatePersonException(); + } + + internalList.set(index, editedClient); + return true; + } + + /** + * Removes the equivalent person from the list. + * + * @throws PersonNotFoundException if no such person could be found in the list. + */ + public boolean remove(Client toRemove) throws PersonNotFoundException { + requireNonNull(toRemove); + final boolean clientFoundAndDeleted = internalList.remove(toRemove); + if (!clientFoundAndDeleted) { + throw new PersonNotFoundException(); + } + return clientFoundAndDeleted; + } + + public void setClients(UniqueClientList replacement) { + this.internalList.setAll(replacement.internalList); + } + + public void setClients(List clients) throws DuplicatePersonException { + requireAllNonNull(clients); + final UniqueClientList replacement = new UniqueClientList(); + for (final Client client : clients) { + replacement.add(client); + } + setClients(replacement); + } + + /** + * Returns the backing list as an unmodifiable {@code ObservableList}. + */ + public ObservableList asObservableList() { + return FXCollections.unmodifiableObservableList(internalList); + } + + @Override + public Iterator iterator() { + return internalList.iterator(); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof UniqueClientList // instanceof handles nulls + && this.internalList.equals(((UniqueClientList) other).internalList)); + } + + @Override + public int hashCode() { + return internalList.hashCode(); + } +} +``` +###### \java\seedu\address\storage\XmlAdaptedClient.java +``` java +/** + * JAXB-friendly version of the Client for tutors. + */ +public class XmlAdaptedClient { + + public static final String MISSING_FIELD_MESSAGE_FORMAT = "Person's %s field is missing!"; + + @XmlElement(required = true) + private String name; + @XmlElement(required = true) + private String phone; + @XmlElement(required = true) + private String email; + @XmlElement(required = true) + private String address; + @XmlElement(required = true) + private String location; + @XmlElement(required = true) + private String grade; + @XmlElement(required = true) + private String subject; + @XmlElement(required = true) + private String category; + + + @XmlElement + private List tagged = new ArrayList<>(); + + /** + * Constructs an XmlAdaptedClient. + * This is the no-arg constructor that is required by JAXB. + */ + public XmlAdaptedClient() {} + + /** + * Constructs an {@code XmlAdaptedClient} with the given person details. + */ + public XmlAdaptedClient(String name, String phone, String email, String address, List tagged, + String location, String grade, String subject, String category) { + this.name = name; + this.phone = phone; + this.email = email; + this.address = address; + if (tagged != null) { + this.tagged = new ArrayList<>(tagged); + } + this.location = location; + this.grade = grade; + this.subject = subject; + this.category = category; + } + + /** + * Converts a given Client into this class for JAXB use. + * + * @param source future changes to this will not affect the created XmlAdaptedClient + */ + public XmlAdaptedClient(Client source) { + name = source.getName().fullName; + phone = source.getPhone().value; + email = source.getEmail().value; + address = source.getAddress().value; + tagged = new ArrayList<>(); + for (Tag tag : source.getTags()) { + tagged.add(new XmlAdaptedTag(tag)); + } + location = source.getLocation().value; + grade = source.getGrade().value; + subject = source.getSubject().value; + category = source.getCategory().value; + } + + /** + * Converts this jaxb-friendly adapted client object into the model's Client object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted person + */ + public Client toModelType() throws IllegalValueException { + final List personTags = new ArrayList<>(); + for (XmlAdaptedTag tag : tagged) { + personTags.add(tag.toModelType()); + } + + if (this.name == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Name.class.getSimpleName())); + } + if (!Name.isValidName(this.name)) { + throw new IllegalValueException(Name.MESSAGE_NAME_CONSTRAINTS); + } + final Name name = new Name(this.name); + + if (this.phone == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Phone.class.getSimpleName())); + } + if (!Phone.isValidPhone(this.phone)) { + throw new IllegalValueException(Phone.MESSAGE_PHONE_CONSTRAINTS); + } + final Phone phone = new Phone(this.phone); + + if (this.email == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Email.class.getSimpleName())); + } + if (!Email.isValidEmail(this.email)) { + throw new IllegalValueException(Email.MESSAGE_EMAIL_CONSTRAINTS); + } + final Email email = new Email(this.email); + + if (this.address == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Address.class.getSimpleName())); + } + if (!Address.isValidAddress(this.address)) { + throw new IllegalValueException(Address.MESSAGE_ADDRESS_CONSTRAINTS); + } + final Address address = new Address(this.address); + + final Set tags = new HashSet<>(personTags); + + if (this.location == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + Location.class.getSimpleName())); + } + if (!Location.isValidLocation(this.location)) { + throw new IllegalValueException(Location.MESSAGE_LOCATION_CONSTRAINTS); + } + final Location location = new Location(this.location); + + if (this.grade == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Grade.class.getSimpleName())); + } + if (!Grade.isValidGrade(this.grade)) { + throw new IllegalValueException(Grade.MESSAGE_GRADE_CONSTRAINTS); + } + final Grade grade = new Grade(this.grade); + + if (this.subject == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Subject.class.getSimpleName())); + } + if (!Subject.isValidSubject(this.subject)) { + throw new IllegalValueException(Subject.MESSAGE_SUBJECT_CONSTRAINTS); + } + final Subject subject = new Subject(this.subject); + + if (this.category == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + Category.class.getSimpleName())); + } + if (!Category.isValidCategory(this.category)) { + throw new IllegalValueException(Subject.MESSAGE_SUBJECT_CONSTRAINTS); + } + final Category category = new Category(this.category); + + + return new Client(name, phone, email, address, tags, location, grade, subject, category); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof XmlAdaptedClient)) { + return false; + } + + XmlAdaptedClient otherClient = (XmlAdaptedClient) other; + return Objects.equals(name, otherClient.name) + && Objects.equals(phone, otherClient.phone) + && Objects.equals(email, otherClient.email) + && Objects.equals(address, otherClient.address) + && tagged.equals(otherClient.tagged) + && Objects.equals(location, otherClient.location) + && Objects.equals(subject, otherClient.subject) + && Objects.equals(grade, otherClient.grade) + && Objects.equals(category, otherClient.category); + + } +} +``` +###### \java\seedu\address\ui\ClientCard.java +``` java +/** + * An UI component that displays information of a {@code Client}. + */ +public class ClientCard extends UiPart { + + private static final String FXML = "ClientListCard.fxml"; + + private static final String[] TAGS_COLOUR_STYLES = + { "red" , "blue" , "green" , "yellow" , "purple" , "lightpink" , "gold" , "wheat" }; + + private static final String MATCH_COLOUR_STYLE = "orange"; + + private static final String UNMATCH_COLOUR_STYLE = "noFill"; + /** + * 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 Client client; + + @FXML + private HBox cardPane; + @FXML + private Label name; + @FXML + private Label id; + @FXML + private Label phone; + @FXML + private Label address; + @FXML + private Label email; + @FXML + private Label places; + @FXML + private Label grades; + @FXML + private Label subjects; + @FXML + private FlowPane tags; + + + public ClientCard(Client client, int displayedIndex) { + super(FXML); + this.client = client; + id.setText(displayedIndex + ". "); + name.setText(client.getName().fullName); + phone.setText(client.getPhone().value); + address.setText(client.getAddress().value); + email.setText(client.getEmail().value); + intplaces(client); + intGrades(client); + intSubjects(client); + intTags(client); + } + + /** + *@author olimhc-reused + *Reused from https://github.com/se-edu/addressbook-level4/pull/798/commits with minor modification + * Initialises tags + * @param client + */ + private void intTags(Client client) { + client.getTags().forEach(tag -> { + Label newLabel = new Label(tag.tagName); + newLabel.getStyleClass().add(getColour(tag.tagName)); + tags.getChildren().add(newLabel); + }); + } + +``` diff --git a/collated/test/Zhu-Jiahui.md b/collated/test/Zhu-Jiahui.md new file mode 100644 index 000000000000..5c439cc277e5 --- /dev/null +++ b/collated/test/Zhu-Jiahui.md @@ -0,0 +1,480 @@ +# Zhu-Jiahui +###### \java\seedu\address\logic\commands\MatchCommandTest.java +``` java +/** + * Contains integration tests (interaction with the Model) for {@code MatchCommand}. + */ +public class MatchCommandTest { + private Model model; + private Model expectedStudentModel; + private Model expectedTutorModel; + private MatchCommand matchCommandForStudentList; + private MatchCommand matchCommandForTutorList; + + private final Category studentCategory = new Category("s"); + private final Category tutorCategory = new Category("t"); + + @Before + public void setup() { + model = new ModelManager(getUnmatchedAddressBook(), new UserPrefs()); + expectedTutorModel = new ModelManager(getMatchedTutorAddressBook(), new UserPrefs()); + expectedStudentModel = new ModelManager(getMatchedStudentAddressBook(), new UserPrefs()); + + matchCommandForStudentList = new MatchCommand(INDEX_FIRST_PERSON, studentCategory); + matchCommandForStudentList.setData(model, new CommandHistory(), new UndoRedoStack()); + matchCommandForTutorList = new MatchCommand(INDEX_FIRST_PERSON, tutorCategory); + matchCommandForTutorList.setData(model, new CommandHistory(), new UndoRedoStack()); + } + + @Test + public void executeMatchCommand() { + assertCommandSuccessForStudentList(matchCommandForStudentList, + MatchCommand.getMessageForClientListShownSummary(1, 2)); + assertCommandSuccessForTutorList(matchCommandForTutorList, + MatchCommand.getMessageForClientListShownSummary(3, 1)); + } + + /** + * Asserts that the code is successfully executed. + * Asserts the command feedback is equal to expectedMessage. + * Asserts actual FilteredList is equal to the expected FilteredList + * Asserts the {@code AddressBook} in model remains the same after executing the {@code command} + */ + private void assertCommandSuccessForStudentList(MatchCommand command, String expectedMessage) { + AddressBook expectedAddressBook = new AddressBook(model.getAddressBook()); + try { + CommandResult commandResult = command.execute(); + assertEquals(expectedMessage, commandResult.feedbackToUser); + assertEquals(expectedStudentModel.getFilteredStudentList(), model.getFilteredStudentList()); + assertEquals(expectedAddressBook, model.getAddressBook()); + } catch (CommandException ce) { + throw new AssertionError("Execution of command should not fail.", ce); + } + } + + /** + * Asserts that the code is successfully executed. + * Asserts the command feedback is equal to expectedMessage. + * Asserts actual FilteredList is equal to the expected FilteredList + * Asserts the {@code AddressBook} in model remains the same after executing the {@code command} + */ + private void assertCommandSuccessForTutorList(MatchCommand command, String expectedMessage) { + AddressBook expectedAddressBook = new AddressBook(model.getAddressBook()); + try { + CommandResult commandResult = command.execute(); + assertEquals(expectedMessage, commandResult.feedbackToUser); + assertEquals(expectedTutorModel.getFilteredTutorList(), model.getFilteredTutorList()); + assertEquals(expectedAddressBook, model.getAddressBook()); + } catch (CommandException ce) { + throw new AssertionError("Execution of command should not fail.", ce); + } + } + @Test + public void equals() { + + MatchCommand matchFirstCommand = new MatchCommand(INDEX_FIRST_PERSON, CATEGORY_FIRST_PERSON); + MatchCommand matchSecondCommand = new MatchCommand(INDEX_SECOND_PERSON, CATEGORY_SECOND_PERSON); + + // same object -> returns true + assertTrue(matchFirstCommand.equals(matchFirstCommand)); + + // same values -> returns true + MatchCommand matchFirstCommandCopy = new MatchCommand(INDEX_FIRST_PERSON, CATEGORY_SECOND_PERSON); + assertTrue(matchFirstCommand.equals(matchFirstCommandCopy)); + + // different types -> returns false + assertFalse(matchFirstCommand.equals(1)); + + // null -> returns false + assertFalse(matchFirstCommand.equals(null)); + + // different person -> returns false + assertFalse(matchFirstCommand.equals(matchSecondCommand)); + } + + /** + * Parses {@code userInput} into a {@code FindCommand}. + */ + private MatchCommand prepareCommand(Index index, Category category) { + MatchCommand command = + new MatchCommand(index, category); + command.setData(model, new CommandHistory(), new UndoRedoStack()); + return command; + } + +} +``` +###### \java\seedu\address\logic\parser\MatchCommandParserTest.java +``` java +public class MatchCommandParserTest { + + private MatchCommandParser parser = new MatchCommandParser(); + + @Test + public void parse_validArgs_returnsMatchCommand() { + assertParseSuccess(parser, "1 c/s", + new MatchCommand(INDEX_FIRST_PERSON, new Category("s"))); + } + + @Test + public void parse_invalidArgs_throwsParseException() { + // Missing index + assertParseFailure(parser, "c/s", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, MatchCommand.MESSAGE_USAGE)); + // Missing category + assertParseFailure(parser, "1", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, MatchCommand.MESSAGE_USAGE)); + } +} +``` +###### \java\seedu\address\model\person\MatchContainsKeywordsPredicateTest.java +``` java + +public class MatchContainsKeywordsPredicateTest { + + @Test + public void equals() { + Client firstTestClient = BENSON; + Client secondTestClient = CARL; + + MatchContainsKeywordsPredicate firstPredicate = + new MatchContainsKeywordsPredicate(firstTestClient); + MatchContainsKeywordsPredicate secondPredicate = + new MatchContainsKeywordsPredicate(secondTestClient); + + // same object -> returns true + assertTrue(firstPredicate.equals(firstPredicate)); + + // same values -> returns true + MatchContainsKeywordsPredicate firstPredicateCopy = + new MatchContainsKeywordsPredicate(firstTestClient); + assertTrue(firstPredicate.equals(firstPredicateCopy)); + + // different types -> returns false + assertFalse(firstPredicate.equals(1)); + + // null -> returns false + assertFalse(firstPredicate.equals(null)); + + // different person -> returns false + assertFalse(firstPredicate.equals(secondPredicate)); + } + + @Test + public void test_matchContainsKeywords_returnsTrue() { + + //all matches + MatchContainsKeywordsPredicate predicate = + new MatchContainsKeywordsPredicate(BENSON); + assertTrue(predicate.test(new ClientBuilder().withName("Benson Meier") + .withAddress("311, Clementi Ave 2, #02-25") + .withEmail("johnd@example.com").withPhone("98765432") + .withTags("owesMoney", "friends").withLocation("north").withGrade("s2").withSubject("physics") + .withCategory("s").build())); + + //some matches + predicate = new MatchContainsKeywordsPredicate(BENSON); + assertTrue(predicate.test(new ClientBuilder().withName("Benson Meier") + .withAddress("311, Clementi Ave 2, #02-25") + .withEmail("johnd@example.com").withPhone("98765431") + .withTags("owesMoney", "friends").withLocation("south").withGrade("s2").withSubject("physics") + .withCategory("s").build())); + + //location, grade subject all matches but the rest of the attributes dont match + predicate = new MatchContainsKeywordsPredicate(BENSON); + assertTrue(predicate.test(new ClientBuilder().withName("Alice Pauline") + .withAddress("123, Jurong West Ave 6, #08-111").withEmail("alice@example.com") + .withPhone("85355255") + .withTags("friends").withLocation("north").withGrade("s2").withSubject("physics") + .withCategory("s").build())); + + //1 match + predicate = new MatchContainsKeywordsPredicate(BENSON); + assertTrue(predicate.test(new ClientBuilder().withName("Benson Meier").build())); + + } + + @Test + public void test_matchDoesNotContainKeywords_returnsFalse() { + + //grade, subject and location no match + MatchContainsKeywordsPredicate predicate = new MatchContainsKeywordsPredicate(BENSON); + assertFalse(predicate.test(new ClientBuilder().withName("Benson") + .withAddress("311, Clementi Ave 2, #02-25") + .withEmail("johnd@example.com").withPhone("98765432") + .withTags("owesMoney", "friends").withLocation("south").withGrade("s1").withSubject("chemistry") + .withCategory("s").build())); + + } +} + +``` +###### \java\seedu\address\model\person\MatchContainsPersonsPredicateTest.java +``` java +public class MatchContainsPersonsPredicateTest { + + @Test + public void equals() { + Client firstTestClient = BENSON; + Client secondTestClient = CARL; + + MatchContainsPersonsPredicate firstPredicate = + new MatchContainsPersonsPredicate(firstTestClient); + MatchContainsPersonsPredicate secondPredicate = + new MatchContainsPersonsPredicate(secondTestClient); + + // same object -> returns true + assertTrue(firstPredicate.equals(firstPredicate)); + + // same values -> returns true + MatchContainsPersonsPredicate firstPredicateCopy = + new MatchContainsPersonsPredicate(firstTestClient); + assertTrue(firstPredicate.equals(firstPredicateCopy)); + + // different types -> returns false + assertFalse(firstPredicate.equals(1)); + + // null -> returns false + assertFalse(firstPredicate.equals(null)); + + // different person -> returns false + assertFalse(firstPredicate.equals(secondPredicate)); + } + + @Test + public void test_matchContainsPersons_returnsTrue() { + + MatchContainsPersonsPredicate predicate = + new MatchContainsPersonsPredicate(BENSON); + assertTrue(predicate.test(new ClientBuilder().withName("Benson Meier") + .withAddress("311, Clementi Ave 2, #02-25") + .withEmail("johnd@example.com").withPhone("98765432") + .withTags("owesMoney", "friends").withLocation("north").withGrade("s2").withSubject("physics") + .withCategory("s").build())); + + } + + @Test + public void test_matchDoesNotContainPersons_returnsFalse() { + + MatchContainsPersonsPredicate predicate = + new MatchContainsPersonsPredicate(BENSON); + assertFalse(predicate.test(new ClientBuilder().withName("Benson") + .withAddress("311, Clementi Ave 2, #02-25") + .withEmail("johnd@example.com").withPhone("98765432") + .withTags("owesMoney", "friends").withLocation("north").withGrade("s2").withSubject("physics") + .withCategory("s").build())); + } +} +``` +###### \java\seedu\address\testutil\MatchedClients.java +``` java +/** + * A utility class containing a list of {@code Clients} objects to be used in tests. + */ +public class MatchedClients { + //Students + public static final Client ALICE = new ClientBuilder().withName("Alice Pauline") + .withAddress("123, Jurong West Ave 6, #08-111").withEmail("alice@example.com") + .withPhone("85355255") + .withTags("friends").withLocation("north").withGrade("k1").withSubject("math").withCategory("s").build(); + public static final Client BENSON = new ClientBuilder().withName("Benson Meier") + .withAddress("311, Clementi Ave 2, #02-25") + .withEmail("johnd@example.com").withPhone("98765432") + .withTags("owesMoney", "friends").withLocation("north").withGrade("s2").withSubject("physics") + .withCategory("s").build(); + public static final Client CARL = new ClientBuilder().withName("Carl Kurz").withPhone("95352563") + .withEmail("heinz@example.com").withAddress("wall street").withLocation("south").withGrade("j1") + .withSubject("physics").withCategory("s").build(); + public static final Client DANIEL = new ClientBuilder().withName("Daniel Meier").withPhone("87652533") + .withEmail("cornelia@example.com").withAddress("10th street").withLocation("east").withGrade("primary6") + .withSubject("english").withCategory("s").build(); + public static final Client ELLE = new ClientBuilder().withName("Elle Meyer").withPhone("9482224") + .withEmail("werner@example.com").withAddress("michegan ave").withLocation("west").withGrade("p3") + .withSubject("math").withCategory("s").build(); + public static final Client FIONA = new ClientBuilder().withName("Fiona Kunz").withPhone("9482427") + .withEmail("lydia@example.com").withAddress("little tokyo").withLocation("north").withGrade("secondary1") + .withSubject("physics").withCategory("s").build(); + public static final Client GEORGE = new ClientBuilder().withName("George Best").withPhone("9482442") + .withEmail("anna@example.com").withAddress("4th street").withLocation("west").withGrade("j2") + .withSubject("chemistry").withCategory("s").build(); + + //Tutors + public static final Client ANDREW = new ClientBuilder().withName("ANDREW LIM").withPhone("5212533") + .withEmail("andrew@example.com").withAddress("Andrew street").withLocation("east").withGrade("primary2") + .withSubject("english").withCategory("t").build(); + public static final Client EDISON = new ClientBuilder().withName("EDISON").withPhone("2313224") + .withEmail("EDISON@example.com").withAddress("EDISON ave").withLocation("west").withGrade("j2") + .withSubject("math").withCategory("t").build(); + public static final Client FLOWER = new ClientBuilder().withName("Flower").withPhone("2182427") + .withEmail("flowerislife@example.com").withAddress("little flower").withLocation("central").withGrade("k1") + .withSubject("physics").withCategory("t").build(); + public static final Client GERRARD = new ClientBuilder().withName("GERRARD").withPhone("8321242") + .withEmail("liverpool@example.com").withAddress("Anfield").withLocation("west").withGrade("u4") + .withSubject("chemistry").withCategory("t").build(); + + // Manually added + public static final Client HOON = new ClientBuilder().withName("Hoon Meier").withPhone("8482424") + .withEmail("stefan@example.com").withAddress("little india").withLocation("north").withGrade("s1") + .withSubject("chemistry").withCategory("s").build(); + public static final Client IDA = new ClientBuilder().withName("Ida Mueller").withPhone("8482131") + .withEmail("hans@example.com").withAddress("chicago ave").withLocation("north").withGrade("k2") + .withSubject("math").withCategory("s").build(); + + // Manually added - Person's details found in {@code CommandTestUtil} + public static final Client AMY = new ClientBuilder().withName(VALID_NAME_AMY).withPhone(VALID_PHONE_AMY) + .withEmail(VALID_EMAIL_AMY).withAddress(VALID_ADDRESS_AMY).withTags(VALID_TAG_FRIEND) + .withLocation(VALID_LOCATION_AMY).withGrade(VALID_GRADE_AMY).withSubject(VALID_SUBJECT_AMY) + .withCategory(VALID_CATEGORY_AMY).build(); + public static final Client BOB = new ClientBuilder().withName(VALID_NAME_BOB).withPhone(VALID_PHONE_BOB) + .withEmail(VALID_EMAIL_BOB).withAddress(VALID_ADDRESS_BOB).withTags(VALID_TAG_HUSBAND, VALID_TAG_FRIEND) + .withLocation(VALID_LOCATION_BOB).withGrade(VALID_GRADE_BOB).withSubject(VALID_SUBJECT_BOB) + .withCategory(VALID_CATEGORY_BOB).build(); + + public static final String KEYWORD_MATCHING_MEIER = "Meier"; // A keyword that matches MEIER + + private MatchedClients() {} // prevents instantiation + + /** + * Returns an {@code AddressBook} with all the typical clients sorted by name. + */ + public static AddressBook getMatchedStudentAddressBook() { + AddressBook ab = new AddressBook(); + for (Client client : getMatchedStudent()) { + try { + ab.addClient(client); + } catch (DuplicatePersonException e) { + throw new AssertionError("not possible"); + } + } + return ab; + } + + /** + * Returns an {@code AddressBook} with all the typical clients sorted by name. + */ + public static AddressBook getMatchedTutorAddressBook() { + AddressBook ab = new AddressBook(); + for (Client client : getMatchedTutor()) { + try { + ab.addClient(client); + } catch (DuplicatePersonException e) { + throw new AssertionError("not possible"); + } + } + return ab; + } + + public static List getMatchedStudent() { + return new ArrayList<>(Arrays.asList(ALICE)); + } + + public static List getMatchedTutor() { + return new ArrayList<>(Arrays.asList(EDISON)); + } + + + +} +``` +###### \java\seedu\address\testutil\TypicalCategories.java +``` java +/** + * A utility class containing a list of {@code Index} objects to be used in tests. + */ +public class TypicalCategories { + public static final Category CATEGORY_FIRST_PERSON = TypicalClients.getTypicalStudents().get(0).getCategory(); + public static final Category CATEGORY_SECOND_PERSON = TypicalClients.getTypicalStudents().get(1).getCategory(); + public static final Category CATEGORY_THIRD_PERSON = TypicalClients.getTypicalStudents().get(2).getCategory(); +} +``` +###### \java\seedu\address\testutil\UnmatchedClients.java +``` java +/** + * A utility class containing a list of {@code Clients} objects to be used in tests. + */ +public class UnmatchedClients { + //Students + public static final Client ALICE = new ClientBuilder().withName("Alice Pauline") + .withAddress("123, Jurong West Ave 6, #08-111").withEmail("alice@example.com") + .withPhone("85355255") + .withTags("friends").withLocation("north").withGrade("k1").withSubject("math").withCategory("s").build(); + public static final Client BENSON = new ClientBuilder().withName("Benson Meier") + .withAddress("311, Clementi Ave 2, #02-25") + .withEmail("johnd@example.com").withPhone("98765432") + .withTags("owesMoney", "friends").withLocation("north").withGrade("s2").withSubject("physics") + .withCategory("s").build(); + public static final Client CARL = new ClientBuilder().withName("Carl Kurz").withPhone("95352563") + .withEmail("heinz@example.com").withAddress("wall street").withLocation("south").withGrade("j1") + .withSubject("physics").withCategory("s").build(); + public static final Client DANIEL = new ClientBuilder().withName("Daniel Meier").withPhone("87652533") + .withEmail("cornelia@example.com").withAddress("10th street").withLocation("east").withGrade("primary6") + .withSubject("english").withCategory("s").build(); + public static final Client ELLE = new ClientBuilder().withName("Elle Meyer").withPhone("9482224") + .withEmail("werner@example.com").withAddress("michegan ave").withLocation("west").withGrade("p3") + .withSubject("math").withCategory("s").build(); + public static final Client FIONA = new ClientBuilder().withName("Fiona Kunz").withPhone("9482427") + .withEmail("lydia@example.com").withAddress("little tokyo").withLocation("north").withGrade("secondary1") + .withSubject("physics").withCategory("s").build(); + public static final Client GEORGE = new ClientBuilder().withName("George Best").withPhone("9482442") + .withEmail("anna@example.com").withAddress("4th street").withLocation("west").withGrade("j2") + .withSubject("chemistry").withCategory("s").build(); + + //Tutors + public static final Client ANDREW = new ClientBuilder().withName("ANDREW LIM").withPhone("5212533") + .withEmail("andrew@example.com").withAddress("Andrew street").withLocation("east").withGrade("primary2") + .withSubject("english").withCategory("t").build(); + public static final Client EDISON = new ClientBuilder().withName("EDISON").withPhone("2313224") + .withEmail("EDISON@example.com").withAddress("EDISON ave").withLocation("west").withGrade("j2") + .withSubject("math").withCategory("t").build(); + public static final Client FLOWER = new ClientBuilder().withName("Flower").withPhone("2182427") + .withEmail("flowerislife@example.com").withAddress("little flower").withLocation("central").withGrade("k1") + .withSubject("physics").withCategory("t").build(); + public static final Client GERRARD = new ClientBuilder().withName("GERRARD").withPhone("8321242") + .withEmail("liverpool@example.com").withAddress("Anfield").withLocation("west").withGrade("u4") + .withSubject("chemistry").withCategory("t").build(); + + // Manually added + public static final Client HOON = new ClientBuilder().withName("Hoon Meier").withPhone("8482424") + .withEmail("stefan@example.com").withAddress("little india").withLocation("north").withGrade("s1") + .withSubject("chemistry").withCategory("s").build(); + public static final Client IDA = new ClientBuilder().withName("Ida Mueller").withPhone("8482131") + .withEmail("hans@example.com").withAddress("chicago ave").withLocation("north").withGrade("k2") + .withSubject("math").withCategory("s").build(); + + // Manually added - Person's details found in {@code CommandTestUtil} + public static final Client AMY = new ClientBuilder().withName(VALID_NAME_AMY).withPhone(VALID_PHONE_AMY) + .withEmail(VALID_EMAIL_AMY).withAddress(VALID_ADDRESS_AMY).withTags(VALID_TAG_FRIEND) + .withLocation(VALID_LOCATION_AMY).withGrade(VALID_GRADE_AMY).withSubject(VALID_SUBJECT_AMY) + .withCategory(VALID_CATEGORY_AMY).build(); + public static final Client BOB = new ClientBuilder().withName(VALID_NAME_BOB).withPhone(VALID_PHONE_BOB) + .withEmail(VALID_EMAIL_BOB).withAddress(VALID_ADDRESS_BOB).withTags(VALID_TAG_HUSBAND, VALID_TAG_FRIEND) + .withLocation(VALID_LOCATION_BOB).withGrade(VALID_GRADE_BOB).withSubject(VALID_SUBJECT_BOB) + .withCategory(VALID_CATEGORY_BOB).build(); + + public static final String KEYWORD_MATCHING_MEIER = "Meier"; // A keyword that matches MEIER + + private UnmatchedClients() {} // prevents instantiation + + /** + * Returns an {@code AddressBook} with all the typical clients. + */ + public static AddressBook getUnmatchedAddressBook() { + AddressBook ab = new AddressBook(); + for (Client client : getUnmachedClients()) { + try { + ab.addClient(client); + } catch (DuplicatePersonException e) { + throw new AssertionError("not possible"); + } + } + return ab; + } + + public static List getUnmachedClients() { + return new ArrayList<>(Arrays.asList(ALICE, BENSON, CARL, DANIEL, ELLE, FIONA, GEORGE, + ANDREW, EDISON, FLOWER, GERRARD)); + } +} + +``` diff --git a/collated/test/olimhc.md b/collated/test/olimhc.md new file mode 100644 index 000000000000..93fa0da333f9 --- /dev/null +++ b/collated/test/olimhc.md @@ -0,0 +1,925 @@ +# olimhc +###### \java\seedu\address\logic\commands\CloseCommandTest.java +``` java +/** + * Contains integration tests (interaction with the Model) for {@code CloseCommandTest}. + */ +public class CloseCommandTest { + private static ListPanelController listPanelController = ListPanelController.getInstance(); + + private Model model = new ModelManager(getTypicalAddressBookNew(), new UserPrefs()); + + private final Category studentCategory = new Category("s"); + private final Category tutorCategory = new Category("t"); + + /** + * Ensure that the list is always displaying active clients. + */ + @Before + public void setUp() { + listPanelController.setDefault(); + } + + /** + * Tests if a particular tutor is closed properly. + * @throws Exception + */ + @Test + public void execute_closeClientTutorFilteredList_success() throws Exception { + CloseCommand closeCommand = prepareCommand(Index.fromZeroBased(0), tutorCategory); + Model expectedModel = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs()); + expectedModel.deleteClient(expectedModel.getFilteredTutorList().get(0), tutorCategory); + + String expectedMessage = String.format(CloseCommand.MESSAGE_CLOSE_TUTOR_SUCCESS, + model.getFilteredTutorList().get(0)); + + expectedModel.addClosedTutor(model.getFilteredTutorList().get(0)); + assertCommandSuccess(closeCommand, model, expectedMessage, expectedModel); + } + + /** + * Tests if a particular student is closed properly. + * @throws Exception + */ + @Test + public void execute_closeClientStudentFilteredList_success() throws Exception { + CloseCommand closeCommand = prepareCommand(Index.fromZeroBased(0), studentCategory); + Model expectedModel = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs()); + expectedModel.deleteClient(expectedModel.getFilteredStudentList().get(0), studentCategory); + + String expectedMessage = String.format(CloseCommand.MESSAGE_CLOSE_STUDENT_SUCCESS, + model.getFilteredStudentList().get(0)); + + expectedModel.addClosedStudent(model.getFilteredStudentList().get(0)); + assertCommandSuccess(closeCommand, model, expectedMessage, expectedModel); + } + + @Test + public void assertsRestoreNotAvailableInClosedList() throws Exception { + ListPanelController listPanelController = ListPanelController.getInstance(); + listPanelController.switchDisplay(); + + CloseCommand closeCommand = prepareCommand(Index.fromZeroBased(0), studentCategory); + try { + closeCommand.execute(); + } catch (CommandNotAvailableInClosedViewException cna) { + assertsRestoreNotAvailableInClosedList(); + } + } + + /** + * Returns an {@code CloseCommand} with parameters {@code index} and {@code category} + */ + private CloseCommand prepareCommand(Index index, Category category) { + CloseCommand closeCommand = new CloseCommand(index, category); + closeCommand.setData(model, new CommandHistory(), new UndoRedoStack()); + return closeCommand; + } +} +``` +###### \java\seedu\address\logic\commands\RestoreCommandTest.java +``` java +/** + * Contains integration tests (interaction with the Model) for {@code RestoreCommandTest}. + */ +public class RestoreCommandTest { + private static ListPanelController listPanelController; + + private Model model = new ModelManager(getTypicalClosedClientsAddressBook(), new UserPrefs()); + + private final Category studentCategory = new Category("s"); + private final Category tutorCategory = new Category("t"); + + /** + * Ensure display is displaying closed client list. + */ + @BeforeClass + public static void setup() { + listPanelController = ListPanelController.getInstance(); + if (ListPanelController.isCurrentDisplayActiveList()) { + listPanelController.switchDisplay(); + } + } + + /** + *Ensure display is at active client list after this class test. + */ + @AfterClass + public static void tearDownAfterClass() { + listPanelController.setDefault(); + } + + /** + * Tests if a particular tutor is restored properly. + * @throws Exception + */ + @Test + public void execute_restoreClientTutorFilteredList_success() throws Exception { + RestoreCommand restoreCommand = prepareCommand(Index.fromZeroBased(0), tutorCategory); + Model expectedModel = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs()); + expectedModel.deleteClosedClient(expectedModel.getFilteredClosedTutorList().get(0), tutorCategory); + + String expectedMessage = String.format(RestoreCommand.MESSAGE_RESTORE_TUTOR_SUCCESS, + model.getFilteredClosedTutorList().get(0)); + + expectedModel.addTutor(model.getFilteredClosedTutorList().get(0)); + assertCommandSuccess(restoreCommand, model, expectedMessage, expectedModel); + } + + /** + * Tests if a particular student is restored properly. + * @throws Exception + */ + @Test + public void execute_closeClientStudentFilteredList_success() throws Exception { + RestoreCommand restoreCommand = prepareCommand(Index.fromZeroBased(0), studentCategory); + Model expectedModel = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs()); + expectedModel.deleteClosedClient(expectedModel.getFilteredClosedStudentList().get(0), studentCategory); + + String expectedMessage = String.format(RestoreCommand.MESSAGE_RESTORE_STUDENT_SUCCESS, + model.getFilteredClosedStudentList().get(0)); + + expectedModel.addStudent(model.getFilteredClosedStudentList().get(0)); + assertCommandSuccess(restoreCommand, model, expectedMessage, expectedModel); + } + + @Test + public void assertsRestoreNotAvailableInActiveList() throws Exception { + listPanelController.switchDisplay(); + RestoreCommand restoreCommand = prepareCommand(Index.fromZeroBased(0), studentCategory); + try { + restoreCommand.execute(); + } catch (CommandNotAvailableInActiveViewException cna) { + assertsRestoreNotAvailableInActiveList(); + } + } + + /** + * Returns an {@code CloseCommand} with parameters {@code index} and {@code category} + */ + private RestoreCommand prepareCommand(Index index, Category category) { + RestoreCommand restoreCommand = new RestoreCommand(index, category); + restoreCommand.setData(model, new CommandHistory(), new UndoRedoStack()); + return restoreCommand; + } +} +``` +###### \java\seedu\address\logic\commands\SortByGradeCommandTest.java +``` java +/** + * Contains integration tests (interaction with the Model) for {@code SortByGradeCommand}. + */ +public class SortByGradeCommandTest { + private Model model; + private Model expectedModel; + private SortByGradeCommand sortByGradeCommandForStudentList; + private SortByGradeCommand sortByGradeCommandForTutorList; + + private final Category studentCategory = new Category("s"); + private final Category tutorCategory = new Category("t"); + + @Before + public void setup() { + model = new ModelManager(getUnsortedAddressBook(), new UserPrefs()); + expectedModel = new ModelManager(getSortedByGradeAddressBook(), new UserPrefs()); + + sortByGradeCommandForStudentList = new SortByGradeCommand(studentCategory); + sortByGradeCommandForStudentList.setData(model, new CommandHistory(), new UndoRedoStack()); + sortByGradeCommandForTutorList = new SortByGradeCommand(tutorCategory); + sortByGradeCommandForTutorList.setData(model, new CommandHistory(), new UndoRedoStack()); + } + + @Test + public void executeSortUnsortedToSortedByGrade() { + assertCommandSuccessForStudentList(sortByGradeCommandForStudentList, (SortByGradeCommand.MESSAGE_SUCCESS_STUDENT + + SortByGradeCommand.MESSAGE_SORT_DESC)); + assertCommandSuccessForTutorList(sortByGradeCommandForTutorList, (SortByGradeCommand.MESSAGE_SUCCESS_TUTOR + + SortByGradeCommand.MESSAGE_SORT_DESC)); + } + + /** + * Asserts that the code is successfully executed. + * Asserts the command feedback is equal to expectedMessage. + * Asserts actual FilteredList is equal to the expected FilteredList + * Asserts the {@code AddressBook} in model remains the same after executing the {@code command} + */ + private void assertCommandSuccessForStudentList(SortByGradeCommand command, String expectedMessage) { + AddressBook expectedAddressBook = new AddressBook(model.getAddressBook()); + try { + CommandResult commandResult = command.execute(); + assertEquals(expectedMessage, commandResult.feedbackToUser); + assertEquals(expectedModel.getFilteredStudentList(), model.getFilteredStudentList()); + assertEquals(expectedAddressBook, model.getAddressBook()); + } catch (CommandException ce) { + throw new AssertionError("Execution of command should not fail.", ce); + } + } + + /** + * Asserts that the code is successfully executed. + * Asserts the command feedback is equal to expectedMessage. + * Asserts actual FilteredList is equal to the expected FilteredList + * Asserts the {@code AddressBook} in model remains the same after executing the {@code command} + */ + private void assertCommandSuccessForTutorList(SortByGradeCommand command, String expectedMessage) { + AddressBook expectedAddressBook = new AddressBook(model.getAddressBook()); + try { + CommandResult commandResult = command.execute(); + assertEquals(expectedMessage, commandResult.feedbackToUser); + assertEquals(expectedModel.getFilteredTutorList(), model.getFilteredTutorList()); + assertEquals(expectedAddressBook, model.getAddressBook()); + } catch (CommandException ce) { + throw new AssertionError("Execution of command should not fail.", ce); + } + } +} +``` +###### \java\seedu\address\logic\commands\SortByLocationCommandTest.java +``` java +/** + * Contains integration tests (interaction with the Model) for {@code SortByLocationCommand}. + */ +public class SortByLocationCommandTest { + private Model model; + private Model expectedModel; + private SortByLocationCommand sortByLocationCommandForStudentList; + private SortByLocationCommand sortByLocationCommandForTutorList; + + private final Category studentCategory = new Category("s"); + private final Category tutorCategory = new Category("t"); + + + @Before + public void setup() { + model = new ModelManager(getUnsortedAddressBook(), new UserPrefs()); + expectedModel = new ModelManager(getSortedByLocationAddressBook(), new UserPrefs()); + + sortByLocationCommandForStudentList = new SortByLocationCommand(studentCategory); + sortByLocationCommandForStudentList.setData(model, new CommandHistory(), new UndoRedoStack()); + sortByLocationCommandForTutorList = new SortByLocationCommand(tutorCategory); + sortByLocationCommandForTutorList.setData(model, new CommandHistory(), new UndoRedoStack()); + } + + @Test + public void executeSortUnsortedToSortedByLocation() { + assertCommandSuccessForStudentList(sortByLocationCommandForStudentList, ( + SortByLocationCommand.MESSAGE_SUCCESS_STUDENT + SortByLocationCommand.MESSAGE_SORT_DESC)); + assertCommandSuccessForTutorList(sortByLocationCommandForTutorList, (SortByLocationCommand.MESSAGE_SUCCESS_TUTOR + + SortByLocationCommand.MESSAGE_SORT_DESC)); + } + + /** + * Asserts that the code is successfully executed. + * Asserts the command feedback is equal to expectedMessage. + * Asserts actual FilteredList is equal to the expected FilteredList + * Asserts the {@code AddressBook} in model remains the same after executing the {@code command} + */ + private void assertCommandSuccessForStudentList(SortByLocationCommand command, String expectedMessage) { + AddressBook expectedAddressBook = new AddressBook(model.getAddressBook()); + try { + CommandResult commandResult = command.execute(); + assertEquals(expectedMessage, commandResult.feedbackToUser); + assertEquals(expectedModel.getFilteredStudentList(), model.getFilteredStudentList()); + assertEquals(expectedAddressBook, model.getAddressBook()); + } catch (CommandException ce) { + throw new AssertionError("Execution of command should not fail.", ce); + } + } + + /** + * Asserts that the code is successfully executed. + * Asserts the command feedback is equal to expectedMessage. + * Asserts actual FilteredList is equal to the expected FilteredList + * Asserts the {@code AddressBook} in model remains the same after executing the {@code command} + */ + private void assertCommandSuccessForTutorList(SortByLocationCommand command, String expectedMessage) { + AddressBook expectedAddressBook = new AddressBook(model.getAddressBook()); + try { + CommandResult commandResult = command.execute(); + assertEquals(expectedMessage, commandResult.feedbackToUser); + assertEquals(expectedModel.getFilteredTutorList(), model.getFilteredTutorList()); + assertEquals(expectedAddressBook, model.getAddressBook()); + } catch (CommandException ce) { + throw new AssertionError("Execution of command should not fail.", ce); + } + } +} +``` +###### \java\seedu\address\logic\commands\SortByNameCommandTest.java +``` java +/** + * Contains integration tests (interaction with the Model) for {@code SortByNameCommand}. + */ +public class SortByNameCommandTest { + private Model model; + private Model expectedModel; + private SortByNameCommand sortByNameCommandForStudentList; + private SortByNameCommand sortByNameCommandForTutorList; + + private final Category studentCategory = new Category("s"); + private final Category tutorCategory = new Category("t"); + + @Before + public void setup() { + model = new ModelManager(getUnsortedAddressBook(), new UserPrefs()); + expectedModel = new ModelManager(getSortedByNameAddressBook(), new UserPrefs()); + + sortByNameCommandForStudentList = new SortByNameCommand(studentCategory); + sortByNameCommandForStudentList.setData(model, new CommandHistory(), new UndoRedoStack()); + sortByNameCommandForTutorList = new SortByNameCommand(tutorCategory); + sortByNameCommandForTutorList.setData(model, new CommandHistory(), new UndoRedoStack()); + } + + @Test + public void executeSortUnsortedToSortedByName() { + assertCommandSuccessForStudentList(sortByNameCommandForStudentList, (SortByNameCommand.MESSAGE_SUCCESS_STUDENT + + SortByNameCommand.MESSAGE_SORT_DESC)); + assertCommandSuccessForTutorList(sortByNameCommandForTutorList, (SortByNameCommand.MESSAGE_SUCCESS_TUTOR + + SortByNameCommand.MESSAGE_SORT_DESC)); + } + + /** + * Asserts that the code is successfully executed. + * Asserts the command feedback is equal to expectedMessage. + * Asserts actual FilteredList is equal to the expected FilteredList + * Asserts the {@code AddressBook} in model remains the same after executing the {@code command} + */ + private void assertCommandSuccessForStudentList(SortByNameCommand command, String expectedMessage) { + AddressBook expectedAddressBook = new AddressBook(model.getAddressBook()); + try { + CommandResult commandResult = command.execute(); + assertEquals(expectedMessage, commandResult.feedbackToUser); + assertEquals(expectedModel.getFilteredStudentList(), model.getFilteredStudentList()); + assertEquals(expectedAddressBook, model.getAddressBook()); + } catch (CommandException ce) { + throw new AssertionError("Execution of command should not fail.", ce); + } + } + + /** + * Asserts that the code is successfully executed. + * Asserts the command feedback is equal to expectedMessage. + * Asserts actual FilteredList is equal to the expected FilteredList + * Asserts the {@code AddressBook} in model remains the same after executing the {@code command} + */ + private void assertCommandSuccessForTutorList(SortByNameCommand command, String expectedMessage) { + AddressBook expectedAddressBook = new AddressBook(model.getAddressBook()); + try { + CommandResult commandResult = command.execute(); + assertEquals(expectedMessage, commandResult.feedbackToUser); + assertEquals(expectedModel.getFilteredTutorList(), model.getFilteredTutorList()); + assertEquals(expectedAddressBook, model.getAddressBook()); + } catch (CommandException ce) { + throw new AssertionError("Execution of command should not fail.", ce); + } + } +} +``` +###### \java\seedu\address\logic\commands\SortBySubjectCommandTest.java +``` java +/** + * Contains integration tests (interaction with the Model) for {@code SortBySubjectCommand}. + */ +public class SortBySubjectCommandTest { + private Model model; + private Model expectedModel; + private SortBySubjectCommand sortBySubjectCommandForStudentList; + private SortBySubjectCommand sortBySubjectCommandForTutorList; + + private final Category studentCategory = new Category("s"); + private final Category tutorCategory = new Category("t"); + + @Before + public void setup() { + model = new ModelManager(getUnsortedAddressBook(), new UserPrefs()); + expectedModel = new ModelManager(getSortedBySubjectAddressBook(), new UserPrefs()); + + sortBySubjectCommandForStudentList = new SortBySubjectCommand(studentCategory); + sortBySubjectCommandForStudentList.setData(model, new CommandHistory(), new UndoRedoStack()); + sortBySubjectCommandForTutorList = new SortBySubjectCommand(tutorCategory); + sortBySubjectCommandForTutorList.setData(model, new CommandHistory(), new UndoRedoStack()); + } + + @Test + public void executeSortUnsortedToSortedBySubject() { + assertCommandSuccessForStudentList(sortBySubjectCommandForStudentList, ( + SortBySubjectCommand.MESSAGE_SUCCESS_STUDENT + SortBySubjectCommand.MESSAGE_SORT_DESC)); + assertCommandSuccessForTutorList(sortBySubjectCommandForTutorList, (SortBySubjectCommand.MESSAGE_SUCCESS_TUTOR + + SortBySubjectCommand.MESSAGE_SORT_DESC)); + } + + /** + * Asserts that the code is successfully executed. + * Asserts the command feedback is equal to expectedMessage. + * Asserts actual FilteredList is equal to the expected FilteredList + * Asserts the {@code AddressBook} in model remains the same after executing the {@code command} + */ + private void assertCommandSuccessForStudentList(SortBySubjectCommand command, String expectedMessage) { + AddressBook expectedAddressBook = new AddressBook(model.getAddressBook()); + try { + CommandResult commandResult = command.execute(); + assertEquals(expectedMessage, commandResult.feedbackToUser); + assertEquals(expectedModel.getFilteredStudentList(), model.getFilteredStudentList()); + assertEquals(expectedAddressBook, model.getAddressBook()); + } catch (CommandException ce) { + throw new AssertionError("Execution of command should not fail.", ce); + } + } + + /** + * Asserts that the code is successfully executed. + * Asserts the command feedback is equal to expectedMessage. + * Asserts actual FilteredList is equal to the expected FilteredList + * Asserts the {@code AddressBook} in model remains the same after executing the {@code command} + */ + private void assertCommandSuccessForTutorList(SortBySubjectCommand command, String expectedMessage) { + AddressBook expectedAddressBook = new AddressBook(model.getAddressBook()); + try { + CommandResult commandResult = command.execute(); + assertEquals(expectedMessage, commandResult.feedbackToUser); + assertEquals(expectedModel.getFilteredTutorList(), model.getFilteredTutorList()); + assertEquals(expectedAddressBook, model.getAddressBook()); + } catch (CommandException ce) { + throw new AssertionError("Execution of command should not fail.", ce); + } + } +} +``` +###### \java\seedu\address\logic\commands\SwitchCommandTest.java +``` java +public class SwitchCommandTest { + private static ListPanelController listPanelController; + private Model model; + private Model expectedModel; + private SwitchCommand switchCommand; + private EventsCollectorRule eventsCollectorRule; + + private final String expectedSwitchToClosedListMessage = String.format(SwitchCommand.MESSAGE_SUCCESS + + SwitchCommand.MESSAGE_CLOSED_DISPLAY_LIST); + private final String expectedSwitchToActiveListMessage = String.format(SwitchCommand.MESSAGE_SUCCESS + + SwitchCommand.MESSAGE_ACTIVE_DISPLAY_LIST); + + @Before + public void setUp() { + eventsCollectorRule = new EventsCollectorRule(); + model = new ModelManager(getTypicalAddressBookNew(), new UserPrefs()); + expectedModel = new ModelManager(getSortedByGradeAddressBook(), new UserPrefs()); + switchCommand = new SwitchCommand(); + switchCommand.setData(model, new CommandHistory(), new UndoRedoStack()); + listPanelController = ListPanelController.getInstance(); + listPanelController.setDefault(); + } + + /** + *Ensure display is at active client list after this class test. + */ + @AfterClass + public static void tearDownAfterClass() { + if (!ListPanelController.isCurrentDisplayActiveList()) { + listPanelController.switchDisplay(); + } + } + + /** + * Asserts that the list is successfully switched. + */ + @Test + public void execute_switch_success() { + CommandResult commandResult = switchCommand.execute(); + assertEquals(expectedSwitchToClosedListMessage, commandResult.feedbackToUser); + assertTrue(eventsCollectorRule.eventsCollector.getMostRecent() instanceof ClientListSwitchEvent); + + commandResult = switchCommand.execute(); + assertEquals(expectedSwitchToActiveListMessage, commandResult.feedbackToUser); + } +} +``` +###### \java\seedu\address\logic\parser\CloseCommandParserTest.java +``` java +public class CloseCommandParserTest { + + private static final Category CATEGORY_STUDENT = new Category("s"); + private static final Category CATEGORY_TUTOR = new Category("t"); + + private CloseCommandParser parser = new CloseCommandParser(); + + @Test + public void parse_validArgs_returnsRestoreCommand() { + assertParseSuccess(parser, "1 c/s", new CloseCommand(INDEX_FIRST_PERSON, CATEGORY_STUDENT)); + assertParseSuccess(parser, "1 c/t", new CloseCommand(INDEX_FIRST_PERSON, CATEGORY_TUTOR)); + } + + @Test + public void parse_emptyArg_throwsParseException() { + assertParseFailure(parser, " ", String.format(MESSAGE_INVALID_COMMAND_FORMAT, + CloseCommand.MESSAGE_USAGE)); + } + + @Test + public void parse_invalidArgs_throwsParseException() { + assertParseFailure(parser, "z c/s", String.format(MESSAGE_INVALID_COMMAND_FORMAT, + CloseCommand.MESSAGE_USAGE)); + + assertParseFailure(parser, "1 c/z", String.format(MESSAGE_INVALID_COMMAND_FORMAT, + CloseCommand.MESSAGE_USAGE)); + + assertParseFailure(parser, "1 w/s", String.format(MESSAGE_INVALID_COMMAND_FORMAT, + CloseCommand.MESSAGE_USAGE)); + } + + @Test + public void parse_missingParts_failure() { + // no index specified + assertParseFailure(parser, " c/t", String.format(MESSAGE_INVALID_COMMAND_FORMAT, + CloseCommand.MESSAGE_USAGE)); + + assertParseFailure(parser, " c/s", String.format(MESSAGE_INVALID_COMMAND_FORMAT, + CloseCommand.MESSAGE_USAGE)); + + //no category prefix and category + assertParseFailure(parser, "1 ", String.format(MESSAGE_INVALID_COMMAND_FORMAT, + CloseCommand.MESSAGE_USAGE)); + + //no category specified + assertParseFailure(parser, "1 c/", String.format(MESSAGE_INVALID_COMMAND_FORMAT, + CloseCommand.MESSAGE_USAGE)); + } + + @Test + public void parse_invalidIndex_failure() { + //negative index + assertParseFailure(parser, "-1 c/t", String.format(MESSAGE_INVALID_COMMAND_FORMAT, + CloseCommand.MESSAGE_USAGE)); + + //zero index + assertParseFailure(parser, "0 c/t", String.format(MESSAGE_INVALID_COMMAND_FORMAT, + CloseCommand.MESSAGE_USAGE)); + } +} +``` +###### \java\seedu\address\logic\parser\RestoreCommandParserTest.java +``` java +public class RestoreCommandParserTest { + + private static final Category CATEGORY_STUDENT = new Category("s"); + private static final Category CATEGORY_TUTOR = new Category("t"); + + private RestoreCommandParser parser = new RestoreCommandParser(); + + @Test + public void parse_validArgs_returnsRestoreCommand() { + assertParseSuccess(parser, "1 c/s", new RestoreCommand(INDEX_FIRST_PERSON, CATEGORY_STUDENT)); + assertParseSuccess(parser, "1 c/t", new RestoreCommand(INDEX_FIRST_PERSON, CATEGORY_TUTOR)); + } + + @Test + public void parse_emptyArg_throwsParseException() { + assertParseFailure(parser, " ", String.format(MESSAGE_INVALID_COMMAND_FORMAT, + RestoreCommand.MESSAGE_USAGE)); + } + + @Test + public void parse_invalidArgs_throwsParseException() { + assertParseFailure(parser, "z c/s", String.format(MESSAGE_INVALID_COMMAND_FORMAT, + RestoreCommand.MESSAGE_USAGE)); + + assertParseFailure(parser, "1 c/z", String.format(MESSAGE_INVALID_COMMAND_FORMAT, + RestoreCommand.MESSAGE_USAGE)); + + assertParseFailure(parser, "1 w/s", String.format(MESSAGE_INVALID_COMMAND_FORMAT, + RestoreCommand.MESSAGE_USAGE)); + } + + @Test + public void parse_missingParts_failure() { + // no index specified + assertParseFailure(parser, " c/t", String.format(MESSAGE_INVALID_COMMAND_FORMAT, + RestoreCommand.MESSAGE_USAGE)); + + assertParseFailure(parser, " c/s", String.format(MESSAGE_INVALID_COMMAND_FORMAT, + RestoreCommand.MESSAGE_USAGE)); + + //no category prefix and category + assertParseFailure(parser, "1 ", String.format(MESSAGE_INVALID_COMMAND_FORMAT, + RestoreCommand.MESSAGE_USAGE)); + + //no category specified + assertParseFailure(parser, "1 c/", String.format(MESSAGE_INVALID_COMMAND_FORMAT, + RestoreCommand.MESSAGE_USAGE)); + } + + @Test + public void parse_invalidIndex_failure() { + //negative index + assertParseFailure(parser, "-1 c/t", String.format(MESSAGE_INVALID_COMMAND_FORMAT, + RestoreCommand.MESSAGE_USAGE)); + + //zero index + assertParseFailure(parser, "0 c/t", String.format(MESSAGE_INVALID_COMMAND_FORMAT, + RestoreCommand.MESSAGE_USAGE)); + } + +} +``` +###### \java\seedu\address\logic\parser\SortCommandParserTest.java +``` java +public class SortCommandParserTest { + + private static final Category CATEGORY_STUDENT = new Category("s"); + private static final Category CATEGORY_TUTOR = new Category("t"); + + private SortCommandParser parser = new SortCommandParser(); + + @Test + public void parse_validArgs_returnsSortCommand() { + assertParseSuccess(parser, "n c/s", new SortByNameCommand(CATEGORY_STUDENT)); + assertParseSuccess(parser, "n c/t", new SortByNameCommand(CATEGORY_TUTOR)); + + assertParseSuccess(parser, "g c/s", new SortByGradeCommand(CATEGORY_STUDENT)); + assertParseSuccess(parser, "g c/t", new SortByGradeCommand(CATEGORY_TUTOR)); + + assertParseSuccess(parser, "l c/s", new SortByLocationCommand(CATEGORY_STUDENT)); + assertParseSuccess(parser, "l c/t", new SortByLocationCommand(CATEGORY_TUTOR)); + + assertParseSuccess(parser, "s c/s", new SortBySubjectCommand(CATEGORY_STUDENT)); + assertParseSuccess(parser, "s c/t", new SortBySubjectCommand(CATEGORY_TUTOR)); + } + + @Test + public void parse_emptyArg_throwsParseException() { + assertParseFailure(parser, " ", String.format(MESSAGE_INVALID_COMMAND_FORMAT, + SortCommand.MESSAGE_USAGE)); + } + + @Test + public void parse_missingParts_failure() { + // no sort type specified + assertParseFailure(parser, " c/t", String.format(MESSAGE_INVALID_COMMAND_FORMAT, + SortCommand.MESSAGE_USAGE)); + + assertParseFailure(parser, " c/s", String.format(MESSAGE_INVALID_COMMAND_FORMAT, + SortCommand.MESSAGE_USAGE)); + + //no category prefix and category specified + assertParseFailure(parser, "n", String.format(MESSAGE_INVALID_COMMAND_FORMAT, + SortCommand.MESSAGE_USAGE)); + + //no category specified + assertParseFailure(parser, "n c/", String.format(MESSAGE_INVALID_COMMAND_FORMAT, + SortCommand.MESSAGE_USAGE)); + } + + @Test + public void parse_invalidValue_failure() { + //invalid type of sort + assertParseFailure(parser, "w c/t", String.format(MESSAGE_INVALID_COMMAND_FORMAT, + SortCommand.MESSAGE_USAGE)); + + //invalid category + assertParseFailure(parser, "w c/w", String.format(MESSAGE_INVALID_COMMAND_FORMAT, + SortCommand.MESSAGE_USAGE)); + + //invalid category prefix + assertParseFailure(parser, "n z/t", String.format(MESSAGE_INVALID_COMMAND_FORMAT, + SortCommand.MESSAGE_USAGE)); + } +} +``` +###### \java\seedu\address\testutil\SortedClients.java +``` java +/** + * A utility class containing a list of {@code Clients} objects to be used in tests. + */ +public class SortedClients { + //Students + public static final Client ALICE = new ClientBuilder().withName("Alice Pauline") + .withAddress("123, Jurong West Ave 6, #08-111").withEmail("alice@example.com") + .withPhone("85355255") + .withTags("friends").withLocation("north").withGrade("k1").withSubject("math").withCategory("s").build(); + public static final Client BENSON = new ClientBuilder().withName("Benson Meier") + .withAddress("311, Clementi Ave 2, #02-25") + .withEmail("johnd@example.com").withPhone("98765432") + .withTags("owesMoney", "friends").withLocation("north").withGrade("s2").withSubject("physics") + .withCategory("s").build(); + public static final Client CARL = new ClientBuilder().withName("Carl Kurz").withPhone("95352563") + .withEmail("heinz@example.com").withAddress("wall street").withLocation("south").withGrade("j1") + .withSubject("physics").withCategory("s").build(); + public static final Client DANIEL = new ClientBuilder().withName("Daniel Meier").withPhone("87652533") + .withEmail("cornelia@example.com").withAddress("10th street").withLocation("east").withGrade("primary6") + .withSubject("english").withCategory("s").build(); + public static final Client ELLE = new ClientBuilder().withName("Elle Meyer").withPhone("9482224") + .withEmail("werner@example.com").withAddress("michegan ave").withLocation("west").withGrade("p3") + .withSubject("math").withCategory("s").build(); + public static final Client FIONA = new ClientBuilder().withName("Fiona Kunz").withPhone("9482427") + .withEmail("lydia@example.com").withAddress("little tokyo").withLocation("north").withGrade("secondary1") + .withSubject("physics").withCategory("s").build(); + public static final Client GEORGE = new ClientBuilder().withName("George Best").withPhone("9482442") + .withEmail("anna@example.com").withAddress("4th street").withLocation("west").withGrade("j2") + .withSubject("chemistry").withCategory("s").build(); + + //Tutors + public static final Client ANDREW = new ClientBuilder().withName("ANDREW LIM").withPhone("5212533") + .withEmail("andrew@example.com").withAddress("Andrew street").withLocation("east").withGrade("primary2") + .withSubject("english").withCategory("t").build(); + public static final Client EDISON = new ClientBuilder().withName("EDISON").withPhone("2313224") + .withEmail("EDISON@example.com").withAddress("EDISON ave").withLocation("west").withGrade("j2") + .withSubject("math").withCategory("t").build(); + public static final Client FLOWER = new ClientBuilder().withName("Flower").withPhone("2182427") + .withEmail("flowerislife@example.com").withAddress("little flower").withLocation("central").withGrade("k1") + .withSubject("physics").withCategory("t").build(); + public static final Client GERRARD = new ClientBuilder().withName("GERRARD").withPhone("8321242") + .withEmail("liverpool@example.com").withAddress("Anfield").withLocation("west").withGrade("u4") + .withSubject("chemistry").withCategory("t").build(); + + // Manually added + public static final Client HOON = new ClientBuilder().withName("Hoon Meier").withPhone("8482424") + .withEmail("stefan@example.com").withAddress("little india").withLocation("north").withGrade("s1") + .withSubject("chemistry").withCategory("s").build(); + public static final Client IDA = new ClientBuilder().withName("Ida Mueller").withPhone("8482131") + .withEmail("hans@example.com").withAddress("chicago ave").withLocation("north").withGrade("k2") + .withSubject("math").withCategory("s").build(); + + // Manually added - Person's details found in {@code CommandTestUtil} + public static final Client AMY = new ClientBuilder().withName(VALID_NAME_AMY).withPhone(VALID_PHONE_AMY) + .withEmail(VALID_EMAIL_AMY).withAddress(VALID_ADDRESS_AMY).withTags(VALID_TAG_FRIEND) + .withLocation(VALID_LOCATION_AMY).withGrade(VALID_GRADE_AMY).withSubject(VALID_SUBJECT_AMY) + .withCategory(VALID_CATEGORY_AMY).build(); + public static final Client BOB = new ClientBuilder().withName(VALID_NAME_BOB).withPhone(VALID_PHONE_BOB) + .withEmail(VALID_EMAIL_BOB).withAddress(VALID_ADDRESS_BOB).withTags(VALID_TAG_HUSBAND, VALID_TAG_FRIEND) + .withLocation(VALID_LOCATION_BOB).withGrade(VALID_GRADE_BOB).withSubject(VALID_SUBJECT_BOB) + .withCategory(VALID_CATEGORY_BOB).build(); + + public static final String KEYWORD_MATCHING_MEIER = "Meier"; // A keyword that matches MEIER + + private SortedClients() {} // prevents instantiation + + /** + * Returns an {@code AddressBook} with all the typical clients sorted by name. + */ + public static AddressBook getSortedByNameAddressBook() { + AddressBook ab = new AddressBook(); + for (Client client : getSortedByNameClients()) { + try { + ab.addClient(client); + } catch (DuplicatePersonException e) { + throw new AssertionError("not possible"); + } + } + return ab; + } + + public static List getSortedByNameClients() { + return new ArrayList<>(Arrays.asList(ALICE, BENSON, CARL, DANIEL, ELLE, FIONA, GEORGE, + ANDREW, EDISON, FLOWER, GERRARD)); + } + + /** + * Returns an {@code AddressBook} with all the typical clients sorted by grade. + */ + public static AddressBook getSortedByGradeAddressBook() { + AddressBook ab = new AddressBook(); + for (Client client : getSortedByGradeClients()) { + try { + ab.addClient(client); + } catch (DuplicatePersonException e) { + throw new AssertionError("not possible"); + } + } + return ab; + } + + public static List getSortedByGradeClients() { + return new ArrayList<>(Arrays.asList(ALICE, ELLE, DANIEL, FIONA, BENSON, CARL, GEORGE, + FLOWER, ANDREW, EDISON, GERRARD)); + } + + /** + * Returns an {@code AddressBook} with all the typical clients sorted by subject + */ + public static AddressBook getSortedBySubjectAddressBook() { + AddressBook ab = new AddressBook(); + for (Client client : getSortedBySubjectClients()) { + try { + ab.addClient(client); + } catch (DuplicatePersonException e) { + throw new AssertionError("not possible"); + } + } + return ab; + } + + public static List getSortedBySubjectClients() { + return new ArrayList<>(Arrays.asList(GEORGE, DANIEL, ALICE, ELLE, BENSON, CARL, FIONA, + GERRARD, ANDREW, EDISON, FLOWER)); + } + + /** + * Returns an {@code AddressBook} with all the typical clients sorted by location + */ + public static AddressBook getSortedByLocationAddressBook() { + AddressBook ab = new AddressBook(); + for (Client client : getSortedByLocationClients()) { + try { + ab.addClient(client); + } catch (DuplicatePersonException e) { + throw new AssertionError("not possible"); + } + } + return ab; + } + + public static List getSortedByLocationClients() { + return new ArrayList<>(Arrays.asList(DANIEL, BENSON, ALICE, FIONA, CARL, ELLE, GEORGE, + FLOWER, ANDREW, GERRARD, EDISON)); + } +} +``` +###### \java\seedu\address\testutil\UnsortedClients.java +``` java +/** + * A utility class containing a list of {@code Clients} objects to be used in tests. + */ +public class UnsortedClients { + //Students + public static final Client ALICE = new ClientBuilder().withName("Alice Pauline") + .withAddress("123, Jurong West Ave 6, #08-111").withEmail("alice@example.com") + .withPhone("85355255") + .withTags("friends").withLocation("north").withGrade("k1").withSubject("math").withCategory("s").build(); + public static final Client BENSON = new ClientBuilder().withName("Benson Meier") + .withAddress("311, Clementi Ave 2, #02-25") + .withEmail("johnd@example.com").withPhone("98765432") + .withTags("owesMoney", "friends").withLocation("north").withGrade("s2").withSubject("physics") + .withCategory("s").build(); + public static final Client CARL = new ClientBuilder().withName("Carl Kurz").withPhone("95352563") + .withEmail("heinz@example.com").withAddress("wall street").withLocation("south").withGrade("j1") + .withSubject("physics").withCategory("s").build(); + public static final Client DANIEL = new ClientBuilder().withName("Daniel Meier").withPhone("87652533") + .withEmail("cornelia@example.com").withAddress("10th street").withLocation("east").withGrade("primary6") + .withSubject("english").withCategory("s").build(); + public static final Client ELLE = new ClientBuilder().withName("Elle Meyer").withPhone("9482224") + .withEmail("werner@example.com").withAddress("michegan ave").withLocation("west").withGrade("p3") + .withSubject("math").withCategory("s").build(); + public static final Client FIONA = new ClientBuilder().withName("Fiona Kunz").withPhone("9482427") + .withEmail("lydia@example.com").withAddress("little tokyo").withLocation("north").withGrade("secondary1") + .withSubject("physics").withCategory("s").build(); + public static final Client GEORGE = new ClientBuilder().withName("George Best").withPhone("9482442") + .withEmail("anna@example.com").withAddress("4th street").withLocation("west").withGrade("j2") + .withSubject("chemistry").withCategory("s").build(); + + //Tutors + public static final Client ANDREW = new ClientBuilder().withName("ANDREW LIM").withPhone("5212533") + .withEmail("andrew@example.com").withAddress("Andrew street").withLocation("east").withGrade("primary2") + .withSubject("english").withCategory("t").build(); + public static final Client EDISON = new ClientBuilder().withName("EDISON").withPhone("2313224") + .withEmail("EDISON@example.com").withAddress("EDISON ave").withLocation("west").withGrade("j2") + .withSubject("math").withCategory("t").build(); + public static final Client FLOWER = new ClientBuilder().withName("Flower").withPhone("2182427") + .withEmail("flowerislife@example.com").withAddress("little flower").withLocation("central").withGrade("k1") + .withSubject("physics").withCategory("t").build(); + public static final Client GERRARD = new ClientBuilder().withName("GERRARD").withPhone("8321242") + .withEmail("liverpool@example.com").withAddress("Anfield").withLocation("west").withGrade("u4") + .withSubject("chemistry").withCategory("t").build(); + + // Manually added + public static final Client HOON = new ClientBuilder().withName("Hoon Meier").withPhone("8482424") + .withEmail("stefan@example.com").withAddress("little india").withLocation("north").withGrade("s1") + .withSubject("chemistry").withCategory("s").build(); + public static final Client IDA = new ClientBuilder().withName("Ida Mueller").withPhone("8482131") + .withEmail("hans@example.com").withAddress("chicago ave").withLocation("north").withGrade("k2") + .withSubject("math").withCategory("s").build(); + + // Manually added - Person's details found in {@code CommandTestUtil} + public static final Client AMY = new ClientBuilder().withName(VALID_NAME_AMY).withPhone(VALID_PHONE_AMY) + .withEmail(VALID_EMAIL_AMY).withAddress(VALID_ADDRESS_AMY).withTags(VALID_TAG_FRIEND) + .withLocation(VALID_LOCATION_AMY).withGrade(VALID_GRADE_AMY).withSubject(VALID_SUBJECT_AMY) + .withCategory(VALID_CATEGORY_AMY).build(); + public static final Client BOB = new ClientBuilder().withName(VALID_NAME_BOB).withPhone(VALID_PHONE_BOB) + .withEmail(VALID_EMAIL_BOB).withAddress(VALID_ADDRESS_BOB).withTags(VALID_TAG_HUSBAND, VALID_TAG_FRIEND) + .withLocation(VALID_LOCATION_BOB).withGrade(VALID_GRADE_BOB).withSubject(VALID_SUBJECT_BOB) + .withCategory(VALID_CATEGORY_BOB).build(); + + public static final String KEYWORD_MATCHING_MEIER = "Meier"; // A keyword that matches MEIER + + private UnsortedClients() {} // prevents instantiation + + /** + * Returns an {@code AddressBook} with all the typical clients. + */ + public static AddressBook getUnsortedAddressBook() { + AddressBook ab = new AddressBook(); + for (Client client : getUnsortedClients()) { + try { + ab.addClient(client); + } catch (DuplicatePersonException e) { + throw new AssertionError("not possible"); + } + } + return ab; + } + + public static List getUnsortedClients() { + return new ArrayList<>(Arrays.asList(BENSON, CARL, ALICE, ELLE, FIONA, GEORGE, + DANIEL, GERRARD, EDISON, ANDREW, FLOWER)); + } +} +``` diff --git a/collated/test/shookshire.md b/collated/test/shookshire.md new file mode 100644 index 000000000000..88aea1ad805e --- /dev/null +++ b/collated/test/shookshire.md @@ -0,0 +1,920 @@ +# shookshire +###### \java\seedu\address\logic\commands\AddClientCommandIntegrationTest.java +``` java +/** + * Contains integration tests (interaction with the Model) for {@code AddCommand}. + */ +public class AddClientCommandIntegrationTest { + + + private Model model; + + @Before + public void setUp() { + model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + } + + @Test + public void execute_newPerson_success() throws Exception { + Client validStudent = new ClientBuilder().build(); + + Model expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs()); + expectedModel.addStudent(validStudent); + + assertCommandSuccess(prepareCommand(validStudent, model), model, + String.format(AddClientCommand.MESSAGE_SUCCESS_STUDENT, validStudent), expectedModel); + + Client validTutor = new ClientBuilder().withName(VALID_NAME_BOB).withPhone(VALID_PHONE_BOB) + .withEmail(VALID_EMAIL_BOB).withAddress(VALID_ADDRESS_BOB).withTags(VALID_TAG_FRIEND) + .withLocation(VALID_LOCATION_BOB).withGrade(VALID_GRADE_BOB).withSubject(VALID_GRADE_BOB) + .withCategory(VALID_CATEGORY_TUTOR_BOB).build(); + + expectedModel.addTutor(validTutor); + + assertCommandSuccess(prepareCommand(validTutor, model), model, + String.format(AddClientCommand.MESSAGE_SUCCESS_TUTOR, validTutor), expectedModel); + } + + @Test + public void execute_duplicatePerson_throwsCommandException() { + Client personInList = model.getAddressBook().getStudentList().get(0); + assertCommandFailure(prepareCommand(personInList, model), model, AddClientCommand.MESSAGE_DUPLICATE_PERSON); + } + + /** + * Generates a new {@code AddCommand} which upon execution, adds {@code person} into the {@code model}. + */ + private AddClientCommand prepareCommand(Client client, Model model) { + AddClientCommand command = new AddClientCommand(client); + command.setData(model, new CommandHistory(), new UndoRedoStack()); + return command; + } +} +``` +###### \java\seedu\address\logic\commands\AddClientCommandTest.java +``` java +public class AddClientCommandTest { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Test + public void constructor_nullClient_throwsNullPointerException() { + thrown.expect(NullPointerException.class); + new AddClientCommand(null); + } + + @Test + public void execute_clientAcceptedByModel_addSuccessful() throws Exception { + ModelStubAcceptingClientAdded modelStub = new ModelStubAcceptingClientAdded(); + Client validClient = new ClientBuilder().build(); + + CommandResult commandResultStudent = getAddClientCommandForStudent(validClient, modelStub).execute(); + + assertEquals(String.format(AddClientCommand.MESSAGE_SUCCESS_STUDENT, validClient), + commandResultStudent.feedbackToUser); + assertEquals(Arrays.asList(validClient), modelStub.studentsAdded); + + Client validTutor = new ClientBuilder().withName(VALID_NAME_BOB).withPhone(VALID_PHONE_BOB) + .withEmail(VALID_EMAIL_BOB).withAddress(VALID_ADDRESS_BOB).withTags(VALID_TAG_FRIEND) + .withLocation(VALID_LOCATION_BOB).withGrade(VALID_GRADE_BOB).withSubject(VALID_GRADE_BOB) + .withCategory(VALID_CATEGORY_TUTOR_BOB).build(); + + CommandResult commandResultTutor = getAddClientCommandForTutor(validTutor, modelStub).execute(); + + assertEquals(String.format(AddClientCommand.MESSAGE_SUCCESS_TUTOR, validTutor), + commandResultTutor.feedbackToUser); + assertEquals(Arrays.asList(validTutor), modelStub.tutorsAdded); + } + + @Test + public void execute_duplicatePerson_throwsCommandException() throws Exception { + ModelStub modelStub = new ModelStubThrowingDuplicateClientException(); + Client validClient = new ClientBuilder().build(); + + thrown.expect(CommandException.class); + thrown.expectMessage(AddClientCommand.MESSAGE_DUPLICATE_PERSON); + + getAddClientCommandForStudent(validClient, modelStub).execute(); + } + + @Test + public void equals() { + Client alice = new ClientBuilder().withName("Alice").build(); + Client bob = new ClientBuilder().withName("Bob").build(); + AddClientCommand addAliceCommand = new AddClientCommand(alice); + AddClientCommand addBobCommand = new AddClientCommand(bob); + + // same object -> returns true + assertTrue(addAliceCommand.equals(addAliceCommand)); + + // same values -> returns true + AddClientCommand addAliceCommandCopy = new AddClientCommand(alice); + assertTrue(addAliceCommand.equals(addAliceCommandCopy)); + + // different types -> returns false + assertFalse(addAliceCommand.equals(1)); + + // null -> returns false + assertFalse(addAliceCommand.equals(null)); + + // different person -> returns false + assertFalse(addAliceCommand.equals(addBobCommand)); + } + + /** + * Generates a new AddClientCommand with the details of the given student. + */ + private AddClientCommand getAddClientCommandForStudent(Client client, Model model) { + AddClientCommand command = new AddClientCommand(client); + command.setData(model, new CommandHistory(), new UndoRedoStack()); + return command; + } + + /** + * Generates a new AddClientCommand with the details of the given tutor. + */ + private AddClientCommand getAddClientCommandForTutor(Client client, Model model) { + AddClientCommand command = new AddClientCommand(client); + command.setData(model, new CommandHistory(), new UndoRedoStack()); + return command; + } + + /** + * A default model stub that have all of the methods failing. + */ + private class ModelStub implements Model { + @Override + public void resetData(ReadOnlyAddressBook newData) { + fail("This method should not be called."); + } + + @Override + public ReadOnlyAddressBook getAddressBook() { + fail("This method should not be called."); + return null; + } + + @Override + public void deleteClient(Client target, Category category) throws PersonNotFoundException { + fail("This method should not be called."); + } + + @Override + public void deleteClosedClient(Client target, Category category) throws PersonNotFoundException { + fail("This method should not be called."); + } + + @Override + public void updateClient(Client target, Client editedPerson, Category category) + throws DuplicatePersonException, PersonNotFoundException { + fail("This method should not be called."); + } + + @Override + public void addTutor(Client tutor) throws DuplicatePersonException { + fail("This method should not be called."); + } + + @Override + public void addStudent(Client student) throws DuplicatePersonException { + fail("This method should not be called."); + } + + @Override + public void addClosedTutor(Client tutor) throws DuplicatePersonException { + fail("This method should not be called."); + } + + @Override + public void addClosedStudent(Client student) throws DuplicatePersonException { + fail("This method should not be called."); + } + + @Override + public ObservableList getFilteredStudentList() { + fail("This method should not be called."); + return null; + } + + @Override + public void updateFilteredStudentList(Predicate predicate) { + fail("This method should not be called."); + } + + @Override + public ObservableList getFilteredTutorList() { + fail("This method should not be called."); + return null; + } + + @Override + public void updateFilteredTutorList(Predicate predicate) { + fail("This method should not be called."); + } + + @Override + public ObservableList getFilteredClosedTutorList() { + fail("This method should not be called."); + return null; + } + + @Override + public void updateFilteredClosedTutorList(Predicate predicate) { + fail("This method should not be called."); + } + + @Override + public ObservableList getFilteredClosedStudentList() { + fail("This method should not be called."); + return null; + } + + @Override + public void updateFilteredClosedStudentList(Predicate predicate) { + fail("This method should not be called."); + } + + @Override + public void updateRankedStudentList() { + fail("This method should not be called."); + } + + @Override + public void updateRankedTutorList() { + fail("This method should not be called."); + } + + @Override + public void sortByNameFilteredClientTutorList() { + fail("This method should not be called."); + } + + @Override + public void sortByLocationFilteredClientTutorList() { + fail("This method should not be called."); + } + + @Override + public void sortByGradeFilteredClientTutorList() { + fail("This method should not be called."); + } + + @Override + public void sortBySubjectFilteredClientTutorList() { + fail("This method should not be called."); + } + + @Override + public void sortByNameFilteredClientStudentList() { + fail("This method should not be called."); + } + + @Override + public void sortByLocationFilteredClientStudentList() { + fail("This method should not be called."); + } + + @Override + public void sortByGradeFilteredClientStudentList() { + fail("This method should not be called."); + } + + @Override + public void sortBySubjectFilteredClientStudentList() { + fail("This method should not be called."); + } + + public void resetHighLight() { + fail("This method should not be called."); + } + } + + /** + * A Model stub that always throw a DuplicatePersonException when trying to add a person. + */ + private class ModelStubThrowingDuplicateClientException extends AddClientCommandTest.ModelStub { + @Override + public void addTutor(Client tutor) throws DuplicatePersonException { + throw new DuplicatePersonException(); + } + + @Override + public void addStudent(Client student) throws DuplicatePersonException { + throw new DuplicatePersonException(); + } + + @Override + public ReadOnlyAddressBook getAddressBook() { + return new AddressBook(); + } + } + + /** + * A Model stub that always accept the client being added. + */ + private class ModelStubAcceptingClientAdded extends AddClientCommandTest.ModelStub { + final ArrayList tutorsAdded = new ArrayList<>(); + final ArrayList studentsAdded = new ArrayList<>(); + + @Override + public void addTutor(Client tutor) throws DuplicatePersonException { + requireNonNull(tutor); + tutorsAdded.add(tutor); + } + + @Override + public void addStudent(Client student) throws DuplicatePersonException { + requireNonNull(student); + studentsAdded.add(student); + } + + @Override + public ReadOnlyAddressBook getAddressBook() { + return new AddressBook(); + } + } + +} +``` +###### \java\seedu\address\logic\parser\AddClientCommandParserTest.java +``` java +public class AddClientCommandParserTest { + private AddClientCommandParser parser = new AddClientCommandParser(); + + @Test + public void parse_allFieldsPresent_success() { + Client expectedPerson = new ClientBuilder().withName(VALID_NAME_BOB).withPhone(VALID_PHONE_BOB) + .withEmail(VALID_EMAIL_BOB).withAddress(VALID_ADDRESS_BOB).withTags(VALID_TAG_FRIEND) + .withLocation(VALID_LOCATION_BOB).withGrade(VALID_GRADE_BOB).withSubject(VALID_GRADE_BOB) + .withCategory(VALID_CATEGORY_BOB).build(); + + Client expectedTutor = new ClientBuilder().withName(VALID_NAME_BOB).withPhone(VALID_PHONE_BOB) + .withEmail(VALID_EMAIL_BOB).withAddress(VALID_ADDRESS_BOB).withTags(VALID_TAG_FRIEND) + .withLocation(VALID_LOCATION_BOB).withGrade(VALID_GRADE_BOB).withSubject(VALID_GRADE_BOB) + .withCategory(VALID_CATEGORY_TUTOR_BOB).build(); + + // whitespace only preamble + assertParseSuccess(parser, PREAMBLE_WHITESPACE + NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + + ADDRESS_DESC_BOB + TAG_DESC_FRIEND + LOCATION_DESC_BOB + GRADE_DESC_BOB + SUBJECT_DESC_BOB + + CATEGORY_DESC_BOB, new AddClientCommand(expectedPerson)); + + // multiple names - last name accepted + assertParseSuccess(parser, NAME_DESC_AMY + NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + + ADDRESS_DESC_BOB + TAG_DESC_FRIEND + LOCATION_DESC_BOB + GRADE_DESC_BOB + SUBJECT_DESC_BOB + + CATEGORY_DESC_BOB, new AddClientCommand(expectedPerson)); + + // multiple phones - last phone accepted + assertParseSuccess(parser, NAME_DESC_BOB + PHONE_DESC_AMY + PHONE_DESC_BOB + EMAIL_DESC_BOB + + ADDRESS_DESC_BOB + TAG_DESC_FRIEND + LOCATION_DESC_BOB + GRADE_DESC_BOB + SUBJECT_DESC_BOB + + CATEGORY_DESC_BOB, new AddClientCommand(expectedPerson)); + + // multiple emails - last email accepted + assertParseSuccess(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_AMY + EMAIL_DESC_BOB + + ADDRESS_DESC_BOB + TAG_DESC_FRIEND + LOCATION_DESC_BOB + GRADE_DESC_BOB + SUBJECT_DESC_BOB + + CATEGORY_DESC_BOB, new AddClientCommand(expectedPerson)); + + // multiple addresses - last address accepted + assertParseSuccess(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_AMY + + ADDRESS_DESC_BOB + TAG_DESC_FRIEND + LOCATION_DESC_BOB + GRADE_DESC_BOB + SUBJECT_DESC_BOB + + CATEGORY_DESC_BOB, new AddClientCommand(expectedPerson)); + + // valid fields added to tutor + assertParseSuccess(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB + + TAG_DESC_FRIEND + LOCATION_DESC_BOB + GRADE_DESC_BOB + SUBJECT_DESC_BOB + + CATEGORY_DESC_TUTOR_BOB, new AddClientCommand(expectedTutor)); + + // multiple tags - all accepted + Client expectedPersonMultipleTags = new ClientBuilder().withName(VALID_NAME_BOB).withPhone(VALID_PHONE_BOB) + .withEmail(VALID_EMAIL_BOB).withAddress(VALID_ADDRESS_BOB) + .withTags(VALID_TAG_FRIEND, VALID_TAG_HUSBAND) + .withLocation(VALID_LOCATION_BOB).withGrade(VALID_GRADE_BOB).withSubject(VALID_GRADE_BOB) + .withCategory(VALID_CATEGORY_BOB).build(); + assertParseSuccess(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB + + TAG_DESC_HUSBAND + TAG_DESC_FRIEND + LOCATION_DESC_BOB + GRADE_DESC_BOB + SUBJECT_DESC_BOB + + CATEGORY_DESC_BOB, new AddClientCommand(expectedPersonMultipleTags)); + } + + @Test + public void parse_optionalFieldsMissing_success() { + // zero tags + Client expectedPerson = new ClientBuilder().withName(VALID_NAME_AMY).withPhone(VALID_PHONE_AMY) + .withEmail(VALID_EMAIL_AMY).withAddress(VALID_ADDRESS_AMY).withTags() + .withLocation(VALID_LOCATION_AMY).withGrade(VALID_GRADE_AMY).withSubject(VALID_GRADE_AMY) + .withCategory(VALID_CATEGORY_AMY).build(); + assertParseSuccess(parser, NAME_DESC_AMY + PHONE_DESC_AMY + EMAIL_DESC_AMY + ADDRESS_DESC_AMY + + LOCATION_DESC_AMY + GRADE_DESC_AMY + SUBJECT_DESC_AMY + CATEGORY_DESC_AMY, + new AddClientCommand(expectedPerson)); + } + + @Test + public void parse_compulsoryFieldMissing_failure() { + String expectedMessage = String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddClientCommand.MESSAGE_USAGE); + + // missing name prefix + assertParseFailure(parser, VALID_NAME_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB, + expectedMessage); + + // missing phone prefix + assertParseFailure(parser, NAME_DESC_BOB + VALID_PHONE_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB, + expectedMessage); + + // missing email prefix + assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + VALID_EMAIL_BOB + ADDRESS_DESC_BOB, + expectedMessage); + + // missing address prefix + assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + VALID_ADDRESS_BOB, + expectedMessage); + + // all prefixes missing + assertParseFailure(parser, VALID_NAME_BOB + VALID_PHONE_BOB + VALID_EMAIL_BOB + VALID_ADDRESS_BOB, + expectedMessage); + } + + @Test + public void parse_invalidValue_failure() { + // invalid name + assertParseFailure(parser, INVALID_NAME_DESC + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB + + TAG_DESC_HUSBAND + TAG_DESC_FRIEND + LOCATION_DESC_BOB + GRADE_DESC_BOB + SUBJECT_DESC_BOB + + CATEGORY_DESC_BOB, Name.MESSAGE_NAME_CONSTRAINTS); + + // invalid phone + assertParseFailure(parser, NAME_DESC_BOB + INVALID_PHONE_DESC + EMAIL_DESC_BOB + ADDRESS_DESC_BOB + + TAG_DESC_HUSBAND + TAG_DESC_FRIEND + LOCATION_DESC_BOB + GRADE_DESC_BOB + SUBJECT_DESC_BOB + + CATEGORY_DESC_BOB, Phone.MESSAGE_PHONE_CONSTRAINTS); + + // invalid email + assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + INVALID_EMAIL_DESC + ADDRESS_DESC_BOB + + TAG_DESC_HUSBAND + TAG_DESC_FRIEND + LOCATION_DESC_BOB + GRADE_DESC_BOB + SUBJECT_DESC_BOB + + CATEGORY_DESC_BOB, Email.MESSAGE_EMAIL_CONSTRAINTS); + + // invalid address + assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + INVALID_ADDRESS_DESC + + TAG_DESC_HUSBAND + TAG_DESC_FRIEND + LOCATION_DESC_BOB + GRADE_DESC_BOB + SUBJECT_DESC_BOB + + CATEGORY_DESC_BOB, Address.MESSAGE_ADDRESS_CONSTRAINTS); + + // invalid tag + assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB + + INVALID_TAG_DESC + VALID_TAG_FRIEND + LOCATION_DESC_BOB + GRADE_DESC_BOB + SUBJECT_DESC_BOB + + CATEGORY_DESC_BOB, Tag.MESSAGE_TAG_CONSTRAINTS); + + // two invalid values, only first invalid value reported + assertParseFailure(parser, INVALID_NAME_DESC + PHONE_DESC_BOB + EMAIL_DESC_BOB + INVALID_ADDRESS_DESC + + LOCATION_DESC_BOB + GRADE_DESC_BOB + SUBJECT_DESC_BOB + CATEGORY_DESC_BOB, + Name.MESSAGE_NAME_CONSTRAINTS); + + // non-empty preamble + assertParseFailure(parser, PREAMBLE_NON_EMPTY + NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + + ADDRESS_DESC_BOB + TAG_DESC_HUSBAND + TAG_DESC_FRIEND + LOCATION_DESC_BOB + GRADE_DESC_BOB + + SUBJECT_DESC_BOB + CATEGORY_DESC_BOB, + String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddClientCommand.MESSAGE_USAGE)); + } +} +``` +###### \java\seedu\address\logic\parser\RemoveCommandParserTest.java +``` java +public class RemoveCommandParserTest { + + private RemoveCommandParser parser = new RemoveCommandParser(); + + @Test + public void parse_validArgs_returnsRemoveCommand() { + assertParseSuccess(parser, "1 c/s s/math", + new RemoveCommand(INDEX_FIRST_PERSON, new Subject("math"), new Category("s"))); + assertParseSuccess(parser, "1 c/t s/math", + new RemoveCommand(INDEX_FIRST_PERSON, new Subject("math"), new Category("t"))); + } + + @Test + public void parse_invalidArgs_throwsParseException() { + // Missing index + assertParseFailure(parser, "c/s s/math", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, RemoveCommand.MESSAGE_USAGE)); + // Missing subject + assertParseFailure(parser, "1 c/s", String.format(MESSAGE_INVALID_COMMAND_FORMAT, RemoveCommand.MESSAGE_USAGE)); + // Missing category + assertParseFailure(parser, "1 s/math", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, RemoveCommand.MESSAGE_USAGE)); + } +} +``` +###### \java\seedu\address\model\person\CategoryTest.java +``` java +public class CategoryTest { + + @Test + public void constructor_null_throwsNullPointerException() { + Assert.assertThrows(NullPointerException.class, () -> new Category(null)); + } + + @Test + public void constructor_invalidSubject_throwsIllegalArgumentException() { + String invalidSubject = ""; + Assert.assertThrows(IllegalArgumentException.class, () -> new Category(invalidSubject)); + } + + @Test + public void isValidCategory() { + // null subject + Assert.assertThrows(NullPointerException.class, () -> Category.isValidCategory(null)); + + // invalid subject + assertFalse(Category.isValidCategory("")); // empty string + assertFalse(Category.isValidCategory(" ")); // spaces only + assertFalse(Category.isValidCategory("a")); // character apart from s or t + assertFalse(Category.isValidCategory("st")); // more than just character s or t + + // valid subject + assertTrue(Category.isValidCategory("s")); + assertTrue(Category.isValidCategory("t")); + } +} +``` +###### \java\seedu\address\model\person\GradeTest.java +``` java +public class GradeTest { + + @Test + public void constructor_null_throwsNullPointerException() { + Assert.assertThrows(NullPointerException.class, () -> new Grade(null)); + } + + @Test + public void constructor_invalidSubject_throwsIllegalArgumentException() { + String invalidSubject = ""; + Assert.assertThrows(IllegalArgumentException.class, () -> new Grade(invalidSubject)); + } + + @Test + public void isValidGrade() { + // null subject + Assert.assertThrows(NullPointerException.class, () -> Grade.isValidGrade(null)); + + // invalid subject + assertFalse(Grade.isValidGrade("")); // empty string + assertFalse(Grade.isValidGrade(" ")); // spaces only + assertFalse(Grade.isValidGrade("pri5")); // invalid format + assertFalse(Grade.isValidGrade("primary 3")); // spacing between "primary" and "3" + + // one or more invalid subject + assertFalse(Grade.isValidGrade("pri4 p2 p1 s3")); // one invalid grade + assertFalse(Grade.isValidGrade("pre2 asdo feiwo")); // many invalid grade + assertFalse(Grade.isValidGrade("p2 p2")); // multiple same grade + + // valid subject + assertTrue(Grade.isValidGrade("p3")); //alias + assertTrue(Grade.isValidGrade("primary3")); // full grade + assertTrue(Grade.isValidGrade("secondary3")); // full grade + assertTrue(Grade.isValidGrade("p3 s3 s1 p4")); // multiple valid grade + } +} +``` +###### \java\seedu\address\model\person\LocationTest.java +``` java +public class LocationTest { + + @Test + public void constructor_null_throwsNullPointerException() { + Assert.assertThrows(NullPointerException.class, () -> new Location(null)); + } + + @Test + public void constructor_invalidLocation_throwsIllegalArgumentException() { + String invalidLocation = ""; + Assert.assertThrows(IllegalArgumentException.class, () -> new Location(invalidLocation)); + } + + @Test + public void isValidLocation() { + // null location + Assert.assertThrows(NullPointerException.class, () -> Location.isValidLocation(null)); + + // invalid location + assertFalse(Location.isValidLocation("")); // empty string + assertFalse(Location.isValidLocation(" ")); // spaces only + assertFalse(Location.isValidLocation("sodv")); // invalid location + assertFalse(Location.isValidLocation("north asdf")); // one invalid location + assertFalse(Location.isValidLocation("fdsaob efowfds idb south")); // multiple invalid location + assertFalse(Location.isValidLocation("north south north")); // repeated location + + // valid location + assertTrue(Location.isValidLocation("north")); + assertTrue(Location.isValidLocation("south")); + assertTrue(Location.isValidLocation("west")); + assertTrue(Location.isValidLocation("east")); + assertTrue(Location.isValidLocation("central")); + assertTrue(Location.isValidLocation("central north south")); // multiple valid location + } +} +``` +###### \java\seedu\address\model\person\SubjectTest.java +``` java +public class SubjectTest { + + @Test + public void constructor_null_throwsNullPointerException() { + Assert.assertThrows(NullPointerException.class, () -> new Subject(null)); + } + + @Test + public void constructor_invalidSubject_throwsIllegalArgumentException() { + String invalidSubject = ""; + Assert.assertThrows(IllegalArgumentException.class, () -> new Subject(invalidSubject)); + } + + @Test + public void isValidSubject() { + // null subject + Assert.assertThrows(NullPointerException.class, () -> Subject.isValidSubject(null)); + + // invalid subject + assertFalse(Subject.isValidSubject("")); // empty string + assertFalse(Subject.isValidSubject(" ")); // spaces only + + // valid subject + assertTrue(Subject.isValidSubject("math")); + assertTrue(Subject.isValidSubject("-")); // one character + assertTrue(Subject.isValidSubject("math, physics, chemistry, english, chinese")); // long subject + } +} +``` +###### \java\seedu\address\model\UniqueClientListTest.java +``` java +public class UniqueClientListTest { + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Test + public void asObservableList_modifyList_throwsUnsupportedOperationException() { + UniqueClientList uniqueClientList = new UniqueClientList(); + thrown.expect(UnsupportedOperationException.class); + uniqueClientList.asObservableList().remove(0); + } +} +``` +###### \java\seedu\address\testutil\ClientBuilder.java +``` java +/** + * A utility class to help with building Client objects. + */ +public class ClientBuilder { + + public static final String DEFAULT_NAME = "Alice Pauline"; + public static final String DEFAULT_PHONE = "85355255"; + public static final String DEFAULT_EMAIL = "alice@gmail.com"; + public static final String DEFAULT_ADDRESS = "123, Jurong West Ave 6, #08-111"; + public static final String DEFAULT_TAGS = "friends"; + public static final String DEFAULT_LOCATION = "north"; + public static final String DEFAULT_GRADE = "p3"; + public static final String DEFAULT_SUBJECT = "physics"; + public static final String DEFAULT_CATEGORY = "s"; + + private Name name; + private Phone phone; + private Email email; + private Address address; + private Location location; + private Grade grade; + private Subject subject; + private Category category; + private Set tags; + + public ClientBuilder() { + name = new Name(DEFAULT_NAME); + phone = new Phone(DEFAULT_PHONE); + email = new Email(DEFAULT_EMAIL); + address = new Address(DEFAULT_ADDRESS); + tags = SampleDataUtil.getTagSet(DEFAULT_TAGS); + location = new Location(DEFAULT_LOCATION); + grade = new Grade(DEFAULT_GRADE); + subject = new Subject(DEFAULT_SUBJECT); + category = new Category(DEFAULT_CATEGORY); + } + + /** + * Initializes the ClientBuilder with the data of {@code ClientToCopy}. + */ + public ClientBuilder(Client clientToCopy) { + name = clientToCopy.getName(); + phone = clientToCopy.getPhone(); + email = clientToCopy.getEmail(); + address = clientToCopy.getAddress(); + tags = new HashSet<>(clientToCopy.getTags()); + location = clientToCopy.getLocation(); + grade = clientToCopy.getGrade(); + subject = clientToCopy.getSubject(); + } + + /** + * Sets the {@code Name} of the {@code Client} that we are building. + */ + public ClientBuilder withName(String name) { + this.name = new Name(name); + return this; + } + + /** + * Parses the {@code tags} into a {@code Set} and set it to the {@code Client} that we are building. + */ + public ClientBuilder withTags(String ... tags) { + this.tags = SampleDataUtil.getTagSet(tags); + return this; + } + + /** + * Sets the {@code Address} of the {@code Client} that we are building. + */ + public ClientBuilder withAddress(String address) { + this.address = new Address(address); + return this; + } + + /** + * Sets the {@code Phone} of the {@code Client} that we are building. + */ + public ClientBuilder withPhone(String phone) { + this.phone = new Phone(phone); + return this; + } + + /** + * Sets the {@code Email} of the {@code Client} that we are building. + */ + public ClientBuilder withEmail(String email) { + this.email = new Email(email); + return this; + } + + /** + * Sets the {@code Location} of the {@code Client} that we are building. + */ + public ClientBuilder withLocation(String location) { + this.location = new Location(location); + return this; + } + + /** + * Sets the {@code Grade} of the {@code Client} that we are building. + */ + public ClientBuilder withGrade(String grade) { + this.grade = new Grade(grade); + return this; + } + + /** + * Sets the {@code Subject} of the {@code Client} that we are building. + */ + public ClientBuilder withSubject(String subject) { + this.subject = new Subject(subject); + return this; + } + + /** + * Sets the {@code Category} of the {@code Client} that we are building. + */ + public ClientBuilder withCategory(String category) { + this.category = new Category(category); + return this; + } + + public Client build() { + return new Client(name, phone, email, address, tags, location, grade, subject, category); + } + +} +``` +###### \java\seedu\address\testutil\ClientUtil.java +``` java +/** + * A utility class for Client. + */ +public class ClientUtil { + + /** + * Returns an add command string for adding the {@code client}. + */ + public static String getAddClientCommand(Client client) { + return AddClientCommand.COMMAND_WORD + " " + getClientDetails(client); + } + + /** + * Returns the part of command string for the given {@code client}'s details. + */ + public static String getClientDetails(Client client) { + StringBuilder sb = new StringBuilder(); + sb.append(PREFIX_CATEGORY + client.getCategory().value + " "); + sb.append(PREFIX_NAME + client.getName().fullName + " "); + sb.append(PREFIX_PHONE + client.getPhone().value + " "); + sb.append(PREFIX_EMAIL + client.getEmail().value + " "); + sb.append(PREFIX_ADDRESS + client.getAddress().value + " "); + client.getTags().stream().forEach(s -> sb.append(PREFIX_TAG + s.tagName + " ")); + sb.append(PREFIX_LOCATION + client.getLocation().value + " "); + sb.append(PREFIX_GRADE + client.getGrade().value + " "); + sb.append(PREFIX_SUBJECT + client.getSubject().value + " "); + + return sb.toString(); + } +} +``` +###### \java\seedu\address\testutil\EditPersonDescriptorBuilder.java +``` java + /** + * Sets the {@code Address} of the {@code EditPersonDescriptor} that we are building. + */ + public EditPersonDescriptorBuilder withLocation(String location) { + descriptor.setLocation(new Location(location)); + return this; + } + + /** + * Sets the {@code Address} of the {@code EditPersonDescriptor} that we are building. + */ + public EditPersonDescriptorBuilder withGrade(String grade) { + descriptor.setGrade(new Grade(grade)); + return this; + } + + /** + * Sets the {@code Address} of the {@code EditPersonDescriptor} that we are building. + */ + public EditPersonDescriptorBuilder withSubject(String subject) { + descriptor.setSubject(new Subject(subject)); + return this; + } + + /** + * Sets the {@code Address} of the {@code EditPersonDescriptor} that we are building. + */ + public EditPersonDescriptorBuilder withCategory(String category) { + descriptor.setCategory(new Category(category)); + return this; + } + + public EditPersonDescriptor build() { + return descriptor; + } +} +``` +###### \java\seedu\address\ui\ClientCardTest.java +``` java +public class ClientCardTest extends GuiUnitTest { + + @Test + public void display() { + // no tags + Client personWithNoTags = new ClientBuilder().withTags(new String[0]).build(); + ClientCard clientCard = new ClientCard(personWithNoTags, 1); + uiPartRule.setUiPart(clientCard); + assertCardDisplay(clientCard, personWithNoTags, 1); + + // with tags + Client personWithTags = new ClientBuilder().build(); + clientCard = new ClientCard(personWithTags, 2); + uiPartRule.setUiPart(clientCard); + assertCardDisplay(clientCard, personWithTags, 2); + } + + @Test + public void equals() { + Client person = new ClientBuilder().build(); + ClientCard personCard = new ClientCard(person, 0); + + // same person, same index -> returns true + ClientCard copy = new ClientCard(person, 0); + assertTrue(personCard.equals(copy)); + + // same object -> returns true + assertTrue(personCard.equals(personCard)); + + // null -> returns false + assertFalse(personCard.equals(null)); + + // different types -> returns false + assertFalse(personCard.equals(0)); + + // different person, same index -> returns false + Client differentPerson = new ClientBuilder().withName("differentName").build(); + assertFalse(personCard.equals(new ClientCard(differentPerson, 0))); + + // same person, different index -> returns false + assertFalse(personCard.equals(new ClientCard(person, 1))); + } + + /** + * Asserts that {@code personCard} displays the details of {@code expectedPerson} correctly and matches + * {@code expectedId}. + */ + private void assertCardDisplay(ClientCard clientCard, Client expectedClient, int expectedId) { + guiRobot.pauseForHuman(); + + PersonCardHandle personCardHandle = new PersonCardHandle(clientCard.getRoot()); + + // verify id is displayed correctly + assertEquals(Integer.toString(expectedId) + ". ", personCardHandle.getId()); + + // verify person details are displayed correctly + assertCardDisplaysPerson(expectedClient, personCardHandle); + } +} +``` diff --git a/docs/AboutUs.adoc b/docs/AboutUs.adoc index 0f0a8e7ab51e..daa750f0d0f3 100644 --- a/docs/AboutUs.adoc +++ b/docs/AboutUs.adoc @@ -3,53 +3,35 @@ :imagesDir: images :stylesDir: stylesheets -AddressBook - Level 4 was developed by the https://se-edu.github.io/docs/Team.html[se-edu] team. + -_{The dummy content given below serves as a placeholder to be used by future forks of the project.}_ + +TuitionCor was developed by the https://github.com/CS2103JAN2018-F11-B2[CS2103JAN2018-F11-B2] team. + {empty} + We are a team based in the http://www.comp.nus.edu.sg[School of Computing, National University of Singapore]. == Project Team -=== John Doe -image::damithc.jpg[width="150", align="left"] -{empty}[http://www.comp.nus.edu.sg/~damithch[homepage]] [https://github.com/damithc[github]] [<>] - -Role: Project Advisor - -''' - -=== John Roe -image::lejolly.jpg[width="150", align="left"] -{empty}[http://github.com/lejolly[github]] [<>] +=== Ngeow Shan Yong Destin +image::destin.jpg[width="150", align="left"] +{empty}[https://github.com/shookshire[github]] [<>] Role: Team Lead + -Responsibilities: UI - -''' - -=== Johnny Doe -image::yijinl.jpg[width="150", align="left"] -{empty}[http://github.com/yijinl[github]] [<>] - -Role: Developer + -Responsibilities: Data +Responsibilities: Model ''' -=== Johnny Roe -image::m133225.jpg[width="150", align="left"] -{empty}[http://github.com/m133225[github]] [<>] +=== Lim Hong Cho +image::hongCho.jpg[width="150", align="left"] +{empty}[https://github.com/olimhc[github]] [<>] Role: Developer + -Responsibilities: Dev Ops + Threading +Responsibilities: UI ''' -=== Benson Meier -image::yl_coder.jpg[width="150", align="left"] -{empty}[http://github.com/yl-coder[github]] [<>] +=== Zhu Jiahui +image::jiahui.jpg[width="150", align="left"] +{empty}[https://github.com/Zhu-Jiahui[github]] [<>] Role: Developer + -Responsibilities: UI +Responsibilities: Logic ''' diff --git a/docs/ContactUs.adoc b/docs/ContactUs.adoc index eafdc9574a50..2ff7294858fd 100644 --- a/docs/ContactUs.adoc +++ b/docs/ContactUs.adoc @@ -1,6 +1,6 @@ = Contact Us :stylesDir: stylesheets -* *Bug reports, Suggestions* : Post in our https://github.com/se-edu/addressbook-level4/issues[issue tracker] if you noticed bugs or have suggestions on how to improve. +* *Bug reports, Suggestions* : Post in our https://github.com/CS2103JAN2018-F11-B2/main/issues[issue tracker] if you noticed bugs or have suggestions on how to improve. * *Contributing* : We welcome pull requests. Follow the process described https://github.com/oss-generic/process[here] -* *Email us* : You can also reach us at `damith [at] comp.nus.edu.sg` +* *Email us* : You can also reach us at `e0030896@u.nus.edu` (Destin) , `e0031013@u.nus.edu` (Hong Cho), `e0032017@u.nus.edu` (Jia Hui). diff --git a/docs/DeveloperGuide.adoc b/docs/DeveloperGuide.adoc index 1733af113b29..597243d0da3d 100644 --- a/docs/DeveloperGuide.adoc +++ b/docs/DeveloperGuide.adoc @@ -1,4 +1,4 @@ -= AddressBook Level 4 - Developer Guide += TuitionCor - Developer Guide :toc: :toc-title: :toc-placement: preamble @@ -10,9 +10,11 @@ ifdef::env-github[] :tip-caption: :bulb: :note-caption: :information_source: endif::[] -:repoURL: https://github.com/se-edu/addressbook-level4/tree/master +:repoURL: https://github.com/CS2103JAN2018-F11-B2/main +:forked from Team SE-EDU +:Team SE-EDU: https://github.com/se-edu/addressbook-level4 -By: `Team SE-EDU`      Since: `Jun 2016`      Licence: `MIT` +By: `Team F11-B2`      Since: `Jun 2016`      Licence: `MIT` == Setting up @@ -68,7 +70,7 @@ Optionally, you can follow the <> docume ==== Updating documentation to match your fork -After forking the repo, links in the documentation will still point to the `se-edu/addressbook-level4` repo. If you plan to develop this as a separate product (i.e. instead of contributing to the `se-edu/addressbook-level4`) , you should replace the URL in the variable `repoURL` in `DeveloperGuide.adoc` and `UserGuide.adoc` with the URL of your fork. +After forking the repo, links in the documentation will still point to the `CS2103JAN2018-F11-B2/main` repo. If you plan to develop this as a separate product (i.e. instead of contributing to the `CS2103JAN2018-F11-B2/main`) , you should replace the URL in the variable `repoURL` in `DeveloperGuide.adoc` and `UserGuide.adoc` with the URL of your fork. ==== Setting up CI @@ -160,7 +162,7 @@ image::UiClassDiagram.png[width="800"] *API* : link:{repoURL}/src/main/java/seedu/address/ui/Ui.java[`Ui.java`] -The UI consists of a `MainWindow` that is made up of parts e.g.`CommandBox`, `ResultDisplay`, `PersonListPanel`, `StatusBarFooter`, `BrowserPanel` etc. All these, including the `MainWindow`, inherit from the abstract `UiPart` class. +The UI consists of a `MainWindow` that is made up of parts e.g.`CommandBox`, `ResultDisplay`, `StudentListPanel`, `TutorListPanel`, `StatusBarFooter` etc. All these, including the `MainWindow`, inherit from the abstract `UiPart` class. The `UI` component uses JavaFx UI framework. The layout of these UI parts are defined in matching `.fxml` files that are in the `src/main/resources/view` folder. For example, the layout of the link:{repoURL}/src/main/java/seedu/address/ui/MainWindow.java[`MainWindow`] is specified in link:{repoURL}/src/main/resources/view/MainWindow.fxml[`MainWindow.fxml`] @@ -185,14 +187,16 @@ link:{repoURL}/src/main/java/seedu/address/logic/Logic.java[`Logic.java`] . `Logic` uses the `AddressBookParser` class to parse the user command. . This results in a `Command` object which is executed by the `LogicManager`. -. The command execution can affect the `Model` (e.g. adding a person) and/or raise events. +. The command execution can affect the `Model` (e.g. adding a client) and/or raise events. . The result of the command execution is encapsulated as a `CommandResult` object which is passed back to the `Ui`. -Given below is the Sequence Diagram for interactions within the `Logic` component for the `execute("delete 1")` API call. +Given below is the Sequence Diagram for interactions within the `Logic` component for the `execute("delete 1 c/s")` API call. -.Interactions Inside the Logic Component for the `delete 1` Command +.Interactions Inside the Logic Component for the `delete 1 c/s` Command image::DeletePersonSdForLogic.png[width="800"] +// tag::modelComponent[] + [[Design-Model]] === Model component @@ -205,9 +209,11 @@ The `Model`, * stores a `UserPref` object that represents the user's preferences. * stores the Address Book data. -* exposes an unmodifiable `ObservableList` that can be 'observed' e.g. the UI can be bound to this list so that the UI automatically updates when the data in the list change. +* exposes an unmodifiable `ObservableList` that can be 'observed' e.g. the UI can be bound to this list so that the UI automatically updates when the data in the list change. * does not depend on any of the other three components. +// end::modelComponent[] + [[Design-Storage]] === Storage component @@ -230,6 +236,92 @@ Classes used by multiple components are in the `seedu.addressbook.commons` packa This section describes some noteworthy details on how certain features are implemented. +// tag::modelImplementation[] + +=== Model +==== Current Implementation + +The AddressBook currently stores Clients in separate UniqueClientList (students, tutors, closedStudents, closedTutors) depending on category the Client belongs to. + +Due to students and tutors being stored as 2 separate UniqueClientList, commands that work on only 1 of the 2 list (eg. addclient, edit, delete etc.) would require the model to either expose 2 functions for the Command to call or to take in a Category argument which the model would then handle as shown below. + +image::StudentTutorList.png[width="800"] + +Commands that work only on either students or tutors would have to declare which Category they wish to work on in the command input. For example for the delete command would have to input `delete 1 c/s` to represent that they wish to delete from students. + +Hence, commands that work only on either students or tutors have to either be split into 2 functions or take in an additional argument stating which UniqueClientList to work on as shown below. + +.Example of splitting call into 2 functions +image::SeparateFunctionForStudentTutor.png[width="800"] + +---- +public class updatePerson(Client target, Client editedClient, Category category) { + if (category.isStudent) { + // ... work on students ... + } else if (category.isTutor) { + // ... work on tutors ... + } +} +---- +Example of function that takes in additional argument to determine whether to work on students or tutors + +==== Design Considerations of Model + +===== Aspect: Keeping students and tutors in 1 or 2 list +* **Alternative 1 (current choice):** Keep students and tutors separated in 2 UniqueClientList +** Pros: Does not have to do an additional search through the list to differentiate students and tutors +** Cons: Calls that work on only one of the 2 list would have to be separated. +* **Alternative 2:** Keep all clients in the same UniqueClientList and differentiate them using the clients' Category value +** Pros: Easy to implement. +** Cons: Have to run an additional search through the list to differentiate students and tutors which could be slow when database is large. + +===== Aspect: Whether to have different commands for students and tutors +* **Alternative 1 (current choice):** Make the Category prefix compulsory for any command that work on only 1 list +** Pros: Minimises number of commands and use already existing prefix instead. +** Cons: Needs to parse an additional prefix +* **Alternative 2:** Have separate commands (eg. deleteStudent and deleteTutor instead of just delete) +** Pros: Easy to implement. +** Cons: Large number of duplicated commands. + +=== Client and the fields of Client +==== Current Implementation + +The Client Class extends Person that consists of 5 fields (name, phone, email, address, tags) while Client has additional 4 fields (location, grade, subject, category). + +All of the fields uses a specific class that takes a single String as it's value as shown below. + +.Example of Subject field with it's value +image::ClientFieldExample.png[width="800"] + +Hence, for fields that should actually take in multiple values (eg. Subject taking in "math physics chemistry") are currently stored as a single String while operations that work on the individual keywords have to manually split them into separate keywords through String operations. + +---- +private static Client createEditedPerson(Client personToEdit, Subject toRemove) { + assert personToEdit != null; + + String editSubjects = personToEdit.getSubject().value; + ArrayList editSubjectArray = new ArrayList<>(Arrays.asList(editSubjects.split(" "))); + editSubjectArray.remove(toRemove.value); + + StringBuilder sb = new StringBuilder(); + ..... +---- + +As you can see from this section taken from Remove Command, after getting the String of the value of Subject, it has to use String operations to split it based on whitespace and reconstruct the string again later. + +On the other hand, a client can have multiple Tags as the Tag are stored through an UniqueTagList. + +==== Design Considerations +===== Aspect: To store multiple values in a single String or as separate objects in a list +* **(Alternative 1 (current choice)):** Store all the values as a single String and have commands that work on individual keywords split them. +** Pros: Most commands simply require to read the value directly as a String and does not require to work on individual keyword. Does not require multiple types of unique list to be created for each of the field. +** Cons: Lacks in flexibility in being able to individual values. +* **(Alternative 2)** Create a list similar to UniqueTagList for each field that requires the storage of multiple values +** Pros: Allows for greater flexibility and the ability to sync the various existing values in the AddressBook. +** Cons: Require many lists to be stored. Whenever more fields are added in future enhancements, it could potentially require the excessive creation of a new type unique list each time. + +// end::modelImplementation[] + // tag::undoredo[] === Undo/Redo feature ==== Current Implementation @@ -358,12 +450,504 @@ image::UndoRedoActivityDiagram.png[width="650"] ** Cons: Requires dealing with commands that have already been undone: We must remember to skip these commands. Violates Single Responsibility Principle and Separation of Concerns as `HistoryManager` now needs to do two * different things. // end::undoredo[] -// tag::dataencryption[] -=== [Proposed] Data Encryption +// tag::match[] +=== Match Function +==== Current Implementation +The match function behaves like a multi-layer find function. It helps to match clients that share one or more similar attributes. + +For example, John is a Tutor client that is staying in the `west` of Singapore, and is looking for students that requires help in `s4`(secondary4) `math`. + +first, locate John by either using `find John` or finding him in the tutor list. Observe John's Index number. + +image::MatchSequence1.png[width="800'] + +from the sample image above, we can see that John's index number is 6 in tutors list. + +image::MatchSequence2.png[width="800'] +Keying in the command in the format `match` `INDEX` `Category`. eg: `match 6 c/t` + +image::MatchSequence3.png[width="800'] +Match function returns a list with all the potential students for John with the highest match (all attributes matched) on top of the list. + +All the matched attributes are highlighted in orange. + +==== Step by Step breakdown + +match function behaves like at double-layered find function. When a user enters `match 1 c/t`, the system will first need to extract the first tutor from the tutor's list. + +To do this, first we need to identify the client is a tutor or a student. + +The detailed code is shown below: +[source,java] +---- +if (category.isStudent()) { + lastShownList = model.getFilteredStudentList(); + } else { + lastShownList = model.getFilteredTutorList(); + } + clientToMatch = lastShownList.get(targetIndex.getZeroBased()); + +---- + +[NOTE] +If the user input format is invalid, an exception will be thrown + + +When the client is located, his data (location, grade and subject) are then sent to `MatchContainsKeywordsPredicate.java` + +The detailed code is shown below: + +[source,java] +---- + @Override + public boolean test(Client other) { + boolean isMatch = false; + + if(other.getLocation().equals(client.getLocation())) { + isMatch = true; + } + if(other.getGrade().equals(client.getGrade())) { + isMatch = true; + } + if(other.getSubject().equals(client.getSubject())) { + isMatch = true; + } + return isMatch; + } + +---- +[NOTE] +If there is no match client found, an empty list will be shown. + +image::MatchZeroClient.png[width="800'] + +If the input client is a tutor, the tutor list will only be showing the particular of the input client. + +image::MatchSequence3.png[width="800'] + +this is done in MatchContainsPersonsPredicate.java +The detailed code is shown below: + +[source,java] +---- +public boolean test(Client other) { + return other.toString().equals(client.toString()); + } +---- +A sequence diagram is shown below to show the current design when user input "match 1 c/s" + +image::MatchSequenceDiagram.png[width="800"] + +==== Design Considerations + +===== Aspect: UserInput + +* **Alternative 1 (current choice):** User key in client index displayed with with client's category. e.g `match 1 c/t`. +** Pros: There will be no mismatch since user specify the exact client to match with. +** Cons: More user input and extra step is taken. User have to locate the client's index first before using match function. +* **Alternative 2:** user key in the client's name. e.g `match John`. +** Pros: More convenient for user to operate, lesser steps. +** Cons: If there are 2 John stored in the application, there is a chance for the application to match a wrong client. + +==== Future Implementations +===== Optimise match function to accommodate multiple `Grade` and `Subject` fields per client. + +image::MatchFunctionFutureImplementation.png[width="800"] + +* As illustrated above, match function currently highlights all the grades/subjects of a client as long as one of the grade/subject is matched. +* Future Implementation could be done to only highlight the respective matched grade/subject. +// end::match[] + +// tag::sorting[] +=== Sort feature +==== Current Implementation + +The sort command is facilitated by a `SortCommandParser` which implements `Parser`. The sort function supports sorting of different fields, namely location, grade, subject and name, which updates the list according to the sorting method. + +Different types of sorting methods extends abstract class `SortCommand` which extends `Command` that resides in Logic as shown in the model below. + +image::SortCommandClassDiagram.PNG[width="800"] + +The different type of sort command are implemented this way: +[source,java] +---- +public class SortByNameCommand extends SortCommand { + @Override + public CommandResult execute() { + // ... list logic ... + } +} + +public class SortByLocationCommand extends SortCommand { + @Override + public CommandResult execute() { + // ... list logic ... + } +} + +public class SortByGradeCommand extends SortCommand { + @Override + public CommandResult execute() { + // ... list logic ... + } +} + +public class SortBySubjectCommand extends SortCommand { + @Override + public CommandResult execute() { + // ... list logic ... + } +} +---- + +When the user wants to sort the specific list according to his/her needs, `SortCommandParser` will parse the user input and decide which type of SortCommand to invoke. +The implementation is as follows: + +[source,java] +---- +public SortCommand parse(String args) throws ParseException { + requireNonNull(args); + ArgumentMultimap argumentMultimap = ArgumentTokenizer.tokenize(args, PREFIX_CATEGORY); + + if (!arePrefixesPresent(argumentMultimap, PREFIX_CATEGORY) + || argumentMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, SortCommand.MESSAGE_USAGE)); + } + + String sortType; + Category category; + + try { + sortType = argumentMultimap.getPreamble(); + category = ParserUtil.parseCategory(argumentMultimap.getValue(PREFIX_CATEGORY)).get(); + return getSortCommandType(category, sortType); + } catch (IllegalValueException ive) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, SortCommand.MESSAGE_USAGE)); + } + } +---- + +[NOTE] +If the user input format is invalid, an ParseException will be thrown and an error message will be displayed to user. + +A sequence diagram is shown below to show the current design when user input "sort l c/t". + +image::SortSequenceDiagram.PNG[width="800'] + +===== Design Considerations + +====== Aspect: Implementation of the different sort commands + +* **Alternative 1 (current choice):** Make SortCommand an abstract method and allow the different sort commands to inherit from it. +** Pros: Able to reuse constants from SortCommand +** Cons: SortCommand might be redundant +* **Alternative 2:** Implement multiple sort commands which extends command directly +** Pros: Might be easier to implement +** Cons: Messy + +====== Aspect: Parsing of user input for sort command + +* **Alternative 1 (current choice):** SortCommandParser parse one single string user input. Eg. 'sort l c/t' which sort tutor list by location. +** Pros: Able to utilise the current Logic and Model Component for command inputs +** Cons: Less intuitive to user. +* **Alternative 2:** Allow multiple user input of before parsing to a command. Eg. User input sort and system will prompts for user to input which list to sort. +** Pros: This is much more intuitive for user and allows further enhancements which require multiple command inputs. +** Cons: This will require to overhaul of Logic and Model Component. + + +// end::sorting[] + +// tag::closedClientList[] +=== Closed client list +==== Current Implementation + +The closed client list is where closed clients students or tutors information are stored and displayed. + +The implementation of the storage is similar to how clients are stored in the TuitionCor. Two `UniqueClientList` namely `closedStudents` and `closedTutors` were created to store the information of closed students and tutors. + +The addition of clients into their respective `UniqueClientList` are implemented this way: +[source,java] +---- + public void addClosedStudent(Client t) throws AssertionError { + closedStudents.add(closedStudent); + } + + public void addClosedTutor(Client t) throws AssertionError { + closedTutors.add(closedTutor); + } + + public void add(Client toAdd) throws AssertionError { + requireNonNull(toAdd); + if (contains(toAdd)) { + throw new AssertionError("It's impossible to have a duplicate person here"); + } + internalList.add(toAdd); + } +---- + +[NOTE] + An `AssertionError` is also thrown as there should not exist any duplicated person in the active list. + +Implementation of the removal of clients from the list are as follows: +[source,java] +---- + public boolean removeClosedClient(Client key, Category category) throws PersonNotFoundException { + Boolean isSuccess; + + if (category.isStudent()) { + isSuccess = closedStudents.remove(key); + } else { + isSuccess = closedTutors.remove(key); + } + + if (isSuccess) { + return true; + } else { + throw new PersonNotFoundException(); + } + } +---- + +With the addition of closed client list, certain commands will be unique to only closed or active client list + +Commands available in closed client's list: + +`clear` `exit` `find` `help` `history` `list` `redo` `undo` `restore` `switch` + +Commands available in active client's list: + +`addclient` `clear` `close` `delete` `edit` `exit` `find` `help` `history` `list` `match` `redo` `undo` `remove` `sort` `switch` + + +A check is implemented this way in all commands features that available only when viewing active client list: + +[source,java] +---- + if (!ListPanelController.isCurrentDisplayActiveList()) { + throw new CommandNotAvailableInClosedViewException(); + } +---- +This allows `CommandNotAvailableInClosedViewException` to be thrown when the +user is attempts to use a command unique to active list view while viewing closed list view. + +==== Design Considerations + +===== Aspect: Integration of commands which were unique to active client list to closed client list. +* **Alternative 1 (current choice):** Limit the number of commands available to closed clients list. +** Pros: This is easier to implement as there would be less changes to existing commands as certain commands are not relevant while viewing closed client list. +** Cons: Design of unique commands would still have to be changed if they were to be made available to both list. +* **Alternative 2:** Allow the display to automatically switch to either closed or active list list where the command is available upon user input. +** Pros: This is more intuitive to users. +** Cons: This is much harder to implement as this would include require changes to be made to the command structure. +// end::closedClientList[] + +// tag::close[] +=== Close feature +==== Current Implementation + +The close command allows users to remove a particular client from the active client's list and stores it in the closed client's list. + +* CloseCommand extends `UndoableCommand` and this is an undoable command. It overwrites `executeUndoableCommand` in abstract class `UndoableCommand`. + +** It will first remove the the selected client from the active client list. If this particular client is not found, a `PersonNotFoundException` will be thrown. +** After which, the particular removed client will be added to the closed client list. An `AssertionError` will be thrown if a duplicated client is detected as it should not be possible to have a duplicated client in the active client list in TuitionCor. + +The implementation is as follows: +[source,java] +---- + @Override + public CommandResult executeUndoableCommand() { + requireNonNull(clientToClose); + try { + model.deleteClient(clientToClose, category); + } catch (PersonNotFoundException pnfe) { + throw new AssertionError("The target client cannot be missing"); + } + + try { + if (category.isStudent()) { + model.addClosedStudent(clientToClose); + } else { + model.addClosedTutor(clientToClose); + } + } catch (DuplicatePersonException e) { + throw new AssertionError("The client should not be duplicated"); + } + + if (category.isStudent()) { + return new CommandResult(String.format(MESSAGE_CLOSE_STUDENT_SUCCESS, clientToClose)); + } else { + return new CommandResult(String.format(MESSAGE_CLOSE_TUTOR_SUCCESS, clientToClose)); + } + } +---- + +A sequence diagram is shown below to show the current design when a user input "close 1 c/s" + +image::CloseSequenceDiagram.PNG[width="800"] + +==== Design Considerations + +===== Aspect: Implementation of the close command +* **Alternative 1 (current choice):** Current implementation calls model component twice to execute deleteClient and addClosedStudent or addClosedTutor. +** Pros: This ensures that deleteClient is able to execute successfully before addClosedStudent is executed. +** Cons: This reduces code efficiency. +* **Alternative 2:** Implement a closeClient method in model to execute deleteClient and addClosedStudent or addClosedTutor. +** Pros: Model can be called once. +** Cons: New developers might no understand as this reduces code readability. + +// end::close[] + + +// tag::restore[] +=== Restore feature + +==== Current Implementation +The restore command allows users to remove a particular client from the closed client's list and restore it back to the active client's list. + +* `RestoreCommand` extends `UndoableCommand` and this is an undoable command. It overwrites `executeUndoableCommand` in abstract class `UndoableCommand`. + +** It will first remove the the selected client from the closed client list. If this particular client is not found, a `PersonNotFoundException` will be thrown. +** After which, the particular removed client will be added to the active client list. An AssertionError will be thrown if a duplicated client is detected as it should not be possible to have a duplicated client in the closed client list in TuitionCor. + +* This design is very similar to close command's design. + +The implementation is as follows: +[source,java] +---- +@Override + public CommandResult executeUndoableCommand() { + requireNonNull(clientToRestore); + try { + model.deleteClosedClient(clientToRestore, category); + } catch (PersonNotFoundException pnfe) { + throw new AssertionError("The target client cannot be missing"); + } + + try { + if (category.isStudent()) { + model.addStudent(clientToRestore); + } else { + model.addTutor(clientToRestore); + } + } catch (DuplicatePersonException e) { + throw new AssertionError("The client should not be duplicated"); + } + + if (category.isStudent()) { + return new CommandResult(String.format(MESSAGE_RESTORE_STUDENT_SUCCESS, clientToRestore)); + } else { + return new CommandResult(String.format(MESSAGE_CLOSE_TUTOR_SUCCESS, clientToRestore)); + } + } +---- + +A sequence diagram is shown below to show the current design when a user input "restore 1 c/s" + +image::RestoreSequenceDiagram.PNG[width="800"] + +// end::restore[] + +// tag::switch[] +=== Switch feature +==== Current Implementation + +Switch commands allows user to toggle between the active and closed client's list and it extends `Command`. + +* Implementation of Switch command that overrides `execute()` method in `Command` is as follows: + +[source,java] +---- +@Override + public CommandResult execute() { + EventsCenter.getInstance().post(new ClientListSwitchEvent()); + listPanelController.switchDisplay(); + if (listPanelController.getCurrentListDisplayed() == ListPanelController.DisplayType.closedList) { + return new CommandResult(MESSAGE_SUCCESS + MESSAGE_CLOSED_DISPLAY_LIST); + } else { + return new CommandResult(MESSAGE_SUCCESS + MESSAGE_ACTIVE_DISPLAY_LIST); + } + } +---- + +* A `ListPanelController` is used to keep track of the currently displayed list and switch the list when called. + +[source, java] +---- + public void switchDisplay() { + switch (currentlyDisplayed) { + case activeList: + currentlyDisplayed = DisplayType.closedList; + break; + + case closedList: + currentlyDisplayed = DisplayType.activeList; + break; + + default: + throw new AssertionError("This should not be possible."); + } + } +---- + +* Switch command uses `ClientListSwitchEvent` to raise an event whenever the user wants to toggle the list displayed. + +[source,java] +---- + public class ClientListSwitchEvent extends BaseEvent { + + @Override + public String toString() { + return this.getClass().getSimpleName(); + } + } +[source,java] +---- + +* When a event is raise it's handled by `handleClientListSwitchEvent` which resides in both `StudentListPanel` and `TutorListPanel`. + +[source,java] +---- + @Subscribe + private void handleClientListSwitchEvent(ClientListSwitchEvent event) { + logger.info(LogsCenter.getEventHandlingLogMessage(event)); + switchListDisplay(); + } +---- + +* This will allow `StudentListPanel` and `TutorListPanel` to be updated accordingly based on the current list that is on display. + +[source,java] +---- + private void switchListDisplay() { + ListPanelController listPanelController = ListPanelController.getInstance(); + switch (listPanelController.getCurrentListDisplayed()) { + case activeList: + setConnectionsForClosedStudents(); + break; + + case closedList: + setConnectionsForStudents(); + break; + + default: + throw new AssertionError("This should not be possible."); + } + } +---- + +* A sequence diagram is shown below when switch command is entered by user. + +image::SwitchSequenceDiagram.PNG[width="800"] -_{Explain here how the data encryption feature will be implemented}_ +==== Design Considerations -// end::dataencryption[] +===== Aspect: Implementation of switch command +* **Alternative 1 (current choice):** Raise new event to indicate switch event. +** Pros: Reduce direct coupling between components StudentListPanel and TutorListPanel with SwitchCommand +** Cons: Code might be harder to understand. +* **Alternative 2:** Call a method in StudentListPanel and TutorListPanel to indicate a switch request. +** Pros: Code is easier to understand. +** Cons: Direct coupling occurs and changing a part might affect another component. +// end::switch[] === Logging @@ -780,15 +1364,21 @@ See this https://github.com/se-edu/addressbook-level4/pull/599[PR] for the step- [appendix] == Product Scope -*Target user profile*: +*Target user profile*: + + Tuition coordinators who handle significant number of contacts and are comfortable with CLI applications. -* has a need to manage a significant number of contacts -* prefer desktop apps over other types -* can type fast -* prefers typing over mouse input -* is reasonably comfortable using CLI apps +* Value proposition*: +** TuitionCor is targeted at tuition coordinators who have to manage a large amount of contacts. +** The daily job-scope of a tuition coordinator involves the need to manage large amount of contacts and match the students to tutors according to their credentials, needs and location. +** Therefore, TuitionCor aims to facilitate this process and make the job of a tuition coordinator easier. -*Value proposition*: manage contacts faster than a typical mouse/GUI driven app +* In addition, users are able to +** Add clients +** Match students to tutors or vice versa +** Sort clients based on a particular aspect +** Find clients based on keywords +** Close assigned clients +** Restore closed clients [appendix] == User Stories @@ -800,33 +1390,123 @@ Priorities: High (must have) - `* * \*`, Medium (nice to have) - `* \*`, Low (un |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 |add a new person (tutor or student) |expand user's addressbook for future tuition coordination -|`* * *` |user |delete a person |remove entries that I no longer need +|`* * *` |user |delete a person |remove entries that the user no longer need |`* * *` |user |find a person by name |locate details of persons without having to go through the entire list +|`* * *` |user |sort contacts based on the clients' location (North, South, East, West, Central |better match students and tutors that are staying near each other. + +|`* * *` |user |edit the information stored |keep the information up to date + +|`* * *` |user |tag tutors and students with multiple fields |do multi-layer searching to better match tutors and students + +|`* * *` |user |categorise contacts between students and tutors |reduce the number of tagging required and make searching more convenient + +|`* * *` |user |group clients based on the subject they wish to study/teach (Math, Eng, Phy, Chem...) |find and match client easily by the subjects registered + +|`* * *` |user |record the gender preference the students/tutors have (male/female) |find and match client easily by the gender preference registered + +|`* * *` |user |see and compare the price that students/tutors are willing to pay/accept|match them based on pricing + +|`* * *` |user |search by tags added to client |generate a list of clients that have the same tags + +|`* * *` |user |differentiate "location" "subject" and "grade" by colours |identify "location", "subject" and "grade" attributes easily + +|`* * *` |user |sort the clients name |better organise my clients + +|`* * *` |new user |sort contacts based on clients subjects |better organise my clients + +|`* * *` |new user |sort contacts based on clients grade |better organise my clients + +|`* * *` |user |sort the clients based on a particular field |better organise my clients + +|`* * *` |user |match a given client and app will show a list of clients sorted from the most suitable to the least suitable |give my clients more choices + +|`* * *` |user |match clients that only fit certain requirements |clients will have more choice to choose from + +|`* * *` |user |easily remove the subjects of the students when they find tutors |edit the subjects of students whenever they find a suitable tutor easily + +|`* * *` |user |advance the grades of all the students in the application |change the grades of all the students without manually editing them one by one + +|`* * *` |user |have an organised application | easily view, update and search the active clients and closed clients + |`* *` |user |hide <> by default |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 +|`* *` |user |search multiple tags |narrow down the search to clients that fit all the different tags searched. + +|`* *` |user |easily delete all students/tutors |save time manually deleting + +|`* *` |user |get a list of all tutors that corresponds to a students requirements |give students more choices on which tutor they want + +|`* *` |user |get a list of all students that corresponds to a tutor's requirements |give tutors more choices on which students they want + +|`* *` |user |automatically update the year of all students |need not manually change all the information during new year + +|`* *` |user |update which time slot the tutor is already working |plan their timetable without any clash in timing + +|`* *` |user |have a ranking on how good the tutor is |understand which tutor the user should recommend more + +|`* *` |user |highlight urgent tuition requests |set reminder to place more attention on these assignments + +|`* *` |user |blacklist certain tutors or students |make sure the user will not accept their assignments + +|`* *` |user |have the option to customise the font size and colour |make changes to the appearance to suit the user's preference + +|`* *` |user |have the option to customise the user's background |make the application more appealing to the user + +|`* *` |user |email the contacts in the application |save the trouble from opening another web-browser or email application + +|`* *` |user |keep track of whether the tutors have paid their agent fee |keep track and remind those who have not paid. + +|`*` |user |directly navigate between contacts |save the trouble from returning to the main page every time + +|`*` |user |keep a record of how much the students improve |keep track of how good the tutor is |======================================================================= -_{More to be added}_ [appendix] == Use Cases -(For all use cases below, the *System* is the `AddressBook` and the *Actor* is the `user`, unless specified otherwise) +(For all use cases below, the *System* is the `TuitionCor` and the *Actor* is the `user`, unless specified otherwise) [discrete] === Use case: Delete person *MSS* -1. User requests to list persons -2. AddressBook shows a list of persons +1. User requests to list clients +2. TuitionCor shows a list of clients 3. User requests to delete a specific person in the list -4. AddressBook deletes the person +4. TuitionCor deletes the person ++ +Use case ends. + +*Extensions* + +[none] +* 2a. The list is empty. ++ +Use case ends. + +* 3a. The given index is invalid. ++ +[none] +** 3a1. TuitionCor shows an error message. ++ +Use case resumes at step 2. + +// tag::mssdestin[] +[discrete] +=== Use case: Remove specific keyword from client's subject + +*MSS* + +1. User requests to list clients +2. TuitionCor shows a list of clients +3. User requests to remove a keyword from specific client +4. TuitionCor removes the keyword from the subject field of the specified client + Use case ends. @@ -840,20 +1520,213 @@ Use case ends. * 3a. The given index is invalid. + [none] -** 3a1. AddressBook shows an error message. +** 3a1. TuitionCor shows an error message. ++ +Use case resumes at step 2. + +* 4a. The given keyword does not exist. ++ +[none] +** 4a1. TuitionCor shows an error message. ++ +Use case resumes at step 2. + +* 4b. The given keyword the last subject in the specific client. ++ +[none] +** 4b1. TuitionCor shows an error message that suggest for the user to close the client instead. ++ +Use case resumes at step 2. +// end::mssdestin[] + +[discrete] +=== Use case: Sort student's or tutor's list by locality + +*MSS* + +1. User types “sort l c/t” or “sort l c/s” in to the command line. +2. TuitionCor will return either a list of students or tutors with respect to the command entered sorted based on their locality in alphabetical order. ++ +Use case ends. + +*Extensions* + +[none] +* 1a. System detects an invalid input. ++ +[none] +** 1a1. TuitionCor will display invalid command and display sort message usage. +** 1a2. User enter required information. +** 1a3. Steps 1a1 and 1a2 are repeated until valid input is entered. ++ +Use case resumes at step 2. + +[none] +* 1b. System detects no further input. ++ +Use case ends. + +[discrete] +=== Use case: Sort student's or tutor's list by grade + +*MSS* + +1. User types “sort g c/t” or “sort g c/s” in to the command line. +2. TuitionCor will return either a list of students or tutors with respect to the command entered sorted based on their grade in ascending order of seniority. ++ +Use case ends. + +*Extensions* + +[none] +* 1a. System detects an invalid input. ++ +[none] +** 1a1. TuitionCor will display invalid command and display sort message usage. +** 1a2. User enter required information. +** 1a3. Steps 1a1 and 1a2 are repeated until valid input is entered. ++ +Use case resumes at step 2. + +[none] +* 1b. System detects no further input. ++ +Use case ends. + +[discrete] +=== Use case: Sort student's or tutor's list by name + +*MSS* + +1. User types “sort n c/t” or “sort n c/s” in to the command line. +2. TuitionCor will return either a list of students or tutors with respect to the command entered sorted based on their name in alphabetical order. ++ +Use case ends. + +*Extensions* + +[none] +* 1a. System detects an invalid input. ++ +[none] +** 1a1. TuitionCor will display invalid command and display sort message usage. +** 1a2. User enter required information. +** 1a3. Steps 1a1 and 1a2 are repeated until valid input is entered. ++ +Use case resumes at step 2. + +[none] +* 1b. System detects no further input. ++ +Use case ends. + +[discrete] +=== Use case: Sort student's or tutor's list by subject + +*MSS* + +1. User types “sort s c/t” or “sort s c/s” in to the command line. +2. TuitionCor will return either a list of students or tutors with respect to the command entered sorted based on their subject in alphabetical order. ++ +Use case ends. + +*Extensions* + +[none] +* 1a. System detects an invalid input. ++ +[none] +** 1a1. TuitionCor will display invalid command and display sort message usage. +** 1a2. User enter required information. +** 1a3. Steps 1a1 and 1a2 are repeated until valid input is entered. ++ +Use case resumes at step 2. + +[none] +* 1b. System detects no further input. ++ +Use case ends. + +[discrete] + + +// tag::matchmss[] +=== Use case: Matching a student + +*MSS* + +1. User enters "match 1 c/s". +2. TuitionCor displays information of the selected student in the student's list and all the relevant tutors which matches the student's needs in tutor's list. ++ +Use case ends. + +*Extensions* + +[none] +* 1a. System detects an invalid input. ++ +[none] +** 1a1. TuitionCor will display invalid command and display sort message usage. +** 1a2. User enter required information. +** 1a3. Steps 1a1 and 1a2 are repeated until valid input is entered. + Use case resumes at step 2. -_{More to be added}_ +[none] +* 1a. System is unable to match any tutor to this student. ++ +[none] +** 1a1. An empty tutor's list will be displayed to the user ++ +Use case ends. + +[discrete] + +=== Use case: Matching a tutor + +*MSS* + +1. User enters "match 1 c/t". +2. TuitionCor displays information of the selected tutor in the tutor's list and all the relevant students which matches the tutor's credentials in student's list. ++ +Use case ends. + +*Extensions* + +[none] +* 1a. System detects an invalid input. ++ +[none] +** 1a1. TuitionCor will display invalid command and display sort message usage. +** 1a2. User enter required information. +** 1a3. Steps 1a1 and 1a2 are repeated until valid input is entered. ++ +Use case resumes at step 2. + +[none] +* 1a. System is unable to match any student to this tutor. ++ +[none] +** 1a1. An empty tutor's list will be displayed to the user ++ +Use case ends. + +[discrete] [appendix] +// end::matchmss[] + == Non Functional Requirements . Should work on any <> as long as it has Java `1.8.0_60` or higher installed. -. Should be able to hold up to 1000 persons without a noticeable sluggishness in performance for typical usage. +. Should be able to hold up to 5000 persons without a noticeable sluggishness in performance for typical usage. . 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. +. Application should be able execute any command within 3 seconds. +. Application should have a self-explanatory installation process. +. Application should be less than 5mb in size. +. Application is expected to allow printing. +. Application should have an auto-save function upon exiting the application. -_{More to be added}_ [appendix] == Glossary @@ -864,23 +1737,6 @@ Windows, Linux, Unix, OS-X [[private-contact-detail]] Private contact detail:: A contact detail that is not meant to be shared with others -[appendix] -== Product Survey - -*Product Name* - -Author: ... - -Pros: - -* ... -* ... - -Cons: - -* ... -* ... - [appendix] == Instructions for Manual Testing @@ -903,26 +1759,178 @@ These instructions only provide a starting point for testers to work on; testers .. Re-launch the app by double-clicking the jar file. + Expected: The most recent window size and location is retained. -_{ more test cases ... }_ - -=== Deleting a person +=== Deleting a client -. Deleting a person while all persons are listed +. Deleting a client while all clients are listed -.. Prerequisites: List all persons using the `list` command. Multiple persons in the list. +.. Prerequisites: List all clients using the `list` command. Multiple clients in the list. .. 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. .. Test case: `delete 0` + - Expected: No person is deleted. Error details shown in the status message. Status bar remains the same. + Expected: No client is deleted. Error details shown in the status message. Status bar remains the same. .. Other incorrect delete commands to try: `delete`, `delete x` (where x is larger than the list size) _{give more}_ + Expected: Similar to previous. -_{ more test cases ... }_ +// tag::removeTest[] + +=== Remove Subject keyword from a client + +. Removing a keyword from the Subject field of a specified client + +.. Prerequisites1: At least 1 student and 1 tutor has to be in TuitionCor. +.. Prerequisites2: Displayed list has to be active list. +.. Test case: `remove 1 c/s s/math` + + Assumption1: First student in TuitionCor has "math" in the Subject field. For example the Subject field is "physics chemistry math english". Otherwise, replace "math" in the command with a word in the Subject field of the client. + Assumption2: First student in TuitionCor has more than 1 keyword in Subject field. (Each keyword is a single word separated by whitespace) + Expected: "math" would be removed from Subject field of the first student. Given the above example, the remaining Subjects should be "physics chemistry english". Success message would be given. Timestamp in status bar is updated. +.. Test case: `remove 1 c/s s/math` + + Assumption: First student in TuitionCor does not have "math" in the Subject field. + Expected: Error message "The inputted message does not exist" +.. Test case: `remove 1 c/s s/math` + + Assumption: First student in TuitionCor has only "math" in Subject field. + Expected: Error message warning user that removing the last keyword in Subject field is not allowed and suggests user to close client instead. +.. Test case: `remove 1 c/t s/math` + + Assumption1: First tutor in TuitionCor has "math" in the Subject field. For example the Subject field is "physics chemistry math english". Otherwise, replace "math" in the command with a word in the Subject field of the client. + Assumption2: First tutor in TuitionCor has more than 1 keyword in Subject field. (Each keyword is a single word separated by whitespace) + Expected: "math" would be removed from Subject field of the first tutor. Given the above example, the remaining Subjects should be "physics chemistry english". Success message would be given. Timestamp in status bar is updated. +.. Test case: `remove 0 c/s s/math` + + Expected: No keyword is removed from Subject field. Error details shown in status message. Status bar remains the same. +.. Other invalid remove commands to try: `remove c/s s/math` (missing INDEX), `remove x c/s s/math` (where x is a number larger than the list size) + + `remove 1 s/math` (missing CATEGORY), `remove 1 c/g s/math` (invalid CATEGORY, CATEGORY should only be "s" or "t") + + `remove 1 c/s` (missing SUBJECT), `remove 1 c/s s/chemistry physics` (invalid SUBJECT, SUBJECT should only be a single word with no special characters and no whitespace). + Expected: Similar to previous. + +// end::removeTest[] + +=== Sorting a list + +. Sorting a list while all clients are listed + +.. Prerequisites: List all clients using the `list` command. Multiple clients in either student list or tutor list. Displayed list must be active client list. +.. Test case: `sort n c/t` + + Expected: Tutor's list is being sorted according alphabetical order based on the client's name. +.. Test case: `sort l c/t` + + Expected: Tutor's list is being sorted according to their location in alphabetical order based on the location displayed or the *first* location displayed if there exist multiple locations. +.. Test case: `sort s c/t` + + Expected: Tutor's list is being sorted according to their subject in alphabetical order based on the subject displayed or the *first* location displayed if there exist multiple locations. +.. Test case: `sort g c/t` + + Expected: Tutor's list is being sorted according to grade in ascending seniority `[Kindergarten->Primary->Secondary->Junior College->University]`. It will be sorted based on the *first* grade if there exist multiple grades. + +.. Test case: `sort n c/s` + + Expected: Student's list is being sorted according alphabetical order based on the client's name. +.. Test case: `sort l c/s` + + Expected: Student's list is being sorted according to their location in alphabetical order based on the location displayed or the *first* location displayed if there exist multiple locations. +.. Test case: `sort s c/s` + + Expected: Student's list is being sorted according to their subject in alphabetical order based on the subject displayed or the *first* location displayed if there exist multiple locations. +.. Test case: `sort g c/s` + + Expected: Student's list is being sorted according to grade in ascending seniority `[Kindergarten->Primary->Secondary->Junior College->University]`. It will be sorted based on the *first* grade if there exist multiple grades. + +=== Switching the displayed client list + +. Default displayed list when launching TuitionCor +.. Launch the application + + Expected: Active client list is being displayed. `` should be shown in the status bar. + +. Switching the display +.. Prerequisites: Currently in active client list. +... Test case: `switch` + + Expected: Closed client list is being displayed. `` should be shown in the status bar. + +.. Prerequisites: Currently in closed client list. +... Test case: `switch` + + Expected: Active client list is being displayed. `` should be shown in the status bar. + +=== Closing a client + +. Closing a client while all clients are listed. +.. Prerequisites: List all clients using the `list` command. Clients displayed in either student list or tutor list. Minimally there must exist at least 1 student and 1 tutor in their respective list. Displayed list must be *active client list*. +.. Test case: `close 1 c/s` + + Assumptions: Assuming that the first student on the student list takes on the name `Alex Yeoh`. + + Expected: Student Alex Yeoh is being closed and is no longer seen on the student list in active client list. Switching the display using `switch`, Alex Yeoh will be seen displayed on the student list in closed client list. + +.. Test case: `close 1 c/t` + + Assumptions: Assuming that the first tutor on the tutor list takes on the name `George`. + + Expected: Tutor George is being closed and is no longer seen on the tutor list in active client list. Switching the display using `switch`, George will be seen displayed on the tutor list in closed client list. + +. Closing a client while in closed client list. +.. Prerequisites: List all clients using the `list` command. Clients displayed in either student list or tutor list. Minimally there must exist at least 1 student and 1 tutor in their respective list. Displayed list must be *closed client list*. +.. Test case: `close 1 c/s` + + Expected: No students are being closed. Error details shown in the status message. + +=== Restoring a client + +. Restoring a client while all clients are listed. +.. Prerequisites: List all clients using the `list` command. Clients displayed in either student list or tutor list. Minimally there must exist at least 1 student and 1 tutor in their respective list. Displayed list must be *closed client list*. +.. Test case: `restore 1 c/s` + + Assumptions: Assuming that the first student on the student list takes on the name `Tom`. + + Expected: Student Tom is being restored and is no longer seen on the student list in closed client list. Switching the display using `switch`, Tom will be seen displayed on the student list in active client list. + +.. Test case: `restore 1 c/t` + + Assumptions: Assuming that the first tutor on the tutor list takes on the name `Cindy`. + + Expected: Tutor Cindy is being restored and is no longer seen on the tutor list in closed client list. Switching the display using `switch`, Cindy will be seen displayed on the tutor list in active client list. + +. Restoring a client while in active client list. +.. Prerequisites: List all clients using the `list` command. Clients displayed in either student list or tutor list. Minimally there must exist at least 1 student and 1 tutor in their respective list. Displayed list must be *active client list*. +.. Test case: `restore 1 c/s` + + Expected: No students are being restored. Error details shown in the status message. + +=== Matching a client +. Matching a client while all clients are listed. +.. Prerequisites: List all clients using the `list` command. Clients displayed in either student list or tutor list. Minimally there must exist at least 1 student and 1 tutor in their respective list. Displayed list must be *active client list*. +.. Test case: `match 1 c/s` + + Assumptions: Assuming that the first student on the student list with name `Alex Yeoh`, location `North`, grade `p2` and subject `math`. + + Expected: Student list will now only display Alex Yeoh. In the tutor list tutor James and Jennifer is being displayed. + + James is matched as his client card contains `Math` as his first subject and `North` as his first location. + + Jennifer is matched as her client card contains grade of `p2` and subject `math` is her only subject. + + The matched tutors fields are also being highlighted in orange. + +.. Test case: `match 4 c/t` + + Assumptions: Assuming that the forth tutor on the tutor list with name `Nancy`, location `Central`, grade `u1 u2` and subject `physics`. + + Expected: Tutor list will now only display Nancy. In the student list, student Bernice Yu, Roy Balakrishnan, Charlotte Oliveiro and David Li is being displayed. + + Berenice Yu is matched as her client card contains location `Central` and `Physics`. + + Roy Balakrishnan is matched as his client card contains location `Central` and `Physics`. + + Charlotte Oliveiro is matched as as her client card contains `Physics` as her first subject. + + David Li is matched as his client card contains location `Central`. + + The matched students fields are also being highlighted in orange. + + The matched students are also being displayed in descending order based on the number of matched fields, with the student with the most matched fields displayed first. + +[appendix] +== Feature Contribution + +Given below are the minor and major feature contributions for each of our team member. + +=== Ngeow Shan Yong Destin + +. Major Contribution + +.. Morphed AddressBook - Level 4 into TuitionCor by adding support for clients in model, logic, storage + + Client class and commands that are specific to TuitionCor such as subject, which is not available in person. + +. Minor Contribution + +.. Splitting storage of UniquePersonList into 2 List, 1 for Student and 1 for Tutor + + In TuitionCor all Client added would be either a student or a tutor, hence this would allow for easier organization of added entries. Also, this would remove the need to have to search through the entire list to see who are student or tutor at every command. + +=== Zhu Jiahui + +. Major Contribution + +.. Match Function + + This function matches a Student to any Tutor in TuitionCor that meets the requirements of the Student (or vice versa).This acts as the main purpose of TuitionCor, which is to help coordinate Students and Tutors. + +. Minor Contribution + +.. Enhancement of Find function to be able to find all fields and not only the person name + + This allows the user be able to search for anything related to the person he wishes to find in TuitionCor. For example, he can now search the address or phone number and be able to find the person. + +=== Lim Hong Cho -=== Saving data +. Major Contribution -. Dealing with missing/corrupted data files +.. Close, restore and switch function + + This would allow user to close a client to the closed list or restore a client to the active list. Switch allows client to toggle between closed list and active list. -.. _{explain how to simulate a missing/corrupted file and the expected behavior}_ +. Minor Contribution -_{ more test cases ... }_ +.. Sort + + This would allow the user to view the sorted list of Students and Tutors separately allowing for easier viewing of a specific category. diff --git a/docs/UserGuide.adoc b/docs/UserGuide.adoc index 74248917e438..41eaa742d9d9 100644 --- a/docs/UserGuide.adoc +++ b/docs/UserGuide.adoc @@ -1,4 +1,4 @@ -= AddressBook Level 4 - User Guide += TuitionCor - User Guide :toc: :toc-title: :toc-placement: preamble @@ -11,13 +11,12 @@ ifdef::env-github[] :tip-caption: :bulb: :note-caption: :information_source: endif::[] -:repoURL: https://github.com/se-edu/addressbook-level4 - -By: `Team SE-EDU` Since: `Jun 2016` Licence: `MIT` +:repoURL: https://github.com/CS2103JAN2018-F11-B2/main +By: `Team F11-B2` Since: `Jun 2016` Licence: `MIT` == Introduction -AddressBook Level 4 (AB4) is for those who *prefer to use a desktop app for managing contacts*. More importantly, AB4 is *optimized for those who prefer to work with a Command Line Interface* (CLI) while still having the benefits of a Graphical User Interface (GUI). If you can type fast, AB4 can get your contact management tasks done faster than traditional GUI apps. Interested? Jump to the <> to get started. Enjoy! +TuitionCor is for those who *prefer to use a desktop app for managing client information*. More importantly, TuitionCor is *optimized for those who prefer to work with a Command Line Interface* (CLI) while still having the benefits of a Graphical User Interface (GUI). If you can type fast, TuitionCor can get your client management tasks done faster than traditional GUI apps. Interested? Jump to the <> to get started. Enjoy! == Quick Start @@ -27,30 +26,103 @@ AddressBook Level 4 (AB4) is for those who *prefer to use a desktop app for mana Having any Java 8 version is not enough. + This app will not work with earlier versions of Java 8. + -. Download the latest `addressbook.jar` link:{repoURL}/releases[here]. -. Copy the file to the folder you want to use as the home folder for your Address Book. +. Download the latest `TuitionCor.jar` link:{repoURL}/releases[here]. +. Copy the file to the folder you want to use as the home folder for your client information. . Double-click the file to start the app. The GUI should appear in a few seconds. + image::Ui.png[width="790"] + +. By default, upon launching TuitionCor you will be shown the active client list. If you are a new user, the active client and closed list will be filled with sample students and tutors. +** Active client list contains two separate list. Clients shown here are those who looking for students or tutors here. +*** The list on the left represents the student's list where you can view information regarding students who are looking for tutors. +*** The list on the right represents the tutor's list where you can view information regarding tutors who are looking for students. +** Closed client list contains two separate list. Clients shown here are those who are no longer looking for students or tutors. +*** The list on the left represents the student's list where you can view information regarding closed students who are no longer looking for tutors. +*** The list on the right represents the tutor's list where you can view information regarding closed tutors who are no longer looking for students. +** You can easily toggle between the closed and active client's list by command word `switch`. + +[NOTE] +Commands available in active client's list: + +`addclient` `clear` `close` `delete` `edit` `exit` `find` `help` `history` `list` `match` `redo` `undo` `remove` `sort` `switch` + +[NOTE] +Commands available in closed client's list: + +`clear` `exit` `find` `help` `history` `list` `redo` `undo` `restore` `switch` + . Type the command in the command box and press kbd:[Enter] to execute it. + e.g. typing *`help`* and pressing kbd:[Enter] will open the help window. -. Some example commands you can try: +. 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. +* **`addclient`**`c/s n/John Doe p/98765432 e/johnd@example.com a/John street, block 123, #01-01 t/Urgent l/north g/s4 s/math` : adds a contact of a student named `John Doe` to TuitionCor. * **`delete`**`3` : deletes the 3rd contact shown in the current list * *`exit`* : exits the app +. Some pointers: + +Duplicates of similar students in the student's list is not allowed, vice versa for tutor's list. However, duplicate contacts between student's and tutor's list is allowed as it's possible for a tutor to also be a student. + ++ +A duplication of student or tutor happens when they do not have at least one difference in the following fields: {Name, Address, Phone Number, Email}. + . Refer to <> for details of each command. [[Features]] == Features +*Breakdown of features* + +* In v1.2: +** Supports addition of client, either tutor or students. + +* In v1.3: +** All commands are working and integrated with client. +** Clients information are saved. +** New match feature which allowing matching of student to tutors or tutor to students. +** Sort feature which allows tutor's and student's list to be sorted by name, subject, location and grade. + +* In v1.4: +** Match function now includes a ranking system. +*** The ranking system works according to the number of matched attributes. +*** The matched attributes will be highlighted in orange. +** Close and restore command which allows user to close or restore a particular student or tutor . +*** This allows user to close a tutor of student upon successful matching which will be stored in a closed tutor's or student's list for future reference. +*** This also allows user to restore a client from the closed list to active list if the client were to become active again. +** Switch command. +*** Allows user to toggle between active list and closed list. +** Enhance grade, subject and location to support multiple field. +** Remove function to remove the specified subject. +*** This allows the user to remove the specified subject from the client instead of having to type out all the remaining subjects in edit. This is under the consideration that the desired subject of the student would often change as he find's a tutor. + +* In v1.5: +** Issues received in v1.4 were carefully reviewed and relevant changes were made. We thank users for your generous feedback. +*** Bugs found were fixed. +*** Find function now works normally with same grades of different format. Eg: (P2 and Primary2). +*** Find function now supports multiple grade fields. + +* Coming in v2.0: +** Auto match function. +*** Whenever a new client either student or tutor is added, a list of tutor or student who fits the criteria of the tutor or student will be displayed to the user. +** Integration with Gmail. +*** Users are able to link a valid Gmail account to TuitionCor. +*** This allows TuitionCor to be able to send email notification to clients. +** Email notification +*** This allows users to to send a client through email relevant contact information regarding their assigned Tutors or Students upon a successful matching. +** Automatic Close function +*** An optional feature that will automatically Close a student (storing his/her contact into backup) when the last subject has been removed. +** Automatic change of grade for students +*** Either by allowing TuitionCor to sync with the computer's clock or through manually calling the command, all student's grade will be changed eg. from p1 to p2. +** Timetable availability +*** Include a timetable into TuitionCor similar to a personal reminder app. However, this timetable would instead be used to show the availability of the student/tutor to allow for better matching. +** Addition grade fields to be added. +*** Polytechnic and ITE. +** Matching Specific grade or subject attributes. +*** Current match command only matches the first grade or subject attribute when a client has more than 1 grade or subject attribute. +*** Search all the grade and subject attribute and highlight the specific matched attribute only. ==== *Command Format* -* Words in `UPPER_CASE` are the parameters to be supplied by the user e.g. in `add n/NAME`, `NAME` is a parameter which can be used as `add n/John Doe`. + +* There are alias for some of the commands, which helps users to save some time. e.g. for addclient command, you can use ac n/... or a n/... +* Words in `UPPER_CASE` are the parameters to be supplied by the user e.g. in `addclient n/NAME`, `NAME` is a parameter which can be used as `addclient n/John Doe`. * Items in square brackets are optional e.g `n/NAME [t/TAG]` can be used as `n/John Doe t/friend` or as `n/John Doe`. * Items with `…`​ after them can be used multiple times including zero times e.g. `[t/TAG]...` can be used as `{nbsp}` (i.e. 0 times), `t/friend`, `t/friend t/family` etc. * 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. @@ -60,31 +132,174 @@ e.g. typing *`help`* and pressing kbd:[Enter] will open the help window. Format: `help` -=== Adding a person: `add` +// tag::addclient[] +=== Adding a client: `addclient` `[Since v1.2]` -Adds a person to the address book + -Format: `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]...` +Adds a client to TuitionCor + +Format: `addclient c/CATEGORY n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]... l/LOCATION g/GRADE s/SUBJECT` +Alias Format: `ac c/CATEGORY n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]... l/LOCATION g/GRADE s/SUBJECT` [TIP] -A person can have any number of tags (including 0) +A client can have any number of tags (including 0) + +[NOTE] +The type of location and grade accepted can be found below. + +**** +* It's required to indicate the category the particular client belongs to. +* Accepted case-insensitive location available for user input are `[North,South,East,West,Central]`. Any other type of location will be invalid. +* Accepted case-insensitive grade available for user input are as follows: +** Format of grade are in this particular format 'LEVEL YEAR' or alias format 'ALIASLEVEL YEAR' without any spacing in-between. Example: 'Primary1' or 'P1'. +** LEVEL available are `[Kindergarten, Primary, Secondary, Tertiary, University]`. Alias LEVEL are `[K,P,S,J,U]` respectively. +** INDEX available varies differently for each Level. +*** For Kindergarten INDEX available are `[1,2,3]`. +*** For Primary INDEX available are `[1,2,3,4,5,6]`. +*** For Secondary INDEX available are `[1,2,3,4,5]`. +*** For Tertiary INDEX available are `[1,2]`. +*** For University INDEX available are `[1,2,3,4]`. +* Subject are split based on black space. Hence a single subject has to be typed without any space. +** Example: "chinese studies" would be treated as two separate subject "chinese" and "studies" by other commands. Hence it should be typed without a blank space such as "chineseStudies". +**** 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` +* `addclient c/t n/John Doe p/98765432 e/johnd@example.com a/John street, block 123, #01-01 t/urgent l/north g/p1 s/math` +* `ac c/s n/Betsy Crowe t/friend e/betsycrowe@example.com a/Newgate Prison p/1234567 t/urgent l/south g/primary1 s/math` + +// end::addclient[] === Listing all persons : `list` Shows a list of all persons in the address book. + Format: `list` -=== Editing a person : `edit` +Alias Format: `l` + +// tag::sorting[] +=== Sorting a client: 'sort' `[Since v1.3]` + +Sorting tutor's list based on name + +Format: `sort n c/t` + +Alias Format: `so n c/t` + +Sorting tutor's list based on location + +Format: `sort l c/t` + +Alias Format: `so l c/t` + +Sorting tutor's list based on grade + +Format: `sort g c/t` + +Alias Format: `so g c/t` -Edits an existing person in the address book. + -Format: `edit INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [t/TAG]...` +Sorting tutor's list based on subject + +Format: `sort s c/t` + +Alias Format: `so s c/t` + +Sorting student's list based on name + +Format: `sort n c/s` + +Alias Format: `so n c/s` + +Sorting student's list based on location + +Format: `sort l c/s` + +Alias Format: `so l c/s` + +Sorting student's list based on grade + +Format: `sort g c/s` + +Alias Format: `so g c/s` + +Sorting student's list based on subject + +Format: `sort s c/s` + +Alias Format: `so s c/s` **** -* Edits the person at the specified `INDEX`. The index refers to the index number shown in the last person listing. The index *must be a positive integer* 1, 2, 3, ... +Sorts the client based on the selected category, either `t` for tutors or `s` for students based on a chosen sort type `n` for name, `g` for grade, `s` for subject and `l` for location. + +Sorting by name, grade, location and subjects are based on alphabetical order. + + +In an event there are mutiple attributes for grade, location or subject, they will be sorted accordingly to the *first* grade, subject and location for the respective field. + +Sorting by grade are based on ascending order of seniority `[Kindergarten->Primary->Secondary->Junior College->University]` +**** +Examples: + +* `sort l c/t` + +Tutor's list displayed will be sorted base on location. +* `sort s c/t` + +Tutor's list displayed will be sorted base on subject. +* `sort n c/t` + +Tutor's list displayed will be sorted base on name. +* `sort g c/t` + +Tutor's list displayed will be sorted base on grade. + +* `sort l c/s` + +Student's list displayed will be sorted base on location. +* `sort s c/s` + +Student's list displayed will be sorted base on subject. +* `sort n c/s` + +Student's list displayed will be sorted base on name. +* `sort g c/s` + +Student's list displayed will be sorted base on grade. +// end::sorting[] + +// tag::switch[] +=== Switching between active and closed client's list : `switch` `[Since v1.4]` + +Switch the display between active and closed client's list. + +Format: `switch` + +Alias Format: `sw` +// end::switch[] + +// tag::close[] +=== Closing a client : `close` `[Since v1.4]` + +Close an existing and active student in active student's list. + +Format: `close INDEX c/s` + +Alias Format: `cs INDEX c/s` + +Close an existing and active tutor in active student's list. + +Format: `close INDEX c/t` + +Alias Format: `cs INDEX c/t` + +[NOTE] +This command is only available when viewing the active client's list. Use command word `switch` to toggle from closed list to active list. + +**** +* Close the client based on selected category either c/s for students or c/t for tutors at the specified `INDEX`. The index refers to the index number shown in the last tutors or students listing. The index *must be a positive integer* 1, 2, 3, ... +* Closed student or tutor will be removed from the active list. They can now be found in the closed list which is accessible by command word `switch`. +**** +// end::close[] + +// tag::restore[] +=== Restoring a client : `restore` `[Since v1.4]` + +Restore an existing and closed student in the closed student's list. + +Format: `restore INDEX c/s` + +Alias Format: `res INDEX c/s` + +Restore an existing and closed tutor in the closed tutor's list. + +Format: `restore INDEX c/t` + +Alias Format: `res INDEX c/t` + +[NOTE] +This command is only available when viewing the closed client's list. Use command word `switch` to toggle from active list to closed list. + +**** +* Restore the client based on selected category either c/s for students or c/t for tutors at the specified `INDEX`. The index refers to the index number shown in the last tutors or students listing. The index *must be a positive integer* 1, 2, 3, ... +* Restored student or tutor will be removed from the closed list. They can now be found in the active list which is accessible by command word `switch`. +**** +// end::restore[] + +=== Editing a client : `edit` `[Since v1.3]` + +Edits an existing student in the address book. + +Format: `edit INDEX c/s [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [t/TAG]...` +Alias Format: `e INDEX c/s [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [t/TAG]...` + +Edits an existing tutor in the address book. + +Format: `edit INDEX c/t [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [t/TAG]...` +Alias Format: `e INDEX c/t [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [t/TAG]...` + +**** +* Edits the client based on selected category either c/s for students or c/t for tutors at the specified `INDEX`. The index refers to the index number shown in the last tutors or students listing. 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. @@ -93,76 +308,141 @@ Format: `edit INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [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 c/s p/91234567 e/johndoe@example.com` + +Edits the phone number and email address of the 1st student to be `91234567` and `johndoe@example.com` respectively. +* `e 2 c/s n/Betsy Crower t/` + +Edits the name of the 2nd student to be `Betsy Crower` and clears all existing tags. +* `edit 1 c/t p/93213456 e/doe@example.com` + +Edits the phone number and email address of the 1st tutor to be `93213456` and `doe@example.com` respectively. +* `e 2 c/t n/Beatty Crower t/` + +Edits the name of the 2nd tutor to be `Beatty Crower` and clears all existing tags. -=== Locating persons by name: `find` +// tag::remove[] + +=== Remove a subject of a client : `remove` `[Since v1.4]` + +Removes the specific subject from an existing student in the address book. + +Format: `remove INDEX c/s s/SUBJECT` +Alias Format: `re INDEX c/s s/SUBJECT` + +Edits an existing tutor in the address book. + +Format: `edit INDEX c/t s/SUBJECT` +Alias Format: `re INDEX c/t s/SUBJECT` + +**** +* Removes the specified subject from the client based on selected category either c/s for students or c/t for tutors at the specified `INDEX`. The index refers to the index number shown in the last tutors or students listing. The index *must be a positive integer* 1, 2, 3, ... +* The provided subject has to be a single word without spacing or special characters. +* If the subject exist (it matches one of the subjects in the specified client's Subject field) it would be removed. +* A client cannot have an empty subject field. Clients with only one subject left should be deleted or closed instead +* The specified subject has to be exact with the subjects of the specified client. Partial words like typing "math" in attempt to remove "mathematics" would not work. +**** -Finds persons whose names contain any of the given keywords. + +Examples: + +* `remove 1 c/s s/math` + +Removes the "math" subject from the 1st student. +* `re 2 c/t s/physics` + +Removes the "physics" subject from the 2nd tutor. + +// end::remove[] + +// tag::find[] +=== Locating clients: `find` `[Since v1.3]` + +Finds clients that contain any of the given keywords. + Format: `find KEYWORD [MORE_KEYWORDS]` +Alias Format: `f KEYWORD [MORE_KEYWORDS]` **** * The search is case insensitive. e.g `hans` will match `Hans` +* *For grades only*, alias grades will match original grades. e.g `p2` will match `primary2`. * The order of the keywords does not matter. e.g. `Hans Bo` will match `Bo Hans` -* Only the name is searched. +* Not only the name is searched, but all the fields of a client 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` +* Clients matching at least one keyword will be returned (i.e. `OR` search). e.g. `Hans Bo` will return `Hans Gruber`, `Bo Yang` **** Examples: * `find John` + Returns `john` and `John Doe` -* `find Betsy Tim John` + -Returns any person having names `Betsy`, `Tim`, or `John` - -=== Deleting a person : `delete` - -Deletes the specified person from the address book. + -Format: `delete INDEX` +* `find p2` + +Returns any client having grades `p2` or `primary2` +* `f Betsy Tim John` + +Returns any client having names `Betsy`, `Tim`, or `John` +* `f 96528541` + +Returns any client having phone number `96528541` +* `f blk` + +Returns any client having keyword `blk` +// end::find[] + +// tag::match[] +=== Matching potential clients: `match` `[Since v1.3]` + +Matches potential tutors to selected student based on `INDEX` + +Format: `match INDEX c/s` +Alias Format: `m INDEX c/s` + +Matches potential students to selected tutor based on `INDEX` + +Format: `match INDEX c/t` +Alias Format: `m INDEX c/t` **** -* Deletes the person at the specified `INDEX`. -* The index refers to the index number shown in the most recent listing. -* The index *must be a positive integer* 1, 2, 3, ... +* Matches the client based on selected category either c/s for students or c/t for tutors at the specified `INDEX`. The index refers to the index number shown in the last tutors or students listing. The index *must be a positive integer* 1, 2, 3, ... +* The match is case insensitive. e.g `hans` will match `Hans` +* Clients will be matched based on subject, grade or location. +* Full words of a `grade` will be matched against Alias of a `grade` e.g. `Primary2` will be matched to `p2` +* Only clients that have any matches based on subject, grade or location will be displayed. +* If the client have multiple grade or subject attributes, only the first grade or subject attribute is used to match any potential clients. +* Matched clients will be listed from the most number of matched attributes to the least number of matched attributes. +* Matched attributes will be highlighted in orange. **** Examples: -* `list` + -`delete 2` + -Deletes the 2nd person in the address book. -* `find Betsy` + -`delete 1` + -Deletes the 1st person in the results of the `find` command. +* `match 1 c/t` + +At index 1 of tutor's list, John is a tutor finding students that are staying in west and is looking for s4 chemistry. This command will return a list of students that are staying in the west or s4 or Chemistry. + +* `match 1 c/s` + +At index 1 of student's list, Jim is a student who requires tutors that are staying in west and teaching s4 chemistry. This command will return a list of tutors that are staying in the west or teaching s4 or teaching Chemistry. + +// end::match[] + +=== Deleting a client : `delete` `[Since v1.3]` -=== Selecting a person : `select` +Deletes the specified tutor from TuitionCor. + +Format: `delete INDEX c/t` +Alias Format: `d INDEX c/t` -Selects the person identified by the index number used in the last person listing. + -Format: `select INDEX` +Deletes the specified student from TuitionCor. + +Format: `delete INDEX c/s` +Alias Format: `d INDEX c/s` **** -* Selects the person and loads the Google search page the person at the specified `INDEX`. +* Deletes the client at the specified `INDEX`. +* `c/t` and `c/s` refers to tutor's category respectively. * The index refers to the index number shown in the most recent listing. -* The index *must be a positive integer* `1, 2, 3, ...` +* The index *must be a positive integer* 1, 2, 3, ... **** Examples: * `list` + -`select 2` + -Selects the 2nd person in the address book. +`delete 2 c/s` + +Deletes the 2nd student in the student's list from TuitionCor. +* `list` + +`delete 2 c/t` + +Deletes the 2nd tutor in the tutor's list from TuitionCor. * `find Betsy` + -`select 1` + -Selects the 1st person in the results of the `find` command. +`d 1 c/t` + +Deletes the 1st person in the tutor's list based the results of the `find` command. === Listing entered commands : `history` Lists all the commands that you have entered in reverse chronological order. + Format: `history` +Alias Format: `h` [NOTE] ==== Pressing the kbd:[↑] and kbd:[↓] arrows will display the previous and next input respectively in the command box. @@ -171,12 +451,13 @@ Pressing the kbd:[↑] and kbd:[↓] arrows will display the previous and // tag::undoredo[] === Undoing previous command : `undo` -Restores the address book to the state before the previous _undoable_ command was executed. + +Restores TuitionCor to the state before the previous _undoable_ command was executed. + Format: `undo` +Alias Format: `u` [NOTE] ==== -Undoable commands: those commands that modify the address book's content (`add`, `delete`, `edit` and `clear`). +Undoable commands: those commands that modify the TuitionCor's content (`addclient`, `delete`, `edit` , `close` , `restore` and `clear`). ==== Examples: @@ -187,7 +468,7 @@ Examples: * `select 1` + `list` + -`undo` + +`u` + The `undo` command fails as there are no undoable commands executed previously. * `delete 1` + @@ -200,6 +481,8 @@ The `undo` command fails as there are no undoable commands executed previously. Reverses the most recent `undo` command. + Format: `redo` +Alias Format: `r` + Examples: * `delete 1` + @@ -207,7 +490,7 @@ Examples: `redo` (reapplies the `delete 1` command) + * `delete 1` + -`redo` + +`r` + The `redo` command fails as there are no `undo` commands executed previously. * `delete 1` + @@ -218,47 +501,159 @@ The `redo` command fails as there are no `undo` commands executed previously. `redo` (reapplies the `clear` command) + // end::undoredo[] -=== Clearing all entries : `clear` +=== Clearing all entries : `clear` `[Since v1.0]` -Clears all entries from the address book. + +Clears all entries from TuitionCor. + Format: `clear` -=== Exiting the program : `exit` +Alias Format: `c` + +=== Exiting the program : `exit` `[Since v1.0]` Exits the program. + Format: `exit` +Alias Format: `x` + === Saving the data -Address book data are saved in the hard disk automatically after any command that changes the data. + +TuitionCor data are saved in the hard disk automatically after any command that changes the data. + There is no need to save manually. -// tag::dataencryption[] -=== Encrypting data files `[coming in v2.0]` +// tag::gradeAdvancement[] + +=== advancement of Grade function `[coming in v2.0]` + +Advances all student's grades when this function is called (eg. change from p1 o p2). +This is done under the consideration that all students would advance in grade at the end of the school year and having to edit every single entry would be very tedious when the data base is large. + +Format: `advanceGrade INDEX...` + +A success message of the format _Successfully advanced students Grades_ will be displayed and all students whose grade is not already at the highest limit would be advanced. +The students at the specified INDEXes would not have their Grade advanced. + +Example: + +* `advanceGrade` + +All students Grades are advanced + +* `advanceGrade 2 10` + +All students except from index 2 and 10 would have their Grades advanced. + +// end::gradeAdvancement[] + +// tag::gmail[] +=== Integration with Gmail `[coming in v2.0]` -_{explain how the user can enable/disable data encryption}_ -// end::dataencryption[] +Link a valid Gmail account to TuitionCor. + + +[NOTE] +TuitionCor will only be linked to *one* valid Gmail account at any point of time + +Format: `set e/EMAIL pw/PASSWORD` + +A success message of the format _Successfully linked EMAIL to TuitionCor_ will be displayed for successful linking. + +A failure message of the format _Failed to link EMAIL to TuitionCor_ will be displayed for unsuccessful linking. Please check that you have inputted the correct email and password. + +Example: + +* `set e/BestCoordinatorInSg@gmail.com pw/Iamthebest` + +Links Gmail account `BestCoordinatorInSg@gmail.com` to TuitionCor upon a successful login. +// end::gmail[] + +// tag::email[] +=== Email notification function `[coming in v2.0]` +[IMPORTANT] +Please ensure that you have successfully linked a Gmail account to TuitionCor before you are able to use this function + +Parameters: + +Format: `notify INDEX c/CATEGORY INDEX c/CATEGORY` + +[TIP] +The first `INDEX c/CATEGORY` refers to the client you would like to notify and send an email with the relevant contact details. + +The second `INDEX c/CATEGORY` refers to the client whose information you would like to send to the former client. + +[NOTE] +Both CATEGORY cannot be referring to the same category. + +[NOTE] +The email notification sent to will be based on the email in the client's information. + +Examples: + +* `notify 1 c/s 1 c/t` + +An email will be send to the first student on the students list using your linked Gmail account. The email will contain relevant information regarding the first tutor on the tutors list. + +* `notify 2 c/t 1 c/s` + +An email will be send to the second tutor on the tutors list using your linked Gmail account. The email will contain relevant information regarding the first student on the students list. + + +// end::email[] + +// tag::automatch[] + +=== Auto match feature `[coming in v2.0]` +**** +* When the user enters a new client. the system will auto generates a list of potential clients for this newly added client. +* This is to enhance the efficiency of our user and shorten the time needed for the user to match potential clients. +* The reason for this enhancement is to facilitate the user when a potential client makes a call to seek for assignments. With this enhancement, the system is able to generate a list of potential clients right after the client is added into the system. The user is then able to reply the client on the available assignments right away before the client even hangs up the phone. +* One possible way of implementing it is to call for matching command with the client info after the addclient command has successfully executed. +**** +// end::automatch[] + +// tag::matchmultiple[] +=== Match multiple grade and subject attributes `[coming in v2.0]` +**** +* Currently, match command assumes all client has only one grade subject and location attribute. If a client has multiple grade attributes, match command only matches potential clients with his/her first grade attribute. +* Enhancement could be made to match all the Grade attributes respectively and list the matching result from the highest match to the lowest match. +* Only matched attributes should be highlighted. +* One possible way of implementing it is to change Grade, Subject and Location to List and compare them one by one with other clients. +**** +// end::matchmultiple[] == FAQ *Q*: How do I transfer my data to another Computer? + -*A*: Install the app in the other computer and overwrite the empty data file it creates with the file that contains the data of your previous Address Book folder. +*A*: Install the app in the other computer and overwrite the empty data file it creates with the file that contains the data of your previous TuitionCor folder. == Command Summary -* *Add* `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]...` + -e.g. `add n/James Ho p/22224444 e/jamesho@example.com a/123, Clementi Rd, 1234665 t/friend t/colleague` -* *Clear* : `clear` +* *AddClient* `addclient c/CATEGORY n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]... l/LOCATION g/GRADE s/SUBJECT` + +e.g. `addclient c/t n/Tutor1 p/98765432 a/Blk 10 Singapore, #01-239 e/testing@example.com t/family l/north g/p3 s/physics` + +Alias: `ac` +* *Clear* : `clear` + +Alias: `c` * *Delete* : `delete INDEX` + -e.g. `delete 3` +e.g. `delete 3` + +Alias: `d` * *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` +e.g. `edit 2 n/James Lee e/jameslee@example.com` + +Alias: `e` +* *Remove* : `remove INDEX c/CATEGORY s/SUBJECT` + +e.g. `remove 1 c/s s/math` +Alias: `re` * *Find* : `find KEYWORD [MORE_KEYWORDS]` + -e.g. `find James Jake` -* *List* : `list` +e.g. `find James Jake` + +Alias: `f` +* *Match* : `match INDEX CATEGORY` + +e.g. `match 1 c/s` + +Alias: `m` +* *Sort* : `sort TYPE CATEGORY` + +eg. `sort n c/s` + +Alias: `so` +* *Close* : `close INDEX CATEGORY` + +eg. `close 1 c/s` + +Alias: `cs` +* *Restore* : `restore INDEX CATEGORY` + +eg. `restore 1 c/s` + +Alias: `res` +* *Switch* : `switch` + +Alias: `sw` +* *List* : `list` + +Alias: `l` * *Help* : `help` -* *Select* : `select INDEX` + -e.g.`select 2` -* *History* : `history` -* *Undo* : `undo` -* *Redo* : `redo` +* *History* : `history` + +Alias: `h` +* *Undo* : `undo` + +Alias: `u` +* *Redo* : `redo` + +Alias: `r` diff --git a/docs/diagrams/HighLevelSequenceDiagrams.pptx b/docs/diagrams/HighLevelSequenceDiagrams.pptx index 38332090a79a..1ee8f50614f6 100644 Binary files a/docs/diagrams/HighLevelSequenceDiagrams.pptx and b/docs/diagrams/HighLevelSequenceDiagrams.pptx differ diff --git a/docs/diagrams/LogicComponentSequenceDiagram.pptx b/docs/diagrams/LogicComponentSequenceDiagram.pptx index c5b6d5fad6e3..743c052da56e 100644 Binary files a/docs/diagrams/LogicComponentSequenceDiagram.pptx and b/docs/diagrams/LogicComponentSequenceDiagram.pptx differ diff --git a/docs/diagrams/ModelComponentClassDiagram.pptx b/docs/diagrams/ModelComponentClassDiagram.pptx index 418360e10b26..2eef257b5261 100644 Binary files a/docs/diagrams/ModelComponentClassDiagram.pptx and b/docs/diagrams/ModelComponentClassDiagram.pptx differ diff --git a/docs/diagrams/UiComponentClassDiagram.pptx b/docs/diagrams/UiComponentClassDiagram.pptx index 384d0a00e6ea..31331a64d9ef 100644 Binary files a/docs/diagrams/UiComponentClassDiagram.pptx and b/docs/diagrams/UiComponentClassDiagram.pptx differ diff --git a/docs/images/ClientFieldExample.png b/docs/images/ClientFieldExample.png new file mode 100644 index 000000000000..9e3ad04ae8fb Binary files /dev/null and b/docs/images/ClientFieldExample.png differ diff --git a/docs/images/CloseSequenceDiagram.PNG b/docs/images/CloseSequenceDiagram.PNG new file mode 100644 index 000000000000..f021752e7342 Binary files /dev/null and b/docs/images/CloseSequenceDiagram.PNG differ diff --git a/docs/images/DeletePersonSdForLogic.png b/docs/images/DeletePersonSdForLogic.png index 0462b9b7be6e..f8e9d9f5687b 100644 Binary files a/docs/images/DeletePersonSdForLogic.png and b/docs/images/DeletePersonSdForLogic.png differ diff --git a/docs/images/MatchFunctionFutureImplementation.png b/docs/images/MatchFunctionFutureImplementation.png new file mode 100644 index 000000000000..d1ecf124bc6d Binary files /dev/null and b/docs/images/MatchFunctionFutureImplementation.png differ diff --git a/docs/images/MatchSequence1.png b/docs/images/MatchSequence1.png new file mode 100644 index 000000000000..a096d8e6265b Binary files /dev/null and b/docs/images/MatchSequence1.png differ diff --git a/docs/images/MatchSequence2.png b/docs/images/MatchSequence2.png new file mode 100644 index 000000000000..096777ebc767 Binary files /dev/null and b/docs/images/MatchSequence2.png differ diff --git a/docs/images/MatchSequence3.png b/docs/images/MatchSequence3.png new file mode 100644 index 000000000000..b13c70a29c23 Binary files /dev/null and b/docs/images/MatchSequence3.png differ diff --git a/docs/images/MatchSequenceDiagram.png b/docs/images/MatchSequenceDiagram.png new file mode 100644 index 000000000000..baaafb22c442 Binary files /dev/null and b/docs/images/MatchSequenceDiagram.png differ diff --git a/docs/images/MatchZeroClient.png b/docs/images/MatchZeroClient.png new file mode 100644 index 000000000000..8ed0eb8c534a Binary files /dev/null and b/docs/images/MatchZeroClient.png differ diff --git a/docs/images/ModelClassDiagram.png b/docs/images/ModelClassDiagram.png index 7ea5b4b42fb2..c9e50b81bac9 100644 Binary files a/docs/images/ModelClassDiagram.png and b/docs/images/ModelClassDiagram.png differ diff --git a/docs/images/RestoreSequenceDiagram.PNG b/docs/images/RestoreSequenceDiagram.PNG new file mode 100644 index 000000000000..5a15f863aab5 Binary files /dev/null and b/docs/images/RestoreSequenceDiagram.PNG differ diff --git a/docs/images/SDforDeletePerson.png b/docs/images/SDforDeletePerson.png index 1e836f10dcd8..c374fbaa0252 100644 Binary files a/docs/images/SDforDeletePerson.png and b/docs/images/SDforDeletePerson.png differ diff --git a/docs/images/SeparateFunctionForStudentTutor.png b/docs/images/SeparateFunctionForStudentTutor.png new file mode 100644 index 000000000000..18572f6e1338 Binary files /dev/null and b/docs/images/SeparateFunctionForStudentTutor.png differ diff --git a/docs/images/SortCommandClassDiagram.PNG b/docs/images/SortCommandClassDiagram.PNG new file mode 100644 index 000000000000..c367e893e7f3 Binary files /dev/null and b/docs/images/SortCommandClassDiagram.PNG differ diff --git a/docs/images/SortSequenceDiagram.PNG b/docs/images/SortSequenceDiagram.PNG new file mode 100644 index 000000000000..93a7db5e9dc7 Binary files /dev/null and b/docs/images/SortSequenceDiagram.PNG differ diff --git a/docs/images/StudentTutorList.png b/docs/images/StudentTutorList.png new file mode 100644 index 000000000000..3ddf53e9b141 Binary files /dev/null and b/docs/images/StudentTutorList.png differ diff --git a/docs/images/SwitchSequenceDiagram.PNG b/docs/images/SwitchSequenceDiagram.PNG new file mode 100644 index 000000000000..381633021c66 Binary files /dev/null and b/docs/images/SwitchSequenceDiagram.PNG differ diff --git a/docs/images/Ui.png b/docs/images/Ui.png index 5ec9c527b49c..800df182a2bb 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 369469ef176e..f6f567ab3362 100644 Binary files a/docs/images/UiClassDiagram.png and b/docs/images/UiClassDiagram.png differ diff --git a/docs/images/WorkingMatchFunction.png b/docs/images/WorkingMatchFunction.png new file mode 100644 index 000000000000..50a559d9070c Binary files /dev/null and b/docs/images/WorkingMatchFunction.png differ diff --git a/docs/images/damithc.jpg b/docs/images/damithc.jpg deleted file mode 100644 index 127543883893..000000000000 Binary files a/docs/images/damithc.jpg and /dev/null differ diff --git a/docs/images/destin.jpg b/docs/images/destin.jpg new file mode 100644 index 000000000000..cfbc5950bece Binary files /dev/null and b/docs/images/destin.jpg differ diff --git a/docs/images/hongCho.jpg b/docs/images/hongCho.jpg new file mode 100644 index 000000000000..d52a5265c366 Binary files /dev/null and b/docs/images/hongCho.jpg differ diff --git a/docs/images/jiahui.jpg b/docs/images/jiahui.jpg new file mode 100644 index 000000000000..50842501dbe4 Binary files /dev/null and b/docs/images/jiahui.jpg differ diff --git a/docs/images/lejolly.jpg b/docs/images/lejolly.jpg deleted file mode 100644 index 2d1d94e0cf5d..000000000000 Binary files a/docs/images/lejolly.jpg and /dev/null differ diff --git a/docs/images/m133225.jpg b/docs/images/m133225.jpg deleted file mode 100644 index fd14fb94593a..000000000000 Binary files a/docs/images/m133225.jpg and /dev/null differ diff --git a/docs/images/yijinl.jpg b/docs/images/yijinl.jpg deleted file mode 100644 index adbf62ad9406..000000000000 Binary files a/docs/images/yijinl.jpg and /dev/null differ diff --git a/docs/images/yl_coder.jpg b/docs/images/yl_coder.jpg deleted file mode 100644 index 17b48a732272..000000000000 Binary files a/docs/images/yl_coder.jpg and /dev/null differ diff --git a/docs/team/Zhu-Jiahui.adoc b/docs/team/Zhu-Jiahui.adoc new file mode 100644 index 000000000000..398a257463f3 --- /dev/null +++ b/docs/team/Zhu-Jiahui.adoc @@ -0,0 +1,53 @@ += Zhu-Jiahui - Project Portfolio +:imagesDir: ../images +:stylesDir: ../stylesheets + +== PROJECT: TuitionCor + +--- + +== Overview + +TuitionCor is for those who *prefer to use a desktop app for managing client information*. More importantly, TuitionCor is *optimized for those who prefer to work with a Command Line Interface* (CLI) while still having the benefits of a Graphical User Interface (GUI). If you can type fast, TuitionCor can get your client management tasks done faster than traditional GUI apps. + +== Summary of contributions + +* *Major enhancement*: added *Match command* +** What it does: Matches a Student to any Tutor in TuitionCor that meets the requirements of the Student (or vice versa). +** Justification: This acts as the main purpose of TuitionCor, which is to help coordinate Students and Tutors that matches in `location`, `subject` and `grade` attributes. +** Highlights: This implementation required the understanding and modification of logic, model, and ui component. It also required in-depth understanding of logic component as it is an integration of multiple command in order to search and sort the clients. + +* *Minor enhancement*: enhanced find command that search all the keywords stored in TuiTionCor. + +* *Code contributed*: [https://github.com/CS2103JAN2018-F11-B2/main/blob/master/collated/functional/Zhu-Jiahui.md[Functional code]] [https://github.com/CS2103JAN2018-F11-B2/main/blob/master/collated/test/Zhu-Jiahui.md[Test code]] + +* *Other contributions*: + +** Project management: +*** Managed releases `v1.3` - `v1.5rc` (3 releases) on GitHub +** Enhancements to existing features: +*** Updated some of the color scheme for tags (https://github.com/CS2103JAN2018-F11-B2/main/pull/92/files) +** Documentation: +*** Did cosmetic tweaks to existing contents of the User Guide: https://github.com/CS2103JAN2018-F11-B2/main/pull/92/files +** Community: +*** Reported bugs and suggestions for other teams in the class (examples: https://github.com/CS2103JAN2018-W15-B3/main/issues/132, https://github.com/CS2103JAN2018-W15-B3/main/issues/130, https://github.com/CS2103JAN2018-W15-B3/main/issues/136, https://github.com/CS2103JAN2018-W15-B3/main/issues/139, https://github.com/CS2103JAN2018-W15-B3/main/issues/123) + +== Contributions to the User Guide + + +|=== +|_Given below are sections I contributed to the User Guide. They showcase my ability to write documentation targeting end-users._ +|=== +include::../UserGuide.adoc[tag=match] +include::../UserGuide.adoc[tag=find] +include::../UserGuide.adoc[tag=automatch] +include::../UserGuide.adoc[tag=matchmultiple] + +== Contributions to the Developer Guide + +|=== +|_Given below are sections I contributed to the Developer Guide. They showcase my ability to write technical documentation and the technical depth of my contributions to the project._ +|=== + +include::../DeveloperGuide.adoc[tag=match] +include::../DeveloperGuide.adoc[tag=matchmss] diff --git a/docs/team/destin.adoc b/docs/team/destin.adoc new file mode 100644 index 000000000000..e6a25a042f91 --- /dev/null +++ b/docs/team/destin.adoc @@ -0,0 +1,66 @@ += Destin Ngeow - Project Portfolio +:imagesDir: ../images +:stylesDir: ../stylesheets + +== PROJECT: TuitionCor + +--- + +== Overview + +TuitionCor is a desktop address book application used for teaching Software Engineering principles. The user interacts with it using a CLI, and it has a GUI created with JavaFX. It is written in Java, and has about 10 kLoC. + +== Summary of contributions + +* *Major enhancement*: *Morphed AddressBook - Level 4 into TuitionCor by adding support for clients in model, logic, storage* +** What it does: Extends or changes the Person related fields and commands in AddressBook to suit Clients in TuitionCor +** Justification: This changes the original AddressBook into TuitionCor and lay the foundation for future enhancements of TuitionCor. +** 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. + +* *Minor enhancement*: added a remove command that allows user to easily edit the the frequently edited subjects without having to re-type the entire remaining subjects. + +* *Code contributed*: [https://github.com/CS2103JAN2018-F11-B2/main/blob/master/collated/functional/shookshire.md[Functional code]] [https://github.com/CS2103JAN2018-F11-B2/main/blob/master/collated/test/shookshire.md[Test code]] + +* *Other contributions*: + +** Enhancements to existing features: +*** Added new fields for the extension of Person to Client (Pull Requests https://github.com/CS2103JAN2018-F11-B2/main/pull/30[#30], https://github.com/CS2103JAN2018-F11-B2/main/pull/20[#20]) +*** Edited the existing add, edit, delete commands to work on the new Client class instead (Pull requests https://github.com/CS2103JAN2018-F11-B2/main/pull/81[#81], https://github.com/CS2103JAN2018-F11-B2/main/pull/30[#30]) +*** Remove command (Pull requests https://github.com/CS2103JAN2018-F11-B2/main/pull/89[#89]) +*** Changing existing commands that work on Person to work on Client instead (Pull requests https://github.com/CS2103JAN2018-F11-B2/main/pull/65[#65]) +*** Changed existing UI to display and to save students and tutors separately. (Pull requests https://github.com/CS2103JAN2018-F11-B2/main/pull/64[#64], https://github.com/CS2103JAN2018-F11-B2/main/pull/4[#4]) +*** Edited existing junit test to work on clients after changes were made to logic and model (Pull requests https://github.com/CS2103JAN2018-F11-B2/main/pull/31[#31]) +*** Allowed for multiple subjects, grades and location to be added with regex checking for each keyword individually instead of as a whole. (Pull requests https://github.com/CS2103JAN2018-F11-B2/main/pull/89[#89]) +** Documentation: +*** Updated the developer guide's model description to reflect the current implementation: https://github.com/CS2103JAN2018-F11-B2/main/pull/78[#78] +*** Added the initial intended features contribution to developer guide: https://github.com/CS2103JAN2018-F11-B2/main/pull/25[#25] +** Community: +*** Provided useful bug reports and suggestions for other teams. (Issues https://github.com/CS2103JAN2018-F14-B3/main/issues/178[#178], https://github.com/CS2103JAN2018-F14-B3/main/issues/180[#180], https://github.com/CS2103JAN2018-F14-B3/main/issues/170[#170]) + +== Contributions to the User Guide + + +|=== +|_Given below are sections I contributed to the User Guide. They showcase my ability to write documentation targeting end-users._ +|=== + +include::../UserGuide.adoc[tag=remove] +include::../UserGuide.adoc[tag=gradeAdvancement] + +== Contributions to the Developer Guide + +|=== +|_Given below are sections I contributed to the Developer Guide. They showcase my ability to write technical documentation and the technical depth of my contributions to the project._ +|=== + +include::../DeveloperGuide.adoc[tag=modelComponent] +include::../DeveloperGuide.adoc[tag=modelImplementation] +include::../DeveloperGuide.adoc[tag=removeTest] + +== Proposed implementations/features + +* Automatic close feature +** Instead of throwing an error when the user tries to remove the last subject from a client, ask the user if he would prefer to close the client. If yes, call the close function for the given client. +* Function for changing the grades of the students +** All the students would promote to the next grade at the end of every year eg. from p1 to p2. This feature would allow for the user to easily change all the student's grades at the end of each school year instead of having to manually edit their profile. +** Alternatively, if a clock/calender feature is to be implemented in the future, this function can sync with that clock/calender to automatically change the grades of all the students at the end of the school year. diff --git a/docs/team/hongcho.adoc b/docs/team/hongcho.adoc new file mode 100644 index 000000000000..942e831eab87 --- /dev/null +++ b/docs/team/hongcho.adoc @@ -0,0 +1,70 @@ += Lim Hong Cho - Project Portfolio +:imagesDir: ../images +:stylesDir: ../stylesheets + +== PROJECT: TuitionCor + +--- + +== Overview + +TuitionCor is for those who *prefer to use a desktop app for managing client information*. More importantly, TuitionCor is *optimized for those who prefer to work with a Command Line Interface* (CLI) while still having the benefits of a Graphical User Interface (GUI). If you can type fast, TuitionCor can get your client management tasks done faster than traditional GUI apps. + +== Summary of contributions + +* *Major enhancement*: added *close, restore and switch command* +** What it does: +*** Close command enables user to be able to close a client which is either a student and tutor. The closed client will be stored and displayed in a closed client list. +*** Restore command enables user to restore a previously closed client from the closed client list. The restored client will now be stored and displayed in active client list. +*** Switch command allows user to toggle between active client list and closed client list. +** Justification: This feature improves the product significantly because after a client is successfully assigned by the user, the client can be closed instead of deleting. This allows the user to maintain an organised and updated active client list containing unassigned clients only. Furthermore, information of past assigned clients can also be retrieved conveniently from the closed clients list if required. Clients can also be restored in an event that they become active again. +** Highlights: This enhancement paves the foundation for further enhancement to TuitionCor. + +* *Minor enhancement*: added a sort commands to allow sorting by name, subject, grade and location* + +* *Code contributed*: [https://github.com/CS2103JAN2018-F11-B2/main/blob/master/collated/functional/olimhc.md[Functional code]] [https://github.com/CS2103JAN2018-F11-B2/main/blob/master/collated/test/olimhc.md[Test code]] + +* *Other contributions*: + +** Project management: +*** Managed releases `v1.3` - `v1.5rc` (3 releases) on GitHub +** Enhancements to existing features: +*** Enhanced find command to be able to match different grade formats and multiple grade fields. (Pull requests https://github.com/CS2103JAN2018-F11-B2/main/pull/121[#121]) +*** Reworked the Ui to allow display of tutor and student list. (Pull requests https://github.com/CS2103JAN2018-F11-B2/main/pull/18[#18]) +*** Added an extra status bar to allow users to know whether they are viewing active client list or closed client list. (Pull requests https://github.com/CS2103JAN2018-F11-B2/main/pull/91[#91]) +** Community: +*** Provided useful bug reports and suggestions for other teams. (Issues https://github.com/CS2103JAN2018-W13-B4/main/issues/218[#218], https://github.com/CS2103JAN2018-W13-B4/main/issues/230[#230], https://github.com/CS2103JAN2018-W13-B4/main/issues/224[#224]) +** Tools: +*** Integrated a new Github plugin (TravisCi) to the team repo. +*** Enabled auto publishing of documentation. + +== Contributions to the User Guide + +|=== +|_Given below are sections I contributed to the User Guide. They showcase my ability to write documentation targeting end-users._ +|=== + +include::../UserGuide.adoc[tag=close] + +include::../UserGuide.adoc[tag=restore] + +include::../UserGuide.adoc[tag=switch] + +include::../UserGuide.adoc[tag=gmail] + +include::../UserGuide.adoc[tag=email] + +== Contributions to the Developer Guide + +|=== +|_Given below are sections I contributed to the Developer Guide. They showcase my ability to write technical documentation and the technical depth of my contributions to the project._ +|=== + +include::../DeveloperGuide.adoc[tag=closedClientList] + +include::../DeveloperGuide.adoc[tag=close] + +include::../DeveloperGuide.adoc[tag=restore] + +include::../DeveloperGuide.adoc[tag=switch] + diff --git a/docs/team/johndoe.adoc b/docs/team/johndoe.adoc deleted file mode 100644 index 0dfa757e454b..000000000000 --- a/docs/team/johndoe.adoc +++ /dev/null @@ -1,71 +0,0 @@ -= John Doe - Project Portfolio -:imagesDir: ../images -:stylesDir: ../stylesheets - -== PROJECT: AddressBook - Level 4 - ---- - -== Overview - -AddressBook - Level 4 is a desktop address book application used for teaching Software Engineering principles. The user interacts with it using a CLI, and it has a GUI created with JavaFX. It is written in Java, and has about 10 kLoC. - -== Summary of contributions - -* *Major enhancement*: added *the ability to undo/redo previous commands* -** What it does: allows the user to undo all previous commands one at a time. Preceding undo commands can be reversed by using the redo command. -** Justification: This feature improves the product significantly because a user can make mistakes in commands and the app should provide a convenient way to rectify them. -** Highlights: This enhancement affects existing commands and commands to be added in future. It required an in-depth analysis of design alternatives. The implementation too was challenging as it required changes to existing commands. -** Credits: _{mention here if you reused any code/ideas from elsewhere or if a third-party library is heavily used in the feature so that a reader can make a more accurate judgement of how much effort went into the feature}_ - -* *Minor enhancement*: added a history command that allows the user to navigate to previous commands using up/down keys. - -* *Code contributed*: [https://github.com[Functional code]] [https://github.com[Test code]] _{give links to collated code files}_ - -* *Other contributions*: - -** Project management: -*** Managed releases `v1.3` - `v1.5rc` (3 releases) on GitHub -** Enhancements to existing features: -*** Updated the GUI color scheme (Pull requests https://github.com[#33], https://github.com[#34]) -*** Wrote additional tests for existing features to increase coverage from 88% to 92% (Pull requests https://github.com[#36], https://github.com[#38]) -** Documentation: -*** Did cosmetic tweaks to existing contents of the User Guide: https://github.com[#14] -** Community: -*** PRs reviewed (with non-trivial review comments): https://github.com[#12], https://github.com[#32], https://github.com[#19], https://github.com[#42] -*** Contributed to forum discussions (examples: https://github.com[1], https://github.com[2], https://github.com[3], https://github.com[4]) -*** Reported bugs and suggestions for other teams in the class (examples: https://github.com[1], https://github.com[2], https://github.com[3]) -*** Some parts of the history feature I added was adopted by several other class mates (https://github.com[1], https://github.com[2]) -** Tools: -*** Integrated a third party library (Natty) to the project (https://github.com[#42]) -*** Integrated a new Github plugin (CircleCI) to the team repo - -_{you can add/remove categories in the list above}_ - -== Contributions to the User Guide - - -|=== -|_Given below are sections I contributed to the User Guide. They showcase my ability to write documentation targeting end-users._ -|=== - -include::../UserGuide.adoc[tag=undoredo] - -include::../UserGuide.adoc[tag=dataencryption] - -== Contributions to the Developer Guide - -|=== -|_Given below are sections I contributed to the Developer Guide. They showcase my ability to write technical documentation and the technical depth of my contributions to the project._ -|=== - -include::../DeveloperGuide.adoc[tag=undoredo] - -include::../DeveloperGuide.adoc[tag=dataencryption] - - -== PROJECT: PowerPointLabs - ---- - -_{Optionally, you may include other projects in your portfolio.}_ diff --git a/src/main/java/seedu/address/commons/core/Config.java b/src/main/java/seedu/address/commons/core/Config.java index 8f4d737d0e24..f94df2d6f713 100644 --- a/src/main/java/seedu/address/commons/core/Config.java +++ b/src/main/java/seedu/address/commons/core/Config.java @@ -11,7 +11,7 @@ public class Config { public static final String DEFAULT_CONFIG_FILE = "config.json"; // Config values customizable through config file - private String appTitle = "Address App"; + private String appTitle = "TuitionCor"; private Level logLevel = Level.INFO; private String userPrefsFilePath = "preferences.json"; diff --git a/src/main/java/seedu/address/commons/core/Messages.java b/src/main/java/seedu/address/commons/core/Messages.java index 1deb3a1e4695..ed88c7fb97ba 100644 --- a/src/main/java/seedu/address/commons/core/Messages.java +++ b/src/main/java/seedu/address/commons/core/Messages.java @@ -8,6 +8,6 @@ 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_PERSONS_LISTED_OVERVIEW = "%1$d persons listed!"; + public static final String MESSAGE_PERSONS_LISTED_OVERVIEW = "%1$d students and %2$d tutors listed!"; } diff --git a/src/main/java/seedu/address/commons/events/model/AddressBookChangedEvent.java b/src/main/java/seedu/address/commons/events/model/AddressBookChangedEvent.java index 7db9b5c48ed6..a5aa66436686 100644 --- a/src/main/java/seedu/address/commons/events/model/AddressBookChangedEvent.java +++ b/src/main/java/seedu/address/commons/events/model/AddressBookChangedEvent.java @@ -14,6 +14,10 @@ public AddressBookChangedEvent(ReadOnlyAddressBook data) { @Override public String toString() { - return "number of persons " + data.getPersonList().size() + ", number of tags " + data.getTagList().size(); + return "number of students " + data.getStudentList().size() + + ", number of tutors " + data.getTutorList().size() + + ", number of tags " + data.getTagList().size() + + ", number of closed student " + data.getClosedStudentList().size() + + ", number of closed tutor " + data.getClosedTutorList().size(); } } diff --git a/src/main/java/seedu/address/commons/events/ui/ClientListSwitchEvent.java b/src/main/java/seedu/address/commons/events/ui/ClientListSwitchEvent.java new file mode 100644 index 000000000000..24e07953c557 --- /dev/null +++ b/src/main/java/seedu/address/commons/events/ui/ClientListSwitchEvent.java @@ -0,0 +1,14 @@ +package seedu.address.commons.events.ui; + +import seedu.address.commons.events.BaseEvent; +//@@author olimhc +/** + * Represents an event when the user wants to switch the list + */ +public class ClientListSwitchEvent extends BaseEvent { + + @Override + public String toString() { + return this.getClass().getSimpleName(); + } +} diff --git a/src/main/java/seedu/address/commons/events/ui/ClientPanelSelectionChangedEvent.java b/src/main/java/seedu/address/commons/events/ui/ClientPanelSelectionChangedEvent.java new file mode 100644 index 000000000000..fe0ece8b5fbd --- /dev/null +++ b/src/main/java/seedu/address/commons/events/ui/ClientPanelSelectionChangedEvent.java @@ -0,0 +1,27 @@ +package seedu.address.commons.events.ui; + +import seedu.address.commons.events.BaseEvent; + +import seedu.address.ui.ClientCard; + +/** + * Represents a selection change in the Client List Panel + */ +public class ClientPanelSelectionChangedEvent extends BaseEvent { + + + private final ClientCard newSelection; + + public ClientPanelSelectionChangedEvent(ClientCard newSelection) { + this.newSelection = newSelection; + } + + @Override + public String toString() { + return this.getClass().getSimpleName(); + } + + public ClientCard getNewSelection() { + return newSelection; + } +} diff --git a/src/main/java/seedu/address/commons/util/StringUtil.java b/src/main/java/seedu/address/commons/util/StringUtil.java index 6e403c17c96e..86a94bf1064c 100644 --- a/src/main/java/seedu/address/commons/util/StringUtil.java +++ b/src/main/java/seedu/address/commons/util/StringUtil.java @@ -28,7 +28,6 @@ public static boolean containsWordIgnoreCase(String sentence, String word) { String preppedWord = word.trim(); checkArgument(!preppedWord.isEmpty(), "Word parameter cannot be empty"); - checkArgument(preppedWord.split("\\s+").length == 1, "Word parameter should be a single word"); String preppedSentence = sentence; String[] wordsInPreppedSentence = preppedSentence.split("\\s+"); diff --git a/src/main/java/seedu/address/logic/Logic.java b/src/main/java/seedu/address/logic/Logic.java index 8b34b862039a..7588aa7838f0 100644 --- a/src/main/java/seedu/address/logic/Logic.java +++ b/src/main/java/seedu/address/logic/Logic.java @@ -4,7 +4,7 @@ import seedu.address.logic.commands.CommandResult; import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.person.Person; +import seedu.address.model.person.Client; /** * API of the Logic component @@ -19,8 +19,17 @@ public interface Logic { */ CommandResult execute(String commandText) throws CommandException, ParseException; - /** Returns an unmodifiable view of the filtered list of persons */ - ObservableList getFilteredPersonList(); + /** Returns an unmodifiable view of the filtered list of students */ + ObservableList getFilteredStudentList(); + + /** Returns an unmodifiable view of the filtered list of tutors */ + ObservableList getFilteredTutorList(); + + /** Returns an unmodifiable view of the filtered list of closed students */ + ObservableList getFilteredClosedStudentList(); + + /** Returns an unmodifiable view of the filtered list of closed tutors */ + ObservableList getFilteredClosedTutorList(); /** Returns the list of input entered by the user, encapsulated in a {@code ListElementPointer} object */ ListElementPointer getHistorySnapshot(); diff --git a/src/main/java/seedu/address/logic/LogicManager.java b/src/main/java/seedu/address/logic/LogicManager.java index 9f6846bdfc74..44d89ce96d5d 100644 --- a/src/main/java/seedu/address/logic/LogicManager.java +++ b/src/main/java/seedu/address/logic/LogicManager.java @@ -11,7 +11,7 @@ import seedu.address.logic.parser.AddressBookParser; import seedu.address.logic.parser.exceptions.ParseException; import seedu.address.model.Model; -import seedu.address.model.person.Person; +import seedu.address.model.person.Client; /** * The main LogicManager of the app. @@ -33,6 +33,7 @@ public LogicManager(Model model) { @Override public CommandResult execute(String commandText) throws CommandException, ParseException { + model.resetHighLight(); logger.info("----------------[USER COMMAND][" + commandText + "]"); try { Command command = addressBookParser.parseCommand(commandText); @@ -46,8 +47,23 @@ public CommandResult execute(String commandText) throws CommandException, ParseE } @Override - public ObservableList getFilteredPersonList() { - return model.getFilteredPersonList(); + public ObservableList getFilteredStudentList() { + return model.getFilteredStudentList(); + } + + @Override + public ObservableList getFilteredTutorList() { + return model.getFilteredTutorList(); + } + + @Override + public ObservableList getFilteredClosedStudentList() { + return model.getFilteredClosedStudentList(); + } + + @Override + public ObservableList getFilteredClosedTutorList() { + return model.getFilteredClosedTutorList(); } @Override diff --git a/src/main/java/seedu/address/logic/commands/AddClientCommand.java b/src/main/java/seedu/address/logic/commands/AddClientCommand.java new file mode 100644 index 000000000000..52945dac6b22 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/AddClientCommand.java @@ -0,0 +1,95 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_CATEGORY; +import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; +import static seedu.address.logic.parser.CliSyntax.PREFIX_GRADE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_LOCATION; +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_SUBJECT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; + +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.logic.commands.exceptions.CommandNotAvailableInClosedViewException; +import seedu.address.model.person.Client; +import seedu.address.model.person.exceptions.DuplicatePersonException; +import seedu.address.ui.util.ListPanelController; + +//@@author shookshire +/** + * Adds a tutor to TuitionCor. + */ +public class AddClientCommand extends UndoableCommand { + + public static final String COMMAND_WORD = "addclient"; + public static final String COMMAND_ALIAS = "ac"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a client to TuitionCor. \n" + + "Parameters: " + + PREFIX_NAME + "NAME " + + PREFIX_CATEGORY + "CATEGORY " + + PREFIX_PHONE + "PHONE " + + PREFIX_EMAIL + "EMAIL " + + PREFIX_ADDRESS + "ADDRESS " + + "[" + PREFIX_TAG + "TAG]... " + + PREFIX_LOCATION + "LOCATION " + + PREFIX_GRADE + "GRADE " + + PREFIX_SUBJECT + "SUBJECT\n" + + "Example: " + COMMAND_WORD + " " + + PREFIX_NAME + "John Doe " + + PREFIX_CATEGORY + "t " + + PREFIX_PHONE + "98765432 " + + PREFIX_EMAIL + "johnd@example.com " + + PREFIX_ADDRESS + "311, Clementi Ave 2, #02-25 " + + PREFIX_TAG + "friends " + + PREFIX_TAG + "owesMoney " + + PREFIX_LOCATION + "east " + + PREFIX_GRADE + "p6 " + + PREFIX_SUBJECT + "physics"; + + public static final String MESSAGE_SUCCESS_STUDENT = "New student added: %1$s"; + public static final String MESSAGE_SUCCESS_TUTOR = "New tutor added: %1$s"; + public static final String MESSAGE_DUPLICATE_PERSON = "This student/tutor already exists in the address book"; + + private final Client toAdd; + + /** + * Creates an AddClientCommand to add the specified {@code Client} + */ + public AddClientCommand(Client client) { + requireNonNull(client); + toAdd = client; + } + + @Override + public CommandResult executeUndoableCommand() throws CommandException { + requireNonNull(model); + + if (!ListPanelController.isCurrentDisplayActiveList()) { + throw new CommandNotAvailableInClosedViewException(); + } + + try { + if (toAdd.getCategory().isStudent()) { + model.addStudent(toAdd); + return new CommandResult(String.format(MESSAGE_SUCCESS_STUDENT, toAdd)); + } else { + model.addTutor(toAdd); + return new CommandResult(String.format(MESSAGE_SUCCESS_TUTOR, toAdd)); + } + + } catch (DuplicatePersonException e) { + throw new CommandException(MESSAGE_DUPLICATE_PERSON); + } + + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof AddClientCommand // instanceof handles nulls + && toAdd.equals(((AddClientCommand) other).toAdd)); + } +} diff --git a/src/main/java/seedu/address/logic/commands/AddCommand.java b/src/main/java/seedu/address/logic/commands/AddCommand.java deleted file mode 100644 index c334710c0ea3..000000000000 --- a/src/main/java/seedu/address/logic/commands/AddCommand.java +++ /dev/null @@ -1,67 +0,0 @@ -package seedu.address.logic.commands; - -import static java.util.Objects.requireNonNull; -import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; -import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; -import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; -import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; -import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; - -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.model.person.Person; -import seedu.address.model.person.exceptions.DuplicatePersonException; - -/** - * Adds a person to the address book. - */ -public class AddCommand extends UndoableCommand { - - public static final String COMMAND_WORD = "add"; - - public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a person to the address book. " - + "Parameters: " - + PREFIX_NAME + "NAME " - + PREFIX_PHONE + "PHONE " - + PREFIX_EMAIL + "EMAIL " - + PREFIX_ADDRESS + "ADDRESS " - + "[" + PREFIX_TAG + "TAG]...\n" - + "Example: " + COMMAND_WORD + " " - + PREFIX_NAME + "John Doe " - + PREFIX_PHONE + "98765432 " - + PREFIX_EMAIL + "johnd@example.com " - + PREFIX_ADDRESS + "311, Clementi Ave 2, #02-25 " - + PREFIX_TAG + "friends " - + PREFIX_TAG + "owesMoney"; - - public static final String MESSAGE_SUCCESS = "New person added: %1$s"; - public static final String MESSAGE_DUPLICATE_PERSON = "This person already exists in the address book"; - - private final Person toAdd; - - /** - * Creates an AddCommand to add the specified {@code Person} - */ - public AddCommand(Person person) { - requireNonNull(person); - toAdd = person; - } - - @Override - public CommandResult executeUndoableCommand() throws CommandException { - requireNonNull(model); - try { - model.addPerson(toAdd); - return new CommandResult(String.format(MESSAGE_SUCCESS, toAdd)); - } catch (DuplicatePersonException e) { - throw new CommandException(MESSAGE_DUPLICATE_PERSON); - } - - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof AddCommand // instanceof handles nulls - && toAdd.equals(((AddCommand) other).toAdd)); - } -} diff --git a/src/main/java/seedu/address/logic/commands/ClearCommand.java b/src/main/java/seedu/address/logic/commands/ClearCommand.java index ceeb7ba913c6..6d04efaf3295 100644 --- a/src/main/java/seedu/address/logic/commands/ClearCommand.java +++ b/src/main/java/seedu/address/logic/commands/ClearCommand.java @@ -10,6 +10,7 @@ public class ClearCommand extends UndoableCommand { public static final String COMMAND_WORD = "clear"; + public static final String COMMAND_ALIAS = "c"; public static final String MESSAGE_SUCCESS = "Address book has been cleared!"; diff --git a/src/main/java/seedu/address/logic/commands/CloseCommand.java b/src/main/java/seedu/address/logic/commands/CloseCommand.java new file mode 100644 index 000000000000..a06346f08be2 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/CloseCommand.java @@ -0,0 +1,101 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_CATEGORY; + +import java.util.List; +import java.util.Objects; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.logic.commands.exceptions.CommandNotAvailableInClosedViewException; +import seedu.address.model.person.Category; +import seedu.address.model.person.Client; +import seedu.address.model.person.exceptions.DuplicatePersonException; +import seedu.address.model.person.exceptions.PersonNotFoundException; +import seedu.address.ui.util.ListPanelController; + +//@@author olimhc +/** + * Deletes a person from the active list and add it to the closed list + */ +public class CloseCommand extends UndoableCommand { + public static final String COMMAND_WORD = "close"; + public static final String COMMAND_ALIAS = "cs"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Close an active tutor or student and store them in " + + "a closed student or tutor list. \n" + + "Parameters: " + COMMAND_WORD + " " + "INDEX" + " " + PREFIX_CATEGORY + "CATEGORY \n" + + "INDEX should be non-zero and non-negative and " + + "CATEGORY can only be either 's' or 't', where 's' represents students and 't' represents tutor).\n" + + "Example: " + COMMAND_WORD + " " + "1" + " " + PREFIX_CATEGORY + "t\n"; + + public static final String MESSAGE_CLOSE_STUDENT_SUCCESS = "Student closed: %1$s"; + public static final String MESSAGE_CLOSE_TUTOR_SUCCESS = "Tutor closed: %1$s"; + + private final Index targetIndex; + private final Category category; + + private Client clientToClose; + + public CloseCommand(Index targetIndex, Category category) { + this.targetIndex = targetIndex; + this.category = category; + } + + @Override + public CommandResult executeUndoableCommand() { + requireNonNull(clientToClose); + try { + model.deleteClient(clientToClose, category); + } catch (PersonNotFoundException pnfe) { + throw new AssertionError("The target client cannot be missing"); + } + + try { + if (category.isStudent()) { + model.addClosedStudent(clientToClose); + } else { + model.addClosedTutor(clientToClose); + } + } catch (DuplicatePersonException e) { + throw new AssertionError("The client should not be duplicated"); + } + + if (category.isStudent()) { + return new CommandResult(String.format(MESSAGE_CLOSE_STUDENT_SUCCESS, clientToClose)); + } else { + return new CommandResult(String.format(MESSAGE_CLOSE_TUTOR_SUCCESS, clientToClose)); + } + } + + @Override + protected void preprocessUndoableCommand() throws CommandException { + if (!ListPanelController.isCurrentDisplayActiveList()) { + throw new CommandNotAvailableInClosedViewException(); + } + + List lastShownList; + + if (category.isStudent()) { + lastShownList = model.getFilteredStudentList(); + } else { + lastShownList = model.getFilteredTutorList(); + } + + if (targetIndex.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + + clientToClose = lastShownList.get(targetIndex.getZeroBased()); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof CloseCommand // instanceof handles nulls + && this.targetIndex.equals(((CloseCommand) other).targetIndex) // state check + && Objects.equals(this.clientToClose, ((CloseCommand) other).clientToClose)); + } +} diff --git a/src/main/java/seedu/address/logic/commands/Command.java b/src/main/java/seedu/address/logic/commands/Command.java index 6580e0b51c90..472e3dbaa5a3 100644 --- a/src/main/java/seedu/address/logic/commands/Command.java +++ b/src/main/java/seedu/address/logic/commands/Command.java @@ -5,6 +5,7 @@ import seedu.address.logic.UndoRedoStack; import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.model.Model; +import seedu.address.ui.util.ListPanelController; /** * Represents a command with hidden internal logic and the ability to be executed. @@ -13,15 +14,23 @@ public abstract class Command { protected Model model; protected CommandHistory history; protected UndoRedoStack undoRedoStack; + protected ListPanelController listPanelController; /** - * Constructs a feedback message to summarise an operation that displayed a listing of persons. + * Initialise listPanelController + */ + protected Command() { + listPanelController = ListPanelController.getInstance(); + } + + /** + * Constructs a feedback message to summarise an operation that displayed a listing of tutors. * - * @param displaySize used to generate summary + * @param displayStudentSize and displayTutorSize used to generate summary * @return summary message for persons displayed */ - public static String getMessageForPersonListShownSummary(int displaySize) { - return String.format(Messages.MESSAGE_PERSONS_LISTED_OVERVIEW, displaySize); + public static String getMessageForClientListShownSummary(int displayStudentSize, int displayTutorSize) { + return String.format(Messages.MESSAGE_PERSONS_LISTED_OVERVIEW, displayStudentSize, displayTutorSize); } /** @@ -41,3 +50,4 @@ public void setData(Model model, CommandHistory history, UndoRedoStack undoRedoS this.model = model; } } + diff --git a/src/main/java/seedu/address/logic/commands/DeleteCommand.java b/src/main/java/seedu/address/logic/commands/DeleteCommand.java index b539d240001a..db2dcd906f74 100644 --- a/src/main/java/seedu/address/logic/commands/DeleteCommand.java +++ b/src/main/java/seedu/address/logic/commands/DeleteCommand.java @@ -1,6 +1,7 @@ package seedu.address.logic.commands; import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_CATEGORY; import java.util.List; import java.util.Objects; @@ -8,8 +9,10 @@ import seedu.address.commons.core.Messages; import seedu.address.commons.core.index.Index; import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.model.person.Person; +import seedu.address.model.person.Category; +import seedu.address.model.person.Client; import seedu.address.model.person.exceptions.PersonNotFoundException; +import seedu.address.ui.util.ListPanelController; /** * Deletes a person identified using it's last displayed index from the address book. @@ -17,44 +20,71 @@ public class DeleteCommand extends UndoableCommand { public static final String COMMAND_WORD = "delete"; + public static final String COMMAND_ALIAS = "d"; public static final String MESSAGE_USAGE = COMMAND_WORD - + ": Deletes the person identified by the index number used in the last person listing.\n" - + "Parameters: INDEX (must be a positive integer)\n" - + "Example: " + COMMAND_WORD + " 1"; + + ": Deletes the student/tutor identified by the index number used in the last student/tutor listing.\n" + + "Parameters: INDEX (must be a positive integer) " + + PREFIX_CATEGORY + "CATEGORY\n" + + "Example: " + COMMAND_WORD + " 1 " + PREFIX_CATEGORY + "s"; - public static final String MESSAGE_DELETE_PERSON_SUCCESS = "Deleted Person: %1$s"; + public static final String MESSAGE_DELETE_CLIENT_SUCCESS = "Deleted Client: %1$s"; private final Index targetIndex; + private final Category category; - private Person personToDelete; + private Client clientToDelete; - public DeleteCommand(Index targetIndex) { + public DeleteCommand(Index targetIndex, Category category) { this.targetIndex = targetIndex; + this.category = category; } - @Override public CommandResult executeUndoableCommand() { - requireNonNull(personToDelete); - try { - model.deletePerson(personToDelete); - } catch (PersonNotFoundException pnfe) { - throw new AssertionError("The target person cannot be missing"); + requireNonNull(clientToDelete); + if (ListPanelController.isCurrentDisplayActiveList()) { + try { + model.deleteClient(clientToDelete, category); + } catch (PersonNotFoundException pnfe) { + throw new AssertionError("The target person cannot be missing"); + } + } else { + assert(!ListPanelController.isCurrentDisplayActiveList()); + try { + model.deleteClosedClient(clientToDelete, category); + } catch (PersonNotFoundException pnfe) { + throw new AssertionError("The target person cannot be missing"); + } } - return new CommandResult(String.format(MESSAGE_DELETE_PERSON_SUCCESS, personToDelete)); + return new CommandResult(String.format(MESSAGE_DELETE_CLIENT_SUCCESS, clientToDelete)); } @Override protected void preprocessUndoableCommand() throws CommandException { - List lastShownList = model.getFilteredPersonList(); + List lastShownList; + + if (ListPanelController.isCurrentDisplayActiveList()) { + if (category.isStudent()) { + lastShownList = model.getFilteredStudentList(); + } else { + lastShownList = model.getFilteredTutorList(); + } + } else { + assert(!ListPanelController.isCurrentDisplayActiveList()); + if (category.isStudent()) { + lastShownList = model.getFilteredClosedStudentList(); + } else { + lastShownList = model.getFilteredClosedTutorList(); + } + } if (targetIndex.getZeroBased() >= lastShownList.size()) { throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); } - personToDelete = lastShownList.get(targetIndex.getZeroBased()); + clientToDelete = lastShownList.get(targetIndex.getZeroBased()); } @Override @@ -62,6 +92,6 @@ public boolean equals(Object other) { return other == this // short circuit if same object || (other instanceof DeleteCommand // instanceof handles nulls && this.targetIndex.equals(((DeleteCommand) other).targetIndex) // state check - && Objects.equals(this.personToDelete, ((DeleteCommand) other).personToDelete)); + && Objects.equals(this.clientToDelete, ((DeleteCommand) other).clientToDelete)); } } diff --git a/src/main/java/seedu/address/logic/commands/EditCommand.java b/src/main/java/seedu/address/logic/commands/EditCommand.java index e6c3a3e034bc..6dfadf84f4e3 100644 --- a/src/main/java/seedu/address/logic/commands/EditCommand.java +++ b/src/main/java/seedu/address/logic/commands/EditCommand.java @@ -6,7 +6,8 @@ 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 static seedu.address.model.Model.PREDICATE_SHOW_ALL_STUDENTS; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_TUTORS; import java.util.Collections; import java.util.HashSet; @@ -19,14 +20,20 @@ import seedu.address.commons.core.index.Index; import seedu.address.commons.util.CollectionUtil; import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.logic.commands.exceptions.CommandNotAvailableInClosedViewException; import seedu.address.model.person.Address; +import seedu.address.model.person.Category; +import seedu.address.model.person.Client; import seedu.address.model.person.Email; +import seedu.address.model.person.Grade; +import seedu.address.model.person.Location; import seedu.address.model.person.Name; -import seedu.address.model.person.Person; import seedu.address.model.person.Phone; +import seedu.address.model.person.Subject; import seedu.address.model.person.exceptions.DuplicatePersonException; import seedu.address.model.person.exceptions.PersonNotFoundException; import seedu.address.model.tag.Tag; +import seedu.address.ui.util.ListPanelController; /** * Edits the details of an existing person in the address book. @@ -34,6 +41,7 @@ public class EditCommand extends UndoableCommand { public static final String COMMAND_WORD = "edit"; + public static final String COMMAND_ALIAS = "e"; public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits the details of the person identified " + "by the index number used in the last person listing. " @@ -53,19 +61,22 @@ public class EditCommand extends UndoableCommand { public static final String MESSAGE_DUPLICATE_PERSON = "This person already exists in the address book."; private final Index index; + private final Category category; private final EditPersonDescriptor editPersonDescriptor; - private Person personToEdit; - private Person editedPerson; + private Client personToEdit; + private Client editedPerson; /** * @param index of the person in the filtered person list to edit * @param editPersonDescriptor details to edit the person with */ - public EditCommand(Index index, EditPersonDescriptor editPersonDescriptor) { + public EditCommand(Index index, EditPersonDescriptor editPersonDescriptor, Category category) { requireNonNull(index); requireNonNull(editPersonDescriptor); + requireNonNull(category); + this.category = category; this.index = index; this.editPersonDescriptor = new EditPersonDescriptor(editPersonDescriptor); } @@ -73,19 +84,30 @@ public EditCommand(Index index, EditPersonDescriptor editPersonDescriptor) { @Override public CommandResult executeUndoableCommand() throws CommandException { try { - model.updatePerson(personToEdit, editedPerson); + model.updateClient(personToEdit, editedPerson, category); } catch (DuplicatePersonException dpe) { throw new CommandException(MESSAGE_DUPLICATE_PERSON); } catch (PersonNotFoundException pnfe) { throw new AssertionError("The target person cannot be missing"); } - model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + model.updateFilteredStudentList(PREDICATE_SHOW_ALL_STUDENTS); + model.updateFilteredTutorList(PREDICATE_SHOW_ALL_TUTORS); return new CommandResult(String.format(MESSAGE_EDIT_PERSON_SUCCESS, editedPerson)); } @Override protected void preprocessUndoableCommand() throws CommandException { - List lastShownList = model.getFilteredPersonList(); + if (!ListPanelController.isCurrentDisplayActiveList()) { + throw new CommandNotAvailableInClosedViewException(); + } + + List lastShownList; + + if (category.isStudent()) { + lastShownList = model.getFilteredStudentList(); + } else { + lastShownList = model.getFilteredTutorList(); + } if (index.getZeroBased() >= lastShownList.size()) { throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); @@ -96,10 +118,10 @@ protected void preprocessUndoableCommand() throws CommandException { } /** - * Creates and returns a {@code Person} with the details of {@code personToEdit} + * Creates and returns a {@code Client} with the details of {@code personToEdit} * edited with {@code editPersonDescriptor}. */ - private static Person createEditedPerson(Person personToEdit, EditPersonDescriptor editPersonDescriptor) { + private static Client createEditedPerson(Client personToEdit, EditPersonDescriptor editPersonDescriptor) { assert personToEdit != null; Name updatedName = editPersonDescriptor.getName().orElse(personToEdit.getName()); @@ -107,8 +129,14 @@ private static Person createEditedPerson(Person personToEdit, EditPersonDescript Email updatedEmail = editPersonDescriptor.getEmail().orElse(personToEdit.getEmail()); Address updatedAddress = editPersonDescriptor.getAddress().orElse(personToEdit.getAddress()); Set updatedTags = editPersonDescriptor.getTags().orElse(personToEdit.getTags()); + Location updatedLocation = editPersonDescriptor.getLocation().orElse(personToEdit.getLocation()); + Grade updatedGrade = editPersonDescriptor.getGrade().orElse(personToEdit.getGrade()); + Subject updatedSubject = editPersonDescriptor.getSubject().orElse(personToEdit.getSubject()); + Category updatedCategory = editPersonDescriptor.getCategory().orElse(personToEdit.getCategory()); + - return new Person(updatedName, updatedPhone, updatedEmail, updatedAddress, updatedTags); + return new Client(updatedName, updatedPhone, updatedEmail, updatedAddress, updatedTags, updatedLocation, + updatedGrade, updatedSubject, updatedCategory); } @Override @@ -131,15 +159,19 @@ public boolean equals(Object other) { } /** - * Stores the details to edit the person with. Each non-empty field value will replace the - * corresponding field value of the person. + * Stores the details to edit the client with. Each non-empty field value will replace the + * corresponding field value of the client. */ - public static class EditPersonDescriptor { + public static class EditPersonDescriptor { private Name name; private Phone phone; private Email email; private Address address; private Set tags; + private Location location; + private Grade grade; + private Subject subject; + private Category category; public EditPersonDescriptor() {} @@ -153,13 +185,18 @@ public EditPersonDescriptor(EditPersonDescriptor toCopy) { setEmail(toCopy.email); setAddress(toCopy.address); setTags(toCopy.tags); + setLocation(toCopy.location); + setGrade(toCopy.grade); + setSubject(toCopy.subject); + setCategory(toCopy.category); } /** * Returns true if at least one field is edited. */ public boolean isAnyFieldEdited() { - return CollectionUtil.isAnyNonNull(this.name, this.phone, this.email, this.address, this.tags); + return CollectionUtil.isAnyNonNull(this.name, this.phone, this.email, this.address, this.tags, + this.location, this.grade, this.subject); } public void setName(Name name) { @@ -194,6 +231,38 @@ public Optional
getAddress() { return Optional.ofNullable(address); } + public void setLocation(Location location) { + this.location = location; + } + + public Optional getLocation() { + return Optional.ofNullable(location); + } + + public void setGrade(Grade grade) { + this.grade = grade; + } + + public Optional getGrade() { + return Optional.ofNullable(grade); + } + + public void setSubject(Subject subject) { + this.subject = subject; + } + + public Optional getSubject() { + return Optional.ofNullable(subject); + } + + public void setCategory(Category category) { + this.category = category; + } + + public Optional getCategory() { + return Optional.ofNullable(category); + } + /** * Sets {@code tags} to this object's {@code tags}. * A defensive copy of {@code tags} is used internally. @@ -230,7 +299,11 @@ public boolean equals(Object other) { && getPhone().equals(e.getPhone()) && getEmail().equals(e.getEmail()) && getAddress().equals(e.getAddress()) - && getTags().equals(e.getTags()); + && getTags().equals(e.getTags()) + && getLocation().equals(e.getLocation()) + && getGrade().equals(e.getGrade()) + && getSubject().equals(e.getSubject()) + && getCategory().equals(e.getCategory()); } } } diff --git a/src/main/java/seedu/address/logic/commands/ExitCommand.java b/src/main/java/seedu/address/logic/commands/ExitCommand.java index fbd1beb2966e..e9d10ba7669f 100644 --- a/src/main/java/seedu/address/logic/commands/ExitCommand.java +++ b/src/main/java/seedu/address/logic/commands/ExitCommand.java @@ -9,6 +9,7 @@ public class ExitCommand extends Command { public static final String COMMAND_WORD = "exit"; + public static final String COMMAND_ALIAS = "x"; public static final String MESSAGE_EXIT_ACKNOWLEDGEMENT = "Exiting Address Book as requested ..."; diff --git a/src/main/java/seedu/address/logic/commands/FindCommand.java b/src/main/java/seedu/address/logic/commands/FindCommand.java index b1e671f633d2..f7ca661f7865 100644 --- a/src/main/java/seedu/address/logic/commands/FindCommand.java +++ b/src/main/java/seedu/address/logic/commands/FindCommand.java @@ -1,31 +1,44 @@ package seedu.address.logic.commands; -import seedu.address.model.person.NameContainsKeywordsPredicate; +import seedu.address.model.person.SearchContainsKeywordsPredicate; +import seedu.address.ui.util.ListPanelController; /** - * Finds and lists all persons in address book whose name contains any of the argument keywords. - * Keyword matching is case sensitive. + * Finds and lists all persons in address book that 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 COMMAND_ALIAS = "f"; - public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all persons whose names contain any of " - + "the specified keywords (case-sensitive) and displays them as a list with index numbers.\n" + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all persons that contain any of " + + "the specified keywords (case-insensitive) and displays them as a list with index numbers.\n" + "Parameters: KEYWORD [MORE_KEYWORDS]...\n" + "Example: " + COMMAND_WORD + " alice bob charlie"; - private final NameContainsKeywordsPredicate predicate; + private final SearchContainsKeywordsPredicate predicate; - public FindCommand(NameContainsKeywordsPredicate predicate) { + public FindCommand(SearchContainsKeywordsPredicate predicate) { this.predicate = predicate; } + //@@author Zhu-Jiahui @Override public CommandResult execute() { - model.updateFilteredPersonList(predicate); - return new CommandResult(getMessageForPersonListShownSummary(model.getFilteredPersonList().size())); + if (ListPanelController.isCurrentDisplayActiveList()) { + model.updateFilteredStudentList(predicate); + model.updateFilteredTutorList(predicate); + return new CommandResult(getMessageForClientListShownSummary( + model.getFilteredStudentList().size(), model.getFilteredTutorList().size())); + } else { + model.updateFilteredClosedStudentList(predicate); + model.updateFilteredClosedTutorList(predicate); + return new CommandResult(getMessageForClientListShownSummary( + model.getFilteredClosedStudentList().size(), model.getFilteredClosedTutorList().size())); + } } + //@@author @Override public boolean equals(Object other) { diff --git a/src/main/java/seedu/address/logic/commands/HistoryCommand.java b/src/main/java/seedu/address/logic/commands/HistoryCommand.java index f87abee5511d..a3a8f11563d0 100644 --- a/src/main/java/seedu/address/logic/commands/HistoryCommand.java +++ b/src/main/java/seedu/address/logic/commands/HistoryCommand.java @@ -15,6 +15,7 @@ public class HistoryCommand extends Command { public static final String COMMAND_WORD = "history"; + public static final String COMMAND_ALIAS = "h"; public static final String MESSAGE_SUCCESS = "Entered commands (from most recent to earliest):\n%1$s"; public static final String MESSAGE_NO_HISTORY = "You have not yet entered any commands."; diff --git a/src/main/java/seedu/address/logic/commands/ListCommand.java b/src/main/java/seedu/address/logic/commands/ListCommand.java index 7b6463780824..96224e154721 100644 --- a/src/main/java/seedu/address/logic/commands/ListCommand.java +++ b/src/main/java/seedu/address/logic/commands/ListCommand.java @@ -1,6 +1,11 @@ package seedu.address.logic.commands; -import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_CLOSED_STUDENTS; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_CLOSED_TUTORS; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_STUDENTS; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_TUTORS; + +import seedu.address.ui.util.ListPanelController; /** * Lists all persons in the address book to the user. @@ -8,13 +13,20 @@ public class ListCommand extends Command { public static final String COMMAND_WORD = "list"; + public static final String COMMAND_ALIAS = "l"; - public static final String MESSAGE_SUCCESS = "Listed all persons"; - + public static final String MESSAGE_SUCCESS = "Listed all clients"; @Override public CommandResult execute() { - model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + if (ListPanelController.isCurrentDisplayActiveList()) { + model.updateFilteredTutorList(PREDICATE_SHOW_ALL_TUTORS); + model.updateFilteredStudentList(PREDICATE_SHOW_ALL_STUDENTS); + } else { + assert(!ListPanelController.isCurrentDisplayActiveList()); + model.updateFilteredClosedTutorList(PREDICATE_SHOW_ALL_CLOSED_TUTORS); + model.updateFilteredClosedStudentList(PREDICATE_SHOW_ALL_CLOSED_STUDENTS); + } return new CommandResult(MESSAGE_SUCCESS); } } diff --git a/src/main/java/seedu/address/logic/commands/MatchCommand.java b/src/main/java/seedu/address/logic/commands/MatchCommand.java new file mode 100644 index 000000000000..53c90342e872 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/MatchCommand.java @@ -0,0 +1,81 @@ +package seedu.address.logic.commands; +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.logic.commands.exceptions.CommandNotAvailableInClosedViewException; +import seedu.address.model.person.Category; +import seedu.address.model.person.Client; +import seedu.address.model.person.MatchContainsKeywordsPredicate; +import seedu.address.model.person.MatchContainsPersonsPredicate; +import seedu.address.ui.util.ListPanelController; + +//@@author Zhu-Jiahui +/** + * Match the entered client and lists all clients in address book that has similar attributes to the matched client. + */ +public class MatchCommand extends Command { + + public static final String COMMAND_WORD = "match"; + public static final String COMMAND_ALIAS = "m"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Finds all clients that match all the fields listed by the person entered.\n" + + "Example: " + COMMAND_WORD + " 1" + " c/t"; + + private final Index targetIndex; + private final Category category; + + private Client clientToMatch; + + public MatchCommand(Index index, Category category) { + this.targetIndex = index; + this.category = category; + } + + @Override + public CommandResult execute() throws CommandException { + if (!ListPanelController.isCurrentDisplayActiveList()) { + throw new CommandNotAvailableInClosedViewException(); + } + List lastShownList; + + if (category.isStudent()) { + lastShownList = model.getFilteredStudentList(); + } else { + lastShownList = model.getFilteredTutorList(); + } + + if (targetIndex.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + + clientToMatch = lastShownList.get(targetIndex.getZeroBased()); + + MatchContainsKeywordsPredicate predicate = new MatchContainsKeywordsPredicate(clientToMatch); + if (category.isStudent()) { + model.updateFilteredTutorList(predicate); + model.updateFilteredStudentList(new MatchContainsPersonsPredicate(clientToMatch)); + model.updateRankedTutorList(); + + } else { + model.updateFilteredStudentList(predicate); + model.updateFilteredTutorList(new MatchContainsPersonsPredicate(clientToMatch)); + model.updateRankedStudentList(); + + } + + return new CommandResult(getMessageForClientListShownSummary( + model.getFilteredStudentList().size(), model.getFilteredTutorList().size())); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof MatchCommand // instanceof handles nulls + && this.targetIndex.equals(((MatchCommand) other).targetIndex)) + && this.category.equals(((MatchCommand) other).category); // state check + } +} +//@@author diff --git a/src/main/java/seedu/address/logic/commands/RedoCommand.java b/src/main/java/seedu/address/logic/commands/RedoCommand.java index 7b99d0f372fc..311c95c1a1f2 100644 --- a/src/main/java/seedu/address/logic/commands/RedoCommand.java +++ b/src/main/java/seedu/address/logic/commands/RedoCommand.java @@ -13,6 +13,7 @@ public class RedoCommand extends Command { public static final String COMMAND_WORD = "redo"; + public static final String COMMAND_ALIAS = "r"; public static final String MESSAGE_SUCCESS = "Redo success!"; public static final String MESSAGE_FAILURE = "No more commands to redo!"; diff --git a/src/main/java/seedu/address/logic/commands/RemoveCommand.java b/src/main/java/seedu/address/logic/commands/RemoveCommand.java new file mode 100644 index 000000000000..94718c409305 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/RemoveCommand.java @@ -0,0 +1,189 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_CATEGORY; +import static seedu.address.logic.parser.CliSyntax.PREFIX_SUBJECT; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_STUDENTS; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_TUTORS; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.logic.commands.exceptions.CommandNotAvailableInClosedViewException; +import seedu.address.model.person.Category; +import seedu.address.model.person.Client; +import seedu.address.model.person.Subject; +import seedu.address.model.person.exceptions.DuplicatePersonException; +import seedu.address.model.person.exceptions.PersonNotFoundException; +import seedu.address.ui.util.ListPanelController; + +//@@author shookshire +/** + * Remove the specified details of an existing client in the address book. + */ +public class RemoveCommand extends UndoableCommand { + + public static final String COMMAND_WORD = "remove"; + public static final String COMMAND_ALIAS = "re"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Removes the subject from the client identified " + + "by the index number used in the last client listing. " + + "If the specified subject exists it would be removed from the client. " + + "Input subject should be a single word without space.\n" + + "Parameters: INDEX (must be a positive integer) " + + "[" + PREFIX_CATEGORY + "CATEGORY] " + + "[" + PREFIX_SUBJECT + "SUBJECT]\n" + + "Example: " + COMMAND_WORD + " 1 " + + PREFIX_CATEGORY + "s " + + PREFIX_SUBJECT + "math"; + + public static final String MESSAGE_REMOVE_CLIENT_SUCCESS = "Edited Person: %1$s"; + public static final String MESSAGE_DUPLICATE_PERSON = "This person already exists in the address book."; + public static final String MESSAGE_VALUE_DONT_EXIST = "The inputted subject does not exist"; + public static final String MESSAGE_LAST_VALUE = "The last subject cannot be removed.\n" + + "Recommended to delete or close client instead"; + + public static final String REMOVE_VALIDATION_REGEX = "[a-zA-Z]+"; + + private final Index index; + private final Category category; + private final Subject toRemove; + + private Client personToEdit; + private Client editedPerson; + + + /** + * @param index of the person in the filtered person list to edit + * @param toRemove the subject to be removed + */ + public RemoveCommand(Index index, Subject toRemove, Category category) { + requireNonNull(index); + requireNonNull(toRemove); + requireNonNull(category); + + this.category = category; + this.index = index; + this.toRemove = toRemove; + } + + @Override + public CommandResult executeUndoableCommand() throws CommandException { + try { + model.updateClient(personToEdit, editedPerson, category); + } catch (DuplicatePersonException dpe) { + throw new CommandException(MESSAGE_DUPLICATE_PERSON); + } catch (PersonNotFoundException pnfe) { + throw new AssertionError("The target person cannot be missing"); + } + model.updateFilteredStudentList(PREDICATE_SHOW_ALL_STUDENTS); + model.updateFilteredTutorList(PREDICATE_SHOW_ALL_TUTORS); + return new CommandResult(String.format(MESSAGE_REMOVE_CLIENT_SUCCESS, editedPerson)); + } + + @Override + protected void preprocessUndoableCommand() throws CommandException { + if (!ListPanelController.isCurrentDisplayActiveList()) { + throw new CommandNotAvailableInClosedViewException(); + } + + List lastShownList; + + if (category.isStudent()) { + lastShownList = model.getFilteredStudentList(); + } else { + lastShownList = model.getFilteredTutorList(); + } + + if (index.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + + personToEdit = lastShownList.get(index.getZeroBased()); + + if (!isValidRemovableSubject(toRemove)) { + throw new CommandException(MESSAGE_USAGE); + } + + if (!containsSubject(personToEdit, toRemove)) { + throw new CommandException(MESSAGE_VALUE_DONT_EXIST); + } + + if (!isMoreThanOne(personToEdit.getSubject())) { + throw new CommandException(MESSAGE_LAST_VALUE); + } + + editedPerson = createEditedPerson(personToEdit, toRemove); + } + + /** + * @param personToEdit the client that we wish to remove a subject from + * @param toRemove the subject to be removed + * Returns true if the subject exists + */ + private static boolean containsSubject(Client personToEdit, Subject toRemove) { + String originalSubject = personToEdit.getSubject().toString(); + ArrayList originalSubjectArrayList = new ArrayList<>(Arrays.asList(originalSubject.split(" "))); + + return originalSubjectArrayList.contains(toRemove.value); + } + + private static boolean isValidRemovableSubject(Subject test) { + return test.value.matches(REMOVE_VALIDATION_REGEX); + } + + private static boolean isMoreThanOne(Subject test) { + String[] testArray = test.value.split(" "); + return testArray.length > 1; + } + + /** + * Creates and returns a {@code Client} with the details of {@code personToEdit} + * edited with {@code editPersonDescriptor}. + */ + private static Client createEditedPerson(Client personToEdit, Subject toRemove) { + assert personToEdit != null; + + String editSubjects = personToEdit.getSubject().value; + ArrayList editSubjectArray = new ArrayList<>(Arrays.asList(editSubjects.split(" "))); + editSubjectArray.remove(toRemove.value); + + StringBuilder sb = new StringBuilder(); + + for (int i = 0; i < editSubjectArray.size(); i++) { + sb.append(editSubjectArray.get(i)); + sb.append(" "); + } + + return new Client(personToEdit.getName(), personToEdit.getPhone(), personToEdit.getEmail(), + personToEdit.getAddress(), personToEdit.getTags(), personToEdit.getLocation(), + personToEdit.getGrade(), new Subject(sb.toString()), personToEdit.getCategory()); + } + + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof RemoveCommand)) { + return false; + } + + // state check + RemoveCommand r = (RemoveCommand) other; + return index.equals(r.index) + && Objects.equals(editedPerson, r.editedPerson) + && Objects.equals(personToEdit, r.personToEdit); + } + +} + diff --git a/src/main/java/seedu/address/logic/commands/RestoreCommand.java b/src/main/java/seedu/address/logic/commands/RestoreCommand.java new file mode 100644 index 000000000000..74fbde43107c --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/RestoreCommand.java @@ -0,0 +1,100 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_CATEGORY; + +import java.util.List; +import java.util.Objects; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.logic.commands.exceptions.CommandNotAvailableInActiveViewException; +import seedu.address.model.person.Category; +import seedu.address.model.person.Client; +import seedu.address.model.person.exceptions.DuplicatePersonException; +import seedu.address.model.person.exceptions.PersonNotFoundException; +import seedu.address.ui.util.ListPanelController; + +//@@author olimhc +/** + * Delete a person from the closed list and add it back to the active list + */ +public class RestoreCommand extends UndoableCommand { + public static final String COMMAND_WORD = "restore"; + public static final String COMMAND_ALIAS = "res"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Restore a closed tutor or student to the active " + + "tutor or student list. \n" + + "Parameters: " + COMMAND_WORD + " " + "INDEX" + " " + PREFIX_CATEGORY + "CATEGORY " + + "(CATEGORY can only be either 's' or 't', where 's' represents students and 't' represents tutor).\n" + + "Example: " + COMMAND_WORD + " " + "1" + " " + PREFIX_CATEGORY + "t\n"; + + public static final String MESSAGE_RESTORE_STUDENT_SUCCESS = "Student restored: %1$s"; + public static final String MESSAGE_RESTORE_TUTOR_SUCCESS = "Tutor restored: %1$s"; + + private final Index targetIndex; + private final Category category; + + private Client clientToRestore; + + public RestoreCommand(Index targetIndex, Category category) { + this.targetIndex = targetIndex; + this.category = category; + } + + @Override + public CommandResult executeUndoableCommand() { + requireNonNull(clientToRestore); + try { + model.deleteClosedClient(clientToRestore, category); + } catch (PersonNotFoundException pnfe) { + throw new AssertionError("The target client cannot be missing"); + } + + try { + if (category.isStudent()) { + model.addStudent(clientToRestore); + } else { + model.addTutor(clientToRestore); + } + } catch (DuplicatePersonException e) { + throw new AssertionError("The client should not be duplicated"); + } + + if (category.isStudent()) { + return new CommandResult(String.format(MESSAGE_RESTORE_STUDENT_SUCCESS, clientToRestore)); + } else { + return new CommandResult(String.format(MESSAGE_RESTORE_TUTOR_SUCCESS, clientToRestore)); + } + } + + @Override + protected void preprocessUndoableCommand() throws CommandException { + if (ListPanelController.isCurrentDisplayActiveList()) { + throw new CommandNotAvailableInActiveViewException(); + } + + List lastShownList; + + if (category.isStudent()) { + lastShownList = model.getFilteredClosedStudentList(); + } else { + lastShownList = model.getFilteredClosedTutorList(); + } + + if (targetIndex.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + + clientToRestore = lastShownList.get(targetIndex.getZeroBased()); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof RestoreCommand // instanceof handles nulls + && this.targetIndex.equals(((RestoreCommand) other).targetIndex) // state check + && Objects.equals(this.clientToRestore, ((RestoreCommand) other).clientToRestore)); + } +} diff --git a/src/main/java/seedu/address/logic/commands/SelectCommand.java b/src/main/java/seedu/address/logic/commands/SelectCommand.java deleted file mode 100644 index 9e3840a9dde6..000000000000 --- a/src/main/java/seedu/address/logic/commands/SelectCommand.java +++ /dev/null @@ -1,52 +0,0 @@ -package seedu.address.logic.commands; - -import java.util.List; - -import seedu.address.commons.core.EventsCenter; -import seedu.address.commons.core.Messages; -import seedu.address.commons.core.index.Index; -import seedu.address.commons.events.ui.JumpToListRequestEvent; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.model.person.Person; - -/** - * Selects a person identified using it's last displayed index from the address book. - */ -public class SelectCommand extends Command { - - public static final String COMMAND_WORD = "select"; - - public static final String MESSAGE_USAGE = COMMAND_WORD - + ": Selects the person identified by the index number used in the last person listing.\n" - + "Parameters: INDEX (must be a positive integer)\n" - + "Example: " + COMMAND_WORD + " 1"; - - public static final String MESSAGE_SELECT_PERSON_SUCCESS = "Selected Person: %1$s"; - - private final Index targetIndex; - - public SelectCommand(Index targetIndex) { - this.targetIndex = targetIndex; - } - - @Override - public CommandResult execute() throws CommandException { - - List lastShownList = model.getFilteredPersonList(); - - if (targetIndex.getZeroBased() >= lastShownList.size()) { - throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); - } - - EventsCenter.getInstance().post(new JumpToListRequestEvent(targetIndex)); - return new CommandResult(String.format(MESSAGE_SELECT_PERSON_SUCCESS, targetIndex.getOneBased())); - - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof SelectCommand // instanceof handles nulls - && this.targetIndex.equals(((SelectCommand) other).targetIndex)); // state check - } -} diff --git a/src/main/java/seedu/address/logic/commands/SortByGradeCommand.java b/src/main/java/seedu/address/logic/commands/SortByGradeCommand.java new file mode 100644 index 000000000000..e3db4ab678e3 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/SortByGradeCommand.java @@ -0,0 +1,43 @@ +package seedu.address.logic.commands; + +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.logic.commands.exceptions.CommandNotAvailableInClosedViewException; +import seedu.address.model.person.Category; +import seedu.address.ui.util.ListPanelController; + +//@@author olimhc +/** + *Sort the selected list according to their grade in ascending order + */ +public class SortByGradeCommand extends SortCommand { + + public static final String MESSAGE_SORT_DESC = " their grade in ascending order."; + + private Category category; + + public SortByGradeCommand(Category category) { + this.category = category; + } + + @Override + public CommandResult execute() throws CommandException { + if (!ListPanelController.isCurrentDisplayActiveList()) { + throw new CommandNotAvailableInClosedViewException(); + } + + if (category.isTutor()) { + model.sortByGradeFilteredClientTutorList(); + return new CommandResult(MESSAGE_SUCCESS_TUTOR + MESSAGE_SORT_DESC); + } else { + model.sortByGradeFilteredClientStudentList(); + return new CommandResult(MESSAGE_SUCCESS_STUDENT + MESSAGE_SORT_DESC); + } + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof SortByGradeCommand // instanceof handles nulls + & this.category.equals(((SortByGradeCommand) other).category)); // state check // state check + } +} diff --git a/src/main/java/seedu/address/logic/commands/SortByLocationCommand.java b/src/main/java/seedu/address/logic/commands/SortByLocationCommand.java new file mode 100644 index 000000000000..640777a651f4 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/SortByLocationCommand.java @@ -0,0 +1,43 @@ +package seedu.address.logic.commands; + +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.logic.commands.exceptions.CommandNotAvailableInClosedViewException; +import seedu.address.model.person.Category; +import seedu.address.ui.util.ListPanelController; + +//@@author olimhc +/** + *Sort the selected list according to their location in alphabetical order + */ +public class SortByLocationCommand extends SortCommand { + + public static final String MESSAGE_SORT_DESC = " their location in alphabetical order."; + + private Category category; + + public SortByLocationCommand(Category category) { + this.category = category; + } + + @Override + public CommandResult execute() throws CommandException { + if (!ListPanelController.isCurrentDisplayActiveList()) { + throw new CommandNotAvailableInClosedViewException(); + } + + if (category.isTutor()) { + model.sortByLocationFilteredClientTutorList(); + return new CommandResult(MESSAGE_SUCCESS_TUTOR + MESSAGE_SORT_DESC); + } else { + model.sortByLocationFilteredClientStudentList(); + return new CommandResult(MESSAGE_SUCCESS_STUDENT + MESSAGE_SORT_DESC); + } + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof SortByLocationCommand // instanceof handles nulls + & this.category.equals(((SortByLocationCommand) other).category)); // state check // state check + } +} diff --git a/src/main/java/seedu/address/logic/commands/SortByNameCommand.java b/src/main/java/seedu/address/logic/commands/SortByNameCommand.java new file mode 100644 index 000000000000..fe8265bb5caa --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/SortByNameCommand.java @@ -0,0 +1,43 @@ +package seedu.address.logic.commands; + +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.logic.commands.exceptions.CommandNotAvailableInClosedViewException; +import seedu.address.model.person.Category; +import seedu.address.ui.util.ListPanelController; + +//@@author olimhc +/** + *Sort the selected list according to their name in alphabetical order + */ +public class SortByNameCommand extends SortCommand { + + public static final String MESSAGE_SORT_DESC = " their name in alphabetical order."; + + private Category category; + + public SortByNameCommand(Category category) { + this.category = category; + } + + @Override + public CommandResult execute() throws CommandException { + if (!ListPanelController.isCurrentDisplayActiveList()) { + throw new CommandNotAvailableInClosedViewException(); + } + + if (category.isTutor()) { + model.sortByNameFilteredClientTutorList(); + return new CommandResult(MESSAGE_SUCCESS_TUTOR + MESSAGE_SORT_DESC); + } else { + model.sortByNameFilteredClientStudentList(); + return new CommandResult(MESSAGE_SUCCESS_STUDENT + MESSAGE_SORT_DESC); + } + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof SortByNameCommand // instanceof handles nulls + & this.category.equals(((SortByNameCommand) other).category)); // state check // state check + } +} diff --git a/src/main/java/seedu/address/logic/commands/SortBySubjectCommand.java b/src/main/java/seedu/address/logic/commands/SortBySubjectCommand.java new file mode 100644 index 000000000000..02e40ea1364b --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/SortBySubjectCommand.java @@ -0,0 +1,44 @@ +package seedu.address.logic.commands; + +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.logic.commands.exceptions.CommandNotAvailableInClosedViewException; +import seedu.address.model.person.Category; +import seedu.address.ui.util.ListPanelController; + +//@@author olimhc +/** + *Sort the selected list according to their subject in alphabetical order + */ +public class SortBySubjectCommand extends SortCommand { + + public static final String MESSAGE_SORT_DESC = " their subject in alphabetical order."; + + private Category category; + + + public SortBySubjectCommand(Category category) { + this.category = category; + } + + @Override + public CommandResult execute() throws CommandException { + if (!ListPanelController.isCurrentDisplayActiveList()) { + throw new CommandNotAvailableInClosedViewException(); + } + + if (category.isTutor()) { + model.sortBySubjectFilteredClientTutorList(); + return new CommandResult(MESSAGE_SUCCESS_TUTOR + MESSAGE_SORT_DESC); + } else { + model.sortBySubjectFilteredClientStudentList(); + return new CommandResult(MESSAGE_SUCCESS_STUDENT + MESSAGE_SORT_DESC); + } + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof SortBySubjectCommand // instanceof handles nulls + & this.category.equals(((SortBySubjectCommand) other).category)); // state check // state check + } +} diff --git a/src/main/java/seedu/address/logic/commands/SortCommand.java b/src/main/java/seedu/address/logic/commands/SortCommand.java new file mode 100644 index 000000000000..7c438dbd6712 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/SortCommand.java @@ -0,0 +1,47 @@ +package seedu.address.logic.commands; + +import static seedu.address.logic.parser.CliSyntax.PREFIX_CATEGORY; + +import seedu.address.logic.commands.exceptions.CommandException; + +//@@author olimhc +/** + * Represents a sort command + */ +public abstract class SortCommand extends Command { + public static final String COMMAND_WORD = "sort"; + public static final String COMMAND_ALIAS = "so"; + + public static final String COMMAND_WORD_NAME = "n"; + public static final String COMMAND_WORD_LOCATION = "l"; + public static final String COMMAND_WORD_SUBJECT = "s"; + public static final String COMMAND_WORD_GRADE = "g"; + public static final String COMMAND_WORD_TUTOR = "t"; + public static final String COMMAND_WORD_STUDENT = "s"; + + public static final String MESSAGE_SUCCESS_TUTOR = "Sorted tutor's list according to"; + public static final String MESSAGE_SUCCESS_STUDENT = "Sorted student's list according to"; + + private static final String USAGE_MESSAGE_LIST = " " + + COMMAND_WORD + " " + COMMAND_WORD_NAME + " " + PREFIX_CATEGORY + COMMAND_WORD_TUTOR + ", " + + COMMAND_WORD + " " + COMMAND_WORD_LOCATION + " " + PREFIX_CATEGORY + COMMAND_WORD_TUTOR + ", " + + COMMAND_WORD + " " + COMMAND_WORD_SUBJECT + " " + PREFIX_CATEGORY + COMMAND_WORD_TUTOR + ", " + + COMMAND_WORD + " " + COMMAND_WORD_GRADE + " " + PREFIX_CATEGORY + COMMAND_WORD_TUTOR + ", " + + "to sort Tutor's list base on name, location, subject and level respectively.\n" + + "Parameters: " + + COMMAND_WORD + " " + COMMAND_WORD_NAME + " " + PREFIX_CATEGORY + COMMAND_WORD_STUDENT + ", " + + COMMAND_WORD + " " + COMMAND_WORD_LOCATION + " " + PREFIX_CATEGORY + COMMAND_WORD_STUDENT + ", " + + COMMAND_WORD + " " + COMMAND_WORD_SUBJECT + " " + PREFIX_CATEGORY + COMMAND_WORD_STUDENT + ", " + + COMMAND_WORD + " " + COMMAND_WORD_GRADE + " " + PREFIX_CATEGORY + COMMAND_WORD_STUDENT + ", " + + "to sort Student's list base on name, location, subject and level respectively.\n" + + "Example: " + + COMMAND_WORD + " " + COMMAND_WORD_LOCATION + " " + PREFIX_CATEGORY + COMMAND_WORD_STUDENT + "\n"; + + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Sort selected list according to user choice.\n" + + "Parameters:" + USAGE_MESSAGE_LIST; + + @Override + public abstract CommandResult execute() throws CommandException; +} diff --git a/src/main/java/seedu/address/logic/commands/SwitchCommand.java b/src/main/java/seedu/address/logic/commands/SwitchCommand.java new file mode 100644 index 000000000000..6dd58c82f0ef --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/SwitchCommand.java @@ -0,0 +1,41 @@ +package seedu.address.logic.commands; + +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_CLOSED_STUDENTS; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_CLOSED_TUTORS; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_STUDENTS; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_TUTORS; + +import seedu.address.commons.core.EventsCenter; +import seedu.address.commons.events.ui.ClientListSwitchEvent; +import seedu.address.ui.util.ListPanelController; + +//@@author olimhc +/** + * Represents a switch command to enable user to switch between closed and active client list + * All active students and tutors or closed students and tutors will be shown after switching + */ +public class SwitchCommand extends Command { + + public static final String COMMAND_WORD = "switch"; + public static final String COMMAND_ALIAS = "sw"; + + public static final String MESSAGE_SUCCESS = "Switched to "; + public static final String MESSAGE_CLOSED_DISPLAY_LIST = "closed client list.\n"; + public static final String MESSAGE_ACTIVE_DISPLAY_LIST = "active client list.\n"; + + + @Override + public CommandResult execute() { + EventsCenter.getInstance().post(new ClientListSwitchEvent()); + listPanelController.switchDisplay(); + if (!ListPanelController.isCurrentDisplayActiveList()) { + model.updateFilteredClosedTutorList(PREDICATE_SHOW_ALL_CLOSED_TUTORS); + model.updateFilteredClosedStudentList(PREDICATE_SHOW_ALL_CLOSED_STUDENTS); + return new CommandResult(MESSAGE_SUCCESS + MESSAGE_CLOSED_DISPLAY_LIST); + } else { + model.updateFilteredTutorList(PREDICATE_SHOW_ALL_TUTORS); + model.updateFilteredStudentList(PREDICATE_SHOW_ALL_STUDENTS); + return new CommandResult(MESSAGE_SUCCESS + MESSAGE_ACTIVE_DISPLAY_LIST); + } + } +} diff --git a/src/main/java/seedu/address/logic/commands/UndoCommand.java b/src/main/java/seedu/address/logic/commands/UndoCommand.java index 1f3dcea8bbaa..7d62dcc53d38 100644 --- a/src/main/java/seedu/address/logic/commands/UndoCommand.java +++ b/src/main/java/seedu/address/logic/commands/UndoCommand.java @@ -13,6 +13,7 @@ public class UndoCommand extends Command { public static final String COMMAND_WORD = "undo"; + public static final String COMMAND_ALIAS = "u"; public static final String MESSAGE_SUCCESS = "Undo success!"; public static final String MESSAGE_FAILURE = "No more commands to undo!"; diff --git a/src/main/java/seedu/address/logic/commands/UndoableCommand.java b/src/main/java/seedu/address/logic/commands/UndoableCommand.java index c107ffcd9cb3..59d5fedcf802 100644 --- a/src/main/java/seedu/address/logic/commands/UndoableCommand.java +++ b/src/main/java/seedu/address/logic/commands/UndoableCommand.java @@ -2,7 +2,10 @@ import static java.util.Objects.requireNonNull; import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; -import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_CLOSED_STUDENTS; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_CLOSED_TUTORS; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_STUDENTS; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_TUTORS; import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.model.AddressBook; @@ -38,7 +41,10 @@ protected void preprocessUndoableCommand() throws CommandException {} protected final void undo() { requireAllNonNull(model, previousAddressBook); model.resetData(previousAddressBook); - model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + model.updateFilteredStudentList(PREDICATE_SHOW_ALL_STUDENTS); + model.updateFilteredTutorList(PREDICATE_SHOW_ALL_TUTORS); + model.updateFilteredClosedStudentList(PREDICATE_SHOW_ALL_CLOSED_STUDENTS); + model.updateFilteredClosedTutorList(PREDICATE_SHOW_ALL_CLOSED_TUTORS); } /** @@ -53,7 +59,10 @@ protected final void redo() { throw new AssertionError("The command has been successfully executed previously; " + "it should not fail now"); } - model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + model.updateFilteredStudentList(PREDICATE_SHOW_ALL_STUDENTS); + model.updateFilteredTutorList(PREDICATE_SHOW_ALL_TUTORS); + model.updateFilteredClosedStudentList(PREDICATE_SHOW_ALL_CLOSED_STUDENTS); + model.updateFilteredClosedTutorList(PREDICATE_SHOW_ALL_CLOSED_TUTORS); } @Override diff --git a/src/main/java/seedu/address/logic/commands/exceptions/CommandNotAvailableInActiveViewException.java b/src/main/java/seedu/address/logic/commands/exceptions/CommandNotAvailableInActiveViewException.java new file mode 100644 index 000000000000..00d44d9acd25 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/exceptions/CommandNotAvailableInActiveViewException.java @@ -0,0 +1,12 @@ +package seedu.address.logic.commands.exceptions; + +//@@author olimhc +/** + * Signals that the command is not available in active list view. + */ +public class CommandNotAvailableInActiveViewException extends CommandException { + public CommandNotAvailableInActiveViewException() { + super("Command is not available in active list view." + + " Please switch back to closed list view with the command word: switch\n"); + } +} diff --git a/src/main/java/seedu/address/logic/commands/exceptions/CommandNotAvailableInClosedViewException.java b/src/main/java/seedu/address/logic/commands/exceptions/CommandNotAvailableInClosedViewException.java new file mode 100644 index 000000000000..f047676bd96b --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/exceptions/CommandNotAvailableInClosedViewException.java @@ -0,0 +1,12 @@ +package seedu.address.logic.commands.exceptions; + +//@@author olimhc +/** + * Signals that the command is not available in closed list view. + */ +public class CommandNotAvailableInClosedViewException extends CommandException { + public CommandNotAvailableInClosedViewException() { + super("Command is not available in closed list view." + + " Please switch back to active list view with the command word: switch\n"); + } +} diff --git a/src/main/java/seedu/address/logic/commands/util/GradeUtil.java b/src/main/java/seedu/address/logic/commands/util/GradeUtil.java new file mode 100644 index 000000000000..892340aaf4af --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/util/GradeUtil.java @@ -0,0 +1,47 @@ +package seedu.address.logic.commands.util; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; +import static seedu.address.model.person.Grade.getAllGradeWeightage; +import static seedu.address.model.person.Grade.getGradeIndex; +import static seedu.address.model.person.Grade.isValidGrade; + +//@@author olimhc +/** + * Helper function for handling different format of grade + */ +public class GradeUtil { + + /** + * Returns true if the {@code value} matches the {@code word} given that word is a valid grade + *
examples:
+     *       A p3 grade should match primary3.
+     *       A client with P3 P4 grades should match a p4 grade
+     *       
+ * @param value cannot be null, can be a string of multiple grades or just a grade + * @param word cannot be null, cannot be empty, must be a single word + */ + public static boolean containsGradeIgnoreCase(String value, String word) { + requireNonNull(value); + requireNonNull(word); + + if (!isValidGrade(word)) { + return false; + } + + String preppedWord = word.trim(); + checkArgument(!preppedWord.isEmpty(), "Word parameter cannot be empty"); + checkArgument(preppedWord.split("\\s+").length == 1, "Word parameter should be a single word"); + + int preppedWordValueWeightage = getGradeIndex(preppedWord); + int[] getAllGradeWeightage = getAllGradeWeightage(value); + + for (int i : getAllGradeWeightage) { + if (i == preppedWordValueWeightage) { + return true; + } + } + + return false; + } +} diff --git a/src/main/java/seedu/address/logic/commands/util/SortByGradeComparator.java b/src/main/java/seedu/address/logic/commands/util/SortByGradeComparator.java new file mode 100644 index 000000000000..35351ca081ac --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/util/SortByGradeComparator.java @@ -0,0 +1,18 @@ +package seedu.address.logic.commands.util; + +import java.util.Comparator; + +import seedu.address.model.person.Client; + +//@@author olimhc +/** + * Comparator to sort by int base on valueWeight + */ +public class SortByGradeComparator + implements Comparator { + + @Override + public int compare(Client o1, Client o2) { + return o1.getGrade().valueWeightage - o2.getGrade().valueWeightage; + } +} diff --git a/src/main/java/seedu/address/logic/parser/AddCommandParser.java b/src/main/java/seedu/address/logic/parser/AddClientCommandParser.java similarity index 56% rename from src/main/java/seedu/address/logic/parser/AddCommandParser.java rename to src/main/java/seedu/address/logic/parser/AddClientCommandParser.java index 3c729b388554..98cc2ed953a0 100644 --- a/src/main/java/seedu/address/logic/parser/AddCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/AddClientCommandParser.java @@ -2,41 +2,52 @@ 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_CATEGORY; import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; +import static seedu.address.logic.parser.CliSyntax.PREFIX_GRADE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_LOCATION; 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_SUBJECT; import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; import java.util.Set; import java.util.stream.Stream; import seedu.address.commons.exceptions.IllegalValueException; -import seedu.address.logic.commands.AddCommand; +import seedu.address.logic.commands.AddClientCommand; import seedu.address.logic.parser.exceptions.ParseException; import seedu.address.model.person.Address; +import seedu.address.model.person.Category; +import seedu.address.model.person.Client; import seedu.address.model.person.Email; +import seedu.address.model.person.Grade; +import seedu.address.model.person.Location; import seedu.address.model.person.Name; -import seedu.address.model.person.Person; import seedu.address.model.person.Phone; +import seedu.address.model.person.Subject; import seedu.address.model.tag.Tag; +//@@author shookshire /** - * Parses input arguments and creates a new AddCommand object + * Parses input arguments and creates a new AddClientCommand object */ -public class AddCommandParser implements Parser { +public class AddClientCommandParser implements Parser { /** - * Parses the given {@code String} of arguments in the context of the AddCommand - * and returns an AddCommand object for execution. + * Parses the given {@code String} of arguments in the context of the AddClientCommand + * and returns an AddClientCommand object for execution. * @throws ParseException if the user input does not conform the expected format */ - public AddCommand parse(String args) throws ParseException { + public AddClientCommand 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_ADDRESS, + PREFIX_TAG, PREFIX_LOCATION, PREFIX_GRADE, PREFIX_SUBJECT, PREFIX_CATEGORY); - if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_ADDRESS, PREFIX_PHONE, PREFIX_EMAIL) + if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_ADDRESS, PREFIX_PHONE, PREFIX_EMAIL, + PREFIX_LOCATION, PREFIX_GRADE, PREFIX_SUBJECT, PREFIX_CATEGORY) || !argMultimap.getPreamble().isEmpty()) { - throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE)); + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddClientCommand.MESSAGE_USAGE)); } try { @@ -45,10 +56,14 @@ public AddCommand parse(String args) throws ParseException { 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)); + Location location = ParserUtil.parseLocation(argMultimap.getValue(PREFIX_LOCATION)).get(); + Grade grade = ParserUtil.parseGrade(argMultimap.getValue(PREFIX_GRADE)).get(); + Subject subject = ParserUtil.parseSubject(argMultimap.getValue(PREFIX_SUBJECT)).get(); + Category category = ParserUtil.parseCategory(argMultimap.getValue(PREFIX_CATEGORY)).get(); - Person person = new Person(name, phone, email, address, tagList); + Client client = new Client(name, phone, email, address, tagList, location, grade, subject, category); - return new AddCommand(person); + return new AddClientCommand(client); } catch (IllegalValueException ive) { throw new ParseException(ive.getMessage(), ive); } diff --git a/src/main/java/seedu/address/logic/parser/AddressBookParser.java b/src/main/java/seedu/address/logic/parser/AddressBookParser.java index b7d57f5db86a..3603bea8f953 100644 --- a/src/main/java/seedu/address/logic/parser/AddressBookParser.java +++ b/src/main/java/seedu/address/logic/parser/AddressBookParser.java @@ -6,8 +6,9 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; -import seedu.address.logic.commands.AddCommand; +import seedu.address.logic.commands.AddClientCommand; import seedu.address.logic.commands.ClearCommand; +import seedu.address.logic.commands.CloseCommand; import seedu.address.logic.commands.Command; import seedu.address.logic.commands.DeleteCommand; import seedu.address.logic.commands.EditCommand; @@ -16,8 +17,12 @@ import seedu.address.logic.commands.HelpCommand; import seedu.address.logic.commands.HistoryCommand; import seedu.address.logic.commands.ListCommand; +import seedu.address.logic.commands.MatchCommand; import seedu.address.logic.commands.RedoCommand; -import seedu.address.logic.commands.SelectCommand; +import seedu.address.logic.commands.RemoveCommand; +import seedu.address.logic.commands.RestoreCommand; +import seedu.address.logic.commands.SortCommand; +import seedu.address.logic.commands.SwitchCommand; import seedu.address.logic.commands.UndoCommand; import seedu.address.logic.parser.exceptions.ParseException; @@ -48,42 +53,74 @@ public Command parseCommand(String userInput) throws ParseException { final String arguments = matcher.group("arguments"); switch (commandWord) { - case AddCommand.COMMAND_WORD: - return new AddCommandParser().parse(arguments); - case EditCommand.COMMAND_WORD: + case EditCommand.COMMAND_ALIAS: return new EditCommandParser().parse(arguments); - case SelectCommand.COMMAND_WORD: - return new SelectCommandParser().parse(arguments); - case DeleteCommand.COMMAND_WORD: + case DeleteCommand.COMMAND_ALIAS: return new DeleteCommandParser().parse(arguments); case ClearCommand.COMMAND_WORD: + case ClearCommand.COMMAND_ALIAS: return new ClearCommand(); case FindCommand.COMMAND_WORD: + case FindCommand.COMMAND_ALIAS: return new FindCommandParser().parse(arguments); case ListCommand.COMMAND_WORD: + case ListCommand.COMMAND_ALIAS: return new ListCommand(); case HistoryCommand.COMMAND_WORD: + case HistoryCommand.COMMAND_ALIAS: return new HistoryCommand(); case ExitCommand.COMMAND_WORD: + case ExitCommand.COMMAND_ALIAS: return new ExitCommand(); case HelpCommand.COMMAND_WORD: return new HelpCommand(); case UndoCommand.COMMAND_WORD: + case UndoCommand.COMMAND_ALIAS: return new UndoCommand(); case RedoCommand.COMMAND_WORD: + + case RedoCommand.COMMAND_ALIAS: return new RedoCommand(); + case SortCommand.COMMAND_WORD: + case SortCommand.COMMAND_ALIAS: + return new SortCommandParser().parse(arguments); + + case MatchCommand.COMMAND_WORD: + case MatchCommand.COMMAND_ALIAS: + return new MatchCommandParser().parse(arguments); + + case AddClientCommand.COMMAND_WORD: + case AddClientCommand.COMMAND_ALIAS: + return new AddClientCommandParser().parse(arguments); + + case SwitchCommand.COMMAND_WORD: + case SwitchCommand.COMMAND_ALIAS: + return new SwitchCommand(); + + case CloseCommand.COMMAND_WORD: + case CloseCommand.COMMAND_ALIAS: + return new CloseCommandParser().parse(arguments); + + case RestoreCommand.COMMAND_WORD: + case RestoreCommand.COMMAND_ALIAS: + return new RestoreCommandParser().parse(arguments); + + case RemoveCommand.COMMAND_WORD: + case RemoveCommand.COMMAND_ALIAS: + return new RemoveCommandParser().parse(arguments); + default: throw new ParseException(MESSAGE_UNKNOWN_COMMAND); } diff --git a/src/main/java/seedu/address/logic/parser/CliSyntax.java b/src/main/java/seedu/address/logic/parser/CliSyntax.java index 75b1a9bf1190..a30ac5ae617a 100644 --- a/src/main/java/seedu/address/logic/parser/CliSyntax.java +++ b/src/main/java/seedu/address/logic/parser/CliSyntax.java @@ -1,5 +1,6 @@ package seedu.address.logic.parser; + /** * Contains Command Line Interface (CLI) syntax definitions common to multiple commands */ @@ -11,5 +12,9 @@ public class CliSyntax { public static final Prefix PREFIX_EMAIL = new Prefix("e/"); public static final Prefix PREFIX_ADDRESS = new Prefix("a/"); public static final Prefix PREFIX_TAG = new Prefix("t/"); + public static final Prefix PREFIX_LOCATION = new Prefix("l/"); + public static final Prefix PREFIX_GRADE = new Prefix("g/"); + public static final Prefix PREFIX_SUBJECT = new Prefix("s/"); + public static final Prefix PREFIX_CATEGORY = new Prefix("c/"); } diff --git a/src/main/java/seedu/address/logic/parser/CloseCommandParser.java b/src/main/java/seedu/address/logic/parser/CloseCommandParser.java new file mode 100644 index 000000000000..bb3228e685f1 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/CloseCommandParser.java @@ -0,0 +1,55 @@ +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_CATEGORY; + +import java.util.stream.Stream; + +import seedu.address.commons.core.index.Index; +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.logic.commands.CloseCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.person.Category; + +//@@author olimhc +/** + * Parses an input and create a new CloseCommand object + */ +public class CloseCommandParser implements Parser { + /** + * Parses the given {@code String} of arguments in the context of the CloseCommand + * and returns a Close Command object for execution. + * + * @throws ParseException if the user input does not conform the expected format + */ + public CloseCommand parse(String args) throws ParseException { + requireNonNull(args); + ArgumentMultimap argumentMultimap = ArgumentTokenizer.tokenize(args, PREFIX_CATEGORY); + + if (!arePrefixesPresent(argumentMultimap, PREFIX_CATEGORY) + || argumentMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, CloseCommand.MESSAGE_USAGE)); + } + + Index index; + Category category; + + try { + index = ParserUtil.parseIndex(argumentMultimap.getPreamble()); + category = ParserUtil.parseCategory(argumentMultimap.getValue(PREFIX_CATEGORY)).get(); + return new CloseCommand(index, category); + } catch (IllegalValueException ive) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, CloseCommand.MESSAGE_USAGE)); + } + } + + /** + * 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/DeleteCommandParser.java b/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java index fe9c1653850e..d65db6dbd91f 100644 --- a/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java @@ -1,11 +1,16 @@ 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_CATEGORY; + +import java.util.stream.Stream; import seedu.address.commons.core.index.Index; import seedu.address.commons.exceptions.IllegalValueException; import seedu.address.logic.commands.DeleteCommand; import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.person.Category; /** * Parses input arguments and creates a new DeleteCommand object @@ -18,13 +23,32 @@ public class DeleteCommandParser implements Parser { * @throws ParseException if the user input does not conform the expected format */ public DeleteCommand parse(String args) throws ParseException { + requireNonNull(args); + ArgumentMultimap argumentMultimap = ArgumentTokenizer.tokenize(args, PREFIX_CATEGORY); + + if (!arePrefixesPresent(argumentMultimap, PREFIX_CATEGORY) + || argumentMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteCommand.MESSAGE_USAGE)); + } + + Index index; + Category category; + try { - Index index = ParserUtil.parseIndex(args); - return new DeleteCommand(index); + index = ParserUtil.parseIndex(argumentMultimap.getPreamble()); + category = ParserUtil.parseCategory(argumentMultimap.getValue(PREFIX_CATEGORY)).get(); + return new DeleteCommand(index, category); } catch (IllegalValueException ive) { throw new ParseException( String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteCommand.MESSAGE_USAGE)); } } + /** + * Returns true if none of the prefixes contains empty {@code Optional} values in the given + * {@code ArgumentMultimap}. + */ + private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); + } } diff --git a/src/main/java/seedu/address/logic/parser/EditCommandParser.java b/src/main/java/seedu/address/logic/parser/EditCommandParser.java index c9cdbed26cf1..375b5a8a289d 100644 --- a/src/main/java/seedu/address/logic/parser/EditCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/EditCommandParser.java @@ -3,21 +3,27 @@ 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_ADDRESS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_CATEGORY; import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; +import static seedu.address.logic.parser.CliSyntax.PREFIX_GRADE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_LOCATION; 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_SUBJECT; import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; import java.util.Collection; import java.util.Collections; import java.util.Optional; import java.util.Set; +import java.util.stream.Stream; import seedu.address.commons.core.index.Index; import seedu.address.commons.exceptions.IllegalValueException; import seedu.address.logic.commands.EditCommand; import seedu.address.logic.commands.EditCommand.EditPersonDescriptor; import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.person.Category; import seedu.address.model.tag.Tag; /** @@ -33,12 +39,20 @@ 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_ADDRESS, + PREFIX_TAG, PREFIX_LOCATION, PREFIX_GRADE, PREFIX_SUBJECT, PREFIX_CATEGORY); + + if (!arePrefixesPresent(argMultimap, PREFIX_CATEGORY) + || argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditCommand.MESSAGE_USAGE)); + } Index index; + Category category; try { index = ParserUtil.parseIndex(argMultimap.getPreamble()); + category = ParserUtil.parseCategory(argMultimap.getValue(PREFIX_CATEGORY)).get(); } catch (IllegalValueException ive) { throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditCommand.MESSAGE_USAGE)); } @@ -50,6 +64,12 @@ public EditCommand parse(String args) throws ParseException { ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL)).ifPresent(editPersonDescriptor::setEmail); ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS)).ifPresent(editPersonDescriptor::setAddress); parseTagsForEdit(argMultimap.getAllValues(PREFIX_TAG)).ifPresent(editPersonDescriptor::setTags); + ParserUtil.parseLocation(argMultimap.getValue(PREFIX_LOCATION)) + .ifPresent(editPersonDescriptor::setLocation); + ParserUtil.parseSubject(argMultimap.getValue(PREFIX_SUBJECT)).ifPresent(editPersonDescriptor::setSubject); + ParserUtil.parseGrade(argMultimap.getValue(PREFIX_GRADE)).ifPresent(editPersonDescriptor::setGrade); + ParserUtil.parseCategory(argMultimap.getValue(PREFIX_CATEGORY)) + .ifPresent(editPersonDescriptor::setCategory); } catch (IllegalValueException ive) { throw new ParseException(ive.getMessage(), ive); } @@ -58,7 +78,7 @@ public EditCommand parse(String args) throws ParseException { throw new ParseException(EditCommand.MESSAGE_NOT_EDITED); } - return new EditCommand(index, editPersonDescriptor); + return new EditCommand(index, editPersonDescriptor, category); } /** @@ -76,4 +96,12 @@ private Optional> parseTagsForEdit(Collection tags) throws Ille return Optional.of(ParserUtil.parseTags(tagSet)); } + /** + * 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/FindCommandParser.java b/src/main/java/seedu/address/logic/parser/FindCommandParser.java index b186a967cb94..f5c3132e0c7b 100644 --- a/src/main/java/seedu/address/logic/parser/FindCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/FindCommandParser.java @@ -6,7 +6,7 @@ import seedu.address.logic.commands.FindCommand; import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.person.NameContainsKeywordsPredicate; +import seedu.address.model.person.SearchContainsKeywordsPredicate; /** * Parses input arguments and creates a new FindCommand object @@ -25,9 +25,9 @@ public FindCommand parse(String args) throws ParseException { String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE)); } - String[] nameKeywords = trimmedArgs.split("\\s+"); + String[] searchKeywords = trimmedArgs.split("\\s+"); - return new FindCommand(new NameContainsKeywordsPredicate(Arrays.asList(nameKeywords))); + return new FindCommand(new SearchContainsKeywordsPredicate(Arrays.asList(searchKeywords))); } } diff --git a/src/main/java/seedu/address/logic/parser/MatchCommandParser.java b/src/main/java/seedu/address/logic/parser/MatchCommandParser.java new file mode 100644 index 000000000000..41680344043c --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/MatchCommandParser.java @@ -0,0 +1,55 @@ +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_CATEGORY; + +import java.util.stream.Stream; + +import seedu.address.commons.core.index.Index; +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.logic.commands.MatchCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.person.Category; + +//@@author Zhu-Jiahui +/** + * Parses input arguments and creates a new MatchCommand object + */ +public class MatchCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the MatchCommand + * and returns an MatchCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public MatchCommand parse(String args) throws ParseException { + requireNonNull(args); + ArgumentMultimap argumentMultimap = ArgumentTokenizer.tokenize(args, PREFIX_CATEGORY); + + if (!arePrefixesPresent(argumentMultimap, PREFIX_CATEGORY) + || argumentMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, MatchCommand.MESSAGE_USAGE)); + } + + Index index; + Category category; + + try { + index = ParserUtil.parseIndex(argumentMultimap.getPreamble()); + category = ParserUtil.parseCategory(argumentMultimap.getValue(PREFIX_CATEGORY)).get(); + return new MatchCommand(index, category); + } catch (IllegalValueException ive) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, MatchCommand.MESSAGE_USAGE)); + } + } + + /** + * 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/ParserUtil.java b/src/main/java/seedu/address/logic/parser/ParserUtil.java index 5d6d4ae3f7b1..e9330fb30d4f 100644 --- a/src/main/java/seedu/address/logic/parser/ParserUtil.java +++ b/src/main/java/seedu/address/logic/parser/ParserUtil.java @@ -11,9 +11,13 @@ import seedu.address.commons.exceptions.IllegalValueException; import seedu.address.commons.util.StringUtil; import seedu.address.model.person.Address; +import seedu.address.model.person.Category; import seedu.address.model.person.Email; +import seedu.address.model.person.Grade; +import seedu.address.model.person.Location; import seedu.address.model.person.Name; import seedu.address.model.person.Phone; +import seedu.address.model.person.Subject; import seedu.address.model.tag.Tag; /** @@ -29,6 +33,7 @@ public class ParserUtil { public static final String MESSAGE_INVALID_INDEX = "Index is not a non-zero unsigned integer."; public static final String MESSAGE_INSUFFICIENT_PARTS = "Number of parts must be more than 1."; + public static final String MESSAGE_INVALID_SORT_TYPE = "Sort type should not be empty."; /** * Parses {@code oneBasedIndex} into an {@code Index} and returns it. Leading and trailing whitespaces will be @@ -165,4 +170,102 @@ public static Set parseTags(Collection tags) throws IllegalValueExc } return tagSet; } + + /** + * Parses a {@code String location} into an {@code Location}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws IllegalValueException if the given {@code location} is invalid. + */ + public static Location parseLocation(String location) throws IllegalValueException { + requireNonNull(location); + String trimmedLocation = location.trim(); + if (!Location.isValidLocation(trimmedLocation)) { + throw new IllegalValueException(Location.MESSAGE_LOCATION_CONSTRAINTS); + } + return new Location(trimmedLocation); + } + + /** + * Parses a {@code Optional location} into an {@code Optional} if {@code location} is present. + * See header comment of this class regarding the use of {@code Optional} parameters. + */ + + public static Optional parseLocation(Optional location) throws IllegalValueException { + requireNonNull(location); + return location.isPresent() ? Optional.of(parseLocation(location.get())) : Optional.empty(); + } + + /** + * Parses a {@code String grade} into an {@code Grade}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws IllegalValueException if the given {@code grade} is invalid. + */ + public static Grade parseGrade(String grade) throws IllegalValueException { + requireNonNull(grade); + String trimmedGrade = grade.trim(); + if (!Grade.isValidGrade(trimmedGrade)) { + throw new IllegalValueException(Grade.MESSAGE_GRADE_CONSTRAINTS); + } + return new Grade(trimmedGrade); + } + + /** + * Parses a {@code Optional grade} into an {@code Optional} if {@code grade} is present. + * See header comment of this class regarding the use of {@code Optional} parameters. + */ + public static Optional parseGrade(Optional grade) throws IllegalValueException { + requireNonNull(grade); + return grade.isPresent() ? Optional.of(parseGrade(grade.get())) : Optional.empty(); + } + + /** + * Parses a {@code String subject} into an {@code Subject}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws IllegalValueException if the given {@code subject} is invalid. + */ + public static Subject parseSubject(String subject) throws IllegalValueException { + requireNonNull(subject); + String trimmedSubject = subject.trim(); + if (!Subject.isValidSubject(trimmedSubject)) { + throw new IllegalValueException(Subject.MESSAGE_SUBJECT_CONSTRAINTS); + } + return new Subject(trimmedSubject); + } + + /** + * Parses a {@code Optional subject} into an {@code Optional} if {@code subject} is present. + * See header comment of this class regarding the use of {@code Optional} parameters. + */ + public static Optional parseSubject(Optional subject) throws IllegalValueException { + requireNonNull(subject); + return subject.isPresent() ? Optional.of(parseSubject(subject.get())) : Optional.empty(); + } + + /** + * Parses a {@code String category} into an {@code Category}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws IllegalValueException if the given {@code type} is invalid. + */ + public static Category parseCategory(String category) throws IllegalValueException { + requireNonNull(category); + String trimmedCategory = category.trim(); + if (!Category.isValidCategory(trimmedCategory)) { + throw new IllegalValueException(Category.MESSAGE_CATEGORY_CONSTRAINTS); + } + return new Category(trimmedCategory); + } + + /** + * Parses a {@code Optional category} into an {@code Optional} if {@code category} is present. + * See header comment of this class regarding the use of {@code Optional} parameters. + */ + public static Optional parseCategory(Optional category) throws IllegalValueException { + requireNonNull(category); + return category.isPresent() ? Optional.of(parseCategory(category.get())) : Optional.empty(); + } } + diff --git a/src/main/java/seedu/address/logic/parser/RemoveCommandParser.java b/src/main/java/seedu/address/logic/parser/RemoveCommandParser.java new file mode 100644 index 000000000000..8f9a6cd2bdc6 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/RemoveCommandParser.java @@ -0,0 +1,60 @@ +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_CATEGORY; +import static seedu.address.logic.parser.CliSyntax.PREFIX_SUBJECT; + +import java.util.stream.Stream; + +import seedu.address.commons.core.index.Index; +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.logic.commands.RemoveCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.person.Category; +import seedu.address.model.person.Subject; + +//@@author shookshire +/** + * Parses input arguments and creates a new RemoveCommand object + */ +public class RemoveCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the RemoveCommand + * and returns an RemoveCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public RemoveCommand parse(String args) throws ParseException { + requireNonNull(args); + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_SUBJECT, PREFIX_CATEGORY); + + if (!arePrefixesPresent(argMultimap, PREFIX_CATEGORY, PREFIX_SUBJECT) + || argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, RemoveCommand.MESSAGE_USAGE)); + } + + Index index; + Category category; + Subject subject; + + try { + index = ParserUtil.parseIndex(argMultimap.getPreamble()); + category = ParserUtil.parseCategory(argMultimap.getValue(PREFIX_CATEGORY)).get(); + subject = ParserUtil.parseSubject(argMultimap.getValue(PREFIX_SUBJECT)).get(); + } catch (IllegalValueException ive) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, RemoveCommand.MESSAGE_USAGE)); + } + + return new RemoveCommand(index, subject, category); + } + + /** + * 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/RestoreCommandParser.java b/src/main/java/seedu/address/logic/parser/RestoreCommandParser.java new file mode 100644 index 000000000000..6d11e7eef2d9 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/RestoreCommandParser.java @@ -0,0 +1,55 @@ +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_CATEGORY; + +import java.util.stream.Stream; + +import seedu.address.commons.core.index.Index; +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.logic.commands.RestoreCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.person.Category; + +//@@author olimhc +/** + * Parse an input and create a new RestoreCommand object + */ +public class RestoreCommandParser implements Parser { + /** + * Parses the given {@code String} of arguments in the context of the RestoreCommand + * and returns a Restore Command object for execution. + * + * @throws ParseException if the user input does not conform the expected format + */ + public RestoreCommand parse(String args) throws ParseException { + requireNonNull(args); + ArgumentMultimap argumentMultimap = ArgumentTokenizer.tokenize(args, PREFIX_CATEGORY); + + if (!arePrefixesPresent(argumentMultimap, PREFIX_CATEGORY) + || argumentMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, RestoreCommand.MESSAGE_USAGE)); + } + + Index index; + Category category; + + try { + index = ParserUtil.parseIndex(argumentMultimap.getPreamble()); + category = ParserUtil.parseCategory(argumentMultimap.getValue(PREFIX_CATEGORY)).get(); + return new RestoreCommand(index, category); + } catch (IllegalValueException ive) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, RestoreCommand.MESSAGE_USAGE)); + } + } + + /** + * 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/SelectCommandParser.java b/src/main/java/seedu/address/logic/parser/SelectCommandParser.java deleted file mode 100644 index bbcae8f4b588..000000000000 --- a/src/main/java/seedu/address/logic/parser/SelectCommandParser.java +++ /dev/null @@ -1,29 +0,0 @@ -package seedu.address.logic.parser; - -import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; - -import seedu.address.commons.core.index.Index; -import seedu.address.commons.exceptions.IllegalValueException; -import seedu.address.logic.commands.SelectCommand; -import seedu.address.logic.parser.exceptions.ParseException; - -/** - * Parses input arguments and creates a new SelectCommand object - */ -public class SelectCommandParser implements Parser { - - /** - * Parses the given {@code String} of arguments in the context of the SelectCommand - * and returns an SelectCommand object for execution. - * @throws ParseException if the user input does not conform the expected format - */ - public SelectCommand parse(String args) throws ParseException { - try { - Index index = ParserUtil.parseIndex(args); - return new SelectCommand(index); - } catch (IllegalValueException ive) { - throw new ParseException( - String.format(MESSAGE_INVALID_COMMAND_FORMAT, SelectCommand.MESSAGE_USAGE)); - } - } -} diff --git a/src/main/java/seedu/address/logic/parser/SortCommandParser.java b/src/main/java/seedu/address/logic/parser/SortCommandParser.java new file mode 100644 index 000000000000..bed1ff98ee89 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/SortCommandParser.java @@ -0,0 +1,83 @@ +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_CATEGORY; + +import java.util.stream.Stream; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.logic.commands.SortByGradeCommand; +import seedu.address.logic.commands.SortByLocationCommand; +import seedu.address.logic.commands.SortByNameCommand; +import seedu.address.logic.commands.SortBySubjectCommand; +import seedu.address.logic.commands.SortCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.person.Category; + +//@@author olimhc +/** + * Parses input arguments and creates a new subclass object of SortCommand + */ +public class SortCommandParser implements Parser { + + /** + * Parse the given {@code String} of arguments in the context of SortCommand + * @return either SortByGradeCommand, SortByNameCommand, SortByGradeCommand, SortBySubjectCommand + * object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public SortCommand parse(String args) throws ParseException { + requireNonNull(args); + ArgumentMultimap argumentMultimap = ArgumentTokenizer.tokenize(args, PREFIX_CATEGORY); + + if (!arePrefixesPresent(argumentMultimap, PREFIX_CATEGORY) + || argumentMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, SortCommand.MESSAGE_USAGE)); + } + + String sortType; + Category category; + + try { + sortType = argumentMultimap.getPreamble(); + category = ParserUtil.parseCategory(argumentMultimap.getValue(PREFIX_CATEGORY)).get(); + return getSortCommandType(category, sortType); + } catch (IllegalValueException ive) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, SortCommand.MESSAGE_USAGE)); + } + } + + /** + * @return the respective sort command based on the category parsed + * @throws ParseException + */ + private SortCommand getSortCommandType(Category category, String sortType) throws ParseException { + + switch (sortType) { + case SortCommand.COMMAND_WORD_NAME: + return new SortByNameCommand(category); + + case SortCommand.COMMAND_WORD_SUBJECT: + return new SortBySubjectCommand(category); + + case SortCommand.COMMAND_WORD_LOCATION: + return new SortByLocationCommand(category); + + case SortCommand.COMMAND_WORD_GRADE: + return new SortByGradeCommand(category); + + default: + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, SortCommand.MESSAGE_USAGE)); + } + } + + /** + * 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/model/AddressBook.java b/src/main/java/seedu/address/model/AddressBook.java index f8d0260de159..4e0b05c133dd 100644 --- a/src/main/java/seedu/address/model/AddressBook.java +++ b/src/main/java/seedu/address/model/AddressBook.java @@ -11,20 +11,23 @@ import java.util.stream.Collectors; import javafx.collections.ObservableList; -import seedu.address.model.person.Person; -import seedu.address.model.person.UniquePersonList; +import seedu.address.model.person.Category; +import seedu.address.model.person.Client; +import seedu.address.model.person.UniqueClientList; import seedu.address.model.person.exceptions.DuplicatePersonException; import seedu.address.model.person.exceptions.PersonNotFoundException; import seedu.address.model.tag.Tag; import seedu.address.model.tag.UniqueTagList; - +//@@author shookshire /** * Wraps all data at the address-book level * Duplicates are not allowed (by .equals comparison) */ public class AddressBook implements ReadOnlyAddressBook { - - private final UniquePersonList persons; + private final UniqueClientList students; + private final UniqueClientList tutors; + private final UniqueClientList closedStudents; + private final UniqueClientList closedTutors; private final UniqueTagList tags; /* @@ -35,10 +38,14 @@ public class AddressBook implements ReadOnlyAddressBook { * among constructors. */ { - persons = new UniquePersonList(); + students = new UniqueClientList(); + tutors = new UniqueClientList(); + closedTutors = new UniqueClientList(); + closedStudents = new UniqueClientList(); tags = new UniqueTagList(); } + //@@author public AddressBook() {} /** @@ -50,11 +57,24 @@ public AddressBook(ReadOnlyAddressBook toBeCopied) { } //// list overwrite operations + //@@author shookshire + public void setStudents(List students) throws DuplicatePersonException { + this.students.setClients(students); + } - public void setPersons(List persons) throws DuplicatePersonException { - this.persons.setPersons(persons); + public void setTutors(List tutors) throws DuplicatePersonException { + this.tutors.setClients(tutors); } + public void setClosedStudents(List closedStudents) throws DuplicatePersonException { + this.closedStudents.setClients(closedStudents); + } + + public void setClosedTutors(List closedTutors) throws DuplicatePersonException { + this.closedTutors.setClients(closedTutors); + } + + //@@author public void setTags(Set tags) { this.tags.setTags(tags); } @@ -65,63 +85,173 @@ public void setTags(Set tags) { public void resetData(ReadOnlyAddressBook newData) { requireNonNull(newData); setTags(new HashSet<>(newData.getTagList())); - List syncedPersonList = newData.getPersonList().stream() + List syncedStudentList = newData.getStudentList().stream() + .map(this::syncWithMasterTagList) + .collect(Collectors.toList()); + List syncedTutorList = newData.getTutorList().stream() + .map(this::syncWithMasterTagList) + .collect(Collectors.toList()); + List syncedClosedStudentList = newData.getClosedStudentList().stream() + .map(this::syncWithMasterTagList) + .collect(Collectors.toList()); + List syncedClosedTutorList = newData.getClosedTutorList().stream() .map(this::syncWithMasterTagList) .collect(Collectors.toList()); try { - setPersons(syncedPersonList); + setStudents(syncedStudentList); + } catch (DuplicatePersonException e) { + throw new AssertionError("AddressBooks should not have duplicate students"); + } + try { + setTutors(syncedTutorList); + } catch (DuplicatePersonException e) { + throw new AssertionError("AddressBooks should not have duplicate tutors"); + } + try { + setClosedStudents(syncedClosedStudentList); + } catch (DuplicatePersonException e) { + throw new AssertionError("AddressBooks should not have duplicate students"); + } + try { + setClosedTutors(syncedClosedTutorList); } catch (DuplicatePersonException e) { - throw new AssertionError("AddressBooks should not have duplicate persons"); + throw new AssertionError("AddressBooks should not have duplicate tutors"); } } //// person-level operations + //@@author shookshire + /** + * Adds a tutor to TuitionCor. + * Also checks the new tutor's tags and updates {@link #tags} with any new tags found, + * and updates the Tag objects in the tutor to point to those in {@link #tags}. + * + * @throws DuplicatePersonException if an equivalent person already exists. + */ + public void addTutor(Client t) throws DuplicatePersonException { + Client tutor = syncWithMasterTagList(t); + // TODO: the tags master list will be updated even though the below line fails. + // This can cause the tags master list to have additional tags that are not tagged to any person + // in the person list. + tutors.add(tutor, closedTutors); + } /** - * Adds a person to the address book. - * Also checks the new person's tags and updates {@link #tags} with any new tags found, - * and updates the Tag objects in the person to point to those in {@link #tags}. + * Adds a student to TuitionCor. + * Also checks the new student's tags and updates {@link #tags} with any new tags found, + * and updates the Tag objects in the student to point to those in {@link #tags}. * * @throws DuplicatePersonException if an equivalent person already exists. */ - public void addPerson(Person p) throws DuplicatePersonException { - Person person = syncWithMasterTagList(p); + public void addStudent(Client t) throws DuplicatePersonException { + Client student = syncWithMasterTagList(t); + // TODO: the tags master list will be updated even though the below line fails. + // This can cause the tags master list to have additional tags that are not tagged to any person + // in the person list. + students.add(student, closedStudents); + } + + /** + * Adds a student to closed student's list. + * Also checks the closed student's tags and updates {@link #tags} with any new tags found, + * and updates the Tag objects in the closed student to point to those in {@link #tags}. + * + * @throws AssertionError if an equivalent person already exists. + */ + public void addClosedStudent(Client t) throws AssertionError { + Client closedStudent = syncWithMasterTagList(t); + // TODO: the tags master list will be updated even though the below line fails. + // This can cause the tags master list to have additional tags that are not tagged to any person + // in the person list. + closedStudents.add(closedStudent); + } + + /** + * Adds a tutor to closed tutor's list. + * Also checks the closed tutor's tags and updates {@link #tags} with any new tags found, + * and updates the Tag objects in the closed tutor to point to those in {@link #tags}. + * + * @throws AssertionError if an equivalent person already exists. + */ + public void addClosedTutor(Client t) throws AssertionError { + Client closedTutor = syncWithMasterTagList(t); // TODO: the tags master list will be updated even though the below line fails. // This can cause the tags master list to have additional tags that are not tagged to any person // in the person list. - persons.add(person); + closedTutors.add(closedTutor); } /** - * Replaces the given person {@code target} in the list with {@code editedPerson}. - * {@code AddressBook}'s tag list will be updated with the tags of {@code editedPerson}. + * For test cases use and when adding sample data + * Adds a closed client to TuitionCor. + * Also checks the new student's tags and updates {@link #tags} with any new tags found, + * and updates the Tag objects in the tutor to point to those in {@link #tags}. * - * @throws DuplicatePersonException if updating the person's details causes the person to be equivalent to - * another existing person in the list. + */ + public void addClosedClient(Client t) { + if (t.getCategory().isStudent()) { + Client closedStudent = syncWithMasterTagList(t); + closedStudents.add(closedStudent); + } else { + Client closedTutor = syncWithMasterTagList(t); + closedTutors.add(closedTutor); + } + } + + /** + * For test cases use and when adding sample data + * Adds a client to TuitionCor + * Also checks the new student's tags and updates {@link #tags} with any new tags found, + * and updates the Tag objects in the tutor to point to those in {@link #tags}. + * + * @throws DuplicatePersonException if an equivalent person already exists. + */ + public void addClient(Client t) throws DuplicatePersonException { + if (t.getCategory().isStudent()) { + Client student = syncWithMasterTagList(t); + students.add(student); + } else { + Client tutor = syncWithMasterTagList(t); + tutors.add(tutor); + } + } + + /** + * Replaces the given client {@code target} in the list with {@code editedClient}. + * {@code AddressBook}'s tag list will be updated with the tags of {@code editedClient}. + * Either closedStudents or closedTutors will be pass in for duplication check when editing the client in active + * list. + * @throws DuplicatePersonException if updating the client's details causes the client to be equivalent to + * another existing client in the list. * @throws PersonNotFoundException if {@code target} could not be found in the list. * - * @see #syncWithMasterTagList(Person) + * @see #syncWithMasterTagList(Client) */ - public void updatePerson(Person target, Person editedPerson) + public void updatePerson(Client target, Client editedClient, Category category) throws DuplicatePersonException, PersonNotFoundException { - requireNonNull(editedPerson); + requireNonNull(editedClient); - Person syncedEditedPerson = syncWithMasterTagList(editedPerson); + Client syncedEditedPerson = syncWithMasterTagList(editedClient); // TODO: the tags master list will be updated even though the below line fails. // This can cause the tags master list to have additional tags that are not tagged to any person // in the person list. - persons.setPerson(target, syncedEditedPerson); + if (category.isStudent()) { + students.setClient(target, syncedEditedPerson, closedStudents); + } else { + tutors.setClient(target, syncedEditedPerson, closedTutors); + } } + //@@author /** - * Updates the master tag list to include tags in {@code person} that are not in the list. - * @return a copy of this {@code person} such that every tag in this person points to a Tag object in the master + * Updates the master tag list to include tags in {@code client} that are not in the list. + * @return a copy of this {@code client} such that every tag in this person points to a Tag object in the master * list. */ - private Person syncWithMasterTagList(Person person) { - final UniqueTagList personTags = new UniqueTagList(person.getTags()); - tags.mergeFrom(personTags); + private Client syncWithMasterTagList(Client client) { + final UniqueTagList clientTags = new UniqueTagList(client.getTags()); + tags.mergeFrom(clientTags); // Create map with values = tag object references in the master list // used for checking person tag references @@ -130,23 +260,54 @@ private Person syncWithMasterTagList(Person person) { // Rebuild the list of person tags to point to the relevant tags in the master tag list. final Set correctTagReferences = new HashSet<>(); - personTags.forEach(tag -> correctTagReferences.add(masterTagObjects.get(tag))); - return new Person( - person.getName(), person.getPhone(), person.getEmail(), person.getAddress(), correctTagReferences); + clientTags.forEach(tag -> correctTagReferences.add(masterTagObjects.get(tag))); + return new Client( + client.getName(), client.getPhone(), client.getEmail(), client.getAddress(), correctTagReferences, + client.getLocation(), client.getGrade(), client.getSubject(), client.getCategory()); + } + + //@@author shookshire + /** + * Removes {@code key} from the active client list in this {@code AddressBook}. + * @throws PersonNotFoundException if the {@code key} is not in this {@code AddressBook}. + */ + public boolean removeClient(Client key, Category category) throws PersonNotFoundException { + Boolean isSuccess; + + if (category.isStudent()) { + isSuccess = students.remove(key); + } else { + isSuccess = tutors.remove(key); + } + + if (isSuccess) { + return true; + } else { + throw new PersonNotFoundException(); + } } /** - * Removes {@code key} from this {@code AddressBook}. + * Removes {@code key} from the closed client list in this {@code AddressBook}. * @throws PersonNotFoundException if the {@code key} is not in this {@code AddressBook}. */ - public boolean removePerson(Person key) throws PersonNotFoundException { - if (persons.remove(key)) { + public boolean removeClosedClient(Client key, Category category) throws PersonNotFoundException { + Boolean isSuccess; + + if (category.isStudent()) { + isSuccess = closedStudents.remove(key); + } else { + isSuccess = closedTutors.remove(key); + } + + if (isSuccess) { return true; } else { throw new PersonNotFoundException(); } } + //@@author //// tag-level operations public void addTag(Tag t) throws UniqueTagList.DuplicateTagException { @@ -157,13 +318,32 @@ public void addTag(Tag t) throws UniqueTagList.DuplicateTagException { @Override public String toString() { - return persons.asObservableList().size() + " persons, " + tags.asObservableList().size() + " tags"; + return students.asObservableList().size() + " students, " + + tutors.asObservableList().size() + " tutors, " + + tags.asObservableList().size() + " tags"; // TODO: refine later } + //@@author shookshire + @Override + public ObservableList getStudentList() { + return students.asObservableList(); + } + + @Override + public ObservableList getTutorList() { + return tutors.asObservableList(); + } + + //@@author + @Override + public ObservableList getClosedStudentList() { + return closedStudents.asObservableList(); + } + @Override - public ObservableList getPersonList() { - return persons.asObservableList(); + public ObservableList getClosedTutorList() { + return closedTutors.asObservableList(); } @Override @@ -175,13 +355,14 @@ public ObservableList getTagList() { public boolean equals(Object other) { return other == this // short circuit if same object || (other instanceof AddressBook // instanceof handles nulls - && this.persons.equals(((AddressBook) other).persons) + && this.students.equals(((AddressBook) other).students) + && this.tutors.equals(((AddressBook) other).tutors) && this.tags.equalsOrderInsensitive(((AddressBook) other).tags)); } @Override public int hashCode() { // use this method for custom fields hashing instead of implementing your own - return Objects.hash(persons, tags); + return Objects.hash(students, tutors, tags); } } diff --git a/src/main/java/seedu/address/model/Model.java b/src/main/java/seedu/address/model/Model.java index 4a6079ce0199..ed9c3d964429 100644 --- a/src/main/java/seedu/address/model/Model.java +++ b/src/main/java/seedu/address/model/Model.java @@ -3,7 +3,8 @@ import java.util.function.Predicate; import javafx.collections.ObservableList; -import seedu.address.model.person.Person; +import seedu.address.model.person.Category; +import seedu.address.model.person.Client; import seedu.address.model.person.exceptions.DuplicatePersonException; import seedu.address.model.person.exceptions.PersonNotFoundException; @@ -11,8 +12,18 @@ * The API of the Model component. */ public interface Model { + + /** {@code Predicate} that always evaluate to true */ + Predicate PREDICATE_SHOW_ALL_STUDENTS = unused -> true; + + /** {@code Predicate} that always evaluate to true */ + Predicate PREDICATE_SHOW_ALL_TUTORS = unused -> true; + /** {@code Predicate} that always evaluate to true */ - Predicate PREDICATE_SHOW_ALL_PERSONS = unused -> true; + Predicate PREDICATE_SHOW_ALL_CLOSED_STUDENTS = unused -> true; + + /** {@code Predicate} that always evaluate to true */ + Predicate PREDICATE_SHOW_ALL_CLOSED_TUTORS = unused -> true; /** Clears existing backing model and replaces with the provided new data. */ void resetData(ReadOnlyAddressBook newData); @@ -20,29 +31,102 @@ public interface Model { /** Returns the AddressBook */ ReadOnlyAddressBook getAddressBook(); - /** Deletes the given person. */ - void deletePerson(Person target) throws PersonNotFoundException; + /** Deletes the given client in the active list. */ + void deleteClient(Client target, Category category) throws PersonNotFoundException; - /** Adds the given person */ - void addPerson(Person person) throws DuplicatePersonException; + /** Deletes the given client in the closed list. */ + void deleteClosedClient(Client target, Category category) throws PersonNotFoundException; /** - * Replaces the given person {@code target} with {@code editedPerson}. + * Replaces the given person {@code target} with {@code editedClient}. * - * @throws DuplicatePersonException if updating the person's details causes the person to be equivalent to - * another existing person in the list. + * @throws DuplicatePersonException if updating the client's details causes the client to be equivalent to + * another existing client in the list. * @throws PersonNotFoundException if {@code target} could not be found in the list. */ - void updatePerson(Person target, Person editedPerson) + void updateClient(Client target, Client editedPerson, Category category) throws DuplicatePersonException, PersonNotFoundException; - /** Returns an unmodifiable view of the filtered person list */ - ObservableList getFilteredPersonList(); + /** Adds the given tutor */ + void addTutor(Client tutor) throws DuplicatePersonException; + + /** Adds the given student */ + void addStudent(Client student) throws DuplicatePersonException; + + /** Adds the given tutor to closed tutor's list */ + void addClosedTutor(Client closedTutor) throws DuplicatePersonException; + + /** Adds the given student to closed student's list */ + void addClosedStudent(Client closedStudent) throws DuplicatePersonException; + + /**Sorts tutor list by name in alphabetical order*/ + void sortByNameFilteredClientTutorList(); + /**Sorts tutor list by location in alphabetical order*/ + void sortByLocationFilteredClientTutorList(); + /**Sorts tutor list by grade in ascending order*/ + void sortByGradeFilteredClientTutorList(); + /**Sorts tutor list by subject in alphabetical order*/ + void sortBySubjectFilteredClientTutorList(); + /**Sorts student list by name in alphabetical order*/ + void sortByNameFilteredClientStudentList(); + /**Sorts student list by location in alphabetical order*/ + void sortByLocationFilteredClientStudentList(); + /**Sorts student list by grade in ascending order*/ + void sortByGradeFilteredClientStudentList(); + /**Sorts student list by subject in alphabetical order*/ + void sortBySubjectFilteredClientStudentList(); + + /** Returns an unmodifiable view of the filtered students list */ + ObservableList getFilteredStudentList(); /** - * Updates the filter of the filtered person list to filter by the given {@code predicate}. + * Updates the filter of the filtered student list to filter by the given {@code predicate}. * @throws NullPointerException if {@code predicate} is null. */ - void updateFilteredPersonList(Predicate predicate); + void updateFilteredStudentList(Predicate predicate); + + /** Returns an unmodifiable view of the filtered tutors list */ + ObservableList getFilteredTutorList(); + + /** + * Updates the filter of the filtered tutor list to filter by the given {@code predicate}. + * @throws NullPointerException if {@code predicate} is null. + */ + void updateFilteredTutorList(Predicate predicate); + + /** Returns an unmodifiable view of the closed filtered tutors list */ + ObservableList getFilteredClosedTutorList(); + + /** + * Updates the filter of the filtered closed tutor list to filter by the given {@code predicate}. + * @throws NullPointerException if {@code predicate} is null. + */ + void updateFilteredClosedTutorList(Predicate predicate); + + /** Returns an unmodifiable view of the closed filtered students list */ + ObservableList getFilteredClosedStudentList(); + + /** + * Updates the filter of the filtered closed tutor list to filter by the given {@code predicate}. + * @throws NullPointerException if {@code predicate} is null. + */ + void updateFilteredClosedStudentList(Predicate predicate); + + /** + * Rank TutorList from the most number of matched attributes to the least number of matched attributes + */ + void updateRankedTutorList(); + + /** + * Rank StudentList from the most number of matched attributes to the least number of matched attributes + */ + void updateRankedStudentList(); + + /** + * Reset {@code rank}, {@code MatchedGrade}, {@code MatchedLocation} and {@code MatchedSubject} in + * all Clientlist to default value + */ + void resetHighLight(); } + diff --git a/src/main/java/seedu/address/model/ModelManager.java b/src/main/java/seedu/address/model/ModelManager.java index 22a7d0eb3f4d..3e9c437ce7fe 100644 --- a/src/main/java/seedu/address/model/ModelManager.java +++ b/src/main/java/seedu/address/model/ModelManager.java @@ -3,16 +3,21 @@ import static java.util.Objects.requireNonNull; import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; +import java.util.Comparator; import java.util.function.Predicate; import java.util.logging.Logger; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.collections.transformation.FilteredList; +import javafx.collections.transformation.SortedList; + import seedu.address.commons.core.ComponentManager; import seedu.address.commons.core.LogsCenter; import seedu.address.commons.events.model.AddressBookChangedEvent; -import seedu.address.model.person.Person; +import seedu.address.logic.commands.util.SortByGradeComparator; +import seedu.address.model.person.Category; +import seedu.address.model.person.Client; import seedu.address.model.person.exceptions.DuplicatePersonException; import seedu.address.model.person.exceptions.PersonNotFoundException; @@ -24,7 +29,14 @@ public class ModelManager extends ComponentManager implements Model { private static final Logger logger = LogsCenter.getLogger(ModelManager.class); private final AddressBook addressBook; - private final FilteredList filteredPersons; + private final FilteredList filteredStudents; + private final FilteredList filteredTutors; + + private final FilteredList filteredClosedStudents; + private final FilteredList filteredClosedTutors; + + private SortedList sortedFilteredTutors; + private SortedList sortedFilteredStudents; /** * Initializes a ModelManager with the given addressBook and userPrefs. @@ -36,7 +48,14 @@ public ModelManager(ReadOnlyAddressBook addressBook, UserPrefs userPrefs) { logger.fine("Initializing with address book: " + addressBook + " and user prefs " + userPrefs); this.addressBook = new AddressBook(addressBook); - filteredPersons = new FilteredList<>(this.addressBook.getPersonList()); + filteredStudents = new FilteredList<>(this.addressBook.getStudentList()); + filteredTutors = new FilteredList<>(this.addressBook.getTutorList()); + filteredClosedStudents = new FilteredList<>(this.addressBook.getClosedStudentList()); + filteredClosedTutors = new FilteredList<>(this.addressBook.getClosedTutorList()); + + sortedFilteredTutors = new SortedList<>(filteredTutors); + sortedFilteredStudents = new SortedList<>(filteredStudents); + } public ModelManager() { @@ -60,42 +79,233 @@ private void indicateAddressBookChanged() { } @Override - public synchronized void deletePerson(Person target) throws PersonNotFoundException { - addressBook.removePerson(target); + public synchronized void deleteClient(Client target, Category category) throws PersonNotFoundException { + addressBook.removeClient(target, category); indicateAddressBookChanged(); } - + //@@author shookshire @Override - public synchronized void addPerson(Person person) throws DuplicatePersonException { - addressBook.addPerson(person); - updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + public synchronized void deleteClosedClient(Client target, Category category) throws PersonNotFoundException { + addressBook.removeClosedClient(target, category); indicateAddressBookChanged(); } @Override - public void updatePerson(Person target, Person editedPerson) + public void updateClient(Client target, Client editedPerson, Category category) throws DuplicatePersonException, PersonNotFoundException { - requireAllNonNull(target, editedPerson); + requireAllNonNull(target, editedPerson, category); + addressBook.updatePerson(target, editedPerson, category); + indicateAddressBookChanged(); + } + + @Override + public synchronized void addTutor(Client tutor) throws DuplicatePersonException { + addressBook.addTutor(tutor); + updateFilteredTutorList(PREDICATE_SHOW_ALL_TUTORS); + indicateAddressBookChanged(); + } + + @Override + public synchronized void addStudent(Client student) throws DuplicatePersonException { + addressBook.addStudent(student); + updateFilteredStudentList(PREDICATE_SHOW_ALL_STUDENTS); + indicateAddressBookChanged(); + } - addressBook.updatePerson(target, editedPerson); + @Override + public synchronized void addClosedTutor(Client closedTutor) throws DuplicatePersonException { + addressBook.addClosedTutor(closedTutor); + updateFilteredClosedTutorList(PREDICATE_SHOW_ALL_CLOSED_TUTORS); indicateAddressBookChanged(); } - //=========== Filtered Person List Accessors ============================================================= + @Override + public synchronized void addClosedStudent(Client closedStudent) throws DuplicatePersonException { + addressBook.addClosedStudent(closedStudent); + updateFilteredClosedStudentList(PREDICATE_SHOW_ALL_CLOSED_STUDENTS); + indicateAddressBookChanged(); + } + + //@@author + //=========== Filtered Client List Accessors ============================================================= + + //@@author olimhc + /** + * Returns an unmodifiable view of the list of {@code Tutor} backed by the internal list of + * {@code addressBook} + */ + + @Override + public void sortByNameFilteredClientTutorList() { + Comparator sortByName = (tutor1, tutor2)-> (tutor1.getName().fullName) + .compareToIgnoreCase(tutor2.getName().fullName); + sortedFilteredTutors.setComparator(sortByName); + } + + @Override + public void sortByNameFilteredClientStudentList() { + Comparator sortByName = (student1, student2)-> (student1.getName().fullName) + .compareToIgnoreCase(student2.getName().fullName); + sortedFilteredStudents.setComparator(sortByName); + } + + @Override + public void sortByLocationFilteredClientTutorList() { + Comparator sortByLocation = (tutor1, tutor2)-> (tutor1.getLocation().value) + .compareToIgnoreCase(tutor2.getLocation().value); + sortedFilteredTutors.setComparator(sortByLocation); + } + + @Override + public void sortByLocationFilteredClientStudentList() { + Comparator sortByLocation = (student1, student2)-> (student1.getLocation().value) + .compareToIgnoreCase(student2.getLocation().value); + sortedFilteredStudents.setComparator(sortByLocation); + } + + + @Override + public void sortByGradeFilteredClientTutorList() { + Comparator sortByGrade = new SortByGradeComparator(); + sortedFilteredTutors.setComparator(sortByGrade); + } + + @Override + public void sortByGradeFilteredClientStudentList() { + Comparator sortByGrade = new SortByGradeComparator(); + sortedFilteredStudents.setComparator(sortByGrade); + } + @Override + public void sortBySubjectFilteredClientTutorList() { + Comparator sortBySubject = (tutor1, tutor2)-> (tutor1.getSubject().value) + .compareToIgnoreCase(tutor2.getSubject().value); + sortedFilteredTutors.setComparator(sortBySubject); + } + + @Override + public void sortBySubjectFilteredClientStudentList() { + Comparator sortBySubject = (student1, student2)-> (student1.getSubject().value) + .compareToIgnoreCase(student2.getSubject().value); + sortedFilteredStudents.setComparator(sortBySubject); + } + //@@author + + //@@author shookshire /** - * Returns an unmodifiable view of the list of {@code Person} backed by the internal list of + * Returns an unmodifiable view of the list of {@code Client} backed by the internal list of * {@code addressBook} */ @Override - public ObservableList getFilteredPersonList() { - return FXCollections.unmodifiableObservableList(filteredPersons); + public ObservableList getFilteredStudentList() { + return FXCollections.unmodifiableObservableList(sortedFilteredStudents); + } + + @Override + public void updateFilteredStudentList(Predicate predicate) { + requireNonNull(predicate); + filteredStudents.setPredicate(predicate); + indicateAddressBookChanged(); + } + + /** + * Returns an unmodifiable view of the list of {@code Client} backed by the internal list of + * {@code addressBook} + */ + @Override + public ObservableList getFilteredTutorList() { + return FXCollections.unmodifiableObservableList(sortedFilteredTutors); + } + + @Override + public void updateFilteredTutorList(Predicate predicate) { + requireNonNull(predicate); + filteredTutors.setPredicate(predicate); + indicateAddressBookChanged(); + } + //@@author + //=========== Ranked Person List Accessors ============================================================= + + //@@author Zhu-Jiahui + /** + * Returns an unmodifiable view of the list of {@code Client} backed by the internal list of + * {@code addressBook} + */ + + @Override + public void updateRankedStudentList() { + Comparator rankStudent = new RankComparator(); + sortedFilteredStudents.setComparator(rankStudent); + } + + /** + * Returns an unmodifiable view of the list of {@code Client} backed by the internal list of + * {@code addressBook} + */ + + @Override + public void updateRankedTutorList() { + Comparator rankTutor = new RankComparator(); + sortedFilteredTutors.setComparator(rankTutor); + } + + /** + * Reset {@code rank}, {@code MatchedGrade}, {@code MatchedLocation} and {@code MatchedSubject} in all + * Clientlist to default value + */ + + @Override + public void resetHighLight() { + for (Client client : filteredTutors) { + client.setRank(0); + client.setMatchedLocation(false); + client.setMatchedGrade(false); + client.setMatchedSubject(false); + } + for (Client client : filteredStudents) { + client.setRank(0); + client.setMatchedLocation(false); + client.setMatchedGrade(false); + client.setMatchedSubject(false); + } + + for (Client client : sortedFilteredStudents) { + client.setRank(0); + client.setMatchedLocation(false); + client.setMatchedGrade(false); + client.setMatchedSubject(false); + } + + for (Client client : sortedFilteredTutors) { + client.setRank(0); + client.setMatchedLocation(false); + client.setMatchedGrade(false); + client.setMatchedSubject(false); + } + + } + //@@author + + @Override + public ObservableList getFilteredClosedTutorList() { + return FXCollections.unmodifiableObservableList(filteredClosedTutors); + } + + @Override + public void updateFilteredClosedTutorList(Predicate predicate) { + requireNonNull(predicate); + filteredClosedTutors.setPredicate(predicate); + } + + @Override + public ObservableList getFilteredClosedStudentList() { + return FXCollections.unmodifiableObservableList(filteredClosedStudents); } @Override - public void updateFilteredPersonList(Predicate predicate) { + public void updateFilteredClosedStudentList(Predicate predicate) { requireNonNull(predicate); - filteredPersons.setPredicate(predicate); + filteredClosedStudents.setPredicate(predicate); } @Override @@ -113,7 +323,10 @@ public boolean equals(Object obj) { // state check ModelManager other = (ModelManager) obj; return addressBook.equals(other.addressBook) - && filteredPersons.equals(other.filteredPersons); + && filteredStudents.equals(other.filteredStudents) + && filteredTutors.equals(other.filteredTutors) + && filteredClosedStudents.equals(other.filteredClosedStudents) + && filteredClosedTutors.equals(other.filteredClosedTutors); } } diff --git a/src/main/java/seedu/address/model/RankComparator.java b/src/main/java/seedu/address/model/RankComparator.java new file mode 100644 index 000000000000..53def0c31cf1 --- /dev/null +++ b/src/main/java/seedu/address/model/RankComparator.java @@ -0,0 +1,17 @@ +package seedu.address.model; + +import java.util.Comparator; + +import seedu.address.model.person.Client; + +/** + * Comparator to sort by int base on valueWeight + */ +public class RankComparator + implements Comparator { + + @Override + public int compare(Client o1, Client o2) { + return o2.getRank() - o1.getRank(); + } +} diff --git a/src/main/java/seedu/address/model/ReadOnlyAddressBook.java b/src/main/java/seedu/address/model/ReadOnlyAddressBook.java index 1f4e49a37d67..30d571de5b14 100644 --- a/src/main/java/seedu/address/model/ReadOnlyAddressBook.java +++ b/src/main/java/seedu/address/model/ReadOnlyAddressBook.java @@ -1,7 +1,7 @@ package seedu.address.model; import javafx.collections.ObservableList; -import seedu.address.model.person.Person; +import seedu.address.model.person.Client; import seedu.address.model.tag.Tag; /** @@ -10,10 +10,28 @@ public interface ReadOnlyAddressBook { /** - * Returns an unmodifiable view of the persons list. - * This list will not contain any duplicate persons. + * Returns an unmodifiable view of the student list. + * This list will not contain any duplicate clients. */ - ObservableList getPersonList(); + ObservableList getStudentList(); + + /** + * Returns an unmodifiable view of the tutors list. + * This list will not contain any duplicate clients. + */ + ObservableList getTutorList(); + + /** + * Returns an unmodifiable view of the closed student list. + * This list will not contain any duplicate clients. + */ + ObservableList getClosedStudentList(); + + /** + * Returns an unmodifiable view of the closed tutors list. + * This list will not contain any duplicate clients. + */ + ObservableList getClosedTutorList(); /** * Returns an unmodifiable view of the tags list. diff --git a/src/main/java/seedu/address/model/person/Category.java b/src/main/java/seedu/address/model/person/Category.java new file mode 100644 index 000000000000..aba183a39b2e --- /dev/null +++ b/src/main/java/seedu/address/model/person/Category.java @@ -0,0 +1,66 @@ +package seedu.address.model.person; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +//@@author shookshire +/** + * Represents if a Client is a student or tutor in TuitionCor. + * Guarantees: immutable; is valid as declared in {@link #isValidCategory(String)} + */ +public class Category { + + public static final String MESSAGE_CATEGORY_CONSTRAINTS = + "Client Category can only be s or t, representing student or tutor respectively"; + + /* + * Must be either s or t + */ + public static final String ADDRESS_VALIDATION_REGEX = "[st]"; + + public final String value; + + /** + * Constructs an {@code Category}. + * + * @param category A valid category. + */ + public Category(String category) { + requireNonNull(category); + checkArgument(isValidCategory(category), MESSAGE_CATEGORY_CONSTRAINTS); + this.value = category; + } + + /** + * Returns true if a given string is a valid client category. + */ + public static boolean isValidCategory(String test) { + return test.matches(ADDRESS_VALIDATION_REGEX); + } + + public boolean isStudent() { + return value.equals("s"); + } + + public boolean isTutor() { + return value.equals("t"); + } + + @Override + public String toString() { + return value; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Category // instanceof handles nulls + && this.value.equals(((Category) other).value)); // state check + } + + @Override + public int hashCode() { + return value.hashCode(); + } + +} diff --git a/src/main/java/seedu/address/model/person/Client.java b/src/main/java/seedu/address/model/person/Client.java new file mode 100644 index 000000000000..d9638f269392 --- /dev/null +++ b/src/main/java/seedu/address/model/person/Client.java @@ -0,0 +1,114 @@ +package seedu.address.model.person; + +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; + +import java.util.Objects; +import java.util.Set; + +import seedu.address.model.tag.Tag; + +//@@author shookshire +/** + * Represents a Client in tuitionCor. + * Guarantees: details are present and not null, field values are validated, immutable. + */ +public class Client extends Person { + + private final Location location; + private final Grade grade; + private final Subject subject; + private final Category category; + private int rank = 0; + private boolean matchedGrade = false; + private boolean matchedSubject = false; + private boolean matchedLocation = false; + + /** + * Every field must be present and not null. + */ + public Client(Name name, Phone phone, Email email, Address address, Set tags, Location location, + Grade grade, Subject subject, Category category) { + super(name, phone, email, address, tags); + requireAllNonNull(location, grade, subject); + this.location = location; + this.grade = grade; + this.subject = subject; + this.category = category; + } + + public Location getLocation() { + return location; + } + + public Grade getGrade() { + return grade; + } + + public Subject getSubject() { + return subject; + } + + public Category getCategory() { + return category; + } + + public int getRank() { + return rank; + } + + public void setRank(int value) { + this.rank = value; + } + + public boolean getMatchedGrade() { + return matchedGrade; + } + + public void setMatchedGrade(boolean isMatch) { + this.matchedGrade = isMatch; + } + + public boolean getMatchedSubject() { + return matchedSubject; + } + + public void setMatchedSubject(boolean isMatch) { + this.matchedSubject = isMatch; + } + + public boolean getMatchedLocation() { + return matchedLocation; + } + + public void setMatchedLocation(boolean isMatch) { + this.matchedLocation = isMatch; + } + + @Override + public int hashCode() { + // use this method for custom fields hashing instead of implementing your own + return Objects.hash(this.getName(), this.getPhone(), this.getEmail(), this.getAddress(), + this.getTags(), location, grade, subject, category); + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append(getName()) + .append(" Phone: ") + .append(getPhone()) + .append(" Email: ") + .append(getEmail()) + .append(" Address: ") + .append(getAddress()) + .append(" Tags: "); + getTags().forEach(builder::append); + builder.append(" Location: ") + .append(getLocation()) + .append(" Grade: ") + .append(getGrade()) + .append(" Subject: ") + .append(getSubject()); + return builder.toString(); + } +} diff --git a/src/main/java/seedu/address/model/person/Grade.java b/src/main/java/seedu/address/model/person/Grade.java new file mode 100644 index 000000000000..0fd9b48e6a0b --- /dev/null +++ b/src/main/java/seedu/address/model/person/Grade.java @@ -0,0 +1,244 @@ +package seedu.address.model.person; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +import java.util.HashSet; +import java.util.Set; + +//@@author shookshire +/** + * Represents a Client's related Grade (the year of study eg. Primary 4, Secondary 3) in the TuitionCor. + * Guarantees: immutable; is valid as declared in {@link #isValidGrade(String)} + */ +public class Grade { + public static final String MESSAGE_WORD_KINDERGARTEN = "Kindergarten"; + public static final String MESSAGE_WORD_KINDERGARTEN_ALIAS = "K"; + public static final String MESSAGE_WORD_PRIMARY = "Primary"; + public static final String MESSAGE_WORD_PRIMARY_ALIAS = "P"; + public static final String MESSAGE_WORD_SECONDARY = "Secondary"; + public static final String MESSAGE_WORD_SECONDARY_ALIAS = "S"; + public static final String MESSAGE_WORD_TERTIARY = "Tertiary"; + public static final String MESSAGE_WORD_TERTIARY_ALIAS = "J"; + public static final String MESSAGE_WORD_UNIVERSITY = "University"; + public static final String MESSAGE_WORD_UNIVERSITY_ALIAS = "U"; + + private static final String KINDERGARTEN_REGEX = "kindergarten"; + private static final String KINDERGARTEN_ALIAS_REGEX = "k"; + private static final String PRIMARY_REGEX = "primary"; + private static final String PRIMARY_ALIAS_REGEX = "p"; + private static final String SECONDARY_REGEX = "secondary"; + private static final String SECONDARY_ALIAS_REGEX = "s"; + private static final String TERTIARY_REGEX = "tertiary"; + private static final String TERTIARY_ALIAS_REGEX = "j"; + private static final String UNIVERSITY_REGEX = "university"; + private static final String UNIVERSITY_ALIAS_REGEX = "u"; + + private static final String GRADE_VALIDATION_REGEX_KINDERGARTEN_FULL = "(?i)" + + MESSAGE_WORD_KINDERGARTEN + "[1-3]"; + private static final String GRADE_VALIDATION_REGEX_KINDERGARTEN_ALIAS = "(?i)" + + MESSAGE_WORD_KINDERGARTEN_ALIAS + "[1-3]"; + private static final String GRADE_VALIDATION_REGEX_PRIMARY_FULL = "(?i)" + + MESSAGE_WORD_PRIMARY + "[1-6]"; + private static final String GRADE_VALIDATION_REGEX_PRIMARY_ALIAS = "(?i)" + + MESSAGE_WORD_PRIMARY_ALIAS + "[1-6]"; + private static final String GRADE_VALIDATION_REGEX_SECONDARY_FULL = "(?i)" + + MESSAGE_WORD_SECONDARY + "[1-5]"; + private static final String GRADE_VALIDATION_REGEX_SECONDARY_ALIAS = "(?i)" + + MESSAGE_WORD_SECONDARY_ALIAS + "[1-5]"; + private static final String GRADE_VALIDATION_REGEX_TERTIARY_FULL = "(?i)" + + MESSAGE_WORD_TERTIARY + "[1-2]"; + private static final String GRADE_VALIDATION_REGEX_TERTIARY_ALIAS = "(?i)" + + MESSAGE_WORD_TERTIARY_ALIAS + "[1-2]"; + private static final String GRADE_VALIDATION_REGEX_UNIVERSITY_FULL = "(?i)" + + MESSAGE_WORD_UNIVERSITY + "[1-4]"; + private static final String GRADE_VALIDATION_REGEX_UNIVERSITY_ALIAS = "(?i)" + + MESSAGE_WORD_UNIVERSITY_ALIAS + "[1-4]"; + + public static final String MESSAGE_GRADE_CONSTRAINTS = + "Grades accepted are " + MESSAGE_WORD_KINDERGARTEN + " (1-3)" + + ", " + MESSAGE_WORD_PRIMARY + " (1-6)" + + ", " + MESSAGE_WORD_SECONDARY + " (1-5)" + + ", " + MESSAGE_WORD_TERTIARY + " (1-2)" + + ", " + MESSAGE_WORD_UNIVERSITY + " (1-4).\n" + + "Alias acceptable are " + MESSAGE_WORD_KINDERGARTEN_ALIAS + " for " + MESSAGE_WORD_KINDERGARTEN + + ", " + MESSAGE_WORD_PRIMARY_ALIAS + " for " + MESSAGE_WORD_PRIMARY + + ", " + MESSAGE_WORD_SECONDARY_ALIAS + " for " + MESSAGE_WORD_SECONDARY + + ", " + MESSAGE_WORD_TERTIARY_ALIAS + " for " + MESSAGE_WORD_TERTIARY + + ", " + MESSAGE_WORD_UNIVERSITY_ALIAS + " for " + MESSAGE_WORD_UNIVERSITY + ".\n" + + "Examples of valid input for grade: " + MESSAGE_WORD_KINDERGARTEN + "1" + + " or " + MESSAGE_WORD_KINDERGARTEN_ALIAS + "1" + ", " + + MESSAGE_WORD_TERTIARY + "2" + + " or " + MESSAGE_WORD_TERTIARY_ALIAS + "2.\n" + + "multiple grades should be typed with a single space between them " + + "in decreasing order of preferences with no repetitions"; + + private static final int levelIndex = 0; + private static final int yearIndex = 1; + + public final String value; + + public final int valueWeightage; //Stores the int value weightage of only the first grade in the list + + /** + * Constructs an {@code Grade}. + * + * @param grade A valid grade. + */ + public Grade(String grade) { + requireNonNull(grade); + checkArgument(isValidGrade(grade), MESSAGE_GRADE_CONSTRAINTS); + this.value = grade.trim().replaceAll(" +", " "); + this.valueWeightage = getGradeIndex(this.value); + } + + /** + * @return an int value base on the grade or the first grade in a string of multiple grades + */ + public static int getGradeIndex(String value) { + final String levelField = getGradeFields(value)[levelIndex].toLowerCase(); + + int tempIndex = 0; + + switch (levelField) { + case KINDERGARTEN_REGEX: + tempIndex += 1; + break; + + case KINDERGARTEN_ALIAS_REGEX: + tempIndex += 1; + break; + + case PRIMARY_REGEX: + tempIndex += 4; + break; + + case PRIMARY_ALIAS_REGEX: + tempIndex += 4; + break; + + case SECONDARY_REGEX: + tempIndex += 10; + break; + + case SECONDARY_ALIAS_REGEX: + tempIndex += 10; + break; + + case TERTIARY_REGEX: + tempIndex += 15; + break; + + case TERTIARY_ALIAS_REGEX: + tempIndex += 15; + break; + + case UNIVERSITY_REGEX: + tempIndex += 17; + break; + + case UNIVERSITY_ALIAS_REGEX: + tempIndex += 17; + break; + + default: + throw new AssertionError("It should not be possible to reach here"); + } + + tempIndex += (Integer.parseInt(getGradeFields(value)[yearIndex]) - 1); + + return tempIndex; + } + + /** + * Returns true if a given string is a valid client Grade. + */ + public static boolean isValidGradeRegex(String test) { + return test.matches(GRADE_VALIDATION_REGEX_KINDERGARTEN_ALIAS) + || test.matches(GRADE_VALIDATION_REGEX_KINDERGARTEN_FULL) + || test.matches(GRADE_VALIDATION_REGEX_PRIMARY_ALIAS) + || test.matches(GRADE_VALIDATION_REGEX_PRIMARY_FULL) + || test.matches(GRADE_VALIDATION_REGEX_SECONDARY_ALIAS) + || test.matches(GRADE_VALIDATION_REGEX_SECONDARY_FULL) + || test.matches(GRADE_VALIDATION_REGEX_TERTIARY_ALIAS) + || test.matches(GRADE_VALIDATION_REGEX_TERTIARY_FULL) + || test.matches(GRADE_VALIDATION_REGEX_UNIVERSITY_ALIAS) + || test.matches(GRADE_VALIDATION_REGEX_UNIVERSITY_FULL); + + } + + /** + * Returns true if all of the given string is a valid client Grade. + */ + public static boolean isValidGrade(String test) { + if (test.matches("\\s+")) { + return false; + } + String[] splitGrade = test.split("\\s+"); + Set isUnique = new HashSet<>(); + Set isUniqueWeightage = new HashSet<>(); + + boolean isValid = true; + for (String ss : splitGrade) { + if (isValid) { + isValid = isValidGradeRegex(ss); + isUnique.add(ss); + } + + if (isValid) { + isUniqueWeightage.add(getGradeIndex(ss)); + } + } + if (isUnique.size() != splitGrade.length || isUniqueWeightage.size() != splitGrade.length) { + isValid = false; + } + return isValid; + } + + /** + * @return gradeFields of only the first Grade in the string in terms of an array containing + * Level(Primary,Secondary..) and Year(1,2... + */ + private static String[] getGradeFields(String value) { + String[] allGrades = value.split("\\s+"); + String[] gradeFields = allGrades[0].split("(?=[\\d])"); + + checkArgument(gradeFields.length == 2, "Error in grade fields format."); + + String temp = gradeFields[levelIndex]; + gradeFields[levelIndex] = temp.trim(); + gradeFields[yearIndex].trim(); + + return gradeFields; + } + + /** + * @return an array containing all the grade weightage of the individual grades + */ + public static int[] getAllGradeWeightage(String value) { + String[] allGrades = value.split("\\s+"); + int[] allGradeWeightage = new int[allGrades.length]; + + for (int i = 0; i < allGrades.length; i++) { + allGradeWeightage[i] = getGradeIndex(allGrades[i]); + } + return allGradeWeightage; + } + + @Override + public String toString() { + return value; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Grade // instanceof handles nulls + && this.value.equals(((Grade) other).value)); // state check + } + + @Override + public int hashCode() { + return value.hashCode(); + } +} diff --git a/src/main/java/seedu/address/model/person/Location.java b/src/main/java/seedu/address/model/person/Location.java new file mode 100644 index 000000000000..fe7aa509f3ae --- /dev/null +++ b/src/main/java/seedu/address/model/person/Location.java @@ -0,0 +1,88 @@ +package seedu.address.model.person; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +import java.util.HashSet; +import java.util.Set; + +//@@author shookshire +/** + * Represents a Person's available Location in the TuitionCor. + * Guarantees: immutable; is valid as declared in {@link #isValidLocation(String)} + */ +public class Location { + + public static final String MESSAGE_LOCATION_CONSTRAINTS = + "Location should only be north, south, east, west and central in decreasing order of preference without " + + "any repetitions"; + + private static final String LOCATION_VALIDATION_REGEX_NORTH = "(?i)North"; + private static final String LOCATION_VALIDATION_REGEX_SOUTH = "(?i)South"; + private static final String LOCATION_VALIDATION_REGEX_EAST = "(?i)East"; + private static final String LOCATION_VALIDATION_REGEX_WEST = "(?i)West"; + private static final String LOCATION_VALIDATION_REGEX_CENTRAL = "(?i)Central"; + + public final String value; + + /** + * Constructs a {@code Location}. + * + * @param location A valid location. + */ + public Location(String location) { + requireNonNull(location); + checkArgument(isValidLocation(location), MESSAGE_LOCATION_CONSTRAINTS); + this.value = location.trim().replaceAll(" +", " "); + } + + /** + * Returns true if a given string is a valid client Location. + */ + public static boolean isValidLocationRegex(String test) { + return test.matches(LOCATION_VALIDATION_REGEX_NORTH) || test.matches(LOCATION_VALIDATION_REGEX_EAST) + || test.matches(LOCATION_VALIDATION_REGEX_SOUTH) + || test.matches(LOCATION_VALIDATION_REGEX_WEST) + || test.matches(LOCATION_VALIDATION_REGEX_CENTRAL); + } + + /** + * Returns true if all of the given string is a valid client Location. + */ + public static boolean isValidLocation(String test) { + if (test.matches("\\s+")) { + return false; + } + String[] splitLocation = test.split("\\s+"); + Set isUnique = new HashSet<>(); + boolean isValid = true; + for (String ss : splitLocation) { + if (isValid) { + isValid = isValidLocationRegex(ss); + isUnique.add(ss); + } + } + if (isUnique.size() != splitLocation.length) { + isValid = false; + } + return isValid; + } + + @Override + public String toString() { + return value; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Location // instanceof handles nulls + && this.value.equals(((Location) other).value)); // state check + } + + @Override + public int hashCode() { + return value.hashCode(); + } + +} diff --git a/src/main/java/seedu/address/model/person/MatchContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/person/MatchContainsKeywordsPredicate.java new file mode 100644 index 000000000000..bb41397adcbf --- /dev/null +++ b/src/main/java/seedu/address/model/person/MatchContainsKeywordsPredicate.java @@ -0,0 +1,55 @@ +package seedu.address.model.person; + +import java.util.function.Predicate; + +import seedu.address.commons.util.StringUtil; +import seedu.address.logic.commands.util.GradeUtil; + + +//@@author Zhu-Jiahui +/** + * Tests that a {@code Client}'s {@code Location, Grade and Subject} matches the entered {@code Client}'s + * {@code Location, Grade and Subject}. + */ +public class MatchContainsKeywordsPredicate implements Predicate { + private final Client client; + + public MatchContainsKeywordsPredicate(Client client) { + this.client = client; + } + + @Override + public boolean test(Client other) { + boolean isMatch = false; + int rank = 0; + + if (StringUtil.containsWordIgnoreCase(other.getLocation().toString(), client.getLocation().toString())) { + isMatch = true; + other.setMatchedLocation(isMatch); + rank++; + } + if (GradeUtil.containsGradeIgnoreCase(other.getGrade().value, client.getGrade().toString() + .split("\\s+")[0])) { + isMatch = true; + other.setMatchedGrade(isMatch); + rank++; + } + if (StringUtil.containsWordIgnoreCase(other.getSubject().value, client.getSubject().toString() + .split("\\s+")[0])) { + isMatch = true; + other.setMatchedSubject(isMatch); + rank++; + } + other.setRank(rank); + return isMatch; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof MatchContainsKeywordsPredicate // instanceof handles nulls + && this.client.equals(((MatchContainsKeywordsPredicate) other).client)); // state check + } + +} +//@@author diff --git a/src/main/java/seedu/address/model/person/MatchContainsPersonsPredicate.java b/src/main/java/seedu/address/model/person/MatchContainsPersonsPredicate.java new file mode 100644 index 000000000000..cbdd435f68fd --- /dev/null +++ b/src/main/java/seedu/address/model/person/MatchContainsPersonsPredicate.java @@ -0,0 +1,29 @@ +package seedu.address.model.person; + +import java.util.function.Predicate; + +//@@author Zhu-Jiahui +/** + * Tests that a {@code Client}'s attributes matches all of the attributes of the entered {@code Client}'s. + */ +public class MatchContainsPersonsPredicate implements Predicate { + private final Client client; + + public MatchContainsPersonsPredicate(Client client) { + this.client = client; + } + + @Override + public boolean test(Client other) { + return other.toString().equals(client.toString()); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof MatchContainsPersonsPredicate // instanceof handles nulls + && this.client.equals(((MatchContainsPersonsPredicate) other).client)); // state check + } + +} +//@@author diff --git a/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java deleted file mode 100644 index 827e2cc106bd..000000000000 --- a/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java +++ /dev/null @@ -1,31 +0,0 @@ -package seedu.address.model.person; - -import java.util.List; -import java.util.function.Predicate; - -import seedu.address.commons.util.StringUtil; - -/** - * Tests that a {@code Person}'s {@code Name} matches any of the keywords given. - */ -public class NameContainsKeywordsPredicate implements Predicate { - private final List keywords; - - public NameContainsKeywordsPredicate(List keywords) { - this.keywords = keywords; - } - - @Override - public boolean test(Person person) { - return keywords.stream() - .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(person.getName().fullName, keyword)); - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof NameContainsKeywordsPredicate // instanceof handles nulls - && this.keywords.equals(((NameContainsKeywordsPredicate) other).keywords)); // state check - } - -} diff --git a/src/main/java/seedu/address/model/person/SearchContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/person/SearchContainsKeywordsPredicate.java new file mode 100644 index 000000000000..c12044f2bcb2 --- /dev/null +++ b/src/main/java/seedu/address/model/person/SearchContainsKeywordsPredicate.java @@ -0,0 +1,48 @@ +package seedu.address.model.person; + +import java.util.List; +import java.util.function.Predicate; + +import seedu.address.commons.util.StringUtil; +import seedu.address.logic.commands.util.GradeUtil; + +/** + * Tests that a {@code Client}'s attributes matches any of the keywords given. + */ +public class SearchContainsKeywordsPredicate implements Predicate { + private final List keywords; + + public SearchContainsKeywordsPredicate(List keywords) { + this.keywords = keywords; + } + + //@@author Zhu-Jiahui + @Override + public boolean test(Client client) { + return keywords.stream() + .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(client.getName().fullName, keyword)) + || keywords.stream() + .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(client.getEmail().value, keyword)) + || keywords.stream() + .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(client.getAddress().value, keyword)) + || keywords.stream() + .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(client.getPhone().value, keyword)) + || keywords.stream() + .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(client.getLocation().value, keyword)) + || keywords.stream() + .anyMatch(keyword -> GradeUtil.containsGradeIgnoreCase(client.getGrade().value, keyword)) + || keywords.stream() + .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(client.getSubject().value, keyword)) + || keywords.stream() + .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(client.getCategory().value, keyword)); + } + //@@author + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof SearchContainsKeywordsPredicate // instanceof handles nulls + && this.keywords.equals(((SearchContainsKeywordsPredicate) other).keywords)); // state check + } + +} diff --git a/src/main/java/seedu/address/model/person/Subject.java b/src/main/java/seedu/address/model/person/Subject.java new file mode 100644 index 000000000000..ce7ce4a06fe8 --- /dev/null +++ b/src/main/java/seedu/address/model/person/Subject.java @@ -0,0 +1,58 @@ +package seedu.address.model.person; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +//@@author shookshire +/** + * Represents a Person's related Subject in the TuitionCor. + * Guarantees: immutable; is valid as declared in {@link #isValidSubject(String)} + */ +public class Subject { + + public static final String MESSAGE_SUBJECT_CONSTRAINTS = + "Subjects can take any value and should not be blank"; + + /* + * The first character of the Subject must not be a whitespace, + * otherwise " " (a blank string) becomes a valid input. + */ + public static final String ADDRESS_VALIDATION_REGEX = "[^\\s].*"; + + public final String value; + + /** + * Constructs an {@code Subject}. + * + * @param subject A valid subject. + */ + public Subject(String subject) { + requireNonNull(subject); + checkArgument(isValidSubject(subject), MESSAGE_SUBJECT_CONSTRAINTS); + this.value = subject.trim().replaceAll(" +", " "); + } + + /** + * Returns true if a given string is a valid person Subject. + */ + public static boolean isValidSubject(String test) { + return test.matches(ADDRESS_VALIDATION_REGEX); + } + + @Override + public String toString() { + return value; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Subject // instanceof handles nulls + && this.value.equals(((Subject) other).value)); // state check + } + + @Override + public int hashCode() { + return value.hashCode(); + } +} diff --git a/src/main/java/seedu/address/model/person/UniqueClientList.java b/src/main/java/seedu/address/model/person/UniqueClientList.java new file mode 100644 index 000000000000..0324c912e0fa --- /dev/null +++ b/src/main/java/seedu/address/model/person/UniqueClientList.java @@ -0,0 +1,139 @@ +package seedu.address.model.person; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; + +import java.util.Iterator; +import java.util.List; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import seedu.address.commons.util.CollectionUtil; +import seedu.address.model.person.exceptions.DuplicatePersonException; +import seedu.address.model.person.exceptions.PersonNotFoundException; + +//@@author shookshire +/** + * A list of client that enforces uniqueness between its elements and does not allow nulls. + * + * Supports a minimal set of list operations. + * + * @see Client#equals(Object) + * @see CollectionUtil#elementsAreUnique(Collection) + */ +public class UniqueClientList implements Iterable { + + private final ObservableList internalList = FXCollections.observableArrayList(); + + /** + * Returns true if the list contains an equivalent client as the given argument. + */ + public boolean contains(Client toCheck) { + requireNonNull(toCheck); + return internalList.contains(toCheck); + } + + /** + * Adds a client to the active list. + * + * @throws DuplicatePersonException if the client to add is a duplicate of an existing client in the list. + */ + public void add(Client toAdd, UniqueClientList closedList) throws DuplicatePersonException { + requireNonNull(toAdd); + if (contains(toAdd) || closedList.contains(toAdd)) { + throw new DuplicatePersonException(); + } + internalList.add(toAdd); + } + + /** + * Adds a client to TuitionCor. + * + * @throws AssertionError as it's impossible to have a duplicate given that we have checked for duplication + * before adding it into active list. + */ + public void add(Client toAdd) throws AssertionError { + requireNonNull(toAdd); + if (contains(toAdd)) { + throw new AssertionError("It's impossible to have a duplicate person here"); + } + internalList.add(toAdd); + } + + /** + * Replaces the client {@code target} in the list with {@code editedClient}. + * + * @throws DuplicatePersonException if the replacement is equivalent to another existing client in the list. + * @throws PersonNotFoundException if {@code target} could not be found in the list. + * + * Returns true if success. + */ + public Boolean setClient(Client target, Client editedClient, UniqueClientList closedList) + throws DuplicatePersonException, PersonNotFoundException { + requireNonNull(editedClient); + + int index = internalList.indexOf(target); + if (index == -1) { + throw new PersonNotFoundException(); + } + + if (!target.equals(editedClient) && (internalList.contains(editedClient) + || closedList.contains(editedClient))) { + throw new DuplicatePersonException(); + } + + internalList.set(index, editedClient); + return true; + } + + /** + * Removes the equivalent person from the list. + * + * @throws PersonNotFoundException if no such person could be found in the list. + */ + public boolean remove(Client toRemove) throws PersonNotFoundException { + requireNonNull(toRemove); + final boolean clientFoundAndDeleted = internalList.remove(toRemove); + if (!clientFoundAndDeleted) { + throw new PersonNotFoundException(); + } + return clientFoundAndDeleted; + } + + public void setClients(UniqueClientList replacement) { + this.internalList.setAll(replacement.internalList); + } + + public void setClients(List clients) throws DuplicatePersonException { + requireAllNonNull(clients); + final UniqueClientList replacement = new UniqueClientList(); + for (final Client client : clients) { + replacement.add(client); + } + setClients(replacement); + } + + /** + * Returns the backing list as an unmodifiable {@code ObservableList}. + */ + public ObservableList asObservableList() { + return FXCollections.unmodifiableObservableList(internalList); + } + + @Override + public Iterator iterator() { + return internalList.iterator(); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof UniqueClientList // instanceof handles nulls + && this.internalList.equals(((UniqueClientList) other).internalList)); + } + + @Override + public int hashCode() { + return internalList.hashCode(); + } +} diff --git a/src/main/java/seedu/address/model/person/exceptions/DuplicatePersonException.java b/src/main/java/seedu/address/model/person/exceptions/DuplicatePersonException.java index fce401885dc8..3d7982c51907 100644 --- a/src/main/java/seedu/address/model/person/exceptions/DuplicatePersonException.java +++ b/src/main/java/seedu/address/model/person/exceptions/DuplicatePersonException.java @@ -7,6 +7,6 @@ */ public class DuplicatePersonException extends DuplicateDataException { public DuplicatePersonException() { - super("Operation would result in duplicate persons"); + super("Operation would result in duplicate clients"); } } diff --git a/src/main/java/seedu/address/model/tag/Tag.java b/src/main/java/seedu/address/model/tag/Tag.java index 65bdd769995d..1c339894ced9 100644 --- a/src/main/java/seedu/address/model/tag/Tag.java +++ b/src/main/java/seedu/address/model/tag/Tag.java @@ -9,7 +9,7 @@ */ public class Tag { - public static final String MESSAGE_TAG_CONSTRAINTS = "Tags names should be alphanumeric"; + public static final String MESSAGE_TAG_CONSTRAINTS = "Tags names should be alphanumeric without space"; public static final String TAG_VALIDATION_REGEX = "\\p{Alnum}+"; public final String tagName; diff --git a/src/main/java/seedu/address/model/util/SampleDataUtil.java b/src/main/java/seedu/address/model/util/SampleDataUtil.java index aea96bfb31f3..4e1c4ceb9ca8 100644 --- a/src/main/java/seedu/address/model/util/SampleDataUtil.java +++ b/src/main/java/seedu/address/model/util/SampleDataUtil.java @@ -6,10 +6,14 @@ import seedu.address.model.AddressBook; import seedu.address.model.ReadOnlyAddressBook; import seedu.address.model.person.Address; +import seedu.address.model.person.Category; +import seedu.address.model.person.Client; import seedu.address.model.person.Email; +import seedu.address.model.person.Grade; +import seedu.address.model.person.Location; import seedu.address.model.person.Name; -import seedu.address.model.person.Person; import seedu.address.model.person.Phone; +import seedu.address.model.person.Subject; import seedu.address.model.person.exceptions.DuplicatePersonException; import seedu.address.model.tag.Tag; @@ -17,34 +21,100 @@ * Contains utility methods for populating {@code AddressBook} with sample data. */ public class SampleDataUtil { - public static Person[] getSamplePersons() { - return new Person[] { - new Person(new Name("Alex Yeoh"), new Phone("87438807"), new Email("alexyeoh@example.com"), + public static Client[] getSampleStudents() { + return new Client[] { + new Client(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"), + getTagSet("FemaleTutorsOnly"), new Location("North"), new Grade("p2"), new Subject("math"), + new Category("s")), + new Client(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"), + getTagSet("Urgent", "WillingToPay"), new Location("Central"), new Grade("s1"), + new Subject("Physics"), new Category("s")), + new Client(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"), + getTagSet("Urgent"), new Location("North"), new Grade("p2"), + new Subject("Physics Chinese"), new Category("s")), + new Client(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"), + getTagSet("ExtraCareRequired"), new Location("Central"), new Grade("s1"), + new Subject("Chemistry Science"), new Category("s")), + new Client(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"), + getTagSet("Smart"), new Location("North"), new Grade("p2"), new Subject("Math"), + new Category("s")), + new Client(new Name("Roy Balakrishnan"), new Phone("92624417"), new Email("royb@example.com"), new Address("Blk 45 Aljunied Street 85, #11-31"), - getTagSet("colleagues")) + getTagSet("Urgent"), new Location("Central"), new Grade("s1"), new Subject("Physics"), + new Category("s")), + }; + } + + public static Client[] getSampleTutors() { + return new Client[]{ + new Client(new Name("Johnny"), new Phone("87438873"), new Email("Johnnyenglish@example.com"), + new Address("Blk 30 London Street, #06-40"), + getTagSet("FemaleTutorsOnly"), new Location("North"), new Grade("p2"), new Subject("math"), + new Category("s")), + new Client(new Name("James"), new Phone("3213283"), new Email("james@example.com"), + new Address("Blk 111 James Avenue"), + getTagSet("Friendly"), new Location("North South"), new Grade("s1 s2 s3 s4"), + new Subject("Math Science English SocialStudies Geography"), new Category("t")), + new Client(new Name("George"), new Phone("66316282"), new Email("george@example.com"), + new Address("Blk 436 George Town Street 26, #16-43"), + getTagSet("GoodReputation"), new Location("Central"), new Grade("k1 k2"), + new Subject("Chinese"), new Category("t")), + new Client(new Name("Jennifer"), new Phone("66632521"), new Email("jeniferrrrrrrr@example.com"), + new Address("Blk 47 Janifer Street 20, #13-35"), + getTagSet("Scholar"), new Location("South"), new Grade("p1 p2 p3"), + new Subject("Math"), new Category("t")), + new Client(new Name("Nancy"), new Phone("666454417"), new Email("nancy@example.com"), + new Address("Blk 999 Queenstown 85, #01-31"), + getTagSet("Scholar"), new Location("Central"), new Grade("u1 u2"), + new Subject("Physics"), new Category("t")) + }; + } + + public static Client[] getSampleClosedTutors() { + return new Client[]{ + new Client(new Name("Cindy"), new Phone("81234123"), new Email("Cindyee@example.com"), + new Address("Blk 111 Cindy Avenue"), + getTagSet("Inactive"), new Location("North South"), new Grade("s1 s2 s3 s4"), + new Subject("Math Science English"), new Category("t")), + new Client(new Name("Galvin"), new Phone("94443283"), new Email("Galvin@example.com"), + new Address("Blk 1 Galvin Avenue"), + getTagSet("Assigned"), new Location("South"), new Grade("u1"), + new Subject("Geography"), new Category("t")) + }; + } + + public static Client[] getSampleClosedStudents() { + return new Client[]{ + new Client(new Name("Tom"), new Phone("8134123"), new Email("Tom@example.com"), + new Address("Blk 121 Jerry Avenue"), + getTagSet("Assigned"), new Location("South"), new Grade("s1"), + new Subject("English"), new Category("s")), + new Client(new Name("Jerry"), new Phone("94449983"), new Email("Jerry@example.com"), + new Address("Blk 1 Tom Avenue"), + getTagSet("Assigned"), new Location("East"), new Grade("k1"), + new Subject("English"), new Category("s")) }; } public static ReadOnlyAddressBook getSampleAddressBook() { try { AddressBook sampleAb = new AddressBook(); - for (Person samplePerson : getSamplePersons()) { - sampleAb.addPerson(samplePerson); + for (Client sampleStudent : getSampleStudents()) { + sampleAb.addClient(sampleStudent); + } + for (Client sampleTutor : getSampleTutors()) { + sampleAb.addClient(sampleTutor); + } + for (Client sampleClosedStudent : getSampleClosedStudents()) { + sampleAb.addClosedClient(sampleClosedStudent); + } + for (Client sampleClosedTutor : getSampleClosedTutors()) { + sampleAb.addClosedTutor(sampleClosedTutor); } return sampleAb; } catch (DuplicatePersonException e) { diff --git a/src/main/java/seedu/address/storage/AddressBookStorage.java b/src/main/java/seedu/address/storage/AddressBookStorage.java index cf5b527c063a..08cdbbe6b0e2 100644 --- a/src/main/java/seedu/address/storage/AddressBookStorage.java +++ b/src/main/java/seedu/address/storage/AddressBookStorage.java @@ -41,4 +41,5 @@ public interface AddressBookStorage { */ void saveAddressBook(ReadOnlyAddressBook addressBook, String filePath) throws IOException; + void backupAddressBook(ReadOnlyAddressBook addressBook) throws IOException; } diff --git a/src/main/java/seedu/address/storage/StorageManager.java b/src/main/java/seedu/address/storage/StorageManager.java index 53967b391a5a..7a17725b0433 100644 --- a/src/main/java/seedu/address/storage/StorageManager.java +++ b/src/main/java/seedu/address/storage/StorageManager.java @@ -77,6 +77,10 @@ public void saveAddressBook(ReadOnlyAddressBook addressBook, String filePath) th addressBookStorage.saveAddressBook(addressBook, filePath); } + @Override + public void backupAddressBook(ReadOnlyAddressBook addressBook) throws IOException { + addressBookStorage.backupAddressBook(addressBook); + } @Override @Subscribe diff --git a/src/main/java/seedu/address/storage/XmlAdaptedClient.java b/src/main/java/seedu/address/storage/XmlAdaptedClient.java new file mode 100644 index 000000000000..9ab0a01c02b9 --- /dev/null +++ b/src/main/java/seedu/address/storage/XmlAdaptedClient.java @@ -0,0 +1,201 @@ +package seedu.address.storage; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; + +import javax.xml.bind.annotation.XmlElement; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.person.Address; +import seedu.address.model.person.Category; +import seedu.address.model.person.Client; +import seedu.address.model.person.Email; +import seedu.address.model.person.Grade; +import seedu.address.model.person.Location; +import seedu.address.model.person.Name; +import seedu.address.model.person.Phone; +import seedu.address.model.person.Subject; +import seedu.address.model.tag.Tag; + +//@@author shookshire +/** + * JAXB-friendly version of the Client for tutors. + */ +public class XmlAdaptedClient { + + public static final String MISSING_FIELD_MESSAGE_FORMAT = "Person's %s field is missing!"; + + @XmlElement(required = true) + private String name; + @XmlElement(required = true) + private String phone; + @XmlElement(required = true) + private String email; + @XmlElement(required = true) + private String address; + @XmlElement(required = true) + private String location; + @XmlElement(required = true) + private String grade; + @XmlElement(required = true) + private String subject; + @XmlElement(required = true) + private String category; + + + @XmlElement + private List tagged = new ArrayList<>(); + + /** + * Constructs an XmlAdaptedClient. + * This is the no-arg constructor that is required by JAXB. + */ + public XmlAdaptedClient() {} + + /** + * Constructs an {@code XmlAdaptedClient} with the given person details. + */ + public XmlAdaptedClient(String name, String phone, String email, String address, List tagged, + String location, String grade, String subject, String category) { + this.name = name; + this.phone = phone; + this.email = email; + this.address = address; + if (tagged != null) { + this.tagged = new ArrayList<>(tagged); + } + this.location = location; + this.grade = grade; + this.subject = subject; + this.category = category; + } + + /** + * Converts a given Client into this class for JAXB use. + * + * @param source future changes to this will not affect the created XmlAdaptedClient + */ + public XmlAdaptedClient(Client source) { + name = source.getName().fullName; + phone = source.getPhone().value; + email = source.getEmail().value; + address = source.getAddress().value; + tagged = new ArrayList<>(); + for (Tag tag : source.getTags()) { + tagged.add(new XmlAdaptedTag(tag)); + } + location = source.getLocation().value; + grade = source.getGrade().value; + subject = source.getSubject().value; + category = source.getCategory().value; + } + + /** + * Converts this jaxb-friendly adapted client object into the model's Client object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted person + */ + public Client toModelType() throws IllegalValueException { + final List personTags = new ArrayList<>(); + for (XmlAdaptedTag tag : tagged) { + personTags.add(tag.toModelType()); + } + + if (this.name == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Name.class.getSimpleName())); + } + if (!Name.isValidName(this.name)) { + throw new IllegalValueException(Name.MESSAGE_NAME_CONSTRAINTS); + } + final Name name = new Name(this.name); + + if (this.phone == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Phone.class.getSimpleName())); + } + if (!Phone.isValidPhone(this.phone)) { + throw new IllegalValueException(Phone.MESSAGE_PHONE_CONSTRAINTS); + } + final Phone phone = new Phone(this.phone); + + if (this.email == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Email.class.getSimpleName())); + } + if (!Email.isValidEmail(this.email)) { + throw new IllegalValueException(Email.MESSAGE_EMAIL_CONSTRAINTS); + } + final Email email = new Email(this.email); + + if (this.address == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Address.class.getSimpleName())); + } + if (!Address.isValidAddress(this.address)) { + throw new IllegalValueException(Address.MESSAGE_ADDRESS_CONSTRAINTS); + } + final Address address = new Address(this.address); + + final Set tags = new HashSet<>(personTags); + + if (this.location == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + Location.class.getSimpleName())); + } + if (!Location.isValidLocation(this.location)) { + throw new IllegalValueException(Location.MESSAGE_LOCATION_CONSTRAINTS); + } + final Location location = new Location(this.location); + + if (this.grade == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Grade.class.getSimpleName())); + } + if (!Grade.isValidGrade(this.grade)) { + throw new IllegalValueException(Grade.MESSAGE_GRADE_CONSTRAINTS); + } + final Grade grade = new Grade(this.grade); + + if (this.subject == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Subject.class.getSimpleName())); + } + if (!Subject.isValidSubject(this.subject)) { + throw new IllegalValueException(Subject.MESSAGE_SUBJECT_CONSTRAINTS); + } + final Subject subject = new Subject(this.subject); + + if (this.category == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + Category.class.getSimpleName())); + } + if (!Category.isValidCategory(this.category)) { + throw new IllegalValueException(Subject.MESSAGE_SUBJECT_CONSTRAINTS); + } + final Category category = new Category(this.category); + + + return new Client(name, phone, email, address, tags, location, grade, subject, category); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof XmlAdaptedClient)) { + return false; + } + + XmlAdaptedClient otherClient = (XmlAdaptedClient) other; + return Objects.equals(name, otherClient.name) + && Objects.equals(phone, otherClient.phone) + && Objects.equals(email, otherClient.email) + && Objects.equals(address, otherClient.address) + && tagged.equals(otherClient.tagged) + && Objects.equals(location, otherClient.location) + && Objects.equals(subject, otherClient.subject) + && Objects.equals(grade, otherClient.grade) + && Objects.equals(category, otherClient.category); + + } +} diff --git a/src/main/java/seedu/address/storage/XmlAddressBookStorage.java b/src/main/java/seedu/address/storage/XmlAddressBookStorage.java index c77ebe67435c..1e3824cc47d2 100644 --- a/src/main/java/seedu/address/storage/XmlAddressBookStorage.java +++ b/src/main/java/seedu/address/storage/XmlAddressBookStorage.java @@ -79,4 +79,8 @@ public void saveAddressBook(ReadOnlyAddressBook addressBook, String filePath) th XmlFileStorage.saveDataToFile(file, new XmlSerializableAddressBook(addressBook)); } + @Override + public void backupAddressBook(ReadOnlyAddressBook addressBook) throws IOException { + saveAddressBook(addressBook, filePath + ".backup"); + } } diff --git a/src/main/java/seedu/address/storage/XmlSerializableAddressBook.java b/src/main/java/seedu/address/storage/XmlSerializableAddressBook.java index dc820896c312..321b91a7bfee 100644 --- a/src/main/java/seedu/address/storage/XmlSerializableAddressBook.java +++ b/src/main/java/seedu/address/storage/XmlSerializableAddressBook.java @@ -18,7 +18,13 @@ public class XmlSerializableAddressBook { @XmlElement - private List persons; + private List students; + @XmlElement + private List tutors; + @XmlElement + private List closedStudents; + @XmlElement + private List closedTutors; @XmlElement private List tags; @@ -27,7 +33,10 @@ public class XmlSerializableAddressBook { * This empty constructor is required for marshalling. */ public XmlSerializableAddressBook() { - persons = new ArrayList<>(); + tutors = new ArrayList<>(); + students = new ArrayList<>(); + closedStudents = new ArrayList<>(); + closedTutors = new ArrayList<>(); tags = new ArrayList<>(); } @@ -36,8 +45,16 @@ public XmlSerializableAddressBook() { */ public XmlSerializableAddressBook(ReadOnlyAddressBook src) { this(); - persons.addAll(src.getPersonList().stream().map(XmlAdaptedPerson::new).collect(Collectors.toList())); - tags.addAll(src.getTagList().stream().map(XmlAdaptedTag::new).collect(Collectors.toList())); + students.addAll(src.getStudentList().stream().map(XmlAdaptedClient::new) + .collect(Collectors.toList())); + tutors.addAll(src.getTutorList().stream().map(XmlAdaptedClient::new) + .collect(Collectors.toList())); + closedStudents.addAll(src.getClosedStudentList().stream().map(XmlAdaptedClient::new) + .collect(Collectors.toList())); + closedTutors.addAll(src.getClosedTutorList().stream().map(XmlAdaptedClient::new) + .collect(Collectors.toList())); + tags.addAll(src.getTagList().stream().map(XmlAdaptedTag::new) + .collect(Collectors.toList())); } /** @@ -51,8 +68,17 @@ public AddressBook toModelType() throws IllegalValueException { for (XmlAdaptedTag t : tags) { addressBook.addTag(t.toModelType()); } - for (XmlAdaptedPerson p : persons) { - addressBook.addPerson(p.toModelType()); + for (XmlAdaptedClient s : students) { + addressBook.addStudent(s.toModelType()); + } + for (XmlAdaptedClient t : tutors) { + addressBook.addTutor(t.toModelType()); + } + for (XmlAdaptedClient cs : closedStudents) { + addressBook.addClosedStudent(cs.toModelType()); + } + for (XmlAdaptedClient ct : closedTutors) { + addressBook.addClosedTutor(ct.toModelType()); } return addressBook; } @@ -68,6 +94,6 @@ public boolean equals(Object other) { } XmlSerializableAddressBook otherAb = (XmlSerializableAddressBook) other; - return persons.equals(otherAb.persons) && tags.equals(otherAb.tags); + return students.equals(otherAb.students) && tutors.equals(otherAb.tutors) && tags.equals(otherAb.tags); } } diff --git a/src/main/java/seedu/address/ui/BrowserPanel.java b/src/main/java/seedu/address/ui/BrowserPanel.java deleted file mode 100644 index bb0d61380d5a..000000000000 --- a/src/main/java/seedu/address/ui/BrowserPanel.java +++ /dev/null @@ -1,72 +0,0 @@ -package seedu.address.ui; - -import java.net.URL; -import java.util.logging.Logger; - -import com.google.common.eventbus.Subscribe; - -import javafx.application.Platform; -import javafx.event.Event; -import javafx.fxml.FXML; -import javafx.scene.layout.Region; -import javafx.scene.web.WebView; -import seedu.address.MainApp; -import seedu.address.commons.core.LogsCenter; -import seedu.address.commons.events.ui.PersonPanelSelectionChangedEvent; -import seedu.address.model.person.Person; - -/** - * The Browser Panel of the App. - */ -public class BrowserPanel extends UiPart { - - public static final String DEFAULT_PAGE = "default.html"; - public static final String SEARCH_PAGE_URL = - "https://se-edu.github.io/addressbook-level4/DummySearchPage.html?name="; - - private static final String FXML = "BrowserPanel.fxml"; - - private final Logger logger = LogsCenter.getLogger(this.getClass()); - - @FXML - private WebView browser; - - public BrowserPanel() { - super(FXML); - - // To prevent triggering events for typing inside the loaded Web page. - getRoot().setOnKeyPressed(Event::consume); - - loadDefaultPage(); - registerAsAnEventHandler(this); - } - - private void loadPersonPage(Person person) { - loadPage(SEARCH_PAGE_URL + person.getName().fullName); - } - - public void loadPage(String url) { - Platform.runLater(() -> browser.getEngine().load(url)); - } - - /** - * Loads a default HTML file with a background that matches the general theme. - */ - private void loadDefaultPage() { - URL defaultPage = MainApp.class.getResource(FXML_FILE_FOLDER + DEFAULT_PAGE); - loadPage(defaultPage.toExternalForm()); - } - - /** - * Frees resources allocated to the browser. - */ - public void freeResources() { - browser = null; - } - - @Subscribe - private void handlePersonPanelSelectionChangedEvent(PersonPanelSelectionChangedEvent event) { - logger.info(LogsCenter.getEventHandlingLogMessage(event)); - loadPersonPage(event.getNewSelection().person); - } -} diff --git a/src/main/java/seedu/address/ui/ClientCard.java b/src/main/java/seedu/address/ui/ClientCard.java new file mode 100644 index 000000000000..8cfea51bbf5f --- /dev/null +++ b/src/main/java/seedu/address/ui/ClientCard.java @@ -0,0 +1,162 @@ +package seedu.address.ui; + +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.layout.FlowPane; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Region; +import seedu.address.model.person.Client; + +//@@author shookshire +/** + * An UI component that displays information of a {@code Client}. + */ +public class ClientCard extends UiPart { + + private static final String FXML = "ClientListCard.fxml"; + + private static final String[] TAGS_COLOUR_STYLES = + { "red" , "blue" , "green" , "yellow" , "purple" , "lightpink" , "gold" , "wheat" }; + + private static final String MATCH_COLOUR_STYLE = "orange"; + + private static final String UNMATCH_COLOUR_STYLE = "noFill"; + /** + * 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 Client client; + + @FXML + private HBox cardPane; + @FXML + private Label name; + @FXML + private Label id; + @FXML + private Label phone; + @FXML + private Label address; + @FXML + private Label email; + @FXML + private Label places; + @FXML + private Label grades; + @FXML + private Label subjects; + @FXML + private FlowPane tags; + + + public ClientCard(Client client, int displayedIndex) { + super(FXML); + this.client = client; + id.setText(displayedIndex + ". "); + name.setText(client.getName().fullName); + phone.setText(client.getPhone().value); + address.setText(client.getAddress().value); + email.setText(client.getEmail().value); + intplaces(client); + intGrades(client); + intSubjects(client); + intTags(client); + } + + /** + *@author olimhc-reused + *Reused from https://github.com/se-edu/addressbook-level4/pull/798/commits with minor modification + * Initialises tags + * @param client + */ + private void intTags(Client client) { + client.getTags().forEach(tag -> { + Label newLabel = new Label(tag.tagName); + newLabel.getStyleClass().add(getColour(tag.tagName)); + tags.getChildren().add(newLabel); + }); + } + + //@@author Zhu-Jiahui + /** + * Initialises Location + * If Location is matched with the client, Location field will be highlighted. + * @param client + */ + + private void intplaces(Client client) { + + places.setText(client.getLocation().value); + + if (client.getMatchedLocation() == true) { + places.getStyleClass().add(MATCH_COLOUR_STYLE); + } else { + places.getStyleClass().add(UNMATCH_COLOUR_STYLE); + } + } + + /** + * Initialises Grade + * If Grade is matched with the client, Grade field will be highlighted. + * @param client + */ + + private void intGrades(Client client) { + + grades.setText(client.getGrade().value); + + if (client.getMatchedGrade() == true) { + grades.getStyleClass().add(MATCH_COLOUR_STYLE); + } else { + grades.getStyleClass().add(UNMATCH_COLOUR_STYLE); + } + } + + /** + *@author Zhu-Jiahui + * Initialises Subject + * If Subject is matched with the client, Subject field will be highlighted. + * @param client + */ + + private void intSubjects(Client client) { + subjects.setText(client.getSubject().value); + + if (client.getMatchedSubject() == true) { + subjects.getStyleClass().add(MATCH_COLOUR_STYLE); + } else { + subjects.getStyleClass().add(UNMATCH_COLOUR_STYLE); + } + } + //@@author + + /** + * @param name + * @return String colour + */ + private String getColour(String name) { + return TAGS_COLOUR_STYLES[(Math.abs(name.hashCode() % TAGS_COLOUR_STYLES.length))]; + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof ClientCard)) { + return false; + } + + // state check + ClientCard card = (ClientCard) other; + return id.getText().equals(card.id.getText()) + && client.equals(card.client); + } +} diff --git a/src/main/java/seedu/address/ui/MainWindow.java b/src/main/java/seedu/address/ui/MainWindow.java index 20ad5fee906a..0ff097876afb 100644 --- a/src/main/java/seedu/address/ui/MainWindow.java +++ b/src/main/java/seedu/address/ui/MainWindow.java @@ -34,13 +34,13 @@ public class MainWindow extends UiPart { private Logic logic; // Independent Ui parts residing in this Ui container - private BrowserPanel browserPanel; - private PersonListPanel personListPanel; + private TutorListPanel tutorListPanel; + private StudentListPanel studentListPanel; private Config config; private UserPrefs prefs; @FXML - private StackPane browserPlaceholder; + private StackPane tutorListPanelPlaceholder; @FXML private StackPane commandBoxPlaceholder; @@ -49,7 +49,7 @@ public class MainWindow extends UiPart { private MenuItem helpMenuItem; @FXML - private StackPane personListPanelPlaceholder; + private StackPane studentListPanelPlaceholder; @FXML private StackPane resultDisplayPlaceholder; @@ -116,11 +116,11 @@ private void setAccelerator(MenuItem menuItem, KeyCombination keyCombination) { * Fills up all the placeholders of this window. */ void fillInnerParts() { - browserPanel = new BrowserPanel(); - browserPlaceholder.getChildren().add(browserPanel.getRoot()); + studentListPanel = new StudentListPanel(logic.getFilteredStudentList(), logic.getFilteredClosedStudentList()); + studentListPanelPlaceholder.getChildren().add(studentListPanel.getRoot()); - personListPanel = new PersonListPanel(logic.getFilteredPersonList()); - personListPanelPlaceholder.getChildren().add(personListPanel.getRoot()); + tutorListPanel = new TutorListPanel(logic.getFilteredTutorList(), logic.getFilteredClosedTutorList()); + tutorListPanelPlaceholder.getChildren().add(tutorListPanel.getRoot()); ResultDisplay resultDisplay = new ResultDisplay(); resultDisplayPlaceholder.getChildren().add(resultDisplay.getRoot()); @@ -181,12 +181,15 @@ private void handleExit() { raise(new ExitAppRequestEvent()); } - public PersonListPanel getPersonListPanel() { - return this.personListPanel; + public StudentListPanel getStudentListPanel() { + return this.studentListPanel; + } + + public TutorListPanel getTutorListPanel () { + return this.tutorListPanel; } void releaseResources() { - browserPanel.freeResources(); } @Subscribe diff --git a/src/main/java/seedu/address/ui/PersonCard.java b/src/main/java/seedu/address/ui/PersonCard.java index f6727ea83abd..5dd228b8ac18 100644 --- a/src/main/java/seedu/address/ui/PersonCard.java +++ b/src/main/java/seedu/address/ui/PersonCard.java @@ -14,6 +14,14 @@ public class PersonCard extends UiPart { private static final String FXML = "PersonListCard.fxml"; + private static final String[] TAGS_COLOUR_STYLES = {"red" , "blue" , "green" , "yellow" , "orange"}; + + private static final String DUMMY_GRADE_TEXT = "-"; + private static final String DUMMY_LOCALE_TEXT = "-"; + private static final String[] DUMMY_SUBJECTS_TEXT = {"-"}; + + private static final String URGENT_PATTERN_REGEX = "Urgent"; + /** * 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 @@ -29,25 +37,71 @@ public class PersonCard extends UiPart { @FXML private Label name; @FXML + private Label grade; + @FXML private Label id; @FXML private Label phone; @FXML + private Label locale; + @FXML private Label address; @FXML private Label email; @FXML private FlowPane tags; + @FXML + private FlowPane subjects; public PersonCard(Person person, int displayedIndex) { super(FXML); this.person = person; id.setText(displayedIndex + ". "); name.setText(person.getName().fullName); + grade.setText(DUMMY_GRADE_TEXT); phone.setText(person.getPhone().value); + locale.setText(DUMMY_LOCALE_TEXT); address.setText(person.getAddress().value); email.setText(person.getEmail().value); - person.getTags().forEach(tag -> tags.getChildren().add(new Label(tag.tagName))); + intTags(person); + intSubjects(person); + } + + /** + *@author olimhc-reused + *Reused from https://github.com/se-edu/addressbook-level4/pull/798/commits with minor modification + * Initialises tags + * @param person + */ + private void intTags(Person person) { + person.getTags().forEach(tag -> { + Label newLabel = new Label(tag.tagName); + newLabel.getStyleClass().add(getColour(tag.tagName)); + tags.getChildren().add(newLabel); + }); + } + + /** + * Initialises subjects + * @param person + */ + private void intSubjects(Person person) { + for (String s : DUMMY_SUBJECTS_TEXT) { + Label newLabel = new Label(s); + subjects.getChildren().add(newLabel); + } + } + + /** + * @param name + * @return String colour + */ + private String getColour(String name) { + if (name.equalsIgnoreCase(URGENT_PATTERN_REGEX)) { + return TAGS_COLOUR_STYLES[0]; + } else { + return TAGS_COLOUR_STYLES[(Math.abs(name.hashCode() % TAGS_COLOUR_STYLES.length))]; + } } @Override diff --git a/src/main/java/seedu/address/ui/PersonListPanel.java b/src/main/java/seedu/address/ui/PersonListPanel.java deleted file mode 100644 index 60a4f70f4e71..000000000000 --- a/src/main/java/seedu/address/ui/PersonListPanel.java +++ /dev/null @@ -1,88 +0,0 @@ -package seedu.address.ui; - -import java.util.logging.Logger; - -import org.fxmisc.easybind.EasyBind; - -import com.google.common.eventbus.Subscribe; - -import javafx.application.Platform; -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.commons.events.ui.JumpToListRequestEvent; -import seedu.address.commons.events.ui.PersonPanelSelectionChangedEvent; -import seedu.address.model.person.Person; - -/** - * Panel containing the list of persons. - */ -public class PersonListPanel extends UiPart { - private static final String FXML = "PersonListPanel.fxml"; - private final Logger logger = LogsCenter.getLogger(PersonListPanel.class); - - @FXML - private ListView personListView; - - public PersonListPanel(ObservableList personList) { - super(FXML); - setConnections(personList); - registerAsAnEventHandler(this); - } - - private void setConnections(ObservableList personList) { - ObservableList mappedList = EasyBind.map( - personList, (person) -> new PersonCard(person, personList.indexOf(person) + 1)); - personListView.setItems(mappedList); - personListView.setCellFactory(listView -> new PersonListViewCell()); - setEventHandlerForSelectionChangeEvent(); - } - - private void setEventHandlerForSelectionChangeEvent() { - personListView.getSelectionModel().selectedItemProperty() - .addListener((observable, oldValue, newValue) -> { - if (newValue != null) { - logger.fine("Selection in person list panel changed to : '" + newValue + "'"); - raise(new PersonPanelSelectionChangedEvent(newValue)); - } - }); - } - - /** - * Scrolls to the {@code PersonCard} at the {@code index} and selects it. - */ - private void scrollTo(int index) { - Platform.runLater(() -> { - personListView.scrollTo(index); - personListView.getSelectionModel().clearAndSelect(index); - }); - } - - @Subscribe - private void handleJumpToListRequestEvent(JumpToListRequestEvent event) { - logger.info(LogsCenter.getEventHandlingLogMessage(event)); - scrollTo(event.targetIndex); - } - - /** - * Custom {@code ListCell} that displays the graphics of a {@code PersonCard}. - */ - class PersonListViewCell extends ListCell { - - @Override - protected void updateItem(PersonCard person, boolean empty) { - super.updateItem(person, empty); - - if (empty || person == null) { - setGraphic(null); - setText(null); - } else { - setGraphic(person.getRoot()); - } - } - } - -} diff --git a/src/main/java/seedu/address/ui/StatusBarFooter.java b/src/main/java/seedu/address/ui/StatusBarFooter.java index 06fb7e50c935..0207a853bcb1 100644 --- a/src/main/java/seedu/address/ui/StatusBarFooter.java +++ b/src/main/java/seedu/address/ui/StatusBarFooter.java @@ -13,6 +13,7 @@ import javafx.scene.layout.Region; import seedu.address.commons.core.LogsCenter; import seedu.address.commons.events.model.AddressBookChangedEvent; +import seedu.address.commons.events.ui.ClientListSwitchEvent; /** * A ui for the status bar that is displayed at the footer of the application. @@ -21,6 +22,8 @@ public class StatusBarFooter extends UiPart { public static final String SYNC_STATUS_INITIAL = "Not updated yet in this session"; public static final String SYNC_STATUS_UPDATED = "Last Updated: %s"; + public static final String SYNC_STATUS_ACTIVE_LIST = ""; + public static final String SYNC_STATUS_CLOSED_LIST = ""; /** * Used to generate time stamps. @@ -40,12 +43,15 @@ public class StatusBarFooter extends UiPart { private StatusBar syncStatus; @FXML private StatusBar saveLocationStatus; + @FXML + private StatusBar displayStatus; public StatusBarFooter(String saveLocation) { super(FXML); setSyncStatus(SYNC_STATUS_INITIAL); setSaveLocation("./" + saveLocation); + setDisplayStatus(SYNC_STATUS_ACTIVE_LIST); registerAsAnEventHandler(this); } @@ -71,6 +77,10 @@ private void setSyncStatus(String status) { Platform.runLater(() -> this.syncStatus.setText(status)); } + private void setDisplayStatus(String status) { + Platform.runLater(() -> this.displayStatus.setText(status)); + } + @Subscribe public void handleAddressBookChangedEvent(AddressBookChangedEvent abce) { long now = clock.millis(); @@ -78,4 +88,16 @@ public void handleAddressBookChangedEvent(AddressBookChangedEvent abce) { logger.info(LogsCenter.getEventHandlingLogMessage(abce, "Setting last updated status to " + lastUpdated)); setSyncStatus(String.format(SYNC_STATUS_UPDATED, lastUpdated)); } + + //@@author olimhc + @Subscribe + private void handleClientListSwitchEvent(ClientListSwitchEvent event) { + logger.info(LogsCenter.getEventHandlingLogMessage(event)); + if (this.displayStatus.getText().equals(SYNC_STATUS_ACTIVE_LIST)) { + setDisplayStatus(SYNC_STATUS_CLOSED_LIST); + } else { + setDisplayStatus(SYNC_STATUS_ACTIVE_LIST); + } + } + //@@author } diff --git a/src/main/java/seedu/address/ui/StudentListPanel.java b/src/main/java/seedu/address/ui/StudentListPanel.java new file mode 100644 index 000000000000..0c42db63270a --- /dev/null +++ b/src/main/java/seedu/address/ui/StudentListPanel.java @@ -0,0 +1,133 @@ +package seedu.address.ui; + +import java.util.logging.Logger; + +import org.fxmisc.easybind.EasyBind; + +import com.google.common.eventbus.Subscribe; + +import javafx.application.Platform; +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.commons.events.ui.ClientListSwitchEvent; +import seedu.address.commons.events.ui.ClientPanelSelectionChangedEvent; +import seedu.address.commons.events.ui.JumpToListRequestEvent; +import seedu.address.model.person.Client; +import seedu.address.ui.util.ListPanelController; + +/** + * Panel containing the list of students. + */ +public class StudentListPanel extends UiPart { + + private static final String FXML = "StudentListPanel.fxml"; + private final Logger logger = LogsCenter.getLogger(StudentListPanel.class); + + @FXML + private ListView studentListView; + + private final ObservableList studentList; + private final ObservableList closedStudentList; + + public StudentListPanel(ObservableList studentList, ObservableList closedStudentList) { + super(FXML); + this.studentList = studentList; + this.closedStudentList = closedStudentList; + setConnectionsForStudents(); + registerAsAnEventHandler(this); + } + + private void setConnectionsForStudents() { + ObservableList mappedList = EasyBind.map( + studentList, (client) -> new ClientCard(client, studentList.indexOf(client) + 1)); + studentListView.setItems(mappedList); + studentListView.setCellFactory(listView -> new StudentListViewCell()); + setEventHandlerForSelectionChangeEvent(); + } + + private void setConnectionsForClosedStudents() { + ObservableList mappedList = EasyBind.map( + closedStudentList, (client) -> new ClientCard(client, closedStudentList.indexOf(client) + 1)); + studentListView.setItems(mappedList); + studentListView.setCellFactory(listView -> new StudentListViewCell()); + setEventHandlerForSelectionChangeEvent(); + } + + //@@author olimhc + /** + * Switch the displayed student's list + */ + private void switchListDisplay() { + ListPanelController listPanelController = ListPanelController.getInstance(); + switch (listPanelController.getCurrentListDisplayed()) { + case activeList: + setConnectionsForClosedStudents(); + break; + + case closedList: + setConnectionsForStudents(); + break; + + default: + throw new AssertionError("This should not be possible."); + } + } + //@@author + + private void setEventHandlerForSelectionChangeEvent() { + studentListView.getSelectionModel().selectedItemProperty() + .addListener((observable, oldValue, newValue) -> { + if (newValue != null) { + logger.fine("Selection in student list panel changed to : '" + newValue + "'"); + raise(new ClientPanelSelectionChangedEvent(newValue)); + } + }); + } + + /** + * Scrolls to the {@code ClientCard} at the {@code index} and selects it. + */ + private void scrollTo(int index) { + Platform.runLater(() -> { + studentListView.scrollTo(index); + studentListView.getSelectionModel().clearAndSelect(index); + }); + } + + @Subscribe + private void handleJumpToListRequestEvent(JumpToListRequestEvent event) { + logger.info(LogsCenter.getEventHandlingLogMessage(event)); + scrollTo(event.targetIndex); + } + + //@@author olimhc + @Subscribe + private void handleClientListSwitchEvent(ClientListSwitchEvent event) { + logger.info(LogsCenter.getEventHandlingLogMessage(event)); + switchListDisplay(); + } + //@@author + + /** + * Custom {@code ListCell} that displays the graphics of a {@code ClientCard}. + */ + class StudentListViewCell extends ListCell { + + @Override + protected void updateItem(ClientCard client, boolean empty) { + super.updateItem(client, empty); + + if (empty || client == null) { + setGraphic(null); + setText(null); + } else { + setGraphic(client.getRoot()); + } + } + } + +} diff --git a/src/main/java/seedu/address/ui/TutorListPanel.java b/src/main/java/seedu/address/ui/TutorListPanel.java new file mode 100644 index 000000000000..dd5d6524bcb5 --- /dev/null +++ b/src/main/java/seedu/address/ui/TutorListPanel.java @@ -0,0 +1,133 @@ +package seedu.address.ui; + +import java.util.logging.Logger; + +import org.fxmisc.easybind.EasyBind; + +import com.google.common.eventbus.Subscribe; + +import javafx.application.Platform; +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.commons.events.ui.ClientListSwitchEvent; +import seedu.address.commons.events.ui.ClientPanelSelectionChangedEvent; +import seedu.address.commons.events.ui.JumpToListRequestEvent; +import seedu.address.model.person.Client; +import seedu.address.ui.util.ListPanelController; + +/** + * Panel containing the list of tutors. + */ +public class TutorListPanel extends UiPart { + + private static final String FXML = "TutorListPanel.fxml"; + private final Logger logger = LogsCenter.getLogger(TutorListPanel.class); + + @FXML + private ListView tutorListView; + + private final ObservableList tutorList; + private final ObservableList closedTutorList; + + public TutorListPanel(ObservableList tutorList, ObservableList closedTutorList) { + super(FXML); + this.tutorList = tutorList; + this.closedTutorList = closedTutorList; + setConnectionsForTutors(); + registerAsAnEventHandler(this); + } + + private void setConnectionsForTutors() { + ObservableList mappedList = EasyBind.map( + tutorList, (client) -> new ClientCard(client, tutorList.indexOf(client) + 1)); + tutorListView.setItems(mappedList); + tutorListView.setCellFactory(listView -> new StudentListViewCell()); + setEventHandlerForSelectionChangeEvent(); + } + + private void setConnectionsForClosedTutors() { + ObservableList mappedList = EasyBind.map( + closedTutorList, (client) -> new ClientCard(client, closedTutorList.indexOf(client) + 1)); + tutorListView.setItems(mappedList); + tutorListView.setCellFactory(listView -> new StudentListViewCell()); + setEventHandlerForSelectionChangeEvent(); + } + + //@@author olimhc + /** + * Switch the displayed tutor's list + */ + private void switchListDisplay() { + ListPanelController listPanelController = ListPanelController.getInstance(); + switch (listPanelController.getCurrentListDisplayed()) { + case activeList: + setConnectionsForClosedTutors(); + break; + + case closedList: + setConnectionsForTutors(); + break; + + default: + throw new AssertionError("This should not be possible."); + } + } + //@@author + + private void setEventHandlerForSelectionChangeEvent() { + tutorListView.getSelectionModel().selectedItemProperty() + .addListener((observable, oldValue, newValue) -> { + if (newValue != null) { + logger.fine("Selection in tutor list panel changed to : '" + newValue + "'"); + raise(new ClientPanelSelectionChangedEvent(newValue)); + } + }); + } + + /** + * Scrolls to the {@code ClientCard} at the {@code index} and selects it. + */ + private void scrollTo(int index) { + Platform.runLater(() -> { + tutorListView.scrollTo(index); + tutorListView.getSelectionModel().clearAndSelect(index); + }); + } + + @Subscribe + private void handleJumpToListRequestEvent(JumpToListRequestEvent event) { + logger.info(LogsCenter.getEventHandlingLogMessage(event)); + scrollTo(event.targetIndex); + } + + //@@author olimhc + @Subscribe + private void handleClientListSwitchEvent(ClientListSwitchEvent event) { + logger.info(LogsCenter.getEventHandlingLogMessage(event)); + switchListDisplay(); + } + //@@author + + /** + * Custom {@code ListCell} that displays the graphics of a {@code ClientCard}. + */ + class StudentListViewCell extends ListCell { + + @Override + protected void updateItem(ClientCard client, boolean empty) { + super.updateItem(client, empty); + + if (empty || client == null) { + setGraphic(null); + setText(null); + } else { + setGraphic(client.getRoot()); + } + } + } + +} diff --git a/src/main/java/seedu/address/ui/util/ListPanelController.java b/src/main/java/seedu/address/ui/util/ListPanelController.java new file mode 100644 index 000000000000..363e15aa38c5 --- /dev/null +++ b/src/main/java/seedu/address/ui/util/ListPanelController.java @@ -0,0 +1,82 @@ +package seedu.address.ui.util; + +import java.util.logging.Logger; + +import seedu.address.commons.core.LogsCenter; + +//@@author olimhc +/** + * Stores the type of list being displayed + */ +public class ListPanelController { + private static final Logger logger = LogsCenter.getLogger(ListPanelController.class); + private static ListPanelController instance = null; + + /** + * An enum to store which the type of list displayed + */ + public enum DisplayType { + closedList, activeList + } + + /** + * Ensure that the active client list is always shown first + */ + private static DisplayType currentlyDisplayed = DisplayType.activeList; + + public DisplayType getCurrentListDisplayed() { + return currentlyDisplayed; + } + + /** + * Switch the current display + */ + public void switchDisplay() { + switch (currentlyDisplayed) { + case activeList: + currentlyDisplayed = DisplayType.closedList; + logger.fine("Switching display to closed client list."); + break; + + case closedList: + currentlyDisplayed = DisplayType.activeList; + logger.fine("Switching display to active client list."); + break; + + default: + throw new AssertionError("This should not be possible."); + } + } + + /** + * Reset the display to its default mode showing active client list. + */ + public void setDefault() { + if (!isCurrentDisplayActiveList()) { + switchDisplay(); + } + } + + /** + * @return true if displayed list is active list + */ + public static boolean isCurrentDisplayActiveList() { + if (currentlyDisplayed == DisplayType.activeList) { + return true; + } else { + return false; + } + } + + /** + * Ensure that only one instance of ListPanelController is created + * @return instance + */ + public static ListPanelController getInstance() { + if (instance == null) { + instance = new ListPanelController(); + } + + return instance; + } +} diff --git a/src/main/resources/view/BrowserPanel.fxml b/src/main/resources/view/BrowserPanel.fxml deleted file mode 100644 index 31670827e3da..000000000000 --- a/src/main/resources/view/BrowserPanel.fxml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/src/main/resources/view/ClientListCard.fxml b/src/main/resources/view/ClientListCard.fxml new file mode 100644 index 000000000000..12d501b14f3b --- /dev/null +++ b/src/main/resources/view/ClientListCard.fxml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/DarkTheme.css b/src/main/resources/view/DarkTheme.css index d06336391cca..2a31ffc432d9 100644 --- a/src/main/resources/view/DarkTheme.css +++ b/src/main/resources/view/DarkTheme.css @@ -87,6 +87,13 @@ -fx-background-color: derive(#1d1d1d, 20%); } +.list-header { + -fx-font-size: 26pt; + -fx-font-family: "Bebas Neue"; + -fx-text-fill: white; + -fx-opacity: 1; +} + .list-view { -fx-background-insets: 0; -fx-padding: 0; @@ -342,10 +349,59 @@ } #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; } + +#tags .red { + -fx-text-fill: white; + -fx-background-color: red; +} + +#tags .blue { + -fx-text-fill: white; + -fx-background-color: blue; +} + +#tags .green { + -fx-text-fill: white; + -fx-background-color: green; +} + +#tags .yellow { + -fx-text-fill: black; + -fx-background-color: yellow; +} + +#tags .purple { + -fx-text-fill: black; + -fx-background-color: purple; +} + +#tags .lightpink { + -fx-text-fill: black; + -fx-background-color: lightpink; +} + +#tags .gold { + -fx-text-fill: black; + -fx-background-color: gold; +} + +#tags .wheat { + -fx-text-fill: black; + -fx-background-color: wheat; +} + +.orange { + -fx-text-fill: black; + -fx-background-color: orange; +} + +.noFill { + -fx-text-fill: black; + -fx-background-color: transparent; +} + diff --git a/src/main/resources/view/MainWindow.fxml b/src/main/resources/view/MainWindow.fxml index 1dadb95b6ffe..3f390611f163 100644 --- a/src/main/resources/view/MainWindow.fxml +++ b/src/main/resources/view/MainWindow.fxml @@ -12,7 +12,7 @@ + minWidth="800" minHeight="600"> @@ -46,19 +46,20 @@ - - + + - + - - - - - + + + + + + diff --git a/src/main/resources/view/PersonListCard.fxml b/src/main/resources/view/PersonListCard.fxml index f08ea32ad558..48b8bfad5754 100644 --- a/src/main/resources/view/PersonListCard.fxml +++ b/src/main/resources/view/PersonListCard.fxml @@ -26,9 +26,12 @@ diff --git a/src/main/resources/view/PersonListPanel.fxml b/src/main/resources/view/PersonListPanel.fxml deleted file mode 100644 index 8836d323cc5d..000000000000 --- a/src/main/resources/view/PersonListPanel.fxml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/src/main/resources/view/StatusBarFooter.fxml b/src/main/resources/view/StatusBarFooter.fxml index 5a9c5a65e43f..eab4cc0e4abe 100644 --- a/src/main/resources/view/StatusBarFooter.fxml +++ b/src/main/resources/view/StatusBarFooter.fxml @@ -10,5 +10,6 @@ - + + diff --git a/src/main/resources/view/StudentListPanel.fxml b/src/main/resources/view/StudentListPanel.fxml new file mode 100644 index 000000000000..807fa8d5c77b --- /dev/null +++ b/src/main/resources/view/StudentListPanel.fxml @@ -0,0 +1,10 @@ + + + + + + + + diff --git a/src/main/resources/view/TutorListPanel.fxml b/src/main/resources/view/TutorListPanel.fxml new file mode 100644 index 000000000000..952fe33516b5 --- /dev/null +++ b/src/main/resources/view/TutorListPanel.fxml @@ -0,0 +1,10 @@ + + + + + + + + diff --git a/src/test/data/XmlAddressBookStorageTest/invalidAndValidPersonAddressBook.xml b/src/test/data/XmlAddressBookStorageTest/invalidAndValidPersonAddressBook.xml index 41e411568a5f..05a3507c9703 100644 --- a/src/test/data/XmlAddressBookStorageTest/invalidAndValidPersonAddressBook.xml +++ b/src/test/data/XmlAddressBookStorageTest/invalidAndValidPersonAddressBook.xml @@ -1,17 +1,25 @@ - + Hans Muster 9482424 hans@example.com
4th street
-
+ north + p1 + math + s + - - Hans Muster - 948asdf2424 + + Ha!!ns Mus!!ter + 9482424 hans@example.com
4th street
-
+ north + p1 + math + s +
diff --git a/src/test/data/XmlAddressBookStorageTest/invalidPersonAddressBook.xml b/src/test/data/XmlAddressBookStorageTest/invalidPersonAddressBook.xml index cfa128e72828..107b1ad6e441 100644 --- a/src/test/data/XmlAddressBookStorageTest/invalidPersonAddressBook.xml +++ b/src/test/data/XmlAddressBookStorageTest/invalidPersonAddressBook.xml @@ -1,10 +1,14 @@ - - Ha!ns Mu@ster + + Han!s Mus!ter 9482424 - hans@example.com -
4th street
-
+ hansexample.com +
4th st@#reet
+ nor!@th + pri1 + ma!%#th + saa +
diff --git a/src/test/data/XmlSerializableAddressBookTest/typicalPersonsAddressBook.xml b/src/test/data/XmlSerializableAddressBookTest/typicalPersonsAddressBook.xml index c778cccc4c89..50aba76a13dc 100644 --- a/src/test/data/XmlSerializableAddressBookTest/typicalPersonsAddressBook.xml +++ b/src/test/data/XmlSerializableAddressBookTest/typicalPersonsAddressBook.xml @@ -2,51 +2,79 @@ - + Alice Pauline 85355255 alice@example.com
123, Jurong West Ave 6, #08-111
+ north + p3 + math + s friends -
- + + Benson Meier 98765432 johnd@example.com
311, Clementi Ave 2, #02-25
+ north + p3 + physics + s owesMoney friends -
- + + Carl Kurz 95352563 heinz@example.com
wall street
-
- + south + j1 + physics + s + + Daniel Meier 87652533 cornelia@example.com
10th street
-
- + east + primary6 + english + s + + Elle Meyer 9482224 werner@example.com
michegan ave
-
- + west + p3 + math + s + + Fiona Kunz 9482427 lydia@example.com
little tokyo
-
- + north + secondary1 + physics + s + + George Best 9482442 anna@example.com
4th street
-
- friends - owesMoney + west + j2 + chemistry + s + friends + owesMoney +
diff --git a/src/test/data/XmlUtilTest/validAddressBook.xml b/src/test/data/XmlUtilTest/validAddressBook.xml index 6265778674d3..95a7a4d65d3c 100644 --- a/src/test/data/XmlUtilTest/validAddressBook.xml +++ b/src/test/data/XmlUtilTest/validAddressBook.xml @@ -1,57 +1,93 @@ - + Hans Muster 9482424 hans@example.com
4th street
-
- + north + p1 + math + s + + Ruth Mueller 87249245 ruth@example.com
81th street
-
- + north + p1 + math + s + + Heinz Kurz 95352563 heinz@example.com
wall street
-
- + north + p1 + math + s + + Cornelia Meier 87652533 cornelia@example.com
10th street
-
- + north + p1 + math + s + + Werner Meyer 9482224 werner@example.com
michegan ave
-
- + north + p1 + math + s + + Lydia Kunz 9482427 lydia@example.com
little tokyo
-
- + north + p1 + math + s + + Anna Best 9482442 anna@example.com
4th street
-
- + north + p1 + math + s + + Stefan Meier 8482424 stefan@example.com
little india
-
- + north + p1 + math + s + + Martin Mueller 8482131 hans@example.com
chicago ave
-
+ north + p1 + math + s +
diff --git a/src/test/java/guitests/guihandles/BrowserPanelHandle.java b/src/test/java/guitests/guihandles/BrowserPanelHandle.java deleted file mode 100644 index bd3633af78f3..000000000000 --- a/src/test/java/guitests/guihandles/BrowserPanelHandle.java +++ /dev/null @@ -1,64 +0,0 @@ -package guitests.guihandles; - -import java.net.URL; - -import guitests.GuiRobot; -import javafx.concurrent.Worker; -import javafx.scene.Node; -import javafx.scene.web.WebEngine; -import javafx.scene.web.WebView; - -/** - * A handler for the {@code BrowserPanel} of the UI. - */ -public class BrowserPanelHandle extends NodeHandle { - - public static final String BROWSER_ID = "#browser"; - - private boolean isWebViewLoaded = true; - - private URL lastRememberedUrl; - - public BrowserPanelHandle(Node browserPanelNode) { - super(browserPanelNode); - - WebView webView = getChildNode(BROWSER_ID); - WebEngine engine = webView.getEngine(); - new GuiRobot().interact(() -> engine.getLoadWorker().stateProperty().addListener((obs, oldState, newState) -> { - if (newState == Worker.State.RUNNING) { - isWebViewLoaded = false; - } else if (newState == Worker.State.SUCCEEDED) { - isWebViewLoaded = true; - } - })); - } - - /** - * Returns the {@code URL} of the currently loaded page. - */ - public URL getLoadedUrl() { - return WebViewUtil.getLoadedUrl(getChildNode(BROWSER_ID)); - } - - /** - * Remembers the {@code URL} of the currently loaded page. - */ - public void rememberUrl() { - lastRememberedUrl = getLoadedUrl(); - } - - /** - * Returns true if the current {@code URL} is different from the value remembered by the most recent - * {@code rememberUrl()} call. - */ - public boolean isUrlChanged() { - return !lastRememberedUrl.equals(getLoadedUrl()); - } - - /** - * Returns true if the browser is done loading a page, or if this browser has yet to load any page. - */ - public boolean isLoaded() { - return isWebViewLoaded; - } -} diff --git a/src/test/java/guitests/guihandles/MainWindowHandle.java b/src/test/java/guitests/guihandles/MainWindowHandle.java index 34e36054f4fd..dc56e8fdcfd2 100644 --- a/src/test/java/guitests/guihandles/MainWindowHandle.java +++ b/src/test/java/guitests/guihandles/MainWindowHandle.java @@ -7,26 +7,26 @@ */ public class MainWindowHandle extends StageHandle { - private final PersonListPanelHandle personListPanel; + private final PersonListPanelHandle studentListPanel; + private final PersonListPanelHandle tutorListPanel; private final ResultDisplayHandle resultDisplay; private final CommandBoxHandle commandBox; private final StatusBarFooterHandle statusBarFooter; private final MainMenuHandle mainMenu; - private final BrowserPanelHandle browserPanel; public MainWindowHandle(Stage stage) { super(stage); - personListPanel = new PersonListPanelHandle(getChildNode(PersonListPanelHandle.PERSON_LIST_VIEW_ID)); + studentListPanel = new PersonListPanelHandle(getChildNode(PersonListPanelHandle.PERSON_LIST_VIEW_ID)); resultDisplay = new ResultDisplayHandle(getChildNode(ResultDisplayHandle.RESULT_DISPLAY_ID)); commandBox = new CommandBoxHandle(getChildNode(CommandBoxHandle.COMMAND_INPUT_FIELD_ID)); statusBarFooter = new StatusBarFooterHandle(getChildNode(StatusBarFooterHandle.STATUS_BAR_PLACEHOLDER)); mainMenu = new MainMenuHandle(getChildNode(MainMenuHandle.MENU_BAR_ID)); - browserPanel = new BrowserPanelHandle(getChildNode(BrowserPanelHandle.BROWSER_ID)); + tutorListPanel = new PersonListPanelHandle(getChildNode(PersonListPanelHandle.PERSON_LIST_VIEW_ID)); } - public PersonListPanelHandle getPersonListPanel() { - return personListPanel; + public PersonListPanelHandle getStudentListPanel() { + return studentListPanel; } public ResultDisplayHandle getResultDisplay() { @@ -45,7 +45,4 @@ public MainMenuHandle getMainMenu() { return mainMenu; } - public BrowserPanelHandle getBrowserPanel() { - return browserPanel; - } } diff --git a/src/test/java/guitests/guihandles/PersonCardHandle.java b/src/test/java/guitests/guihandles/PersonCardHandle.java index d337d3a4cee9..f78097866766 100644 --- a/src/test/java/guitests/guihandles/PersonCardHandle.java +++ b/src/test/java/guitests/guihandles/PersonCardHandle.java @@ -8,7 +8,7 @@ import javafx.scene.layout.Region; /** - * Provides a handle to a person card in the person list panel. + * Provides a handle to a client card in the person list panel. */ public class PersonCardHandle extends NodeHandle { private static final String ID_FIELD_ID = "#id"; @@ -17,6 +17,11 @@ public class PersonCardHandle extends NodeHandle { private static final String PHONE_FIELD_ID = "#phone"; private static final String EMAIL_FIELD_ID = "#email"; private static final String TAGS_FIELD_ID = "#tags"; + private static final String LOCATION_FIELD_ID = "#places"; + private static final String GRADE_FIELD_ID = "#grades"; + private static final String SUBJECT_FIELD_ID = "#subjects"; + + private final Label idLabel; private final Label nameLabel; @@ -24,6 +29,12 @@ public class PersonCardHandle extends NodeHandle { private final Label phoneLabel; private final Label emailLabel; private final List