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 = $(
'
' +
- '' +
- '
' +
+ '' +
+ '
' +
'
'
);
@@ -112,32 +143,32 @@ function getAbsoluteUrl(url) {
if (orgSettings.length > 0) {
const table = $(
'' +
- '' +
- '' +
- '' + gettext('Notification Type') + ' | ' +
- '' +
- '' +
- '
' +
- '' +
- '' +
+ '' +
+ '' +
+ '' + gettext('Notification Type') + ' | ' +
+ '' +
+ '' +
+ ' | ' +
+ '' +
+ '' +
+ ' | ' +
+ '
' +
+ '' +
+ '' +
'
'
);
@@ -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 = `
-
+
@@ -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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Email
+
Enable or Disable all email notifications globally
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{% 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(