diff --git a/ModuleModelClassDiagram.png b/ModuleModelClassDiagram.png new file mode 100644 index 00000000000..f87f85bf41e Binary files /dev/null and b/ModuleModelClassDiagram.png differ diff --git a/README.adoc b/README.adoc index e36efe534bb..9972b4884a0 100644 --- a/README.adoc +++ b/README.adoc @@ -1,36 +1,35 @@ -= Address Book (Level 3) += TA-Tracker ifdef::env-github,env-browser[:relfileprefix: docs/] -https://travis-ci.org/se-edu/addressbook-level3[image:https://travis-ci.org/se-edu/addressbook-level3.svg?branch=master[Build Status]] -https://ci.appveyor.com/project/damithc/addressbook-level3[image:https://ci.appveyor.com/api/projects/status/3boko2x2vr5cc3w2?svg=true[Build status]] -https://coveralls.io/github/se-edu/addressbook-level3?branch=master[image:https://coveralls.io/repos/github/se-edu/addressbook-level3/badge.svg?branch=master[Coverage Status]] -https://www.codacy.com/app/damith/addressbook-level3?utm_source=github.com&utm_medium=referral&utm_content=se-edu/addressbook-level3&utm_campaign=Badge_Grade[image:https://api.codacy.com/project/badge/Grade/fc0b7775cf7f4fdeaf08776f3d8e364a[Codacy Badge]] - +https://travis-ci.org/AY1920S2-CS2103T-W17-4/main[image:https://travis-ci.org/AY1920S2-CS2103T-W17-4/main.svg?branch=master[Build Status]] +https://ci.appveyor.com/project/PotatoCombat/main[image:https://ci.appveyor.com/api/projects/status/hj9hoqnof01ge3pp/branch/master?svg=true[Build status]] +https://coveralls.io/github/AY1920S2-CS2103T-W17-4/main?branch=master[image:https://coveralls.io/repos/github/AY1920S2-CS2103T-W17-4/main/badge.svg?branch=master[Coverage Status]] +image:https://api.codacy.com/project/badge/Grade/a87445f769c04ad3863dad750abbb321["Codacy code quality", link="https://www.codacy.com/gh/AY1920S2-CS2103T-W17-4/main?utm_source=github.com&utm_medium=referral&utm_content=AY1920S2-CS2103T-W17-4/main&utm_campaign=Badge_Grade"] ifdef::env-github[] image::docs/images/Ui.png[width="600"] endif::[] ifndef::env-github[] -image::images/Ui.png[width="600"] +image::docs/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. +* This is a desktop personal income and task management application for NUS Computing Teaching Assistants (TAs). +* It is intended for TAs who want to be able to track and manage all of their claimable hours of teaching in NUS. == Site Map -* <> -* <> -* <> -* <> -* <> +* https://github.com/AY1920S2-CS2103T-W17-4/main/blob/master/docs/UserGuide.adoc[User Guide] +* https://github.com/AY1920S2-CS2103T-W17-4/main/blob/master/docs/DeveloperGuide.adoc[Developer Guide] +* https://github.com/AY1920S2-CS2103T-W17-4/main/blob/master/docs/AboutUs.adoc[About Us] +* https://github.com/AY1920S2-CS2103T-W17-4/main/blob/master/docs/ContactUs.adoc[Contact Us] == Acknowledgements * Some parts of this sample application were inspired by the excellent http://code.makery.ch/library/javafx-8-tutorial/[Java FX tutorial] by _Marco Jakob_. * Libraries used: https://openjfx.io/[JavaFX], https://github.com/FasterXML/jackson[Jackson], https://github.com/junit-team/junit5[JUnit5] +* TA-Tracker is a brownfield project based on the https://github.com/nus-cs2103-AY1920S2/addressbook-level3[AddressBook-Level3] + project created by https://se-education.org[SE-EDU initiative]. == Licence : link:LICENSE[MIT] diff --git a/RecurringSessionsActivityDiagram.png b/RecurringSessionsActivityDiagram.png new file mode 100644 index 00000000000..6ab92483cea Binary files /dev/null and b/RecurringSessionsActivityDiagram.png differ diff --git a/StudentTabClassDiagram.png b/StudentTabClassDiagram.png new file mode 100644 index 00000000000..8b94993aa08 Binary files /dev/null and b/StudentTabClassDiagram.png differ diff --git a/build.gradle b/build.gradle index 93029ef8262..7751ee663ff 100644 --- a/build.gradle +++ b/build.gradle @@ -8,6 +8,7 @@ plugins { id 'java' id 'jacoco' id 'checkstyle' + id 'pmd' id 'com.github.kt3k.coveralls' version '2.4.0' id 'com.github.johnrengelman.shadow' version '4.0.4' id 'org.asciidoctor.convert' version '1.5.6' @@ -15,7 +16,7 @@ plugins { } // Specifies the entry point of the application -mainClassName = 'seedu.address.Main' +mainClassName = 'tatracker.Main' sourceCompatibility = JavaVersion.VERSION_11 targetCompatibility = JavaVersion.VERSION_11 @@ -41,6 +42,29 @@ test { useJUnitPlatform() } +apply plugin: 'findbugs' +findbugs { + ignoreFailures = false + toolVersion = "3.0.1" + sourceSets=[sourceSets.main] + reportsDir = file("$project.buildDir/reports/findbugs") + effort = "max" +} +tasks.withType(FindBugs) { + reports { + xml.enabled false + html.enabled true + } +} + + +pmd { + toolVersion = '6.22.0' + ignoreFailures = true + sourceSets = [sourceSets.main] + reportsDir = file("$project.buildDir/reports/pmd") +} + dependencies { String jUnitVersion = '5.4.0' String javaFxVersion = '11' @@ -67,7 +91,7 @@ dependencies { } shadowJar { - archiveName = 'addressbook.jar' + archiveName = 'TaTracker.jar' destinationDir = file("${buildDir}/jar/") } @@ -133,8 +157,8 @@ asciidoctor { idprefix: '', // for compatibility with GitHub preview idseparator: '-', 'site-root': "${sourceDir}", // must be the same as sourceDir, do not modify - 'site-name': 'AddressBook-Level3', - 'site-githuburl': 'https://github.com/se-edu/addressbook-level3', + 'site-name': 'TA-Tracker', + 'site-githuburl': 'https://github.com/AY1920S2-CS2103T-W17-4/main', 'site-seedu': true, // delete this line if your project is not a fork (not a SE-EDU project) ] diff --git a/claims.png b/claims.png new file mode 100644 index 00000000000..771541cc927 Binary files /dev/null and b/claims.png differ diff --git a/docs/AboutUs.adoc b/docs/AboutUs.adoc index 458e6134f45..b7a007b3af6 100644 --- a/docs/AboutUs.adoc +++ b/docs/AboutUs.adoc @@ -4,53 +4,53 @@ :imagesDir: images :stylesDir: stylesheets -AddressBook - Level 3 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.}_ + -{empty} + +Acknowledgements: Based on the AddressBook-Level3 project created by https://se-education.org[SE-EDU initiative]. + + 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]] [<>] +=== GABRIEL BENEDICT TEO JIAN CHENG +image::potatocombat.png[flip = "90", width = "150", align = "left"] +{empty} [https://github.com/PotatoCombat[github]] [<>] -Role: Project Advisor +Role: Project Lead + +Responsibilities: Scheduling and tracking ''' -=== John Roe -image::lejolly.jpg[width="150", align="left"] -{empty}[http://github.com/lejolly[github]] [<>] +=== AAKANKSHA RAI +image::aakanksha-rai.png[width="150", align="left"] +{empty}[https://github.com/aakanksha-rai[github]] [<>] -Role: Team Lead + -Responsibilities: UI +Role: Developer + +Responsibilities: In charge of Student View, PlantUML Expert, Deliverables and deadlines ''' -=== Johnny Doe -image::yijinl.jpg[width="150", align="left"] -{empty}[http://github.com/yijinl[github]] [<>] +=== CHUA YI JING +image::chuayijing.png[width="150", align="left"] +{empty}[https://github.com/chuayijing[github]] [<>] Role: Developer + -Responsibilities: Data +Responsibilities: Testing ''' -=== Johnny Roe -image::m133225.jpg[width="150", align="left"] -{empty}[http://github.com/m133225[github]] [<>] +=== FATIN NABILAH BTE SUHAIMI +image::fatin99.png[width="150", align="left] +{empty}[https://github.com/fatin99[github]] [<>] Role: Developer + -Responsibilities: Dev Ops + Threading +Responsibilities: Documentation and Code Quality ''' -=== Benson Meier -image::yl_coder.jpg[width="150", align="left"] -{empty}[http://github.com/yl-coder[github]] [<>] +=== HUANG HAOYI +image::eclmist.png[width="150", align="left"] +{empty}[https://github.com/Eclmist[github]] [<>] Role: Developer + -Responsibilities: UI +Responsibilities: Integration ''' diff --git a/docs/ContactUs.adoc b/docs/ContactUs.adoc index 81be279ef6d..c58a512093c 100644 --- a/docs/ContactUs.adoc +++ b/docs/ContactUs.adoc @@ -2,6 +2,20 @@ :site-section: ContactUs :stylesDir: stylesheets -* *Bug reports, Suggestions* : Post in our https://github.com/se-edu/addressbook-level3/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` +== Bug reports, Suggestions + +Post in our https://github.com/AY1920S2-CS2103T-W17-4/main/issues[issue tracker] if you noticed bugs or have suggestions on how to improve. + +== Email us + +You can also reach us at the following emails: + +Aakanksha Rai: + +`rai.aakanksha [at] gmail.com` + +Chua Yi Jing: + +`yijing.chua [at] gmail.com` + +Fatin Nabilah Bte Suhaimi: + +`fatinnbs [at] gmail.com` + +Gabriel Benedict Teo Jian Cheng: + +`gtforce [at] gmail.com` + +Huang Haoyi: + +`samuel.huanghaoyi [at] gmail.com` diff --git a/docs/DeveloperGuide.adoc b/docs/DeveloperGuide.adoc index 3d65905a853..cb79349b2ed 100644 --- a/docs/DeveloperGuide.adoc +++ b/docs/DeveloperGuide.adoc @@ -1,9 +1,13 @@ -= AddressBook Level 3 - Developer Guide += TA-Tracker +- Developer Guide :site-section: DeveloperGuide :toc: :toc-title: :toc-placement: preamble +:toclevels: 3 + :sectnums: +:sectnumlevels: 4 :imagesDir: images :stylesDir: stylesheets :xrefstyle: full @@ -12,61 +16,121 @@ ifdef::env-github[] :note-caption: :information_source: :warning-caption: :warning: endif::[] -:repoURL: https://github.com/se-edu/addressbook-level3/tree/master - -By: `Team SE-EDU`      Since: `Jun 2016`      Licence: `MIT` +:repoURL: https://github.com/AY1920S2-CS2103T-W17-4/main/tree/master + +By: `Team W17-4` Since: `Jan 2020` Licence: `MIT` + +== Introduction + +TA-Tracker +is a productivity tool made for *NUS School of Computing (SoC) Teaching Assistants (TAs)* +who want to be able to track and manage their *students* and *claimable +hours* in one place. + +Teaching Assistants at SoC have to keep a track of: + +* claimable hours so that they can fill up the TSS Claims form at the end of each semester. +* teaching-related sessions with claimable hours +* students and their contact information +* students' participation marks +* notes for certain students (such as recommendations) + +Rather than managing this through several excel +spreadsheets and notes, TA-Tracker +allows TAs to track everything in a single, convenient-to-use +platform. + +In particular, TA-Tracker +is a *Command Line Interface (CLI)* application packaged +with a *Graphical User Interface (GUI)*. This means that users are expected to interact +with the TA-Tracker +mainly through the command line, and each command executed will +evoke a visual response in the TA-Tracker +. + +Any help on the development of the TA-Tracker +would be greatly appreciated, and there +are a couple of ways that you can do so: + +* contribute to TA-Tracker +'s code base by expanding its features +* help us improve test coverage +* propose and implement improvements on current features + +This guide aims to kick-start your journey as a contributor to the TA-Tracker +by getting +you up to speed with how TA-Tracker +'s codebase and inner workings function. It also +hopes to serve as a useful reference to current contributors in times of confusion or +when faced with difficulties. == Setting up -Refer to the guide <>. +You can refer to the guide <>. == Design +TA-Tracker +has been designed with `Object-Oriented Programming` +principles in mind. We also attempted to use `Defensive Programming` wherever +possible. This section serves to give a description of the +major components in the architecture of TA-Tracker +. Subsequent sections +provide more information on the inner workings of individual components. + [[Design-Architecture]] === Architecture .Architecture Diagram image::ArchitectureDiagram.png[] -The *_Architecture Diagram_* given above explains the high-level design of the App. Given below is a quick overview of each component. +The *_Architecture Diagram_* given above explains the high-level design of the TA-Tracker +. +Given below is a quick overview of each component. [TIP] The `.puml` files used to create diagrams in this document can be found in the link:{repoURL}/docs/diagrams/[diagrams] folder. Refer to the <> to learn how to create and edit diagrams. -`Main` has two classes called link:{repoURL}/src/main/java/seedu/address/Main.java[`Main`] and link:{repoURL}/src/main/java/seedu/address/MainApp.java[`MainApp`]. It is responsible for, +`Main` has two classes called link:{repoURL}/src/main/java/tatracker/Main.java[`Main`] and link:{repoURL}/src/main/java/tatracker/MainApp.java[`MainApp`]. It is responsible for, -* At app launch: Initializes the components in the correct sequence, and connects them up with each other. -* At shut down: Shuts down the components and invokes cleanup method where necessary. +* At app launch: Initializing the components in the correct sequence, and connects them up with each other. +* At shut down: Shutting down the components and invoking cleanup methods where necessary. -<> represents a collection of classes used by multiple other components. +<> represents a collection of classes used by the other components. The following class plays an important role at the architecture level: -* `LogsCenter` : Used by many classes to write log messages to the App's log file. +* `LogsCenter` : Used by many classes to write log messages to the TA-Tracker +'s log file. The rest of the App consists of four components. -* <>: The UI of the App. -* <>: The command executor. -* <>: Holds the data of the App in-memory. +* <>: The UI of the TA-Tracker +. +* <>: Handles execution of TA-Tracker +commands. +* <>: Organises the data of the TA-Tracker +into different sections. * <>: Reads data from, and writes data to, the hard disk. Each of the four components -* Defines its _API_ in an `interface` with the same name as the Component. +* Defines its *API* in an `interface` with the same name as the Component. * Exposes its functionality using a `{Component Name}Manager` class. -For example, the `Logic` component (see the class diagram given below) defines it's API in the `Logic.java` interface and exposes its functionality using the `LogicManager.java` class. +For example, the `Logic` component (see the _Class Diagram_ given below) defines its API in the `Logic` interface +and exposes its functionality using the `LogicManager` class. -.Class Diagram of the Logic Component -image::LogicClassDiagram.png[] +.Simplified Class Diagram of the Logic Component +image::LogicClassDiagram1.png[] [discrete] ==== How the architecture components interact with each other -The _Sequence Diagram_ below shows how the components interact with each other for the scenario where the user issues the command `delete 1`. +The _Sequence Diagram_ below shows how the components interact with each other +for the scenario where the user enters the command `session delete 1`. -.Component interactions for `delete 1` command +.Component interactions for `session delete 1` command image::ArchitectureSequenceDiagram.png[] The sections below give more details of each component. @@ -74,352 +138,2096 @@ The sections below give more details of each component. [[Design-Ui]] === UI component +The _Class Diagram_ below shows how the `UI` components interact with each other. + .Structure of the UI Component image::UiClassDiagram.png[] -*API* : link:{repoURL}/src/main/java/seedu/address/ui/Ui.java[`Ui.java`] +*API* : link:{repoURL}/src/main/java/tatracker/ui/Ui.java[`Ui.java`] -The UI consists of a `MainWindow` that is made up of parts e.g.`CommandBox`, `ResultDisplay`, `PersonListPanel`, `StatusBarFooter` etc. All these, including the `MainWindow`, inherit from the abstract `UiPart` class. +The UI consists of a `MainWindow` that is made up of parts e.g.`CommandBox`, `ResultDisplay`, +`StudentTab`, `StatusBarFooter` etc. The UI also contains 2 more windows, namely: -The `UI` component uses JavaFx UI framework. The layout of these UI parts are defined in matching `.fxml` files that are in the `src/main/resources/view` folder. For example, the layout of the link:{repoURL}/src/main/java/seedu/address/ui/MainWindow.java[`MainWindow`] is specified in link:{repoURL}/src/main/resources/view/MainWindow.fxml[`MainWindow.fxml`] +. the `HelpWindow` and +. the `StatisticsWindow` + +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/tatracker/ui/MainWindow.java[`MainWindow`] is +specified in link:{repoURL}/src/main/resources/view/MainWindow.fxml[`MainWindow.fxml`] The `UI` component, * Executes user commands using the `Logic` component. * Listens for changes to `Model` data so that the UI can be updated with the modified data. +==== Tabs + +The _Class Diagram_ below shows how the components in the `Student Tab` interact with each other. + +.Structure of the Student Tab Component +image::StudentTabClassDiagram.png[] + +[NOTE] +==== +[horizontal] +All the `ListPanels` and `Cards` inherit from the abstract `UiPart` class. +==== + +The UI contains 3 `tabs`: + +. The `Student Tab` +. The `Session Tab` +. The `Claims Tab` + +Each of these tabs consist of one or more List Panels (e.g. `StudentListPanel`) and its +respective Card (e.g. `StudentCard`). In each List Panel, the the `Graphics` component of +each of the List Cells is defined by the respective Card. + +The other 2 `Tabs` follow the same structure as the _Class Diagram_ above. + [[Design-Logic]] === Logic component +The `Logic` component of `TA-Tracker +`: + +* processes user inputs into different `Command` objects, +* executes these `Command` objects to interact with the `Model` component, and +* saves data in the `Storage` component. + +==== Logic Structure +The following _Class Diagram_ shows a simplified view of the structure of the `Logic` component . + [[fig-LogicClassDiagram]] .Structure of the Logic Component -image::LogicClassDiagram.png[] +image::LogicClassDiagram1.png[] *API* : -link:{repoURL}/src/main/java/seedu/address/logic/Logic.java[`Logic.java`] +link:{repoURL}/src/main/java/tatracker/logic/Logic.java[`Logic.java`] + +In the figure above, you can see that: + +. `Logic` behaves as a Façade class between the different `TA-Tracker +` components. +. `LogicManager` is the main driver class behind the logic in `TA-Tracker +` +. `LogicManager` interacts with classes in the `Model` and `Storage` component. +. `TA-Tracker +` logic is organised into *commands* and *parsers*. +. `TaTrackerParser` is the *main parser*. +. A `Command` can interact with classes in the `Model` component. + +==== Logic Hierarchy + +The following _Class Diagram_ shows how the `commands` and `parsers` are organised. + +.Structure of commands and the hierarchy of parsers +image::LogicClassDiagram2.png[] + +[NOTE] +==== +[horizontal] +* `X` is the category name for a group of commands + +(e.g. `Student`, `Session`, `Module`). +* `XY` is the category name for actions specific to a group of commands + +(e.g. `AddStudent`, `EditSession`, `DeleteModule`). +==== + +[NOTE] +==== +[horizontal] +* `Prefixes` contains some `Prefix` constants that are shared among classes in the *Logic component*. +* The *red arrow* shows that each command knows about the `Prefix` constants stored in `Prefixes`. + +==== + +In addition to Figure 6., in the figure above, you can see that: + +. There is a *hierarchy* of parsers, starting from `TaTrackerParser`. +. Each group of parsers have been split into *smaller packages* (named as `X` in the diagram) +within the package of parsers. +. Each `Command` has a `Parser` that creates it from a user input. +. These `Command` classes are placed in *smaller packages* (with the same name `X`) within the package of commands. +. A `Command` produces a `CommandResult` when executed by `LogicManager`. +* This will modify the `Model` internally (e.g. adding a student). +. The resulting `CommandResult` encapsulates the feedback that needs to be passed to the `Ui`. + +This feedback includes: +* showing messages in the `UI`, and +* instructing the `Ui` to perform certain actions (e.g. displaying the `HelpWindow`). + +==== +[horizontal] +In most cases, there are *two levels* of parsing before a command is created + +(i.e. `XCommandParser` passes the parsing to `XYCommandParser`). + +However there are some cases where only *one level* of parsing is needed + +(e.g. for the `help`, `report`, and `exit` commands). + +.Skipping the second layer of parsers +image::LogicClassDiagram3.png[,500] -. `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). -. The result of the command execution is encapsulated as a `CommandResult` object which is passed back to the `Ui`. -. In addition, the `CommandResult` object can also instruct the `Ui` to perform certain actions, such as displaying help to the user. +These command parsers will immediately create the respective `Command`, +*skipping the second layer* of parsers (represented by the *red arrow* in the figure above). +==== -Given below is the Sequence Diagram for interactions within the `Logic` component for the `execute("delete 1")` API call. +==== Overview of Commands -.Interactions Inside the Logic Component for the `delete 1` Command -image::DeleteSequenceDiagram.png[] +The following _Class Diagram_ illustrates how all `commands` have been packaged under their respective categories. -NOTE: The lifeline for `DeleteCommandParser` should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram. +.Structure of commands in different categories +image::CommandsPackageDiagram1.png[] + +These packages depend on the `Command` class since they contain commands that inherit from the `Command` class (not shown above). + +In addition, the following `Class Diagram` shows the information that a `command` should have. + +.Internal structure of all commands +image::CommandsPackageDiagram2.png[,500] + +. In each package, all commands should have their own `CommandDetails`. +. `CommandDetails` store all the information that each command should have, + +(e.g. their `commandWord` and `usage` message). +. For their `commandWord` and `sub word`, commands may use constants in `CommandWords` to avoid repetition + +(e.g. "add", "delete", "edit"). +. The `CommandDictionary`, stores a list of `CommandDetails` for all the commands. +. The `CommandDictionary` is used for: +* *syntax highlighting*, and +* listing information in the *help window* + +==== Overview of Parsers + +In a similar way, all *parsers* have been packaged under the same respective categories. + +.Structure of parsers in different categories +image::ParserPackageDiagram1.png[] + +These packages depend on the `Parser` interface since they contain command parsers that inherit from the `Parser` interface +(not shown above). + +In addition, the following `Class Diagram` shows the information that a `command parser` should have. + +.Internal structure of all command parsers +image::ParserPackageDiagram2.png[,300] + +. All command parsers use some `Prefix` constants defined in `Prefixes`. +. `PrefixDetails` adds more information to a `Prefix` + +(e.g. their `constraint` message and a list of `examples`). +. A `PrefixDetail` has a `Predicate` to validate the user input arguments. +. The `PrefixDictionary` stores two lists of `Prefix` objects: +** `parameters` - a list of command parameters, and +** `optionals` - a list of optional parameters for the same command. +. The `PrefixDictionary` stores a list of `PrefixDetails` for all the `Prefix` objects in `parameters` and `optionals`. +. The `PrefixDictionary` is used for: +* *syntax highlighting*, and +* listing information in the *help window* + +==== Example Logic Sequence +Given below is the _Sequence Diagram_ for interactions within the `Logic` component for the `execute("group add m/CS2103 g/G03 t/lab")` API call. + +.Interactions Inside the Logic Component for the `group add m/CS2103 g/G03 t/lab` Command +image::AddGroupSequenceDiagram.png[] + +[NOTE] +==== +[horizontal] +* The lifeline for `GroupCommandParser` and `AddGroupCommandParser` should end at the +destroy marker (X) but due to a limitation of *PlantUML*, the lifeline reaches the end of diagram. + +* Since the purpose of this diagram is to show the interactions within the `Logic` component, +irrelevant interactions with the `Model` component have been omitted. +==== + +==== Design Considerations + +===== Rationale behind `PrefixDetails` not inheriting from the `Prefix` class +The following _Class Diagrams_ compare the structure of `commands` and their respective `parsers.` + +[.clearfix] +-- +[.left] +.Structure of a `command parser` +image::ParserPackageDiagram2.png[,300] +[.left] +.Structure of a `command` +image::CommandsPackageDiagram2.png[,500] +-- + +The difference between the *commands* and *parsers* is that the *commands* store their own `CommandDetails`, +while the *parsers* do not store any `PrefixDetails`. Instead, they use a number of `Prefix` objects to +parse user inputs. This is because they do not need the extra information stored in `PrefixDetails`. + +`PrefixDetails` adds more information to a `Prefix` instead of extending it. +Therefore, it can be detached from the command parsers without changing the `Prefix` constants in `Prefixes`. + +===== Moving `Prefixes` to the *Commands* package + +In future versions, `Prefixes` could be stored in the *Commands* package, +since each command will need to know all the `Prefix` that they are using. + +The following `Class Diagrams` shows how the command parsers can obtain all the required `Prefix` from the respective `command.` + +.Improving the Logic component +image::LogicClassDiagram4.png[,500] + +In the diagram above: + +* The *red arrow* will be removed to show that the command parsers no longer need information in `Prefixes`. +* `Prefixes` will be moved into the *commands* package. [[Design-Model]] === Model component +The following _Class Diagram_ shows how the different `Model` components interact with each other. + .Structure of the Model Component image::ModelClassDiagram.png[] -*API* : link:{repoURL}/src/main/java/seedu/address/model/Model.java[`Model.java`] +*API* : link:{repoURL}/src/main/java/tatracker/model/Model.java[`Model.java`] 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. -* does not depend on any of the other three components. +* Stores a `UserPref` object that represents the user's preferences +* Stores the TA-Tracker +data +* Exposes 5 unmodifiable `ObservableList<>` objects: +. `filteredStudentList`, an that contains all the `Students` in the TA-Tracker -[NOTE] -As a more OOP model, we can store a `Tag` list in `Address Book`, which `Person` can reference. This would allow `Address Book` to only require one `Tag` object per unique `Tag`, instead of each `Person` needing their own `Tag` object. An example of how such a model may look like is given below. + - + -image:BetterModelClassDiagram.png[] +. `filteredSessionList`, an that contains all the `Sessions` in the TA-Tracker +that have *not* been marked as done +. `filteredDoneSessionList`, an that contains all the `Sessions` in the TA-Tracker +that *have been marked as done* +. `filteredModuleList`, an that contains all the `Modules` in the TA-Tracker + +. `filteredGroupList`, an that contains all the `Groups` in the TA-Tracker + +* These lists 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 + +The following _Class Diagram_ shows the relationship between the different classes +in the `Model` component. + +.Model Components - Class Diagram +image::ModelComponentsClassDiagram.png[] + +==== Example of Model Usage + +The following _Object Diagram_ shows an example of the relationship between the different `Model` objects. +This example is based on the state of TA-Tracker +when it is first run (without any user data). + +.Model Components - Object Diagram +image::ModelObjectDiagram.png[] [[Design-Storage]] === Storage component +The following `Class Diagram` is a simplified view of the `Storage` component. -.Structure of the Storage Component -image::StorageClassDiagram.png[] +.Simplified structure of the Storage Component +image::StorageClassDiagram1.png[] -*API* : link:{repoURL}/src/main/java/seedu/address/storage/Storage.java[`Storage.java`] +*API* : link:{repoURL}/src/main/java/tatracker/storage/Storage.java[`Storage.java`] The `Storage` component, * can save `UserPref` objects in json format and read it back. -* can save the Address Book data in json format and read it back. +* can save the TA-Tracker +data in json format and read it back. + +==== Saved Data +The following `Class Diagram` is a breakdown of the data managed by the `Storage` component. + +.Structure of the data stored by TA-Tracker + +image::StorageClassDiagram2.png[] + +`TA-Tracker +` saves the following data: + +* a list of `Module` objects representing the modules that the user is assisting. + ++ +Within each module, there is: + +** a list of `Session`, representing the `done sessions` that the user has completed for that module. +** a list of `Group`, representing the groups that the user is +in charge of, such as a tutorial or lab. + ++ +Within each group, there is: + +*** a list of `Student`, representing the students enrolled in +the respective groups. + +* a separate list of `Session` objects representing the sessions that the user has scheduled in the future. [[Design-Commons]] === Common classes -Classes used by multiple components are in the `seedu.addressbook.commons` package. +Classes used by multiple components are in the `tatracker.commons` package. == Implementation This section describes some noteworthy details on how certain features are implemented. -// tag::undoredo[] -=== [Proposed] Undo/Redo feature -==== Proposed Implementation +[[Implementation-Goto]] +=== Goto Command + +==== Description + +The `goto` command has been implemented to allow users to programmatically switch through the `tabs` using +the command line, rather than clicking on the tab headers. + +The command can be utilised by inputing `goto TAB_NAME`. +`TAB_NAME` is a compulsory parameter for the user. + +==== Implementation +This section describes the implementation of the `goto` command. + +The following _Sequence Diagram_ shows the interactions between the `Logic` and `UI` components of +the TA-Tracker +when the user enters the command `goto claims`. + +.Sequence Diagram for Goto Claims Command +image::GotoSequenceDiagram.png[] + +Given below is an example scenario where the user enters a command to switch to the `Claims Tab`. + +. The user command is passed through the `LogicManager` to `TaTrackerParser`. +`TaTrackerParser` checks the input arguments and identify the String keywords. + +. The `TaTrackerParser` sees that the command is a `GotoCommand` and passes the command +to the `GotoCommandParser`. + +. The `GotoCommandParser` creates a `GotoCommand` object +with the relevant keywords. + +. `LogicManager` calls `GotoCommand` 's execute method. + +. The `GotoCommand` object checks whether any of the keywords given by the user +matches the existing tab headers. If it does, the `GotoCommand` returns a `CommandResult` +with a success message. + +. `MainWindow` selects the `ClaimsTab` in the `TabPane` to switch to the `Claims Tab` + +[[StudentView]] +=== Student View +*Student View* is used to display all modules, groups and students in the TA-Tracker +. + +Students are a part of *groups* and groups are a part of *modules*. -The undo/redo mechanism is facilitated by `VersionedAddressBook`. -It extends `AddressBook` with an undo/redo history, stored internally as an `addressBookStateList` and `currentStatePointer`. -Additionally, it implements the following operations: +==== Implementation of the Model Framework +The following _Class Diagram_ shows how different classes are related in the +functioning of the *Student View*. -* `VersionedAddressBook#commit()` -- Saves the current address book state in its history. -* `VersionedAddressBook#undo()` -- Restores the previous address book state from its history. -* `VersionedAddressBook#redo()` -- Restores a previously undone address book state from its history. +.Student View - Class Diagram +image::ModuleModelClassDiagram.png[] -These operations are exposed in the `Model` interface as `Model#commitAddressBook()`, `Model#undoAddressBook()` and `Model#redoAddressBook()` respectively. +In the diagram above, you can see that: -Given below is an example usage scenario and how the undo/redo mechanism behaves at each step. +* The `TaTracker` class contains a `UniqueModuleList` which helps it keep track +of the different *modules* the user is teaching. -Step 1. The user launches the application for the first time. The `VersionedAddressBook` will be initialized with the initial address book state, and the `currentStatePointer` pointing to that single address book state. +* Each `Module` contains a `UniqueGroupList`. -image::UndoRedoState0.png[] +* The `UniqueGroupList` contains a list of all the *groups of a module* that the user +is teaching. -Step 2. The user executes `delete 5` command to delete the 5th person in the address book. The `delete` command calls `Model#commitAddressBook()`, causing the modified state of the address book after the `delete 5` command executes to be saved in the `addressBookStateList`, and the `currentStatePointer` is shifted to the newly inserted address book state. +* Each `Group` contains a `UniqueStudentsList` that contains the *students in that group*. -image::UndoRedoState1.png[] +==== +*Alternative Implementation* -Step 3. The user executes `add n/David ...` to add a new person. The `add` command also calls `Model#commitAddressBook()`, causing another modified address book state to be saved into the `addressBookStateList`. +* An alternative implementation would be to have a single `UniqueModuleList` to store +all modules, a `List` to store all groups and a `List` to store all students. -image::UndoRedoState2.png[] +* We would then have to filter by module code and/or group code to show the appropriate +groups and students. + +* This would require students to keep track of which group and which module they're +a part of. Similarly, groups would have to keep a track of the students it contains. +This would create a cyclic dependency (which could be solved using an association class). + +* The `List` of groups could contain multiple groups with the same group code as group code +is only unique within a module. Group codes can be shared across modules. + +* While this implementation would make it easier to generate a report at the end of +the semester (explained towards the end of the guide), it would require more commands +and the creation of association classes which would unnecessarily complicate the model. +That is why we decided to stick to our current implementation. + +==== + +The following _Class Diagram_ shows how different classes are related in the functioning of a `Student` Object. + +.Structure of the Student Component +image::StudentClassDiagram.png[] + +*API* : link:{repoURL}/src/main/java/tatracker/model/student/Student.java[`Student.java`] + +The other models (`Module`, `Group` and `Session`) have been implemented in a similar manner. The main difference is that the other models do not +have any `Tags`. [NOTE] -If a command fails its execution, it will not call `Model#commitAddressBook()`, so the address book state will not be saved into the `addressBookStateList`. +As a more `OOP` model, we can store a `Tag` list in `TaTracker`, which `Student` can +reference. This would allow `TaTracker` to only require one `Tag` object per unique +`Tag`, instead of each `Student` needing their own `Tag` object. An example of what +such a model may look like is given below. + + + +image:BetterModelClassDiagram.png[] -Step 4. The user now decides that adding the person was a mistake, and decides to undo that action by executing the `undo` command. The `undo` command will call `Model#undoAddressBook()`, which will shift the `currentStatePointer` once to the left, pointing it to the previous address book state, and restores the address book to that state. +==== Implementation of the Module Add, Delete and Edit Commands -image::UndoRedoState3.png[] +The following _Sequence Diagram_ shows the interactions +between the `Logic` and `Model` components of the TA-Tracker +when the user enters the +command `module add m/CS2103 n/Software Engineering`. + +.Module Add - Sequence Diagram +image::AddModuleSequenceDiagram.png[] [NOTE] -If the `currentStatePointer` is at index 0, pointing to the initial address book state, then there are no previous address book states to restore. The `undo` command uses `Model#canUndoAddressBook()` to check if this is the case. If so, it will return an error to the user rather than attempting to perform the undo. +==== +* This diagram assumes that a module with the module code `CS2103` +exists in the TA-Tracker +. +* The lifeline for `ModuleCommandParser` and `AddModuleCommandParser` should end at the +destroy marker (X) but due to a limitation of *PlantUML*, the lifeline reaches the end of diagram. +==== + +1. `LogicManager` uses the `TaTrackerParser` to first parse the user command. + +2. The `TaTrackerParser` sees that this command is a *module* command and passes the +command to the `ModuleCommandParser`. + +3. The `ModuleCommandParser` sees that this command is an *add* command and passes the +arguments to the `AddModuleCommandParser`. + +4. The `AddModuleCommandParser` creates a `Module` with the given module code and +name. + +5. The `AddModuleCommandParser` then creates an `AddModuleCommand` object with a newly +created module. The parser then returns the `AddModuleCommand` object. -The following sequence diagram shows how the undo operation works: +6. `LogicManager` calls `AddModuleCommand` 's execute method. -image::UndoSequenceDiagram.png[] +7. The `AddModuleCommand` object +checks whether a module with the given module code already exists in *TA-Tracker +* -NOTE: The lifeline for `UndoCommand` should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram. +.. If it does, a command exception is thrown saying that a module with the given module +code already exists in the *TA-Tracker +*. -The `redo` command does the opposite -- it calls `Model#redoAddressBook()`, which shifts the `currentStatePointer` once to the right, pointing to the previously undone state, and restores the address book to that state. +.. If no such module exists, the module is added to the *TA-Tracker +*. + +8. The `AddModuleCommand` returns a `CommandResult` with a success message. + +The command used to delete a module has been implemented in a similar way. The main +difference is that the `DeleteModuleCommand` checks whether an object with the given +module code exists in the TA-Tracker +. If no such module exists, a command exception +is thrown saying that a module with the given module code doesn't exist. If it does +exist, *first all the sessions linked to that module are removed* , then the module +is removed from the TA-Tracker +. + +The `module edit` command has been implemented in a similar manner. + +==== Implementation of the Group Add, Delete and Edit Commands + +A *group* is added to the TA-Tracker +in a similar manner to to how a *module* is added to +the TA-Tracker +. + +The following steps are taken once the _execute_ command of an `AddGroupCommand` object +is called: + +. The `AddGroupCommand` object checks whether the *module* is present in the model of the TA-Tracker +. +.. If it exists, the module is retrieved. +.. If it doesn't exist, an exception is thrown explaining that the module doesn't +exist. +. The `AddGroupCommand` object checks whether a *group* with the same group code as +the new group exists in the module retrieved beforehand. +.. If it doesn't exist, the group is added to the module and a `CommandResult` object +with the success message is returned. +.. If it does exist, an exception is thrown explaining that you can't have two groups +with the same group code in a module. + +The interactions between the `Logic` and `Model` components when adding a group are similar +to the interactions when deleting a group as shown below. + +The following _Sequence Diagram_ shows the interactions between the `logic` and `model` +components when the user inputs the command `group delete m/CS2103 g/G03`. + +.Group Delete - Sequence Diagram +image::DeleteGroupSequenceDiagram.png[] [NOTE] -If the `currentStatePointer` is at index `addressBookStateList.size() - 1`, pointing to the latest address book state, then there are no undone address book states to restore. The `redo` command uses `Model#canRedoAddressBook()` to check if this is the case. If so, it will return an error to the user rather than attempting to perform the redo. +==== +* This diagram is under the case where a group with the group code G03 does exist +in the module with module code CS2103 inside the TA-Tracker +. -Step 5. The user then decides to execute the command `list`. Commands that do not modify the address book, such as `list`, will usually not call `Model#commitAddressBook()`, `Model#undoAddressBook()` or `Model#redoAddressBook()`. Thus, the `addressBookStateList` remains unchanged. +* The lifeline for `GroupCommandParser` and `DeleteGroupCommandParser` should end at the +destroy marker (X) but due to a limitation of *PlantUML*, the lifeline reaches the end of diagram. -image::UndoRedoState4.png[] +* The main difference between the `Module` and `Group` commands is that the `Group` +commands require extra checks to check whether a group with the given group code +exists inside the module with the given module code. +==== -Step 6. The user executes `clear`, which calls `Model#commitAddressBook()`. Since the `currentStatePointer` is not pointing at the end of the `addressBookStateList`, all address book states after the `currentStatePointer` will be purged. We designed it this way because it no longer makes sense to redo the `add n/David ...` command. This is the behavior that most modern desktop applications follow. +1. `LogicManager` uses the `TaTrackerParser` to first parse the user command. -image::UndoRedoState5.png[] +2. The `TaTrackerParser` sees that the command is of type *group* and passes the +command to the `GroupCommandParser`. -The following activity diagram summarizes what happens when a user executes a new command: +3. The `GroupCommandParser` sees that the command is of type *delete* and passes the +arguments to the `DeleteGroupCommandParser`. -image::CommitActivityDiagram.png[] +4. The `DeleteGroupCommandParser` then creates a `DeleteGroupCommand` object and passes +it the module code, group code and group type. The parser then returns the `DeleteGroupCommand` object. -==== Design Considerations +5. `LogicManager` calls `DeleteGroupCommand` 's execute method. The `DeleteGroupCommand` object +checks whether a *module* with the given module code already exists in TA-Tracker +. +If it doesn't, a command exception is thrown saying that a module with the given module +code doesn't exist in the TA-Tracker +. -===== Aspect: How undo & redo executes +6. If the module exists, the `DeleteGroupCommand` then checks whether a group with the +given group code exists within that module. +.. If the group doesn't exist, a command exception is thrown saying that no such group exists. +.. If the group does exist, it is removed from the module. -* **Alternative 1 (current choice):** Saves the entire address book. -** Pros: Easy to implement. -** Cons: May have performance issues in terms of memory usage. -* **Alternative 2:** Individual command knows how to undo/redo by itself. -** Pros: Will use less memory (e.g. for `delete`, just save the person being deleted). -** Cons: We must ensure that the implementation of each individual command are correct. +7. The `DeleteGroupCommand` returns a `CommandResult` with a success message. -===== Aspect: Data structure to support the undo/redo commands +The `group edit` command has been implemented in a similar manner. -* **Alternative 1 (current choice):** Use a list to store the history of address book states. -** Pros: Easy for new Computer Science student undergraduates to understand, who are likely to be the new incoming developers of our project. -** Cons: Logic is duplicated twice. For example, when a new command is executed, we must remember to update both `HistoryManager` and `VersionedAddressBook`. -* **Alternative 2:** Use `HistoryManager` for undo/redo -** Pros: We do not need to maintain a separate list, and just reuse what is already in the codebase. -** 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[] +==== Implementation of the Student Add, Delete and Edit Commands -// tag::dataencryption[] -=== [Proposed] Data Encryption +The following _Sequence Diagram_ shows the interactions that take place +between the `Logic` and `Model` components of the TA-Tracker +when the user enters the +command `student delete id/A0181234G`. -_{Explain here how the data encryption feature will be implemented}_ +.Student Delete - Sequence Diagram +image::DeleteStudentSequenceDiagram.png[] -// end::dataencryption[] +[NOTE] +==== -=== Logging +* This diagram assumes that there is a student with the matric number `A0181234G` +already exists in the TA-Tracker -We are using `java.util.logging` package for logging. The `LogsCenter` class is used to manage the logging levels and logging destinations. +* The lifeline for `StudentCommandParser` and `DeleteStudentCommandParser` should end at the +destroy marker (X) but due to a limitation of *PlantUML*, the lifeline reaches the end of diagram. +==== -* The logging level can be controlled using the `logLevel` setting in the configuration file (See <>) -* The `Logger` for a class can be obtained using `LogsCenter.getLogger(Class)` which will log messages according to the specified logging level -* Currently log messages are output through: `Console` and to a `.log` file. +1. `LogicManager` uses the `TaTrackerParser` to first parse the user command. -*Logging Levels* +2. The `TaTrackerParser` sees that the command is of type *student* and passes the +command to the `StudentCommandParser`. -* `SEVERE` : Critical problem detected which may possibly cause the termination of the application -* `WARNING` : Can continue, but with caution -* `INFO` : Information showing the noteworthy actions by the App -* `FINE` : Details that is not usually noteworthy but may be useful in debugging e.g. print the actual list instead of just its size +3. The `StudentCommandParser` sees that the command is of type *delete* and passes the +arguments to the `DeleteStudentCommandParser`. -[[Implementation-Configuration]] -=== Configuration +4. The `DeleteStudentCommandParser` creates a *Student* with the given matric number, +`A0181234G`. -Certain properties of the application can be controlled (e.g user prefs file location, logging level) through the configuration file (default: `config.json`). +5. The `DeleteStudentCommandParser` then creates a `DeleteStudentCommand` object and passes +it the created student. The parser then returns the `DeleteStudentCommand` -== Documentation +6. `LogicManager` calls `DeleteStudentCommand's` execute method. -Refer to the guide <>. +7. The `DeleteStudentCommand` object +checks whether a student with the given matric number already exists in TA-Tracker -== Testing +.. If it student doesn't exist, a command exception is thrown saying that a student with the given matric number +doesn't exist in the TA-Tracker +.. If the student exists, the `DeleteStudentCommand` object retrieves the student from the +model and removes the student. -Refer to the guide <>. +The `student edit` command has been implemented in a similar manner. -== Dev Ops +The `student add` command has been implemented in a similar way as well. The main +difference is that the `AddStudentCommand` checks whether an object with the given +matric number exists in the TA-Tracker +. If such student exists, a command exception +is thrown saying that a student with the given matric number already exists. -Refer to the guide <>. +==== Implementation of the Sort Group, Module and All Command -[appendix] -== Product Scope +The sort command allows the user to sort the students in the *Student View*. Here are the ways you can use the sort command: -*Target user profile*: +. alphabetically +. rating (in ascending or descending order) +. matriculation number -* 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 +The sort command can be used in three ways: -*Value proposition*: manage contacts faster than a typical mouse/GUI driven app +1. `sort group g/GROUP_CODE m/MODULE_CODE t/TYPE` : This sorts all the students of the given +group in the given module by type `TYPE`. -[appendix] -== User Stories +2. `sort module g/MODULE_CODE t/TYPE` : This sorts all the students of all the groups in the +given module by type `TYPE`. -Priorities: High (must have) - `* * \*`, Medium (nice to have) - `* \*`, Low (unlikely to have) - `*` +3. `sort all t/TYPE` : This sorts all students of all groups of all the modules in the +TA-Tracker +by the type `TYPE` -[width="59%",cols="22%,<23%,<25%,<30%",options="header",] -|======================================================================= -|Priority |As a ... |I want to ... |So that I can... -|`* * *` |new user |see usage instructions |refer to instructions when I forget how to use the App +[NOTE] +==== +* `TYPE` here could mean any of the following: +** `alpha`, `alphabetical` or `alphabetically` to sort alphabetically. +** `rating asc` to sort by rating in ascending order. +** `rating desc` to sort by rating in descending order. +** `matric` to sort by matriculation number. +==== -|`* * *` |user |add a new person | +Since these `Sort` commands function differently but use the same parser, the structure shown in the following +_Class Diagram_ is used. -|`* * *` |user |delete a person |remove entries that I no longer need +.Sort Commands - Class Diagram +image::SortCommandsClassDiagram.png[] -|`* * *` |user |find a person by name |locate details of persons without having to go through the entire list +Since the different commands use the same parser, the `SortCommandParser` needs to: -|`* *` |user |hide <> by default |minimize chance of someone else seeing them by accident +. check which prefixes have been passed and +. return the appropriate command accordingly. -|`*` |user with many persons in the address book |sort persons by name |locate a person easily -|======================================================================= +The following _Activity Diagram_ shows the steps the `SortCommandParser` takes once +its _parse_ method is called (assuming that no exception is thrown). -_{More to be added}_ +.SortCommandParser - Activity Diagram +image::SortParserActivityDiagram.png[] -[appendix] -== Use Cases +[NOTE] +==== +* The final else clause would throw an error explaining that the command format is +invalid. But due to a limitation in *PlantUML's* beta version of showing activity diagrams, +we were unable to indicate exceptions thrown in the proper way and decided to mention +it here instead. -(For all use cases below, the *System* is the `AddressBook` and the *Actor* is the `user`, unless specified otherwise) +* Command word here refers to `all`, `module` or `group`. -[discrete] -=== Use case: Delete person +* If the user enters the `sort` command with a command word but doesn't include the +appropriate parameters with the correct prefixes, a command exception is thrown. +==== -*MSS* +The following _Sequence Diagram_ illustrates the interactions between the `Logic` and +`Model` components when the user enters the command `sort all t/matric`. -1. User requests to list persons -2. AddressBook shows a list of persons -3. User requests to delete a specific person in the list -4. AddressBook deletes the person -+ -Use case ends. +.Sort - Sequence Diagram +image::SortAllSequenceDiagram.png[] -*Extensions* +[NOTE] +==== +* The lifeline for `SortCommandParser` should end at the +destroy marker (X) but due to a limitation of *PlantUML*, the lifeline reaches the end of diagram. -[none] -* 2a. The list is empty. -+ -Use case ends. +* The `SortCommandParser`, which creates `Sort` commands, is different from the other +command parsers. While the other commands have another level of parsing (such as the +`ModuleCommandParser` for `Module` commands), the `SortCommandParser` +creates all the different Sort commands within itself. +==== -* 3a. The given index is invalid. -+ -[none] -** 3a1. AddressBook shows an error message. -+ -Use case resumes at step 2. +1. `LogicManager` uses the `TaTrackerParser` to first parse the user command. -_{More to be added}_ +2. The `TaTrackerParser` sees that the command is of type `sort` and passes the +command to the `SortCommandParser`. -[appendix] -== Non Functional Requirements +3. The `SortCommandParser` performs the steps shown in the previous activity diagram +and determines that since the sort command word is `all` , it must create and return a +`SortCommand`. -. Should work on any <> as long as it has Java `11` or above installed. -. Should be able to hold up to 1000 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. +4. `LogicManager` calls `SortCommand` 's execute method. -_{More to be added}_ +5. `SortCommand` checks the type of sorting that is indicated. Since the sort type +is `matric` , it calls `Model` 's `sortModulesByMatricNumber()` command. -[appendix] -== Glossary +6. The `SortCommand` returns a `CommandResult` with a success message. -[[mainstream-os]] Mainstream OS:: -Windows, Linux, Unix, OS-X +//tag::tssview[] +[[SessionView]] +=== Session View -[[private-contact-detail]] Private contact detail:: -A contact detail that is not meant to be shared with others +*Session View* is the term used to refer to the view that contains a list of all sessions +that haven't been completed yet. -[appendix] -== Product Survey +==== Model Framework -*Product Name* +The following _Class diagram_ shows how different classes are related in the functioning +of the *Session View*. -Author: ... +._Class Diagram_ of Session View +image::SessionModelClassDiagram.png[] -Pros: +The TA-Tracker model class contains a `UniqueSessionList` which helps keep track of +all the *sessions* in TA-Tracker +that have *not* been marked as done. -* ... -* ... +==== Implementation of the Session Done Command -Cons: +The following _Sequence Diagram_ shows the sequence of commands that take place between +the `Logic` and `Model` components of the TA-Tracker +when the user enters the command +`session done 1`. -* ... -* ... +._Sequence Diagram_ for Done Session +image::DoneSessionSequenceDiagram.png[] -[appendix] -== Instructions for Manual Testing +1. The `LogicManager` uses the `TaTrackerParser` to first parse the user command. + +2. The `TaTrackerParser` sees that the command is a `Session` command and passes the command +to the `SessionCommandParser`. + +3. The `SessionCommandParser` sees that the command is a `DoneSessionCommand` and passes the +arguments to the `DoneSessionCommandParser`. + +4. The `DoneSessionCommandParser` creates a `DoneSessionCommand` with the given index. + +5. `LogicManager` calls `DoneSessionCommand#execute()` method. + +6. The `DoneSessionCommand` +checks whether the current session called by the user has a recurring period. + + a. If it does, a new session with the updated date will be added to `Model#UniqueSessionList()`. + b. If it does not have a recurring period, it will move on to *Step 6*. -Given below are instructions to test the app manually. +7. The current session will be removed from `Model#UniqueSessionList`. + +8. The updated session list will be displayed to the user. + +The following _Activity Diagram_ describes how TaTracker is updated when a `SessionDone` command is entered. + +.Session Done- Activity Diagram +image::TssActivityDiagram.png[] [NOTE] -These instructions only provide a starting point for testers to work on; testers are expected to do more _exploratory_ testing. +==== +The above diagram assumes that a valid index has been input into the TA-Tracker +during the done session command. +==== -=== Launch and Shutdown +==== Implementation of Session Add, Edit and Delete -. Initial launch +The `session edit` and `session delete` commands have been implemented in a similar manner +to `DoneSessionCommand`. -.. Download the jar file and copy into an empty folder -.. Double-click the jar file + - Expected: Shows the GUI with a set of sample contacts. The window size may not be optimum. -. Saving window preferences +The `session add` command has been implemented in a similar way. The main difference is that the +`SessionAddCommand` checks whether an object with the given module code exists in the TA-Tracker. -.. Resize the window to an optimum size. Move the window to a different location. Close the window. -.. Re-launch the app by double-clicking the jar file. + - Expected: The most recent window size and location is retained. +* If no such module code exists, the session is created successfully. + +* If it doesn’t exist, an exception is thrown saying that the given module code doesn’t exist. + +//tag::claimsview[] + +=== Claims View +*Claims View* refers to the view that contains a list of all the sessions that have +been done. + +==== Model Framework +The following _Class Diagram_ shows how different classes are related in the +functioning of the *Claims View*. + +.Claims View - Class Diagram +image::TssModelClassDiagram.png[] + +The TaTracker model class contains a UniqueDoneSessionList which keeps track of +all the *sessions that have been marked as done*. Each of the sessions must belong to a Module in the UniqueModuleList. + +==== Set Rate Command + +Given below is an example scenario where the user enters the command `setrate 50`. + +. The user command is passed through the `LogicManager` to `TaTrackerParser`. + +. `TaTrackerParser` checks the input arguments and identify the String keywords. + +. The `TaTrackerParser` sees that the command is a type of SetRate and passes the command +to the `SetRateCommandParser`. + +. The `SetRateCommandParser` object checks that the given `RATE` input +by the user is a valid integer. If it is, the `SetRateCommandParser` creates a +`SetRateCommand` object with the relevant integer. + +. `LogicManager` calls `SetRateCommand` 's execute method. + +. `MainWindow` updates the `TotalEarnings` label in the `ClaimsTab` and the `StatisticsWindow` + + +// tag::syntaxhighlighting[] +=== Syntax Highlighting +When a user types a command into the `CommandBox`, their inputs will be highlighted in different colours +as a form of input validation. + +In addition, the `ResultDisplay` will display different messages based on the result of the syntax highlighting. + +The following shows how the `CommandBox` and `ResultDisplay` appear in the `MainWindow` of `TA-Tracker +` + +.The `CommandBox` and the `ResultDisplay` in `TA-Tracker +` +image::syntax-highlighting/SH-CommandBox.png[] + +In the figure above: + +* There is a user input highlighted in `green` in the `CommandBox` +* There is a message in `white` showing in the `ResultDisplay` +* The command being entered is `session edit` +* There are three arguments: `date`, `start time`, and `end time`. + +==== Overview +The following _Class Diagram_ shows how the `Logic` and `UI` components interact with each other to produce the highlighting. + +.Syntax Highlighting - Class Diagram +image::SyntaxHighlightingClassDiagram.png[] + +The `CommandBox`: + +* Uses a `CommandDictionary` to search for valid commands. +* Stores a `CommandDetail` for processing the current command in the user input. +* Stores a `PrefixDictionary` containing the required argument details (`PrefixDetails`) for the current command. +* Uses a `CommandBoxUtil` to scan for valid user inputs. +* Returns feedback to the `ResultDisplay`. + +The `ResultDisplay` displays the given feedback tas a message in the program. + +==== Implementation +The following diagrams show the steps for the different types of syntax highlighting. + +Here are some keywords that will be used to explain the implementation: + +* A `full command word` is a text that identifies a command. + +* An `argument` is a text that contains a `prefix` and a `value`. + +** The `prefix` is the text value of a `Prefix` in the Logic component. +** The `value` is all the text following the `prefix`. + +* The `preamble` is the text (including whitespaces) between the end of the +`full command word`, and the beginning of the first `argument`. + +[NOTE] +==== +Due to the limitations of *PlantUML*, some of the diagrams may not appear as expected. + +In particular, there are issues with representing alternate paths in *PlantUML* BETA Activity Diagrams + +For example: + +. They split into multiple diamonds, instead of all originating from the same diamond. +. They do not converge at a single diamond, and instead skips it altogether. +. They cannot join other branches. + +In particular, the biggest limitation was the third issue. +To overcome this, you will see multiple end nodes in some of the diagrams. +Please assume that they all converge into a single diamond before reaching the end node. + +==== + +===== Step 1. Highlighting a new input +When a new user input is entered in the `CommandBox`, +the syntax highlighting will be reapplied. + +The following _Activity Diagram_ explains how the `full command word` is highlighted, +up until the beginning of the `preamble`. + +.Step 1 - Highlighting a new input +image::SyntaxHighlightingActivityDiagram1.png[] + +Here is the purpose of each partition in the above diagram: + +* `blank` + +When there is no input, +the `ResultDisplay` should not show anything. + +.Blank `ResultDisplay` +image::syntax-highlighting/SH-1-Blank.png[,300] + +* `invalid` + +When there is no matching `full command word`, +the `ResultDisplay` should indicate that a wrong command is entered. + +.Invalid `full command word` +image::syntax-highlighting/SH-1-Invalid.png[,300] + +* `valid` + +When there is a matching `full command word`, +change the highlighting rules for the new command. + +* `no_arguments` + +When `full command word` has just been entered, +the `ResultDisplay` should indicate that a correct command has been entered. + +.`CommandBox` has no `arguments` +image::syntax-highlighting/SH-1-NoArgs.png[,300] + +* `has_arguments` + +After processing the `full command word`, +proceed to step 2. + ++ +NOTE: There should be a rake symbol next to the only activity in this partition. + +.`CommandBox` has `arguments` +image::syntax-highlighting/SH-1-HasArgs.png[,250] + +===== Step 2. Highlighting the preamble +After verifying that the new input has a `full command word`, +the highlighting will also need to be reapplied to the `preamble` and `arguments`. + +The following _Activity Diagram_ explains how the `preamble` is highlighted, +up until the beginning of the first `argument`. + +.Step 2 - Highlighting the preamble +image::SyntaxHighlightingActivityDiagram2.png[] + +Here is the purpose of each partition in the above diagram: + +* `whitespaces` + +Inputs that have trailing whitespaces will be handled separately. + +.Trailing whitespace, coloured in blue for this diagram +image::syntax-highlighting/SH-2-Space.png[,250] + +* `many_whitespaces` + +When there are more than two trailing whitespaces, +the `ResultDisplay` should reshow how to use the command. + +.Many trailing whitespaces, coloured in blue for this diagram +image::syntax-highlighting/SH-2-ManySpace.png[,400] + +* `preamble_error` + +When the input has a `preamble` that the command does not need, +the `ResultDisplay` should indicate that a wrong command is entered. + +.Invalid `preamble` in input +image::syntax-highlighting/SH-2-WrongPreamble.png[,350] + +* `preamble_end` + +After processing the `preamble`, +proceed to step 3. + ++ +NOTE: There should be a rake symbol next to the only activity in this partition. + +.Valid `preamble` in input +image::syntax-highlighting/SH-2-RightPreamble.png[,350] + +===== Step 3. Highlighting the remaining arguments +After verifying that the `preamble` is valid for the given `full command word`, +the highlighting will need to be reapplied to each `argument` in the remaining user input. + +The following _Activity Diagram_ explains how each `argument` is highlighted, +up until end of the user input. + +.Step 3 - Highlighting the remaining arguments +image::SyntaxHighlightingActivityDiagram3.png[] + +Here is the purpose of each partition in the above diagram: + +* `wrong` + +When the command does not need the given `argument`, +the `ResultDisplay` should reshow how to use the command. + +.Wrong `argument` in input +image::syntax-highlighting/SH-3-Wrong.png[,500] + +* `invalid` + +When the `argument` cannot have the given `value`, +the `ResultDisplay` should show how to use the `argument`. + +.Invalid `argument` in input +image::syntax-highlighting/SH-3-Invalid.png[,500] + +* `valid` + +After processing the current `argument`, +the `ResultDisplay` will still show how to use the argument in case the `argument` +can have spaces. + +.Valid `argument` in input +image::syntax-highlighting/SH-3-Valid.png[,500] + +==== Design Considerations + +===== Aspect #1: Executing the syntax highlighting in real-time +The current implementation reapplies the highlighting whenever the user input changes. +When no input is changed, the program will idle. + +In contrast, having an infinite loop can achieve the same highlighting, +but the program cannot idle and will consume memory when in use. + +===== Aspect #2: Range of syntax highlighting +The current implementation uses three colours for the syntax highlighting. + +The following colours are used: + +* `Red` - invalid input +* `Green` - valid input +* `White` - the default font colour for the `CommandBox` input + +These colours have been chosen because they are standard signalling colours used in +everyday life. Therefore the user will be familiar with these colour signals without +a need for an explanation. + +Valid inputs have been highlighted in order to signal to the user that they have used +the command correctly. An alternative would be to remove this colour though, as having too +many flashing colours may be distracting to the user. +// end::syntaxhighlighting[] + +[[Implementation-Filter]] +=== Filter Command + +==== Description + +Different view has its own designated filter command. + +==== +* *Student View*, has the `student filter` +* *Session View*, has the `session filter` +* *Claims View*, has the `claims filter` +==== + +==== Implementation +This section describes the implementation of the `filter` command. + +The _Activity diagram_ below summarises what happens when the user executes a `filter` command: + +._Activity Diagram_ of the Filter Command +image::FilterCommandActivityDiagram.png[] + +The filter feature consists of three main steps: + +1. *Validating and parsing* user input + +2. Creating a *filtering predicate* from user's input + +3. *Updating the filtered* list with the *filtering predicate* + +===== Filter under Student View + +Students are filtered based on the module code and/or +group code given by the user. + +Module code is a compulsory parameter for the user. + +The following _Sequence diagram_ shows the sequence of commands that take place between +the `Logic` and `Model` components of the Ta-Tracker when the user enters the command +`student filter m\CS2103T g\G06`. This command will return students from module code `CS2103T`, under group `G06`. + +._Sequence Diagram_ for Filter Student Command +image::FilterStudentSequenceDiagram.png[] + +Given below is an example scenario where the user enters a command to filter students. + +. The user command is passed through the `LogicManager` to `TaTrackerParser`. +`TaTrackerParser` checks the input arguments and identify the String keywords. + +. The `TaTrackerParser` sees that the command is a type of Student and passes the command +to the `StudentCommandParser`. + +. The `StudentCommandParser` sees that the command is a type of filter and passes the +arguments to the `FilterStudentCommandParser`. + +. The `FilterStudentCommandParser` creates a `FilterStudentCommand` object +with the relevant keywords. + +. `LogicManager` calls `FilterStudentCommand` 's execute method. + +. The `FilterStudentCommand` object checks whether any of the keywords given by the user matches the existing +module and/or group. +.. If it doesn't, a `CommandException` is thrown saying that no such students exists. +.. If it does, the `FilterStudentCommand` returns a `CommandResult` with a success message. + +===== Filter under Session View + +Sessions can be filtered with the following parameters: + +* `d/DATE` +* `m/MODULE CODE` +* `t/SESSION_TYPE` + +These parameters can be used alone or together. + +The command used to filter sessions has been implemented in a similar way. The main +difference is that the `FilterSessionCommandParser` creates a `SessionPredicate` object. +The `SessionPredicate` object updates the filtered session list by keywords in Model. +The filtered list will then be displayed. + +When the user specifies a keyword, sessions that contain the keywords will be filtered and shown to the user. +If none of the keywords supplied by the user appears in any sessions, a `CommandException` +will be shown. + +The following _Class Diagram_ shows how different classes are related in the functioning of +the `SessionFilter` Command. + +.Class Diagram for Filter Session Command +image::FindCommandClassDiagram.png[] + +===== Filter under Claims View + +The user can only filter the *Claims View* by module code. +When the user enters the command `claims filter m/MODULE_CODE`, claims that contain the module code +will be filtered. + +The command used to filter claims is implemented the same way as `SessionFilterCommand`. + +=== Logging + +We are using `java.util.logging` package for logging. The `LogsCenter` class is used to manage the logging levels and logging destinations. + +* The logging level can be controlled using the `logLevel` setting in the configuration file (See <>) +* The `Logger` for a class can be obtained using `LogsCenter.getLogger(Class)` which will log messages according to the specified logging level +* Currently log messages are output through: `Console` and to a `.log` file. + +*Logging Levels* + +* `SEVERE` : Critical problem detected which may possibly cause the termination of the application +* `WARNING` : Can continue, but with caution +* `INFO` : Information showing the noteworthy actions by the App +* `FINE` : Details that is not usually noteworthy but may be useful in debugging e.g. print the actual list instead of just its size + +[[Implementation-Configuration]] +=== Configuration + +Certain properties of the application can be controlled (e.g user prefs file location, logging level) through the configuration file (default: `config.json`). + +== Documentation + +Refer to the guide <>. + +== Testing + +Refer to the guide <>. + +== Dev Ops + +Refer to the guide <>. + +[appendix] +== Product Scope + +*Target user profile*: + +* targets NUS Computing Teaching Assistants +* has a need to track and manage all their claimable hours of teaching +* has a need to keep track of their tasks and reminders (TA-related and/or personal) +* prefer apps on desktop over other platforms +* types quickly and prefers it over mouse +* experiences no discomfort with CLI navigation + +*Value proposition*: + +* congregates all information regarding claimable hours of teaching in a single location +* provides desired (TSS) format back to users for convenient viewing + +[appendix] +== User Stories + +Priorities: High (must have) - `* * \*`, Medium (nice to have) - `* \*`, Low (unlikely to have) - `*` + +[width="59%",cols="22%,<23%,<25%,<30%",options="header",] +|======================================================================= +|Priority |As a ... |I want to ... |So that I can... + +|`* * *` |new user | be able to use a help command |refer to instructions on what commands are available when +I forget about them + +|`* * *` |TA |set my hourly rate |get the value of my estimated pay according to the latest rate of the semester + +|`* * *` |TA |store contact details of my students |I can contact them with ease whenever +necessary + +|`* * *` |TA |see an overview upcoming tasks I have | plan my schedule accordingly + +|`* * *` |TA |see all my claimable hours in one place |type my claims easily at the end of the semester + +|`* * *` |user |switch between the different views using command line |view the +information in the different views + +|`* * *` |TA |add students to a group in a particular module | So that I know which group which student belongs to + +|`* * *` |TA |add multiple modules |keep track of the different modules I am a TA for + +|`* * *` |TA |add a tutorial/lab group |keep track of the different tutorial and lab groups I conduct + +|`* * *` |careless TA |edit student details | rectify mistakes I make + +|`* * *` |TA |remove students from a tutorial or lab group |no longer have details of students that are no longer in my tutorial/lab group + +|`* * *` |TA |mark a session as done | keep a track of things I have completed in my claims + +|`* * *` |TA |schedule consultation sessions with my students |keep track of claimable hours spent in consultations + +|`* *` |TA |get information on how many hours I've worked so far |keep track of how much work I've done + +|`* *` |TA |get information on how much money I've earned so far |keep track of how +much money I have earned and stay motivated + +|`* *` |TA |give students ratings |keep a track of student participation in class + +|`* *` |TA |delete tasks and events |remove cancelled tasks and events from my session tracker + +|`* *` |TA |be able to get tasks on a particular date | plan events accordingly + +|`* *` |TA |filter by a module |see events relating to a particular module clearly + +|`* *` |TA |delete a tutorial group |remove tasks relating to a tutorial group I am no longer the TA of + +|`* *` |TA |delete a module |remove tasks relating to a module I am no longer the TA of + +|`*` |TA |get a message when a new task clashes with an old one |prevent clashes in my schedule (coming in V2.0) + +|`*` |TA |state that a task is recurring |prevent the need to put a recurring task in my schedule each week + +|======================================================================= + +[appendix] +== Use Cases +:sectnums!: // Disables section numbering to avoid typing [discrete] tag for headers + +(For all use cases below, the *System* is the `TA-Tracker` and the *Actor* is the `user`, unless specified otherwise) + +=== Navigation + +[discrete] + +[discrete] +==== Use case: UC01 - Viewing the help menu + +*MSS* + +1. User requests to view the `help window.` +2. TA-Tracker opens a new window showing the list of commands. ++ +Use case ends. + +[discrete] +==== Use case: UC02 - Going to a different tab + +*MSS* + +1. User requests to go to a different `tab`. +2. TA-Tracker switches to the requested `tab`. + ++ +Use case ends. + +*Extensions* + +* 1a. The requested `tab` is invalid. ++ +[none] +** 1a1. TA-Tracker shows an error message. ++ +Use case resumes at step 1. + +[discrete] +==== Use case: UC03 - Exiting the app + +*MSS* + +1. User requests to exit the app. +2. TA-Tracker closes the App window. ++ +Use case ends. + +=== Student View + +[discrete] +==== Use case: UC04 - Adding a module + +*MSS* + +1. User requests to add a new module. +2. TA-Tracker adds a new module. +3. TA-Tracker switches to the `Student Tab`. + ++ +Use case ends. + +*Extensions* + +* 1a. The given module code is invalid. ++ +[none] +** 1a1. TA-Tracker shows an error message. ++ +Use case resumes at step 1. + +[discrete] +==== Use case: UC05 - Editing a module + +*MSS* + +. User requests to go to the `Student Tab` (UC02) to view +the list of existing modules in the *Student View*. +. TA-Tracker switches to the `Student Tab`. +. User requests to edit an existing module. +. TA-Tracker edits the module. + ++ +Use case ends. + +[discrete] +==== Use case: UC06 - Deleting module + +*MSS* + +. User requests to go to the `Student Tab` (UC02) to view +the list of existing modules in the *Student View*. +. TA-Tracker switches to the `Student Tab`. +. User requests to delete an existing module. +. TA-Tracker deletes the module and all of the sessions, groups, and students in it. ++ +Use case ends. + +*Extensions* + +[none] +* 2a. The list is empty. ++ +Use case ends. + +* 3a. The given module code is invalid. ++ +[none] +** 3a1. TA-Tracker shows an error message. ++ +Use case resumes at step 3. + +[discrete] +==== Use case: UC07 - Adding a group + +*MSS* + +. User requests to go to the `Student Tab` (UC02) to view +the list of existing modules in the *Student View*. +. TA-Tracker switches to the `Student Tab`. +. User requests to add a group to a module +. TA-Tracker adds the new group + ++ +Use case ends. + +*Extensions* + +[none] +* 1a. The given module code is invalid. ++ +[none] +** 1a1. TA-Tracker shows an error message. ++ +Use case resumes at step 1. + +[none] +* 1a. The given class code is invalid. ++ +[none] +** 1a1. TA-Tracker shows an error message. ++ +Use case resumes at step 1. + +[discrete] +==== Use case: UC08 - Editing a group + +*MSS* +. User requests to go to the `Student Tab` (UC02) to view +the list of existing groups in the *Student View*. +. TA-Tracker switches to the `Student Tab`. +. User requests to edit a group +. TA-Tracker edits the group + +[discrete] +==== Use case: UC09 - Deleting group + +*MSS* + +. User requests to go to the `Student Tab` (UC02) to view +the list of existing groups in the *Student View*. +. TA-Tracker switches to the `Student Tab`. +. User requests to delete a group +. TA-Tracker deletes the group and all of the students in it ++ +Use case ends. + +*Extensions* + +[none] +* 2a. The list is empty. ++ +Use case ends. + +* 3a. The given group code is invalid. ++ +[none] +** 3a1. TA-Tracker shows an error message. ++ +Use case resumes at step 3. + +[discrete] +==== Use case: UC10 - Adding a student + +*MSS* + +. User requests to go to the `Student Tab` (UC02) to view +the list of existing groups in the *Student View*. +. TA-Tracker switches to the `Student Tab`. +. User requests to add a new student to a group +. TA-Tracker adds the new student + ++ +Use case ends. + +*Extensions* + +* 1a. The input required (eg. Matric Number) to add a student is invalid. ++ +[none] +** 1a1. TA-Tracker shows an error message. ++ +Use case resumes at step 1. + +[discrete] +==== Use case: UC11 - Editing a Student + +*MSS* + +. User requests to go to the `Student Tab` (UC02) to view +the list of existing students in the *Student View*. +. TA-Tracker switches to the `Student Tab`. +. User requests to edit a student +. TA-Tracker edits the student ++ +Use case ends. + +*Extensions* + +[none] +* 2a. The list is empty. ++ +Use case ends. + +* 3a. The given matric number is invalid. ++ +[none] +** 3a1. TA-Tracker shows an error message. ++ +Use case resumes at step 3. + +* 3a. The given new input for the parameter(s) are invalid. ++ +[none] +** 3a1. TA-Tracker shows an error message. ++ +Use case resumes at step 3. + +[discrete] +==== Use case: UC12 - Deleting a student + +*MSS* + +. User requests to go to the `Student Tab` (UC02) to view +the list of existing students in the *Student View*. +. TA-Tracker switches to the `Student Tab`. +. User requests to delete a student +. TA-Tracker deletes the student ++ +Use case ends. + +*Extensions* + +[none] +* 2a. The list is empty. ++ +Use case ends. + +* 3a. The given matric number is invalid. ++ +[none] +** 3a1. TA-Tracker shows an error message. ++ +Use case resumes at step 3. + +[discrete] +==== Use case: UC13 - Sorting a group + +[discrete] +==== Use case: UC14 - Sorting a module + +[discrete] +==== Use case: UC15 - Sorting a all + +[discrete] +==== Use case: UC16 - Filtering the Student View + +*MSS* + +. User requests to go to the `Student Tab` (UC02) to view +the list of existing students in the *Student View*. +. TA-Tracker switches to the `Student Tab`. +. User requests to filter students from a specific module and/or group. +. TA-Tracker shows the filtered students. ++ +Use case ends. + +*Extensions* + +[none] +* 3a. The module and/or group does not exist. +[none] +** 3a1. TA-Tracker shows an error message. ++ +Use case resumes at step 3. + +=== Session View + +[discrete] +==== Use case: UC17 - Adding a session + +*MSS* + +. User requests to add a session. +. TA-Tracker adds the session. +. TA-Tracker switches to the `Session Tab`. ++ +Use case ends. + +*Extensions* + +[none] +* 1a. The user requests to add a recurring session. +[none] +** 1a1. TA-Tracker creates a new session, and labels it as recurring. ++ +Use case resumes at step 2. + +[none] +* 1a. The user adds a session with a module code that does not exists. +[none] +** 1a1. TA-Tracker shows an error message. ++ +Use case resumes at step 1. + +[discrete] +==== Use case: UC18 - Deleting a session + +*MSS* + +. User requests to go to the `Session Tab` (UC02) to view +the list of existing sessions in the *Session View*. +. TA-Tracker switches to the `Session Tab`. +. User requests to delete a session. +. TA-Tracker deletes the session. ++ +Use case ends. + +*Extensions* + +[none] +* 3a. The index is invalid +[none] +** 3a1. TA-Tracker shows an error message. ++ +Use case resumes at step 3. + +[discrete] +==== Use case: UC19 - Editing a session + +*MSS* + +. User requests to go to the `Session Tab` (UC02) to view +the list of existing sessions in the *Session View*. +. TA-Tracker switches to the `Session Tab`. +. User requests to edit a session. +. TA-Tracker edits the session. ++ +Use case ends. + +*Extensions* + +[none] +* 3a. The given session list index is invalid. +[none] +** 3a1. TA-Tracker shows an error message. ++ +Use case resumes at step 3. + +[discrete] +==== Use case: UC20 - Marking a session as done + +*MSS* + +. User requests to go to the `Session Tab` (UC02) to view +the list of existing sessions in the *Session View*. +. TA-Tracker switches to the `Session Tab`. +. User requests to mark a session as done. +. TA-Tracker marks the session as done and removes the session from the *Session View*. +. TA-Tracker adds the session to the *Claims View* and switches to the `Claims Tab`. ++ +Use case ends. + +*Extensions* + +[none] +* 3a. The given session list index is invalid. +[none] +** 3a1. TA-Tracker shows an error message. ++ +Use case resumes at step 3. + +[discrete] +==== Use case: UC21 - Filtering under Session View + +*MSS* + +. User requests to go to the `Session Tab` (UC02) to view +the list of existing sessions in the *Session View*. +. TA-Tracker switches to the `Session Tab`. +. User requests to filter sessions specific to date/module code/session type. +. TA-Tracker retrieves a list of sessions containing the keyword in any of their fields. +. TA-Tracker shows the list of sessions. ++ +Use case ends. + +*Extensions* + +[none] +* 3a. The search did find any matches. +[none] +** 3a1. TA-Tracker shows an error. ++ +Use case resumes at step 3. + +=== Claims View + +[discrete] +==== Use case: UC22 - Changing the hourly pay rate + +*MSS* + +1. User requests to change the hourly pay rate to a specified amount. +2. TA-Tracker changes the pay rate and adjusted the total earnings to reflect the new pay rate. +3. TA-Tracker switches to the `Claims Tab`. + ++ +Use case ends. + +*Extensions* + +[none] +* 1a. The given rate is invalid. ++ +[none] +** 1a1. TA-Tracker shows an error message. ++ +Use case resumes at step 1. + +[discrete] +==== Use case: UC23 - Filtering under Claims View + +*MSS* + +. User requests to go to the `Claims Tab` (UC02) to view +the list of existing claims in the *Claims View*. +. TA-Tracker switches to the `Claims Tab`. +. User requests to filter claims specific to module code. +. TA-Tracker retrieves a list of claims containing the keyword. +. TA-Tracker shows the list of claims. ++ +Use case ends. + +*Extensions* + +[none] +* 3a. The search did find any matches. +[none] +** 3a1. TA-Tracker shows an error. ++ +Use case resumes at step 3. + +:sectnums: // Enables section numbering again outside of the use cases + +[appendix] +== Non Functional Requirements + +. `**TAT**` should be able to run on any <> as long as it has `Java 11` installed. +. A user with above average typing speed for <> (i.e. not code, not system admin commands) should be able to accomplish most of the tasks faster using commands than using the mouse. +. A user should be able to easily see the commands that they have wrongly typed. +. `**TAT**` should be able to run with or without internet connection. +. `**TAT**` should work for a single user only. +. `**TAT**` should not require user to install. +. Features implemented should be testable using manual testing and automated testing. +. `**TAT**` should support screen resolution of 1920 x 1080 or higher. + +[appendix] +== Glossary + +== GLOSSARY + +[width="%",cols="<20%,<40,<40,options="header",] +|======================================================================= +|Term | Explanation | Examples + +| TSS Claims Form | This refers the claims form that Teaching Assistants +at NUS School of Computing have to fill up at the end of each semester to claim money +for the tasks they have completed. | + +| TA | This is the short form for `Teaching Assistant. | + +| SOC or SoC | This is the short form for School of Computing. | + +| Index | This refers to the position of an item on a list. | Index of 1 refers to the first +item in a list. + +| Matric Number | This refers to a student's matriculation number. | A0123456X + +| Group | The is the general term given to a group of students a TA teaches. | +lab , tutorial , recitation + +| TAT | This is the short form of TA-Tracker. | + +| NUS | This is the short form of National Univeristy of Singapore. | + +| Module | Refers to one of the academic courses in NUS. | + +| Tutorial | A tutorial is a regular meeting between a tutor and one or several +students, for discussion of a subject that is being studied. | + +| API | Stands for "Application Programming Interface" which simplifies programming +by abstracting the underlying implementation and only exposing objects or actions +the developer needs. | + +| Locale | Stands for a setting on the user's computer that defines the user's +language and region. | + +| PlantUML | Stands for a software tool that we use to render the diagrams used +in this document. | + +| NFR | Stands for "Non-functional Requirement" | + +| Mainstream OS | Stands for commonly used Operating Systems (OS) such as Windows, Linux, Unix, OS-X. | + +| Regular English Text | +Stands for text with ordinary english grammar structures and vocabulary generally used by the public. +It excludes syntax related to programming and <>. +| + +| System Administration | +Stands for the field of work in which someone manages one or more systems, be they software, hardware, servers or workstations +with the goal of ensuring the systems are running efficiently and effectively. +| + +| MSS | +Stands for Main Success Scenario that describes the interaction for a given use case, which assumes that nothing goes wrong. | + + +|======================================================================= + +[appendix] +== Instructions for Manual Testing + +Given below are instructions to test the app manually. These instructions will help you +navigate through the app and get an idea of what to test. We suggest you use this in +conjunction with our User Guide to test the product thoroughly. + +[NOTE] +These instructions only provide a starting point for testers to work on; testers are expected to do more _exploratory_ testing. + +=== Launch and Shutdown + +. Initial launch + +.. Download the jar file and copy into an empty folder +.. Double-click the jar file + + Expected: Shows the GUI with a set of sample contacts. The window size may not be optimum. + +. Saving window preferences + +.. Resize the window to an optimum size. Move the window to a different location. Close the window. +.. Re-launch the app by double-clicking the jar file. + + Expected: The most recent window size and location is retained. + +. Default view + +.. Switch to a tab different from the student tab. Close the window. +.. Relaunch the app + +Expected: The student view under the student app is shown. + +=== Viewing help + +. Opens the help window. + +.. Test Case: `help` + +Expected: Opens the help window. + +=== Changing Tabs + +. Changes the tab. + +.. Test Case: `goto session` + +Expected: Opens the session tab. +.. Test Case: `goto claims` + +Expected: Opens the claims tab. +.. Test Case: `goto student` + +Expected: Opens the student tab. + +=== Adding a module + +. Adding a module from any view. + +.. Test Case: `module add m/CS1101S n/Programming Methodology I` + + Expected: A module with the module code `CS1101S` and name `Programming Methodology I` +is added to the module list on student view. If you were on a different tab, you are +automatically switched to *Student View*. +.. Test Case: `module add m/CS1101S n/PE2` + + Expected: You will see an error message that this module already exists. (Assuming you +added a module with module code CS1101S) + +[NOTE] +==== +The test cases after this assume that your TA-Tracker has a module with module code +CS1101S. +==== + +=== Adding a group to a module + +. Adding a group to a module. + +.. Test Case: `group add g/G06 m/CS1101S t/lab` + +Expected: A group with group code `G06` of type `lab` will be added to the module +`CS1101S`. If you were on a different tab, you are +automatically switched to *Student View*. +.. Test Case: `group add g/G06 m/CS1101S t/lab`` + +Expected: You will see an error message that this group already exists in the module. (Assuming you +added a group with group code G06 to the module CS1101S) +.. Test Case: `group add g/G06 m/CS3243 t/lab` + +Expected: Assuming a module with module code `CS3243` exists in the TA-Tracker and +doesn't contain a group with group code `G06` , +A group with group code `G06` of type `lab` will be added to the module +`CS3243`. + +[NOTE] +==== +The test cases after this assume that your TA-Tracker has a group with group code +`G06` in the module CS1101S. +==== + +=== Adding students to a group + +. Adding a student to a group. + +.. Test Case: `student add id/A0123456X g/G06 m/CS1101S n/Jane Doe` + +Expected: A student named `Jane Doe` with matriculation number `A0123456X` is added +to the group `G06` of the module `CS1101S` with a default rating of 3. +.. Test Case: `student add id/A0123457X g/G06 m/CS1101S n/John Doe r/5` + +Expected: A student named `John Doe` with matriculation number `A0123457X` is added +to the group `G06` of the module `CS1101S` with a rating of 5. + +=== Editing a student + +. Editing a student in a group. + +.. Test Case: `student edit g/G06 m/CS1101S id/A0123456X r/4` + +Expected: Changes the rating of the student with matric number `A123456X` to 4. + +=== Editing a group + +. Editing a group in a module. + +.. Test Case: `group edit g/G06 m/CS1101S nt/tutorial` + +Expected: The group with group code `G06` will be changed to type `tutorial` from the module +`CS1101S`. The students inside the group will be unchanged. If you were on a different +tab, you are automatically switched to *Student View*. + +=== Editing a module's name + +. Edits the name of a module. + +.. Test Case: `module edit m/CS1101S n/New Name` +Expected: The name of the module with module code `CS1101S` will change to `New Name` +but the groups and students inside it will remain intact. + +=== Viewing a specific module + +. Allows you to view groups in a particular module. + +.. Test Case: `student filter m/CS1101S` +Expected: You can now view the groups of the module `CS1101S`. You will see the students +of the group at index 1 of the module's group list. + +=== Viewing a specific group + +. Allows you to view students in a particular group of a particular module. + +.. Test Case: `student filter g/G06 m/CS1101S` +Expected: You can now view the groups of the module `CS1101S`. You will see the students +of the group `G06`. + +=== Sorting a group + +. Sorting students in a group. + +.. Test Case: `sort group g/G06 m/CS1101S t/alpha` + +Expected: Sorts all the students in the group `G06` of module `CS1101S` alphabetically. + +=== Sorting a module + +. Sorting students in a module. + +.. Test Case: `sort module g/G06 m/CS1101S t/alpha` + +Expected: Sorts all the students in all the groups of module `CS1101S` alphabetically. +You will see the students in the group at index 1 of the group list of module `CS1101S`. + +=== Sorting all modules + +. Sorting students in all modules. + +.. Test Case: `sort all t/alpha` + +Expected: Sorts all the students in all the groups of all modules alphabetically. +You will see the students in the group at index 1 of the group list of the module +at index 1 of the module list. + + +=== Deleting a student + +. Deleting a student from a group + +.. Test Case: `student delete g/G06 m/CS1101S id/A0123456X` + +Expected: Deletes the student with matric number `A123456X` from the group `G06` +of the module `m/CS1101S`. + +=== Deleting a group + +. Deleting a group from a module. + +.. Test Case: `group delete g/G06 m/CS1101S` + +Expected: The group with group code `G06`` will be deleted from the module +`CS1101S`. The students inside the group will be deleted. If you were on a different +tab, you are automatically switched to *Student View*. + +=== Adding a session + +. Adding a session to the session list. + +.. Test Case: `session add m/CS2103T s/14:00 e/16:00 d/2020-06-20 w/2 +t/consultation n/with Alice and Bob` + +Expected: A session starting at `14:00` and ending at `16:00` on `2020-06-20` +recurring every two weeks will be added to the sessions list. It will be associated +with the module `CS1101S` and be a `consultation` with Alice and Bob. + +=== Marking a session as done + +. Marking a session as done. + +.. Test Case: `session done 1` + +Expected: Marks the session at index `1` of the session list as done. If it is a +recurring session, a new session will be added in its place, dated after the recurring +period. The session marked as done will be added to the claims list. + +=== Deleting a session + +. Deleting a session. + +.. Test Case: `session delete 1` + +Expected: Deletes the session at index `1` of the session list. + +=== Editing a session + +. Editing a session + +.. Test Case: `session edit 1 t/lab` + +Expected: Edits the session at index `1` of the session list to be of type `lab`. + +=== Filtering sessions + +. Filtering sessions based on keywords. + +.. Test Case: `session filter m/CS1101S` + +Expected: Shows all sessions associated with the *module* `CS1101S`. +.. Test Case: `session filter t/tutorial` + +Expected: Shows all sessions that are of *type* `tutorial`. +.. Test Case: `session filter d/2020-03-20` + +Expected: Shows all sessions on the *date* `2020-03-20`. +.. Test Case: `session filter d/2020-03-20 t/tutorial m/CS1101S` + +Expected: Shows all sessions that contains *date* `2020-03-20`, *session type* `tutorial` +and *module code* `CS1101S`. + +=== Filtering Claims + +. Filtering claims by module code. + +.. Test Case: `claims filter m/CS1101S` + +Expected: Shows all sessions that have been marked as done +(aka claims) associated with the module `CS1101S`. + +=== Listing sessions and claims + +. Lists all sessions and claims again (removes all filters that have been previously applied). + +.. Test Case: `list` + +Expected: Shows all sessions and claims that have previously been filtered. + +=== Changing rate + +. Setting the hourly rate. + +.. Test Case: `setrate 25` + +Expected: Sets the rate of the claims to be 25$ per hour. Money computation is +changed accordingly. +=== Viewing Statistics + +. Displays the statistics window. + +.. Test Case: `report` + +Expected: Displays the statistics report showing statistics of all modules. +.. Test Case: `report CS1101S` + +Expected: Displays the statistics report of the module `CS1101S`. + +=== Deleting a module + +. Deleting a module. + +.. Test Case: `module delete m/CS1101S` + +Expected: Assuming you already had a module with module code m/CS1101S in your TA-Tracker, +this would delete the module with module code m/CS1101S. This would also delete all +groups, students and sessions related to this module. +.. Test Case: `module delete m/CS1101S` + +Expected: Assuming you already deleted the module with module code m/CS1101S from your TA-Tracker, +this would show you an error message. + +=== Exiting the program -_{ more test cases ... }_ +. Exiting the program. -=== Deleting a person +.. Test Case: `exit` + +Expected: Exits the program. -. Deleting a person while all persons are listed +== Effort -.. Prerequisites: List all persons using the `list` command. Multiple persons 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. -.. Other incorrect delete commands to try: `delete`, `delete x` (where x is larger than the list size) _{give more}_ + - Expected: Similar to previous. +Creating this application was fairly difficult and required a lot of effort from us. -_{ more test cases ... }_ +While AB3 deals with only one entity, this application deals with multiple entities. +AB3 contains only `persons`. This application has 3 views to begin with - the *Student View*, +*Session View* and *Claims View*. This is a big change from AB3's UI. -=== Saving data +While we were able to refactor `person` into `student`, +we had to create `session`, `group` and `module` from scratch. In addition to that, +we also have a `statistics` window and our `help` window is significantly more appealing +than the AB3 `help` window. -. Dealing with missing/corrupted data files +Due to our inexperience with UI work, we had initially planned *Student View* to look +very different. Therefore, the way it was implemented was a little different. +Once we got more familiar with JavaFX, we realised that our initial plan was no longer +feasible and had to change the way *Student View* was implemented and had to add a lot +of supporting methods to allow the UI to work the way it does. -.. _{explain how to simulate a missing/corrupted file and the expected behavior}_ +Another challenge we faced was that we were unaware of restrictions regarding timing +in features for the module CS2103 for which this application has been created. Two of +our features that relied on timing that were almost ready had to be scrapped and we +had to look for new features to replace them. -_{ more test cases ... }_ diff --git a/docs/Documentation.adoc b/docs/Documentation.adoc index ad90ac87bda..9af3bd85584 100644 --- a/docs/Documentation.adoc +++ b/docs/Documentation.adoc @@ -1,4 +1,4 @@ -= AddressBook Level 3 - Documentation += TA-Tracker - Documentation :site-section: DeveloperGuide :toc: :toc-title: @@ -12,7 +12,7 @@ ifdef::env-github[] :note-caption: :information_source: :warning-caption: :warning: endif::[] -:repoURL: https://github.com/se-edu/addressbook-level3/tree/master +:repoURL: https://github.com/AY1920S2-CS2103T-W17-4/main/tree/master == Introduction @@ -71,10 +71,6 @@ If set, the name will be displayed near the top of the page. Setting this will add a "View on GitHub" link in the navigation bar. |_not set_ -|`site-seedu` -|Define this attribute if the project is an official SE-EDU project. -This will render the SE-EDU navigation bar at the top of the page, and add some SE-EDU-specific navigation items. -|_not set_ |=== diff --git a/docs/LearningOutcomes.adoc b/docs/LearningOutcomes.adoc index 436c1777617..a35dc183079 100644 --- a/docs/LearningOutcomes.adoc +++ b/docs/LearningOutcomes.adoc @@ -33,7 +33,7 @@ What other user stories do you think AddressBook should support? Add those user === Exercise: Add a 'Rename tag' use case * Add a use case to the `DeveloperGuide.adoc` to cover the case of _renaming of an existing tag_. -e.g. rename the tag `friends` to `buddies` (i.e. all persons who had the `friends` tag will now have +e.g. rename the tag `friends` to `buddies` (i.e. all students who had the `friends` tag will now have a `buddies` tag instead) Assume that AddressBook confirms the change with the user before carrying out the operation. @@ -108,7 +108,7 @@ image::PrintableInterface.png[width=400] String getPrintableString(Printable... printables) { ---- + -The above method can be used to get a printable string representing a bunch of person details. +The above method can be used to get a printable string representing a bunch of student details. For example, you should be able to call that method like this: + [source,java] diff --git a/docs/SettingUp.adoc b/docs/SettingUp.adoc index c0659782fab..986f437adb4 100644 --- a/docs/SettingUp.adoc +++ b/docs/SettingUp.adoc @@ -1,4 +1,4 @@ -= AddressBook Level 3 - Setting Up += TA-Tracker - Setting Up :site-section: DeveloperGuide :toc: :toc-title: @@ -37,7 +37,7 @@ Do not disable them. If you have disabled them, go to `File` > `Settings` > `Plu == Verifying the setup -. Run the `seedu.address.Main` and try a few commands +. Run the `tatracker.Main` and try a few commands . <> to ensure they all pass. == Configurations to do before writing code @@ -55,16 +55,6 @@ This project follows https://github.com/oss-generic/process/blob/master/docs/Cod Optionally, you can follow the <> document to configure Intellij to check style-compliance as you write code. -=== Updating documentation to match your fork - -After forking the repo, the documentation will still have the SE-EDU branding and refer to the `se-edu/addressbook-level3` repo. - -If you plan to develop this fork as a separate product (i.e. instead of contributing to `se-edu/addressbook-level3`), you should do the following: - -. Configure the <> in link:{repoURL}/build.gradle[`build.gradle`], such as the `site-name`, to suit your own project. - -. Replace the URL in the attribute `repoURL` in link:{repoURL}/docs/DeveloperGuide.adoc[`DeveloperGuide.adoc`] and link:{repoURL}/docs/UserGuide.adoc[`UserGuide.adoc`] with the URL of your fork. - === Setting up CI Set up Travis to perform Continuous Integration (CI) for your fork. See <> to learn how to set it up. @@ -81,4 +71,4 @@ Having both Travis and AppVeyor ensures your App works on both Unix-based platfo === Getting started with coding -When you are ready to start coding, we recommend that you get some sense of the overall design by reading about <>. +When you are ready to start coding, we recommend that you get some sense of the overall design by reading about <>. diff --git a/docs/Testing.adoc b/docs/Testing.adoc index 5767b92912c..f1d06da4e4a 100644 --- a/docs/Testing.adoc +++ b/docs/Testing.adoc @@ -1,4 +1,4 @@ -= AddressBook Level 3 - Testing += TA-Tracker - Testing :site-section: DeveloperGuide :toc: :toc-title: @@ -35,11 +35,11 @@ See <> for more info on how to run tests using G We have three types of tests: . _Unit tests_ targeting the lowest level methods/classes. + -e.g. `seedu.address.commons.StringUtilTest` +e.g. `tatracker.commons.StringUtilTest` . _Integration tests_ that are checking the integration of multiple code units (those code units are assumed to be working). + -e.g. `seedu.address.storage.StorageManagerTest` +e.g. `tatracker.storage.StorageManagerTest` . Hybrids of unit and integration tests. These test are checking multiple code units as well as how the are connected together. + -e.g. `seedu.address.logic.LogicManagerTest` +e.g. `tatracker.logic.LogicManagerTest` == Troubleshooting Testing diff --git a/docs/UserGuide.adoc b/docs/UserGuide.adoc index 4e5d297a19f..cd0d5c1be70 100644 --- a/docs/UserGuide.adoc +++ b/docs/UserGuide.adoc @@ -1,9 +1,13 @@ -= AddressBook Level 3 - User Guide += TA-Tracker - User Guide :site-section: UserGuide :toc: :toc-title: :toc-placement: preamble +:toclevels: 3 :sectnums: +:sectnumlevels: 4 +:sectlinks: +:sectanchors: :imagesDir: images :stylesDir: stylesheets :xrefstyle: full @@ -12,166 +16,1502 @@ ifdef::env-github[] :tip-caption: :bulb: :note-caption: :information_source: endif::[] -:repoURL: https://github.com/se-edu/addressbook-level3 +:repoURL: https://github.com/AY1920S2-CS2103T-W17-4/main/ -By: `Team SE-EDU` Since: `Jun 2016` Licence: `MIT` +By: `Team W17-4` Since: `Jan 2020` Licence: `MIT` == Introduction -AddressBook Level 3 (AB3) is for those who *prefer to use a desktop app for managing contacts*. More importantly, AB3 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, AB3 can get your contact management tasks done faster than traditional GUI apps. Interested? Jump to the <> to get started. Enjoy! +Are you a *Teaching Assistant* in *NUS School of Computing (NUS SOC)* who is tired of having to +fill out the *Teaching Support Student (TSS) Claims Form* accurately at the end of the semester? + +Do you wish there was a *desktop application* that can help you *keep track of your teaching duties*, +such as your *claimable hours* and *student ratings*, all in *one place*? + +Look no further, because *TA-Tracker* is the tool just for *you*! + +*No more* saving your claimable hours in an *unappealing spreadsheet*. + +*No more* keeping notes about your students in a *cluttered diary*. + +*TA-Tracker* is a *productivity tool* that is made for all you *NUS SOC Teaching Assistants (TAs)* +who need to easily *fill out forms*, such as the *TSS Claims Form*, at the end of the semester. + +*TA-Tracker* has features that will help you easily *track and manage* +your *claimable hours* and *students* in *one place*. + +What's more, *TA-Tracker* has: + +* A *Command Line Interface (CLI)* for all you *TAs* who +can type fast and prefer to use a keyboard, and + +* A *Graphical User Interface (GUI)* so that +you can easily view all your *teaching duties*. + +If you are interested, jump to <> to discover how easy it is to fill out forms using *TA-Tracker*. == Quick Start -. Ensure you have Java `11` or above installed in your Computer. -. 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. +This section gives you a step-by-step explanation on how you can download and open +the application. + +. Ensure that you have Java `11` or above installed in your Computer. +. Download the latest `tatracker.jar` link:{repoURL}/releases[here]. +. Copy the file to the folder you want to use as the home folder for your TA tracker. . Double-click the file to start the app. The GUI should appear in a few seconds. + + image::Ui.png[width="790"] +This is how the GUI may look like when the TA-Tracker is opened. + -. Type the command in the command box and press kbd:[Enter] to execute it. + +. Type a command in the command box, and press kbd:[Enter] to execute it. + e.g. typing *`help`* and pressing kbd:[Enter] will open the help window. -. Some example commands you can try: -* *`list`* : lists all contacts -* **`add`**`n/John Doe p/98765432 e/johnd@example.com a/John street, block 123, #01-01` : adds a contact named `John Doe` to the Address Book. -* **`delete`**`3` : deletes the 3rd contact shown in the current list -* *`exit`* : exits the app +. Refer to <> for details of each command. + +NOTE: TA-Tracker data is saved periodically so you don't have to worry about saving +your data manually. + +== About the User Guide +This User Guide introduces you to TA-Tracker's features and shows you how you can use +the TA-Tracker to make your life as a Teaching Assistant easier. + +=== Common Symbols +This section shows you the symbols commonly used in this guide. + +[NOTE] +==== +This symbol indicates that there is something that you should take note of. +==== + +[TIP] +==== +This symbol indicates that a tip is being mentioned. +==== + +[CAUTION] +==== +This symbol indicates that there is something you should be careful to avoid. +==== + +=== Command Format + +This section shows you how all the commands in this guide have been formatted. + +[width="%",cols="<20%a,<30%a,<50a",options="header"] +|======================================================================= +| Format | Meaning | Example + +| +`lower_case/` + +Any lower case letters, followed by a forward slash +| +These are *prefixes*. + +They are used to separate the different parameters of a command. +| +These are prefixes: + +`n/`, `d/`, `t/` + +Note that prefixes *cannot have spaces*: + +`n /` is *not a prefix*, and will not be recognized. + +| +`UPPER_CASE` + +Words in upper case +| +These are *parameters*. + +You will need to supply parameters in order to complete certain commands. +| +You can create a *student* with the name *John Doe* using the `student add` command. + +Suppose the `student add` command looks like this: + +`student add n/NAME` + +Simply replace `NAME` with `John Doe` to create the student *John Doe*: + +`student add n/John Doe` + +| +`[UPPER_CASE]` + +Words in upper case, surrounded by square brackets +| +These are *optional parameters*. + +Certain commands can be used without these parameters. +| +Suppose a command contains *two parameters* next to each other: + +`n/NAME [t/TAG]` + +The *first parameter* `NAME` is *compulsory*. + +The *second parameter* `TAG` is *optional*. + +Since a `TAG` is *optional*, you will be able to use the command with these *inputs*: + +* `n/John Doe t/Fast learner`, or +* `n/John Doe` + +| +`UPPER_CASE...` + +`[UPPER_CASE]...` + +An ellipsis `...` following any words in upper case +| +These are parameters that can be used *multiple times* or *none at all*. +| +The following parameter can be used *multiple times*: + +`t/TAG...` + +This means that it can be: + +* *Left empty* (i.e. 0 times): + +`t/` +* *Used one time* (i.e. 1 time): + +`t/friend` +* *Used multiple times* (i.e. 2 or more times): + +`t/friend t/family` + +|======================================================================= + +=== Common Parameters + +This section lists and explains what the parameters commonly used in the commands mean. + +[width="%",cols="<20%a,<40%a,<40%a,options="header",] +|======================================================================= +|Parameter | Explanation | Examples + +| +`TAB_NAME` +| +This refers to the different `tab` names. +| +*student* - to indicate the *Student View* + +*session* - to indicate the *Session View* + +*claims* - to indicate the *Claims View* + +| +`INDEX` +| +Indicates the position of an item in a list +| +*1* - refers to the +first item in a list + +| +`MOD_CODE` +| +Refers to the unique code given to the module. + +You can personalise this and give it your own code. However, we recommend you +use the module's official code. +| +*CS2103T* + +| +`GROUP_CODE` +| +Refers to the unique code given to the group. + +You can personalise this and create your own code. However, we recommend that you use +the group's official code assigned by SoC +| +*G06* + +| +`NEW_GROUP_CODE` +| +Refers to the new group code of a group when you edit a group. +| +*G05* + +| +`GROUP_TYPE` +| +Refers to the group type. +| +*lab*, *tutorial*, *recitation* or *other* + +| +`NEW_GROUP_TYPE` +| +Refers to the new group type you want a group to be. +| +*lab*, *tutorial*, +*recitation* or *other* + +| +`MATRIC_NUMBER` +| +Refers to the matriculation number of a student. -. Refer to <> for details of each command. +It must start with an A, have 7 numbers in between and end with an alphabet. +| +*A0123456X* -[[Features]] -== Features +| +`NAME` +| +Depending on the command, this could either refer to the student's name +or the module's name. +| +*John Doe* or *Software Engineering* +| +`SORT_TYPE` +| +Refers to how you want to sort the *Student View*. +| +*alpha*, *alphabetical* or *alphabetically* - to sort alphabetically. + +*rating asc* - to sort by rating in ascending order. + +*rating desc* - to sort by rating in descending order. + +*matric* - to sort by matriculation number. + +| +`SESSION_TYPE` +| +Refers to the type of session. +| +*consult* - consultation + +*tutorial* - tutorial + +*lab* - lab + +*grading* - grading assignments, projects or assessments + +*prep* - class preparation + +*todo* - other tasks and notes + +| +`NOTES` +| +Refers to any extra description +| +*This time was spent correcting 40 assignments* + +| +`START` +| +Indicates the starting time. Must be in the `HH:mm` format. +| +*10:42* + +| +`END` +| +Indicates the ending time. Must be in the `HH:mm` format. +| +*16:42* + +| +`DATE` +| +Indicates the date of a session. Must be in the `yyyy-MM-dd` format. +| +*2020-06-20* + +| +`WEEK` +| +Indicates the recurring period of a session. +| +*1* - represents a session that repeats every week. + +| +`RATING` +| +Indicates the rating of a student. + +A rating is as a number between 1 (Poor) to 5 (Excellent). +| +*3* - represents an average student rating. + +| +`EMAIL` +| +Indicates the email of a student. + +Emails should be in `local-part@domain format`. + +The `local-part` can only contain: + +* Alphanumeric characters, and +* The following special characters: + +!#$%&'*+/=?`{\|}~^.- + +The `domain` can only contain: +* Alphanumeric characters +* * The following special characters in between: + +.- + +The domain name must also: + +* Have at least 2 characters +* Start and end with alphanumeric characters +| +*johnDoe97@example123.com* + +|======================================================================= + +==== Duplicate Parameters + +You can specify the same parameter *more than once* in a command. +Depending on the command, you will see *different outcomes*. + +Here is a table explaining the different outcomes: + +[width="%",cols="<20%a,<30%a,<50a",options="header"] +|======================================================================= +| +Parameter +| +Explanation +| +Example + +| +Can be used multiple times +| +*All occurrences* will be used when executing the command +| +In the `student add` command, you can create a *student* with *multiple tags*. + +Therefore, you can input multiple tags by chaining them: + +`t/needs help t/aka henry` + +This will give a student the tags `needs help` and `aka henry`. + +| +Can only be used once +| +The very *last occurrence* will be used when executing the command +| +`module add m/CS2103T n/SE n/Software Engineering` + +If you execute this command, you will create a *module* with the name `Software Engineering`. + +The name `SE` will be ignored. + +|======================================================================= + +==== Rearranging Parameters + +Parameters can be in *any order* if they have *prefixes*. + +For example, if a command needs a `n/NAME` and `p/PHONE_NUMBER`, you can specify them in any order: + +* `n/NAME p/PHONE_NUMBER`, or +* `p/PHONE_NUMBER n/NAME` + +However, if the command has a parameter *without a prefix*, that parameter *must* be +the very *first parameter*. + +For example, if a command needs an `INDEX`, it must be the *first parameter*: + +* `INDEX n/NAME` is valid, but +* `n/NAME INDEX` is invalid + +[[Layout]] +== Layout +This section gives you a brief overview of the layout of the *TA-Tracker*. + +*TA-Tracker* is divided into three `tabs` representing the different *Views*: + +* The *Student View* under the `student tab`, +* The *Session View* under the `session tab`, and +* The *Claims View* under the `claims tab` + +When you switch to a `tab`, that `tab` will be highlighted in orange. + +Furthermore, when you enter a new command, you will be automatically switched to the relevant `tab` +so that you can instantly see the result of the command. + +[NOTE] ==== -*Command Format* +* You can *select* a `tab` to show a different *View*. +This `tab` will be highlighted in *blue*. -* Words in `UPPER_CASE` are the parameters to be supplied by the user e.g. in `add n/NAME`, `NAME` is a parameter which can be used as `add n/John Doe`. -* Items in square brackets are optional e.g `n/NAME [t/TAG]` can be used as `n/John Doe t/friend` or as `n/John Doe`. -* Items with `…`​ after them can be used multiple times including zero times e.g. `[t/TAG]...` can be used as `{nbsp}` (i.e. 0 times), `t/friend`, `t/friend t/family` etc. -* Parameters can be in any order e.g. if the command specifies `n/NAME p/PHONE_NUMBER`, `p/PHONE_NUMBER n/NAME` is also acceptable. +* If you are *switched* to a `tab` when you *enter a command*, +that `tab` will be highlighted in *orange*. + ++ +This should help you easily remember +where you last made a change in *TA-Tracker*. + +* You will sometimes see the `tabs` highlighted in both *orange* and *blue*. + ++ +The *orange* `tab` will remain highlighted even if you select another `tab`. + +* You may notice that the *orange* and *blue* highlights for the `tabs` are *not +the same size*. + ++ +This is to *prevent* the highlights from *overlapping* each other, +allowing you to see them better. +==== + +=== Student View +Under the `student tab`, the *Student View* is used to show you the students that you're teaching. The students +have been grouped according to the *module* and *group* they belong to. The *Student View* has been divided into +three columns. + +. The first column shows you a list of all the modules that you are a teaching +assistant for. + +. The second column shows you a list of all groups that you're a teaching assistant for +in a module of your choice. +If you haven't chosen anything, you will be shown the groups of the module +in the first index in the list of modules by default. + +. The third column shows you a list of all students in the group of your choice. If you +haven't chosen anything, you will be shown the students of the group in the +first index in the list of groups by default. + +The purpose of the *Student View* is to help you keep track a of your students. It will +show you information such as: + +* `NAME`: student name +* `MATRIC_NUMBER`: matriculation number +* `RATING`: ratings you have given the student, represented by stars (on a scale from 1 - 5) +* `EMAIL` and `PHONE`: contact details +* `TAG`: any additional information you have about the student + +[NOTE] +==== +The `RATING` value must be a positive integer. ==== -=== Viewing help : `help` +=== Session View +Under the `session tab`, the *Session View* contains a list of the upcoming sessions +that you haven't done yet. + +The sessions are automatically sorted by date. + +The purpose of the *Session View* is to help you keep track a of your upcoming teaching. +duties. It will show you information such as: + +* `SESSION_TYPE`: the type of session +* `DATE`: the date that the session will occur on +* `START` and `END`: the start time and end time of the session +* `MODULE`: the module that the session is under +* `NOTES`: any additional information +* `WEEK`: the recurring period of the session + +=== Claims View +Under the `claims tab`, the *Claims View* contains a list of all the claimable teaching +duties you have completed so far. + +The purpose of this view is to allow a you to keep track of all your claims so +you can easily enter it into the TSS claims form at the end of the semester. + +The *Claims View* has been +divided into two columns. + +. The first column shows you a list of all the modules that you are a teaching +assistant for. + +. The second column shows you a list of all the sessions that you have marked as done. + +[[Usage]] +== Usage +This section describes how you can interact with *TA-Tracker*. + +=== Keyboard Shortcuts +You can navigate everything in *TA-Tracker* with just a *keyboard*! + +Here are some keyboard shortcuts that you can use: + +[width="%",cols="<20%a,<80a",options="header"] +|======================================================================= +| +Shortcut Key +| +Usages + +| +kbd:[Esc] +| +* *Toggle* between the `command box` and the *Views*. + +* *Close* a popup window. + +| +kbd:[↑] `up` + +kbd:[↓] `down` + +arrow keys +| +* *Scroll* through a list + +(you must toggle out of the `command box` in order to use this shortcut) + +| +kbd:[←] `left` + +kbd:[→] `right` + +arrow keys +| +* *Navigate* between *different lists* in the same *View*. + +(you must toggle out of the `command box` in order to use this shortcut) + +|======================================================================= + +=== Syntax Highlighting +When you type a command into the `command box`, your input will be *highlighted*. + +Here is a table explaining the meaning of each colour: + +[width="%",cols="<10%a,<90a",options="header"] +|======================================================================= +| +Colour +| +Meaning + +| +*green* +| +*Valid* input. + +| +*red* +| +*Invalid* input. +| +*white* +| +The *default* font colour. + +|======================================================================= + +=== Hints +When you type out a command, you will also see `hints` about how to use the command. + +Here is a table describing the different types of hints that you will encounter: + +[width="%",cols="<10%a,<90a",options="header"] +|======================================================================= +| +Hint +| +Trigger + +| +Showing the *command usage* +| +* When you *first type out* the `command word`. + +* After typing *two whitespaces* in a row. + +| +Showing the *parameter usage* +| +* When you *first type out* a *valid prefix*. + +* When there is an *invalid prefix* in your command. + +|======================================================================= + +[[Commands]] +== Commands +This section explains how to use all the `CLI` commands in *TA-Tracker*. + +=== Navigation + +These commands are used to control the different *windows* in *TA-Tracker*, +as well as switching between the different *Views*. + +==== Viewing help : `help` + +You can open the `help window` with this command. You can close the `help window` by pressing the `ESC` key +on your keyboard. + Format: `help` -=== Adding a person: `add` +==== Switching tabs : `goto` + +You can switch to different `tabs` with this command to show their associated *view*. + +Format: `goto TAB_NAME` + +[NOTE] +==== +* You cannot switch to a `tab` that does not exist in TA-Tracker +==== + +==== +Example: + +`goto student` + +This command will take you to the `student tab`. + +==== + + +==== Exiting the program : `exit` + +You can exit the program with this command. + +Format: `exit` + +==== Listing all sessions : `list` + +`list` command shows you all the sessions under Session View and Claims View. + +[TIP] +==== +* You can use this command to display all your sessions after using the *filter* command. +==== + +=== Student View + +This section explains all the commands you can use in the *Student View*. + +[[AddModule]] +==== Adding a Module : `module add` + +You can use this command to add a new module to the TA-Tracker. + +When a new module is created, the *Student View* will show the groups +and students of the new module. (Initially, a new module doesn't have any students or +groups till you add some). +If you were on a different `tab`, you will automatically be switched to the `student tab`. + +Format: `module add m/MOD_CODE n/NAME` + +[NOTE] +==== +* You can't have two modules with the same module code in the TA-Tracker. + +* MOD_CODE and MOD_NAME can't be an empty string or a sequence of spaces. + +* You can give a module your own custom MOD_CODE or NAME if you find it easier +to remember. However, we recommend you use the official +module code and name for it. + +* If the module name or module code are very long, you can increase the width of +the module name list by dragging the edge with your cursor. However, we suggest not +giving the modules long codes or long names. The standard official names are usually +of a good length. -Adds a person to the address book + -Format: `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]...` +==== [TIP] -A person can have any number of tags (including 0) +==== +* You can't edit the `MOD_CODE` once the module has been created, so do take +care to ensure that the code is correct. +==== +==== 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` +* `module add m/CS2103 n/Software Engineering` ++ +This will add a module with the module code `CS2103` and name `Software Engineering` +to the TA-Tracker. +==== -=== Listing all persons : `list` +[[DeleteModule]] +==== Deleting a Module : `module delete` -Shows a list of all persons in the address book. + -Format: `list` +You can use this command to delete a module from the TA-Tracker. -=== Editing a person : `edit` +When you delete a module, all groups, students and sessions associated with +the module will also be deleted. -Edits an existing person in the address book. + -Format: `edit INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [t/TAG]...` +When a module is deleted, the *Student View* will go back to its default +setting. That is, you will see the details of the first group in the first module of +the TA-Tracker. +If you were on a different `tab`, you will automatically be switched to the `student tab`. -**** -* Edits the person at the specified `INDEX`. The index refers to the index number shown in the displayed person list. The index *must be a positive integer* 1, 2, 3, ... -* At least one of the optional fields must be provided. -* Existing values will be updated to the input values. -* When editing tags, the existing tags of the person will be removed i.e adding of tags is not cumulative. -* You can remove all the person's tags by typing `t/` without specifying any tags after it. -**** +Format: `module delete m/MOD_CODE` +[NOTE] +==== +* You cannot delete a module that doesn't exist. +==== + +==== 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. +* `module delete m/CS2103` ++ +This will delete the module with the module code `CS2103` from the TA-Tracker. +==== -=== Locating persons by name: `find` +[[EditModule]] +==== Editing a Module : `module edit` -Finds persons whose names contain any of the given keywords. + -Format: `find KEYWORD [MORE_KEYWORDS]` +You can use this command to edit a module's name in the TA-Tracker. -**** -* The search is case insensitive. e.g `hans` will match `Hans` -* The order of the keywords does not matter. e.g. `Hans Bo` will match `Bo Hans` -* Only the name is searched. -* Only full words will be matched e.g. `Han` will not match `Hans` -* Persons matching at least one keyword will be returned (i.e. `OR` search). e.g. `Hans Bo` will return `Hans Gruber`, `Bo Yang` -**** +When a module is edited, the *Student View* will show the groups of the edited module and +the students of the first group of the edited module. If you were on a different `tab`, +you will automatically be switched to the `student tab`. + +Format: `module edit m/MOD_CODE n/NEW NAME` + +[NOTE] +==== +* You can only use this command to change the name of the module. The module +code can't be changed. + +* Editing a module doesn't affect the students and +groups inside the module. +* MOD_NAME can't be an empty string or a sequence of spaces. + +* You cannot edit a module that doesn't exist. +==== + +==== Examples: -* `find John` + -Returns `john` and `John Doe` -* `find Betsy Tim John` + -Returns any person having names `Betsy`, `Tim`, or `John` +* `module edit m/CS2103 n/Software Engineering` ++ +This will change the name of the module with module code `CS2103` to `Software Engineering`. +==== -// tag::delete[] -=== Deleting a person : `delete` +[[AddGroup]] +==== Adding a Group : `group add` -Deletes the specified person from the address book. + -Format: `delete INDEX` +You can use this command to add a group to the TA-Tracker. -**** -* Deletes the person at the specified `INDEX`. -* The index refers to the index number shown in the displayed person list. -* The index *must be a positive integer* 1, 2, 3, ... -**** +When a new group is created, the *Student View* will show the groups of the module +this new group belongs to and students of the new group. +If you were on a different `tab`, you will automatically be switched to the `student tab`. +Format: `group add g/GROUP_CODE m/MOD_CODE t/GROUP_TYPE` + +[NOTE] +==== +[horizontal] + +* `MOD_CODE` here refers to the module code of the module you want to add the group to. + +* You cannot add a group to a module that doesn't exist. + +* The GROUP_CODE can't be an empty string or a sequences of spaces. + +* You cannot add multiple groups with the same group code into the same module. + +* You can give a group your own custom GROUP_CODE (ex: 10AMGROUP) if you find it easier +to remember. However, we recommend you use the official +group code for it. +==== + +==== 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. +* `group add g/G03 m/CS2103 t/tutorial` ++ +This will add a group with the group code `G03`, which is a `tutorial`, inside the module that +has module code `CS2103`. +==== -// end::delete[] -=== Clearing all entries : `clear` +[[DeleteGroup]] +==== Deleting a Group : `group delete` -Clears all entries from the address book. + -Format: `clear` +You can use this command to delete a group from the TA-Tracker. -=== Exiting the program : `exit` +When a group is deleted from the TA-Tracker, all students in the group are also deleted. -Exits the program. + -Format: `exit` +When a group is deleted, the *Student View* will show the details of the first group +of the module the group was deleted from. +If you were on a different `tab`, you will automatically be switched to the `student tab`. -=== Saving the data +Format: `group delete g/GROUP_CODE m/MOD_CODE` -Address book data are saved in the hard disk automatically after any command that changes the data. + -There is no need to save manually. +[NOTE] +==== +[horizontal] -// tag::dataencryption[] -=== Encrypting data files `[coming in v2.0]` +* `MOD_CODE` here refers to the module code of the module that contains the group that +you want to delete. -_{explain how the user can enable/disable data encryption}_ -// end::dataencryption[] +* You can't delete a group from a module that doesn't exist. -== FAQ +* A group with the given group code must exist inside the module before you can delete it. +==== -*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. +==== +Examples: -== Command Summary +* `group delete g/G03 m/CS2103` ++ +This will delete the group with the group code `G03` from the module that +has module code `CS2103`. +==== + +[[EditGroup]] +==== Editing a Group : `group edit` + +You can use this command to edit a group in the TA-Tracker. + +This command can be used to change the group code and the group type of the group. +The students inside the group will remain intact. + +When a group is edited, the *Student View* will show the groups in the module that +the edited group belongs to, as well as the students that belong to the edited group. +If you were on a different `tab`, you will automatically be switched to the `student tab`. + +Format: `group edit g/GROUP_CODE m/MOD_CODE [ng/NEW_GROUP_CODE] [nt/NEW_GROUP_TYPE]` + +[NOTE] +==== +[horizontal] +* `MOD_CODE` here refers to the module code of the module that contains the group that +you want to edit. + +* You can't edit a group inside a module that doesn't exist. + +* You can't edit a group that doesn't exist. + +* If you are changing the group code, the module shouldn't contain a group that has the +same group code as the new group code. + +* While the `nt/` and `ng/` prefixes are optional, at least one of them must be mentioned. +==== + +==== +Examples: + +* `group edit g/G03 m/CS2103 nt/tutorial` ++ +This will change the group type of the group with group code `G03`, inside the module with +module code `CS2103`, to be a `tutorial`. +==== + +[[AddStudent]] +==== Adding a Student : `student add` + +You can use this command to add a new student to the TA-Tracker. + +When a new student is added, the *Student View* will show that the new student +is added into the student list of the provided module group. +If you were on a different `tab`, you will automatically be switched to the `student tab`. + +Format: `student add id/MATRIC_NUMBER n/NAME m/MOD_CODE g/GROUP_CODE +[e/EMAIL] [r/RATING] [t/TAG]…​` + +[NOTE] +==== +[horizontal] +* You cannot add a student to a module that does not exist in the TA-Tracker. + +* You cannot add a student to a group that does not exist inside the given module. + +* You cannot add multiple students with the same matric number inside the same module group. + +* Student names are auto-capitalized. However, if the name includes a hyphen (-) with no +space after the hyphen, the second half of the name will not be capitalised. +==== + +==== +Examples: + +* `student add id/A0123456J n/Alice m/CS2103 g/G03` ++ +This will add a student named Alice with the matriculation number `A0123456J` +inside group `G03` of the module `CS2103`. +==== + +[TIP] +==== +[horizontal] +* * You can't edit the `MATRIC_NUMBER` once the student has been created, so do take +care to ensure that the number is correct. +* You can use the rating feature to give your students participation marks. +* A student is given a default rating of 3/5 (average) if you do not specify a rating. +* You can either specify a rating in the add student +command or, +* you can edit their rating later by using the `student edit` command (details given below +in 5.2.9.) +==== + + +[[DeleteStudent]] +==== Deleting a Student : `student delete` + +You can use this command to delete a student from the TA-Tracker. + +When a student is removed, the *Student View* will show that the student +is removed from the student list of the provided module group. +You will see an empty list if there are no more students inside the module group. +If you were on a different `tab`, you will automatically be switched to the `student tab`. + +Format: `student delete id/MATRIC_NUMBER g/GROUP_CODE m/MOD_CODE` + +[NOTE] +==== +[horizontal] +* You cannot remove a student from a module that does not exist in the TA-Tracker. + +* You cannot remove a student from a group that does not exist inside the given module. + +* You cannot remove a student that does not exist inside the given module group. +==== +==== +Examples: + +* `student delete id/A0123456J g/G03 m/CS2103` ++ +Deletes the student with the matriculation number `A0123456J` from group `G03` of +the module `CS2103`. +==== + +[[EditStudent]] +==== Editing a Student : `student edit` + +You can use this command to edit a student in the TA-Tracker. + +When a student is edited, the *Student View* will show that the student has been +edited in the student list of the provided module group. +If you were on a different `tab`, you will automatically be switched to the `student tab`. + +Format: `student edit id/MATRIC_NUMBER m/MOD_CODE g/GROUP_CODE +[n/NAME] [e/EMAIL] [r/RATING] [t/TAG]…​` + +[NOTE] +==== +[horizontal] +* You cannot edit a student in a module that does not exist in the TA-Tracker. + +* You cannot edit a student in a group that does not exist inside the given module. + +* You cannot edit a student that does not exist inside the given module group. + +* You cannot edit a student's matriculation number + +* You must edit the student with at least one of the optional fields. +==== +[TIP] +==== +[horizontal] +* If you edit the tags of a student, the new tags will replace the old tags. + +* You can remove all tags from a student with an empty tag + +(i.e. typing `t/` without specifying any tags after it). +==== +==== +Examples: + +* `student edit id/A0123456J g/G03 m/CS2103 p/91234567 e/johndoe@example.com` ++ +Edits the student with the matriculation number `A0123456J` to have: + +** The new phone number `91234567` +** The new email address `johndoe@example.com` + +* `student edit A9876543K n/Betsy Crower t/` ++ +Edits student with the matriculation number `A9876543K` to have: + +** The new name `Betsy Crower` +** All existing tags removed +==== + +[[Sort]] +==== Sorting a Group : `sort group` + +You can use this command to chose how you want to sort your students in a specific group. +The students will be sorted according to your specified `SORT_TYPE`. + +The *Student View* will show you the students inside the group that you have sorted. +If you were on a different `tab`, you will automatically be switched to the `student tab`. + +Format: `sort group g/GROUP_CODE m/MOD_CODE t/SORT_TYPE` + +[NOTE] +==== +[horizontal] +* You cannot sort a group inside a module that doesn't exist. + +* You cannot sort a group that doesn't exist. + +* To sort alphabetically you can use the following to indicate sort type: +** `alphabetically` +** `alphabetical` +** `alpha` + +* To sort by matriculation number, sort type must be `matric`. + +* To sort by rating in ascending order, sort type must be `rating asc`. + +* To sort by rating in descending order, sort type must be `rating desc`. +==== + +==== +Examples: + +* `sort group g/G03 m/CS2103 t/alpha` ++ +Sorts the student inside `G03` of module `CS2103` `alphabetically`. +==== + + +==== Sorting a Module : `sort module` + +You can use this command to chose how you want to sort your students in a specific module. +The students will be sorted according to your specified `SORT_TYPE`. + +The *Student View* will show you the students inside the first group of the module +that you have sorted. If you want to see the other groups, you can use the `student filter` +command (details given below in 5.2.13.) +If you were on a different `tab`, you will automatically be switched to the `student tab`. + +Format: `sort module m/MOD_CODE t/SORT_TYPE` + +[NOTE] +==== +[horizontal] +* You cannot sort a module that doesn't exist. + +* To sort alphabetically you can use the following to indicate sort type: +** `alphabetically` +** `alphabetical` +** `alpha` + +* To sort by matriculation number, sort type must be `matric`. + +* To sort by rating in ascending order, sort type must be `rating asc`. + +* To sort by rating in descending order, sort type must be `rating desc`. +==== + +==== +Examples: + +* `sort module m/CS2103 t/alpha` ++ +Sorts all students inside all groups inside the module `CS2103` `alphabetically`. +==== + +==== Sorting Everything : `sort all` + +You can use this command to chose how you want to sort all of your students. +The students will be sorted according to your specified `SORT_TYPE`. + +The *Student View* will show you the students inside the first group of the first module +in the TA-Tracker. If you want to see the other groups or modules, you can use the `student filter` +command (details given below in 5.2.13.) +If you were on a different `tab`, you will automatically be switched to the `student tab`. + +Format: `sort all t/SORT_TYPE` + +[NOTE] +==== +[horizontal] +* To sort alphabetically you can use the following to indicate sort type: +** `alphabetically` +** `alphabetical` +** `alpha` + +* To sort by matriculation number, sort type must be `matric`. + +* To sort by rating in ascending order, sort type must be `rating asc`. + +* To sort by rating in descending order, sort type must be `rating desc`. +==== + +==== +Examples: + +* `sort all t/alpha` ++ +Sorts all students in all groups inside all modules `alphabetically`. +==== + +[[FilterStudent]] +==== Filter : `filter` + +You can use this command to see the students in a particular group +and module. + +You can filter the students in *Student View* in the following ways: + +**** +1. module code and group code `m/` `g/` +2. module code `m/` +**** + +You will see the group with the specified `GROUP_CODE` and the module with the specified +`MOD_CODE` highlighted in orange in the list. + +If you were on a different `tab`, you will automatically be switched to the `student tab`. + +Format: `student filter m/MOD_CODE [g/GROUP_CODE]` +[NOTE] +==== +[horizontal] +* Using both module code and group code will show you the students +inside the group with the given group code. + +* The group must belong to the module specified +by the module code. +==== +==== +Examples: + +* `student filter m/cs2103t g/g06` ++ +This will show you all students in module `CS2103T`, under group `G06`. +==== + +If you use just the module code, you will see: + +* The first group of the module specified by the given module code highlighted in orange + +* All the students belonging to that group + +* You can use this command when you want to see all the groups belonging to a +particular module. + +Format: `student filter m/MOD_CODE` + +==== +Examples: + +* `student filter m/cs2103t` ++ +This shows you the students in the first group of the module `CS2103` +==== + +[NOTE] +==== +[horizontal] +*Keywords are case-insensitive. e.g. `cs2103t` is the same as `CS2103T`* +==== + + +=== Session View + +This section explains the different commands that can be used in the *Session View*. + +[[AddSession]] +==== Adding a Session : `session add` + +You can use this command to add a new session. + +The new session will be shown in the *Session View*, and will automatically +be marked as `not done`. + +If you were on a different `tab`, you will automatically be switched to the `session tab`. + +Format: `session add m/MOD_CODE [s/START] [e/END] [d/DATE] [w/WEEKS] [t/SESSION_TYPE] [n/NOTES]` + + +[TIP] +==== +. If you want to create a session with the current date, start time and end time, +you can omit `s/START` , `e/END` and `d/DATE`. + +. You can edit the end time `e/END` and other details later by using the `session edit` +command (details given below in 5.3.3.). + +. If the session has a recurring period `[w/WEEKS]`, a new session with the updated date +and timing will be added to *Session View* when it is marked as done. + +. You will find the recurring period `[w/WEEKS]` field handy for tasks that happen once every few weeks +such as your weekly lab sessions or fortnightly assignment grading. + +==== + +==== +Examples: + +* `session add m/CS2103T s/14:00 e/16:00 d/2020-06-20 w/2 t/consultation n/with Alice and Bob` ++ +Adds a new session with: + +** `START_TIME` at 14:00 in `24hr` format +** `END_TIME` at 16:00 in `24hr` format +** `DATE` on 2020-06-20 in `yyyy-MM-dd` format +** `WEEK` recurring period of 2 weeks: Once this session has been marked as done, a new session will +be created with `DATE d/2020-07-04` (2 weeks later from the date of the original session) +** `MOD_CODE` of CS2103T +** `TYPE` consultation +** `NOTE` "with Alice and Bob" + +==== +==== Deleting a Session : `session delete` + +You can use this command to delete a session at a specific `index`. + +If you were on a different `tab`, you will automatically be switched to the `session tab`. + +Format: `session delete INDEX` + +[CAUTION] +==== +[horizontal] +Do not confuse the `session delete` and `session done` commands. +==== + +==== +Examples: + +* `session delete 3` ++ +This command deletes the third session in the *Session View*. +==== + +==== Editing a Session : `session edit` +You can use this command to edit a session in the TA-Tracker. + +If you were on a different `tab`, you will automatically be switched to the `session tab`. + +Format: `session edit INDEX [s/START_TIME] [e/END_TIME] [d/DATE] [w/RECUR] +[m/MODULE] [t/SESSION_TYPE] [n/NOTES]​` + +[NOTE] +==== +[horizontal] + +* You must edit the session with at least one of the optional fields. +==== +==== +Examples: + +* `edit 1 s/14:00 e/16:00 d/2020-02-19 m/CS3243 t/grading n/Location: PLAB 04` ++ +Edits the session at index 1 to have: + +** The new `START_TIME` at 14:00 +** The new `END_TIME` at 16:00 +** The new `DATE` on 2020-02-19 in `yyyy-MM-dd` format +** The new `MOD_CODE` CS3243 +** The new `TYPE` grading +** The new `NOTE` "Location: PLAB04" +==== + +==== Marking a Session as Done : `session done` + +You can use this command to mark a session as done. + +The session marked as done will be removed from the *Session View* and +will automatically appear as a new claim in the *Claims View*. +If the session marked done has a recurring period, a new session with the updated date +and timing will be added to *Session View*. + +If you were on a different `tab`, you will automatically be switched to the `claims tab`. + +Format: `session done INDEX` + +Marks the session with the given unique index as done. + +==== +Examples: + +* `session done 25` + +This will mark the 25th session in the *Session View* as done. +==== + +==== Filter : `filter` + +You can use this command to filter the sessions in the *Session View*. + +You can filter the sessions in the following ways: + +* `MOD_CODE` - filtering by module code will show you only the sessions affiliated +with that module. + +* `SESSION_TYPE` - filtering by session type will show you only the sessions with the specified type + +* `DATE` - filtering by a date will show you only the sessions on that date. + +The keyword is case-insensitive. + +When you execute a new filter command, any previous filters applied on the sessions will +be removed and only the filters from the latest command will be applied on the sessions +list. The filters that are currently being applied are shown at the top of the *Session View* + +If you were on a different `tab`, you will automatically be switched to the `session tab`. + +Format: `session filter [m/MOD_CODE] [t/SESSION_TYPE] [d/DATE]` + +==== +Example: + +* `session filter m/CS2103T` ++ +All sessions in the *Session View* with module code `CS2103T` are displayed. + +* `session filter m/CS2030T t/tutorial d/2020-03-20` ++ +All sessions in the *Session View* that take place on `2020-03-20`, with module code `CS2103T`, +session type ‘Tutorial` will be shown. +==== + +=== Claims View + +==== Filter : `filter` + +You can use this command to filters sessions in the *Claims View* by the module code. + +You will see the module with the specified `MOD_CODE` highlighted in orange in the list. + +All completed sessions with matching `MOD_CODE` are displayed. Keyword is case-insensitive. + +Format: `claims filter m/MOD_CODE` + +==== +Example: + +* `claims filter m/CS2103T` ++ +All sessions in the *Claims View* with module code `CS2103T` are displayed. +==== + +[#setrate] +==== Changing the hourly rate : `setrate` + +Sets the hourly rate for the total income and claim computation. + +Format: `setrate RATE` + +[NOTE] +==== +* If you don't specify a rate, it is set at $40.00 by default (the rate at which most SOC TAs are being paid per hour). +* `RATE` is the amount you want to change the hourly rate to, this value will be used to calulate the +`Total Earnings` label in the `Claims Tab` as well as the `Statistics Window`. +* The `RATE` must be a positive integer. +==== + +Examples: + +* `setrate 25` + +Sets the current hourly rate to $25.00. + +=== Statistics Window + +==== Generate Statistic Report : `report` + +You can use this command to generate a report to display information such as: + +* A breakdown and summary of completed sessions +* The number of hours of each type of completed sessions +* A breakdown of your student's ratings + +Optionally, you can specify a module code. If a module code is specified, the report generated will only include data from the specified module. + +Pressing the `esc` key on your keyboard will close the statistics window. + +Format: `report [MOD_CODE]` + +[NOTE] +==== +* Similar to the *Claims View*, the report will only display sessions that have been marked as done. +* `Total Claimable Hours` is computed using the current specified `rate`. See <<#setrate>>. +==== + +Example: + +* `report` ++ +Generate and display a report of sessions and students from all modules. + +* `report CS3243` ++ +Generate and display a report of sessions and students from the module CS3243. + +== Glossary + +[width="%",cols="<20%,<40,<40,options="header",] +|======================================================================= +|Term | Explanation | Examples + +| TSS Claims Form | This refers the claims form that Teaching Assistants +at NUS School of Computing have to fill up at the end of each semester to claim money +for the tasks they have completed. | + +| TA | This is the short form for `Teaching Assistant. | + +| SOC or SoC | This is the short form for School of Computing. | + +| CLI | This is the short form for Command Line Interface. It processes commands to +TA-Tracker in the form of lines of text | + +| GUI | This is the short form for Graphical User Interface. It is a form of user interface +that allows users to interact with electronic devices through graphical icons | + +| Index | This refers to the position of an item on a list. | Index of 1 refers to the first +item in a list. + +| Matric Number | This refers to a student's matriculation number. | A0123456X + +| Group | The is the general term given to a group of students a TA teaches. | +lab , tutorial , recitation + +| TAT | This is the short form of TA-Tracker. | + +| NUS | This is the short form of National Univeristy of Singapore. | + +| Module | Refers to one of the academic courses in NUS. | + +| Tutorial | A tutorial is a regular meeting between a tutor and one or several +students, for discussion of a subject that is being studied. | + +|======================================================================= + + +== FAQ + +*Q*: How can I transfer my data to another Computer? + +*A*: You can do so by first installing the app in another computer. You can then replace +the empty data file it creates with the data file of your previous TA-Tracker folder. + +== Command Summary + +=== Navigation +* *View Help:* `help` +* *Switch Tabs:* `goto TAB_NAME` +* *Exit the Program:* `exit` + +=== Student View + +==== Module commands +* *Add Module:* `module add m/MOD_CODE n/NAME` +* *Edit Module:* `module edit m/MOD_CODE n/NEW NAME` +* *Delete Module:* `module delete m/MOD_CODE` + +==== Group commands +* *Add Group:* `group add g/GROUP_CODE m/MOD_CODE t/GROUP_TYPE` +* *Edit Group:* `group edit g/GROUP_CODE m/MOD_CODE [ng/NEW_GROUP_CODE] [nt/NEW_GROUP_TYPE]` +* *Delete Group:* `group delete g/GROUP_CODE m/MOD_CODE` + +==== Student commands +* *Add Student:* `student add id/MATRIC_NUMBER n/NAME m/MOD_CODE g/GROUP_CODE [e/EMAIL] [r/RATING] [t/TAG]…` +* *Delete Student:* `student delete id/MATRIC_NUMBER m/MOD_CODE g/GROUP_CODE` +* *Edit Student:* `student edit id/MATRIC_NUMBER m/MOD_CODE g/GROUP_CODE [n/NAME] [e/EMAIL] [r/RATING] [t/TAG]…` + +==== Others +* *Sort Group:* `sort group g/GROUP_CODE m/MOD_CODE t/SORT_TYPE` +* *Sort Module:* `sort module m/MOD_CODE t/SORT_TYPE` +* *Sort All Modules:* `sort all t/SORT_TYPE` +* *Filter Students:* `student filter m/MOD_CODE [g/GROUP_CODE]` + +=== Session View + +==== Session commands +* *Add Session:* `session add m/MOD_CODE [s/START] [e/END] [d/DATE] [w/WEEK] [t/SESSION_TYPE] [n/NOTES]` +* *Delete Session:* `session delete INDEX` +* *Edit Session:* `session edit INDEX [s/START_TIME] [e/END_TIME] [d/DATE] [w/RECUR] [m/MODULE] [t/SESSION_TYPE] [n/NOTES]` +* *Done Session:* `session done INDEX` + +==== Others +* *Filter Sessions:* `session filter [d/DATE] [m/MOD_CODE] [t/SESSION_TYPE]` + +=== Claims View +* *Set Rate:* `setrate RATE` +* *Filter Claims:* `claims filter m/MOD_CODE` -* *Add* `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]...` + -e.g. `add n/James Ho p/22224444 e/jamesho@example.com a/123, Clementi Rd, 1234665 t/friend t/colleague` -* *Clear* : `clear` -* *Delete* : `delete INDEX` + -e.g. `delete 3` -* *Edit* : `edit INDEX [n/NAME] [p/PHONE_NUMBER] [e/EMAIL] [a/ADDRESS] [t/TAG]...` + -e.g. `edit 2 n/James Lee e/jameslee@example.com` -* *Find* : `find KEYWORD [MORE_KEYWORDS]` + -e.g. `find James Jake` -* *List* : `list` -* *Help* : `help` +=== Statistics +* *Generate Statistics Report:* `report [MOD_CODE]` diff --git a/docs/UsingCheckstyle.adoc b/docs/UsingCheckstyle.adoc index a12ab09cc9c..80358b66e32 100644 --- a/docs/UsingCheckstyle.adoc +++ b/docs/UsingCheckstyle.adoc @@ -11,7 +11,7 @@ endif::[] [NOTE] ==== This document was originally written for _AddressBook Level 4_ and hence its screenshots refer to `addressbook-level4`. -For use with _AddressBook Level 3_, wherever `addressbook-level4` is used in the screenshots, you should use *`addressbook-level3`*. +For use with _TA-Tracker_, wherever `addressbook-level4` is used in the screenshots, you should use *`tatracker`*. ==== == Configuring Checkstyle-IDEA diff --git a/docs/diagrams/ArchitectureSequenceDiagram.puml b/docs/diagrams/ArchitectureSequenceDiagram.puml index d1e2ae93675..f9c0d2a7a5f 100644 --- a/docs/diagrams/ArchitectureSequenceDiagram.puml +++ b/docs/diagrams/ArchitectureSequenceDiagram.puml @@ -7,19 +7,19 @@ Participant ":Logic" as logic LOGIC_COLOR Participant ":Model" as model MODEL_COLOR Participant ":Storage" as storage STORAGE_COLOR -user -[USER_COLOR]> ui : "delete 1" +user -[USER_COLOR]> ui : "session delete 1" activate ui UI_COLOR -ui -[UI_COLOR]> logic : execute("delete 1") +ui -[UI_COLOR]> logic : execute("session delete 1") activate logic LOGIC_COLOR -logic -[LOGIC_COLOR]> model : deletePerson(p) +logic -[LOGIC_COLOR]> model : deleteSession(s) activate model MODEL_COLOR model -[MODEL_COLOR]-> logic deactivate model -logic -[LOGIC_COLOR]> storage : saveAddressBook(addressBook) +logic -[LOGIC_COLOR]> storage : saveTaTracker(taTracker) activate storage STORAGE_COLOR storage -[STORAGE_COLOR]> storage : Save to file diff --git a/docs/diagrams/BetterModelClassDiagram.puml b/docs/diagrams/BetterModelClassDiagram.puml deleted file mode 100644 index 7790472da52..00000000000 --- a/docs/diagrams/BetterModelClassDiagram.puml +++ /dev/null @@ -1,21 +0,0 @@ -@startuml -!include style.puml -skinparam arrowThickness 1.1 -skinparam arrowColor MODEL_COLOR -skinparam classBackgroundColor MODEL_COLOR - -AddressBook *-right-> "1" UniquePersonList -AddressBook *-right-> "1" UniqueTagList -UniqueTagList -[hidden]down- UniquePersonList -UniqueTagList -[hidden]down- UniquePersonList - -UniqueTagList *-right-> "*" Tag -UniquePersonList o-right-> Person - -Person o-up-> "*" Tag - -Person *--> Name -Person *--> Phone -Person *--> Email -Person *--> Address -@enduml diff --git a/docs/diagrams/CommandsPackageDiagram.puml b/docs/diagrams/CommandsPackageDiagram.puml new file mode 100644 index 00000000000..63b0fdc0b23 --- /dev/null +++ b/docs/diagrams/CommandsPackageDiagram.puml @@ -0,0 +1,119 @@ +@startuml +!include style.puml +skinparam arrowThickness 1.1 +skinparam arrowColor LOGIC_COLOR +skinparam classBackgroundColor LOGIC_COLOR + +Package Commands <>{ +Class "{abstract}\nCommand" as Command + +Package Commons {} +Package Module {} +Package Group {} +Package Student {} +Package Sort {} +Package Session {} +Package Statistic {} + +Commons .down.> Command +Module .down.> Command +Group .down.> Command +Student .down.> Command +Sort .down.> Command +Session .down.> Command +Statistic .down.> Command +@enduml + +@startuml +!include style.puml +skinparam arrowThickness 1.1 +skinparam arrowColor LOGIC_COLOR +skinparam classBackgroundColor WHITE +skinparam classHeaderBackgroundColor LOGIC_COLOR +skinparam classBorderColor LOGIC_COLOR +skinparam classAttributeIconSize 0 + +Package Commands <>{ +Class CommandDictionary + +Class CommandDetails { + - commandWord : String + - subWord : String + - info : String + - usage : String + - example : String +} + +show CommandDetails members +hide CommandDetails methods + +Package X { + Class XCommand +} + +Class CommandWords + + +CommandDictionary -right-> "*" CommandDetails : command details +XCommand -up-> "1" CommandDetails + +Class "{abstract}\nCommand" as Command +XCommand -left-|> Command +XCommand .right.> CommandWords +@enduml + +@startuml +!include style.puml +skinparam arrowThickness 1.1 +skinparam arrowColor LOGIC_COLOR +skinparam classBackgroundColor LOGIC_COLOR + +Package Commands <>{ +Class CommandDictionary +Class CommandDetails { + String id +} + + +Class CommandWords + +Package Commons { + Class HI1 #FFFFFF +} +Package Module { + Class HI2 #FFFFFF +} +Package Group { + Class HI3 #FFFFFF +} +Package Student { + Class HI4 #FFFFFF +} +Package Sort { + Class HI5 #FFFFFF +} +Package Session { + Class HI6 #FFFFFF +} +Package Statistic { + Class HI7 #FFFFFF +} + +CommandDictionary --> "*" CommandDetails : command details + +HI1 .up.> CommandDetails +HI2 .up.> CommandDetails +HI3 .up.> CommandDetails +HI4 .up.> CommandDetails +HI5 .up.> CommandDetails +HI6 .up.> CommandDetails +HI7 .up.> CommandDetails + +HI1 .down.> CommandWords +HI2 .down.> CommandWords +HI3 .down.> CommandWords +HI4 .down.> CommandWords +HI5 .down.> CommandWords +HI6 .down.> CommandWords +HI7 .down.> CommandWords +@enduml diff --git a/docs/diagrams/CommitActivityDiagram.puml b/docs/diagrams/CommitActivityDiagram.puml index 7f8fe407f89..5c57b909c00 100644 --- a/docs/diagrams/CommitActivityDiagram.puml +++ b/docs/diagrams/CommitActivityDiagram.puml @@ -5,10 +5,10 @@ start 'Since the beta syntax does not support placing the condition outside the 'diamond we place it as the true branch instead. -if () then ([command commits AddressBook]) +if () then ([command commits TaTracker]) :Purge redunant states; - :Save AddressBook to - addressBookStateList; + :Save TaTracker to + taTrackerStateList; else ([else]) endif stop diff --git a/docs/diagrams/DeleteSequenceDiagram.puml b/docs/diagrams/DeleteSequenceDiagram.puml deleted file mode 100644 index 1dc2311b245..00000000000 --- a/docs/diagrams/DeleteSequenceDiagram.puml +++ /dev/null @@ -1,69 +0,0 @@ -@startuml -!include style.puml - -box Logic LOGIC_COLOR_T1 -participant ":LogicManager" as LogicManager LOGIC_COLOR -participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR -participant ":DeleteCommandParser" as DeleteCommandParser LOGIC_COLOR -participant "d:DeleteCommand" as DeleteCommand LOGIC_COLOR -participant ":CommandResult" as CommandResult LOGIC_COLOR -end box - -box Model MODEL_COLOR_T1 -participant ":Model" as Model MODEL_COLOR -end box - -[-> LogicManager : execute("delete 1") -activate LogicManager - -LogicManager -> AddressBookParser : parseCommand("delete 1") -activate AddressBookParser - -create DeleteCommandParser -AddressBookParser -> DeleteCommandParser -activate DeleteCommandParser - -DeleteCommandParser --> AddressBookParser -deactivate DeleteCommandParser - -AddressBookParser -> DeleteCommandParser : parse("1") -activate DeleteCommandParser - -create DeleteCommand -DeleteCommandParser -> DeleteCommand -activate DeleteCommand - -DeleteCommand --> DeleteCommandParser : d -deactivate DeleteCommand - -DeleteCommandParser --> AddressBookParser : d -deactivate DeleteCommandParser -'Hidden arrow to position the destroy marker below the end of the activation bar. -DeleteCommandParser -[hidden]-> AddressBookParser -destroy DeleteCommandParser - -AddressBookParser --> LogicManager : d -deactivate AddressBookParser - -LogicManager -> DeleteCommand : execute() -activate DeleteCommand - -DeleteCommand -> Model : deletePerson(1) -activate Model - -Model --> DeleteCommand -deactivate Model - -create CommandResult -DeleteCommand -> CommandResult -activate CommandResult - -CommandResult --> DeleteCommand -deactivate CommandResult - -DeleteCommand --> LogicManager : result -deactivate DeleteCommand - -[<--LogicManager -deactivate LogicManager -@enduml diff --git a/docs/diagrams/GotoSequenceDiagram.puml b/docs/diagrams/GotoSequenceDiagram.puml new file mode 100644 index 00000000000..ef3c7878824 --- /dev/null +++ b/docs/diagrams/GotoSequenceDiagram.puml @@ -0,0 +1,69 @@ +@startuml +!include style.puml + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":TaTrackerParser" as TaTrackerParser LOGIC_COLOR +participant ":GotoCommandParser" as GotoCommandParser LOGIC_COLOR +participant "g:GotoCommand" as GotoCommand LOGIC_COLOR +participant ":CommandResult" as CommandResult LOGIC_COLOR +end box + +box UI UI_COLOR_T1 +participant ":MainWindow" as MainWindow UI_COLOR +end box + +[-> LogicManager : execute("goto claims") +activate LogicManager + +LogicManager -> TaTrackerParser : parseCommand("goto claims") +activate TaTrackerParser + +create GotoCommandParser +TaTrackerParser -> GotoCommandParser +activate GotoCommandParser + +GotoCommandParser --> TaTrackerParser +deactivate GotoCommandParser + +TaTrackerParser -> GotoCommandParser : parse("claims") +activate GotoCommandParser + +create GotoCommand +GotoCommandParser -> GotoCommand +activate GotoCommand + +GotoCommand --> GotoCommandParser : g +deactivate GotoCommand + +GotoCommandParser --> TaTrackerParser : g +deactivate GotoCommandParser +'Hidden arrow to position the destroy marker below the end of the activation bar. +GotoCommandParser -[hidden]-> TaTrackerParser +destroy GotoCommandParser + +TaTrackerParser --> LogicManager : g +deactivate TaTrackerParser + +LogicManager -> GotoCommand : execute() +activate GotoCommand + +GotoCommand -> MainWindow : handleGoto(claimsTab) +activate MainWindow + +MainWindow --> GotoCommand +deactivate MainWindow + +create CommandResult +GotoCommand -> CommandResult +activate CommandResult + +CommandResult --> GotoCommand +deactivate CommandResult + +GotoCommand --> LogicManager : result +deactivate GotoCommand + +[<--LogicManager +deactivate LogicManager +@enduml diff --git a/docs/diagrams/LogicClassDiagram.puml b/docs/diagrams/LogicClassDiagram.puml index 016ef33e2e2..87c3e9144bf 100644 --- a/docs/diagrams/LogicClassDiagram.puml +++ b/docs/diagrams/LogicClassDiagram.puml @@ -4,59 +4,214 @@ skinparam arrowThickness 1.1 skinparam arrowColor LOGIC_COLOR_T4 skinparam classBackgroundColor LOGIC_COLOR -package Logic { +package Model {} +package Storage {} -package Parser { -Interface Parser <> -Class AddressBookParser -Class XYZCommandParser -Class CliSyntax -Class ParserUtil -Class ArgumentMultimap -Class ArgumentTokenizer -Class Prefix -} +package Logic as LogicPkg { -package Command { -Class XYZCommand -Class CommandResult -Class "{abstract}\nCommand" as Command -} + Interface Logic <> + Class LogicManager -Interface Logic <> -Class LogicManager -} + package Parser as ParserPkg { + Interface Parser <> + Class TaTrackerParser + + Parser .up[hidden]..> TaTrackerParser + } + + package Commands as CommandPkg { + Class CommandResult + Class "{abstract}\nCommand" as Command -package Model{ -Class HiddenModel #FFFFFF + CommandResult <.left. Command + } + + Logic .down.> CommandResult + + Logic <|.left. LogicManager + + LogicManager -right-> "1" TaTrackerParser + LogicManager .down.> CommandResult + LogicManager .down.> Command } Class HiddenOutside #FFFFFF -HiddenOutside ..> Logic - -LogicManager .up.|> Logic -LogicManager -->"1" AddressBookParser -AddressBookParser .left.> XYZCommandParser: creates > - -XYZCommandParser ..> XYZCommand : creates > -XYZCommandParser ..|> Parser -XYZCommandParser ..> ArgumentMultimap -XYZCommandParser ..> ArgumentTokenizer -ArgumentTokenizer .left.> ArgumentMultimap -XYZCommandParser ..> CliSyntax -CliSyntax ..> Prefix -XYZCommandParser ..> ParserUtil -ParserUtil .down.> Prefix -ArgumentTokenizer .down.> Prefix -XYZCommand -up-|> Command -LogicManager .left.> Command : executes > - -LogicManager --> Model +'HiddenOutside .[hidden]down.> LogicPkg +HiddenOutside .down.> Logic + +LogicManager -up-> Storage + +LogicManager -right-> Model Command .right.> Model -note right of XYZCommand: XYZCommand = AddCommand, \nFindCommand, etc -Logic ..> CommandResult -LogicManager .down.> CommandResult -Command .up.> CommandResult -CommandResult -[hidden]-> Parser +@enduml + +@startuml +!include style.puml +skinparam arrowThickness 1.1 +skinparam arrowColor LOGIC_COLOR_T4 +skinparam classBackgroundColor LOGIC_COLOR + +package Logic { + package Parser as ParserPkg { + + Class TaTrackerParser + Interface Parser <> + + + package X as ModelParser { + Class XCommandParser + Class XYCommandParser + + TaTrackerParser .down.> XCommandParser + XCommandParser .down.> XYCommandParser + } + + XYCommandParser .right.|> Parser + + Class Prefix + + Class Prefixes + + Class ArgumentTokenizer + Class ArgumentMultimap + + Class ParserUtil + + + Prefixes -right[hidden]- ParserUtil + + XYCommandParser .down.> ArgumentMultimap + XYCommandParser .down.> ArgumentTokenizer + XYCommandParser .down[#Red].> Prefixes + XYCommandParser .down.> ParserUtil + + ArgumentTokenizer .right.> ArgumentMultimap + + ArgumentTokenizer .down.> Prefix + ParserUtil .down.> Prefix + Prefixes -down-> "*" Prefix + } + + package Commands as ModelCommand { + package X { + Class XYCommand + } + + Class CommandResult + Class "{abstract}\nCommand" as Command + + Command .up.> CommandResult + XYCommand -up-|> Command + + XYCommand .down[LOGIC_COLOR_T4].> Prefixes + } + + XYCommandParser .right.> XYCommand +} +@enduml + +@startuml +!include style.puml +skinparam arrowThickness 1.1 +skinparam arrowColor LOGIC_COLOR_T4 +skinparam classBackgroundColor LOGIC_COLOR + +package Logic { + package Parser as ParserPkg { + + Class TaTrackerParser + Interface Parser <> + + + package X as ModelParser { + Class XCommandParser + + TaTrackerParser ..down[#Red].> XCommandParser + } + + XCommandParser .right.|> Parser + } + + package Commands as ModelCommand { + package X { + Class XCommand + } + + Class CommandResult + Class "{abstract}\nCommand" as Command + + Command .up.> CommandResult + XCommand -up-|> Command + } + + XCommandParser .right.> XCommand +} +@enduml + +@startuml +!include style.puml +skinparam arrowThickness 1.1 +skinparam arrowColor LOGIC_COLOR_T4 +skinparam classBackgroundColor LOGIC_COLOR + +package Logic { + package Parser as ParserPkg { + + Class TaTrackerParser + Interface Parser <> + + + package X as ModelParser { + Class XCommandParser + Class XYCommandParser + + TaTrackerParser .down.> XCommandParser + XCommandParser .down.> XYCommandParser + } + + XYCommandParser .right.|> Parser + + Class Prefix + + Class ArgumentTokenizer + Class ArgumentMultimap + + Class ParserUtil + + + XYCommandParser .down.> ArgumentMultimap + XYCommandParser .down.> ArgumentTokenizer + XYCommandParser .down.> ParserUtil + + ArgumentTokenizer .right.> ArgumentMultimap + + ArgumentTokenizer .down.> Prefix + ParserUtil .down.> Prefix + + } + + package Commands as ModelCommand { + package X { + Class XYCommand + } + + Class Prefixes #RED + + Class CommandResult + Class "{abstract}\nCommand" as Command + + Command .up.> CommandResult + XYCommand -up-|> Command + + XYCommand .down[LOGIC_COLOR_T4].> Prefixes + } + + XYCommandParser .down[#RED].> Prefixes + + Prefixes -right[hidden]- ParserUtil + Prefixes -down-> "*" Prefix + + XYCommandParser .right.> XYCommand +} @enduml diff --git a/docs/diagrams/ModelClassDiagram.puml b/docs/diagrams/ModelClassDiagram.puml index e85a00d4107..f4b4431a917 100644 --- a/docs/diagrams/ModelClassDiagram.puml +++ b/docs/diagrams/ModelClassDiagram.puml @@ -5,52 +5,50 @@ skinparam arrowColor MODEL_COLOR skinparam classBackgroundColor MODEL_COLOR Package Model <>{ -Interface ReadOnlyAddressBook <> +Interface ReadOnlyTaTracker <> Interface Model <> Interface ObservableList <> -Class AddressBook -Class ReadOnlyAddressBook +Class TaTracker +Class ReadOnlyTaTracker Class Model Class ModelManager Class UserPrefs Class ReadOnlyUserPrefs -Package Person { -Class Person -Class Address -Class Email -Class Name -Class Phone -Class UniquePersonList +Package Student { +Class UniqueStudentList } -Package Tag { -Class Tag +Package Session { +Class UniqueSessionList +Class UniqueDoneSessionList } + +Package Group { +Class UniqueGroupList +} + +Package Module { +Class UniqueModuleList +} + } Class HiddenOutside #FFFFFF HiddenOutside ..> Model -AddressBook .up.|> ReadOnlyAddressBook +TaTracker .up.|> ReadOnlyTaTracker ModelManager .up.|> Model Model .right.> ObservableList -ModelManager o--> "1" AddressBook +ModelManager o--> "1" TaTracker ModelManager o-left-> "1" UserPrefs UserPrefs .up.|> ReadOnlyUserPrefs -AddressBook *--> "1" UniquePersonList -UniquePersonList o--> "*" Person -Person *--> Name -Person *--> Phone -Person *--> Email -Person *--> Address -Person *--> "*" Tag - -Name -[hidden]right-> Phone -Phone -[hidden]right-> Address -Address -[hidden]right-> Email +TaTracker *--> "1" UniqueStudentList +TaTracker *--> "1" UniqueModuleList +TaTracker *--> "1" UniqueGroupList +TaTracker *--> "1" UniqueSessionList +TaTracker *--> "1" UniqueDoneSessionList -ModelManager -->"1" Person : filtered list @enduml diff --git a/docs/diagrams/ModelComponentsClassDiagram.puml b/docs/diagrams/ModelComponentsClassDiagram.puml new file mode 100644 index 00000000000..694c6c66a7c --- /dev/null +++ b/docs/diagrams/ModelComponentsClassDiagram.puml @@ -0,0 +1,45 @@ +@startuml +!include /style.puml +package Model { + +skinparam arrowThickness 1.1 +skinparam arrowColor MODEL_COLOR +skinparam classBackgroundColor MODEL_COLOR + +class TaTracker + +package Module { +class Module +class UniqueModuleList +} +package Group { +class Group +class UniqueGroupList +} +package Session { +class Session +class UniqueSessionList +class UniqueDoneSessionList +} +package Student { +class Student +class UniqueStudentList +} +} + +TaTracker --> "1 " UniqueModuleList + +UniqueModuleList -right-> "*" Module + +Module --> "1" UniqueGroupList +TaTracker --> "1 " UniqueSessionList +TaTracker --> "1 " UniqueDoneSessionList + +UniqueGroupList -left-> "*" Group +UniqueSessionList -up-> "*" Session +UniqueDoneSessionList -down-> "*" Session + +Group --> "1 " UniqueStudentList +UniqueStudentList -right-> "*" Student + +@enduml diff --git a/docs/diagrams/ModelObjectDiagram.puml b/docs/diagrams/ModelObjectDiagram.puml new file mode 100644 index 00000000000..0860b56bbe2 --- /dev/null +++ b/docs/diagrams/ModelObjectDiagram.puml @@ -0,0 +1,91 @@ +@startuml + +package Student { + class Student1 as "__irfan:Student__" { + name = "Irfan Ibrahim" + matric = "A0180474R" + rating = 2 + email = "irfan@example.com" + phone = "92492021" + tag = "classmates" + } + + class Student2 as "__jeffry:Student__" { + name = "Jeffry Lum" + matric = "A0195558H" + rating = 5 + email = "Jeffry@u.nus.edu" + phone = "65162727" + tag = "tutors" + } + + class Student3 as "__alex:Student__" { + name = "Alex Yeoh" + matric = "A0187945J" + rating = 4 + email = "alexyeoh@example.com" + phone = "87438807" + tag = "classmates" + } + + class Student4 as "__charlotte:Student__" { + name = "Charlotte Oliveiro" + matric = "A0187565N" + rating = 5 + email = "charlotte@example.com" + phone = "93210283" + tag = "neighbours" + } +} + +package Group { + class Group1 as "__g02:Group__" { + id = "G02" + group_type = RECITATION + } + + class Group2 as "__g03:Group__" { + id = "G03" + group_type = OTHER + } +} + +package Module { + class Module1 as "__cs2103t:Module__" { + id = "CS2103T" + name = "Software Engineering" + } +} + +package Session { + class Session1 as "__tutorial:Session__" { + session_type = TUTORIAL + date = "2020-06-26" + start = "14:00" + end = "15:00" + notes = "check work" + week = 1 + } + + class Session2 as "__grading:Session__" { + session_type = GRADING + date = "2020-03-21" + start = "09:30" + end = "11:00" + notes = "grade group 1 first" + week = 0 + } +} + +Group1 -- Module1 +Group2 -- Module1 + +Student1 -- Group2 +Student2 -- Group2 +Student3 -- Group1 +Student4 -- Group1 + +Session1 -up- Module1 +Session2 -up- Module1 + +@enduml diff --git a/docs/diagrams/ParserPackageDiagram.puml b/docs/diagrams/ParserPackageDiagram.puml new file mode 100644 index 00000000000..b56f287666d --- /dev/null +++ b/docs/diagrams/ParserPackageDiagram.puml @@ -0,0 +1,127 @@ +@startuml +!include style.puml +skinparam arrowThickness 1.1 +skinparam arrowColor LOGIC_COLOR +skinparam classBackgroundColor LOGIC_COLOR + +Package Parser as ParserPkg <>{ +Interface Parser <> + +Package Commons {} +Package Module {} +Package Group {} +Package Student {} +Package Sort {} +Package Session {} +Package Statistic {} + +Commons .down.> Parser +Module .down.> Parser +Group .down.> Parser +Student .down.> Parser +Sort .down.> Parser +Session .down.> Parser +Statistic .down.> Parser +@enduml + +@startuml +!include style.puml +skinparam arrowThickness 1.1 +skinparam arrowColor LOGIC_COLOR +skinparam classBackgroundColor LOGIC_COLOR + +Package Parser <>{ +Class Prefixes +Class Prefix + +Package Commons {} +Package Module {} +Package Group {} +Package Student {} +Package Sort {} +Package Session {} +Package Statistic {} + +Commons .down.> Prefixes +Module .down.> Prefixes +Group .down.> Prefixes +Student .down.> Prefixes +Sort .down.> Prefixes +Session .down.> Prefixes +Statistic .down.> Prefixes + +Prefixes .down.> Prefix +@enduml + +@startuml +!include style.puml +skinparam arrowThickness 1.1 +skinparam arrowColor LOGIC_COLOR +skinparam classBackgroundColor LOGIC_COLOR + + +Package Parser <>{ +Class PrefixDetails +Class Prefixes +Class PrefixDictionary +Class Prefix + +Interface Parser <> +Package X { + Class XCommandParser +} + +Prefixes <.left. XCommandParser +XCommandParser .up.|> Parser + +PrefixDictionary -left-> "*" Prefix : parameters +PrefixDictionary -left-> "*" Prefix : optionals +PrefixDictionary .up.> Prefixes +PrefixDictionary -down-> "*" PrefixDetails : prefix details + +Prefixes .down.> Prefix +PrefixDetails -up-> "0..1" Prefix +@enduml + +@startuml +!include style.puml +skinparam arrowThickness 1.1 +skinparam arrowColor LOGIC_COLOR +skinparam classBackgroundColor WHITE +skinparam classHeaderBackgroundColor LOGIC_COLOR +skinparam classBorderColor LOGIC_COLOR +skinparam classAttributeIconSize 0 + +Package Parser <>{ + +Class PrefixDetails { + - constraint : String + - examples : List +} + +Interface Predicate <> +PrefixDetails .> Predicate + +show PrefixDetails members +hide PrefixDetails methods + +Class Prefixes +Class PrefixDictionary +Class Prefix + +Interface Parser <> +Package X { + Class XCommandParser +} + +Prefixes <.left. XCommandParser +XCommandParser .up.|> Parser + +PrefixDictionary -left-> "*" Prefix : parameters +PrefixDictionary -left-> "*" Prefix : optionals +PrefixDictionary .up.> Prefixes +PrefixDictionary -down-> "*" PrefixDetails : prefix details + +Prefixes .down.> Prefix +PrefixDetails -up-> "0..1" Prefix +@enduml diff --git a/docs/diagrams/RecurringSession/DoneSessionActivityDiagram.puml b/docs/diagrams/RecurringSession/DoneSessionActivityDiagram.puml new file mode 100644 index 00000000000..64560a44475 --- /dev/null +++ b/docs/diagrams/RecurringSession/DoneSessionActivityDiagram.puml @@ -0,0 +1,19 @@ +@startuml +start +:User executes `session done` Command; + +:Checks the recurring period of the session; + +if () then ([recurring period > 0]) + :Creates a new Session with the updated date; + +else ([else]) +endif + +:Remove the current session from session view; +:Current session added under Claims view; +:Shows user the added Session; + +stop +@enduml + diff --git a/docs/diagrams/StorageClassDiagram.puml b/docs/diagrams/StorageClassDiagram.puml index 6adb2e156bf..855d1d47339 100644 --- a/docs/diagrams/StorageClassDiagram.puml +++ b/docs/diagrams/StorageClassDiagram.puml @@ -3,22 +3,93 @@ skinparam arrowThickness 1.1 skinparam arrowColor STORAGE_COLOR skinparam classBackgroundColor STORAGE_COLOR +skinparam linetype ortho +skinparam nodesep 50 Interface Storage <> Interface UserPrefsStorage <> -Interface AddressBookStorage <> +Interface TaTrackerStorage <> Class StorageManager Class JsonUserPrefsStorage -Class JsonAddressBookStorage +Class JsonTaTrackerStorage -StorageManager .left.|> Storage -StorageManager o-right-> UserPrefsStorage -StorageManager o--> AddressBookStorage +StorageManager .up.|> Storage +StorageManager "1" o-left-> UserPrefsStorage +StorageManager "1" o-right-> TaTrackerStorage + +JsonUserPrefsStorage -[hidden]up-> UserPrefsStorage +JsonTaTrackerStorage -[hidden]up-> TaTrackerStorage + +JsonUserPrefsStorage -[hidden]right-> JsonTaTrackerStorage + +JsonUserPrefsStorage .up.|> UserPrefsStorage + +JsonTaTrackerStorage .up.|> TaTrackerStorage +JsonTaTrackerStorage .down.> JsonSerializableTaTrackerStorage + +JsonSerializableTaTrackerStorage -down-> "*" JsonAdaptedModule +JsonSerializableTaTrackerStorage -down-> "*" JsonAdaptedSession + +JsonAdaptedModule -right-> "*" JsonAdaptedSession + +JsonAdaptedModule -down-> "*" JsonAdaptedGroup +JsonAdaptedGroup -down-> "*" JsonAdaptedStudent + +JsonAdaptedSession -down-> "1" JsonAdaptedDateTime : end +JsonAdaptedSession -down-> "1" JsonAdaptedDateTime : start + +@enduml + +@startuml +!include style.puml +skinparam arrowThickness 1.1 +skinparam arrowColor STORAGE_COLOR +skinparam classBackgroundColor STORAGE_COLOR +skinparam linetype ortho +skinparam nodesep 50 + +Interface Storage <> +Interface UserPrefsStorage <> +Interface TaTrackerStorage <> + +Class StorageManager +Class JsonUserPrefsStorage +Class JsonTaTrackerStorage + +StorageManager .up.|> Storage +StorageManager "1" o-left-> UserPrefsStorage +StorageManager "1" o-right-> TaTrackerStorage + +JsonUserPrefsStorage -[hidden]up-> UserPrefsStorage +JsonTaTrackerStorage -[hidden]up-> TaTrackerStorage + +JsonUserPrefsStorage -[hidden]right-> JsonTaTrackerStorage + +JsonUserPrefsStorage .up.|> UserPrefsStorage + +JsonTaTrackerStorage .up.|> TaTrackerStorage +@enduml + +@startuml +!include style.puml +skinparam arrowThickness 1.1 +skinparam arrowColor STORAGE_COLOR +skinparam classBackgroundColor STORAGE_COLOR +skinparam linetype ortho +skinparam nodesep 50 + + +Class JsonTaTrackerStorage + +JsonTaTrackerStorage .down.> JsonSerializableTaTrackerStorage + +JsonSerializableTaTrackerStorage -down-> "*" JsonAdaptedModule +JsonSerializableTaTrackerStorage -down-> "*" JsonAdaptedSession + +JsonAdaptedModule -right-> "*" JsonAdaptedSession : done sessions + +JsonAdaptedModule -down-> "*" JsonAdaptedGroup +JsonAdaptedGroup -down-> "*" JsonAdaptedStudent -JsonUserPrefsStorage .left.|> UserPrefsStorage -JsonAddressBookStorage .left.|> AddressBookStorage -JsonAddressBookStorage .down.> JsonSerializableAddressBookStorage -JsonSerializableAddressBookStorage .right.> JsonSerializablePerson -JsonSerializablePerson .right.> JsonAdaptedTag @enduml diff --git a/docs/diagrams/StudentTabClassDiagram.puml b/docs/diagrams/StudentTabClassDiagram.puml new file mode 100644 index 00000000000..5ee5b5db646 --- /dev/null +++ b/docs/diagrams/StudentTabClassDiagram.puml @@ -0,0 +1,38 @@ +@startuml +!include style.puml +skinparam arrowThickness 1.1 +skinparam arrowColor UI_COLOR_T4 +skinparam classBackgroundColor UI_COLOR + +package UI <>{ +Class MainWindow +package StudentTab{ +Class StudentListPanel +Class StudentCard +Class ModuleListPanel +Class ModuleCard +Class GroupListPanel +Class GroupCard +} +} + +package Model <> { +Class HiddenModel #FFFFFF +} + +Class HiddenOutside #FFFFFF + + +MainWindow *--> StudentListPanel +MainWindow *--> ModuleListPanel +MainWindow *--> GroupListPanel + +StudentListPanel --> StudentCard +ModuleListPanel --> ModuleCard +GroupListPanel --> GroupCard + +StudentCard .down.> Model +ModuleCard .down.> Model +GroupCard .down..> Model + +@enduml diff --git a/docs/diagrams/SyntaxHighlightingActivityDiagram.puml b/docs/diagrams/SyntaxHighlightingActivityDiagram.puml new file mode 100644 index 00000000000..59807ed03f9 --- /dev/null +++ b/docs/diagrams/SyntaxHighlightingActivityDiagram.puml @@ -0,0 +1,104 @@ +@startuml +start +:Check new input in command box; + +if () then ([empty input]) +partition blank { + :Clear result display; +} +else if () then ([invalid command word]) +partition invalid { + :Highlight input in red; + :Show invalid command + in result display; +} +else ([valid command word]) +partition valid { + :Change highlighting rules + for command in input; + + :Check input arguments; + + if () then ([no arguments]) + partition no_arguments { + :Highlight input in green; + :Show command usage message; + } + else ([has arguments]) + partition has_arguments { + :Highlight arguments; + } + endif +} +endif +stop +@enduml + +@startuml +start +:Check arguments; + +if () then ([arguments have trailing whitespace]) +partition whitespaces { + :Clear highlighting; + :Count number of trailing whitespaces in arguments; + if () then ([1 whitespace]) + else ([else]) + partition many_whitespaces { + :Show command usage message; + } + endif + stop +} +else if () then ([command does not need preamble]) + :Remove preamble; + + :Check if removed preamble is blank; + + if () then ([blank preamble]) + else ([else]) + partition preamble_error { + :Highlight input in red; + :Show command usage message; + stop + } + endif +else ([else]) +endif +partition preamble_end { +:Highlight invalid arguments; +} +stop +@enduml + + +@startuml +start + +:Check each prefix; + +while () is ([has next prefix]) + +:Check next prefix; + +if () then ([wrong prefix]) +partition wrong { + :Highlight input in red; + :Show command usage message; + stop +} +else if () then ([invalid prefix value]) +partition invalid { + :Highlight input in red; + :Show prefix usage message; + stop +} +else ([valid prefix value]) +partition valid { + :Highlight input in green; + :Show prefix usage message; +} +endif +endwhile([else]) +stop +@enduml diff --git a/docs/diagrams/SyntaxHighlightingClassDiagram.puml b/docs/diagrams/SyntaxHighlightingClassDiagram.puml new file mode 100644 index 00000000000..b1ca84b4ed9 --- /dev/null +++ b/docs/diagrams/SyntaxHighlightingClassDiagram.puml @@ -0,0 +1,39 @@ +@startuml +!include style.puml +skinparam arrowThickness 1.1 +skinparam arrowColor UI_COLOR_T4 +skinparam classBackgroundColor UI_COLOR + +package UI <>{ + Class MainWindow + Class ResultDisplay + Class CommandBox + Class CommandBoxUtil + Class ArgumentMatch + Class CommandMatch +} + +package Logic <>{ + Class CommandDictionary LOGIC_COLOR + Class CommandDetails LOGIC_COLOR + Class PrefixDictionary LOGIC_COLOR + Class PrefixDetails LOGIC_COLOR +} + +CommandDictionary -right[LOGIC_COLOR_T4]-> "*" CommandDetails +PrefixDictionary -right[LOGIC_COLOR_T4]-> "*" PrefixDetails + +MainWindow *-right-> "1" CommandBox +MainWindow *-down-> "1" ResultDisplay + +CommandBox -left-> "1" ResultDisplay +CommandBox .right.> CommandBoxUtil + +CommandBox -down-> "0..1" CommandDetails +CommandBox .down.> CommandDictionary +CommandBox .down.> PrefixDetails +CommandBox -down-> "1" PrefixDictionary + +CommandMatch .down.> CommandBoxUtil +ArgumentMatch .down.> CommandBoxUtil +@enduml diff --git a/docs/diagrams/UiClassDiagram.puml b/docs/diagrams/UiClassDiagram.puml index 92746f9fcf7..db7cbd324a4 100644 --- a/docs/diagrams/UiClassDiagram.puml +++ b/docs/diagrams/UiClassDiagram.puml @@ -10,9 +10,11 @@ Class "{abstract}\nUiPart" as UiPart Class UiManager Class MainWindow Class HelpWindow +Class StatisticsWindow Class ResultDisplay -Class PersonListPanel -Class PersonCard +Class StudentTab +Class ClaimsTab +Class SessionTab Class StatusBarFooter Class CommandBox } @@ -30,28 +32,36 @@ HiddenOutside ..> Ui UiManager .left.|> Ui UiManager -down-> MainWindow -MainWindow --> HelpWindow -MainWindow *-down-> CommandBox -MainWindow *-down-> ResultDisplay -MainWindow *-down-> PersonListPanel -MainWindow *-down-> StatusBarFooter +MainWindow ---> HelpWindow +MainWindow ----> StatisticsWindow +MainWindow *--down--> CommandBox +MainWindow *--down--> ResultDisplay +MainWindow *--down--> StudentTab +MainWindow *--down--> SessionTab +MainWindow *--down--> ClaimsTab +MainWindow *--down--> StatusBarFooter + +CommandBox -up-> ResultDisplay -PersonListPanel -down-> PersonCard MainWindow -left-|> UiPart -ResultDisplay --|> UiPart -CommandBox --|> UiPart -PersonListPanel --|> UiPart -PersonCard --|> UiPart -StatusBarFooter --|> UiPart -HelpWindow -down-|> UiPart +ResultDisplay ----|> UiPart +CommandBox ----|> UiPart +StudentTab ----|> UiPart +SessionTab ----|> UiPart +ClaimsTab ----|> UiPart +StatusBarFooter ----|> UiPart +HelpWindow --down--|> UiPart +StatisticsWindow --down--|> UiPart -PersonCard ..> Model UiManager -right-> Logic MainWindow -left-> Logic -PersonListPanel -[hidden]left- HelpWindow +StudentTab .down...> Model +SessionTab .down..> Model +ClaimsTab .down..> Model + HelpWindow -[hidden]left- CommandBox CommandBox -[hidden]left- ResultDisplay ResultDisplay -[hidden]left- StatusBarFooter diff --git a/docs/diagrams/UndoRedoState0.puml b/docs/diagrams/UndoRedoState0.puml index 96e30744d24..641e7c3d9eb 100644 --- a/docs/diagrams/UndoRedoState0.puml +++ b/docs/diagrams/UndoRedoState0.puml @@ -6,9 +6,9 @@ skinparam ClassBorderColor #000000 title Initial state package States { - class State1 as "__ab0:AddressBook__" - class State2 as "__ab1:AddressBook__" - class State3 as "__ab2:AddressBook__" + class State1 as "__ab0:TaTracker__" + class State2 as "__ab1:TaTracker__" + class State3 as "__ab2:TaTracker__" } State1 -[hidden]right-> State2 State2 -[hidden]right-> State3 diff --git a/docs/diagrams/UndoRedoState1.puml b/docs/diagrams/UndoRedoState1.puml index 01fcb9b2b96..94a9d971e99 100644 --- a/docs/diagrams/UndoRedoState1.puml +++ b/docs/diagrams/UndoRedoState1.puml @@ -6,9 +6,9 @@ skinparam ClassBorderColor #000000 title After command "delete 5" package States <> { - class State1 as "__ab0:AddressBook__" - class State2 as "__ab1:AddressBook__" - class State3 as "__ab2:AddressBook__" + class State1 as "__ab0:TaTracker__" + class State2 as "__ab1:TaTracker__" + class State3 as "__ab2:TaTracker__" } State1 -[hidden]right-> State2 diff --git a/docs/diagrams/UndoRedoState2.puml b/docs/diagrams/UndoRedoState2.puml index bccc230a5d1..0307abe0959 100644 --- a/docs/diagrams/UndoRedoState2.puml +++ b/docs/diagrams/UndoRedoState2.puml @@ -6,9 +6,9 @@ skinparam ClassBorderColor #000000 title After command "add n/David" package States <> { - class State1 as "__ab0:AddressBook__" - class State2 as "__ab1:AddressBook__" - class State3 as "__ab2:AddressBook__" + class State1 as "__ab0:TaTracker__" + class State2 as "__ab1:TaTracker__" + class State3 as "__ab2:TaTracker__" } State1 -[hidden]right-> State2 diff --git a/docs/diagrams/UndoRedoState3.puml b/docs/diagrams/UndoRedoState3.puml index ea29c9483e4..551674062b8 100644 --- a/docs/diagrams/UndoRedoState3.puml +++ b/docs/diagrams/UndoRedoState3.puml @@ -6,9 +6,9 @@ skinparam ClassBorderColor #000000 title After command "undo" package States <> { - class State1 as "__ab0:AddressBook__" - class State2 as "__ab1:AddressBook__" - class State3 as "__ab2:AddressBook__" + class State1 as "__ab0:TaTracker__" + class State2 as "__ab1:TaTracker__" + class State3 as "__ab2:TaTracker__" } State1 -[hidden]right-> State2 diff --git a/docs/diagrams/UndoRedoState4.puml b/docs/diagrams/UndoRedoState4.puml index 1b784cece80..09b87ceb1ca 100644 --- a/docs/diagrams/UndoRedoState4.puml +++ b/docs/diagrams/UndoRedoState4.puml @@ -6,9 +6,9 @@ skinparam ClassBorderColor #000000 title After command "list" package States <> { - class State1 as "__ab0:AddressBook__" - class State2 as "__ab1:AddressBook__" - class State3 as "__ab2:AddressBook__" + class State1 as "__ab0:TaTracker__" + class State2 as "__ab1:TaTracker__" + class State3 as "__ab2:TaTracker__" } State1 -[hidden]right-> State2 diff --git a/docs/diagrams/UndoRedoState5.puml b/docs/diagrams/UndoRedoState5.puml index 88927be32bc..4af64b5c08a 100644 --- a/docs/diagrams/UndoRedoState5.puml +++ b/docs/diagrams/UndoRedoState5.puml @@ -6,9 +6,9 @@ skinparam ClassBorderColor #000000 title After command "clear" package States <> { - class State1 as "__ab0:AddressBook__" - class State2 as "__ab1:AddressBook__" - class State3 as "__ab3:AddressBook__" + class State1 as "__ab0:TaTracker__" + class State2 as "__ab1:TaTracker__" + class State3 as "__ab3:TaTracker__" } State1 -[hidden]right-> State2 diff --git a/docs/diagrams/UndoSequenceDiagram.puml b/docs/diagrams/UndoSequenceDiagram.puml index 410aab4e412..91b4d8ed5f2 100644 --- a/docs/diagrams/UndoSequenceDiagram.puml +++ b/docs/diagrams/UndoSequenceDiagram.puml @@ -3,42 +3,42 @@ box Logic LOGIC_COLOR_T1 participant ":LogicManager" as LogicManager LOGIC_COLOR -participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR +participant ":TaTrackerParser" as TaTrackerParser LOGIC_COLOR participant "u:UndoCommand" as UndoCommand LOGIC_COLOR end box box Model MODEL_COLOR_T1 participant ":Model" as Model MODEL_COLOR -participant ":VersionedAddressBook" as VersionedAddressBook MODEL_COLOR +participant ":VersionedTaTracker" as VersionedTaTracker MODEL_COLOR end box [-> LogicManager : execute(undo) activate LogicManager -LogicManager -> AddressBookParser : parseCommand(undo) -activate AddressBookParser +LogicManager -> TaTrackerParser : parseCommand(undo) +activate TaTrackerParser create UndoCommand -AddressBookParser -> UndoCommand +TaTrackerParser -> UndoCommand activate UndoCommand -UndoCommand --> AddressBookParser +UndoCommand --> TaTrackerParser deactivate UndoCommand -AddressBookParser --> LogicManager : u -deactivate AddressBookParser +TaTrackerParser --> LogicManager : u +deactivate TaTrackerParser LogicManager -> UndoCommand : execute() activate UndoCommand -UndoCommand -> Model : undoAddressBook() +UndoCommand -> Model : undoTaTracker() activate Model -Model -> VersionedAddressBook : undo() -activate VersionedAddressBook +Model -> VersionedTaTracker : undo() +activate VersionedTaTracker -VersionedAddressBook -> VersionedAddressBook :resetData(ReadOnlyAddressBook) -VersionedAddressBook --> Model : -deactivate VersionedAddressBook +VersionedTaTracker -> VersionedTaTracker :resetData(ReadOnlyTaTracker) +VersionedTaTracker --> Model : +deactivate VersionedTaTracker Model --> UndoCommand deactivate Model diff --git a/docs/diagrams/find/FilterCommandActivityDiagram.puml b/docs/diagrams/find/FilterCommandActivityDiagram.puml new file mode 100644 index 00000000000..9cca5a78fe1 --- /dev/null +++ b/docs/diagrams/find/FilterCommandActivityDiagram.puml @@ -0,0 +1,34 @@ +@startuml +start +:User executes filter command; + +:User can call filter command on + claims, student or session; + +:Parse and validate the keywords; + +:Respective filter command will be called; + +if () then ([filterClaimCommand OR +filterSessionCommand is called]) + :User key in keywords; + :Create filtering predicate using the keywords specified; + :Update the filtered list; + :Display the filtered list to the user; + + +else ([filterStudentCommand is called]) + :User filter via module code and/or group code ; + + if () then ([group code does not exists]) + :Update the filtered list with students from first group of the module code; + else ([group code exists]) + :Update the filtered list with students from group code and module code; + +endif +:Display the filtered list to the user; + +endif + +stop +@enduml diff --git a/docs/diagrams/find/FilterStudentSequenceDiagram.puml b/docs/diagrams/find/FilterStudentSequenceDiagram.puml new file mode 100644 index 00000000000..cb3c93a49c5 --- /dev/null +++ b/docs/diagrams/find/FilterStudentSequenceDiagram.puml @@ -0,0 +1,77 @@ +@startuml +!include ../style.puml +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":TaTrackerParser" as TaTrackerParser LOGIC_COLOR +participant ":StudentCommandParser" as StudentCommandParser LOGIC_COLOR +participant ":FilterStudentViewCommandParser" as FilterStudentViewCommandParser LOGIC_COLOR +participant "d:FilterStudentViewCommand" as FilterStudentViewCommand LOGIC_COLOR +participant ":CommandResult" as CommandResult LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant "m:Student" as Student MODEL_COLOR +participant ":Model" as Model MODEL_COLOR +end box + +[-> LogicManager : execute("student filter \nm/CS2103T g/G06") +activate LogicManager + +LogicManager -> TaTrackerParser : parseCommand("student filter \nm/CS2103T g/G06") +activate TaTrackerParser + +create StudentCommandParser +TaTrackerParser -> StudentCommandParser: StudentCommandParser() +activate StudentCommandParser + +StudentCommandParser --> TaTrackerParser +deactivate StudentCommandParser + +TaTrackerParser -> StudentCommandParser : parse("filter \nm/CS2103T g/G06") +activate StudentCommandParser + +create FilterStudentViewCommand +FilterStudentViewCommandParser -> FilterStudentViewCommand : FilterStudentViewCommand(m, g) +activate FilterStudentViewCommand +FilterStudentViewCommand --> FilterStudentViewCommandParser +deactivate FilterStudentViewCommand + +FilterStudentViewCommandParser --> StudentCommandParser : d +deactivate FilterStudentViewCommandParser +FilterStudentViewCommandParser -[hidden]-> StudentCommandParser +destroy FilterStudentViewCommandParser + +StudentCommandParser --> TaTrackerParser : d +deactivate StudentCommandParser +StudentCommandParser -[hidden]-> TaTrackerParser +destroy StudentCommandParser + +TaTrackerParser --> LogicManager : d +deactivate TaTrackerParser + +LogicManager -> FilterStudentViewCommand : execute() +activate FilterStudentViewCommand + +FilterStudentViewCommand -> Model : hasModule("CS2103T") +activate Model +Model --> FilterStudentViewCommand : true + +FilterStudentViewCommand -> Model : hasGroup("G06", "CS2103T") +Model --> FilterStudentViewCommand : true + +FilterStudentViewCommand -> Model : setFilteredStudentList(CS2103T, G06); +Model --> FilterStudentViewCommand +deactivate Model + +create CommandResult +FilterStudentViewCommand -> CommandResult : CommandResult() +activate CommandResult +CommandResult --> FilterStudentViewCommand +deactivate CommandResult + +FilterStudentViewCommand --> LogicManager : result +deactivate FilterStudentViewCommand + +[<--LogicManager +deactivate LogicManager +@enduml diff --git a/docs/diagrams/find/FindCommandClassDiagram.puml b/docs/diagrams/find/FindCommandClassDiagram.puml new file mode 100644 index 00000000000..b935933cabe --- /dev/null +++ b/docs/diagrams/find/FindCommandClassDiagram.puml @@ -0,0 +1,26 @@ +@startuml +!include ../style.puml +show interface members +skinparam arrowThickness 1.1 +skinparam classBackgroundColor LOGIC_COLOR_T4 + +abstract class Command + +Interface Parser <> + +class FilterClaimCommand + +class DoneSessionPredicate + +class FilterClaimCommandParser + +Command <|.. FilterClaimCommand + +Parser <|-- FilterClaimCommandParser + +DoneSessionPredicate <.. FilterClaimCommand +DoneSessionPredicate <.. FilterClaimCommandParser + + +@enduml + diff --git a/docs/diagrams/module-view/AddGroupSequenceDiagram.puml b/docs/diagrams/module-view/AddGroupSequenceDiagram.puml new file mode 100644 index 00000000000..cf2aa04cd28 --- /dev/null +++ b/docs/diagrams/module-view/AddGroupSequenceDiagram.puml @@ -0,0 +1,79 @@ +@startuml +!include ../style.puml + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":TaTrackerParser" as TaTrackerParser LOGIC_COLOR +participant ":GroupCommandParser" as GroupCommandParser LOGIC_COLOR +participant ":AddGroupCommandParser" as AddGroupCommandParser LOGIC_COLOR +participant "a:AddGroupCommand" as AddGroupCommand LOGIC_COLOR +participant "result:CommandResult" as CommandResult LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant ":Model" as Model MODEL_COLOR +end box + +[-> LogicManager : execute("group add \nm/CS2103 g/G03 t/lab") +activate LogicManager + +LogicManager -> TaTrackerParser : parseCommand("group add \nm/CS2103 g/G03 t/lab") +activate TaTrackerParser + +create GroupCommandParser +TaTrackerParser -> GroupCommandParser : GroupCommandParser() +activate GroupCommandParser +GroupCommandParser --> TaTrackerParser +deactivate GroupCommandParser + +TaTrackerParser -> GroupCommandParser : parseCommand("add \nm/CS2103 g/G03 t/tutorial") +activate GroupCommandParser + +create AddGroupCommandParser +GroupCommandParser -> AddGroupCommandParser : AddGroupCommandParser() +activate AddGroupCommandParser +AddGroupCommandParser --> GroupCommandParser +deactivate AddGroupCommandParser + +GroupCommandParser -> AddGroupCommandParser : parseCommand("m/CS2103 g/G03 t/lab") +activate AddGroupCommandParser + +create AddGroupCommand +AddGroupCommandParser -> AddGroupCommand : AddGroupCommand() +activate AddGroupCommand +AddGroupCommand --> AddGroupCommandParser +deactivate AddGroupCommand + +AddGroupCommandParser --> GroupCommandParser : a +deactivate AddGroupCommandParser +AddGroupCommandParser -[hidden]-> GroupCommandParser +destroy AddGroupCommandParser + +GroupCommandParser --> TaTrackerParser : a +deactivate GroupCommandParser +GroupCommandParser -[hidden]-> TaTrackerParser +destroy GroupCommandParser + +TaTrackerParser --> LogicManager : a +deactivate TaTrackerParser + +LogicManager -> AddGroupCommand : execute() +activate AddGroupCommand + +AddGroupCommand -> Model : addGroup() +activate Model +Model --> AddGroupCommand +deactivate Model + +create CommandResult +AddGroupCommand -> CommandResult : CommandResult(SuccessMessage) +activate CommandResult +CommandResult --> AddGroupCommand +deactivate CommandResult + +AddGroupCommand --> LogicManager : result +deactivate AddGroupCommand + +[<--LogicManager +deactivate LogicManager +@enduml diff --git a/docs/diagrams/module-view/AddModuleSequenceDiagram.puml b/docs/diagrams/module-view/AddModuleSequenceDiagram.puml new file mode 100644 index 00000000000..331758181cf --- /dev/null +++ b/docs/diagrams/module-view/AddModuleSequenceDiagram.puml @@ -0,0 +1,91 @@ +@startuml +!include ../style.puml + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":TaTrackerParser" as TaTrackerParser LOGIC_COLOR +participant ":ModuleCommandParser" as ModuleCommandParser LOGIC_COLOR +participant ":AddModuleCommandParser" as AddModuleCommandParser LOGIC_COLOR +participant "a:AddModuleCommand" as AddModuleCommand LOGIC_COLOR +participant "result:CommandResult" as CommandResult LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant "m:Module" as Module MODEL_COLOR +participant ":Model" as Model MODEL_COLOR +end box + +[-> LogicManager : execute("module \nadd m/CS2103 \nn/Software Engineering") +activate LogicManager + +LogicManager -> TaTrackerParser : parseCommand("module \nadd m/CS2103 \nn/Software Engineering") +activate TaTrackerParser + +create ModuleCommandParser +TaTrackerParser -> ModuleCommandParser : ModuleCommandParser() +activate ModuleCommandParser +ModuleCommandParser --> TaTrackerParser +deactivate ModuleCommandParser + +TaTrackerParser -> ModuleCommandParser : parseCommand("add m/CS2103 \nn/Software Engineering") +activate ModuleCommandParser + +create AddModuleCommandParser +ModuleCommandParser -> AddModuleCommandParser : AddModuleCommandParser() +activate AddModuleCommandParser +AddModuleCommandParser --> ModuleCommandParser +deactivate AddModuleCommandParser + +ModuleCommandParser -> AddModuleCommandParser : parseCommand("m/CS2103 \nn/Software Engineering") +activate AddModuleCommandParser + +create Module +AddModuleCommandParser -> Module : Module("CS2103", "Software Engineering") +activate Module +Module --> AddModuleCommandParser +deactivate Module + +create AddModuleCommand +AddModuleCommandParser -> AddModuleCommand : AddModuleCommand(m) +activate AddModuleCommand +AddModuleCommand --> AddModuleCommandParser +deactivate AddModuleCommand + + + +AddModuleCommandParser --> ModuleCommandParser : a +deactivate AddModuleCommandParser +AddModuleCommandParser -[hidden]-> ModuleCommandParser +destroy AddModuleCommandParser + +ModuleCommandParser --> TaTrackerParser : a +deactivate ModuleCommandParser +ModuleCommandParser -[hidden]-> TaTrackerParser +destroy ModuleCommandParser + +TaTrackerParser --> LogicManager : a +deactivate TaTrackerParser + +LogicManager -> AddModuleCommand : execute() +activate AddModuleCommand + +AddModuleCommand -> Model : hasModule("CS2103") +activate Model +Model --> AddModuleCommand + +AddModuleCommand -> Model : addModule(m) +Model --> AddModuleCommand +deactivate Model + +create CommandResult +AddModuleCommand -> CommandResult : CommandResult() +activate CommandResult +CommandResult --> AddModuleCommand +deactivate CommandResult + +AddModuleCommand --> LogicManager : result +deactivate AddModuleCommand + +[<--LogicManager +deactivate LogicManager +@enduml diff --git a/docs/diagrams/module-view/DeleteGroupSequenceDiagram.puml b/docs/diagrams/module-view/DeleteGroupSequenceDiagram.puml new file mode 100644 index 00000000000..9d6d512e4d6 --- /dev/null +++ b/docs/diagrams/module-view/DeleteGroupSequenceDiagram.puml @@ -0,0 +1,85 @@ +@startuml +!include ../style.puml + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":TaTrackerParser" as TaTrackerParser LOGIC_COLOR +participant ":GroupCommandParser" as GroupCommandParser LOGIC_COLOR +participant ":DeleteGroupCommandParser" as DeleteGroupCommandParser LOGIC_COLOR +participant "d:DeleteGroupCommand" as DeleteGroupCommand LOGIC_COLOR +participant "result:CommandResult" as CommandResult LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant ":Model" as Model MODEL_COLOR +end box + +[-> LogicManager : execute(\n"group delete \nm/CS2103 g/G03") +activate LogicManager + +LogicManager -> TaTrackerParser : parseCommand(\n"group delete \nm/CS2103 g/G03") +activate TaTrackerParser + +create GroupCommandParser +TaTrackerParser -> GroupCommandParser : GroupCommandParser() +activate GroupCommandParser +GroupCommandParser --> TaTrackerParser +deactivate GroupCommandParser + +TaTrackerParser -> GroupCommandParser : parseCommand("delete \nm/CS2103 g/G03") +activate GroupCommandParser + +create DeleteGroupCommandParser +GroupCommandParser -> DeleteGroupCommandParser : DeleteGroupCommandParser() +activate DeleteGroupCommandParser +DeleteGroupCommandParser --> GroupCommandParser +deactivate DeleteGroupCommandParser + +GroupCommandParser -> DeleteGroupCommandParser : parseCommand("m/CS2103 g/G03") +activate DeleteGroupCommandParser + +create DeleteGroupCommand +DeleteGroupCommandParser -> DeleteGroupCommand : DeleteGroupCommand(g, m) +activate DeleteGroupCommand +DeleteGroupCommand --> DeleteGroupCommandParser +deactivate DeleteGroupCommand + +DeleteGroupCommandParser --> GroupCommandParser : d +deactivate DeleteGroupCommandParser +DeleteGroupCommandParser -[hidden]-> GroupCommandParser +destroy DeleteGroupCommandParser + +GroupCommandParser --> TaTrackerParser : d +deactivate GroupCommandParser +GroupCommandParser -[hidden]-> TaTrackerParser +destroy GroupCommandParser + +TaTrackerParser --> LogicManager : d +deactivate TaTrackerParser + +LogicManager -> DeleteGroupCommand : execute() +activate DeleteGroupCommand + +DeleteGroupCommand -> Model : hasModule("CS2103") +activate Model +Model --> DeleteGroupCommand : true + +DeleteGroupCommand -> Model : hasGroup("G03", "CS2103") +Model --> DeleteGroupCommand : true + +DeleteGroupCommand -> Model : deleteGroup("G03", "CS2103") +Model --> DeleteGroupCommand +deactivate Model + +create CommandResult +DeleteGroupCommand -> CommandResult : CommandResult() +activate CommandResult +CommandResult --> DeleteGroupCommand +deactivate CommandResult + +DeleteGroupCommand --> LogicManager : result +deactivate DeleteGroupCommand + +[<--LogicManager +deactivate LogicManager +@enduml diff --git a/docs/diagrams/module-view/ModuleModelClassDiagram.puml b/docs/diagrams/module-view/ModuleModelClassDiagram.puml new file mode 100644 index 00000000000..059db1955ae --- /dev/null +++ b/docs/diagrams/module-view/ModuleModelClassDiagram.puml @@ -0,0 +1,38 @@ +@startuml +!include ../style.puml +package Model { + +skinparam arrowThickness 1.1 +skinparam arrowColor MODEL_COLOR +skinparam classBackgroundColor MODEL_COLOR + +class TaTracker + +package Module { +class Module +class UniqueModuleList +} +package Group { +class Group +class UniqueGroupList +} + +package Student { +class Student +class UniqueStudentList +} +} + +TaTracker --> "1 " UniqueModuleList + +UniqueModuleList -right-> "*" Module + +Module -right-> "1" UniqueGroupList + +UniqueGroupList -right-> "*" Group + +Group --> "1 " UniqueStudentList +UniqueStudentList -left-> "*" Student + +@enduml + diff --git a/docs/diagrams/module-view/SortAllSequenceDiagram.puml b/docs/diagrams/module-view/SortAllSequenceDiagram.puml new file mode 100644 index 00000000000..63bdf99fc4a --- /dev/null +++ b/docs/diagrams/module-view/SortAllSequenceDiagram.puml @@ -0,0 +1,64 @@ +@startuml +!include ../style.puml + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":TaTrackerParser" as TaTrackerParser LOGIC_COLOR +participant ":SortCommandParser" as SortCommandParser LOGIC_COLOR +participant "s:SortCommand" as SortCommand LOGIC_COLOR +participant "result:CommandResult" as CommandResult LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant ":Model" as Model MODEL_COLOR +end box + +[-> LogicManager : execute("sort all t/matric") +activate LogicManager + +LogicManager -> TaTrackerParser : parseCommand("sort all t/matric") +activate TaTrackerParser + +create SortCommandParser +TaTrackerParser -> SortCommandParser : SortCommandParser() +activate SortCommandParser +SortCommandParser --> TaTrackerParser +deactivate SortCommandParser + +TaTrackerParser -> SortCommandParser : parseCommand("all t/matric") +activate SortCommandParser + +create SortCommand +SortCommandParser -> SortCommand : SortCommand("matric") +activate SortCommand +SortCommand --> SortCommandParser +deactivate SortCommand + +SortCommandParser --> TaTrackerParser : s +deactivate SortCommandParser +SortCommandParser -[hidden]-> TaTrackerParser +destroy SortCommandParser + +TaTrackerParser --> LogicManager : s +deactivate TaTrackerParser + +LogicManager -> SortCommand : execute() +activate SortCommand + +SortCommand -> Model : sortModulesByMatricNumber() +activate Model +Model --> SortCommand +deactivate Model + +create CommandResult +SortCommand -> CommandResult : CommandResult() +activate CommandResult +CommandResult --> SortCommand +deactivate CommandResult + +SortCommand --> LogicManager : result +deactivate SortCommand + +[<--LogicManager +deactivate LogicManager +@enduml diff --git a/docs/diagrams/module-view/SortCommandsClassDiagram.puml b/docs/diagrams/module-view/SortCommandsClassDiagram.puml new file mode 100644 index 00000000000..b135cc2a907 --- /dev/null +++ b/docs/diagrams/module-view/SortCommandsClassDiagram.puml @@ -0,0 +1,16 @@ +@startuml +!include ../style.puml +skinparam arrowThickness 1.1 +skinparam arrowColor LOGIC_COLOR +skinparam classBackgroundColor LOGIC_COLOR + +abstract class Command +class SortCommand +class SortModuleCommand +class SortGroupCommand + +SortCommand -up-|> Command +SortModuleCommand -up-|> SortCommand +SortGroupCommand -up-|> SortCommand + +@enduml diff --git a/docs/diagrams/module-view/SortParserActivityDiagram.puml b/docs/diagrams/module-view/SortParserActivityDiagram.puml new file mode 100644 index 00000000000..bfd87f54af5 --- /dev/null +++ b/docs/diagrams/module-view/SortParserActivityDiagram.puml @@ -0,0 +1,35 @@ +@startuml +start +:SortCommandParser's parse + method is called; + +:ArgumentMultimap is created; + +:The sort command word is extracted from + the multimap; + +if() then ([The sort command word is 'all']) + :The sort type is extracted; + :A SortCommand object with + the given sort type as a + parameter is returned; + +else if() then ([The sort command word is 'module']) + :The sort type and module code are extracted; + : A SortModuleCommand object + with the given module code + and sort type is returned; + +else if() then ([The sort command word is 'group']) + :The sort type, module code + and group code are extracted; + : A SortGroupCommand object + with the given group code, + module code and sort type + is returned; + + +endif + +stop +@enduml diff --git a/docs/diagrams/session-view/DoneSessionSequenceDiagram.puml b/docs/diagrams/session-view/DoneSessionSequenceDiagram.puml new file mode 100644 index 00000000000..43b842c3caf --- /dev/null +++ b/docs/diagrams/session-view/DoneSessionSequenceDiagram.puml @@ -0,0 +1,87 @@ +@startuml +!include ../style.puml +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":TaTrackerParser" as TaTrackerParser LOGIC_COLOR +participant ":SessionCommandParser" as SessionCommandParser LOGIC_COLOR +participant ":DoneSessionCommandParser" as DoneSessionCommandParser LOGIC_COLOR +participant "a:DoneSessionCommand" as DoneSessionCommand LOGIC_COLOR +participant ":AddSessionCommandParser" as AddSessionCommandParser LOGIC_COLOR +participant "a:AddSessionCommand" as AddSessionCommand LOGIC_COLOR +participant ":CommandResult" as CommandResult LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant ":Model" as Model MODEL_COLOR +end box + +[-> LogicManager : execute("session done 1") +activate LogicManager + +LogicManager -> TaTrackerParser : parseCommand("session done 1") +activate TaTrackerParser +create SessionCommandParser +TaTrackerParser -> SessionCommandParser : SessionCommandParser() +activate SessionCommandParser +SessionCommandParser --> TaTrackerParser +deactivate SessionCommandParser + +TaTrackerParser -> SessionCommandParser : parseCommand("session done 1") +activate SessionCommandParser + +create DoneSessionCommandParser +SessionCommandParser -> DoneSessionCommandParser : DoneSessionCommandParser() +activate DoneSessionCommandParser +DoneSessionCommandParser --> SessionCommandParser +deactivate DoneSessionCommandParser + +SessionCommandParser -> DoneSessionCommandParser : parseCommand("session done 1") +activate DoneSessionCommandParser + +create DoneSessionCommand +DoneSessionCommandParser -> DoneSessionCommand : DoneSessionCommand(1); +activate DoneSessionCommand +DoneSessionCommand --> DoneSessionCommandParser +deactivate DoneSessionCommand + +DoneSessionCommandParser --> SessionCommandParser : a +deactivate DoneSessionCommandParser +DoneSessionCommandParser -[hidden]-> SessionCommandParser +destroy DoneSessionCommandParser + +SessionCommandParser --> TaTrackerParser : a +deactivate SessionCommandParser +SessionCommandParser -[hidden]-> TaTrackerParser +destroy SessionCommandParser + +TaTrackerParser --> LogicManager : a +deactivate TaTrackerParser + +LogicManager -> DoneSessionCommand : execute() +activate DoneSessionCommand + +alt #beige recurring() > 0 + DoneSessionCommand -> Model : addSession(updatedSession) + activate Model + Model --> AddSessionCommand + deactivate Model + +end + +DoneSessionCommand -> Model : DoneSession() +activate Model +Model --> DoneSessionCommand +deactivate Model + +create CommandResult +DoneSessionCommand -> CommandResult : CommandResult(SuccessMessage) +activate CommandResult +CommandResult --> DoneSessionCommand +deactivate CommandResult + +DoneSessionCommand --> LogicManager : result +deactivate DoneSessionCommand + +[<--LogicManager +deactivate LogicManager +@enduml diff --git a/docs/diagrams/session-view/SessionModelClassDiagram.puml b/docs/diagrams/session-view/SessionModelClassDiagram.puml new file mode 100644 index 00000000000..5911887d779 --- /dev/null +++ b/docs/diagrams/session-view/SessionModelClassDiagram.puml @@ -0,0 +1,25 @@ +@startuml +!include ../style.puml +package Model { +skinparam arrowThickness 1.1 +skinparam arrowColor MODEL_COLOR +skinparam classBackgroundColor MODEL_COLOR + +class TaTracker + +package Session { +class Session +class UniqueSessionList +} + + + +TaTracker --> "1 " UniqueSessionList + +UniqueSessionList --> "*" Session + + + + +@enduml + diff --git a/docs/diagrams/student-view/AddStudentSequenceDiagram.puml b/docs/diagrams/student-view/AddStudentSequenceDiagram.puml new file mode 100644 index 00000000000..2777dcf081e --- /dev/null +++ b/docs/diagrams/student-view/AddStudentSequenceDiagram.puml @@ -0,0 +1,88 @@ +@startuml +!include ../style.puml + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":TaTrackerParser" as TaTrackerParser LOGIC_COLOR +participant ":StudentCommandParser" as StudentCommandParser LOGIC_COLOR +participant ":AddStudentCommandParser" as AddStudentCommandParser LOGIC_COLOR +participant "a:AddStudentCommand" as AddStudentCommand LOGIC_COLOR +participant ":CommandResult" as CommandResult LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant "m:Student" as Student MODEL_COLOR +participant ":Model" as Model MODEL_COLOR +end box + +[-> LogicManager : execute("Student add \nn/John Doe \nm/A0181234G") +activate LogicManager + +LogicManager -> TaTrackerParser : parseCommand("Student add \nn/John Doe \nm/A0181234G") +activate TaTrackerParser + +create StudentCommandParser +TaTrackerParser -> StudentCommandParser : StudentCommandParser() +activate StudentCommandParser +StudentCommandParser --> TaTrackerParser +deactivate StudentCommandParser + +TaTrackerParser -> StudentCommandParser : parseCommand("add \nn/John Doe \nm/A0181234G") +activate StudentCommandParser + +create AddStudentCommandParser +StudentCommandParser -> AddStudentCommandParser : AddStudentCommandParser() +activate AddStudentCommandParser +AddStudentCommandParser --> StudentCommandParser +deactivate AddStudentCommandParser + +StudentCommandParser -> AddStudentCommandParser : parseCommand("\nn/John Doe \nm/A0181234G") +activate AddStudentCommandParser + +create Student +AddStudentCommandParser -> Student : Student(\n"John Doe", \n"A0181234G") +activate Student +Student --> AddStudentCommandParser +deactivate Student + +create AddStudentCommand +AddStudentCommandParser -> AddStudentCommand : AddStudentCommand(m) +activate AddStudentCommand +AddStudentCommand --> AddStudentCommandParser +deactivate AddStudentCommand + + + +AddStudentCommandParser --> StudentCommandParser : a +deactivate AddStudentCommandParser +AddStudentCommandParser -[hidden]-> StudentCommandParser +destroy AddStudentCommandParser + +StudentCommandParser --> TaTrackerParser : a +deactivate StudentCommandParser +StudentCommandParser -[hidden]-> TaTrackerParser +destroy StudentCommandParser + +TaTrackerParser --> LogicManager : a +deactivate TaTrackerParser + +LogicManager -> AddStudentCommand : execute() +activate AddStudentCommand + +AddStudentCommand -> Model : addStudent(m) +activate Model +Model --> AddStudentCommand +deactivate Model + +create CommandResult +AddStudentCommand -> CommandResult : CommandResult(SuccessMessage) +activate CommandResult +CommandResult --> AddStudentCommand +deactivate CommandResult + +AddStudentCommand --> LogicManager : result +deactivate AddStudentCommand + +[<--LogicManager +deactivate LogicManager +@enduml diff --git a/docs/diagrams/student-view/BetterModelClassDiagram.puml b/docs/diagrams/student-view/BetterModelClassDiagram.puml new file mode 100644 index 00000000000..a85d1ee43a5 --- /dev/null +++ b/docs/diagrams/student-view/BetterModelClassDiagram.puml @@ -0,0 +1,22 @@ +@startuml +!include ../style.puml +skinparam arrowThickness 1.1 +skinparam arrowColor MODEL_COLOR +skinparam classBackgroundColor MODEL_COLOR + +TaTracker *-right-> "1" UniqueTagList +UniqueTagList -[hidden]down- UniqueStudentList +UniqueTagList -[hidden]down- UniqueStudentList + +UniqueTagList *-right-> "*" Tag +UniqueStudentList o-right-> Student + +Student o-up-> "*" Tag + +Student *--> Name +Student *--> Phone +Student *--> Email +Student *--> Matric +Student *--> Rating + +@enduml diff --git a/docs/diagrams/student-view/DeleteStudentSequenceDiagram.puml b/docs/diagrams/student-view/DeleteStudentSequenceDiagram.puml new file mode 100644 index 00000000000..a7699870e0b --- /dev/null +++ b/docs/diagrams/student-view/DeleteStudentSequenceDiagram.puml @@ -0,0 +1,88 @@ +@startuml +!include ../style.puml + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":TaTrackerParser" as TaTrackerParser LOGIC_COLOR +participant ":StudentCommandParser" as StudentCommandParser LOGIC_COLOR +participant ":DeleteStudentCommandParser" as DeleteStudentCommandParser LOGIC_COLOR +participant "d:DeleteStudentCommand" as DeleteStudentCommand LOGIC_COLOR +participant ":CommandResult" as CommandResult LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant ":Model" as Model MODEL_COLOR +end box + +[-> LogicManager : execute("student \ndelete A0181234G") +activate LogicManager + +LogicManager -> TaTrackerParser : parseCommand("student \ndelete A0181234G") +activate TaTrackerParser + +create StudentCommandParser +TaTrackerParser -> StudentCommandParser: StudentCommandParser() +activate StudentCommandParser + +StudentCommandParser --> TaTrackerParser +deactivate StudentCommandParser + +TaTrackerParser -> StudentCommandParser : parse("delete \nA0181234G") +activate StudentCommandParser + +create DeleteStudentCommandParser +StudentCommandParser -> DeleteStudentCommandParser +activate DeleteStudentCommandParser + +DeleteStudentCommandParser --> StudentCommandParser +deactivate DeleteStudentCommandParser + +StudentCommandParser -> DeleteStudentCommandParser : parse("A0181234G") +activate DeleteStudentCommandParser + +create DeleteStudentCommand +DeleteStudentCommandParser -> DeleteStudentCommand +activate DeleteStudentCommand + +DeleteStudentCommand --> DeleteStudentCommandParser : d +deactivate DeleteStudentCommand + +DeleteStudentCommandParser --> StudentCommandParser : d +deactivate DeleteStudentCommandParser + +'Hidden arrow to position the destroy marker below the end of the activation bar. +DeleteStudentCommandParser -[hidden]-> StudentCommandParser +destroy DeleteStudentCommandParser + +StudentCommandParser --> TaTrackerParser : d +deactivate StudentCommandParser + +'Hidden arrow to position the destroy marker below the end of the activation bar. +StudentCommandParser -[hidden]-> TaTrackerParser +destroy StudentCommandParser + +TaTrackerParser --> LogicManager : d +deactivate TaTrackerParser + +LogicManager -> DeleteStudentCommand : execute() +activate DeleteStudentCommand + +DeleteStudentCommand -> Model : deleteStudent(A0181234G) +activate Model + +Model --> DeleteStudentCommand +deactivate Model + +create CommandResult +DeleteStudentCommand -> CommandResult +activate CommandResult + +CommandResult --> DeleteStudentCommand +deactivate CommandResult + +DeleteStudentCommand --> LogicManager : result +deactivate DeleteStudentCommand + +[<--LogicManager +deactivate LogicManager +@enduml diff --git a/docs/diagrams/student-view/StudentClassDiagram.puml b/docs/diagrams/student-view/StudentClassDiagram.puml new file mode 100644 index 00000000000..6ac99979470 --- /dev/null +++ b/docs/diagrams/student-view/StudentClassDiagram.puml @@ -0,0 +1,38 @@ +@startuml +!include ../style.puml +skinparam arrowThickness 1.1 +skinparam arrowColor MODEL_COLOR +skinparam classBackgroundColor MODEL_COLOR + +Package Model <>{ + +Package Student { +Class Student +Class Email +Class Rating +Class Matric +Class Name +Class Phone +Class UniqueStudentList +} + +Package Tag { +Class Tag +} +} + +Class HiddenOutside #FFFFFF + +UniqueStudentList o--> "*" Student +Student *--> Name +Student *--> Rating +Student *--> Phone +Student *--> Email +Student *--> Matric +Student *--> "*" Tag + +Name -[hidden]right-> Phone +Phone -[hidden]right-> Matric +Matric -[hidden]right-> Email + +@enduml diff --git a/docs/diagrams/student-view/StudentModelClassDiagram.puml b/docs/diagrams/student-view/StudentModelClassDiagram.puml new file mode 100644 index 00000000000..397d8cdd82c --- /dev/null +++ b/docs/diagrams/student-view/StudentModelClassDiagram.puml @@ -0,0 +1,39 @@ +@startuml +!include ../style.puml +skinparam arrowThickness 1.1 +skinparam arrowColor MODEL_COLOR +skinparam classBackgroundColor MODEL_COLOR + +Package Model <>{ + +Package Student { +Class Student +Class Email +Class Matric +Class Name +Class Phone +Class Rating +Class UniqueStudentList +} + +Package Tag { +Class Tag +} +} + +Class HiddenOutside #FFFFFF + +TaTracker *--> "1" UniqueStudentList +UniqueStudentList o--> "*" Student +Student *-->"1" Name +Student *-->"0...1" Phone +Student *-->"0...1" Email +Student *-->"1" Matric +Student *-->"1" Rating +Student *--> "*" Tag + +Name -[hidden]right-> Phone +Phone -[hidden]right-> Matric +Matric -[hidden]right-> Email + +@enduml diff --git a/docs/diagrams/tracing/LogicSequenceDiagram.puml b/docs/diagrams/tracing/LogicSequenceDiagram.puml index fdcbe1c0ccc..91bb76eb527 100644 --- a/docs/diagrams/tracing/LogicSequenceDiagram.puml +++ b/docs/diagrams/tracing/LogicSequenceDiagram.puml @@ -2,7 +2,7 @@ !include ../style.puml Participant ":LogicManager" as logic LOGIC_COLOR -Participant ":AddressBookParser" as abp LOGIC_COLOR +Participant ":TaTrackerParser" as abp LOGIC_COLOR Participant ":EditCommandParser" as ecp LOGIC_COLOR Participant "command:EditCommand" as ec LOGIC_COLOR @@ -13,7 +13,7 @@ create ecp abp -> ecp abp -> ecp ++: parse(arguments) create ec -ecp -> ec ++: index, editPersonDescriptor +ecp -> ec ++: index, editStudentDescriptor ec --> ecp -- ecp --> abp --: command abp --> logic --: command diff --git a/docs/diagrams/tss-view/TssActivityDiagram.puml b/docs/diagrams/tss-view/TssActivityDiagram.puml new file mode 100644 index 00000000000..7c943271459 --- /dev/null +++ b/docs/diagrams/tss-view/TssActivityDiagram.puml @@ -0,0 +1,16 @@ +@startuml +start +:A DoneSessionCommand is called; + :Session is added to UniqueDoneSessionList; + + if () then (session is recurring) + :A new session is created with startTime + and endTime incremented; + :New session is added to UniqueSessionList; + + else (session is not recurring) +endif + :Old session is deleted from UniqueSessionList; + + stop +@enduml diff --git a/docs/diagrams/tss-view/TssModelClassDiagram.puml b/docs/diagrams/tss-view/TssModelClassDiagram.puml new file mode 100644 index 00000000000..67a711265c9 --- /dev/null +++ b/docs/diagrams/tss-view/TssModelClassDiagram.puml @@ -0,0 +1,28 @@ +@startuml +!include ../style.puml +package Model { + +skinparam arrowThickness 1.1 +skinparam arrowColor MODEL_COLOR +skinparam classBackgroundColor MODEL_COLOR + +Package Model <> { +package Session { +class Session +class UniqueDoneSessionList +} +package Module { +class Module +class UniqueModuleList +} +} + +Class HiddenOutside #FFFFFF + +TaTracker --> "1 " UniqueDoneSessionList +UniqueModuleList -down-> "*" Module +TaTracker -down-> "1 " UniqueModuleList +UniqueDoneSessionList --> "*" Session + + +@enduml diff --git a/docs/images/AddGroupActivityDiagram.png b/docs/images/AddGroupActivityDiagram.png new file mode 100644 index 00000000000..8af4e1088a9 Binary files /dev/null and b/docs/images/AddGroupActivityDiagram.png differ diff --git a/docs/images/AddGroupSequenceDiagram.png b/docs/images/AddGroupSequenceDiagram.png new file mode 100644 index 00000000000..6cd7ac4c668 Binary files /dev/null and b/docs/images/AddGroupSequenceDiagram.png differ diff --git a/docs/images/AddModuleSequenceDiagram.png b/docs/images/AddModuleSequenceDiagram.png new file mode 100644 index 00000000000..ceb8fcdb06c Binary files /dev/null and b/docs/images/AddModuleSequenceDiagram.png differ diff --git a/docs/images/AddStudentSequenceDiagram.png b/docs/images/AddStudentSequenceDiagram.png new file mode 100644 index 00000000000..cba35565db6 Binary files /dev/null and b/docs/images/AddStudentSequenceDiagram.png differ diff --git a/docs/images/ArchitectureSequenceDiagram.png b/docs/images/ArchitectureSequenceDiagram.png index aa198138f8f..c5e46b007c4 100644 Binary files a/docs/images/ArchitectureSequenceDiagram.png and b/docs/images/ArchitectureSequenceDiagram.png differ diff --git a/docs/images/BetterModelClassDiagram.png b/docs/images/BetterModelClassDiagram.png index bc7ed18ae29..a21a7fc16a0 100644 Binary files a/docs/images/BetterModelClassDiagram.png and b/docs/images/BetterModelClassDiagram.png differ diff --git a/docs/images/CommandsPackageDiagram1.png b/docs/images/CommandsPackageDiagram1.png new file mode 100644 index 00000000000..a7593772e86 Binary files /dev/null and b/docs/images/CommandsPackageDiagram1.png differ diff --git a/docs/images/CommandsPackageDiagram2.png b/docs/images/CommandsPackageDiagram2.png new file mode 100644 index 00000000000..579d7698f3d Binary files /dev/null and b/docs/images/CommandsPackageDiagram2.png differ diff --git a/docs/images/DeleteGroupSequenceDiagram.png b/docs/images/DeleteGroupSequenceDiagram.png new file mode 100644 index 00000000000..bfcd4fc731f Binary files /dev/null and b/docs/images/DeleteGroupSequenceDiagram.png differ diff --git a/docs/images/DeleteStudentSequenceDiagram.png b/docs/images/DeleteStudentSequenceDiagram.png new file mode 100644 index 00000000000..d5714a0a1f1 Binary files /dev/null and b/docs/images/DeleteStudentSequenceDiagram.png differ diff --git a/docs/images/DoneSessionActivityDiagram.png b/docs/images/DoneSessionActivityDiagram.png new file mode 100644 index 00000000000..96b820ad5b6 Binary files /dev/null and b/docs/images/DoneSessionActivityDiagram.png differ diff --git a/docs/images/DoneSessionSequenceDiagram.png b/docs/images/DoneSessionSequenceDiagram.png new file mode 100644 index 00000000000..94eebff2f8d Binary files /dev/null and b/docs/images/DoneSessionSequenceDiagram.png differ diff --git a/docs/images/FilterCommandActivityDiagram.png b/docs/images/FilterCommandActivityDiagram.png new file mode 100644 index 00000000000..7511bf1a79c Binary files /dev/null and b/docs/images/FilterCommandActivityDiagram.png differ diff --git a/docs/images/FilterStudentSequenceDiagram.png b/docs/images/FilterStudentSequenceDiagram.png new file mode 100644 index 00000000000..690671df925 Binary files /dev/null and b/docs/images/FilterStudentSequenceDiagram.png differ diff --git a/docs/images/FindCommandActivityDiagram.png b/docs/images/FindCommandActivityDiagram.png new file mode 100644 index 00000000000..5a84a8d321c Binary files /dev/null and b/docs/images/FindCommandActivityDiagram.png differ diff --git a/docs/images/FindCommandClassDiagram.png b/docs/images/FindCommandClassDiagram.png new file mode 100644 index 00000000000..de1a2ecae3c Binary files /dev/null and b/docs/images/FindCommandClassDiagram.png differ diff --git a/docs/images/GotoSequenceDiagram.png b/docs/images/GotoSequenceDiagram.png new file mode 100644 index 00000000000..7b6ca18967e Binary files /dev/null and b/docs/images/GotoSequenceDiagram.png differ diff --git a/docs/images/LogicClassDiagram.png b/docs/images/LogicClassDiagram.png index b9e853cef12..a7912d8388c 100644 Binary files a/docs/images/LogicClassDiagram.png and b/docs/images/LogicClassDiagram.png differ diff --git a/docs/images/LogicClassDiagram1.png b/docs/images/LogicClassDiagram1.png new file mode 100644 index 00000000000..44ca8d6583a Binary files /dev/null and b/docs/images/LogicClassDiagram1.png differ diff --git a/docs/images/LogicClassDiagram2.png b/docs/images/LogicClassDiagram2.png new file mode 100644 index 00000000000..2df637eeb77 Binary files /dev/null and b/docs/images/LogicClassDiagram2.png differ diff --git a/docs/images/LogicClassDiagram3.png b/docs/images/LogicClassDiagram3.png new file mode 100644 index 00000000000..2469d09026b Binary files /dev/null and b/docs/images/LogicClassDiagram3.png differ diff --git a/docs/images/LogicClassDiagram4.png b/docs/images/LogicClassDiagram4.png new file mode 100644 index 00000000000..55f1d40f742 Binary files /dev/null and b/docs/images/LogicClassDiagram4.png differ diff --git a/docs/images/ModelClassDiagram.png b/docs/images/ModelClassDiagram.png index 280064118cf..b2ee5f1ad7d 100644 Binary files a/docs/images/ModelClassDiagram.png and b/docs/images/ModelClassDiagram.png differ diff --git a/docs/images/ModelComponentsClassDiagram.png b/docs/images/ModelComponentsClassDiagram.png new file mode 100644 index 00000000000..2f2dd0e9261 Binary files /dev/null and b/docs/images/ModelComponentsClassDiagram.png differ diff --git a/docs/images/ModelObjectDiagram.png b/docs/images/ModelObjectDiagram.png new file mode 100644 index 00000000000..07d71d86f35 Binary files /dev/null and b/docs/images/ModelObjectDiagram.png differ diff --git a/docs/images/ModuleModelClassDiagram.png b/docs/images/ModuleModelClassDiagram.png new file mode 100644 index 00000000000..a5a4db3d668 Binary files /dev/null and b/docs/images/ModuleModelClassDiagram.png differ diff --git a/docs/images/ParserPackageDiagram1.png b/docs/images/ParserPackageDiagram1.png new file mode 100644 index 00000000000..8e1ed8ca89d Binary files /dev/null and b/docs/images/ParserPackageDiagram1.png differ diff --git a/docs/images/ParserPackageDiagram2.png b/docs/images/ParserPackageDiagram2.png new file mode 100644 index 00000000000..e334135d585 Binary files /dev/null and b/docs/images/ParserPackageDiagram2.png differ diff --git a/docs/images/RecurringSessionsActivityDiagram.png b/docs/images/RecurringSessionsActivityDiagram.png new file mode 100644 index 00000000000..96b820ad5b6 Binary files /dev/null and b/docs/images/RecurringSessionsActivityDiagram.png differ diff --git a/docs/images/SessionModelClassDiagram.png b/docs/images/SessionModelClassDiagram.png new file mode 100644 index 00000000000..0598c3f9ae3 Binary files /dev/null and b/docs/images/SessionModelClassDiagram.png differ diff --git a/docs/images/SortAllSequenceDiagram.png b/docs/images/SortAllSequenceDiagram.png new file mode 100644 index 00000000000..c359a1b254f Binary files /dev/null and b/docs/images/SortAllSequenceDiagram.png differ diff --git a/docs/images/SortCommandsClassDiagram.png b/docs/images/SortCommandsClassDiagram.png new file mode 100644 index 00000000000..92f823cdc97 Binary files /dev/null and b/docs/images/SortCommandsClassDiagram.png differ diff --git a/docs/images/SortGroupSequenceDiagram.png b/docs/images/SortGroupSequenceDiagram.png new file mode 100644 index 00000000000..bf3bc77329b Binary files /dev/null and b/docs/images/SortGroupSequenceDiagram.png differ diff --git a/docs/images/SortParserActivityDiagram.png b/docs/images/SortParserActivityDiagram.png new file mode 100644 index 00000000000..b2c78257e7a Binary files /dev/null and b/docs/images/SortParserActivityDiagram.png differ diff --git a/docs/images/StorageClassDiagram.png b/docs/images/StorageClassDiagram.png deleted file mode 100644 index d87c1216820..00000000000 Binary files a/docs/images/StorageClassDiagram.png and /dev/null differ diff --git a/docs/images/StorageClassDiagram1.png b/docs/images/StorageClassDiagram1.png new file mode 100644 index 00000000000..9e10d6afd02 Binary files /dev/null and b/docs/images/StorageClassDiagram1.png differ diff --git a/docs/images/StorageClassDiagram2.png b/docs/images/StorageClassDiagram2.png new file mode 100644 index 00000000000..d213a14d210 Binary files /dev/null and b/docs/images/StorageClassDiagram2.png differ diff --git a/docs/images/StudentClassDiagram.png b/docs/images/StudentClassDiagram.png new file mode 100644 index 00000000000..0b58aea1996 Binary files /dev/null and b/docs/images/StudentClassDiagram.png differ diff --git a/docs/images/StudentModelClassDiagram.png b/docs/images/StudentModelClassDiagram.png new file mode 100644 index 00000000000..383d58cd53e Binary files /dev/null and b/docs/images/StudentModelClassDiagram.png differ diff --git a/docs/images/StudentTabClassDiagram.png b/docs/images/StudentTabClassDiagram.png new file mode 100644 index 00000000000..f9a33225a6f Binary files /dev/null and b/docs/images/StudentTabClassDiagram.png differ diff --git a/docs/images/SyntaxHighlightingActivityDiagram1.png b/docs/images/SyntaxHighlightingActivityDiagram1.png new file mode 100644 index 00000000000..dbef776494a Binary files /dev/null and b/docs/images/SyntaxHighlightingActivityDiagram1.png differ diff --git a/docs/images/SyntaxHighlightingActivityDiagram2.png b/docs/images/SyntaxHighlightingActivityDiagram2.png new file mode 100644 index 00000000000..8c0a168a34c Binary files /dev/null and b/docs/images/SyntaxHighlightingActivityDiagram2.png differ diff --git a/docs/images/SyntaxHighlightingActivityDiagram3.png b/docs/images/SyntaxHighlightingActivityDiagram3.png new file mode 100644 index 00000000000..ce6e963f7cf Binary files /dev/null and b/docs/images/SyntaxHighlightingActivityDiagram3.png differ diff --git a/docs/images/SyntaxHighlightingClassDiagram.png b/docs/images/SyntaxHighlightingClassDiagram.png new file mode 100644 index 00000000000..be4853880a0 Binary files /dev/null and b/docs/images/SyntaxHighlightingClassDiagram.png differ diff --git a/docs/images/TssActivityDiagram.png b/docs/images/TssActivityDiagram.png new file mode 100644 index 00000000000..fba2d47e614 Binary files /dev/null and b/docs/images/TssActivityDiagram.png differ diff --git a/docs/images/TssModelClassDiagram.png b/docs/images/TssModelClassDiagram.png new file mode 100644 index 00000000000..9e4963fb109 Binary files /dev/null and b/docs/images/TssModelClassDiagram.png differ diff --git a/docs/images/Ui.png b/docs/images/Ui.png index 5bd77847aa2..070888b1526 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 7b4b3dbea45..f3506704b9e 100644 Binary files a/docs/images/UiClassDiagram.png and b/docs/images/UiClassDiagram.png differ diff --git a/docs/images/aakanksha-rai.png b/docs/images/aakanksha-rai.png new file mode 100644 index 00000000000..80670fc5a55 Binary files /dev/null and b/docs/images/aakanksha-rai.png differ diff --git a/docs/images/chuayijing.png b/docs/images/chuayijing.png new file mode 100644 index 00000000000..b7fef168db6 Binary files /dev/null and b/docs/images/chuayijing.png differ diff --git a/docs/images/eclmist.png b/docs/images/eclmist.png new file mode 100644 index 00000000000..db19a979db5 Binary files /dev/null and b/docs/images/eclmist.png differ diff --git a/docs/images/fatin99.png b/docs/images/fatin99.png new file mode 100644 index 00000000000..599b5d3d322 Binary files /dev/null and b/docs/images/fatin99.png differ diff --git a/docs/images/potatocombat.png b/docs/images/potatocombat.png new file mode 100644 index 00000000000..e7d841dd547 Binary files /dev/null and b/docs/images/potatocombat.png differ diff --git a/docs/images/syntax-highlighting/SH-1-Blank.png b/docs/images/syntax-highlighting/SH-1-Blank.png new file mode 100644 index 00000000000..87713f7b4c0 Binary files /dev/null and b/docs/images/syntax-highlighting/SH-1-Blank.png differ diff --git a/docs/images/syntax-highlighting/SH-1-HasArgs.png b/docs/images/syntax-highlighting/SH-1-HasArgs.png new file mode 100644 index 00000000000..d8fac684aa8 Binary files /dev/null and b/docs/images/syntax-highlighting/SH-1-HasArgs.png differ diff --git a/docs/images/syntax-highlighting/SH-1-Invalid.png b/docs/images/syntax-highlighting/SH-1-Invalid.png new file mode 100644 index 00000000000..5ffabecddcd Binary files /dev/null and b/docs/images/syntax-highlighting/SH-1-Invalid.png differ diff --git a/docs/images/syntax-highlighting/SH-1-NoArgs.png b/docs/images/syntax-highlighting/SH-1-NoArgs.png new file mode 100644 index 00000000000..0bfb8484a8c Binary files /dev/null and b/docs/images/syntax-highlighting/SH-1-NoArgs.png differ diff --git a/docs/images/syntax-highlighting/SH-2-ManySpace.png b/docs/images/syntax-highlighting/SH-2-ManySpace.png new file mode 100644 index 00000000000..61c68e1b6c3 Binary files /dev/null and b/docs/images/syntax-highlighting/SH-2-ManySpace.png differ diff --git a/docs/images/syntax-highlighting/SH-2-RightPreamble.png b/docs/images/syntax-highlighting/SH-2-RightPreamble.png new file mode 100644 index 00000000000..e18cba7815f Binary files /dev/null and b/docs/images/syntax-highlighting/SH-2-RightPreamble.png differ diff --git a/docs/images/syntax-highlighting/SH-2-Space.png b/docs/images/syntax-highlighting/SH-2-Space.png new file mode 100644 index 00000000000..8e42a1edc75 Binary files /dev/null and b/docs/images/syntax-highlighting/SH-2-Space.png differ diff --git a/docs/images/syntax-highlighting/SH-2-WrongPreamble.png b/docs/images/syntax-highlighting/SH-2-WrongPreamble.png new file mode 100644 index 00000000000..551efee6482 Binary files /dev/null and b/docs/images/syntax-highlighting/SH-2-WrongPreamble.png differ diff --git a/docs/images/syntax-highlighting/SH-3-Invalid.png b/docs/images/syntax-highlighting/SH-3-Invalid.png new file mode 100644 index 00000000000..ba364e3a69e Binary files /dev/null and b/docs/images/syntax-highlighting/SH-3-Invalid.png differ diff --git a/docs/images/syntax-highlighting/SH-3-Valid.png b/docs/images/syntax-highlighting/SH-3-Valid.png new file mode 100644 index 00000000000..193a31819fc Binary files /dev/null and b/docs/images/syntax-highlighting/SH-3-Valid.png differ diff --git a/docs/images/syntax-highlighting/SH-3-Wrong.png b/docs/images/syntax-highlighting/SH-3-Wrong.png new file mode 100644 index 00000000000..034aece1189 Binary files /dev/null and b/docs/images/syntax-highlighting/SH-3-Wrong.png differ diff --git a/docs/images/syntax-highlighting/SH-CommandBox.png b/docs/images/syntax-highlighting/SH-CommandBox.png new file mode 100644 index 00000000000..dd7448a9665 Binary files /dev/null and b/docs/images/syntax-highlighting/SH-CommandBox.png differ diff --git a/docs/team/johndoe.adoc b/docs/team/johndoe.adoc index f39e76e49b2..952328e791f 100644 --- a/docs/team/johndoe.adoc +++ b/docs/team/johndoe.adoc @@ -3,14 +3,14 @@ :imagesDir: ../images :stylesDir: ../stylesheets -== PROJECT: AddressBook - Level 3 +== PROJECT: TA-Tracker --- == Overview -AddressBook - Level 3 is a desktop address book application used for teaching Software Engineering principles. The user interacts with it using a CLI, and it has a GUI created with JavaFX. It is written in Java, and has about 10 kLoC. - +TA-Tracker is a productivity tool made for NUS Computing +Teaching Assistants (TAs) who want to be able to track and manage all of their claimable hours in NUS. == Summary of contributions * *Major enhancement*: added *the ability to undo/redo previous commands* diff --git a/docs/tutorials/AddRemark.adoc b/docs/tutorials/AddRemark.adoc index ea388068303..1bdbd6c6b4e 100644 --- a/docs/tutorials/AddRemark.adoc +++ b/docs/tutorials/AddRemark.adoc @@ -38,10 +38,10 @@ We accomplish that by returning a `CommandResult` with an accompanying message. ---- package seedu.address.logic.commands; -import seedu.address.model.Model; +import tatracker.model.Model; /** - * Changes the remark of an existing person in the address book. + * Changes the remark of an existing student in the address book. */ public class RemarkCommand extends Command { @@ -82,8 +82,8 @@ Following the convention in other commands, we add relevant messages as constant .RemarkCommand.java [source, java] ---- - public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits the remark of the person identified " - + "by the index number used in the last person listing. " + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits the remark of the student identified " + + "by the index number used in the last student listing. " + "Existing remark will be overwritten by the input.\n" + "Parameters: INDEX (must be a positive integer) " + "r/ [REMARK]\n" @@ -110,7 +110,7 @@ While this is not a replacement for tests, it is an obvious way to tell if our c [source, java] ---- -import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; +import static tatracker.commons.util.CollectionUtil.requireAllNonNull; //... public class RemarkCommand extends Command { //... @@ -120,8 +120,8 @@ public class RemarkCommand extends Command { private final String remark; /** - * @param index of the person in the filtered person list to edit the remark - * @param remark of the person to be updated to + * @param index of the student in the filtered student list to edit the remark + * @param remark of the student to be updated to */ public RemarkCommand(Index index, String remark) { requireAllNonNull(index, remark); @@ -246,12 +246,12 @@ If you are stuck, check out the sample link:https://github.com/nus-cs2103-AY1920 Now that we have all the information that we need, let's lay the groundwork for some _persistent_ changes. We achieve that by working with the `Person` model. -Each field in a Person is implemented as a separate class (e.g. a `Name` object represents the person's name). -That means we should add a `Remark` class so that we can use a `Remark` object to represent a remark given to a person. +Each field in a Person is implemented as a separate class (e.g. a `Name` object represents the student's name). +That means we should add a `Remark` class so that we can use a `Remark` object to represent a remark given to a student. === Add a new `Remark` class -Create a new `Remark` in `seedu.address.model.person`. Since a `Remark` is a field that is similar to `Address`, we can reuse a significant bit of code. +Create a new `Remark` in `seedu.address.model.student`. Since a `Remark` is a field that is similar to `Address`, we can reuse a significant bit of code. A copy-paste and search-replace later, you should have something like link:https://github.com/nus-cs2103-AY1920S1/addressbook-level3/commit/b7a47c50c8e5f0430d343a23d2863446b6ce9298#diff-af2f075d24dfcd333876f0fbce321f25[this]. Note how `Remark` has no constrains and thus does not require input validation. @@ -263,7 +263,7 @@ These should be relatively simple changes. == Add a placeholder element for remark to the UI -Without getting too deep into `fxml`, let's go on a 5 minute adventure to get some placeholder text to show up for each person. +Without getting too deep into `fxml`, let's go on a 5 minute adventure to get some placeholder text to show up for each student. Simply add [source, java] @@ -326,9 +326,9 @@ Just add link:https://github.com/nus-cs2103-AY1920S1/addressbook-level3/commit/5 [source, java] .PersonCard.java ---- -public PersonCard(Person person, int displayedIndex) { +public PersonCard(Person student, int displayedIndex) { //... - remark.setText(person.getRemark().value); + remark.setText(student.getRemark().value); } ---- diff --git a/docs/tutorials/RemovingFields.adoc b/docs/tutorials/RemovingFields.adoc index 5a50b6965a6..2550a9073e6 100644 --- a/docs/tutorials/RemovingFields.adoc +++ b/docs/tutorials/RemovingFields.adoc @@ -24,7 +24,7 @@ Fortunately, the IntelliJ IDEA provides a robust refactoring tool that can ident Let's try to use it as much as we can. === Assisted refactoring -The `address` field in `Person` is actually an instance of the `seedu.address.model.person.Address` class. +The `address` field in `Person` is actually an instance of the `seedu.address.model.student.Address` class. Since removing the `Address` class will break the application, we start by identifying ``Address``'s usages. This allows us to see code that depends on `Address` to function properly and edit them on a case-by-case basis. Right-click the `Address` class and select `Refactor` > `Safe Delete` through the menu. diff --git a/docs/tutorials/TracingCode.adoc b/docs/tutorials/TracingCode.adoc index 5f0aaba1741..5796463b0f8 100644 --- a/docs/tutorials/TracingCode.adoc +++ b/docs/tutorials/TracingCode.adoc @@ -49,7 +49,7 @@ However, the execution path through a GUI is often somewhat obscure due to vario used by GUI frameworks, which happens to be the case here too. Therefore, let us put the breakpoint where the UI transfers control to the Logic component. According to the sequence diagram, the UI component yields control to the Logic component through a method named `execute`. Searching through the code base for `execute()` yields a promising candidate in -`seedu.address.ui.CommandBox.CommandExecutor`. +`tatracker.ui.CommandBox.CommandExecutor`. .Using the `Search for target by name` feature. `Navigate` > `Symbol`. image::Execute.png[] diff --git a/libs/controlsfx-11.0.1.jar b/libs/controlsfx-11.0.1.jar new file mode 100644 index 00000000000..c0f149ecc3d Binary files /dev/null and b/libs/controlsfx-11.0.1.jar differ diff --git a/list.png b/list.png new file mode 100644 index 00000000000..e652c81799c Binary files /dev/null and b/list.png differ diff --git a/person.png b/person.png new file mode 100644 index 00000000000..2959879e8e0 Binary files /dev/null and b/person.png differ diff --git a/person2.png b/person2.png new file mode 100644 index 00000000000..b29341a3f32 Binary files /dev/null and b/person2.png differ diff --git a/schedule.png b/schedule.png new file mode 100644 index 00000000000..0d4be5150a1 Binary files /dev/null and b/schedule.png differ diff --git a/src/main/java/seedu/address/commons/core/Messages.java b/src/main/java/seedu/address/commons/core/Messages.java deleted file mode 100644 index 1deb3a1e469..00000000000 --- a/src/main/java/seedu/address/commons/core/Messages.java +++ /dev/null @@ -1,13 +0,0 @@ -package seedu.address.commons.core; - -/** - * Container for user visible messages. - */ -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!"; - -} diff --git a/src/main/java/seedu/address/logic/Logic.java b/src/main/java/seedu/address/logic/Logic.java deleted file mode 100644 index 92cd8fa605a..00000000000 --- a/src/main/java/seedu/address/logic/Logic.java +++ /dev/null @@ -1,50 +0,0 @@ -package seedu.address.logic; - -import java.nio.file.Path; - -import javafx.collections.ObservableList; -import seedu.address.commons.core.GuiSettings; -import seedu.address.logic.commands.CommandResult; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.person.Person; - -/** - * API of the Logic component - */ -public interface Logic { - /** - * Executes the command and returns the result. - * @param commandText The command as entered by the user. - * @return the result of the command execution. - * @throws CommandException If an error occurs during command execution. - * @throws ParseException If an error occurs during parsing. - */ - CommandResult execute(String commandText) throws CommandException, ParseException; - - /** - * Returns the AddressBook. - * - * @see seedu.address.model.Model#getAddressBook() - */ - ReadOnlyAddressBook getAddressBook(); - - /** Returns an unmodifiable view of the filtered list of persons */ - ObservableList getFilteredPersonList(); - - /** - * Returns the user prefs' address book file path. - */ - Path getAddressBookFilePath(); - - /** - * Returns the user prefs' GUI settings. - */ - GuiSettings getGuiSettings(); - - /** - * Set the user prefs' GUI settings. - */ - void setGuiSettings(GuiSettings guiSettings); -} diff --git a/src/main/java/seedu/address/logic/LogicManager.java b/src/main/java/seedu/address/logic/LogicManager.java deleted file mode 100644 index d47ce874b1a..00000000000 --- a/src/main/java/seedu/address/logic/LogicManager.java +++ /dev/null @@ -1,78 +0,0 @@ -package seedu.address.logic; - -import java.io.IOException; -import java.nio.file.Path; -import java.util.logging.Logger; - -import javafx.collections.ObservableList; -import seedu.address.commons.core.GuiSettings; -import seedu.address.commons.core.LogsCenter; -import seedu.address.logic.commands.Command; -import seedu.address.logic.commands.CommandResult; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.logic.parser.AddressBookParser; -import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.Model; -import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.person.Person; -import seedu.address.storage.Storage; - -/** - * The main LogicManager of the app. - */ -public class LogicManager implements Logic { - public static final String FILE_OPS_ERROR_MESSAGE = "Could not save data to file: "; - private final Logger logger = LogsCenter.getLogger(LogicManager.class); - - private final Model model; - private final Storage storage; - private final AddressBookParser addressBookParser; - - public LogicManager(Model model, Storage storage) { - this.model = model; - this.storage = storage; - addressBookParser = new AddressBookParser(); - } - - @Override - public CommandResult execute(String commandText) throws CommandException, ParseException { - logger.info("----------------[USER COMMAND][" + commandText + "]"); - - CommandResult commandResult; - Command command = addressBookParser.parseCommand(commandText); - commandResult = command.execute(model); - - try { - storage.saveAddressBook(model.getAddressBook()); - } catch (IOException ioe) { - throw new CommandException(FILE_OPS_ERROR_MESSAGE + ioe, ioe); - } - - return commandResult; - } - - @Override - public ReadOnlyAddressBook getAddressBook() { - return model.getAddressBook(); - } - - @Override - public ObservableList getFilteredPersonList() { - return model.getFilteredPersonList(); - } - - @Override - public Path getAddressBookFilePath() { - return model.getAddressBookFilePath(); - } - - @Override - public GuiSettings getGuiSettings() { - return model.getGuiSettings(); - } - - @Override - public void setGuiSettings(GuiSettings guiSettings) { - model.setGuiSettings(guiSettings); - } -} 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 71656d7c5c8..00000000000 --- 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.Model; -import seedu.address.model.person.Person; - -/** - * Adds a person to the address book. - */ -public class AddCommand extends Command { - - public static final String COMMAND_WORD = "add"; - - public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a person to the address book. " - + "Parameters: " - + PREFIX_NAME + "NAME " - + PREFIX_PHONE + "PHONE " - + PREFIX_EMAIL + "EMAIL " - + PREFIX_ADDRESS + "ADDRESS " - + "[" + 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 execute(Model model) throws CommandException { - requireNonNull(model); - - if (model.hasPerson(toAdd)) { - throw new CommandException(MESSAGE_DUPLICATE_PERSON); - } - - model.addPerson(toAdd); - return new CommandResult(String.format(MESSAGE_SUCCESS, toAdd)); - } - - @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 deleted file mode 100644 index 9c86b1fa6e4..00000000000 --- a/src/main/java/seedu/address/logic/commands/ClearCommand.java +++ /dev/null @@ -1,23 +0,0 @@ -package seedu.address.logic.commands; - -import static java.util.Objects.requireNonNull; - -import seedu.address.model.AddressBook; -import seedu.address.model.Model; - -/** - * Clears the address book. - */ -public class ClearCommand extends Command { - - public static final String COMMAND_WORD = "clear"; - public static final String MESSAGE_SUCCESS = "Address book has been cleared!"; - - - @Override - public CommandResult execute(Model model) { - requireNonNull(model); - model.setAddressBook(new AddressBook()); - return new CommandResult(MESSAGE_SUCCESS); - } -} diff --git a/src/main/java/seedu/address/logic/commands/DeleteCommand.java b/src/main/java/seedu/address/logic/commands/DeleteCommand.java deleted file mode 100644 index 02fd256acba..00000000000 --- a/src/main/java/seedu/address/logic/commands/DeleteCommand.java +++ /dev/null @@ -1,53 +0,0 @@ -package seedu.address.logic.commands; - -import static java.util.Objects.requireNonNull; - -import java.util.List; - -import seedu.address.commons.core.Messages; -import seedu.address.commons.core.index.Index; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.model.Model; -import seedu.address.model.person.Person; - -/** - * Deletes a person identified using it's displayed index from the address book. - */ -public class DeleteCommand extends Command { - - public static final String COMMAND_WORD = "delete"; - - public static final String MESSAGE_USAGE = COMMAND_WORD - + ": Deletes the person identified by the index number used in the displayed person list.\n" - + "Parameters: INDEX (must be a positive integer)\n" - + "Example: " + COMMAND_WORD + " 1"; - - public static final String MESSAGE_DELETE_PERSON_SUCCESS = "Deleted Person: %1$s"; - - private final Index targetIndex; - - public DeleteCommand(Index targetIndex) { - this.targetIndex = targetIndex; - } - - @Override - public CommandResult execute(Model model) throws CommandException { - requireNonNull(model); - List lastShownList = model.getFilteredPersonList(); - - if (targetIndex.getZeroBased() >= lastShownList.size()) { - throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); - } - - Person personToDelete = lastShownList.get(targetIndex.getZeroBased()); - model.deletePerson(personToDelete); - return new CommandResult(String.format(MESSAGE_DELETE_PERSON_SUCCESS, personToDelete)); - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof DeleteCommand // instanceof handles nulls - && targetIndex.equals(((DeleteCommand) other).targetIndex)); // state check - } -} diff --git a/src/main/java/seedu/address/logic/commands/EditCommand.java b/src/main/java/seedu/address/logic/commands/EditCommand.java deleted file mode 100644 index 7e36114902f..00000000000 --- a/src/main/java/seedu/address/logic/commands/EditCommand.java +++ /dev/null @@ -1,226 +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 static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; - -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Optional; -import java.util.Set; - -import seedu.address.commons.core.Messages; -import seedu.address.commons.core.index.Index; -import seedu.address.commons.util.CollectionUtil; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.model.Model; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Person; -import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; - -/** - * Edits the details of an existing person in the address book. - */ -public class EditCommand extends Command { - - public static final String COMMAND_WORD = "edit"; - - public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits the details of the person identified " - + "by the index number used in the displayed person list. " - + "Existing values will be overwritten by the input values.\n" - + "Parameters: INDEX (must be a positive integer) " - + "[" + PREFIX_NAME + "NAME] " - + "[" + PREFIX_PHONE + "PHONE] " - + "[" + PREFIX_EMAIL + "EMAIL] " - + "[" + PREFIX_ADDRESS + "ADDRESS] " - + "[" + PREFIX_TAG + "TAG]...\n" - + "Example: " + COMMAND_WORD + " 1 " - + PREFIX_PHONE + "91234567 " - + PREFIX_EMAIL + "johndoe@example.com"; - - public static final String MESSAGE_EDIT_PERSON_SUCCESS = "Edited Person: %1$s"; - public static final String MESSAGE_NOT_EDITED = "At least one field to edit must be provided."; - public static final String MESSAGE_DUPLICATE_PERSON = "This person already exists in the address book."; - - private final Index index; - private final EditPersonDescriptor editPersonDescriptor; - - /** - * @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) { - requireNonNull(index); - requireNonNull(editPersonDescriptor); - - this.index = index; - this.editPersonDescriptor = new EditPersonDescriptor(editPersonDescriptor); - } - - @Override - public CommandResult execute(Model model) throws CommandException { - requireNonNull(model); - List lastShownList = model.getFilteredPersonList(); - - if (index.getZeroBased() >= lastShownList.size()) { - throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); - } - - Person personToEdit = lastShownList.get(index.getZeroBased()); - Person editedPerson = createEditedPerson(personToEdit, editPersonDescriptor); - - if (!personToEdit.isSamePerson(editedPerson) && model.hasPerson(editedPerson)) { - throw new CommandException(MESSAGE_DUPLICATE_PERSON); - } - - model.setPerson(personToEdit, editedPerson); - model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); - return new CommandResult(String.format(MESSAGE_EDIT_PERSON_SUCCESS, editedPerson)); - } - - /** - * Creates and returns a {@code Person} with the details of {@code personToEdit} - * edited with {@code editPersonDescriptor}. - */ - private static Person createEditedPerson(Person personToEdit, EditPersonDescriptor editPersonDescriptor) { - assert personToEdit != null; - - Name updatedName = editPersonDescriptor.getName().orElse(personToEdit.getName()); - Phone updatedPhone = editPersonDescriptor.getPhone().orElse(personToEdit.getPhone()); - Email updatedEmail = editPersonDescriptor.getEmail().orElse(personToEdit.getEmail()); - Address updatedAddress = editPersonDescriptor.getAddress().orElse(personToEdit.getAddress()); - Set updatedTags = editPersonDescriptor.getTags().orElse(personToEdit.getTags()); - - return new Person(updatedName, updatedPhone, updatedEmail, updatedAddress, updatedTags); - } - - @Override - public boolean equals(Object other) { - // short circuit if same object - if (other == this) { - return true; - } - - // instanceof handles nulls - if (!(other instanceof EditCommand)) { - return false; - } - - // state check - EditCommand e = (EditCommand) other; - return index.equals(e.index) - && editPersonDescriptor.equals(e.editPersonDescriptor); - } - - /** - * Stores the details to edit the person with. Each non-empty field value will replace the - * corresponding field value of the person. - */ - public static class EditPersonDescriptor { - private Name name; - private Phone phone; - private Email email; - private Address address; - private Set tags; - - public EditPersonDescriptor() {} - - /** - * Copy constructor. - * A defensive copy of {@code tags} is used internally. - */ - public EditPersonDescriptor(EditPersonDescriptor toCopy) { - setName(toCopy.name); - setPhone(toCopy.phone); - setEmail(toCopy.email); - setAddress(toCopy.address); - setTags(toCopy.tags); - } - - /** - * Returns true if at least one field is edited. - */ - public boolean isAnyFieldEdited() { - return CollectionUtil.isAnyNonNull(name, phone, email, address, tags); - } - - public void setName(Name name) { - this.name = name; - } - - public Optional getName() { - return Optional.ofNullable(name); - } - - public void setPhone(Phone phone) { - this.phone = phone; - } - - public Optional getPhone() { - return Optional.ofNullable(phone); - } - - public void setEmail(Email email) { - this.email = email; - } - - public Optional getEmail() { - return Optional.ofNullable(email); - } - - public void setAddress(Address address) { - this.address = address; - } - - public Optional
getAddress() { - return Optional.ofNullable(address); - } - - /** - * Sets {@code tags} to this object's {@code tags}. - * A defensive copy of {@code tags} is used internally. - */ - public void setTags(Set tags) { - this.tags = (tags != null) ? new HashSet<>(tags) : null; - } - - /** - * Returns an unmodifiable tag set, which throws {@code UnsupportedOperationException} - * if modification is attempted. - * Returns {@code Optional#empty()} if {@code tags} is null. - */ - public Optional> getTags() { - return (tags != null) ? Optional.of(Collections.unmodifiableSet(tags)) : Optional.empty(); - } - - @Override - public boolean equals(Object other) { - // short circuit if same object - if (other == this) { - return true; - } - - // instanceof handles nulls - if (!(other instanceof EditPersonDescriptor)) { - return false; - } - - // state check - EditPersonDescriptor e = (EditPersonDescriptor) other; - - return getName().equals(e.getName()) - && getPhone().equals(e.getPhone()) - && getEmail().equals(e.getEmail()) - && getAddress().equals(e.getAddress()) - && getTags().equals(e.getTags()); - } - } -} diff --git a/src/main/java/seedu/address/logic/commands/ExitCommand.java b/src/main/java/seedu/address/logic/commands/ExitCommand.java deleted file mode 100644 index 3dd85a8ba90..00000000000 --- a/src/main/java/seedu/address/logic/commands/ExitCommand.java +++ /dev/null @@ -1,19 +0,0 @@ -package seedu.address.logic.commands; - -import seedu.address.model.Model; - -/** - * Terminates the program. - */ -public class ExitCommand extends Command { - - public static final String COMMAND_WORD = "exit"; - - public static final String MESSAGE_EXIT_ACKNOWLEDGEMENT = "Exiting Address Book as requested ..."; - - @Override - public CommandResult execute(Model model) { - return new CommandResult(MESSAGE_EXIT_ACKNOWLEDGEMENT, false, true); - } - -} diff --git a/src/main/java/seedu/address/logic/commands/FindCommand.java b/src/main/java/seedu/address/logic/commands/FindCommand.java deleted file mode 100644 index d6b19b0a0de..00000000000 --- a/src/main/java/seedu/address/logic/commands/FindCommand.java +++ /dev/null @@ -1,42 +0,0 @@ -package seedu.address.logic.commands; - -import static java.util.Objects.requireNonNull; - -import seedu.address.commons.core.Messages; -import seedu.address.model.Model; -import seedu.address.model.person.NameContainsKeywordsPredicate; - -/** - * Finds and lists all persons in address book whose name contains any of the argument keywords. - * Keyword matching is case insensitive. - */ -public class FindCommand extends Command { - - public static final String COMMAND_WORD = "find"; - - public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all persons whose names contain any of " - + "the specified keywords (case-insensitive) and displays them as a list with index numbers.\n" - + "Parameters: KEYWORD [MORE_KEYWORDS]...\n" - + "Example: " + COMMAND_WORD + " alice bob charlie"; - - private final NameContainsKeywordsPredicate predicate; - - public FindCommand(NameContainsKeywordsPredicate predicate) { - this.predicate = predicate; - } - - @Override - public CommandResult execute(Model model) { - requireNonNull(model); - model.updateFilteredPersonList(predicate); - return new CommandResult( - String.format(Messages.MESSAGE_PERSONS_LISTED_OVERVIEW, model.getFilteredPersonList().size())); - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof FindCommand // instanceof handles nulls - && predicate.equals(((FindCommand) other).predicate)); // state check - } -} diff --git a/src/main/java/seedu/address/logic/commands/HelpCommand.java b/src/main/java/seedu/address/logic/commands/HelpCommand.java deleted file mode 100644 index bf824f91bd0..00000000000 --- a/src/main/java/seedu/address/logic/commands/HelpCommand.java +++ /dev/null @@ -1,21 +0,0 @@ -package seedu.address.logic.commands; - -import seedu.address.model.Model; - -/** - * Format full help instructions for every command for display. - */ -public class HelpCommand extends Command { - - public static final String COMMAND_WORD = "help"; - - public static final String MESSAGE_USAGE = COMMAND_WORD + ": Shows program usage instructions.\n" - + "Example: " + COMMAND_WORD; - - public static final String SHOWING_HELP_MESSAGE = "Opened help window."; - - @Override - public CommandResult execute(Model model) { - return new CommandResult(SHOWING_HELP_MESSAGE, true, false); - } -} diff --git a/src/main/java/seedu/address/logic/commands/ListCommand.java b/src/main/java/seedu/address/logic/commands/ListCommand.java deleted file mode 100644 index 84be6ad2596..00000000000 --- a/src/main/java/seedu/address/logic/commands/ListCommand.java +++ /dev/null @@ -1,24 +0,0 @@ -package seedu.address.logic.commands; - -import static java.util.Objects.requireNonNull; -import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; - -import seedu.address.model.Model; - -/** - * Lists all persons in the address book to the user. - */ -public class ListCommand extends Command { - - public static final String COMMAND_WORD = "list"; - - public static final String MESSAGE_SUCCESS = "Listed all persons"; - - - @Override - public CommandResult execute(Model model) { - requireNonNull(model); - model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); - return new CommandResult(MESSAGE_SUCCESS); - } -} diff --git a/src/main/java/seedu/address/logic/parser/AddCommandParser.java b/src/main/java/seedu/address/logic/parser/AddCommandParser.java deleted file mode 100644 index 3b8bfa035e8..00000000000 --- a/src/main/java/seedu/address/logic/parser/AddCommandParser.java +++ /dev/null @@ -1,60 +0,0 @@ -package seedu.address.logic.parser; - -import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; -import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; -import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; -import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; -import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; -import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; - -import java.util.Set; -import java.util.stream.Stream; - -import seedu.address.logic.commands.AddCommand; -import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Person; -import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; - -/** - * Parses input arguments and creates a new AddCommand object - */ -public class AddCommandParser implements Parser { - - /** - * Parses the given {@code String} of arguments in the context of the AddCommand - * and returns an AddCommand object for execution. - * @throws ParseException if the user input does not conform the expected format - */ - public AddCommand parse(String args) throws ParseException { - ArgumentMultimap argMultimap = - ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, PREFIX_TAG); - - if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_ADDRESS, PREFIX_PHONE, PREFIX_EMAIL) - || !argMultimap.getPreamble().isEmpty()) { - throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE)); - } - - Name name = ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get()); - Phone phone = ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE).get()); - Email email = ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL).get()); - Address address = ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS).get()); - Set tagList = ParserUtil.parseTags(argMultimap.getAllValues(PREFIX_TAG)); - - Person person = new Person(name, phone, email, address, tagList); - - return new AddCommand(person); - } - - /** - * Returns true if none of the prefixes contains empty {@code Optional} values in the given - * {@code ArgumentMultimap}. - */ - private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { - return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); - } - -} diff --git a/src/main/java/seedu/address/logic/parser/AddressBookParser.java b/src/main/java/seedu/address/logic/parser/AddressBookParser.java deleted file mode 100644 index 1e466792b46..00000000000 --- a/src/main/java/seedu/address/logic/parser/AddressBookParser.java +++ /dev/null @@ -1,76 +0,0 @@ -package seedu.address.logic.parser; - -import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; -import static seedu.address.commons.core.Messages.MESSAGE_UNKNOWN_COMMAND; - -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import seedu.address.logic.commands.AddCommand; -import seedu.address.logic.commands.ClearCommand; -import seedu.address.logic.commands.Command; -import seedu.address.logic.commands.DeleteCommand; -import seedu.address.logic.commands.EditCommand; -import seedu.address.logic.commands.ExitCommand; -import seedu.address.logic.commands.FindCommand; -import seedu.address.logic.commands.HelpCommand; -import seedu.address.logic.commands.ListCommand; -import seedu.address.logic.parser.exceptions.ParseException; - -/** - * Parses user input. - */ -public class AddressBookParser { - - /** - * Used for initial separation of command word and args. - */ - private static final Pattern BASIC_COMMAND_FORMAT = Pattern.compile("(?\\S+)(?.*)"); - - /** - * Parses user input into command for execution. - * - * @param userInput full user input string - * @return the command based on the user input - * @throws ParseException if the user input does not conform the expected format - */ - public Command parseCommand(String userInput) throws ParseException { - final Matcher matcher = BASIC_COMMAND_FORMAT.matcher(userInput.trim()); - if (!matcher.matches()) { - throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, HelpCommand.MESSAGE_USAGE)); - } - - final String commandWord = matcher.group("commandWord"); - final String arguments = matcher.group("arguments"); - switch (commandWord) { - - case AddCommand.COMMAND_WORD: - return new AddCommandParser().parse(arguments); - - case EditCommand.COMMAND_WORD: - return new EditCommandParser().parse(arguments); - - case DeleteCommand.COMMAND_WORD: - return new DeleteCommandParser().parse(arguments); - - case ClearCommand.COMMAND_WORD: - return new ClearCommand(); - - case FindCommand.COMMAND_WORD: - return new FindCommandParser().parse(arguments); - - case ListCommand.COMMAND_WORD: - return new ListCommand(); - - case ExitCommand.COMMAND_WORD: - return new ExitCommand(); - - case HelpCommand.COMMAND_WORD: - return new HelpCommand(); - - 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 deleted file mode 100644 index 75b1a9bf119..00000000000 --- a/src/main/java/seedu/address/logic/parser/CliSyntax.java +++ /dev/null @@ -1,15 +0,0 @@ -package seedu.address.logic.parser; - -/** - * Contains Command Line Interface (CLI) syntax definitions common to multiple commands - */ -public class CliSyntax { - - /* Prefix definitions */ - public static final Prefix PREFIX_NAME = new Prefix("n/"); - public static final Prefix PREFIX_PHONE = new Prefix("p/"); - public static final Prefix PREFIX_EMAIL = new Prefix("e/"); - public static final Prefix PREFIX_ADDRESS = new Prefix("a/"); - public static final Prefix PREFIX_TAG = new Prefix("t/"); - -} diff --git a/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java b/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java deleted file mode 100644 index 522b93081cc..00000000000 --- a/src/main/java/seedu/address/logic/parser/DeleteCommandParser.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.logic.commands.DeleteCommand; -import seedu.address.logic.parser.exceptions.ParseException; - -/** - * Parses input arguments and creates a new DeleteCommand object - */ -public class DeleteCommandParser implements Parser { - - /** - * Parses the given {@code String} of arguments in the context of the DeleteCommand - * and returns a DeleteCommand object for execution. - * @throws ParseException if the user input does not conform the expected format - */ - public DeleteCommand parse(String args) throws ParseException { - try { - Index index = ParserUtil.parseIndex(args); - return new DeleteCommand(index); - } catch (ParseException pe) { - throw new ParseException( - String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteCommand.MESSAGE_USAGE), pe); - } - } - -} diff --git a/src/main/java/seedu/address/logic/parser/EditCommandParser.java b/src/main/java/seedu/address/logic/parser/EditCommandParser.java deleted file mode 100644 index 845644b7dea..00000000000 --- a/src/main/java/seedu/address/logic/parser/EditCommandParser.java +++ /dev/null @@ -1,82 +0,0 @@ -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_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 java.util.Collection; -import java.util.Collections; -import java.util.Optional; -import java.util.Set; - -import seedu.address.commons.core.index.Index; -import seedu.address.logic.commands.EditCommand; -import seedu.address.logic.commands.EditCommand.EditPersonDescriptor; -import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.tag.Tag; - -/** - * Parses input arguments and creates a new EditCommand object - */ -public class EditCommandParser implements Parser { - - /** - * Parses the given {@code String} of arguments in the context of the EditCommand - * and returns an EditCommand object for execution. - * @throws ParseException if the user input does not conform the expected format - */ - public EditCommand parse(String args) throws ParseException { - requireNonNull(args); - ArgumentMultimap argMultimap = - ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, PREFIX_TAG); - - Index index; - - try { - index = ParserUtil.parseIndex(argMultimap.getPreamble()); - } catch (ParseException pe) { - throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditCommand.MESSAGE_USAGE), pe); - } - - EditPersonDescriptor editPersonDescriptor = new EditPersonDescriptor(); - if (argMultimap.getValue(PREFIX_NAME).isPresent()) { - editPersonDescriptor.setName(ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get())); - } - if (argMultimap.getValue(PREFIX_PHONE).isPresent()) { - editPersonDescriptor.setPhone(ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE).get())); - } - if (argMultimap.getValue(PREFIX_EMAIL).isPresent()) { - editPersonDescriptor.setEmail(ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL).get())); - } - if (argMultimap.getValue(PREFIX_ADDRESS).isPresent()) { - editPersonDescriptor.setAddress(ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS).get())); - } - parseTagsForEdit(argMultimap.getAllValues(PREFIX_TAG)).ifPresent(editPersonDescriptor::setTags); - - if (!editPersonDescriptor.isAnyFieldEdited()) { - throw new ParseException(EditCommand.MESSAGE_NOT_EDITED); - } - - return new EditCommand(index, editPersonDescriptor); - } - - /** - * Parses {@code Collection tags} into a {@code Set} if {@code tags} is non-empty. - * If {@code tags} contain only one element which is an empty string, it will be parsed into a - * {@code Set} containing zero tags. - */ - private Optional> parseTagsForEdit(Collection tags) throws ParseException { - assert tags != null; - - if (tags.isEmpty()) { - return Optional.empty(); - } - Collection tagSet = tags.size() == 1 && tags.contains("") ? Collections.emptySet() : tags; - return Optional.of(ParserUtil.parseTags(tagSet)); - } - -} diff --git a/src/main/java/seedu/address/logic/parser/FindCommandParser.java b/src/main/java/seedu/address/logic/parser/FindCommandParser.java deleted file mode 100644 index 4fb71f23103..00000000000 --- a/src/main/java/seedu/address/logic/parser/FindCommandParser.java +++ /dev/null @@ -1,33 +0,0 @@ -package seedu.address.logic.parser; - -import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; - -import java.util.Arrays; - -import seedu.address.logic.commands.FindCommand; -import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.person.NameContainsKeywordsPredicate; - -/** - * Parses input arguments and creates a new FindCommand object - */ -public class FindCommandParser implements Parser { - - /** - * Parses the given {@code String} of arguments in the context of the FindCommand - * and returns a FindCommand object for execution. - * @throws ParseException if the user input does not conform the expected format - */ - public FindCommand parse(String args) throws ParseException { - String trimmedArgs = args.trim(); - if (trimmedArgs.isEmpty()) { - throw new ParseException( - String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE)); - } - - String[] nameKeywords = trimmedArgs.split("\\s+"); - - return new FindCommand(new NameContainsKeywordsPredicate(Arrays.asList(nameKeywords))); - } - -} diff --git a/src/main/java/seedu/address/logic/parser/ParserUtil.java b/src/main/java/seedu/address/logic/parser/ParserUtil.java deleted file mode 100644 index b117acb9c55..00000000000 --- a/src/main/java/seedu/address/logic/parser/ParserUtil.java +++ /dev/null @@ -1,124 +0,0 @@ -package seedu.address.logic.parser; - -import static java.util.Objects.requireNonNull; - -import java.util.Collection; -import java.util.HashSet; -import java.util.Set; - -import seedu.address.commons.core.index.Index; -import seedu.address.commons.util.StringUtil; -import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; - -/** - * Contains utility methods used for parsing strings in the various *Parser classes. - */ -public class ParserUtil { - - public static final String MESSAGE_INVALID_INDEX = "Index is not a non-zero unsigned integer."; - - /** - * Parses {@code oneBasedIndex} into an {@code Index} and returns it. Leading and trailing whitespaces will be - * trimmed. - * @throws ParseException if the specified index is invalid (not non-zero unsigned integer). - */ - public static Index parseIndex(String oneBasedIndex) throws ParseException { - String trimmedIndex = oneBasedIndex.trim(); - if (!StringUtil.isNonZeroUnsignedInteger(trimmedIndex)) { - throw new ParseException(MESSAGE_INVALID_INDEX); - } - return Index.fromOneBased(Integer.parseInt(trimmedIndex)); - } - - /** - * Parses a {@code String name} into a {@code Name}. - * Leading and trailing whitespaces will be trimmed. - * - * @throws ParseException if the given {@code name} is invalid. - */ - public static Name parseName(String name) throws ParseException { - requireNonNull(name); - String trimmedName = name.trim(); - if (!Name.isValidName(trimmedName)) { - throw new ParseException(Name.MESSAGE_CONSTRAINTS); - } - return new Name(trimmedName); - } - - /** - * Parses a {@code String phone} into a {@code Phone}. - * Leading and trailing whitespaces will be trimmed. - * - * @throws ParseException if the given {@code phone} is invalid. - */ - public static Phone parsePhone(String phone) throws ParseException { - requireNonNull(phone); - String trimmedPhone = phone.trim(); - if (!Phone.isValidPhone(trimmedPhone)) { - throw new ParseException(Phone.MESSAGE_CONSTRAINTS); - } - return new Phone(trimmedPhone); - } - - /** - * Parses a {@code String address} into an {@code Address}. - * Leading and trailing whitespaces will be trimmed. - * - * @throws ParseException if the given {@code address} is invalid. - */ - public static Address parseAddress(String address) throws ParseException { - requireNonNull(address); - String trimmedAddress = address.trim(); - if (!Address.isValidAddress(trimmedAddress)) { - throw new ParseException(Address.MESSAGE_CONSTRAINTS); - } - return new Address(trimmedAddress); - } - - /** - * Parses a {@code String email} into an {@code Email}. - * Leading and trailing whitespaces will be trimmed. - * - * @throws ParseException if the given {@code email} is invalid. - */ - public static Email parseEmail(String email) throws ParseException { - requireNonNull(email); - String trimmedEmail = email.trim(); - if (!Email.isValidEmail(trimmedEmail)) { - throw new ParseException(Email.MESSAGE_CONSTRAINTS); - } - return new Email(trimmedEmail); - } - - /** - * Parses a {@code String tag} into a {@code Tag}. - * Leading and trailing whitespaces will be trimmed. - * - * @throws ParseException if the given {@code tag} is invalid. - */ - public static Tag parseTag(String tag) throws ParseException { - requireNonNull(tag); - String trimmedTag = tag.trim(); - if (!Tag.isValidTagName(trimmedTag)) { - throw new ParseException(Tag.MESSAGE_CONSTRAINTS); - } - return new Tag(trimmedTag); - } - - /** - * Parses {@code Collection tags} into a {@code Set}. - */ - public static Set parseTags(Collection tags) throws ParseException { - requireNonNull(tags); - final Set tagSet = new HashSet<>(); - for (String tagName : tags) { - tagSet.add(parseTag(tagName)); - } - return tagSet; - } -} diff --git a/src/main/java/seedu/address/model/AddressBook.java b/src/main/java/seedu/address/model/AddressBook.java deleted file mode 100644 index 1a943a0781a..00000000000 --- a/src/main/java/seedu/address/model/AddressBook.java +++ /dev/null @@ -1,120 +0,0 @@ -package seedu.address.model; - -import static java.util.Objects.requireNonNull; - -import java.util.List; - -import javafx.collections.ObservableList; -import seedu.address.model.person.Person; -import seedu.address.model.person.UniquePersonList; - -/** - * Wraps all data at the address-book level - * Duplicates are not allowed (by .isSamePerson comparison) - */ -public class AddressBook implements ReadOnlyAddressBook { - - private final UniquePersonList persons; - - /* - * The 'unusual' code block below is a 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. - */ - { - persons = new UniquePersonList(); - } - - public AddressBook() {} - - /** - * Creates an AddressBook using the Persons in the {@code toBeCopied} - */ - public AddressBook(ReadOnlyAddressBook toBeCopied) { - this(); - resetData(toBeCopied); - } - - //// list overwrite operations - - /** - * Replaces the contents of the person list with {@code persons}. - * {@code persons} must not contain duplicate persons. - */ - public void setPersons(List persons) { - this.persons.setPersons(persons); - } - - /** - * Resets the existing data of this {@code AddressBook} with {@code newData}. - */ - public void resetData(ReadOnlyAddressBook newData) { - requireNonNull(newData); - - setPersons(newData.getPersonList()); - } - - //// person-level operations - - /** - * Returns true if a person with the same identity as {@code person} exists in the address book. - */ - public boolean hasPerson(Person person) { - requireNonNull(person); - return persons.contains(person); - } - - /** - * Adds a person to the address book. - * The person must not already exist in the address book. - */ - public void addPerson(Person p) { - persons.add(p); - } - - /** - * Replaces the given person {@code target} in the list with {@code editedPerson}. - * {@code target} must exist in the address book. - * The person identity of {@code editedPerson} must not be the same as another existing person in the address book. - */ - public void setPerson(Person target, Person editedPerson) { - requireNonNull(editedPerson); - - persons.setPerson(target, editedPerson); - } - - /** - * Removes {@code key} from this {@code AddressBook}. - * {@code key} must exist in the address book. - */ - public void removePerson(Person key) { - persons.remove(key); - } - - //// util methods - - @Override - public String toString() { - return persons.asUnmodifiableObservableList().size() + " persons"; - // TODO: refine later - } - - @Override - public ObservableList getPersonList() { - return persons.asUnmodifiableObservableList(); - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof AddressBook // instanceof handles nulls - && persons.equals(((AddressBook) other).persons)); - } - - @Override - public int hashCode() { - return persons.hashCode(); - } -} diff --git a/src/main/java/seedu/address/model/Model.java b/src/main/java/seedu/address/model/Model.java deleted file mode 100644 index d54df471c1f..00000000000 --- a/src/main/java/seedu/address/model/Model.java +++ /dev/null @@ -1,87 +0,0 @@ -package seedu.address.model; - -import java.nio.file.Path; -import java.util.function.Predicate; - -import javafx.collections.ObservableList; -import seedu.address.commons.core.GuiSettings; -import seedu.address.model.person.Person; - -/** - * The API of the Model component. - */ -public interface Model { - /** {@code Predicate} that always evaluate to true */ - Predicate PREDICATE_SHOW_ALL_PERSONS = unused -> true; - - /** - * Replaces user prefs data with the data in {@code userPrefs}. - */ - void setUserPrefs(ReadOnlyUserPrefs userPrefs); - - /** - * Returns the user prefs. - */ - ReadOnlyUserPrefs getUserPrefs(); - - /** - * Returns the user prefs' GUI settings. - */ - GuiSettings getGuiSettings(); - - /** - * Sets the user prefs' GUI settings. - */ - void setGuiSettings(GuiSettings guiSettings); - - /** - * Returns the user prefs' address book file path. - */ - Path getAddressBookFilePath(); - - /** - * Sets the user prefs' address book file path. - */ - void setAddressBookFilePath(Path addressBookFilePath); - - /** - * Replaces address book data with the data in {@code addressBook}. - */ - void setAddressBook(ReadOnlyAddressBook addressBook); - - /** Returns the AddressBook */ - ReadOnlyAddressBook getAddressBook(); - - /** - * Returns true if a person with the same identity as {@code person} exists in the address book. - */ - boolean hasPerson(Person person); - - /** - * Deletes the given person. - * The person must exist in the address book. - */ - void deletePerson(Person target); - - /** - * Adds the given person. - * {@code person} must not already exist in the address book. - */ - void addPerson(Person person); - - /** - * Replaces the given person {@code target} with {@code editedPerson}. - * {@code target} must exist in the address book. - * The person identity of {@code editedPerson} must not be the same as another existing person in the address book. - */ - void setPerson(Person target, Person editedPerson); - - /** Returns an unmodifiable view of the filtered person list */ - ObservableList getFilteredPersonList(); - - /** - * Updates the filter of the filtered person list to filter by the given {@code predicate}. - * @throws NullPointerException if {@code predicate} is null. - */ - void updateFilteredPersonList(Predicate predicate); -} diff --git a/src/main/java/seedu/address/model/ModelManager.java b/src/main/java/seedu/address/model/ModelManager.java deleted file mode 100644 index 0650c954f5c..00000000000 --- a/src/main/java/seedu/address/model/ModelManager.java +++ /dev/null @@ -1,151 +0,0 @@ -package seedu.address.model; - -import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; - -import java.nio.file.Path; -import java.util.function.Predicate; -import java.util.logging.Logger; - -import javafx.collections.ObservableList; -import javafx.collections.transformation.FilteredList; -import seedu.address.commons.core.GuiSettings; -import seedu.address.commons.core.LogsCenter; -import seedu.address.model.person.Person; - -/** - * Represents the in-memory model of the address book data. - */ -public class ModelManager implements Model { - private static final Logger logger = LogsCenter.getLogger(ModelManager.class); - - private final AddressBook addressBook; - private final UserPrefs userPrefs; - private final FilteredList filteredPersons; - - /** - * Initializes a ModelManager with the given addressBook and userPrefs. - */ - public ModelManager(ReadOnlyAddressBook addressBook, ReadOnlyUserPrefs userPrefs) { - super(); - requireAllNonNull(addressBook, userPrefs); - - logger.fine("Initializing with address book: " + addressBook + " and user prefs " + userPrefs); - - this.addressBook = new AddressBook(addressBook); - this.userPrefs = new UserPrefs(userPrefs); - filteredPersons = new FilteredList<>(this.addressBook.getPersonList()); - } - - public ModelManager() { - this(new AddressBook(), new UserPrefs()); - } - - //=========== UserPrefs ================================================================================== - - @Override - public void setUserPrefs(ReadOnlyUserPrefs userPrefs) { - requireNonNull(userPrefs); - this.userPrefs.resetData(userPrefs); - } - - @Override - public ReadOnlyUserPrefs getUserPrefs() { - return userPrefs; - } - - @Override - public GuiSettings getGuiSettings() { - return userPrefs.getGuiSettings(); - } - - @Override - public void setGuiSettings(GuiSettings guiSettings) { - requireNonNull(guiSettings); - userPrefs.setGuiSettings(guiSettings); - } - - @Override - public Path getAddressBookFilePath() { - return userPrefs.getAddressBookFilePath(); - } - - @Override - public void setAddressBookFilePath(Path addressBookFilePath) { - requireNonNull(addressBookFilePath); - userPrefs.setAddressBookFilePath(addressBookFilePath); - } - - //=========== AddressBook ================================================================================ - - @Override - public void setAddressBook(ReadOnlyAddressBook addressBook) { - this.addressBook.resetData(addressBook); - } - - @Override - public ReadOnlyAddressBook getAddressBook() { - return addressBook; - } - - @Override - public boolean hasPerson(Person person) { - requireNonNull(person); - return addressBook.hasPerson(person); - } - - @Override - public void deletePerson(Person target) { - addressBook.removePerson(target); - } - - @Override - public void addPerson(Person person) { - addressBook.addPerson(person); - updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); - } - - @Override - public void setPerson(Person target, Person editedPerson) { - requireAllNonNull(target, editedPerson); - - addressBook.setPerson(target, editedPerson); - } - - //=========== Filtered Person List Accessors ============================================================= - - /** - * Returns an unmodifiable view of the list of {@code Person} backed by the internal list of - * {@code versionedAddressBook} - */ - @Override - public ObservableList getFilteredPersonList() { - return filteredPersons; - } - - @Override - public void updateFilteredPersonList(Predicate predicate) { - requireNonNull(predicate); - filteredPersons.setPredicate(predicate); - } - - @Override - public boolean equals(Object obj) { - // short circuit if same object - if (obj == this) { - return true; - } - - // instanceof handles nulls - if (!(obj instanceof ModelManager)) { - return false; - } - - // state check - ModelManager other = (ModelManager) obj; - return addressBook.equals(other.addressBook) - && userPrefs.equals(other.userPrefs) - && filteredPersons.equals(other.filteredPersons); - } - -} diff --git a/src/main/java/seedu/address/model/ReadOnlyAddressBook.java b/src/main/java/seedu/address/model/ReadOnlyAddressBook.java deleted file mode 100644 index 6ddc2cd9a29..00000000000 --- a/src/main/java/seedu/address/model/ReadOnlyAddressBook.java +++ /dev/null @@ -1,17 +0,0 @@ -package seedu.address.model; - -import javafx.collections.ObservableList; -import seedu.address.model.person.Person; - -/** - * Unmodifiable view of an address book - */ -public interface ReadOnlyAddressBook { - - /** - * Returns an unmodifiable view of the persons list. - * This list will not contain any duplicate persons. - */ - ObservableList getPersonList(); - -} diff --git a/src/main/java/seedu/address/model/person/Address.java b/src/main/java/seedu/address/model/person/Address.java deleted file mode 100644 index 60472ca22a0..00000000000 --- a/src/main/java/seedu/address/model/person/Address.java +++ /dev/null @@ -1,57 +0,0 @@ -package seedu.address.model.person; - -import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.AppUtil.checkArgument; - -/** - * Represents a Person's address in the address book. - * Guarantees: immutable; is valid as declared in {@link #isValidAddress(String)} - */ -public class Address { - - public static final String MESSAGE_CONSTRAINTS = "Addresses can take any values, and it should not be blank"; - - /* - * The first character of the address must not be a whitespace, - * otherwise " " (a blank string) becomes a valid input. - */ - public static final String VALIDATION_REGEX = "[^\\s].*"; - - public final String value; - - /** - * Constructs an {@code Address}. - * - * @param address A valid address. - */ - public Address(String address) { - requireNonNull(address); - checkArgument(isValidAddress(address), MESSAGE_CONSTRAINTS); - value = address; - } - - /** - * Returns true if a given string is a valid email. - */ - public static boolean isValidAddress(String test) { - return test.matches(VALIDATION_REGEX); - } - - @Override - public String toString() { - return value; - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof Address // instanceof handles nulls - && value.equals(((Address) other).value)); // state check - } - - @Override - public int hashCode() { - return value.hashCode(); - } - -} diff --git a/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java deleted file mode 100644 index c9b5868427c..00000000000 --- a/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java +++ /dev/null @@ -1,31 +0,0 @@ -package seedu.address.model.person; - -import java.util.List; -import java.util.function.Predicate; - -import seedu.address.commons.util.StringUtil; - -/** - * Tests that a {@code Person}'s {@code Name} matches any of the keywords given. - */ -public class NameContainsKeywordsPredicate implements Predicate { - private final List keywords; - - public NameContainsKeywordsPredicate(List keywords) { - this.keywords = keywords; - } - - @Override - public boolean test(Person person) { - return keywords.stream() - .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(person.getName().fullName, keyword)); - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof NameContainsKeywordsPredicate // instanceof handles nulls - && keywords.equals(((NameContainsKeywordsPredicate) other).keywords)); // state check - } - -} diff --git a/src/main/java/seedu/address/model/person/Person.java b/src/main/java/seedu/address/model/person/Person.java deleted file mode 100644 index 557a7a60cd5..00000000000 --- a/src/main/java/seedu/address/model/person/Person.java +++ /dev/null @@ -1,120 +0,0 @@ -package seedu.address.model.person; - -import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; - -import java.util.Collections; -import java.util.HashSet; -import java.util.Objects; -import java.util.Set; - -import seedu.address.model.tag.Tag; - -/** - * Represents a Person in the address book. - * Guarantees: details are present and not null, field values are validated, immutable. - */ -public class Person { - - // Identity fields - private final Name name; - private final Phone phone; - private final Email email; - - // Data fields - private final Address address; - private final Set tags = new HashSet<>(); - - /** - * Every field must be present and not null. - */ - public Person(Name name, Phone phone, Email email, Address address, Set tags) { - requireAllNonNull(name, phone, email, address, tags); - this.name = name; - this.phone = phone; - this.email = email; - this.address = address; - this.tags.addAll(tags); - } - - public Name getName() { - return name; - } - - public Phone getPhone() { - return phone; - } - - public Email getEmail() { - return email; - } - - public Address getAddress() { - return address; - } - - /** - * Returns an immutable tag set, which throws {@code UnsupportedOperationException} - * if modification is attempted. - */ - public Set getTags() { - return Collections.unmodifiableSet(tags); - } - - /** - * Returns true if both persons of the same name have at least one other identity field that is the same. - * This defines a weaker notion of equality between two persons. - */ - public boolean isSamePerson(Person otherPerson) { - if (otherPerson == this) { - return true; - } - - return otherPerson != null - && otherPerson.getName().equals(getName()) - && (otherPerson.getPhone().equals(getPhone()) || otherPerson.getEmail().equals(getEmail())); - } - - /** - * Returns true if both persons have the same identity and data fields. - * This defines a stronger notion of equality between two persons. - */ - @Override - public boolean equals(Object other) { - if (other == this) { - return true; - } - - if (!(other instanceof Person)) { - return false; - } - - Person otherPerson = (Person) other; - return otherPerson.getName().equals(getName()) - && otherPerson.getPhone().equals(getPhone()) - && otherPerson.getEmail().equals(getEmail()) - && otherPerson.getAddress().equals(getAddress()) - && otherPerson.getTags().equals(getTags()); - } - - @Override - public int hashCode() { - // use this method for custom fields hashing instead of implementing your own - return Objects.hash(name, phone, email, address, tags); - } - - @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); - return builder.toString(); - } - -} diff --git a/src/main/java/seedu/address/model/person/UniquePersonList.java b/src/main/java/seedu/address/model/person/UniquePersonList.java deleted file mode 100644 index 0fee4fe57e6..00000000000 --- a/src/main/java/seedu/address/model/person/UniquePersonList.java +++ /dev/null @@ -1,137 +0,0 @@ -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.model.person.exceptions.DuplicatePersonException; -import seedu.address.model.person.exceptions.PersonNotFoundException; - -/** - * A list of persons that enforces uniqueness between its elements and does not allow nulls. - * A person is considered unique by comparing using {@code Person#isSamePerson(Person)}. As such, adding and updating of - * persons uses Person#isSamePerson(Person) for equality so as to ensure that the person being added or updated is - * unique in terms of identity in the UniquePersonList. However, the removal of a person uses Person#equals(Object) so - * as to ensure that the person with exactly the same fields will be removed. - * - * Supports a minimal set of list operations. - * - * @see Person#isSamePerson(Person) - */ -public class UniquePersonList implements Iterable { - - private final ObservableList internalList = FXCollections.observableArrayList(); - private final ObservableList internalUnmodifiableList = - FXCollections.unmodifiableObservableList(internalList); - - /** - * Returns true if the list contains an equivalent person as the given argument. - */ - public boolean contains(Person toCheck) { - requireNonNull(toCheck); - return internalList.stream().anyMatch(toCheck::isSamePerson); - } - - /** - * Adds a person to the list. - * The person must not already exist in the list. - */ - public void add(Person toAdd) { - requireNonNull(toAdd); - if (contains(toAdd)) { - throw new DuplicatePersonException(); - } - internalList.add(toAdd); - } - - /** - * Replaces the person {@code target} in the list with {@code editedPerson}. - * {@code target} must exist in the list. - * The person identity of {@code editedPerson} must not be the same as another existing person in the list. - */ - public void setPerson(Person target, Person editedPerson) { - requireAllNonNull(target, editedPerson); - - int index = internalList.indexOf(target); - if (index == -1) { - throw new PersonNotFoundException(); - } - - if (!target.isSamePerson(editedPerson) && contains(editedPerson)) { - throw new DuplicatePersonException(); - } - - internalList.set(index, editedPerson); - } - - /** - * Removes the equivalent person from the list. - * The person must exist in the list. - */ - public void remove(Person toRemove) { - requireNonNull(toRemove); - if (!internalList.remove(toRemove)) { - throw new PersonNotFoundException(); - } - } - - public void setPersons(UniquePersonList replacement) { - requireNonNull(replacement); - internalList.setAll(replacement.internalList); - } - - /** - * Replaces the contents of this list with {@code persons}. - * {@code persons} must not contain duplicate persons. - */ - public void setPersons(List persons) { - requireAllNonNull(persons); - if (!personsAreUnique(persons)) { - throw new DuplicatePersonException(); - } - - internalList.setAll(persons); - } - - /** - * Returns the backing list as an unmodifiable {@code ObservableList}. - */ - public ObservableList asUnmodifiableObservableList() { - return internalUnmodifiableList; - } - - @Override - public Iterator iterator() { - return internalList.iterator(); - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof UniquePersonList // instanceof handles nulls - && internalList.equals(((UniquePersonList) other).internalList)); - } - - @Override - public int hashCode() { - return internalList.hashCode(); - } - - /** - * Returns true if {@code persons} contains only unique persons. - */ - private boolean personsAreUnique(List persons) { - for (int i = 0; i < persons.size() - 1; i++) { - for (int j = i + 1; j < persons.size(); j++) { - if (persons.get(i).isSamePerson(persons.get(j))) { - return false; - } - } - } - return true; - } -} diff --git a/src/main/java/seedu/address/model/person/exceptions/DuplicatePersonException.java b/src/main/java/seedu/address/model/person/exceptions/DuplicatePersonException.java deleted file mode 100644 index d7290f59442..00000000000 --- a/src/main/java/seedu/address/model/person/exceptions/DuplicatePersonException.java +++ /dev/null @@ -1,11 +0,0 @@ -package seedu.address.model.person.exceptions; - -/** - * Signals that the operation will result in duplicate Persons (Persons are considered duplicates if they have the same - * identity). - */ -public class DuplicatePersonException extends RuntimeException { - public DuplicatePersonException() { - super("Operation would result in duplicate persons"); - } -} diff --git a/src/main/java/seedu/address/model/person/exceptions/PersonNotFoundException.java b/src/main/java/seedu/address/model/person/exceptions/PersonNotFoundException.java deleted file mode 100644 index fa764426ca7..00000000000 --- a/src/main/java/seedu/address/model/person/exceptions/PersonNotFoundException.java +++ /dev/null @@ -1,6 +0,0 @@ -package seedu.address.model.person.exceptions; - -/** - * Signals that the operation is unable to find the specified person. - */ -public class PersonNotFoundException extends RuntimeException {} diff --git a/src/main/java/seedu/address/model/util/SampleDataUtil.java b/src/main/java/seedu/address/model/util/SampleDataUtil.java deleted file mode 100644 index 1806da4facf..00000000000 --- a/src/main/java/seedu/address/model/util/SampleDataUtil.java +++ /dev/null @@ -1,60 +0,0 @@ -package seedu.address.model.util; - -import java.util.Arrays; -import java.util.Set; -import java.util.stream.Collectors; - -import seedu.address.model.AddressBook; -import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Person; -import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; - -/** - * 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"), - new Address("Blk 30 Geylang Street 29, #06-40"), - getTagSet("friends")), - new Person(new Name("Bernice Yu"), new Phone("99272758"), new Email("berniceyu@example.com"), - new Address("Blk 30 Lorong 3 Serangoon Gardens, #07-18"), - getTagSet("colleagues", "friends")), - new Person(new Name("Charlotte Oliveiro"), new Phone("93210283"), new Email("charlotte@example.com"), - new Address("Blk 11 Ang Mo Kio Street 74, #11-04"), - getTagSet("neighbours")), - new Person(new Name("David Li"), new Phone("91031282"), new Email("lidavid@example.com"), - new Address("Blk 436 Serangoon Gardens Street 26, #16-43"), - getTagSet("family")), - new Person(new Name("Irfan Ibrahim"), new Phone("92492021"), new Email("irfan@example.com"), - new Address("Blk 47 Tampines Street 20, #17-35"), - getTagSet("classmates")), - new Person(new Name("Roy Balakrishnan"), new Phone("92624417"), new Email("royb@example.com"), - new Address("Blk 45 Aljunied Street 85, #11-31"), - getTagSet("colleagues")) - }; - } - - public static ReadOnlyAddressBook getSampleAddressBook() { - AddressBook sampleAb = new AddressBook(); - for (Person samplePerson : getSamplePersons()) { - sampleAb.addPerson(samplePerson); - } - return sampleAb; - } - - /** - * Returns a tag set containing the list of strings given. - */ - public static Set getTagSet(String... strings) { - return Arrays.stream(strings) - .map(Tag::new) - .collect(Collectors.toSet()); - } - -} diff --git a/src/main/java/seedu/address/storage/AddressBookStorage.java b/src/main/java/seedu/address/storage/AddressBookStorage.java deleted file mode 100644 index 4599182b3f9..00000000000 --- a/src/main/java/seedu/address/storage/AddressBookStorage.java +++ /dev/null @@ -1,45 +0,0 @@ -package seedu.address.storage; - -import java.io.IOException; -import java.nio.file.Path; -import java.util.Optional; - -import seedu.address.commons.exceptions.DataConversionException; -import seedu.address.model.ReadOnlyAddressBook; - -/** - * Represents a storage for {@link seedu.address.model.AddressBook}. - */ -public interface AddressBookStorage { - - /** - * Returns the file path of the data file. - */ - Path getAddressBookFilePath(); - - /** - * Returns AddressBook data as a {@link ReadOnlyAddressBook}. - * Returns {@code Optional.empty()} if storage file is not found. - * @throws DataConversionException if the data in storage is not in the expected format. - * @throws IOException if there was any problem when reading from the storage. - */ - Optional readAddressBook() throws DataConversionException, IOException; - - /** - * @see #getAddressBookFilePath() - */ - Optional readAddressBook(Path filePath) throws DataConversionException, IOException; - - /** - * Saves the given {@link ReadOnlyAddressBook} to the storage. - * @param addressBook cannot be null. - * @throws IOException if there was any problem writing to the file. - */ - void saveAddressBook(ReadOnlyAddressBook addressBook) throws IOException; - - /** - * @see #saveAddressBook(ReadOnlyAddressBook) - */ - void saveAddressBook(ReadOnlyAddressBook addressBook, Path filePath) throws IOException; - -} diff --git a/src/main/java/seedu/address/storage/JsonAdaptedPerson.java b/src/main/java/seedu/address/storage/JsonAdaptedPerson.java deleted file mode 100644 index a6321cec2ea..00000000000 --- a/src/main/java/seedu/address/storage/JsonAdaptedPerson.java +++ /dev/null @@ -1,109 +0,0 @@ -package seedu.address.storage; - -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import java.util.stream.Collectors; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; - -import seedu.address.commons.exceptions.IllegalValueException; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Person; -import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; - -/** - * Jackson-friendly version of {@link Person}. - */ -class JsonAdaptedPerson { - - public static final String MISSING_FIELD_MESSAGE_FORMAT = "Person's %s field is missing!"; - - private final String name; - private final String phone; - private final String email; - private final String address; - private final List tagged = new ArrayList<>(); - - /** - * Constructs a {@code JsonAdaptedPerson} with the given person details. - */ - @JsonCreator - public JsonAdaptedPerson(@JsonProperty("name") String name, @JsonProperty("phone") String phone, - @JsonProperty("email") String email, @JsonProperty("address") String address, - @JsonProperty("tagged") List tagged) { - this.name = name; - this.phone = phone; - this.email = email; - this.address = address; - if (tagged != null) { - this.tagged.addAll(tagged); - } - } - - /** - * Converts a given {@code Person} into this class for Jackson use. - */ - public JsonAdaptedPerson(Person source) { - name = source.getName().fullName; - phone = source.getPhone().value; - email = source.getEmail().value; - address = source.getAddress().value; - tagged.addAll(source.getTags().stream() - .map(JsonAdaptedTag::new) - .collect(Collectors.toList())); - } - - /** - * Converts this Jackson-friendly adapted person object into the model's {@code Person} object. - * - * @throws IllegalValueException if there were any data constraints violated in the adapted person. - */ - public Person toModelType() throws IllegalValueException { - final List personTags = new ArrayList<>(); - for (JsonAdaptedTag tag : tagged) { - personTags.add(tag.toModelType()); - } - - if (name == null) { - throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Name.class.getSimpleName())); - } - if (!Name.isValidName(name)) { - throw new IllegalValueException(Name.MESSAGE_CONSTRAINTS); - } - final Name modelName = new Name(name); - - if (phone == null) { - throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Phone.class.getSimpleName())); - } - if (!Phone.isValidPhone(phone)) { - throw new IllegalValueException(Phone.MESSAGE_CONSTRAINTS); - } - final Phone modelPhone = new Phone(phone); - - if (email == null) { - throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Email.class.getSimpleName())); - } - if (!Email.isValidEmail(email)) { - throw new IllegalValueException(Email.MESSAGE_CONSTRAINTS); - } - final Email modelEmail = new Email(email); - - if (address == null) { - throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Address.class.getSimpleName())); - } - if (!Address.isValidAddress(address)) { - throw new IllegalValueException(Address.MESSAGE_CONSTRAINTS); - } - final Address modelAddress = new Address(address); - - final Set modelTags = new HashSet<>(personTags); - return new Person(modelName, modelPhone, modelEmail, modelAddress, modelTags); - } - -} diff --git a/src/main/java/seedu/address/storage/JsonAddressBookStorage.java b/src/main/java/seedu/address/storage/JsonAddressBookStorage.java deleted file mode 100644 index dfab9daaa0d..00000000000 --- a/src/main/java/seedu/address/storage/JsonAddressBookStorage.java +++ /dev/null @@ -1,80 +0,0 @@ -package seedu.address.storage; - -import static java.util.Objects.requireNonNull; - -import java.io.IOException; -import java.nio.file.Path; -import java.util.Optional; -import java.util.logging.Logger; - -import seedu.address.commons.core.LogsCenter; -import seedu.address.commons.exceptions.DataConversionException; -import seedu.address.commons.exceptions.IllegalValueException; -import seedu.address.commons.util.FileUtil; -import seedu.address.commons.util.JsonUtil; -import seedu.address.model.ReadOnlyAddressBook; - -/** - * A class to access AddressBook data stored as a json file on the hard disk. - */ -public class JsonAddressBookStorage implements AddressBookStorage { - - private static final Logger logger = LogsCenter.getLogger(JsonAddressBookStorage.class); - - private Path filePath; - - public JsonAddressBookStorage(Path filePath) { - this.filePath = filePath; - } - - public Path getAddressBookFilePath() { - return filePath; - } - - @Override - public Optional readAddressBook() throws DataConversionException { - return readAddressBook(filePath); - } - - /** - * Similar to {@link #readAddressBook()}. - * - * @param filePath location of the data. Cannot be null. - * @throws DataConversionException if the file is not in the correct format. - */ - public Optional readAddressBook(Path filePath) throws DataConversionException { - requireNonNull(filePath); - - Optional jsonAddressBook = JsonUtil.readJsonFile( - filePath, JsonSerializableAddressBook.class); - if (!jsonAddressBook.isPresent()) { - return Optional.empty(); - } - - try { - return Optional.of(jsonAddressBook.get().toModelType()); - } catch (IllegalValueException ive) { - logger.info("Illegal values found in " + filePath + ": " + ive.getMessage()); - throw new DataConversionException(ive); - } - } - - @Override - public void saveAddressBook(ReadOnlyAddressBook addressBook) throws IOException { - saveAddressBook(addressBook, filePath); - } - - /** - * Similar to {@link #saveAddressBook(ReadOnlyAddressBook)}. - * - * @param filePath location of the data. Cannot be null. - */ - public void saveAddressBook(ReadOnlyAddressBook addressBook, Path filePath) throws IOException { - requireNonNull(addressBook); - requireNonNull(filePath); - - FileUtil.createIfMissing(filePath); - JsonUtil.saveJsonFile(new JsonSerializableAddressBook(addressBook), filePath); - } - -} diff --git a/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java b/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java deleted file mode 100644 index 5efd834091d..00000000000 --- a/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java +++ /dev/null @@ -1,60 +0,0 @@ -package seedu.address.storage; - -import java.util.ArrayList; -import java.util.List; -import java.util.stream.Collectors; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonRootName; - -import seedu.address.commons.exceptions.IllegalValueException; -import seedu.address.model.AddressBook; -import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.person.Person; - -/** - * An Immutable AddressBook that is serializable to JSON format. - */ -@JsonRootName(value = "addressbook") -class JsonSerializableAddressBook { - - public static final String MESSAGE_DUPLICATE_PERSON = "Persons list contains duplicate person(s)."; - - private final List persons = new ArrayList<>(); - - /** - * Constructs a {@code JsonSerializableAddressBook} with the given persons. - */ - @JsonCreator - public JsonSerializableAddressBook(@JsonProperty("persons") List persons) { - this.persons.addAll(persons); - } - - /** - * Converts a given {@code ReadOnlyAddressBook} into this class for Jackson use. - * - * @param source future changes to this will not affect the created {@code JsonSerializableAddressBook}. - */ - public JsonSerializableAddressBook(ReadOnlyAddressBook source) { - persons.addAll(source.getPersonList().stream().map(JsonAdaptedPerson::new).collect(Collectors.toList())); - } - - /** - * Converts this address book into the model's {@code AddressBook} object. - * - * @throws IllegalValueException if there were any data constraints violated. - */ - public AddressBook toModelType() throws IllegalValueException { - AddressBook addressBook = new AddressBook(); - for (JsonAdaptedPerson jsonAdaptedPerson : persons) { - Person person = jsonAdaptedPerson.toModelType(); - if (addressBook.hasPerson(person)) { - throw new IllegalValueException(MESSAGE_DUPLICATE_PERSON); - } - addressBook.addPerson(person); - } - return addressBook; - } - -} diff --git a/src/main/java/seedu/address/storage/Storage.java b/src/main/java/seedu/address/storage/Storage.java deleted file mode 100644 index beda8bd9f11..00000000000 --- a/src/main/java/seedu/address/storage/Storage.java +++ /dev/null @@ -1,32 +0,0 @@ -package seedu.address.storage; - -import java.io.IOException; -import java.nio.file.Path; -import java.util.Optional; - -import seedu.address.commons.exceptions.DataConversionException; -import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.ReadOnlyUserPrefs; -import seedu.address.model.UserPrefs; - -/** - * API of the Storage component - */ -public interface Storage extends AddressBookStorage, UserPrefsStorage { - - @Override - Optional readUserPrefs() throws DataConversionException, IOException; - - @Override - void saveUserPrefs(ReadOnlyUserPrefs userPrefs) throws IOException; - - @Override - Path getAddressBookFilePath(); - - @Override - Optional readAddressBook() throws DataConversionException, IOException; - - @Override - void saveAddressBook(ReadOnlyAddressBook addressBook) throws IOException; - -} diff --git a/src/main/java/seedu/address/storage/StorageManager.java b/src/main/java/seedu/address/storage/StorageManager.java deleted file mode 100644 index e4f452b6cbf..00000000000 --- a/src/main/java/seedu/address/storage/StorageManager.java +++ /dev/null @@ -1,77 +0,0 @@ -package seedu.address.storage; - -import java.io.IOException; -import java.nio.file.Path; -import java.util.Optional; -import java.util.logging.Logger; - -import seedu.address.commons.core.LogsCenter; -import seedu.address.commons.exceptions.DataConversionException; -import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.ReadOnlyUserPrefs; -import seedu.address.model.UserPrefs; - -/** - * Manages storage of AddressBook data in local storage. - */ -public class StorageManager implements Storage { - - private static final Logger logger = LogsCenter.getLogger(StorageManager.class); - private AddressBookStorage addressBookStorage; - private UserPrefsStorage userPrefsStorage; - - - public StorageManager(AddressBookStorage addressBookStorage, UserPrefsStorage userPrefsStorage) { - super(); - this.addressBookStorage = addressBookStorage; - this.userPrefsStorage = userPrefsStorage; - } - - // ================ UserPrefs methods ============================== - - @Override - public Path getUserPrefsFilePath() { - return userPrefsStorage.getUserPrefsFilePath(); - } - - @Override - public Optional readUserPrefs() throws DataConversionException, IOException { - return userPrefsStorage.readUserPrefs(); - } - - @Override - public void saveUserPrefs(ReadOnlyUserPrefs userPrefs) throws IOException { - userPrefsStorage.saveUserPrefs(userPrefs); - } - - - // ================ AddressBook methods ============================== - - @Override - public Path getAddressBookFilePath() { - return addressBookStorage.getAddressBookFilePath(); - } - - @Override - public Optional readAddressBook() throws DataConversionException, IOException { - return readAddressBook(addressBookStorage.getAddressBookFilePath()); - } - - @Override - public Optional readAddressBook(Path filePath) throws DataConversionException, IOException { - logger.fine("Attempting to read data from file: " + filePath); - return addressBookStorage.readAddressBook(filePath); - } - - @Override - public void saveAddressBook(ReadOnlyAddressBook addressBook) throws IOException { - saveAddressBook(addressBook, addressBookStorage.getAddressBookFilePath()); - } - - @Override - public void saveAddressBook(ReadOnlyAddressBook addressBook, Path filePath) throws IOException { - logger.fine("Attempting to write to data file: " + filePath); - addressBookStorage.saveAddressBook(addressBook, filePath); - } - -} diff --git a/src/main/java/seedu/address/ui/CommandBox.java b/src/main/java/seedu/address/ui/CommandBox.java deleted file mode 100644 index 7d76e691f52..00000000000 --- a/src/main/java/seedu/address/ui/CommandBox.java +++ /dev/null @@ -1,77 +0,0 @@ -package seedu.address.ui; - -import javafx.collections.ObservableList; -import javafx.fxml.FXML; -import javafx.scene.control.TextField; -import javafx.scene.layout.Region; -import seedu.address.logic.commands.CommandResult; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.logic.parser.exceptions.ParseException; - -/** - * The UI component that is responsible for receiving user command inputs. - */ -public class CommandBox extends UiPart { - - public static final String ERROR_STYLE_CLASS = "error"; - private static final String FXML = "CommandBox.fxml"; - - private final CommandExecutor commandExecutor; - - @FXML - private TextField commandTextField; - - public CommandBox(CommandExecutor commandExecutor) { - super(FXML); - this.commandExecutor = commandExecutor; - // calls #setStyleToDefault() whenever there is a change to the text of the command box. - commandTextField.textProperty().addListener((unused1, unused2, unused3) -> setStyleToDefault()); - } - - /** - * Handles the Enter button pressed event. - */ - @FXML - private void handleCommandEntered() { - try { - commandExecutor.execute(commandTextField.getText()); - commandTextField.setText(""); - } catch (CommandException | ParseException e) { - setStyleToIndicateCommandFailure(); - } - } - - /** - * Sets the command box style to use the default style. - */ - private void setStyleToDefault() { - commandTextField.getStyleClass().remove(ERROR_STYLE_CLASS); - } - - /** - * Sets the command box style to indicate a failed command. - */ - private void setStyleToIndicateCommandFailure() { - ObservableList styleClass = commandTextField.getStyleClass(); - - if (styleClass.contains(ERROR_STYLE_CLASS)) { - return; - } - - styleClass.add(ERROR_STYLE_CLASS); - } - - /** - * Represents a function that can execute commands. - */ - @FunctionalInterface - public interface CommandExecutor { - /** - * Executes the command and returns the result. - * - * @see seedu.address.logic.Logic#execute(String) - */ - CommandResult execute(String commandText) throws CommandException, ParseException; - } - -} diff --git a/src/main/java/seedu/address/ui/MainWindow.java b/src/main/java/seedu/address/ui/MainWindow.java deleted file mode 100644 index 90bbf11de97..00000000000 --- a/src/main/java/seedu/address/ui/MainWindow.java +++ /dev/null @@ -1,193 +0,0 @@ -package seedu.address.ui; - -import java.util.logging.Logger; - -import javafx.event.ActionEvent; -import javafx.fxml.FXML; -import javafx.scene.control.MenuItem; -import javafx.scene.control.TextInputControl; -import javafx.scene.input.KeyCombination; -import javafx.scene.input.KeyEvent; -import javafx.scene.layout.StackPane; -import javafx.stage.Stage; -import seedu.address.commons.core.GuiSettings; -import seedu.address.commons.core.LogsCenter; -import seedu.address.logic.Logic; -import seedu.address.logic.commands.CommandResult; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.logic.parser.exceptions.ParseException; - -/** - * The Main Window. Provides the basic application layout containing - * a menu bar and space where other JavaFX elements can be placed. - */ -public class MainWindow extends UiPart { - - private static final String FXML = "MainWindow.fxml"; - - private final Logger logger = LogsCenter.getLogger(getClass()); - - private Stage primaryStage; - private Logic logic; - - // Independent Ui parts residing in this Ui container - private PersonListPanel personListPanel; - private ResultDisplay resultDisplay; - private HelpWindow helpWindow; - - @FXML - private StackPane commandBoxPlaceholder; - - @FXML - private MenuItem helpMenuItem; - - @FXML - private StackPane personListPanelPlaceholder; - - @FXML - private StackPane resultDisplayPlaceholder; - - @FXML - private StackPane statusbarPlaceholder; - - public MainWindow(Stage primaryStage, Logic logic) { - super(FXML, primaryStage); - - // Set dependencies - this.primaryStage = primaryStage; - this.logic = logic; - - // Configure the UI - setWindowDefaultSize(logic.getGuiSettings()); - - setAccelerators(); - - helpWindow = new HelpWindow(); - } - - public Stage getPrimaryStage() { - return primaryStage; - } - - private void setAccelerators() { - setAccelerator(helpMenuItem, KeyCombination.valueOf("F1")); - } - - /** - * Sets the accelerator of a MenuItem. - * @param keyCombination the KeyCombination value of the accelerator - */ - private void setAccelerator(MenuItem menuItem, KeyCombination keyCombination) { - menuItem.setAccelerator(keyCombination); - - /* - * TODO: the code below can be removed once the bug reported here - * https://bugs.openjdk.java.net/browse/JDK-8131666 - * is fixed in later version of SDK. - * - * According to the bug report, TextInputControl (TextField, TextArea) will - * consume function-key events. Because CommandBox contains a TextField, and - * ResultDisplay contains a TextArea, thus some accelerators (e.g F1) will - * not work when the focus is in them because the key event is consumed by - * the TextInputControl(s). - * - * For now, we add following event filter to capture such key events and open - * help window purposely so to support accelerators even when focus is - * in CommandBox or ResultDisplay. - */ - getRoot().addEventFilter(KeyEvent.KEY_PRESSED, event -> { - if (event.getTarget() instanceof TextInputControl && keyCombination.match(event)) { - menuItem.getOnAction().handle(new ActionEvent()); - event.consume(); - } - }); - } - - /** - * Fills up all the placeholders of this window. - */ - void fillInnerParts() { - personListPanel = new PersonListPanel(logic.getFilteredPersonList()); - personListPanelPlaceholder.getChildren().add(personListPanel.getRoot()); - - resultDisplay = new ResultDisplay(); - resultDisplayPlaceholder.getChildren().add(resultDisplay.getRoot()); - - StatusBarFooter statusBarFooter = new StatusBarFooter(logic.getAddressBookFilePath()); - statusbarPlaceholder.getChildren().add(statusBarFooter.getRoot()); - - CommandBox commandBox = new CommandBox(this::executeCommand); - commandBoxPlaceholder.getChildren().add(commandBox.getRoot()); - } - - /** - * Sets the default size based on {@code guiSettings}. - */ - private void setWindowDefaultSize(GuiSettings guiSettings) { - primaryStage.setHeight(guiSettings.getWindowHeight()); - primaryStage.setWidth(guiSettings.getWindowWidth()); - if (guiSettings.getWindowCoordinates() != null) { - primaryStage.setX(guiSettings.getWindowCoordinates().getX()); - primaryStage.setY(guiSettings.getWindowCoordinates().getY()); - } - } - - /** - * Opens the help window or focuses on it if it's already opened. - */ - @FXML - public void handleHelp() { - if (!helpWindow.isShowing()) { - helpWindow.show(); - } else { - helpWindow.focus(); - } - } - - void show() { - primaryStage.show(); - } - - /** - * Closes the application. - */ - @FXML - private void handleExit() { - GuiSettings guiSettings = new GuiSettings(primaryStage.getWidth(), primaryStage.getHeight(), - (int) primaryStage.getX(), (int) primaryStage.getY()); - logic.setGuiSettings(guiSettings); - helpWindow.hide(); - primaryStage.hide(); - } - - public PersonListPanel getPersonListPanel() { - return personListPanel; - } - - /** - * Executes the command and returns the result. - * - * @see seedu.address.logic.Logic#execute(String) - */ - private CommandResult executeCommand(String commandText) throws CommandException, ParseException { - try { - CommandResult commandResult = logic.execute(commandText); - logger.info("Result: " + commandResult.getFeedbackToUser()); - resultDisplay.setFeedbackToUser(commandResult.getFeedbackToUser()); - - if (commandResult.isShowHelp()) { - handleHelp(); - } - - if (commandResult.isExit()) { - handleExit(); - } - - return commandResult; - } catch (CommandException | ParseException e) { - logger.info("Invalid command: " + commandText); - resultDisplay.setFeedbackToUser(e.getMessage()); - throw e; - } - } -} 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 1328917096e..00000000000 --- a/src/main/java/seedu/address/ui/PersonListPanel.java +++ /dev/null @@ -1,46 +0,0 @@ -package seedu.address.ui; - -import java.util.logging.Logger; - -import javafx.collections.ObservableList; -import javafx.fxml.FXML; -import javafx.scene.control.ListCell; -import javafx.scene.control.ListView; -import javafx.scene.layout.Region; -import seedu.address.commons.core.LogsCenter; -import seedu.address.model.person.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); - personListView.setItems(personList); - personListView.setCellFactory(listView -> new PersonListViewCell()); - } - - /** - * Custom {@code ListCell} that displays the graphics of a {@code Person} using a {@code PersonCard}. - */ - class PersonListViewCell extends ListCell { - @Override - protected void updateItem(Person person, boolean empty) { - super.updateItem(person, empty); - - if (empty || person == null) { - setGraphic(null); - setText(null); - } else { - setGraphic(new PersonCard(person, getIndex() + 1).getRoot()); - } - } - } - -} diff --git a/src/main/java/seedu/address/AppParameters.java b/src/main/java/tatracker/AppParameters.java similarity index 93% rename from src/main/java/seedu/address/AppParameters.java rename to src/main/java/tatracker/AppParameters.java index ab552c398f3..55bb401449d 100644 --- a/src/main/java/seedu/address/AppParameters.java +++ b/src/main/java/tatracker/AppParameters.java @@ -1,4 +1,4 @@ -package seedu.address; +package tatracker; import java.nio.file.Path; import java.nio.file.Paths; @@ -7,8 +7,8 @@ import java.util.logging.Logger; import javafx.application.Application; -import seedu.address.commons.core.LogsCenter; -import seedu.address.commons.util.FileUtil; +import tatracker.commons.core.LogsCenter; +import tatracker.commons.util.FileUtil; /** * Represents the parsed command-line parameters given to the application. diff --git a/src/main/java/seedu/address/Main.java b/src/main/java/tatracker/Main.java similarity index 97% rename from src/main/java/seedu/address/Main.java rename to src/main/java/tatracker/Main.java index 052a5068631..45a4f7a3402 100644 --- a/src/main/java/seedu/address/Main.java +++ b/src/main/java/tatracker/Main.java @@ -1,4 +1,4 @@ -package seedu.address; +package tatracker; import javafx.application.Application; diff --git a/src/main/java/seedu/address/MainApp.java b/src/main/java/tatracker/MainApp.java similarity index 68% rename from src/main/java/seedu/address/MainApp.java rename to src/main/java/tatracker/MainApp.java index e5cfb161b73..d2c14f51637 100644 --- a/src/main/java/seedu/address/MainApp.java +++ b/src/main/java/tatracker/MainApp.java @@ -1,4 +1,4 @@ -package seedu.address; +package tatracker; import java.io.IOException; import java.nio.file.Path; @@ -7,29 +7,30 @@ import javafx.application.Application; import javafx.stage.Stage; -import seedu.address.commons.core.Config; -import seedu.address.commons.core.LogsCenter; -import seedu.address.commons.core.Version; -import seedu.address.commons.exceptions.DataConversionException; -import seedu.address.commons.util.ConfigUtil; -import seedu.address.commons.util.StringUtil; -import seedu.address.logic.Logic; -import seedu.address.logic.LogicManager; -import seedu.address.model.AddressBook; -import seedu.address.model.Model; -import seedu.address.model.ModelManager; -import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.ReadOnlyUserPrefs; -import seedu.address.model.UserPrefs; -import seedu.address.model.util.SampleDataUtil; -import seedu.address.storage.AddressBookStorage; -import seedu.address.storage.JsonAddressBookStorage; -import seedu.address.storage.JsonUserPrefsStorage; -import seedu.address.storage.Storage; -import seedu.address.storage.StorageManager; -import seedu.address.storage.UserPrefsStorage; -import seedu.address.ui.Ui; -import seedu.address.ui.UiManager; +import tatracker.commons.core.Config; +import tatracker.commons.core.LogsCenter; +import tatracker.commons.core.Notification; +import tatracker.commons.core.Version; +import tatracker.commons.exceptions.DataConversionException; +import tatracker.commons.util.ConfigUtil; +import tatracker.commons.util.StringUtil; +import tatracker.logic.Logic; +import tatracker.logic.LogicManager; +import tatracker.model.Model; +import tatracker.model.ModelManager; +import tatracker.model.ReadOnlyTaTracker; +import tatracker.model.ReadOnlyUserPrefs; +import tatracker.model.TaTracker; +import tatracker.model.UserPrefs; +import tatracker.model.util.SampleDataUtil; +import tatracker.storage.JsonTaTrackerStorage; +import tatracker.storage.JsonUserPrefsStorage; +import tatracker.storage.Storage; +import tatracker.storage.StorageManager; +import tatracker.storage.TaTrackerStorage; +import tatracker.storage.UserPrefsStorage; +import tatracker.ui.Ui; +import tatracker.ui.UiManager; /** * Runs the application. @@ -48,7 +49,7 @@ public class MainApp extends Application { @Override public void init() throws Exception { - logger.info("=============================[ Initializing AddressBook ]==========================="); + logger.info("=============================[ Initializing TaTracker ]==========================="); super.init(); AppParameters appParameters = AppParameters.parse(getParameters()); @@ -56,8 +57,8 @@ public void init() throws Exception { UserPrefsStorage userPrefsStorage = new JsonUserPrefsStorage(config.getUserPrefsFilePath()); UserPrefs userPrefs = initPrefs(userPrefsStorage); - AddressBookStorage addressBookStorage = new JsonAddressBookStorage(userPrefs.getAddressBookFilePath()); - storage = new StorageManager(addressBookStorage, userPrefsStorage); + TaTrackerStorage taTrackerStorage = new JsonTaTrackerStorage(userPrefs.getTaTrackerFilePath()); + storage = new StorageManager(taTrackerStorage, userPrefsStorage); initLogging(config); @@ -69,25 +70,25 @@ public void init() throws Exception { } /** - * Returns a {@code ModelManager} with the data from {@code storage}'s address book and {@code userPrefs}.
- * The data from the sample address book will be used instead if {@code storage}'s address book is not found, - * or an empty address book will be used instead if errors occur when reading {@code storage}'s address book. + * Returns a {@code ModelManager} with the data from {@code storage}'s TA-Tracker and {@code userPrefs}.
+ * The data from the sample ta-tracker will be used instead if {@code storage}'s ta-tracker is not found, + * or an empty ta-tracker will be used instead if errors occur when reading {@code storage}'s ta-tracker. */ private Model initModelManager(Storage storage, ReadOnlyUserPrefs userPrefs) { - Optional addressBookOptional; - ReadOnlyAddressBook initialData; + Optional taTrackerOptional; + ReadOnlyTaTracker initialData; try { - addressBookOptional = storage.readAddressBook(); - if (!addressBookOptional.isPresent()) { - logger.info("Data file not found. Will be starting with a sample AddressBook"); + taTrackerOptional = storage.readTaTracker(); + if (!taTrackerOptional.isPresent()) { + logger.info("Data file not found. Will be starting with a sample TaTracker"); } - initialData = addressBookOptional.orElseGet(SampleDataUtil::getSampleAddressBook); + initialData = taTrackerOptional.orElseGet(SampleDataUtil::getSampleTaTracker); } catch (DataConversionException e) { - logger.warning("Data file not in the correct format. Will be starting with an empty AddressBook"); - initialData = new AddressBook(); + logger.warning("Data file not in the correct format. Will be starting with an empty TaTracker"); + initialData = new TaTracker(); } catch (IOException e) { - logger.warning("Problem while reading from the file. Will be starting with an empty AddressBook"); - initialData = new AddressBook(); + logger.warning("Problem while reading from the file. Will be starting with an empty TaTracker"); + initialData = new TaTracker(); } return new ModelManager(initialData, userPrefs); @@ -151,7 +152,7 @@ protected UserPrefs initPrefs(UserPrefsStorage storage) { + "Using default user prefs"); initializedPrefs = new UserPrefs(); } catch (IOException e) { - logger.warning("Problem while reading from the file. Will be starting with an empty AddressBook"); + logger.warning("Problem while reading from the file. Will be starting with an empty TaTracker"); initializedPrefs = new UserPrefs(); } @@ -167,17 +168,20 @@ protected UserPrefs initPrefs(UserPrefsStorage storage) { @Override public void start(Stage primaryStage) { - logger.info("Starting AddressBook " + MainApp.VERSION); + logger.info("Starting TaTracker " + MainApp.VERSION); ui.start(primaryStage); } @Override public void stop() { - logger.info("============================ [ Stopping Address Book ] ============================="); + logger.info("============================ [ Stopping TA-Tracker ] ============================="); try { storage.saveUserPrefs(model.getUserPrefs()); } catch (IOException e) { logger.severe("Failed to save preferences " + StringUtil.getDetails(e)); } + + // Dispose the system tray icon + Notification.dispose(); } } diff --git a/src/main/java/seedu/address/commons/core/Config.java b/src/main/java/tatracker/commons/core/Config.java similarity index 97% rename from src/main/java/seedu/address/commons/core/Config.java rename to src/main/java/tatracker/commons/core/Config.java index 91145745521..698350d8be2 100644 --- a/src/main/java/seedu/address/commons/core/Config.java +++ b/src/main/java/tatracker/commons/core/Config.java @@ -1,4 +1,4 @@ -package seedu.address.commons.core; +package tatracker.commons.core; import java.nio.file.Path; import java.nio.file.Paths; diff --git a/src/main/java/seedu/address/commons/core/GuiSettings.java b/src/main/java/tatracker/commons/core/GuiSettings.java similarity index 98% rename from src/main/java/seedu/address/commons/core/GuiSettings.java rename to src/main/java/tatracker/commons/core/GuiSettings.java index 5ace559ad15..d54cdd5d2e6 100644 --- a/src/main/java/seedu/address/commons/core/GuiSettings.java +++ b/src/main/java/tatracker/commons/core/GuiSettings.java @@ -1,4 +1,4 @@ -package seedu.address.commons.core; +package tatracker.commons.core; import java.awt.Point; import java.io.Serializable; diff --git a/src/main/java/seedu/address/commons/core/LogsCenter.java b/src/main/java/tatracker/commons/core/LogsCenter.java similarity index 97% rename from src/main/java/seedu/address/commons/core/LogsCenter.java rename to src/main/java/tatracker/commons/core/LogsCenter.java index 431e7185e76..e23c6e372f5 100644 --- a/src/main/java/seedu/address/commons/core/LogsCenter.java +++ b/src/main/java/tatracker/commons/core/LogsCenter.java @@ -1,4 +1,4 @@ -package seedu.address.commons.core; +package tatracker.commons.core; import java.io.IOException; import java.util.Arrays; @@ -18,7 +18,7 @@ public class LogsCenter { private static final int MAX_FILE_COUNT = 5; private static final int MAX_FILE_SIZE_IN_BYTES = (int) (Math.pow(2, 20) * 5); // 5MB - private static final String LOG_FILE = "addressbook.log"; + private static final String LOG_FILE = "tatracker.log"; private static Level currentLogLevel = Level.INFO; private static final Logger logger = LogsCenter.getLogger(LogsCenter.class); private static FileHandler fileHandler; diff --git a/src/main/java/tatracker/commons/core/Messages.java b/src/main/java/tatracker/commons/core/Messages.java new file mode 100644 index 00000000000..853832ddc87 --- /dev/null +++ b/src/main/java/tatracker/commons/core/Messages.java @@ -0,0 +1,46 @@ +package tatracker.commons.core; + +/** + * Container for user visible messages. + */ +public class Messages { + + public static final String MESSAGE_WELCOME = "Welcome to TA-Tracker!\n\n"; + public static final String MESSAGE_HELP = "Enter help to view the list of commands"; + + public static final String MESSAGE_INVALID_COMMAND_FORMAT = "Invalid command format!\n%1$s"; + public static final String MESSAGE_INVALID_COMMAND = "Invalid command format!\n\n"; + + public static final String MESSAGE_UNKNOWN_COMMAND = "Unknown command\n\n"; + + public static final String MESSAGE_NOT_EDITED = "You must provide at least one edited field"; + + public static final String MESSAGE_INVALID_MODULE_CODE = "There is no module with the given module code"; + public static final String MESSAGE_DUPLICATE_MODULE = "This module already exists"; + + public static final String MESSAGE_INVALID_GROUP_CODE = "There is no group with the given group code"; + public static final String MESSAGE_DUPLICATE_GROUP = "This group already exists in the given module"; + + public static final String MESSAGE_INVALID_STUDENT = "There is no student with the given matric number"; + public static final String MESSAGE_DUPLICATE_STUDENT = "This student already exists in the given module and group"; + + public static final String MESSAGE_INVALID_SESSION_DISPLAYED_INDEX = "There is no session at the given list index"; + public static final String MESSAGE_DUPLICATE_SESSION = "This session already exists"; + public static final String MESSAGE_INVALID_SESSION_TIMES = "You cannot have a session start after it ends!"; + + // TODO: Delete these + public static final String MESSAGE_INVALID_STUDENT_DISPLAYED_INDEX = "The student index provided is invalid"; + public static final String MESSAGE_STUDENTS_LISTED_OVERVIEW = "%1$d students listed!"; + + public static String getUnknownCommandWithHelpMessage() { + return MESSAGE_UNKNOWN_COMMAND + MESSAGE_HELP; + } + + public static String getInvalidCommandWithHelpMessage() { + return MESSAGE_INVALID_COMMAND + MESSAGE_HELP; + } + + public static String getInvalidCommandMessage(String commandUsage) { + return String.format(MESSAGE_INVALID_COMMAND_FORMAT, commandUsage); + } +} diff --git a/src/main/java/tatracker/commons/core/Notification.java b/src/main/java/tatracker/commons/core/Notification.java new file mode 100644 index 00000000000..8332de31061 --- /dev/null +++ b/src/main/java/tatracker/commons/core/Notification.java @@ -0,0 +1,69 @@ +package tatracker.commons.core; + +import java.awt.AWTException; +import java.awt.Image; +import java.awt.SystemTray; +import java.awt.Toolkit; +import java.awt.TrayIcon; +import java.io.IOException; + +import javax.imageio.ImageIO; + +/** + * A class that handles the displaying of System Notifications. + */ +public class Notification { + public static final String APPICON_PATH = "images/icon.png"; + private static Notification singleton; + private final TrayIcon trayIcon; + + private Notification() throws AWTException { + SystemTray tray = SystemTray.getSystemTray(); + Image trayImage; + + try { + trayImage = ImageIO.read(getClass().getClassLoader().getResource(APPICON_PATH)); + } catch (IOException e) { + System.err.println("Unable to load Application Icon for the System Tray!"); + trayImage = Toolkit.getDefaultToolkit().createImage("placeholder-icon.png"); + } + + trayIcon = new TrayIcon(trayImage, "TrayIcon"); + trayIcon.setImageAutoSize(true); + trayIcon.setToolTip("TA Tracker"); + tray.add(trayIcon); + } + + private static Notification getInstance() throws AWTException { + if (singleton == null) { + singleton = new Notification(); + } + + return singleton; + } + + private void notify(String caption, String message, TrayIcon.MessageType type) { + trayIcon.displayMessage(caption, message, type); + } + + /** + * Triggers a OS-level system notification. + * + * @param caption The title displayed in the notification + * @param message The message displayed in the notification + * @param type the type of notification (error, info, warning, etc.) + * @throws AWTException if {@code SystemTray} is not supported on the current platform. + */ + public static void sendNotification(String caption, String message, TrayIcon.MessageType type) throws AWTException { + Notification.getInstance().notify(caption, message, type); + } + + /** + * Immediately destroys the system tray icon + */ + public static void dispose() { + if (singleton != null) { + SystemTray.getSystemTray().remove(singleton.trayIcon); + } + } +} diff --git a/src/main/java/seedu/address/commons/core/Version.java b/src/main/java/tatracker/commons/core/Version.java similarity index 98% rename from src/main/java/seedu/address/commons/core/Version.java rename to src/main/java/tatracker/commons/core/Version.java index e117f91b3b2..1c0eb1a854e 100644 --- a/src/main/java/seedu/address/commons/core/Version.java +++ b/src/main/java/tatracker/commons/core/Version.java @@ -1,4 +1,4 @@ -package seedu.address.commons.core; +package tatracker.commons.core; import java.util.regex.Matcher; import java.util.regex.Pattern; diff --git a/src/main/java/seedu/address/commons/core/index/Index.java b/src/main/java/tatracker/commons/core/index/Index.java similarity index 91% rename from src/main/java/seedu/address/commons/core/index/Index.java rename to src/main/java/tatracker/commons/core/index/Index.java index 19536439c09..8ab51a9f9a1 100644 --- a/src/main/java/seedu/address/commons/core/index/Index.java +++ b/src/main/java/tatracker/commons/core/index/Index.java @@ -1,4 +1,4 @@ -package seedu.address.commons.core.index; +package tatracker.commons.core.index; /** * Represents a zero-based or one-based index. @@ -9,6 +9,8 @@ * convert it back to an int if the index will not be passed to a different component again. */ public class Index { + + public static final String MESSAGE_CONSTRAINTS = "An index must be a list item number (a positive number)"; private int zeroBasedIndex; /** diff --git a/src/main/java/seedu/address/commons/exceptions/DataConversionException.java b/src/main/java/tatracker/commons/exceptions/DataConversionException.java similarity index 84% rename from src/main/java/seedu/address/commons/exceptions/DataConversionException.java rename to src/main/java/tatracker/commons/exceptions/DataConversionException.java index 1f689bd8e3f..195db3caedc 100644 --- a/src/main/java/seedu/address/commons/exceptions/DataConversionException.java +++ b/src/main/java/tatracker/commons/exceptions/DataConversionException.java @@ -1,4 +1,4 @@ -package seedu.address.commons.exceptions; +package tatracker.commons.exceptions; /** * Represents an error during conversion of data from one format to another diff --git a/src/main/java/seedu/address/commons/exceptions/IllegalValueException.java b/src/main/java/tatracker/commons/exceptions/IllegalValueException.java similarity index 93% rename from src/main/java/seedu/address/commons/exceptions/IllegalValueException.java rename to src/main/java/tatracker/commons/exceptions/IllegalValueException.java index 19124db485c..6c7cce158b4 100644 --- a/src/main/java/seedu/address/commons/exceptions/IllegalValueException.java +++ b/src/main/java/tatracker/commons/exceptions/IllegalValueException.java @@ -1,4 +1,4 @@ -package seedu.address.commons.exceptions; +package tatracker.commons.exceptions; /** * Signals that some given data does not fulfill some constraints. diff --git a/src/main/java/seedu/address/commons/util/AppUtil.java b/src/main/java/tatracker/commons/util/AppUtil.java similarity index 94% rename from src/main/java/seedu/address/commons/util/AppUtil.java rename to src/main/java/tatracker/commons/util/AppUtil.java index da90201dfd6..5a4f76e8f88 100644 --- a/src/main/java/seedu/address/commons/util/AppUtil.java +++ b/src/main/java/tatracker/commons/util/AppUtil.java @@ -1,9 +1,9 @@ -package seedu.address.commons.util; +package tatracker.commons.util; import static java.util.Objects.requireNonNull; import javafx.scene.image.Image; -import seedu.address.MainApp; +import tatracker.MainApp; /** * A container for App specific utility functions diff --git a/src/main/java/seedu/address/commons/util/CollectionUtil.java b/src/main/java/tatracker/commons/util/CollectionUtil.java similarity index 96% rename from src/main/java/seedu/address/commons/util/CollectionUtil.java rename to src/main/java/tatracker/commons/util/CollectionUtil.java index eafe4dfd681..465cbbc7be1 100644 --- a/src/main/java/seedu/address/commons/util/CollectionUtil.java +++ b/src/main/java/tatracker/commons/util/CollectionUtil.java @@ -1,4 +1,4 @@ -package seedu.address.commons.util; +package tatracker.commons.util; import static java.util.Objects.requireNonNull; diff --git a/src/main/java/seedu/address/commons/util/ConfigUtil.java b/src/main/java/tatracker/commons/util/ConfigUtil.java similarity index 77% rename from src/main/java/seedu/address/commons/util/ConfigUtil.java rename to src/main/java/tatracker/commons/util/ConfigUtil.java index f7f8a2bd44c..1d2de24dbc9 100644 --- a/src/main/java/seedu/address/commons/util/ConfigUtil.java +++ b/src/main/java/tatracker/commons/util/ConfigUtil.java @@ -1,11 +1,11 @@ -package seedu.address.commons.util; +package tatracker.commons.util; import java.io.IOException; import java.nio.file.Path; import java.util.Optional; -import seedu.address.commons.core.Config; -import seedu.address.commons.exceptions.DataConversionException; +import tatracker.commons.core.Config; +import tatracker.commons.exceptions.DataConversionException; /** * A class for accessing the Config File. diff --git a/src/main/java/tatracker/commons/util/DateTimeUtil.java b/src/main/java/tatracker/commons/util/DateTimeUtil.java new file mode 100644 index 00000000000..d73ef9f0136 --- /dev/null +++ b/src/main/java/tatracker/commons/util/DateTimeUtil.java @@ -0,0 +1,56 @@ +package tatracker.commons.util; + +import static java.util.Objects.requireNonNull; + +import java.time.LocalDate; +import java.time.LocalTime; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; +import java.time.format.ResolverStyle; + +/** + * Helper functions for handling dates and times. + */ +public class DateTimeUtil { + + public static final String PATTERN_DATE = "uuuu-MM-dd"; // yyyy does not work with the strict resolving mode + public static final String PATTERN_TIME = "HH:mm"; + + public static final DateTimeFormatter FORMAT_DATE = DateTimeFormatter + .ofPattern(PATTERN_DATE) + .withResolverStyle(ResolverStyle.STRICT); + public static final DateTimeFormatter FORMAT_TIME = DateTimeFormatter + .ofPattern(PATTERN_TIME) + .withResolverStyle(ResolverStyle.STRICT); + + public static final String CONSTRAINTS_DATE = "Dates should be in yyyy-MM-dd format"; + public static final String CONSTRAINTS_TIME = String.format("Times should be in %s format", PATTERN_TIME); + + /** + * Returns true if {@code String date} represents a {@code LocalDate} + * in yyyy-MM-dd format. + */ + public static boolean isDate(String date) { + requireNonNull(date); + try { + LocalDate.parse(date, FORMAT_DATE); + return true; + } catch (DateTimeParseException dtpe) { + return false; + } + } + + /** + * Returns true if {@code String date} represents a {@code LocalDate} + * in HH:mm format. + */ + public static boolean isTime(String time) { + requireNonNull(time); + try { + LocalTime.parse(time, FORMAT_TIME); + return true; + } catch (DateTimeParseException dtpe) { + return false; + } + } +} diff --git a/src/main/java/seedu/address/commons/util/FileUtil.java b/src/main/java/tatracker/commons/util/FileUtil.java similarity index 98% rename from src/main/java/seedu/address/commons/util/FileUtil.java rename to src/main/java/tatracker/commons/util/FileUtil.java index b1e2767cdd9..bd23f8afba5 100644 --- a/src/main/java/seedu/address/commons/util/FileUtil.java +++ b/src/main/java/tatracker/commons/util/FileUtil.java @@ -1,4 +1,4 @@ -package seedu.address.commons.util; +package tatracker.commons.util; import java.io.IOException; import java.nio.file.Files; diff --git a/src/main/java/seedu/address/commons/util/JsonUtil.java b/src/main/java/tatracker/commons/util/JsonUtil.java similarity index 97% rename from src/main/java/seedu/address/commons/util/JsonUtil.java rename to src/main/java/tatracker/commons/util/JsonUtil.java index 8ef609f055d..77164b4431e 100644 --- a/src/main/java/seedu/address/commons/util/JsonUtil.java +++ b/src/main/java/tatracker/commons/util/JsonUtil.java @@ -1,4 +1,4 @@ -package seedu.address.commons.util; +package tatracker.commons.util; import static java.util.Objects.requireNonNull; @@ -20,8 +20,8 @@ import com.fasterxml.jackson.databind.module.SimpleModule; import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; -import seedu.address.commons.core.LogsCenter; -import seedu.address.commons.exceptions.DataConversionException; +import tatracker.commons.core.LogsCenter; +import tatracker.commons.exceptions.DataConversionException; /** * Converts a Java object instance to JSON and vice versa diff --git a/src/main/java/seedu/address/commons/util/StringUtil.java b/src/main/java/tatracker/commons/util/StringUtil.java similarity index 88% rename from src/main/java/seedu/address/commons/util/StringUtil.java rename to src/main/java/tatracker/commons/util/StringUtil.java index 61cc8c9a1cb..1590b4a9231 100644 --- a/src/main/java/seedu/address/commons/util/StringUtil.java +++ b/src/main/java/tatracker/commons/util/StringUtil.java @@ -1,7 +1,7 @@ -package seedu.address.commons.util; +package tatracker.commons.util; import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.AppUtil.checkArgument; +import static tatracker.commons.util.AppUtil.checkArgument; import java.io.PrintWriter; import java.io.StringWriter; @@ -65,4 +65,11 @@ public static boolean isNonZeroUnsignedInteger(String s) { return false; } } + + /** + * Returns true if {@code s} represents an unsigned integer. + */ + public static boolean isUnsignedInteger(String s) { + return isNonZeroUnsignedInteger(s) | "0".equals(s); + } } diff --git a/src/main/java/tatracker/logic/Logic.java b/src/main/java/tatracker/logic/Logic.java new file mode 100644 index 00000000000..28a29d1d73c --- /dev/null +++ b/src/main/java/tatracker/logic/Logic.java @@ -0,0 +1,78 @@ +package tatracker.logic; + +import java.nio.file.Path; + +import javafx.collections.ObservableList; + +import tatracker.commons.core.GuiSettings; +import tatracker.logic.commands.CommandResult; +import tatracker.logic.commands.exceptions.CommandException; +import tatracker.logic.parser.exceptions.ParseException; +import tatracker.model.ReadOnlyTaTracker; +import tatracker.model.group.Group; +import tatracker.model.module.Module; +import tatracker.model.session.Session; +import tatracker.model.student.Student; + +/** + * API of the Logic component + */ +public interface Logic { + /** + * Executes the command and returns the result. + * @param commandText The command as entered by the user. + * @return the result of the command execution. + * @throws CommandException If an error occurs during command execution. + * @throws ParseException If an error occurs during parsing. + */ + CommandResult execute(String commandText) throws CommandException, ParseException; + + /** + * Returns the TaTracker. + * + * @see tatracker.model.Model#getTaTracker() + */ + ReadOnlyTaTracker getTaTracker(); + + /** + * Returns the user prefs' ta-tracker file path. + */ + Path getTaTrackerFilePath(); + + /** + * Returns the user prefs' GUI settings. + */ + GuiSettings getGuiSettings(); + + /** + * Set the user prefs' GUI settings. + */ + void setGuiSettings(GuiSettings guiSettings); + + /** Returns an unmodifiable view of the filtered list of sessions. */ + ObservableList getFilteredSessionList(); + + /** Returns an unmodifiable view of the filtered list of done sessions. */ + ObservableList getFilteredDoneSessionList(); + + /** Returns an unmodifiable view of the filtered list of module. */ + ObservableList getFilteredModuleList(); + + /** Returns an unmodifiable view of the filtered list of module groups. */ + ObservableList getFilteredGroupList(); + + /** Returns an unmodifiable view of the filtered list of students. */ + ObservableList getFilteredStudentList(); + + /** Returns an the currently used session filters. */ + String getCurrSessionFilter(); + + /** Returns an the currently used session date filters. */ + String getCurrSessionDateFilter(); + + /** Returns an the currently used session module filters. */ + String getCurrSessionModuleFilter(); + + /** Returns an the currently used session type filters. */ + String getCurrSessionTypeFilter(); +} diff --git a/src/main/java/tatracker/logic/LogicManager.java b/src/main/java/tatracker/logic/LogicManager.java new file mode 100644 index 00000000000..a91055047a2 --- /dev/null +++ b/src/main/java/tatracker/logic/LogicManager.java @@ -0,0 +1,123 @@ +package tatracker.logic; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.logging.Logger; + +import javafx.collections.ObservableList; + +import tatracker.commons.core.GuiSettings; +import tatracker.commons.core.LogsCenter; +import tatracker.logic.commands.Command; +import tatracker.logic.commands.CommandResult; +import tatracker.logic.commands.exceptions.CommandException; +import tatracker.logic.parser.TaTrackerParser; +import tatracker.logic.parser.exceptions.ParseException; +import tatracker.model.Model; +import tatracker.model.ReadOnlyTaTracker; +import tatracker.model.group.Group; +import tatracker.model.module.Module; +import tatracker.model.session.Session; +import tatracker.model.student.Student; +import tatracker.storage.Storage; + +/** + * The main LogicManager of the app. + */ +public class LogicManager implements Logic { + public static final String FILE_OPS_ERROR_MESSAGE = "Could not save data to file: "; + private final Logger logger = LogsCenter.getLogger(LogicManager.class); + + private final Model model; + private final Storage storage; + private final TaTrackerParser taTrackerParser; + + public LogicManager(Model model, Storage storage) { + this.model = model; + this.storage = storage; + this.taTrackerParser = new TaTrackerParser(); + } + + @Override + public CommandResult execute(String commandText) throws CommandException, ParseException { + logger.info("----------------[USER COMMAND][" + commandText + "]"); + + CommandResult commandResult; + Command command = taTrackerParser.parseCommand(commandText); + commandResult = command.execute(model); + + try { + storage.saveTaTracker(model.getTaTracker()); + } catch (IOException ioe) { + throw new CommandException(FILE_OPS_ERROR_MESSAGE + ioe, ioe); + } + + return commandResult; + } + + @Override + public ReadOnlyTaTracker getTaTracker() { + return model.getTaTracker(); + } + + @Override + public Path getTaTrackerFilePath() { + return model.getTaTrackerFilePath(); + } + + @Override + public GuiSettings getGuiSettings() { + return model.getGuiSettings(); + } + + @Override + public void setGuiSettings(GuiSettings guiSettings) { + model.setGuiSettings(guiSettings); + } + + @Override + public ObservableList getFilteredSessionList() { + return model.getFilteredSessionList(); + } + + @Override + public ObservableList getFilteredDoneSessionList() { + return model.getFilteredDoneSessionList(); + } + + @Override + public ObservableList getFilteredModuleList() { + return model.getFilteredModuleList(); + } + + @Override + public ObservableList getFilteredGroupList() { + return model.getFilteredGroupList(); + } + + @Override + public ObservableList getFilteredStudentList() { + return model.getFilteredStudentList(); + } + + @Override + public String getCurrSessionFilter() { + return model.getCurrSessionFilter(); + } + + @Override + public String getCurrSessionDateFilter() { + return model.getCurrSessionDateFilter(); + } + + @Override + public String getCurrSessionModuleFilter() { + return model.getCurrSessionModuleFilter(); + } + + @Override + public String getCurrSessionTypeFilter() { + return model.getCurrSessionTypeFilter(); + } + +} diff --git a/src/main/java/seedu/address/logic/commands/Command.java b/src/main/java/tatracker/logic/commands/Command.java similarity index 78% rename from src/main/java/seedu/address/logic/commands/Command.java rename to src/main/java/tatracker/logic/commands/Command.java index 64f18992160..f50fdac6644 100644 --- a/src/main/java/seedu/address/logic/commands/Command.java +++ b/src/main/java/tatracker/logic/commands/Command.java @@ -1,7 +1,7 @@ -package seedu.address.logic.commands; +package tatracker.logic.commands; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.model.Model; +import tatracker.logic.commands.exceptions.CommandException; +import tatracker.model.Model; /** * Represents a command with hidden internal logic and the ability to be executed. diff --git a/src/main/java/tatracker/logic/commands/CommandDetails.java b/src/main/java/tatracker/logic/commands/CommandDetails.java new file mode 100644 index 00000000000..6df18a73451 --- /dev/null +++ b/src/main/java/tatracker/logic/commands/CommandDetails.java @@ -0,0 +1,97 @@ +package tatracker.logic.commands; + +import java.util.List; + +import tatracker.logic.parser.Prefix; +import tatracker.logic.parser.PrefixDictionary; + +/** + * Represents all compulsory information for a command. + */ +public class CommandDetails { + private static final String NO_SUBWORD = ""; + + private final String commandWord; + private final String subWord; + private final String info; + + private final PrefixDictionary dictionary; + private final List prefixesForExample; + + private final String usage; + private final String example; + + public CommandDetails(String commandWord, + String info, + List parameters, List optionals, + Prefix ... prefixesForExample) { + this(commandWord, NO_SUBWORD, info, parameters, optionals, prefixesForExample); + } + + public CommandDetails(String commandWord, String subWord, + String info, + List parameters, List optionals, + Prefix ... prefixesForExample) { + if (commandWord.contains(" ")) { + throw new IllegalArgumentException("CommandDetails: commandWord cannot have spaces"); + } + this.commandWord = commandWord; + + if (subWord.contains(" ")) { + throw new IllegalArgumentException("CommandDetails: subWord cannot have spaces"); + } + this.subWord = subWord; + + this.info = info; + + this.dictionary = new PrefixDictionary(parameters, optionals); + this.prefixesForExample = List.of(prefixesForExample); + + this.usage = this.dictionary.getPrefixesWithInfo(); + this.example = this.dictionary.getPrefixesWithExamples(prefixesForExample); + } + + public String getFullCommandWord() { + if (subWord.isEmpty()) { + return commandWord; + } else { + return commandWord + " " + subWord; + } + } + + public String getCommandWord() { + return commandWord; + } + + public String getSubWord() { + return commandWord; + } + + public String getInfo() { + return info; + } + + public PrefixDictionary getPrefixDictionary() { + return dictionary; + } + + public List getParameters() { + return dictionary.getParameters(); + } + + public List getOptionals() { + return dictionary.getOptionals(); + } + + public List getPrefixesForExample() { + return prefixesForExample; + } + + public String getUsage() { + return usage; + } + + public String getExample() { + return example; + } +} diff --git a/src/main/java/tatracker/logic/commands/CommandDictionary.java b/src/main/java/tatracker/logic/commands/CommandDictionary.java new file mode 100644 index 00000000000..3f8df8f1392 --- /dev/null +++ b/src/main/java/tatracker/logic/commands/CommandDictionary.java @@ -0,0 +1,112 @@ +package tatracker.logic.commands; + +import static java.util.Objects.requireNonNull; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import tatracker.logic.commands.commons.ClearCommand; +import tatracker.logic.commands.commons.ExitCommand; +import tatracker.logic.commands.commons.GotoCommand; +import tatracker.logic.commands.commons.HelpCommand; +import tatracker.logic.commands.commons.ListCommand; +import tatracker.logic.commands.commons.SetRateCommand; +import tatracker.logic.commands.group.AddGroupCommand; +import tatracker.logic.commands.group.DeleteGroupCommand; +import tatracker.logic.commands.group.EditGroupCommand; +import tatracker.logic.commands.module.AddModuleCommand; +import tatracker.logic.commands.module.DeleteModuleCommand; +import tatracker.logic.commands.module.EditModuleCommand; +import tatracker.logic.commands.session.AddSessionCommand; +import tatracker.logic.commands.session.DeleteSessionCommand; +import tatracker.logic.commands.session.DoneSessionCommand; +import tatracker.logic.commands.session.EditSessionCommand; +import tatracker.logic.commands.session.FilterClaimCommand; +import tatracker.logic.commands.session.FilterSessionCommand; +import tatracker.logic.commands.sort.SortCommand; +import tatracker.logic.commands.sort.SortGroupCommand; +import tatracker.logic.commands.sort.SortModuleCommand; +import tatracker.logic.commands.statistic.ShowStatisticCommand; +import tatracker.logic.commands.student.AddStudentCommand; +import tatracker.logic.commands.student.DeleteStudentCommand; +import tatracker.logic.commands.student.EditStudentCommand; +import tatracker.logic.commands.student.FilterStudentCommand; + +/** + * Stores a list of all the commands. + */ +public class CommandDictionary { + private static final List COMMAND_DETAILS = List.of( + /* Student View */ + AddModuleCommand.DETAILS, + DeleteModuleCommand.DETAILS, + EditModuleCommand.DETAILS, + + AddGroupCommand.DETAILS, + DeleteGroupCommand.DETAILS, + EditGroupCommand.DETAILS, + + AddStudentCommand.DETAILS, + DeleteStudentCommand.DETAILS, + EditStudentCommand.DETAILS, + FilterStudentCommand.DETAILS, + + SortCommand.DETAILS, + SortGroupCommand.DETAILS, + SortModuleCommand.DETAILS, + + /* Session View */ + AddSessionCommand.DETAILS, + DeleteSessionCommand.DETAILS, + EditSessionCommand.DETAILS, + DoneSessionCommand.DETAILS, + + /* Session - Claims Filtering */ + FilterSessionCommand.DETAILS, + FilterClaimCommand.DETAILS, + ListCommand.DETAILS, + + /* Claims View */ + SetRateCommand.DETAILS, + + /* Storage Operations */ + ClearCommand.DETAILS, + + /* Navigation */ + GotoCommand.DETAILS, + HelpCommand.DETAILS, + ShowStatisticCommand.DETAILS, + ExitCommand.DETAILS + ); + + private static final Map FULL_COMMAND_WORDS = COMMAND_DETAILS + .stream() + .collect(Collectors.toUnmodifiableMap(CommandDetails::getFullCommandWord, + entry -> entry, (key1, key2) -> { + throw new IllegalArgumentException("CommandDictionary: cannot have two commands with the same id"); + })); + + /** + * Returns true if the {@code fullCommandWord} is valid. + */ + public static boolean hasFullCommandWord(String fullCommandWord) { + requireNonNull(fullCommandWord); + return FULL_COMMAND_WORDS.containsKey(fullCommandWord); + } + + /** + * Returns the matching CommandDetails based on the {@code fullCommandWord}. + */ + public static CommandDetails getDetailsWithFullCommandWord(String fullCommandWord) { + requireNonNull(fullCommandWord); + return FULL_COMMAND_WORDS.get(fullCommandWord); + } + + /** + * Returns the message information of all the commands. + */ + public static List getDetails() { + return COMMAND_DETAILS; + } +} diff --git a/src/main/java/seedu/address/logic/commands/CommandResult.java b/src/main/java/tatracker/logic/commands/CommandResult.java similarity index 51% rename from src/main/java/seedu/address/logic/commands/CommandResult.java rename to src/main/java/tatracker/logic/commands/CommandResult.java index 92f900b7916..c7247f1ebd4 100644 --- a/src/main/java/seedu/address/logic/commands/CommandResult.java +++ b/src/main/java/tatracker/logic/commands/CommandResult.java @@ -1,4 +1,4 @@ -package seedu.address.logic.commands; +package tatracker.logic.commands; import static java.util.Objects.requireNonNull; @@ -11,39 +11,40 @@ public class CommandResult { private final String feedbackToUser; - /** Help information should be shown to the user. */ - private final boolean showHelp; - - /** The application should exit. */ - private final boolean exit; - /** - * Constructs a {@code CommandResult} with the specified fields. + * Represents all the actions that can occur in the GUI. + * Note that each action cannot happen at the same time. */ - public CommandResult(String feedbackToUser, boolean showHelp, boolean exit) { - this.feedbackToUser = requireNonNull(feedbackToUser); - this.showHelp = showHelp; - this.exit = exit; + public enum Action { + DONE, + EXIT, + FILTER_STUDENT, + FILTER_SESSION, + FILTER_CLAIMS, + GOTO_STUDENT, + GOTO_SESSION, + GOTO_CLAIMS, + HELP, + LIST, + NONE; } + private final Action nextAction; + /** - * Constructs a {@code CommandResult} with the specified {@code feedbackToUser}, - * and other fields set to their default value. + * Constructs a {@code CommandResult} with the specified fields. */ - public CommandResult(String feedbackToUser) { - this(feedbackToUser, false, false); + public CommandResult(String feedbackToUser, Action nextAction) { + this.feedbackToUser = requireNonNull(feedbackToUser); + this.nextAction = nextAction; } public String getFeedbackToUser() { return feedbackToUser; } - public boolean isShowHelp() { - return showHelp; - } - - public boolean isExit() { - return exit; + public Action getNextAction() { + return nextAction; } @Override @@ -59,13 +60,12 @@ public boolean equals(Object other) { CommandResult otherCommandResult = (CommandResult) other; return feedbackToUser.equals(otherCommandResult.feedbackToUser) - && showHelp == otherCommandResult.showHelp - && exit == otherCommandResult.exit; + && nextAction == otherCommandResult.nextAction; } @Override public int hashCode() { - return Objects.hash(feedbackToUser, showHelp, exit); + return Objects.hash(feedbackToUser, nextAction); } } diff --git a/src/main/java/tatracker/logic/commands/CommandWords.java b/src/main/java/tatracker/logic/commands/CommandWords.java new file mode 100644 index 00000000000..7679d8df538 --- /dev/null +++ b/src/main/java/tatracker/logic/commands/CommandWords.java @@ -0,0 +1,39 @@ +package tatracker.logic.commands; + +/** + * Contains a list of common command words in TA-Tracker. + * These have been separated as they will be used by the CommandDictionary in future versions. + */ +public final class CommandWords { + + /* List of command words for each model in TA-Tracker. */ + public static final String MODULE = "module"; + public static final String GROUP = "group"; + public static final String STUDENT = "student"; + public static final String SESSION = "session"; + public static final String CLAIM = "claims"; + + /* List of command words for commands that are common between TA-Tracker models. */ + public static final String ADD_MODEL = "add"; + public static final String DELETE_MODEL = "delete"; + public static final String EDIT_MODEL = "edit"; + public static final String FILTER_MODEL = "filter"; + + /* List of command words for the different sort types in TA-Tracker. */ + public static final String SORT = "sort"; + public static final String SORT_ALL = "all"; + public static final String SORT_GROUP = "group"; + public static final String SORT_MODULE = "module"; + + /* List of command words for special actions in TA-Tracker. */ + public static final String CLEAR = "clear"; + public static final String GOTO = "goto"; + public static final String REPORT = "report"; + public static final String SET_RATE = "setrate"; + public static final String HELP = "help"; + public static final String EXIT = "exit"; + + /* Others */ + public static final String LIST = "list"; + public static final String DONE_SESSION = "done"; +} diff --git a/src/main/java/tatracker/logic/commands/commons/ClearCommand.java b/src/main/java/tatracker/logic/commands/commons/ClearCommand.java new file mode 100644 index 00000000000..f42c3199476 --- /dev/null +++ b/src/main/java/tatracker/logic/commands/commons/ClearCommand.java @@ -0,0 +1,35 @@ +package tatracker.logic.commands.commons; + +import static java.util.Objects.requireNonNull; + +import java.util.List; + +import tatracker.logic.commands.Command; +import tatracker.logic.commands.CommandDetails; +import tatracker.logic.commands.CommandResult; +import tatracker.logic.commands.CommandResult.Action; +import tatracker.logic.commands.CommandWords; +import tatracker.model.Model; +import tatracker.model.TaTracker; + +/** + * Clears the TA-Tracker. + */ +public class ClearCommand extends Command { + + public static final CommandDetails DETAILS = new CommandDetails( + CommandWords.CLEAR, + "Clears all data stored inside TA-Tracker", + List.of(), + List.of() + ); + + public static final String MESSAGE_CLEAR_SUCCESS = "TA-Tracker has been cleared!"; + + @Override + public CommandResult execute(Model model) { + requireNonNull(model); + model.setTaTracker(new TaTracker()); + return new CommandResult(MESSAGE_CLEAR_SUCCESS, Action.NONE); + } +} diff --git a/src/main/java/tatracker/logic/commands/commons/ExitCommand.java b/src/main/java/tatracker/logic/commands/commons/ExitCommand.java new file mode 100644 index 00000000000..b5879bbd9b3 --- /dev/null +++ b/src/main/java/tatracker/logic/commands/commons/ExitCommand.java @@ -0,0 +1,31 @@ +package tatracker.logic.commands.commons; + +import java.util.List; + +import tatracker.logic.commands.Command; +import tatracker.logic.commands.CommandDetails; +import tatracker.logic.commands.CommandResult; +import tatracker.logic.commands.CommandResult.Action; +import tatracker.logic.commands.CommandWords; +import tatracker.model.Model; + +/** + * Terminates the program. + */ +public class ExitCommand extends Command { + + public static final CommandDetails DETAILS = new CommandDetails( + CommandWords.EXIT, + "Exits TA-Tracker", + List.of(), + List.of() + ); + + public static final String MESSAGE_EXIT_ACKNOWLEDGEMENT = "Exiting TA-Tracker as requested ..."; + + @Override + public CommandResult execute(Model model) { + return new CommandResult(MESSAGE_EXIT_ACKNOWLEDGEMENT, Action.EXIT); + } + +} diff --git a/src/main/java/tatracker/logic/commands/commons/GotoCommand.java b/src/main/java/tatracker/logic/commands/commons/GotoCommand.java new file mode 100644 index 00000000000..26cedc92ea0 --- /dev/null +++ b/src/main/java/tatracker/logic/commands/commons/GotoCommand.java @@ -0,0 +1,90 @@ +package tatracker.logic.commands.commons; + +import static java.util.Objects.requireNonNull; +import static tatracker.logic.parser.Prefixes.TAB_NAME; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import tatracker.logic.commands.Command; +import tatracker.logic.commands.CommandDetails; +import tatracker.logic.commands.CommandResult; +import tatracker.logic.commands.CommandResult.Action; +import tatracker.logic.commands.CommandWords; +import tatracker.model.Model; + +/** + * Switches between tabs + */ +public class GotoCommand extends Command { + + public static final CommandDetails DETAILS = new CommandDetails( + CommandWords.GOTO, + "Switches to the specified tab", + List.of(TAB_NAME), + List.of(), + TAB_NAME + ); + + /** + * Represents the name of a tab view in TA-Tracker. + */ + public enum Tab { + STUDENT, + SESSION, + CLAIMS; + + public static final String MESSAGE_CONSTRAINTS = + "These are the only tab names: student, session, claims"; + + private static final Map TABS = Arrays.stream(values()) + .collect(Collectors.toUnmodifiableMap(tab -> tab.name().toLowerCase(), tab -> tab)); + + public static boolean isValidTab(String test) { + return TABS.containsKey(test.toLowerCase()); + } + + public static Tab getTab(String tabName) { + requireNonNull(tabName); + return TABS.get(tabName.toLowerCase()); + } + + @Override + public String toString() { + return name().toLowerCase(); + } + } + + public static final String MESSAGE_SWITCHED_TAB = "Switched to the %s tab"; + + private final Tab tab; + private final Action tabToSwitchTo; + + public GotoCommand(Tab tab) { + this.tab = tab; + + switch(tab) { + case STUDENT: + tabToSwitchTo = Action.GOTO_STUDENT; + break; + case SESSION: + tabToSwitchTo = Action.GOTO_SESSION; + break; + case CLAIMS: + tabToSwitchTo = Action.GOTO_CLAIMS; + break; + default: + assert false; + tabToSwitchTo = null; + break; + } + } + + + @Override + public CommandResult execute(Model model) { + return new CommandResult(String.format(MESSAGE_SWITCHED_TAB, tab), tabToSwitchTo); + } +} diff --git a/src/main/java/tatracker/logic/commands/commons/HelpCommand.java b/src/main/java/tatracker/logic/commands/commons/HelpCommand.java new file mode 100644 index 00000000000..461c6ea77a9 --- /dev/null +++ b/src/main/java/tatracker/logic/commands/commons/HelpCommand.java @@ -0,0 +1,30 @@ +package tatracker.logic.commands.commons; + +import java.util.List; + +import tatracker.logic.commands.Command; +import tatracker.logic.commands.CommandDetails; +import tatracker.logic.commands.CommandResult; +import tatracker.logic.commands.CommandResult.Action; +import tatracker.logic.commands.CommandWords; +import tatracker.model.Model; + +/** + * Format full help instructions for every command for display. + */ +public class HelpCommand extends Command { + + public static final CommandDetails DETAILS = new CommandDetails( + CommandWords.HELP, + "Shows the help window", + List.of(), + List.of() + ); + + public static final String MESSAGE_SHOWING_HELP = "Opened help window"; + + @Override + public CommandResult execute(Model model) { + return new CommandResult(MESSAGE_SHOWING_HELP, Action.HELP); + } +} diff --git a/src/main/java/tatracker/logic/commands/commons/ListCommand.java b/src/main/java/tatracker/logic/commands/commons/ListCommand.java new file mode 100644 index 00000000000..4a30316ff8d --- /dev/null +++ b/src/main/java/tatracker/logic/commands/commons/ListCommand.java @@ -0,0 +1,40 @@ +package tatracker.logic.commands.commons; + +import static java.util.Objects.requireNonNull; +import static tatracker.model.Model.PREDICATE_SHOW_ALL_SESSIONS; + +import java.util.List; + +import tatracker.logic.commands.Command; +import tatracker.logic.commands.CommandDetails; +import tatracker.logic.commands.CommandResult; +import tatracker.logic.commands.CommandResult.Action; +import tatracker.logic.commands.CommandWords; +import tatracker.model.Model; + +/** + * Lists all students in the TA-Tracker to the user. + */ +public class ListCommand extends Command { + + public static final CommandDetails DETAILS = new CommandDetails( + CommandWords.LIST, + "Removes all session and claim filters inside TA-Tracker", + List.of(), + List.of() + ); + + public static final String MESSAGE_LISTED_SESSIONS = "Removed all filters"; + + + @Override + public CommandResult execute(Model model) { + requireNonNull(model); + model.setCurrSessionDateFilter(""); + model.setCurrSessionModuleFilter(""); + model.setCurrSessionTypeFilter(""); + model.updateFilteredSessionList(PREDICATE_SHOW_ALL_SESSIONS); + model.updateFilteredDoneSessionList(PREDICATE_SHOW_ALL_SESSIONS, ""); + return new CommandResult(MESSAGE_LISTED_SESSIONS, Action.LIST); + } +} diff --git a/src/main/java/tatracker/logic/commands/commons/SetRateCommand.java b/src/main/java/tatracker/logic/commands/commons/SetRateCommand.java new file mode 100644 index 00000000000..848c6ea53e6 --- /dev/null +++ b/src/main/java/tatracker/logic/commands/commons/SetRateCommand.java @@ -0,0 +1,42 @@ +package tatracker.logic.commands.commons; + +import static tatracker.logic.parser.Prefixes.RATE; + +import java.util.List; + +import tatracker.logic.commands.Command; +import tatracker.logic.commands.CommandDetails; +import tatracker.logic.commands.CommandResult; +import tatracker.logic.commands.CommandResult.Action; +import tatracker.logic.commands.CommandWords; +import tatracker.model.Model; + + +/** + * Switches between tabs + */ +public class SetRateCommand extends Command { + + public static final CommandDetails DETAILS = new CommandDetails( + CommandWords.SET_RATE, + "Sets the pay rate to a specified integer greater than zero.", + List.of(RATE), + List.of(), + RATE + ); + + public static final String MESSAGE_SET_RATE = "Set pay rate at $%s per hour."; + + private final int rate; + + public SetRateCommand(int rate) { + this.rate = rate; + } + + + @Override + public CommandResult execute(Model model) { + model.setRate(rate); + return new CommandResult(String.format(MESSAGE_SET_RATE, rate), Action.GOTO_CLAIMS); + } +} diff --git a/src/main/java/seedu/address/logic/commands/exceptions/CommandException.java b/src/main/java/tatracker/logic/commands/exceptions/CommandException.java similarity index 80% rename from src/main/java/seedu/address/logic/commands/exceptions/CommandException.java rename to src/main/java/tatracker/logic/commands/exceptions/CommandException.java index a16bd14f2cd..3d8668d5e8b 100644 --- a/src/main/java/seedu/address/logic/commands/exceptions/CommandException.java +++ b/src/main/java/tatracker/logic/commands/exceptions/CommandException.java @@ -1,7 +1,9 @@ -package seedu.address.logic.commands.exceptions; +package tatracker.logic.commands.exceptions; + +import tatracker.logic.commands.Command; /** - * Represents an error which occurs during execution of a {@link Command}. + * Represents an error which occurs during execution of a {@link Command#execute }. */ public class CommandException extends Exception { public CommandException(String message) { diff --git a/src/main/java/tatracker/logic/commands/group/AddGroupCommand.java b/src/main/java/tatracker/logic/commands/group/AddGroupCommand.java new file mode 100644 index 00000000000..530f37f8787 --- /dev/null +++ b/src/main/java/tatracker/logic/commands/group/AddGroupCommand.java @@ -0,0 +1,93 @@ +package tatracker.logic.commands.group; + +import static java.util.Objects.requireNonNull; +import static tatracker.commons.core.Messages.MESSAGE_DUPLICATE_GROUP; +import static tatracker.commons.core.Messages.MESSAGE_INVALID_MODULE_CODE; +import static tatracker.logic.parser.Prefixes.GROUP; +import static tatracker.logic.parser.Prefixes.MODULE; +import static tatracker.logic.parser.Prefixes.TYPE; + +import java.util.List; + +import tatracker.logic.commands.Command; +import tatracker.logic.commands.CommandDetails; +import tatracker.logic.commands.CommandResult; +import tatracker.logic.commands.CommandResult.Action; +import tatracker.logic.commands.CommandWords; +import tatracker.logic.commands.exceptions.CommandException; +import tatracker.model.Model; +import tatracker.model.group.Group; +import tatracker.model.module.Module; + +/** + * Adds a group to the TA-Tracker. + */ +public class AddGroupCommand extends Command { + + public static final CommandDetails DETAILS = new CommandDetails( + CommandWords.GROUP, + CommandWords.ADD_MODEL, + "Adds a group into TA-Tracker", + List.of(GROUP, MODULE, TYPE), + List.of(), + GROUP, MODULE, TYPE + ); + + public static final String MESSAGE_ADD_GROUP_SUCCESS = "New group added: %s [%s]"; + + private final Group toAdd; + private final String targetModule; + + /** + * Creates an addGroupCommand + */ + + public AddGroupCommand(Group group, String module) { + requireNonNull(group); + requireNonNull(module); + toAdd = group; + targetModule = module; + } + + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + if (!model.hasModule(targetModule)) { + throw new CommandException(MESSAGE_INVALID_MODULE_CODE); + } + + Module actualModule = model.getModule(targetModule); + + if (toAdd.getIdentifier().isBlank()) { + throw new CommandException(Group.CONSTRAINTS_GROUP_CODE); + } + if (actualModule.hasGroup(toAdd)) { + throw new CommandException(MESSAGE_DUPLICATE_GROUP); + } + + actualModule.addGroup(toAdd); + + model.updateFilteredGroupList(actualModule.getIdentifier()); + model.updateFilteredStudentList(toAdd.getIdentifier(), actualModule.getIdentifier()); + + return new CommandResult(String.format(MESSAGE_ADD_GROUP_SUCCESS, targetModule, toAdd.getIdentifier()), + Action.GOTO_STUDENT); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; // short circuit if same object + } + + if (!(other instanceof AddGroupCommand)) { + return false; // instanceof handles nulls + } + + AddGroupCommand otherCommand = (AddGroupCommand) other; + return toAdd.equals(otherCommand.toAdd) + && targetModule.equals(otherCommand.targetModule); + } +} diff --git a/src/main/java/tatracker/logic/commands/group/DeleteGroupCommand.java b/src/main/java/tatracker/logic/commands/group/DeleteGroupCommand.java new file mode 100644 index 00000000000..e9e483bc365 --- /dev/null +++ b/src/main/java/tatracker/logic/commands/group/DeleteGroupCommand.java @@ -0,0 +1,84 @@ +package tatracker.logic.commands.group; + +import static java.util.Objects.requireNonNull; +import static tatracker.commons.core.Messages.MESSAGE_INVALID_GROUP_CODE; +import static tatracker.commons.core.Messages.MESSAGE_INVALID_MODULE_CODE; +import static tatracker.logic.parser.Prefixes.GROUP; +import static tatracker.logic.parser.Prefixes.MODULE; + +import java.util.List; + +import tatracker.logic.commands.Command; +import tatracker.logic.commands.CommandDetails; +import tatracker.logic.commands.CommandResult; +import tatracker.logic.commands.CommandResult.Action; +import tatracker.logic.commands.CommandWords; +import tatracker.logic.commands.exceptions.CommandException; +import tatracker.model.Model; + +/** + * Deletes a group identified using it's group code. + */ +public class DeleteGroupCommand extends Command { + + public static final CommandDetails DETAILS = new CommandDetails( + CommandWords.GROUP, + CommandWords.DELETE_MODEL, + "Deletes the group with the given group code", + List.of(MODULE, GROUP), + List.of(), + MODULE, GROUP + ); + + public static final String MESSAGE_DELETE_GROUP_SUCCESS = "Deleted group: %s [%s]"; + private static final int FIRST_GROUP_INDEX = 0; + + private final String group; + private final String targetModule; + + public DeleteGroupCommand(String groupCode, String moduleCode) { + this.group = groupCode; + this.targetModule = moduleCode; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + if (!model.hasModule(targetModule)) { + throw new CommandException(MESSAGE_INVALID_MODULE_CODE); + } + + if (!model.hasGroup(group, targetModule)) { + throw new CommandException(MESSAGE_INVALID_GROUP_CODE); + } + + model.deleteGroup(group, targetModule); + + model.updateFilteredGroupList(targetModule); + + if (model.getFilteredGroupList() == null || model.getFilteredGroupList().isEmpty()) { + model.setFilteredStudentList(); + } else { + model.setFilteredStudentList(targetModule, FIRST_GROUP_INDEX); + } + + return new CommandResult(String.format(MESSAGE_DELETE_GROUP_SUCCESS, targetModule, group), + Action.GOTO_STUDENT); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; // short circuit if same object + } + + if (!(other instanceof DeleteGroupCommand)) { + return false; // instanceof handles nulls + } + + DeleteGroupCommand otherCommand = (DeleteGroupCommand) other; + return group.equals(otherCommand.group) + && targetModule.equals(otherCommand.targetModule); + } +} diff --git a/src/main/java/tatracker/logic/commands/group/EditGroupCommand.java b/src/main/java/tatracker/logic/commands/group/EditGroupCommand.java new file mode 100644 index 00000000000..08a8256a1ab --- /dev/null +++ b/src/main/java/tatracker/logic/commands/group/EditGroupCommand.java @@ -0,0 +1,113 @@ +package tatracker.logic.commands.group; + +import static java.util.Objects.requireNonNull; +import static tatracker.commons.core.Messages.MESSAGE_INVALID_GROUP_CODE; +import static tatracker.commons.core.Messages.MESSAGE_INVALID_MODULE_CODE; +import static tatracker.commons.core.Messages.MESSAGE_NOT_EDITED; +import static tatracker.logic.parser.Prefixes.GROUP; +import static tatracker.logic.parser.Prefixes.MODULE; +import static tatracker.logic.parser.Prefixes.NEWGROUP; +import static tatracker.logic.parser.Prefixes.NEWTYPE; + +import java.util.List; + +import tatracker.logic.commands.Command; +import tatracker.logic.commands.CommandDetails; +import tatracker.logic.commands.CommandResult; +import tatracker.logic.commands.CommandResult.Action; +import tatracker.logic.commands.CommandWords; +import tatracker.logic.commands.exceptions.CommandException; +import tatracker.model.Model; +import tatracker.model.group.Group; +import tatracker.model.group.GroupType; +import tatracker.model.module.Module; + +/** + * Edits a group identified using it's group code. + */ +public class EditGroupCommand extends Command { + + public static final CommandDetails DETAILS = new CommandDetails( + CommandWords.GROUP, + CommandWords.EDIT_MODEL, + "Edits the group with the given group code", + List.of(MODULE, GROUP), + List.of(NEWGROUP, NEWTYPE), // TODO: new type not needed? + MODULE, GROUP, NEWGROUP, NEWTYPE + ); + + public static final String MESSAGE_EDIT_GROUP_SUCCESS = "Edited group: %s [%s]"; + public static final String MESSAGE_EDIT_GROUP_FAILURE = "There is already a group with the group code" + + " that you are editing with."; + + private final Group group; + private final String targetModule; + private final String newGroupCode; + private final GroupType newGroupType; + + public EditGroupCommand(Group group, String module, String newGroupCode, GroupType newGroupType) { + this.group = group; + this.targetModule = module; + this.newGroupCode = newGroupCode; + this.newGroupType = newGroupType; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + if (!model.hasModule(targetModule)) { + throw new CommandException(MESSAGE_INVALID_MODULE_CODE); + } + + Module actualModule = model.getModule(targetModule); + + if (!actualModule.hasGroup(group)) { + throw new CommandException(MESSAGE_INVALID_GROUP_CODE); + } + + if (newGroupCode.equals(group.getIdentifier()) && newGroupType == null) { + throw new CommandException(MESSAGE_NOT_EDITED); + } + + if (newGroupCode.isBlank()) { + throw new CommandException(Group.CONSTRAINTS_GROUP_CODE); + } + + if (!newGroupCode.equals(group.getIdentifier()) && actualModule.hasGroup(new Group(newGroupCode))) { + throw new CommandException(MESSAGE_EDIT_GROUP_FAILURE); + } + Group editedGroup = actualModule.getGroup(group.getIdentifier()); + editedGroup.setIdentifier(newGroupCode); + + if (newGroupType != null) { + editedGroup.setGroupType(newGroupType); + } + + model.updateFilteredGroupList(actualModule.getIdentifier()); + + if (model.getFilteredGroupList() == null || model.getFilteredGroupList().isEmpty()) { + model.setFilteredStudentList(); + } else { + model.updateFilteredStudentList(editedGroup.getIdentifier(), actualModule.getIdentifier()); + } + + return new CommandResult(String.format(MESSAGE_EDIT_GROUP_SUCCESS, targetModule, editedGroup.getIdentifier()), + Action.GOTO_STUDENT); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; // short circuit if same object + } + + if (!(other instanceof EditGroupCommand)) { + return false; // instanceof handles nulls + } + + EditGroupCommand otherCommand = (EditGroupCommand) other; + return group.equals(otherCommand.group) + && targetModule.equals(otherCommand.targetModule); + } +} diff --git a/src/main/java/tatracker/logic/commands/module/AddModuleCommand.java b/src/main/java/tatracker/logic/commands/module/AddModuleCommand.java new file mode 100644 index 00000000000..e54be0b8f18 --- /dev/null +++ b/src/main/java/tatracker/logic/commands/module/AddModuleCommand.java @@ -0,0 +1,76 @@ +package tatracker.logic.commands.module; + +import static java.util.Objects.requireNonNull; +import static tatracker.commons.core.Messages.MESSAGE_DUPLICATE_MODULE; +import static tatracker.logic.parser.Prefixes.MODULE; +import static tatracker.logic.parser.Prefixes.MODULE_NAME; + +import java.util.List; + +import tatracker.logic.commands.Command; +import tatracker.logic.commands.CommandDetails; +import tatracker.logic.commands.CommandResult; +import tatracker.logic.commands.CommandResult.Action; +import tatracker.logic.commands.CommandWords; +import tatracker.logic.commands.exceptions.CommandException; +import tatracker.model.Model; +import tatracker.model.module.Module; + +/** + * Adds a module to the TA-Tracker. + */ +public class AddModuleCommand extends Command { + + public static final CommandDetails DETAILS = new CommandDetails( + CommandWords.MODULE, + CommandWords.ADD_MODEL, + "Adds a module into TA-Tracker", + List.of(MODULE, MODULE_NAME), + List.of(), + MODULE, MODULE_NAME + ); + + public static final String MESSAGE_ADD_MODULE_SUCCESS = "New module added: %s"; + + private final Module toAdd; + + /** + * Creates an AddCommand to add the specified {@code Module} + */ + public AddModuleCommand(Module module) { + requireNonNull(module); + toAdd = module; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + if (toAdd.getIdentifier().isBlank()) { + throw new CommandException(Module.CONSTRAINTS_MODULE_CODE); + } + + if (toAdd.getName().isBlank()) { + throw new CommandException(Module.CONSTRAINTS_MODULE_NAME); + } + + if (model.hasModule(toAdd.getIdentifier())) { + throw new CommandException(MESSAGE_DUPLICATE_MODULE); + } + + model.addModule(toAdd); + + model.updateFilteredGroupList(toAdd.getIdentifier()); + + model.setFilteredStudentList(); + + return new CommandResult(String.format(MESSAGE_ADD_MODULE_SUCCESS, toAdd.getIdentifier()), Action.GOTO_STUDENT); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof AddModuleCommand // instanceof handles nulls + && toAdd.equals(((AddModuleCommand) other).toAdd)); + } +} diff --git a/src/main/java/tatracker/logic/commands/module/DeleteModuleCommand.java b/src/main/java/tatracker/logic/commands/module/DeleteModuleCommand.java new file mode 100644 index 00000000000..d40847a7e58 --- /dev/null +++ b/src/main/java/tatracker/logic/commands/module/DeleteModuleCommand.java @@ -0,0 +1,64 @@ +package tatracker.logic.commands.module; + +import static java.util.Objects.requireNonNull; +import static tatracker.commons.core.Messages.MESSAGE_INVALID_MODULE_CODE; +import static tatracker.logic.parser.Prefixes.MODULE; + +import java.util.List; + +import tatracker.logic.commands.Command; +import tatracker.logic.commands.CommandDetails; +import tatracker.logic.commands.CommandResult; +import tatracker.logic.commands.CommandResult.Action; +import tatracker.logic.commands.CommandWords; +import tatracker.logic.commands.exceptions.CommandException; +import tatracker.model.Model; +import tatracker.model.module.Module; + +/** + * Deletes a module identified using it's module code. + */ +public class DeleteModuleCommand extends Command { + + public static final CommandDetails DETAILS = new CommandDetails( + CommandWords.MODULE, + CommandWords.DELETE_MODEL, + "Deletes the module with the given module code", + List.of(MODULE), + List.of(), + MODULE + ); + + public static final String MESSAGE_DELETE_MODULE_SUCCESS = "Deleted module: %s"; + + private final String module; + + public DeleteModuleCommand(String module) { + this.module = module; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + if (!model.hasModule(module)) { + throw new CommandException(MESSAGE_INVALID_MODULE_CODE); + } + + model.setDefaultStudentViewList(); + + Module moduleToDelete = model.getModule(module); + model.deleteModule(moduleToDelete); + + model.setDefaultStudentViewList(); + + return new CommandResult(String.format(MESSAGE_DELETE_MODULE_SUCCESS, module), Action.GOTO_STUDENT); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof DeleteModuleCommand // instanceof handles nulls + && module.equals(((DeleteModuleCommand) other).module)); // state check + } +} diff --git a/src/main/java/tatracker/logic/commands/module/EditModuleCommand.java b/src/main/java/tatracker/logic/commands/module/EditModuleCommand.java new file mode 100644 index 00000000000..95f0ab16d8f --- /dev/null +++ b/src/main/java/tatracker/logic/commands/module/EditModuleCommand.java @@ -0,0 +1,84 @@ +package tatracker.logic.commands.module; + +import static java.util.Objects.requireNonNull; +import static tatracker.commons.core.Messages.MESSAGE_INVALID_MODULE_CODE; +import static tatracker.logic.parser.Prefixes.MODULE; +import static tatracker.logic.parser.Prefixes.MODULE_NEW_NAME; + +import java.util.List; + +import tatracker.logic.commands.Command; +import tatracker.logic.commands.CommandDetails; +import tatracker.logic.commands.CommandResult; +import tatracker.logic.commands.CommandResult.Action; +import tatracker.logic.commands.CommandWords; +import tatracker.logic.commands.exceptions.CommandException; +import tatracker.model.Model; +import tatracker.model.module.Module; + +/** + * Edits a module identified using it's module code. + */ +public class EditModuleCommand extends Command { + + public static final CommandDetails DETAILS = new CommandDetails( + CommandWords.MODULE, + CommandWords.EDIT_MODEL, + "Edits the module with the given module code", + List.of(MODULE, MODULE_NEW_NAME), + List.of(), + MODULE, MODULE_NEW_NAME + ); + + public static final String MESSAGE_EDIT_MODULE_SUCCESS = "Edited module: %s"; + private static final int FIRST_GROUP_INDEX = 0; + + private final String targetModule; + private final String newName; + + public EditModuleCommand(String module, String newName) { + this.targetModule = module; + this.newName = newName; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + if (!model.hasModule(targetModule)) { + throw new CommandException(MESSAGE_INVALID_MODULE_CODE); + } + + if (newName.isBlank()) { + throw new CommandException(Module.CONSTRAINTS_MODULE_NAME); + } + Module actualModule = model.getModule(targetModule); + actualModule.setName(newName); + + model.showAllModules(); + model.updateFilteredGroupList(actualModule.getIdentifier()); + + if (model.getFilteredGroupList() == null || model.getFilteredGroupList().isEmpty()) { + model.setFilteredStudentList(); + } else { + model.setFilteredStudentList(actualModule.getIdentifier(), FIRST_GROUP_INDEX); + } + + return new CommandResult(String.format(MESSAGE_EDIT_MODULE_SUCCESS, actualModule.getIdentifier()), + Action.GOTO_STUDENT); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; // short circuit if same object + } + + if (!(other instanceof EditModuleCommand)) { + return false; // instanceof handles nulls + } + + EditModuleCommand otherCommand = (EditModuleCommand) other; + return targetModule.equals(otherCommand.targetModule) && newName.equals(otherCommand.newName); + } +} diff --git a/src/main/java/tatracker/logic/commands/session/AddSessionCommand.java b/src/main/java/tatracker/logic/commands/session/AddSessionCommand.java new file mode 100644 index 00000000000..f3d46c2ff44 --- /dev/null +++ b/src/main/java/tatracker/logic/commands/session/AddSessionCommand.java @@ -0,0 +1,86 @@ +package tatracker.logic.commands.session; + +import static java.util.Objects.requireNonNull; +import static tatracker.commons.core.Messages.MESSAGE_DUPLICATE_SESSION; +import static tatracker.commons.core.Messages.MESSAGE_INVALID_MODULE_CODE; +import static tatracker.commons.core.Messages.MESSAGE_INVALID_SESSION_TIMES; +import static tatracker.logic.parser.Prefixes.DATE; +import static tatracker.logic.parser.Prefixes.END_TIME; +import static tatracker.logic.parser.Prefixes.MODULE; +import static tatracker.logic.parser.Prefixes.NOTES; +import static tatracker.logic.parser.Prefixes.RECUR; +import static tatracker.logic.parser.Prefixes.SESSION_TYPE; +import static tatracker.logic.parser.Prefixes.START_TIME; + +import java.time.LocalDateTime; +import java.util.List; + +import tatracker.logic.commands.Command; +import tatracker.logic.commands.CommandDetails; +import tatracker.logic.commands.CommandResult; +import tatracker.logic.commands.CommandResult.Action; +import tatracker.logic.commands.CommandWords; +import tatracker.logic.commands.exceptions.CommandException; +import tatracker.model.Model; +import tatracker.model.session.Session; + +/** + * Adds a session to the TATracker. + */ +public class AddSessionCommand extends Command { + + public static final CommandDetails DETAILS = new CommandDetails( + CommandWords.SESSION, + CommandWords.ADD_MODEL, + "Adds a session into TA-Tracker", + List.of(MODULE), + List.of(START_TIME, END_TIME, DATE, RECUR, SESSION_TYPE, NOTES), + MODULE, START_TIME, END_TIME, DATE, SESSION_TYPE, NOTES + ); + + public static final String MESSAGE_ADD_SESSION_SUCCESS = "New session added: %s"; + + private final Session toAdd; + + /** + * Creates an AddSessionCommand to add the specified {@code Session} + */ + public AddSessionCommand(Session session) { + requireNonNull(session); + toAdd = session; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + LocalDateTime start = toAdd.getStartDateTime(); + LocalDateTime end = toAdd.getEndDateTime(); + + + + if (start.compareTo(end) > 0) { + throw new CommandException(MESSAGE_INVALID_SESSION_TIMES); + } + + if (!model.hasModule(toAdd.getModuleCode())) { + throw new CommandException(MESSAGE_INVALID_MODULE_CODE); + } + + if (model.hasSession(toAdd)) { + throw new CommandException(MESSAGE_DUPLICATE_SESSION); + } + + model.addSession(toAdd); + return new CommandResult( + String.format(MESSAGE_ADD_SESSION_SUCCESS, toAdd.getMinimalDescription()), + Action.GOTO_SESSION); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof AddSessionCommand // instanceof handles nulls + && toAdd.equals(((AddSessionCommand) other).toAdd)); + } +} diff --git a/src/main/java/tatracker/logic/commands/session/DeleteSessionCommand.java b/src/main/java/tatracker/logic/commands/session/DeleteSessionCommand.java new file mode 100644 index 00000000000..34be8e21d47 --- /dev/null +++ b/src/main/java/tatracker/logic/commands/session/DeleteSessionCommand.java @@ -0,0 +1,64 @@ +package tatracker.logic.commands.session; + +import static java.util.Objects.requireNonNull; +import static tatracker.commons.core.Messages.MESSAGE_INVALID_SESSION_DISPLAYED_INDEX; +import static tatracker.logic.parser.Prefixes.INDEX; + +import java.util.List; + +import tatracker.commons.core.index.Index; +import tatracker.logic.commands.Command; +import tatracker.logic.commands.CommandDetails; +import tatracker.logic.commands.CommandResult; +import tatracker.logic.commands.CommandResult.Action; +import tatracker.logic.commands.CommandWords; +import tatracker.logic.commands.exceptions.CommandException; +import tatracker.model.Model; +import tatracker.model.session.Session; + +/** + * Deletes a session identified using it's index. + */ +public class DeleteSessionCommand extends Command { + + public static final CommandDetails DETAILS = new CommandDetails( + CommandWords.SESSION, + CommandWords.DELETE_MODEL, + "Deletes the session at the shown list index", + List.of(INDEX), + List.of(), + INDEX + ); + + public static final String MESSAGE_DELETE_SESSION_SUCCESS = "Deleted session: %s"; + + private final Index index; + + public DeleteSessionCommand(Index index) { + this.index = index; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + List lastShownList = model.getFilteredSessionList(); + + if (index.getZeroBased() >= lastShownList.size()) { + throw new CommandException(MESSAGE_INVALID_SESSION_DISPLAYED_INDEX); + } + + Session sessionToDelete = lastShownList.get(index.getZeroBased()); + model.deleteSession(sessionToDelete); + return new CommandResult( + String.format(MESSAGE_DELETE_SESSION_SUCCESS, sessionToDelete.getMinimalDescription()), + Action.GOTO_SESSION); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof DeleteSessionCommand // instanceof handles nulls + && index.equals(((DeleteSessionCommand) other).index)); // state check + } +} diff --git a/src/main/java/tatracker/logic/commands/session/DoneSessionCommand.java b/src/main/java/tatracker/logic/commands/session/DoneSessionCommand.java new file mode 100644 index 00000000000..54fd7db3e74 --- /dev/null +++ b/src/main/java/tatracker/logic/commands/session/DoneSessionCommand.java @@ -0,0 +1,114 @@ +package tatracker.logic.commands.session; + +import static java.util.Objects.requireNonNull; +import static tatracker.commons.core.Messages.MESSAGE_INVALID_SESSION_DISPLAYED_INDEX; +import static tatracker.logic.parser.Prefixes.INDEX; + +import java.time.LocalDateTime; +import java.util.List; + +import tatracker.commons.core.index.Index; +import tatracker.logic.commands.Command; +import tatracker.logic.commands.CommandDetails; +import tatracker.logic.commands.CommandResult; +import tatracker.logic.commands.CommandResult.Action; +import tatracker.logic.commands.CommandWords; +import tatracker.logic.commands.exceptions.CommandException; +import tatracker.model.Model; +import tatracker.model.session.Session; +import tatracker.model.session.SessionType; + +/** + * Marks a session as done in TAT. + */ +public class DoneSessionCommand extends Command { + + public static final CommandDetails DETAILS = new CommandDetails( + CommandWords.SESSION, + CommandWords.DONE_SESSION, + "Marks the session at the shown list index as done", + List.of(INDEX), + List.of(), + INDEX + ); + + public static final String MESSAGE_DONE_SESSION_SUCCESS = "Session completed: %s"; + public static final String MESSAGE_REPEAT_SESSION_SUCCESS = "\nRepeating session: %s - %s"; + + private final Index index; + + /** + * @param index of the session in the filtered session list to edit + */ + public DoneSessionCommand(Index index) { + requireNonNull(index); + this.index = index; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + List lastShownList = model.getFilteredSessionList(); + + if (index.getZeroBased() >= lastShownList.size()) { + throw new CommandException(MESSAGE_INVALID_SESSION_DISPLAYED_INDEX); + } + + Session session = lastShownList.get(index.getZeroBased()); + session.done(); + + if (session.getRecurring() > 0) { + + int recurring = session.getRecurring(); + LocalDateTime startTime = session.getStartDateTime().plusWeeks(recurring); + LocalDateTime endTime = session.getEndDateTime().plusWeeks(recurring); + SessionType sessionType = session.getSessionType(); + String moduleCode = session.getModuleCode(); + String description = session.getDescription(); + + Session newSession = new Session(startTime, endTime, sessionType, + recurring, moduleCode, description); + + model.addSession(newSession); + model.deleteSession(session); + model.updateFilteredSessionList(Model.PREDICATE_SHOW_ALL_SESSIONS); + model.addDoneSession(session); + model.updateFilteredDoneSessionList(Model.PREDICATE_SHOW_ALL_SESSIONS, ""); + return new CommandResult(getRepeatMessage(newSession), Action.DONE); + } + + model.deleteSession(session); + model.addDoneSession(session); + model.updateFilteredDoneSessionList(Model.PREDICATE_SHOW_ALL_SESSIONS, ""); + + return new CommandResult(String.format(MESSAGE_DONE_SESSION_SUCCESS, session.getMinimalDescription()), + Action.DONE); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof DoneSessionCommand)) { + return false; + } + + // state check + DoneSessionCommand e = (DoneSessionCommand) other; + return index.equals(e.index); + } + + private String getRepeatMessage(Session session) { + String doneMessage = String.format(MESSAGE_DONE_SESSION_SUCCESS, session.getMinimalDescription()); + String repeatMessage = String.format(MESSAGE_REPEAT_SESSION_SUCCESS, + session.getStartDateTimeDescription(), + session.getEndDateTimeDescription()); + + return doneMessage + repeatMessage; + } +} diff --git a/src/main/java/tatracker/logic/commands/session/DoneSessionPredicate.java b/src/main/java/tatracker/logic/commands/session/DoneSessionPredicate.java new file mode 100644 index 00000000000..136420afdaa --- /dev/null +++ b/src/main/java/tatracker/logic/commands/session/DoneSessionPredicate.java @@ -0,0 +1,27 @@ +package tatracker.logic.commands.session; + +import java.util.function.Predicate; + +import tatracker.commons.util.StringUtil; +import tatracker.model.session.Session; + +/** + * Tests that a {@code ModuleCode} matches the keyword given. + */ +public class DoneSessionPredicate implements Predicate { + + private final String moduleCode; + + public DoneSessionPredicate(String moduleCode) { + this.moduleCode = moduleCode; + } + + public String getModuleCode() { + return this.moduleCode; + } + + @Override + public boolean test(Session session) { + return StringUtil.containsWordIgnoreCase(moduleCode, session.getModuleCode()); + } +} diff --git a/src/main/java/tatracker/logic/commands/session/EditSessionCommand.java b/src/main/java/tatracker/logic/commands/session/EditSessionCommand.java new file mode 100644 index 00000000000..5a63216421e --- /dev/null +++ b/src/main/java/tatracker/logic/commands/session/EditSessionCommand.java @@ -0,0 +1,265 @@ +package tatracker.logic.commands.session; + +import static java.util.Objects.requireNonNull; +import static tatracker.commons.core.Messages.MESSAGE_INVALID_SESSION_DISPLAYED_INDEX; +import static tatracker.logic.parser.Prefixes.DATE; +import static tatracker.logic.parser.Prefixes.END_TIME; +import static tatracker.logic.parser.Prefixes.INDEX; +import static tatracker.logic.parser.Prefixes.MODULE; +import static tatracker.logic.parser.Prefixes.NOTES; +import static tatracker.logic.parser.Prefixes.RECUR; +import static tatracker.logic.parser.Prefixes.SESSION_TYPE; +import static tatracker.logic.parser.Prefixes.START_TIME; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; + +import tatracker.commons.core.index.Index; +import tatracker.commons.util.CollectionUtil; +import tatracker.logic.commands.Command; +import tatracker.logic.commands.CommandDetails; +import tatracker.logic.commands.CommandResult; +import tatracker.logic.commands.CommandResult.Action; +import tatracker.logic.commands.CommandWords; +import tatracker.logic.commands.exceptions.CommandException; +import tatracker.model.Model; +import tatracker.model.session.Session; +import tatracker.model.session.SessionType; + + +/** + * Edits the details of an existing session in TAT. + */ +public class EditSessionCommand extends Command { + + public static final CommandDetails DETAILS = new CommandDetails( + CommandWords.SESSION, + CommandWords.EDIT_MODEL, + "Edits a session at the shown list index", + List.of(INDEX), + List.of(MODULE, START_TIME, END_TIME, DATE, RECUR, SESSION_TYPE, NOTES), + MODULE, START_TIME, END_TIME, DATE, SESSION_TYPE, NOTES + ); + + public static final String MESSAGE_EDITED_SESSION_SUCCESS = "Edited session: %s"; + + private final Index index; + private final EditSessionCommand.EditSessionDescriptor editSessionDescriptor; + + /** + * @param index of the session in the filtered session list to edit + * @param editSessionDescriptor details to edit the session with + */ + public EditSessionCommand(Index index, EditSessionCommand.EditSessionDescriptor editSessionDescriptor) { + requireNonNull(index); + requireNonNull(editSessionDescriptor); + + this.index = index; + this.editSessionDescriptor = new EditSessionCommand.EditSessionDescriptor(editSessionDescriptor); + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + List lastShownList = model.getFilteredSessionList(); + + if (index.getZeroBased() >= lastShownList.size()) { + throw new CommandException(MESSAGE_INVALID_SESSION_DISPLAYED_INDEX); + } + + Session sessionToEdit = lastShownList.get(index.getZeroBased()); + Session editedSession = createEditedSession(sessionToEdit, editSessionDescriptor); + + // Session does not have an unique identifier like students/modules, and as such checking if + // the two objects are the same should not be done based on their field values. + // In this case, it is probably best to ignore no-edits. + /* + if (!sessionToEdit.isSameStudent(editedStudent) && model.hasStudent(editedStudent)) { + throw new CommandException(MESSAGE_DUPLICATE_STUDENT); + } + */ + + model.setSession(sessionToEdit, editedSession); + model.updateFilteredSessionList(Model.PREDICATE_SHOW_ALL_SESSIONS); + + return new CommandResult(String.format(MESSAGE_EDITED_SESSION_SUCCESS, editedSession.getMinimalDescription()), + Action.GOTO_SESSION); + } + + /** + * Creates and returns a {@code Session} with the details of {@code sessionToEdit} + * edited with {@code editSessionDescriptor}. + */ + private static Session createEditedSession(Session sessionToEdit, + EditSessionCommand.EditSessionDescriptor editSessionDescriptor) { + assert sessionToEdit != null; + + LocalDateTime startTime = editSessionDescriptor.getStartTime().orElse(sessionToEdit.getStartDateTime()); + LocalDateTime endTime = editSessionDescriptor.getEndTime().orElse(sessionToEdit.getEndDateTime()); + int isRecurring = editSessionDescriptor.getRecurring().orElse(sessionToEdit.getRecurring()); + String moduleCode = editSessionDescriptor.getModuleCode().orElse(sessionToEdit.getModuleCode()); + SessionType type = editSessionDescriptor.getSessionType().orElse(sessionToEdit.getSessionType()); + String description = editSessionDescriptor.getDescription().orElse(sessionToEdit.getDescription()); + + // If date is not updated, reset the date to the original date. + // We need to do this because EditSessionCommandParser is not able to know that is the original date + // to use as default value, so an arbitrary default date is used. + if (!editSessionDescriptor.getIsDateChanged()) { + LocalDate originalDate = sessionToEdit.getDate(); + startTime = LocalDateTime.of(originalDate.getYear(), originalDate.getMonth(), originalDate.getDayOfMonth(), + startTime.getHour(), startTime.getMinute(), startTime.getSecond()); + endTime = LocalDateTime.of(originalDate.getYear(), originalDate.getMonth(), originalDate.getDayOfMonth(), + endTime.getHour(), endTime.getMinute(), endTime.getSecond()); + } + + return new Session(startTime, endTime, type, isRecurring, moduleCode, description); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof EditSessionCommand)) { + return false; + } + + // state check + EditSessionCommand e = (EditSessionCommand) other; + return index.equals(e.index) + && editSessionDescriptor.equals(e.editSessionDescriptor); + } + + /** + * Stores the details to edit the student with. Each non-empty field value will replace the + * corresponding field value of the student. + */ + public static class EditSessionDescriptor { + + private static final int NO_RECURRING_VALUE = -1; + + private LocalDateTime newStartTime; + private LocalDateTime newEndTime; + private boolean isDateChanged; + private int newRecurring = NO_RECURRING_VALUE; + private String newModuleCode; + private SessionType newSessionType; + private String newDescription; + + public EditSessionDescriptor() { + } + + /** + * Copy constructor. + * A defensive copy of {@code tags} is used internally. + */ + public EditSessionDescriptor(EditSessionDescriptor toCopy) { + setStartTime(toCopy.newStartTime); + setEndTime(toCopy.newEndTime); + setRecurring(toCopy.newRecurring); + setModuleCode(toCopy.newModuleCode); + setSessionType(toCopy.newSessionType); + setDescription(toCopy.newDescription); + this.isDateChanged = toCopy.isDateChanged; + } + + /** + * Returns true if at least one field is edited. + */ + public boolean isAnyFieldEdited() { + boolean dateChanged = isDateChanged; + boolean recurringChanged = newRecurring >= 0; + boolean otherVariables = CollectionUtil.isAnyNonNull(newStartTime, newEndTime, + newModuleCode, newDescription, newSessionType); + return dateChanged || otherVariables || recurringChanged; + } + + public void setStartTime(LocalDateTime startTime) { + this.newStartTime = startTime; + } + + public Optional getStartTime() { + return Optional.ofNullable(newStartTime); + } + + public void setEndTime(LocalDateTime endTime) { + this.newEndTime = endTime; + } + + public Optional getEndTime() { + return Optional.ofNullable(newEndTime); + } + + public void setRecurring(int isRecurring) { + this.newRecurring = isRecurring; + } + + public Optional getRecurring() { + if (newRecurring < 0) { + return Optional.empty(); + } else { + return Optional.of(newRecurring); + } + } + + public void setIsDateChanged(boolean isChanged) { + this.isDateChanged = isChanged; + } + + public boolean getIsDateChanged() { + return this.isDateChanged; + } + + public void setModuleCode(String moduleCode) { + this.newModuleCode = moduleCode; + } + + public Optional getModuleCode() { + return Optional.ofNullable(newModuleCode); + } + + public void setSessionType(SessionType sessionType) { + this.newSessionType = sessionType; + } + + public Optional getSessionType() { + return Optional.ofNullable(newSessionType); + } + + public void setDescription(String description) { + this.newDescription = description; + } + + public Optional getDescription() { + return Optional.ofNullable(newDescription); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof EditSessionCommand.EditSessionDescriptor)) { + return false; + } + + // state check + EditSessionCommand.EditSessionDescriptor e = (EditSessionCommand.EditSessionDescriptor) other; + + return getStartTime().equals(e.getStartTime()) + && getEndTime().equals(e.getEndTime()) + && getSessionType().equals(e.getSessionType()) + && getRecurring() == e.getRecurring() + && getDescription().equals(e.getDescription()); + } + } +} diff --git a/src/main/java/tatracker/logic/commands/session/FilterClaimCommand.java b/src/main/java/tatracker/logic/commands/session/FilterClaimCommand.java new file mode 100644 index 00000000000..517177cc6c6 --- /dev/null +++ b/src/main/java/tatracker/logic/commands/session/FilterClaimCommand.java @@ -0,0 +1,59 @@ +package tatracker.logic.commands.session; + +import static java.util.Objects.requireNonNull; +import static tatracker.commons.core.Messages.MESSAGE_INVALID_MODULE_CODE; +import static tatracker.logic.parser.Prefixes.MODULE; + +import java.util.List; + +import tatracker.logic.commands.Command; +import tatracker.logic.commands.CommandDetails; +import tatracker.logic.commands.CommandResult; +import tatracker.logic.commands.CommandResult.Action; +import tatracker.logic.commands.CommandWords; +import tatracker.logic.commands.exceptions.CommandException; +import tatracker.model.Model; + +/** + * Filter Module based on user's inputs. + * Used under TSS view. + */ +public class FilterClaimCommand extends Command { + + public static final CommandDetails DETAILS = new CommandDetails( + CommandWords.CLAIM, + CommandWords.FILTER_MODEL, + "Filters a claim of a particular module", + List.of(MODULE), + List.of(), + MODULE + ); + + public static final String MESSAGE_FILTERED_CLAIMS_SUCCESS = "Filtered claims for module: %s"; + + private final DoneSessionPredicate predicate; + + public FilterClaimCommand(DoneSessionPredicate predicate) { + this.predicate = predicate; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + String module = predicate.getModuleCode(); + if (!model.hasModule(module)) { + throw new CommandException(MESSAGE_INVALID_MODULE_CODE); + } + model.setCurrClaimFilter("Module: " + module); + model.updateFilteredDoneSessionList(predicate, module); + return new CommandResult(String.format(MESSAGE_FILTERED_CLAIMS_SUCCESS, module), + Action.FILTER_CLAIMS); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof FilterClaimCommand // instanceof handles nulls + && predicate.equals(((FilterClaimCommand) other).predicate)); // state check + } +} diff --git a/src/main/java/tatracker/logic/commands/session/FilterSessionCommand.java b/src/main/java/tatracker/logic/commands/session/FilterSessionCommand.java new file mode 100644 index 00000000000..8cb23002dcb --- /dev/null +++ b/src/main/java/tatracker/logic/commands/session/FilterSessionCommand.java @@ -0,0 +1,86 @@ +package tatracker.logic.commands.session; + +import static java.util.Objects.requireNonNull; +import static tatracker.logic.parser.Prefixes.DATE; +import static tatracker.logic.parser.Prefixes.MODULE; +import static tatracker.logic.parser.Prefixes.SESSION_TYPE; + +import java.time.format.DateTimeFormatter; +import java.util.List; + +import tatracker.logic.commands.Command; +import tatracker.logic.commands.CommandDetails; +import tatracker.logic.commands.CommandResult; +import tatracker.logic.commands.CommandResult.Action; +import tatracker.logic.commands.CommandWords; +import tatracker.model.Model; +import tatracker.model.session.SessionPredicate; +import tatracker.model.session.SessionType; + +/** + * Filters sessions based on user's inputs. + */ +public class FilterSessionCommand extends Command { + + public static final CommandDetails DETAILS = new CommandDetails( + CommandWords.SESSION, + CommandWords.FILTER_MODEL, + "Filters all the sessions inside TA-Tracker", + List.of(), + List.of(MODULE, DATE, SESSION_TYPE), + MODULE, DATE, SESSION_TYPE + ); + + public static final String MESSAGE_FILTERED_SESSIONS_SUCCESS = "Filtered session list"; + + // public static final String MESSAGE_NO_SESSIONS_IN_MODULE = "There are no sessions" + // + " for the module with the given module code."; + // public static final String MESSAGE_INVALID_DATE = "There are no sessions with the given date"; + // public static final String MESSAGE_INVALID_SESSIONTYPE = "There are no sessions with the given session type"; + + private final DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern("dd MMM yyyy"); + + private final SessionPredicate predicate; + + public FilterSessionCommand(SessionPredicate predicate) { + this.predicate = predicate; + } + + @Override + public CommandResult execute(Model model) { + requireNonNull(model); + StringBuilder returnMsg = new StringBuilder(MESSAGE_FILTERED_SESSIONS_SUCCESS); + + String dateFilter = predicate.getDate().map(dateFormat::format).orElse(""); + + if (predicate.getDate().isPresent()) { + returnMsg.append("\nDate: ").append(dateFilter); + } + + String moduleFilter = predicate.getModuleCode().orElse(""); + + if (predicate.getModuleCode().isPresent()) { + returnMsg.append("\nModule: ").append(moduleFilter); + } + + String sessionTypeFilter = predicate.getSessionType().map(SessionType::toString).orElse(""); + + if (predicate.getSessionType().isPresent()) { + returnMsg.append("\nType: ").append(sessionTypeFilter); + } + + model.setCurrSessionDateFilter(dateFilter); + model.setCurrSessionModuleFilter(moduleFilter); + model.setCurrSessionTypeFilter(sessionTypeFilter); + model.updateFilteredSessionList(predicate); + + return new CommandResult(returnMsg.toString(), Action.FILTER_SESSION); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof FilterSessionCommand // instanceof handles nulls + && predicate.equals(((FilterSessionCommand) other).predicate)); // state check + } +} diff --git a/src/main/java/tatracker/logic/commands/sort/SortCommand.java b/src/main/java/tatracker/logic/commands/sort/SortCommand.java new file mode 100644 index 00000000000..add186d3a09 --- /dev/null +++ b/src/main/java/tatracker/logic/commands/sort/SortCommand.java @@ -0,0 +1,78 @@ +package tatracker.logic.commands.sort; + +import static java.util.Objects.requireNonNull; +import static tatracker.logic.parser.Prefixes.SORT_TYPE; + +import java.util.List; + +import tatracker.logic.commands.Command; +import tatracker.logic.commands.CommandDetails; +import tatracker.logic.commands.CommandResult; +import tatracker.logic.commands.CommandResult.Action; +import tatracker.logic.commands.CommandWords; +import tatracker.logic.commands.exceptions.CommandException; +import tatracker.model.Model; + +/** + * Command to sort all students in all groups of all modules. + */ +public class SortCommand extends Command { + + public static final CommandDetails DETAILS = new CommandDetails( + CommandWords.SORT, + CommandWords.SORT_ALL, + "Sorts all students in all modules and groups inside TA-Tracker", + List.of(SORT_TYPE), + List.of(), + SORT_TYPE + ); + + public static final String MESSAGE_SORT_ALL_SUCCESS = "All students in each module have been sorted"; + + protected final SortType type; + + public SortCommand(SortType type) { + this.type = type; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + switch(type) { + case ALPHABETIC: + model.sortModulesAlphabetically(); + break; + case MATRIC: + model.sortModulesByMatricNumber(); + break; + case RATING_ASC: + model.sortModulesByRatingAscending(); + break; + case RATING_DESC: + model.sortModulesByRatingDescending(); + break; + default: + throw new CommandException(SortType.MESSAGE_CONSTRAINTS); + } + + model.setDefaultStudentViewList(); + + return new CommandResult(MESSAGE_SORT_ALL_SUCCESS, Action.GOTO_STUDENT); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; // short circuit if same object + } + + if (!(other instanceof SortCommand)) { + return false; // instanceof handles nulls + } + + SortCommand otherCommand = (SortCommand) other; + return type.equals(otherCommand.type); + } + +} diff --git a/src/main/java/tatracker/logic/commands/sort/SortGroupCommand.java b/src/main/java/tatracker/logic/commands/sort/SortGroupCommand.java new file mode 100644 index 00000000000..968d30a4882 --- /dev/null +++ b/src/main/java/tatracker/logic/commands/sort/SortGroupCommand.java @@ -0,0 +1,112 @@ +package tatracker.logic.commands.sort; + +import static java.util.Objects.requireNonNull; +import static tatracker.commons.core.Messages.MESSAGE_INVALID_GROUP_CODE; +import static tatracker.commons.core.Messages.MESSAGE_INVALID_MODULE_CODE; +import static tatracker.logic.parser.Prefixes.GROUP; +import static tatracker.logic.parser.Prefixes.MODULE; +import static tatracker.logic.parser.Prefixes.SORT_TYPE; + +import java.util.List; + +import tatracker.logic.commands.CommandDetails; +import tatracker.logic.commands.CommandResult; +import tatracker.logic.commands.CommandResult.Action; +import tatracker.logic.commands.CommandWords; +import tatracker.logic.commands.exceptions.CommandException; +import tatracker.model.Model; +import tatracker.model.group.Group; +import tatracker.model.module.Module; + +/** + * Sorts all students in the group. + */ +public class SortGroupCommand extends SortCommand { + + public static final CommandDetails DETAILS = new CommandDetails( + CommandWords.SORT, + CommandWords.SORT_GROUP, + "Sorts all students in the given group", + List.of(GROUP, MODULE, SORT_TYPE), + List.of(), + GROUP, MODULE, SORT_TYPE + ); + + public static final String MESSAGE_SORT_GROUP_SUCCESS = "All students in %s [%s] have been sorted"; + + private final String groupCode; + private final String moduleCode; + + public SortGroupCommand(SortType type, String groupCode, String moduleCode) { + super(type); + this.groupCode = groupCode; + this.moduleCode = moduleCode; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + if (!model.hasModule(moduleCode)) { + throw new CommandException(MESSAGE_INVALID_MODULE_CODE); + } + + Module module = model.getModule(moduleCode); + + Group group = new Group(groupCode, null); + + if (!module.hasGroup(group)) { + throw new CommandException(MESSAGE_INVALID_GROUP_CODE); + } + + group = module.getGroup(groupCode); + + switch(type) { + case ALPHABETIC: + model.sortModulesAlphabetically(); + break; + case MATRIC: + model.sortModulesByMatricNumber(); + break; + case RATING_ASC: + model.sortModulesByRatingAscending(); + break; + case RATING_DESC: + model.sortModulesByRatingDescending(); + break; + default: + throw new CommandException(SortType.MESSAGE_CONSTRAINTS); + } + + if (model.getFilteredModuleList().isEmpty()) { + model.setFilteredGroupList(); + model.setFilteredStudentList(); + } else { + model.updateFilteredGroupList(module.getIdentifier()); + if (model.getFilteredGroupList().isEmpty()) { + model.setFilteredStudentList(); + } else { + model.updateFilteredStudentList(group.getIdentifier(), module.getIdentifier()); + } + } + + return new CommandResult(String.format(MESSAGE_SORT_GROUP_SUCCESS, moduleCode, groupCode), Action.GOTO_STUDENT); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; // short circuit if same object + } + + if (!(other instanceof SortGroupCommand)) { + return false; // instanceof handles nulls + } + + SortGroupCommand otherCommand = (SortGroupCommand) other; + return type.equals(otherCommand.type) + && groupCode.equals(otherCommand.groupCode) + && moduleCode.equals(((SortGroupCommand) other).moduleCode); + } + +} diff --git a/src/main/java/tatracker/logic/commands/sort/SortModuleCommand.java b/src/main/java/tatracker/logic/commands/sort/SortModuleCommand.java new file mode 100644 index 00000000000..d7a0114e276 --- /dev/null +++ b/src/main/java/tatracker/logic/commands/sort/SortModuleCommand.java @@ -0,0 +1,100 @@ +package tatracker.logic.commands.sort; + +import static java.util.Objects.requireNonNull; +import static tatracker.commons.core.Messages.MESSAGE_INVALID_MODULE_CODE; +import static tatracker.logic.parser.Prefixes.MODULE; +import static tatracker.logic.parser.Prefixes.SORT_TYPE; + +import java.util.List; + +import tatracker.logic.commands.CommandDetails; +import tatracker.logic.commands.CommandResult; +import tatracker.logic.commands.CommandResult.Action; +import tatracker.logic.commands.CommandWords; +import tatracker.logic.commands.exceptions.CommandException; +import tatracker.model.Model; +import tatracker.model.module.Module; + +/** + * Sorts all students of all groups in a module. + */ +public class SortModuleCommand extends SortCommand { + + + public static final CommandDetails DETAILS = new CommandDetails( + CommandWords.SORT, + CommandWords.SORT_MODULE, + "Sorts all students in all groups of the given module", + List.of(MODULE, SORT_TYPE), + List.of(), + MODULE, SORT_TYPE + ); + + public static final String MESSAGE_SORT_MODULE_SUCCESS = "All students in %s have been sorted"; + public static final int FIRST_GROUP_INDEX = 0; + + private final String moduleCode; + + public SortModuleCommand(SortType type, String moduleCode) { + super(type); + this.moduleCode = moduleCode; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + if (!model.hasModule(moduleCode)) { + throw new CommandException(MESSAGE_INVALID_MODULE_CODE); + } + + Module module = model.getModule(moduleCode); + + switch(type) { + case ALPHABETIC: + model.sortModulesAlphabetically(); + break; + case MATRIC: + model.sortModulesByMatricNumber(); + break; + case RATING_ASC: + model.sortModulesByRatingAscending(); + break; + case RATING_DESC: + model.sortModulesByRatingDescending(); + break; + default: + throw new CommandException(SortType.MESSAGE_CONSTRAINTS); + } + + if (model.getFilteredModuleList().isEmpty()) { + model.setFilteredGroupList(); + model.setFilteredStudentList(); + } else { + model.updateFilteredGroupList(module.getIdentifier()); + if (model.getFilteredGroupList().isEmpty()) { + model.setFilteredStudentList(); + } else { + model.setFilteredStudentList(module.getIdentifier(), FIRST_GROUP_INDEX); + } + } + + return new CommandResult(String.format(MESSAGE_SORT_MODULE_SUCCESS, moduleCode), Action.GOTO_STUDENT); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; // short circuit if same object + } + + if (!(other instanceof SortModuleCommand)) { + return false; // instanceof handles nulls + } + + SortModuleCommand otherCommand = (SortModuleCommand) other; + return moduleCode.equals(otherCommand.moduleCode) + && type.equals(otherCommand.type); + } + +} diff --git a/src/main/java/tatracker/logic/commands/sort/SortType.java b/src/main/java/tatracker/logic/commands/sort/SortType.java new file mode 100644 index 00000000000..6a9df6e6f9d --- /dev/null +++ b/src/main/java/tatracker/logic/commands/sort/SortType.java @@ -0,0 +1,52 @@ +package tatracker.logic.commands.sort; + +import static java.util.Objects.requireNonNull; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Represents the different types of sorting. + */ +public enum SortType { + ALPHABETIC("alphabetically", "alpha", "alphabetical"), + MATRIC("matric"), + RATING_ASC("rating asc", "asc"), + RATING_DESC("rating desc", "desc"); + + public static final String MESSAGE_CONSTRAINTS = + "These are the only sort types: alphabetically, matric, rating asc, rating desc"; + + private static final Map SORT_TYPES = createSortDictionary(); + + private final List names; + + SortType(String ... names) { + this.names = List.of(names); + } + + public static boolean isValidSortType(String test) { + return SORT_TYPES.containsKey(test.toLowerCase()); + } + + public static SortType getSortType(String sortType) { + requireNonNull(sortType); + return SORT_TYPES.get(sortType.toLowerCase()); + } + + /** + * Returns a String to {@code SortType} lookup table. + */ + private static Map createSortDictionary() { + Map dictionary = new HashMap<>(); + + for (SortType type : values()) { + for (String name : type.names) { + dictionary.put(name, type); + } + } + return Collections.unmodifiableMap(dictionary); + } +} diff --git a/src/main/java/tatracker/logic/commands/statistic/ShowStatisticCommand.java b/src/main/java/tatracker/logic/commands/statistic/ShowStatisticCommand.java new file mode 100644 index 00000000000..577bd528b53 --- /dev/null +++ b/src/main/java/tatracker/logic/commands/statistic/ShowStatisticCommand.java @@ -0,0 +1,55 @@ +package tatracker.logic.commands.statistic; + +import static java.util.Objects.requireNonNull; +import static tatracker.commons.core.Messages.MESSAGE_INVALID_MODULE_CODE; +import static tatracker.logic.parser.Prefixes.MODULE_ID; + +import java.util.List; + +import tatracker.logic.commands.Command; +import tatracker.logic.commands.CommandDetails; +import tatracker.logic.commands.CommandResult; +import tatracker.logic.commands.CommandWords; +import tatracker.logic.commands.exceptions.CommandException; +import tatracker.model.Model; + +/** + * Format full help instructions for every command for display. + */ +public class ShowStatisticCommand extends Command { + + public static final CommandDetails DETAILS = new CommandDetails( + CommandWords.REPORT, + "Shows the statistics report of a particular module", + List.of(MODULE_ID), + List.of(), + MODULE_ID + ); + + public static final String MESSAGE_OPENED_STATS = "Opened statistic window"; + + private final String module; + + public ShowStatisticCommand() { + this(null); + } + + public ShowStatisticCommand(String module) { + this.module = module; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + if (module == null) { + return new StatisticCommandResult(MESSAGE_OPENED_STATS, null); + } + + if (!model.hasModule(module)) { + throw new CommandException(MESSAGE_INVALID_MODULE_CODE); + } + + return new StatisticCommandResult(MESSAGE_OPENED_STATS, module); + } +} diff --git a/src/main/java/tatracker/logic/commands/statistic/StatisticCommandResult.java b/src/main/java/tatracker/logic/commands/statistic/StatisticCommandResult.java new file mode 100644 index 00000000000..283ab86fb05 --- /dev/null +++ b/src/main/java/tatracker/logic/commands/statistic/StatisticCommandResult.java @@ -0,0 +1,16 @@ +package tatracker.logic.commands.statistic; + +import tatracker.logic.commands.CommandResult; + +/** + * Shows the statistics window for the selected module. + */ +public class StatisticCommandResult extends CommandResult { + + public final String targetModuleCode; + + public StatisticCommandResult(String feedbackToUser, String moduleCode) { + super(feedbackToUser, Action.NONE); + targetModuleCode = moduleCode; + } +} diff --git a/src/main/java/tatracker/logic/commands/student/AddStudentCommand.java b/src/main/java/tatracker/logic/commands/student/AddStudentCommand.java new file mode 100644 index 00000000000..ebd295fdd7c --- /dev/null +++ b/src/main/java/tatracker/logic/commands/student/AddStudentCommand.java @@ -0,0 +1,107 @@ +package tatracker.logic.commands.student; + +import static java.util.Objects.requireNonNull; +import static tatracker.commons.core.Messages.MESSAGE_DUPLICATE_STUDENT; +import static tatracker.commons.core.Messages.MESSAGE_INVALID_GROUP_CODE; +import static tatracker.commons.core.Messages.MESSAGE_INVALID_MODULE_CODE; +import static tatracker.logic.parser.Prefixes.EMAIL; +import static tatracker.logic.parser.Prefixes.GROUP; +import static tatracker.logic.parser.Prefixes.MATRIC; +import static tatracker.logic.parser.Prefixes.MODULE; +import static tatracker.logic.parser.Prefixes.NAME; +import static tatracker.logic.parser.Prefixes.PHONE; +import static tatracker.logic.parser.Prefixes.RATING; +import static tatracker.logic.parser.Prefixes.TAG; + +import java.util.List; + +import tatracker.logic.commands.Command; +import tatracker.logic.commands.CommandDetails; +import tatracker.logic.commands.CommandResult; +import tatracker.logic.commands.CommandResult.Action; +import tatracker.logic.commands.CommandWords; +import tatracker.logic.commands.exceptions.CommandException; +import tatracker.model.Model; +import tatracker.model.student.Student; + +/** + * Adds a student to the TA-Tracker. + */ +public class AddStudentCommand extends Command { + + public static final CommandDetails DETAILS = new CommandDetails( + CommandWords.STUDENT, + CommandWords.ADD_MODEL, + "Adds a student into the given module group", + List.of(MATRIC, MODULE, GROUP, NAME), + List.of(PHONE, EMAIL, RATING, TAG), + MATRIC, MODULE, GROUP, NAME, PHONE, EMAIL, RATING, TAG + ); + + public static final String MESSAGE_ADD_STUDENT_SUCCESS = "New student added: %s (%s)\nIn %s [%s]"; + + private final Student toAdd; + + private final String targetGroup; + private final String targetModule; + + /** + * Creates an AddStudentCommand to add the specified {@code Student} + */ + public AddStudentCommand(Student student, String group, String module) { + requireNonNull(student); + requireNonNull(group); + requireNonNull(module); + toAdd = student; + targetGroup = group; + targetModule = module; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + if (!model.hasModule(targetModule)) { + throw new CommandException(MESSAGE_INVALID_MODULE_CODE); + } + + if (!model.hasGroup(targetGroup, targetModule)) { + throw new CommandException(MESSAGE_INVALID_GROUP_CODE); + } + + if (model.hasStudent(toAdd.getMatric(), targetGroup, targetModule)) { + throw new CommandException(MESSAGE_DUPLICATE_STUDENT); + } + + model.addStudent(toAdd, targetGroup, targetModule); + + model.updateFilteredGroupList(targetModule); + model.updateFilteredStudentList(targetGroup, targetModule); + + return new CommandResult(getSuccessMessage(toAdd), Action.GOTO_STUDENT); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; // short circuit if same object + } + + if (!(other instanceof AddStudentCommand)) { + return false; // instanceof handles nulls + } + + AddStudentCommand otherCommand = (AddStudentCommand) other; + return toAdd.equals(otherCommand.toAdd) + && targetGroup.equals(otherCommand.targetGroup) + && targetModule.equals(otherCommand.targetModule); + } + + private String getSuccessMessage(Student student) { + return String.format(MESSAGE_ADD_STUDENT_SUCCESS, + student.getName(), + student.getMatric(), + targetModule, + targetGroup); + } +} diff --git a/src/main/java/tatracker/logic/commands/student/DeleteStudentCommand.java b/src/main/java/tatracker/logic/commands/student/DeleteStudentCommand.java new file mode 100644 index 00000000000..838689d9f75 --- /dev/null +++ b/src/main/java/tatracker/logic/commands/student/DeleteStudentCommand.java @@ -0,0 +1,106 @@ +package tatracker.logic.commands.student; + +import static java.util.Objects.requireNonNull; +import static tatracker.commons.core.Messages.MESSAGE_INVALID_GROUP_CODE; +import static tatracker.commons.core.Messages.MESSAGE_INVALID_MODULE_CODE; +import static tatracker.commons.core.Messages.MESSAGE_INVALID_STUDENT; +import static tatracker.logic.parser.Prefixes.GROUP; +import static tatracker.logic.parser.Prefixes.MATRIC; +import static tatracker.logic.parser.Prefixes.MODULE; + +import java.util.List; + +import tatracker.logic.commands.Command; +import tatracker.logic.commands.CommandDetails; +import tatracker.logic.commands.CommandResult; +import tatracker.logic.commands.CommandResult.Action; +import tatracker.logic.commands.CommandWords; +import tatracker.logic.commands.exceptions.CommandException; +import tatracker.model.Model; +import tatracker.model.group.Group; +import tatracker.model.module.Module; +import tatracker.model.student.Matric; +import tatracker.model.student.Student; + +/** + * Deletes a student identified using it's displayed index from the TA-Tracker. + */ +public class DeleteStudentCommand extends Command { + + public static final CommandDetails DETAILS = new CommandDetails( + CommandWords.STUDENT, + CommandWords.DELETE_MODEL, + "Deletes the student with the given matric number from the given module group", + List.of(MATRIC, MODULE, GROUP), + List.of(), + MATRIC, MODULE, GROUP + ); + + public static final String MESSAGE_DELETE_STUDENT_SUCCESS = "Deleted student: %s (%s)\nIn %s [%s]"; + + private final Matric toDelete; + private final String targetGroup; + private final String targetModule; + + public DeleteStudentCommand(Matric matric, String group, String module) { + requireNonNull(matric); + requireNonNull(group); + requireNonNull(module); + this.toDelete = matric; + this.targetGroup = group; + this.targetModule = module; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + if (!model.hasModule(targetModule)) { + throw new CommandException(MESSAGE_INVALID_MODULE_CODE); + } + + if (!model.hasGroup(targetGroup, targetModule)) { + throw new CommandException(MESSAGE_INVALID_GROUP_CODE); + } + + Module actualModule = model.getModule(targetModule); + Group actualGroup = actualModule.getGroup(targetGroup); + + Student studentToDelete = actualGroup.getStudent(toDelete); + + if (studentToDelete == null) { + throw new CommandException(MESSAGE_INVALID_STUDENT); + } + + model.deleteStudent(studentToDelete, targetGroup, targetModule); + + model.updateFilteredGroupList(targetModule); + model.updateFilteredStudentList(targetGroup, targetModule); + + return new CommandResult(getSuccessMessage(studentToDelete), Action.GOTO_STUDENT); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; // short circuit if same object + } + + if (!(other instanceof DeleteStudentCommand)) { + return false; // instanceof handles nulls + } + + DeleteStudentCommand otherCommand = (DeleteStudentCommand) other; + return toDelete.equals(otherCommand.toDelete) + && targetGroup.equals(otherCommand.targetGroup) + && targetModule.equals(otherCommand.targetModule); + } + + private String getSuccessMessage(Student student) { + return String.format(MESSAGE_DELETE_STUDENT_SUCCESS, + student.getName(), + student.getMatric(), + targetModule, + targetGroup); + } +} diff --git a/src/main/java/tatracker/logic/commands/student/EditStudentCommand.java b/src/main/java/tatracker/logic/commands/student/EditStudentCommand.java new file mode 100644 index 00000000000..de93f730b90 --- /dev/null +++ b/src/main/java/tatracker/logic/commands/student/EditStudentCommand.java @@ -0,0 +1,263 @@ +package tatracker.logic.commands.student; + +import static java.util.Objects.requireNonNull; +import static tatracker.commons.core.Messages.MESSAGE_INVALID_GROUP_CODE; +import static tatracker.commons.core.Messages.MESSAGE_INVALID_MODULE_CODE; +import static tatracker.commons.core.Messages.MESSAGE_INVALID_STUDENT; +import static tatracker.commons.core.Messages.MESSAGE_NOT_EDITED; +import static tatracker.logic.parser.Prefixes.EMAIL; +import static tatracker.logic.parser.Prefixes.GROUP; +import static tatracker.logic.parser.Prefixes.MATRIC; +import static tatracker.logic.parser.Prefixes.MODULE; +import static tatracker.logic.parser.Prefixes.NAME; +import static tatracker.logic.parser.Prefixes.PHONE; +import static tatracker.logic.parser.Prefixes.RATING; +import static tatracker.logic.parser.Prefixes.TAG; + +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Optional; +import java.util.Set; + +import tatracker.commons.util.CollectionUtil; +import tatracker.logic.commands.Command; +import tatracker.logic.commands.CommandDetails; +import tatracker.logic.commands.CommandResult; +import tatracker.logic.commands.CommandResult.Action; +import tatracker.logic.commands.CommandWords; +import tatracker.logic.commands.exceptions.CommandException; +import tatracker.model.Model; +import tatracker.model.student.Email; +import tatracker.model.student.Matric; +import tatracker.model.student.Name; +import tatracker.model.student.Phone; +import tatracker.model.student.Rating; +import tatracker.model.student.Student; +import tatracker.model.tag.Tag; + +/** + * Edits the details of an existing student in the TA-Tracker. + */ +public class EditStudentCommand extends Command { + + public static final CommandDetails DETAILS = new CommandDetails( + CommandWords.STUDENT, + CommandWords.EDIT_MODEL, + "Edits the student with the given matric number inside the given module group", + List.of(MATRIC, MODULE, GROUP), + List.of(NAME, PHONE, EMAIL, RATING, TAG), + MATRIC, MODULE, GROUP, NAME, PHONE, EMAIL, RATING, TAG + ); + + public static final String MESSAGE_EDIT_STUDENT_SUCCESS = "Edited student: %s (%s)\nIn %s [%s]"; + + private final Matric matric; + private final String moduleCode; + private final String groupCode; + + private final EditStudentDescriptor editStudentDescriptor; + + /** + * Creates an EditStudentCommand to edit the specified {@code Student} in the given module group. + * @param matric id of the student in the filtered student list to edit + * @param moduleCode of the module containing the student. + * @param groupCode of the group containing the student. + * @param editStudentDescriptor details to edit the student with + */ + public EditStudentCommand(Matric matric, String moduleCode, String groupCode, + EditStudentDescriptor editStudentDescriptor) { + requireNonNull(matric); + requireNonNull(moduleCode); + requireNonNull(groupCode); + requireNonNull(editStudentDescriptor); + + this.matric = matric; + this.moduleCode = moduleCode; + this.groupCode = groupCode; + + this.editStudentDescriptor = new EditStudentDescriptor(editStudentDescriptor); + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + if (!editStudentDescriptor.isAnyFieldEdited()) { + throw new CommandException(MESSAGE_NOT_EDITED); + } + + if (!model.hasModule(moduleCode)) { + throw new CommandException(MESSAGE_INVALID_MODULE_CODE); + } + + if (!model.hasGroup(groupCode, moduleCode)) { + throw new CommandException(MESSAGE_INVALID_GROUP_CODE); + } + + if (!model.hasStudent(matric, groupCode, moduleCode)) { + throw new CommandException(MESSAGE_INVALID_STUDENT); + } + + Student studentToEdit = model.getStudent(matric, groupCode, moduleCode); + Student editedStudent = createEditedStudent(studentToEdit, editStudentDescriptor); + + model.setStudent(studentToEdit, editedStudent, groupCode, moduleCode); + + model.updateFilteredGroupList(moduleCode); + model.updateFilteredStudentList(groupCode, moduleCode); + + return new CommandResult(getSuccessMessage(editedStudent), Action.GOTO_STUDENT); + } + + /** + * Creates and returns a {@code Student} with the details of {@code studentToEdit} + * edited with {@code editStudentDescriptor}. + */ + private static Student createEditedStudent(Student studentToEdit, EditStudentDescriptor editStudentDescriptor) { + assert studentToEdit != null; + + Matric sameMatric = studentToEdit.getMatric(); + + Name updatedName = editStudentDescriptor.getName().orElse(studentToEdit.getName()); + Phone updatedPhone = editStudentDescriptor.getPhone().orElse(studentToEdit.getPhone()); + Email updatedEmail = editStudentDescriptor.getEmail().orElse(studentToEdit.getEmail()); + Rating updatedRating = editStudentDescriptor.getRating().orElse(studentToEdit.getRating()); + Set updatedTags = editStudentDescriptor.getTags().orElse(studentToEdit.getTags()); + + return new Student(sameMatric, updatedName, updatedPhone, updatedEmail, updatedRating, updatedTags); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof EditStudentCommand)) { + return false; + } + + // state check + EditStudentCommand otherCommand = (EditStudentCommand) other; + return matric.equals(otherCommand.matric) + && groupCode.equals(otherCommand.groupCode) + && moduleCode.equals(otherCommand.moduleCode) + && editStudentDescriptor.equals(otherCommand.editStudentDescriptor); + } + + private String getSuccessMessage(Student student) { + return String.format(MESSAGE_EDIT_STUDENT_SUCCESS, + student.getName(), + student.getMatric(), + moduleCode, + groupCode); + } + + /** + * Stores the details to edit the student with. Each non-empty field value will replace the + * corresponding field value of the student. + */ + public static class EditStudentDescriptor { + private Name name; + private Phone phone; + private Email email; + private Rating rating; + private Set tags; + + public EditStudentDescriptor() {} + + /** + * Copy constructor. + * A defensive copy of {@code tags} is used internally. + */ + public EditStudentDescriptor(EditStudentDescriptor toCopy) { + setName(toCopy.name); + setPhone(toCopy.phone); + setEmail(toCopy.email); + setRating(toCopy.rating); + setTags(toCopy.tags); + } + + /** + * Returns true if at least one field is edited. + */ + public boolean isAnyFieldEdited() { + return CollectionUtil.isAnyNonNull(name, phone, email, rating, tags); + } + + public void setName(Name name) { + this.name = name; + } + + public Optional getName() { + return Optional.ofNullable(name); + } + + public void setPhone(Phone phone) { + this.phone = phone; + } + + public Optional getPhone() { + return Optional.ofNullable(phone); + } + + public void setEmail(Email email) { + this.email = email; + } + + public Optional getEmail() { + return Optional.ofNullable(email); + } + + public void setRating(Rating rating) { + this.rating = rating; + } + + public Optional getRating() { + return Optional.ofNullable(rating); + } + + /** + * Sets {@code tags} to this object's {@code tags}. + * A defensive copy of {@code tags} is used internally. + */ + public void setTags(Set tags) { + this.tags = (tags != null) ? new HashSet<>(tags) : null; + } + + /** + * Returns an unmodifiable tag set, which throws {@code UnsupportedOperationException} + * if modification is attempted. + * Returns {@code Optional#empty()} if {@code tags} is null. + */ + public Optional> getTags() { + return (tags != null) ? Optional.of(Collections.unmodifiableSet(tags)) : Optional.empty(); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof EditStudentDescriptor)) { + return false; + } + + // state check + EditStudentDescriptor e = (EditStudentDescriptor) other; + + return getName().equals(e.getName()) + && getPhone().equals(e.getPhone()) + && getEmail().equals(e.getEmail()) + && getRating().equals(e.getRating()) + && getTags().equals(e.getTags()); + } + } +} + diff --git a/src/main/java/tatracker/logic/commands/student/FilterStudentCommand.java b/src/main/java/tatracker/logic/commands/student/FilterStudentCommand.java new file mode 100644 index 00000000000..3e90f0d82e0 --- /dev/null +++ b/src/main/java/tatracker/logic/commands/student/FilterStudentCommand.java @@ -0,0 +1,138 @@ +package tatracker.logic.commands.student; + +import static java.util.Objects.requireNonNull; +import static tatracker.commons.core.Messages.MESSAGE_INVALID_GROUP_CODE; +import static tatracker.commons.core.Messages.MESSAGE_INVALID_MODULE_CODE; +import static tatracker.logic.parser.Prefixes.GROUP; +import static tatracker.logic.parser.Prefixes.MODULE; + +import java.util.List; + +import tatracker.logic.commands.Command; +import tatracker.logic.commands.CommandDetails; +import tatracker.logic.commands.CommandResult; +import tatracker.logic.commands.CommandResult.Action; +import tatracker.logic.commands.CommandWords; +import tatracker.logic.commands.exceptions.CommandException; +import tatracker.model.Model; + +/** + *Filters by Group and/or Module under Student View. + * A module can contains many groups. + * A group contains students related it its group and module. + */ +public class FilterStudentCommand extends Command { + + public static final CommandDetails DETAILS = new CommandDetails( + CommandWords.STUDENT, + CommandWords.FILTER_MODEL, + "Filters the students inside TA-Tracker", + List.of(), + List.of(GROUP, MODULE), + GROUP, MODULE + ); + + public static final String MESSAGE_FILTERED_MODULES_SUCCESS = "Filtered all students in module: %s"; + public static final String MESSAGE_FILTERED_GROUPS_SUCCESS = "Filtered all students in module group: %s [%s]"; + + public static final String MESSAGE_NO_STUDENTS_IN_MODULE = "There are no students in the module" + + " with the given module code"; + public static final String MESSAGE_NO_STUDENTS_IN_GROUP = "There are no students in the module group" + + " with the given group code"; + + public static final int FIRST_GROUP_INDEX = 0; + + private final String moduleCode; + private final String groupCode; + + public FilterStudentCommand(String moduleCode, String groupCode) { + + this.moduleCode = moduleCode; + this.groupCode = groupCode; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + CommandResult returnMsg = new CommandResult(MESSAGE_INVALID_MODULE_CODE, Action.FILTER_STUDENT); + + boolean hasModule = !moduleCode.isBlank(); + boolean hasGroup = hasModule && !groupCode.isBlank(); + + if (hasGroup) { + returnMsg = filterGroup(model); + } else if (hasModule) { + returnMsg = filterModule(model); + } + return returnMsg; + } + + /** + * Filter Students when both Group code and Module Code given by User. + * @return a Successful Command Result + * @throws CommandException if the module or group code is invalid. + */ + public CommandResult filterGroup(Model model) throws CommandException { + requireNonNull(model); + + if (!model.hasModule(moduleCode)) { + throw new CommandException(MESSAGE_INVALID_MODULE_CODE); + } else { + if (!model.hasGroup(groupCode, moduleCode)) { + model.setFilteredStudentList(); + throw new CommandException(MESSAGE_INVALID_GROUP_CODE); + } else { + String result = buildParams(groupCode, moduleCode); + model.setCurrStudentFilter(result); + model.updateFilteredGroupList(moduleCode); + model.updateFilteredStudentList(groupCode, moduleCode); + } + } + return new CommandResult(String.format(MESSAGE_FILTERED_GROUPS_SUCCESS, moduleCode, groupCode), + Action.FILTER_STUDENT); + } + + /** + * Filter Students if users only give Module Code. + * Module's first group will automatically be used. + * @return filtered students + * @throws CommandException if the module code is invalid. + */ + public CommandResult filterModule(Model model) throws CommandException { + requireNonNull(model); + + if (!model.hasModule(moduleCode)) { + throw new CommandException(MESSAGE_INVALID_MODULE_CODE); + } else { + model.updateFilteredGroupList(moduleCode); + if (model.getFilteredGroupList().isEmpty()) { + model.setFilteredStudentList(); + throw new CommandException(MESSAGE_NO_STUDENTS_IN_MODULE); + } else { + model.setCurrStudentFilter("Module Code: " + moduleCode); + model.setFilteredStudentList(moduleCode, FIRST_GROUP_INDEX); + } + } + return new CommandResult(String.format(MESSAGE_FILTERED_MODULES_SUCCESS, moduleCode), Action.FILTER_STUDENT); + } + + /** + *Creates a string consisting of all the params inputted by users. + */ + public String buildParams(String group, String module) { + StringBuilder builder = new StringBuilder(); + builder.append("Module Code: ").append(module).append("\n"); + builder.append("Group Code: ").append(group).append("\n"); + String result = builder.toString(); + return result; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof FilterStudentCommand // instanceof handles nulls + && (moduleCode.equals(((FilterStudentCommand) other).moduleCode) + && groupCode.equals(((FilterStudentCommand) other).groupCode))); // state check + } +} diff --git a/src/main/java/seedu/address/logic/parser/ArgumentMultimap.java b/src/main/java/tatracker/logic/parser/ArgumentMultimap.java similarity index 86% rename from src/main/java/seedu/address/logic/parser/ArgumentMultimap.java rename to src/main/java/tatracker/logic/parser/ArgumentMultimap.java index 954c8e18f8e..5139e464328 100644 --- a/src/main/java/seedu/address/logic/parser/ArgumentMultimap.java +++ b/src/main/java/tatracker/logic/parser/ArgumentMultimap.java @@ -1,10 +1,11 @@ -package seedu.address.logic.parser; +package tatracker.logic.parser; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.stream.Stream; /** * Stores mapping of prefixes to their respective arguments. @@ -57,4 +58,12 @@ public List getAllValues(Prefix prefix) { public String getPreamble() { return getValue(new Prefix("")).orElse(""); } + + /** + * Returns true if none of the prefixes contains empty {@code Optional} values in the given + * {@code ArgumentMultimap}. + */ + public boolean arePrefixesPresent(Prefix... prefixes) { + return Stream.of(prefixes).allMatch(prefix -> getValue(prefix).isPresent()); + } } diff --git a/src/main/java/seedu/address/logic/parser/ArgumentTokenizer.java b/src/main/java/tatracker/logic/parser/ArgumentTokenizer.java similarity index 99% rename from src/main/java/seedu/address/logic/parser/ArgumentTokenizer.java rename to src/main/java/tatracker/logic/parser/ArgumentTokenizer.java index 5c9aebfa488..9f94790b39a 100644 --- a/src/main/java/seedu/address/logic/parser/ArgumentTokenizer.java +++ b/src/main/java/tatracker/logic/parser/ArgumentTokenizer.java @@ -1,4 +1,4 @@ -package seedu.address.logic.parser; +package tatracker.logic.parser; import java.util.ArrayList; import java.util.Arrays; diff --git a/src/main/java/seedu/address/logic/parser/Parser.java b/src/main/java/tatracker/logic/parser/Parser.java similarity index 72% rename from src/main/java/seedu/address/logic/parser/Parser.java rename to src/main/java/tatracker/logic/parser/Parser.java index d6551ad8e3f..0b0ebc13596 100644 --- a/src/main/java/seedu/address/logic/parser/Parser.java +++ b/src/main/java/tatracker/logic/parser/Parser.java @@ -1,7 +1,7 @@ -package seedu.address.logic.parser; +package tatracker.logic.parser; -import seedu.address.logic.commands.Command; -import seedu.address.logic.parser.exceptions.ParseException; +import tatracker.logic.commands.Command; +import tatracker.logic.parser.exceptions.ParseException; /** * Represents a Parser that is able to parse user input into a {@code Command} of type {@code T}. diff --git a/src/main/java/tatracker/logic/parser/ParserUtil.java b/src/main/java/tatracker/logic/parser/ParserUtil.java new file mode 100644 index 00000000000..e96a788535f --- /dev/null +++ b/src/main/java/tatracker/logic/parser/ParserUtil.java @@ -0,0 +1,315 @@ +package tatracker.logic.parser; + +import static java.util.Objects.requireNonNull; + +import java.time.LocalDate; +import java.time.LocalTime; +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; + +import tatracker.commons.core.index.Index; +import tatracker.commons.util.DateTimeUtil; +import tatracker.commons.util.StringUtil; +import tatracker.logic.commands.commons.GotoCommand.Tab; +import tatracker.logic.commands.sort.SortType; +import tatracker.logic.parser.exceptions.ParseException; +import tatracker.model.TaTracker; +import tatracker.model.group.GroupType; +import tatracker.model.session.Session; +import tatracker.model.session.SessionType; +import tatracker.model.student.Email; +import tatracker.model.student.Matric; +import tatracker.model.student.Name; +import tatracker.model.student.Phone; +import tatracker.model.student.Rating; +import tatracker.model.tag.Tag; + +/** + * Contains utility methods used for parsing strings in the various *Parser classes. + */ +public class ParserUtil { + + public static final String MESSAGE_INVALID_UNSIGNED_INT = "Number is not an unsigned integer."; + + /** + * Parses a {@code String integer} into an integer primitive. + * This is different from the standard Java version as it does not + * allow any signed values (i.e. the following values cannot be parsed: +5, -2). + */ + public static int parseUnsignedInteger(String integer) throws ParseException { + requireNonNull(integer); + String trimmedInteger = integer.trim(); + + if (!StringUtil.isUnsignedInteger(trimmedInteger)) { + throw new ParseException(MESSAGE_INVALID_UNSIGNED_INT); + } + + return Integer.parseUnsignedInt(trimmedInteger); + } + + /** + * Parses {@code oneBasedIndex} into an {@code Index} and returns it. Leading and trailing whitespaces will be + * trimmed. + * + * @throws ParseException if the specified index is invalid (not non-zero unsigned integer). + */ + public static Index parseIndex(String oneBasedIndex) throws ParseException { + String trimmedIndex = oneBasedIndex.trim(); + if (!StringUtil.isNonZeroUnsignedInteger(trimmedIndex)) { + throw new ParseException(Index.MESSAGE_CONSTRAINTS); + } + return Index.fromOneBased(Integer.parseInt(trimmedIndex)); + } + + /** + * Parses a {@code String name} into a {@code Name}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code name} is invalid. + */ + public static Name parseName(String name) throws ParseException { + requireNonNull(name); + String trimmedName = name.trim().toLowerCase(); + StringBuilder nameBuilder = new StringBuilder(); + String[] nameParts = trimmedName.split(" "); + + for (int i = 0; i < nameParts.length; ++i) { + if (nameParts[i].isBlank()) { + continue; + } + nameBuilder.append(Character.toUpperCase(nameParts[i].charAt(0))); + nameBuilder.append(nameParts[i].substring(1) + " "); + } + + trimmedName = nameBuilder.toString().trim(); + + if (!Name.isValidName(trimmedName)) { + throw new ParseException(Name.MESSAGE_CONSTRAINTS); + } + return new Name(trimmedName); + } + + /** + * Parses a {@code String phone} into a {@code Phone}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code phone} is invalid. + */ + public static Phone parsePhone(String phone) throws ParseException { + requireNonNull(phone); + String trimmedPhone = phone.trim(); + if (!Phone.isValidPhone(trimmedPhone)) { + throw new ParseException(Phone.MESSAGE_CONSTRAINTS); + } + return new Phone(trimmedPhone); + } + + /** + * Parses a {@code String email} into an {@code Email}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code email} is invalid. + */ + public static Email parseEmail(String email) throws ParseException { + requireNonNull(email); + String trimmedEmail = email.trim(); + if (!Email.isValidEmail(trimmedEmail)) { + throw new ParseException(Email.MESSAGE_CONSTRAINTS); + } + return new Email(trimmedEmail); + } + + /** + * Parses a {@code String matric} into an {@code Matric}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code matric} is invalid. + */ + public static Matric parseMatric(String matric) throws ParseException { + requireNonNull(matric); + String trimmedMatric = matric.trim(); + if (!Matric.isValidMatric(trimmedMatric)) { + throw new ParseException(Matric.MESSAGE_CONSTRAINTS); + } + return new Matric(trimmedMatric); + } + + /** + * Parses a {@code String tag} into a {@code Tag}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code tag} is invalid. + */ + public static Tag parseTag(String tag) throws ParseException { + requireNonNull(tag); + String trimmedTag = tag.trim(); + if (!Tag.isValidTagName(trimmedTag)) { + throw new ParseException(Tag.MESSAGE_CONSTRAINTS); + } + return new Tag(trimmedTag); + } + + /** + * Parses {@code Collection tags} into a {@code Set}. + */ + public static Set parseTags(Collection tags) throws ParseException { + requireNonNull(tags); + final Set tagSet = new HashSet<>(); + for (String tagName : tags) { + tagSet.add(parseTag(tagName)); + } + return tagSet; + } + + /** + * Parses a {@code String date} into a {@code LocalDate} + * + * @throws ParseException if the given {@code date} is invalid. + */ + public static LocalDate parseDate(String date) throws ParseException { + requireNonNull(date); + String trimmedDate = date.trim(); + + if (!DateTimeUtil.isDate(trimmedDate)) { + throw new ParseException(DateTimeUtil.CONSTRAINTS_DATE); + } + return LocalDate.parse(trimmedDate); + } + + /** + * Parses a {@code String time} into a {@code LocalTime} + * + * @throws ParseException if the given {@code time} is invalid. + */ + public static LocalTime parseTime(String time) throws ParseException { + requireNonNull(time); + String trimmedTime = time.trim(); + + if (!DateTimeUtil.isTime(trimmedTime)) { + throw new ParseException(DateTimeUtil.CONSTRAINTS_TIME); + } + return LocalTime.parse(trimmedTime); + } + + /** + * Parses a {@code String sessionType} into a {@code SessionType} + */ + public static SessionType parseSessionType(String sessionType) throws ParseException { + requireNonNull(sessionType); + String trimmedType = sessionType.trim(); + + switch (trimmedType) { + case "tutorial": + return SessionType.TUTORIAL; + case "lab": + return SessionType.LAB; + case "consultation": + return SessionType.CONSULTATION; + case "grading": + return SessionType.GRADING; + case "preparation": + return SessionType.PREPARATION; + case "other": + return SessionType.OTHER; + default: + throw new ParseException(SessionType.MESSAGE_CONSTRAINTS); + } + } + + /** + * Parses and returns Group Type of group. + */ + public static GroupType parseGroupType(String type) throws ParseException { + requireNonNull(type); + String trimmedType = type.trim(); + + switch (trimmedType) { + case "tutorial": + return GroupType.TUTORIAL; + case "lab": + return GroupType.LAB; + case "recitation": + return GroupType.RECITATION; + case "other": + return GroupType.OTHER; + default: + throw new ParseException(GroupType.MESSAGE_CONSTRAINTS); + } + } + + /** + * Parses and returns Group Type of group. + */ + public static SortType parseSortType(String type) throws ParseException { + requireNonNull(type); + String trimmedType = type.trim(); + + if (!SortType.isValidSortType(trimmedType)) { + throw new ParseException(SortType.MESSAGE_CONSTRAINTS); + } + return SortType.getSortType(trimmedType); + } + + /** + * Parses a {@code String rating} into a {@code Rating} + */ + public static Rating parseRating(String rating) throws ParseException { + requireNonNull(rating); + String trimmedRating = rating.trim(); + + if (!Rating.isValidRating(trimmedRating)) { + throw new ParseException(Rating.MESSAGE_CONSTRAINTS); + } + + int parsedRating = Integer.parseUnsignedInt(trimmedRating); + return new Rating(parsedRating); + } + + /** + * Parses a {@code String numWeeks} into a number of weeks. + */ + public static int parseNumWeeks(String numWeeks) throws ParseException { + try { + return parseUnsignedInteger(numWeeks); + } catch (ParseException pe) { + throw new ParseException(Session.CONSTRAINTS_RECURRING_WEEKS); + } + } + + /** + * Parses and returns the tab name specified by the user in the goto command + * + * @param tabName user input + * @return the tab specified by the user + * @throws ParseException invalid tab name + */ + public static Tab parseTabName(String tabName) throws ParseException { + requireNonNull(tabName); + String trimmedType = tabName.trim(); + + if (!Tab.isValidTab(trimmedType)) { + throw new ParseException(Tab.MESSAGE_CONSTRAINTS); + } + return Tab.getTab(trimmedType); + } + + /** + * Parses and returns the pay rate specified by the user in the setrate command + * + * @param rate user input + * @return the pay rate specified by the user + * @throws ParseException invalid pay rate + */ + public static int parseRate(String rate) throws ParseException { + try { + int parsedRate = parseUnsignedInteger(rate); + if (parsedRate <= 0) { + throw new NumberFormatException(); + } + return parsedRate; + } catch (NumberFormatException | ParseException e) { + throw new ParseException(TaTracker.CONSTRAINTS_RATE); + } + } +} diff --git a/src/main/java/seedu/address/logic/parser/Prefix.java b/src/main/java/tatracker/logic/parser/Prefix.java similarity index 64% rename from src/main/java/seedu/address/logic/parser/Prefix.java rename to src/main/java/tatracker/logic/parser/Prefix.java index c859d5fa5db..53dadd45c21 100644 --- a/src/main/java/seedu/address/logic/parser/Prefix.java +++ b/src/main/java/tatracker/logic/parser/Prefix.java @@ -1,20 +1,33 @@ -package seedu.address.logic.parser; +package tatracker.logic.parser; /** * A prefix that marks the beginning of an argument in an arguments string. * E.g. 't/' in 'add James t/ friend'. */ public class Prefix { + + private static final String NO_INFO = ""; + private final String prefix; + private final String info; public Prefix(String prefix) { + this(prefix, NO_INFO); + } + + public Prefix(String prefix, String info) { this.prefix = prefix; + this.info = info; } public String getPrefix() { return prefix; } + public String getInfo() { + return info; + } + public String toString() { return getPrefix(); } @@ -34,6 +47,7 @@ public boolean equals(Object obj) { } Prefix otherPrefix = (Prefix) obj; - return otherPrefix.getPrefix().equals(getPrefix()); + return otherPrefix.getPrefix().equals(getPrefix()) + && otherPrefix.getInfo().equals(getInfo()); } } diff --git a/src/main/java/tatracker/logic/parser/PrefixDetails.java b/src/main/java/tatracker/logic/parser/PrefixDetails.java new file mode 100644 index 00000000000..cd890fab775 --- /dev/null +++ b/src/main/java/tatracker/logic/parser/PrefixDetails.java @@ -0,0 +1,81 @@ +package tatracker.logic.parser; + +import java.util.List; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * Contains Command Line Interface (CLI) syntax definitions common to multiple commands + */ +public class PrefixDetails { + private static final String OPTIONAL_FORMAT = "[%s]"; + private static final String MULTIPLE_FORMAT = "%s..."; + + private final Prefix prefix; + + private final String constraint; + private final Function validator; + + private final List examples; + + PrefixDetails(Prefix prefix, String constraint, Function validator, + String ... examples) { + this.prefix = prefix; + this.constraint = constraint; + this.validator = validator; + this.examples = List.of(examples); + } + + PrefixDetails(Prefix prefix, String ... examples) { + this(prefix, "", value -> true, examples); + } + + public Prefix getPrefix() { + return prefix; + } + + public String getInfo() { + return prefix.getInfo(); + } + + public String getConstraint() { + return constraint; + } + + public Function getValidator() { + return validator; + } + + public List getExamples() { + return examples; + } + + public boolean isValidValue(String test) { + return validator.apply(test); + } + + public String getPrefixWithInfo() { + return addMultiplePrefixInfo(prefix + getInfo()); + } + + public String getPrefixWithOptionalInfo() { + return addMultiplePrefixInfo(String.format(OPTIONAL_FORMAT, prefix + getInfo())); + } + + public String getPrefixWithExamples() { + return examples.stream() + .map(e -> prefix + e) + .collect(Collectors.joining(" ")); + } + + /** + * Adds an ellipsis for all prefixes that can be repeated. + */ + private String addMultiplePrefixInfo(String usage) { + if (examples.size() <= 1) { + return usage; + } else { + return String.format(MULTIPLE_FORMAT, usage); + } + } +} diff --git a/src/main/java/tatracker/logic/parser/PrefixDictionary.java b/src/main/java/tatracker/logic/parser/PrefixDictionary.java new file mode 100644 index 00000000000..da2a745d5a1 --- /dev/null +++ b/src/main/java/tatracker/logic/parser/PrefixDictionary.java @@ -0,0 +1,285 @@ +package tatracker.logic.parser; + +import static java.util.Objects.requireNonNull; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import tatracker.commons.core.index.Index; +import tatracker.commons.util.DateTimeUtil; +import tatracker.commons.util.StringUtil; +import tatracker.logic.commands.commons.GotoCommand.Tab; +import tatracker.logic.commands.sort.SortType; +import tatracker.model.TaTracker; +import tatracker.model.group.Group; +import tatracker.model.group.GroupType; +import tatracker.model.module.Module; +import tatracker.model.session.Session; +import tatracker.model.session.SessionType; +import tatracker.model.student.Email; +import tatracker.model.student.Matric; +import tatracker.model.student.Name; +import tatracker.model.student.Phone; +import tatracker.model.student.Rating; +import tatracker.model.tag.Tag; + +/** + * Contains Command Line Interface (CLI) syntax definitions common to multiple commands + */ +public class PrefixDictionary { + private static final List PREFIX_DETAILS = List.of( + /* Placeholders */ + new PrefixDetails(Prefixes.INDEX, + Index.MESSAGE_CONSTRAINTS, StringUtil::isNonZeroUnsignedInteger, + "1" + ), + new PrefixDetails(Prefixes.TAB_NAME, + Tab.MESSAGE_CONSTRAINTS, Tab::isValidTab, + "student" + ), + new PrefixDetails(Prefixes.RATE, + TaTracker.CONSTRAINTS_RATE, StringUtil::isNonZeroUnsignedInteger, + "40" + ), + + /* Session definitions */ + new PrefixDetails(Prefixes.START_TIME, + DateTimeUtil.CONSTRAINTS_TIME, DateTimeUtil::isTime, + "14:00" + ), + new PrefixDetails(Prefixes.END_TIME, + DateTimeUtil.CONSTRAINTS_TIME, DateTimeUtil::isTime, + "16:00" + ), + new PrefixDetails(Prefixes.DATE, + DateTimeUtil.CONSTRAINTS_DATE, DateTimeUtil::isDate, + "2020-02-19" + ), + new PrefixDetails(Prefixes.RECUR, + Session.CONSTRAINTS_RECURRING_WEEKS, StringUtil::isUnsignedInteger, + "1" // Number of weeks + ), + new PrefixDetails(Prefixes.SESSION_TYPE, + SessionType.MESSAGE_CONSTRAINTS, SessionType::isValidSessionType, + "grading" + ), + new PrefixDetails(Prefixes.NOTES, + "Location: PLAB 04" + ), + + /* Module definitions */ + new PrefixDetails(Prefixes.MODULE, + Module.CONSTRAINTS_MODULE_CODE, value -> !value.isBlank(), + "CS3243" + ), + new PrefixDetails(Prefixes.MODULE_ID, + Module.CONSTRAINTS_MODULE_CODE, value -> !value.isBlank(), + "CS3243" + ), + new PrefixDetails(Prefixes.MODULE_NAME, + Module.CONSTRAINTS_MODULE_NAME, value -> !value.isBlank(), + "Introduction to AI" + ), + new PrefixDetails(Prefixes.MODULE_NEW_NAME, + Module.CONSTRAINTS_MODULE_NAME, value -> !value.isBlank(), + "Software Engineering" + ), + + /* Group definitions */ + new PrefixDetails(Prefixes.GROUP, + Group.CONSTRAINTS_GROUP_CODE, value -> !value.isBlank(), + "G06" + ), + new PrefixDetails(Prefixes.NEWGROUP, + Group.CONSTRAINTS_GROUP_CODE, value -> !value.isBlank(), + "G05" + ), + new PrefixDetails(Prefixes.TYPE, + GroupType.MESSAGE_CONSTRAINTS, GroupType::isValidGroupType, + "tutorial" + ), + new PrefixDetails(Prefixes.NEWTYPE, + GroupType.MESSAGE_CONSTRAINTS, GroupType::isValidGroupType, + "lab" + ), + + /* Student definitions */ + new PrefixDetails(Prefixes.MATRIC, + Matric.MESSAGE_CONSTRAINTS, Matric::isValidMatric, + "A0181234G" + ), + new PrefixDetails(Prefixes.NAME, + Name.MESSAGE_CONSTRAINTS, Name::isValidName, + "John Doe" + ), + new PrefixDetails(Prefixes.PHONE, + Phone.MESSAGE_CONSTRAINTS, Phone::isValidPhone, + "98765432" + ), + new PrefixDetails(Prefixes.EMAIL, + Email.MESSAGE_CONSTRAINTS, Email::isValidEmail, + "johnd@example.com" + ), + new PrefixDetails(Prefixes.RATING, + Rating.MESSAGE_CONSTRAINTS, Rating::isValidRating, + "3" + ), + new PrefixDetails(Prefixes.TAG, + Tag.MESSAGE_CONSTRAINTS, Tag::isValidTagName, + "friends", "owes money" + ), + + /* Action definitions */ + new PrefixDetails(Prefixes.SORT_TYPE, + SortType.MESSAGE_CONSTRAINTS, SortType::isValidSortType, + "alphabetically" + ) + ); + + /** Collects all the prefix entries into a lookup table. */ + private static final Map PREFIXES = PREFIX_DETAILS + .stream() + .collect(Collectors.toUnmodifiableMap(PrefixDetails::getPrefix, + entry -> entry, (key1, key2) -> { + throw new IllegalArgumentException("PrefixDictionary: cannot have duplicate prefixes"); + })); + + private static final PrefixDictionary EMPTY_DICTIONARY = new PrefixDictionary(); + + private static final String MESSAGE_UNKNOWN_ENTRY = "PrefixDictionary Instance: does not contain the prefix: %s"; + + // ======== Instance Declaration ======== + + private final Map dictionary; + + private final List parameters; + private final List optionals; + + public PrefixDictionary() { + this(List.of(), List.of()); + } + + public PrefixDictionary(List parameters) { + this(parameters, List.of()); + } + + public PrefixDictionary(List parameters, List optionals) { + requireNonNull(parameters); + requireNonNull(optionals); + + this.parameters = List.copyOf(parameters); + this.optionals = List.copyOf(optionals); + + this.dictionary = createDictionary(this.parameters, this.optionals); + } + + /** + * Creates a new Prefix dictionary for syntax matching. + * + * If there are duplicate prefixes, the first inserted prefix is kept, and the rest are skipped. + * Duplicates are skipped since repeated prefixes must mean the same thing, or else + * it will be unclear what the prefix represents. + * + * Each prefix in {@code parameters} and {@code optionals} must have a matching {@code PrefixDetails}. + */ + private static Map createDictionary(List parameters, List optionals) { + List arguments = new ArrayList<>(parameters); + arguments.addAll(optionals); + + if (!arguments.stream().allMatch(PREFIXES::containsKey)) { + throw new IllegalArgumentException( + "PrefixDictionary Instance: prefix is not recognized"); + } + + return arguments.stream() + .collect(Collectors.toUnmodifiableMap(Prefix::getPrefix, + PREFIXES::get, (key1, key2) -> { + throw new IllegalArgumentException( + "PrefixDictionary Instance: arguments cannot have the same prefix"); + })); + } + + /** + * Returns the same unmodifiable empty {@code PrefixDictionary}. + */ + public static PrefixDictionary getEmptyDictionary() { + return EMPTY_DICTIONARY; + } + + public List getParameters() { + return parameters; + } + + public List getOptionals() { + return optionals; + } + + /** + * Returns true if the dictionary requires a preamble. + */ + public boolean hasPreamble() { + return dictionary.keySet().stream().anyMatch(String::isBlank); + } + + /** + * Returns true if the {@code prefix} is part of the dictionary. + */ + public boolean hasPrefixDetails(String prefix) { + requireNonNull(prefix); + return dictionary.containsKey(prefix); + } + + public PrefixDetails getPrefixDetails(String prefix) { + requireNonNull(prefix); + return dictionary.get(prefix); + } + + /** + * Returns true if the {@code prefix} is part of the dictionary. + */ + public String getPrefixesWithInfo() { + Stream arguments = Stream.concat( + parameters.stream() + .map(Prefix::getPrefix) + .map(dictionary::get) + .map(PrefixDetails::getPrefixWithInfo), + optionals.stream() + .map(Prefix::getPrefix) + .map(dictionary::get) + .map(PrefixDetails::getPrefixWithOptionalInfo) + ); + return arguments.collect(Collectors.joining(" ")); + } + + /** + * Returns a String joining all the prefix examples in {@code prefixes}. + * Each prefix in {@code prefixes} must have a matching PrefixDetails in the current dictionary. + * + * @throws IllegalArgumentException if there is a prefix in {@code prefixes} + * that does not match an entries in the current dictionary. + */ + public String getPrefixesWithExamples(Prefix ... prefixes) throws IllegalArgumentException { + Set arguments = new HashSet<>(); + for (Prefix p : prefixes) { + if (arguments.contains(p)) { + throw new IllegalArgumentException("PrefixDictionary Instance: cannot have duplicate prefixes"); + } + arguments.add(p); + } + + return arguments.stream() + .map(p -> { + String prefixId = p.getPrefix(); + if (!hasPrefixDetails(prefixId)) { + throw new IllegalArgumentException(String.format(MESSAGE_UNKNOWN_ENTRY, p.getInfo())); + } + return dictionary.get(prefixId).getPrefixWithExamples(); + }) + .collect(Collectors.joining(" ")); + } +} diff --git a/src/main/java/tatracker/logic/parser/Prefixes.java b/src/main/java/tatracker/logic/parser/Prefixes.java new file mode 100644 index 00000000000..1752e062ca1 --- /dev/null +++ b/src/main/java/tatracker/logic/parser/Prefixes.java @@ -0,0 +1,46 @@ +package tatracker.logic.parser; + +/** + * Contains Command Line Interface (CLI) syntax definitions common to multiple commands + */ +public class Prefixes { + + public static final String DELIMITER = "/"; + + /* Placeholders */ + public static final Prefix INDEX = new Prefix("", "INDEX"); + public static final Prefix TAB_NAME = new Prefix("", "TAB_NAME"); + public static final Prefix RATE = new Prefix("", "RATE"); + + /* Session definitions */ + public static final Prefix START_TIME = new Prefix("s/", "START_TIME"); + public static final Prefix END_TIME = new Prefix("e/", "END_TIME"); + public static final Prefix DATE = new Prefix("d/", "DATE"); + public static final Prefix RECUR = new Prefix("w/", "RECUR"); + public static final Prefix SESSION_TYPE = new Prefix("t/", "SESSION_TYPE"); + public static final Prefix NOTES = new Prefix("n/", "NOTES"); + + /* Module definitions */ + public static final Prefix MODULE = new Prefix("m/", "MODULE"); + public static final Prefix MODULE_ID = new Prefix("", "MODULE_ID"); + public static final Prefix MODULE_NAME = new Prefix("n/", "MODULE_NAME"); + public static final Prefix MODULE_NEW_NAME = new Prefix("n/", "NEW_NAME"); + + + /* Group definitions */ + public static final Prefix GROUP = new Prefix("g/", "GROUP"); + public static final Prefix NEWGROUP = new Prefix("ng/", "NEW_GROUP"); + public static final Prefix TYPE = new Prefix("t/", "GROUP_TYPE"); + public static final Prefix NEWTYPE = new Prefix("nt/", "NEW_TYPE"); + + /* Student definitions */ + public static final Prefix MATRIC = new Prefix("id/", "MATRIC"); + public static final Prefix NAME = new Prefix("n/", "NAME"); + public static final Prefix PHONE = new Prefix("p/", "PHONE"); + public static final Prefix EMAIL = new Prefix("e/", "EMAIL"); + public static final Prefix RATING = new Prefix("r/", "RATING"); + public static final Prefix TAG = new Prefix("t/", "TAG"); + + /* Action definitions */ + public static final Prefix SORT_TYPE = new Prefix("t/", "SORT_TYPE"); +} diff --git a/src/main/java/tatracker/logic/parser/TaTrackerParser.java b/src/main/java/tatracker/logic/parser/TaTrackerParser.java new file mode 100644 index 00000000000..d4464b42cbd --- /dev/null +++ b/src/main/java/tatracker/logic/parser/TaTrackerParser.java @@ -0,0 +1,107 @@ +package tatracker.logic.parser; + +import static tatracker.commons.core.Messages.MESSAGE_HELP; +import static tatracker.commons.core.Messages.MESSAGE_INVALID_COMMAND; +import static tatracker.commons.core.Messages.MESSAGE_UNKNOWN_COMMAND; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import tatracker.logic.commands.Command; +import tatracker.logic.commands.CommandWords; +import tatracker.logic.commands.commons.ClearCommand; +import tatracker.logic.commands.commons.ExitCommand; +import tatracker.logic.commands.commons.HelpCommand; +import tatracker.logic.commands.commons.ListCommand; +import tatracker.logic.parser.commons.GotoCommandParser; +import tatracker.logic.parser.commons.SetRateCommandParser; +import tatracker.logic.parser.exceptions.ParseException; +import tatracker.logic.parser.group.GroupCommandParser; +import tatracker.logic.parser.module.ModuleCommandParser; +import tatracker.logic.parser.session.ClaimCommandParser; +import tatracker.logic.parser.session.SessionCommandParser; +import tatracker.logic.parser.sort.SortCommandParser; +import tatracker.logic.parser.statistic.ShowStatisticCommandParser; +import tatracker.logic.parser.student.StudentCommandParser; + +/** + * Parses user input. + */ +public class TaTrackerParser { + + /** + * Used for initial separation of command word and args. + */ + private static final Pattern BASIC_COMMAND_FORMAT = Pattern.compile("(?\\S+)(?.*)"); + + /** + * Parses user input into command for execution. + * + * @param userInput full user input string + * @return the command based on the user input + * @throws ParseException if the user input does not conform the expected format + */ + public Command parseCommand(String userInput) throws ParseException { + if (userInput.isEmpty()) { + throw new ParseException(MESSAGE_HELP); + } + + final Matcher matcher = BASIC_COMMAND_FORMAT.matcher(userInput.trim()); + if (!matcher.matches()) { + throw new ParseException(MESSAGE_INVALID_COMMAND + MESSAGE_HELP); + } + + final String commandWord = matcher.group("commandWord"); + final String arguments = matcher.group("arguments"); + switch (commandWord) { + + /* Student View */ + case CommandWords.MODULE: + return new ModuleCommandParser().parseCommand(arguments); + + case CommandWords.GROUP: + return new GroupCommandParser().parseCommand(arguments); + + case CommandWords.STUDENT: + return new StudentCommandParser().parseCommand(arguments); + + case CommandWords.SORT: + return new SortCommandParser().parse(arguments); + + /* Session View */ + case CommandWords.SESSION: + return new SessionCommandParser().parseCommand(arguments); + + case CommandWords.LIST: + return new ListCommand(); + + /* TSS View */ + case CommandWords.CLAIM: + return new ClaimCommandParser().parseCommand(arguments); + + /* Storage Operations */ + case CommandWords.CLEAR: + return new ClearCommand(); + + /* Navigation */ + case CommandWords.GOTO: + return new GotoCommandParser().parse(arguments); + + case CommandWords.REPORT: + return new ShowStatisticCommandParser().parse(arguments); + + case CommandWords.HELP: + return new HelpCommand(); + + case CommandWords.EXIT: + return new ExitCommand(); + + /* Others */ + case CommandWords.SET_RATE: + return new SetRateCommandParser().parse(arguments); + + default: + throw new ParseException(MESSAGE_UNKNOWN_COMMAND); + } + } +} diff --git a/src/main/java/tatracker/logic/parser/commons/GotoCommandParser.java b/src/main/java/tatracker/logic/parser/commons/GotoCommandParser.java new file mode 100644 index 00000000000..2ffcb44cea6 --- /dev/null +++ b/src/main/java/tatracker/logic/parser/commons/GotoCommandParser.java @@ -0,0 +1,29 @@ +package tatracker.logic.parser.commons; + +import tatracker.commons.core.Messages; +import tatracker.logic.commands.commons.GotoCommand; +import tatracker.logic.commands.commons.GotoCommand.Tab; +import tatracker.logic.parser.Parser; +import tatracker.logic.parser.ParserUtil; +import tatracker.logic.parser.exceptions.ParseException; + + +/** + * Parses input arguments and creates a new GotoCommand object + */ +public class GotoCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the GotoCommand + * and returns a GotoCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public GotoCommand parse(String args) throws ParseException { + try { + Tab tabName = ParserUtil.parseTabName(args); + return new GotoCommand(tabName); + } catch (ParseException pe) { + throw new ParseException(Messages.getInvalidCommandMessage(GotoCommand.DETAILS.getUsage())); + } + } +} diff --git a/src/main/java/tatracker/logic/parser/commons/SetRateCommandParser.java b/src/main/java/tatracker/logic/parser/commons/SetRateCommandParser.java new file mode 100644 index 00000000000..1dfe29518f5 --- /dev/null +++ b/src/main/java/tatracker/logic/parser/commons/SetRateCommandParser.java @@ -0,0 +1,28 @@ +package tatracker.logic.parser.commons; + +import java.util.logging.Logger; + +import tatracker.commons.core.LogsCenter; +import tatracker.logic.commands.commons.SetRateCommand; +import tatracker.logic.parser.Parser; +import tatracker.logic.parser.ParserUtil; +import tatracker.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a new SetRateCommand object + */ +public class SetRateCommandParser implements Parser { + + private final Logger logger = LogsCenter.getLogger(getClass()); + + /** + * Parses the given {@code String} of arguments in the context of the SetRateCommand + * and returns a SetRateCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public SetRateCommand parse(String args) throws ParseException { + int rate = ParserUtil.parseRate(args); + logger.fine("return new SetRateCommand:" + rate); + return new SetRateCommand(rate); + } +} diff --git a/src/main/java/seedu/address/logic/parser/exceptions/ParseException.java b/src/main/java/tatracker/logic/parser/exceptions/ParseException.java similarity index 73% rename from src/main/java/seedu/address/logic/parser/exceptions/ParseException.java rename to src/main/java/tatracker/logic/parser/exceptions/ParseException.java index 158a1a54c1c..1fcf1795117 100644 --- a/src/main/java/seedu/address/logic/parser/exceptions/ParseException.java +++ b/src/main/java/tatracker/logic/parser/exceptions/ParseException.java @@ -1,6 +1,6 @@ -package seedu.address.logic.parser.exceptions; +package tatracker.logic.parser.exceptions; -import seedu.address.commons.exceptions.IllegalValueException; +import tatracker.commons.exceptions.IllegalValueException; /** * Represents a parse error encountered by a parser. diff --git a/src/main/java/tatracker/logic/parser/group/AddGroupCommandParser.java b/src/main/java/tatracker/logic/parser/group/AddGroupCommandParser.java new file mode 100644 index 00000000000..e12f4cbe251 --- /dev/null +++ b/src/main/java/tatracker/logic/parser/group/AddGroupCommandParser.java @@ -0,0 +1,44 @@ +package tatracker.logic.parser.group; + +import static tatracker.logic.parser.Prefixes.GROUP; +import static tatracker.logic.parser.Prefixes.MODULE; +import static tatracker.logic.parser.Prefixes.TYPE; + +import tatracker.commons.core.Messages; +import tatracker.logic.commands.group.AddGroupCommand; +import tatracker.logic.parser.ArgumentMultimap; +import tatracker.logic.parser.ArgumentTokenizer; +import tatracker.logic.parser.Parser; +import tatracker.logic.parser.ParserUtil; +import tatracker.logic.parser.exceptions.ParseException; +import tatracker.model.group.Group; +import tatracker.model.group.GroupType; + +/** + * Parses input arguments and creates a new AddGroupCommand object + */ +public class AddGroupCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the AddGroupCommand + * and returns an AddGroupCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public AddGroupCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, GROUP, MODULE, TYPE); + + if (!argMultimap.arePrefixesPresent(GROUP, MODULE, TYPE) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(Messages.getInvalidCommandMessage(AddGroupCommand.DETAILS.getUsage())); + } + + String groupCode = argMultimap.getValue(GROUP).get().toUpperCase(); + String moduleCode = argMultimap.getValue(MODULE).get().toUpperCase(); + GroupType groupType = ParserUtil.parseGroupType(argMultimap.getValue(TYPE).get()); + + Group group = new Group(groupCode, groupType); + + return new AddGroupCommand(group, moduleCode); + } +} diff --git a/src/main/java/tatracker/logic/parser/group/DeleteGroupCommandParser.java b/src/main/java/tatracker/logic/parser/group/DeleteGroupCommandParser.java new file mode 100644 index 00000000000..96201b8b9bd --- /dev/null +++ b/src/main/java/tatracker/logic/parser/group/DeleteGroupCommandParser.java @@ -0,0 +1,37 @@ +package tatracker.logic.parser.group; + +import static tatracker.logic.parser.Prefixes.GROUP; +import static tatracker.logic.parser.Prefixes.MODULE; + +import tatracker.commons.core.Messages; +import tatracker.logic.commands.group.DeleteGroupCommand; +import tatracker.logic.parser.ArgumentMultimap; +import tatracker.logic.parser.ArgumentTokenizer; +import tatracker.logic.parser.Parser; +import tatracker.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a new DeleteGroupCommand object + */ +public class DeleteGroupCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the DeleteGroupCommand + * and returns an DeleteGroupCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public DeleteGroupCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, GROUP, MODULE); + + if (!argMultimap.arePrefixesPresent(GROUP, MODULE) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(Messages.getInvalidCommandMessage(DeleteGroupCommand.DETAILS.getUsage())); + } + + String groupCode = argMultimap.getValue(GROUP).get().toUpperCase(); + String moduleCode = argMultimap.getValue(MODULE).get().toUpperCase(); + + return new DeleteGroupCommand(groupCode, moduleCode); + } +} diff --git a/src/main/java/tatracker/logic/parser/group/EditGroupCommandParser.java b/src/main/java/tatracker/logic/parser/group/EditGroupCommandParser.java new file mode 100644 index 00000000000..815976cb930 --- /dev/null +++ b/src/main/java/tatracker/logic/parser/group/EditGroupCommandParser.java @@ -0,0 +1,58 @@ +package tatracker.logic.parser.group; + +import static tatracker.logic.parser.Prefixes.GROUP; +import static tatracker.logic.parser.Prefixes.MODULE; +import static tatracker.logic.parser.Prefixes.NEWGROUP; +import static tatracker.logic.parser.Prefixes.NEWTYPE; + +import tatracker.commons.core.Messages; +import tatracker.logic.commands.group.EditGroupCommand; +import tatracker.logic.parser.ArgumentMultimap; +import tatracker.logic.parser.ArgumentTokenizer; +import tatracker.logic.parser.Parser; +import tatracker.logic.parser.ParserUtil; +import tatracker.logic.parser.exceptions.ParseException; +import tatracker.model.group.Group; +import tatracker.model.group.GroupType; + +/** + * Parses input arguments and creates a new EditGroupCommand object + */ +public class EditGroupCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the EditGroupCommand + * and returns an EditGroupCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public EditGroupCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, GROUP, MODULE, NEWTYPE, NEWGROUP); + + if (!argMultimap.arePrefixesPresent(GROUP, MODULE) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(Messages.getInvalidCommandMessage(EditGroupCommand.DETAILS.getUsage())); + } + + String groupCode = argMultimap.getValue(GROUP).get().toUpperCase(); + String moduleCode = argMultimap.getValue(MODULE).get().toUpperCase(); + + Group group = new Group(groupCode); + + String newGroupCode; + if (argMultimap.getValue(NEWGROUP).isPresent()) { + newGroupCode = argMultimap.getValue(NEWGROUP).map(String::trim).map(String::toUpperCase).get(); + } else { + newGroupCode = groupCode; + } + + GroupType newGroupType; + if (argMultimap.getValue(NEWTYPE).isPresent()) { + newGroupType = ParserUtil.parseGroupType(argMultimap.getValue(NEWTYPE).get()); + } else { + newGroupType = null; + } + + return new EditGroupCommand(group, moduleCode, newGroupCode, newGroupType); + } +} diff --git a/src/main/java/tatracker/logic/parser/group/GroupCommandParser.java b/src/main/java/tatracker/logic/parser/group/GroupCommandParser.java new file mode 100644 index 00000000000..699e47c84e5 --- /dev/null +++ b/src/main/java/tatracker/logic/parser/group/GroupCommandParser.java @@ -0,0 +1,51 @@ +package tatracker.logic.parser.group; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import tatracker.commons.core.Messages; +import tatracker.logic.commands.Command; +import tatracker.logic.commands.CommandWords; +import tatracker.logic.parser.exceptions.ParseException; + +/** + * Parses user input into commands that interact with Group models. + */ +public class GroupCommandParser { + + /** + * Used for initial separation of command word and args. + */ + private static final Pattern BASIC_COMMAND_FORMAT = Pattern.compile("(?\\S+)(?.*)"); + + /** + * Parses user input into command for execution. + * + * @param userInput full user input string + * @return the command based on the user input + * @throws ParseException if the user input does not conform the expected format + */ + public Command parseCommand(String userInput) throws ParseException { + final Matcher matcher = BASIC_COMMAND_FORMAT.matcher(userInput.trim()); + if (!matcher.matches()) { + throw new ParseException(Messages.getInvalidCommandWithHelpMessage()); + } + + final String commandWord = matcher.group("commandWord"); + final String arguments = matcher.group("arguments"); + switch (commandWord) { + + case CommandWords.ADD_MODEL: + return new AddGroupCommandParser().parse(arguments); + + case CommandWords.DELETE_MODEL: + return new DeleteGroupCommandParser().parse(arguments); + + case CommandWords.EDIT_MODEL: + return new EditGroupCommandParser().parse(arguments); + + default: + throw new ParseException(Messages.getUnknownCommandWithHelpMessage()); + } + } +} diff --git a/src/main/java/tatracker/logic/parser/module/AddModuleCommandParser.java b/src/main/java/tatracker/logic/parser/module/AddModuleCommandParser.java new file mode 100644 index 00000000000..9dc0ac7860e --- /dev/null +++ b/src/main/java/tatracker/logic/parser/module/AddModuleCommandParser.java @@ -0,0 +1,41 @@ +package tatracker.logic.parser.module; + +import static tatracker.logic.parser.Prefixes.MODULE; +import static tatracker.logic.parser.Prefixes.NAME; + +import tatracker.commons.core.Messages; +import tatracker.logic.commands.module.AddModuleCommand; +import tatracker.logic.parser.ArgumentMultimap; +import tatracker.logic.parser.ArgumentTokenizer; +import tatracker.logic.parser.Parser; +import tatracker.logic.parser.exceptions.ParseException; +import tatracker.model.module.Module; + +/** + * Parses input arguments and creates a new AddModuleCommand object + */ +public class AddModuleCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the AddModuleCommand + * and returns an AddModuleCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public AddModuleCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, MODULE, NAME); + + if (!argMultimap.arePrefixesPresent(MODULE, NAME) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(Messages.getInvalidCommandMessage(AddModuleCommand.DETAILS.getUsage())); + } + + // No need to parse trimmed strings + String moduleCode = argMultimap.getValue(MODULE).get().toUpperCase(); + String name = argMultimap.getValue(NAME).get(); + + Module module = new Module(moduleCode, name); + + return new AddModuleCommand(module); + } +} diff --git a/src/main/java/tatracker/logic/parser/module/DeleteModuleCommandParser.java b/src/main/java/tatracker/logic/parser/module/DeleteModuleCommandParser.java new file mode 100644 index 00000000000..4e3ddf5114f --- /dev/null +++ b/src/main/java/tatracker/logic/parser/module/DeleteModuleCommandParser.java @@ -0,0 +1,35 @@ +package tatracker.logic.parser.module; + +import static tatracker.logic.parser.Prefixes.MODULE; + +import tatracker.commons.core.Messages; +import tatracker.logic.commands.module.DeleteModuleCommand; +import tatracker.logic.parser.ArgumentMultimap; +import tatracker.logic.parser.ArgumentTokenizer; +import tatracker.logic.parser.Parser; +import tatracker.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a new DeleteModuleCommand object + */ +public class DeleteModuleCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the DeleteModuleCommand + * and returns an DeleteModuleCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public DeleteModuleCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, MODULE); + + if (!argMultimap.arePrefixesPresent(MODULE) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(Messages.getInvalidCommandMessage(DeleteModuleCommand.DETAILS.getUsage())); + } + + String moduleCode = argMultimap.getValue(MODULE).get().toUpperCase(); + + return new DeleteModuleCommand(moduleCode); + } +} diff --git a/src/main/java/tatracker/logic/parser/module/EditModuleCommandParser.java b/src/main/java/tatracker/logic/parser/module/EditModuleCommandParser.java new file mode 100644 index 00000000000..9f0de7e7673 --- /dev/null +++ b/src/main/java/tatracker/logic/parser/module/EditModuleCommandParser.java @@ -0,0 +1,38 @@ +package tatracker.logic.parser.module; + +import static tatracker.logic.parser.Prefixes.MODULE; +import static tatracker.logic.parser.Prefixes.NAME; + +import tatracker.commons.core.Messages; +import tatracker.logic.commands.module.EditModuleCommand; +import tatracker.logic.parser.ArgumentMultimap; +import tatracker.logic.parser.ArgumentTokenizer; +import tatracker.logic.parser.Parser; +import tatracker.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a new EditModuleCommand object + */ +public class EditModuleCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the EditModuleCommand + * and returns an EditModuleCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public EditModuleCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, MODULE, NAME); + + if (!argMultimap.arePrefixesPresent(MODULE, NAME) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(Messages.getInvalidCommandMessage(EditModuleCommand.DETAILS.getUsage())); + } + + String moduleCode = argMultimap.getValue(MODULE).map(String::trim).map(String::toUpperCase).get(); + + String newName = argMultimap.getValue(NAME).map(String::trim).get(); + + return new EditModuleCommand(moduleCode, newName); + } +} diff --git a/src/main/java/tatracker/logic/parser/module/ModuleCommandParser.java b/src/main/java/tatracker/logic/parser/module/ModuleCommandParser.java new file mode 100644 index 00000000000..7d03964661d --- /dev/null +++ b/src/main/java/tatracker/logic/parser/module/ModuleCommandParser.java @@ -0,0 +1,51 @@ +package tatracker.logic.parser.module; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import tatracker.commons.core.Messages; +import tatracker.logic.commands.Command; +import tatracker.logic.commands.CommandWords; +import tatracker.logic.parser.exceptions.ParseException; + +/** + * Parses user input into commands that interact with Module models. + */ +public class ModuleCommandParser { + + /** + * Used for initial separation of command word and args. + */ + private static final Pattern BASIC_COMMAND_FORMAT = Pattern.compile("(?\\S+)(?.*)"); + + + /** + * Parses user input into command for execution. + * + * @param userInput full user input string + * @return the command based on the user input + * @throws ParseException if the user input does not conform the expected format + */ + public Command parseCommand(String userInput) throws ParseException { + final Matcher matcher = BASIC_COMMAND_FORMAT.matcher(userInput.trim()); + if (!matcher.matches()) { + throw new ParseException(Messages.getInvalidCommandWithHelpMessage()); + } + + final String commandWord = matcher.group("commandWord"); + final String arguments = matcher.group("arguments"); + switch (commandWord) { + case CommandWords.ADD_MODEL: + return new AddModuleCommandParser().parse(arguments); + + case CommandWords.DELETE_MODEL: + return new DeleteModuleCommandParser().parse(arguments); + + case CommandWords.EDIT_MODEL: + return new EditModuleCommandParser().parse(arguments); + + default: + throw new ParseException(Messages.getUnknownCommandWithHelpMessage()); + } + } +} diff --git a/src/main/java/tatracker/logic/parser/session/AddSessionCommandParser.java b/src/main/java/tatracker/logic/parser/session/AddSessionCommandParser.java new file mode 100644 index 00000000000..0a00ceb39c2 --- /dev/null +++ b/src/main/java/tatracker/logic/parser/session/AddSessionCommandParser.java @@ -0,0 +1,97 @@ +package tatracker.logic.parser.session; + +import static tatracker.logic.parser.Prefixes.DATE; +import static tatracker.logic.parser.Prefixes.END_TIME; +import static tatracker.logic.parser.Prefixes.MODULE; +import static tatracker.logic.parser.Prefixes.NOTES; +import static tatracker.logic.parser.Prefixes.RECUR; +import static tatracker.logic.parser.Prefixes.SESSION_TYPE; +import static tatracker.logic.parser.Prefixes.START_TIME; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; + +import tatracker.commons.core.Messages; +import tatracker.logic.commands.session.AddSessionCommand; +import tatracker.logic.parser.ArgumentMultimap; +import tatracker.logic.parser.ArgumentTokenizer; +import tatracker.logic.parser.Parser; +import tatracker.logic.parser.ParserUtil; +import tatracker.logic.parser.exceptions.ParseException; +import tatracker.model.session.Session; + +/* + * === BUGS === + * TODO: No error when end time is after start time. + * + * TODO: Sessions cannot have dates that are earlier than the current date. + * Earlier dates are replaced by the current date. + */ + +/** + * Parses input arguments and creates a new AddSessionCommand object + */ +public class AddSessionCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the AddSessionCommand + * and returns an AddSessionCommand object for execution. + * + * @throws ParseException if the user input does not conform the expected format + */ + public AddSessionCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, MODULE, START_TIME, END_TIME, + DATE, RECUR, SESSION_TYPE, NOTES); + + if (!argMultimap.arePrefixesPresent(MODULE) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(Messages.getInvalidCommandMessage(AddSessionCommand.DETAILS.getUsage())); + } + + LocalDate date = LocalDate.now(); + Session sessionToAdd = new Session(); + + + if (argMultimap.getValue(MODULE).isPresent()) { + sessionToAdd.setModuleCode(argMultimap.getValue(MODULE).map(String::trim).map(String::toUpperCase).get()); + } + + if (argMultimap.getValue(DATE).isPresent()) { + date = ParserUtil.parseDate(argMultimap.getValue(DATE).get()); + } + + if (argMultimap.getValue(START_TIME).isPresent()) { + LocalTime startTime = ParserUtil.parseTime(argMultimap.getValue(START_TIME).get()); + sessionToAdd.setStartDateTime(LocalDateTime.of(date.getYear(), date.getMonth(), date.getDayOfMonth(), + startTime.getHour(), startTime.getMinute(), startTime.getSecond())); + } + + if (argMultimap.getValue(END_TIME).isPresent()) { + LocalTime endTime = ParserUtil.parseTime(argMultimap.getValue(END_TIME).get()); + sessionToAdd.setEndDateTime(LocalDateTime.of(date.getYear(), date.getMonth(), date.getDayOfMonth(), + endTime.getHour(), endTime.getMinute(), endTime.getSecond())); + } else { + if (argMultimap.getValue(START_TIME).isPresent()) { + LocalTime startTime = ParserUtil.parseTime(argMultimap.getValue(START_TIME).get()); + sessionToAdd.setEndDateTime(LocalDateTime.of(date.getYear(), date.getMonth(), date.getDayOfMonth(), + startTime.getHour(), startTime.getMinute(), startTime.getSecond())); + } + } + + if (argMultimap.getValue(RECUR).isPresent()) { + sessionToAdd.setRecurring(ParserUtil.parseNumWeeks(argMultimap.getValue(RECUR).get())); + } + + if (argMultimap.getValue(SESSION_TYPE).isPresent()) { + sessionToAdd.setType( + ParserUtil.parseSessionType(argMultimap.getValue(SESSION_TYPE).get())); + } + + if (argMultimap.getValue(NOTES).isPresent()) { + sessionToAdd.setDescription(argMultimap.getValue(NOTES).map(String::trim).get()); + } + + return new AddSessionCommand(sessionToAdd); + } +} diff --git a/src/main/java/tatracker/logic/parser/session/ClaimCommandParser.java b/src/main/java/tatracker/logic/parser/session/ClaimCommandParser.java new file mode 100644 index 00000000000..9a027eb498f --- /dev/null +++ b/src/main/java/tatracker/logic/parser/session/ClaimCommandParser.java @@ -0,0 +1,46 @@ +package tatracker.logic.parser.session; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import tatracker.commons.core.Messages; +import tatracker.logic.commands.Command; +import tatracker.logic.commands.CommandWords; +import tatracker.logic.parser.exceptions.ParseException; + +/** + * Parses user input into commands that interact with Session model. + */ +public class ClaimCommandParser { + + /** + * Used for initial separation of command word and args. + */ + private static final Pattern BASIC_COMMAND_FORMAT = Pattern.compile("(?\\S+)(?.*)"); + + /** + * Parses user input into command for execution. + * + * @param userInput full user input string + * @return the command based on the user input + * @throws ParseException if the user input does not conform the expected format + */ + public Command parseCommand(String userInput) throws ParseException { + final Matcher matcher = BASIC_COMMAND_FORMAT.matcher(userInput.trim()); + if (!matcher.matches()) { + throw new ParseException(Messages.getInvalidCommandWithHelpMessage()); + } + + final String commandWord = matcher.group("commandWord"); + final String arguments = matcher.group("arguments"); + + switch (commandWord) { + + case CommandWords.FILTER_MODEL: + return new FilterClaimCommandParser().parse(arguments); + + default: + throw new ParseException(Messages.getUnknownCommandWithHelpMessage()); + } + } +} diff --git a/src/main/java/tatracker/logic/parser/session/DeleteSessionCommandParser.java b/src/main/java/tatracker/logic/parser/session/DeleteSessionCommandParser.java new file mode 100644 index 00000000000..bea358b50b2 --- /dev/null +++ b/src/main/java/tatracker/logic/parser/session/DeleteSessionCommandParser.java @@ -0,0 +1,30 @@ +package tatracker.logic.parser.session; + +import static tatracker.commons.core.Messages.MESSAGE_INVALID_SESSION_DISPLAYED_INDEX; + +import tatracker.commons.core.index.Index; +import tatracker.logic.commands.session.DeleteSessionCommand; +import tatracker.logic.parser.Parser; +import tatracker.logic.parser.ParserUtil; +import tatracker.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a new DeleteSessionCommand object + */ +public class DeleteSessionCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the DeleteSessionCommand + * and returns an DeleteSessionCommand object for execution. + * + * @throws ParseException if the user input does not conform the expected format + */ + public DeleteSessionCommand parse(String args) throws ParseException { + try { + Index index = ParserUtil.parseIndex(args); + return new DeleteSessionCommand(index); + } catch (ParseException pe) { + throw new ParseException(MESSAGE_INVALID_SESSION_DISPLAYED_INDEX, pe); + } + } +} diff --git a/src/main/java/tatracker/logic/parser/session/DoneSessionCommandParser.java b/src/main/java/tatracker/logic/parser/session/DoneSessionCommandParser.java new file mode 100644 index 00000000000..9a035fa930a --- /dev/null +++ b/src/main/java/tatracker/logic/parser/session/DoneSessionCommandParser.java @@ -0,0 +1,30 @@ +package tatracker.logic.parser.session; + +import static tatracker.commons.core.Messages.MESSAGE_INVALID_SESSION_DISPLAYED_INDEX; + +import tatracker.commons.core.index.Index; +import tatracker.logic.commands.session.DoneSessionCommand; +import tatracker.logic.parser.Parser; +import tatracker.logic.parser.ParserUtil; +import tatracker.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a new DoneSessionCommand object + */ +public class DoneSessionCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the DoneSessionCommand + * and returns an DoneSessionCommand object for execution. + * + * @throws ParseException if the user input does not conform the expected format + */ + public DoneSessionCommand parse(String args) throws ParseException { + try { + Index index = ParserUtil.parseIndex(args); + return new DoneSessionCommand(index); + } catch (ParseException pe) { + throw new ParseException(MESSAGE_INVALID_SESSION_DISPLAYED_INDEX, pe); + } + } +} diff --git a/src/main/java/tatracker/logic/parser/session/EditSessionCommandParser.java b/src/main/java/tatracker/logic/parser/session/EditSessionCommandParser.java new file mode 100644 index 00000000000..584fc9527be --- /dev/null +++ b/src/main/java/tatracker/logic/parser/session/EditSessionCommandParser.java @@ -0,0 +1,104 @@ +package tatracker.logic.parser.session; + +import static java.util.Objects.requireNonNull; +import static tatracker.commons.core.Messages.MESSAGE_NOT_EDITED; +import static tatracker.logic.parser.Prefixes.DATE; +import static tatracker.logic.parser.Prefixes.END_TIME; +import static tatracker.logic.parser.Prefixes.MODULE; +import static tatracker.logic.parser.Prefixes.NOTES; +import static tatracker.logic.parser.Prefixes.RECUR; +import static tatracker.logic.parser.Prefixes.SESSION_TYPE; +import static tatracker.logic.parser.Prefixes.START_TIME; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; + +import tatracker.commons.core.Messages; +import tatracker.commons.core.index.Index; +import tatracker.logic.commands.session.EditSessionCommand; +import tatracker.logic.parser.ArgumentMultimap; +import tatracker.logic.parser.ArgumentTokenizer; +import tatracker.logic.parser.Parser; +import tatracker.logic.parser.ParserUtil; +import tatracker.logic.parser.exceptions.ParseException; + +/* + * === BUGS === + * TODO: No error when end time is after start time. + * + * TODO: Sessions cannot have dates that are earlier than the current date. + * Earlier dates are replaced by the current date. + */ + +/** + * Parses input arguments and creates a new EditSessionCommand object + */ +public class EditSessionCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the EditSessionCommand + * and returns an AddSessionCommand object for execution. + * + * @throws ParseException if the user input does not conform the expected format + */ + public EditSessionCommand parse(String args) throws ParseException { + requireNonNull(args); + ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, START_TIME, END_TIME, + DATE, RECUR, MODULE, SESSION_TYPE, NOTES); + + Index index; + + try { + index = ParserUtil.parseIndex(argMultimap.getPreamble()); + } catch (ParseException pe) { + throw new ParseException(Messages.getInvalidCommandMessage(EditSessionCommand.DETAILS.getUsage()), pe); + } + + EditSessionCommand.EditSessionDescriptor editSessionDescriptor = new EditSessionCommand.EditSessionDescriptor(); + + LocalDate date = LocalDate.now(); // Arbitrary default value. Will be overwritten by EditSessionCommand + if (argMultimap.getValue(DATE).isPresent()) { + date = ParserUtil.parseDate(argMultimap.getValue(DATE).get()); + editSessionDescriptor.setIsDateChanged(true); + } + + if (argMultimap.getValue(START_TIME).isPresent()) { + LocalTime startTime = ParserUtil.parseTime(argMultimap.getValue(START_TIME).get()); + editSessionDescriptor.setStartTime(LocalDateTime.of(date.getYear(), date.getMonth(), date.getDayOfMonth(), + startTime.getHour(), startTime.getMinute(), startTime.getSecond())); + } + + if (argMultimap.getValue(END_TIME).isPresent()) { + LocalTime endTime = ParserUtil.parseTime(argMultimap.getValue(END_TIME).get()); + editSessionDescriptor.setEndTime(LocalDateTime.of(date.getYear(), date.getMonth(), date.getDayOfMonth(), + endTime.getHour(), endTime.getMinute(), endTime.getSecond())); + } + + if (argMultimap.getValue(RECUR).isPresent()) { + editSessionDescriptor.setRecurring(ParserUtil.parseNumWeeks(argMultimap.getValue(RECUR).get())); + } + + if (argMultimap.getValue(MODULE).isPresent()) { + editSessionDescriptor.setModuleCode(argMultimap.getValue(MODULE) + .map(String::trim) + .map(String::toUpperCase) + .get()); + } + + if (argMultimap.getValue(SESSION_TYPE).isPresent()) { + editSessionDescriptor.setSessionType( + ParserUtil.parseSessionType(argMultimap.getValue(SESSION_TYPE).get())); + } + + if (argMultimap.getValue(NOTES).isPresent()) { + editSessionDescriptor.setDescription(argMultimap.getValue(NOTES).map(String::trim).get()); + } + + if (!editSessionDescriptor.isAnyFieldEdited()) { + throw new ParseException(MESSAGE_NOT_EDITED); + } + + return new EditSessionCommand(index, editSessionDescriptor); + } +} diff --git a/src/main/java/tatracker/logic/parser/session/FilterClaimCommandParser.java b/src/main/java/tatracker/logic/parser/session/FilterClaimCommandParser.java new file mode 100644 index 00000000000..7aeaf268053 --- /dev/null +++ b/src/main/java/tatracker/logic/parser/session/FilterClaimCommandParser.java @@ -0,0 +1,39 @@ +package tatracker.logic.parser.session; + +import static tatracker.logic.parser.Prefixes.MODULE; + +import tatracker.commons.core.Messages; +import tatracker.logic.commands.session.DoneSessionPredicate; +import tatracker.logic.commands.session.FilterClaimCommand; +import tatracker.logic.parser.ArgumentMultimap; +import tatracker.logic.parser.ArgumentTokenizer; +import tatracker.logic.parser.Parser; +import tatracker.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a new FilterModuleCommand object + */ +public class FilterClaimCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the FilterSessionCommand + * and returns a FilterSessionCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public FilterClaimCommand parse(String args) throws ParseException { + + ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, MODULE); + + if (!argMultimap.arePrefixesPresent(MODULE) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(Messages.getInvalidCommandMessage(FilterClaimCommand.DETAILS.getUsage())); + } + + String moduleCode = ""; + + if (argMultimap.getValue(MODULE).isPresent()) { + moduleCode = argMultimap.getValue(MODULE).map(String::trim).map(String::toUpperCase).get(); + } + return new FilterClaimCommand(new DoneSessionPredicate(moduleCode)); + } +} diff --git a/src/main/java/tatracker/logic/parser/session/FilterSessionCommandParser.java b/src/main/java/tatracker/logic/parser/session/FilterSessionCommandParser.java new file mode 100644 index 00000000000..ee0a13b78c6 --- /dev/null +++ b/src/main/java/tatracker/logic/parser/session/FilterSessionCommandParser.java @@ -0,0 +1,64 @@ +package tatracker.logic.parser.session; + +import static tatracker.logic.parser.Prefixes.DATE; +import static tatracker.logic.parser.Prefixes.MODULE; +import static tatracker.logic.parser.Prefixes.SESSION_TYPE; + +import tatracker.commons.core.Messages; +import tatracker.logic.commands.session.FilterSessionCommand; +import tatracker.logic.parser.ArgumentMultimap; +import tatracker.logic.parser.ArgumentTokenizer; +import tatracker.logic.parser.Parser; +import tatracker.logic.parser.ParserUtil; +import tatracker.logic.parser.exceptions.ParseException; +import tatracker.model.module.Module; +import tatracker.model.session.SessionPredicate; + + +/** + * Parses input arguments and creates a new FilterSessionCommand object + */ +public class FilterSessionCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the FilterSessionCommand + * and returns a FilterSessionCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public FilterSessionCommand parse(String args) throws ParseException { + + ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, DATE, + MODULE, SESSION_TYPE); + + if (!argMultimap.getPreamble().isEmpty()) { + throw new ParseException(Messages.getInvalidCommandMessage(FilterSessionCommand.DETAILS.getUsage())); + } + + SessionPredicate predicate = new SessionPredicate(); + + if (argMultimap.getValue(DATE).isPresent()) { + predicate.setDate(ParserUtil.parseDate(argMultimap.getValue(DATE).get())); + } + + if (argMultimap.getValue(MODULE).isPresent()) { + String moduleCode = argMultimap.getValue(MODULE).map(String::trim).get(); + + if (moduleCode.isBlank()) { + throw new ParseException(Module.CONSTRAINTS_MODULE_CODE); + } + + predicate.setModuleCode(moduleCode); + } + + if (argMultimap.getValue(SESSION_TYPE).isPresent()) { + predicate.setSessionType(ParserUtil.parseSessionType(argMultimap.getValue(SESSION_TYPE).get())); + } + + if (!predicate.isAnyFieldEdited()) { + throw new ParseException(Messages.getInvalidCommandMessage(FilterSessionCommand.DETAILS.getUsage())); + } + + return new FilterSessionCommand(predicate); + } +} + diff --git a/src/main/java/tatracker/logic/parser/session/SessionCommandParser.java b/src/main/java/tatracker/logic/parser/session/SessionCommandParser.java new file mode 100644 index 00000000000..fe2fc4acc2a --- /dev/null +++ b/src/main/java/tatracker/logic/parser/session/SessionCommandParser.java @@ -0,0 +1,58 @@ +package tatracker.logic.parser.session; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import tatracker.commons.core.Messages; +import tatracker.logic.commands.Command; +import tatracker.logic.commands.CommandWords; +import tatracker.logic.parser.exceptions.ParseException; + +/** + * Parses user input into commands that interact with Session model. + */ +public class SessionCommandParser { + + /** + * Used for initial separation of command word and args. + */ + private static final Pattern BASIC_COMMAND_FORMAT = Pattern.compile("(?\\S+)(?.*)"); + + /** + * Parses user input into command for execution. + * + * @param userInput full user input string + * @return the command based on the user input + * @throws ParseException if the user input does not conform the expected format + */ + public Command parseCommand(String userInput) throws ParseException { + final Matcher matcher = BASIC_COMMAND_FORMAT.matcher(userInput.trim()); + if (!matcher.matches()) { + throw new ParseException(Messages.getInvalidCommandWithHelpMessage()); + } + + final String commandWord = matcher.group("commandWord"); + final String arguments = matcher.group("arguments"); + + switch (commandWord) { + + case CommandWords.ADD_MODEL: + return new AddSessionCommandParser().parse(arguments); + + case CommandWords.DELETE_MODEL: + return new DeleteSessionCommandParser().parse(arguments); + + case CommandWords.EDIT_MODEL: + return new EditSessionCommandParser().parse(arguments); + + case CommandWords.FILTER_MODEL: + return new FilterSessionCommandParser().parse(arguments); + + case CommandWords.DONE_SESSION: + return new DoneSessionCommandParser().parse(arguments); + + default: + throw new ParseException(Messages.getUnknownCommandWithHelpMessage()); + } + } +} diff --git a/src/main/java/tatracker/logic/parser/sort/SortCommandParser.java b/src/main/java/tatracker/logic/parser/sort/SortCommandParser.java new file mode 100644 index 00000000000..9a1a4412b85 --- /dev/null +++ b/src/main/java/tatracker/logic/parser/sort/SortCommandParser.java @@ -0,0 +1,96 @@ +package tatracker.logic.parser.sort; + +import static tatracker.logic.parser.Prefixes.GROUP; +import static tatracker.logic.parser.Prefixes.MODULE; +import static tatracker.logic.parser.Prefixes.TYPE; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import tatracker.commons.core.Messages; +import tatracker.logic.commands.CommandWords; +import tatracker.logic.commands.commons.HelpCommand; +import tatracker.logic.commands.sort.SortCommand; +import tatracker.logic.commands.sort.SortGroupCommand; +import tatracker.logic.commands.sort.SortModuleCommand; +import tatracker.logic.commands.sort.SortType; +import tatracker.logic.parser.ArgumentMultimap; +import tatracker.logic.parser.ArgumentTokenizer; +import tatracker.logic.parser.Parser; +import tatracker.logic.parser.ParserUtil; +import tatracker.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a new SortCommand object + */ +public class SortCommandParser implements Parser { + /** + * Used for initial separation of command word and args. + */ + private static final Pattern BASIC_COMMAND_FORMAT = Pattern.compile("(?\\S+)(?.*)"); + + /** + * Parses the given {@code String} of arguments in the context of the SortCommand + * and returns a SortCommand object for execution. + * + * @throws ParseException if the user input does not conform the expected format + */ + public SortCommand parse(String userInput) throws ParseException { + final Matcher matcher = BASIC_COMMAND_FORMAT.matcher(userInput.trim()); + if (!matcher.matches()) { + throw new ParseException(Messages.getInvalidCommandMessage(HelpCommand.DETAILS.getUsage())); + } + + final String commandWord = matcher.group("commandWord"); + final String args = matcher.group("arguments"); + + ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, TYPE, MODULE, GROUP); + + if (!argMultimap.arePrefixesPresent(TYPE) || !argMultimap.getPreamble().isEmpty()) { + switch(commandWord) { + case CommandWords.SORT_ALL: + throw new ParseException(Messages.getInvalidCommandMessage(SortCommand.DETAILS.getUsage())); + case CommandWords.SORT_GROUP: + throw new ParseException(Messages.getInvalidCommandMessage(SortGroupCommand.DETAILS.getUsage())); + case CommandWords.SORT_MODULE: + throw new ParseException(Messages.getInvalidCommandMessage(SortModuleCommand.DETAILS.getUsage())); + default: + throw new ParseException(Messages.getInvalidCommandMessage(SortCommand.DETAILS.getUsage())); + } + } + + boolean hasType = argMultimap.getValue(TYPE).isPresent(); + + SortType type = ParserUtil.parseSortType(argMultimap.getValue(TYPE).get()); + + boolean hasModule = argMultimap.getValue(MODULE).isPresent(); + boolean hasGroup = argMultimap.getValue(GROUP).isPresent(); + + String moduleCode = argMultimap.getValue(MODULE).map(String::trim).orElse("").toUpperCase(); + String groupCode = argMultimap.getValue(GROUP).map(String::trim).orElse("").toUpperCase(); + + switch (commandWord) { + + case CommandWords.SORT_ALL: + if (!hasType) { + throw new ParseException(Messages.getInvalidCommandMessage(SortCommand.DETAILS.getUsage())); + } + return new SortCommand(type); + + case CommandWords.SORT_MODULE: + if (!hasModule || !hasType) { + throw new ParseException(Messages.getInvalidCommandMessage(SortModuleCommand.DETAILS.getUsage())); + } + return new SortModuleCommand(type, moduleCode); + + case CommandWords.SORT_GROUP: + if (!hasModule || !hasGroup || !hasType) { + throw new ParseException(Messages.getInvalidCommandMessage(SortGroupCommand.DETAILS.getUsage())); + } + return new SortGroupCommand(type, groupCode, moduleCode); + + default: + throw new ParseException(Messages.getInvalidCommandMessage(SortCommand.DETAILS.getUsage())); + } + } +} diff --git a/src/main/java/tatracker/logic/parser/statistic/ShowStatisticCommandParser.java b/src/main/java/tatracker/logic/parser/statistic/ShowStatisticCommandParser.java new file mode 100644 index 00000000000..f2c8d267848 --- /dev/null +++ b/src/main/java/tatracker/logic/parser/statistic/ShowStatisticCommandParser.java @@ -0,0 +1,25 @@ +package tatracker.logic.parser.statistic; + +import tatracker.logic.commands.statistic.ShowStatisticCommand; +import tatracker.logic.parser.Parser; + +/** + * Parses input arguments and creates a new ShowStatisticCommand object + */ +public class ShowStatisticCommandParser implements Parser { + + /** + * Parse the user input for command execution. + * + * @param args The input arguments + * @return a ShowStatisticCommand object + */ + public ShowStatisticCommand parse(String args) { + String trimmedArgs = args.trim(); + if (trimmedArgs.isEmpty()) { + return new ShowStatisticCommand(); + } + + return new ShowStatisticCommand(trimmedArgs); + } +} diff --git a/src/main/java/tatracker/logic/parser/student/AddStudentCommandParser.java b/src/main/java/tatracker/logic/parser/student/AddStudentCommandParser.java new file mode 100644 index 00000000000..84ede6c0933 --- /dev/null +++ b/src/main/java/tatracker/logic/parser/student/AddStudentCommandParser.java @@ -0,0 +1,84 @@ +package tatracker.logic.parser.student; + +import static tatracker.logic.parser.Prefixes.EMAIL; +import static tatracker.logic.parser.Prefixes.GROUP; +import static tatracker.logic.parser.Prefixes.MATRIC; +import static tatracker.logic.parser.Prefixes.MODULE; +import static tatracker.logic.parser.Prefixes.NAME; +import static tatracker.logic.parser.Prefixes.PHONE; +import static tatracker.logic.parser.Prefixes.RATING; +import static tatracker.logic.parser.Prefixes.TAG; + +import java.util.Set; + +import tatracker.commons.core.Messages; +import tatracker.logic.commands.student.AddStudentCommand; +import tatracker.logic.parser.ArgumentMultimap; +import tatracker.logic.parser.ArgumentTokenizer; +import tatracker.logic.parser.Parser; +import tatracker.logic.parser.ParserUtil; +import tatracker.logic.parser.exceptions.ParseException; +import tatracker.model.student.Email; +import tatracker.model.student.Matric; +import tatracker.model.student.Name; +import tatracker.model.student.Phone; +import tatracker.model.student.Rating; +import tatracker.model.student.Student; +import tatracker.model.tag.Tag; + + +/** + * Parses input arguments and creates a new AddStudentCommand object + */ +public class AddStudentCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the AddStudentCommand + * and returns an AddStudentCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public AddStudentCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, MATRIC, MODULE, GROUP, + NAME, PHONE, EMAIL, RATING, TAG); + + if (!argMultimap.arePrefixesPresent(MATRIC, MODULE, GROUP, NAME) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(Messages.getInvalidCommandMessage(AddStudentCommand.DETAILS.getUsage())); + } + + // ==== Identity fields ==== + + Matric matric = ParserUtil.parseMatric(argMultimap.getValue(MATRIC).get()); + + String module = argMultimap.getValue(MODULE).get().toUpperCase(); + String group = argMultimap.getValue(GROUP).get().toUpperCase(); + + Name name = ParserUtil.parseName(argMultimap.getValue(NAME).get()); + + // ==== Optional fields ==== + + Phone phone = new Phone(); + if (argMultimap.getValue(PHONE).isPresent()) { + phone = ParserUtil.parsePhone(argMultimap.getValue(PHONE).get()); + } + + Email email = new Email(); + if (argMultimap.getValue(EMAIL).isPresent()) { + email = ParserUtil.parseEmail(argMultimap.getValue(EMAIL).get()); + } + + Rating rating = new Rating(); + if (argMultimap.getValue(RATING).isPresent()) { + rating = ParserUtil.parseRating(argMultimap.getValue(RATING).get()); + } + + Set tagList = ParserUtil.parseTags(argMultimap.getAllValues(TAG)); + + // ==== Build Student ==== + + Student student = new Student(matric, name, phone, email, rating, tagList); + + return new AddStudentCommand(student, group, module); + } +} diff --git a/src/main/java/tatracker/logic/parser/student/DeleteStudentCommandParser.java b/src/main/java/tatracker/logic/parser/student/DeleteStudentCommandParser.java new file mode 100644 index 00000000000..2190a15f0b7 --- /dev/null +++ b/src/main/java/tatracker/logic/parser/student/DeleteStudentCommandParser.java @@ -0,0 +1,42 @@ +package tatracker.logic.parser.student; + +import static tatracker.logic.parser.Prefixes.GROUP; +import static tatracker.logic.parser.Prefixes.MATRIC; +import static tatracker.logic.parser.Prefixes.MODULE; + +import tatracker.commons.core.Messages; +import tatracker.logic.commands.student.DeleteStudentCommand; +import tatracker.logic.parser.ArgumentMultimap; +import tatracker.logic.parser.ArgumentTokenizer; +import tatracker.logic.parser.Parser; +import tatracker.logic.parser.ParserUtil; +import tatracker.logic.parser.exceptions.ParseException; +import tatracker.model.student.Matric; + +/** + * Parses input arguments and creates a new DeleteStudentCommand object + */ +public class DeleteStudentCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the DeleteStudentCommand + * and returns a DeleteStudentCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public DeleteStudentCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, MATRIC, GROUP, MODULE); + + if (!argMultimap.arePrefixesPresent(MATRIC, GROUP, MODULE) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(Messages.getInvalidCommandMessage(DeleteStudentCommand.DETAILS.getUsage())); + } + + Matric matric = ParserUtil.parseMatric(argMultimap.getValue(MATRIC).get()); + + String groupCode = argMultimap.getValue(GROUP).get(); + String moduleCode = argMultimap.getValue(MODULE).get(); + + return new DeleteStudentCommand(matric, groupCode, moduleCode); + } +} diff --git a/src/main/java/tatracker/logic/parser/student/EditStudentCommandParser.java b/src/main/java/tatracker/logic/parser/student/EditStudentCommandParser.java new file mode 100644 index 00000000000..2f2908a395f --- /dev/null +++ b/src/main/java/tatracker/logic/parser/student/EditStudentCommandParser.java @@ -0,0 +1,98 @@ +package tatracker.logic.parser.student; + +import static java.util.Objects.requireNonNull; +import static tatracker.commons.core.Messages.MESSAGE_NOT_EDITED; +import static tatracker.logic.parser.Prefixes.EMAIL; +import static tatracker.logic.parser.Prefixes.GROUP; +import static tatracker.logic.parser.Prefixes.MATRIC; +import static tatracker.logic.parser.Prefixes.MODULE; +import static tatracker.logic.parser.Prefixes.NAME; +import static tatracker.logic.parser.Prefixes.PHONE; +import static tatracker.logic.parser.Prefixes.RATING; +import static tatracker.logic.parser.Prefixes.TAG; + +import java.util.Collection; +import java.util.Collections; +import java.util.Optional; +import java.util.Set; + +import tatracker.commons.core.Messages; +import tatracker.logic.commands.student.EditStudentCommand; +import tatracker.logic.commands.student.EditStudentCommand.EditStudentDescriptor; +import tatracker.logic.parser.ArgumentMultimap; +import tatracker.logic.parser.ArgumentTokenizer; +import tatracker.logic.parser.Parser; +import tatracker.logic.parser.ParserUtil; +import tatracker.logic.parser.exceptions.ParseException; +import tatracker.model.student.Matric; +import tatracker.model.tag.Tag; + +/** + * Parses input arguments and creates a new EditStudentCommand object + */ +public class EditStudentCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the EditStudentCommand + * and returns an EditStudentCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public EditStudentCommand parse(String args) throws ParseException { + requireNonNull(args); + ArgumentMultimap argMultimap = ArgumentTokenizer + .tokenize(args, MATRIC, MODULE, GROUP, NAME, PHONE, EMAIL, RATING, TAG); + + if (!argMultimap.arePrefixesPresent(MATRIC, MODULE, GROUP) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(Messages.getInvalidCommandMessage(EditStudentCommand.DETAILS.getUsage())); + } + + // ==== Identity fields ==== + + Matric matric = ParserUtil.parseMatric(argMultimap.getValue(MATRIC).get()); + + String moduleCode = argMultimap.getValue(MODULE).map(String::trim).get(); + String groupCode = argMultimap.getValue(GROUP).map(String::trim).get(); + + // ==== Optional fields ==== + EditStudentDescriptor editStudentDescriptor = new EditStudentDescriptor(); + + if (argMultimap.getValue(NAME).isPresent()) { + editStudentDescriptor.setName(ParserUtil.parseName(argMultimap.getValue(NAME).get())); + } + if (argMultimap.getValue(PHONE).isPresent()) { + editStudentDescriptor.setPhone(ParserUtil.parsePhone(argMultimap.getValue(PHONE).get())); + } + if (argMultimap.getValue(EMAIL).isPresent()) { + editStudentDescriptor.setEmail(ParserUtil.parseEmail(argMultimap.getValue(EMAIL).get())); + } + if (argMultimap.getValue(RATING).isPresent()) { + editStudentDescriptor.setRating(ParserUtil.parseRating(argMultimap.getValue(RATING).get())); + } + parseTagsForEdit(argMultimap.getAllValues(TAG)).ifPresent(editStudentDescriptor::setTags); + + // ==== Build Student ==== + + if (!editStudentDescriptor.isAnyFieldEdited()) { + throw new ParseException(MESSAGE_NOT_EDITED); + } + + return new EditStudentCommand(matric, moduleCode, groupCode, editStudentDescriptor); + } + + /** + * Parses {@code Collection tags} into a {@code Set} if {@code tags} is non-empty. + * If {@code tags} contain only one element which is an empty string, it will be parsed into a + * {@code Set} containing zero tags. + */ + private Optional> parseTagsForEdit(Collection tags) throws ParseException { + assert tags != null; + + if (tags.isEmpty()) { + return Optional.empty(); + } + Collection tagSet = tags.size() == 1 && tags.contains("") ? Collections.emptySet() : tags; + return Optional.of(ParserUtil.parseTags(tagSet)); + } +} + diff --git a/src/main/java/tatracker/logic/parser/student/FilterStudentCommandParser.java b/src/main/java/tatracker/logic/parser/student/FilterStudentCommandParser.java new file mode 100644 index 00000000000..f9b1e79e399 --- /dev/null +++ b/src/main/java/tatracker/logic/parser/student/FilterStudentCommandParser.java @@ -0,0 +1,46 @@ +package tatracker.logic.parser.student; + +import static tatracker.logic.parser.Prefixes.GROUP; +import static tatracker.logic.parser.Prefixes.MODULE; + +import tatracker.commons.core.Messages; +import tatracker.logic.commands.student.FilterStudentCommand; +import tatracker.logic.parser.ArgumentMultimap; +import tatracker.logic.parser.ArgumentTokenizer; +import tatracker.logic.parser.Parser; +import tatracker.logic.parser.exceptions.ParseException; + +/** + * Parse input arguments and create a new FilterStudentCommand object + */ +public class FilterStudentCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the FilterSessionCommand + * and returns a FilterSessionCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public FilterStudentCommand parse(String args) throws ParseException { + + ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, MODULE, + GROUP); + + if (!argMultimap.arePrefixesPresent(MODULE) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(Messages.getInvalidCommandMessage(FilterStudentCommand.DETAILS.getUsage())); + } + + String moduleCode = ""; + String groupCode = ""; + + if (argMultimap.getValue(MODULE).isPresent()) { + moduleCode = argMultimap.getValue(MODULE).map(String::trim).map(String::toUpperCase).get(); + } + + if (argMultimap.getValue(GROUP).isPresent()) { + groupCode = argMultimap.getValue(GROUP).map(String::trim).map(String::toUpperCase).get(); + } + + return new FilterStudentCommand(moduleCode, groupCode); + } +} diff --git a/src/main/java/tatracker/logic/parser/student/StudentCommandParser.java b/src/main/java/tatracker/logic/parser/student/StudentCommandParser.java new file mode 100644 index 00000000000..5f8876c6fe8 --- /dev/null +++ b/src/main/java/tatracker/logic/parser/student/StudentCommandParser.java @@ -0,0 +1,55 @@ +package tatracker.logic.parser.student; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import tatracker.commons.core.Messages; +import tatracker.logic.commands.Command; +import tatracker.logic.commands.CommandWords; +import tatracker.logic.parser.exceptions.ParseException; + + +/** + * Parses user input into commands that interact with Student models. + */ +public class StudentCommandParser { + + /** + * Used for initial separation of command word and args. + */ + private static final Pattern BASIC_COMMAND_FORMAT = Pattern.compile("(?\\S+)(?.*)"); + + /** + * Parses user input into command for execution. + * + * @param userInput full user input string + * @return the command based on the user input + * @throws ParseException if the user input does not conform the expected format + */ + public Command parseCommand(String userInput) throws ParseException { + final Matcher matcher = BASIC_COMMAND_FORMAT.matcher(userInput.trim()); + if (!matcher.matches()) { + throw new ParseException(Messages.getInvalidCommandWithHelpMessage()); + } + + final String commandWord = matcher.group("commandWord"); + final String arguments = matcher.group("arguments"); + switch (commandWord) { + + case CommandWords.FILTER_MODEL: + return new FilterStudentCommandParser().parse(arguments); + + case CommandWords.ADD_MODEL: + return new AddStudentCommandParser().parse(arguments); + + case CommandWords.DELETE_MODEL: + return new DeleteStudentCommandParser().parse(arguments); + + case CommandWords.EDIT_MODEL: + return new EditStudentCommandParser().parse(arguments); + + default: + throw new ParseException(Messages.getUnknownCommandWithHelpMessage()); + } + } +} diff --git a/src/main/java/tatracker/model/Model.java b/src/main/java/tatracker/model/Model.java new file mode 100644 index 00000000000..f1528dd58d1 --- /dev/null +++ b/src/main/java/tatracker/model/Model.java @@ -0,0 +1,426 @@ +package tatracker.model; + +import java.nio.file.Path; +import java.util.function.Predicate; + +import javafx.collections.ObservableList; + +import tatracker.commons.core.GuiSettings; +import tatracker.model.group.Group; +import tatracker.model.module.Module; +import tatracker.model.session.Session; +import tatracker.model.student.Matric; +import tatracker.model.student.Student; + + +/** + * The API of the Model component. + */ +public interface Model { + // TODO: Remove interface constants (aim for pure interface). + + /** {@code Predicate} that always evaluate to true */ + Predicate PREDICATE_SHOW_ALL_STUDENTS = unused -> true; + + /** {@code Predicates} that always evaluate to true */ + Predicate PREDICATE_SHOW_ALL_MODULES = unused -> true; + + /** {@code Predicates} that always evaluate to true */ + Predicate PREDICATE_SHOW_ALL_SESSIONS = unused -> true; + + // ======== TaTracker Methods ============================================== + + /** + * Returns the TaTracker. + */ + ReadOnlyTaTracker getTaTracker(); + + /** + * Replaces TaTracker data with the data in {@code taTracker}. + */ + void setTaTracker(ReadOnlyTaTracker taTracker); + + // ======== User Prefs Methods ============================================= + + /** + * Returns the User Prefs. + */ + ReadOnlyUserPrefs getUserPrefs(); + + /** + * Replaces the data in User Prefs with the data in {@code userPrefs}. + */ + void setUserPrefs(ReadOnlyUserPrefs userPrefs); + + /** + * Returns the TaTracker file path in the User Prefs. + */ + Path getTaTrackerFilePath(); + + /** + * Replaces the TaTracker file path in the User Prefs. + */ + void setTaTrackerFilePath(Path taTrackerFilePath); + + /** + * Returns the GUI settings in the User Prefs. + */ + GuiSettings getGuiSettings(); + + /** + * Replaces the GUI settings in the User Prefs. + */ + void setGuiSettings(GuiSettings guiSettings); + + /** + * Sets default settings of the student view. + */ + void setDefaultStudentViewList(); + + // ======== Filter Methods ================================================ + + /** + *Sets the currently used filter under Claim View. + */ + void setCurrClaimFilter(String module); + + /** + * Get the currently used filter under Claim View. + */ + String getCurrClaimFilter(); + + /** + *Sets the currently used filter under Session View. + */ + void setCurrSessionFilter(String params); + + /** + *Sets the currently used date filter under Session View. + */ + void setCurrSessionDateFilter(String params); + + /** + *Sets the currently used module filter under Session View. + */ + void setCurrSessionModuleFilter(String params); + + /** + *Sets the currently used type filter under Session View. + */ + void setCurrSessionTypeFilter(String params); + + /** + * Get the currently used filter under Session View. + */ + String getCurrSessionFilter(); + + /** + * Get the currently used date filter under Session View. + */ + String getCurrSessionDateFilter(); + + /** + * Get the currently used module filter under Session View. + */ + String getCurrSessionModuleFilter(); + + /** + * Get the currently used type filter under Session View. + */ + String getCurrSessionTypeFilter(); + + /** + *Sets the currently used filter under Student View. + */ + void setCurrStudentFilter(String params); + + /** + * Get the currently used filter under Student View. + */ + String getCurrStudentFilter(); + + // ======== Session Methods ================================================ + + /** + * Returns true if a given session with the same identity as {@code session} + * exists in TaTracker. + */ + boolean hasSession(Session session); + + /** + * Adds the given session into the TaTracker. + * @param session session to add, which must not already exist in the TaTracker. + */ + void addSession(Session session); + + /** + * Deletes the given session {@code target} from the TaTracker. + * @param target session to delete, which must exist in the TaTracker. + */ + void deleteSession(Session target); + + /** + * Replaces the given session {@code target} in the TaTracker with {@code editedSession}. + * @param target session to edit, which must exist in the TaTracker. + * @param editedSession the edited session {@code target}. + * The identity of {@code editedSession} must be the same as {@code target}. + */ + void setSession(Session target, Session editedSession); + + /** Returns an unmodifiable view of the filtered session list */ + ObservableList getFilteredSessionList(); + + /** + * Updates the filter of the filtered session list to filter by the given {@code predicate}. + * @throws NullPointerException if {@code predicate} is null. + */ + void updateFilteredSessionList(Predicate predicate); + + // ======== Done Session Methods ================================================= + + /** + * Adds the given session to the list of completed sessions. + * The session must exist in the ta-tracker. + */ + void addDoneSession(Session session); + + // ======== Module Methods ================================================= + + /** + * Returns the TaTracker module with the given module identifier. + */ + Module getModule(String moduleId); + + /** Returns an unmodifiable view of the filtered done session list */ + ObservableList getFilteredDoneSessionList(); + + /** + * Updates the filter of the filtered done session list to filter by the given {@code predicate}. + * @throws NullPointerException if {@code predicate} is null. + */ + void updateFilteredDoneSessionList(Predicate predicate, String moduleCode); + + /** + * Returns true if a given module with the same identity as {@code module} + * exists in TaTracker. + * @param moduleCode + */ + boolean hasModule(String moduleCode); + + /** + * Adds the given module into the TaTracker. + * @param module module to add, which must not already exist in the TaTracker. + */ + void addModule(Module module); + + /** + * Deletes the given module {@code target} from the TaTracker. + * @param target module to delete, which must exist in the TaTracker. + */ + void deleteModule(Module target); + + /** + * Sorts the modules by rating in ascending order. + */ + void sortModulesByRatingAscending(); + + /** + * Sorts the modules by rating in descending order. + */ + void sortModulesByRatingDescending(); + + /** + * Sorts all the students of all groups in all the modules alphabetically. + */ + void sortModulesAlphabetically(); + + /** + * Sorts all students of all groups in all the modules by matric number. + */ + void sortModulesByMatricNumber(); + + + /** + * Replaces the given module {@code target} in the TaTracker with {@code editedModule}. + * @param target module to edit, which must exist in the TaTracker. + * @param editedModule the edited module {@code target}. + * The identity of {@code editedModule} must be the same as {@code target}. + */ + void setModule(Module target, Module editedModule); + + /** Returns an unmodifiable view of the filtered module list */ + ObservableList getFilteredModuleList(); + + /** + * Shows all modules. + */ + void showAllModules(); + + /** + * Updates the filter of the filtered module list to filter by the given {@code predicate}. + * @throws NullPointerException if {@code predicate} is null. + */ + void updateFilteredModuleList(Predicate predicate); + + // ======== Group Methods ================================================== + + + /** + * Returns true if a given group with the same identity as {@code group} + * exists in a module that is in TaTracker. + * @param groupCode + * @param moduleCode module that contains {@code group}. + */ + boolean hasGroup(String groupCode, String moduleCode); + + /** + * Adds the given group into a module that is in TaTracker. + * @param group group to add, which must not already exist in the TaTracker module. + * @param targetModule module to add {@code group} into, which must exist in the TaTracker. + */ + void addGroup(Group group, Module targetModule); + + /** + * Deletes the given group {@code target} from a module that is in TaTracker. + * @param target group code whose group to delete, which must exist in the TaTracker module. + * @param targetModule module code whose module has the group that must be deleted. + */ + void deleteGroup(String target, String targetModule); + + /** + * Replaces the given group {@code target} in a TaTracker module with {@code editedGroup}. + * @param target group to edit, which must exist in the TaTracker module. + * @param editedGroup the edited group {@code target}. + * The identity of {@code editedGroup} must be the same as {@code target}. + * @param targetModule module with the group to edit, which must exist in the TaTracker. + */ + void setGroup(Group target, Group editedGroup, Module targetModule); + + /** Returns an unmodifiable view of the filtered group list */ + ObservableList getFilteredGroupList(); + + /** + * Updates the filtered group list to show the groups in module with the given + * module code. + */ + void updateFilteredGroupList(String moduleCode); + + /** + * Sets the filtered group list to be an empty list. + */ + void setFilteredGroupList(); + + /** + * Updates the group list to show the groups in the module with the given index. + */ + void updateGroupList(int index); + + // ======== Student Methods ================================================ + + /** + * Returns true if a student with the same identity as {@code student} exists in the TaTracker. + */ + boolean hasStudent(Student student); + + /** + * Returns true if a given student with the given {@code matric} + * exists in a module group that is in TaTracker. + * @param targetGroup id of group to check if {@code student} is enrolled in. + * @param targetModule id of module that contains {@code group}. + */ + boolean hasStudent(Matric matric, String targetGroup, String targetModule); + + /** + * Returns the student with the given {@code matric} in the TA-Tracker. + * This student will be inside the group with the given {@code groupCode}, + * which in turn is inside the module with the given {@code moduleCode}/ + * @param matric of the student + * @param groupCode group to check if {@code student} is enrolled in. + * @param moduleCode module that contains {@code group}. + */ + Student getStudent(Matric matric, String groupCode, String moduleCode); + + /** + * Adds the given student. + * {@code student} must not already exist in the ta-tracker. + */ + void addStudent(Student student); + + /** + * Adds the given student into a module group that is in TaTracker. + * @param student student to add, which must not already exist in the TaTracker module group. + * @param targetGroup group to add {@code student} into, which must exist in the TaTracker module. + * @param targetModule module to add {@code student} into, which must exist in the TaTracker. + */ + void addStudent(Student student, String targetGroup, String targetModule); + + /** + * Deletes the given student. + * The student must exist in the ta-tracker. + */ + void deleteStudent(Student target); + + /** + * Deletes the given student {@code target} from a module group that is in TaTracker. + * @param target student to delete, which must exist in the TaTracker module group. + * @param targetGroup group to delete student {@code target} from, which must exist in the TaTracker module. + * @param targetModule module to delete student {@code target} from, which must exist in the TaTracker. + */ + void deleteStudent(Student target, String targetGroup, String targetModule); + + /** + * Replaces the given student {@code target} with {@code editedStudent}. + * {@code target} must exist in the ta-tracker. + * The student identity of {@code editedStudent} must not be the same as another existing student in the tracker. + */ + void setStudent(Student target, Student editedStudent); + + /** + * Replaces the given student {@code target} in a TaTracker module group with {@code editedStudent}. + * @param target student to edit, which must exist in the TaTracker module group. + * @param editedStudent the edited student {@code target}. + * The identity of {@code editedStudent} must be the same as {@code target}. + * @param targetGroup group with the student to edit, which must exist in the TaTracker module. + * @param targetModule module with the student to edit, which must exist in the TaTracker. + */ + void setStudent(Student target, Student editedStudent, String targetGroup, String targetModule); + + /** + * Sets the student list to be of group of index groupIndex in the module of index moduleIndex. + */ + void updateStudentList(int moduleIndex, int groupIndex); + + + // TODO: Student filter methods. Javadoc comments should mention students are inside group -> inside module + + /** Returns an unmodifiable view of the filtered student list */ + ObservableList getFilteredStudentList(); + + /** + * Updates the currently shown student list to show students of the given group + * from the given module. + */ + void updateFilteredStudentList(String groupCode, String moduleCode); + + /** + * Updates the filter of the filtered person list to filter by the given {@code predicate}. + * @throws NullPointerException if {@code predicate} is null. + */ + void updateFilteredStudentList(Predicate predicate); + + /** + * Sets the filtered student list to be an empty list. + */ + void setFilteredStudentList(); + + /** + * Sets the filtered student list to be an that of given index group in given module. + */ + void setFilteredStudentList(String moduleCode, int groupIndex); + + /** + * Sets the pay rate to a integer specified by the user + * @param rate the new rate + */ + void setRate(int rate); +} diff --git a/src/main/java/tatracker/model/ModelManager.java b/src/main/java/tatracker/model/ModelManager.java new file mode 100644 index 00000000000..ea2a245181a --- /dev/null +++ b/src/main/java/tatracker/model/ModelManager.java @@ -0,0 +1,513 @@ +package tatracker.model; + +import static java.util.Objects.requireNonNull; +import static tatracker.commons.util.CollectionUtil.requireAllNonNull; + +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.function.Predicate; +import java.util.logging.Logger; + +import javafx.collections.ObservableList; +import javafx.collections.transformation.FilteredList; + +import tatracker.commons.core.GuiSettings; +import tatracker.commons.core.LogsCenter; +import tatracker.model.group.Group; +import tatracker.model.module.Module; +import tatracker.model.session.Session; +import tatracker.model.student.Matric; +import tatracker.model.student.Student; + +/** + * Represents the in-memory model of the ta-tracker data. + */ +public class ModelManager implements Model { + private static final Logger logger = LogsCenter.getLogger(ModelManager.class); + private static final int FIRST_GROUP_INDEX = 0; + private static final int FIRST_MODULE_INDEX = 0; + + private final TaTracker taTracker; + private final UserPrefs userPrefs; + + private final FilteredList filteredSessions; + private final FilteredList filteredDoneSessions; + private final FilteredList filteredModules; + + /** + * Initializes a ModelManager with the given taTracker and userPrefs. + */ + public ModelManager(ReadOnlyTaTracker taTracker, ReadOnlyUserPrefs userPrefs) { + super(); // TODO: Super gets interface constants. + requireAllNonNull(taTracker, userPrefs); + + logger.fine("Initializing with ta-tracker: " + taTracker + " and user prefs " + userPrefs); + + this.taTracker = new TaTracker(taTracker); + this.userPrefs = new UserPrefs(userPrefs); + filteredSessions = new FilteredList<>(this.taTracker.getSessionList()); + filteredDoneSessions = new FilteredList<>(this.taTracker.getDoneSessionList()); + filteredModules = new FilteredList<>(this.taTracker.getModuleList()); + this.setDefaultStudentViewList(); + } + + public ModelManager() { + this(new TaTracker(), new UserPrefs()); + } + + // ======== TaTracker ====================================================== + + @Override + public ReadOnlyTaTracker getTaTracker() { + return taTracker; + } + + @Override + public void setTaTracker(ReadOnlyTaTracker taTracker) { + this.taTracker.resetData(taTracker); + } + + // ======== UserPrefs ====================================================== + + @Override + public ReadOnlyUserPrefs getUserPrefs() { + return userPrefs; + } + + @Override + public void setUserPrefs(ReadOnlyUserPrefs userPrefs) { + requireNonNull(userPrefs); + this.userPrefs.resetData(userPrefs); + } + + @Override + public Path getTaTrackerFilePath() { + return userPrefs.getTaTrackerFilePath(); + } + + @Override + public void setTaTrackerFilePath(Path taTrackerFilePath) { + requireNonNull(taTrackerFilePath); + userPrefs.setTaTrackerFilePath(taTrackerFilePath); + } + + @Override + public GuiSettings getGuiSettings() { + return userPrefs.getGuiSettings(); + } + + @Override + public void setGuiSettings(GuiSettings guiSettings) { + requireNonNull(guiSettings); + userPrefs.setGuiSettings(guiSettings); + } + + @Override + public void setDefaultStudentViewList() { + if (this.getFilteredModuleList().isEmpty()) { + this.setFilteredGroupList(); + this.setFilteredStudentList(); + taTracker.setCurrentlyShownGroup(null); + taTracker.setCurrentlyShownModule(null); + } else { + this.updateGroupList(FIRST_MODULE_INDEX); + taTracker.setCurrentlyShownModule(taTracker.getModule(FIRST_MODULE_INDEX)); + if (this.getFilteredGroupList().isEmpty()) { + taTracker.setCurrentlyShownGroup(null); + this.setFilteredStudentList(); + } else { + taTracker.setCurrentlyShownGroup(taTracker.getModule(FIRST_MODULE_INDEX).get(FIRST_GROUP_INDEX)); + this.updateStudentList(FIRST_GROUP_INDEX, FIRST_MODULE_INDEX); + } + } + } + + // ======== Filter Methods ============================================== + @Override + public void setCurrClaimFilter(String module) { + requireAllNonNull(module); + logger.info(String.format("Claims are filtered by %s", module)); + taTracker.setCurrClaimFilter(module); + } + + @Override + public String getCurrClaimFilter() { + return taTracker.getCurrClaimFilter(); + } + + @Override + public void setCurrSessionFilter(String params) { + requireAllNonNull(params); + logger.info(String.format("Claims are filtered by %s", params)); + taTracker.setCurrSessionFilter(params); + } + + @Override + public void setCurrSessionDateFilter(String params) { + requireAllNonNull(params); + logger.info(String.format("Sessions are filtered by %s", params)); + taTracker.setCurrSessionDateFilter(params); + } + + @Override + public void setCurrSessionModuleFilter(String params) { + requireAllNonNull(params); + logger.info(String.format("Sessions are filtered by %s", params)); + taTracker.setCurrSessionModuleFilter(params); + } + + @Override + public void setCurrSessionTypeFilter(String params) { + requireAllNonNull(params); + taTracker.setCurrSessionTypeFilter(params); + } + + @Override + public String getCurrSessionFilter() { + return taTracker.getCurrSessionFilter(); + } + + @Override + public String getCurrSessionDateFilter() { + return taTracker.getCurrSessionDateFilter(); + } + + @Override + public String getCurrSessionModuleFilter() { + return taTracker.getCurrSessionModuleFilter(); + } + + @Override + public String getCurrSessionTypeFilter() { + return taTracker.getCurrSessionTypeFilter(); + } + + @Override + public void setCurrStudentFilter(String params) { + requireAllNonNull(params); + taTracker.setCurrStudentFilter(params); + } + + @Override + public String getCurrStudentFilter() { + return taTracker.getCurrStudentFilter(); + } + + // ======== Session Methods ================================================ + + @Override + public boolean hasSession(Session session) { + requireNonNull(session); + return taTracker.hasSession(session); + } + + @Override + public void addSession(Session session) { + taTracker.addSession(session); + logger.info(String.format("Session added is %s", session)); + updateFilteredSessionList(PREDICATE_SHOW_ALL_SESSIONS); + } + + @Override + public void deleteSession(Session target) { + logger.info(String.format("Session deleted is %s", target)); + taTracker.removeSession(target); + } + + @Override + public void setSession(Session target, Session editedSession) { + requireAllNonNull(target, editedSession); + logger.info(String.format("Session %s is changed to %s", target, editedSession)); + taTracker.setSession(target, editedSession); + } + + @Override + public ObservableList getFilteredSessionList() { + return filteredSessions; + } + + @Override + public void updateFilteredSessionList(Predicate predicate) { + requireNonNull(predicate); + filteredSessions.setPredicate(predicate); + } + + // ======== Done Session Methods ================================================= + + @Override + public void addDoneSession(Session session) { + logger.info(String.format("Session marked as done is %s", session)); + taTracker.addDoneSession(session); + updateFilteredDoneSessionList(PREDICATE_SHOW_ALL_SESSIONS, ""); + } + + /** + * Returns an unmodifiable view of the list of {@code Session} backed by the internal list of + * {@code versionedTaTracker} + */ + @Override + public ObservableList getFilteredDoneSessionList() { + return filteredDoneSessions; + } + + @Override + public void updateFilteredDoneSessionList(Predicate predicate, String moduleCode) { + requireNonNull(predicate); + taTracker.setCurrentlyShownModuleClaim(moduleCode); + logger.info("Done sessions are filtered by " + moduleCode); + filteredDoneSessions.setPredicate(predicate); + } + + @Override + public void setRate (int rate) { + taTracker.setRate(rate); + } + + // ======== Module Methods ================================================= + + public Module getModule(String code) { + requireNonNull(code); + return taTracker.getModule(code); + } + + @Override + public boolean hasModule(String moduleCode) { + requireNonNull(moduleCode); + return taTracker.hasModule(moduleCode); + } + + @Override + public void addModule(Module module) { + requireNonNull(module); + logger.info(String.format("Module added is %s", module)); + taTracker.addModule(module); + } + + @Override + public void deleteModule(Module module) { + requireNonNull(module); + logger.info(String.format("Module deleted is %s", module)); + taTracker.deleteModule(module); + } + + @Override + public void setModule(Module target, Module editedModule) { + requireAllNonNull(target, editedModule); + logger.info(String.format("Module %s is changed to %s", target, editedModule)); + taTracker.setModule(target, editedModule); + } + + @Override + public void sortModulesAlphabetically() { + logger.info(String.format("Modules are sorted alphabetically")); + taTracker.sortModulesAlphabetically(); + } + + @Override + public void sortModulesByRatingAscending() { + logger.info(String.format("Modules are sorted by rating in ascending order")); + taTracker.sortModulesByRatingAscending(); + } + + @Override + public void sortModulesByRatingDescending() { + logger.info(String.format("Modules are sorted by rating in descending order")); + taTracker.sortModulesByRatingDescending(); + } + + @Override + public void sortModulesByMatricNumber() { + logger.info(String.format("Modules are sorted by matric number in ascending order")); + taTracker.sortModulesByMatricNumber(); + } + + + @Override + public ObservableList getFilteredModuleList() { + return filteredModules; + } + + @Override + public void updateFilteredModuleList(Predicate predicate) { + requireNonNull(predicate); + filteredModules.setPredicate(predicate); + } + + @Override + public void showAllModules() { + logger.info(String.format("All modules are shown")); + updateFilteredModuleList(PREDICATE_SHOW_ALL_MODULES); + } + + // ======== Group Methods ================================================== + + @Override + public boolean hasGroup(String groupCode, String moduleCode) { + requireNonNull(groupCode, moduleCode); + return taTracker.hasGroup(groupCode, moduleCode); + } + + @Override + public void addGroup(Group group, Module targetModule) { + requireNonNull(group); + logger.info(String.format("Group %s is added to module %s", group, targetModule)); + taTracker.addGroup(group, targetModule); + } + + @Override + public void deleteGroup(String target, String targetModule) { + requireNonNull(target); + logger.info(String.format("Group %s is deleted from %s", target, targetModule)); + taTracker.removeGroup(new Group(target), new Module(targetModule)); + } + + @Override + public void setGroup(Group target, Group editedGroup, Module targetModule) { + requireAllNonNull(target, editedGroup); + logger.info(String.format("Group %s in module %s is changed to %s", + target, targetModule, editedGroup)); + taTracker.setGroup(target, editedGroup, targetModule); + } + + @Override + public ObservableList getFilteredGroupList() { + return taTracker.getCurrentlyShownGroupList(); + } + + @Override + public void updateFilteredGroupList(String moduleCode) { + logger.info(String.format("Group list being shown is of module %s", moduleCode)); + taTracker.updateCurrentlyShownGroups(moduleCode); + } + + @Override + public void setFilteredGroupList() { + logger.info(String.format("Empty grouplist is shown")); + taTracker.setCurrentlyShownGroups(new ArrayList<>()); + } + + @Override + public void updateGroupList(int moduleIndex) { + logger.info(String.format("Groups from module %d is showns", moduleIndex)); + taTracker.setCurrentlyShownGroups(moduleIndex); + } + + // ======== Student Methods ================================================ + + @Override + public boolean hasStudent(Student student) { + requireNonNull(student); + return taTracker.hasStudent(student); + } + + @Override + public boolean hasStudent(Matric matric, String targetGroup, String targetModule) { + requireNonNull(matric); + return taTracker.hasStudent(matric, targetGroup, targetModule); + } + + @Override + public Student getStudent(Matric matric, String groupCode, String moduleCode) { + return taTracker.getStudent(matric, groupCode, moduleCode); + } + + @Override + public void addStudent(Student student) { + logger.info(String.format("Student added is %s", student)); + taTracker.addStudent(student); + } + + @Override + public void addStudent(Student student, String targetGroup, String targetModule) { + requireNonNull(student); + logger.info(String.format("Student added is %s into group %s of module %s", + student, targetGroup, targetModule)); + taTracker.addStudent(student, targetGroup, targetModule); + } + + @Override + public void deleteStudent(Student target) { + taTracker.removeStudent(target); + } + + @Override + public void deleteStudent(Student target, String targetGroup, String targetModule) { + requireNonNull(target); + logger.info(String.format("Student deleted is %s from group %s of module %s", + target, targetGroup, targetModule)); + taTracker.deleteStudent(target, targetGroup, targetModule); + } + + @Override + public void setStudent(Student target, Student editedStudent) { + requireAllNonNull(target, editedStudent); + taTracker.setStudent(target, editedStudent); + } + + @Override + public void setStudent(Student target, Student editedStudent, String targetGroup, String targetModule) { + requireAllNonNull(target, editedStudent); + logger.info(String.format("Student edited is %s to %s from group %s of module %s", + target, editedStudent, targetGroup, targetModule)); + taTracker.setStudent(target, editedStudent, targetGroup, targetModule); + } + + @Override + public void updateStudentList(int moduleIndex, int groupIndex) { + logger.info(String.format("Students are from module of index %d and group index %d", + moduleIndex, groupIndex)); + taTracker.setCurrentlyShownStudents(moduleIndex, groupIndex); + } + + @Override + public ObservableList getFilteredStudentList() { + return taTracker.getCurrentlyShownStudentList(); + } + + @Override + public void setFilteredStudentList() { + logger.info(String.format("Empty student list is shown.")); + taTracker.setCurrentlyShownStudents(new ArrayList<>()); + } + + @Override + public void setFilteredStudentList(String moduleCode, int groupIndex) { + logger.info(String.format("Students of group index %d from module %s are showmn", + groupIndex, moduleCode)); + taTracker.setCurrentlyShownStudents(moduleCode, groupIndex); + } + + @Override + public void updateFilteredStudentList(String groupCode, String moduleCode) { + logger.info(String.format("Students are shown of group %s of module %s", + groupCode, moduleCode)); + taTracker.updateCurrentlyShownStudents(groupCode, moduleCode); + } + + @Override + public void updateFilteredStudentList(Predicate predicate) { + requireNonNull(predicate); + } + + // ======== Others Methods ================================================= + + @Override + public boolean equals(Object obj) { + // short circuit if same object + if (obj == this) { + return true; + } + + // instanceof handles nulls + if (!(obj instanceof ModelManager)) { + return false; + } + + // state check + ModelManager other = (ModelManager) obj; + return taTracker.equals(other.taTracker) + && userPrefs.equals(other.userPrefs) + && filteredSessions.equals(other.filteredSessions) + && filteredModules.equals(other.filteredModules); + } +} diff --git a/src/main/java/tatracker/model/ReadOnlyTaTracker.java b/src/main/java/tatracker/model/ReadOnlyTaTracker.java new file mode 100644 index 00000000000..a3396ce5b98 --- /dev/null +++ b/src/main/java/tatracker/model/ReadOnlyTaTracker.java @@ -0,0 +1,65 @@ +package tatracker.model; + +import javafx.collections.ObservableList; + +import tatracker.model.group.Group; +import tatracker.model.module.Module; +import tatracker.model.session.Session; +import tatracker.model.student.Student; + +/** + * Unmodifiable view of a ta-tracker. + */ +public interface ReadOnlyTaTracker { + + /** + * Returns an unmodifiable view of the student list. + * This list will not contain any duplicate student. + */ + ObservableList getCurrentlyShownStudentList(); + + /** + * Returns an unmodifiable view of the modules list. + * This list will not contain any duplicate modules. + */ + ObservableList getModuleList(); + + /** + * Returns an unmodifiable view of the groups list. + * This list will not contain any duplicate groups. + */ + ObservableList getCurrentlyShownGroupList(); + + /** + * Returns an unmodifiable view of the sessions list. + * This list will not contain any duplicate sessions. + */ + ObservableList getSessionList(); + + /** + * Returns an unmodifiable view of the done sessions list. + * This list will not contain any duplicate sessions. + */ + ObservableList getDoneSessionList(); + + /** + * Returns an unmodifiable view of the student list. + * This list will contain ALL students in TA-Tracker. + */ + ObservableList getCompleteStudentList(); + + /** + * Returns the number of hours spent teaching. + */ + long getTotalHours(); + + /** + * Returns the rate per hour of teaching. + */ + int getRate(); + + /** + * Returns the total amount earned by teaching. + */ + long getTotalEarnings(); +} diff --git a/src/main/java/seedu/address/model/ReadOnlyUserPrefs.java b/src/main/java/tatracker/model/ReadOnlyUserPrefs.java similarity index 57% rename from src/main/java/seedu/address/model/ReadOnlyUserPrefs.java rename to src/main/java/tatracker/model/ReadOnlyUserPrefs.java index befd58a4c73..10472029592 100644 --- a/src/main/java/seedu/address/model/ReadOnlyUserPrefs.java +++ b/src/main/java/tatracker/model/ReadOnlyUserPrefs.java @@ -1,8 +1,8 @@ -package seedu.address.model; +package tatracker.model; import java.nio.file.Path; -import seedu.address.commons.core.GuiSettings; +import tatracker.commons.core.GuiSettings; /** * Unmodifiable view of user prefs. @@ -11,6 +11,6 @@ public interface ReadOnlyUserPrefs { GuiSettings getGuiSettings(); - Path getAddressBookFilePath(); + Path getTaTrackerFilePath(); } diff --git a/src/main/java/tatracker/model/TaTracker.java b/src/main/java/tatracker/model/TaTracker.java new file mode 100644 index 00000000000..0653f7271fb --- /dev/null +++ b/src/main/java/tatracker/model/TaTracker.java @@ -0,0 +1,737 @@ +package tatracker.model; + +import static java.util.Objects.requireNonNull; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.logging.Logger; + +import javafx.collections.ObservableList; + +import tatracker.commons.core.LogsCenter; +import tatracker.model.group.Group; +import tatracker.model.group.GroupNotFoundException; +import tatracker.model.group.UniqueGroupList; +import tatracker.model.module.Module; +import tatracker.model.module.ModuleNotFoundException; +import tatracker.model.module.UniqueModuleList; +import tatracker.model.session.Session; +import tatracker.model.session.UniqueDoneSessionList; +import tatracker.model.session.UniqueSessionList; +import tatracker.model.student.Matric; +import tatracker.model.student.Student; +import tatracker.model.student.UniqueStudentList; + + +/** + * Wraps all data at the ta-tracker level + * Duplicates are not allowed (by .isSameSession comparison) + * Duplicates are not allowed (by .isSameStudent comparison) + */ +public class TaTracker implements ReadOnlyTaTracker { + + public static final String CONSTRAINTS_RATE = "Rate must be an integer representing an amount that is more than $0"; + + private static final int DEFAULT_RATE = 40; + + private static String currClaimFilter = ""; + private static String currSessionFilter = ""; + private static String currSessionDateFilter = ""; + private static String currSessionModuleFilter = ""; + private static String currSessionTypeFilter = ""; + private static String currStudentFilter = ""; + + private static Group currentlyShownGroup = null; + private static Module currentlyShownModule = null; + private static Module currentlyShownModuleClaim = null; + + private int rate; + + private final UniqueSessionList sessions; + private final UniqueDoneSessionList doneSessions; + private final UniqueModuleList modules; + private final UniqueGroupList currentlyShownGroups; + private final UniqueStudentList currentlyShownStudents; + + private final Logger logger = LogsCenter.getLogger(getClass()); + + public TaTracker() { + sessions = new UniqueSessionList(); + doneSessions = new UniqueDoneSessionList(); + modules = new UniqueModuleList(); + currentlyShownGroups = new UniqueGroupList(); + currentlyShownStudents = new UniqueStudentList(); + + rate = DEFAULT_RATE; + } + + /** + * Creates a TaTracker using the Students in the {@code toBeCopied} + */ + public TaTracker(ReadOnlyTaTracker toBeCopied) { + this(); + resetData(toBeCopied); + } + + /** + * Resets the existing data of this {@code TaTracker} with {@code newData}. + */ + public void resetData(ReadOnlyTaTracker newData) { + requireNonNull(newData); + + setSessions(newData.getSessionList()); + setDoneSessionList(newData.getDoneSessionList()); + setModules(newData.getModuleList()); + setCurrentlyShownGroups(newData.getCurrentlyShownGroupList()); + setCurrentlyShownStudents(newData.getCurrentlyShownStudentList()); + + this.rate = newData.getRate(); + } + + // ======== Filter Methods ================================================ + + /** + *Sets the currently used filter under Claim View. + */ + public void setCurrClaimFilter(String module) { + requireNonNull(module); + currClaimFilter = module; + } + + /** + * Get the currently used filter under Claim View. + */ + public String getCurrClaimFilter() { + return currClaimFilter; + } + + /** + *Sets the currently used filter under Session View. + */ + public void setCurrSessionFilter(String params) { + requireNonNull(params); + currSessionFilter = params; + } + + /** + * Get the currently used filter under Session View. + */ + public String getCurrSessionFilter() { + return currSessionFilter; + } + + /** + *Sets the currently used filter under Session View. + */ + public void setCurrSessionDateFilter(String params) { + requireNonNull(params); + currSessionDateFilter = params; + } + + /** + * Get the currently used filter under Session View. + */ + public String getCurrSessionDateFilter() { + return currSessionDateFilter; + } + + /** + *Sets the currently used filter under Session View. + */ + public void setCurrSessionModuleFilter(String params) { + requireNonNull(params); + currSessionModuleFilter = params; + } + + /** + * Get the currently used filter under Session View. + */ + public String getCurrSessionModuleFilter() { + return currSessionModuleFilter; + } + + /** + *Sets the currently used filter under Session View. + */ + public void setCurrSessionTypeFilter(String params) { + requireNonNull(params); + currSessionTypeFilter = params; + } + + /** + * Get the currently used filter under Session View. + */ + public String getCurrSessionTypeFilter() { + return currSessionTypeFilter; + } + + /** + *Sets the currently used filter under Student View. + */ + public void setCurrStudentFilter(String params) { + requireNonNull(params); + currStudentFilter = params; + } + + /** + * Get the currently used filter under Student View. + */ + public String getCurrStudentFilter() { + return currStudentFilter; + } + + // ======== Session Methods ================================================ + + /** + * Returns true if a session with the same identity as {@code session} exists in the ta-tracker. + */ + public boolean hasSession(Session session) { + requireNonNull(session); + return sessions.contains(session); + } + + /** + * Adds a session to the ta-tracker. + * The session must not already exist in the ta-tracker. + */ + public void addSession(Session s) { + sessions.add(s); + } + + /** + * Returns true if a session with the same identity as {@code session} exists in the ta-tracker. + * Removes {@code session} from this {@code TaTracker}. + * {@code session} must exist in the ta-tracker. + */ + public void removeSession(Session session) { + sessions.remove(session); + } + + /** + * Replaces the given session {@code target} in the list with {@code editedSession}. + * {@code target} must exist in the ta-tracker. + * The session identity of {@code editedSession} must not be the same as another + * existing session in the ta-tracker. + */ + public void setSession(Session target, Session editedSession) { + requireNonNull(editedSession); + + sessions.setSession(target, editedSession); + } + + /** + * Replaces the contents of the session list with {@code sessions}. + * {@code sessions} must not contain duplicate sessions. + */ + public void setSessions(List sessions) { + this.sessions.setSessions(sessions); + } + + @Override + public ObservableList getSessionList() { + return sessions.asUnmodifiableObservableList(); + } + + // ======== Done Session Methods ================================================= + + /** + * Adds a completed session to the list of done sessions. + */ + public void addDoneSession(Session s) { + doneSessions.add(s); + } + + @Override + public long getTotalHours() { + long totalHours = 0; + for (int i = 0; i < doneSessions.size(); i += 1) { + if (currentlyShownModuleClaim != null) { + if (doneSessions.get(i).getModuleCode().equals(currentlyShownModuleClaim.getIdentifier())) { + logger.info("reached: has claim filter" + currentlyShownModuleClaim.toString()); + totalHours += doneSessions.get(i).getDurationToNearestHour().toHours(); + logger.info(String.valueOf(totalHours)); + } + } else { + logger.info("reached: no claim filter"); + totalHours += doneSessions.get(i).getDurationToNearestHour().toHours(); + logger.info(String.valueOf(totalHours)); + } + } + return totalHours; + } + + @Override + public int getRate() { + return rate; + } + + public void setRate(int newRate) { + logger.fine("Reached SetRate in TaTracker"); + rate = newRate; + } + + @Override + public long getTotalEarnings() { + return rate * getTotalHours(); + } + + /** + * Replaces the contents of the donesession list with {@code donesessions}. + * {@code donesessions} must not contain duplicate donesessions. + */ + public void setDoneSessionList(List donesessions) { + this.doneSessions.setSessions(donesessions); + } + + @Override + public ObservableList getDoneSessionList() { + return doneSessions.asUnmodifiableObservableList(); + } + + @Override + public ObservableList getCompleteStudentList() { + UniqueStudentList completeStudentList = new UniqueStudentList(); + + // TODO: Fix this before PE, when the master student list is not stored per module group. + // There should be a master list of students managed by TaTracker + + HashSet allStudents = new HashSet<>(); + for (Module module : modules) { + for (Group group : module.getGroupList()) { + allStudents.addAll(group.getStudentList()); + } + } + completeStudentList.setStudents(new ArrayList<>(allStudents)); + return completeStudentList.asUnmodifiableObservableList(); + } + + public void setCurrentlyShownModuleClaim(String moduleCode) { + if ("".equals(moduleCode)) { + currentlyShownModuleClaim = null; + } else { + currentlyShownModuleClaim = modules.getModule(moduleCode); + } + } + + public static Module getCurrentlyShownModuleClaim() { + return currentlyShownModuleClaim; + } + + // ======== Module Methods ================================================= + + /** + * Returns module from TATracker. + */ + public Module getModule(String moduleId) { + return modules.getModule(moduleId); + } + + /** + * Returns module from TATracker. + */ + public Module getModule(int index) { + return modules.get(index); + } + + /** + * Returns true if a module with the same module code exists in the TATracker. + */ + public boolean hasModule(String moduleCode) { + requireNonNull(moduleCode); + return modules.contains(new Module(moduleCode)); + } + + /** + * Adds a module to the TATracker. + */ + public void addModule(Module module) { + modules.add(module); + } + + /** + * Removes module with same module code from TA-Tracker. + */ + public void deleteModule(Module module) { + UniqueSessionList copiedSessions = new UniqueSessionList(); + copiedSessions.setSessions(sessions); + + for (Session session : copiedSessions) { + if (session.getModuleCode().equals(module.getIdentifier())) { + sessions.remove(session); + } + } + modules.remove(module); + } + + /** + * Removes {@code key} from this {@code TaTracker}. + * {@code key} must exist in the ta-tracker. + */ + public void removeModule(Module key) { + modules.remove(key); + } + + /** + * Replaces the given module {@code target} in the list with {@code editedModule}. + * {@code target} must exist in the ta-tracker. + * The module identity of {@code editedModule} must not be the same as another existing module in the tracker. + */ + public void setModule(Module target, Module editedModule) { + requireNonNull(editedModule); + + modules.setModule(target, editedModule); + } + + /** + * Sorts modules alphabetically. + */ + public void sortModulesAlphabetically() { + for (Module module : modules) { + module.sortGroupsAlphabetically(); + } + } + + /** + * Sorts modules by rating in ascending order. + */ + public void sortModulesByRatingAscending() { + for (Module module : modules) { + module.sortGroupsByRatingAscending(); + } + } + + /** + * Sorts modules alphabetically. + */ + public void sortModulesByRatingDescending() { + for (Module module : modules) { + module.sortGroupsByRatingDescending(); + } + } + + /** + * Sorts modules by matric number. + */ + public void sortModulesByMatricNumber() { + for (Module module : modules) { + module.sortGroupsByMatricNumber(); + } + } + + /** + * Replaces the contents of the modules list with {@code modules}. + * {@code modules} must not contain duplicate modules. + */ + public void setModules(List modules) { + this.modules.setModules(modules); + } + + @Override + public ObservableList getModuleList() { + return modules.asUnmodifiableObservableList(); + } + + public static Module getCurrentlyShownModule() { + return currentlyShownModule; + } + + public void setCurrentlyShownModule(Module module) { + currentlyShownModule = module; + } + + // ======== Group Methods ================================================== + + /** + * Returns true if a group with the same group code exists in the TATracker. + */ + public boolean hasGroup(String groupCode, String moduleCode) { + requireNonNull(groupCode, moduleCode); + + if (!hasModule(moduleCode)) { + return false; + } + + Module module = getModule(moduleCode); + return module.hasGroup(new Group(groupCode)); + } + + /** + * Adds a group to the TATracker. + */ + public void addGroup(Group group, Module targetModule) { + if (!hasModule(targetModule.getIdentifier())) { + throw new ModuleNotFoundException(); + } + Module module = getModule(targetModule.getIdentifier()); + module.addGroup(group); + } + + /** + * Removes {@code key} from this {@code TaTracker}. + * {@code key} must exist in the ta-tracker. + */ + public void removeGroup(Group group, Module targetModule) { + if (!hasModule(targetModule.getIdentifier())) { + throw new ModuleNotFoundException(); + } + Module module = getModule(targetModule.getIdentifier()); + module.deleteGroup(group); + } + + /** + * Replaces the given group {@code target} in the list with {@code editedGroup}. + * {@code target} must exist in the ta-tracker. + * The group identity of {@code editedGroup} must not be the same as another existing group in the tracker. + */ + public void setGroup(Group target, Group editedGroup, Module targetModule) { + requireNonNull(editedGroup); + + if (!hasModule(targetModule.getIdentifier())) { + throw new ModuleNotFoundException(); + } + + Module module = getModule(targetModule.getIdentifier()); + module.setGroup(target, editedGroup); + } + + /** + * Replaces the contents of the group list with {@code groups}. + * {@code groups} must not contain duplicate groups. + */ + public void setCurrentlyShownGroups(List groups) { + if (groups.isEmpty()) { + this.currentlyShownGroup = null; + } + this.currentlyShownGroups.setGroups(groups); + } + + /** + * Replaces the contents of the group list with the groups at the given + * module index. + */ + public void setCurrentlyShownGroups(int n) { + setCurrentlyShownModule(modules.get(n)); + setCurrentlyShownGroups(((modules.get(n)).getGroupList())); + } + + /** + * Updates the currently shown groups to be that of the currently shown module + * code. + */ + public void updateCurrentlyShownGroups(String moduleCode) { + setCurrentlyShownModule(modules.getModule(moduleCode)); + setCurrentlyShownGroups((modules.getModule(moduleCode)).getGroupList()); + } + + @Override + public ObservableList getCurrentlyShownGroupList() { + return currentlyShownGroups.asUnmodifiableObservableList(); + } + + public static Group getCurrentlyShownGroup() { + return currentlyShownGroup; + } + + public void setCurrentlyShownGroup(Group group) { + currentlyShownGroup = group; + } + + // ======== Student Methods ================================================ + + /** + * Returns true if a given student with the same identity as {@code student} + * exists in a module group that is in TaTracker. + * @param targetGroup group to check if {@code student} is enrolled in. + * @param targetModule module that contains {@code group}. + */ + public boolean hasStudent(Student student, String targetGroup, String targetModule) { + requireNonNull(student); + + if (!hasGroup(targetGroup, targetModule)) { + return false; + } + + Module module = getModule(targetModule); + Group group = module.getGroup(targetGroup); + return group.hasStudent(student); + } + + /** + * Returns true if a given student with the same identity as {@code student} + * exists in a module group that is in TaTracker. + * @param targetGroup id of group to check if {@code student} is enrolled in. + * @param targetModule id of module that contains {@code group}. + */ + public boolean hasStudent(Matric matric, String targetGroup, String targetModule) { + requireNonNull(matric); + + if (!hasGroup(targetGroup, targetModule)) { + return false; + } + return modules.getModule(targetModule).hasStudent(matric, targetGroup); + } + + /** + * Returns true if a student with the same identity as {@code student} exists in the ta-tracker. + */ + public boolean hasStudent(Student student) { + requireNonNull(student); + return currentlyShownStudents.contains(student); + } + + public Student getStudent(Matric matric, String groupCode, String moduleCode) { + requireNonNull(matric); + return modules.getModule(moduleCode).getStudent(matric, groupCode); + } + + /** + * Adds the given student into a module group that is in TaTracker. + * @param student student to add, which must not already exist in the TaTracker module group. + * @param targetGroup group to add {@code student} into, which must exist in the TaTracker module. + * @param targetModule module to add {@code student} into, which must exist in the TaTracker. + */ + public void addStudent(Student student, String targetGroup, String targetModule) { + if (!hasModule(targetModule)) { + throw new ModuleNotFoundException(); + } + + if (!hasGroup(targetGroup, targetModule)) { + throw new GroupNotFoundException(); + } + + Module module = getModule(targetModule); + Group group = module.getGroup(targetGroup); + group.addStudent(student); + } + + /** + * Adds a student to the ta-tracker. + * The student must not already exist in the ta-tracker. + */ + public void addStudent(Student p) { + currentlyShownStudents.add(p); + } + + /** + * Deletes the given student {@code target} from a module group that is in TaTracker. + * @param target student to delete, which must exist in the TaTracker module group. + * @param targetGroup group to delete student {@code target} from, which must exist in the TaTracker module. + * @param targetModule module to delete student {@code target} from, which must exist in the TaTracker. + */ + public void deleteStudent(Student target, String targetGroup, String targetModule) { + if (!hasModule(targetModule)) { + throw new ModuleNotFoundException(); + } + + if (!hasGroup(targetGroup, targetModule)) { + throw new GroupNotFoundException(); + } + + Module module = getModule(targetModule); + Group group = module.getGroup(targetGroup); + group.deleteStudent(target); + } + + /** + * Removes {@code key} from this {@code TaTracker}. + * {@code key} must exist in the ta-tracker. + */ + public void removeStudent(Student key) { + currentlyShownStudents.remove(key); + } + + /** + * Replaces the given student {@code target} in a TaTracker module group with {@code editedStudent}. + * @param target student to edit, which must exist in the TaTracker module group. + * @param editedStudent the edited student {@code target}. + * The identity of {@code editedStudent} must be the same as {@code target}. + * @param targetGroup group with the student to edit, which must exist in the TaTracker module. + * @param targetModule module with the student to edit, which must exist in the TaTracker. + */ + public void setStudent(Student target, Student editedStudent, String targetGroup, String targetModule) { + if (!hasModule(targetModule)) { + throw new ModuleNotFoundException(); + } + + if (!hasGroup(targetGroup, targetModule)) { + throw new GroupNotFoundException(); + } + + modules.getModule(targetModule).setStudent(target, editedStudent, targetGroup); + } + + /** + * Replaces the given student {@code target} in the list with {@code editedStudent}. + * {@code target} must exist in the ta-tracker. + * The student identity of {@code editedStudent} must not be the same as another existing student in the tracker. + */ + public void setStudent(Student target, Student editedStudent) { + requireNonNull(editedStudent); + + currentlyShownStudents.setStudent(target, editedStudent); + } + + /** + * Replaces the contents of the student list with {@code students}. + * {@code students} must not contain duplicate students. + */ + public void setCurrentlyShownStudents(List students) { + this.currentlyShownStudents.setStudents(students); + } + + /** + * Replaces the contents of the student list with the students at the given + * group index of the given module. + */ + public void setCurrentlyShownStudents(String moduleCode, int n) { + setCurrentlyShownModule(modules.getModule(moduleCode)); + setCurrentlyShownGroup(currentlyShownModule.get(n)); + setCurrentlyShownStudents(currentlyShownGroup.getStudentList()); + } + + /** + * Replaces the contents of the student list with the students at the given + * group index of the given module. + */ + public void setCurrentlyShownStudents(int moduleIndex, int groupIndex) { + setCurrentlyShownModule(modules.get(moduleIndex)); + setCurrentlyShownGroup(currentlyShownModule.get(groupIndex)); + setCurrentlyShownStudents(((modules.get(moduleIndex).get(groupIndex)).getStudentList())); + } + + /** + * Updates the currently shown groups to be that of the currently shown module + * code. + */ + public void updateCurrentlyShownStudents(String groupCode, String moduleCode) { + setCurrentlyShownModule(modules.getModule(moduleCode)); + setCurrentlyShownGroup(currentlyShownModule.getGroup(groupCode)); + setCurrentlyShownStudents(((modules.getModule(moduleCode)).getGroup(groupCode)).getStudentList()); + } + + @Override + public ObservableList getCurrentlyShownStudentList() { + return currentlyShownStudents.asUnmodifiableObservableList(); + } + + // ======== Utility Methods ================================================ + + @Override + public int hashCode() { + return modules.hashCode(); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof TaTracker); // instanceof handles nulls + // && modules.equals(((TaTracker) other).modules)); + } + + @Override + public String toString() { + return modules.asUnmodifiableObservableList().size() + " students"; + // TODO: refine later + } +} diff --git a/src/main/java/seedu/address/model/UserPrefs.java b/src/main/java/tatracker/model/UserPrefs.java similarity index 70% rename from src/main/java/seedu/address/model/UserPrefs.java rename to src/main/java/tatracker/model/UserPrefs.java index 25a5fd6eab9..872e5358ad6 100644 --- a/src/main/java/seedu/address/model/UserPrefs.java +++ b/src/main/java/tatracker/model/UserPrefs.java @@ -1,4 +1,4 @@ -package seedu.address.model; +package tatracker.model; import static java.util.Objects.requireNonNull; @@ -6,7 +6,7 @@ import java.nio.file.Paths; import java.util.Objects; -import seedu.address.commons.core.GuiSettings; +import tatracker.commons.core.GuiSettings; /** * Represents User's preferences. @@ -14,7 +14,7 @@ public class UserPrefs implements ReadOnlyUserPrefs { private GuiSettings guiSettings = new GuiSettings(); - private Path addressBookFilePath = Paths.get("data" , "addressbook.json"); + private Path taTrackerFilePath = Paths.get("data" , "tatracker.json"); /** * Creates a {@code UserPrefs} with default values. @@ -35,7 +35,7 @@ public UserPrefs(ReadOnlyUserPrefs userPrefs) { public void resetData(ReadOnlyUserPrefs newUserPrefs) { requireNonNull(newUserPrefs); setGuiSettings(newUserPrefs.getGuiSettings()); - setAddressBookFilePath(newUserPrefs.getAddressBookFilePath()); + setTaTrackerFilePath(newUserPrefs.getTaTrackerFilePath()); } public GuiSettings getGuiSettings() { @@ -47,13 +47,13 @@ public void setGuiSettings(GuiSettings guiSettings) { this.guiSettings = guiSettings; } - public Path getAddressBookFilePath() { - return addressBookFilePath; + public Path getTaTrackerFilePath() { + return taTrackerFilePath; } - public void setAddressBookFilePath(Path addressBookFilePath) { - requireNonNull(addressBookFilePath); - this.addressBookFilePath = addressBookFilePath; + public void setTaTrackerFilePath(Path taTrackerFilePath) { + requireNonNull(taTrackerFilePath); + this.taTrackerFilePath = taTrackerFilePath; } @Override @@ -68,19 +68,19 @@ public boolean equals(Object other) { UserPrefs o = (UserPrefs) other; return guiSettings.equals(o.guiSettings) - && addressBookFilePath.equals(o.addressBookFilePath); + && taTrackerFilePath.equals(o.taTrackerFilePath); } @Override public int hashCode() { - return Objects.hash(guiSettings, addressBookFilePath); + return Objects.hash(guiSettings, taTrackerFilePath); } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append("Gui Settings : " + guiSettings); - sb.append("\nLocal data file location : " + addressBookFilePath); + sb.append("\nLocal data file location : " + taTrackerFilePath); return sb.toString(); } diff --git a/src/main/java/tatracker/model/group/DuplicateGroupException.java b/src/main/java/tatracker/model/group/DuplicateGroupException.java new file mode 100644 index 00000000000..6d8604af43a --- /dev/null +++ b/src/main/java/tatracker/model/group/DuplicateGroupException.java @@ -0,0 +1,12 @@ +package tatracker.model.group; + +/** + * Signals that the operation will result in duplicate Groups + * (Groups are considered duplicates if they have the same + * identifiers). + */ +public class DuplicateGroupException extends RuntimeException { + public DuplicateGroupException() { + super("Operation would result in duplicate groups"); + } +} diff --git a/src/main/java/tatracker/model/group/Group.java b/src/main/java/tatracker/model/group/Group.java new file mode 100644 index 00000000000..1020943a0af --- /dev/null +++ b/src/main/java/tatracker/model/group/Group.java @@ -0,0 +1,203 @@ +package tatracker.model.group; + +import static java.util.Objects.requireNonNull; + +import java.util.Objects; + +import javafx.collections.ObservableList; + +import tatracker.model.student.Matric; +import tatracker.model.student.Student; +import tatracker.model.student.UniqueStudentList; + +/** + * Represents a group in TAT. + * A group is anything that would include a + * group of students such as a lab or tutorial. + */ +public class Group { + + public static final String CONSTRAINTS_GROUP_CODE = "Group codes cannot be blank"; + + private static final GroupType DEFAULT_GROUP_TYPE = GroupType.TUTORIAL; + + private String identifier; + private GroupType groupType; + private final UniqueStudentList students; + + /** + * Constructs a group object with a default group type. + */ + public Group(String identifier) { + this(identifier, DEFAULT_GROUP_TYPE); + } + + /** + * Constructs a group object. + * + * @param identifier identifies the group. + * For example, the tutorial code for a tutorial, etc. + */ + public Group(String identifier, GroupType groupType) { + this.identifier = identifier; + this.groupType = groupType; + this.students = new UniqueStudentList(); + } + + /** + * Constructor to be used in testing. + */ + public Group(String identifier, GroupType groupType, UniqueStudentList students) { + this.identifier = identifier; + this.groupType = groupType; + this.students = students; + } + + /** + * Sorts students alphabetically. + */ + public void sortStudentsAlphabetically() { + students.sortAlphabetically(); + } + + /** + * Sorts students by rating in ascending order. + */ + public void sortStudentsByRatingAscending() { + students.sortByRatingAscending(); + } + + /** + * Sorts students by rating in descending order. + */ + public void sortStudentsByRatingDescending() { + students.sortByRatingDescending(); + } + + /** + * Sorts students by matric number in ascending order. + */ + public void sortStudentsByMatricNumber() { + students.sortByMatric(); + } + + /** + * Returns the group identifier. + */ + public String getIdentifier() { + return identifier; + } + + /** + * Returns the list of students in the group. + */ + public UniqueStudentList getUniqueStudentList() { + return students; + } + + /** + * Updates the group code. + */ + public void setIdentifier(String newIdentifier) { + this.identifier = newIdentifier; + } + + /** + * Updates the group type. + */ + public void setGroupType(GroupType newGroupType) { + this.groupType = newGroupType; + } + + /** + * Returns the group type of this group. + * For example, if it is a tutorial or lab. + */ + public GroupType getGroupType() { + return groupType; + } + + /** + * Returns the student list. + */ + public ObservableList getStudentList() { + return students.asUnmodifiableObservableList(); + } + + public boolean hasStudent(Student student) { + return students.contains(student); + } + + public boolean hasStudent(Matric matric) { + return students.contains(matric); + } + + /** + * Returns the student enrolled in this module with the given + * matriculation number (the student id). + * Returns null if no such student exists. + */ + public Student getStudent(Matric studentId) { + return students.get(studentId); + } + + /** + * Returns the student at index i. + */ + public Student get(int i) { + return students.get(i); + } + + /** + * Adds a student to the list of enrolled students. + */ + public void addStudent(Student student) { + students.add(student); + } + + /** + * Deletes the given student from the list of enrolled students, + * if it exists. + */ + public void deleteStudent(Student student) { + students.remove(student); + } + + /** + * Replaces the given student {@code target} in the list with {@code editedStudent}. + * {@code target} must exist in the list of enrolled students. + * The student identity of {@code editedStudent} must not be the same as another existing student in the group. + */ + public void setStudent(Student target, Student editedStudent) { + requireNonNull(editedStudent); + students.setStudent(target, editedStudent); + } + + /** + * Returns true if both groups have the same identifiers. + */ + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof Group)) { + return false; + } + + Group otherGroup = (Group) other; + return this.identifier.equals(otherGroup.identifier); + } + + @Override + public int hashCode() { + return Objects.hash(identifier); + } + + //TODO: edit once Student is made + @Override + public String toString() { + return String.format("%s", identifier); + } +} diff --git a/src/main/java/tatracker/model/group/GroupNotFoundException.java b/src/main/java/tatracker/model/group/GroupNotFoundException.java new file mode 100644 index 00000000000..0437411c23a --- /dev/null +++ b/src/main/java/tatracker/model/group/GroupNotFoundException.java @@ -0,0 +1,6 @@ +package tatracker.model.group; + +/** + * Signals that the operation is unable to find the specified group. + */ +public class GroupNotFoundException extends RuntimeException {} diff --git a/src/main/java/tatracker/model/group/GroupType.java b/src/main/java/tatracker/model/group/GroupType.java new file mode 100644 index 00000000000..c90ff290719 --- /dev/null +++ b/src/main/java/tatracker/model/group/GroupType.java @@ -0,0 +1,43 @@ +package tatracker.model.group; + +import static java.util.Objects.requireNonNull; + +import java.util.Arrays; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * Represents a group type. + * Can be a lab or a tutorial. + */ +public enum GroupType { + LAB, + TUTORIAL, + RECITATION, + OTHER; + + public static final String MESSAGE_CONSTRAINTS = + "These are the only group types: lab, tutorial, recitation, other"; + + private static final Map GROUP_TYPES = Arrays.stream(values()) + .collect(Collectors.toUnmodifiableMap(type -> type.name().toLowerCase(), type -> type)); + + public static boolean isValidGroupType(String test) { + return GROUP_TYPES.containsKey(test.toLowerCase()); + } + + public static GroupType getGroupType(String groupType) { + requireNonNull(groupType); + return GROUP_TYPES.get(groupType.toLowerCase()); + } + + @Override + public String toString() { + String name = name(); + if (name.length() > 0) { + // Capitalise first letter + name = name.substring(0, 1).toUpperCase() + name.substring(1); + } + return name; + } +} diff --git a/src/main/java/tatracker/model/group/UniqueGroupList.java b/src/main/java/tatracker/model/group/UniqueGroupList.java new file mode 100644 index 00000000000..2919b55782e --- /dev/null +++ b/src/main/java/tatracker/model/group/UniqueGroupList.java @@ -0,0 +1,155 @@ +package tatracker.model.group; + +import static java.util.Objects.requireNonNull; +import static tatracker.commons.util.CollectionUtil.requireAllNonNull; + +import java.util.Iterator; +import java.util.List; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; + +/** + * A list of groups that enforces uniqueness between its elements and does not allow nulls. + * A group is considered unique by comparing using {@code Group#equals(Object)}. As such, adding and updating of + * groups uses Group#equals(Object) for equality so as to ensure that the group being added or updated is + * unique in terms of identity in the UniqueGroupList. + * + * Supports a minimal set of list operations. + * + * @see Group#equals(Object) + */ +public class UniqueGroupList implements Iterable { + + private final ObservableList internalList = FXCollections.observableArrayList(); + private final ObservableList internalUnmodifiableList = + FXCollections.unmodifiableObservableList(internalList); + + public int size() { + return internalList.size(); + } + + /** + * Returns true if the list contains an equivalent group as the given argument. + */ + public boolean contains(Group toCheck) { + requireNonNull(toCheck); + return internalList.stream().anyMatch(toCheck::equals); + } + + public Group get(int n) { + return internalList.get(n); + } + + /** + * Returns the group in this list with the given group id. + * Returns null if no such group exists. + */ + public Group get(String groupId) { + for (Group group : internalList) { + if (group.getIdentifier().equals(groupId)) { + return group; + } + } + return null; // Did not find a group with the given group id + } + + /** + * Adds a group to the list. + * The group must not already exist in the list. + */ + public void add(Group toAdd) { + requireNonNull(toAdd); + if (contains(toAdd)) { + throw new DuplicateGroupException(); + } + internalList.add(toAdd); + } + + /** + * Removes the equivalent group from the list. + * The group must exist in the list. + */ + public void remove(Group toRemove) { + requireNonNull(toRemove); + if (!internalList.remove(toRemove)) { + throw new GroupNotFoundException(); + } + } + + /** + * Replaces the group {@code target} in the list with {@code editedGroup}. + * {@code target} must exist in the list. + * The group identity of {@code editedGroup} must not be the same as another existing group in the list. + */ + public void setGroup(Group target, Group editedGroup) { + requireAllNonNull(target, editedGroup); + + int index = internalList.indexOf(target); + if (index == -1) { + throw new GroupNotFoundException(); + } + + if (!target.equals(editedGroup) && contains(editedGroup)) { + throw new DuplicateGroupException(); + } + + internalList.set(index, editedGroup); + } + + public void setGroups(UniqueGroupList replacement) { + requireNonNull(replacement); + internalList.setAll(replacement.internalList); + } + + /** + * Replaces the contents of this list with {@code groups}. + * {@code groups} must not contain duplicate groups. + */ + public void setGroups(List groups) { + requireAllNonNull(groups); + if (!groupsAreUnique(groups)) { + throw new DuplicateGroupException(); + } + + internalList.setAll(groups); + } + + /** + * Returns the backing list as an unmodifiable {@code ObservableList}. + */ + public ObservableList asUnmodifiableObservableList() { + return internalUnmodifiableList; + } + + @Override + public Iterator iterator() { + return internalList.iterator(); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof UniqueGroupList // instanceof handles nulls + && internalList.equals(((UniqueGroupList) other).internalList)); + } + + @Override + public int hashCode() { + return internalList.hashCode(); + } + + /** + * Returns true if {@code students} contains only unique students. + */ + private boolean groupsAreUnique(List groups) { + for (int i = 0; i < groups.size() - 1; i++) { + for (int j = i + 1; j < groups.size(); j++) { + if (groups.get(i).equals(groups.get(j))) { + return false; + } + } + } + return true; + } +} diff --git a/src/main/java/tatracker/model/module/DuplicateModuleException.java b/src/main/java/tatracker/model/module/DuplicateModuleException.java new file mode 100644 index 00000000000..010b317a8b3 --- /dev/null +++ b/src/main/java/tatracker/model/module/DuplicateModuleException.java @@ -0,0 +1,12 @@ +package tatracker.model.module; + +/** + * Signals that the operation will result in duplicate Modules + * (Modules are considered duplicates if they have the same + * identifiers). + */ +public class DuplicateModuleException extends RuntimeException { + public DuplicateModuleException() { + super("Operation would result in duplicate modules"); + } +} diff --git a/src/main/java/tatracker/model/module/Module.java b/src/main/java/tatracker/model/module/Module.java new file mode 100644 index 00000000000..530f033ad0a --- /dev/null +++ b/src/main/java/tatracker/model/module/Module.java @@ -0,0 +1,213 @@ +package tatracker.model.module; + +import java.util.Objects; + +import javafx.collections.ObservableList; + +import tatracker.model.group.Group; +import tatracker.model.group.UniqueGroupList; +import tatracker.model.student.Matric; +import tatracker.model.student.Student; + +/** + * Represents a module in the TAT. + */ +public class Module { + + public static final String CONSTRAINTS_MODULE_CODE = "Module codes cannot be blank"; + public static final String CONSTRAINTS_MODULE_NAME = "Module names cannot be blank"; + + private static final String DEFAULT_NAME = ""; + + private final String identifier; + private String name; + private final UniqueGroupList groups; + + /** + * Constructs a module object with no name. + */ + public Module(String identifier) { + this(identifier, DEFAULT_NAME); + } + + /** + * Constructs a module object. + * + * @param identifier identifies the module. + * Usually equal to the module code. + * @param name the name of the module. + */ + public Module(String identifier, String name) { + this.identifier = identifier; + this.name = name; + this.groups = new UniqueGroupList(); + } + + /** + * Constructor for use in testing. + */ + public Module(String identifier, String name, UniqueGroupList groups) { + this.identifier = identifier; + this.name = name; + this.groups = groups; + } + + /** + * Returns the module identifier. + */ + public String getIdentifier() { + return identifier; + } + + /** + * Returns module name. + */ + public String getName() { + return name; + } + + /** + * Changes the module name. + */ + public void setName(String newName) { + this.name = newName; + } + + /** + * Returns module at index n. + */ + public Group get(int n) { + return groups.get(n); + } + + public Student getStudent(Matric matric, String groupCode) { + return groups.get(groupCode).getStudent(matric); + } + + /** + * Returns the group list. + */ + public ObservableList getGroupList() { + return groups.asUnmodifiableObservableList(); + } + + /** + * Returns the unique group list. + */ + public UniqueGroupList getUniqueGroupList() { + return groups; + } + + + public boolean hasStudent(Matric matric, String targetGroup) { + return groups.get(targetGroup).hasStudent(matric); + } + + /** + * Adds a group to the list of module groups. + * Returns the unique sessions list. + */ + public void setStudent(Student student, Student editedStudent, String targetGroup) { + groups.get(targetGroup).setStudent(student, editedStudent); + } + + public boolean hasGroup(Group group) { + return groups.contains(group); + } + + /** + * Adds a group to the list of module groups. + */ + public void addGroup(Group group) { + groups.add(group); + } + + /** + * Returns the group in this module with the given group id. + * Returns null if no such group exists. + */ + public Group getGroup(String groupId) { + return groups.get(groupId); + } + + /** + * Deletes the given group from the list of module groups, + * if it exists. + */ + public void deleteGroup(Group group) { + groups.remove(group); + } + + /** + * Replaces the given group {@code target} in the list with {@code editedGroup}. + * {@code target} must exist in the list of groups. + * The group identity of {@code editedGroup} must not be the same as another existing group in the module. + */ + public void setGroup(Group target, Group editedGroup) { + groups.setGroup(target, editedGroup); + } + + /** + * Sorts students in the groups alphabetically. + */ + public void sortGroupsAlphabetically() { + for (Group group : groups) { + group.sortStudentsAlphabetically(); + } + } + + /** + * Sorts the students in the groups by rating in ascending order. + */ + public void sortGroupsByRatingAscending() { + for (Group group : groups) { + group.sortStudentsByRatingAscending(); + } + } + + /** + * Sorts the students in the groups by rating in descending order. + */ + public void sortGroupsByRatingDescending() { + for (Group group : groups) { + group.sortStudentsByRatingDescending(); + } + } + + /** + * Sorts the students in the groups by matric number in descending order. + */ + public void sortGroupsByMatricNumber() { + for (Group group : groups) { + group.sortStudentsByMatricNumber(); + } + } + + /** + * Returns true if both modules have the same identifiers. + */ + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof Module)) { + return false; + } + + Module otherModule = (Module) other; + return this.identifier.equals(otherModule.identifier); + } + + @Override + public int hashCode() { + return Objects.hash(identifier); + } + + @Override + public String toString() { + return String.format("%s (%s)", name, identifier); + } + +} diff --git a/src/main/java/tatracker/model/module/ModuleNotFoundException.java b/src/main/java/tatracker/model/module/ModuleNotFoundException.java new file mode 100644 index 00000000000..e3ea0b81035 --- /dev/null +++ b/src/main/java/tatracker/model/module/ModuleNotFoundException.java @@ -0,0 +1,6 @@ +package tatracker.model.module; + +/** + * Signals that the operation is unable to find the specified module. + */ +public class ModuleNotFoundException extends RuntimeException {} diff --git a/src/main/java/tatracker/model/module/UniqueModuleList.java b/src/main/java/tatracker/model/module/UniqueModuleList.java new file mode 100644 index 00000000000..efb60110cec --- /dev/null +++ b/src/main/java/tatracker/model/module/UniqueModuleList.java @@ -0,0 +1,167 @@ +package tatracker.model.module; + +import static java.util.Objects.requireNonNull; +import static tatracker.commons.util.CollectionUtil.requireAllNonNull; + +import java.util.Iterator; +import java.util.List; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; + +/** + * A list of modules that enforces uniqueness between its elements and does not allow nulls. + * A module is considered unique by comparing using {@code Module#equals(Object)}. As such, adding and updating of + * groups uses Module#equals(Object) for equality so as to ensure that the module being added or updated is + * unique in terms of identity in the UniqueModuleList. + * + * Supports a minimal set of list operations. + * + * @see Module#equals(Object) + */ +public class UniqueModuleList implements Iterable { + + private final ObservableList internalList = FXCollections.observableArrayList(); + private final ObservableList internalUnmodifiableList = + FXCollections.unmodifiableObservableList(internalList); + + /** + * Returns true if the list contains an equivalent module as the given argument. + */ + public boolean contains(Module toCheck) { + requireNonNull(toCheck); + return internalList.stream().anyMatch(toCheck::equals); + } + + public int size() { + return internalList.size(); + } + + public Module get(int n) { + return internalList.get(n); + } + + /** + * Adds a module to the list. + * The module must not already exist in the list. + */ + public void add(Module toAdd) { + requireNonNull(toAdd); + if (contains(toAdd)) { + throw new DuplicateModuleException(); + } + internalList.add(toAdd); + } + + /** + * Replaces the module {@code target} in the list with {@code editedModule}. + * {@code target} must exist in the list. + * The module identity of {@code editedModule} must not be the same as another existing module in the list. + */ + public void setModule(Module target, Module editedModule) { + requireAllNonNull(target, editedModule); + + int index = internalList.indexOf(target); + if (index == -1) { + throw new ModuleNotFoundException(); + } + + if (!target.equals(editedModule) && contains(editedModule)) { + throw new DuplicateModuleException(); + } + + internalList.set(index, editedModule); + } + + public Module getModule(Module module) { + for (int i = 0; i < internalList.size(); i++) { + if (module.getIdentifier().equals(internalList.get(i).getIdentifier())) { + return internalList.get(i); + } + } + return null; + } + + /** + * Gets module with given module code. + * Returns null if no such module exists. + */ + public Module getModule(String code) { + Module module = null; + for (int i = 0; i < this.size(); ++i) { + module = this.get(i); + if (module.getIdentifier().equals(code)) { + break; + } + module = null; + } + return module; + } + + /** + * Removes the equivalent module from the list. + * The module must exist in the list. + */ + public void remove(Module toRemove) { + requireNonNull(toRemove); + if (!internalList.remove(toRemove)) { + throw new ModuleNotFoundException(); + } + } + + public void setModules(UniqueModuleList replacement) { + requireNonNull(replacement); + internalList.setAll(replacement.internalList); + } + + /** + * Replaces the contents of this list with {@code modules}. + * {@code modules} must not contain duplicate modules. + */ + public void setModules(List modules) { + requireAllNonNull(modules); + if (!modulesAreUnique(modules)) { + throw new DuplicateModuleException(); + } + + internalList.setAll(modules); + } + + /** + * Returns the backing list as an unmodifiable {@code ObservableList}. + */ + public ObservableList asUnmodifiableObservableList() { + return internalUnmodifiableList; + } + + @Override + public Iterator iterator() { + return internalList.iterator(); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof UniqueModuleList // instanceof handles nulls + && internalList.equals(((UniqueModuleList) other).internalList)); + } + + @Override + public int hashCode() { + return internalList.hashCode(); + } + + /** + * Returns true if {@code students} contains only unique students. + */ + private boolean modulesAreUnique(List modules) { + for (int i = 0; i < modules.size() - 1; i++) { + for (int j = i + 1; j < modules.size(); j++) { + if (modules.get(i).equals(modules.get(j))) { + return false; + } + } + } + return true; + } +} diff --git a/src/main/java/tatracker/model/session/Session.java b/src/main/java/tatracker/model/session/Session.java new file mode 100644 index 00000000000..3dfec3ed232 --- /dev/null +++ b/src/main/java/tatracker/model/session/Session.java @@ -0,0 +1,252 @@ +package tatracker.model.session; + +import java.time.Duration; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.Objects; + +/** + * Represents a session in TAT. + * A session is any claimable duty that has a start and end time. + * Guarantees: Date, Start Time and End Time are not null. + */ +public class Session implements Comparable { + + public static final String CONSTRAINTS_RECURRING_WEEKS = "Recurring weeks must be an unsigned number"; + + /** For converting date times to strings. Example: "2020-03-03 14:00" */ + private static final DateTimeFormatter FORMAT_DATE_TIME = DateTimeFormatter.ofPattern("dd MMM yyyy, hh:mma"); + + /** For formatting sessions with minimal notation. */ + private static final String FORMAT_MIN_DESCRIPTION = "%s (%s)\nStart: %s\nEnd: %s"; + + private LocalDateTime startDateTime; + private LocalDateTime endDateTime; + private String moduleCode; + private SessionType type; + private String description; + private int recurring; + private boolean isDone; + + /** + * Default Constructor for Session. + * Creates a session object with default values. + */ + public Session() { + this.startDateTime = LocalDateTime.now(); + this.endDateTime = LocalDateTime.now(); + this.recurring = 0; + this.moduleCode = ""; + this.type = SessionType.OTHER; + this.description = "Default Session"; + this.isDone = false; + } + + /** + * Constructs a Session object. + * The session's end time should be strictly after the session's start time. + */ + public Session(LocalDateTime start, LocalDateTime end, SessionType type, int recurring, String moduleCode, + String description) throws IllegalArgumentException { + + if (start.compareTo(end) > 0) { + throw new IllegalArgumentException("The start time of a session cannot be after the end time!"); + } + + this.startDateTime = start; + this.endDateTime = end; + this.recurring = recurring; + this.moduleCode = moduleCode; + this.type = type; + this.description = description; + this.isDone = false; + } + + /** + * Returns true if both sessions have the same date, timing, module, and type. + * This defines a weaker notion of equality between two sessions. + */ + public boolean isSameSession(Session s) { + return startDateTime.equals(s.startDateTime) + && endDateTime.equals(s.endDateTime) + && moduleCode.equals(s.moduleCode) + && type.equals(s.type); + } + + public LocalDate getDate() { + return this.startDateTime.toLocalDate(); + } + + /** + * Returns the start time of the session. + */ + public LocalDateTime getStartDateTime() { + return this.startDateTime; + } + + /** + * Sets the start time of the session. + */ + public void setStartDateTime(LocalDateTime startDateTime) { + this.startDateTime = startDateTime; + } + + /** + * Returns the end time of the session. + */ + public LocalDateTime getEndDateTime() { + return this.endDateTime; + } + + /** + * Sets the end time of the session. + */ + public void setEndDateTime(LocalDateTime endDateTime) { + this.endDateTime = endDateTime; + } + + /** + * Returns a value that states how long a session will occur. weekly basis. + */ + public int getRecurring() { + return this.recurring; + } + + /** + * Returns true if session is already completed; false otherwise. + */ + public boolean getIsDone() { + return this.isDone; + } + + /** + * Marks the session as done. + */ + public void done() { + this.isDone = true; + } + + /** + * Sets whether the session's recurring value. + */ + public void setRecurring(int recurring) { + this.recurring = recurring; + } + + /** + * Returns the module code associated with this session. + */ + public String getModuleCode() { + return this.moduleCode; + } + + /** + * Sets the module code associated with this session. + */ + public void setModuleCode(String moduleCode) { + this.moduleCode = moduleCode; + } + + /** + * Returns the type of session. + */ + public SessionType getSessionType() { + return this.type; + } + + /** + * Sets the type of session. + */ + public void setType(SessionType type) { + this.type = type; + } + + /** + * Returns the description of the session. + */ + public String getDescription() { + return this.description; + } + + /** + * Sets the description of the session. + */ + public void setDescription(String description) { + this.description = description; + } + + /** + * Returns the duration of the session to the nearest hour. + */ + public Duration getDurationToNearestHour() { + Duration duration = Duration.between(this.startDateTime, this.endDateTime); + + long hours = duration.toHours(); + int minutesPart = duration.toMinutesPart(); + + if (minutesPart > 0) { + hours += 1; + } + return Duration.ofHours(hours); + } + + public String getStartDateTimeDescription() { + return startDateTime.format(FORMAT_DATE_TIME); + } + + public String getEndDateTimeDescription() { + return endDateTime.format(FORMAT_DATE_TIME); + } + + public String getMinimalDescription() { + return String.format(FORMAT_MIN_DESCRIPTION, type, moduleCode, + getStartDateTimeDescription(), getEndDateTimeDescription()); + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append("Type: ").append(type) + .append(" Start: ").append(getStartDateTimeDescription()) + .append(" End: ").append(getEndDateTimeDescription()) + .append(" Module Code: ").append(moduleCode) + .append(" Description: ").append(description) + .append(" Recurs: ").append(recurring); + return builder.toString(); + } + + /** + * Returns true if both sessions have the same identity and data fields. + */ + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof Session)) { + return false; + } + + Session otherSession = (Session) other; + + return isSameSession(otherSession) + && description.equals(otherSession.description) + && recurring == otherSession.recurring + && isDone == otherSession.isDone; + } + + @Override + public int hashCode() { + return Objects.hash(startDateTime, endDateTime, moduleCode, type); + } + + /** + * Compare Sessions based on the session that will occur first. + */ + @Override + public int compareTo(Session other) { + return getDate().compareTo(other.getDate()); + } +} diff --git a/src/main/java/tatracker/model/session/SessionPredicate.java b/src/main/java/tatracker/model/session/SessionPredicate.java new file mode 100644 index 00000000000..ca1f9bb04c4 --- /dev/null +++ b/src/main/java/tatracker/model/session/SessionPredicate.java @@ -0,0 +1,83 @@ +package tatracker.model.session; + +import java.time.LocalDate; +import java.util.Optional; +import java.util.function.Predicate; + +import tatracker.commons.util.CollectionUtil; + + +/** + * Tests that a {@code Session}'s arguments matches any of the keywords given. + */ +public class SessionPredicate implements Predicate { + + private LocalDate date; + private String moduleCode; + private SessionType sessionType; + + public SessionPredicate() {} + + /** + * Copy constructor. + */ + public SessionPredicate(SessionPredicate toCopy) { + setDate(toCopy.date); + setModuleCode(toCopy.moduleCode); + setSessionType(toCopy.sessionType); + } + + /** + * Returns true if at least one field is specified. + */ + public boolean isAnyFieldEdited() { + return CollectionUtil.isAnyNonNull(date, moduleCode, sessionType); + } + + public void setDate(LocalDate date) { + this.date = date; + } + + public Optional getDate() { + return Optional.ofNullable(date); + } + + public void setModuleCode(String moduleCode) { + this.moduleCode = moduleCode.toUpperCase(); + } + + public Optional getModuleCode() { + return Optional.ofNullable(moduleCode); + } + + public void setSessionType(SessionType sessionType) { + this.sessionType = sessionType; + } + + public Optional getSessionType() { + return Optional.ofNullable(sessionType); + } + + @Override + public boolean test(Session session) { + + return session.getDate().equals(date) + || session.getModuleCode().equals(moduleCode) + || session.getSessionType().equals(sessionType); + } + + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + if (!(other instanceof SessionPredicate)) { + return false; + } + SessionPredicate otherPredicate = (SessionPredicate) other; + return date.equals(otherPredicate.date) + && moduleCode.equals(otherPredicate.moduleCode) + && sessionType.equals(otherPredicate.sessionType); + } +} diff --git a/src/main/java/tatracker/model/session/SessionType.java b/src/main/java/tatracker/model/session/SessionType.java new file mode 100644 index 00000000000..07ee079d318 --- /dev/null +++ b/src/main/java/tatracker/model/session/SessionType.java @@ -0,0 +1,59 @@ +package tatracker.model.session; + +import static java.util.Objects.requireNonNull; + +import java.util.Arrays; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * Represents a session type. Session types follows the same specifications as the TSS. + * Example session types include: Tutorial, Grading, Consultation, etc. + */ +public enum SessionType { + TUTORIAL, + LAB, + CONSULTATION, + GRADING, + PREPARATION, + OTHER; + + // TODO: Add Recitations to report + public static final int NUM_SESSION_TYPES = values().length; + + public static final String MESSAGE_CONSTRAINTS = + "These are the only session types: lab, tutorial, consultation, grading, preparation, other"; + + private static final Map SESSION_TYPES = Arrays.stream(values()) + .collect(Collectors.toUnmodifiableMap(type -> type.name().toLowerCase(), type -> type)); + + private static final Map SESSION_TYPE_IDS = Arrays.stream(values()) + .collect(Collectors.toUnmodifiableMap(Enum::ordinal, type -> type)); + + + public static boolean isValidSessionType(String test) { + return SESSION_TYPES.containsKey(test.toLowerCase()); + } + + public static SessionType getSessionType(String sessionType) { + requireNonNull(sessionType); + return SESSION_TYPES.get(sessionType.toLowerCase()); + } + + public static SessionType getSessionTypeById(int id) { + if (id < 0 || id >= NUM_SESSION_TYPES) { + throw new IllegalArgumentException("The requested SessionType ID is invalid!"); + } + return SESSION_TYPE_IDS.get(id); + } + + @Override + public String toString() { + String name = name(); + if (name.length() > 0) { + // Capitalise first letter + name = name.substring(0, 1).toUpperCase() + name.substring(1).toLowerCase(); + } + return name; + } +} diff --git a/src/main/java/tatracker/model/session/UniqueDoneSessionList.java b/src/main/java/tatracker/model/session/UniqueDoneSessionList.java new file mode 100644 index 00000000000..fd8623c6e4e --- /dev/null +++ b/src/main/java/tatracker/model/session/UniqueDoneSessionList.java @@ -0,0 +1,166 @@ +package tatracker.model.session; + +import static java.util.Objects.requireNonNull; +import static tatracker.commons.util.CollectionUtil.requireAllNonNull; + +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; + +import tatracker.model.session.exceptions.DuplicateSessionException; +import tatracker.model.session.exceptions.SessionNotFoundException; + +/** + * A list of done sessions that enforces uniqueness between its elements and does not allow nulls. + * A done session is considered unique by comparing using {@code Session#isSameSession(Session)}. + * + * Supports a minimal set of list operations. + * + * @see Session#isSameSession(Session) + */ +public class UniqueDoneSessionList implements Iterable { + + private final ObservableList internalList = FXCollections.observableArrayList(); + private final ObservableList internalUnmodifiableList = + FXCollections.unmodifiableObservableList(internalList); + + /** + * Returns true if the list contains an equivalent session as the given argument. + */ + public boolean contains(Session toCheck) { + requireNonNull(toCheck); + return internalList.stream().anyMatch(toCheck::isSameSession); + } + + /** + * Returns the size of the UniqueDoneSessionList. + */ + public int size() { + return internalList.size(); + } + + /** + * Returns the session at the given index. + */ + public Session get(int n) { + return internalList.get(n); + } + + /** + * Adds a session to the list. + * The session must not already exist in the list. + */ + public void add(Session toAdd) { + requireNonNull(toAdd); + if (contains(toAdd)) { + throw new DuplicateSessionException(); + } + internalList.add(toAdd); + } + + /** + * Replaces the session {@code target} in the list with {@code editedSession}. + * {@code target} must exist in the list. + * The session identity of {@code editedSession} must not be the same as another existing session in the list. + */ + public void setSession(Session target, Session editedSession) { + requireAllNonNull(target, editedSession); + + int index = internalList.indexOf(target); + if (index == -1) { + throw new SessionNotFoundException(); + } + + if (!target.isSameSession(editedSession) && contains(editedSession)) { + throw new DuplicateSessionException(); + } + + internalList.set(index, editedSession); + } + + /** + * Removes the equivalent session from the list. + * The session must exist in the list. + */ + public void remove(Session toRemove) { + requireNonNull(toRemove); + if (!internalList.remove(toRemove)) { + throw new SessionNotFoundException(); + } + } + + /** + * Removes the session of the given index from the list. + * The session must exist in the list. + */ + public void remove(int n) { + if (n < 0 || n > internalList.size()) { + throw new SessionNotFoundException(); + } + + internalList.remove(n); + } + + public void setSessions(UniqueDoneSessionList replacement) { + requireNonNull(replacement); + internalList.setAll(replacement.internalList); + } + + /** + * Replaces the contents of this list with {@code sessions}. + * {@code sessions} must not contain duplicate sessions. + */ + public void setSessions(List sessions) { + requireAllNonNull(sessions); + if (!sessionsAreUnique(sessions)) { + throw new DuplicateSessionException(); + } + + internalList.setAll(sessions); + } + + /** + * Returns the session list as an unmodifiable {@code ObservableList}. + */ + public ObservableList asUnmodifiableObservableList() { + + FXCollections.sort(internalList, Comparator.comparing(Session::getDate) + .thenComparing(Session::getStartDateTime) + .thenComparing(Session::getEndDateTime)); + return internalUnmodifiableList; + } + + @Override + public Iterator iterator() { + return internalList.iterator(); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof UniqueDoneSessionList // instanceof handles nulls + && internalList.equals(((UniqueDoneSessionList) other).internalList)); + } + + @Override + public int hashCode() { + return internalList.hashCode(); + } + + /** + * Returns true if {@code sessions} contains only unique sessions. + */ + private boolean sessionsAreUnique(List sessions) { + for (int i = 0; i < sessions.size() - 1; i++) { + for (int j = i + 1; j < sessions.size(); j++) { + if (sessions.get(i).isSameSession(sessions.get(j))) { + return false; + } + } + } + return true; + } +} diff --git a/src/main/java/tatracker/model/session/UniqueSessionList.java b/src/main/java/tatracker/model/session/UniqueSessionList.java new file mode 100644 index 00000000000..2fc5bf47520 --- /dev/null +++ b/src/main/java/tatracker/model/session/UniqueSessionList.java @@ -0,0 +1,201 @@ +package tatracker.model.session; + +import static java.util.Objects.requireNonNull; +import static tatracker.commons.util.CollectionUtil.requireAllNonNull; + +import java.time.Duration; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; + +import tatracker.model.session.exceptions.DuplicateSessionException; +import tatracker.model.session.exceptions.SessionNotFoundException; + +/** + * A list of sessions that enforces uniqueness between its elements and does not allow nulls. + * A session is considered unique by comparing using {@code Session#isSameSession(Session)}. + * As such, adding and updating of sessions uses Session#isSameSession(Session) + * for equality so as to ensure that the session being added or updated is + * unique in terms of identity in the UniqueSessionList. However, + * the removal of a session uses Session#equals(Object) so as to ensure that + * the session with exactly the same fields will be removed. + * + * Supports a minimal set of list operations. + * + * @see Session#isSameSession(Session) + */ +public class UniqueSessionList implements Iterable { + + private final ObservableList internalList = FXCollections.observableArrayList(); + private final ObservableList internalUnmodifiableList = + FXCollections.unmodifiableObservableList(internalList); + + /** + * Returns true if the list contains an equivalent session as the given argument. + */ + public boolean contains(Session toCheck) { + requireNonNull(toCheck); + return internalList.stream().anyMatch(toCheck::isSameSession); + } + + /** + * Returns the size of the UniqueSessionList. + */ + public int size() { + return internalList.size(); + } + + /** + * Returns the session at the given index. + */ + public Session get(int n) { + return internalList.get(n); + } + + /** + * Adds a session to the list. + * The session must not already exist in the list. + */ + public void add(Session toAdd) { + requireNonNull(toAdd); + if (contains(toAdd)) { + throw new DuplicateSessionException(); + } + internalList.add(toAdd); + } + + /** + * Replaces the session {@code target} in the list with {@code editedSession}. + * {@code target} must exist in the list. + * The session identity of {@code editedSession} must not be the same as another existing session in the list. + */ + public void setSession(Session target, Session editedSession) { + requireAllNonNull(target, editedSession); + + int index = internalList.indexOf(target); + if (index == -1) { + throw new SessionNotFoundException(); + } + + if (!target.isSameSession(editedSession) && contains(editedSession)) { + throw new DuplicateSessionException(); + } + + internalList.set(index, editedSession); + } + + /** + * Removes the equivalent session from the list. + * The session must exist in the list. + */ + public void remove(Session toRemove) { + requireNonNull(toRemove); + if (!internalList.remove(toRemove)) { + throw new SessionNotFoundException(); + } + } + + /** + * Removes the session of the given index from the list. + * The session must exist in the list. + */ + public void remove(int n) { + if (n < 0 || n > internalList.size()) { + throw new SessionNotFoundException(); + } + + internalList.remove(n); + } + + public void setSessions(UniqueSessionList replacement) { + requireNonNull(replacement); + internalList.setAll(replacement.internalList); + } + + /** + * Replaces the contents of this list with {@code sessions}. + * {@code sessions} must not contain duplicate sessions. + */ + public void setSessions(List sessions) { + requireAllNonNull(sessions); + if (!sessionsAreUnique(sessions)) { + throw new DuplicateSessionException(); + } + + internalList.setAll(sessions); + } + + /** + * Returns the session list as an unmodifiable {@code ObservableList}. + */ + public ObservableList asUnmodifiableObservableList() { + + FXCollections.sort(internalList, Comparator.comparing(Session::getDate) + .thenComparing(Session::getStartDateTime) + .thenComparing(Session::getEndDateTime)); + return internalUnmodifiableList; + } + + /** + * Returns all sessions of type {@code type}. + * @param type The type of session to return. + */ + public UniqueSessionList getSessionsOfType(SessionType type) { + UniqueSessionList filteredList = new UniqueSessionList(); + filteredList.setSessions(internalList.filtered(s -> s.getSessionType() == type)); + return filteredList; + } + + /** + * Returns all sessions of module code {@code code}. + * @param code The module code of session to return. + */ + public UniqueSessionList getSessionsOfModuleCode(String code) { + UniqueSessionList filteredList = new UniqueSessionList(); + filteredList.setSessions(internalList.filtered(s -> s.getModuleCode().equals(code))); + return filteredList; + } + + /** + * @return the total duration of all sessions in this session list. + */ + public Duration getTotalDuration() { + return internalList.stream() + .map(Session::getDurationToNearestHour) + .reduce(Duration.ZERO, Duration::plus); + } + + @Override + public Iterator iterator() { + return internalList.iterator(); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof UniqueSessionList // instanceof handles nulls + && internalList.equals(((UniqueSessionList) other).internalList)); + } + + @Override + public int hashCode() { + return internalList.hashCode(); + } + + /** + * Returns true if {@code sessions} contains only unique sessions. + */ + private boolean sessionsAreUnique(List sessions) { + for (int i = 0; i < sessions.size() - 1; i++) { + for (int j = i + 1; j < sessions.size(); j++) { + if (sessions.get(i).isSameSession(sessions.get(j))) { + return false; + } + } + } + return true; + } +} diff --git a/src/main/java/tatracker/model/session/exceptions/DuplicateSessionException.java b/src/main/java/tatracker/model/session/exceptions/DuplicateSessionException.java new file mode 100644 index 00000000000..086e8e966cd --- /dev/null +++ b/src/main/java/tatracker/model/session/exceptions/DuplicateSessionException.java @@ -0,0 +1,11 @@ +package tatracker.model.session.exceptions; + +/** + * Signals that the operation will result in duplicate {@code Session} + * (Session are considered duplicates if they have the same identity). + */ +public class DuplicateSessionException extends RuntimeException { + public DuplicateSessionException() { + super("Operation would result in duplicate session"); + } +} diff --git a/src/main/java/tatracker/model/session/exceptions/SessionNotFoundException.java b/src/main/java/tatracker/model/session/exceptions/SessionNotFoundException.java new file mode 100644 index 00000000000..0ee1a7be197 --- /dev/null +++ b/src/main/java/tatracker/model/session/exceptions/SessionNotFoundException.java @@ -0,0 +1,8 @@ +package tatracker.model.session.exceptions; + + +/** + * Signals that the operation is unable to find the specified {@code Session}. + */ +public class SessionNotFoundException extends RuntimeException {} + diff --git a/src/main/java/tatracker/model/statistic/Statistic.java b/src/main/java/tatracker/model/statistic/Statistic.java new file mode 100644 index 00000000000..11cd9af660a --- /dev/null +++ b/src/main/java/tatracker/model/statistic/Statistic.java @@ -0,0 +1,111 @@ +package tatracker.model.statistic; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; + +import tatracker.model.ReadOnlyTaTracker; +import tatracker.model.session.SessionType; +import tatracker.model.session.UniqueSessionList; +import tatracker.model.student.Rating; +import tatracker.model.student.Student; +import tatracker.model.student.UniqueStudentList; +import tatracker.ui.StatisticWindow; + +/** + * A data container that stores the statistic of TA-Tracker. + * The UI classes will read statistics from this class to display statistics data in the Statistics window. + */ +public class Statistic { + + public static final String ALL_MODULES_STRING = "ALL MODULES"; + + public final int[] numHoursPerCategory = new int [SessionType.NUM_SESSION_TYPES]; + public final int[] studentRatingBinValues = new int[Rating.RANGE]; + public final RatedStudent[] worstStudents = new RatedStudent[StatisticWindow.NUM_STUDENTS_TO_DISPLAY]; + public final String targetModuleCode; + + private final ReadOnlyTaTracker taTracker; + + public Statistic(ReadOnlyTaTracker taTracker, String targetModuleCode) { + + this.taTracker = taTracker; + + UniqueSessionList fList = new UniqueSessionList(); + UniqueStudentList sList = new UniqueStudentList(); + + fList.setSessions(taTracker.getDoneSessionList()); + sList.setStudents(taTracker.getCompleteStudentList()); + + // If targetModule is not null, filter by target module. + if (targetModuleCode != null) { + this.targetModuleCode = targetModuleCode; + fList = fList.getSessionsOfModuleCode(targetModuleCode); + } else { + this.targetModuleCode = ALL_MODULES_STRING; + } + + for (int i = 0; i < numHoursPerCategory.length; ++i) { + this.numHoursPerCategory[i] = fList.getSessionsOfType(SessionType.getSessionTypeById(i)) + .getTotalDuration().toHoursPart(); + } + + for (int i = 0; i < studentRatingBinValues.length; ++i) { + this.studentRatingBinValues[i] = sList.getStudentsOfRating(new Rating(Rating.MIN_RATING + i)).size(); + } + + // Setup worst students + List students = new ArrayList<>(taTracker.getCompleteStudentList()); + students.sort(Comparator.comparingInt((Student a) -> a.getRating().value)); + + for (int i = 0; i < worstStudents.length; ++i) { + if (i < students.size()) { + worstStudents[i] = new RatedStudent(students.get(i)); + } else { + worstStudents[i] = new RatedStudent(); + } + } + } + + public int getTotalHours() { + int total = 0; + for (int h : this.numHoursPerCategory) { + total += h; + } + return total; + } + + public int getTotalEarnings() { + return getTotalHours() * taTracker.getRate(); + } + + /** + * Represents a Statistics entry containing a Student's name and their associated rating. + */ + public static class RatedStudent { + private final Student student; + private final String fullName; + private final int rating; + + public RatedStudent() { + this.student = null; + this.fullName = ""; + this.rating = 0; + } + + public RatedStudent(Student student) { + this.student = student; + this.fullName = student.getName().fullName; + this.rating = student.getRating().value; + } + + public String getFullName() { + return fullName; + } + + public int getRating() { + return rating; + } + } +} + diff --git a/src/main/java/seedu/address/model/person/Email.java b/src/main/java/tatracker/model/student/Email.java similarity index 67% rename from src/main/java/seedu/address/model/person/Email.java rename to src/main/java/tatracker/model/student/Email.java index a5bbe0b6a5f..1c274e08b50 100644 --- a/src/main/java/seedu/address/model/person/Email.java +++ b/src/main/java/tatracker/model/student/Email.java @@ -1,24 +1,19 @@ -package seedu.address.model.person; +package tatracker.model.student; import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.AppUtil.checkArgument; +import static tatracker.commons.util.AppUtil.checkArgument; /** - * Represents a Person's email in the address book. + * Represents a Student's email in the TA-Tracker. * Guarantees: immutable; is valid as declared in {@link #isValidEmail(String)} */ public class Email { + public static final String MESSAGE_CONSTRAINTS = "Emails should be of the format local-part@domain" + + " Please refer to the User Guide for more details"; + private static final String SPECIAL_CHARACTERS = "!#$%&'*+/=?`{|}~^.-"; - public static final String MESSAGE_CONSTRAINTS = "Emails should be of the format local-part@domain " - + "and adhere to the following constraints:\n" - + "1. The local-part should only contain alphanumeric characters and these special characters, excluding " - + "the parentheses, (" + SPECIAL_CHARACTERS + ") .\n" - + "2. This is followed by a '@' and then a domain name. " - + "The domain name must:\n" - + " - be at least 2 characters long\n" - + " - start and end with alphanumeric characters\n" - + " - consist of alphanumeric characters, a period or a hyphen for the characters in between, if any."; + // alphanumeric and special characters private static final String LOCAL_PART_REGEX = "^[\\w" + SPECIAL_CHARACTERS + "]+"; private static final String DOMAIN_FIRST_CHARACTER_REGEX = "[^\\W_]"; // alphanumeric characters except underscore @@ -27,8 +22,17 @@ public class Email { public static final String VALIDATION_REGEX = LOCAL_PART_REGEX + "@" + DOMAIN_FIRST_CHARACTER_REGEX + DOMAIN_MIDDLE_REGEX + DOMAIN_LAST_CHARACTER_REGEX; + private static final String DEFAULT_VALUE = ""; + public final String value; + /** + * Constructs an empty {@code Email}. + */ + public Email() { + this(DEFAULT_VALUE); + } + /** * Constructs an {@code Email}. * @@ -44,7 +48,7 @@ public Email(String email) { * Returns if a given string is a valid email. */ public static boolean isValidEmail(String test) { - return test.matches(VALIDATION_REGEX); + return test.isEmpty() || test.matches(VALIDATION_REGEX); } @Override diff --git a/src/main/java/tatracker/model/student/Matric.java b/src/main/java/tatracker/model/student/Matric.java new file mode 100644 index 00000000000..9bd620a107d --- /dev/null +++ b/src/main/java/tatracker/model/student/Matric.java @@ -0,0 +1,63 @@ +package tatracker.model.student; + +import static java.util.Objects.requireNonNull; +import static tatracker.commons.util.AppUtil.checkArgument; + +/** + * Represents a Student's matric number in the TA-Tracker. + * Guarantees: immutable; is valid as declared in {@link #isValidMatric(String)} + */ +public class Matric implements Comparable { + + public static final String MESSAGE_CONSTRAINTS = + "Matric numbers should start with an \"A\", followed by 7 digits, and one final capital letter," + + " and it should not be blank"; + + /* + * matric number must start with an "A" followed by 7 digits and one final capital letter + */ + public static final String VALIDATION_REGEX = "A" + "\\d{7}" + "[A-Z]"; + + public final String value; + + /** + * Constructs a {@code Matric}. + * + * @param matric A valid matric. + */ + public Matric(String matric) { + requireNonNull(matric); + checkArgument(isValidMatric(matric), MESSAGE_CONSTRAINTS); + value = matric; + } + + /** + * Returns true if a given string is a valid matric number. + */ + public static boolean isValidMatric(String test) { + return test.matches(VALIDATION_REGEX); + } + + + @Override + public String toString() { + return value; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Matric // instanceof handles nulls + && value.equals(((Matric) other).value)); // state check + } + + @Override + public int hashCode() { + return value.hashCode(); + } + + @Override + public int compareTo(Matric other) { + return value.compareToIgnoreCase(other.value); + } +} diff --git a/src/main/java/seedu/address/model/person/Name.java b/src/main/java/tatracker/model/student/Name.java similarity index 76% rename from src/main/java/seedu/address/model/person/Name.java rename to src/main/java/tatracker/model/student/Name.java index 79244d71cf7..100f21c354a 100644 --- a/src/main/java/seedu/address/model/person/Name.java +++ b/src/main/java/tatracker/model/student/Name.java @@ -1,22 +1,22 @@ -package seedu.address.model.person; +package tatracker.model.student; import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.AppUtil.checkArgument; +import static tatracker.commons.util.AppUtil.checkArgument; /** - * Represents a Person's name in the address book. + * Represents a Student's name in the TA-Tracker. * Guarantees: immutable; is valid as declared in {@link #isValidName(String)} */ -public class Name { +public class Name implements Comparable { public static final String MESSAGE_CONSTRAINTS = "Names should only contain alphanumeric characters and spaces, and it should not be blank"; /* - * The first character of the address must not be a whitespace, + * The first character of the name must not be a whitespace, * otherwise " " (a blank string) becomes a valid input. */ - public static final String VALIDATION_REGEX = "[\\p{Alnum}][\\p{Alnum} ]*"; + public static final String VALIDATION_REGEX = "[\\p{Alnum}][\\p{Alnum}\\.\\- ]*"; public final String fullName; @@ -56,4 +56,8 @@ public int hashCode() { return fullName.hashCode(); } + @Override + public int compareTo(Name other) { + return fullName.compareToIgnoreCase(other.fullName); + } } diff --git a/src/main/java/seedu/address/model/person/Phone.java b/src/main/java/tatracker/model/student/Phone.java similarity index 76% rename from src/main/java/seedu/address/model/person/Phone.java rename to src/main/java/tatracker/model/student/Phone.java index 872c76b382f..389495e10ac 100644 --- a/src/main/java/seedu/address/model/person/Phone.java +++ b/src/main/java/tatracker/model/student/Phone.java @@ -1,20 +1,29 @@ -package seedu.address.model.person; +package tatracker.model.student; import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.AppUtil.checkArgument; +import static tatracker.commons.util.AppUtil.checkArgument; /** - * Represents a Person's phone number in the address book. + * Represents a Student's phone number in the TA-Tracker. * Guarantees: immutable; is valid as declared in {@link #isValidPhone(String)} */ public class Phone { - public static final String MESSAGE_CONSTRAINTS = "Phone numbers should only contain numbers, and it should be at least 3 digits long"; public static final String VALIDATION_REGEX = "\\d{3,}"; + + private static final String DEFAULT_VALUE = ""; + public final String value; + /** + * Constructs an empty {@code Phone}. + */ + public Phone() { + this(DEFAULT_VALUE); + } + /** * Constructs a {@code Phone}. * @@ -30,7 +39,7 @@ public Phone(String phone) { * Returns true if a given string is a valid phone number. */ public static boolean isValidPhone(String test) { - return test.matches(VALIDATION_REGEX); + return test.isEmpty() || test.matches(VALIDATION_REGEX); } @Override diff --git a/src/main/java/tatracker/model/student/Rating.java b/src/main/java/tatracker/model/student/Rating.java new file mode 100644 index 00000000000..f0c7b2fa262 --- /dev/null +++ b/src/main/java/tatracker/model/student/Rating.java @@ -0,0 +1,86 @@ +package tatracker.model.student; + +import static tatracker.commons.util.AppUtil.checkArgument; + +import tatracker.commons.util.StringUtil; + +/** + * Represents a Rating in the TA-Tracker. A Rating is an integer on a scale from 1 - 5, + * where 1 represents the poorest rating, and 5 represents the best rating. + * Guarantees: immutable; rating is valid as declared in {@link #isValidRating(int)} + */ +public class Rating implements Comparable { + + public static final String MESSAGE_CONSTRAINTS = "Ratings should be a number" + + " between 1 (POOR) to 5 (EXCELLENT) inclusive"; + + public static final int MIN_RATING = 1; + public static final int MAX_RATING = 5; + + /* The range of rating values. */ + public static final int RANGE = MAX_RATING - MIN_RATING + 1; + + private static final int DEFAULT_VALUE = 3; + + public final int value; + + /** + * Constructs a default {@code Rating} (The default rating is 3 for AVERAGE). + */ + public Rating() { + this.value = DEFAULT_VALUE; + } + + /** + * Constructs a {@code Rating}. + * + * @param value A valid rating on a scale from 1 (POOR) to 5 (EXCELLENT). + */ + public Rating(int value) { + checkArgument(isValidRating(value), MESSAGE_CONSTRAINTS); + this.value = value; + } + + /** + * Returns true if a given number is a valid rating. + */ + public static boolean isValidRating(int test) { + return MIN_RATING <= test && test <= MAX_RATING; + } + + /** + * Returns true if a given number is a valid rating. + */ + public static boolean isValidRating(String test) { + if (!StringUtil.isNonZeroUnsignedInteger(test)) { + return false; + } + int parsedRating = Integer.parseUnsignedInt(test); + return isValidRating(parsedRating); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Rating // instanceof handles nulls + && value == ((Rating) other).value); // state check + } + + @Override + public int hashCode() { + return Integer.hashCode(value); + } + + /** + * Format state as text for viewing. + */ + @Override + public String toString() { + return String.format("%d/5", value); + } + + @Override + public int compareTo(Rating other) { + return value - other.value; + } +} diff --git a/src/main/java/tatracker/model/student/Student.java b/src/main/java/tatracker/model/student/Student.java new file mode 100644 index 00000000000..052cf50d61b --- /dev/null +++ b/src/main/java/tatracker/model/student/Student.java @@ -0,0 +1,135 @@ +package tatracker.model.student; + +import static tatracker.commons.util.CollectionUtil.requireAllNonNull; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; + +import tatracker.model.tag.Tag; + + +/** + * Represents a Student in the Ta-Tracker. + * Guarantees: details are present and not null, field values are validated, immutable. + */ +public class Student { + + // Identity fields + private final Matric matric; + private final Name name; + + // Optional fields + private final Phone phone; + private final Email email; + private final Rating rating; + private final Set tags; + + /** + * Every field must be present and not null. + */ + public Student(Matric matric, Name name, Phone phone, Email email, Rating rating, Set tags) { + requireAllNonNull(matric, name, phone, email, rating, tags); + this.matric = matric; + this.name = name; + + this.phone = phone; + this.email = email; + this.rating = rating; + + this.tags = new HashSet<>(); + this.tags.addAll(tags); + } + + public Matric getMatric() { + return matric; + } + + public Name getName() { + return name; + } + + public Phone getPhone() { + return phone; + } + + public Email getEmail() { + return email; + } + + public Rating getRating() { + return rating; + } + + /** + * Returns an immutable tag set, which throws {@code UnsupportedOperationException} + * if modification is attempted. + */ + public Set getTags() { + return Collections.unmodifiableSet(tags); + } + + /** + * Returns true if both students of the same name have at least one other identity field that is the same. + * This defines a weaker notion of equality between two students. + */ + public boolean isSameStudent(Student otherStudent) { + if (otherStudent == this) { + return true; + } + return otherStudent != null && otherStudent.getMatric().equals(getMatric()); + } + + /** + * Returns true if both students have the same identity and data fields. + * This defines a stronger notion of equality between two students. + */ + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof Student)) { + return false; + } + + Student otherStudent = (Student) other; + return otherStudent.getName().equals(getName()) + && otherStudent.getPhone().equals(getPhone()) + && otherStudent.getEmail().equals(getEmail()) + && otherStudent.getMatric().equals(getMatric()) + && otherStudent.getRating().equals(getRating()) + && otherStudent.getTags().equals(getTags()); + } + + @Override + public int hashCode() { + // use this method for custom fields hashing instead of implementing your own + return Objects.hash(name, phone, email, matric, rating, tags); + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + // Append identity fields + builder.append(getName()) + .append(" Phone: ") + .append(getPhone()) + .append(" Email: ") + .append(getEmail()) + .append(" Matric: ") + .append(getMatric()); + + // Append optional fields + builder.append(" Rating: ") + .append(getRating()); + + // Append Tags + builder.append(" Tags: "); + getTags().forEach(builder::append); + + return builder.toString(); + } +} diff --git a/src/main/java/tatracker/model/student/UniqueStudentList.java b/src/main/java/tatracker/model/student/UniqueStudentList.java new file mode 100644 index 00000000000..83b8923a687 --- /dev/null +++ b/src/main/java/tatracker/model/student/UniqueStudentList.java @@ -0,0 +1,215 @@ +package tatracker.model.student; + +import static java.util.Objects.requireNonNull; +import static tatracker.commons.util.CollectionUtil.requireAllNonNull; + +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; + +import tatracker.model.student.exceptions.DuplicateStudentException; +import tatracker.model.student.exceptions.StudentNotFoundException; + +/** + * A list of students that enforces uniqueness between its elements and does not allow nulls. + * A student is considered unique by comparing using {@code Student#isSameStudent(Student)}. As such, adding and + * updating of students uses Student#isSameStudent(Student) for equality so as to ensure that the student being added or + * updated is unique in terms of identity in the UniqueStudentList. However, the removal of a student uses + * Student#equals(Object) so as to ensure that the student with exactly the same fields will be removed. + *

+ * Supports a minimal set of list operations. + * + * @see Student#isSameStudent(Student) + */ +public class UniqueStudentList implements Iterable { + + private final ObservableList internalList = FXCollections.observableArrayList(); + private final ObservableList internalUnmodifiableList = + FXCollections.unmodifiableObservableList(internalList); + + private Comparator alphabetically = Comparator.comparing(Student::getName); + + private Comparator ratingAscending = Comparator.comparing(Student::getRating); + private Comparator ratingDescending = ratingAscending.reversed(); + + private Comparator matric = Comparator.comparing(Student::getMatric); + + public int size() { + return internalList.size(); + } + + /** + * Returns true if the list contains an equivalent student as the given argument. + */ + public boolean contains(Student toCheck) { + requireNonNull(toCheck); + return internalList.stream().anyMatch(toCheck::isSameStudent); + } + + /** + * Returns true if the list contains an equivalent student with the given matric number. + */ + public boolean contains(Matric toCheck) { + requireNonNull(toCheck); + return internalList.stream().anyMatch(student -> student.getMatric().equals(toCheck)); + } + + public Student get(int n) { + return internalList.get(n); + } + + /** + * Returns the student in this list with the given student + * matriculation number (the student id). + * Returns null if no such student exists. + */ + public Student get(Matric studentId) { + for (Student student : internalList) { + if (student.getMatric().equals(studentId)) { + return student; + } + } + return null; // Did not find a student with the given student id + } + + /** + * Adds a student to the list. + * The student must not already exist in the list. + */ + public void add(Student toAdd) { + requireNonNull(toAdd); + if (contains(toAdd)) { + throw new DuplicateStudentException(); + } + internalList.add(toAdd); + } + + /** + * Removes the equivalent student from the list. + * The student must exist in the list. + */ + public void remove(Student toRemove) { + requireNonNull(toRemove); + if (!internalList.remove(toRemove)) { + throw new StudentNotFoundException(); + } + } + + /** + * Replaces the student {@code target} in the list with {@code editedStudent}. + * {@code target} must exist in the list. + * The student identity of {@code editedStudent} must not be the same as another existing student in the list. + */ + public void setStudent(Student target, Student editedStudent) { + requireAllNonNull(target, editedStudent); + + int index = internalList.indexOf(target); + if (index == -1) { + throw new StudentNotFoundException(); + } + + if (!target.isSameStudent(editedStudent) && contains(editedStudent)) { + throw new DuplicateStudentException(); + } + + internalList.set(index, editedStudent); + } + + public void setStudents(UniqueStudentList replacement) { + requireNonNull(replacement); + internalList.setAll(replacement.internalList); + } + + /** + * Replaces the contents of this list with {@code students}. + * {@code students} must not contain duplicate students. + */ + public void setStudents(List students) { + requireAllNonNull(students); + if (!studentsAreUnique(students)) { + throw new DuplicateStudentException(); + } + + internalList.setAll(students); + } + + + /** + * Returns all students of a particular rating + * + * @param rating The target rating of students to return + */ + public List getStudentsOfRating(Rating rating) { + return internalList.filtered(s -> s.getRating().equals(rating)); + } + + /** + * Sorts the students alphabetically. + */ + public void sortAlphabetically() { + FXCollections.sort(internalList, alphabetically); + } + + /** + * Sorts the students by rating in ascending order. + */ + public void sortByRatingAscending() { + FXCollections.sort(internalList, ratingAscending); + } + + /** + * Sorts the students by matric number. + */ + public void sortByMatric() { + FXCollections.sort(internalList, matric); + } + + /** + * Sorts the students by rating in descending order. + */ + public void sortByRatingDescending() { + FXCollections.sort(internalList, ratingDescending); + } + + + /** + * Returns the backing list as an unmodifiable {@code ObservableList}. + */ + public ObservableList asUnmodifiableObservableList() { + return internalUnmodifiableList; + } + + @Override + public Iterator iterator() { + return internalList.iterator(); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof UniqueStudentList // instanceof handles nulls + && internalList.equals(((UniqueStudentList) other).internalList)); + } + + @Override + public int hashCode() { + return internalList.hashCode(); + } + + /** + * Returns true if {@code students} contains only unique students. + */ + private boolean studentsAreUnique(List students) { + for (int i = 0; i < students.size() - 1; i++) { + for (int j = i + 1; j < students.size(); j++) { + if (students.get(i).isSameStudent(students.get(j))) { + return false; + } + } + } + return true; + } +} diff --git a/src/main/java/tatracker/model/student/exceptions/DuplicateStudentException.java b/src/main/java/tatracker/model/student/exceptions/DuplicateStudentException.java new file mode 100644 index 00000000000..d34761f5d2b --- /dev/null +++ b/src/main/java/tatracker/model/student/exceptions/DuplicateStudentException.java @@ -0,0 +1,11 @@ +package tatracker.model.student.exceptions; + +/** + * Signals that the operation will result in duplicate Student (Student are considered duplicates if they have the same + * identity). + */ +public class DuplicateStudentException extends RuntimeException { + public DuplicateStudentException() { + super("Operation would result in duplicate students"); + } +} diff --git a/src/main/java/tatracker/model/student/exceptions/StudentNotFoundException.java b/src/main/java/tatracker/model/student/exceptions/StudentNotFoundException.java new file mode 100644 index 00000000000..35b5a121d81 --- /dev/null +++ b/src/main/java/tatracker/model/student/exceptions/StudentNotFoundException.java @@ -0,0 +1,6 @@ +package tatracker.model.student.exceptions; + +/** + * Signals that the operation is unable to find the specified student. + */ +public class StudentNotFoundException extends RuntimeException {} diff --git a/src/main/java/seedu/address/model/tag/Tag.java b/src/main/java/tatracker/model/tag/Tag.java similarity index 84% rename from src/main/java/seedu/address/model/tag/Tag.java rename to src/main/java/tatracker/model/tag/Tag.java index b0ea7e7dad7..15a8af2807f 100644 --- a/src/main/java/seedu/address/model/tag/Tag.java +++ b/src/main/java/tatracker/model/tag/Tag.java @@ -1,16 +1,16 @@ -package seedu.address.model.tag; +package tatracker.model.tag; import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.AppUtil.checkArgument; +import static tatracker.commons.util.AppUtil.checkArgument; /** - * Represents a Tag in the address book. + * Represents a Tag in the TA-Tracker. * Guarantees: immutable; name is valid as declared in {@link #isValidTagName(String)} */ public class Tag { public static final String MESSAGE_CONSTRAINTS = "Tags names should be alphanumeric"; - public static final String VALIDATION_REGEX = "\\p{Alnum}+"; + public static final String VALIDATION_REGEX = "[\\p{Alnum}][\\p{Alnum}\\.\\?\\!\\- ]*"; public final String tagName; diff --git a/src/main/java/tatracker/model/util/SampleDataUtil.java b/src/main/java/tatracker/model/util/SampleDataUtil.java new file mode 100644 index 00000000000..0dba3cb5e77 --- /dev/null +++ b/src/main/java/tatracker/model/util/SampleDataUtil.java @@ -0,0 +1,255 @@ +package tatracker.model.util; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Set; +import java.util.stream.Collectors; + +import tatracker.model.ReadOnlyTaTracker; +import tatracker.model.TaTracker; +import tatracker.model.group.Group; +import tatracker.model.group.GroupType; +import tatracker.model.module.Module; +import tatracker.model.session.Session; +import tatracker.model.session.SessionType; +import tatracker.model.student.Email; +import tatracker.model.student.Matric; +import tatracker.model.student.Name; +import tatracker.model.student.Phone; +import tatracker.model.student.Rating; +import tatracker.model.student.Student; +import tatracker.model.tag.Tag; + + +/** + * Contains utility methods for populating {@code TaTracker} with sample data. + */ +public class SampleDataUtil { + public static Student[] getSampleStudents() { + return new Student[]{ + new Student(new Matric("A0187945J"), + new Name("Alex Yeoh"), + new Phone("87438807"), + new Email("alexyeoh@example.com"), + new Rating(4), + getTagSet("friends")), + new Student(new Matric("A0181137L"), + new Name("Bernice Yu"), + new Phone("99272758"), + new Email("berniceyu@example.com"), + new Rating(1), + getTagSet("colleagues", "friends")), + new Student(new Matric("A0187565N"), + new Name("Charlotte Oliveiro"), + new Phone("93210283"), + new Email("charlotte@example.com"), + new Rating(5), + getTagSet("neighbours")), + new Student(new Matric("A0186153P"), + new Name("David Li"), + new Phone("91031282"), + new Email("lidavid@example.com"), + new Rating(3), + getTagSet("family")), + new Student(new Matric("A0180474R"), + new Name("Irfan Ibrahim"), + new Phone("92492021"), + new Email("irfan@example.com"), + new Rating(2), + getTagSet("classmates")), + new Student(new Matric("A0187613T"), + new Name("Roy Balakrishnan"), + new Phone("92624417"), + new Email("royb@example.com"), + new Rating(4), + getTagSet("colleagues")), + new Student(new Matric("A0195558H"), + new Name("Jeffry Lum"), + new Phone("65162727"), + new Email("Jeffry@u.nus.edu"), + new Rating(5), + getTagSet("tutors")) + }; + } + + public static Group[] getSampleGroups() { + return new Group[]{ + new Group("G06", GroupType.LAB), + new Group("G01", GroupType.TUTORIAL), + new Group("G02", GroupType.RECITATION), + new Group("G03", GroupType.OTHER), + }; + } + + public static String[] getSampleGroupsString() { + return new String[]{ + "G06", + "G01", + "G02", + "G03", + }; + } + + + public static Module[] getSampleModules() { + return new Module[]{ + new Module("CS3243", "Introduction to AI"), + new Module("CS2103T", "Software Engineering"), + }; + } + + + public static String[] getSampleModulesString() { + return new String[]{ + "CS3243", + "CS2103T", + }; + } + + public static Session[] getSampleSessions() { + return new Session[]{ + new Session(LocalDateTime.of(2020, 04, 30, 13, 00), + LocalDateTime.of(2020, 04, 30, 14, 30), + SessionType.CONSULTATION, 1, + "CS2103T", + "with Alice and Bob"), + new Session(LocalDateTime.of(2020, 04, 10, 10, 00), + LocalDateTime.of(2020, 04, 10, 11, 00), + SessionType.LAB, 2, + "CS3243", + "prepare notes"), + new Session(LocalDateTime.of(2020, 06, 26, 14, 00), + LocalDateTime.of(2020, 06, 26, 15, 00), + SessionType.TUTORIAL, 1, + "CS2103T", + "check work"), + new Session(LocalDateTime.of(2020, 04, 29, 9, 30), + LocalDateTime.of(2020, 04, 29, 11, 00), + SessionType.GRADING, 0, + "CS2103T", + "grade group 1 first"), + new Session(LocalDateTime.of(2020, 05, 26, 14, 00), + LocalDateTime.of(2020, 05, 26, 17, 00), + SessionType.GRADING, 0, + "CS3243", + "clarifications for Bob's question"), + new Session(LocalDateTime.of(2020, 03, 26, 14, 00), + LocalDateTime.of(2020, 03, 26, 17, 00), + SessionType.CONSULTATION, 0, + "CS3243", + "clarifications for Alice's question"), + new Session(LocalDateTime.of(2020, 03, 21, 9, 30), + LocalDateTime.of(2020, 03, 21, 11, 00), + SessionType.GRADING, 0, + "CS2103T", + "grade group 1 first"), + }; + } + + /** + * Randomise the groupings and students. + * + * @return student 6 and student 7 from {@code getSampleStudents}; + */ + public static Student[] module1Group1Students() { + ArrayList sList = new ArrayList<>(); + for (int i = 5; i < getSampleStudents().length; i++) { + sList.add(getSampleStudents()[i]); + } + Student[] s = sList.toArray(new Student[sList.size()]); + return s; + } + + /** + * Randomise the groupings and students. + * + * @return students 1 to 5 from {@code getSampleStudents}; + */ + public static Student[] module1Group2Students() { + ArrayList sList = new ArrayList<>(); + for (int i = 0; i < 5; i++) { + sList.add(getSampleStudents()[i]); + } + Student[] s = sList.toArray(new Student[sList.size()]); + return s; + } + + /** + * Randomise the groupings and students. + * + * @return students 1 to 3 from {@code getSampleStudents}; + */ + public static Student[] module2Group1Students() { + ArrayList sList = new ArrayList<>(); + for (int i = 0; i < 3; i++) { + sList.add(getSampleStudents()[i]); + } + Student[] s = sList.toArray(new Student[sList.size()]); + return s; + } + + /** + * Randomise the groupings and students. + * + * @return students 4 to 7 from {@code getSampleStudents}; + */ + public static Student[] module2Group2Students() { + ArrayList sList = new ArrayList<>(); + for (int i = 3; i < getSampleStudents().length; i++) { + sList.add(getSampleStudents()[i]); + } + Student[] s = sList.toArray(new Student[sList.size()]); + return s; + } + + /** + * each module consists of 2 groups, each group consists of students. + * @return + */ + public static ReadOnlyTaTracker getSampleTaTracker() { + TaTracker sampleAb = new TaTracker(); + sampleAb.addModule(getSampleModules()[0]); + sampleAb.addGroup(getSampleGroups()[0], getSampleModules()[0]); + for (int i = 0; i < module1Group1Students().length; i++) { + sampleAb.addStudent(module1Group1Students()[i], + getSampleGroupsString()[0], getSampleModulesString()[0]); + } + sampleAb.addGroup(getSampleGroups()[1], getSampleModules()[0]); + for (int i = 0; i < module1Group2Students().length; i++) { + sampleAb.addStudent(module1Group2Students()[i], + getSampleGroupsString()[1], getSampleModulesString()[0]); + } + + sampleAb.addModule(getSampleModules()[1]); + sampleAb.addGroup(getSampleGroups()[3], getSampleModules()[1]); + for (int i = 0; i < module2Group2Students().length; i++) { + sampleAb.addStudent(module2Group2Students()[i], + getSampleGroupsString()[3], getSampleModulesString()[1]); + } + sampleAb.addGroup(getSampleGroups()[2], getSampleModules()[1]); + for (int i = 0; i < module2Group1Students().length; i++) { + sampleAb.addStudent(module2Group1Students()[i], + getSampleGroupsString()[2], getSampleModulesString()[1]); + } + for (int i = 0; i < 5; i++) { + sampleAb.addSession(getSampleSessions()[i]); + } + for (int i = 5; i < getSampleSessions().length; i++) { + sampleAb.addDoneSession(getSampleSessions()[i]); + } + return sampleAb; + } + + /** + * Returns a tag set containing the list of strings given. + */ + + public static Set getTagSet(String... strings) { + return Arrays.stream(strings) + .map(Tag::new) + .collect(Collectors.toSet()); + } + +} + diff --git a/src/main/java/tatracker/storage/JsonAdaptedGroup.java b/src/main/java/tatracker/storage/JsonAdaptedGroup.java new file mode 100644 index 00000000000..b5773df81a3 --- /dev/null +++ b/src/main/java/tatracker/storage/JsonAdaptedGroup.java @@ -0,0 +1,98 @@ +package tatracker.storage; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +import tatracker.commons.exceptions.IllegalValueException; +import tatracker.model.group.Group; +import tatracker.model.group.GroupType; +import tatracker.model.student.Matric; +import tatracker.model.student.Student; + +/** + * Jackson-friendly version of {@link Group}. + */ +class JsonAdaptedGroup { + public static final String MESSAGE_DUPLICATE_STUDENTS = "Group's list of students contains duplicate student(s)."; + + private static final String MISSING_FIELD_MESSAGE_FORMAT = "Group's %s field is missing!"; + + public static final String MISSING_GROUP_ID = String.format(MISSING_FIELD_MESSAGE_FORMAT, "id"); + public static final String MISSING_GROUP_TYPE = String.format(MISSING_FIELD_MESSAGE_FORMAT, "type"); + + private final String id; + private final String type; + private final List students = new ArrayList<>(); + + /** + * Constructs a {@code JsonAdaptedGroup} with the given group details. + */ + @JsonCreator + public JsonAdaptedGroup(@JsonProperty("id") String id, + @JsonProperty("type") String type, + @JsonProperty("students") List students) { + this.id = id; + this.type = type; + if (students != null) { + this.students.addAll(students); + } + } + + /** + * Converts a given {@code Group} into this class for Jackson use. + */ + public JsonAdaptedGroup(Group source) { + id = source.getIdentifier(); + type = source.getGroupType().name(); + students.addAll(source.getStudentList() + .stream() + .map(JsonAdaptedStudent::new) + .collect(Collectors.toList())); + } + + /** + * Converts this Jackson-friendly adapted group object into the model's {@code Group} object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted group. + */ + public Group toModelType() throws IllegalValueException { + // ==== ID ==== + if (id == null) { + throw new IllegalValueException(MISSING_GROUP_ID); + } + if (id.isBlank()) { + throw new IllegalValueException(Group.CONSTRAINTS_GROUP_CODE); + } + + // ==== Type ==== + if (type == null) { + throw new IllegalValueException(MISSING_GROUP_TYPE); + } + if (!GroupType.isValidGroupType(type)) { + throw new IllegalValueException(GroupType.MESSAGE_CONSTRAINTS); + } + final GroupType modelGroupType = GroupType.getGroupType(type); + + // ==== Students ==== + final Map modelStudents = new HashMap<>(); + for (JsonAdaptedStudent jsonAdaptedStudent : students) { + Student student = jsonAdaptedStudent.toModelType(); + if (modelStudents.containsKey(student.getMatric())) { + throw new IllegalValueException(MESSAGE_DUPLICATE_STUDENTS); + } + modelStudents.put(student.getMatric(), student); + } + + // ==== Build ==== + Group group = new Group(id, modelGroupType); + modelStudents.values().forEach(group::addStudent); + + return group; + } +} diff --git a/src/main/java/tatracker/storage/JsonAdaptedModule.java b/src/main/java/tatracker/storage/JsonAdaptedModule.java new file mode 100644 index 00000000000..349db8b57af --- /dev/null +++ b/src/main/java/tatracker/storage/JsonAdaptedModule.java @@ -0,0 +1,113 @@ +package tatracker.storage; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +import tatracker.commons.exceptions.IllegalValueException; +import tatracker.model.group.Group; +import tatracker.model.module.Module; + +/** + * Jackson-friendly version of {@link Module}. + */ +class JsonAdaptedModule { + + // public static final String MESSAGE_DUPLICATE_SESSIONS = + // "Module's list of sessions contains duplicate session(s)."; + public static final String MESSAGE_DUPLICATE_GROUPS = "Module's list of groups contains duplicate group(s)."; + + public static final String MISSING_FIELD_MESSAGE_FORMAT = "Module's %s field is missing!"; + + public static final String MISSING_MODULE_ID = String.format(MISSING_FIELD_MESSAGE_FORMAT, "id"); + public static final String MISSING_MODULE_NAME = String.format(MISSING_FIELD_MESSAGE_FORMAT, "name"); + + private final String id; + private final String name; + // private final List sessions = new ArrayList<>(); + private final List groups = new ArrayList<>(); + + /** + * Constructs a {@code JsonAdaptedModule} with the given module details. + */ + @JsonCreator + public JsonAdaptedModule(@JsonProperty("id") String id, + @JsonProperty("name") String name, + // @JsonProperty("sessions") List sessions, + @JsonProperty("groups") List groups) { + this.id = id; + this.name = name; + // if (sessions != null) { + // this.sessions.addAll(sessions); + // } + if (groups != null) { + this.groups.addAll(groups); + } + } + + /** + * Converts a given {@code Module} into this class for Jackson use. + */ + public JsonAdaptedModule(Module source) { + id = source.getIdentifier(); + name = source.getName(); + groups.addAll(source.getGroupList().stream() + .map(JsonAdaptedGroup::new) + .collect(Collectors.toList())); + } + + /** + * Converts this Jackson-friendly adapted module object into the model's {@code Module} object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted module. + */ + public Module toModelType() throws IllegalValueException { + // ==== ID ==== + if (id == null) { + throw new IllegalValueException(MISSING_MODULE_ID); + } + if (id.isBlank()) { + throw new IllegalValueException(Module.CONSTRAINTS_MODULE_CODE); + } + + // ==== Name ==== + if (name == null) { + throw new IllegalValueException(MISSING_MODULE_NAME); + } + if (name.isBlank()) { + throw new IllegalValueException(Module.CONSTRAINTS_MODULE_NAME); + } + + // // ==== Done Sessions ==== + // final Set modelDoneSessions = new HashSet<>(); + // for (JsonAdaptedSession jsonAdaptedSession : sessions) { + // Session doneSession = jsonAdaptedSession.toModelType(); + // if (modelDoneSessions.contains(doneSession)) { + // throw new IllegalValueException(MESSAGE_DUPLICATE_SESSIONS); + // } + // modelDoneSessions.add(doneSession); + // } + + // ==== Groups ==== + final Map modelGroups = new HashMap<>(); + for (JsonAdaptedGroup jsonAdaptedGroup : groups) { + Group group = jsonAdaptedGroup.toModelType(); + if (modelGroups.containsKey(group.getIdentifier())) { + throw new IllegalValueException(MESSAGE_DUPLICATE_GROUPS); + } + modelGroups.put(group.getIdentifier(), group); + } + + // ==== Build ==== + Module module = new Module(id, name); + // modelDoneSessions.stream().forEach(module::); + modelGroups.values().forEach(module::addGroup); + + return module; + } +} diff --git a/src/main/java/tatracker/storage/JsonAdaptedSession.java b/src/main/java/tatracker/storage/JsonAdaptedSession.java new file mode 100644 index 00000000000..9e5b7a1d127 --- /dev/null +++ b/src/main/java/tatracker/storage/JsonAdaptedSession.java @@ -0,0 +1,153 @@ +package tatracker.storage; + +import java.time.LocalDateTime; +import java.time.format.DateTimeParseException; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +import tatracker.commons.exceptions.IllegalValueException; +import tatracker.model.module.Module; +import tatracker.model.session.Session; +import tatracker.model.session.SessionType; + +/** + * Jackson-friendly version of {@link Session}. + */ +class JsonAdaptedSession { + public static final String MESSAGE_INVALID_TIMING = "Session's timing is invalid" + + " because the end date time is before its start date time"; + + private static final String MESSAGE_INVALID_DATE_TIME = "\nDate Times should" + + " start with a date in yyyy-MM-dd format," + + " followed by the letter T," + + " then the time in HH:mm format."; + + public static final String MESSAGE_INVALID_START_DATE_TIME = "Session's start date time is invalid." + + MESSAGE_INVALID_DATE_TIME; + public static final String MESSAGE_INVALID_END_DATE_TIME = "Session's end date time is invalid." + + MESSAGE_INVALID_DATE_TIME; + + private static final String MISSING_FIELD_MESSAGE_FORMAT = "Session's %s field is missing!"; + public static final String MISSING_START_DATE_TIME = String.format(MISSING_FIELD_MESSAGE_FORMAT, "start date time"); + public static final String MISSING_END_DATE_TIME = String.format(MISSING_FIELD_MESSAGE_FORMAT, "end date time"); + public static final String MISSING_SESSION_TYPE = String.format(MISSING_FIELD_MESSAGE_FORMAT, "type"); + public static final String MISSING_DESCRIPTION = String.format(MISSING_FIELD_MESSAGE_FORMAT, "description"); + public static final String MISSING_MODULE_ID = String.format(MISSING_FIELD_MESSAGE_FORMAT, "module"); + + private String startDateTime; + private String endDateTime; + private String type; + private String description; + private String moduleId; + private boolean isDone; + private int recurring; + + /** + * Constructs a {@code JsonAdaptedModule} with the given module details. + */ + @JsonCreator + public JsonAdaptedSession(@JsonProperty("start") String startDateTime, + @JsonProperty("end") String endDateTime, + @JsonProperty("type") String type, + @JsonProperty("description") String description, + @JsonProperty("moduleId") String moduleId, + @JsonProperty("isDone") boolean isDone, + @JsonProperty("recurring") int recurring) { + this.startDateTime = startDateTime; + this.endDateTime = endDateTime; + + this.type = type; + this.description = description; + this.moduleId = moduleId; + + this.isDone = isDone; + this.recurring = recurring; + } + + /** + * Converts a given {@code Module} into this class for Jackson use. + */ + public JsonAdaptedSession(Session source) { + startDateTime = source.getStartDateTime().toString(); + endDateTime = source.getEndDateTime().toString(); + + type = source.getSessionType().name(); + description = source.getDescription(); + moduleId = source.getModuleCode(); + + isDone = source.getIsDone(); + recurring = source.getRecurring(); + } + + /** + * Converts this Jackson-friendly adapted module object into the model's {@code Module} object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted module. + */ + public Session toModelType() throws IllegalValueException { + // ==== Start Date Time ==== + if (startDateTime == null) { + throw new IllegalValueException(MISSING_START_DATE_TIME); + } + + final LocalDateTime modelStartDateTime; + try { + modelStartDateTime = LocalDateTime.parse(startDateTime); + } catch (DateTimeParseException dtpe) { + throw new IllegalValueException(MESSAGE_INVALID_START_DATE_TIME); + } + + // ==== End Date Time ==== + if (endDateTime == null) { + throw new IllegalValueException(MISSING_END_DATE_TIME); + } + + final LocalDateTime modelEndDateTime; + try { + modelEndDateTime = LocalDateTime.parse(endDateTime); + } catch (DateTimeParseException dtpe) { + throw new IllegalValueException(MESSAGE_INVALID_END_DATE_TIME); + } + + // ==== Time Constraints ==== + if (modelEndDateTime.isBefore(modelStartDateTime)) { + throw new IllegalValueException(MESSAGE_INVALID_TIMING); + } + + // ==== Type ==== + if (type == null) { + throw new IllegalValueException(MISSING_SESSION_TYPE); + } + if (!SessionType.isValidSessionType(type)) { + throw new IllegalValueException(SessionType.MESSAGE_CONSTRAINTS); + } + final SessionType modelSessionType = SessionType.getSessionType(type); + + // ==== Description ==== + if (description == null) { + throw new IllegalValueException(MISSING_DESCRIPTION); + } + + // ==== Module Id ==== + if (moduleId == null) { + throw new IllegalValueException(MISSING_MODULE_ID); + } + if (moduleId.isBlank()) { + throw new IllegalValueException(Module.CONSTRAINTS_MODULE_CODE); + } + + // ==== Module Id ==== + if (recurring < 0) { + throw new IllegalValueException(Session.CONSTRAINTS_RECURRING_WEEKS); + } + + Session session = new Session(modelStartDateTime, modelEndDateTime, modelSessionType, + recurring, moduleId, description); + + if (isDone) { + session.done(); + } + return session; + } +} diff --git a/src/main/java/tatracker/storage/JsonAdaptedStudent.java b/src/main/java/tatracker/storage/JsonAdaptedStudent.java new file mode 100644 index 00000000000..8eb196f8915 --- /dev/null +++ b/src/main/java/tatracker/storage/JsonAdaptedStudent.java @@ -0,0 +1,126 @@ +package tatracker.storage; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +import tatracker.commons.exceptions.IllegalValueException; +import tatracker.model.student.Email; +import tatracker.model.student.Matric; +import tatracker.model.student.Name; +import tatracker.model.student.Phone; +import tatracker.model.student.Rating; +import tatracker.model.student.Student; +import tatracker.model.tag.Tag; + +/** + * Jackson-friendly version of {@link Student}. + */ +class JsonAdaptedStudent { + + public static final String MISSING_FIELD_MESSAGE_FORMAT = "Student's %s field is missing!"; + + private final String matric; + private final String name; + private final String phone; + private final String email; + private final int rating; + private final List tagged = new ArrayList<>(); + + /** + * Constructs a {@code JsonAdaptedStudent} with the given student details. + */ + @JsonCreator + public JsonAdaptedStudent(@JsonProperty("matric") String matric, + @JsonProperty("name") String name, + @JsonProperty("phone") String phone, + @JsonProperty("email") String email, + @JsonProperty("rating") int rating, + @JsonProperty("tagged") List tagged) { + this.matric = matric; + this.name = name; + this.phone = phone; + this.email = email; + this.rating = rating; + if (tagged != null) { + this.tagged.addAll(tagged); + } + } + + /** + * Converts a given {@code Student} into this class for Jackson use. + */ + public JsonAdaptedStudent(Student source) { + matric = source.getMatric().value; + name = source.getName().fullName; + phone = source.getPhone().value; + email = source.getEmail().value; + rating = source.getRating().value; + tagged.addAll(source.getTags().stream() + .map(JsonAdaptedTag::new) + .collect(Collectors.toList())); + } + + /** + * Converts this Jackson-friendly adapted student object into the model's {@code Student} object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted student. + */ + public Student toModelType() throws IllegalValueException { + // ==== Matric ==== + if (matric == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Matric.class.getSimpleName())); + } + if (!Matric.isValidMatric(matric)) { + throw new IllegalValueException(Matric.MESSAGE_CONSTRAINTS); + } + final Matric modelMatric = new Matric(matric); + + // ==== Name ==== + if (name == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Name.class.getSimpleName())); + } + if (!Name.isValidName(name)) { + throw new IllegalValueException(Name.MESSAGE_CONSTRAINTS); + } + final Name modelName = new Name(name); + + // ==== Phone ==== + if (phone == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Phone.class.getSimpleName())); + } + if (!Phone.isValidPhone(phone)) { + throw new IllegalValueException(Phone.MESSAGE_CONSTRAINTS); + } + final Phone modelPhone = new Phone(phone); + + // ==== Email ==== + if (email == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Email.class.getSimpleName())); + } + if (!Email.isValidEmail(email)) { + throw new IllegalValueException(Email.MESSAGE_CONSTRAINTS); + } + final Email modelEmail = new Email(email); + + // ==== Rating ==== + if (!Rating.isValidRating(rating)) { + throw new IllegalValueException(Rating.MESSAGE_CONSTRAINTS); + } + final Rating modelRating = new Rating(rating); + + // ==== Tags ==== + final Set modelTags = new HashSet<>(); + for (JsonAdaptedTag tag : tagged) { + modelTags.add(tag.toModelType()); + } + + return new Student(modelMatric, modelName, modelPhone, modelEmail, modelRating, modelTags); + } + +} diff --git a/src/main/java/seedu/address/storage/JsonAdaptedTag.java b/src/main/java/tatracker/storage/JsonAdaptedTag.java similarity index 89% rename from src/main/java/seedu/address/storage/JsonAdaptedTag.java rename to src/main/java/tatracker/storage/JsonAdaptedTag.java index 0df22bdb754..28b923c5ca6 100644 --- a/src/main/java/seedu/address/storage/JsonAdaptedTag.java +++ b/src/main/java/tatracker/storage/JsonAdaptedTag.java @@ -1,10 +1,10 @@ -package seedu.address.storage; +package tatracker.storage; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonValue; -import seedu.address.commons.exceptions.IllegalValueException; -import seedu.address.model.tag.Tag; +import tatracker.commons.exceptions.IllegalValueException; +import tatracker.model.tag.Tag; /** * Jackson-friendly version of {@link Tag}. diff --git a/src/main/java/tatracker/storage/JsonSerializableTaTracker.java b/src/main/java/tatracker/storage/JsonSerializableTaTracker.java new file mode 100644 index 00000000000..a7744d6549a --- /dev/null +++ b/src/main/java/tatracker/storage/JsonSerializableTaTracker.java @@ -0,0 +1,113 @@ +package tatracker.storage; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonRootName; + +import tatracker.commons.exceptions.IllegalValueException; +import tatracker.model.ReadOnlyTaTracker; +import tatracker.model.TaTracker; +import tatracker.model.module.Module; +import tatracker.model.session.Session; + +/** + * An Immutable TaTracker that is serializable to JSON format. + */ +@JsonRootName(value = "tatracker") +class JsonSerializableTaTracker { + + public static final String MESSAGE_DUPLICATE_SESSIONS = "Session list contains duplicate session(s)."; + public static final String MESSAGE_DUPLICATE_DONE_SESSIONS = "Done session list contains duplicate session(s)."; + public static final String MESSAGE_DUPLICATE_MODULES = "Module list contains duplicate module(s)."; + + private final List sessions = new ArrayList<>(); + private final List doneSessions = new ArrayList<>(); + private final List modules = new ArrayList<>(); + + /** + * Constructs a {@code JsonSerializableTaTracker} with the given lists. + */ + @JsonCreator + public JsonSerializableTaTracker(@JsonProperty("sessions") List sessions, + @JsonProperty("doneSessions") List doneSessions, + @JsonProperty("modules") List modules) { + this.sessions.addAll(sessions); + this.doneSessions.addAll(doneSessions); + this.modules.addAll(modules); + } + + /** + * Converts a given {@code ReadOnlyTaTracker} into this class for Jackson use. + * + * @param source future changes to this will not affect the created {@code JsonSerializableTaTracker}. + */ + public JsonSerializableTaTracker(ReadOnlyTaTracker source) { + sessions.addAll(source.getSessionList() + .stream() + .map(JsonAdaptedSession::new) + .collect(Collectors.toList())); + + doneSessions.addAll(source.getDoneSessionList() + .stream() + .map(JsonAdaptedSession::new) + .collect(Collectors.toList())); + + modules.addAll(source.getModuleList() + .stream() + .map(JsonAdaptedModule::new) + .collect(Collectors.toList())); + } + + /** + * Converts this Ta Tracker into the model's {@code TaTracker} object. + * + * @throws IllegalValueException if there were any data constraints violated. + */ + public TaTracker toModelType() throws IllegalValueException { + // ==== Sessions ==== + final Set modelSessions = new HashSet<>(); + for (JsonAdaptedSession jsonAdaptedSession : sessions) { + Session session = jsonAdaptedSession.toModelType(); + if (modelSessions.contains(session)) { + throw new IllegalValueException(MESSAGE_DUPLICATE_SESSIONS); + } + modelSessions.add(session); + } + + // ==== Done Sessions ==== + final Set modelDoneSessions = new HashSet<>(); + for (JsonAdaptedSession jsonAdaptedSession : doneSessions) { + Session doneSession = jsonAdaptedSession.toModelType(); + if (modelDoneSessions.contains(doneSession)) { + throw new IllegalValueException(MESSAGE_DUPLICATE_DONE_SESSIONS); + } + modelDoneSessions.add(doneSession); + } + + // ==== Modules ==== + final Map modelModules = new HashMap<>(); + for (JsonAdaptedModule jsonAdaptedModule : modules) { + Module module = jsonAdaptedModule.toModelType(); + if (modelModules.containsKey(module.getIdentifier())) { + throw new IllegalValueException(MESSAGE_DUPLICATE_MODULES); + } + modelModules.put(module.getIdentifier(), module); + } + + // ==== Build ==== + TaTracker taTracker = new TaTracker(); + modelSessions.forEach(taTracker::addSession); + modelDoneSessions.forEach(taTracker::addDoneSession); + modelModules.values().forEach(taTracker::addModule); + + return taTracker; + } +} diff --git a/src/main/java/tatracker/storage/JsonTaTrackerStorage.java b/src/main/java/tatracker/storage/JsonTaTrackerStorage.java new file mode 100644 index 00000000000..a26d464375d --- /dev/null +++ b/src/main/java/tatracker/storage/JsonTaTrackerStorage.java @@ -0,0 +1,80 @@ +package tatracker.storage; + +import static java.util.Objects.requireNonNull; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.Optional; +import java.util.logging.Logger; + +import tatracker.commons.core.LogsCenter; +import tatracker.commons.exceptions.DataConversionException; +import tatracker.commons.exceptions.IllegalValueException; +import tatracker.commons.util.FileUtil; +import tatracker.commons.util.JsonUtil; +import tatracker.model.ReadOnlyTaTracker; + +/** + * A class to access TaTracker data stored as a json file on the hard disk. + */ +public class JsonTaTrackerStorage implements TaTrackerStorage { + + private static final Logger logger = LogsCenter.getLogger(JsonTaTrackerStorage.class); + + private Path filePath; + + public JsonTaTrackerStorage(Path filePath) { + this.filePath = filePath; + } + + public Path getTaTrackerFilePath() { + return filePath; + } + + @Override + public Optional readTaTracker() throws DataConversionException { + return readTaTracker(filePath); + } + + /** + * Similar to {@link #readTaTracker()}. + * + * @param filePath location of the data. Cannot be null. + * @throws DataConversionException if the file is not in the correct format. + */ + public Optional readTaTracker(Path filePath) throws DataConversionException { + requireNonNull(filePath); + + Optional jsonTaTracker = JsonUtil.readJsonFile( + filePath, JsonSerializableTaTracker.class); + if (!jsonTaTracker.isPresent()) { + return Optional.empty(); + } + + try { + return Optional.of(jsonTaTracker.get().toModelType()); + } catch (IllegalValueException ive) { + logger.info("Illegal values found in " + filePath + ": " + ive.getMessage()); + throw new DataConversionException(ive); + } + } + + @Override + public void saveTaTracker(ReadOnlyTaTracker taTracker) throws IOException { + saveTaTracker(taTracker, filePath); + } + + /** + * Similar to {@link #saveTaTracker(ReadOnlyTaTracker)}. + * + * @param filePath location of the data. Cannot be null. + */ + public void saveTaTracker(ReadOnlyTaTracker taTracker, Path filePath) throws IOException { + requireNonNull(taTracker); + requireNonNull(filePath); + + FileUtil.createIfMissing(filePath); + JsonUtil.saveJsonFile(new JsonSerializableTaTracker(taTracker), filePath); + } + +} diff --git a/src/main/java/seedu/address/storage/JsonUserPrefsStorage.java b/src/main/java/tatracker/storage/JsonUserPrefsStorage.java similarity index 83% rename from src/main/java/seedu/address/storage/JsonUserPrefsStorage.java rename to src/main/java/tatracker/storage/JsonUserPrefsStorage.java index bc2bbad84aa..ef25a1a98db 100644 --- a/src/main/java/seedu/address/storage/JsonUserPrefsStorage.java +++ b/src/main/java/tatracker/storage/JsonUserPrefsStorage.java @@ -1,13 +1,13 @@ -package seedu.address.storage; +package tatracker.storage; import java.io.IOException; import java.nio.file.Path; import java.util.Optional; -import seedu.address.commons.exceptions.DataConversionException; -import seedu.address.commons.util.JsonUtil; -import seedu.address.model.ReadOnlyUserPrefs; -import seedu.address.model.UserPrefs; +import tatracker.commons.exceptions.DataConversionException; +import tatracker.commons.util.JsonUtil; +import tatracker.model.ReadOnlyUserPrefs; +import tatracker.model.UserPrefs; /** * A class to access UserPrefs stored in the hard disk as a json file diff --git a/src/main/java/tatracker/storage/Storage.java b/src/main/java/tatracker/storage/Storage.java new file mode 100644 index 00000000000..b31a3c65048 --- /dev/null +++ b/src/main/java/tatracker/storage/Storage.java @@ -0,0 +1,32 @@ +package tatracker.storage; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.Optional; + +import tatracker.commons.exceptions.DataConversionException; +import tatracker.model.ReadOnlyTaTracker; +import tatracker.model.ReadOnlyUserPrefs; +import tatracker.model.UserPrefs; + +/** + * API of the Storage component + */ +public interface Storage extends TaTrackerStorage, UserPrefsStorage { + + @Override + Optional readUserPrefs() throws DataConversionException, IOException; + + @Override + void saveUserPrefs(ReadOnlyUserPrefs userPrefs) throws IOException; + + @Override + Path getTaTrackerFilePath(); + + @Override + Optional readTaTracker() throws DataConversionException, IOException; + + @Override + void saveTaTracker(ReadOnlyTaTracker taTracker) throws IOException; + +} diff --git a/src/main/java/tatracker/storage/StorageManager.java b/src/main/java/tatracker/storage/StorageManager.java new file mode 100644 index 00000000000..28d555dd83f --- /dev/null +++ b/src/main/java/tatracker/storage/StorageManager.java @@ -0,0 +1,77 @@ +package tatracker.storage; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.Optional; +import java.util.logging.Logger; + +import tatracker.commons.core.LogsCenter; +import tatracker.commons.exceptions.DataConversionException; +import tatracker.model.ReadOnlyTaTracker; +import tatracker.model.ReadOnlyUserPrefs; +import tatracker.model.UserPrefs; + +/** + * Manages storage of TaTracker data in local storage. + */ +public class StorageManager implements Storage { + + private static final Logger logger = LogsCenter.getLogger(StorageManager.class); + private TaTrackerStorage taTrackerStorage; + private UserPrefsStorage userPrefsStorage; + + + public StorageManager(TaTrackerStorage taTrackerStorage, UserPrefsStorage userPrefsStorage) { + super(); + this.taTrackerStorage = taTrackerStorage; + this.userPrefsStorage = userPrefsStorage; + } + + // ================ UserPrefs methods ============================== + + @Override + public Path getUserPrefsFilePath() { + return userPrefsStorage.getUserPrefsFilePath(); + } + + @Override + public Optional readUserPrefs() throws DataConversionException, IOException { + return userPrefsStorage.readUserPrefs(); + } + + @Override + public void saveUserPrefs(ReadOnlyUserPrefs userPrefs) throws IOException { + userPrefsStorage.saveUserPrefs(userPrefs); + } + + + // ================ TaTracker methods ============================== + + @Override + public Path getTaTrackerFilePath() { + return taTrackerStorage.getTaTrackerFilePath(); + } + + @Override + public Optional readTaTracker() throws DataConversionException, IOException { + return readTaTracker(taTrackerStorage.getTaTrackerFilePath()); + } + + @Override + public Optional readTaTracker(Path filePath) throws DataConversionException, IOException { + logger.fine("Attempting to read data from file: " + filePath); + return taTrackerStorage.readTaTracker(filePath); + } + + @Override + public void saveTaTracker(ReadOnlyTaTracker taTracker) throws IOException { + saveTaTracker(taTracker, taTrackerStorage.getTaTrackerFilePath()); + } + + @Override + public void saveTaTracker(ReadOnlyTaTracker taTracker, Path filePath) throws IOException { + logger.fine("Attempting to write to data file: " + filePath); + taTrackerStorage.saveTaTracker(taTracker, filePath); + } + +} diff --git a/src/main/java/tatracker/storage/TaTrackerStorage.java b/src/main/java/tatracker/storage/TaTrackerStorage.java new file mode 100644 index 00000000000..d656f5a570f --- /dev/null +++ b/src/main/java/tatracker/storage/TaTrackerStorage.java @@ -0,0 +1,46 @@ +package tatracker.storage; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.Optional; + +import tatracker.commons.exceptions.DataConversionException; +import tatracker.model.ReadOnlyTaTracker; +import tatracker.model.TaTracker; + +/** + * Represents a storage for {@link TaTracker}. + */ +public interface TaTrackerStorage { + + /** + * Returns the file path of the data file. + */ + Path getTaTrackerFilePath(); + + /** + * Returns TaTracker data as a {@link ReadOnlyTaTracker}. + * Returns {@code Optional.empty()} if storage file is not found. + * @throws DataConversionException if the data in storage is not in the expected format. + * @throws IOException if there was any problem when reading from the storage. + */ + Optional readTaTracker() throws DataConversionException, IOException; + + /** + * @see #getTaTrackerFilePath() + */ + Optional readTaTracker(Path filePath) throws DataConversionException, IOException; + + /** + * Saves the given {@link ReadOnlyTaTracker} to the storage. + * @param taTracker cannot be null. + * @throws IOException if there was any problem writing to the file. + */ + void saveTaTracker(ReadOnlyTaTracker taTracker) throws IOException; + + /** + * @see #saveTaTracker(ReadOnlyTaTracker) + */ + void saveTaTracker(ReadOnlyTaTracker taTracker, Path filePath) throws IOException; + +} diff --git a/src/main/java/seedu/address/storage/UserPrefsStorage.java b/src/main/java/tatracker/storage/UserPrefsStorage.java similarity index 71% rename from src/main/java/seedu/address/storage/UserPrefsStorage.java rename to src/main/java/tatracker/storage/UserPrefsStorage.java index 29eef178dbc..a1c42e99b6d 100644 --- a/src/main/java/seedu/address/storage/UserPrefsStorage.java +++ b/src/main/java/tatracker/storage/UserPrefsStorage.java @@ -1,15 +1,15 @@ -package seedu.address.storage; +package tatracker.storage; import java.io.IOException; import java.nio.file.Path; import java.util.Optional; -import seedu.address.commons.exceptions.DataConversionException; -import seedu.address.model.ReadOnlyUserPrefs; -import seedu.address.model.UserPrefs; +import tatracker.commons.exceptions.DataConversionException; +import tatracker.model.ReadOnlyUserPrefs; +import tatracker.model.UserPrefs; /** - * Represents a storage for {@link seedu.address.model.UserPrefs}. + * Represents a storage for {@link tatracker.model.UserPrefs}. */ public interface UserPrefsStorage { @@ -27,7 +27,7 @@ public interface UserPrefsStorage { Optional readUserPrefs() throws DataConversionException, IOException; /** - * Saves the given {@link seedu.address.model.ReadOnlyUserPrefs} to the storage. + * Saves the given {@link tatracker.model.ReadOnlyUserPrefs} to the storage. * @param userPrefs cannot be null. * @throws IOException if there was any problem writing to the file. */ diff --git a/src/main/java/tatracker/ui/CommandBox.java b/src/main/java/tatracker/ui/CommandBox.java new file mode 100644 index 00000000000..df2964df65e --- /dev/null +++ b/src/main/java/tatracker/ui/CommandBox.java @@ -0,0 +1,298 @@ +package tatracker.ui; + +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.scene.control.TextField; +import javafx.scene.layout.Region; + +import tatracker.commons.core.LogsCenter; +import tatracker.commons.core.Messages; +import tatracker.logic.commands.CommandDetails; +import tatracker.logic.commands.CommandDictionary; +import tatracker.logic.commands.CommandResult; +import tatracker.logic.commands.exceptions.CommandException; +import tatracker.logic.parser.PrefixDetails; +import tatracker.logic.parser.PrefixDictionary; +import tatracker.logic.parser.exceptions.ParseException; +import tatracker.ui.CommandBoxParser.ArgumentMatch; +import tatracker.ui.CommandBoxParser.CommandMatch; + +/** + * The UI component that is responsible for receiving user command inputs. + */ +public class CommandBox extends UiPart implements Focusable { + public static final String ERROR_STYLE_CLASS = "error"; + public static final String VALID_STYLE_CLASS = "valid"; + + private static final String FXML = "CommandBox.fxml"; + + private static final Logger logger = LogsCenter.getLogger(CommandBox.class); + + static { + logger.setLevel(Level.WARNING); + } + + private CommandDetails commandDetails = null; + private PrefixDictionary dictionary = PrefixDictionary.getEmptyDictionary(); + + private final CommandExecutor commandExecutor; + private final ResultDisplay resultDisplay; + + @FXML + private TextField commandTextField; + + public CommandBox(CommandExecutor commandExecutor, ResultDisplay resultDisplay) { + super(FXML); + this.commandExecutor = commandExecutor; + this.resultDisplay = resultDisplay; + this.resultDisplay.setFeedbackToUser(Messages.MESSAGE_WELCOME + Messages.MESSAGE_HELP); + + resetCommandDetails(); + + // calls #setStyleToDefault() whenever there is a change to the text of the command box. + commandTextField.textProperty().addListener((property, oldInput, newInput) -> highlightInput(newInput)); + } + + @Override + public void requestFocus() { + commandTextField.requestFocus(); + } + + @Override + public boolean isFocused() { + return commandTextField.isFocused(); + } + + /** + * Sets the command box style to use the default style. + */ + private void setStyleToDefault() { + ObservableList styleClass = commandTextField.getStyleClass(); + int size = styleClass.size(); + + styleClass.removeAll(ERROR_STYLE_CLASS, VALID_STYLE_CLASS); + + if (size > styleClass.size()) { + logger.finer("Set style to default"); + } + } + + /** + * Sets the command box style to indicate a valid command. + */ + private void setStyleToIndicateValidCommand() { + ObservableList styleClass = commandTextField.getStyleClass(); + + if (!styleClass.contains(VALID_STYLE_CLASS)) { + setStyleToDefault(); + styleClass.add(VALID_STYLE_CLASS); + logger.finer("Set style to valid"); + } + } + + /** + * Sets the command box style to indicate a failed command. + */ + private void setStyleToIndicateCommandFailure() { + ObservableList styleClass = commandTextField.getStyleClass(); + + if (!styleClass.contains(ERROR_STYLE_CLASS)) { + setStyleToDefault(); + styleClass.add(ERROR_STYLE_CLASS); + logger.finer("Set style to error"); + } + } + + /** + * Applies syntax highlighting to the text area of the command box. + */ + private void highlightInput(String input) { + if (input.isEmpty()) { + logger.info("======== [ No input ]"); + handleNoInput(); + return; + } + + CommandMatch match = CommandBoxParser.parseInput(input); + + if (!match.hasFullCommandWord()) { + logger.info("======== [ Invalid input ]"); + handleInvalidInput(); + return; + } + + changeCommandDetails(match.fullCommandWord); + + if (!match.hasArguments()) { + handleNoArguments(); + return; + } + + highlightArguments(match.arguments); + } + + /** + * Applies syntax highlighting to command arguments in the text area of the command box. + */ + private void highlightArguments(String arguments) { + assert !arguments.isEmpty(); + + int numWhitespaces = CommandBoxParser.countTrailingWhitespaces(arguments); + logger.info("" + numWhitespaces); + + if (numWhitespaces > 0) { + logger.info("======== [ Next argument? ]"); + handleNextArgument(arguments); + return; + } + + List matches = CommandBoxParser.parseArguments(arguments); + logger.info(matches.toString()); + + assert matches.size() > 0; // always have preamble + + if (!dictionary.hasPreamble()) { + logger.info("======== [ No need preamble ] "); + + String preamble = matches.remove(0).value; // remove preamble + if (!preamble.isBlank()) { + handleNoPrefix(); + return; + } + } + + for (ArgumentMatch match : matches) { + if (!dictionary.hasPrefixDetails(match.prefix)) { + logger.info("======== [ No prefix ] "); + handleNoPrefix(); + return; + } + + PrefixDetails prefixEntry = dictionary.getPrefixDetails(match.prefix); + logger.info(String.format("======== [ %s = %s ]", prefixEntry.getPrefixWithInfo(), match.value)); + + if (!prefixEntry.isValidValue(match.value.trim())) { + logger.info("======== [ Invalid ] "); + handleInvalidPrefix(prefixEntry); + return; + } + logger.info("======== [ Valid ] "); + handleRequiredPrefix(prefixEntry); + } + } + + private String getCommandFeedback() { + return String.format("%s\nParameters: %s\nExample: %s", + commandDetails.getInfo(), + commandDetails.getUsage(), + commandDetails.getExample()); + } + + private String getPrefixFeedback(PrefixDetails prefixEntry) { + return String.format("%s\n%s\nExample: %s", + prefixEntry.getPrefixWithInfo(), + prefixEntry.getConstraint(), + prefixEntry.getPrefixWithExamples()); + } + + /** + * Handles the Enter button pressed event. + */ + @FXML + private void handleCommandEntered() { + try { + commandExecutor.execute(commandTextField.getText()); + commandTextField.setText(""); + } catch (CommandException | ParseException e) { + setStyleToIndicateCommandFailure(); + } + } + + private void handleNoInput() { + resetCommandDetails(); + setStyleToDefault(); + } + + private void handleInvalidInput() { + resetCommandDetails(); + setStyleToIndicateCommandFailure(); + resultDisplay.setFeedbackToUser(""); + } + + private void handleNoArguments() { + setStyleToIndicateValidCommand(); + resultDisplay.setFeedbackToUser(getCommandFeedback()); + } + + /** + * Controls the result of entering trailing spaces at the end of a command. + * @param arguments to count the number of trailing whitespaces in the command. + */ + private void handleNextArgument(String arguments) { + setStyleToDefault(); + + if (CommandBoxParser.countTrailingWhitespaces(arguments) > 1) { + resultDisplay.setFeedbackToUser(getCommandFeedback()); + } + } + + private void handleNoPrefix() { + setStyleToIndicateCommandFailure(); + resultDisplay.setFeedbackToUser(getCommandFeedback()); + } + + private void handleRequiredPrefix(PrefixDetails prefixEntry) { + setStyleToIndicateValidCommand(); + resultDisplay.setFeedbackToUser(getPrefixFeedback(prefixEntry)); + } + + private void handleInvalidPrefix(PrefixDetails prefixEntry) { + setStyleToIndicateCommandFailure(); + resultDisplay.setFeedbackToUser(getPrefixFeedback(prefixEntry)); + } + + /** + * Changes the reference to one of the commands, which is specified by {@code commandWord}. + */ + private void resetCommandDetails() { + commandDetails = null; + dictionary = PrefixDictionary.getEmptyDictionary(); + logger.fine("Clear command details"); + } + + /** + * Changes the reference to one of the commands, which is specified by {@code commandWord}. + */ + private void changeCommandDetails(String fullCommandWord) { + // Call this only if full command word is valid + assert CommandDictionary.hasFullCommandWord(fullCommandWord); + + CommandDetails oldDetails = commandDetails; + + commandDetails = CommandDictionary.getDetailsWithFullCommandWord(fullCommandWord); + dictionary = commandDetails.getPrefixDictionary(); + + // Compare with reference. If the same command word is logged, it means that + // a new CommandDetail with same internal fields was created. + if (oldDetails == commandDetails) { + logger.info(String.format("======== [ %s ]", commandDetails.getFullCommandWord())); + } + } + + /** + * Represents a function that can execute commands. + */ + @FunctionalInterface + public interface CommandExecutor { + /** + * Executes the command and returns the result. + * + * @see tatracker.logic.Logic#execute(String) + */ + CommandResult execute(String commandText) throws CommandException, ParseException; + } +} diff --git a/src/main/java/tatracker/ui/CommandBoxParser.java b/src/main/java/tatracker/ui/CommandBoxParser.java new file mode 100644 index 00000000000..f9988431576 --- /dev/null +++ b/src/main/java/tatracker/ui/CommandBoxParser.java @@ -0,0 +1,137 @@ +package tatracker.ui; + +import static java.util.Objects.requireNonNull; + +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import tatracker.commons.core.LogsCenter; +import tatracker.logic.commands.CommandDictionary; + +/** + * Analyses the input in the CommandBox in order to perform syntax highlight. + */ +public class CommandBoxParser { + private static final Pattern COMMAND_FORMAT = Pattern.compile( + "\\s*(?\\S+)(?\\s*(?$|\\S+)(?.*))"); + + private static final Pattern PREFIX = Pattern.compile("(?\\S+/)"); + + private static final Logger logger = LogsCenter.getLogger(CommandBoxParser.class); + + static { + logger.setLevel(Level.WARNING); + } + + /** + * Returns a pair containing the command word and arguments from the given input. + */ + public static CommandMatch parseInput(String input) { + Matcher matcher = COMMAND_FORMAT.matcher(input); + + if (!matcher.matches()) { + logger.info("============ [ Invalid Command ]"); + return new CommandMatch(); + } + + String word1 = matcher.group("word1"); + String word2 = word1 + " " + matcher.group("word2"); + + boolean isBasicCommand = CommandDictionary.hasFullCommandWord(word1); + boolean isComplexCommand = CommandDictionary.hasFullCommandWord(word2); + + if (isComplexCommand) { + logger.fine("============ [ Complex Command ]"); + return new CommandMatch(word2, matcher.group("args2")); + } else if (isBasicCommand) { + logger.fine("============ [ Basic Command ]"); + return new CommandMatch(word1, matcher.group("args1")); + } else { + logger.fine("============ [ Unknown Command ]"); + return new CommandMatch(); + } + } + + /** + * Returns a pair containing the last prefix and the value associated with it from the given input. + */ + public static List parseArguments(String arguments) { + Matcher prefixer = PREFIX.matcher(arguments); + + int start = 0; + + String prefix = ""; // None + + List matchedArguments = new ArrayList<>(); + + while (prefixer.find()) { + int end = prefixer.start(); + + if (end > start) { + String value = arguments.substring(start, end); + matchedArguments.add(new ArgumentMatch(prefix, value)); + + prefix = prefixer.group("prefix"); + } + + start = prefixer.end(); + } + matchedArguments.add(new ArgumentMatch(prefix, arguments.substring(start))); + return matchedArguments; + } + + /** + * Returns true if the given input has more than one whitespace at the end. + */ + public static int countTrailingWhitespaces(String input) { + requireNonNull(input); + int length = input.length(); + int trimmedLength = input.stripTrailing().length(); + + assert trimmedLength <= length; + + return length - trimmedLength; + } + + /** + * Wraps the result of an command matching from a {@code Matcher}. + */ + public static class CommandMatch { + public final String fullCommandWord; + public final String arguments; + + public CommandMatch() { + this("", ""); + } + + public CommandMatch(String fullCommandWord, String arguments) { + this.fullCommandWord = fullCommandWord; + this.arguments = arguments; + } + + public boolean hasFullCommandWord() { + return !fullCommandWord.isEmpty(); + } + + public boolean hasArguments() { + return !arguments.isEmpty(); + } + } + + /** + * Wraps the result of an argument matching from a {@code Matcher}. + */ + public static class ArgumentMatch { + public final String prefix; + public final String value; + + public ArgumentMatch(String prefix, String value) { + this.prefix = prefix; + this.value = value; + } + } +} diff --git a/src/main/java/tatracker/ui/Focusable.java b/src/main/java/tatracker/ui/Focusable.java new file mode 100644 index 00000000000..0840b769e84 --- /dev/null +++ b/src/main/java/tatracker/ui/Focusable.java @@ -0,0 +1,17 @@ +package tatracker.ui; + +/** + * Ensure that a custom UI element can be focused on. + */ +public interface Focusable { + + /** + * Sets the focus on this UI element. + */ + void requestFocus(); + + /** + * Returns true if the focus is on this UI element. + */ + boolean isFocused(); +} diff --git a/src/main/java/tatracker/ui/HelpCard.java b/src/main/java/tatracker/ui/HelpCard.java new file mode 100644 index 00000000000..6875ddc96fc --- /dev/null +++ b/src/main/java/tatracker/ui/HelpCard.java @@ -0,0 +1,57 @@ +package tatracker.ui; + +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Region; + +import tatracker.logic.commands.CommandDetails; + +/** + * An UI component that displays information of a {@code Student}. + */ +public class HelpCard extends UiPart { + + private static final String FXML = "HelpListCard.fxml"; + + /** + * Note: Certain keywords such as "location" and "resources" are reserved keywords in JavaFX. + * As a consequence, UI elements' variable names cannot be set to such keywords + * or an exception will be thrown by JavaFX during runtime. + * + * @see The issue on AddressBook level 4 + */ + + public final CommandDetails details; + + @FXML + private HBox cardPane; + @FXML + private Label commandWord; + @FXML + private Label summary; + + public HelpCard(CommandDetails details) { + super(FXML); + this.details = details; + this.commandWord.setText(details.getFullCommandWord()); + this.summary.setText(details.getInfo()); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof HelpCard)) { + return false; + } + + // state check + HelpCard card = (HelpCard) other; + return details.equals(card.details); + } +} diff --git a/src/main/java/tatracker/ui/HelpListPanel.java b/src/main/java/tatracker/ui/HelpListPanel.java new file mode 100644 index 00000000000..06847f14c8d --- /dev/null +++ b/src/main/java/tatracker/ui/HelpListPanel.java @@ -0,0 +1,58 @@ +package tatracker.ui; + +import java.util.logging.Logger; + +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.scene.control.ListCell; +import javafx.scene.control.ListView; +import javafx.scene.layout.Region; + +import tatracker.commons.core.LogsCenter; +import tatracker.logic.commands.CommandDetails; + +/** + * Panel containing the list of groups. + */ +public class HelpListPanel extends UiPart implements Focusable { + private static final String FXML = "HelpListPanel.fxml"; + + private final Logger logger = LogsCenter.getLogger(HelpListPanel.class); + + @FXML + private ListView helpListView; + + public HelpListPanel(ObservableList helpList) { + super(FXML); + logger.fine("Showing Help List"); + helpListView.setItems(helpList); + helpListView.setCellFactory(listView -> new HelpListViewCell()); + } + + @Override + public void requestFocus() { + helpListView.requestFocus(); + } + + @Override + public boolean isFocused() { + return helpListView.isFocused(); + } + + /** + * Custom {@code ListCell} that displays the graphics of a {@code Group} using a {@code GroupCard}. + */ + class HelpListViewCell extends ListCell { + @Override + protected void updateItem(CommandDetails details, boolean empty) { + super.updateItem(details, empty); + + if (empty || details == null) { + setGraphic(null); + setText(null); + } else { + setGraphic(new HelpCard(details).getRoot()); + } + } + } +} diff --git a/src/main/java/seedu/address/ui/HelpWindow.java b/src/main/java/tatracker/ui/HelpWindow.java similarity index 54% rename from src/main/java/seedu/address/ui/HelpWindow.java rename to src/main/java/tatracker/ui/HelpWindow.java index 9a665915949..92e23a6bb82 100644 --- a/src/main/java/seedu/address/ui/HelpWindow.java +++ b/src/main/java/tatracker/ui/HelpWindow.java @@ -1,47 +1,84 @@ -package seedu.address.ui; +package tatracker.ui; import java.util.logging.Logger; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.event.EventHandler; import javafx.fxml.FXML; import javafx.scene.control.Button; import javafx.scene.control.Label; import javafx.scene.input.Clipboard; import javafx.scene.input.ClipboardContent; +import javafx.scene.input.KeyCode; +import javafx.scene.input.KeyEvent; +import javafx.scene.layout.StackPane; import javafx.stage.Stage; -import seedu.address.commons.core.LogsCenter; + +import tatracker.commons.core.GuiSettings; +import tatracker.commons.core.LogsCenter; +import tatracker.logic.commands.CommandDetails; +import tatracker.logic.commands.CommandDictionary; /** * Controller for a help page */ public class HelpWindow extends UiPart { - public static final String USERGUIDE_URL = "https://se-education.org/addressbook-level3/UserGuide.html"; - public static final String HELP_MESSAGE = "Refer to the user guide: " + USERGUIDE_URL; + public static final String USERGUIDE_URL = "https://ay1920s2-cs2103t-w17-4.github.io/TA-Tracker/UserGuide.html"; private static final Logger logger = LogsCenter.getLogger(HelpWindow.class); private static final String FXML = "HelpWindow.fxml"; + private static final ObservableList COMMAND_DETAILS = FXCollections + .observableArrayList(CommandDictionary.getDetails()); + + @FXML + private StackPane helpListPanelPlaceholder; + @FXML private Button copyButton; @FXML - private Label helpMessage; + private Label website; /** * Creates a new HelpWindow. * * @param root Stage to use as the root of the HelpWindow. */ - public HelpWindow(Stage root) { + public HelpWindow(Stage root, GuiSettings guiSettings) { super(FXML, root); - helpMessage.setText(HELP_MESSAGE); + + if (guiSettings.getWindowCoordinates() != null) { + root.setX(guiSettings.getWindowCoordinates().getX()); + root.setY(guiSettings.getWindowCoordinates().getY()); + } + website.setText(USERGUIDE_URL); + + HelpListPanel helpListPanel = new HelpListPanel(COMMAND_DETAILS); + helpListPanelPlaceholder.getChildren().add(helpListPanel.getRoot()); + + getRoot().addEventHandler(KeyEvent.KEY_RELEASED, new EventHandler() { + @Override + public void handle(KeyEvent t) { + logger.info("escaped"); + + if (t.getCode() == KeyCode.ESCAPE) { + getRoot().show(); + logger.info("click on escape"); + root.close(); + } + } + }); } /** * Creates a new HelpWindow. + * @param guiSettings for resizing the window. */ - public HelpWindow() { - this(new Stage()); + public HelpWindow(GuiSettings guiSettings) { + this(new Stage(), guiSettings); } /** diff --git a/src/main/java/tatracker/ui/MainWindow.java b/src/main/java/tatracker/ui/MainWindow.java new file mode 100644 index 00000000000..d77ae4821b0 --- /dev/null +++ b/src/main/java/tatracker/ui/MainWindow.java @@ -0,0 +1,467 @@ +package tatracker.ui; + +import java.util.logging.Logger; + +import javafx.event.ActionEvent; +import javafx.fxml.FXML; +import javafx.scene.control.MenuItem; +import javafx.scene.control.Tab; +import javafx.scene.control.TabPane; +import javafx.scene.control.TextInputControl; +import javafx.scene.input.KeyCode; +import javafx.scene.input.KeyCombination; +import javafx.scene.input.KeyEvent; +import javafx.scene.layout.StackPane; +import javafx.stage.Stage; + +import tatracker.commons.core.GuiSettings; +import tatracker.commons.core.LogsCenter; +import tatracker.logic.Logic; +import tatracker.logic.commands.CommandResult; +import tatracker.logic.commands.exceptions.CommandException; +import tatracker.logic.commands.statistic.StatisticCommandResult; +import tatracker.logic.parser.exceptions.ParseException; +import tatracker.model.statistic.Statistic; +import tatracker.ui.claimstab.ClaimsListPanel; +import tatracker.ui.claimstab.ModuleListPanelCopy; +import tatracker.ui.sessiontab.SessionListPanel; +import tatracker.ui.studenttab.GroupListPanel; +import tatracker.ui.studenttab.ModuleListPanel; +import tatracker.ui.studenttab.StudentListPanel; + +/** + * The Main Window. Provides the basic application layout containing + * a menu bar and space where other JavaFX elements can be placed. + */ +public class MainWindow extends UiPart { + + private static final String FXML = "MainWindow.fxml"; + private static final String BORDER_COLOUR = "#917b3e"; + private static final String BORDER_WIDTH = "1"; + + private final Logger logger = LogsCenter.getLogger(getClass()); + + private Stage primaryStage; + private Logic logic; + + private CommandBox commandBox; + + // Independent Ui parts residing in this Ui container + private StudentListPanel studentListPanel; + private GroupListPanel groupListPanel; + private ModuleListPanel moduleListPanel; + + private ModuleListPanelCopy moduleListPanelCopy; + private Focusable currentStudentViewList; + + private SessionListPanel sessionListPanel; + private ClaimsListPanel claimsListPanel; + + private ResultDisplay resultDisplay; + private HelpWindow helpWindow; + + private StatisticWindow statisticWindow; + + @FXML + private StackPane commandBoxPlaceholder; + + @FXML + private MenuItem helpMenuItem; + + @FXML + private TabPane tabPane; + + @FXML + private Tab studentListTab; + + @FXML + private Tab sessionListTab; + + @FXML + private Tab claimsListTab; + + @FXML + private StackPane studentListPanelPlaceholder; + + @FXML + private StackPane groupListPanelPlaceholder; + + @FXML + private StackPane moduleListPanelPlaceholder; + + @FXML + private StackPane moduleListPanelPlaceholderCopy; + + @FXML + private StackPane sessionListPanelPlaceholder; + + @FXML + private StackPane claimsListPanelPlaceholder; + + @FXML + private StackPane resultDisplayPlaceholder; + + @FXML + private StackPane statusbarPlaceholder; + + public MainWindow(Stage primaryStage, Logic logic) { + super(FXML, primaryStage); + + studentListTab.setStyle("-fx-border-color: " + BORDER_COLOUR + "; " + + "-fx-border-width: " + BORDER_WIDTH + ";"); + + // Set dependencies + this.primaryStage = primaryStage; + this.logic = logic; + + // Configure the UI + setWindowDefaultSize(logic.getGuiSettings()); + + setAccelerators(); + + helpWindow = new HelpWindow(logic.getGuiSettings()); + } + + public Stage getPrimaryStage() { + return primaryStage; + } + + private void setAccelerators() { + setAccelerator(helpMenuItem, KeyCombination.valueOf("F1")); + } + + /** + * Sets the accelerator of a MenuItem. + * + * @param keyCombination the KeyCombination value of the accelerator + */ + private void setAccelerator(MenuItem menuItem, KeyCombination keyCombination) { + menuItem.setAccelerator(keyCombination); + + /* + * TODO: the code below can be removed once the bug reported here + * https://bugs.openjdk.java.net/browse/JDK-8131666 + * is fixed in later version of SDK. + * + * According to the bug report, TextInputControl (TextField, TextArea) will + * consume function-key events. Because CommandBox contains a TextField, and + * ResultDisplay contains a TextArea, thus some accelerators (e.g F1) will + * not work when the focus is in them because the key event is consumed by + * the TextInputControl(s). + * + * For now, we add following event filter to capture such key events and open + * help window purposely so to support accelerators even when focus is + * in CommandBox or ResultDisplay. + */ + getRoot().addEventFilter(KeyEvent.KEY_PRESSED, event -> { + if (event.getTarget() instanceof TextInputControl && keyCombination.match(event)) { + menuItem.getOnAction().handle(new ActionEvent()); + event.consume(); + } + }); + } + + /** + * Fills up all the placeholders of this window. + */ + void fillInnerParts() { + studentListPanel = new StudentListPanel(logic.getFilteredStudentList()); + studentListPanelPlaceholder.getChildren().add(studentListPanel.getRoot()); + + groupListPanel = new GroupListPanel(logic.getFilteredGroupList()); + groupListPanelPlaceholder.getChildren().add(groupListPanel.getRoot()); + + moduleListPanel = new ModuleListPanel(logic.getFilteredModuleList()); + moduleListPanelPlaceholder.getChildren().add(moduleListPanel.getRoot()); + + moduleListPanelCopy = new ModuleListPanelCopy(logic.getFilteredModuleList()); + moduleListPanelPlaceholderCopy.getChildren().add(moduleListPanelCopy.getRoot()); + + currentStudentViewList = studentListPanel; + + sessionListPanel = new SessionListPanel(logic.getFilteredSessionList()); + sessionListPanelPlaceholder.getChildren().add(sessionListPanel.getRoot()); + + claimsListPanel = new ClaimsListPanel(logic.getFilteredDoneSessionList(), logic.getTaTracker()); + claimsListPanelPlaceholder.getChildren().add(claimsListPanel.getRoot()); + + resultDisplay = new ResultDisplay(); + resultDisplayPlaceholder.getChildren().add(resultDisplay.getRoot()); + + StatusBarFooter statusBarFooter = new StatusBarFooter(logic.getTaTrackerFilePath()); + statusbarPlaceholder.getChildren().add(statusBarFooter.getRoot()); + + commandBox = new CommandBox(this::executeCommand, resultDisplay); + commandBoxPlaceholder.getChildren().add(commandBox.getRoot()); + + getRoot().addEventFilter(KeyEvent.KEY_RELEASED, this::handleFocusOnCommandBox); + getRoot().addEventFilter(KeyEvent.KEY_RELEASED, this::handleFocusOnView); + getRoot().addEventFilter(KeyEvent.KEY_RELEASED, this::handleSwitchingStudentViewLists); + } + + /** + * Sets the default size based on {@code guiSettings}. + */ + private void setWindowDefaultSize(GuiSettings guiSettings) { + primaryStage.setHeight(guiSettings.getWindowHeight()); + primaryStage.setWidth(guiSettings.getWindowWidth()); + if (guiSettings.getWindowCoordinates() != null) { + primaryStage.setX(guiSettings.getWindowCoordinates().getX()); + primaryStage.setY(guiSettings.getWindowCoordinates().getY()); + } + } + + /** + * Switched to user specified tab. + */ + @FXML + public void handleGoto(Tab tabToSwitchTo) { + tabPane.getSelectionModel().select(tabToSwitchTo); + studentListTab.setStyle("-fx-border-color: " + "black" + "; " + + "-fx-border-width: " + "0" + ";"); + sessionListTab.setStyle("-fx-border-color: " + "black" + "; " + + "-fx-border-width: " + "0" + ";"); + claimsListTab.setStyle("-fx-border-color: " + "black" + "; " + + "-fx-border-width: " + "0" + ";"); + tabToSwitchTo.setStyle("-fx-border-color: " + BORDER_COLOUR + "; " + + "-fx-border-width: " + BORDER_WIDTH + ";"); + } + + /** + * Opens the help window or focuses on it if it's already opened. + */ + @FXML + public void handleHelp() { + if (!helpWindow.isShowing()) { + helpWindow.show(); + } else { + helpWindow.focus(); + } + } + + /** + * Opens the statistic window or focuses on it if it's already opened. + */ + @FXML + public void handleStatistic() { + handleStatistic(null); + } + + /** + * Opens the statistic window for the input module. + * @param moduleCode the module code for which the stats will be for + */ + public void handleStatistic(String moduleCode) { + if (statisticWindow != null && statisticWindow.isShowing()) { + statisticWindow.hide(); + } + + // Create a new statistic window + statisticWindow = new StatisticWindow(new Statistic(logic.getTaTracker(), moduleCode)); + statisticWindow.show(); + statisticWindow.focus(); + } + + void show() { + primaryStage.show(); + } + + private boolean isSelectedTab(Tab tab) { + return tab.equals(tabPane.getSelectionModel().getSelectedItem()); + } + + /** + * Closes the application. + */ + @FXML + private void handleExit() { + GuiSettings guiSettings = new GuiSettings(primaryStage.getWidth(), primaryStage.getHeight(), + (int) primaryStage.getX(), (int) primaryStage.getY()); + logic.setGuiSettings(guiSettings); + helpWindow.hide(); + primaryStage.hide(); + + if (statisticWindow != null) { + statisticWindow.hide(); + } + } + + /** + * Alternates the focus on the command box. + */ + private void handleFocusOnCommandBox(KeyEvent event) { + if (!KeyCode.ESCAPE.equals(event.getCode())) { + return; + } + if (commandBox.isFocused()) { + commandBoxPlaceholder.requestFocus(); + logger.info("Focus on view"); + } else { + commandBox.requestFocus(); + logger.info("Focus on text"); + } + } + + /** + * Alternates the focus on the current tab view. + */ + private void handleFocusOnView(KeyEvent event) { + if (!KeyCode.ESCAPE.equals(event.getCode()) || commandBox.isFocused()) { + return; + } + if (isSelectedTab(studentListTab)) { + currentStudentViewList.requestFocus(); + } else if (isSelectedTab(sessionListTab)) { + sessionListPanel.requestFocus(); + } else if (isSelectedTab(claimsListTab)) { + moduleListPanelCopy.requestFocus(); + } else { + assert false; + logger.warning("Tab does not exist"); + } + } + + /** + * Alternates the focus on the module, group, and student list in the StudentView. + */ + private void handleSwitchingStudentViewLists(KeyEvent event) { + if (!isSelectedTab(studentListTab) || commandBox.isFocused()) { + return; + } + switch (event.getCode()) { + + case LEFT: + handleLeftKeyReleased(); + break; + case RIGHT: + handleRightKeyReleased(); + break; + default: + logger.fine("Not switching lists"); + break; + } + } + + /** + * Sets the focus on the list view to the left of the currently active list view. + * This can only be used in the Student View since it has multiple lists. + */ + private void handleLeftKeyReleased() { + if (currentStudentViewList.equals(studentListPanel)) { + logger.info("LEFT: Showing groups"); + currentStudentViewList = groupListPanel; + + } else if (currentStudentViewList.equals(groupListPanel)) { + logger.info("LEFT: Showing modules"); + currentStudentViewList = moduleListPanel; + + } else { + assert currentStudentViewList.equals(moduleListPanel); + logger.fine("Nothing to the left of module list panel"); + } + currentStudentViewList.requestFocus(); + } + + /** + * Sets the focus on the list view to the right of the currently active list view. + * This can only be used in the Student View since it has multiple lists. + */ + private void handleRightKeyReleased() { + if (currentStudentViewList.equals(moduleListPanel)) { + logger.info("RIGHT: Showing groups"); + currentStudentViewList = groupListPanel; + + } else if (currentStudentViewList.equals(groupListPanel)) { + logger.info("RIGHT: Showing students"); + currentStudentViewList = studentListPanel; + + } else { + assert currentStudentViewList.equals(studentListPanel); + logger.fine("Nothing to the right of student list panel"); + } + currentStudentViewList.requestFocus(); + } + + /** + * Executes the command and returns the result. + * + * @see tatracker.logic.Logic#execute(String) + */ + private CommandResult executeCommand(String commandText) throws CommandException, ParseException { + try { + CommandResult commandResult = logic.execute(commandText); + logger.info("Result: " + commandResult.getFeedbackToUser()); + resultDisplay.setFeedbackToUser(commandResult.getFeedbackToUser()); + + if (commandResult instanceof StatisticCommandResult) { + StatisticCommandResult scr = (StatisticCommandResult) commandResult; + handleStatistic(scr.targetModuleCode); + } + + switch (commandResult.getNextAction()) { + case DONE: + claimsListPanel.updateLabel(); + handleGoto(claimsListTab); + break; + + case EXIT: + handleExit(); + break; + + case GOTO_CLAIMS: + case FILTER_CLAIMS: + moduleListPanelCopy.updateCells(logic.getFilteredModuleList()); + claimsListPanel.updateLabel(); + handleGoto(claimsListTab); + break; + + case FILTER_SESSION: + sessionListPanel.updateLabel( + logic.getCurrSessionDateFilter(), + logic.getCurrSessionModuleFilter(), + logic.getCurrSessionTypeFilter()); + handleGoto(sessionListTab); + break; + + case FILTER_STUDENT: + moduleListPanel.updateCells(logic.getFilteredModuleList()); + groupListPanel.updateCells(logic.getFilteredGroupList()); + handleGoto(studentListTab); + break; + + case GOTO_SESSION: + handleGoto(sessionListTab); + break; + + case GOTO_STUDENT: + moduleListPanel.updateCells(logic.getFilteredModuleList()); + moduleListPanelCopy.updateCells(logic.getFilteredModuleList()); + groupListPanel.updateCells(logic.getFilteredGroupList()); + handleGoto(studentListTab); + break; + + case HELP: + handleHelp(); + break; + + case LIST: + claimsListPanel.updateLabel(); + moduleListPanelCopy.updateCells(logic.getFilteredModuleList()); + sessionListPanel.updateLabel( + logic.getCurrSessionDateFilter(), + logic.getCurrSessionModuleFilter(), + logic.getCurrSessionTypeFilter()); + break; + + default: + break; + } + return commandResult; + + } catch (CommandException | ParseException e) { + logger.info("Invalid command: " + commandText); + resultDisplay.setFeedbackToUser(e.getMessage()); + throw e; + } + } +} diff --git a/src/main/java/seedu/address/ui/ResultDisplay.java b/src/main/java/tatracker/ui/ResultDisplay.java similarity index 95% rename from src/main/java/seedu/address/ui/ResultDisplay.java rename to src/main/java/tatracker/ui/ResultDisplay.java index 7d98e84eedf..9ac9665ea63 100644 --- a/src/main/java/seedu/address/ui/ResultDisplay.java +++ b/src/main/java/tatracker/ui/ResultDisplay.java @@ -1,4 +1,4 @@ -package seedu.address.ui; +package tatracker.ui; import static java.util.Objects.requireNonNull; diff --git a/src/main/java/tatracker/ui/StatisticWindow.java b/src/main/java/tatracker/ui/StatisticWindow.java new file mode 100644 index 00000000000..6f1c51f0075 --- /dev/null +++ b/src/main/java/tatracker/ui/StatisticWindow.java @@ -0,0 +1,183 @@ +package tatracker.ui; + +import java.util.logging.Logger; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.event.EventHandler; +import javafx.fxml.FXML; +import javafx.scene.chart.BarChart; +import javafx.scene.chart.PieChart; +import javafx.scene.control.Label; +import javafx.scene.input.KeyCode; +import javafx.scene.input.KeyEvent; +import javafx.stage.Stage; + +import tatracker.commons.core.LogsCenter; +import tatracker.model.session.SessionType; +import tatracker.model.statistic.Statistic; + +/** + * Controller for a statistic page + */ +public class StatisticWindow extends UiPart { + + public static final int NUM_STUDENTS_TO_DISPLAY = 5; + + private static final Logger logger = LogsCenter.getLogger(HelpWindow.class); + private static final String FXML = "StatisticWindow.fxml"; + + @FXML + private PieChart hoursBreakdownPieChart; + + @FXML + private BarChart studentRatingBarChart; + + // Labels + @FXML + private Label moduleCodeLabel; + @FXML + private Label numHoursTutorialLabel; + @FXML + private Label numHoursConsultationLabel; + @FXML + private Label numHoursLabLabel; + @FXML + private Label numHoursGradingLabel; + @FXML + private Label numHoursPreparationLabel; + @FXML + private Label numHoursOtherLabel; + @FXML + private Label numHoursTotalLabel; + + @FXML + private Label studentName1Label; + @FXML + private Label studentName2Label; + @FXML + private Label studentName3Label; + @FXML + private Label studentName4Label; + @FXML + private Label studentName5Label; + @FXML + private Label rating1Label; + @FXML + private Label rating2Label; + @FXML + private Label rating3Label; + @FXML + private Label rating4Label; + @FXML + private Label rating5Label; + + + /** + * Creates a new HelpWindow. + * + * @param root Stage to use as the root of the HelpWindow. + */ + public StatisticWindow(Stage root, Statistic stats) { + super(FXML, root); + + ObservableList pieChartData = FXCollections.observableArrayList(); + + for (int i = 0; i < SessionType.NUM_SESSION_TYPES; ++i) { + if (stats.numHoursPerCategory[i] > 0) { + pieChartData.add( + new PieChart.Data(SessionType.getSessionTypeById(i).toString(), stats.numHoursPerCategory[i])); + } + } + hoursBreakdownPieChart.setData(pieChartData); + + BarChart.Series dataSeries1 = new BarChart.Series<>(); + for (int i = 0; i < 5; ++i) { + dataSeries1.getData().add(new BarChart.Data<>(Integer.toString(i), stats.studentRatingBinValues[i])); + } + studentRatingBarChart.getData().add(dataSeries1); + + // Set labels + moduleCodeLabel.setText(stats.targetModuleCode); + numHoursTutorialLabel.setText(stats.numHoursPerCategory[0] + " Hours"); + numHoursLabLabel.setText(stats.numHoursPerCategory[1] + " Hours"); + numHoursConsultationLabel.setText(stats.numHoursPerCategory[2] + " Hours"); + numHoursGradingLabel.setText(stats.numHoursPerCategory[3] + " Hours"); + numHoursPreparationLabel.setText(stats.numHoursPerCategory[4] + " Hours"); + numHoursOtherLabel.setText(stats.numHoursPerCategory[5] + " Hours"); + numHoursTotalLabel.setText(stats.getTotalHours() + " (S$" + stats.getTotalEarnings() + ")"); + + studentName1Label.setText(stats.worstStudents[0].getFullName()); + rating1Label.setText(Integer.toString(stats.worstStudents[0].getRating())); + studentName2Label.setText(stats.worstStudents[1].getFullName()); + rating2Label.setText(Integer.toString(stats.worstStudents[1].getRating())); + studentName3Label.setText(stats.worstStudents[2].getFullName()); + rating3Label.setText(Integer.toString(stats.worstStudents[2].getRating())); + studentName4Label.setText(stats.worstStudents[3].getFullName()); + rating4Label.setText(Integer.toString(stats.worstStudents[3].getRating())); + studentName5Label.setText(stats.worstStudents[4].getFullName()); + rating5Label.setText(Integer.toString(stats.worstStudents[4].getRating())); + + root.addEventHandler(KeyEvent.KEY_PRESSED, new EventHandler() { + @Override + public void handle(KeyEvent t) { + if (t.getCode() == KeyCode.ESCAPE) { + logger.info("click on escape"); + root.close(); + } + } + }); + } + + /** + * Creates a new HelpWindow. + */ + public StatisticWindow(Statistic stats) { + this(new Stage(), stats); + } + + /** + * Shows the statistic window. + * + * @throws IllegalStateException

    + *
  • + * if this method is called on a thread other than the JavaFX Application Thread. + *
  • + *
  • + * if this method is called during animation or layout processing. + *
  • + *
  • + * if this method is called on the primary stage. + *
  • + *
  • + * if {@code dialogStage} is already showing. + *
  • + *
+ */ + public void show() { + logger.fine("Showing statistic page about the application."); + getRoot().show(); + getRoot().centerOnScreen(); + } + + /** + * Returns true if the statistic window is currently being shown. + */ + public boolean isShowing() { + return getRoot().isShowing(); + } + + /** + * Hides the statistic window. + */ + public void hide() { + getRoot().hide(); + } + + /** + * Focuses on the statistic window. + */ + public void focus() { + getRoot().requestFocus(); + } +} diff --git a/src/main/java/seedu/address/ui/StatusBarFooter.java b/src/main/java/tatracker/ui/StatusBarFooter.java similarity index 95% rename from src/main/java/seedu/address/ui/StatusBarFooter.java rename to src/main/java/tatracker/ui/StatusBarFooter.java index 7e17911323f..510a53f8fd8 100644 --- a/src/main/java/seedu/address/ui/StatusBarFooter.java +++ b/src/main/java/tatracker/ui/StatusBarFooter.java @@ -1,4 +1,4 @@ -package seedu.address.ui; +package tatracker.ui; import java.nio.file.Path; import java.nio.file.Paths; diff --git a/src/main/java/seedu/address/ui/Ui.java b/src/main/java/tatracker/ui/Ui.java similarity index 86% rename from src/main/java/seedu/address/ui/Ui.java rename to src/main/java/tatracker/ui/Ui.java index 17aa0b494fe..62f32eedf80 100644 --- a/src/main/java/seedu/address/ui/Ui.java +++ b/src/main/java/tatracker/ui/Ui.java @@ -1,4 +1,4 @@ -package seedu.address.ui; +package tatracker.ui; import javafx.stage.Stage; diff --git a/src/main/java/seedu/address/ui/UiManager.java b/src/main/java/tatracker/ui/UiManager.java similarity index 91% rename from src/main/java/seedu/address/ui/UiManager.java rename to src/main/java/tatracker/ui/UiManager.java index 876621d79b9..a1fb2dfdca2 100644 --- a/src/main/java/seedu/address/ui/UiManager.java +++ b/src/main/java/tatracker/ui/UiManager.java @@ -1,4 +1,4 @@ -package seedu.address.ui; +package tatracker.ui; import java.util.logging.Logger; @@ -7,10 +7,10 @@ import javafx.scene.control.Alert.AlertType; import javafx.scene.image.Image; import javafx.stage.Stage; -import seedu.address.MainApp; -import seedu.address.commons.core.LogsCenter; -import seedu.address.commons.util.StringUtil; -import seedu.address.logic.Logic; +import tatracker.MainApp; +import tatracker.commons.core.LogsCenter; +import tatracker.commons.util.StringUtil; +import tatracker.logic.Logic; /** * The manager of the UI component. @@ -20,7 +20,7 @@ public class UiManager implements Ui { public static final String ALERT_DIALOG_PANE_FIELD_ID = "alertDialogPane"; private static final Logger logger = LogsCenter.getLogger(UiManager.class); - private static final String ICON_APPLICATION = "/images/address_book_32.png"; + private static final String ICON_APPLICATION = "/images/icon.png"; private Logic logic; private MainWindow mainWindow; diff --git a/src/main/java/seedu/address/ui/UiPart.java b/src/main/java/tatracker/ui/UiPart.java similarity index 97% rename from src/main/java/seedu/address/ui/UiPart.java rename to src/main/java/tatracker/ui/UiPart.java index fc820e01a9c..be489f6d9b7 100644 --- a/src/main/java/seedu/address/ui/UiPart.java +++ b/src/main/java/tatracker/ui/UiPart.java @@ -1,4 +1,4 @@ -package seedu.address.ui; +package tatracker.ui; import static java.util.Objects.requireNonNull; @@ -6,7 +6,7 @@ import java.net.URL; import javafx.fxml.FXMLLoader; -import seedu.address.MainApp; +import tatracker.MainApp; /** * Represents a distinct part of the UI. e.g. Windows, dialogs, panels, status bars, etc. diff --git a/src/main/java/tatracker/ui/claimstab/ClaimsCard.java b/src/main/java/tatracker/ui/claimstab/ClaimsCard.java new file mode 100644 index 00000000000..a7078c79c8d --- /dev/null +++ b/src/main/java/tatracker/ui/claimstab/ClaimsCard.java @@ -0,0 +1,77 @@ +package tatracker.ui.claimstab; + +import java.time.format.DateTimeFormatter; + +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Region; + +import tatracker.model.session.Session; +import tatracker.ui.UiPart; + +/** + * An UI component that displays information of a done {@code Session}. + */ +public class ClaimsCard extends UiPart { + + private static final String FXML = "ClaimsListCard.fxml"; + + /** + * Note: Certain keywords such as "location" and "resources" are reserved keywords in JavaFX. + * As a consequence, UI elements' variable names cannot be set to such keywords + * or an exception will be thrown by JavaFX during runtime. + * + * @see The issue on AddressBook level 4 + */ + + public final Session claim; + + @FXML + private HBox cardPane; + @FXML + private Label id; + @FXML + private Label type; + @FXML + private Label date; + @FXML + private Label time; + @FXML + private Label module; + @FXML + private Label description; + + private final DateTimeFormatter timeFormat = DateTimeFormatter.ofPattern("hh:mma"); + private final DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern("dd MMM yyyy"); + + public ClaimsCard(Session claim, int displayedIndex) { + super(FXML); + this.claim = claim; + id.setText(displayedIndex + ". "); + type.setText(claim.getSessionType().toString()); + date.setText(claim.getStartDateTime().format(dateFormat)); + time.setText(claim.getStartDateTime().format(timeFormat) + " - " + + claim.getEndDateTime().format(timeFormat)); + module.setText(claim.getModuleCode().toUpperCase()); + description.setText(claim.getDescription()); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof ClaimsCard)) { + return false; + } + + // state check + ClaimsCard card = (ClaimsCard) other; + return id.getText().equals(card.id.getText()) + && claim.equals(card.claim); + } +} diff --git a/src/main/java/tatracker/ui/claimstab/ClaimsListPanel.java b/src/main/java/tatracker/ui/claimstab/ClaimsListPanel.java new file mode 100644 index 00000000000..68a130c5525 --- /dev/null +++ b/src/main/java/tatracker/ui/claimstab/ClaimsListPanel.java @@ -0,0 +1,86 @@ +package tatracker.ui.claimstab; + +import static tatracker.model.TaTracker.getCurrentlyShownModuleClaim; + +import java.util.logging.Logger; + +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.control.ListCell; +import javafx.scene.control.ListView; +import javafx.scene.layout.Region; + +import tatracker.commons.core.LogsCenter; +import tatracker.model.ReadOnlyTaTracker; +import tatracker.model.session.Session; +import tatracker.ui.UiPart; + +/** + * Panel containing the list of done sessions. + */ +public class ClaimsListPanel extends UiPart { + private static final String FXML = "ClaimsListPanel.fxml"; + + private final Logger logger = LogsCenter.getLogger(ClaimsListPanel.class); + + @FXML + private ListView claimsListView; + + @FXML + private Label totalEarnings; + + private final ReadOnlyTaTracker taTracker; + + public ClaimsListPanel(ObservableList claimsList, ReadOnlyTaTracker taTracker) { + super(FXML); + this.taTracker = taTracker; + + logger.fine("Showing Claims List"); + claimsListView.setItems(claimsList); + claimsListView.setCellFactory(listView -> new ClaimsListViewCell()); + totalEarnings.setText("Total Earnings: " + this.taTracker.getTotalEarnings()); + + claimsListView.focusedProperty().addListener((arg, oldVal, focused) -> { + if (focused) { + claimsListView.setStyle("-fx-border-color: #264780; -fx-border-width: 1;"); + } else { + claimsListView.setStyle(""); + } + }); + } + + /** + * Update Label in order to facilitate incrementing total earnings + */ + public void updateLabel() { + logger.fine("Update label: Total Earning"); + totalEarnings.setText("Total Earnings: " + taTracker.getTotalEarnings()); + } + + /** + * Custom {@code ListCell} that displays the graphics of a done {@code Session} using a {@code ClaimsCard}. + */ + class ClaimsListViewCell extends ListCell { + @Override + protected void updateItem(Session claim, boolean empty) { + super.updateItem(claim, empty); + getStyleClass().removeAll("filtered", "list-cell"); + + if (empty || claim == null) { + setGraphic(null); + setText(null); + getStyleClass().add("list-cell"); + setStyle(""); + } else { + setGraphic(new ClaimsCard(claim, getIndex() + 1).getRoot()); + if ((getCurrentlyShownModuleClaim() != null)) { + getStyleClass().add("filtered"); + } else { + getStyleClass().add("list-cell"); + setStyle(""); + } + } + } + } +} diff --git a/src/main/java/tatracker/ui/claimstab/ModuleListPanelCopy.java b/src/main/java/tatracker/ui/claimstab/ModuleListPanelCopy.java new file mode 100644 index 00000000000..136ae55dc72 --- /dev/null +++ b/src/main/java/tatracker/ui/claimstab/ModuleListPanelCopy.java @@ -0,0 +1,89 @@ +package tatracker.ui.claimstab; + +import static tatracker.model.TaTracker.getCurrentlyShownModuleClaim; + +import java.util.logging.Logger; + +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.scene.control.ListCell; +import javafx.scene.control.ListView; +import javafx.scene.layout.Region; + +import tatracker.commons.core.LogsCenter; +import tatracker.model.module.Module; +import tatracker.ui.Focusable; +import tatracker.ui.UiPart; +import tatracker.ui.studenttab.ModuleCard; + +/** + * Panel containing the list of modules. + */ +public class ModuleListPanelCopy extends UiPart implements Focusable { + private static final String FXML = "ModuleListPanelCopy.fxml"; + + private final Logger logger = LogsCenter.getLogger(ModuleListPanelCopy.class); + + @FXML + private ListView moduleListViewCopy; + + public ModuleListPanelCopy(ObservableList moduleListCopy) { + super(FXML); + moduleListViewCopy.setItems(moduleListCopy); + moduleListViewCopy.setCellFactory(listView -> new ModuleListViewCellCopy()); + moduleListViewCopy.focusedProperty().addListener((arg, oldVal, focused) -> { + if (focused) { + moduleListViewCopy.setStyle("-fx-border-color: #264780; -fx-border-width: 1;"); + } else { + moduleListViewCopy.setStyle(""); + } + }); + } + + @Override + public void requestFocus() { + moduleListViewCopy.requestFocus(); + } + + @Override + public boolean isFocused() { + return moduleListViewCopy.isFocused(); + } + + /** + * Update ListCells in order to facilitate highlighting when a filter command is entered + * @param moduleList the updated moduleList + */ + public void updateCells(ObservableList moduleList) { + logger.fine("reached updateCells"); + moduleListViewCopy.setItems(moduleList); + moduleListViewCopy.setCellFactory(listView -> new ModuleListViewCellCopy()); + } + + /** + * Custom {@code ListCell} that displays the graphics of a {@code Module} using a {@code ModuleCardCopy}. + */ + class ModuleListViewCellCopy extends ListCell { + @Override + protected void updateItem(Module module, boolean empty) { + super.updateItem(module, empty); + getStyleClass().removeAll("filtered", "list-cell"); + + if (empty || module == null) { + setGraphic(null); + setText(null); + getStyleClass().add("list-cell"); + setStyle(""); + } else { + setGraphic(new ModuleCard(module, getIndex() + 1).getRoot()); + if (module.equals(getCurrentlyShownModuleClaim())) { + getStyleClass().add("filtered"); + } else { + getStyleClass().add("list-cell"); + setStyle(""); + } + } + } + } + +} diff --git a/src/main/java/tatracker/ui/sessiontab/SessionCard.java b/src/main/java/tatracker/ui/sessiontab/SessionCard.java new file mode 100644 index 00000000000..cc37710f75d --- /dev/null +++ b/src/main/java/tatracker/ui/sessiontab/SessionCard.java @@ -0,0 +1,84 @@ +package tatracker.ui.sessiontab; + +import java.time.format.DateTimeFormatter; + +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Region; + +import tatracker.model.session.Session; +import tatracker.ui.UiPart; + +/** + * An UI component that displays information of a {@code Session}. + */ +public class SessionCard extends UiPart { + + private static final String FXML = "SessionListCard.fxml"; + + /** + * Note: Certain keywords such as "location" and "resources" are reserved keywords in JavaFX. + * As a consequence, UI elements' variable names cannot be set to such keywords + * or an exception will be thrown by JavaFX during runtime. + * + * @see The issue on AddressBook level 4 + */ + + public final Session session; + + @FXML + private HBox cardPane; + @FXML + private Label id; + @FXML + private Label type; + @FXML + private Label date; + @FXML + private Label time; + @FXML + private Label module; + @FXML + private Label description; + @FXML + private Label recur; + + private final DateTimeFormatter timeFormat = DateTimeFormatter.ofPattern("hh:mma"); + private final DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern("dd MMM yyyy"); + + public SessionCard(Session session, int displayedIndex) { + super(FXML); + this.session = session; + id.setText(displayedIndex + ". "); + type.setText(session.getSessionType().toString()); + date.setText(session.getStartDateTime().format(dateFormat)); + time.setText(session.getStartDateTime().format(timeFormat) + " - " + + session.getEndDateTime().format(timeFormat)); + module.setText(session.getModuleCode().toUpperCase()); + description.setText(session.getDescription()); + if (session.getRecurring() > 0) { + recur.setText("Every " + session.getRecurring() + " Week(s)"); + } else { + recur.setText("Not Recurring"); + } + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof SessionCard)) { + return false; + } + + // state check + SessionCard card = (SessionCard) other; + return id.getText().equals(card.id.getText()) + && session.equals(card.session); + } +} diff --git a/src/main/java/tatracker/ui/sessiontab/SessionListPanel.java b/src/main/java/tatracker/ui/sessiontab/SessionListPanel.java new file mode 100644 index 00000000000..6c5c30d9a41 --- /dev/null +++ b/src/main/java/tatracker/ui/sessiontab/SessionListPanel.java @@ -0,0 +1,113 @@ +package tatracker.ui.sessiontab; + +import java.util.logging.Logger; + +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.control.ListCell; +import javafx.scene.control.ListView; +import javafx.scene.layout.GridPane; +import javafx.scene.layout.Region; + +import tatracker.commons.core.LogsCenter; +import tatracker.model.session.Session; +import tatracker.ui.Focusable; +import tatracker.ui.UiPart; + +/** + * Panel containing the list of sessions. + */ +public class SessionListPanel extends UiPart implements Focusable { + private static final String NO_FILTER = "No filter"; + + private static final String FXML = "SessionListPanel.fxml"; + + private final Logger logger = LogsCenter.getLogger(SessionListPanel.class); + + @FXML + private ListView sessionListView; + + @FXML + private GridPane currentFiltersGrid; + + @FXML + private Label currentDateFilters; + + @FXML + private Label currentTypeFilters; + + @FXML + private Label currentModuleFilters; + + public SessionListPanel(ObservableList sessionList) { + super(FXML); + logger.fine("Showing Session List"); + sessionListView.setItems(sessionList); + sessionListView.setCellFactory(listView -> new SessionListViewCell()); + currentDateFilters.setText(NO_FILTER); + currentModuleFilters.setText(NO_FILTER); + currentTypeFilters.setText(NO_FILTER); + + sessionListView.focusedProperty().addListener((arg, oldVal, focused) -> { + if (focused) { + sessionListView.setStyle("-fx-border-color: #264780; -fx-border-width: 1;"); + } else { + sessionListView.setStyle(""); + } + }); + } + + /** + * Update Label in order to facilitate changing current filters + */ + public void updateLabel(String dateFilter, String moduleFilter, String typeFilter) { + logger.fine("Update session filters label"); + + String actualDateFilter = dateFilter; + if (dateFilter.isBlank()) { + actualDateFilter = NO_FILTER; + } + + String actualModuleFilter = moduleFilter; + if (moduleFilter.isBlank()) { + actualModuleFilter = NO_FILTER; + } + + String actualTypeFilter = typeFilter; + if (typeFilter.isBlank()) { + actualTypeFilter = NO_FILTER; + } + + currentDateFilters.setText(actualDateFilter); + currentModuleFilters.setText(actualModuleFilter); + currentTypeFilters.setText(actualTypeFilter); + } + + @Override + public void requestFocus() { + sessionListView.requestFocus(); + } + + @Override + public boolean isFocused() { + return sessionListView.isFocused(); + } + + /** + * Custom {@code ListCell} that displays the graphics of a {@code Session} using a {@code SessionCard}. + */ + class SessionListViewCell extends ListCell { + @Override + protected void updateItem(Session session, boolean empty) { + super.updateItem(session, empty); + + if (empty || session == null) { + setGraphic(null); + setText(null); + } else { + setGraphic(new SessionCard(session, getIndex() + 1).getRoot()); + } + } + } +} diff --git a/src/main/java/tatracker/ui/studenttab/GroupCard.java b/src/main/java/tatracker/ui/studenttab/GroupCard.java new file mode 100644 index 00000000000..db42fcbb97b --- /dev/null +++ b/src/main/java/tatracker/ui/studenttab/GroupCard.java @@ -0,0 +1,62 @@ +package tatracker.ui.studenttab; + +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Region; + +import tatracker.model.group.Group; +import tatracker.ui.UiPart; + +/** + * An UI component that displays information of a {@code Group}. + */ +public class GroupCard extends UiPart { + + private static final String FXML = "GroupListCard.fxml"; + + /** + * Note: Certain keywords such as "location" and "resources" are reserved keywords in JavaFX. + * As a consequence, UI elements' variable names cannot be set to such keywords + * or an exception will be thrown by JavaFX during runtime. + * + * @see The issue on AddressBook level 4 + */ + + public final Group group; + + @FXML + private HBox cardPane; + @FXML + private Label name; + @FXML + private Label id; + @FXML + private Label type; + + public GroupCard(Group group, int displayedIndex) { + super(FXML); + this.group = group; + id.setText(displayedIndex + ". "); + name.setText(group.getIdentifier()); + type.setText(group.getGroupType().toString()); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof GroupCard)) { + return false; + } + + // state check + GroupCard card = (GroupCard) other; + return id.getText().equals(card.id.getText()) + && group.equals(card.group); + } +} diff --git a/src/main/java/tatracker/ui/studenttab/GroupListPanel.java b/src/main/java/tatracker/ui/studenttab/GroupListPanel.java new file mode 100644 index 00000000000..ecaf8e0fe3e --- /dev/null +++ b/src/main/java/tatracker/ui/studenttab/GroupListPanel.java @@ -0,0 +1,87 @@ +package tatracker.ui.studenttab; + +import static tatracker.model.TaTracker.getCurrentlyShownGroup; + +import java.util.logging.Logger; + +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.scene.control.ListCell; +import javafx.scene.control.ListView; +import javafx.scene.layout.Region; + +import tatracker.commons.core.LogsCenter; +import tatracker.model.group.Group; +import tatracker.ui.Focusable; +import tatracker.ui.UiPart; + +/** + * Panel containing the list of groups. + */ +public class GroupListPanel extends UiPart implements Focusable { + private static final String FXML = "GroupListPanel.fxml"; + + private final Logger logger = LogsCenter.getLogger(GroupListPanel.class); + + @FXML + private ListView groupListView; + + public GroupListPanel(ObservableList groupList) { + super(FXML); + groupListView.setItems(groupList); + groupListView.setCellFactory(listView -> new GroupListViewCell()); + groupListView.focusedProperty().addListener((arg, oldVal, focused) -> { + if (focused) { + groupListView.setStyle("-fx-border-color: #264780; -fx-border-width: 1;"); + } else { + groupListView.setStyle(""); + } + }); + } + + @Override + public void requestFocus() { + groupListView.requestFocus(); + } + + @Override + public boolean isFocused() { + return groupListView.isFocused(); + } + + /** + * Update ListCells in order to facilitate highlighting when a filter command is entered + * @param groupList the updated groupList + */ + public void updateCells(ObservableList groupList) { + logger.fine("reached updateCells"); + groupListView.setItems(groupList); + groupListView.setCellFactory(listView -> new GroupListViewCell()); + } + + /** + * Custom {@code ListCell} that displays the graphics of a {@code Group} using a {@code GroupCard}. + */ + class GroupListViewCell extends ListCell { + @Override + protected void updateItem(Group group, boolean empty) { + super.updateItem(group, empty); + getStyleClass().removeAll("filtered", "list-cell"); + + if (empty || group == null) { + setGraphic(null); + setText(null); + getStyleClass().add("list-cell"); + setStyle(""); + } else { + setGraphic(new GroupCard(group, getIndex() + 1).getRoot()); + if (group.equals(getCurrentlyShownGroup())) { + getStyleClass().add("filtered"); + } else { + getStyleClass().add("list-cell"); + setStyle(""); + } + } + } + } +} diff --git a/src/main/java/tatracker/ui/studenttab/ModuleCard.java b/src/main/java/tatracker/ui/studenttab/ModuleCard.java new file mode 100644 index 00000000000..45038fde662 --- /dev/null +++ b/src/main/java/tatracker/ui/studenttab/ModuleCard.java @@ -0,0 +1,62 @@ +package tatracker.ui.studenttab; + +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Region; + +import tatracker.model.module.Module; +import tatracker.ui.UiPart; + +/** + * An UI component that displays information of a {@code Module}. + */ +public class ModuleCard extends UiPart { + + private static final String FXML = "ModuleListCard.fxml"; + + /** + * Note: Certain keywords such as "location" and "resources" are reserved keywords in JavaFX. + * As a consequence, UI elements' variable names cannot be set to such keywords + * or an exception will be thrown by JavaFX during runtime. + * + * @see The issue on AddressBook level 4 + */ + + public final Module module; + + @FXML + private HBox cardPane; + @FXML + private Label name; + @FXML + private Label id; + @FXML + private Label identifier; + + public ModuleCard(Module module, int displayedIndex) { + super(FXML); + this.module = module; + id.setText(displayedIndex + ". "); + name.setText(module.getName()); + identifier.setText(module.getIdentifier()); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof ModuleCard)) { + return false; + } + + // state check + ModuleCard card = (ModuleCard) other; + return id.getText().equals(card.id.getText()) + && module.equals(card.module); + } +} diff --git a/src/main/java/tatracker/ui/studenttab/ModuleListPanel.java b/src/main/java/tatracker/ui/studenttab/ModuleListPanel.java new file mode 100644 index 00000000000..38077fbd9da --- /dev/null +++ b/src/main/java/tatracker/ui/studenttab/ModuleListPanel.java @@ -0,0 +1,87 @@ +package tatracker.ui.studenttab; + +import static tatracker.model.TaTracker.getCurrentlyShownModule; + +import java.util.logging.Logger; + +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.scene.control.ListCell; +import javafx.scene.control.ListView; +import javafx.scene.layout.Region; + +import tatracker.commons.core.LogsCenter; +import tatracker.model.module.Module; +import tatracker.ui.Focusable; +import tatracker.ui.UiPart; + +/** + * Panel containing the list of modules. + */ +public class ModuleListPanel extends UiPart implements Focusable { + private static final String FXML = "ModuleListPanel.fxml"; + + private final Logger logger = LogsCenter.getLogger(ModuleListPanel.class); + + @FXML + private ListView moduleListView; + + public ModuleListPanel(ObservableList moduleList) { + super(FXML); + moduleListView.setItems(moduleList); + moduleListView.setCellFactory(listView -> new ModuleListViewCell()); + moduleListView.focusedProperty().addListener((arg, oldVal, focused) -> { + if (focused) { + moduleListView.setStyle("-fx-border-color: #264780; -fx-border-width: 1;"); + } else { + moduleListView.setStyle(""); + } + }); + } + + @Override + public void requestFocus() { + moduleListView.requestFocus(); + } + + @Override + public boolean isFocused() { + return moduleListView.isFocused(); + } + + /** + * Update ListCells in order to facilitate highlighting when a filter command is entered + * @param moduleList the updated moduleList + */ + public void updateCells(ObservableList moduleList) { + logger.fine("reached updateCells"); + moduleListView.setItems(moduleList); + moduleListView.setCellFactory(listView -> new ModuleListViewCell()); + } + + /** + * Custom {@code ListCell} that displays the graphics of a {@code Module} using a {@code ModuleCard}. + */ + class ModuleListViewCell extends ListCell { + @Override + protected void updateItem(Module module, boolean empty) { + super.updateItem(module, empty); + getStyleClass().removeAll("filtered", "list-cell"); + + if (empty || module == null) { + setGraphic(null); + setText(null); + getStyleClass().add("list-cell"); + setStyle(""); + } else { + setGraphic(new ModuleCard(module, getIndex() + 1).getRoot()); + if (module.equals(getCurrentlyShownModule())) { + getStyleClass().add("filtered"); + } else { + getStyleClass().add("list-cell"); + setStyle(""); + } + } + } + } +} diff --git a/src/main/java/seedu/address/ui/PersonCard.java b/src/main/java/tatracker/ui/studenttab/StudentCard.java similarity index 56% rename from src/main/java/seedu/address/ui/PersonCard.java rename to src/main/java/tatracker/ui/studenttab/StudentCard.java index 0684b088868..b2805ef764b 100644 --- a/src/main/java/seedu/address/ui/PersonCard.java +++ b/src/main/java/tatracker/ui/studenttab/StudentCard.java @@ -1,4 +1,4 @@ -package seedu.address.ui; +package tatracker.ui.studenttab; import java.util.Comparator; @@ -7,14 +7,16 @@ import javafx.scene.layout.FlowPane; import javafx.scene.layout.HBox; import javafx.scene.layout.Region; -import seedu.address.model.person.Person; + +import tatracker.model.student.Student; +import tatracker.ui.UiPart; /** - * An UI component that displays information of a {@code Person}. + * An UI component that displays information of a {@code Student}. */ -public class PersonCard extends UiPart { +public class StudentCard extends UiPart { - private static final String FXML = "PersonListCard.fxml"; + private static final String FXML = "StudentListCard.fxml"; /** * Note: Certain keywords such as "location" and "resources" are reserved keywords in JavaFX. @@ -24,7 +26,7 @@ public class PersonCard extends UiPart { * @see The issue on AddressBook level 4 */ - public final Person person; + public final Student student; @FXML private HBox cardPane; @@ -35,21 +37,28 @@ public class PersonCard extends UiPart { @FXML private Label phone; @FXML - private Label address; - @FXML private Label email; @FXML + private Label matric; + @FXML + private Label rating; + @FXML private FlowPane tags; - public PersonCard(Person person, int displayedIndex) { + public StudentCard(Student student, int displayedIndex) { super(FXML); - this.person = person; + this.student = student; id.setText(displayedIndex + ". "); - name.setText(person.getName().fullName); - phone.setText(person.getPhone().value); - address.setText(person.getAddress().value); - email.setText(person.getEmail().value); - person.getTags().stream() + name.setText(student.getName().fullName); + phone.setText(student.getPhone().value); + email.setText(student.getEmail().value); + matric.setText(student.getMatric().value); + StringBuilder stars = new StringBuilder(); + for (int i = 0; i < student.getRating().value; i += 1) { + stars.append("\u2605"); + } + rating.setText(stars.toString()); + student.getTags().stream() .sorted(Comparator.comparing(tag -> tag.tagName)) .forEach(tag -> tags.getChildren().add(new Label(tag.tagName))); } @@ -62,13 +71,13 @@ public boolean equals(Object other) { } // instanceof handles nulls - if (!(other instanceof PersonCard)) { + if (!(other instanceof StudentCard)) { return false; } // state check - PersonCard card = (PersonCard) other; + StudentCard card = (StudentCard) other; return id.getText().equals(card.id.getText()) - && person.equals(card.person); + && student.equals(card.student); } } diff --git a/src/main/java/tatracker/ui/studenttab/StudentListPanel.java b/src/main/java/tatracker/ui/studenttab/StudentListPanel.java new file mode 100644 index 00000000000..5cc3eb5570b --- /dev/null +++ b/src/main/java/tatracker/ui/studenttab/StudentListPanel.java @@ -0,0 +1,73 @@ +package tatracker.ui.studenttab; + +import java.util.logging.Logger; + +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.scene.control.ListCell; +import javafx.scene.control.ListView; +import javafx.scene.layout.Region; + +import tatracker.commons.core.LogsCenter; +import tatracker.model.student.Student; +import tatracker.ui.Focusable; +import tatracker.ui.UiPart; + +/** + * Panel containing the list of students. + */ +public class StudentListPanel extends UiPart implements Focusable { + private static final String FXML = "StudentListPanel.fxml"; + + private final Logger logger = LogsCenter.getLogger(StudentListPanel.class); + + @FXML + private ListView studentListView; + + public StudentListPanel(ObservableList studentList) { + super(FXML); + logger.fine("Showing Student List"); + + studentListView.setItems(studentList); + studentListView.setCellFactory(listView -> new StudentListViewCell()); + studentListView.focusedProperty().addListener((arg, oldVal, focused) -> { + if (focused) { + studentListView.setStyle("-fx-border-color: #264780; -fx-border-width: 1;"); + } else { + studentListView.setStyle(""); + } + }); + } + + @Override + public void requestFocus() { + studentListView.requestFocus(); + } + + @Override + public boolean isFocused() { + return studentListView.isFocused(); + } + + /** + * Custom {@code ListCell} that displays the graphics of a {@code Student} using a {@code StudentCard}. + */ + class StudentListViewCell extends ListCell { + @Override + protected void updateItem(Student student, boolean empty) { + super.updateItem(student, empty); + getStyleClass().removeAll("filtered", "list-cell"); + + if (empty || student == null) { + setGraphic(null); + setText(null); + getStyleClass().add("list-cell"); + setStyle(""); + } else { + setGraphic(new StudentCard(student, getIndex() + 1).getRoot()); + getStyleClass().add("filtered"); + } + } + } + +} diff --git a/src/main/resources/images/claims_icon.png b/src/main/resources/images/claims_icon.png new file mode 100644 index 00000000000..a824c15f0f4 Binary files /dev/null and b/src/main/resources/images/claims_icon.png differ diff --git a/src/main/resources/images/icon.png b/src/main/resources/images/icon.png new file mode 100644 index 00000000000..48f4cf80f0e Binary files /dev/null and b/src/main/resources/images/icon.png differ diff --git a/src/main/resources/images/session_icon.png b/src/main/resources/images/session_icon.png new file mode 100644 index 00000000000..2de82106d3a Binary files /dev/null and b/src/main/resources/images/session_icon.png differ diff --git a/src/main/resources/images/student_icon.png b/src/main/resources/images/student_icon.png new file mode 100644 index 00000000000..9180522ca21 Binary files /dev/null and b/src/main/resources/images/student_icon.png differ diff --git a/src/main/resources/images/tracker.png b/src/main/resources/images/tracker.png new file mode 100644 index 00000000000..f73b63dc943 Binary files /dev/null and b/src/main/resources/images/tracker.png differ diff --git a/src/main/resources/view/ClaimsListCard.fxml b/src/main/resources/view/ClaimsListCard.fxml new file mode 100644 index 00000000000..564bfba1221 --- /dev/null +++ b/src/main/resources/view/ClaimsListCard.fxml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/ClaimsListPanel.fxml b/src/main/resources/view/ClaimsListPanel.fxml new file mode 100644 index 00000000000..93309c45c78 --- /dev/null +++ b/src/main/resources/view/ClaimsListPanel.fxml @@ -0,0 +1,14 @@ + + + + + + + + + + diff --git a/src/main/resources/view/CommandBox.fxml b/src/main/resources/view/CommandBox.fxml index 09f6d6fe9e4..d25c2c4383e 100644 --- a/src/main/resources/view/CommandBox.fxml +++ b/src/main/resources/view/CommandBox.fxml @@ -2,7 +2,6 @@ - diff --git a/src/main/resources/view/DarkTheme.css b/src/main/resources/view/DarkTheme.css index 36e6b001cd8..9a2d9911c83 100644 --- a/src/main/resources/view/DarkTheme.css +++ b/src/main/resources/view/DarkTheme.css @@ -1,6 +1,6 @@ .background { - -fx-background-color: derive(#1d1d1d, 20%); - background-color: #383838; /* Used in the default.html file */ + -fx-background-color: #232c3d; + background-color: #232c3d; /* Used in the default.html file */ } .label { @@ -30,19 +30,39 @@ } .tab-pane { + -fx-background-color: #232c3d; -fx-padding: 0 0 0 1; + -fx-tab-min-height: 3em; + -fx-tab-max-height: 3em; } .tab-pane .tab-header-area { + -fx-background-color: #232c3d; -fx-padding: 0 0 0 0; -fx-min-height: 0; -fx-max-height: 0; } +.tab-pane .tab-header-area .tab { + -fx-background-color: #232c3d; +} + +.tab-pane .tab-header-area .tab-header-background { + -fx-background-color: #232c3d; + -fx-opacity: 0.9; +} + +.tab-pane .tab-header-area .tab .tab-label { + -fx-font-size: 15pt; + -fx-font-family: "Segoe UI Light"; + -fx-text-fill: white; + -fx-opacity: 0.9; +} + .table-view { - -fx-base: #1d1d1d; - -fx-control-inner-background: #1d1d1d; - -fx-background-color: #1d1d1d; + -fx-base: #232c3d; + -fx-control-inner-background: #232c3d; + -fx-background-color: #232c3d; -fx-table-cell-border-color: transparent; -fx-table-header-border-color: transparent; -fx-padding: 5; @@ -77,20 +97,24 @@ } .split-pane:horizontal .split-pane-divider { - -fx-background-color: derive(#1d1d1d, 20%); + -fx-background-color: #232c3d; -fx-border-color: transparent transparent transparent #4d4d4d; } .split-pane { -fx-border-radius: 1; -fx-border-width: 1; - -fx-background-color: derive(#1d1d1d, 20%); + -fx-background-color: #232c3d; +} + +.label { + -fx-text-fill: white; } .list-view { -fx-background-insets: 0; -fx-padding: 0; - -fx-background-color: derive(#1d1d1d, 20%); + -fx-background-color: #232c3d; } .list-cell { @@ -100,22 +124,17 @@ } .list-cell:filled:even { - -fx-background-color: #3c3e3f; + -fx-background-color: #212938; } .list-cell:filled:odd { - -fx-background-color: #515658; + -fx-background-color: #232c3d; } .list-cell:filled:selected { -fx-background-color: #424d5f; } -.list-cell:filled:selected #cardPane { - -fx-border-color: #3e7b91; - -fx-border-width: 1; -} - .list-cell .label { -fx-text-fill: white; } @@ -133,17 +152,17 @@ } .stack-pane { - -fx-background-color: derive(#1d1d1d, 20%); + -fx-background-color: #232c3d; } .pane-with-border { - -fx-background-color: derive(#1d1d1d, 20%); - -fx-border-color: derive(#1d1d1d, 10%); + -fx-background-color: #232c3d; + -fx-border-color: #232c3d; -fx-border-top-width: 1px; } .status-bar { - -fx-background-color: derive(#1d1d1d, 30%); + -fx-background-color: #232c3d; } .result-display { @@ -165,8 +184,8 @@ } .status-bar-with-border { - -fx-background-color: derive(#1d1d1d, 30%); - -fx-border-color: derive(#1d1d1d, 25%); + -fx-background-color: #232c3d; + -fx-border-color: #232c3d; -fx-border-width: 1px; } @@ -175,17 +194,17 @@ } .grid-pane { - -fx-background-color: derive(#1d1d1d, 30%); - -fx-border-color: derive(#1d1d1d, 30%); + -fx-background-color: #232c3d; + -fx-border-color: #232c3d; -fx-border-width: 1px; } .grid-pane .stack-pane { - -fx-background-color: derive(#1d1d1d, 30%); + -fx-background-color: #232c3d; } .context-menu { - -fx-background-color: derive(#1d1d1d, 50%); + -fx-background-color: #232c3d; } .context-menu .label { @@ -193,7 +212,7 @@ } .menu-bar { - -fx-background-color: derive(#1d1d1d, 20%); + -fx-background-color: #232c3d; } .menu-bar .label { @@ -203,6 +222,10 @@ -fx-opacity: 0.9; } +.menu-item:focused { + -fx-background-color: #0096C9; +} + .menu .left-container { -fx-background-color: black; } @@ -217,7 +240,7 @@ -fx-border-color: #e2e2e2; -fx-border-width: 2; -fx-background-radius: 0; - -fx-background-color: #1d1d1d; + -fx-background-color: #232c3d; -fx-font-family: "Segoe UI", Helvetica, Arial, sans-serif; -fx-font-size: 11pt; -fx-text-fill: #d8d8d8; @@ -230,7 +253,7 @@ .button:pressed, .button:default:hover:pressed { -fx-background-color: white; - -fx-text-fill: #1d1d1d; + -fx-text-fill: #232c3d; } .button:focused { @@ -243,7 +266,7 @@ .button:disabled, .button:default:disabled { -fx-opacity: 0.4; - -fx-background-color: #1d1d1d; + -fx-background-color: #232c3d; -fx-text-fill: white; } @@ -257,11 +280,11 @@ } .dialog-pane { - -fx-background-color: #1d1d1d; + -fx-background-color: #232c3d; } .dialog-pane > *.button-bar > *.container { - -fx-background-color: #1d1d1d; + -fx-background-color: #232c3d; } .dialog-pane > *.label.content { @@ -271,7 +294,7 @@ } .dialog-pane:header *.header-panel { - -fx-background-color: derive(#1d1d1d, 25%); + -fx-background-color: #232c3d; } .dialog-pane:header *.header-panel *.label { @@ -282,11 +305,11 @@ } .scroll-bar { - -fx-background-color: derive(#1d1d1d, 20%); + -fx-background-color: #232c3d; } .scroll-bar .thumb { - -fx-background-color: derive(#1d1d1d, 50%); + -fx-background-color: #2f3a4f; -fx-background-insets: 3; } @@ -318,9 +341,9 @@ } #commandTextField { - -fx-background-color: transparent #383838 transparent #383838; + -fx-background-color: transparent #2f3a4f transparent #2f3a4f; -fx-background-insets: 0; - -fx-border-color: #383838 #383838 #ffffff #383838; + -fx-border-color: #2f3a4f #2f3a4f #555555 #2f3a4f; -fx-border-insets: 0; -fx-border-width: 1; -fx-font-family: "Segoe UI Light"; @@ -328,12 +351,12 @@ -fx-text-fill: white; } -#filterField, #personListPanel, #personWebpage { +#filterField, #studentListPanel, #studentWebpage { -fx-effect: innershadow(gaussian, black, 10, 0, 0, 0); } #resultDisplay .content { - -fx-background-color: transparent, #383838, transparent, #383838; + -fx-background-color: transparent, #2f3a4f, transparent, #2f3a4f; -fx-background-radius: 0; } @@ -350,3 +373,12 @@ -fx-background-radius: 2; -fx-font-size: 11; } + +.commandWord { + -fx-text-fill: white; + -fx-background-color: #264780; + -fx-padding: 3 5 3 5; + -fx-border-radius: 2; + -fx-background-radius: 2; + -fx-font-size: 18; +} diff --git a/src/main/resources/view/Extensions.css b/src/main/resources/view/Extensions.css index bfe82a85964..819a2396977 100644 --- a/src/main/resources/view/Extensions.css +++ b/src/main/resources/view/Extensions.css @@ -3,9 +3,20 @@ -fx-text-fill: #d06651 !important; /* The error class should always override the default text-fill style */ } +.valid { + -fx-text-fill: #3abf29 !important; /* The valid class should always override the default text-fill style */ +} + .list-cell:empty { /* Empty cells will not have alternating colours */ - -fx-background: #383838; + -fx-background: #1e2533; +} + + +.background-2 { + -fx-background-color: #2f3a4f; + -fx-background-radius: 5px; + background-color: #2f3a4f; /* Used in the default.html file */ } .tag-selector { @@ -18,3 +29,92 @@ .tooltip-text { -fx-text-fill: white; } + + +.label-new { + -fx-font-size: 14; + -fx-font-family: "Segoe UI Semibold"; + -fx-text-fill: #aaa; + -fx-opacity: 1; +} + +.label-title { + -fx-font-size: 16; + -fx-font-family: "Segoe UI Semibold"; + -fx-text-fill: #ccc; + -fx-opacity: 1; +} + +.label-h1 { + -fx-font-size: 28px; + -fx-font-family: "Segoe UI Semibold"; + -fx-text-fill: #aaa; + -fx-opacity: 1; +} + +.chart-plot-background { + -fx-background-color: #232b3b; +} + +.chart-bar { + -fx-background-color: rgba(115, 157, 235,0.85); + -fx-border-color: rgba(0,168,355,0.3) rgba(0,168,355,0.3) + transparent rgba(0,168,355,0.3); + -fx-background-radius: 0; +} + +.default-color0.chart-pie { -fx-pie-color: #FFADCA; } +.default-color1.chart-pie { -fx-pie-color: #BA72E8; } +.default-color2.chart-pie { -fx-pie-color: #8999FF; } +.default-color3.chart-pie { -fx-pie-color: #72D7E8; } +.default-color4.chart-pie { -fx-pie-color: #7DFFAF; } +.default-color5.chart-pie { -fx-pie-color: #cccccc; } + +.chart-title { + -fx-font-size: 16; + -fx-font-family: "Segoe UI Semibold"; + -fx-text-fill: #ccc; + -fx-opacity: 1; +} + +.axis { + -fx-text-fill: #aaa; + -fx-tick-label-fill: #aaa; +} + +.chart-pie-label-line { + -fx-stroke: #aaa; + -fx-fill: #aaa; +} + +.chart-pie-label { /*this is what you need for labels*/ + -fx-fill: #aaa; +} + +.filtered { + -fx-background-color: #5f4d42; + -fx-border-color: #917b3e transparent #917b3e transparent; + -fx-border-width: 1; +} + +.filtered { + -fx-label-padding: 0 0 0 0; + -fx-graphic-text-gap : 0; + -fx-padding: 0 0 0 0; +} + +.filtered:filled:even { + -fx-background-color: #503f35; +} + +.filtered:filled:odd { + -fx-background-color: #5f4d42; +} + +.filtered:filled:selected { + -fx-background-color: #424d5f; +} + +.filtered .label { + -fx-text-fill: white; +} diff --git a/src/main/resources/view/GroupListCard.fxml b/src/main/resources/view/GroupListCard.fxml new file mode 100644 index 00000000000..dae380f9e04 --- /dev/null +++ b/src/main/resources/view/GroupListCard.fxml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/PersonListPanel.fxml b/src/main/resources/view/GroupListPanel.fxml similarity index 77% rename from src/main/resources/view/PersonListPanel.fxml rename to src/main/resources/view/GroupListPanel.fxml index 8836d323cc5..dd01e1530a0 100644 --- a/src/main/resources/view/PersonListPanel.fxml +++ b/src/main/resources/view/GroupListPanel.fxml @@ -2,7 +2,6 @@ - - + diff --git a/src/main/resources/view/HelpListCard.fxml b/src/main/resources/view/HelpListCard.fxml new file mode 100644 index 00000000000..281f8c087e4 --- /dev/null +++ b/src/main/resources/view/HelpListCard.fxml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/HelpListPanel.fxml b/src/main/resources/view/HelpListPanel.fxml new file mode 100644 index 00000000000..7b1fc7202f3 --- /dev/null +++ b/src/main/resources/view/HelpListPanel.fxml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/main/resources/view/HelpWindow.fxml b/src/main/resources/view/HelpWindow.fxml index fa0fb54d9f4..3494482a2bb 100644 --- a/src/main/resources/view/HelpWindow.fxml +++ b/src/main/resources/view/HelpWindow.fxml @@ -1,39 +1,81 @@ + - + + + + + - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + +