From c76b4594d811714128df0e37af23599a950b63af Mon Sep 17 00:00:00 2001 From: Nikolai R Kristiansen Date: Fri, 23 Sep 2016 01:01:19 +0200 Subject: [PATCH 1/4] Stats: Add start date filter --- stats/index.php | 190 ++++++++++-------------------------------------- stats/stats.css | 4 + stats/stats.js | 129 ++++++++++++++++++++++++++++++++ 3 files changed, 171 insertions(+), 152 deletions(-) create mode 100644 stats/stats.css create mode 100644 stats/stats.js diff --git a/stats/index.php b/stats/index.php index 049792c..c4b09fd 100644 --- a/stats/index.php +++ b/stats/index.php @@ -1,5 +1,9 @@ date_create()) { + $semester_start = date_modify($semester_start, '-1 year'); + } + $start = isset($_GET['start']) ? date_create($_GET['start']) : $semester_start; ?> @@ -7,163 +11,45 @@ Medlemsstatistikk - Det Norske Studentersamfund - - - - - - - + + +
-

Salg per dag

- Fra 1. august 2015 -
-

Salg totalt

-
-
SMS:
-
App:
-
Bar:
-
Totalt:
+
+
+

Salg per dag

+ +
+

Salg totalt

+
+
SMS:
+
App:
+
Bar:
+
Totalt:
+
+

Salg idag

+ Dato: +
+
+
SMS:
+
App:
+
Bar:
+
Totalt:
+
+



Disse tallene kan avvike fra faktiske salgtall.
+
-

Salg idag

- Dato: -
-
-
SMS:
-
App:
-
Bar:
-
Totalt:
-
- -



Disse tallene kan avvike fra faktiske salgtall.
+ + + + + + + diff --git a/stats/stats.css b/stats/stats.css new file mode 100644 index 0000000..6353e5b --- /dev/null +++ b/stats/stats.css @@ -0,0 +1,4 @@ +.big-number { + font-weight: bold; + font-size: 1.5em; +} \ No newline at end of file diff --git a/stats/stats.js b/stats/stats.js new file mode 100644 index 0000000..7278bf8 --- /dev/null +++ b/stats/stats.js @@ -0,0 +1,129 @@ +var barSales; +var appSales; +var smsSales; +var salesChart; +var salesChartToday; + +var urls = { + cards: 'https://kassa.neuf.no/stats/card-sales/', + tekstmelding: 'https://tekstmelding.neuf.no/stats/memberships/', + snapporder: '/stats/snapporder.php' +}; + +function sumSales(memo, num) { + return memo + num.sales; +} + +function toHighchartsSeries(memberships) { + return _.map(memberships, function (el) { + return [moment.utc(el.date).valueOf(), el.sales] + }); +} + +function getBarSales(start) { + return $.getJSON(urls.cards + '?start=' + start, function(data) { + barSales = data.memberships; + salesChart.addSeries({name: 'Bar-salg', data: toHighchartsSeries(barSales)}); + }); +} + +function getSmsSales(start) { + return $.getJSON(urls.tekstmelding + '?start=' + start, function(data) { + smsSales = data.memberships; + salesChart.addSeries({name: 'SMS-salg', data: toHighchartsSeries(smsSales)}); + }); +} + +function getAppSales(start) { + return $.getJSON(urls.snapporder + '?start=' + start, function (data) { + appSales = data.memberships; + salesChart.addSeries({name: 'App-salg', data: toHighchartsSeries(appSales)}); + }); +} + +function recalc(start) { + salesChart = new Highcharts.Chart({ + chart: { + renderTo: 'sales-chart' + }, + title: { + text: false + }, + xAxis: { + type: "datetime" + }, + yAxis: { + title: { + text: "Salg" + }, + min: 0 + }, + plotOptions: { + series: { + marker: { + radius: 4 + } + } + }, + series: [] + }); + + $.when(getSmsSales(start), getAppSales(start), getBarSales(start)).done(function() { + /* Totals */ + var barTotal = _.reduce(barSales, sumSales, 0); + $('.bar').html(barTotal); + var smsTotal = _.reduce(smsSales, sumSales, 0); + $('.sms').html(smsTotal); + var appTotal = _.reduce(appSales, sumSales, 0); + $('.app').html(appTotal); + + $('.sum').html(barTotal + smsTotal + appTotal); + + /* Today */ + var today = moment.utc().format('YYYY-MM-DD'); + $('.today-date-wrap').text(today); + var todayBarSales = _.findWhere(barSales, {date:today}) || {sales: 0}; + $('.bar-today').html(todayBarSales.sales); + var todaySmsSales = _.findWhere(smsSales, {date:today}) || {sales: 0}; + $('.sms-today').html(todaySmsSales.sales); + var todayAppSales = _.findWhere(appSales, {date:today}) || {sales: 0}; + $('.app-today').html(todayAppSales.sales); + + $('.sum-today').html(todayBarSales.sales + todaySmsSales.sales + todayAppSales.sales); + + salesChartToday = new Highcharts.Chart({ + chart: { + renderTo: 'sales-chart-today', + plotBackgroundColor: null, + plotBorderWidth: null, + plotShadow: false, + type: 'pie' + }, + title: { + text: false + }, + series: [{ + name: 'Salg', data: [ + {name: 'Bar-salg', y: todayBarSales.sales}, + {name: 'SMS-salg', y: todaySmsSales.sales}, + {name: 'App-salg', y: todayAppSales.sales} + ]} + ] + }); + }); +} + +$(document).ready(function() { + Highcharts.setOptions({ + credits: {enabled: false} + }); + + var $startInput = $('#start'); + var start = $startInput.val(); + recalc(start); + + $startInput.on('input', function(e) { + start = $(e.target).val(); + recalc(start); + }) +}); From e2bab9ab8d6ea626a7c86a871c4bd3646295d490 Mon Sep 17 00:00:00 2001 From: Nikolai R Kristiansen Date: Fri, 23 Sep 2016 01:05:00 +0200 Subject: [PATCH 2/4] stats: put start date in url --- stats/stats.js | 1 + 1 file changed, 1 insertion(+) diff --git a/stats/stats.js b/stats/stats.js index 7278bf8..030fb11 100644 --- a/stats/stats.js +++ b/stats/stats.js @@ -124,6 +124,7 @@ $(document).ready(function() { $startInput.on('input', function(e) { start = $(e.target).val(); + history.replaceState(null, null, '?start=' + start); recalc(start); }) }); From 25563043f8c8e28786f8b23bc10a9ecb1b3c5355 Mon Sep 17 00:00:00 2001 From: Nikolai R Kristiansen Date: Sat, 24 Sep 2016 13:49:31 +0200 Subject: [PATCH 3/4] feat(stats): Add table with export to csv --- stats/index.php | 29 ++++++-- stats/stats.js | 182 ++++++++++++++++++++++++++++++++++++------------ 2 files changed, 162 insertions(+), 49 deletions(-) diff --git a/stats/index.php b/stats/index.php index c4b09fd..d394ebf 100644 --- a/stats/index.php +++ b/stats/index.php @@ -1,9 +1,14 @@ 0 && date_create($_GET['start']); + } + $semester_start = date_create('first day of august'); if($semester_start > date_create()) { $semester_start = date_modify($semester_start, '-1 year'); } - $start = isset($_GET['start']) ? date_create($_GET['start']) : $semester_start; + $start = is_valid_start_param() ? date_create($_GET['start']) : $semester_start; ?> @@ -13,23 +18,33 @@ -
-
+
+

Medlemskapsstatistikk for DNS

+
Disse tallene kan avvike fra faktiske salgtall.
+
+
+
+
+

Salg per dag

-

Salg totalt

+ +

Salg totalt

SMS:
App:
Bar:
Totalt:
+ +
+

Salg idag

Dato:
@@ -39,7 +54,11 @@
Bar:
Totalt:
-



Disse tallene kan avvike fra faktiske salgtall.
+
+
+

Salg per dag

+ +
diff --git a/stats/stats.js b/stats/stats.js index 030fb11..7b1c20f 100644 --- a/stats/stats.js +++ b/stats/stats.js @@ -1,8 +1,10 @@ var barSales; var appSales; var smsSales; +var salesData; var salesChart; var salesChartToday; +var saleTypes = ['bar', 'sms', 'app']; var urls = { cards: 'https://kassa.neuf.no/stats/card-sales/', @@ -15,6 +17,7 @@ function sumSales(memo, num) { } function toHighchartsSeries(memberships) { + memberships = _.sortBy(memberships, 'date'); return _.map(memberships, function (el) { return [moment.utc(el.date).valueOf(), el.sales] }); @@ -41,7 +44,119 @@ function getAppSales(start) { }); } -function recalc(start) { +function totals() { + var barTotal = _.reduce(barSales, sumSales, 0); + $('.bar').html(barTotal); + var smsTotal = _.reduce(smsSales, sumSales, 0); + $('.sms').html(smsTotal); + var appTotal = _.reduce(appSales, sumSales, 0); + $('.app').html(appTotal); + + $('.sum').html(barTotal + smsTotal + appTotal); +} + +function today() { + var today = moment.utc().format('YYYY-MM-DD'); + $('.today-date-wrap').text(today); + var todayBarSales = _.findWhere(barSales, {date: today}) || {sales: 0}; + $('.bar-today').html(todayBarSales.sales); + var todaySmsSales = _.findWhere(smsSales, {date: today}) || {sales: 0}; + $('.sms-today').html(todaySmsSales.sales); + var todayAppSales = _.findWhere(appSales, {date: today}) || {sales: 0}; + $('.app-today').html(todayAppSales.sales); + + $('.sum-today').html(todayBarSales.sales + todaySmsSales.sales + todayAppSales.sales); + + salesChartToday = new Highcharts.Chart({ + chart: { + renderTo: 'sales-chart-today', + plotBackgroundColor: null, + plotBorderWidth: null, + plotShadow: false, + type: 'pie' + }, + title: { + text: false + }, + series: [{ + name: 'Salg', data: [ + {name: 'Bar-salg', y: todayBarSales.sales}, + {name: 'SMS-salg', y: todaySmsSales.sales}, + {name: 'App-salg', y: todayAppSales.sales} + ] + } + ] + }); +} + +function groupSalesByDate() { + var types = { + 'bar': barSales, + 'sms': smsSales, + 'app': appSales + }; + var salesByDate = {}; + _.each(types, function (source, type) { + _.each(source, function (el) { + var d = {}; + d[el.date] = { + 'bar': 0, + 'sms': 0, + 'app': 0 + }; + d[el.date][type]= el.sales; + + $.extend(true, salesByDate, d); + }); + }); + + var table_friendly = []; + _.each(salesByDate, function (sales, date) { + sales.date = date; + table_friendly.push(sales); + }); + table_friendly = _.sortBy(table_friendly, 'date').reverse(); + + return table_friendly; +} + +function salesTable() { + var html = 'DatoBarSMSApp'; + html += ''; + salesData = groupSalesByDate(); + + _.each(salesData, function(el) { + html += '' + el.date + ''; + _.each(saleTypes, function(type) { + html += '' + el[type] + ''; + }); + html += ''; + }); + html += ''; + $('#sales-table').html(html); +} + +function toCSV(data) { + var csvLines = data.map(function(d){ + return d.date + ',' + saleTypes.map(function(t) { return d[t]; }).join(','); + }); + var csvHeader = 'date,' +saleTypes.join(',') + '\n'; + return csvHeader + csvLines.join('\n') +} + +function downloadCSVFile(csvData, fileName) { + csvData = 'data:text/csv;charset=utf-8,' + csvData; + var encodedUri = encodeURI(csvData); + + var link = document.createElement("a"); + link.setAttribute("href", encodedUri); + link.setAttribute("download", fileName); + document.body.appendChild(link); // Required for FF + + link.click(); +} + +function recalc(start, cb) { salesChart = new Highcharts.Chart({ chart: { renderTo: 'sales-chart' @@ -69,47 +184,12 @@ function recalc(start) { }); $.when(getSmsSales(start), getAppSales(start), getBarSales(start)).done(function() { - /* Totals */ - var barTotal = _.reduce(barSales, sumSales, 0); - $('.bar').html(barTotal); - var smsTotal = _.reduce(smsSales, sumSales, 0); - $('.sms').html(smsTotal); - var appTotal = _.reduce(appSales, sumSales, 0); - $('.app').html(appTotal); + totals(); + salesTable(); - $('.sum').html(barTotal + smsTotal + appTotal); - - /* Today */ - var today = moment.utc().format('YYYY-MM-DD'); - $('.today-date-wrap').text(today); - var todayBarSales = _.findWhere(barSales, {date:today}) || {sales: 0}; - $('.bar-today').html(todayBarSales.sales); - var todaySmsSales = _.findWhere(smsSales, {date:today}) || {sales: 0}; - $('.sms-today').html(todaySmsSales.sales); - var todayAppSales = _.findWhere(appSales, {date:today}) || {sales: 0}; - $('.app-today').html(todayAppSales.sales); - - $('.sum-today').html(todayBarSales.sales + todaySmsSales.sales + todayAppSales.sales); - - salesChartToday = new Highcharts.Chart({ - chart: { - renderTo: 'sales-chart-today', - plotBackgroundColor: null, - plotBorderWidth: null, - plotShadow: false, - type: 'pie' - }, - title: { - text: false - }, - series: [{ - name: 'Salg', data: [ - {name: 'Bar-salg', y: todayBarSales.sales}, - {name: 'SMS-salg', y: todaySmsSales.sales}, - {name: 'App-salg', y: todayAppSales.sales} - ]} - ] - }); + if(cb) { + cb(); + } }); } @@ -118,13 +198,27 @@ $(document).ready(function() { credits: {enabled: false} }); + var $exportBtn = $('.export-data-btn'); var $startInput = $('#start'); - var start = $startInput.val(); - recalc(start); + var start = $startInput.val(); + recalc(start, function() { + today(); + }); + + /* Dynamic date change */ $startInput.on('input', function(e) { start = $(e.target).val(); history.replaceState(null, null, '?start=' + start); recalc(start); - }) + }); + + /* Export to CSV */ + $exportBtn.on('click', function (e) { + e.preventDefault(); + var fileName = 'medlemskapsstats-' + $startInput.val() + '.csv'; + var csvData = toCSV(salesData); + downloadCSVFile(csvData, fileName); + }); + }); From c5845afc3a8aba3964a0997fbb17a8387d3ab60d Mon Sep 17 00:00:00 2001 From: Nikolai R Kristiansen Date: Sat, 24 Sep 2016 14:08:58 +0200 Subject: [PATCH 4/4] fix(stats): Fix zero vals, minor refactor --- stats/stats.js | 64 ++++++++++++++++++++++++-------------------------- 1 file changed, 31 insertions(+), 33 deletions(-) diff --git a/stats/stats.js b/stats/stats.js index 7b1c20f..308c335 100644 --- a/stats/stats.js +++ b/stats/stats.js @@ -1,10 +1,9 @@ -var barSales; -var appSales; -var smsSales; -var salesData; +/* FIXME: Make this more generic */ +var saleTypes = ['bar', 'sms', 'app']; +var salesData = {}; + var salesChart; var salesChartToday; -var saleTypes = ['bar', 'sms', 'app']; var urls = { cards: 'https://kassa.neuf.no/stats/card-sales/', @@ -25,44 +24,44 @@ function toHighchartsSeries(memberships) { function getBarSales(start) { return $.getJSON(urls.cards + '?start=' + start, function(data) { - barSales = data.memberships; - salesChart.addSeries({name: 'Bar-salg', data: toHighchartsSeries(barSales)}); + salesData['bar'] = data.memberships; + salesChart.addSeries({name: 'Bar-salg', data: toHighchartsSeries(salesData['bar'])}); }); } function getSmsSales(start) { return $.getJSON(urls.tekstmelding + '?start=' + start, function(data) { - smsSales = data.memberships; - salesChart.addSeries({name: 'SMS-salg', data: toHighchartsSeries(smsSales)}); + salesData['sms'] = data.memberships; + salesChart.addSeries({name: 'SMS-salg', data: toHighchartsSeries(salesData['sms'])}); }); } function getAppSales(start) { return $.getJSON(urls.snapporder + '?start=' + start, function (data) { - appSales = data.memberships; - salesChart.addSeries({name: 'App-salg', data: toHighchartsSeries(appSales)}); + salesData['app'] = data.memberships; + salesChart.addSeries({name: 'App-salg', data: toHighchartsSeries(salesData['app'])}); }); } function totals() { - var barTotal = _.reduce(barSales, sumSales, 0); - $('.bar').html(barTotal); - var smsTotal = _.reduce(smsSales, sumSales, 0); - $('.sms').html(smsTotal); - var appTotal = _.reduce(appSales, sumSales, 0); - $('.app').html(appTotal); + var sum = 0; + _.each(saleTypes, function(type) { + var total = _.reduce(salesData[type], sumSales, 0); + $('.'+ type).html(total); + sum += total; + }) ; - $('.sum').html(barTotal + smsTotal + appTotal); + $('.sum').html(sum); } function today() { var today = moment.utc().format('YYYY-MM-DD'); $('.today-date-wrap').text(today); - var todayBarSales = _.findWhere(barSales, {date: today}) || {sales: 0}; + var todayBarSales = _.findWhere(salesData['bar'], {date: today}) || {sales: 0}; $('.bar-today').html(todayBarSales.sales); - var todaySmsSales = _.findWhere(smsSales, {date: today}) || {sales: 0}; + var todaySmsSales = _.findWhere(salesData['sms'], {date: today}) || {sales: 0}; $('.sms-today').html(todaySmsSales.sales); - var todayAppSales = _.findWhere(appSales, {date: today}) || {sales: 0}; + var todayAppSales = _.findWhere(salesData['app'], {date: today}) || {sales: 0}; $('.app-today').html(todayAppSales.sales); $('.sum-today').html(todayBarSales.sales + todaySmsSales.sales + todayAppSales.sales); @@ -90,25 +89,24 @@ function today() { } function groupSalesByDate() { - var types = { - 'bar': barSales, - 'sms': smsSales, - 'app': appSales - }; var salesByDate = {}; - _.each(types, function (source, type) { - _.each(source, function (el) { + _.each(saleTypes, function (type) { + _.each(salesData[type], function (el) { var d = {}; - d[el.date] = { - 'bar': 0, - 'sms': 0, - 'app': 0 - }; + d[el.date] = {}; d[el.date][type]= el.sales; $.extend(true, salesByDate, d); }); }); + /* add zeros */ + _.each(salesByDate, function (sales, date) { + _.each(saleTypes, function (type) { + if(typeof sales[type] == 'undefined') { + salesByDate[date][type] = 0; + } + }); + }); var table_friendly = []; _.each(salesByDate, function (sales, date) {