Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
135 changes: 135 additions & 0 deletions assets/css/analytics.scss
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,141 @@ $mq-xs: 600px;
}
}

// -----------------------------------------------------------------------------
// Audience Overview KPI block
// -----------------------------------------------------------------------------
.mailchimp-sf-ao {

&__metrics {
display: grid;
gap: 24px;
grid-template-columns: repeat(4, minmax(0, 1fr));
margin: 0 0 16px;
}

&__metric {
display: flex;
flex-direction: column;
min-width: 0;
}

&__metric-label {
align-self: flex-start;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
color: var(--mc-sa-text-strong);
font-size: 14px;
font-weight: 600;
margin: 0;

&::after {
background-image: linear-gradient(
90deg,
color-mix(in srgb, var(--mailchimp-color-link, #017e89) 45%, #ffffff) 5px,
transparent 5px
);
background-repeat: repeat-x;
background-size: 9px 2px;
content: "";
display: block;
height: 2px;
margin-top: 6px;
}
}

&__metric-value {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
color: var(--mc-sa-text-strong);
font-size: 32px;
font-weight: 700;
letter-spacing: -0.01em;
line-height: 1.1;
margin: 8px 0 0;
}

&.is-loading &__metric-value,
&.is-error &__metric-value {
color: var(--mc-sa-grey);
opacity: 0.85;
}

&__error-banner {
align-items: center;
background: var(--mc-sa-error-bg);
border: 1px solid var(--mc-sa-error-border);
border-radius: 8px;
display: flex;
gap: 12px;
margin: 4px 0 16px;
padding: 12px 14px;

&[hidden] {
display: none;
}
}

&__error-banner-icon {
align-items: center;
color: var(--mc-sa-error-text);
display: inline-flex;
flex-shrink: 0;
justify-content: center;
line-height: 0;
}

&__error-banner-body {
flex: 1 1 auto;
min-width: 0;
}

&__error-banner-title {
color: var(--mc-sa-error-title);
font-size: 13px;
font-weight: 600;
line-height: 1.3;
margin: 0 0 2px;
}

&__error-banner-message {
color: var(--mc-sa-error-text);
font-size: 13px;
line-height: 1.4;
margin: 0;
}

&__error-banner-action {
flex-shrink: 0;
}

button#mailchimp-sf-ao-error-retry {
background-color: #fff;
border: 1px solid #ccd6dc;

&:hover,
&:focus,
&:active {
background-color: #f6f7f7;
}
}
}

@media screen and (max-width: $mq-medium) {

.mailchimp-sf-ao__metrics {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}

@media screen and (max-width: $mq-xs) {

.mailchimp-sf-ao__metrics {
grid-template-columns: 1fr;
}

.mailchimp-sf-ao__metric-value {
font-size: 28px;
}
}

// -----------------------------------------------------------------------------
// Shared analytics chart-card
// -----------------------------------------------------------------------------
Expand Down
192 changes: 192 additions & 0 deletions assets/js/analytics.js
Original file line number Diff line number Diff line change
Expand Up @@ -489,6 +489,8 @@ import { __ } from '@wordpress/i18n';
}

/**
* Render the bar+line chart from API rows.
*
* @param {Array} rows Payload `data` rows from the API.
*/
function renderChart(rows) {
Expand Down Expand Up @@ -661,9 +663,24 @@ import { __ } from '@wordpress/i18n';
renderChart(rows);
}

/**
* Custom event so other analytics modules (Audience
* Overview, etc.) can render from the same fetch without making
* their own AJAX call.
*
* @param {string} name Event suffix — appended to `mailchimp-analytics-`.
* @param {object} eventDetail Payload passed as the event's `detail`.
*/
function broadcast(name, eventDetail) {
document.dispatchEvent(
new CustomEvent(`mailchimp-analytics-${name}`, { detail: eventDetail }),
);
}

function fetchPerformance(detail) {
if (!window.mailchimpSFAnalytics || !window.mailchimpSFAnalytics.ajax_url) {
showError();
broadcast('error', { message: STRINGS.errorDefault });
return;
}
if (!detail || !detail.listId || !detail.from || !detail.to) {
Expand Down Expand Up @@ -693,6 +710,7 @@ import { __ } from '@wordpress/i18n';
formData.append('date_to', detail.to);

showLoading();
broadcast('loading', { from: detail.from, to: detail.to });

fetch(window.mailchimpSFAnalytics.ajax_url, {
method: 'POST',
Expand All @@ -711,16 +729,23 @@ import { __ } from '@wordpress/i18n';
const message =
body && body.data && body.data.message ? body.data.message : '';
showError(message);
broadcast('error', { message: message || STRINGS.errorDefault });
return;
}
render(body.data, detail.from, detail.to);
broadcast('loaded', {
data: body.data,
from: detail.from,
to: detail.to,
});
})
.catch(function (err) {
if (err && err.name === 'AbortError') {
return;
}
inFlight = null;
showError();
broadcast('error', { message: STRINGS.errorDefault });
});
}

Expand Down Expand Up @@ -1160,6 +1185,173 @@ import { __ } from '@wordpress/i18n';
});
})();

/**
* Audience Overview KPI block — Total subscribers, Form views, New submissions, Conversion rate.
*/
(function audienceOverviewModule() {
const section = document.querySelector('[data-section="audience-overview"]');
if (!section) {
return;
}
Comment on lines +1188 to +1195
Comment on lines +1188 to +1195

const subscribersEl = document.getElementById('mailchimp-sf-ao-total-subscribers');
const viewsEl = document.getElementById('mailchimp-sf-ao-views');
const submissionsEl = document.getElementById('mailchimp-sf-ao-submissions');
const rateEl = document.getElementById('mailchimp-sf-ao-rate');
const dateRangeEl = document.getElementById('mailchimp-sf-ao-daterange');
const errorBannerEl = document.getElementById('mailchimp-sf-ao-error-banner');
const errorMessageEl = document.getElementById('mailchimp-sf-ao-error-message');
const retryBtnEl = document.getElementById('mailchimp-sf-ao-error-retry');

const STRINGS = {
loadingSubtitle: __('Loading audience overview…', 'mailchimp'),
errorDefault: __(
'Unable to load audience overview. Please check your connection and try again.',
'mailchimp',
),
};

const STATE_CLASSES = ['is-loading', 'is-ready', 'is-error'];

let lastDetail = null;

function setState(state) {
STATE_CLASSES.forEach(function (cls) {
section.classList.toggle(cls, cls === `is-${state}`);
});
}

function setSubtitle(text) {
if (dateRangeEl) {
dateRangeEl.textContent = text || '';
}
}

function setErrorBanner(visible, message) {
if (!errorBannerEl) {
return;
}
if (visible) {
if (errorMessageEl) {
errorMessageEl.textContent = message || STRINGS.errorDefault;
}
errorBannerEl.hidden = false;
} else {
errorBannerEl.hidden = true;
}
}

function setPlaceholders() {
[subscribersEl, viewsEl, submissionsEl, rateEl].forEach(function (el) {
if (el) {
el.textContent = '-';
}
});
}

function formatRangeLabel(from, to) {
try {
const fromDate = new Date(`${from}T00:00:00`);
const toDate = new Date(`${to}T00:00:00`);
const fmt = new Intl.DateTimeFormat(undefined, {
month: 'short',
day: 'numeric',
year: 'numeric',
});
return `${fmt.format(fromDate)} – ${fmt.format(toDate)}`;
} catch (err) {
return `${from} – ${to}`;
}
}

function formatNumber(n) {
if (n === null || typeof n === 'undefined') {
return '-';
}
try {
return new Intl.NumberFormat().format(n);
} catch (err) {
return String(n);
}
}

function showLoading() {
setErrorBanner(false);
setSubtitle(STRINGS.loadingSubtitle);
setPlaceholders();
setState('loading');
}

function showError(message) {
if (lastDetail && lastDetail.from && lastDetail.to) {
setSubtitle(formatRangeLabel(lastDetail.from, lastDetail.to));
}
setPlaceholders();
setErrorBanner(true, message);
setState('error');
}

function render(data, fromLabel, toLabel) {
setErrorBanner(false);
setSubtitle(formatRangeLabel(fromLabel, toLabel));

if (subscribersEl) {
subscribersEl.textContent = formatNumber(data.total_subscribers);
}
if (viewsEl) {
viewsEl.textContent = formatNumber(data.total_views);
}
if (submissionsEl) {
submissionsEl.textContent = formatNumber(data.total_submissions);
}
if (rateEl) {
const rate = data.total_conversion_rate;
rateEl.textContent =
rate === null || typeof rate === 'undefined'
? '-'
: `${Number(rate).toFixed(2)}%`;
}

setState('ready');
}

document.addEventListener('mailchimp-analytics-refresh', function (e) {
if (e.detail) {
lastDetail = {
listId: e.detail.listId,
from: e.detail.from,
to: e.detail.to,
};
}
});

document.addEventListener('mailchimp-analytics-loading', function () {
showLoading();
});

document.addEventListener('mailchimp-analytics-loaded', function (e) {
if (e.detail && e.detail.data) {
render(e.detail.data, e.detail.from, e.detail.to);
}
});

document.addEventListener('mailchimp-analytics-error', function (e) {
showError(e.detail && e.detail.message);
});

if (retryBtnEl) {
retryBtnEl.addEventListener('click', function () {
if (!lastDetail) {
return;
}

document.dispatchEvent(
new CustomEvent('mailchimp-analytics-refresh', { detail: lastDetail }),
);
});
}
})();

// Initialize.
updateTriggerLabel();
syncDateInputs();
Expand Down
Loading