mirror of
https://github.com/flarum/framework.git
synced 2025-02-28 21:24:11 +08:00
193 lines
6.3 KiB
JavaScript
193 lines
6.3 KiB
JavaScript
/*
|
|
* This file is part of Flarum.
|
|
*
|
|
* (c) Toby Zerner <toby.zerner@gmail.com>
|
|
*
|
|
* For the full copyright and license information, please view the LICENSE
|
|
* file that was distributed with this source code.
|
|
*/
|
|
|
|
import DashboardWidget from 'flarum/components/DashboardWidget';
|
|
import SelectDropdown from 'flarum/components/SelectDropdown';
|
|
import Button from 'flarum/components/Button';
|
|
import icon from 'flarum/helpers/icon';
|
|
import abbreviateNumber from 'flarum/utils/abbreviateNumber';
|
|
|
|
import { Chart } from 'frappe-charts/dist/frappe-charts.esm.js';
|
|
|
|
export default class StatisticsWidget extends DashboardWidget {
|
|
init() {
|
|
super.init();
|
|
|
|
// Create a Date object which represents the start of the day in the
|
|
// configured timezone. To do this we convert a UTC time into that timezone,
|
|
// reset to the first hour of the day, and then convert back into UTC time.
|
|
// We'll be working with seconds rather than milliseconds throughout too.
|
|
let today = new Date();
|
|
today.setTime(today.getTime() + app.data.statistics.timezoneOffset * 1000);
|
|
today.setUTCHours(0, 0, 0, 0);
|
|
today.setTime(today.getTime() - app.data.statistics.timezoneOffset * 1000);
|
|
today = today / 1000;
|
|
|
|
this.entities = ['users', 'discussions', 'posts'];
|
|
this.periods = {
|
|
today: {start: today, end: today + 86400, step: 3600},
|
|
last_7_days: {start: today - 86400 * 7, end: today, step: 86400},
|
|
last_28_days: {start: today - 86400 * 28, end: today, step: 86400},
|
|
last_12_months: {start: today - 86400 * 364, end: today, step: 86400 * 7}
|
|
};
|
|
|
|
this.selectedEntity = 'users';
|
|
this.selectedPeriod = 'last_7_days';
|
|
}
|
|
|
|
className() {
|
|
return 'StatisticsWidget';
|
|
}
|
|
|
|
content() {
|
|
const thisPeriod = this.periods[this.selectedPeriod];
|
|
|
|
return (
|
|
<div className="StatisticsWidget-table">
|
|
<div className="StatisticsWidget-labels">
|
|
<div className="StatisticsWidget-label">{app.translator.trans('flarum-statistics.admin.statistics.total_label')}</div>
|
|
<div className="StatisticsWidget-label">
|
|
<SelectDropdown buttonClassName="Button Button--text" caretIcon="fas fa-caret-down">
|
|
{Object.keys(this.periods).map(period => (
|
|
<Button
|
|
active={period === this.selectedPeriod}
|
|
onclick={this.changePeriod.bind(this, period)}
|
|
icon={period === this.selectedPeriod ? 'fas fa-check' : true}>
|
|
{app.translator.trans(`flarum-statistics.admin.statistics.${period}_label`)}
|
|
</Button>
|
|
))}
|
|
</SelectDropdown>
|
|
</div>
|
|
</div>
|
|
|
|
{this.entities.map(entity => {
|
|
const totalCount = this.getTotalCount(entity);
|
|
const thisPeriodCount = this.getPeriodCount(entity, thisPeriod);
|
|
const lastPeriodCount = this.getPeriodCount(entity, this.getLastPeriod(thisPeriod));
|
|
const periodChange = lastPeriodCount > 0 && (thisPeriodCount - lastPeriodCount) / lastPeriodCount * 100;
|
|
|
|
return (
|
|
<a className={'StatisticsWidget-entity'+(this.selectedEntity === entity ? ' active' : '')} onclick={this.changeEntity.bind(this, entity)}>
|
|
<h3 className="StatisticsWidget-heading">{app.translator.trans('flarum-statistics.admin.statistics.'+entity+'_heading')}</h3>
|
|
<div className="StatisticsWidget-total" title={totalCount}>{abbreviateNumber(totalCount)}</div>
|
|
<div className="StatisticsWidget-period" title={thisPeriodCount}>
|
|
{abbreviateNumber(thisPeriodCount)}{' '}
|
|
{periodChange ? (
|
|
<span className={'StatisticsWidget-change StatisticsWidget-change--'+(periodChange > 0 ? 'up' : 'down')}>
|
|
{icon('fas fa-arrow-'+(periodChange > 0 ? 'up' : 'down'))}
|
|
{Math.abs(periodChange.toFixed(1))}%
|
|
</span>
|
|
) : ''}
|
|
</div>
|
|
</a>
|
|
);
|
|
})}
|
|
|
|
<div className="StatisticsWidget-chart" config={this.drawChart.bind(this)}/>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
drawChart(elm, isInitialized, context) {
|
|
if (context.chart && context.entity === this.selectedEntity && context.period === this.selectedPeriod) {
|
|
return;
|
|
}
|
|
|
|
const offset = app.data.statistics.timezoneOffset;
|
|
const period = this.periods[this.selectedPeriod];
|
|
const periodLength = period.end - period.start;
|
|
const labels = [];
|
|
const thisPeriod = [];
|
|
const lastPeriod = [];
|
|
|
|
for (let i = period.start; i < period.end; i += period.step) {
|
|
let label;
|
|
|
|
if (period.step < 86400) {
|
|
label = moment.unix(i + offset).utc().format('h A');
|
|
} else {
|
|
label = moment.unix(i + offset).utc().format('D MMM');
|
|
|
|
if (period.step > 86400) {
|
|
label += ' - ' + moment.unix(i + offset + period.step - 1).utc().format('D MMM');
|
|
}
|
|
}
|
|
|
|
labels.push(label);
|
|
|
|
thisPeriod.push(this.getPeriodCount(this.selectedEntity, {start: i, end: i + period.step}));
|
|
|
|
lastPeriod.push(this.getPeriodCount(this.selectedEntity, {start: i - periodLength, end: i - periodLength + period.step}));
|
|
}
|
|
|
|
const datasets = [
|
|
{values: lastPeriod},
|
|
{values: thisPeriod}
|
|
];
|
|
const data = {
|
|
labels,
|
|
datasets
|
|
};
|
|
|
|
if (!context.chart) {
|
|
context.chart = new Chart(elm, {
|
|
data,
|
|
type: 'line',
|
|
height: 280,
|
|
axisOptions: {
|
|
xAxisMode: 'tick',
|
|
yAxisMode: 'span',
|
|
xIsSeries: true
|
|
},
|
|
lineOptions: {
|
|
hideDots: 1
|
|
},
|
|
colors: ['black', app.forum.attribute('themePrimaryColor')]
|
|
});
|
|
} else {
|
|
context.chart.update(data);
|
|
}
|
|
|
|
context.entity = this.selectedEntity;
|
|
context.period = this.selectedPeriod;
|
|
}
|
|
|
|
changeEntity(entity) {
|
|
this.selectedEntity = entity;
|
|
}
|
|
|
|
changePeriod(period) {
|
|
this.selectedPeriod = period;
|
|
}
|
|
|
|
getTotalCount(entity) {
|
|
return app.data.statistics[entity].total;
|
|
}
|
|
|
|
getPeriodCount(entity, period) {
|
|
const timed = app.data.statistics[entity].timed;
|
|
let count = 0;
|
|
|
|
for (const time in timed) {
|
|
if (time >= period.start && time < period.end) {
|
|
count += timed[time];
|
|
}
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
getLastPeriod(thisPeriod) {
|
|
return {
|
|
start: thisPeriod.start - (thisPeriod.end - thisPeriod.start),
|
|
end: thisPeriod.start
|
|
};
|
|
}
|
|
}
|