1
1
Fork 0

Redesign components in frontend (DRY)

This commit is contained in:
Jordy van Zeeland 2022-10-27 11:17:03 +02:00
parent 91c530bcd5
commit cfb3290194
9 changed files with 454 additions and 147 deletions

View File

@ -2,6 +2,8 @@ from django.urls import path
from .views import *
urlpatterns = [
path('books', getAllBooks),
path('books/challenge', getChallengeOfYear),
path('books/years', getYears),
path('books/stats', getStats),

View File

@ -17,6 +17,15 @@ def getBooksData():
return df
def getBookChallenge(year = None):
engine = create_engine('mysql+mysqldb://' + ras.settings.DATABASES['default']['USER'] + ':' + ras.settings.DATABASES['default']['PASSWORD'] + '@' + ras.settings.DATABASES['default']['HOST'] + ':3306/' + ras.settings.DATABASES['default']['NAME'])
if(year):
df = pd.read_sql('SELECT * FROM book_challenge where year = ' + year, engine)
else:
df = pd.read_sql('SELECT * FROM book_challenge', engine)
return df
def filterData(df, datayear = None):
df['readed'] = pd.to_datetime(df['readed'], format='%Y-%m-%d')
df['readed'] = df['readed'].dt.strftime('%m-%Y')
@ -27,6 +36,43 @@ def filterData(df, datayear = None):
return df
@api_view(['GET'])
def getAllBooks(request):
data = []
books = getBooksData()
for index, row in books.iterrows():
data.append({
"id": row['id'],
"name": row['name'],
"author": row['author'],
"genre": row['genre'],
"author": row['author'],
"country": row['country'],
"country_code": row['country_code'],
"pages": row['pages'],
"readed": row['readed'],
"rating": row['rating'],
})
return Response(data)
@api_view(['GET'])
def getChallengeOfYear(request):
if request.META.get('HTTP_YEAR'):
data = []
df = getBookChallenge(request.META.get('HTTP_YEAR'))
for index, row in df.iterrows():
data.append({
"year": row['year'],
"nrofbooks": row['nrofbooks']
})
return Response(data)
else:
return Response("No year header included")
@api_view(['GET'])
def books_per_genre_per_month(request):

View File

@ -1,4 +1,6 @@
import React, { Component } from "react";
import Challenge from "./components/Challenge";
import BookStats from "./components/Stats";
export default class App extends Component {
constructor(props) {
@ -6,18 +8,14 @@ export default class App extends Component {
this.state = {
year: new Date().getFullYear(),
readingYears: [],
totalbooks: 0,
totalpages: 0,
totalauthors: 0,
totalcountries: 0,
totalgenres: 0,
countries: []
countries: [],
pagesStats: [],
}
this.yearsArray = [];
}
getGenres(){
getGenres() {
fetch('/api/books/genres', {
"method": "GET",
"headers": {
@ -30,7 +28,7 @@ export default class App extends Component {
})
}
getCountries(init){
getCountries(init) {
fetch('/api/books/countries', {
"method": "GET",
"headers": {
@ -43,7 +41,7 @@ export default class App extends Component {
countries: data
})
if(init == true){
if (init == true) {
$('#DataTable').DataTable({
paging: false,
ordering: false,
@ -54,33 +52,31 @@ export default class App extends Component {
})
}
getShortestLongestBook(currentyear) {
fetch('/api/books/pages/stats', {
"method": "GET",
"headers": {
"year": currentyear
}
})
.then(response => response.json())
.then(bookstats => {
this.setState({
pagesStats: bookstats
})
})
}
changeYear(event) {
this.setState({
year: event.target.value
})
fetch('/api/books/stats', {
"method": "GET",
"headers": {
"year": event.target.value
}
})
.then(response => response.json())
.then(data => {
this.setState({
totalbooks: data.totalbooks,
totalpages: data.totalpages,
totalauthors: data.totalauthors,
totalcountries: data.totalcountries,
totalgenres: data.totalgenres
})
})
fetch('/api/books/countries', {
"method": "GET",
"headers": {
"year": this.state.year
"year": event.target.value
}
})
.then(response => response.json())
@ -94,19 +90,32 @@ export default class App extends Component {
var $this = this;
this.getShortestLongestBook(event.target.value);
fetch('/api/books/genres/count', {
"method": "GET",
"headers": {
"year": this.state.year
"year": event.target.value
}
})
.then(response => response.json())
.then(data => {
this.initDoughnut(data);
})
fetch('/api/books/genres', {
"method": "GET",
"headers": {
"year": event.target.value
}
})
.then(response => response.json())
.then(books => {
this.initChart(books, event.target.value);
})
}
initHorBar(data){
initHorBar(data) {
var countries = [];
var counts = [];
@ -177,10 +186,10 @@ export default class App extends Component {
const legendMargin = {
id: 'legendMargin',
beforeInit(chart, legend, options){
beforeInit(chart, legend, options) {
const fitValue = chart.legend.fit;
chart.legend.fit = function fit(){
chart.legend.fit = function fit() {
fitValue.bind(chart.legend)();
return this.height += 30;
}
@ -205,7 +214,7 @@ export default class App extends Component {
borderColor: '#1f2940',
tooltip: {
callbacks: {
label: function(context) {
label: function (context) {
let label = context.label;
let value = context.formattedValue;
@ -246,15 +255,15 @@ export default class App extends Component {
},
plugins: [{
id: 'legendMargin',
beforeInit(chart, legend, options){
beforeInit(chart, legend, options) {
const fitValue = chart.legend.fit;
chart.legend.fit = function fit(){
chart.legend.fit = function fit() {
fitValue.bind(chart.legend)();
return this.height += 30;
}
}
},{
}, {
afterDraw: chart => {
var ctx = chart.ctx;
ctx.save();
@ -263,7 +272,7 @@ export default class App extends Component {
var imageSize = 80;
ctx.drawImage(image, chart.width / 2 - imageSize / 2, chart.height / 2 - imageSize / 6, imageSize, imageSize);
ctx.restore();
}
}
}],
});
}
@ -335,10 +344,10 @@ export default class App extends Component {
const legendMargin = {
id: 'legendMargin',
beforeInit(chart, legend, options){
beforeInit(chart, legend, options) {
const fitValue = chart.legend.fit;
chart.legend.fit = function fit(){
chart.legend.fit = function fit() {
fitValue.bind(chart.legend)();
return this.height += 30;
}
@ -358,7 +367,7 @@ export default class App extends Component {
legend: {
display: true,
labels: {
usePointStyle: true,
usePointStyle: true,
}
},
interaction: {
@ -405,15 +414,11 @@ export default class App extends Component {
});
}
componentDidUpdate() {
this.getGenres();
}
componentDidMount() {
var $this = this;
var currentyear = this.state.year ? this.state.year : new Date().getFullYear()
var currentyear = this.state.year ? this.state.year : new Date().getFullYear();
fetch('/api/books/genres', {
"method": "GET",
@ -426,6 +431,8 @@ export default class App extends Component {
this.initChart(books, currentyear);
})
this.getShortestLongestBook(this.state.year);
fetch('/api/books/genres/count', {
"method": "GET",
"headers": {
@ -439,46 +446,54 @@ export default class App extends Component {
this.getCountries(true);
fetch('/api/books/stats', {
fetch('/api/books/years', {
"method": "GET",
"headers": {
"year": this.state.year
}
})
.then(response => response.json())
.then(data => {
$this.setState({
totalbooks: data.totalbooks,
totalpages: data.totalpages,
totalauthors: data.totalauthors,
totalcountries: data.totalcountries,
totalgenres: data.totalgenres
this.setState({
readingYears: data
})
})
fetch('/api/books/years', {
"method": "GET",
})
.then(response => response.json())
.then(data => {
this.setState({
readingYears: data
})
})
}
render() {
var url = window.location.href.split("/");
var ratingshort = '';
var ratinglong = '';
if (this.state.pagesStats.shortestbook) {
for (var i = 0; i < this.state.pagesStats.shortestbook.rating; i++) {
ratingshort += "<i class='fas fa-star'></i>";
}
}
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 += "<i class='fas fa-star'></i>";
}
}
if (document.getElementById("longest_rating") !== null) {
document.getElementById('longest_rating').innerHTML = ratinglong;
}
return (
<React.Fragment>
<div className="sidebar">
<div className={`menu-item ${ url && url[3] == "" ? 'selected' : ''}`}>
<i class="fa fa-chart-bar"></i>
<div className={`menu-item ${url && url[3] == "" ? 'selected' : ''}`}>
<i className="fa fa-chart-bar"></i>
</div>
<div className={`menu-item ${ url && url[3] == "books" ? 'selected' : ''}`}>
<i class="fa fa-book"></i>
<div className={`menu-item ${url && url[3] == "books" ? 'selected' : ''}`}>
<i className="fa fa-book"></i>
</div>
</div>
@ -492,96 +507,85 @@ export default class App extends Component {
<div className="row">
<div className="col-md-2">
<div className="stat-block">
<i class="fa fa-calendar"></i>
<i className="fa fa-calendar"></i>
<span className="stats-number">
<select className="yearselector" defaultValue={this.state.year} onChange={(event) => this.changeYear(event)}>
{this.state.readingYears.map((year) => {
{this.state.readingYears.map((year, i) => {
if(year === this.state.year){
if (year === this.state.year) {
var selected = 'selected'
}else{
} else {
selected = ''
}
return(<option selected={selected} value={year}>{year}</option>)
return (<option key={i} selected={selected} value={year}>{year}</option>)
})}
</select>
</span>
</div>
</div>
<div className="col-md-2">
<div className="stat-block">
<i class="fa fa-book"></i>
<span className="stats-number">{this.state.totalbooks}</span>
<span className="stats-label">Boeken</span>
</div>
</div>
<div className="col-md-2">
<div className="stat-block">
<i class="fa fa-book-open"></i>
<span className="stats-number">{this.state.totalpages}</span>
<span className="stats-label">Bladzijdes</span>
</div>
</div>
<div className="col-md-2">
<div className="stat-block">
<i class="fa fa-pen"></i>
<span className="stats-number">{this.state.totalauthors}</span>
<span className="stats-label">Schrijvers</span>
</div>
</div>
<div className="col-md-2">
<div className="stat-block">
<i class="fa fa-book"></i>
<span className="stats-number">{this.state.totalgenres}</span>
<span className="stats-label">Genres</span>
</div>
</div>
<div className="col-md-2">
<div className="stat-block">
<i class="fa fa-globe"></i>
<span className="stats-number">{this.state.totalcountries}</span>
<span className="stats-label">Landen</span>
</div>
</div>
<BookStats year={this.state.year} />
</div>
</div>
</div>
<Challenge year={this.state.year} />
<div className="container-fluid">
<div className="row">
<div className="col-md-9">
<div className="books-per-month"><span className="block_name">Boeken per maand per genre</span><canvas id="chart"></canvas></div>
<div className="row">
<div className="col-md-6">
<div className="book shortest">
<span className="block_name">Kortste boek</span>
<i className="fa fa-book book-icon"></i>
<div className="book_pages">{this.state.pagesStats.shortestbook ? this.state.pagesStats.shortestbook.pages : ''} pagina's</div>
<div className="book_title_author">{this.state.pagesStats.shortestbook ? this.state.pagesStats.shortestbook.name : ''} - {this.state.pagesStats.shortestbook ? this.state.pagesStats.shortestbook.author : ''}</div>
<div id="shortest_rating" className="book_rating"></div>
</div>
</div>
<div className="col-md-6">
<div className="book longest">
<span className="block_name">Langste boek</span>
<i className="fa fa-book book-icon"></i>
<div className="book_pages">{this.state.pagesStats.longestbook ? this.state.pagesStats.longestbook.pages : ''} pagina's</div>
<div className="book_title_author">{this.state.pagesStats.longestbook ? this.state.pagesStats.longestbook.name : ''} - {this.state.pagesStats.longestbook ? this.state.pagesStats.longestbook.author : ''}</div>
<div id="longest_rating" className="book_rating"></div>
</div>
</div>
</div>
</div>
<div className="col-md-3">
<div className="books-per-country">
<div className="books-per-country">
<span className="block_name">Landen</span>
<table id="DataTable" class="showHead table responsive nowrap" width="100%">
<thead>
<tr>
<th>#</th>
<th>Land</th>
<th>Boeken</th>
</tr>
</thead>
<tbody>
{this.state.countries.map((country, i) => {
<table id="DataTable" className="showHead table responsive nowrap" width="100%">
<thead>
<tr>
<th>#</th>
<th>Land</th>
<th>Boeken</th>
</tr>
</thead>
<tbody>
{this.state.countries.map((country, i) => {
var code = country.code.toLowerCase();
return(
<React.Fragment>
<tr>
<td>{i+1}</td>
<td><img src={`https://flagcdn.com/32x24/${code}.png`} /> {country.country}</td>
<td>{country.count}</td>
</tr>
</React.Fragment>
)
var code = country.code.toLowerCase();
return (
<React.Fragment>
<tr key="{i}">
<td>{i + 1}</td>
<td><img src={`https://flagcdn.com/32x24/${code}.png`} /> {country.country}</td>
<td>{country.count}</td>
</tr>
</React.Fragment>
)
})}
</tbody>
})}
</tbody>
</table>
</div>
<div className="genresPercent"><span className="block_name">Genres</span><canvas id="chartGenres"></canvas></div>

View File

@ -0,0 +1,65 @@
import React, { Component } from 'react';
import { getChallenge, getStats } from "./Data.js";
export default class Challenge extends Component {
constructor(props) {
super(props);
this.state = {
readingYears: [],
challenge: 0
}
}
getComponentData(){
var $this = this;
getStats(this.props.year).then(data => {
$this.setState({
totalbooks: data.totalbooks
})
});
getChallenge(this.props.year).then(data => {
this.setState({
challenge: data && data.length > 0 ? data[0].nrofbooks : 0
})
});
}
componentDidMount() {
this.getComponentData();
}
componentDidUpdate(prevProps, prevState) {
if (prevProps.year !== this.props.year) {
this.getComponentData();
}
}
render() {
var challengePercentage = (this.state.totalbooks / this.state.challenge) * 100
return (
<React.Fragment>
{this.state.challenge && this.state.challenge !== 0 ?
<div className="container-fluid">
<div className="row">
<div className="col-md-12">
<div className="stat-block">
<span className="block_name">Book Challenge</span>
<div className="progress">
<div className="progress-bar progress-bar-striped" role="progressbar" style={{ width: challengePercentage + '%' }} aria-valuenow={challengePercentage} aria-valuemin="0" aria-valuemax="100">
<div className="progress-bar-number">{challengePercentage}%</div>
</div>
</div>
<span className="stats-number">{this.state.totalbooks}</span><span className="stats-label">van de</span><span className="stats-number">{this.state.challenge}</span><span className="stats-label">boeken gelezen</span>
</div>
</div>
</div>
</div>
: ''}
</React.Fragment>
)
}
}

View File

@ -0,0 +1,35 @@
export const getStats = (year) => {
return fetch('/api/books/stats', {
"method": "GET",
"headers": {
"year": year
}
})
.then(response => response.json())
.then(data => {
return data;
})
}
export const getChallenge = (year) => {
return fetch('/api/books/challenge', {
"method": "GET",
"headers": {
"year": year
}
})
.then(response => response.json())
.then(data => {
return data
})
}
export const getReadingYears = () => {
return fetch('/api/books/years', {
"method": "GET",
})
.then(response => response.json())
.then(data => {
return data
})
}

View File

@ -0,0 +1,88 @@
import React, { Component } from 'react';
import { getStats, getReadingYears } from "./Data.js";
export default class BookStats extends Component {
constructor(props) {
super(props);
this.state = {
readingYears: [],
totalbooks: 0,
totalpages: 0,
totalauthors: 0,
totalcountries: 0,
totalgenres: 0,
}
}
getComponentData(){
var $this = this;
getStats(this.props.year).then(data => {
$this.setState({
totalbooks: data.totalbooks,
totalpages: data.totalpages,
totalauthors: data.totalauthors,
totalcountries: data.totalcountries,
totalgenres: data.totalgenres
})
});
getReadingYears().then(data => {
this.setState({
readingYears: data
})
});
}
componentDidMount() {
this.getComponentData();
}
componentDidUpdate(prevProps, prevState) {
if (prevProps.year !== this.props.year) {
this.getComponentData();
}
}
render() {
return (
<React.Fragment>
<div className="col-md-2">
<div className="stat-block">
<i className="fa fa-book"></i>
<span className="stats-number">{this.state.totalbooks}</span>
<span className="stats-label">Boeken</span>
</div>
</div>
<div className="col-md-2">
<div className="stat-block">
<i className="fa fa-book-open"></i>
<span className="stats-number">{this.state.totalpages}</span>
<span className="stats-label">Bladzijdes</span>
</div>
</div>
<div className="col-md-2">
<div className="stat-block">
<i className="fa fa-pen"></i>
<span className="stats-number">{this.state.totalauthors}</span>
<span className="stats-label">Schrijvers</span>
</div>
</div>
<div className="col-md-2">
<div className="stat-block">
<i className="fa fa-book"></i>
<span className="stats-number">{this.state.totalgenres}</span>
<span className="stats-label">Genres</span>
</div>
</div>
<div className="col-md-2">
<div className="stat-block">
<i className="fa fa-globe"></i>
<span className="stats-number">{this.state.totalcountries}</span>
<span className="stats-label">Landen</span>
</div>
</div>
</React.Fragment>
)
}
}

File diff suppressed because one or more lines are too long

View File

@ -6,10 +6,22 @@
!*** ./src/index.js ***!
\**********************/
/*!********************************!*\
!*** ./src/components/Data.js ***!
\********************************/
/*!*********************************!*\
!*** ./src/components/Stats.js ***!
\*********************************/
/*!*************************************!*\
!*** ./node_modules/react/index.js ***!
\*************************************/
/*!*************************************!*\
!*** ./src/components/Challenge.js ***!
\*************************************/
/*!*****************************************!*\
!*** ./node_modules/react-dom/index.js ***!
\*****************************************/

View File

@ -73,7 +73,7 @@
height:600px !important;
}
.books-per-month, .genresPercent, .books-per-country{
.books-per-month, .genresPercent, .books-per-country, .book{
background: #ffffff;
padding: 20px;
box-shadow: 0 2px 0px 1px rgb(0 0 0 / 3%);
@ -81,6 +81,33 @@
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;
@ -116,7 +143,7 @@
padding: 50px;
}
.books-stats .stat-block{
.books-stats .stat-block, .stat-block{
background: #ffffff;
box-shadow: 0 2px 0px 1px rgb(0 0 0 / 3%);
padding: 15px 5px;
@ -163,7 +190,7 @@
/* box-shadow: 2px 2px 0px 0px rgba(0, 0, 0, 0.3); */
}
.books-stats .stat-block .stats-number{
.books-stats .stat-block .stats-number, .stats-number{
font-weight: 600;
display: inline-block;
margin-left: 10px;
@ -171,7 +198,7 @@
margin-right: 10px;
}
.books-stats .stat-block .stats-label{
.books-stats .stat-block .stats-label, .stats-label{
color: #a7adbd;
font-weight: 400;
font-size: 20px;
@ -218,6 +245,34 @@
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;
}
</style>
</head>