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);