diff --git a/openwisp_notifications/static/openwisp-notifications/css/preferences.css b/openwisp_notifications/static/openwisp-notifications/css/preferences.css index 982cda1e..ce5f990a 100644 --- a/openwisp_notifications/static/openwisp-notifications/css/preferences.css +++ b/openwisp_notifications/static/openwisp-notifications/css/preferences.css @@ -3,23 +3,128 @@ display: none; } .global-settings-container { - display: inline-flex; - align-items: center; - justify-content: center; - gap: 8px; + display: flex; + border: 1px solid #e0e0e0; + border-radius: 8px; +} +.icon { + min-width: 24px; + min-height: 24px; + padding-right: 6px; +} +.icon-web { + background: url("../../openwisp-notifications/images/icons/icon-web.svg") + 0 0 no-repeat; +} +.icon-email { + background: url("../../openwisp-notifications/images/icons/icon-email.svg") + 0 0 no-repeat; +} +.global-setting-text h2 { + margin: 0 0 5px 0; +} +.global-setting-text p { + color: #666; +} +.global-setting-divider { + width: 1px; + background-color: #e0e0e0; +} +.global-setting-dropdown { + position: relative; +} +.global-setting-dropdown-toggle .mg-arrow { + display: block; +} +.global-setting-dropdown-toggle { + display: flex; + padding: 10px 15px; + background-color: #f5f5f5; + border: 1px solid #e0e0e0; + border-radius: 4px; + cursor: pointer; +} +.global-setting-dropdown-menu { + display: none; + position: absolute; + background-color: #fff; + border: 1px solid #e0e0e0; + border-radius: 4px; + box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1); + padding: 0; + margin: 0; +} +.global-setting-dropdown-menu li { + padding: 10px 15px; + cursor: pointer; +} +.global-setting-dropdown-menu-open { + display: block; +} +.global-setting { + flex: 1; + padding: 20px; } .global-settings-container { - margin-left: 20px; + width: 840px; +} +.global-setting-content { + display: flex; + margin-bottom: 10px; +} +.global-setting-content h2 { + color: #555; +} +.modal { + display: none; + position: fixed; + z-index: 9999; + left: 0; + top: 0; + width: 100%; + height: 100%; + overflow: auto; + background-color: rgba(0, 0, 0, 0.4); +} +.modal-content { + background-color: #fff; + margin: 15% auto; + padding: 20px; + border: 1px solid #888; + width: 400px; + border-radius: 5px; +} +.modal-header { + margin-bottom: 20px; +} +.modal-buttons { + display: flex; + gap: 10px; + margin-top: 20px; +} +#go-back, +#confirm { + width: 100%; } .module h2 { padding: 15px 10px; cursor: pointer; display: flex; - justify-content: space-between; + justify-content: space-around; align-items: center; font-weight: bold; font-size: 14px; text-transform: uppercase; + padding: 6px; +} +.toggle-header { + border: none !important; +} +.org-name { + width: 40%; +} +.email-row { + position: relative; } .org-content { margin-top: 0; @@ -32,8 +137,8 @@ table { width: 100%; } -th:not(:last-child), -td:not(:last-child) { +table:not(.toggle-header) th:not(:last-child), +table:not(.toggle-header) td:not(:last-child) { border-right: 1px solid #ddd; } th:not(:first-child), @@ -58,6 +163,9 @@ td:not(:first-child) { z-index: 9999; cursor: pointer; } +.toast .icon { + background-repeat: no-repeat; +} .toast .progress-bar { position: absolute; bottom: 0; @@ -68,6 +176,9 @@ td:not(:first-child) { transition: width 3s linear; } span.toggle-icon { + position: absolute; + right: 15px; + top: 15px; width: 16px; height: 16px; margin-right: 5px; @@ -157,7 +268,8 @@ input:checked + .slider:before { -ms-transform: translateX(20px); transform: translateX(20px); } -.notification-web-header, .notification-email-header { +.notification-web-header, +.notification-email-header { text-align: center; } .notification-header-container { @@ -175,3 +287,6 @@ input:checked + .slider:before { .module { border: 1px solid rgba(0, 0, 0, 0.1); } +ul > li { + list-style-type: none !important; +} diff --git a/openwisp_notifications/static/openwisp-notifications/images/icons/icon-email.svg b/openwisp_notifications/static/openwisp-notifications/images/icons/icon-email.svg new file mode 100644 index 00000000..a1816b7c --- /dev/null +++ b/openwisp_notifications/static/openwisp-notifications/images/icons/icon-email.svg @@ -0,0 +1,3 @@ + + + diff --git a/openwisp_notifications/static/openwisp-notifications/images/icons/icon-web.svg b/openwisp_notifications/static/openwisp-notifications/images/icons/icon-web.svg new file mode 100644 index 00000000..212c6ae1 --- /dev/null +++ b/openwisp_notifications/static/openwisp-notifications/images/icons/icon-web.svg @@ -0,0 +1,3 @@ + + + diff --git a/openwisp_notifications/static/openwisp-notifications/js/preferences.js b/openwisp_notifications/static/openwisp-notifications/js/preferences.js index 13906d87..c1a9cc89 100644 --- a/openwisp_notifications/static/openwisp-notifications/js/preferences.js +++ b/openwisp_notifications/static/openwisp-notifications/js/preferences.js @@ -15,6 +15,7 @@ function getAbsoluteUrl(url) { $(document).ready(function () { const userId = $('.settings-container').data('user-id'); fetchNotificationSettings(userId); + initializeGlobalSettings(userId); }); function fetchNotificationSettings(userId) { @@ -62,10 +63,7 @@ function getAbsoluteUrl(url) { const isGlobalEmailChecked = globalSetting.email; globalSettingId = globalSetting.id; - $('#global-web').prop('checked', isGlobalWebChecked); - $('#global-email').prop('checked', isGlobalEmailChecked); - - initializeGlobalSettingsEventListener(userId); + initializeGlobalDropdowns(isGlobalWebChecked, isGlobalEmailChecked); } else { showToast('error', gettext('Global settings not found.')); } @@ -78,6 +76,26 @@ function getAbsoluteUrl(url) { $('.global-settings').show(); } + function initializeGlobalDropdowns(isGlobalWebChecked, isGlobalEmailChecked) { + // Initialize Web dropdown + const webDropdown = document.querySelector('.global-setting-dropdown[data-web-state]'); + const webToggle = webDropdown.querySelector('.global-setting-dropdown-toggle'); + const webState = isGlobalWebChecked ? 'on' : 'off'; + + // Update toggle's data-state and button text + webToggle.setAttribute('data-state', webState); + webToggle.innerHTML = (isGlobalWebChecked ? 'Notify on Web' : 'Don\'t Notify on Web') + ' ' + createArrowSpanHtml(); + + // Initialize Email dropdown + const emailDropdown = document.querySelector('.global-setting-dropdown[data-email-state]'); + const emailToggle = emailDropdown.querySelector('.global-setting-dropdown-toggle'); + const emailState = isGlobalEmailChecked ? 'on' : 'off'; + + // Update toggle's data-state and button text + emailToggle.setAttribute('data-state', emailState); + emailToggle.innerHTML = (isGlobalEmailChecked ? 'Notify by Email' : 'Don\'t Notify by Email') + ' ' + createArrowSpanHtml(); + } + function groupBy(array, key) { return array.reduce((result, currentValue) => { (result[currentValue[key]] = result[currentValue[key]] || []).push(currentValue); @@ -101,10 +119,23 @@ function getAbsoluteUrl(url) { const orgName = orgSettings[0].organization_name; + // Calculate counts + const totalNotifications = orgSettings.length; + const enabledWebNotifications = orgSettings.filter(setting => setting.web).length; + const enabledEmailNotifications = orgSettings.filter(setting => setting.email).length; + const orgPanel = $( '
' + - '

' + `${gettext('Organization')}: ${orgName}` + '

' + - '
' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '

' + `${gettext('Organization')}: ${orgName}` + '

' + gettext('Web') + ' ' + enabledWebNotifications + '/' + totalNotifications + '

' + + '
' + '
' ); @@ -112,32 +143,32 @@ function getAbsoluteUrl(url) { if (orgSettings.length > 0) { const table = $( '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + '
' + gettext('Notification Type') + '' + - '
' + - '' + gettext('Web') + '' + - '?' + - '' + - '
' + - '
' + - '
' + - '' + gettext('Email') + '' + - '?' + - '' + - '
' + - '
' + gettext('Notification Type') + '' + + '
' + + '' + gettext('Web') + '' + + '?' + + '' + + '
' + + '
' + + '
' + + '' + gettext('Email') + '' + + '?' + + '' + + '
' + + '
' ); @@ -145,19 +176,19 @@ function getAbsoluteUrl(url) { orgSettings.forEach(function(setting, settingIndex) { const row = $( '' + - '' + setting.type_label + '' + - '' + - '' + - '' + - '' + - '' + - '' + + '' + setting.type_label + '' + + '' + + '' + + '' + + '' + + '' + + '' + '' ); table.find('tbody').append(row); @@ -171,8 +202,8 @@ function getAbsoluteUrl(url) { orgPanelsContainer.append(orgPanel); - // Expand the first organization by default - if (orgIndex === 0) { + // Expand the first organization if there is only one organization + if (orgIndex === 0 && orgSettings.length === 1) { orgContent.addClass('active'); orgPanel.find('.toggle-icon').removeClass('collapsed').addClass('expanded'); } else { @@ -181,12 +212,18 @@ function getAbsoluteUrl(url) { }); } - // Update the org level checkboxes based on individual checkbox states in the table + // Update the org level checkboxes function updateMainCheckboxes(table) { table.find('.main-checkbox').each(function () { const column = $(this).data('column'); - const allChecked = table.find('.' + column + '-checkbox').length === table.find('.' + column + '-checkbox:checked').length; + const totalCheckboxes = table.find('.' + column + '-checkbox').length; + const checkedCheckboxes = table.find('.' + column + '-checkbox:checked').length; + const allChecked = totalCheckboxes === checkedCheckboxes; $(this).prop('checked', allChecked); + + // Update counts in the header + const headerSpan = table.find('.notification-' + column + '-header .notification-header-container span').first(); + headerSpan.text((column === 'web' ? gettext('Web') : gettext('Email')) + ' ' + checkedCheckboxes + '/' + totalCheckboxes); }); } @@ -329,6 +366,7 @@ function getAbsoluteUrl(url) { } updateMainCheckboxes(table); + updateOrgLevelCheckboxes(orgId); $.ajax({ type: 'POST', @@ -358,109 +396,361 @@ function getAbsoluteUrl(url) { }); } - // Update individual setting checkboxes at the organization level + // Update individual setting checkboxes and counts at the organization level function updateOrgLevelCheckboxes(organizationId) { - const webCheckboxes = $('.web-checkbox[data-organization-id="' + organizationId + '"]'); - const emailCheckboxes = $('.email-checkbox[data-organization-id="' + organizationId + '"]'); - const webMainCheckbox = $('.main-checkbox[data-column="web"][data-organization-id="' + organizationId + '"]'); - const emailMainCheckbox = $('.main-checkbox[data-column="email"][data-organization-id="' + organizationId + '"]'); - webMainCheckbox.prop('checked', webCheckboxes.length === webCheckboxes.filter(':checked').length); - emailMainCheckbox.prop('checked', emailCheckboxes.length === emailCheckboxes.filter(':checked').length); + const table = $(`.main-checkbox[data-organization-id="${organizationId}"]`).closest('table'); + const webCheckboxes = table.find('.web-checkbox'); + const emailCheckboxes = table.find('.email-checkbox'); + const webMainCheckbox = table.find('.main-checkbox[data-column="web"]'); + const emailMainCheckbox = table.find('.main-checkbox[data-column="email"]'); + const totalWebCheckboxes = webCheckboxes.length; + const totalEmailCheckboxes = emailCheckboxes.length; + const checkedWebCheckboxes = webCheckboxes.filter(':checked').length; + const checkedEmailCheckboxes = emailCheckboxes.filter(':checked').length; + + webMainCheckbox.prop('checked', totalWebCheckboxes === checkedWebCheckboxes); + emailMainCheckbox.prop('checked', totalEmailCheckboxes === checkedEmailCheckboxes); + + // Update counts in the header + const orgModule = table.closest('.module'); + const webCountSpan = orgModule.find('.web-count'); + const emailCountSpan = orgModule.find('.email-count'); + webCountSpan.text(gettext('Web') + ' ' + checkedWebCheckboxes + '/' + totalWebCheckboxes); + emailCountSpan.text(gettext('Email') + ' ' + checkedEmailCheckboxes + '/' + totalEmailCheckboxes); } - // Initialize event listener for global settings - function initializeGlobalSettingsEventListener(userId) { - $('#global-email, #global-web').change(function (event) { + function initializeGlobalSettings(userId) { + var $dropdowns = $(".global-setting-dropdown"); + var $modal = $("#confirmation-modal"); + var $goBackBtn = $("#go-back"); + var $confirmBtn = $("#confirm"); + var activeDropdown = null; + var selectedOptionText = ""; + var selectedOptionElement = null; + var previousCheckboxStates = null; + + $dropdowns.each(function () { + var $dropdown = $(this); + var $toggle = $dropdown.find(".global-setting-dropdown-toggle"); + var $menu = $dropdown.find(".global-setting-dropdown-menu"); + + $toggle.on("click", function (e) { + e.stopPropagation(); + closeAllDropdowns(); + $menu.toggleClass("global-setting-dropdown-menu-open"); + adjustDropdownWidth($menu); + }); + + $menu.find("li").on("click", function () { + activeDropdown = $dropdown; + selectedOptionText = $(this).text().trim(); + selectedOptionElement = $(this); + updateModalContent(); // Update modal content before showing + $modal.show(); + }); + }); + + // Close all dropdowns when clicking outside + $(document).on("click", closeAllDropdowns); + + function closeAllDropdowns() { + $dropdowns.each(function () { + $(this) + .find(".global-setting-dropdown-menu") + .removeClass("global-setting-dropdown-menu-open"); + }); + } + + function adjustDropdownWidth($menu) { + var $toggle = $menu.prev(".global-setting-dropdown-toggle"); + var maxWidth = Math.max.apply( + null, + $menu + .find("li") + .map(function () { + return $(this).outerWidth(); + }) + .get() + ); + $menu.css( + "width", + Math.max($toggle.outerWidth(), maxWidth) + "px" + ); + } + + $goBackBtn.on("click", function () { + $modal.hide(); + }); + + $confirmBtn.on("click", function () { if (isUpdateInProgress) { return; } - const triggeredBy = $(event.target).attr('id'); + if (activeDropdown) { + var dropdownType = activeDropdown.is("[data-web-state]") ? "web" : "email"; + var triggeredBy = dropdownType; + + var $webDropdown = $('.global-setting-dropdown[data-web-state]'); + var $emailDropdown = $('.global-setting-dropdown[data-email-state]'); + var $webToggle = $webDropdown.find('.global-setting-dropdown-toggle'); + var $emailToggle = $emailDropdown.find('.global-setting-dropdown-toggle'); + + // Determine the current states + var isGlobalWebChecked = $webToggle.attr('data-state') === 'on'; + var isGlobalEmailChecked = $emailToggle.attr('data-state') === 'on'; + + // Store previous states for potential rollback + var previousGlobalWebChecked = isGlobalWebChecked; + var previousGlobalEmailChecked = isGlobalEmailChecked; + + previousCheckboxStates = { + mainWebChecked: $('.main-checkbox[data-column="web"]') + .map(function () { + return { + orgId: $(this).data("organization-id"), + checked: $(this).is(":checked"), + }; + }) + .get(), + mainEmailChecked: $('.main-checkbox[data-column="email"]') + .map(function () { + return { + orgId: $(this).data("organization-id"), + checked: $(this).is(":checked"), + }; + }) + .get(), + webChecked: $(".web-checkbox") + .map(function () { + return { + id: $(this).data("pk"), + orgId: $(this).data("organization-id"), + checked: $(this).is(":checked"), + }; + }) + .get(), + emailChecked: $(".email-checkbox") + .map(function () { + return { + id: $(this).data("pk"), + orgId: $(this).data("organization-id"), + checked: $(this).is(":checked"), + }; + }) + .get(), + }; + + // Update the state based on the selected option + if (dropdownType === "web") { + isGlobalWebChecked = selectedOptionText === "Notify on Web"; + } else if (dropdownType === "email") { + isGlobalEmailChecked = selectedOptionText === "Notify by Email"; + } - let isGlobalWebChecked = $('#global-web').is(':checked'); - let isGlobalEmailChecked = $('#global-email').is(':checked'); + // Email notifications require web notifications to be enabled + if (triggeredBy === "email" && isGlobalEmailChecked) { + isGlobalWebChecked = true; + } - // Store previous states for potential rollback - let previousGlobalWebChecked, previousGlobalEmailChecked; - if (triggeredBy === 'global-email') { - previousGlobalEmailChecked = !isGlobalEmailChecked; - previousGlobalWebChecked = isGlobalWebChecked; - } else { - previousGlobalWebChecked = !isGlobalWebChecked; - previousGlobalEmailChecked = isGlobalEmailChecked; + // Disabling web notifications also disables email notifications + if (triggeredBy === "web" && !isGlobalWebChecked) { + isGlobalEmailChecked = false; + } + + isUpdateInProgress = true; + + // Update the UI and data-state attributes + $webToggle + .html( + (isGlobalWebChecked ? "Notify on Web" : "Don't Notify on Web") + + " " + + createArrowSpanHtml() + ) + .attr("data-state", isGlobalWebChecked ? "on" : "off"); + $webDropdown.attr("data-web-state", isGlobalWebChecked ? "Yes" : "No"); + + $emailToggle + .html( + (isGlobalEmailChecked ? "Notify by Email" : "Don't Notify by Email") + + " " + + createArrowSpanHtml() + ) + .attr("data-state", isGlobalEmailChecked ? "on" : "off"); + $emailDropdown.attr("data-email-state", isGlobalEmailChecked ? "Yes" : "No"); + + // Update the checkboxes + $('.main-checkbox[data-column="web"]') + .prop("checked", isGlobalWebChecked) + .change(); + $(".web-checkbox").prop("checked", isGlobalWebChecked); + if ( + (dropdownType === "web" && !isGlobalWebChecked) || + dropdownType === "email" + ) { + $(".email-checkbox").prop("checked", isGlobalEmailChecked); + $('.main-checkbox[data-column="email"]') + .prop("checked", isGlobalEmailChecked) + .change(); + } + + var data = JSON.stringify({ + web: isGlobalWebChecked, + email: isGlobalEmailChecked, + }); + + $('.module').each(function () { + const organizationId = $(this).find('.main-checkbox').data('organization-id'); + updateOrgLevelCheckboxes(organizationId); + }); + + $.ajax({ + type: "PATCH", + url: getAbsoluteUrl( + `/api/v1/notifications/user/${userId}/user-setting/${globalSettingId}/` + ), + headers: { + "X-CSRFToken": $('input[name="csrfmiddlewaretoken"]').val(), + }, + contentType: "application/json", + data: data, + success: function () { + showToast( + "success", + gettext("Global settings updated successfully.") + ); + }, + error: function () { + showToast( + "error", + gettext("Something went wrong. Please try again.") + ); + + // Rollback the UI changes + isGlobalWebChecked = previousGlobalWebChecked; + isGlobalEmailChecked = previousGlobalEmailChecked; + + // Update the dropdown toggles and data-state attributes + $webToggle + .html( + (isGlobalWebChecked ? "Notify on Web" : "Don't Notify on Web") + + " " + + createArrowSpanHtml() + ) + .attr("data-state", isGlobalWebChecked ? "on" : "off"); + $webDropdown.attr("data-web-state", isGlobalWebChecked ? "Yes" : "No"); + + $emailToggle + .html( + (isGlobalEmailChecked ? "Notify by Email" : "Don't Notify by Email") + + " " + + createArrowSpanHtml() + ) + .attr("data-state", isGlobalEmailChecked ? "on" : "off"); + $emailDropdown.attr("data-email-state", isGlobalEmailChecked ? "Yes" : "No"); + + // Restore the checkboxes + previousCheckboxStates.mainWebChecked.forEach(function (item) { + $( + `.main-checkbox[data-organization-id="${item.orgId}"][data-column="web"]` + ).prop("checked", item.checked); + }); + previousCheckboxStates.mainEmailChecked.forEach(function (item) { + $( + `.main-checkbox[data-organization-id="${item.orgId}"][data-column="email"]` + ).prop("checked", item.checked); + }); + previousCheckboxStates.webChecked.forEach(function (item) { + $( + `.web-checkbox[data-organization-id="${item.orgId}"][data-pk="${item.id}"]` + ).prop("checked", item.checked); + }); + previousCheckboxStates.emailChecked.forEach(function (item) { + $( + `.email-checkbox[data-organization-id="${item.orgId}"][data-pk="${item.id}"]` + ).prop("checked", item.checked); + }); + + $('.module').each(function () { + const organizationId = $(this).find('.main-checkbox').data('organization-id'); + updateOrgLevelCheckboxes(organizationId); + }); + }, + complete: function () { + isUpdateInProgress = false; + }, + }); } + $modal.hide(); + }); - const previousCheckboxStates = { - mainWebChecked: $('.main-checkbox[data-column="web"]').map(function() { - return { orgId: $(this).data('organization-id'), checked: $(this).is(':checked') }; - }).get(), - mainEmailChecked: $('.main-checkbox[data-column="email"]').map(function() { - return { orgId: $(this).data('organization-id'), checked: $(this).is(':checked') }; - }).get(), - webChecked: $('.web-checkbox').map(function() { - return { id: $(this).data('pk'), orgId: $(this).data('organization-id'), checked: $(this).is(':checked') }; - }).get(), - emailChecked: $('.email-checkbox').map(function() { - return { id: $(this).data('pk'), orgId: $(this).data('organization-id'), checked: $(this).is(':checked') }; - }).get() - }; + // Update modal content dynamically + function updateModalContent() { + var $modalIcon = $modal.find('.modal-icon'); + var $modalHeader = $modal.find('.modal-header h2'); + var $modalMessage = $modal.find('.modal-message'); - // Email notifications require web notifications to be enabled - if (triggeredBy === 'global-email' && isGlobalEmailChecked) { - isGlobalWebChecked = true; + // Clear previous icon + $modalIcon.empty(); + + var dropdownType = activeDropdown.is("[data-web-state]") ? "web" : "email"; + + var newGlobalWebChecked = selectedOptionText === "Notify on Web"; + var newGlobalEmailChecked = selectedOptionText === "Notify by Email"; + + // Enabling email notifications requires web notifications to be enabled + if (newGlobalEmailChecked && !newGlobalWebChecked) { + newGlobalWebChecked = true; } // Disabling web notifications also disables email notifications - if (triggeredBy === 'global-web' && !isGlobalWebChecked) { - isGlobalEmailChecked = false; + if (!newGlobalWebChecked) { + newGlobalEmailChecked = false; } - isUpdateInProgress = true; - - // Update the UI - $('#global-web').prop('checked', isGlobalWebChecked); - $('#global-email').prop('checked', isGlobalEmailChecked); - - $('.main-checkbox[data-column="web"]').prop('checked', isGlobalWebChecked).change(); - $('.web-checkbox').prop('checked', isGlobalWebChecked); - if ((triggeredBy === 'global-web' && !isGlobalWebChecked) || triggeredBy === 'global-email') { - $('.email-checkbox').prop('checked', isGlobalEmailChecked); - $('.main-checkbox[data-column="email"]').prop('checked', isGlobalEmailChecked).change(); + // Message to show the settings that will be updated + var changes = []; + + // Case 1: Enabling global web notifications, email remains the same + var isOnlyEnablingWeb = + newGlobalWebChecked === true && + dropdownType === "web"; + + // Case 2: Disabling global email notifications, web remains the same + var isOnlyDisablingEmail = + newGlobalEmailChecked === false && + dropdownType === "email"; + + if (isOnlyEnablingWeb) { + // Only web notification is being enabled + changes.push('Web notifications will be enabled.'); + } else if (isOnlyDisablingEmail) { + // Only email notification is being disabled + changes.push('Email notifications will be disabled.'); + } else { + // For all other cases, display both settings + changes.push('Web notifications will be ' + (newGlobalWebChecked ? 'enabled' : 'disabled') + '.'); + changes.push('Email notifications will be ' + (newGlobalEmailChecked ? 'enabled' : 'disabled') + '.'); } - $.ajax({ - type: 'PATCH', - url: getAbsoluteUrl(`/api/v1/notifications/user/${userId}/user-setting/${globalSettingId}/`), - headers: { 'X-CSRFToken': $('input[name="csrfmiddlewaretoken"]').val() }, - contentType: 'application/json', - data: JSON.stringify({ web: isGlobalWebChecked, email: isGlobalEmailChecked }), - success: function () { - showToast('success', gettext('Global settings updated successfully.')); - }, - error: function () { - showToast('error', gettext('Something went wrong. Please try again.')); + // Set the modal icon + if (dropdownType === "web") { + $modalIcon.html('
'); + } else if (dropdownType === "email") { + $modalIcon.html('
'); + } - $('#global-web').prop('checked', previousGlobalWebChecked); - $('#global-email').prop('checked', previousGlobalEmailChecked); + // Update the modal header text + if (dropdownType === "web") { + $modalHeader.text('Apply Global Setting for Web'); + } else if (dropdownType === "email") { + $modalHeader.text('Apply Global Setting for Email'); + } - previousCheckboxStates.mainWebChecked.forEach(function(item) { - $(`.main-checkbox[data-organization-id="${item.orgId}"][data-column="web"]`).prop('checked', item.checked); - }); - previousCheckboxStates.mainEmailChecked.forEach(function(item) { - $(`.main-checkbox[data-organization-id="${item.orgId}"][data-column="email"]`).prop('checked', item.checked); - }); - previousCheckboxStates.webChecked.forEach(function(item) { - $(`.web-checkbox[data-organization-id="${item.orgId}"][data-pk="${item.id}"]`).prop('checked', item.checked); - }); - previousCheckboxStates.emailChecked.forEach(function(item) { - $(`.email-checkbox[data-organization-id="${item.orgId}"][data-pk="${item.id}"]`).prop('checked', item.checked); - }); - }, - complete: function () { - isUpdateInProgress = false; - } - }); - }); + // Update the modal message + var message = 'The following settings will be applied:
' + changes.join('
') + '
Do you want to continue?'; + $modalMessage.html(message); + } } function showToast(level, message) { @@ -472,7 +762,7 @@ function getAbsoluteUrl(url) { const toast = document.createElement('div'); toast.className = `toast ${level}`; toast.innerHTML = ` -
+
${message}
@@ -506,4 +796,8 @@ function getAbsoluteUrl(url) { } }); } + + function createArrowSpanHtml() { + return ''; + } })(django.jQuery); diff --git a/openwisp_notifications/templates/openwisp_notifications/preferences.html b/openwisp_notifications/templates/openwisp_notifications/preferences.html index f201d197..4170f4eb 100644 --- a/openwisp_notifications/templates/openwisp_notifications/preferences.html +++ b/openwisp_notifications/templates/openwisp_notifications/preferences.html @@ -24,31 +24,66 @@
{% endblock breadcrumbs %} - {% block content %}
- {% trans "Global Settings:" %} -
- {% trans "Web" %} - - ? -
+

Global Settings

- {% trans "Email" %} - - ? + +
+
+
+
+

Web

+

Enable or Disable all web notifications globally

+
+ +
    +
  • Notify on Web
  • +
  • Don't Notify on Web
  • +
+
+
+
+
+
+ +
+
+
+
+

Email

+

Enable or Disable all email notifications globally

+
+ +
    +
  • Notify by Email
  • +
  • Don't Notify by Email
  • +
+
+
+
+
+ + + {% endblock content %} {% block footer %} diff --git a/openwisp_notifications/tests/test_selenium.py b/openwisp_notifications/tests/test_selenium.py index 8d0d2548..fa2cf330 100644 --- a/openwisp_notifications/tests/test_selenium.py +++ b/openwisp_notifications/tests/test_selenium.py @@ -100,13 +100,43 @@ def test_notification_preference_page(self): self.login() self.open('/notifications/preferences/') + WebDriverWait(self.web_driver, 30).until( + EC.visibility_of_element_located( + (By.CLASS_NAME, 'global-settings-container') + ) + ) + # Uncheck the global web checkbox - global_web_label = WebDriverWait(self.web_driver, 30).until( + global_web_dropdown_toggle = WebDriverWait(self.web_driver, 30).until( EC.element_to_be_clickable( - (By.XPATH, "//*[@id='global-web']/parent::label") + ( + By.CSS_SELECTOR, + '.global-setting-dropdown[data-web-state] .global-setting-dropdown-toggle', + ) ) ) - global_web_label.click() + global_web_dropdown_toggle.click() + + global_web_dropdown_menu = WebDriverWait(self.web_driver, 10).until( + EC.visibility_of_element_located( + ( + By.CSS_SELECTOR, + '.global-setting-dropdown[data-web-state] .global-setting-dropdown-menu-open', + ) + ) + ) + + dont_notify_on_web_option = global_web_dropdown_menu.find_element( + By.XPATH, './/li[normalize-space()="Don\'t Notify on Web"]' + ) + dont_notify_on_web_option.click() + + confirmation_modal = WebDriverWait(self.web_driver, 10).until( + EC.visibility_of_element_located((By.ID, 'confirmation-modal')) + ) + + confirm_button = confirmation_modal.find_element(By.ID, 'confirm') + confirm_button.click() all_checkboxes = self.web_driver.find_elements( By.CSS_SELECTOR, 'input[type="checkbox"]' @@ -114,12 +144,19 @@ def test_notification_preference_page(self): for checkbox in all_checkboxes: self.assertFalse(checkbox.is_selected()) + # Expand the first organization panel if it's collapsed + first_org_toggle = WebDriverWait(self.web_driver, 10).until( + EC.element_to_be_clickable((By.CSS_SELECTOR, '.module .toggle-header')) + ) + first_org_toggle.click() + # Check the org-level web checkbox org_level_web_checkbox = WebDriverWait(self.web_driver, 10).until( - EC.visibility_of_element_located((By.ID, 'org-1-web')) + EC.element_to_be_clickable((By.ID, 'org-1-web')) ) org_level_web_checkbox.click() + # Verify that all web checkboxes under org-1 are selected web_checkboxes = self.web_driver.find_elements( By.CSS_SELECTOR, 'input[id^="org-1-web-"]' ) @@ -142,7 +179,7 @@ def test_empty_notification_preference_page(self): self.login() self.open('/notifications/preferences/') - no_organizations_element = WebDriverWait(self.web_driver, 10).until( + no_organizations_element = WebDriverWait(self.web_driver, 30).until( EC.visibility_of_element_located((By.CLASS_NAME, 'no-organizations')) ) self.assertEqual(