From 98fb68ad14513f36835745f55074b1c14e3e5d0c Mon Sep 17 00:00:00 2001 From: Jonathan Bobel Date: Tue, 8 Oct 2024 16:05:00 -0400 Subject: [PATCH 1/3] Updated to table captions and hiding the legend on the dashboard if it's empty --- app/assets/javascripts/activityChart.js | 380 ++++++++---------- .../views/activity/all-activity.html | 7 +- app/templates/views/dashboard/dashboard.html | 4 +- 3 files changed, 174 insertions(+), 217 deletions(-) diff --git a/app/assets/javascripts/activityChart.js b/app/assets/javascripts/activityChart.js index 19d8bcc49..368067d46 100644 --- a/app/assets/javascripts/activityChart.js +++ b/app/assets/javascripts/activityChart.js @@ -33,36 +33,57 @@ .attr('id', 'tooltip') .style('display', 'none'); } - // Create legend + + // Check if there is any data in deliveredData or failedData before creating the legend + const totalMessages = d3.sum(deliveredData) + d3.sum(failedData); + const legendContainer = d3.select('.chart-legend'); - legendContainer.selectAll('*').remove(); // Clear any existing legend - const legendData = [ - { label: 'Delivered', color: COLORS.delivered }, - { label: 'Failed', color: COLORS.failed } - ]; + if (totalMessages === 0) { + legendContainer.style('display', 'none'); // Try manually setting this in the console to see if it hides the legend + console.log('Hiding legend'); // Ensure this branch is being hit + } else { + legendContainer.style('display', 'flex'); + console.log('Showing legend'); + } - const legendItem = legendContainer.selectAll('.legend-item') - .data(legendData) - .enter() - .append('div') - .attr('class', 'legend-item'); + if (totalMessages > 0) { + // Create legend only if there is data + legendContainer.selectAll('*').remove(); // Clear any existing legend - legendItem.append('div') - .attr('class', 'legend-rect') - .style('background-color', d => d.color) - .style('display', 'inline-block') - .style('margin-right', '5px'); + const legendData = [ + { label: 'Delivered', color: COLORS.delivered }, + { label: 'Failed', color: COLORS.failed } + ]; - legendItem.append('span') - .attr('class', 'legend-label') - .text(d => d.label); + const legendItem = legendContainer.selectAll('.legend-item') + .data(legendData) + .enter() + .append('div') + .attr('class', 'legend-item'); + legendItem.append('div') + .attr('class', 'legend-rect') + .style('background-color', d => d.color) + .style('display', 'inline-block') + .style('margin-right', '5px'); + + legendItem.append('span') + .attr('class', 'legend-label') + .text(d => d.label); + + legendContainer.style('display', 'flex'); // Ensure the legend is shown + } else { + // Hide legend container if there is no data + legendContainer.style('display', 'none'); // Hide the legend + } + + // Proceed with creating the chart as usual const x = d3.scaleBand() .domain(labels) .range([0, width]) .padding(0.1); - // Adjust the y-axis domain to add some space above the tallest bar + const maxY = d3.max(deliveredData.map((d, i) => d + (failedData[i] || 0))); const y = d3.scaleSqrt() .domain([0, maxY + 2]) // Add 2 units of space at the top @@ -74,23 +95,20 @@ .attr('transform', `translate(0,${height})`) .call(d3.axisBottom(x)); - // Generate the y-axis with whole numbers const yAxis = d3.axisLeft(y) - .ticks(Math.min(maxY + 2, 10)) // Generate up to 10 ticks based on the data - .tickFormat(d3.format('d')); // Ensure whole numbers on the y-axis + .ticks(Math.min(maxY + 2, 10)) + .tickFormat(d3.format('d')); svg.append('g') .attr('class', 'y axis') .call(yAxis); - // Data for stacking const stackData = labels.map((label, i) => ({ label: label, delivered: deliveredData[i], failed: failedData[i] || 0 // Ensure there's a value for failed, even if it's 0 })); - // Stack the data const stack = d3.stack() .keys(['delivered', 'failed']) .order(d3.stackOrderNone) @@ -98,203 +116,137 @@ const series = stack(stackData); - // Color scale const color = d3.scaleOrdinal() .domain(['delivered', 'failed']) .range([COLORS.delivered, COLORS.failed]); - // Create bars with animation - const barGroups = svg.selectAll('.bar-group') - .data(series) - .enter() - .append('g') - .attr('class', 'bar-group') - .attr('fill', d => color(d.key)); + const barGroups = svg.selectAll('.bar-group') + .data(series) + .enter() + .append('g') + .attr('class', 'bar-group') + .attr('fill', d => color(d.key)); - barGroups.selectAll('rect') - .data(d => d) - .enter() - .append('rect') - .attr('x', d => x(d.data.label)) - .attr('y', height) - .attr('height', 0) - .attr('width', x.bandwidth()) - .on('mouseover', function(event, d) { - const key = d3.select(this.parentNode).datum().key; - const capitalizedKey = key.charAt(0).toUpperCase() + key.slice(1); - tooltip.style('display', 'block') - .html(`${d.data.label}
${capitalizedKey}: ${d.data[key]}`); - }) - .on('mousemove', function(event) { - tooltip.style('left', `${event.pageX + 10}px`) - .style('top', `${event.pageY - 20}px`); - }) - .on('mouseout', function() { - tooltip.style('display', 'none'); - }) - .transition() - .duration(1000) - .attr('y', d => y(d[1])) - .attr('height', d => y(d[0]) - y(d[1])); - }; + barGroups.selectAll('rect') + .data(d => d) + .enter() + .append('rect') + .attr('x', d => x(d.data.label)) + .attr('y', height) + .attr('height', 0) + .attr('width', x.bandwidth()) + .on('mouseover', function(event, d) { + const key = d3.select(this.parentNode).datum().key; + const capitalizedKey = key.charAt(0).toUpperCase() + key.slice(1); + tooltip.style('display', 'block') + .html(`${d.data.label}
${capitalizedKey}: ${d.data[key]}`); + }) + .on('mousemove', function(event) { + tooltip.style('left', `${event.pageX + 10}px`) + .style('top', `${event.pageY - 20}px`); + }) + .on('mouseout', function() { + tooltip.style('display', 'none'); + }) + .transition() + .duration(1000) + .attr('y', d => y(d[1])) + .attr('height', d => y(d[0]) - y(d[1])); + }; - // Function to create an accessible table - const createTable = function(tableId, chartType, labels, deliveredData, failedData) { - const table = document.getElementById(tableId); - table.innerHTML = ""; // Clear previous data - - const captionText = document.querySelector(`#${chartType} .chart-subtitle`).textContent; - const caption = document.createElement('caption'); - caption.textContent = captionText; - const thead = document.createElement('thead'); - const tbody = document.createElement('tbody'); - - // Create table header - const headerRow = document.createElement('tr'); - const headers = ['Day', 'Delivered', 'Failed']; - headers.forEach(headerText => { - const th = document.createElement('th'); - th.textContent = headerText; - headerRow.appendChild(th); - }); - thead.appendChild(headerRow); - - // Create table body - labels.forEach((label, index) => { - const row = document.createElement('tr'); - const cellDay = document.createElement('td'); - cellDay.textContent = label; - row.appendChild(cellDay); - - const cellDelivered = document.createElement('td'); - cellDelivered.textContent = deliveredData[index]; - row.appendChild(cellDelivered); - - const cellFailed = document.createElement('td'); - cellFailed.textContent = failedData[index]; - row.appendChild(cellFailed); - - tbody.appendChild(row); - }); - - table.appendChild(caption); - table.appendChild(thead); - table.append(tbody); - }; - - const fetchData = function(type) { - var ctx = document.getElementById('weeklyChart'); - if (!ctx) { - return; - } - - var url = type === 'service' ? `/daily_stats.json` : `/daily_stats_by_user.json`; - return fetch(url) - .then(response => { - if (!response.ok) { - throw new Error('Network response was not ok'); - } - return response.json(); - }) - .then(data => { - labels = []; - deliveredData = []; - failedData = []; - - let totalMessages = 0; - - for (var dateString in data) { - if (data.hasOwnProperty(dateString)) { - const dateParts = dateString.split('-'); - const formattedDate = `${dateParts[1]}/${dateParts[2]}/${dateParts[0].slice(2)}`; - - labels.push(formattedDate); - deliveredData.push(data[dateString].sms.delivered); - failedData.push(data[dateString].sms.failure); - - // Calculate the total number of messages - totalMessages += data[dateString].sms.delivered + data[dateString].sms.failure; - } - } - - // Check if there are no messages sent - const subTitle = document.querySelector(`#activityChartContainer .chart-subtitle`); - if (totalMessages === 0) { - // Remove existing chart and render the alert message - d3.select('#weeklyChart').selectAll('*').remove(); - d3.select('#weeklyChart') - .append('div') - .html(` -
-
-

- No messages sent in the last 7 days -

-
-
- `); - // Hide the subtitle - if (subTitle) { - subTitle.style.display = 'none'; - } - } else { - // If there are messages, create the chart and table - createChart('#weeklyChart', labels, deliveredData, failedData); - createTable('weeklyTable', 'activityChart', labels, deliveredData, failedData); - } - - return data; - }) - .catch(error => console.error('Error fetching daily stats:', error)); - }; - - const handleDropdownChange = function(event) { - const selectedValue = event.target.value; - const subTitle = document.querySelector(`#activityChartContainer .chart-subtitle`); - const selectElement = document.getElementById('options'); - const selectedText = selectElement.options[selectElement.selectedIndex].text; - - subTitle.textContent = `${selectedText} - last 7 days`; - fetchData(selectedValue); - - // Update ARIA live region - const liveRegion = document.getElementById('aria-live-account'); - liveRegion.textContent = `Data updated for ${selectedText} - last 7 days`; - - // Switch tables based on dropdown selection - const selectedTable = selectedValue === "individual" ? "table1" : "table2"; - const tables = document.querySelectorAll('.table-overflow-x-auto'); - tables.forEach(function(table) { - table.classList.add('hidden'); // Hide all tables by adding the hidden class - table.classList.remove('visible'); // Ensure they are not visible - }); - const tableToShow = document.getElementById(selectedTable); - tableToShow.classList.remove('hidden'); // Remove hidden class - tableToShow.classList.add('visible'); // Add visible class - }; - - document.addEventListener('DOMContentLoaded', function() { - // Initialize activityChart chart and table with service data by default - fetchData('service'); - - // Add event listener to the dropdown - const dropdown = document.getElementById('options'); - dropdown.addEventListener('change', handleDropdownChange); - }); - - // Resize chart on window resize - window.addEventListener('resize', function() { - if (labels.length > 0 && deliveredData.length > 0 && failedData.length > 0) { - createChart('#weeklyChart', labels, deliveredData, failedData); - createTable('weeklyTable', 'activityChart', labels, deliveredData, failedData); + const fetchData = function(type) { + const ctx = document.getElementById('weeklyChart'); + if (!ctx) { + return; } - }); - // Export functions for testing - window.createChart = createChart; - window.createTable = createTable; - window.handleDropdownChange = handleDropdownChange; - window.fetchData = fetchData; + const url = type === 'service' ? `/daily_stats.json` : `/daily_stats_by_user.json`; + return fetch(url) + .then(response => { + if (!response.ok) { + throw new Error('Network response was not ok'); + } + return response.json(); + }) + .then(data => { + + let labels = []; + let deliveredData = []; + let failedData = []; + + let totalMessages = 0; + + for (var dateString in data) { + if (data.hasOwnProperty(dateString)) { + const dateParts = dateString.split('-'); + const formattedDate = `${dateParts[1]}/${dateParts[2]}/${dateParts[0].slice(2)}`; + + labels.push(formattedDate); + deliveredData.push(data[dateString].sms.delivered || 0); // Fallback to 0 if missing + failedData.push(data[dateString].sms.failure || 0); // Fallback to 0 if missing + + totalMessages += data[dateString].sms.delivered + data[dateString].sms.failure; + } + } + + + const subTitle = document.querySelector(`#activityChartContainer .chart-subtitle`); + const liveRegion = document.getElementById('aria-live-account'); + + if (totalMessages === 0) { + d3.select('#weeklyChart').selectAll('*').remove(); + d3.select('#weeklyChart') + .append('div') + .html(` +
+
+

+ No messages sent in the last 7 days +

+
+
+ `); + if (subTitle) { + subTitle.style.display = 'none'; + } + liveRegion.textContent = `No data available for ${type} - last 7 days.`; + + // Here: Check if the legend exists and remove it if necessary + const legendContainer = document.querySelector('.chart-legend'); + if (legendContainer) { + console.log('Legend exists, hiding it...'); + legendContainer.style.display = 'none'; // Hide the legend + } else { + console.log('Legend does not exist at this point.'); + } + + } else { + createChart('#weeklyChart', labels, deliveredData, failedData); + liveRegion.textContent = `Data updated for ${type} - last 7 days.`; + + // Check if legend should be shown after chart is created + const legendContainer = document.querySelector('.chart-legend'); + if (legendContainer) { + console.log('Legend exists, showing it...'); + legendContainer.style.display = 'flex'; // Show the legend + } else { + console.log('Legend does not exist at this point.'); + } + } + + return data; + }) + .catch(error => console.error('Error fetching daily stats:', error)); + }; + + document.addEventListener('DOMContentLoaded', function() { + fetchData('service'); + + const dropdown = document.getElementById('options'); + dropdown.addEventListener('change', function(event) { + fetchData(event.target.value); + }); + }); } })(window); diff --git a/app/templates/views/activity/all-activity.html b/app/templates/views/activity/all-activity.html index 1067bc07b..b925a7802 100644 --- a/app/templates/views/activity/all-activity.html +++ b/app/templates/views/activity/all-activity.html @@ -60,7 +60,7 @@

Sent jobs

- +
Table showing all sent jobs for this service
@@ -103,7 +103,10 @@ {{ job.created_by.name }} {% if job.time_left != "Data no longer available" %} - File Download Icon + + + Download report link + {% else %} N/A {% endif %} diff --git a/app/templates/views/dashboard/dashboard.html b/app/templates/views/dashboard/dashboard.html index 6c554d515..41cba8ff2 100644 --- a/app/templates/views/dashboard/dashboard.html +++ b/app/templates/views/dashboard/dashboard.html @@ -38,7 +38,7 @@
@@ -60,6 +60,7 @@