mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-12-26 20:34:02 +03:00
Dashboard consolidated graph (#2323)
* Basic work to combine the three line graphs for the new dashboard refs: https://github.com/TryGhost/Team/issues/1467 - combined three line graphs into one main one at the top - still working on this, so some code is a little rough * Tidying up a few bits of consolidated graph in new dashboard refs: https://github.com/TryGhost/Team/issues/1467 * Updated chart anchor component for removed member counts no issue * Updated chart paid members to not reload on days change no refs * Moved did-insert to top element in chart-anchor * Fixed chart anchor to use filled member data * Replaced chart anchor divs with buttons * Tweaking up the paid graphs below anchor to improve visuals refs: https://github.com/TryGhost/Team/issues/1467 * Fixed missing type attributes on buttons in chart anchor * Updated MMR to MRR for the new consolidated graph refs: https://github.com/TryGhost/Team/issues/1467 * Added real MRR to chart anchor * Added open rate percentage data to chart email Co-authored-by: Simon Backx <simon@ghost.org>
This commit is contained in:
parent
da972bfe58
commit
8fe18d7e30
@ -1,12 +1,11 @@
|
|||||||
<section {{did-insert this.onInsert}}>
|
<section {{did-insert this.onInsert}}>
|
||||||
|
|
||||||
{{#if this.isLoading }}
|
{{#if this.isLoading }}
|
||||||
<GhLoadingSpinner />
|
<GhLoadingSpinner />
|
||||||
{{else}}
|
{{else}}
|
||||||
{{#if this.areMembersEnabled}}
|
{{#if this.areMembersEnabled}}
|
||||||
<section class="gh-dashboard5-section gh-dashboard5-overview">
|
<section class="gh-dashboard5-section gh-dashboard5-anchor">
|
||||||
<article class="gh-dashboard5-box">
|
<article class="gh-dashboard5-box">
|
||||||
<Dashboard::V5::ChartTotalMembers @days={{this.days}} />
|
<Dashboard::V5::ChartAnchor @days={{this.days}} />
|
||||||
</article>
|
</article>
|
||||||
<div class="prototype-selection">
|
<div class="prototype-selection">
|
||||||
<PowerSelect
|
<PowerSelect
|
||||||
@ -29,14 +28,6 @@
|
|||||||
<div class="gh-dashboard5-paid">
|
<div class="gh-dashboard5-paid">
|
||||||
<section class="gh-dashboard5-section">
|
<section class="gh-dashboard5-section">
|
||||||
<div class="gh-dashboard5-growth">
|
<div class="gh-dashboard5-growth">
|
||||||
<article class="gh-dashboard5-box">
|
|
||||||
<Dashboard::V5::ChartMonthlyRevenue @days={{this.days}} />
|
|
||||||
</article>
|
|
||||||
|
|
||||||
<article class="gh-dashboard5-box">
|
|
||||||
<Dashboard::V5::ChartTotalPaid @days={{this.days}} />
|
|
||||||
</article>
|
|
||||||
|
|
||||||
<article class="gh-dashboard5-box">
|
<article class="gh-dashboard5-box">
|
||||||
<Dashboard::V5::ChartPaidMembers @days={{this.days}}/>
|
<Dashboard::V5::ChartPaidMembers @days={{this.days}}/>
|
||||||
</article>
|
</article>
|
||||||
|
39
ghost/admin/app/components/dashboard/v5/chart-anchor.hbs
Normal file
39
ghost/admin/app/components/dashboard/v5/chart-anchor.hbs
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
<div class="gh-dashboard5-stats" {{did-insert this.loadCharts}}>
|
||||||
|
<button type="button" {{on "click" (fn this.changeChartDisplay "total")}} class={{if this.chartShowingTotal 'is-selected'}}>
|
||||||
|
<div class="gh-dashboard5-number">
|
||||||
|
{{format-number this.totalMembers}}
|
||||||
|
{{#if this.hasTrends}}
|
||||||
|
<Dashboard::v5::parts::ChartPercentage @percentage={{this.totalMembersTrend}}/>
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
<small class="gh-dashboard5-info">{{gh-pluralize this.totalMembers "Total member" without-count=true}}</small>
|
||||||
|
</button>
|
||||||
|
<button type="button" {{on "click" (fn this.changeChartDisplay "paid")}} class={{if this.chartShowingPaid 'is-selected'}}>
|
||||||
|
<div class="gh-dashboard5-number">
|
||||||
|
{{format-number this.paidMembers}}
|
||||||
|
{{#if this.hasTrends}}
|
||||||
|
<Dashboard::v5::parts::ChartPercentage @percentage={{this.paidMembersTrend}}/>
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
<small class="gh-dashboard5-info">{{gh-pluralize this.paidMembers "Paid member" without-count=true}}</small>
|
||||||
|
</button>
|
||||||
|
<button type="button" {{on "click" (fn this.changeChartDisplay "monthly")}} class={{if this.chartShowingMonthly 'is-selected'}}>
|
||||||
|
<div class="gh-dashboard5-number">
|
||||||
|
${{gh-price-amount this.currentMRR}}
|
||||||
|
{{#if this.hasTrends}}
|
||||||
|
<Dashboard::v5::parts::ChartPercentage @percentage={{this.mrrTrend}}/>
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
<small class="gh-dashboard5-info">Monthly revenue (MRR)</small>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="gh-dashboard5-chart">
|
||||||
|
{{#if this.loading}}
|
||||||
|
<div class="gh-dashboard5-loading" style={{html-safe (concat "height: " this.chartHeight "px;")}}/>
|
||||||
|
{{else}}
|
||||||
|
<EmberChart
|
||||||
|
@type={{this.chartType}}
|
||||||
|
@data={{this.chartData}}
|
||||||
|
@options={{this.chartOptions}} />
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
198
ghost/admin/app/components/dashboard/v5/chart-anchor.js
Normal file
198
ghost/admin/app/components/dashboard/v5/chart-anchor.js
Normal file
@ -0,0 +1,198 @@
|
|||||||
|
import Component from '@glimmer/component';
|
||||||
|
import {action} from '@ember/object';
|
||||||
|
import {inject as service} from '@ember/service';
|
||||||
|
import {tracked} from '@glimmer/tracking';
|
||||||
|
|
||||||
|
export default class ChartAnchor extends Component {
|
||||||
|
@service dashboardStats;
|
||||||
|
@tracked chartDisplay = 'total';
|
||||||
|
|
||||||
|
@action
|
||||||
|
loadCharts() {
|
||||||
|
this.dashboardStats.loadMemberCountStats();
|
||||||
|
this.dashboardStats.loadMrrStats();
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
changeChartDisplay(type) {
|
||||||
|
this.chartDisplay = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
get chartShowingTotal() {
|
||||||
|
return (this.chartDisplay === 'total');
|
||||||
|
}
|
||||||
|
|
||||||
|
get chartShowingPaid() {
|
||||||
|
return (this.chartDisplay === 'paid');
|
||||||
|
}
|
||||||
|
|
||||||
|
get chartShowingMonthly() {
|
||||||
|
return (this.chartDisplay === 'monthly');
|
||||||
|
}
|
||||||
|
|
||||||
|
get loading() {
|
||||||
|
if (this.chartDisplay === 'total') {
|
||||||
|
return this.dashboardStats.memberCountStats === null;
|
||||||
|
} else if (this.chartDisplay === 'paid') {
|
||||||
|
return this.dashboardStats.memberCountStats === null;
|
||||||
|
} else if (this.chartDisplay === 'monthly') {
|
||||||
|
return this.dashboardStats.mrrStats === null;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
get totalMembers() {
|
||||||
|
return this.dashboardStats.memberCounts?.total ?? 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
get paidMembers() {
|
||||||
|
return this.dashboardStats.memberCounts?.paid ?? 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
get freeMembers() {
|
||||||
|
return this.dashboardStats.memberCounts?.free ?? 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
get currentMRR() {
|
||||||
|
return this.dashboardStats.currentMRR ?? 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
get hasTrends() {
|
||||||
|
return this.dashboardStats.memberCounts !== null && this.dashboardStats.memberCountsTrend !== null;
|
||||||
|
}
|
||||||
|
|
||||||
|
get totalMembersTrend() {
|
||||||
|
return this.calculatePercentage(this.dashboardStats.memberCountsTrend.total, this.dashboardStats.memberCounts.total);
|
||||||
|
}
|
||||||
|
|
||||||
|
get paidMembersTrend() {
|
||||||
|
return this.calculatePercentage(this.dashboardStats.memberCountsTrend.paid, this.dashboardStats.memberCounts.paid);
|
||||||
|
}
|
||||||
|
|
||||||
|
get freeMembersTrend() {
|
||||||
|
return this.calculatePercentage(this.dashboardStats.memberCountsTrend.free, this.dashboardStats.memberCounts.free);
|
||||||
|
}
|
||||||
|
|
||||||
|
get mrrTrend() {
|
||||||
|
return this.calculatePercentage(this.dashboardStats.currentMRRTrend, this.dashboardStats.currentMRR);
|
||||||
|
}
|
||||||
|
|
||||||
|
get chartType() {
|
||||||
|
return 'line';
|
||||||
|
}
|
||||||
|
|
||||||
|
get chartData() {
|
||||||
|
let stats = [];
|
||||||
|
let labels = [];
|
||||||
|
let data = [];
|
||||||
|
|
||||||
|
if (this.chartDisplay === 'total') {
|
||||||
|
stats = this.dashboardStats.filledMemberCountStats;
|
||||||
|
labels = stats.map(stat => stat.date);
|
||||||
|
data = stats.map(stat => stat.paid + stat.free + stat.comped);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.chartDisplay === 'paid') {
|
||||||
|
stats = this.dashboardStats.filledMemberCountStats;
|
||||||
|
labels = stats.map(stat => stat.date);
|
||||||
|
data = stats.map(stat => stat.paid);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.chartDisplay === 'monthly') {
|
||||||
|
stats = this.dashboardStats.filledMrrStats;
|
||||||
|
labels = stats.map(stat => stat.date);
|
||||||
|
data = stats.map(stat => stat.mrr);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
labels: labels,
|
||||||
|
datasets: [{
|
||||||
|
data: data,
|
||||||
|
tension: 0.1,
|
||||||
|
cubicInterpolationMode: 'monotone',
|
||||||
|
fill: true,
|
||||||
|
fillColor: '#F5FBFF',
|
||||||
|
backgroundColor: '#F5FBFF',
|
||||||
|
pointRadius: 0,
|
||||||
|
pointHitRadius: 10,
|
||||||
|
borderColor: '#14b8ff',
|
||||||
|
borderJoinStyle: 'miter',
|
||||||
|
maxBarThickness: 20,
|
||||||
|
minBarLength: 2
|
||||||
|
}]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
get chartOptions() {
|
||||||
|
return {
|
||||||
|
responsive: true,
|
||||||
|
maintainAspectRatio: false,
|
||||||
|
title: {
|
||||||
|
display: false
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
display: false
|
||||||
|
},
|
||||||
|
layout: {
|
||||||
|
padding: {
|
||||||
|
top: 20
|
||||||
|
}
|
||||||
|
},
|
||||||
|
scales: {
|
||||||
|
yAxes: [{
|
||||||
|
gridLines: {
|
||||||
|
drawTicks: false,
|
||||||
|
display: false,
|
||||||
|
drawBorder: false
|
||||||
|
},
|
||||||
|
ticks: {
|
||||||
|
display: false,
|
||||||
|
maxTicksLimit: 5,
|
||||||
|
fontColor: '#7C8B9A',
|
||||||
|
padding: 8,
|
||||||
|
precision: 0
|
||||||
|
}
|
||||||
|
}],
|
||||||
|
xAxes: [{
|
||||||
|
gridLines: {
|
||||||
|
drawTicks: false,
|
||||||
|
display: false,
|
||||||
|
drawBorder: false
|
||||||
|
},
|
||||||
|
ticks: {
|
||||||
|
display: false,
|
||||||
|
maxTicksLimit: 5,
|
||||||
|
autoSkip: true,
|
||||||
|
maxRotation: 0,
|
||||||
|
minRotation: 0
|
||||||
|
},
|
||||||
|
type: 'time',
|
||||||
|
time: {
|
||||||
|
displayFormats: {
|
||||||
|
millisecond: 'MMM DD',
|
||||||
|
second: 'MMM DD',
|
||||||
|
minute: 'MMM DD',
|
||||||
|
hour: 'MMM DD',
|
||||||
|
day: 'MMM DD',
|
||||||
|
week: 'MMM DD',
|
||||||
|
month: 'MMM DD',
|
||||||
|
quarter: 'MMM DD',
|
||||||
|
year: 'MMM DD'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
calculatePercentage(from, to) {
|
||||||
|
if (from === 0) {
|
||||||
|
if (to > 0) {
|
||||||
|
return 100;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Math.round((to - from) / from * 100);
|
||||||
|
}
|
||||||
|
}
|
@ -1,13 +1,9 @@
|
|||||||
<h3 {{did-insert this.loadCharts}}>Email</h3>
|
<h3 {{did-insert this.loadCharts}}>Email</h3>
|
||||||
<div class="gh-dashboard5-insert">
|
<div class="gh-dashboard5-insert">
|
||||||
<div class="gh-dashboard5-box">
|
<div class="gh-dashboard5-box">
|
||||||
{{!-- <h4 class="gh-dashboard5-metric">Newsletter subscribers</h4> --}}
|
<div class="gh-dashboard5-number is-solo">
|
||||||
{{!-- <div class="gh-dashboard5-number is-small">{{format-number this.dataSubscribers.total}}</div> --}}
|
{{format-number this.dataSubscribers.total}}<small><span class="gh-dashboard5-slash">/</span>{{format-number this.dataSubscribers.paid}} paid</small>
|
||||||
{{!-- <div class="gh-dashboard5-number is-small">{{format-number this.dataSubscribers.paid}} paid</div> --}}
|
</div>
|
||||||
{{!-- <div class="gh-dashboard5-number is-small">{{format-number this.dataSubscribers.free}} free</div> --}}
|
|
||||||
|
|
||||||
<h4 class="gh-dashboard5-metric is-split">Email</h4>
|
|
||||||
<div class="gh-dashboard5-number">{{format-number this.dataSubscribers.total}}</div>
|
|
||||||
<small class="gh-dashboard5-info">Newsletter subscribers</small>
|
<small class="gh-dashboard5-info">Newsletter subscribers</small>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -23,11 +19,10 @@
|
|||||||
<EmberChart
|
<EmberChart
|
||||||
@type={{this.chartType}}
|
@type={{this.chartType}}
|
||||||
@data={{this.chartData}}
|
@data={{this.chartData}}
|
||||||
@options={{this.chartOptions}}
|
@options={{this.chartOptions}} />
|
||||||
@height={{this.chartHeight}} />
|
|
||||||
{{/if}}
|
{{/if}}
|
||||||
<div>
|
<div>
|
||||||
<div class="gh-dashboard5-number">58%</div>
|
<div class="gh-dashboard5-number">{{this.currentOpenRate}}%</div>
|
||||||
<small class="gh-dashboard5-info">Open rate</small>
|
<small class="gh-dashboard5-info">Open rate</small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -25,6 +25,14 @@ export default class ChartEmailOpenRate extends Component {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get currentOpenRate() {
|
||||||
|
if (this.dashboardStats.emailOpenRateStats === null || this.dashboardStats.emailOpenRateStats.length === 0) {
|
||||||
|
return '-';
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.dashboardStats.emailOpenRateStats[this.dashboardStats.emailOpenRateStats.length - 1].openRate;
|
||||||
|
}
|
||||||
|
|
||||||
get dataEmailsSent() {
|
get dataEmailsSent() {
|
||||||
return this.dashboardStats.emailsSent30d ?? 0;
|
return this.dashboardStats.emailsSent30d ?? 0;
|
||||||
}
|
}
|
||||||
@ -62,6 +70,8 @@ export default class ChartEmailOpenRate extends Component {
|
|||||||
|
|
||||||
get chartOptions() {
|
get chartOptions() {
|
||||||
return {
|
return {
|
||||||
|
responsive: true,
|
||||||
|
maintainAspectRatio: true,
|
||||||
title: {
|
title: {
|
||||||
display: false
|
display: false
|
||||||
},
|
},
|
||||||
|
@ -3,8 +3,7 @@
|
|||||||
<article class="gh-dashboard5-box">
|
<article class="gh-dashboard5-box">
|
||||||
<div class="gh-dashboard5-insert">
|
<div class="gh-dashboard5-insert">
|
||||||
<div class="gh-dashboard5-box">
|
<div class="gh-dashboard5-box">
|
||||||
<h4 class="gh-dashboard5-metric is-split">Engagement</h4>
|
<div class="gh-dashboard5-number is-solo">{{this.data30Days}}</div>
|
||||||
<div class="gh-dashboard5-number">{{this.data30Days}}</div>
|
|
||||||
<small class="gh-dashboard5-info">Last 30 days</small>
|
<small class="gh-dashboard5-info">Last 30 days</small>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -1,16 +1,16 @@
|
|||||||
<h4
|
<small
|
||||||
class="gh-dashboard5-metric is-main"
|
class="gh-dashboard5-info is-solo"
|
||||||
{{did-insert this.loadCharts}}
|
{{did-insert this.loadCharts}}
|
||||||
>
|
>Paid members</small>
|
||||||
Paid members
|
|
||||||
</h4>
|
|
||||||
|
|
||||||
{{#if this.loading}}
|
{{#if this.loading}}
|
||||||
<div class="gh-dashboard5-loading" style={{html-safe (concat "height: " this.chartHeight "px;")}}/>
|
<div class="gh-dashboard5-loading" style={{html-safe (concat "height: " this.chartHeight "px;")}}/>
|
||||||
{{else}}
|
{{else}}
|
||||||
|
<div class="gh-dashboard5-chart">
|
||||||
<EmberChart
|
<EmberChart
|
||||||
@type={{this.chartType}}
|
@type={{this.chartType}}
|
||||||
@data={{this.chartData}}
|
@data={{this.chartData}}
|
||||||
@options={{this.chartOptions}}
|
@options={{this.chartOptions}}
|
||||||
@height={{this.chartHeight}} />
|
@height={{150}} />
|
||||||
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
@ -53,6 +53,9 @@ export default class ChartPaidMembers extends Component {
|
|||||||
|
|
||||||
get chartOptions() {
|
get chartOptions() {
|
||||||
return {
|
return {
|
||||||
|
animation: {
|
||||||
|
duration: 0
|
||||||
|
},
|
||||||
title: {
|
title: {
|
||||||
display: false
|
display: false
|
||||||
},
|
},
|
||||||
@ -105,8 +108,4 @@ export default class ChartPaidMembers extends Component {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
get chartHeight() {
|
|
||||||
return 100;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -17,14 +17,18 @@
|
|||||||
</div>
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
<h4 class="gh-dashboard5-metric is-main" {{did-insert this.loadCharts}}>Paid mix</h4>
|
<small
|
||||||
|
class="gh-dashboard5-info is-solo"
|
||||||
|
{{did-insert this.loadCharts}}
|
||||||
|
>Paid mix</small>
|
||||||
|
|
||||||
{{#if this.loading}}
|
{{#if this.loading}}
|
||||||
<div class="gh-dashboard5-loading" style={{html-safe (concat "height: " this.chartHeight "px;")}}/>
|
<div class="gh-dashboard5-loading" style={{html-safe (concat "height: " this.chartHeight "px;")}}/>
|
||||||
{{else}}
|
{{else}}
|
||||||
|
<div class="gh-dashboard5-chart">
|
||||||
<EmberChart
|
<EmberChart
|
||||||
@type={{this.chartType}}
|
@type={{this.chartType}}
|
||||||
@data={{this.chartData}}
|
@data={{this.chartData}}
|
||||||
@options={{this.chartOptions}}
|
@options={{this.chartOptions}} />
|
||||||
@height={{this.chartHeight}} />
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
@ -90,11 +90,12 @@ export default class ChartPaidMix extends Component {
|
|||||||
return {
|
return {
|
||||||
legend: {
|
legend: {
|
||||||
display: false
|
display: false
|
||||||
|
},
|
||||||
|
responsive: true,
|
||||||
|
maintainAspectRatio: false,
|
||||||
|
animation: {
|
||||||
|
duration: 0
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
get chartHeight() {
|
|
||||||
return 100;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,3 @@
|
|||||||
{{!-- <h4
|
|
||||||
class="gh-dashboard5-metric is-main"
|
|
||||||
{{did-insert this.loadCharts}}
|
|
||||||
{{did-update this.loadCharts @days}}
|
|
||||||
>
|
|
||||||
Total members
|
|
||||||
</h4> --}}
|
|
||||||
{{#if this.loading}}
|
{{#if this.loading}}
|
||||||
<div {{did-insert this.loadCharts}} class="gh-dashboard5-loading" style={{html-safe (concat "height: " this.chartHeight "px;")}}/>
|
<div {{did-insert this.loadCharts}} class="gh-dashboard5-loading" style={{html-safe (concat "height: " this.chartHeight "px;")}}/>
|
||||||
{{else}}
|
{{else}}
|
||||||
|
@ -157,6 +157,15 @@ export default class DashboardStatsService extends Service {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get currentMRR() {
|
||||||
|
if (!this.mrrStats) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const stat = this.mrrStats[this.mrrStats.length - 1];
|
||||||
|
return stat.mrr;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type {?MemberCounts}
|
* @type {?MemberCounts}
|
||||||
*/
|
*/
|
||||||
@ -192,6 +201,30 @@ export default class DashboardStatsService extends Service {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get currentMRRTrend() {
|
||||||
|
if (!this.mrrStats) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.chartDays === 'all') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search for the value at chartDays ago (if any, else the first before it, or the next one if not one before it)
|
||||||
|
const searchDate = moment().add(-this.chartDays, 'days').format('YYYY-MM-DD');
|
||||||
|
|
||||||
|
for (let index = this.mrrStats.length - 1; index >= 0; index -= 1) {
|
||||||
|
const stat = this.mrrStats[index];
|
||||||
|
if (stat.date <= searchDate) {
|
||||||
|
return stat.mrr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We don't have any statistic from more than x days ago.
|
||||||
|
// Return all zero values
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
get filledMemberCountStats() {
|
get filledMemberCountStats() {
|
||||||
if (this.memberCountStats === null) {
|
if (this.memberCountStats === null) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -995,7 +995,7 @@ a.gh-dashboard-container {
|
|||||||
.prototype-paid-mix-dropdown {
|
.prototype-paid-mix-dropdown {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 15px;
|
right: 15px;
|
||||||
top: 15px;
|
top: 25px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.prototype-counts {
|
.prototype-counts {
|
||||||
@ -1093,15 +1093,16 @@ a.gh-dashboard-container {
|
|||||||
|
|
||||||
.gh-dashboard5-number {
|
.gh-dashboard5-number {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: flex-start;
|
align-items: flex-end;
|
||||||
font-size: 3rem;
|
font-size: 3rem;
|
||||||
line-height: 4rem;
|
line-height: 4rem;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: #15171a;
|
color: var(--black);
|
||||||
letter-spacing: -.1px;
|
letter-spacing: -.1px;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
margin: 0 0 4px;
|
margin: 0 0 4px;
|
||||||
|
gap: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.gh-dashboard5-number.is-small {
|
.gh-dashboard5-number.is-small {
|
||||||
@ -1109,7 +1110,17 @@ a.gh-dashboard-container {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.gh-dashboard5-number.is-solo {
|
.gh-dashboard5-number.is-solo {
|
||||||
margin-top: 46px;
|
margin-top: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gh-dashboard5-number small {
|
||||||
|
font-size: 1.4rem;
|
||||||
|
padding: 0 0 0.25rem;
|
||||||
|
color: var(--darkgrey);
|
||||||
|
}
|
||||||
|
|
||||||
|
.gh-dashboard5-slash {
|
||||||
|
margin: 0 0.5em 0 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.gh-dashboard5-metric {
|
.gh-dashboard5-metric {
|
||||||
@ -1157,6 +1168,10 @@ a.gh-dashboard-container {
|
|||||||
color: var(--midgrey);
|
color: var(--midgrey);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.gh-dashboard5-info.is-solo {
|
||||||
|
margin: 0 0 20px;
|
||||||
|
}
|
||||||
|
|
||||||
.gh-dashboard5-percentage {
|
.gh-dashboard5-percentage {
|
||||||
flex: 0;
|
flex: 0;
|
||||||
background: var(--whitegrey-d1);
|
background: var(--whitegrey-d1);
|
||||||
@ -1167,7 +1182,7 @@ a.gh-dashboard-container {
|
|||||||
letter-spacing: 0;
|
letter-spacing: 0;
|
||||||
color: var(--midgrey);
|
color: var(--midgrey);
|
||||||
padding: 2px 4px;
|
padding: 2px 4px;
|
||||||
margin: 5px 0 1px 0;
|
margin: 5px 0 3px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.gh-dashboard5-percentage.is-positive {
|
.gh-dashboard5-percentage.is-positive {
|
||||||
@ -1233,10 +1248,7 @@ a.gh-dashboard-container {
|
|||||||
} */
|
} */
|
||||||
|
|
||||||
.gh-dashboard5-growth {
|
.gh-dashboard5-growth {
|
||||||
display: grid;
|
display: flex;
|
||||||
grid-template-columns: 1fr 1fr 1fr 1fr;
|
|
||||||
grid-template-rows: auto auto;
|
|
||||||
gap: 0;
|
|
||||||
margin-top: -25px;
|
margin-top: -25px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1248,22 +1260,24 @@ a.gh-dashboard-container {
|
|||||||
} */
|
} */
|
||||||
|
|
||||||
.gh-dashboard5-growth .gh-dashboard5-box:nth-child(1) {
|
.gh-dashboard5-growth .gh-dashboard5-box:nth-child(1) {
|
||||||
grid-column: 1 / span 2;
|
/* grid-column: 1;
|
||||||
grid-row: 1 / span 2;
|
grid-row: 1; */
|
||||||
|
flex: 2;
|
||||||
border-radius: 3px 0 0 0;
|
border-radius: 3px 0 0 0;
|
||||||
border-width: 1px;
|
border-width: 1px;
|
||||||
padding-top: 32px;
|
padding-top: 32px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.gh-dashboard5-growth .gh-dashboard5-box:nth-child(2) {
|
.gh-dashboard5-growth .gh-dashboard5-box:nth-child(2) {
|
||||||
grid-column: 3 / span 2;
|
/* grid-column: 2;
|
||||||
grid-row: 1;
|
grid-row: 1; */
|
||||||
|
flex: 1;
|
||||||
border-radius: 0 3px 0 0;
|
border-radius: 0 3px 0 0;
|
||||||
border-width: 1px 1px 1px 0;
|
border-width: 1px 1px 1px 0;
|
||||||
padding-top: 32px;
|
padding-top: 32px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.gh-dashboard5-growth .gh-dashboard5-box:nth-child(3) {
|
/* .gh-dashboard5-growth .gh-dashboard5-box:nth-child(3) {
|
||||||
grid-column: 3;
|
grid-column: 3;
|
||||||
grid-row: 2;
|
grid-row: 2;
|
||||||
border-radius: 0 0 0 3px;
|
border-radius: 0 0 0 3px;
|
||||||
@ -1275,6 +1289,15 @@ a.gh-dashboard-container {
|
|||||||
grid-row: 2;
|
grid-row: 2;
|
||||||
border-radius: 0 0 3px 0;
|
border-radius: 0 0 3px 0;
|
||||||
border-width: 0 1px 1px 0;
|
border-width: 0 1px 1px 0;
|
||||||
|
} */
|
||||||
|
|
||||||
|
.gh-dashboard5-growth {
|
||||||
|
height: 250px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gh-dashboard5-growth canvas {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.gh-dashboard5 .gh-dashboard-box {
|
.gh-dashboard5 .gh-dashboard-box {
|
||||||
@ -1355,11 +1378,18 @@ a.gh-dashboard-container {
|
|||||||
padding: 8px 24px 0 0;
|
padding: 8px 24px 0 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.gh-dashboard5-engagement .gh-dashboard5-insert .gh-dashboard5-box:first-child {
|
||||||
|
justify-content: flex-end;
|
||||||
|
padding-top: 0;
|
||||||
|
padding-bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
.gh-dashboard5-engagement .gh-dashboard5-insert .gh-dashboard5-box:last-child {
|
.gh-dashboard5-engagement .gh-dashboard5-insert .gh-dashboard5-box:last-child {
|
||||||
border-left-width: 0;
|
border-left-width: 0;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
padding-left: 24px;
|
padding-top: 24px;
|
||||||
|
padding-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.gh-dashboard5-split .gh-dashboard5-engagement .gh-dashboard5-insert {
|
.gh-dashboard5-split .gh-dashboard5-engagement .gh-dashboard5-insert {
|
||||||
@ -1375,12 +1405,18 @@ a.gh-dashboard-container {
|
|||||||
padding: 8px 24px 24px 0;
|
padding: 8px 24px 24px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.gh-dashboard5-split .gh-dashboard5-engagement .gh-dashboard5-insert .gh-dashboard5-box:first-child {
|
||||||
|
padding-top: 0;
|
||||||
|
padding-bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
.gh-dashboard5-split .gh-dashboard5-engagement .gh-dashboard5-insert .gh-dashboard5-box:last-child {
|
.gh-dashboard5-split .gh-dashboard5-engagement .gh-dashboard5-insert .gh-dashboard5-box:last-child {
|
||||||
border-bottom-width: 0;
|
border-bottom-width: 0;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
|
padding-top: 24px;
|
||||||
padding-bottom: 0;
|
padding-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1475,3 +1511,45 @@ a.gh-dashboard-container {
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.gh-dashboard5-anchor {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gh-dashboard5-anchor > .gh-dashboard5-box {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gh-dashboard5-stats {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-evenly;
|
||||||
|
width: 75%;
|
||||||
|
padding: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gh-dashboard5-stats > button {
|
||||||
|
flex: 1;
|
||||||
|
border-left: 2px solid #D6EDF9;
|
||||||
|
padding: 2px 0 0 12px;
|
||||||
|
margin: 0 8px 0 0;
|
||||||
|
transition: all 100ms ease-out;
|
||||||
|
cursor: pointer;
|
||||||
|
display: block;
|
||||||
|
text-align: left;
|
||||||
|
padding-left: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gh-dashboard5-stats > button.is-selected {
|
||||||
|
border-color: #14b8ff;
|
||||||
|
border-width: 3px;
|
||||||
|
padding-left: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gh-dashboard5-chart {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: stretch;
|
||||||
|
width: 100%;
|
||||||
|
height: 200px;
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user