@@ -499,6 +53,21 @@ export default class App extends Component {
+
+
+
+ Data wordt geladen...
+
+
+
Dashboard
Leesanalyse van Jordy van Zeeland
@@ -509,16 +78,9 @@ export default class App extends Component {
-
@@ -535,66 +97,16 @@ export default class App extends Component {
-
Boeken per maand per genre
-
-
-
-
Kortste boek
-
-
{this.state.pagesStats.shortestbook ? this.state.pagesStats.shortestbook.pages : ''} pagina's
-
{this.state.pagesStats.shortestbook ? this.state.pagesStats.shortestbook.name : ''} - {this.state.pagesStats.shortestbook ? this.state.pagesStats.shortestbook.author : ''}
-
-
-
-
-
-
-
Langste boek
-
-
{this.state.pagesStats.longestbook ? this.state.pagesStats.longestbook.pages : ''} pagina's
-
{this.state.pagesStats.longestbook ? this.state.pagesStats.longestbook.name : ''} - {this.state.pagesStats.longestbook ? this.state.pagesStats.longestbook.author : ''}
-
-
-
-
+
+
-
-
Landen
-
-
-
- # |
- Land |
- Boeken |
-
-
-
- {this.state.countries.map((country, i) => {
-
- var code = country.code.toLowerCase();
- return (
-
-
- {i + 1} |
- {country.country} |
- {country.count} |
-
-
- )
-
- })}
-
-
-
-
Genres
+
+
-
-
-
)
diff --git a/ras/frontend/src/components/Books.js b/ras/frontend/src/components/Books.js
new file mode 100644
index 0000000..333d62e
--- /dev/null
+++ b/ras/frontend/src/components/Books.js
@@ -0,0 +1,43 @@
+import React, { Component } from 'react';
+import { getBooksPerYearPerGenres } from "./Data.js";
+import { initChart } from "./Charts.js";
+
+export default class Books extends Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ books: []
+ }
+ }
+
+ getComponentData() {
+ getBooksPerYearPerGenres(this.props.year).then(books => {
+ this.setState({
+ books: books
+ })
+
+ initChart(books, this.props.year);
+ })
+ }
+
+ componentDidMount() {
+ this.getComponentData();
+ }
+
+ componentDidUpdate(prevProps, prevState) {
+ if (prevProps.year !== this.props.year) {
+ this.getComponentData();
+ }
+ }
+
+ render() {
+ return (
+
+
+ Boeken per maand per genre
+
+
+
+ )
+ }
+}
\ No newline at end of file
diff --git a/ras/frontend/src/components/Charts.js b/ras/frontend/src/components/Charts.js
new file mode 100644
index 0000000..3242f76
--- /dev/null
+++ b/ras/frontend/src/components/Charts.js
@@ -0,0 +1,298 @@
+export const initChart = (data, year) => {
+
+ /*
+ ----------------------------------
+ Books per month per genre
+ ----------------------------------
+ */
+
+ var genres = [];
+
+ var colors = [
+ // '#696ffc', '#7596fa', '#92adfe', '#abc0ff'
+ '#8066ee', '#58c8d6', '#fe4c62', '#49b8fd', '#ffbe0e'
+ ]
+
+ var dataSet = [];
+
+ data.forEach(book => {
+ if (!genres.includes(book.genre)) {
+ genres.push(book.genre)
+ }
+ });
+
+ if (genres && genres.length > 0) {
+ genres.forEach((genre, index) => {
+ var genreData = [];
+
+ for (var i = 0; i < 12; i++) {
+
+ genreData[i] = 0;
+
+ if ((i + 1) < 10) {
+ var month = "0" + (i + 1);
+ } else {
+ month = (i + 1);
+ }
+
+ for (var j = 0; j < data.length; j++) {
+ if (data && data[j] && data[j].readed == month + '-' + year) {
+ if (data[j].genre == genre) {
+ genreData[i] = data[j].count;
+ }
+
+ }
+ }
+ }
+
+ dataSet.push({
+ label: genre,
+ data: genreData,
+ backgroundColor: colors[index],
+ order: 2
+ })
+ })
+ }
+
+ /*
+ ----------------------------------
+ Stacked bar chart
+ ----------------------------------
+ */
+
+ $("canvas#chart").remove();
+ $("div.books-per-month").append('
');
+
+ const legendMargin = {
+ id: 'legendMargin',
+ beforeInit(chart, legend, options) {
+ const fitValue = chart.legend.fit;
+
+ chart.legend.fit = function fit() {
+ fitValue.bind(chart.legend)();
+ return this.height += 30;
+ }
+ }
+ };
+
+ new Chart(document.getElementById('chart'), {
+ type: 'bar',
+ data: {
+ labels: ["Januari", "Februari", "Maart", "April", "Mei", "Juni", "Juli", "Augustus", "September", "Oktober", "November", "December"],
+ datasets: dataSet
+ },
+ options: {
+ maintainAspectRatio: false,
+ responsive: true,
+ showTooltips: true,
+ legend: {
+ display: true,
+ labels: {
+ usePointStyle: true,
+ }
+ },
+ interaction: {
+ mode: 'index'
+ },
+ scales: {
+ x: {
+ ticks: {
+ beginAtZero: true,
+ color: "#101010",
+ fontFamily: "Source Sans Pro",
+ },
+ stacked: true,
+ },
+ y: {
+ ticks: {
+ beginAtZero: true,
+ stepSize: 1,
+ color: "#101010",
+ fontFamily: "Source Sans Pro",
+ },
+ stacked: true
+ }
+ },
+ plugins: {
+ legend: {
+ position: 'top',
+ labels: {
+ usePointStyle: true,
+ color: "#101010",
+ padding: 20,
+ font: {
+ size: 14,
+ family: 'Source Sans Pro',
+ }
+ }
+ }
+ },
+ tooltips: {
+ bodyFont: 'Source Sans Pro'
+ }
+ },
+ plugins: [legendMargin],
+ });
+}
+
+export const initDoughnut = (data) => {
+
+ var labels = [];
+ var counts = [];
+
+ data.forEach((count) => {
+ if (!labels.includes(count.genre)) {
+ labels.push(count.genre)
+ }
+
+ counts.push(count.count)
+ })
+
+ const legendMargin = {
+ id: 'legendMargin',
+ beforeInit(chart, legend, options) {
+ const fitValue = chart.legend.fit;
+
+ chart.legend.fit = function fit() {
+ fitValue.bind(chart.legend)();
+ return this.height += 30;
+ }
+ }
+ };
+
+ $("canvas#chartGenres").remove();
+ $("div.genresPercent").append('
');
+
+ var ctx = document.getElementById("chartGenres");
+ var myChart = new Chart(ctx, {
+ type: 'pie',
+ data: {
+ labels: labels,
+ datasets: [{
+ label: '# of Tomatoes',
+ data: counts,
+ backgroundColor: [
+ '#8066ee', '#58c8d6', '#fe4c62', '#49b8fd', '#ffbe0e'
+ ],
+ borderWidth: 0,
+ borderColor: '#1f2940',
+ tooltip: {
+ callbacks: {
+ label: function (context) {
+ let label = context.label;
+ let value = context.formattedValue;
+
+ if (!label)
+ label = 'Unknown'
+
+ let sum = 0;
+ let dataArr = context.chart.data.datasets[0].data;
+ dataArr.map(data => {
+ sum += Number(data);
+ });
+
+ let percentage = (value * 100 / sum).toFixed(1) + '%';
+ return label + ": " + percentage;
+ }
+ }
+ }
+ }]
+ },
+ options: {
+ cutout: '80%',
+ responsive: true,
+ plugins: {
+ legend: {
+ position: 'top',
+ labels: {
+ padding: 20,
+ usePointStyle: true,
+ // This more specific font property overrides the global property
+ color: "##101010",
+ font: {
+ size: 14,
+ family: 'Source Sans Pro'
+ }
+ }
+ }
+ }
+ },
+ plugins: [{
+ id: 'legendMargin',
+ beforeInit(chart, legend, options) {
+ const fitValue = chart.legend.fit;
+
+ chart.legend.fit = function fit() {
+ fitValue.bind(chart.legend)();
+ return this.height += 30;
+ }
+ }
+ }, {
+ afterDraw: chart => {
+ var ctx = chart.ctx;
+ ctx.save();
+ var image = new Image();
+ image.src = 'https://www.iconsdb.com/icons/preview/gray/book-xxl.png';
+ var imageSize = 80;
+ ctx.drawImage(image, chart.width / 2 - imageSize / 2, chart.height / 2 - imageSize / 6, imageSize, imageSize);
+ ctx.restore();
+ }
+ }],
+ });
+}
+
+export const initHorBar = (data) => {
+
+ var countries = [];
+ var counts = [];
+
+ data.forEach((count) => {
+ if (!countries.includes(count.country)) {
+ countries.push(count.country)
+ }
+
+ counts.push(count.count)
+ })
+
+ $("canvas#countryChart").remove();
+ $("div.books-per-country").append('
');
+
+ var ctx = document.getElementById("countryChart");
+ new Chart(ctx, {
+ type: 'bar',
+ options: {
+ indexAxis: 'y',
+ plugins: {
+ legend: {
+ display: false
+ },
+ },
+ scales: {
+ x: {
+ ticks: {
+ beginAtZero: true,
+ color: "white",
+ },
+ stacked: true,
+ },
+ y: {
+ ticks: {
+ beginAtZero: true,
+ stepSize: 1,
+ color: "white",
+ },
+ stacked: true
+ }
+ },
+ },
+ data: {
+ labels: countries,
+ datasets: [
+ {
+ label: "Boeken",
+ data: counts,
+ backgroundColor: '#696ffc',
+ }]
+ }
+ });
+}
\ No newline at end of file
diff --git a/ras/frontend/src/components/Countries.js b/ras/frontend/src/components/Countries.js
new file mode 100644
index 0000000..c4a0dc3
--- /dev/null
+++ b/ras/frontend/src/components/Countries.js
@@ -0,0 +1,60 @@
+import React, { Component } from 'react';
+import { getCountries } from "./Data.js";
+
+export default class Countries extends Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ countries: []
+ }
+ }
+
+ getComponentData() {
+ getCountries(this.props.year).then(countries => {
+ this.setState({
+ countries: countries
+ })
+ })
+ }
+
+ componentDidMount() {
+ this.getComponentData();
+ }
+
+ componentDidUpdate(prevProps, prevState) {
+ if (prevProps.year !== this.props.year) {
+ this.getComponentData();
+ }
+ }
+
+ render() {
+ return (
+
+
+
Landen
+
+
+
+ # |
+ Land |
+ Boeken |
+
+
+
+ {this.state.countries.map((country, i) => {
+ var code = country.code.toLowerCase();
+ return (
+
+ {i + 1} |
+ {country.country} |
+ {country.count} |
+
+ )
+ })}
+
+
+
+
+ )
+ }
+}
\ No newline at end of file
diff --git a/ras/frontend/src/components/Data.js b/ras/frontend/src/components/Data.js
index 676ca3b..adf3ac4 100644
--- a/ras/frontend/src/components/Data.js
+++ b/ras/frontend/src/components/Data.js
@@ -32,4 +32,56 @@ export const getReadingYears = () => {
.then(data => {
return data
})
+}
+
+export const getCountries = (year) => {
+ return fetch('/api/books/countries', {
+ "method": "GET",
+ "headers": {
+ "year": year
+ }
+ })
+ .then(response => response.json())
+ .then(data => {
+ return data;
+ })
+}
+
+export const getShortestLongestBook = (year) => {
+ return fetch('/api/books/pages/stats', {
+ "method": "GET",
+ "headers": {
+ "year": year
+ }
+ })
+ .then(response => response.json())
+ .then(data => {
+ return data;
+ })
+}
+
+export const getBooksPerYearPerGenres = (year) => {
+ return fetch('/api/books/genres', {
+ "method": "GET",
+ "headers": {
+ "year": year
+ }
+ })
+ .then(response => response.json())
+ .then(data => {
+ return data;
+ })
+}
+
+export const getGenresCount = (year) => {
+ return fetch('/api/books/genres/count', {
+ "method": "GET",
+ "headers": {
+ "year": year
+ }
+ })
+ .then(response => response.json())
+ .then(data => {
+ return data;
+ })
}
\ No newline at end of file
diff --git a/ras/frontend/src/components/Genres.js b/ras/frontend/src/components/Genres.js
new file mode 100644
index 0000000..33e99f0
--- /dev/null
+++ b/ras/frontend/src/components/Genres.js
@@ -0,0 +1,43 @@
+import React, { Component } from 'react';
+import { getGenresCount } from "./Data.js";
+import { initDoughnut } from "./Charts.js";
+
+export default class Genres extends Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ genres: []
+ }
+ }
+
+ getComponentData() {
+ getGenresCount(this.props.year).then(genres => {
+ this.setState({
+ genres: genres
+ })
+
+ initDoughnut(this.state.genres, this.props.year);
+ })
+ }
+
+ componentDidMount() {
+ this.getComponentData();
+ }
+
+ componentDidUpdate(prevProps, prevState) {
+ if (prevProps.year !== this.props.year) {
+ this.getComponentData();
+ }
+ }
+
+ render() {
+ return (
+
+
+ Genres
+
+
+
+ )
+ }
+}
\ No newline at end of file
diff --git a/ras/frontend/src/components/Pages.js b/ras/frontend/src/components/Pages.js
new file mode 100644
index 0000000..5d85511
--- /dev/null
+++ b/ras/frontend/src/components/Pages.js
@@ -0,0 +1,82 @@
+import React, { Component } from 'react';
+import { getShortestLongestBook } from "./Data.js";
+
+export default class Pages extends Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ pagesStats: []
+ }
+ }
+
+ getComponentData() {
+ getShortestLongestBook(this.props.year).then(bookstats => {
+ this.setState({
+ pagesStats: bookstats
+ })
+ })
+ }
+
+ componentDidMount() {
+ this.getComponentData();
+ }
+
+ componentDidUpdate(prevProps, prevState) {
+ if (prevProps.year !== this.props.year) {
+ this.getComponentData();
+ }
+ }
+
+ render() {
+
+ var ratingshort = '';
+ var ratinglong = '';
+
+
+ if (this.state.pagesStats.shortestbook) {
+ for (var i = 0; i < this.state.pagesStats.shortestbook.rating; i++) {
+ ratingshort += "
";
+ }
+ }
+
+ if (document.getElementById("shortest_rating") !== null) {
+ document.getElementById('shortest_rating').innerHTML = ratingshort;
+ }
+
+ if (this.state.pagesStats.longestbook) {
+ for (var i = 0; i < this.state.pagesStats.longestbook.rating; i++) {
+ ratinglong += "
";
+ }
+ }
+
+ if (document.getElementById("longest_rating") !== null) {
+ document.getElementById('longest_rating').innerHTML = ratinglong;
+ }
+
+ return (
+
+
+
+
+
Kortste boek
+
+
{this.state.pagesStats.shortestbook ? this.state.pagesStats.shortestbook.pages : ''} pagina's
+
{this.state.pagesStats.shortestbook ? this.state.pagesStats.shortestbook.name : ''} - {this.state.pagesStats.shortestbook ? this.state.pagesStats.shortestbook.author : ''}
+
+
+
+
+
+
+
Langste boek
+
+
{this.state.pagesStats.longestbook ? this.state.pagesStats.longestbook.pages : ''} pagina's
+
{this.state.pagesStats.longestbook ? this.state.pagesStats.longestbook.name : ''} - {this.state.pagesStats.longestbook ? this.state.pagesStats.longestbook.author : ''}
+
+
+
+
+
+ )
+ }
+}
\ No newline at end of file
diff --git a/ras/frontend/static/css/style.css b/ras/frontend/static/css/style.css
new file mode 100644
index 0000000..550d3d1
--- /dev/null
+++ b/ras/frontend/static/css/style.css
@@ -0,0 +1,262 @@
+html, body{
+ background:#f8f8fa;
+ margin:0;
+ padding:0;
+ font-family: 'Source Sans Pro', sans-serif;
+ }
+
+ h1{
+ font-size: 30px;
+ font-weight: 500;
+ padding: 0 10px 0px 10px;
+ margin-bottom: 5px;
+ }
+
+ h2{
+ padding-left: 10px;
+ font-weight: 400;
+ font-size: 18px;
+ color: #a7adbd;
+ display: block;
+ margin-bottom: 50px;
+ }
+
+
+ .content{
+ padding: 50px 50px 50px 110px;
+ }
+
+ .filter{
+ width:100%;
+ background:#1f2940;
+ padding: 20px 0;
+ }
+
+ .books-per-month{
+ height:700px;
+ }
+
+ .books-per-month canvas{
+ height:600px !important;
+ }
+
+ .books-per-month, .genresPercent, .books-per-country, .book{
+ background: #ffffff;
+ padding: 20px;
+ box-shadow: 0 2px 0px 1px rgb(0 0 0 / 3%);
+ margin-bottom: 20px;
+ border-radius: 10px;
+ }
+
+ .book .book-icon{
+ font-size: 60px;
+ float: left;
+ margin-right: 20px;
+ margin-bottom: 40px;
+ color: #808080;
+ }
+
+ .book_rating{
+ margin-top: 5px;
+ }
+
+ .book_rating i{
+ font-family: "Font Awesome 5 Free";
+ color: #ffbe0e;
+ }
+
+ .book .book_pages{
+ font-size: 18px;
+ font-weight: 600;
+ }
+
+ .book .book_title_author{
+ font-size: 16px;
+ color: #808080;
+ }
+
+ .sidebar{
+ background: #363a53;
+ width: 70px;
+ height: 100vh;
+ position: fixed;
+ padding-top: 10px;
+ }
+
+ .sidebar .menu-item{
+ text-align: center;
+ padding: 15px 0;
+ }
+
+ .sidebar .menu-item i{
+ font-size: 25px;
+ color: #727794;
+ }
+
+ .sidebar .menu-item.selected i{
+ color:#fff;
+ }
+
+ .sidebar svg{
+ color:#ffffff;
+ }
+
+ .books-stats{
+ margin:20px 0;
+ }
+
+ .books-icon{
+ background:#000;
+ padding: 50px;
+ }
+
+ .books-stats .stat-block, .stat-block{
+ background: #ffffff;
+ box-shadow: 0 2px 0px 1px rgb(0 0 0 / 3%);
+ padding: 15px 5px;
+ color: #101010;
+ text-align: center;
+ border-radius: 10px;
+ }
+
+ .books-stats .col-md-2:nth-child(1) i{
+ background: #f8f5fc;
+ color: #8066ee;
+ }
+ .books-stats .col-md-2:nth-child(2) i{
+ background: #f1fcf8;
+ color: #58c8d6;
+ }
+ .books-stats .col-md-2:nth-child(3) i{
+ background: #fff5f6;
+ color: #fe4c62;
+ }
+ .books-stats .col-md-2:nth-child(4) i{
+ background: #f2f9ff;
+ color: #49b8fd;
+ }
+ .books-stats .col-md-2:nth-child(5) i{
+ background: #fffaee;
+ color: #ffbe0e;
+ }
+ .books-stats .col-md-2:nth-child(6) i{
+ background: #f8f5fc;
+ color: #8066ee;
+ }
+
+ .books-stats .stat-block i{
+ font-weight: 900;
+ font-size: 25px;
+ border-radius: 50%;
+ padding: 17px;
+ width: 65px;
+ height: 65px;
+ line-height: 30px;
+ text-align: center;
+ background: #696ffc;
+ /* box-shadow: 2px 2px 0px 0px rgba(0, 0, 0, 0.3); */
+ }
+
+ .books-stats .stat-block .stats-number, .stats-number{
+ font-weight: 600;
+ display: inline-block;
+ margin-left: 10px;
+ font-size: 20px;
+ margin-right: 10px;
+ }
+
+ .books-stats .stat-block .stats-label, .stats-label{
+ color: #a7adbd;
+ font-weight: 400;
+ font-size: 20px;
+ }
+
+ .yearselector{
+ display: inline-block;
+ width: auto;
+ background: #ffffff;
+ border: none;
+ color: #101010;
+ font-weight: 600;
+ }
+
+ .container-fluid{
+ margin-bottom:20px !important;
+ }
+
+ .table{
+ border-bottom: none !important;
+ }
+
+ .table td{
+ color: #101010;
+ border-bottom:none !important;
+ padding: 10px 10px !important;
+ }
+
+ .table td img{
+ margin-right:5px;
+ }
+
+ #DataTable thead{
+ display:none !important;
+ }
+
+ span.block_name{
+ color: #101010;
+ font-weight: 600;
+ border-bottom: solid 1px #f5f6fa;
+ width: 100%;
+ display: block;
+ padding-bottom: 10px;
+ margin-bottom: 10px;
+ }
+
+ .progress{
+ background: #f8f8fa;
+ height: 50px;
+ border: solid 2px #efefef;
+ padding: 5px;
+ border-radius: 0;
+ margin: 0 15px 15px 15px;
+ position: relative;
+ overflow: visible;
+ }
+
+ .progress-bar{
+ background-color: #8066ee;
+ position: relative;
+ overflow: visible;
+ border-right: solid 2px #333;
+ }
+
+ .progress-bar-number{
+ position: absolute;
+ right: 0;
+ background: #333;
+ border-radius: 50%;
+ padding: 10px;
+ top: -20px;
+ right: -20px;
+ }
+
+ .loading-screen-overlay{
+ background: #f8f8fa;
+ width:100%;
+ height:100vh;
+ position: fixed;
+ z-index: 1;
+ top:0;
+ }
+
+ .loading-screen{
+ width: 100%;
+ position: fixed;
+ background: #f8f8fa;
+ z-index: 1;
+ text-align: center;
+ top: 50%;
+ -webkit-transform: translateY(-50%);
+ -ms-transform: translateY(-50%);
+ transform: translateY(-50%);
+ }
\ No newline at end of file
diff --git a/ras/frontend/static/js/main.js b/ras/frontend/static/js/main.js
index 4b89348..b08f84a 100644
--- a/ras/frontend/static/js/main.js
+++ b/ras/frontend/static/js/main.js
@@ -1,2 +1,2 @@
/*! For license information please see main.js.LICENSE.txt */
-(()=>{"use strict";var __webpack_modules__={"./src/App.js":(__unused_webpack_module,__webpack_exports__,__webpack_require__)=>{eval('__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ "default": () => (/* binding */ App)\n/* harmony export */ });\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! react */ "./node_modules/react/index.js");\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var _components_Challenge__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./components/Challenge */ "./src/components/Challenge.js");\n/* harmony import */ var _components_Stats__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./components/Stats */ "./src/components/Stats.js");\n\n\n\nclass App extends react__WEBPACK_IMPORTED_MODULE_0__.Component {\n constructor(props) {\n super(props);\n this.state = {\n year: new Date().getFullYear(),\n readingYears: [],\n countries: [],\n pagesStats: []\n };\n this.yearsArray = [];\n }\n\n getGenres() {\n fetch(\'/api/books/genres\', {\n "method": "GET",\n "headers": {\n "year": this.state.year\n }\n }).then(response => response.json()).then(books => {\n this.initChart(books, this.state.year);\n });\n }\n\n getCountries(init) {\n fetch(\'/api/books/countries\', {\n "method": "GET",\n "headers": {\n "year": this.state.year\n }\n }).then(response => response.json()).then(data => {\n this.setState({\n countries: data\n });\n\n if (init == true) {\n $(\'#DataTable\').DataTable({\n paging: false,\n ordering: false,\n info: false,\n searching: false\n });\n }\n });\n }\n\n getShortestLongestBook(currentyear) {\n fetch(\'/api/books/pages/stats\', {\n "method": "GET",\n "headers": {\n "year": currentyear\n }\n }).then(response => response.json()).then(bookstats => {\n this.setState({\n pagesStats: bookstats\n });\n });\n }\n\n changeYear(event) {\n this.setState({\n year: event.target.value\n });\n fetch(\'/api/books/countries\', {\n "method": "GET",\n "headers": {\n "year": event.target.value\n }\n }).then(response => response.json()).then(data => {\n this.setState({\n countries: data\n });\n this.getCountries(false);\n });\n var $this = this;\n this.getShortestLongestBook(event.target.value);\n fetch(\'/api/books/genres/count\', {\n "method": "GET",\n "headers": {\n "year": event.target.value\n }\n }).then(response => response.json()).then(data => {\n this.initDoughnut(data);\n });\n fetch(\'/api/books/genres\', {\n "method": "GET",\n "headers": {\n "year": event.target.value\n }\n }).then(response => response.json()).then(books => {\n this.initChart(books, event.target.value);\n });\n }\n\n initHorBar(data) {\n var countries = [];\n var counts = [];\n data.forEach(count => {\n if (!countries.includes(count.country)) {\n countries.push(count.country);\n }\n\n counts.push(count.count);\n });\n $("canvas#countryChart").remove();\n $("div.books-per-country").append(\'
\');\n var ctx = document.getElementById("countryChart");\n new Chart(ctx, {\n type: \'bar\',\n options: {\n indexAxis: \'y\',\n plugins: {\n legend: {\n display: false\n }\n },\n scales: {\n x: {\n ticks: {\n beginAtZero: true,\n color: "white"\n },\n stacked: true\n },\n y: {\n ticks: {\n beginAtZero: true,\n stepSize: 1,\n color: "white"\n },\n stacked: true\n }\n }\n },\n data: {\n labels: countries,\n datasets: [{\n label: "Boeken",\n data: counts,\n backgroundColor: \'#696ffc\'\n }]\n }\n });\n }\n\n initDoughnut(data) {\n var labels = [];\n var counts = [];\n data.forEach(count => {\n if (!labels.includes(count.genre)) {\n labels.push(count.genre);\n }\n\n counts.push(count.count);\n });\n const legendMargin = {\n id: \'legendMargin\',\n\n beforeInit(chart, legend, options) {\n const fitValue = chart.legend.fit;\n\n chart.legend.fit = function fit() {\n fitValue.bind(chart.legend)();\n return this.height += 30;\n };\n }\n\n };\n $("canvas#chartGenres").remove();\n $("div.genresPercent").append(\'
\');\n var ctx = document.getElementById("chartGenres");\n var myChart = new Chart(ctx, {\n type: \'pie\',\n data: {\n labels: labels,\n datasets: [{\n label: \'# of Tomatoes\',\n data: counts,\n backgroundColor: [\'#8066ee\', \'#58c8d6\', \'#fe4c62\', \'#49b8fd\', \'#ffbe0e\'],\n borderWidth: 0,\n borderColor: \'#1f2940\',\n tooltip: {\n callbacks: {\n label: function (context) {\n let label = context.label;\n let value = context.formattedValue;\n if (!label) label = \'Unknown\';\n let sum = 0;\n let dataArr = context.chart.data.datasets[0].data;\n dataArr.map(data => {\n sum += Number(data);\n });\n let percentage = (value * 100 / sum).toFixed(1) + \'%\';\n return label + ": " + percentage;\n }\n }\n }\n }]\n },\n options: {\n cutout: \'80%\',\n responsive: true,\n plugins: {\n legend: {\n position: \'top\',\n labels: {\n padding: 20,\n usePointStyle: true,\n // This more specific font property overrides the global property\n color: "##101010",\n font: {\n size: 14,\n family: \'Source Sans Pro\'\n }\n }\n }\n }\n },\n plugins: [{\n id: \'legendMargin\',\n\n beforeInit(chart, legend, options) {\n const fitValue = chart.legend.fit;\n\n chart.legend.fit = function fit() {\n fitValue.bind(chart.legend)();\n return this.height += 30;\n };\n }\n\n }, {\n afterDraw: chart => {\n var ctx = chart.ctx;\n ctx.save();\n var image = new Image();\n image.src = \'https://www.iconsdb.com/icons/preview/gray/book-xxl.png\';\n var imageSize = 80;\n ctx.drawImage(image, chart.width / 2 - imageSize / 2, chart.height / 2 - imageSize / 6, imageSize, imageSize);\n ctx.restore();\n }\n }]\n });\n }\n\n initChart(data, year) {\n /*\r\n ----------------------------------\r\n Books per month per genre\r\n ----------------------------------\r\n */\n var genres = [];\n var colors = [// \'#696ffc\', \'#7596fa\', \'#92adfe\', \'#abc0ff\'\n \'#8066ee\', \'#58c8d6\', \'#fe4c62\', \'#49b8fd\', \'#ffbe0e\'];\n var dataSet = [];\n data.forEach(book => {\n if (!genres.includes(book.genre)) {\n genres.push(book.genre);\n }\n });\n\n if (genres && genres.length > 0) {\n genres.forEach((genre, index) => {\n var genreData = [];\n\n for (var i = 0; i < 12; i++) {\n genreData[i] = 0;\n\n if (i + 1 < 10) {\n var month = "0" + (i + 1);\n } else {\n month = i + 1;\n }\n\n for (var j = 0; j < data.length; j++) {\n if (data && data[j] && data[j].readed == month + \'-\' + year) {\n if (data[j].genre == genre) {\n genreData[i] = data[j].count;\n }\n }\n }\n }\n\n dataSet.push({\n label: genre,\n data: genreData,\n backgroundColor: colors[index],\n order: 2\n });\n });\n }\n /*\r\n ----------------------------------\r\n Stacked bar chart\r\n ----------------------------------\r\n */\n\n\n $("canvas#chart").remove();\n $("div.books-per-month").append(\'
\');\n const legendMargin = {\n id: \'legendMargin\',\n\n beforeInit(chart, legend, options) {\n const fitValue = chart.legend.fit;\n\n chart.legend.fit = function fit() {\n fitValue.bind(chart.legend)();\n return this.height += 30;\n };\n }\n\n };\n new Chart(document.getElementById(\'chart\'), {\n type: \'bar\',\n data: {\n labels: ["Januari", "Februari", "Maart", "April", "Mei", "Juni", "Juli", "Augustus", "September", "Oktober", "November", "December"],\n datasets: dataSet\n },\n options: {\n maintainAspectRatio: false,\n responsive: true,\n showTooltips: true,\n legend: {\n display: true,\n labels: {\n usePointStyle: true\n }\n },\n interaction: {\n mode: \'index\'\n },\n scales: {\n x: {\n ticks: {\n beginAtZero: true,\n color: "#101010",\n fontFamily: "Source Sans Pro"\n },\n stacked: true\n },\n y: {\n ticks: {\n beginAtZero: true,\n stepSize: 1,\n color: "#101010",\n fontFamily: "Source Sans Pro"\n },\n stacked: true\n }\n },\n plugins: {\n legend: {\n position: \'top\',\n labels: {\n usePointStyle: true,\n color: "#101010",\n padding: 20,\n font: {\n size: 14,\n family: \'Source Sans Pro\'\n }\n }\n }\n },\n tooltips: {\n bodyFont: \'Source Sans Pro\'\n }\n },\n plugins: [legendMargin]\n });\n }\n\n componentDidMount() {\n var $this = this;\n var currentyear = this.state.year ? this.state.year : new Date().getFullYear();\n fetch(\'/api/books/genres\', {\n "method": "GET",\n "headers": {\n "year": currentyear\n }\n }).then(response => response.json()).then(books => {\n this.initChart(books, currentyear);\n });\n this.getShortestLongestBook(this.state.year);\n fetch(\'/api/books/genres/count\', {\n "method": "GET",\n "headers": {\n "year": this.state.year\n }\n }).then(response => response.json()).then(data => {\n this.initDoughnut(data);\n });\n this.getCountries(true);\n fetch(\'/api/books/years\', {\n "method": "GET"\n }).then(response => response.json()).then(data => {\n this.setState({\n readingYears: data\n });\n });\n }\n\n render() {\n var url = window.location.href.split("/");\n var ratingshort = \'\';\n var ratinglong = \'\';\n\n if (this.state.pagesStats.shortestbook) {\n for (var i = 0; i < this.state.pagesStats.shortestbook.rating; i++) {\n ratingshort += "
";\n }\n }\n\n if (document.getElementById("shortest_rating") !== null) {\n document.getElementById(\'shortest_rating\').innerHTML = ratingshort;\n }\n\n if (this.state.pagesStats.longestbook) {\n for (var i = 0; i < this.state.pagesStats.longestbook.rating; i++) {\n ratinglong += "
";\n }\n }\n\n if (document.getElementById("longest_rating") !== null) {\n document.getElementById(\'longest_rating\').innerHTML = ratinglong;\n }\n\n return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement((react__WEBPACK_IMPORTED_MODULE_0___default().Fragment), null, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {\n className: "sidebar"\n }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {\n className: `menu-item ${url && url[3] == "" ? \'selected\' : \'\'}`\n }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("i", {\n className: "fa fa-chart-bar"\n })), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {\n className: `menu-item ${url && url[3] == "books" ? \'selected\' : \'\'}`\n }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("i", {\n className: "fa fa-book"\n }))), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {\n className: "content"\n }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("h1", null, "Dashboard"), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("h2", null, "Leesanalyse van Jordy van Zeeland"), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {\n className: "books-stats"\n }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {\n className: "container-fluid"\n }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {\n className: "row"\n }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {\n className: "col-md-2"\n }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {\n className: "stat-block"\n }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("i", {\n className: "fa fa-calendar"\n }), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", {\n className: "stats-number"\n }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("select", {\n className: "yearselector",\n defaultValue: this.state.year,\n onChange: event => this.changeYear(event)\n }, this.state.readingYears.map((year, i) => {\n if (year === this.state.year) {\n var selected = \'selected\';\n } else {\n selected = \'\';\n }\n\n return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("option", {\n key: i,\n selected: selected,\n value: year\n }, year);\n }))))), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_components_Stats__WEBPACK_IMPORTED_MODULE_2__["default"], {\n year: this.state.year\n })))), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_components_Challenge__WEBPACK_IMPORTED_MODULE_1__["default"], {\n year: this.state.year\n }), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {\n className: "container-fluid"\n }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {\n className: "row"\n }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {\n className: "col-md-9"\n }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {\n className: "books-per-month"\n }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", {\n className: "block_name"\n }, "Boeken per maand per genre"), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("canvas", {\n id: "chart"\n })), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {\n className: "row"\n }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {\n className: "col-md-6"\n }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {\n className: "book shortest"\n }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", {\n className: "block_name"\n }, "Kortste boek"), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("i", {\n className: "fa fa-book book-icon"\n }), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {\n className: "book_pages"\n }, this.state.pagesStats.shortestbook ? this.state.pagesStats.shortestbook.pages : \'\', " pagina\'s"), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {\n className: "book_title_author"\n }, this.state.pagesStats.shortestbook ? this.state.pagesStats.shortestbook.name : \'\', " - ", this.state.pagesStats.shortestbook ? this.state.pagesStats.shortestbook.author : \'\'), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {\n id: "shortest_rating",\n className: "book_rating"\n }))), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {\n className: "col-md-6"\n }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {\n className: "book longest"\n }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", {\n className: "block_name"\n }, "Langste boek"), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("i", {\n className: "fa fa-book book-icon"\n }), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {\n className: "book_pages"\n }, this.state.pagesStats.longestbook ? this.state.pagesStats.longestbook.pages : \'\', " pagina\'s"), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {\n className: "book_title_author"\n }, this.state.pagesStats.longestbook ? this.state.pagesStats.longestbook.name : \'\', " - ", this.state.pagesStats.longestbook ? this.state.pagesStats.longestbook.author : \'\'), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {\n id: "longest_rating",\n className: "book_rating"\n }))))), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {\n className: "col-md-3"\n }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {\n className: "books-per-country"\n }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", {\n className: "block_name"\n }, "Landen"), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("table", {\n id: "DataTable",\n className: "showHead table responsive nowrap",\n width: "100%"\n }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("thead", null, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("tr", null, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("th", null, "#"), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("th", null, "Land"), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("th", null, "Boeken"))), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("tbody", null, this.state.countries.map((country, i) => {\n var code = country.code.toLowerCase();\n return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement((react__WEBPACK_IMPORTED_MODULE_0___default().Fragment), null, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("tr", {\n key: "{i}"\n }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("td", null, i + 1), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("td", null, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("img", {\n src: `https://flagcdn.com/32x24/${code}.png`\n }), " ", country.country), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("td", null, country.count)));\n })))), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {\n className: "genresPercent"\n }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", {\n className: "block_name"\n }, "Genres"), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("canvas", {\n id: "chartGenres"\n })))))));\n }\n\n}\n\n//# sourceURL=webpack://frontend/./src/App.js?')},"./src/components/Challenge.js":(__unused_webpack_module,__webpack_exports__,__webpack_require__)=>{eval('__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ "default": () => (/* binding */ Challenge)\n/* harmony export */ });\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! react */ "./node_modules/react/index.js");\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var _Data_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./Data.js */ "./src/components/Data.js");\n\n\nclass Challenge extends react__WEBPACK_IMPORTED_MODULE_0__.Component {\n constructor(props) {\n super(props);\n this.state = {\n readingYears: [],\n challenge: 0\n };\n }\n\n getComponentData() {\n var $this = this;\n (0,_Data_js__WEBPACK_IMPORTED_MODULE_1__.getStats)(this.props.year).then(data => {\n $this.setState({\n totalbooks: data.totalbooks\n });\n });\n (0,_Data_js__WEBPACK_IMPORTED_MODULE_1__.getChallenge)(this.props.year).then(data => {\n this.setState({\n challenge: data && data.length > 0 ? data[0].nrofbooks : 0\n });\n });\n }\n\n componentDidMount() {\n this.getComponentData();\n }\n\n componentDidUpdate(prevProps, prevState) {\n if (prevProps.year !== this.props.year) {\n this.getComponentData();\n }\n }\n\n render() {\n var challengePercentage = this.state.totalbooks / this.state.challenge * 100;\n return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement((react__WEBPACK_IMPORTED_MODULE_0___default().Fragment), null, this.state.challenge && this.state.challenge !== 0 ? /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {\n className: "container-fluid"\n }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {\n className: "row"\n }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {\n className: "col-md-12"\n }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {\n className: "stat-block"\n }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", {\n className: "block_name"\n }, "Book Challenge"), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {\n className: "progress"\n }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {\n className: "progress-bar progress-bar-striped",\n role: "progressbar",\n style: {\n width: challengePercentage + \'%\'\n },\n "aria-valuenow": challengePercentage,\n "aria-valuemin": "0",\n "aria-valuemax": "100"\n }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {\n className: "progress-bar-number"\n }, challengePercentage, "%"))), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", {\n className: "stats-number"\n }, this.state.totalbooks), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", {\n className: "stats-label"\n }, "van de"), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", {\n className: "stats-number"\n }, this.state.challenge), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", {\n className: "stats-label"\n }, "boeken gelezen"))))) : \'\');\n }\n\n}\n\n//# sourceURL=webpack://frontend/./src/components/Challenge.js?')},"./src/components/Data.js":(__unused_webpack_module,__webpack_exports__,__webpack_require__)=>{eval('__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ "getChallenge": () => (/* binding */ getChallenge),\n/* harmony export */ "getReadingYears": () => (/* binding */ getReadingYears),\n/* harmony export */ "getStats": () => (/* binding */ getStats)\n/* harmony export */ });\nconst getStats = year => {\n return fetch(\'/api/books/stats\', {\n "method": "GET",\n "headers": {\n "year": year\n }\n }).then(response => response.json()).then(data => {\n return data;\n });\n};\nconst getChallenge = year => {\n return fetch(\'/api/books/challenge\', {\n "method": "GET",\n "headers": {\n "year": year\n }\n }).then(response => response.json()).then(data => {\n return data;\n });\n};\nconst getReadingYears = () => {\n return fetch(\'/api/books/years\', {\n "method": "GET"\n }).then(response => response.json()).then(data => {\n return data;\n });\n};\n\n//# sourceURL=webpack://frontend/./src/components/Data.js?')},"./src/components/Stats.js":(__unused_webpack_module,__webpack_exports__,__webpack_require__)=>{eval('__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ "default": () => (/* binding */ BookStats)\n/* harmony export */ });\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! react */ "./node_modules/react/index.js");\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var _Data_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./Data.js */ "./src/components/Data.js");\n\n\nclass BookStats extends react__WEBPACK_IMPORTED_MODULE_0__.Component {\n constructor(props) {\n super(props);\n this.state = {\n readingYears: [],\n totalbooks: 0,\n totalpages: 0,\n totalauthors: 0,\n totalcountries: 0,\n totalgenres: 0\n };\n }\n\n getComponentData() {\n var $this = this;\n (0,_Data_js__WEBPACK_IMPORTED_MODULE_1__.getStats)(this.props.year).then(data => {\n $this.setState({\n totalbooks: data.totalbooks,\n totalpages: data.totalpages,\n totalauthors: data.totalauthors,\n totalcountries: data.totalcountries,\n totalgenres: data.totalgenres\n });\n });\n (0,_Data_js__WEBPACK_IMPORTED_MODULE_1__.getReadingYears)().then(data => {\n this.setState({\n readingYears: data\n });\n });\n }\n\n componentDidMount() {\n this.getComponentData();\n }\n\n componentDidUpdate(prevProps, prevState) {\n if (prevProps.year !== this.props.year) {\n this.getComponentData();\n }\n }\n\n render() {\n return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement((react__WEBPACK_IMPORTED_MODULE_0___default().Fragment), null, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {\n className: "col-md-2"\n }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {\n className: "stat-block"\n }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("i", {\n className: "fa fa-book"\n }), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", {\n className: "stats-number"\n }, this.state.totalbooks), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", {\n className: "stats-label"\n }, "Boeken"))), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {\n className: "col-md-2"\n }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {\n className: "stat-block"\n }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("i", {\n className: "fa fa-book-open"\n }), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", {\n className: "stats-number"\n }, this.state.totalpages), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", {\n className: "stats-label"\n }, "Bladzijdes"))), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {\n className: "col-md-2"\n }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {\n className: "stat-block"\n }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("i", {\n className: "fa fa-pen"\n }), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", {\n className: "stats-number"\n }, this.state.totalauthors), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", {\n className: "stats-label"\n }, "Schrijvers"))), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {\n className: "col-md-2"\n }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {\n className: "stat-block"\n }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("i", {\n className: "fa fa-book"\n }), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", {\n className: "stats-number"\n }, this.state.totalgenres), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", {\n className: "stats-label"\n }, "Genres"))), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {\n className: "col-md-2"\n }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {\n className: "stat-block"\n }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("i", {\n className: "fa fa-globe"\n }), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", {\n className: "stats-number"\n }, this.state.totalcountries), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", {\n className: "stats-label"\n }, "Landen"))));\n }\n\n}\n\n//# sourceURL=webpack://frontend/./src/components/Stats.js?')},"./src/index.js":(__unused_webpack_module,__webpack_exports__,__webpack_require__)=>{eval('__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! react */ "./node_modules/react/index.js");\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var react_dom__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! react-dom */ "./node_modules/react-dom/index.js");\n/* harmony import */ var _App__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./App */ "./src/App.js");\n\n\n\nreact_dom__WEBPACK_IMPORTED_MODULE_1__.render( /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_App__WEBPACK_IMPORTED_MODULE_2__["default"], null), document.getElementById(\'app\'));\n\n//# sourceURL=webpack://frontend/./src/index.js?')},"./node_modules/react-dom/cjs/react-dom.development.js":(__unused_webpack_module,exports,__webpack_require__)=>{eval("/**\n * @license React\n * react-dom.development.js\n *\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n\n\nif (true) {\n (function() {\n\n 'use strict';\n\n/* global __REACT_DEVTOOLS_GLOBAL_HOOK__ */\nif (\n typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ !== 'undefined' &&\n typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart ===\n 'function'\n) {\n __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart(new Error());\n}\n var React = __webpack_require__(/*! react */ \"./node_modules/react/index.js\");\nvar Scheduler = __webpack_require__(/*! scheduler */ \"./node_modules/scheduler/index.js\");\n\nvar ReactSharedInternals = React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;\n\nvar suppressWarning = false;\nfunction setSuppressWarning(newSuppressWarning) {\n {\n suppressWarning = newSuppressWarning;\n }\n} // In DEV, calls to console.warn and console.error get replaced\n// by calls to these methods by a Babel plugin.\n//\n// In PROD (or in packages without access to React internals),\n// they are left as they are instead.\n\nfunction warn(format) {\n {\n if (!suppressWarning) {\n for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {\n args[_key - 1] = arguments[_key];\n }\n\n printWarning('warn', format, args);\n }\n }\n}\nfunction error(format) {\n {\n if (!suppressWarning) {\n for (var _len2 = arguments.length, args = new Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) {\n args[_key2 - 1] = arguments[_key2];\n }\n\n printWarning('error', format, args);\n }\n }\n}\n\nfunction printWarning(level, format, args) {\n // When changing this logic, you might want to also\n // update consoleWithStackDev.www.js as well.\n {\n var ReactDebugCurrentFrame = ReactSharedInternals.ReactDebugCurrentFrame;\n var stack = ReactDebugCurrentFrame.getStackAddendum();\n\n if (stack !== '') {\n format += '%s';\n args = args.concat([stack]);\n } // eslint-disable-next-line react-internal/safe-string-coercion\n\n\n var argsWithFormat = args.map(function (item) {\n return String(item);\n }); // Careful: RN currently depends on this prefix\n\n argsWithFormat.unshift('Warning: ' + format); // We intentionally don't use spread (or .apply) directly because it\n // breaks IE9: https://github.com/facebook/react/issues/13610\n // eslint-disable-next-line react-internal/no-production-logging\n\n Function.prototype.apply.call(console[level], console, argsWithFormat);\n }\n}\n\nvar FunctionComponent = 0;\nvar ClassComponent = 1;\nvar IndeterminateComponent = 2; // Before we know whether it is function or class\n\nvar HostRoot = 3; // Root of a host tree. Could be nested inside another node.\n\nvar HostPortal = 4; // A subtree. Could be an entry point to a different renderer.\n\nvar HostComponent = 5;\nvar HostText = 6;\nvar Fragment = 7;\nvar Mode = 8;\nvar ContextConsumer = 9;\nvar ContextProvider = 10;\nvar ForwardRef = 11;\nvar Profiler = 12;\nvar SuspenseComponent = 13;\nvar MemoComponent = 14;\nvar SimpleMemoComponent = 15;\nvar LazyComponent = 16;\nvar IncompleteClassComponent = 17;\nvar DehydratedFragment = 18;\nvar SuspenseListComponent = 19;\nvar ScopeComponent = 21;\nvar OffscreenComponent = 22;\nvar LegacyHiddenComponent = 23;\nvar CacheComponent = 24;\nvar TracingMarkerComponent = 25;\n\n// -----------------------------------------------------------------------------\n\nvar enableClientRenderFallbackOnTextMismatch = true; // TODO: Need to review this code one more time before landing\n// the react-reconciler package.\n\nvar enableNewReconciler = false; // Support legacy Primer support on internal FB www\n\nvar enableLazyContextPropagation = false; // FB-only usage. The new API has different semantics.\n\nvar enableLegacyHidden = false; // Enables unstable_avoidThisFallback feature in Fiber\n\nvar enableSuspenseAvoidThisFallback = false; // Enables unstable_avoidThisFallback feature in Fizz\n// React DOM Chopping Block\n//\n// Similar to main Chopping Block but only flags related to React DOM. These are\n// grouped because we will likely batch all of them into a single major release.\n// -----------------------------------------------------------------------------\n// Disable support for comment nodes as React DOM containers. Already disabled\n// in open source, but www codebase still relies on it. Need to remove.\n\nvar disableCommentsAsDOMContainers = true; // Disable javascript: URL strings in href for XSS protection.\n// and client rendering, mostly to allow JSX attributes to apply to the custom\n// element's object properties instead of only HTML attributes.\n// https://github.com/facebook/react/issues/11347\n\nvar enableCustomElementPropertySupport = false; // Disables children for