mirror of
https://github.com/Crazyco-xyz/48hr.email.git
synced 2026-01-09 11:19:36 +01:00
[Fix]: Fix stats page
This commit is contained in:
parent
d2d187d4d5
commit
0c8db0b597
3 changed files with 149 additions and 125 deletions
|
|
@ -6,6 +6,8 @@
|
||||||
// Store chart instance globally for updates
|
// Store chart instance globally for updates
|
||||||
let statsChart = null;
|
let statsChart = null;
|
||||||
let chartContext = null;
|
let chartContext = null;
|
||||||
|
let lastReloadTime = 0;
|
||||||
|
const RELOAD_COOLDOWN_MS = 2000; // 2 second cooldown between reloads
|
||||||
|
|
||||||
// Initialize stats chart if on stats page
|
// Initialize stats chart if on stats page
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
|
@ -14,7 +16,6 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
|
||||||
// Get data from global variables (set by template)
|
// Get data from global variables (set by template)
|
||||||
if (typeof window.initialStatsData === 'undefined') {
|
if (typeof window.initialStatsData === 'undefined') {
|
||||||
console.error('Initial stats data not found');
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -22,25 +23,16 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||||
const historicalData = window.historicalData || [];
|
const historicalData = window.historicalData || [];
|
||||||
const predictionData = window.predictionData || [];
|
const predictionData = window.predictionData || [];
|
||||||
|
|
||||||
console.log(`Loaded data: ${historicalData.length} historical, ${realtimeData.length} realtime, ${predictionData.length} predictions`);
|
// Set up Socket.IO connection for real-time updates with rate limiting
|
||||||
|
|
||||||
// If no data yet (lazy loading), add a placeholder message
|
|
||||||
const hasData = historicalData.length > 0 || realtimeData.length > 0 || predictionData.length > 0;
|
|
||||||
if (!hasData) {
|
|
||||||
console.log('No chart data yet - will populate after lazy load');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set up Socket.IO connection for real-time updates
|
|
||||||
if (typeof io !== 'undefined') {
|
if (typeof io !== 'undefined') {
|
||||||
const socket = io();
|
const socket = io();
|
||||||
|
|
||||||
socket.on('stats-update', () => {
|
socket.on('stats-update', () => {
|
||||||
console.log('Stats update received (page will not auto-reload)');
|
const now = Date.now();
|
||||||
// Don't auto-reload - user can manually refresh if needed
|
if (now - lastReloadTime >= RELOAD_COOLDOWN_MS) {
|
||||||
});
|
lastReloadTime = now;
|
||||||
|
reloadStatsData();
|
||||||
socket.on('reconnect', () => {
|
}
|
||||||
console.log('Reconnected to server');
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -257,7 +249,6 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||||
*/
|
*/
|
||||||
function rebuildStatsChart() {
|
function rebuildStatsChart() {
|
||||||
if (!statsChart || !chartContext) {
|
if (!statsChart || !chartContext) {
|
||||||
console.log('Chart not initialized, skipping rebuild');
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -265,8 +256,6 @@ function rebuildStatsChart() {
|
||||||
const historicalData = window.historicalData || [];
|
const historicalData = window.historicalData || [];
|
||||||
const predictionData = window.predictionData || [];
|
const predictionData = window.predictionData || [];
|
||||||
|
|
||||||
console.log(`Rebuilding chart with: ${historicalData.length} historical, ${realtimeData.length} realtime, ${predictionData.length} predictions`);
|
|
||||||
|
|
||||||
const allTimePoints = [
|
const allTimePoints = [
|
||||||
...historicalData.map(d => ({...d, type: 'historical' })),
|
...historicalData.map(d => ({...d, type: 'historical' })),
|
||||||
...realtimeData.map(d => ({...d, type: 'realtime' })),
|
...realtimeData.map(d => ({...d, type: 'realtime' })),
|
||||||
|
|
@ -274,7 +263,6 @@ function rebuildStatsChart() {
|
||||||
].sort((a, b) => a.timestamp - b.timestamp);
|
].sort((a, b) => a.timestamp - b.timestamp);
|
||||||
|
|
||||||
if (allTimePoints.length === 0) {
|
if (allTimePoints.length === 0) {
|
||||||
console.log('No data points to chart');
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -311,87 +299,120 @@ function lazyLoadStats() {
|
||||||
// Check if this is a lazy-loaded page (has placeholder data)
|
// Check if this is a lazy-loaded page (has placeholder data)
|
||||||
const currentCountEl = document.getElementById('currentCount');
|
const currentCountEl = document.getElementById('currentCount');
|
||||||
if (!currentCountEl) {
|
if (!currentCountEl) {
|
||||||
console.log('Stats lazy load: currentCount element not found');
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentText = currentCountEl.textContent.trim();
|
const currentText = currentCountEl.textContent.trim();
|
||||||
console.log('Stats lazy load: current count text is:', currentText);
|
|
||||||
|
|
||||||
if (currentText !== '...') {
|
if (currentText !== '...') {
|
||||||
console.log('Stats lazy load: already loaded with real data, skipping');
|
|
||||||
return; // Already loaded with real data
|
return; // Already loaded with real data
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('Stats lazy load: fetching data from /stats/api');
|
reloadStatsData();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reload statistics data from API and update DOM
|
||||||
|
*/
|
||||||
|
function reloadStatsData() {
|
||||||
fetch('/stats/api')
|
fetch('/stats/api')
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then(data => {
|
.then(data => {
|
||||||
console.log('Stats lazy load: received data', data);
|
updateStatsDOM(data);
|
||||||
// Update main stat cards
|
|
||||||
document.getElementById('currentCount').textContent = data.currentCount || '0';
|
|
||||||
document.getElementById('historicalTotal').textContent = data.allTimeTotal || '0';
|
|
||||||
document.getElementById('receives24h').textContent = (data.last24Hours && data.last24Hours.receives) || '0';
|
|
||||||
document.getElementById('deletes24h').textContent = (data.last24Hours && data.last24Hours.deletes) || '0';
|
|
||||||
document.getElementById('forwards24h').textContent = (data.last24Hours && data.last24Hours.forwards) || '0';
|
|
||||||
|
|
||||||
// Update enhanced stats if available
|
|
||||||
if (data.enhanced) {
|
|
||||||
const topSenderDomains = document.querySelector('[data-stats="top-sender-domains"]');
|
|
||||||
const topRecipientDomains = document.querySelector('[data-stats="top-recipient-domains"]');
|
|
||||||
|
|
||||||
if (topSenderDomains && data.enhanced.topSenderDomains && data.enhanced.topSenderDomains.length > 0) {
|
|
||||||
let html = '';
|
|
||||||
data.enhanced.topSenderDomains.slice(0, 5).forEach(item => {
|
|
||||||
html += `<li class="stat-list-item"><span class="stat-list-label">${item.domain}</span><span class="stat-list-value">${item.count}</span></li>`;
|
|
||||||
});
|
|
||||||
topSenderDomains.innerHTML = html;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (topRecipientDomains && data.enhanced.topRecipientDomains && data.enhanced.topRecipientDomains.length > 0) {
|
|
||||||
let html = '';
|
|
||||||
data.enhanced.topRecipientDomains.slice(0, 5).forEach(item => {
|
|
||||||
html += `<li class="stat-list-item"><span class="stat-list-label">${item.domain}</span><span class="stat-list-value">${item.count}</span></li>`;
|
|
||||||
});
|
|
||||||
topRecipientDomains.innerHTML = html;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update unique domains count
|
|
||||||
const uniqueSenderDomains = document.querySelector('[data-stats="unique-sender-domains"]');
|
|
||||||
if (uniqueSenderDomains && data.enhanced.uniqueSenderDomains) {
|
|
||||||
uniqueSenderDomains.textContent = data.enhanced.uniqueSenderDomains;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update average email size
|
|
||||||
const avgSize = document.querySelector('[data-stats="average-email-size"]');
|
|
||||||
if (avgSize && data.enhanced.averageEmailSize) {
|
|
||||||
avgSize.textContent = data.enhanced.averageEmailSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update peak hour
|
|
||||||
const peakHour = document.querySelector('[data-stats="peak-hour"]');
|
|
||||||
if (peakHour && data.enhanced.peakHour) {
|
|
||||||
peakHour.textContent = data.enhanced.peakHour;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update 24h prediction
|
|
||||||
const prediction24h = document.querySelector('[data-stats="prediction-24h"]');
|
|
||||||
if (prediction24h && data.enhanced.prediction24h) {
|
|
||||||
prediction24h.textContent = data.enhanced.prediction24h;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update window data for charts
|
|
||||||
window.initialStatsData = (data.last24Hours && data.last24Hours.timeline) || [];
|
|
||||||
window.historicalData = data.historical || [];
|
|
||||||
window.predictionData = data.prediction || [];
|
|
||||||
|
|
||||||
// Rebuild chart with new data
|
|
||||||
rebuildStatsChart();
|
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
console.error('Error loading stats:', error);
|
console.error('Error reloading stats:', error);
|
||||||
// Stats remain as placeholder
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update DOM with stats data
|
||||||
|
*/
|
||||||
|
function updateStatsDOM(data) {
|
||||||
|
// Update main stat cards
|
||||||
|
document.getElementById('currentCount').textContent = data.currentCount || '0';
|
||||||
|
document.getElementById('historicalTotal').textContent = data.allTimeTotal || '0';
|
||||||
|
document.getElementById('receives24h').textContent = (data.last24Hours && data.last24Hours.receives) || '0';
|
||||||
|
document.getElementById('deletes24h').textContent = (data.last24Hours && data.last24Hours.deletes) || '0';
|
||||||
|
document.getElementById('forwards24h').textContent = (data.last24Hours && data.last24Hours.forwards) || '0';
|
||||||
|
|
||||||
|
// Update enhanced stats if available
|
||||||
|
if (data.enhanced) {
|
||||||
|
const topSenderDomains = document.querySelector('[data-stats="top-sender-domains"]');
|
||||||
|
const topRecipientDomains = document.querySelector('[data-stats="top-recipient-domains"]');
|
||||||
|
const busiestHours = document.querySelector('[data-stats="busiest-hours"]');
|
||||||
|
if (topSenderDomains && data.enhanced.topSenderDomains && data.enhanced.topSenderDomains.length > 0) {
|
||||||
|
let html = '';
|
||||||
|
data.enhanced.topSenderDomains.slice(0, 5).forEach(item => {
|
||||||
|
html += `<li class="stat-list-item"><span class="stat-list-label">${item.domain}</span><span class="stat-list-value">${item.count}</span></li>`;
|
||||||
|
});
|
||||||
|
topSenderDomains.innerHTML = html;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (topRecipientDomains && data.enhanced.topRecipientDomains && data.enhanced.topRecipientDomains.length > 0) {
|
||||||
|
let html = '';
|
||||||
|
data.enhanced.topRecipientDomains.slice(0, 5).forEach(item => {
|
||||||
|
html += `<li class="stat-list-item"><span class="stat-list-label">${item.domain}</span><span class="stat-list-value">${item.count}</span></li>`;
|
||||||
|
});
|
||||||
|
topRecipientDomains.innerHTML = html;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (busiestHours && data.enhanced.busiestHours && data.enhanced.busiestHours.length > 0) {
|
||||||
|
let html = '';
|
||||||
|
data.enhanced.busiestHours.forEach(item => {
|
||||||
|
html += `<li class="stat-list-item"><span class="stat-list-label">${item.hour}:00 - ${item.hour + 1}:00</span><span class="stat-list-value">${item.count}</span></li>`;
|
||||||
|
});
|
||||||
|
busiestHours.innerHTML = html;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update unique domains count
|
||||||
|
const uniqueSenderDomains = document.querySelector('[data-stats="unique-sender-domains"]');
|
||||||
|
if (uniqueSenderDomains && data.enhanced.uniqueSenderDomains !== undefined) {
|
||||||
|
uniqueSenderDomains.textContent = data.enhanced.uniqueSenderDomains;
|
||||||
|
}
|
||||||
|
|
||||||
|
const uniqueRecipientDomains = document.querySelector('[data-stats="unique-recipient-domains"]');
|
||||||
|
if (uniqueRecipientDomains && data.enhanced.uniqueRecipientDomains !== undefined) {
|
||||||
|
uniqueRecipientDomains.textContent = data.enhanced.uniqueRecipientDomains;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update Quick Insights values
|
||||||
|
const avgSubjectLength = document.querySelector('[data-stats="average-subject-length"]');
|
||||||
|
if (avgSubjectLength && data.enhanced.averageSubjectLength !== undefined) {
|
||||||
|
avgSubjectLength.textContent = data.enhanced.averageSubjectLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
const uniqueSenderDomainsValue = document.querySelector('[data-stats="unique-sender-domains-value"]');
|
||||||
|
if (uniqueSenderDomainsValue && data.enhanced.uniqueSenderDomains !== undefined) {
|
||||||
|
uniqueSenderDomainsValue.textContent = data.enhanced.uniqueSenderDomains;
|
||||||
|
}
|
||||||
|
|
||||||
|
const uniqueRecipientDomainsValue = document.querySelector('[data-stats="unique-recipient-domains-value"]');
|
||||||
|
if (uniqueRecipientDomainsValue && data.enhanced.uniqueRecipientDomains !== undefined) {
|
||||||
|
uniqueRecipientDomainsValue.textContent = data.enhanced.uniqueRecipientDomains;
|
||||||
|
}
|
||||||
|
|
||||||
|
const peakHourPercentage = document.querySelector('[data-stats="peak-hour-percentage"]');
|
||||||
|
if (peakHourPercentage && data.enhanced.peakHourPercentage !== undefined) {
|
||||||
|
peakHourPercentage.textContent = data.enhanced.peakHourPercentage + '%';
|
||||||
|
}
|
||||||
|
|
||||||
|
const emailsPerHour = document.querySelector('[data-stats="emails-per-hour"]');
|
||||||
|
if (emailsPerHour && data.enhanced.emailsPerHour !== undefined) {
|
||||||
|
emailsPerHour.textContent = data.enhanced.emailsPerHour;
|
||||||
|
}
|
||||||
|
|
||||||
|
const dayPercentage = document.querySelector('[data-stats="day-percentage"]');
|
||||||
|
if (dayPercentage && data.enhanced.dayPercentage !== undefined) {
|
||||||
|
dayPercentage.textContent = data.enhanced.dayPercentage + '%';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update window data for charts
|
||||||
|
window.initialStatsData = (data.last24Hours && data.last24Hours.timeline) || [];
|
||||||
|
window.historicalData = data.historical || [];
|
||||||
|
window.predictionData = data.prediction || [];
|
||||||
|
|
||||||
|
// Rebuild chart with new data
|
||||||
|
rebuildStatsChart();
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -34,10 +34,13 @@ router.get('/', async(req, res) => {
|
||||||
enhanced: {
|
enhanced: {
|
||||||
topSenderDomains: [],
|
topSenderDomains: [],
|
||||||
topRecipientDomains: [],
|
topRecipientDomains: [],
|
||||||
|
busiestHours: [],
|
||||||
uniqueSenderDomains: '...',
|
uniqueSenderDomains: '...',
|
||||||
averageEmailSize: '...',
|
uniqueRecipientDomains: '...',
|
||||||
peakHour: '...',
|
averageSubjectLength: '...',
|
||||||
prediction24h: '...'
|
peakHourPercentage: '...',
|
||||||
|
emailsPerHour: '...',
|
||||||
|
dayPercentage: '...'
|
||||||
},
|
},
|
||||||
historical: [],
|
historical: [],
|
||||||
prediction: []
|
prediction: []
|
||||||
|
|
|
||||||
|
|
@ -96,19 +96,19 @@
|
||||||
<h3 class="section-header-small">
|
<h3 class="section-header-small">
|
||||||
Top Sender Domains
|
Top Sender Domains
|
||||||
</h3>
|
</h3>
|
||||||
|
<ul class="stat-list" data-stats="top-sender-domains">
|
||||||
{% if stats.enhanced.topSenderDomains|length > 0 %}
|
{% if stats.enhanced.topSenderDomains|length > 0 %}
|
||||||
<ul class="stat-list" data-stats="top-sender-domains">
|
{% for item in stats.enhanced.topSenderDomains|slice(0, 5) %}
|
||||||
{% for item in stats.enhanced.topSenderDomains|slice(0, 5) %}
|
<li class="stat-list-item">
|
||||||
<li class="stat-list-item">
|
<span class="stat-list-label">{{ item.domain }}</span>
|
||||||
<span class="stat-list-label">{{ item.domain }}</span>
|
<span class="stat-list-value">{{ item.count }}</span>
|
||||||
<span class="stat-list-value">{{ item.count }}</span>
|
</li>
|
||||||
</li>
|
{% endfor %}
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
<p class="stat-footer"><span data-stats="unique-sender-domains">{{ stats.enhanced.uniqueSenderDomains }}</span> unique domains</p>
|
|
||||||
{% else %}
|
{% else %}
|
||||||
<p class="stat-empty">No data yet</p>
|
<li class="stat-empty">Loading...</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
</ul>
|
||||||
|
<p class="stat-footer"><span data-stats="unique-sender-domains">{{ stats.enhanced.uniqueSenderDomains }}</span> unique domains</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Top Recipient Domains -->
|
<!-- Top Recipient Domains -->
|
||||||
|
|
@ -116,19 +116,19 @@
|
||||||
<h3 class="section-header-small">
|
<h3 class="section-header-small">
|
||||||
Top Recipient Domains
|
Top Recipient Domains
|
||||||
</h3>
|
</h3>
|
||||||
|
<ul class="stat-list" data-stats="top-recipient-domains">
|
||||||
{% if stats.enhanced.topRecipientDomains|length > 0 %}
|
{% if stats.enhanced.topRecipientDomains|length > 0 %}
|
||||||
<ul class="stat-list" data-stats="top-recipient-domains">
|
{% for item in stats.enhanced.topRecipientDomains|slice(0, 5) %}
|
||||||
{% for item in stats.enhanced.topRecipientDomains|slice(0, 5) %}
|
<li class="stat-list-item">
|
||||||
<li class="stat-list-item">
|
<span class="stat-list-label">{{ item.domain }}</span>
|
||||||
<span class="stat-list-label">{{ item.domain }}</span>
|
<span class="stat-list-value">{{ item.count }}</span>
|
||||||
<span class="stat-list-value">{{ item.count }}</span>
|
</li>
|
||||||
</li>
|
{% endfor %}
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
<p class="stat-footer"><span data-stats="unique-recipient-domains">{{ stats.enhanced.uniqueRecipientDomains }}</span> unique domains</p>
|
|
||||||
{% else %}
|
{% else %}
|
||||||
<p class="stat-empty">No data yet</p>
|
<li class="stat-empty">Loading...</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
</ul>
|
||||||
|
<p class="stat-footer"><span data-stats="unique-recipient-domains">{{ stats.enhanced.uniqueRecipientDomains }}</span> unique domains</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Busiest Hours -->
|
<!-- Busiest Hours -->
|
||||||
|
|
@ -136,18 +136,18 @@
|
||||||
<h3 class="section-header-small">
|
<h3 class="section-header-small">
|
||||||
Busiest Hours
|
Busiest Hours
|
||||||
</h3>
|
</h3>
|
||||||
|
<ul class="stat-list" data-stats="busiest-hours">
|
||||||
{% if stats.enhanced.busiestHours|length > 0 %}
|
{% if stats.enhanced.busiestHours|length > 0 %}
|
||||||
<ul class="stat-list">
|
{% for item in stats.enhanced.busiestHours %}
|
||||||
{% for item in stats.enhanced.busiestHours %}
|
<li class="stat-list-item">
|
||||||
<li class="stat-list-item">
|
<span class="stat-list-label">{{ item.hour }}:00 - {{ item.hour + 1 }}:00</span>
|
||||||
<span class="stat-list-label">{{ item.hour }}:00 - {{ item.hour + 1 }}:00</span>
|
<span class="stat-list-value">{{ item.count }}</span>
|
||||||
<span class="stat-list-value">{{ item.count }}</span>
|
</li>
|
||||||
</li>
|
{% endfor %}
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
{% else %}
|
{% else %}
|
||||||
<p class="stat-empty">No data yet</p>
|
<li class="stat-empty">Loading...</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Quick Stats -->
|
<!-- Quick Stats -->
|
||||||
|
|
@ -157,27 +157,27 @@
|
||||||
</h3>
|
</h3>
|
||||||
<div class="quick-stats">
|
<div class="quick-stats">
|
||||||
<div class="quick-stat-item">
|
<div class="quick-stat-item">
|
||||||
<div class="quick-stat-value">{{ stats.enhanced.averageSubjectLength }}</div>
|
<div class="quick-stat-value" data-stats="average-subject-length">{{ stats.enhanced.averageSubjectLength }}</div>
|
||||||
<div class="quick-stat-label">Avg Subject Length</div>
|
<div class="quick-stat-label">Avg Subject Length</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="quick-stat-item">
|
<div class="quick-stat-item">
|
||||||
<div class="quick-stat-value">{{ stats.enhanced.uniqueSenderDomains }}</div>
|
<div class="quick-stat-value" data-stats="unique-sender-domains-value">{{ stats.enhanced.uniqueSenderDomains }}</div>
|
||||||
<div class="quick-stat-label">Unique Senders</div>
|
<div class="quick-stat-label">Unique Senders</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="quick-stat-item">
|
<div class="quick-stat-item">
|
||||||
<div class="quick-stat-value">{{ stats.enhanced.uniqueRecipientDomains }}</div>
|
<div class="quick-stat-value" data-stats="unique-recipient-domains-value">{{ stats.enhanced.uniqueRecipientDomains }}</div>
|
||||||
<div class="quick-stat-label">Unique Recipients</div>
|
<div class="quick-stat-label">Unique Recipients</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="quick-stat-item">
|
<div class="quick-stat-item">
|
||||||
<div class="quick-stat-value">{{ stats.enhanced.peakHourPercentage }}%</div>
|
<div class="quick-stat-value" data-stats="peak-hour-percentage">{{ stats.enhanced.peakHourPercentage }}%</div>
|
||||||
<div class="quick-stat-label">Peak Hour Traffic</div>
|
<div class="quick-stat-label">Peak Hour Traffic</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="quick-stat-item">
|
<div class="quick-stat-item">
|
||||||
<div class="quick-stat-value">{{ stats.enhanced.emailsPerHour }}</div>
|
<div class="quick-stat-value" data-stats="emails-per-hour">{{ stats.enhanced.emailsPerHour }}</div>
|
||||||
<div class="quick-stat-label">Emails per Hour</div>
|
<div class="quick-stat-label">Emails per Hour</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="quick-stat-item">
|
<div class="quick-stat-item">
|
||||||
<div class="quick-stat-value">{{ stats.enhanced.dayPercentage }}%</div>
|
<div class="quick-stat-value" data-stats="day-percentage">{{ stats.enhanced.dayPercentage }}%</div>
|
||||||
<div class="quick-stat-label">Daytime (6am-6pm)</div>
|
<div class="quick-stat-label">Daytime (6am-6pm)</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue