package nl.itopia.corendon.controller.manager;
import java.io.File;
import com.sun.javafx.tk.Toolkit;
import javafx.application.Platform;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.chart.BarChart;
import javafx.scene.chart.Chart;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.XYChart;
import javafx.scene.control.Button;
import javafx.scene.control.CheckBox;
import javafx.scene.control.DatePicker;
import javafx.scene.control.Label;
import javafx.scene.image.ImageView;
import javafx.scene.layout.StackPane;
import nl.itopia.corendon.Config;
import nl.itopia.corendon.controller.LoginController;
import nl.itopia.corendon.data.Luggage;
import nl.itopia.corendon.data.Status;
import nl.itopia.corendon.data.manager.ChartData;
import nl.itopia.corendon.model.LuggageModel;
import nl.itopia.corendon.model.StatusModel;
import nl.itopia.corendon.mvc.Controller;
import nl.itopia.corendon.pdf.ManagerStatisticsPDF;
import nl.itopia.corendon.utils.DateUtil;
import java.time.LocalDate;
import java.util.*;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.stage.FileChooser;
import nl.itopia.corendon.model.EmployeeModel;
import nl.itopia.corendon.utils.Log;
import javax.swing.Timer;
import nl.itopia.corendon.controller.ChangePasswordController;
import nl.itopia.corendon.controller.InfoController;
import nl.itopia.corendon.utils.IO;
/**
* © 2014, Biodiscus.net robin
*/
public class ManagerController extends Controller {
@FXML private LineChart lineDiagram;
@FXML private BarChart barDiagram;
@FXML private Button filterButton, helpButton, logoutButton, printstatisticsButton, lineDiagrambutton, barDiagrambutton,logfilesbutton, changePasswordButton, refreshButton;
@FXML private CheckBox foundLuggagecheckbox, lostLuggagecheckbox, resolvedLuggagecheckbox;
@FXML private DatePicker datepicker1, datepicker2;
@FXML private Label userName, userIDLoggedInPerson;
private LuggageModel luggageModel;
private ImageView spinningIcon;
private StackPane iconPane;
private Chart currentChart;
private XYChart.Series<Date, Integer> foundSeries, lostSeries, resolvedSeries;
private XYChart.Series<String, Integer> foundBarSeries, lostBarSeries, resolvedBarSeries;
private InfoController infoController;
private final Timer timer;
public ManagerController() {
// Set view
registerFXML("gui/manager_linediagram.fxml");
// lineDiagram.setAnimated(false);
// lineDiagram.setCreateSymbols(true);
//
// barDiagram.setAnimated(false);
EmployeeModel employeeModel = EmployeeModel.getDefault();
userIDLoggedInPerson.setText(""+employeeModel.currentEmployee.id);
userName.setText(employeeModel.currentEmployee.firstName + " " + employeeModel.currentEmployee.lastName);
luggageModel = LuggageModel.getDefault();
foundLuggagecheckbox.setOnAction(this::filterHandle);
lostLuggagecheckbox.setOnAction(this::filterHandle);
resolvedLuggagecheckbox.setOnAction(this::filterHandle);
logoutButton.setOnAction(this::logoutHandler);
view.fxmlPane.setOnKeyReleased(this::keypressHandler);
helpButton.setOnAction(this::helpHandler);
printstatisticsButton.setOnAction(this::printStatisticsHandler);
lineDiagrambutton.setOnAction(this::lineDiagramHandler);
barDiagrambutton.setOnAction(this::barDiagramHandler);
changePasswordButton.setOnAction(this::changePassword);
refreshButton.setOnAction(this::refreshHandler);
currentChart = lineDiagram;
// TODO: Set the datePicker1 to something else
datepicker1.setValue(LocalDate.of(1970, 1, 1));
datepicker1.setOnAction(this::datePickerHandler);
// Set the datePicker2 to today date
datepicker2.setValue(LocalDate.now());
datepicker2.setOnAction(this::datePickerHandler);
// Show a spinning icon to indicate to the user that we are getting the tableData
showLoadingIcon();
// Initialize the Series, give them a name and give them a color
// foundSeries = new XYChart.Series<>();
// foundSeries.setName("Found");
//
// foundBarSeries = new XYChart.Series<>();
// foundBarSeries.setName("Found");
//
// lostSeries = new XYChart.Series<>();
// lostSeries.setName("Lost");
//
// lostBarSeries = new XYChart.Series<>();
// lostBarSeries.setName("Lost");
//
// resolvedSeries = new XYChart.Series<>();
// resolvedSeries.setName("Resolved");
//
// resolvedBarSeries = new XYChart.Series<>();
// resolvedBarSeries.setName("Resolved");
// Create a timer with a certain interval, every time it ticks refresh the entire to receive new data
timer = new Timer(Config.DATA_REFRESH_INTERVAL, (e) -> refreshHandler(null));
timer.start();
// Tell the stylesheet that there should be an image on the button
refreshButton.setId("button_refresh");
// Make a new thread that will recieve the tableData from the database
Thread dataThread = new Thread(this::receiveData);
dataThread.setDaemon(true); // If for some reason the program quits, let the threads get destroyed with the main thread
dataThread.start();
}
// When the datepickers received a action event
private void datePickerHandler(ActionEvent event) {
// Log.display(event.getSource());
// DatePicker source = (DatePicker)event.getSource();
// Simulate the refresh button
refreshHandler(null);
}
// Fired when lineDiagrambutton is clicked
private void lineDiagramHandler(ActionEvent e) {
lineDiagrambutton.setDisable(true);
barDiagrambutton.setDisable(false);
lineDiagram.setVisible(true);
barDiagram.setVisible(false);
currentChart = lineDiagram;
}
// Fired when barDiagrambutton is clicked
private void barDiagramHandler(ActionEvent e) {
lineDiagrambutton.setDisable(false);
barDiagrambutton.setDisable(true);
lineDiagram.setVisible(false);
barDiagram.setVisible(true);
currentChart = barDiagram;
}
private void filterHandle(ActionEvent e) {
boolean found = foundLuggagecheckbox.isSelected();
boolean lost = lostLuggagecheckbox.isSelected();
boolean resolved = resolvedLuggagecheckbox.isSelected();
showLuggage(found, lost, resolved);
}
private void showLuggage(boolean found, boolean lost, boolean resolved) {
ObservableList<XYChart.Series> lineSeries = lineDiagram.getData();
ObservableList<XYChart.Series> barSeries = barDiagram.getData();
if (found) {
// If the chartSeries is not in the chart, add it
if (!lineSeries.contains(foundSeries)) {
lineSeries.add(foundSeries);
}
if(!barSeries.contains(foundBarSeries)) {
barSeries.add(foundBarSeries);
}
} else {
lineDiagram.getData().remove(foundSeries);
barDiagram.getData().remove(foundBarSeries);
}
if (lost) {
// If the chartSeries is not in the chart, add it
if (!lineSeries.contains(lostSeries)) {
lineSeries.add(lostSeries);
}
if(!barSeries.contains(lostBarSeries)) {
barSeries.add(lostBarSeries);
}
} else {
lineDiagram.getData().remove(lostSeries);
barDiagram.getData().remove(lostBarSeries);
}
if (resolved) {
// If the chartSeries is not in the chart, add it
if (!lineSeries.contains(resolvedSeries)) {
lineSeries.add(resolvedSeries);
}
if(!barSeries.contains(resolvedBarSeries)) {
barSeries.add(resolvedBarSeries);
}
} else {
lineDiagram.getData().remove(resolvedSeries);
barDiagram.getData().remove(resolvedBarSeries);
}
}
private void receiveData() {
// The Date object is in milliseconds, toEpochDay() is in seconds
Date date1 = DateUtil.localDateToDate(datepicker1.getValue());
Date date2 = DateUtil.localDateToDate(datepicker2.getValue());
// The first date will stay on 0:00:00, the second date will be set to the end of the day 23:59:59
date2 = DateUtil.getEndOfDTheDay(date2);
receiveData(date1, date2);
}
/**
* This function will be mosly called in a different thread, so the user can still interact with the scene
* Supply a start and end date.
* @param start
* @param end
*/
private void receiveData(Date start, Date end) {
// Disable the input until we have added everything to the UI
// We need to run this in the JavaFX thread
Platform.runLater(()->{
datepicker1.setDisable(true);
datepicker2.setDisable(true);
foundLuggagecheckbox.setDisable(true);
lostLuggagecheckbox.setDisable(true);
resolvedLuggagecheckbox.setDisable(true);
});
long startTimeStamp = DateUtil.dateToTimestamp(start);
long endTimeStamp = DateUtil.dateToTimestamp(end);
StatusModel status = StatusModel.getDefault();
Status foundStatus = status.getStatus("Found");
Status lostStatus = status.getStatus("Lost");
Status resolvedStatus = status.getStatus("Resolved");
List<ChartData> foundDates = new ArrayList<>();
List<ChartData> lostDates = new ArrayList<>();
List<ChartData> resolvedDates = new ArrayList<>();
// If the date is not set, add it to the list.
// If the date is set, add one to the count
List<Luggage> luggages = luggageModel.getAllLuggage();
Platform.runLater(()->{
// Clear the old table data
lineDiagram.getData().clear();
barDiagram.getData().clear();
});
// There is a bug with the XYChart. Series where clearing sometimes doesn't work.
// Because this is sometimes, it can't be trusted. So create a new series
// Initialize the Series, give them a name and give them a color
foundSeries = new XYChart.Series<>();
foundSeries.setName("Found");
foundBarSeries = new XYChart.Series<>();
foundBarSeries.setName("Found");
lostSeries = new XYChart.Series<>();
lostSeries.setName("Lost");
lostBarSeries = new XYChart.Series<>();
lostBarSeries.setName("Lost");
resolvedSeries = new XYChart.Series<>();
resolvedSeries.setName("Resolved");
resolvedBarSeries = new XYChart.Series<>();
resolvedBarSeries.setName("Resolved");
for (Luggage luggage : luggages) {
long uxt = luggage.createDate; // Unix time stamp
// If the unix timestamp is outside of the given period
// Continue to the next luggage
if(!(uxt >= startTimeStamp && uxt <= endTimeStamp)) {
continue;
}
String statusName = luggage.status.getName();
ChartData contains = null;
// If the luggage status is Found
if(statusName.equals(foundStatus.getName())) {
for (ChartData d : foundDates) {
// Format the unix timestamps
String date1 = DateUtil.formatDate("MMM yy", uxt);
String date2 = DateUtil.formatDate("MMM yy", d.timestamp);
// If the first date matches the second one, the date is already in our array
// Increment it with 1 later on
if (date1.equals(date2)) {
contains = d;
break;
}
}
}
// If the luggage status is Lost
if (statusName.equals(lostStatus.getName())) {
for (ChartData d : lostDates) {
// Format the unix timestamps
String date1 = DateUtil.formatDate("MMM yy", uxt);
String date2 = DateUtil.formatDate("MMM yy", d.timestamp);
// If the first date matches the second one, the date is already in our array
// Increment it with 1 later on
if (date1.equals(date2)) {
contains = d;
break;
}
}
}
// If the luggage status is Resolved
if (statusName.equals(resolvedStatus.getName())) {
for (ChartData d : resolvedDates) {
// Format the unix timestamps
String date1 = DateUtil.formatDate("MMM yy", uxt);
String date2 = DateUtil.formatDate("MMM yy", d.timestamp);
// If the first date matches the second one, the date is already in our array
// Increment it with 1 later on
if (date1.equals(date2)) {
contains = d;
break;
}
}
}
if (contains == null) {
// If we didn't find a match, create one
if (foundStatus.getName().equals(statusName)) {
foundDates.add(new ChartData(uxt, 1));
} else if (lostStatus.getName().equals(statusName)) {
lostDates.add(new ChartData(uxt, 1));
} else if (resolvedStatus.getName().equals(statusName)) {
resolvedDates.add(new ChartData(uxt, 1));
}
} else {
// We found a match, increment the count
contains.count++;
}
}
// The line diagram and bar diagram both need different data.
// The line diagram works with a Date.
// The bar diagram works with a String
for (ChartData data : foundDates) {
int count = data.count;
// Create a new Date object from the unix timestamp
// The Date object works with milliseconds, so we need to convert the unix timestamp to milliseconds
Date date = DateUtil.timestampToDate(data.timestamp);
XYChart.Data<Date, Integer> pointData = new XYChart.Data<>(date, count);
// Format the date
String dateFormat = DateUtil.formatDate("MMM yy", data.timestamp);
XYChart.Data<String, Integer> barData = new XYChart.Data<>(dateFormat, count);
// Be sure to use the javafx thread
Platform.runLater(()->{
foundSeries.getData().add(pointData); // Add it to an array created for Found luggage in the Line Diagram
foundBarSeries.getData().add(barData); // Add it to an array created for Found luggage in the Bar Diagramar Diagram
});
}
for (ChartData data : lostDates) {
int count = data.count;
// Create a new Date object from the unix timestamp
// The Date object works with milliseconds, so we need to convert the unix timestamp to milliseconds
Date date = DateUtil.timestampToDate(data.timestamp);
XYChart.Data<Date, Integer> pointData = new XYChart.Data<>(date, count);
// Format the date
String dateFormat = DateUtil.formatDate("MMM yy", data.timestamp);
XYChart.Data<String, Integer> barData = new XYChart.Data<>(dateFormat, count);
// Be sure to use the javafx thread
Platform.runLater(()->{
lostSeries.getData().add(pointData); // Add it to an array created for Lost luggage in the Line Diagram
lostBarSeries.getData().add(barData); // Add it to an array created for Lost luggage in the Bar Diagram
});
}
for (ChartData data : resolvedDates) {
int count = data.count;
// Create a new Date object from the unix timestamp
// The Date object works with milliseconds, so we need to convert the unix timestamp to milliseconds
Date date = DateUtil.timestampToDate(data.timestamp);
XYChart.Data<Date, Integer> pointData = new XYChart.Data<>(date, count);
// Format the date
String dateFormat = DateUtil.formatDate("MMM yy", data.timestamp);
XYChart.Data<String, Integer> barData = new XYChart.Data<>(dateFormat, count);
// Be sure to use the javafx thread
Platform.runLater(()->{
resolvedSeries.getData().add(pointData); // Add it to an array created for Resolved luggage in the Line Diagram
resolvedBarSeries.getData().add(barData); // Add it to an array created for Resolved luggage in the Bar Diagram
});
}
// Notify the javafx thread to run this next command
Platform.runLater(() -> {
// Enable every filter component
datepicker1.setDisable(false);
datepicker2.setDisable(false);
foundLuggagecheckbox.setDisable(false);
lostLuggagecheckbox.setDisable(false);
resolvedLuggagecheckbox.setDisable(false);
// Enable the button, remove the loading icon
refreshButton.setDisable(false);
refreshButton.setId("button_refresh");
// Add the series to the diagram
lineDiagram.getData().addAll(foundSeries, lostSeries, resolvedSeries);
barDiagram.getData().addAll(foundBarSeries, lostBarSeries, resolvedBarSeries);
// Remove the spinning icon
view.fxmlPane.getChildren().remove(iconPane);
});
}
private void showLoadingIcon() {
// Show a spinning icon to indicate to the IMAGE_USER that we are getting the tableData
spinningIcon = new ImageView("img/loader.gif");
iconPane = new StackPane();
iconPane.setPickOnBounds(false); // Needed to click trough transparent panes
iconPane.getChildren().add(spinningIcon);
view.fxmlPane.getChildren().add(iconPane);
}
private void refreshHandler(ActionEvent e) {
Platform.runLater(() -> {
refreshButton.setDisable(true);
refreshButton.setId("button_refresh_animate");
});
Thread dataThread = new Thread(this::receiveData);
dataThread.setDaemon(true);
dataThread.start();
}
private void changePassword(ActionEvent e) {
addController(new ChangePasswordController());
}
private void logoutHandler(ActionEvent e) {
changeController(new LoginController());
}
private void printStatisticsHandler(ActionEvent e) {
//SAVE FILE WITH FILECHOOSER
FileChooser fileChooser = new FileChooser();
fileChooser.setTitle("Select location to save PDF.");
fileChooser.getExtensionFilters().addAll(
new FileChooser.ExtensionFilter("PDF", "*.pdf"));
File pdf = fileChooser.showSaveDialog(view.getScene().getWindow());
if (pdf != null) {
ManagerStatisticsPDF.generateManagerReportPDF(pdf, currentChart);
System.out.println("PDF OF MANAGER REPORT SAVED");
}
}
/**
* Open F1 InfoWindow
* @param e
*/
private void keypressHandler(KeyEvent e) {
//opens helpfunction with the f1 key
if(e.getEventType() == KeyEvent.KEY_RELEASED) {
if (e.getCode() == KeyCode.F1) {
// If it's already openend, close it
if (infoController == null) {
openHelp();
} else {
removeController(infoController);
infoController = null;
}
}
}
}
private void helpHandler(ActionEvent e) {
if(infoController == null) {
openHelp();
}
//opens help function
}
private void openHelp() {
infoController = new InfoController("Manager information", IO.get("help/manager.htm").toString());
infoController.setControllerDeleteHandler((obj)->{
removeController(infoController);
infoController = null;
});
addController(infoController);
}
}