mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-11-25 09:03:12 +03:00
Extracted dashboard members graphs block to a component
refs https://github.com/TryGhost/Team/issues/1406 - separates concerns from the Dashboard controller so it's easier to refactor and change behaviour - pre-req for introducing optional display of the graphs based on wider system status in a clean way
This commit is contained in:
parent
8787fc82de
commit
b5992de5ea
106
ghost/admin/app/components/dashboard/members-graphs.hbs
Normal file
106
ghost/admin/app/components/dashboard/members-graphs.hbs
Normal file
@ -0,0 +1,106 @@
|
||||
<section class="gh-dashboard-area charts">
|
||||
<div class="gh-dashboard-box mrr">
|
||||
<div class="flex items-center justify-between">
|
||||
<h4 class="gh-dashboard-header">MRR</h4>
|
||||
<h4 class="gh-dashboard-header secondary">30 days</h4>
|
||||
</div>
|
||||
<div class="gh-dashboard-chart-container">
|
||||
{{#if this.mrrStatsLoading}}
|
||||
Loading...
|
||||
{{else}}
|
||||
{{#if this.mrrStatsError}}
|
||||
<p class="error">
|
||||
There was an error loading MRR
|
||||
<code>{{this.mrrStatsError.message}}</code>
|
||||
</p>
|
||||
{{else}}
|
||||
<div class="gh-dashboard-summary">
|
||||
<div class="data"><span class="currency">{{this.mrrStatsData.currency}}</span>{{format-number this.mrrStatsData.currentAmount}}</div>
|
||||
<div class="growth {{this.mrrStatsData.percentClass}}">{{this.mrrStatsData.percentGrowth}}%</div>
|
||||
</div>
|
||||
{{#if this.mrrStatsData}}
|
||||
<div class="gh-dashboard-chart">
|
||||
<GhMembersChart @type="LineWithLine" @nightShift={{feature "nightShift"}} @showSummary={{false}} @showRange={{false}} @chartType="mrr" @chartStats={{this.mrrStatsData}} />
|
||||
</div>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="gh-dashboard-box total-members">
|
||||
<div class="gh-dashboard-chart-container">
|
||||
{{#if this.memberCountStatsLoading}}
|
||||
Loading...
|
||||
{{else}}
|
||||
{{#if this.memberCountStatsError}}
|
||||
<p class="error">
|
||||
There was an error loading total members
|
||||
<code>{{this.memberCountStatsData.message}}</code>
|
||||
</p>
|
||||
{{else}}
|
||||
<div class="gh-dashboard-summary small">
|
||||
<h4 class="gh-dashboard-header">Total members</h4>
|
||||
<div class="data-container">
|
||||
<div class="data">{{format-number this.memberCountStatsData.all.total}}</div>
|
||||
<div class="growth {{this.memberCountStatsData.all.percentClass}}">{{this.memberCountStatsData.all.percentGrowth}}%</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="gh-dashboard-chart small">
|
||||
<GhMembersChart @type="LineWithLine" @nightShift={{feature "nightShift"}} @chartSize="small" @showSummary={{false}} @chartType="all-members" @showRange={{false}} @chartStats={{this.memberCountStatsData.all}} />
|
||||
</div>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="gh-dashboard-box paid-members">
|
||||
<div class="gh-dashboard-chart-container">
|
||||
{{#if this.memberCountStatsLoading}}
|
||||
Loading...
|
||||
{{else}}
|
||||
{{#if this.memberCountStatsError}}
|
||||
<p class="error">
|
||||
There was an error loading paid members
|
||||
<code>{{this.memberCountStatsData.message}}</code>
|
||||
</p>
|
||||
{{else}}
|
||||
<div class="gh-dashboard-summary small">
|
||||
<h4 class="gh-dashboard-header">Paid members</h4>
|
||||
<div class="data-container">
|
||||
<div class="data">{{format-number this.memberCountStatsData.paid.total}}</div>
|
||||
<div class="growth {{this.memberCountStatsData.paid.percentClass}}">{{this.memberCountStatsData.paid.percentGrowth}}%</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="gh-dashboard-chart small">
|
||||
<GhMembersChart @type="LineWithLine" @nightShift={{feature "nightShift"}} @chartSize="small" @showSummary={{false}} @chartType="paid-members" @showRange={{false}} @chartStats={{this.memberCountStatsData.paid}} />
|
||||
</div>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="gh-dashboard-box newsletter-open-rate">
|
||||
<div class="gh-dashboard-chart-container">
|
||||
{{#if this.newsletterOpenRatesLoading}}
|
||||
Loading...
|
||||
{{else}}
|
||||
{{#if this.newsletterOpenRatesError}}
|
||||
<p class="error">
|
||||
There was an error loading newsletter open rates
|
||||
<code>{{this.memberCountStatsData.message}}</code>
|
||||
</p>
|
||||
{{else}}
|
||||
<div class="gh-dashboard-summary small">
|
||||
<h4 class="gh-dashboard-header">Email open rate</h4>
|
||||
|
||||
<div class="data-container">
|
||||
<div class="data">{{this.newsletterOpenRatesData.current}}%</div>
|
||||
<div class="growth {{this.newsletterOpenRatesData.percentClass}}">{{this.newsletterOpenRatesData.percentGrowth}}%</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="gh-dashboard-chart small">
|
||||
<GhMembersChart @type="bar" @nightShift={{feature "nightShift"}} @chartSize="small" @showSummary={{false}} @chartType="open-rate" @showRange={{false}} @chartStats={{this.newsletterOpenRatesData}} />
|
||||
</div>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
161
ghost/admin/app/components/dashboard/members-graphs.js
Normal file
161
ghost/admin/app/components/dashboard/members-graphs.js
Normal file
@ -0,0 +1,161 @@
|
||||
import Component from '@glimmer/component';
|
||||
import {getSymbol} from 'ghost-admin/utils/currency';
|
||||
import {inject as service} from '@ember/service';
|
||||
import {tracked} from '@glimmer/tracking';
|
||||
|
||||
export default class DashboardMembersGraphs extends Component {
|
||||
@service membersStats;
|
||||
@service store;
|
||||
|
||||
@tracked mrrStatsData = null;
|
||||
@tracked mrrStatsError = null;
|
||||
@tracked mrrStatsLoading = false;
|
||||
|
||||
@tracked memberCountStatsData = null;
|
||||
@tracked memberCountStatsError = null;
|
||||
@tracked memberCountStatsLoading = false;
|
||||
|
||||
@tracked newsletterOpenRatesData = null;
|
||||
@tracked newsletterOpenRatesError = null;
|
||||
@tracked newsletterOpenRatesLoading = false;
|
||||
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this.loadCharts();
|
||||
}
|
||||
|
||||
loadCharts() {
|
||||
this.loadMRRStats();
|
||||
this.loadMemberCountStats();
|
||||
this.loadNewsletterOpenRates();
|
||||
}
|
||||
|
||||
async loadMRRStats() {
|
||||
const products = await this.store.query('product', {
|
||||
filter: 'type:paid', include: 'monthly_price,yearly_price', limit: 'all'
|
||||
});
|
||||
const defaultProduct = products?.firstObject;
|
||||
|
||||
this.mrrStatsLoading = true;
|
||||
this.membersStats.fetchMRR().then((stats) => {
|
||||
this.mrrStatsLoading = false;
|
||||
const statsData = stats.data || [];
|
||||
const defaultCurrency = defaultProduct?.monthlyPrice?.currency || 'usd';
|
||||
let currencyStats = statsData.find((stat) => {
|
||||
return stat.currency === defaultCurrency;
|
||||
});
|
||||
currencyStats = currencyStats || {
|
||||
data: [],
|
||||
currency: defaultCurrency
|
||||
};
|
||||
if (currencyStats) {
|
||||
const currencyStatsData = this.membersStats.fillDates(currencyStats.data) || {};
|
||||
const dateValues = Object.values(currencyStatsData).map(val => Math.round((val / 100)));
|
||||
const currentMRR = dateValues.length ? dateValues[dateValues.length - 1] : 0;
|
||||
const rangeStartMRR = dateValues.length ? dateValues[0] : 0;
|
||||
const percentGrowth = rangeStartMRR !== 0 ? ((currentMRR - rangeStartMRR) / rangeStartMRR) * 100 : 0;
|
||||
this.mrrStatsData = {
|
||||
currentAmount: currentMRR,
|
||||
currency: getSymbol(currencyStats.currency),
|
||||
percentGrowth: percentGrowth.toFixed(1),
|
||||
percentClass: (percentGrowth > 0 ? 'positive' : (percentGrowth < 0 ? 'negative' : '')),
|
||||
options: {
|
||||
rangeInDays: 30
|
||||
},
|
||||
data: {
|
||||
label: 'MRR',
|
||||
dateLabels: Object.keys(currencyStatsData),
|
||||
dateValues
|
||||
},
|
||||
title: 'MRR',
|
||||
stats: currencyStats
|
||||
};
|
||||
}
|
||||
}, (error) => {
|
||||
this.mrrStatsError = error;
|
||||
this.mrrStatsLoading = false;
|
||||
});
|
||||
}
|
||||
|
||||
loadMemberCountStats() {
|
||||
this.memberCountStatsLoading = true;
|
||||
this.membersStats.fetchCounts().then((stats) => {
|
||||
this.memberCountStatsLoading = false;
|
||||
|
||||
if (stats) {
|
||||
const statsDateObj = this.membersStats.fillCountDates(stats.data) || {};
|
||||
const dateValues = Object.values(statsDateObj);
|
||||
const currentAllCount = dateValues.length ? dateValues[dateValues.length - 1].total : 0;
|
||||
const currentPaidCount = dateValues.length ? dateValues[dateValues.length - 1].paid : 0;
|
||||
const rangeStartAllCount = dateValues.length ? dateValues[0].total : 0;
|
||||
const rangeStartPaidCount = dateValues.length ? dateValues[0].paid : 0;
|
||||
const allCountPercentGrowth = rangeStartAllCount !== 0 ? ((currentAllCount - rangeStartAllCount) / rangeStartAllCount) * 100 : 0;
|
||||
const paidCountPercentGrowth = rangeStartPaidCount !== 0 ? ((currentPaidCount - rangeStartPaidCount) / rangeStartPaidCount) * 100 : 0;
|
||||
|
||||
this.memberCountStatsData = {
|
||||
all: {
|
||||
percentGrowth: allCountPercentGrowth.toFixed(1),
|
||||
percentClass: (allCountPercentGrowth > 0 ? 'positive' : (allCountPercentGrowth < 0 ? 'negative' : '')),
|
||||
total: dateValues.length ? dateValues[dateValues.length - 1].total : 0,
|
||||
options: {
|
||||
rangeInDays: 30
|
||||
},
|
||||
data: {
|
||||
label: 'Members',
|
||||
dateLabels: Object.keys(statsDateObj),
|
||||
dateValues: dateValues.map(d => d.total)
|
||||
},
|
||||
title: 'Total Members',
|
||||
stats: stats
|
||||
},
|
||||
paid: {
|
||||
percentGrowth: paidCountPercentGrowth.toFixed(1),
|
||||
percentClass: (paidCountPercentGrowth > 0 ? 'positive' : (paidCountPercentGrowth < 0 ? 'negative' : '')),
|
||||
total: dateValues.length ? dateValues[dateValues.length - 1].paid : 0,
|
||||
options: {
|
||||
rangeInDays: 30
|
||||
},
|
||||
data: {
|
||||
label: 'Members',
|
||||
dateLabels: Object.keys(statsDateObj),
|
||||
dateValues: dateValues.map(d => d.paid)
|
||||
},
|
||||
title: 'Paid Members',
|
||||
stats: stats
|
||||
}
|
||||
};
|
||||
}
|
||||
}, (error) => {
|
||||
this.memberCountStatsError = error;
|
||||
this.memberCountStatsLoading = false;
|
||||
});
|
||||
}
|
||||
|
||||
loadNewsletterOpenRates() {
|
||||
this.newsletterOpenRatesLoading = true;
|
||||
this.membersStats.fetchNewsletterStats().then((results) => {
|
||||
const rangeStartOpenRate = results.length > 1 ? results[results.length - 2].openRate : 0;
|
||||
const rangeEndOpenRate = results.length > 0 ? results[results.length - 1].openRate : 0;
|
||||
const percentGrowth = rangeStartOpenRate !== 0 ? ((rangeEndOpenRate - rangeStartOpenRate) / rangeStartOpenRate) * 100 : 0;
|
||||
this.newsletterOpenRatesData = {
|
||||
percentGrowth: percentGrowth.toFixed(1),
|
||||
percentClass: (percentGrowth > 0 ? 'positive' : (percentGrowth < 0 ? 'negative' : '')),
|
||||
current: rangeEndOpenRate,
|
||||
options: {
|
||||
rangeInDays: 30
|
||||
},
|
||||
data: {
|
||||
label: 'Open rate',
|
||||
dateLabels: results.map(d => d.subject),
|
||||
dateValues: results.map(d => d.openRate)
|
||||
},
|
||||
title: 'Open rate',
|
||||
stats: results
|
||||
};
|
||||
this.newsletterOpenRatesLoading = false;
|
||||
}, (error) => {
|
||||
this.newsletterOpenRatesError = error;
|
||||
this.newsletterOpenRatesLoading = false;
|
||||
});
|
||||
}
|
||||
}
|
@ -1,6 +1,5 @@
|
||||
import Controller from '@ember/controller';
|
||||
import {action} from '@ember/object';
|
||||
import {getSymbol} from 'ghost-admin/utils/currency';
|
||||
import {inject as service} from '@ember/service';
|
||||
import {tracked} from '@glimmer/tracking';
|
||||
|
||||
@ -12,22 +11,10 @@ export default class DashboardController extends Controller {
|
||||
@service settings;
|
||||
@service whatsNew;
|
||||
|
||||
@tracked mrrStatsData = null;
|
||||
@tracked mrrStatsError = null;
|
||||
@tracked mrrStatsLoading = false;
|
||||
|
||||
@tracked memberCountStatsData = null;
|
||||
@tracked memberCountStatsError = null;
|
||||
@tracked memberCountStatsLoading = false;
|
||||
|
||||
@tracked topMembersData = null;
|
||||
@tracked topMembersError = null;
|
||||
@tracked topMembersLoading = false;
|
||||
|
||||
@tracked newsletterOpenRatesData = null;
|
||||
@tracked newsletterOpenRatesError = null;
|
||||
@tracked newsletterOpenRatesLoading = false;
|
||||
|
||||
@tracked whatsNewEntries = null;
|
||||
@tracked whatsNewEntriesLoading = null;
|
||||
@tracked whatsNewEntriesError = null;
|
||||
@ -44,145 +31,9 @@ export default class DashboardController extends Controller {
|
||||
|
||||
initialise() {
|
||||
this.loadTopMembers();
|
||||
this.loadCharts();
|
||||
this.loadWhatsNew();
|
||||
}
|
||||
|
||||
async loadMRRStats() {
|
||||
const products = await this.store.query('product', {
|
||||
filter: 'type:paid', include: 'monthly_price,yearly_price', limit: 'all'
|
||||
});
|
||||
const defaultProduct = products?.firstObject;
|
||||
|
||||
this.mrrStatsLoading = true;
|
||||
this.membersStats.fetchMRR().then((stats) => {
|
||||
this.mrrStatsLoading = false;
|
||||
const statsData = stats.data || [];
|
||||
const defaultCurrency = defaultProduct?.monthlyPrice?.currency || 'usd';
|
||||
let currencyStats = statsData.find((stat) => {
|
||||
return stat.currency === defaultCurrency;
|
||||
});
|
||||
currencyStats = currencyStats || {
|
||||
data: [],
|
||||
currency: defaultCurrency
|
||||
};
|
||||
if (currencyStats) {
|
||||
const currencyStatsData = this.membersStats.fillDates(currencyStats.data) || {};
|
||||
const dateValues = Object.values(currencyStatsData).map(val => Math.round((val / 100)));
|
||||
const currentMRR = dateValues.length ? dateValues[dateValues.length - 1] : 0;
|
||||
const rangeStartMRR = dateValues.length ? dateValues[0] : 0;
|
||||
const percentGrowth = rangeStartMRR !== 0 ? ((currentMRR - rangeStartMRR) / rangeStartMRR) * 100 : 0;
|
||||
this.mrrStatsData = {
|
||||
currentAmount: currentMRR,
|
||||
currency: getSymbol(currencyStats.currency),
|
||||
percentGrowth: percentGrowth.toFixed(1),
|
||||
percentClass: (percentGrowth > 0 ? 'positive' : (percentGrowth < 0 ? 'negative' : '')),
|
||||
options: {
|
||||
rangeInDays: 30
|
||||
},
|
||||
data: {
|
||||
label: 'MRR',
|
||||
dateLabels: Object.keys(currencyStatsData),
|
||||
dateValues
|
||||
},
|
||||
title: 'MRR',
|
||||
stats: currencyStats
|
||||
};
|
||||
}
|
||||
}, (error) => {
|
||||
this.mrrStatsError = error;
|
||||
this.mrrStatsLoading = false;
|
||||
});
|
||||
}
|
||||
|
||||
loadMemberCountStats() {
|
||||
this.memberCountStatsLoading = true;
|
||||
this.membersStats.fetchCounts().then((stats) => {
|
||||
this.memberCountStatsLoading = false;
|
||||
|
||||
if (stats) {
|
||||
const statsDateObj = this.membersStats.fillCountDates(stats.data) || {};
|
||||
const dateValues = Object.values(statsDateObj);
|
||||
const currentAllCount = dateValues.length ? dateValues[dateValues.length - 1].total : 0;
|
||||
const currentPaidCount = dateValues.length ? dateValues[dateValues.length - 1].paid : 0;
|
||||
const rangeStartAllCount = dateValues.length ? dateValues[0].total : 0;
|
||||
const rangeStartPaidCount = dateValues.length ? dateValues[0].paid : 0;
|
||||
const allCountPercentGrowth = rangeStartAllCount !== 0 ? ((currentAllCount - rangeStartAllCount) / rangeStartAllCount) * 100 : 0;
|
||||
const paidCountPercentGrowth = rangeStartPaidCount !== 0 ? ((currentPaidCount - rangeStartPaidCount) / rangeStartPaidCount) * 100 : 0;
|
||||
|
||||
this.memberCountStatsData = {
|
||||
all: {
|
||||
percentGrowth: allCountPercentGrowth.toFixed(1),
|
||||
percentClass: (allCountPercentGrowth > 0 ? 'positive' : (allCountPercentGrowth < 0 ? 'negative' : '')),
|
||||
total: dateValues.length ? dateValues[dateValues.length - 1].total : 0,
|
||||
options: {
|
||||
rangeInDays: 30
|
||||
},
|
||||
data: {
|
||||
label: 'Members',
|
||||
dateLabels: Object.keys(statsDateObj),
|
||||
dateValues: dateValues.map(d => d.total)
|
||||
},
|
||||
title: 'Total Members',
|
||||
stats: stats
|
||||
},
|
||||
paid: {
|
||||
percentGrowth: paidCountPercentGrowth.toFixed(1),
|
||||
percentClass: (paidCountPercentGrowth > 0 ? 'positive' : (paidCountPercentGrowth < 0 ? 'negative' : '')),
|
||||
total: dateValues.length ? dateValues[dateValues.length - 1].paid : 0,
|
||||
options: {
|
||||
rangeInDays: 30
|
||||
},
|
||||
data: {
|
||||
label: 'Members',
|
||||
dateLabels: Object.keys(statsDateObj),
|
||||
dateValues: dateValues.map(d => d.paid)
|
||||
},
|
||||
title: 'Paid Members',
|
||||
stats: stats
|
||||
}
|
||||
};
|
||||
}
|
||||
}, (error) => {
|
||||
this.memberCountStatsError = error;
|
||||
this.memberCountStatsLoading = false;
|
||||
});
|
||||
}
|
||||
|
||||
loadCharts() {
|
||||
this.loadMRRStats();
|
||||
this.loadMemberCountStats();
|
||||
this.loadNewsletterOpenRates();
|
||||
}
|
||||
|
||||
loadNewsletterOpenRates() {
|
||||
this.newsletterOpenRatesLoading = true;
|
||||
this.membersStats.fetchNewsletterStats().then((results) => {
|
||||
const rangeStartOpenRate = results.length > 1 ? results[results.length - 2].openRate : 0;
|
||||
const rangeEndOpenRate = results.length > 0 ? results[results.length - 1].openRate : 0;
|
||||
const percentGrowth = rangeStartOpenRate !== 0 ? ((rangeEndOpenRate - rangeStartOpenRate) / rangeStartOpenRate) * 100 : 0;
|
||||
this.newsletterOpenRatesData = {
|
||||
percentGrowth: percentGrowth.toFixed(1),
|
||||
percentClass: (percentGrowth > 0 ? 'positive' : (percentGrowth < 0 ? 'negative' : '')),
|
||||
current: rangeEndOpenRate,
|
||||
options: {
|
||||
rangeInDays: 30
|
||||
},
|
||||
data: {
|
||||
label: 'Open rate',
|
||||
dateLabels: results.map(d => d.subject),
|
||||
dateValues: results.map(d => d.openRate)
|
||||
},
|
||||
title: 'Open rate',
|
||||
stats: results
|
||||
};
|
||||
this.newsletterOpenRatesLoading = false;
|
||||
}, (error) => {
|
||||
this.newsletterOpenRatesError = error;
|
||||
this.newsletterOpenRatesLoading = false;
|
||||
});
|
||||
}
|
||||
|
||||
loadTopMembers() {
|
||||
if (this.feature.membersActivityFeed) {
|
||||
return;
|
||||
|
@ -58,112 +58,7 @@
|
||||
</div>
|
||||
</section>
|
||||
{{else if this.showMembersData}}
|
||||
<section class="gh-dashboard-area charts">
|
||||
<div class="gh-dashboard-box mrr">
|
||||
<div class="flex items-center justify-between">
|
||||
<h4 class="gh-dashboard-header">MRR</h4>
|
||||
<h4 class="gh-dashboard-header secondary">30 days</h4>
|
||||
</div>
|
||||
<div class="gh-dashboard-chart-container">
|
||||
{{#if this.mrrStatsLoading}}
|
||||
Loading...
|
||||
{{else}}
|
||||
{{#if this.mrrStatsError}}
|
||||
<p class="error">
|
||||
There was an error loading MRR
|
||||
<code>{{this.mrrStatsError.message}}</code>
|
||||
</p>
|
||||
{{else}}
|
||||
<div class="gh-dashboard-summary">
|
||||
<div class="data"><span class="currency">{{this.mrrStatsData.currency}}</span>{{format-number this.mrrStatsData.currentAmount}}</div>
|
||||
<div class="growth {{this.mrrStatsData.percentClass}}">{{this.mrrStatsData.percentGrowth}}%</div>
|
||||
</div>
|
||||
{{#if this.mrrStatsData}}
|
||||
<div class="gh-dashboard-chart">
|
||||
<GhMembersChart @type="LineWithLine" @nightShift={{feature "nightShift"}} @showSummary={{false}} @showRange={{false}} @chartType="mrr" @chartStats={{this.mrrStatsData}} />
|
||||
</div>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="gh-dashboard-box total-members">
|
||||
<div class="gh-dashboard-chart-container">
|
||||
{{#if this.memberCountStatsLoading}}
|
||||
Loading...
|
||||
{{else}}
|
||||
{{#if this.memberCountStatsError}}
|
||||
<p class="error">
|
||||
There was an error loading total members
|
||||
<code>{{this.memberCountStatsData.message}}</code>
|
||||
</p>
|
||||
{{else}}
|
||||
<div class="gh-dashboard-summary small">
|
||||
<h4 class="gh-dashboard-header">Total members</h4>
|
||||
<div class="data-container">
|
||||
<div class="data">{{format-number this.memberCountStatsData.all.total}}</div>
|
||||
<div class="growth {{this.memberCountStatsData.all.percentClass}}">{{this.memberCountStatsData.all.percentGrowth}}%</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="gh-dashboard-chart small">
|
||||
<GhMembersChart @type="LineWithLine" @nightShift={{feature "nightShift"}} @chartSize="small" @showSummary={{false}} @chartType="all-members" @showRange={{false}} @chartStats={{this.memberCountStatsData.all}} />
|
||||
</div>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="gh-dashboard-box paid-members">
|
||||
<div class="gh-dashboard-chart-container">
|
||||
{{#if this.memberCountStatsLoading}}
|
||||
Loading...
|
||||
{{else}}
|
||||
{{#if this.memberCountStatsError}}
|
||||
<p class="error">
|
||||
There was an error loading paid members
|
||||
<code>{{this.memberCountStatsData.message}}</code>
|
||||
</p>
|
||||
{{else}}
|
||||
<div class="gh-dashboard-summary small">
|
||||
<h4 class="gh-dashboard-header">Paid members</h4>
|
||||
<div class="data-container">
|
||||
<div class="data">{{format-number this.memberCountStatsData.paid.total}}</div>
|
||||
<div class="growth {{this.memberCountStatsData.paid.percentClass}}">{{this.memberCountStatsData.paid.percentGrowth}}%</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="gh-dashboard-chart small">
|
||||
<GhMembersChart @type="LineWithLine" @nightShift={{feature "nightShift"}} @chartSize="small" @showSummary={{false}} @chartType="paid-members" @showRange={{false}} @chartStats={{this.memberCountStatsData.paid}} />
|
||||
</div>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="gh-dashboard-box newsletter-open-rate">
|
||||
<div class="gh-dashboard-chart-container">
|
||||
{{#if this.newsletterOpenRatesLoading}}
|
||||
Loading...
|
||||
{{else}}
|
||||
{{#if this.newsletterOpenRatesError}}
|
||||
<p class="error">
|
||||
There was an error loading newsletter open rates
|
||||
<code>{{this.memberCountStatsData.message}}</code>
|
||||
</p>
|
||||
{{else}}
|
||||
<div class="gh-dashboard-summary small">
|
||||
<h4 class="gh-dashboard-header">Email open rate</h4>
|
||||
|
||||
<div class="data-container">
|
||||
<div class="data">{{this.newsletterOpenRatesData.current}}%</div>
|
||||
<div class="growth {{this.newsletterOpenRatesData.percentClass}}">{{this.newsletterOpenRatesData.percentGrowth}}%</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="gh-dashboard-chart small">
|
||||
<GhMembersChart @type="bar" @nightShift={{feature "nightShift"}} @chartSize="small" @showSummary={{false}} @chartType="open-rate" @showRange={{false}} @chartStats={{this.newsletterOpenRatesData}} />
|
||||
</div>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<Dashboard::MembersGraphs />
|
||||
{{/if}}
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user