class UsersViewController extends TimeCardsViewController {
constructor(element) {
super(element);
this.userCells = { };
//We store the project association rows in here for further management.
//The association rows are the same for all users.
this.projectRows = { };
this.tariffFields = { };
this.associationCheckboxes = { };
TimeCards.dataManager.addDataListener("project", null, this.onProjectUpdate.bind(this));
TimeCards.dataManager.addDataListener("user", null, this.onUserUpdate.bind(this));
this.deleteUserDialog = new Dialog("Are you sure you want to delete this employee?
They will no longer be able to log in. The recorded time periods will not be deleted.", this.onDeleteDialogReturned.bind(this), Dialog.BUTTONS_DESTRUCTIVE);
this.resetPasswordDialog = new Dialog("Are you sure you want to reset the password for this employee?
They will receive an email with a new temporary password and will be prompted to set a new password upon next login.", this.onResetPasswordDialogReturned.bind(this), {
buttons: {
cancel: {
title: "Cancel"
},
ok: {
title: "Reset Password",
type: "default"
}
}
});
}
viewDidLoad() {
this.usersTableController.addEventListener("cellselected", this.onCellSelected.bind(this));
this.newUserCell = new TableViewCell();
this.newUserCell.titleLabel.innerText = "New Employee";
//Give the report view a reference to the view controller.
this.reportsViewController.usersViewController = this;
}
onUserUpdate(userId, user, sortedBefore) {
if (!user) {
//Handle the deleted user.
if (!(userId in this.userCells)) {
console.warn("A user deletion event has occurred, but the deleted user is not in the list of users of the UsersViewController.");
return;
}
var userCell = this.userCells[userId];
this.usersTableController.removeCell(userCell);
delete this.userCells[userId];
}
else if (!(userId in this.userCells)) {
//Handle the added user.
var userCell = new UserCell();
userCell.setData(user);
this.usersTableController.addCell(userCell, (user.is_session_user ? 0 : 1), sortedBefore ? this.userCells[sortedBefore] : null);
this.userCells[userId] = userCell;
if (this.waitingForUserCreation) {
this.waitingForUserCreation = false;
this.usersTableController.selectCell(userCell);
}
}
else {
//Handle the changed user.
var userCell = this.userCells[userId];
userCell.setData(user);
if (this.selectedUser && userId == this.selectedUser.user_id) {
//Apply the new user data if the updated user is currently selected.
this.setUserData(user);
}
//Re-add the cell to move it to the appropriate position in case the name has changed.
this.usersTableController.addCell(userCell, (user.is_session_user ? 0 : 1), sortedBefore ? this.userCells[sortedBefore] : null);
}
//Store the last assigned projects of the session user.
//We do this to detect projects that have been removed from the session user.
//Removed projects will be reported to the data manager.
//However, projects that the session user owns are not removed.
if (user && user.is_session_user && user.projects) {
if (this.lastAssignedProjectsForSessionUser) {
//Go through the previously assigned projects and find ones that are no longer present in the new user object.
for (var projectId in this.lastAssignedProjectsForSessionUser) {
if (!(projectId in user.projects) && this.allProjects[projectId].id_user != user.user_id) {
//Tell the data manager to remove this project locally.
TimeCards.dataManager.deleteLocally("project", projectId);
}
}
}
this.lastAssignedProjectsForSessionUser = user.projects;
}
}
onCellSelected(event) {
if (!event.cell) {
this.userSettingsSheet.style.display = "none";
this.selectedUser = null;
}
else {
//Hide the new user cell if another cell was selected.
if (this.isNewUser && event.cell != this.newUserCell) {
this.onDeleteButtonPressed(event);
}
this.setUserData(event.cell.user);
this.userSettingsSheet.style.display = "";
}
}
onFieldChanged(event) {
event.stopPropagation();
this.save();
}
onFormSubmitted(event) {
event.preventDefault();
event.stopPropagation();
this.save();
}
save() {
if (!this.editForm.reportValidity()) {
return;
}
//Force username field to be lowercase.
this.usernameField.value = this.usernameField.value.toLowerCase();
var user = {
full_name: this.fullNameField.value,
username: this.usernameField.value,
id_role: this.roleSelect.value,
default_tariff: this.defaultTariffField.value,
projects: this.gatherProjectAssociations()
};
if (this.isNewUser) {
if (!this.waitingForUserCreation) {
user.email = this.emailField.value;
TimeCards.dataManager.store("user", user);
this.waitingForUserCreation = true;
}
}
else {
user.email = this.emailField.value;
TimeCards.dataManager.store("user", this.selectedUser.user_id, user);
}
//Update the project tariff field placeholders.
for (var projectId in this.tariffFields) {
this.tariffFields[projectId].placeholder = user.default_tariff;
}
}
/**
* Returns an array of all the projects currently selected in the projects table with their tariffs (or null when none is specified).
*/
gatherProjectAssociations() {
var checkboxes = this.projectsTableBody.querySelectorAll("input[type=\"checkbox\"]");
var projects = { };
for (var i = 0; i < checkboxes.length; i++) {
var checkbox = checkboxes[i];
if (checkbox.checked) {
var tariff = this.tariffFields[checkbox.name].value;
projects[checkbox.name] = tariff == "" ? null : tariff;
}
}
return projects;
}
setUserData(user) {
this.selectedUser = user;
if (document.activeElement != this.fullNameField) {
this.fullNameField.value = user.full_name;
}
if (document.activeElement != this.usernameField) {
this.usernameField.value = user.username;
}
if (document.activeElement != this.emailField) {
this.emailField.value = user.email;
}
if (document.activeElement != this.roleSelect) {
this.roleSelect.value = user.id_role;
}
if (document.activeElement != this.defaultTariffField) {
this.defaultTariffField.value = user.default_tariff;
}
//Apply the correct permission classes depending on whether this is the current user or not.
this.fullNameField.classList.remove("available_w__user");
this.fullNameField.classList.remove("available_w__user_samerole");
this.fullNameField.classList.remove("available_w__user_all");
this.usernameField.classList.remove("available_w__user");
this.usernameField.classList.remove("available_w__user_samerole");
this.usernameField.classList.remove("available_w__user_all");
this.emailField.classList.remove("available_w__user");
this.emailField.classList.remove("available_w__user_samerole");
this.emailField.classList.remove("available_w__user_all");
this.roleSelect.classList.remove("available_w__user");
this.roleSelect.classList.remove("available_w__user_samerole");
this.roleSelect.classList.remove("available_w__user_all");
this.defaultTariffField.classList.remove("available_w__user");
this.defaultTariffField.classList.remove("available_w__user_samerole");
this.defaultTariffField.classList.remove("available_w__user_all");
if (user.is_session_user) {
this.fullNameField.classList.add("available_w__user");
this.usernameField.classList.add("available_w__user");
this.emailField.classList.add("available_w__user");
this.roleSelect.classList.add("available_w__user");
this.defaultTariffField.classList.add("available_w__user");
}
else if (user.id_role == Authentication.currentUser.id_role) {
this.fullNameField.classList.add("available_w__user_samerole");
this.usernameField.classList.add("available_w__user_samerole");
this.emailField.classList.add("available_w__user_samerole");
this.roleSelect.classList.add("available_w__user_samerole");
this.defaultTariffField.classList.add("available_w__user_samerole");
}
else {
this.fullNameField.classList.add("available_w__user_all");
this.usernameField.classList.add("available_w__user_all");
this.emailField.classList.add("available_w__user_all");
this.roleSelect.classList.add("available_w__user_all");
this.defaultTariffField.classList.add("available_w__user_all");
}
//Show or hide change and reset password buttons depending on session user or not.
this.resetPasswordButton.parentNode.style.display = user.is_session_user ? "none" : "";
this.changePasswordButton.parentNode.style.display = user.is_session_user ? "" : "none";
//Set the project association checkbox and tariff field values.
//Set all default tariff placeholders for the project associations.
for (var projectId in this.tariffFields) {
//The project is checked if the project is actually assigned or if the displaying user owns that project.
var checked = (projectId in user.projects) || this.allProjects[projectId].id_user == user.user_id;
this.setProjectAssociation(projectId, (projectId in user.projects) ? user.projects[projectId] : null, checked);
this.tariffFields[projectId].placeholder = this.selectedUser.default_tariff;
}
//Hide the check all checkbox if it is the session user that is being displayed.
this.assignAllBox.style.display = (user.is_session_user ? "none" : "");
this.updateCheckAllCheckbox();
//Show or hide the own user notification and prevent the editing of the own user and projects settings.
this.ownRoleInformation.style.display = user.user_id == Authentication.currentUser.user_id ? "" : "none";
this.deleteButton.disabled = user.user_id == Authentication.currentUser.user_id;
this.ownUserDeletionInformation.style.display = user.user_id == Authentication.currentUser.user_id ? "" : "none";
this.roleSelect.disabled = user.user_id == Authentication.currentUser.user_id;
//TODO: Find out why this line exists and document it.
this.updatePermissions();
}
setProjectAssociation(projectId, tariff = null, checked = false) {
var associationRow = this.projectRows[projectId];
//Update the tariff field value.
var tariffField = associationRow.querySelector("#project-tariff-field__" + projectId);
if (document.activeElement != tariffField) {
tariffField.value = tariff;
}
tariffField.disabled = !checked;
//Update the owner label.
this.updateOwnerLabel(projectId);
this.associationCheckboxes[projectId].checked = checked;
//Hide the checkbox if it is the session user that is being displayed or if the project is owned by the displayed user.
this.associationCheckboxes[projectId].style.display = ((this.selectedUser.is_session_user || this.allProjects[projectId].id_user == this.selectedUser.user_id) ? "none" : "");
}
/**
* Updates the owner label for the given project ID's association row.
*/
updateOwnerLabel(projectId) {
var owner = TimeCards.dataManager.getEntity("user", this.allProjects[projectId].id_user);
var ownerCell = this.projectRows[projectId].querySelector(".project-owner-cell");
ownerCell.innerText = !owner ? "" : (owner.is_session_user ? "Own project" : "Owned by " + owner.full_name); //TODO: Localize this.
}
/**
* Updates the state of the check all checkbox.
*/
updateCheckAllCheckbox() {
//Iterate over all rows and check the checkbox.
var checkedCount = 0;
var projectCount = Object.keys(this.associationCheckboxes).length;
for (var projectId in this.associationCheckboxes) {
checkedCount += this.associationCheckboxes[projectId].checked;
}
this.checkAllCheckbox.indeterminate = checkedCount > 0 && checkedCount < projectCount;
this.checkAllCheckbox.checked = checkedCount == projectCount;
}
/**
* Called when a association checkbox has been changed.
*/
onAssociationCheckboxChanged(event) {
//Forward to the general changed event and thereby save the user information.
this.onFieldChanged(event);
}
/**
* Called when the assign all checkbox has been changed.
*/
onCheckAllCheckboxChanged(event) {
//Check or uncheck all checkboxes.
for (var projectId in this.associationCheckboxes) {
this.associationCheckboxes[projectId].checked = this.checkAllCheckbox.checked;
}
//Save the changes.
this.onFieldChanged(event);
}
onDeleteButtonPressed(event) {
if (this.isNewUser) {
this.isNewUser = false;
this.usersTableController.removeCell(this.newUserCell);
}
else {
this.deleteUserDialog.show();
}
}
onDeleteDialogReturned(buttonId) {
if (buttonId == "delete") {
TimeCards.dataManager.store("user", this.selectedUser.user_id, null);
}
}
onCreateButtonPressed(event) {
this.isNewUser = true;
this.newUserCell.user = {
full_name: "",
email: "",
username: "",
id_role: null,
default_tariff: 0,
projects: { }
};
this.usersTableController.addCell(this.newUserCell, 1);
this.usersTableController.selectCell(this.newUserCell);
this.fullNameField.focus();
}
onResetPasswordButtonPressed(event) {
this.resetPasswordDialog.show();
}
onResetPasswordDialogReturned(buttonId) {
if (buttonId != "ok") {
return;
}
//Reset password by setting it to null.
TimeCards.dataManager.store("user", this.selectedUser.user_id, {
password: null
}, function () {
new Dialog("Password reset. An email with a temporary password has been sent to " + this.selectedUser.email + ".", null, Dialog.BUTTONS_OK_ONLY).show();
}.bind(this));
}
onChangePasswordButtonPressed(event) {
var changePasswordViewController = UIKit.getViewControllerById("change-password-view-controller");
this.presentModalViewController(changePasswordViewController);
}
onProjectUpdate(projectId, project, sortedBefore) {
if (!project) {
//Handle the deleted project.
if (!(projectId in this.projectRows)) {
return;
}
this.projectsTableBody.removeChild(this.projectRows[projectId]);
delete this.projectRows[projectId];
delete this.tariffFields[projectId];
delete this.associationCheckboxes[projectId];
}
else if (!(projectId in this.projectRows)) {
//Handle the added project.
//We store the projects in a local data object because we need the project data at several locations within this class.
if (!this.allProjects) {
this.allProjects = { };
}
this.allProjects[projectId] = project;
//Create the new row for the association table.
var associationRow = document.createElement("tr");
associationRow.id = "project-association__" + projectId;
//The checkbox cell.
var projectCheckboxCell = document.createElement("td");
associationRow.appendChild(projectCheckboxCell);
//The checkbox.
var projectCheckbox = document.createElement("input");
projectCheckbox.type = "checkbox";
if (this.selectedUser) {
projectCheckbox.checked = (projectId in this.selectedUser.projects);
}
projectCheckbox.name = projectId;
projectCheckbox.className = "available_w__project_user";
projectCheckbox.addEventListener("change", this.onAssociationCheckboxChanged.bind(this));
projectCheckboxCell.appendChild(projectCheckbox);
this.associationCheckboxes[projectId] = projectCheckbox;
//The left cell.
var projectNameCell = document.createElement("td");
projectNameCell.innerText = project.name;
projectNameCell.style.paddingLeft = "0.25em";
projectNameCell.className = "project-name-cell";
associationRow.appendChild(projectNameCell);
//The center cell.
var centerCell = document.createElement("td");
centerCell.className = "project-owner-cell require__multiuser";
associationRow.appendChild(centerCell);
//The right cell.
var rightCell = document.createElement("td");
rightCell.style.textAlign = "right";
associationRow.appendChild(rightCell);
var tariffField = document.createElement("input");
tariffField.type = "number";
if (this.createUser && (projectId in this.selectedUser.projects)) {
tariffField.value = this.selectedUser.projects[projectId];
}
tariffField.id = "project-tariff-field__" + projectId;
if (this.selectedUser) {
tariffField.placeholder = this.selectedUser.default_tariff;
}
tariffField.className = "tariff-field available__multiuser require__project_user_tariff available_w__project_user_tariff";
tariffField.addEventListener("change", this.onFieldChanged.bind(this));
Validation.addAttributes(tariffField, "project_user", "tariff");
rightCell.appendChild(tariffField);
this.tariffFields[projectId] = tariffField;
var perHourLabel = document.createElement("label");
perHourLabel.setAttribute("for", "project-tariff-field__" + projectId);
perHourLabel.innerText = "/ h";
perHourLabel.className = "require__project_user_tariff";
rightCell.appendChild(perHourLabel);
this.projectsTableBody.insertBefore(associationRow, sortedBefore ? this.projectRows[sortedBefore] : null);
this.projectRows[projectId] = associationRow;
}
else {
this.allProjects[projectId] = project;
//Handle the changed project.
this.projectRows[projectId].querySelector(".project-name-cell").innerText = project.name;
//Decide upon the placement of the changed association row. This is because we want the sorting to stay integer when the name changes.
this.projectsTableBody.insertBefore(this.projectRows[projectId], sortedBefore ? this.projectRows[sortedBefore] : null);
//Update the owner label.
this.updateOwnerLabel(projectId);
}
}
}
UIKit.registerViewControllerType(UsersViewController);