{"version":3,"file":"legacy.stats.B55uf0mW.js","names":[],"sources":["../../src/legacy/highcharts/hcbasetheme.es6.js","../../src/legacy/highcharts/themes/hcareaspline.es6.js","../../src/legacy/highcharts/themes/hcsplinecolumn.es6.js","../../src/legacy/highcharts/themes/hcgroupedcolumns.es6.js","../../src/legacy/react/stats/utils/poller.es6.js","../../src/legacy/react/stats/utils/odo.es6.js","../../src/legacy/stats.es6.js","../../src/legacy/react/stats/components/HeaderView.js","../../src/legacy/react/stats/components/ItemView.js","../../src/legacy/react/stats/components/RowView.js","../../src/legacy/react/stats/components/LinearTableView.js","../../src/legacy/react/stats/components/LabelView.js","../../src/legacy/react/stats/components/DataView.js","../../src/legacy/react/stats/components/ColumnView.js","../../src/legacy/react/stats/components/ComparisonTableView.js","../../src/legacy/react/stats/components/stats.js"],"sourcesContent":["'use strict';\n\nclass HCBaseTheme {\n constructor() {\n let options = {\n chart: {\n height: 240,\n style: {\n fontFamily: '\"Helvetica\", \"Arial\", sans-serif'\n }\n },\n credits: {\n enabled: false\n },\n exporting: {\n enabled: false\n },\n title: {\n text: ''\n },\n plotOptions: {\n series: {\n pointPadding: 0,\n groupPadding: 0\n }\n },\n xAxis: {\n allowDecimals: false,\n tickLength: 0,\n title: null,\n labels: {\n groupedOptions: [{\n style: {\n color: '#222222',\n fontSize: '12px',\n fontWeight: 200\n }\n }],\n style: {\n color: '#222222',\n fontSize: '12px',\n fontWeight: 200\n }\n }\n },\n yAxis: {\n allowDecimals: false,\n endOnTick: true,\n title: null,\n gridLineColor: '#F3F3F3',\n showFirstLabel: false,\n tickPixelInterval: 50,\n labels: {\n style: {\n color: '#222222',\n fontSize: '12px',\n fontWeight: 200\n }\n }\n },\n tooltip: {\n shared: false,\n backgroundColor: '#BBBBBB',\n borderRadius: 12,\n borderWidth: 0,\n shadow: false,\n useHTML: true,\n style: {\n color: '#FFFFFF'\n }\n },\n legend: {\n enabled: false\n }\n };\n\n Highcharts.setOptions(options);\n }\n\n drawChart(container, options, callback) {\n $(() => { // Prevents errors with undefined $(container).highcharts\n $(container).highcharts(options, callback);\n this.chart = $(container).highcharts();\n })\n }\n\n setAdditionalOptions(options) {\n $.extend(true, this.options, options);\n return this;\n }\n\n // Definitions for formatters that are/might be reused\n static simpleFormattedNumberTooltip() {\n return `
${accounting.formatNumber(this.y)}
`;\n }\n\n static groupedColumnsTooltip() {\n const leftTranslations = this.series.chart.series[0].options.translations;\n const rightTranslations = this.series.chart.series[1].options.translations;\n\n const leftValue = this.series.chart.series[0].points[this.point.index].y;\n const rightValue = this.series.chart.series[1].points[this.point.index].y;\n\n const leftLabel = Stats.pluralize(leftTranslations[0], leftTranslations[1], leftValue)\n const rightLabel = Stats.pluralize(rightTranslations[0], rightTranslations[1], rightValue);\n\n const gap = accounting.formatNumber((rightValue / leftValue) * 100, 2);\n\n return `${leftValue} ${leftLabel}
\n${rightValue} ${rightLabel}
\n${gap}%
`;\n }\n}\n\nexport default HCBaseTheme;\n","/* global $ */\n'use strict;'\n\nimport HCBaseTheme from '../hcbasetheme.es6.js';\n\nclass HCAreaSpline extends HCBaseTheme {\n constructor(container) {\n super();\n\n this.container = container;\n\n this.options = {\n chart: {\n type: 'areaspline'\n },\n plotOptions: {\n areaspline: {\n color: {\n linearGradient: { x1: 0, x2: 1, y1: 0, y2: 0 },\n stops: [\n [0, '#D6F1FD'],\n [1, '#64C9F5']\n ]\n },\n marker: {\n enabled: false,\n states: {\n hover: {\n enabled: false\n }\n }\n },\n states: {\n hover: {\n lineWidthPlus: 0\n }\n }\n }\n },\n yAxis: {\n labels: {\n format: '{value}'\n }\n }\n };\n\n this.paths = [];\n this._drawAreaColumns = this._drawAreaColumns.bind(this);\n\n this._resizeEventHandler();\n }\n\n drawChart() {\n super.drawChart(this.container, this.options, this._drawAreaColumns);\n return this;\n }\n\n _resizeEventHandler() {\n let resizeTimer;\n\n $(window).on('resize', (e) => {\n clearTimeout(resizeTimer);\n resizeTimer = setTimeout(() => {\n this._drawAreaColumns($(this.container).highcharts());\n }, 150);\n });\n }\n\n _drawAreaColumns(chart) {\n (this.paths).forEach((path, index) => {\n path.element.remove();\n });\n\n (chart.series[0].data).forEach((point, index) => {\n const pos = chart.plotBox.x + point.plotX;\n\n let path = chart.renderer.path(['M', pos, 0, 'V', chart.chartHeight])\n .attr({\n 'stroke-width': 1,\n stroke: 'white',\n zIndex: 4\n })\n .add();\n\n this.paths.push(path);\n });\n }\n}\n\nexport default HCAreaSpline;\n","'use strict';\n\nimport HCBaseTheme from '../hcbasetheme.es6.js';\n\n/* global lang */\nclass HCSplineColumn extends HCBaseTheme {\n constructor(container, highlightToday=false) {\n super();\n\n let self = this;\n let baseColor;\n\n this.container = container;\n this.highlightToday = highlightToday;\n\n this.options = {\n plotOptions: {\n column: {\n animation: false,\n color: '#F0F0F0'\n },\n spline: {\n animation: {\n duration: 2500\n },\n color: '#B4B4B4',\n cursor: 'pointer',\n lineWidth: 1,\n marker: {\n fillColor: '#18A5D6',\n radius: 5\n }\n },\n series: {\n point: {\n events: {\n mouseOver() {\n const chart = $(container).highcharts();\n baseColor = chart.series[0].data[this.x].graphic.attr('fill');\n chart.series[0].data[this.x].graphic.attr({ fill: '#CBE4F0' });\n },\n mouseOut() {\n const chart = $(container).highcharts();\n chart.series[0].data[this.x].graphic.attr({ fill: baseColor });\n }\n },\n },\n states: {\n hover: {\n lineWidth: 1\n }\n }\n }\n },\n yAxis: [{\n floor: 0\n }, {\n linkedTo: 0,\n labels: {\n enabled: false\n }\n }],\n series: [{\n type: 'column',\n yAxis: 1,\n enableMouseTracking: false\n }, {\n type: 'spline'\n }]\n };\n\n this._highlightCurrentDate = this._highlightCurrentDate.bind(this);\n\n if (this.highlightToday === true) {\n this.options.chart = {\n events: {\n redraw() {\n self._highlightCurrentDate();\n }\n }\n }\n }\n }\n\n drawChart() {\n if (this.highlightToday === true) {\n super.drawChart(this.container, this.options, this._highlightCurrentDate);\n } else {\n super.drawChart(this.container, this.options);\n }\n\n return this;\n }\n\n _highlightCurrentDate() {\n const chart = $(this.container).highcharts();\n const columns = chart.series[0].data;\n\n for (let i = 0; i < columns.length; ++i) {\n let column = columns[i];\n\n if (column.category.name === moment().lang(lang).format('L')) {\n column.graphic.attr({ fill: '#C9EAC9' });\n } else {\n column.graphic.attr({ fill: '#F0F0F0' });\n }\n }\n }\n}\n\nexport default HCSplineColumn;\n","'use strict';\n\nimport HCBaseTheme from '../hcbasetheme.es6.js';\n\nclass HCGroupedColumns extends HCBaseTheme {\n constructor(container) {\n super();\n\n this.container = container;\n\n this.options = {\n chart: {\n type: 'column'\n },\n plotOptions: {\n series: {\n point: {\n events: {\n mouseOver() {\n HCGroupedColumns._changeColumnColor($(container), '#18A5D6', '#0071A4', this);\n },\n mouseOut() {\n HCGroupedColumns._changeColumnColor($(container), '#F0F0F0', '#D5D5D5', this);\n }\n }\n },\n states: {\n hover: {\n enabled: false\n }\n }\n }\n },\n series: [{\n color: '#F0F0F0',\n stack: 'left'\n }, {\n color: '#D5D5D5',\n stack: 'right'\n }]\n };\n }\n\n drawChart() {\n super.drawChart(this.container, this.options);\n return this;\n }\n\n static _changeColumnColor(container, firstColor, secondColor, self) {\n let chart = container.highcharts();\n chart.series[0].data[self.x].graphic.attr({ fill: firstColor });\n chart.series[1].data[self.x].graphic.attr({ fill: secondColor });\n }\n}\n\nexport default HCGroupedColumns;\n","'use strict';\n/**\n * @desc (Object) Options object\n * @TODO Add XHR support :)\n*/\nclass Poller {\n constructor(options) {\n if (!options || !options.url) {\n console.log('[Poller] Missing options or URL.');\n return null;\n }\n\n this.url = options.url;\n\n this.maxRetries = options.maxRetries === undefined ? 3 : options.maxRetries;\n this.retryInterval = options.retryInterval || 1000;\n this.pollTime = options.pollTime || 10000;\n this.onSuccess = options.onSuccess || null;\n this.onError = options.onError || null;\n this.onPoll = options.onPoll || null;\n this.onFail = options.onFail || null;\n\n // Inner state\n this.failed = false;\n this.polling = false;\n this.failCount = 0;\n this.pollTimer = null;\n\n // Save options if we need em someday, dunno, bbq dawg\n this.options = options;\n\n this.create();\n }\n\n create(interval) {\n interval = interval || 0;\n\n if (this.pollTimer) {\n clearTimeout(this.pollTimer);\n }\n\n this.pollTimer = setTimeout(() => {\n this.doPoll(this);\n }, this.pollTime + interval);\n }\n\n doPoll(self) {\n this.polling = true;\n\n if (this.onPoll !== null) {\n this.onPoll();\n }\n\n $.getJSON(self.url, (data) => {\n self.doSuccess(data);\n }).fail((data) => {\n self.doRetry(data);\n });\n }\n\n doRetry(e) {\n this.polling = false;\n\n if (this.failCount === this.maxRetries) {\n console.log(`[Poller] URL exceeded maxRetries (${this.maxRetries}), stopping.`);\n this.doFail(e);\n } else {\n console.log(`[Poller] URL failed, retrying (${this.failCount}/${this.maxRetries})`);\n this.create(this.retryInterval);\n }\n\n if (this.onError) {\n this.onError(e);\n }\n\n this.failCount++;\n }\n\n doFail(e) {\n this.failed = true;\n this.polling = false;\n this.remove();\n\n if (this.onFail !== null) {\n this.onFail(e);\n }\n }\n\n doSuccess(s) {\n this.failCount = 0;\n this.polling = false;\n this.create(this.retryInterval);\n\n if (this.onSuccess !== null) {\n this.onSuccess(s);\n }\n }\n\n remove() {\n if (this.pollTimer) {\n clearTimeout(this.pollTimer);\n this.pollTimer = null;\n }\n }\n}\n\nexport default Poller;\n\nwindow.Poller = Poller;\n","'use strict';\n/**\n * @desc (String) String for the Odo element\n * @desc (Object) Options object\n * @desc (Function) Overload setup function (Odometer)\n */\nclass Odo {\n constructor(elm, options) {\n if (!elm || !options) {\n return null;\n }\n\n this.elm = document.querySelector(elm);\n this.options = options;\n this.setup();\n }\n\n setup() {\n this.odometer = new Odometer({\n el: this.elm,\n value: 0,\n theme: 'default',\n format: this.options.format || '',\n duration: this.options.duration || '1000'\n });\n\n this.update();\n }\n\n update(v) {\n if (v) {\n this.odometer.update(v);\n } else {\n this.odometer.update(this.options.value);\n }\n }\n}\n\nexport default Odo;\n","'use strict';\n\nimport accounting from 'accounting'\nimport HCBaseTheme from './highcharts/hcbasetheme.es6.js';\nimport HCAreaSpline from './highcharts/themes/hcareaspline.es6.js';\nimport HCSplineColumn from './highcharts/themes/hcsplinecolumn.es6.js';\nimport HCGroupedColumns from './highcharts/themes/hcgroupedcolumns.es6.js';\nimport Poller from './react/stats/utils/poller.es6.js';\nimport Odo from './react/stats/utils/odo.es6.js';\n\nwindow.accounting = accounting\nwindow.Swipe = Swipe\n\n/*global Odometer, Poller, window */\nclass Stats {\n constructor(options) {\n if (!options.url) {\n console.log('[Stats] Cannot init, no URL');\n return null;\n }\n\n this.url = options.url;\n this.dataset = options.data;\n this.currentLang = options.langCode;\n this.translations = options.translations || {};\n this.showLastMonth = options.showLastMonth || false;\n\n this.datum = [];\n this.graphs = {};\n this.odometers = {};\n\n this.currency = options.currentCurrency;\n\n this.hcwrapper = new HCBaseTheme();\n\n this._initPoller();\n this._initOdometers();\n this._drawCharts()\n this._updateTextData();\n\n Stats.reloadSprites();\n\n this._handleCurrencyChange();\n }\n\n static pluralize(singular, plural, count) {\n return (count === 1) ? singular : plural;\n }\n\n static reloadSprites() {\n let sprites = document.querySelectorAll('main i');\n\n [].forEach.call(sprites, (sprite, index) => {\n if (window.innerWidth <= 320) {\n sprite.className = 'sprite-stats-' + sprite.className;\n } else {\n sprite.className = 'sprite-stats-big-' + sprite.className;\n }\n });\n }\n\n static sortObject(source) {\n let keys = [];\n let result = {};\n\n for (let key in source) {\n if (source.hasOwnProperty(key)) {\n keys.push(key);\n }\n }\n\n keys.sort();\n\n $.each(keys, (i, key) => {\n result[key] = source[key];\n });\n\n return result;\n }\n\n static selectPeriod(endDate, period, datum) {\n let selection = [];\n\n const startDate = moment(endDate).subtract('months', period).toDate();\n const years = Stats.sortObject(Stats.parseSelection(startDate, endDate, datum));\n\n for (const year in years) {\n selection.push({key: year, value: years[year]})\n }\n\n return selection;\n }\n\n static parseSelection(first, current, datum) {\n const firstYear = first.getFullYear();\n const firstMonth = first.getMonth() + 1;\n const currentYear = current.getFullYear();\n const currentMonth = current.getMonth() + 1;\n\n let months = [];\n let years = {};\n\n var entry = {};\n\n let selectMonths = function(range) {\n if (entry.year === currentYear) {\n if (range.month <= currentMonth) {\n return range;\n }\n }\n else if (entry.year === firstYear) {\n if (range.month >= firstMonth) {\n return range;\n }\n }\n else {\n return range;\n }\n };\n\n for (let i = 0; i < datum.length; ++i) {\n entry = datum[i];\n\n if (entry.year >= firstYear && entry.year <= currentYear) {\n months = entry.months.filter(selectMonths);\n years[entry.year] = months;\n } else {\n continue;\n }\n }\n\n return years;\n }\n\n static flatten(array, callback) {\n let result = [];\n\n for (let i = 0; i < array.length; ++i) {\n const year = array[i];\n for (let j = 0; j < year.value.length; ++j) {\n const month = year.value[j];\n callback(result, year, month);\n }\n }\n\n return result;\n }\n\n static combine(date, value) {\n let res = [];\n\n for (let i = 0, len = value.length; i < len; ++i) {\n const obj = {\n 'date': date[i],\n 'value': value[i]\n };\n res.push(obj);\n }\n\n return res;\n }\n\n _handleCurrencyChange() {\n let currenciesSelectors = $('.currency-selector span');\n\n currenciesSelectors.on('click', ((e) => {\n let selector = $(e.target);\n let selectedCurrency = selector.attr(\"data-currency\");\n\n if (selectedCurrency === this.currency) return;\n\n this.currency = selectedCurrency;\n $('.currency-selector span').removeClass('active');\n selector.addClass('active');\n\n accounting.settings.currency.symbol = selector.text();\n\n this.cumulCollectedFundsChartUpdate();\n\n this._updateTextData();\n this.odometers.collectedFunds.update(this.dataset.amount_raised[this.currency]);\n\n document.querySelector('#graph-cumul-collected-funds header span').textContent = accounting.formatMoney(this.dataset.amount_raised[this.currency]);\n }).bind(this))\n }\n\n _setFlatten(selection, callback) {\n return Stats.combine(\n Stats.flatten(selection, ((result, year, month) => {\n let date = new Date(parseInt(year.key, 10), parseInt(month.month, 10) - 1, 1);\n date.label = month.languages_abbrv[this.currentLang];\n date.legend = month.languages[this.currentLang];\n\n result.push(date);\n }).bind(this)),\n Stats.flatten(selection, (result, year, month) => {\n result.push(callback(month));\n })\n );\n }\n\n cumulCollectedFundsChart(selection) {\n const data = this._setFlatten(selection, (m) => {\n return [m.amount_raised_cumulated[this.currency], m.amount_raised[this.currency]];\n });\n\n let chart = new HCAreaSpline($('#graph-cumul-collected-funds > .graph-container.chart'));\n\n chart.setAdditionalOptions({\n chart: {\n height: 320\n },\n xAxis: {\n labels: {\n style: {\n color: 'transparent',\n fontSize: '3px'\n }\n },\n categories: selection.map((entry, index) => {\n return {\n name: entry.key,\n categories: entry.value.map((entry, index) => {\n return entry.languages[this.currentLang];\n })\n }\n })\n },\n yAxis: {\n labels: {\n formatter() {\n return accounting.formatMoney(this.value);\n }\n }\n },\n tooltip: {\n formatter() {\n const cumul = accounting.formatMoney(this.y);\n const diff = accounting.formatMoney(data[this.point.index].value[1]);\n return `${this.x}
\n${cumul}
\n${diff}
`\n }\n },\n series: [{\n data: data.map((entry, index) => {\n return entry.value[0];\n })\n }]\n })\n .drawChart();\n\n this.graphs.cumulCollectedFundsChart = chart;\n }\n\n cumulCollectedFundsChartUpdate() {\n const selection = Stats.selectPeriod(moment().toDate(), 36, this.datum);\n\n const data = this._setFlatten(selection, (m) => {\n return [m.amount_raised_cumulated[this.currency], m.amount_raised[this.currency]];\n });\n\n this.graphs.cumulCollectedFundsChart.chart.series[0].setData(data.map((entry, index) => {\n return entry.value[0]\n }));\n }\n\n collectedFundsChart(selection, showLastMonth) {\n const data = this._setFlatten(selection, (m) => {\n return m.amount_raised[this.currency];\n });\n\n let chart = new HCSplineColumn($('#graph-collected-funds > .graph-container.chart'));\n\n chart.setAdditionalOptions({\n xAxis: {\n labels: {\n style: {\n color: 'transparent',\n fontSize: '3px'\n }\n },\n categories: selection.map((entry, index) => {\n return {\n name: entry.key,\n categories: entry.value.map((entry, index) => {\n return entry.languages[this.currentLang];\n })\n }\n })\n },\n yAxis: [{\n labels: {\n formatter() {\n return accounting.formatMoney(this.value);\n }\n }\n }, {}],\n tooltip: {\n formatter() {\n const value = accounting.formatMoney(this.y);\n return `${this.x}
\n${value}
`;\n }\n },\n series: [{\n data: data.map((entry, index) => {\n return entry.value;\n })\n }, {\n data: data.map((entry, index) => {\n return entry.value;\n })\n }]\n })\n .drawChart();\n\n this.graphs.collectedFundsChart = chart;\n }\n\n presalesChart(selection) {\n const data = this._setFlatten(selection, (m) => {\n return m.contributions_count;\n });\n\n let chart = new HCAreaSpline($('#graph-presales > .graph-container.chart'));\n\n chart.setAdditionalOptions({\n xAxis: {\n categories: data.map((entry, index) => {\n return entry.date.label;\n })\n },\n yAxis: {\n labels: {\n formatter() {\n return accounting.formatNumber(this.value);\n }\n }\n },\n tooltip: {\n formatter: HCBaseTheme.simpleFormattedNumberTooltip\n },\n series: [{\n data: data.map((entry, index) => {\n return entry.value;\n })\n }]\n })\n .drawChart();\n }\n\n projectsCountChart(selection) {\n const data = this._setFlatten(selection, (m) => {\n return [m.projects_started_count, m.projects_success_count, m.projects_success_percent];\n });\n\n let chart = new HCGroupedColumns($('#graph-projects > .graph-container.chart'));\n\n chart.setAdditionalOptions({\n xAxis: {\n categories: data.map((entry, index) => {\n return entry.date.label;\n })\n },\n tooltip: {\n formatter: HCBaseTheme.groupedColumnsTooltip\n },\n series: [{\n translations: this.translations.started,\n data: data.map((entry, index) => {\n return entry.value[0];\n })\n }, {\n translations: this.translations.funded,\n data: data.map((entry, index) => {\n return entry.value[1];\n })\n }]\n })\n .drawChart();\n }\n\n proposalsCountChart(selection) {\n const data = this._setFlatten(selection, (m) => {\n return [m.proposals_started_count, m.proposals_accepted_count, m.proposals_accepted_percent];\n });\n\n let chart = new HCGroupedColumns($('#graph-proposals > .graph-container.chart'));\n\n chart.setAdditionalOptions({\n xAxis: {\n categories: data.map((entry, index) => {\n return entry.date.label;\n })\n },\n tooltip: {\n formatter: HCBaseTheme.groupedColumnsTooltip\n },\n series: [{\n translations: this.translations.started,\n data: data.map((entry, index) => {\n return entry.value[0];\n })\n }, {\n translations: this.translations.accepted,\n data: data.map((entry, index) => {\n return entry.value[1];\n })\n }]\n })\n .drawChart();\n }\n\n _initPoller() {\n let plr = new Poller({\n url: this.url,\n onSuccess: ((s) => {\n this.odometers.collectedFunds.update(s.amount_raised[this.currency]);\n this.odometers.projectsFunded.update(s.projects_success_count);\n this.odometers.projectsFunding.update(s.projects_started_count);\n this.odometers.totalMembers.update(s.users_count);\n }).bind(this),\n maxRetries: 3,\n pollTime: 30000\n });\n }\n\n _drawCharts() {\n this.datum = this.dataset.years.slice().reverse();\n const endDate = moment().toDate();\n\n if (document.querySelector('#graph-cumul-collected-funds') !== null) {\n const selection = Stats.selectPeriod(endDate, 36, this.datum);\n this.cumulCollectedFundsChart(selection);\n document.querySelector('#graph-cumul-collected-funds header span').textContent = accounting.formatMoney(this.dataset.amount_raised[this.currency]);\n }\n\n if (document.querySelector('#graph-presales') !== null) {\n const selection = Stats.selectPeriod(endDate, 12, this.datum);\n this.presalesChart(selection);\n document.querySelector('#graph-presales header span').textContent = accounting.formatNumber(this.dataset.contributions_count);\n }\n\n if (document.querySelector('#graph-projects') !== null) {\n const selection = Stats.selectPeriod(endDate, 12, this.datum);\n this.projectsCountChart(selection);\n document.querySelector('#graph-projects .graph-selector .started span').textContent = accounting.formatNumber(this.dataset.projects_started_count);\n document.querySelector('#graph-projects .graph-selector .funded span').textContent = accounting.formatNumber(this.dataset.projects_success_count);\n }\n\n if (document.querySelector('#graph-proposals') !== null) {\n const selection = Stats.selectPeriod(endDate, 12, this.datum);\n this.proposalsCountChart(selection);\n document.querySelector('#graph-proposals .graph-selector .accepted span').textContent = accounting.formatNumber(this.dataset.proposals_accepted_count);\n document.querySelector('#graph-proposals .graph-selector .started span').textContent = accounting.formatNumber(this.dataset.proposals_started_count);\n }\n }\n\n _initOdometers() {\n this.odometers.collectedFunds = new Odo('#counter-collected-funds .stats-counter', {\n duration: 1600,\n format: '( ddd),dd',\n value: this.dataset.amount_raised[this.currency]\n });\n\n this.odometers.successRate = new Odo('#counter-success-rate .stats-counter', {\n duration: 1400,\n format: '',\n value: Math.round(this.dataset.years[(this.dataset.years.length - 1)].projects_success_percent)\n });\n\n this.odometers.projectsFunded = new Odo('#counter-projects-funded .stats-counter', {\n duration: 1800,\n format: '( ddd),dd',\n value: this.dataset.projects_success_count\n });\n\n this.odometers.projectsFunding = new Odo('#counter-projects-funding .stats-counter', {\n duration: 2400,\n format: '( ddd),dd',\n value: this.dataset.projects_started_count\n });\n\n this.odometers.totalMembers = new Odo('#counter-total-members .stats-counter', {\n duration: 2200,\n format: '( ddd),dd',\n value: this.dataset.users_count\n });\n }\n\n _updateTextData() {\n document.querySelector('#contrib-moy').textContent = accounting.formatMoney(this.dataset.amount_avg[this.currency]);\n document.querySelector('#current_year').textContent = this.dataset.years[(this.dataset.years.length - 1)].year;\n document.querySelector('#total-members').textContent = accounting.formatNumber(this.dataset.users_count);\n\n const currency = this.dataset.currencies.find(currency => currency.value === this.currency);\n if (currency) {\n document.querySelector('#counter-collected-funds .currency-value').textContent = currency.label;\n }\n }\n}\n\nexport default Stats;\n\nwindow.Stats = Stats;\nwindow.dispatchEvent(new Event('stats-loaded'));\n","\"use strict\";\n\nimport React from 'react';\n\n\nclass HeaderView extends React.Component {\n render() {\n let headers = this.props.headers.map((header, index) =>\n