diff --git a/docs/DeveloperGuide.md b/docs/DeveloperGuide.md index 88167edac4e..dce95469068 100644 --- a/docs/DeveloperGuide.md +++ b/docs/DeveloperGuide.md @@ -230,7 +230,6 @@ Once the indexes of the `Person` and `Meeting` objects to view (if any) are stor - For the case of `editc` and `editm`, this is judged to not be an issue as the view commands still obey their definition of displaying the item at a specified list index. - For the case of `deletec`, `deletem`, `findc` and `findm`, a simple fix is to simply set the stored `Index` to null only for these commands. - ### Find meeting feature #### Behaviour and Implementation @@ -259,7 +258,6 @@ Step 2. The `FindMeetingCommand` will be immediately executed and will call `set The following diagrams show the entire sequence flow for `LogicManager#execute()` for FindMeetingCommand. ![FindMeetingSequence2](images/FindMeetingSequence2.png) - #### Design Considerations and Rationale 1. Given the amount of predicates `FindMeetingCommand` is supposed to use, every predicate needs to be combined in order to maintain good SLAP. @@ -288,8 +286,8 @@ The following sequence diagram shows how the add attendee operation works: A Person object can be obtained from a Meeting's list of attendees by searching through `UniquePersonList` for a `Person` with a name matching `attendeeName`. - ### Remove attendee feature + User can specify an Attendee to remove from a specified Meeting by specifying its index in the list of Attendees. This is the main motivation behind using a LinkedHashSet for the implementation of the Attendee Set. @@ -297,7 +295,6 @@ The following sequence diagram shows how the remove attendee operation works: ![RemoveAttendeeSequenceDiagram](images/RemoveAttendeeSequenceDiagram.png) - ### \[Proposed\] Undo/redo feature #### Proposed Implementation @@ -459,6 +456,7 @@ Priorities: High (must have) - `* * *`, Medium (nice to have) - `* *`, Low (unli | `* * *` | agent | add contacts to meetings | | | `* * *` | agent | remove contacts from meetings | | | `* * *` | agent | view contacts in meetings | | +| `* *` | agent | mark meetings as complete | know which meetings are still pending | | `*` | agent who wants to meet clients regularly | know the last contacted date | when to touch base with a client | _{More to be added}_ @@ -516,6 +514,8 @@ _{More to be added}_ 4. OutBook shows the details of the meeting. 5. User requests to remove a specific contact from the meeting. 6. OutBook removes the contact from the meeting. +7. User requests to remove a specific contact from the meeting. +8. OutBook removes the contact from the meeting. Use case ends. diff --git a/docs/UserGuide.md b/docs/UserGuide.md index 73d96733563..0556bfe1c02 100644 --- a/docs/UserGuide.md +++ b/docs/UserGuide.md @@ -9,16 +9,15 @@ This guide is to help you explore and learn about what are its features and how Outbook has 2 lists which are used to track contacts and meetings respectively. These list can be filtered to show the specific data you need. It is able to add, edit and delete any contacts and meetings you want. As well as add custom remarks and tags for your specific needs. - -
# Table of Content -* Table of Contents -{:toc} +- Table of Contents + {:toc} + +--- ---------------------------------------------------------------------------------------------------------------------
# Quick start @@ -36,20 +35,21 @@ It is able to add, edit and delete any contacts and meetings you want. As well a 5. Type the command in the command box and press Enter to execute it. e.g. typing **`help`** and pressing Enter will open the help window.
Some example commands you can try: - * `listc` : Lists all contacts. + - `listc` : Lists all contacts. - * `addc n/John Doe p/98765432 e/johnd@example.com l/10.10.2023 1000 o/NUS` : Adds a contact named `John Doe` to OutBook. + - `addc n/John Doe p/98765432 e/johnd@example.com lc/10.10.2023 1000 o/NUS` : Adds a contact named `John Doe` to OutBook. - * `deletec 3` : Deletes the 3rd contact shown in the contact list. + - `deletec 3` : Deletes the 3rd contact shown in the contact list. - * `deletem 1` : Deletes the 1st meeting shown in the meeting list. + - `deletem 1` : Deletes the 1st meeting shown in the meeting list. - * `clear` : Deletes all contacts and meetings. + - `clear` : Deletes all contacts and meetings. + + - `exit` : Exits the app. - * `exit` : Exits the app. 6. Refer to the [Features](#features) below for details of each command. --------------------------------------------------------------------------------------------------------------------- +---
@@ -62,19 +62,19 @@ It is able to add, edit and delete any contacts and meetings you want. As well a * Words in `UPPER_CASE` are the parameters to be supplied by you.
e.g. in `addc n/NAME`, `NAME` is a parameter which can be used as `addc n/John Doe`. -* Items in square brackets are optional.
+- 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.
+- Items with `…`​ after them can be used multiple times including zero times.
e.g. `[t/TAG]…​` can be used as ` ` (i.e. 0 times), `t/friend`, `t/friend t/family` etc. -* You can place parameters in any order.
+- You can place parameters in any order.
e.g. if the command specifies `n/NAME p/PHONE_NUMBER`, `p/PHONE_NUMBER n/NAME` is also acceptable. * Any extraneous parameters you place for commands that do not take in parameters (such as `help`, `listc`, `exit` and `clear`) will be ignored.
e.g. if you type `help 123`, it will be interpreted as `help`. -* If you are using a PDF version of this document, be careful when copying and pasting commands that span multiple lines as space characters surrounding line-breaks may be omitted when copied over to the application. +- If you are using a PDF version of this document, be careful when copying and pasting commands that span multiple lines as space characters surrounding line-breaks may be omitted when copied over to the application. ### Viewing help : `help` @@ -102,9 +102,9 @@ Format: `addc n/NAME p/PHONE_NUMBER e/EMAIL lc/LAST_CONTACTED_TIME [s/STATUS] [r 2. The local-part may not start or end with any special characters. 3. The domain name is made up of domain labels separated by periods. The domain name must: - - end with a domain label at least 2 characters long - - have each domain label start and end with alphanumeric characters - - have each domain label consist of alphanumeric characters, separated only by hyphen + - end with a domain label at least 2 characters long + - have each domain label start and end with alphanumeric characters + - have each domain label consist of alphanumeric characters, separated only by hyphen * `LAST_CONTACTED_TIME` must contain both date and time and adhere to the `DD.MM.YYYY HHMM` format. - eg. 1st October 2023, 10:00am will be written as `01.10.2023 1000`. @@ -115,8 +115,8 @@ Format: `addc n/NAME p/PHONE_NUMBER e/EMAIL lc/LAST_CONTACTED_TIME [s/STATUS] [r You can put any number of tags (including 0) on a contact. -* `addc n/John Doe p/98765432 e/johnd@example.com lc/01.10.2023 1000` -* `addc n/Betsy Crowe t/friend e/betsycrowe@example.com p/1234567 lc/01.01.2023 0100 t/Professor` +- `addc n/John Doe p/98765432 e/johnd@example.com lc/01.10.2023 1000` +- `addc n/Betsy Crowe t/friend e/betsycrowe@example.com p/1234567 lc/01.01.2023 0100 t/Professor`

@@ -126,34 +126,33 @@ Shows a list of all contacts in OutBook. Contacts are sorted by LAST_CONTACTED_T Format: `listc` - ### Deleting a person : `deletec` Deletes a contact from OutBook. Format: `deletec INDEX` -* Deletes the contact at the specified `INDEX`. -* The index refers to the index number shown in the displayed person list. -* The index **must be a positive integer** 1, 2, 3, …​ +- Deletes the contact at the specified `INDEX`. +- The index refers to the index number shown in the displayed person list. +- The index **must be a positive integer** 1, 2, 3, …​ Examples: -* `listc` followed by `delete 2` deletes the 2nd person in the results of the `listc` command. -* `findc Betsy` followed by `delete 1` deletes the 1st person in the results of the `findc` command. +- `listc` followed by `delete 2` deletes the 2nd person in the results of the `listc` command. +- `findc Betsy` followed by `delete 1` deletes the 1st person in the results of the `findc` command. ### Editing a contact : `editc` Edits an existing contact in OutBook. -Format: `editc INDEX [n/NAME] [p/PHONE_NUMBER] [e/EMAIL] [l/LAST_CONTACTED_TIME] [s/STATUS] [r/REMARK] [t/TAG]…​` +Format: `editc INDEX [n/NAME] [p/PHONE_NUMBER] [e/EMAIL] [lc/LAST_CONTACTED_TIME] [s/STATUS] [r/REMARK] [t/TAG]…​` -* Edits the contact at the specified `INDEX`. The index refers to the index number shown in the displayed person list. The index **must be a positive integer** 1, 2, 3, …​ -* All fields are optional, but at least one must be provided. -* Existing values will be updated to the input values. -* When editing tags, the existing tags of the person will be removed i.e adding of tags is not cumulative. -* You can remove all the person’s tags by typing `t/` without - specifying any tags after it. +- Edits the contact at the specified `INDEX`. The index refers to the index number shown in the displayed person list. The index **must be a positive integer** 1, 2, 3, …​ +- All fields are optional, but at least one must be provided. +- Existing values will be updated to the input values. +- When editing tags, the existing tags of the person will be removed i.e adding of tags is not cumulative. +- You can remove all the person’s tags by typing `t/` without + specifying any tags after it. Examples: * `editc 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. @@ -162,6 +161,8 @@ Examples:
+- `editc 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. +- `editc 2 n/Betsy Crower t/` Edits the name of the 2nd person to be `Betsy Crower` and clears all existing tags. ### Viewing detailed contact information : `viewc` @@ -179,7 +180,6 @@ Format: `viewc INDEX` Examples: * `viewc 2` Displays detailed information related to the 2nd contact on the list. -



@@ -207,7 +207,6 @@ Examples: * `findc p/9 s/inactive claimant t/friends` returns persons with a `9` in their phone number, whose status is either `inactive` or `claimant`, and has a `friends` tag ![result for 'findContact'](images/findContactResult.png) -
@@ -224,8 +223,9 @@ Format: `addm m/TITLE a/LOCATION s/START e/END [t/TAG]…​` - eg. 1st October 2023, 10:00am will be written as `01.10.2023 1000`. Examples: -* `addm m/Lunch a/Cafeteria s/20.09.2023 1200 e/20.09.2023 1300` -* `addm m/CS2103T meeting a/Zoom call url s/20.09.2023 1000 e/20.09.2023 1200` + +- `addm m/Lunch a/Cafeteria s/20.09.2023 1200 e/20.09.2023 1300` +- `addm m/CS2103T meeting a/Zoom call url s/20.09.2023 1000 e/20.09.2023 1200` ### Listing all meetings : `listm` @@ -233,21 +233,20 @@ Shows a list of all meetings in OutBook. Meetings are sorted by START by default Format: `listm` - ### Deleting a meeting : `deletem` Deletes a meeting from OutBook. Format: `deletem INDEX` -* Deletes the meeting at the specified `INDEX`. -* The index refers to the index number shown in the displayed meeting list. -* The index **must be a positive integer** 1, 2, 3, …​ +- Deletes the meeting at the specified `INDEX`. +- The index refers to the index number shown in the displayed meeting list. +- The index **must be a positive integer** 1, 2, 3, …​ Examples: -* `listm` followed by `deletem 2` deletes the 2nd meeting in the results of the `listm` command. -* `findm m/Project` followed by `deletem 1` deletes the 1st meeting in the results of the `findm` command. +- `listm` followed by `deletem 2` deletes the 2nd meeting in the results of the `listm` command. +- `findm m/Project` followed by `deletem 1` deletes the 1st meeting in the results of the `findm` command. ### Editing a meeting : `editm` @@ -255,17 +254,17 @@ Edits an existing meeting in OutBook. Format: `editm INDEX [m/TITLE] [a/LOCATION] [s/START] [e/END] [t/TAG]…​` -* Edits the meeting at the specified `INDEX`. The index refers to the index number shown in the displayed meeting list. The index **must be a positive integer** 1, 2, 3, …​ -* At least one of the optional fields must be provided. -* Existing values will be updated to the input values. -* When editing tags, the existing tags of the meeting will be removed i.e adding of tags is not cumulative. -* You can remove all the meeting’s tags by typing `t/` without +- Edits the meeting at the specified `INDEX`. The index refers to the index number shown in the displayed meeting list. The index **must be a positive integer** 1, 2, 3, …​ +- At least one of the optional fields must be provided. +- Existing values will be updated to the input values. +- When editing tags, the existing tags of the meeting will be removed i.e adding of tags is not cumulative. +- You can remove all the meeting’s tags by typing `t/` without specifying any tags after it. Examples: -* `editm 1 a/Hawker Centre s/15.09.2023 1500` Edits the location and start of the 1st meeting to be `Hawker Centre` and `15.09.2023 1500` respectively. -* `editm 2 m/Zoom meeting t/` Edits the title of the 2nd meeting to be `Zoom meeting` and clears all existing tags. +- `editm 1 a/Hawker Centre s/15.09.2023 1500` Edits the location and start of the 1st meeting to be `Hawker Centre` and `15.09.2023 1500` respectively. +- `editm 2 m/Zoom meeting t/` Edits the title of the 2nd meeting to be `Zoom meeting` and clears all existing tags. ### Viewing detailed meeting information : `viewm` @@ -328,7 +327,8 @@ Format: `addmc MEETING_INDEX CONTACT_INDEX` * Contact name will be listed in the detailed description of meetings when `viewm` is used. Examples: -* `addmc 3 1` adds the 1st contact as an attendee to the 3rd meeting in OutBook. + +- `addmc 3 1` adds the 1st contact as an attendee to the 3rd meeting in OutBook. ### Remove contact from meeting: `rmmc` @@ -336,16 +336,29 @@ Removes a contact from a meeting. Format: `rmmc MEETING_INDEX ATTENDEE_INDEX` -* Removes a contact at the specified `ATTENDEE_INDEX` to the meeting at the specified `MEETING_INDEX`. -* `MEETING_INDEX` refers to the index number shown in the displayed meeting list. -* `ATTENDEE_INDEX` refers to the index number of the attendee as shown in `viewm`. -* The indexes **must be positive integers** 1, 2, 3, …​ -* Both `MEETING_INDEX` & `ATTENDEE_INDEX` must refer to the index of an existing meeting or attendee. +- Removes a contact at the specified `ATTENDEE_INDEX` to the meeting at the specified `MEETING_INDEX`. +- `MEETING_INDEX` refers to the index number shown in the displayed meeting list. +- `ATTENDEE_INDEX` refers to the index number of the attendee as shown in `viewm`. +- The indexes **must be positive integers** 1, 2, 3, …​ +- Both `MEETING_INDEX` & `ATTENDEE_INDEX` must refer to the index of an existing meeting or attendee. Examples: -* `rmmc 3 2` removes the 2nd attendee from the 3rd meeting in OutBook. -



+- `rmmc 3 2` removes the 2nd attendee from the 3rd meeting in OutBook. + +### Marking a meeting as complete : `mark` + +Marks a meeting in OutBook as complete. All attendees of the meeting will have their LC (last contacted) field updated to the end time of the meeting. + +Format: `mark INDEX` + +- Marks the meeting at the specified `INDEX` as complete. +- The index refers to the index number shown in the displayed meeting list. +- The index **must be a positive integer** 1, 2, 3, …​ + +Examples: + +- `listm` followed by `mark 2` marks the 2nd meeting in the results of the `listm` command. ### Clearing all entries : `clear` @@ -371,8 +384,8 @@ OutBook data are saved automatically as a JSON file `[JAR file location]/data/ou If your changes to the data file makes its format invalid, OutBook will discard all data and start with an empty data file at the next run. Hence, it is recommended to take a backup of the file before editing it. +--- ---------------------------------------------------------------------------------------------------------------------
## FAQ @@ -380,20 +393,21 @@ If your changes to the data file makes its format invalid, OutBook will discard **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 OutBook home folder. --------------------------------------------------------------------------------------------------------------------- +--- ## Known issues 1. **When using multiple screens**, if you move the application to a secondary screen, and later switch to using only the primary screen, the GUI will open off-screen. The remedy is to delete the `preferences.json` file created by the application before running the application again. --------------------------------------------------------------------------------------------------------------------- +--- +
## Command summary | Action | Format, Examples | |---------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| **Add contact** | `addc n/NAME p/PHONE_NUMBER e/EMAIL lc/LAST_CONTACTED_TIME [r/REMARK] [t/TAG]…​`
e.g., `add n/James Ho p/22224444 e/jamesho@example.com l/09.09.2023 0000 o/NUS t/friend t/colleague` | +| **Add contact** | `addc n/NAME p/PHONE_NUMBER e/EMAIL lc/LAST_CONTACTED_TIME [r/REMARK] [t/TAG]…​`
e.g., `add n/James Ho p/22224444 e/jamesho@example.com lc/09.09.2023 0000 o/NUS t/friend t/colleague` | | **Add contact to meeting** | `addmc MEETING_INDEX CONTACT_INDEX`
e.g., `addmc 2 1` | | **Add meeting** | `addm m/TITLE a/LOCATION s/START e/END [t/TAG]…​`
e.g., `addm m/Lunch a/Cafeteria s/20.09.2023 1200 e/20.09.2023 1300` | | **Clear** | `clear` | diff --git a/docs/team/jason-raiin.md b/docs/team/jason-raiin.md index 6040360fbf8..d1df38938a7 100644 --- a/docs/team/jason-raiin.md +++ b/docs/team/jason-raiin.md @@ -12,36 +12,40 @@ My contributions to the project are listed below. - **New Feature**: Remove contact from meeting command - Added command and parser - - Thorough testing +- **New Feature**: Mark meeting command + - Added command and parser + - Updates last contacted time for contacts - **Code contributed**: [RepoSense link](https://nus-cs2103-ay2324s1.github.io/tp-dashboard/?search=jason-raiin&breakdown=true) - **Project management**: - - - to be added soon + - Contributed issues + - Reviewed PRs - **Enhancements to existing features**: - - - Add Tag to meetings: logic and UI + - Add Tags field to meetings - Convert Tag to factory class with no duplicates + - Added Status field to meetings - **Documentation**: - User Guide - - Remove meeting contact command + - `rmmc` command + - `mark` command - Minor edits - Developer Guide + - `rmmc` command + - `mark` command - User profile - Value proposition - User stories - Use cases - **Community**: - - to be added soon - **Tools**: - - Added util method `parseIndexes` + - Improved methods for `typicalMeetings` and `typicalAddressBook` diff --git a/src/main/java/seedu/address/logic/Messages.java b/src/main/java/seedu/address/logic/Messages.java index 8a181503c7a..fa867f6e1d4 100644 --- a/src/main/java/seedu/address/logic/Messages.java +++ b/src/main/java/seedu/address/logic/Messages.java @@ -71,6 +71,8 @@ public static String format(Meeting meeting) { .append(meeting.getEnd()) .append("; Attendees: "); meeting.getAttendees().forEach(builder::append); + builder.append("; Completed: ") + .append(meeting.getStatus()); return builder.toString(); } diff --git a/src/main/java/seedu/address/logic/commands/AddMeetingContactCommand.java b/src/main/java/seedu/address/logic/commands/AddMeetingContactCommand.java index b348a8fa54e..90956474d43 100644 --- a/src/main/java/seedu/address/logic/commands/AddMeetingContactCommand.java +++ b/src/main/java/seedu/address/logic/commands/AddMeetingContactCommand.java @@ -79,7 +79,7 @@ static Meeting addAttendee(Meeting meeting, Attendee attendeeToAdd) { Set updatedAttendees = new LinkedHashSet<>(meeting.getAttendees()); updatedAttendees.add(attendeeToAdd); Meeting updatedMeeting = new Meeting(meeting.getTitle(), meeting.getLocation(), meeting.getStart(), - meeting.getEnd(), updatedAttendees, meeting.getTags()); + meeting.getEnd(), updatedAttendees, meeting.getTags(), meeting.getStatus()); return updatedMeeting; } diff --git a/src/main/java/seedu/address/logic/commands/EditMeetingCommand.java b/src/main/java/seedu/address/logic/commands/EditMeetingCommand.java index 0dc569cad03..3cc3e9aa1f4 100644 --- a/src/main/java/seedu/address/logic/commands/EditMeetingCommand.java +++ b/src/main/java/seedu/address/logic/commands/EditMeetingCommand.java @@ -25,6 +25,7 @@ import seedu.address.model.meeting.Attendee; import seedu.address.model.meeting.Location; import seedu.address.model.meeting.Meeting; +import seedu.address.model.meeting.MeetingStatus; import seedu.address.model.meeting.MeetingTime; import seedu.address.model.meeting.Title; import seedu.address.model.tag.Tag; @@ -102,12 +103,13 @@ static Meeting createEditedMeeting(Meeting meetingToEdit, LocalDateTime updatedEnd = editMeetingDescriptor.getEnd().orElse(meetingToEdit.getEnd()); Set attendees = meetingToEdit.getAttendees(); Set updatedTags = editMeetingDescriptor.getTags().orElse(meetingToEdit.getTags()); + MeetingStatus status = meetingToEdit.getStatus(); if (!MeetingTime.isValidMeetingTime(updatedStart, updatedEnd)) { throw new CommandException(MeetingTime.MESSAGE_CONSTRAINTS); } - return new Meeting(updatedTitle, updatedLocation, updatedStart, updatedEnd, attendees, updatedTags); + return new Meeting(updatedTitle, updatedLocation, updatedStart, updatedEnd, attendees, updatedTags, status); } @Override diff --git a/src/main/java/seedu/address/logic/commands/FindMeetingCommand.java b/src/main/java/seedu/address/logic/commands/FindMeetingCommand.java index a7f06bef8d1..ae87b33ca79 100644 --- a/src/main/java/seedu/address/logic/commands/FindMeetingCommand.java +++ b/src/main/java/seedu/address/logic/commands/FindMeetingCommand.java @@ -34,7 +34,7 @@ public class FindMeetingCommand extends Command { * @param predicate The predicate that will be used by the FindMeetingCommand object. */ public FindMeetingCommand(GeneralMeetingPredicate predicate) { - assert predicate != null; + requireNonNull(predicate); this.predicate = predicate; } diff --git a/src/main/java/seedu/address/logic/commands/MarkMeetingCommand.java b/src/main/java/seedu/address/logic/commands/MarkMeetingCommand.java new file mode 100644 index 00000000000..139af978911 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/MarkMeetingCommand.java @@ -0,0 +1,105 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import java.time.LocalDateTime; +import java.util.Iterator; +import java.util.List; + +import seedu.address.commons.core.index.Index; +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.logic.Messages; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.meeting.Attendee; +import seedu.address.model.meeting.Meeting; +import seedu.address.model.meeting.MeetingStatus; +import seedu.address.model.person.Person; + +/** + * Marks a meeting as complete. + */ +public class MarkMeetingCommand extends Command { + + public static final String COMMAND_WORD = "mark"; + + public static final String MESSAGE_MARK_MEETING_SUCCESS = "Meeting marked as complete: %1$s"; + + public static final String MESSAGE_MEETING_ALREADY_COMPLETE = "Meeting has already been marked as complete."; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Marks the meeting identified by the index number used in the displayed meetings list as complete.\n" + + "Parameters: INDEX (must be a positive integer)\n" + "Example: " + COMMAND_WORD + "1"; + + private final Index targetIndex; + + public MarkMeetingCommand(Index targetIndex) { + this.targetIndex = targetIndex; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + List lastShownList = model.getFilteredMeetingList(); + + if (targetIndex.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_MEETING_DISPLAYED_INDEX); + } + + Meeting meetingToMark = lastShownList.get(targetIndex.getZeroBased()); + Meeting updatedMeeting = markMeeting(meetingToMark); + model.setMeeting(meetingToMark, updatedMeeting); + + Iterator attendeeIterator = meetingToMark.getAttendees().iterator(); + while (attendeeIterator.hasNext()) { + Attendee attendee = attendeeIterator.next(); + Person person = model.getPerson(attendee.getAttendeeName()); + Person updatedPerson = updateLastContactedTime(person, meetingToMark.getEnd()); + model.setPerson(person, updatedPerson); + } + + return new CommandResult(String.format(MESSAGE_MARK_MEETING_SUCCESS, Messages.format(updatedMeeting))); + } + + static Meeting markMeeting(Meeting meeting) throws CommandException { + if (meeting.getStatus().isComplete) { + throw new CommandException(MESSAGE_MEETING_ALREADY_COMPLETE); + } + + Meeting markedMeeting = new Meeting(meeting.getTitle(), meeting.getLocation(), meeting.getStart(), + meeting.getEnd(), meeting.getAttendees(), meeting.getTags(), new MeetingStatus(true)); + + return markedMeeting; + } + + static Person updateLastContactedTime(Person person, LocalDateTime lastContactedTime) { + if (lastContactedTime.isBefore(person.getLastContactedTime())) { + return person; + } + + Person updatedPerson = new Person(person.getName(), person.getPhone(), person.getEmail(), lastContactedTime, + person.getStatus(), person.getRemark(), person.getTags()); + + return updatedPerson; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof MarkMeetingCommand)) { + return false; + } + + MarkMeetingCommand otherMarkMeetingCommand = (MarkMeetingCommand) other; + return targetIndex.equals(otherMarkMeetingCommand.targetIndex); + } + + @Override + public String toString() { + return new ToStringBuilder(this).add("targetIndex", targetIndex).toString(); + } +} diff --git a/src/main/java/seedu/address/logic/commands/RemoveMeetingContactCommand.java b/src/main/java/seedu/address/logic/commands/RemoveMeetingContactCommand.java index 2b12de4252e..e0f0c163370 100644 --- a/src/main/java/seedu/address/logic/commands/RemoveMeetingContactCommand.java +++ b/src/main/java/seedu/address/logic/commands/RemoveMeetingContactCommand.java @@ -68,7 +68,7 @@ static Meeting removeAttendee(Meeting meeting, Attendee attendeeToRemove) { Set updatedAttendees = new LinkedHashSet<>(meeting.getAttendees()); updatedAttendees.remove(attendeeToRemove); Meeting updatedMeeting = new Meeting(meeting.getTitle(), meeting.getLocation(), meeting.getStart(), - meeting.getEnd(), updatedAttendees, meeting.getTags()); + meeting.getEnd(), updatedAttendees, meeting.getTags(), meeting.getStatus()); return updatedMeeting; } diff --git a/src/main/java/seedu/address/logic/parser/AddMeetingCommandParser.java b/src/main/java/seedu/address/logic/parser/AddMeetingCommandParser.java index c8b4d780313..8c01d89c298 100644 --- a/src/main/java/seedu/address/logic/parser/AddMeetingCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/AddMeetingCommandParser.java @@ -20,6 +20,7 @@ import seedu.address.model.meeting.Attendee; import seedu.address.model.meeting.Location; import seedu.address.model.meeting.Meeting; +import seedu.address.model.meeting.MeetingStatus; import seedu.address.model.meeting.MeetingTime; import seedu.address.model.meeting.Title; import seedu.address.model.tag.Tag; @@ -56,8 +57,9 @@ public AddMeetingCommand parse(String args) throws ParseException { } Set attendeeList = ParserUtil.parseAttendees(argMultimap.getAllValues(PREFIX_NAME)); Set tagList = ParserUtil.parseTags(argMultimap.getAllValues(PREFIX_TAG)); + MeetingStatus status = new MeetingStatus(false); - Meeting meeting = new Meeting(title, location, start, end, attendeeList, tagList); + Meeting meeting = new Meeting(title, location, start, end, attendeeList, tagList, status); return new AddMeetingCommand(meeting); } diff --git a/src/main/java/seedu/address/logic/parser/AddressBookParser.java b/src/main/java/seedu/address/logic/parser/AddressBookParser.java index e437598624d..918ae644c32 100644 --- a/src/main/java/seedu/address/logic/parser/AddressBookParser.java +++ b/src/main/java/seedu/address/logic/parser/AddressBookParser.java @@ -23,6 +23,7 @@ import seedu.address.logic.commands.HelpCommand; import seedu.address.logic.commands.ListCommand; import seedu.address.logic.commands.ListMeetingCommand; +import seedu.address.logic.commands.MarkMeetingCommand; import seedu.address.logic.commands.RemoveMeetingContactCommand; import seedu.address.logic.commands.ViewContactCommand; import seedu.address.logic.commands.ViewMeetingCommand; @@ -113,6 +114,9 @@ public Command parseCommand(String userInput) throws ParseException { case FindMeetingCommand.COMMAND_WORD: return new FindMeetingCommandParser().parse(arguments); + case MarkMeetingCommand.COMMAND_WORD: + return new MarkMeetingCommandParser().parse(arguments); + default: logger.finer("This user input caused a ParseException: " + userInput); throw new ParseException(MESSAGE_UNKNOWN_COMMAND); diff --git a/src/main/java/seedu/address/logic/parser/MarkMeetingCommandParser.java b/src/main/java/seedu/address/logic/parser/MarkMeetingCommandParser.java new file mode 100644 index 00000000000..07deae8d5be --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/MarkMeetingCommandParser.java @@ -0,0 +1,29 @@ +package seedu.address.logic.parser; + +import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.MarkMeetingCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a new MarkMeetingCommand object + */ +public class MarkMeetingCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the MarkMeetingCommand + * and returns a MarkMeetingCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public MarkMeetingCommand parse(String args) throws ParseException { + try { + Index index = ParserUtil.parseIndex(args); + return new MarkMeetingCommand(index); + } catch (ParseException pe) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, MarkMeetingCommand.MESSAGE_USAGE), pe); + } + } + +} diff --git a/src/main/java/seedu/address/model/AddressBook.java b/src/main/java/seedu/address/model/AddressBook.java index 90be75d873a..d4eb0bc493d 100644 --- a/src/main/java/seedu/address/model/AddressBook.java +++ b/src/main/java/seedu/address/model/AddressBook.java @@ -109,6 +109,14 @@ public void addMeeting(Meeting m) { meetings.add(m); } + /** + * Returns the person with the given name + * or throws {@code IndexOutOfBoundsException} if it does not exist. + */ + public Person getPerson(String name) { + return persons.getPerson(name); + } + /** * Replaces the given person {@code target} in the list with {@code editedPerson}. * {@code target} must exist in the address book. diff --git a/src/main/java/seedu/address/model/Model.java b/src/main/java/seedu/address/model/Model.java index 8d7ddd20905..53de634126b 100644 --- a/src/main/java/seedu/address/model/Model.java +++ b/src/main/java/seedu/address/model/Model.java @@ -90,6 +90,12 @@ public interface Model { */ void addMeeting(Meeting meeting); + /** + * Returns the person with the given name. + * A person with the given name must exist in the address book. + */ + Person getPerson(String name); + /** * Replaces the given person {@code target} with {@code editedPerson}. * {@code target} must exist in the address book. diff --git a/src/main/java/seedu/address/model/ModelManager.java b/src/main/java/seedu/address/model/ModelManager.java index 09a34df0081..a86ed002641 100644 --- a/src/main/java/seedu/address/model/ModelManager.java +++ b/src/main/java/seedu/address/model/ModelManager.java @@ -136,6 +136,11 @@ public void addMeeting(Meeting meeting) { updateFilteredMeetingList(PREDICATE_SHOW_ALL_MEETINGS); } + @Override + public Person getPerson(String name) { + return addressBook.getPerson(name); + } + @Override public void setPerson(Person target, Person editedPerson) { requireAllNonNull(target, editedPerson); diff --git a/src/main/java/seedu/address/model/meeting/Meeting.java b/src/main/java/seedu/address/model/meeting/Meeting.java index c6bbcbd007c..b57d98baafc 100644 --- a/src/main/java/seedu/address/model/meeting/Meeting.java +++ b/src/main/java/seedu/address/model/meeting/Meeting.java @@ -24,17 +24,19 @@ public class Meeting { private final MeetingTime meetingTime; private final Set attendees; private final Set tags; + private final MeetingStatus status; /** * Every field must be present and not null. */ public Meeting(Title title, Location location, LocalDateTime start, LocalDateTime end, Set attendees, - Set tags) { + Set tags, MeetingStatus status) { this.title = title; this.location = location; this.meetingTime = new MeetingTime(start, end); this.attendees = new LinkedHashSet<>(attendees); this.tags = new HashSet<>(tags); + this.status = status; } public Title getTitle() { @@ -57,6 +59,10 @@ public MeetingTime getMeetingTime() { return meetingTime; } + public MeetingStatus getStatus() { + return status; + } + public boolean withinSpecifiedTime(LocalDateTime start, LocalDateTime end) { return !meetingTime.getStart().isBefore(start) && !meetingTime.getEnd().isAfter(end); } @@ -135,7 +141,7 @@ public boolean equals(Object other) { Meeting otherMeeting = (Meeting) other; return title.equals(otherMeeting.title) && location.equals(otherMeeting.location) && meetingTime.equals(otherMeeting.meetingTime) && attendees.equals(otherMeeting.attendees) - && tags.equals(otherMeeting.tags); + && tags.equals(otherMeeting.tags) && status.equals(otherMeeting.status); } @Override @@ -148,7 +154,7 @@ public int hashCode() { public String toString() { return new ToStringBuilder(this).add("title", title).add("location", location) .add("start", meetingTime.getStart()).add("end", meetingTime.getEnd()).add("attendees", attendees) - .add("tags", tags).toString(); + .add("tags", tags).add("status", status).toString(); } /** diff --git a/src/main/java/seedu/address/model/meeting/MeetingStatus.java b/src/main/java/seedu/address/model/meeting/MeetingStatus.java new file mode 100644 index 00000000000..011ecaad3e6 --- /dev/null +++ b/src/main/java/seedu/address/model/meeting/MeetingStatus.java @@ -0,0 +1,44 @@ +package seedu.address.model.meeting; + +/** + * Contains a Boolean representing whether a meeting is completed. + */ +public class MeetingStatus { + + public static final String MESSAGE_CONSTRAINTS = "Status must be exactly 'true' or 'false'"; + public final Boolean isComplete; + + /** + * Constructs a {@code Status} field representing whether a meeting is complete. + */ + public MeetingStatus(Boolean isComplete) { + this.isComplete = isComplete; + } + + @Override + public int hashCode() { + return isComplete.hashCode(); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof MeetingStatus)) { + return false; + } + + MeetingStatus completed = (MeetingStatus) other; + return isComplete.equals(completed.isComplete); + } + + /** + * Format state as text for viewing. + */ + @Override + public String toString() { + return isComplete.toString(); + } +} diff --git a/src/main/java/seedu/address/model/person/UniquePersonList.java b/src/main/java/seedu/address/model/person/UniquePersonList.java index 1bf241c0c32..22536919254 100644 --- a/src/main/java/seedu/address/model/person/UniquePersonList.java +++ b/src/main/java/seedu/address/model/person/UniquePersonList.java @@ -66,6 +66,21 @@ public void add(Person toAdd) { FXCollections.sort(internalList, sortByLastContactComparator); } + /** + * Returns the person with the given name + * or throws {@code IndexOutOfBoundsException} if it does not exist. + */ + public Person getPerson(String fullName) { + Name name = new Name(fullName); + List filteredList = internalList.filtered(person -> person.getName().equals(name)); + + if (filteredList.size() == 0) { + throw new PersonNotFoundException(); + } + + return filteredList.get(0); + } + /** * Replaces the person {@code target} in the list with {@code editedPerson}. * {@code target} must exist in the list. diff --git a/src/main/java/seedu/address/model/util/SampleDataUtil.java b/src/main/java/seedu/address/model/util/SampleDataUtil.java index 9096dcd8915..5d4bcb4937b 100644 --- a/src/main/java/seedu/address/model/util/SampleDataUtil.java +++ b/src/main/java/seedu/address/model/util/SampleDataUtil.java @@ -13,6 +13,7 @@ import seedu.address.model.meeting.Attendee; import seedu.address.model.meeting.Location; import seedu.address.model.meeting.Meeting; +import seedu.address.model.meeting.MeetingStatus; import seedu.address.model.meeting.Title; import seedu.address.model.person.Email; import seedu.address.model.person.Name; @@ -54,11 +55,11 @@ public static Meeting[] getSampleMeetings() { new Meeting(new Title("Test Meeting 1"), new Location("Room 1"), LocalDateTime.parse("02.10.2023 1000", FORMAT), LocalDateTime.parse("03.10.2023 1000", FORMAT), - getAttendeeSet("Alex Yeoh"), getTagSet("work")), + getAttendeeSet("Alex Yeoh"), getTagSet("work"), new MeetingStatus(false)), new Meeting(new Title("Test Meeting 2"), new Location("Room 2"), LocalDateTime.parse("02.10.2023 1000", FORMAT), LocalDateTime.parse("02.10.2023 1000", FORMAT), - getAttendeeSet(), getTagSet()), + getAttendeeSet(), getTagSet(), new MeetingStatus(false)), }; } diff --git a/src/main/java/seedu/address/storage/JsonAdaptedMeeting.java b/src/main/java/seedu/address/storage/JsonAdaptedMeeting.java index 702ee11d3cb..a9b8f4bfb24 100644 --- a/src/main/java/seedu/address/storage/JsonAdaptedMeeting.java +++ b/src/main/java/seedu/address/storage/JsonAdaptedMeeting.java @@ -15,6 +15,7 @@ import seedu.address.model.meeting.Attendee; import seedu.address.model.meeting.Location; import seedu.address.model.meeting.Meeting; +import seedu.address.model.meeting.MeetingStatus; import seedu.address.model.meeting.MeetingTime; import seedu.address.model.meeting.Title; import seedu.address.model.tag.Tag; @@ -32,6 +33,7 @@ class JsonAdaptedMeeting { private final String end; private final List attendees = new ArrayList<>(); private final List tags = new ArrayList<>(); + private final String status; /** * Constructs a {@code JsonAdaptedPerson} with the given person details. @@ -40,7 +42,7 @@ class JsonAdaptedMeeting { public JsonAdaptedMeeting(@JsonProperty("title") String title, @JsonProperty("location") String location, @JsonProperty("start") String start, @JsonProperty("end") String end, @JsonProperty("attendees") List attendees, - @JsonProperty("tags") List tags) { + @JsonProperty("tags") List tags, @JsonProperty("status") String status) { this.title = title; this.location = location; @@ -52,6 +54,7 @@ public JsonAdaptedMeeting(@JsonProperty("title") String title, @JsonProperty("lo if (tags != null) { this.tags.addAll(tags); } + this.status = status; } /** @@ -68,6 +71,7 @@ public JsonAdaptedMeeting(Meeting source) { tags.addAll(source.getTags().stream() .map(JsonAdaptedTag::new) .collect(Collectors.toList())); + status = source.getStatus().toString(); } /** @@ -80,11 +84,13 @@ public Meeting toModelType() throws IllegalValueException { for (JsonAdaptedAttendee person : attendees) { meetingAttendees.add(person.toModelType()); } + final Set modelAttendees = new HashSet<>(meetingAttendees); final List meetingTags = new ArrayList<>(); for (JsonAdaptedTag tag : tags) { meetingTags.add(tag.toModelType()); } + final Set modelTags = new HashSet<>(meetingTags); if (title == null) { throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, @@ -118,10 +124,16 @@ public Meeting toModelType() throws IllegalValueException { final LocalDateTime modelStart = LocalDateTime.parse(start); final LocalDateTime modelEnd = LocalDateTime.parse(end); - final Set modelAttendees = new HashSet<>(meetingAttendees); + if (status == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + MeetingStatus.class.getSimpleName())); + } + if (!status.equals(Boolean.FALSE.toString()) && !status.equals(Boolean.TRUE.toString())) { + throw new IllegalValueException(MeetingStatus.MESSAGE_CONSTRAINTS); + } + final MeetingStatus modelStatus = new MeetingStatus(Boolean.parseBoolean(status)); - final Set modelTags = new HashSet<>(meetingTags); - return new Meeting(modelTitle, modelLocation, modelStart, modelEnd, modelAttendees, modelTags); + return new Meeting(modelTitle, modelLocation, modelStart, modelEnd, modelAttendees, modelTags, modelStatus); } } diff --git a/src/main/java/seedu/address/ui/MeetingCard.java b/src/main/java/seedu/address/ui/MeetingCard.java index 50b1eb487fa..242de1f7d44 100644 --- a/src/main/java/seedu/address/ui/MeetingCard.java +++ b/src/main/java/seedu/address/ui/MeetingCard.java @@ -50,6 +50,8 @@ public class MeetingCard extends UiPart { private Label end; @FXML private FlowPane tags; + @FXML + private Label status; /** * Creates a {@code MeetingCode} with the given {@code Meeting} and index to display. @@ -71,5 +73,10 @@ public MeetingCard(Meeting meeting, int displayedIndex) { meeting.getTags().stream() .sorted(Comparator.comparing(tag -> tag.tagName)) .forEach(tag -> tags.getChildren().add(new Label(tag.tagName))); + if (meeting.getStatus().isComplete) { + status.setText("[COMPLETE]"); + } else { + status.setText(""); + } } } diff --git a/src/main/resources/view/MeetingScheduleCard.fxml b/src/main/resources/view/MeetingScheduleCard.fxml index 9b20050fec1..330e62b9bf7 100644 --- a/src/main/resources/view/MeetingScheduleCard.fxml +++ b/src/main/resources/view/MeetingScheduleCard.fxml @@ -28,6 +28,9 @@