1
1
Fork 0

Challenges + manage books

This commit is contained in:
Jordy van Zeeland 2023-11-13 16:09:59 +01:00
parent 5dd2ad780c
commit 9a33be5460
23 changed files with 1192 additions and 326 deletions

View File

@ -0,0 +1,122 @@
import jwt, json
from django.contrib.auth import get_user_model
from rest_framework.decorators import api_view
import ras.settings
from sqlalchemy import create_engine
from sqlalchemy.sql import text
from django.http import JsonResponse
import pandas as pd
from rest_framework.response import Response
def filterData(df, datayear = None):
df['readed'] = pd.to_datetime(df['readed'], format='%Y-%m-%d')
df['readed'] = df['readed'].dt.strftime('%m-%Y')
# Filter data on year
if datayear and datayear is not None:
df = df.where(df['readed'].str.contains(datayear))
return df
def getBooksData():
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'])
df = pd.read_sql('SELECT * FROM api_books ORDER BY readed', engine, parse_dates={'readed': {'format': '%m-%Y'}})
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
@api_view(['GET'])
def getAllChallenges(request):
data = []
df = getBookChallenge()
for index, row in df.iterrows():
books = filterData(getBooksData(), str(row['year']))
books = books.dropna()
totalBooksRead = books['name'].count()
data.append({
"id": row['id'],
"year": row['year'],
"nrofbooks": row['nrofbooks'],
"booksread": totalBooksRead
})
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(['POST'])
def addChallenge(request):
if(request.headers.get('Authorization')):
token = request.headers.get('Authorization').split(' ')[1]
try:
User = get_user_model()
payload = jwt.decode(token, 'secret', algorithms=['HS256'])
user = User.objects.get(id=payload['id'])
if(user):
year = request.POST.get('year')
challenge = request.POST.get('challenge')
if(year and challenge):
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'])
conn = engine.connect()
conn.execute(text("INSERT INTO book_challenge (year, nrofbooks) VALUES ('" + str(year) + "', '" + str(challenge) + "')"))
return JsonResponse("OK", safe=False)
else:
return JsonResponse({'error': 'No year and challenge detected'}, safe=False)
else:
return JsonResponse({'error': 'No user detected'}, safe=False)
except (jwt.DecodeError, User.DoesNotExist):
return JsonResponse({'error': 'Token invalid'}, safe=False)
@api_view(['DELETE'])
def deleteChallenge(request, id = None):
if(request.headers.get('Authorization')):
token = request.headers.get('Authorization').split(' ')[1]
try:
User = get_user_model()
payload = jwt.decode(token, 'secret', algorithms=['HS256'])
user = User.objects.get(id=payload['id'])
if(user):
if(id):
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'])
conn = engine.connect()
conn.execute(text("DELETE FROM book_challenge WHERE id = " + str(id)))
return JsonResponse("OK", safe=False)
else:
return JsonResponse({'error': 'No challengeid detected'}, safe=False)
else:
return JsonResponse({'error': 'No user detected'}, safe=False)
except (jwt.DecodeError, User.DoesNotExist):
return JsonResponse({'error': 'Token invalid'}, safe=False)

View File

@ -8,33 +8,53 @@ from django.http import JsonResponse
import pandas as pd import pandas as pd
from rest_framework.response import Response from rest_framework.response import Response
def getBooksData(): 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'])
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']) conn = engine.connect()
df = pd.read_sql('SELECT * FROM api_books ORDER BY readed', engine, parse_dates={'readed': {'format': '%m-%Y'}})
return df # -------------------------------
# Get all books in the database
# -------------------------------
@api_view(['GET']) @api_view(['GET'])
def getAllBooks(request): def getAllBooks(request):
if(request.headers.get('Authorization')):
token = request.headers.get('Authorization').split(' ')[1]
data = [] try:
books = getBooksData() User = get_user_model()
payload = jwt.decode(token, 'secret', algorithms=['HS256'])
user = User.objects.get(id=payload['id'])
for index, row in books.iterrows(): if(user):
data.append({ books = pd.read_sql('SELECT * FROM api_books ORDER BY readed', engine, parse_dates={'readed': {'format': '%m-%Y'}})
"id": row['id'], data = []
"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) 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)
else:
return JsonResponse({'error': 'No user detected'}, safe=False)
except (jwt.DecodeError, User.DoesNotExist):
return JsonResponse({'error': 'Token invalid'}, safe=False)
else:
return JsonResponse({'error': 'Unauthorized'}, safe=False)
# -------------------------------
# Add a book into the database
# -------------------------------
@api_view(['POST']) @api_view(['POST'])
def addBook(request): def addBook(request):
@ -49,8 +69,6 @@ def addBook(request):
user = User.objects.get(id=payload['id']) user = User.objects.get(id=payload['id'])
if(user): if(user):
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'])
conn = engine.connect()
conn.execute(text("INSERT INTO api_books (name, author, genre, country, country_code, pages, readed, rating) VALUES ('" + str(book['name']) + "', '" + str(book['author']) + "', '" + str(book['genre']) + "', '" + str(book['country']) + "', '" + str(book['country_code']) + "', " + str(book['pages']) + ", '" + str(book['readed']) + "', " + str(book['rating']) + ")")) conn.execute(text("INSERT INTO api_books (name, author, genre, country, country_code, pages, readed, rating) VALUES ('" + str(book['name']) + "', '" + str(book['author']) + "', '" + str(book['genre']) + "', '" + str(book['country']) + "', '" + str(book['country_code']) + "', " + str(book['pages']) + ", '" + str(book['readed']) + "', " + str(book['rating']) + ")"))
return JsonResponse("OK", safe=False) return JsonResponse("OK", safe=False)
else: else:
@ -59,7 +77,11 @@ def addBook(request):
except (jwt.DecodeError, User.DoesNotExist): except (jwt.DecodeError, User.DoesNotExist):
return JsonResponse({'error': 'Token invalid'}, safe=False) return JsonResponse({'error': 'Token invalid'}, safe=False)
else: else:
return JsonResponse({'error': 'testing'}, safe=False) return JsonResponse({'error': 'Unauthorized'}, safe=False)
# -------------------------------
# Update a book in the database
# -------------------------------
@api_view(['PUT']) @api_view(['PUT'])
def updateBook(request): def updateBook(request):
@ -75,10 +97,7 @@ def updateBook(request):
user = User.objects.get(id=payload['id']) user = User.objects.get(id=payload['id'])
if(user): if(user):
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'])
conn = engine.connect()
conn.execute(text("UPDATE api_books set name='" + str(book['name']) + "', author='" + str(book['author']) + "', genre='" + str(book['genre']) + "', country='" + str(book['country']) + "', country_code='" + str(book['country_code']) + "', pages='" + str(book['pages']) + "', readed='" + str(book['readed']) + "', rating='" + str(book['rating']) + "' WHERE id=" + str(bookid))) conn.execute(text("UPDATE api_books set name='" + str(book['name']) + "', author='" + str(book['author']) + "', genre='" + str(book['genre']) + "', country='" + str(book['country']) + "', country_code='" + str(book['country_code']) + "', pages='" + str(book['pages']) + "', readed='" + str(book['readed']) + "', rating='" + str(book['rating']) + "' WHERE id=" + str(bookid)))
return JsonResponse("OK", safe=False) return JsonResponse("OK", safe=False)
else: else:
return JsonResponse({'error': 'No user detected'}, safe=False) return JsonResponse({'error': 'No user detected'}, safe=False)
@ -86,7 +105,12 @@ def updateBook(request):
except (jwt.DecodeError, User.DoesNotExist): except (jwt.DecodeError, User.DoesNotExist):
return JsonResponse({'error': 'Token invalid'}, safe=False) return JsonResponse({'error': 'Token invalid'}, safe=False)
else: else:
return JsonResponse({'error': 'No Token'}, safe=False) return JsonResponse({'error': 'Unauthorized'}, safe=False)
# -------------------------------
# Delete a book in the database
# -------------------------------
@api_view(['DELETE']) @api_view(['DELETE'])
def deleteBook(request): def deleteBook(request):
@ -100,8 +124,6 @@ def deleteBook(request):
user = User.objects.get(id=payload['id']) user = User.objects.get(id=payload['id'])
if(user): if(user):
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'])
conn = engine.connect()
conn.execute(text("DELETE FROM api_books WHERE id = " + str(bookid))) conn.execute(text("DELETE FROM api_books WHERE id = " + str(bookid)))
return JsonResponse("OK", safe=False) return JsonResponse("OK", safe=False)
else: else:
@ -110,4 +132,4 @@ def deleteBook(request):
except (jwt.DecodeError, User.DoesNotExist): except (jwt.DecodeError, User.DoesNotExist):
return JsonResponse({'error': 'Token invalid'}, safe=False) return JsonResponse({'error': 'Token invalid'}, safe=False)
else: else:
return JsonResponse({'error': 'No Token'}, safe=False) return JsonResponse({'error': 'Unauthorized'}, safe=False)

View File

@ -0,0 +1,37 @@
from sqlalchemy import create_engine
import ras.settings
import pandas as pd
from django.contrib.auth import get_user_model
import jwt
from django.http import JsonResponse
def isAuthorized(authtoken):
if(authtoken):
token = authtoken.split(' ')[1]
User = get_user_model()
payload = jwt.decode(token, 'secret', algorithms=['HS256'])
user = User.objects.get(id=payload['id'])
if(user):
return True;
else:
return False;
else:
return JsonResponse({'error': 'Unauthorized'}, safe=False)
def getBooksData():
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'])
df = pd.read_sql('SELECT * FROM api_books ORDER BY readed', engine, parse_dates={'readed': {'format': '%m-%Y'}})
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')
# Filter data on year
if datayear and datayear is not None:
df = df.where(df['readed'].str.contains(datayear))
return df

View File

@ -1,191 +1,182 @@
from rest_framework.decorators import api_view from rest_framework.decorators import api_view
from sqlalchemy import create_engine
import ras.settings
import pandas as pd import pandas as pd
import math
from rest_framework.response import Response from rest_framework.response import Response
from django.http import JsonResponse
from .functions import isAuthorized, getBooksData, filterData
def getBooksData(): # ----------------------
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']) # Get all reading years
df = pd.read_sql('SELECT * FROM api_books ORDER BY readed', engine, parse_dates={'readed': {'format': '%m-%Y'}}) # ----------------------
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')
# Filter data on year
if datayear and datayear is not None:
df = df.where(df['readed'].str.contains(datayear))
return df
@api_view(['GET']) @api_view(['GET'])
def getYears(request): def getYears(request):
df = filterData(getBooksData()) if(request.headers.get('Authorization')):
isLoggedIn = isAuthorized(request.headers.get('Authorization'));
df['readed'] = pd.to_datetime(df['readed'], errors='coerce') if(isLoggedIn):
df['year']= df['readed'].dt.year df = filterData(getBooksData())
df['readed'] = pd.to_datetime(df['readed'], errors='coerce')
df['year']= df['readed'].dt.year
years = df.groupby('year')['year'].count().reset_index(name="count")
years = df.groupby('year')['year'].count().reset_index(name="count") return Response(years['year'])
else:
return JsonResponse({'error': 'Unauthorized'}, safe=False)
else:
return JsonResponse({'error': 'No authorization token'}, safe=False)
return Response(years['year']) # ------------------------------------------------------------------
# Get books of selected year and filter it per month and per genre
@api_view(['GET']) # ------------------------------------------------------------------
def getBooksByYear(request):
if request.META.get('HTTP_YEAR'):
data = []
df = getBooksData()
df['readed'] = pd.to_datetime(df['readed'], format='%Y-%m-%d')
df['readed'] = df['readed'].dt.strftime('%Y-%m-%d')
df = df.where(df['readed'].str.contains(request.META.get('HTTP_YEAR')))
df = df.fillna('')
for index, row in df.iterrows():
if row['id'] and row['id'] != '':
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']) @api_view(['GET'])
def books_per_genre_per_month(request): def books_per_genre_per_month(request):
if request.META.get('HTTP_YEAR'): if(request.headers.get('Authorization')):
isLoggedIn = isAuthorized(request.headers.get('Authorization'));
data = [] if(isLoggedIn):
df = filterData(getBooksData(), request.META.get('HTTP_YEAR')) if request.META.get('HTTP_YEAR'):
data = []
df = filterData(getBooksData(), request.META.get('HTTP_YEAR'))
booksPerMonth = df.groupby(['genre','readed'])['genre'].count().reset_index(name="count")
booksPerMonth = booksPerMonth.sort_values(by=['genre', 'readed', 'count'], ascending=False)
# Filter array on genre and date for index, row in booksPerMonth.iterrows():
booksPerMonth = df.groupby(['genre','readed'])['genre'].count().reset_index(name="count") data.append({
booksPerMonth = booksPerMonth.sort_values(by=['genre', 'readed', 'count'], ascending=False) "genre": row['genre'],
"readed": row['readed'],
for index, row in booksPerMonth.iterrows(): "count": row['count']
data.append({ })
"genre": row['genre'],
"readed": row['readed'], return Response(data)
"count": row['count'] else:
}) return JsonResponse({'error': 'No year in header'}, safe=False)
else:
return Response(data) return JsonResponse({'error': 'Unauthorized'}, safe=False)
else: else:
return Response("No year header included") return JsonResponse({'error': 'No authorization token'}, safe=False)
# ---------------------------------------------
# Get genres of selected year with percentages
# ---------------------------------------------
@api_view(['GET']) @api_view(['GET'])
def countGenres(request): def countGenres(request):
if request.META.get('HTTP_YEAR'): if(request.headers.get('Authorization')):
isLoggedIn = isAuthorized(request.headers.get('Authorization'));
data = []
df = filterData(getBooksData(), request.META.get('HTTP_YEAR'))
genres = df.groupby('genre')['genre'].count().reset_index(name="count")
genres = genres.sort_values(by='count', ascending=False)
for index, row in genres.iterrows(): if(isLoggedIn):
if request.META.get('HTTP_YEAR'):
data = []
df = filterData(getBooksData(), request.META.get('HTTP_YEAR'))
genres = df.groupby('genre')['genre'].count().reset_index(name="count")
genres = genres.sort_values(by='count', ascending=False)
data.append({ for index, row in genres.iterrows():
"genre": row['genre'], data.append({
"count": int(row['count']) "genre": row['genre'],
}) "count": int(row['count'])
})
return Response(data) return Response(data)
else:
return JsonResponse({'error': 'No year in header'}, safe=False)
else:
return JsonResponse({'error': 'Unauthorized'}, safe=False)
else: else:
return Response("No year header included") return JsonResponse({'error': 'No authorization token'}, safe=False)
# ----------------
# Get year stats
# ----------------
@api_view(['GET']) @api_view(['GET'])
def getStats(request): def getStats(request):
if request.META.get('HTTP_YEAR'): if(request.headers.get('Authorization')):
data = [] isLoggedIn = isAuthorized(request.headers.get('Authorization'));
df = filterData(getBooksData(), request.META.get('HTTP_YEAR'))
df = df.dropna()
statsTotalBooks = df['name'].count() if(isLoggedIn):
statsTotalGenres = df['genre'].nunique() if request.META.get('HTTP_YEAR'):
data = []
df = filterData(getBooksData(), request.META.get('HTTP_YEAR'))
df = df.dropna()
statsTotalBooks = df['name'].count()
statsTotalGenres = df['genre'].nunique()
avgratingsperyear = round((df['rating'].sum() / df['rating'].count()), 0)
data.append({
'totalbooks': statsTotalBooks,
'totalgenres': statsTotalGenres,
'avgyearrating': avgratingsperyear
})
data.append({ return Response(data[0])
'totalbooks': statsTotalBooks, else:
'totalgenres': statsTotalGenres return JsonResponse({'error': 'No year in header'}, safe=False)
}) else:
return JsonResponse({'error': 'Unauthorized'}, safe=False)
return Response(data[0])
else: else:
return Response("No year header included") return JsonResponse({'error': 'No authorization token'}, safe=False)
@api_view(['GET']) # ------------------------------------------------
def books_per_country(request): # Get books of selected year and group by ratings
if request.META.get('HTTP_YEAR'): # ------------------------------------------------
data = []
df = filterData(getBooksData(), request.META.get('HTTP_YEAR'))
countries = df.groupby(['country_code', 'country'])['country'].count().reset_index(name="count")
countries = countries.sort_values(by='count', ascending=False)
for index, row in countries.iterrows():
data.append({
"code": row['country_code'],
"country": row['country'],
"count": int(row['count'])
})
return Response(data)
else:
return Response("No year header included")
@api_view(['GET']) @api_view(['GET'])
def avg_ratings_per_month(request): def avg_ratings_per_month(request):
datayear = request.META.get('HTTP_YEAR') if(request.headers.get('Authorization')):
isLoggedIn = isAuthorized(request.headers.get('Authorization'));
if datayear: if(isLoggedIn):
data = [] if request.META.get('HTTP_YEAR'):
data = []
df = filterData(getBooksData(), request.META.get('HTTP_YEAR'))
avgratingspermonth = df.groupby('readed')['rating'].mean().reset_index(name="rating")
# Get CSV file with book data for index, row in avgratingspermonth.iterrows():
df = filterData(getBooksData(), request.META.get('HTTP_YEAR')) data.append({
"date": row['readed'],
"rating": int(row['rating'])
})
avgratingspermonth = df.groupby('readed')['rating'].mean().reset_index(name="rating") return Response(data)
else:
for index, row in avgratingspermonth.iterrows(): return JsonResponse({'error': 'No year in header'}, safe=False)
else:
data.append({ return JsonResponse({'error': 'Unauthorized'}, safe=False)
"date": row['readed'],
"rating": int(row['rating'])
})
return Response(data)
else: else:
return Response("No year header included") return JsonResponse({'error': 'No authorization token'}, safe=False)
# -----------------------------
# Get ratings of selected year
# -----------------------------
@api_view(['GET']) @api_view(['GET'])
def countRatings(request): def countRatings(request):
datayear = request.META.get('HTTP_YEAR') if(request.headers.get('Authorization')):
isLoggedIn = isAuthorized(request.headers.get('Authorization'));
if datayear: if(isLoggedIn):
data = [] if request.META.get('HTTP_YEAR'):
data = []
df = filterData(getBooksData(), request.META.get('HTTP_YEAR'))
countratings = df.groupby('rating')['rating'].count().reset_index(name="count")
countratings = countratings.sort_values(by='rating', ascending=False)
# Get CSV file with book data for index, row in countratings.iterrows():
df = filterData(getBooksData(), request.META.get('HTTP_YEAR')) data.append({
"rating": int(row['rating']),
"count": int(row['count'])
})
countratings = df.groupby('rating')['rating'].count().reset_index(name="count") return Response(data)
countratings = countratings.sort_values(by='rating', ascending=False) else:
return JsonResponse({'error': 'No year in header'}, safe=False)
for index, row in countratings.iterrows(): else:
return JsonResponse({'error': 'Unauthorized'}, safe=False)
data.append({
"rating": int(row['rating']),
"count": int(row['count'])
})
return Response(data)
else: else:
return Response("No year header included") return JsonResponse({'error': 'No authorization token'}, safe=False)

View File

@ -3,11 +3,12 @@ from django.views.decorators.csrf import csrf_exempt
from .views import * from .views import *
from .modules.auth import * from .modules.auth import *
from .modules.crud import * from .modules.crud import *
from .modules.challenges import *
from .modules.pandas import * from .modules.pandas import *
urlpatterns = [ urlpatterns = [
path('books/all', getAllBooks), path('books/all', getAllBooks),
path('books', getBooksByYear), # path('books', getBooksByYear),
path('books/years', getYears), path('books/years', getYears),
path('books/stats', getStats), path('books/stats', getStats),
path('books/insert', addBook), path('books/insert', addBook),
@ -17,6 +18,11 @@ urlpatterns = [
path('books/genres/count', countGenres), path('books/genres/count', countGenres),
path('books/ratings', avg_ratings_per_month), path('books/ratings', avg_ratings_per_month),
path('books/ratings/count', countRatings), path('books/ratings/count', countRatings),
path('books/countries', books_per_country), # path('books/countries', books_per_country),
path('auth/login', csrf_exempt(login)), path('auth/login', csrf_exempt(login)),
path('books/challenge', getChallengeOfYear),
path('books/challenges', getAllChallenges),
path('books/challenges/insert', addChallenge),
path('books/challenges/<int:id>/delete', deleteChallenge),
] ]

View File

@ -2,6 +2,8 @@ import React, { Component, lazy, Suspense } from "react";
import { Route, Routes, BrowserRouter as Router } from 'react-router-dom'; import { Route, Routes, BrowserRouter as Router } from 'react-router-dom';
import { ColorRing } from 'react-loader-spinner' import { ColorRing } from 'react-loader-spinner'
import Login from "./views/login"; import Login from "./views/login";
import Books from "./views/bookslist";
import BooksList from "./views/bookslist";
const Dashboard = lazy(() => import("./views/dashboard")); const Dashboard = lazy(() => import("./views/dashboard"));
@ -25,6 +27,7 @@ function App() {
</div>}> </div>}>
<Routes> <Routes>
<Route exact path="/" element={localStorage.getItem('token') ? <Dashboard /> : <Login />} /> <Route exact path="/" element={localStorage.getItem('token') ? <Dashboard /> : <Login />} />
<Route exact path="/books" element={<BooksList />} />
{/* <Route exact path="/login" element={<Login />} /> */} {/* <Route exact path="/login" element={<Login />} /> */}
</Routes> </Routes>
</Suspense> </Suspense>

View File

@ -1,59 +1,35 @@
import React, { Component } from 'react'; import React, { useEffect, useState } from 'react';
import { getChallenge, getStats } from "./Data.js";
export default class Challenge extends Component { const Challenge = (props) => {
constructor(props) { const [challenge, setChallenge] = useState(0);
super(props); const [challengePercentage, setChallengePercentage] = useState(0)
this.state = {
readingYears: [], const getData = async () => {
challenge: 0 const data = await import("./Data.js");
} const stats = await data.getStats(props.year);
const yearchallenge = await data.getChallenge(props.year);
setChallenge(yearchallenge ? yearchallenge[0].nrofbooks : 0);
setChallengePercentage(Math.round((stats.totalbooks / yearchallenge[0].nrofbooks) * 100, 0))
} }
getComponentData() { useEffect(() => {
var $this = this; getData();
}, [props.year])
getStats(this.props.year).then(data => { return (
$this.setState({ <React.Fragment>
totalbooks: data.totalbooks {challenge && challenge !== 0 ?
}) <div className="stat-block challenge" style={{ marginBottom: '20px' }}>
}); <span className="block_name">Book Challenge ({challenge} boeken)</span>
<div className="progress">
getChallenge(this.props.year).then(data => { <div className="progress-bar" role="progressbar" style={{ width: challengePercentage + '%' }} aria-valuenow={challengePercentage} aria-valuemin="0" aria-valuemax="100">
this.setState({ <div className="progress-bar-number">{challengePercentage}%</div>
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 = Math.round((this.state.totalbooks / this.state.challenge) * 100, 0)
return (
<React.Fragment>
{this.state.challenge && this.state.challenge !== 0 ?
<div className="stat-block" style={{ marginBottom: '20px' }}>
<span className="block_name">Book Challenge</span>
<div className="progress">
<div className="progress-bar" role="progressbar" style={{ width: challengePercentage + '%' }} aria-valuenow={challengePercentage} aria-valuemin="0" aria-valuemax="100">
<div className="progress-bar-number">{challengePercentage}%</div>
</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>
</React.Fragment> : ''}
) </React.Fragment>
} )
} }
export default Challenge;

View File

@ -87,8 +87,6 @@ export const initChart = (data, ratings, year) => {
order: 1 order: 1
}) })
console.log(dataSet);
/* /*
---------------------------------- ----------------------------------
Stacked bar chart Stacked bar chart
@ -181,7 +179,6 @@ export const initChart = (data, ratings, year) => {
export const initDoughnut = (data) => { export const initDoughnut = (data) => {
console.log(data);
var labels = []; var labels = [];
var counts = []; var counts = [];

View File

@ -3,19 +3,9 @@ import { readCookie } from "../Functions";
export const getAllBooks = () => { export const getAllBooks = () => {
return fetch('/api/books/all', { return fetch('/api/books/all', {
"method": "GET", "method": "GET",
})
.then(response => response.json())
.then(data => {
return data;
})
}
export const getBooksByYear = (year) => {
return fetch('/api/books', {
"method": "GET",
"headers": { "headers": {
"year": year "Authorization": "Bearer " + localStorage.getItem("token")
} },
}) })
.then(response => response.json()) .then(response => response.json())
.then(data => { .then(data => {
@ -23,15 +13,28 @@ export const getBooksByYear = (year) => {
}) })
} }
export const getAllChallenges = () => { // export const getBooksByYear = (year) => {
return fetch('/api/books/challenges', { // return fetch('/api/books', {
"method": "GET", // "method": "GET",
}) // "headers": {
.then(response => response.json()) // "year": year
.then(data => { // }
return data; // })
}) // .then(response => response.json())
} // .then(data => {
// return data;
// })
// }
// export const getAllChallenges = () => {
// return fetch('/api/books/challenges', {
// "method": "GET",
// })
// .then(response => response.json())
// .then(data => {
// return data;
// })
// }
export const insertChallenge = (data) => { export const insertChallenge = (data) => {
return fetch('/api/books/challenges/insert', { return fetch('/api/books/challenges/insert', {
@ -69,7 +72,8 @@ export const getStats = (year) => {
return fetch('/api/books/stats', { return fetch('/api/books/stats', {
"method": "GET", "method": "GET",
"headers": { "headers": {
"year": year "year": year,
"Authorization": "Bearer " + localStorage.getItem("token")
} }
}) })
.then(response => response.json()) .then(response => response.json())
@ -94,6 +98,9 @@ export const getChallenge = (year) => {
export const getReadingYears = () => { export const getReadingYears = () => {
return fetch('/api/books/years', { return fetch('/api/books/years', {
"method": "GET", "method": "GET",
"headers": {
"Authorization": "Bearer " + localStorage.getItem("token")
}
}) })
.then(response => response.json()) .then(response => response.json())
.then(data => { .then(data => {
@ -101,37 +108,39 @@ export const getReadingYears = () => {
}) })
} }
export const getCountries = (year) => { // export const getCountries = (year) => {
return fetch('/api/books/countries', { // return fetch('/api/books/countries', {
"method": "GET", // "method": "GET",
"headers": { // "headers": {
"year": year // "year": year
} // }
}) // })
.then(response => response.json()) // .then(response => response.json())
.then(data => { // .then(data => {
return data; // return data;
}) // })
} // }
export const getShortestLongestBook = (year) => { // export const getShortestLongestBook = (year) => {
return fetch('/api/books/pages/stats', { // return fetch('/api/books/pages/stats', {
"method": "GET", // "method": "GET",
"headers": { // "headers": {
"year": year // "year": year,
} // "Authorization": "Bearer " + localStorage.getItem("token")
}) // }
.then(response => response.json()) // })
.then(data => { // .then(response => response.json())
return data; // .then(data => {
}) // return data;
} // })
// }
export const getBooksPerYearPerGenres = (year) => { export const getBooksPerYearPerGenres = (year) => {
return fetch('/api/books/genres', { return fetch('/api/books/genres', {
"method": "GET", "method": "GET",
"headers": { "headers": {
"year": year "year": year,
"Authorization": "Bearer " + localStorage.getItem("token")
} }
}) })
.then(response => response.json()) .then(response => response.json())
@ -144,7 +153,8 @@ export const getGenresCount = (year) => {
return fetch('/api/books/genres/count', { return fetch('/api/books/genres/count', {
"method": "GET", "method": "GET",
"headers": { "headers": {
"year": year "year": year,
"Authorization": "Bearer " + localStorage.getItem("token")
} }
}) })
.then(response => response.json()) .then(response => response.json())
@ -157,7 +167,8 @@ export const getAvgRatings = (year) => {
return fetch('/api/books/ratings', { return fetch('/api/books/ratings', {
"method": "GET", "method": "GET",
"headers": { "headers": {
"year": year "year": year,
"Authorization": "Bearer " + localStorage.getItem("token")
} }
}) })
.then(response => response.json()) .then(response => response.json())
@ -170,7 +181,8 @@ export const getRatingsCount = (year) => {
return fetch('/api/books/ratings/count', { return fetch('/api/books/ratings/count', {
"method": "GET", "method": "GET",
"headers": { "headers": {
"year": year "year": year,
"Authorization": "Bearer " + localStorage.getItem("token")
} }
}) })
.then(response => response.json()) .then(response => response.json())

View File

@ -8,7 +8,7 @@
#DataTable td { #DataTable td {
color: #333; color: #333;
font-size: 13px; font-size: 12px;
height: 40px; height: 40px;
font-weight: 300; font-weight: 300;
vertical-align: middle; vertical-align: middle;
@ -22,7 +22,8 @@
#DataTable{ #DataTable{
margin-bottom:0 !important; margin-bottom:0 !important;
box-shadow: 0 2px 0 1px rgb(0 0 0/3%); -webkit-box-shadow: 0px 1px 1px 1px #eee;
box-shadow: 0px 1px 1px 1px #eee;
} }
#DataTable a { #DataTable a {
@ -30,17 +31,22 @@
text-decoration: none; text-decoration: none;
} }
.content-manage #DataTable .fa-star {
color: #ffbe0e;
}
.content-manage #DataTable i { .content-manage #DataTable i {
color: #ffffff; color: #ffffff;
} }
.content-manage #DataTable button{ .content-manage #DataTable button{
text-align: center; text-align: center;
border-radius: 100%;
} }
.content-manage #DataTable button i{ .content-manage #DataTable button i{
margin:auto; margin:auto;
font-size: 14px; font-size: 12px;
} }
#DataTable i { #DataTable i {
@ -49,10 +55,10 @@
} }
#DataTable thead th{ #DataTable thead th{
padding: 20px 20px; padding: 15px 20px;
border-bottom: 1px solid #efefef; border-bottom: 1px solid #efefef;
font-weight: 600; font-weight: 600;
font-size: 15px; font-size: 12px;
color:#000000; color:#000000;
} }
@ -68,11 +74,6 @@
padding:14px; padding:14px;
} }
#DataTable{
margin-bottom:0;
box-shadow: none !important;
}
#DataTable .openAlarm .pictogram { #DataTable .openAlarm .pictogram {
/* display: inline-block; /* display: inline-block;
padding: 0 0 0 0; padding: 0 0 0 0;
@ -97,7 +98,7 @@
height: 0px; height: 0px;
vertical-align: middle; vertical-align: middle;
border-top: none; border-top: none;
border-bottom: none; border-bottom: solid 1px #f8f8f8 !important;
} }
#DataTable .openAlarm td{ #DataTable .openAlarm td{

View File

@ -44,7 +44,7 @@ const Ratings = (props) => {
<React.Fragment> <React.Fragment>
<div className="ratings"> <div className="ratings">
<span className="block_name">Ratings</span> <span className="block_name">Ratings</span>
<table id="DataTable" className="table responsive nowrap" width="100%"> <table className="ratingstable responsive nowrap" width="100%">
<thead> <thead>
<tr> <tr>
<th>#</th> <th>#</th>
@ -53,12 +53,10 @@ const Ratings = (props) => {
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{ratings.map((rating) => { {ratings.map((rating, i) => {
var ratingstars = ''; var ratingstars = '';
var rating_percentage = rating[1] / totalRatings * 100; var rating_percentage = rating[1] / totalRatings * 100;
console.log(rating[1], totalRatings);
if (rating[0]) { if (rating[0]) {
for (var i = 0; i < rating[0]; i++) { for (var i = 0; i < rating[0]; i++) {
ratingstars += "<i class='fas fa-star'></i>"; ratingstars += "<i class='fas fa-star'></i>";
@ -66,7 +64,7 @@ const Ratings = (props) => {
} }
return( return(
<tr> <tr key={i}>
<td style={{width: '200px'}} className='book_rating' dangerouslySetInnerHTML={{__html: ratingstars}}></td> <td style={{width: '200px'}} className='book_rating' dangerouslySetInnerHTML={{__html: ratingstars}}></td>
<td style={{width: '257px'}}> <td style={{width: '257px'}}>
<div className="progress"> <div className="progress">

View File

@ -7,15 +7,21 @@ const Sidebar = () => {
<img className="logo_text" src="/static/images/logo_white.png" style={{ width: '100%', padding: '10px 25px' }}/> <img className="logo_text" src="/static/images/logo_white.png" style={{ width: '100%', padding: '10px 25px' }}/>
<ul> <ul>
<NavLink to={'/'} exact="true"> <NavLink to={'/'} exact="true">
<li><i class="fas fa-chart-line"></i> Dashboard</li> <li><i className="fas fa-chart-line"></i> Dashboard</li>
</NavLink> </NavLink>
<NavLink to={'/books'} exact="true"> <NavLink to={'/books'} exact="true">
<li><i class="fas fa-book"></i> Boeken</li> <li><i className="fas fa-book"></i> Boeken</li>
</NavLink> </NavLink>
<NavLink to={'/challenges'} exact="true">
<li><i className="fas fa-tasks"></i> Challenges</li>
</NavLink>
</ul>
<ul className="bottom-menu">
<NavLink to={'/settings'} exact="true"> <NavLink to={'/settings'} exact="true">
<li><i class="fas fa-cog"></i> Instellingen</li> <li><i className="fas fa-cog"></i> Instellingen</li>
</NavLink> </NavLink>
<li><i class="fas fa-power-off"></i> Uitloggen</li> <li><i className="fas fa-power-off"></i> Uitloggen</li>
</ul> </ul>
</div> </div>
) )

View File

@ -3,6 +3,7 @@ import React, { useEffect, useState } from 'react';
const BookStats = (props) =>{ const BookStats = (props) =>{
const [totalbooks, setTotalbooks] = useState(0); const [totalbooks, setTotalbooks] = useState(0);
const [totalgenres, setTotalgenres] = useState(0); const [totalgenres, setTotalgenres] = useState(0);
const [yearrating, setYearrating] = useState(0);
const getData = async () => { const getData = async () => {
const data = await import("./Data.js"); const data = await import("./Data.js");
@ -10,6 +11,7 @@ const BookStats = (props) =>{
setTotalbooks(stats.totalbooks); setTotalbooks(stats.totalbooks);
setTotalgenres(stats.totalgenres); setTotalgenres(stats.totalgenres);
setYearrating(stats.avgyearrating);
} }
useEffect(() => { useEffect(() => {
@ -21,7 +23,7 @@ const BookStats = (props) =>{
<div className='row'> <div className='row'>
<div className="col-md-4"> <div className="col-md-4">
<div className="stat-block"> <div className="stat-block">
<i class="fas fa-book-open"></i> <i className="fas fa-book-open"></i>
<span className="stats-label">Gelezen boeken:</span> <span className="stats-label">Gelezen boeken:</span>
<span className="stats-number">{totalbooks}</span> <span className="stats-number">{totalbooks}</span>
@ -30,7 +32,7 @@ const BookStats = (props) =>{
<div className="col-md-4"> <div className="col-md-4">
<div className="stat-block"> <div className="stat-block">
<i class="fas fa-book-open"></i> <i className="fas fa-book-open"></i>
<span className="stats-label">Genres:</span> <span className="stats-label">Genres:</span>
<span className="stats-number">{totalgenres}</span> <span className="stats-number">{totalgenres}</span>
@ -39,9 +41,9 @@ const BookStats = (props) =>{
<div className="col-md-4"> <div className="col-md-4">
<div className="stat-block"> <div className="stat-block">
<i class="fas fa-star"></i> <i className="fas fa-star"></i>
<span className="stats-label">Jaarbeoordeling:</span> <span className="stats-label">Jaarbeoordeling:</span>
<span className="stats-number">7</span> <span className="stats-number">{yearrating}</span>
</div> </div>
</div> </div>
</div> </div>

View File

@ -0,0 +1,74 @@
import React, { useEffect, useState } from "react";
import Sidebar from "../components/Sidebar.js";
import * as moment from 'moment';
import "../components/DataTables.css";
moment.locale('nl');
const BooksList = (props) => {
var [books, setBooks] = useState([]);
const getData = async() => {
const data = await import("../components/Data.js");
const getbooks = await data.getAllBooks();
console.log(getbooks)
setBooks(getbooks);
setTimeout(() => {
$('#DataTable').DataTable({
language: {
url: 'https://cdn.datatables.net/plug-ins/9dcbecd42ad/i18n/Dutch.json',
search: "",
searchPlaceholder: "Zoeken"
},
dom: 'frt<"bottom"pl><"clear">',
order: []
});
}, 300)
}
useEffect(() => {
getData();
}, [])
return (
<React.Fragment>
<Sidebar />
<div className="content-manage">
{/* <button type="button" class="btn btn-success">Toevoegen</button> */}
<h1>Boeken beheren</h1>
<div className="DataTable_Container">
<table id="DataTable" className="showHead table responsive nowrap" width="100%">
<thead>
<tr>
<th>Boek</th>
<th>Schrijver</th>
<th>Gelezen op</th>
<th>Rating</th>
<th>Acties</th>
</tr>
</thead>
<tbody>
{books.map((book, i) => {
return (
<tr key={book.id}>
<td>{book.name}</td>
<td>{book.author}</td>
<td>{moment(book.readed).format('MMMM YYYY')}</td>
<td><i class='fas fa-star'></i> {book.rating}</td>
<td>
<button type="button" class="btn btn-danger"><i className="fa fa-trash"></i></button>
</td>
</tr>
)
})}
</tbody>
</table>
</div>
</div>
</React.Fragment>
)
}
export default BooksList;

View File

@ -3,6 +3,7 @@ import Genres from "../components/Genres";
import Books from "../components/Books"; import Books from "../components/Books";
import Ratings from "../components/Ratings"; import Ratings from "../components/Ratings";
import Stats from "../components/Stats"; import Stats from "../components/Stats";
import Challenge from "../components/Challenge";
import Sidebar from "../components/Sidebar"; import Sidebar from "../components/Sidebar";
const Dashboard = (props) => { const Dashboard = (props) => {
@ -37,9 +38,15 @@ const Dashboard = (props) => {
<Sidebar /> <Sidebar />
<div className="content"> <div className="content">
<div className="container-fluid"> <div className="container-fluid">
{/* <div className="row">
<div className="col-md-12">
</div>
</div> */}
<div className="row"> <div className="row">
<div className="col-md-8"> <div className="col-md-8">
<Stats year={year} /> <Stats year={year} />
<Challenge year={year} />
<Books year={year} /> <Books year={year} />
</div> </div>

View File

@ -8,20 +8,14 @@ html, body{
font-family: 'Poppins', sans-serif; font-family: 'Poppins', sans-serif;
letter-spacing: .3px; letter-spacing: .3px;
font-weight: 400; font-weight: 400;
font-size: 12px;
color:#333; color:#333;
} }
h1{ h1{
/* font-size: 30px;
font-weight: 500;
padding: 0 10px 0px 10px;
margin-bottom: 5px; */
border-bottom: 1px solid #ddd;
display: inline-block; display: inline-block;
font-size: 25px; font-size: 18px;
font-weight: 500; font-weight: 500;
margin-bottom: 20px;
padding: 0 0 10px;
width: 100%; width: 100%;
} }
@ -228,6 +222,13 @@ html, body{
color: #48a5a9; color: #48a5a9;
} }
.sidebar .bottom-menu{
position: absolute;
bottom: 0;
width:100%;
border-top: solid 1px rgba(255,255,255,0.2);
}
.books-stats{ .books-stats{
margin:20px 0; margin:20px 0;
} }
@ -357,7 +358,7 @@ html, body{
.table td{ .table td{
color: #101010; color: #101010;
border-bottom:none !important; border-bottom:none !important;
padding: 10px 10px !important; padding: 10px 20px !important;
font-size: 13px; font-size: 13px;
font-weight: 300; font-weight: 300;
} }
@ -375,7 +376,13 @@ html, body{
display:none; display:none;
} }
.challenge .stats-number, .challenge .stats-label{
display: inline-block;
}
.challenge .stats-number{
font-size:14px;
}
span.block_name{ span.block_name{
color: #333; color: #333;
@ -592,4 +599,13 @@ html, body{
.stats{ .stats{
margin-bottom:20px; margin-bottom:20px;
}
.ratingstable thead{
display:none;
}
.ratingstable td{
height: 35px;
padding: 0 10px 0 0px;
} }

File diff suppressed because one or more lines are too long

View File

@ -10,10 +10,26 @@
!*** ./src/views/login.js ***! !*** ./src/views/login.js ***!
\****************************/ \****************************/
/*!********************************!*\
!*** ./src/views/bookslist.js ***!
\********************************/
/*!***********************************!*\
!*** ./src/components/Sidebar.js ***!
\***********************************/
/*!*************************************!*\ /*!*************************************!*\
!*** ./node_modules/react/index.js ***! !*** ./node_modules/react/index.js ***!
\*************************************/ \*************************************/
/*!***************************************!*\
!*** ./node_modules/moment/moment.js ***!
\***************************************/
/*!***************************************!*\
!*** ./src/components/DataTables.css ***!
\***************************************/
/*!****************************************!*\ /*!****************************************!*\
!*** ./node_modules/react-is/index.js ***! !*** ./node_modules/react-is/index.js ***!
\****************************************/ \****************************************/
@ -26,18 +42,562 @@
!*** ./node_modules/scheduler/index.js ***! !*** ./node_modules/scheduler/index.js ***!
\*****************************************/ \*****************************************/
/*!******************************************!*\
!*** ./node_modules/moment/locale/af.js ***!
\******************************************/
/*!******************************************!*\
!*** ./node_modules/moment/locale/ar.js ***!
\******************************************/
/*!******************************************!*\
!*** ./node_modules/moment/locale/az.js ***!
\******************************************/
/*!******************************************!*\
!*** ./node_modules/moment/locale/be.js ***!
\******************************************/
/*!******************************************!*\
!*** ./node_modules/moment/locale/bg.js ***!
\******************************************/
/*!******************************************!*\
!*** ./node_modules/moment/locale/bm.js ***!
\******************************************/
/*!******************************************!*\
!*** ./node_modules/moment/locale/bn.js ***!
\******************************************/
/*!******************************************!*\
!*** ./node_modules/moment/locale/bo.js ***!
\******************************************/
/*!******************************************!*\
!*** ./node_modules/moment/locale/br.js ***!
\******************************************/
/*!******************************************!*\
!*** ./node_modules/moment/locale/bs.js ***!
\******************************************/
/*!******************************************!*\
!*** ./node_modules/moment/locale/ca.js ***!
\******************************************/
/*!******************************************!*\
!*** ./node_modules/moment/locale/cs.js ***!
\******************************************/
/*!******************************************!*\
!*** ./node_modules/moment/locale/cv.js ***!
\******************************************/
/*!******************************************!*\
!*** ./node_modules/moment/locale/cy.js ***!
\******************************************/
/*!******************************************!*\
!*** ./node_modules/moment/locale/da.js ***!
\******************************************/
/*!******************************************!*\
!*** ./node_modules/moment/locale/de.js ***!
\******************************************/
/*!******************************************!*\
!*** ./node_modules/moment/locale/dv.js ***!
\******************************************/
/*!******************************************!*\
!*** ./node_modules/moment/locale/el.js ***!
\******************************************/
/*!******************************************!*\
!*** ./node_modules/moment/locale/eo.js ***!
\******************************************/
/*!******************************************!*\
!*** ./node_modules/moment/locale/es.js ***!
\******************************************/
/*!******************************************!*\
!*** ./node_modules/moment/locale/et.js ***!
\******************************************/
/*!******************************************!*\
!*** ./node_modules/moment/locale/eu.js ***!
\******************************************/
/*!******************************************!*\
!*** ./node_modules/moment/locale/fa.js ***!
\******************************************/
/*!******************************************!*\
!*** ./node_modules/moment/locale/fi.js ***!
\******************************************/
/*!******************************************!*\
!*** ./node_modules/moment/locale/fo.js ***!
\******************************************/
/*!******************************************!*\
!*** ./node_modules/moment/locale/fr.js ***!
\******************************************/
/*!******************************************!*\
!*** ./node_modules/moment/locale/fy.js ***!
\******************************************/
/*!******************************************!*\
!*** ./node_modules/moment/locale/ga.js ***!
\******************************************/
/*!******************************************!*\
!*** ./node_modules/moment/locale/gd.js ***!
\******************************************/
/*!******************************************!*\
!*** ./node_modules/moment/locale/gl.js ***!
\******************************************/
/*!******************************************!*\
!*** ./node_modules/moment/locale/gu.js ***!
\******************************************/
/*!******************************************!*\
!*** ./node_modules/moment/locale/he.js ***!
\******************************************/
/*!******************************************!*\
!*** ./node_modules/moment/locale/hi.js ***!
\******************************************/
/*!******************************************!*\
!*** ./node_modules/moment/locale/hr.js ***!
\******************************************/
/*!******************************************!*\
!*** ./node_modules/moment/locale/hu.js ***!
\******************************************/
/*!******************************************!*\
!*** ./node_modules/moment/locale/id.js ***!
\******************************************/
/*!******************************************!*\
!*** ./node_modules/moment/locale/is.js ***!
\******************************************/
/*!******************************************!*\
!*** ./node_modules/moment/locale/it.js ***!
\******************************************/
/*!******************************************!*\
!*** ./node_modules/moment/locale/ja.js ***!
\******************************************/
/*!******************************************!*\
!*** ./node_modules/moment/locale/jv.js ***!
\******************************************/
/*!******************************************!*\
!*** ./node_modules/moment/locale/ka.js ***!
\******************************************/
/*!******************************************!*\
!*** ./node_modules/moment/locale/kk.js ***!
\******************************************/
/*!******************************************!*\
!*** ./node_modules/moment/locale/km.js ***!
\******************************************/
/*!******************************************!*\
!*** ./node_modules/moment/locale/kn.js ***!
\******************************************/
/*!******************************************!*\
!*** ./node_modules/moment/locale/ko.js ***!
\******************************************/
/*!******************************************!*\
!*** ./node_modules/moment/locale/ku.js ***!
\******************************************/
/*!******************************************!*\
!*** ./node_modules/moment/locale/ky.js ***!
\******************************************/
/*!******************************************!*\
!*** ./node_modules/moment/locale/lb.js ***!
\******************************************/
/*!******************************************!*\
!*** ./node_modules/moment/locale/lo.js ***!
\******************************************/
/*!******************************************!*\
!*** ./node_modules/moment/locale/lt.js ***!
\******************************************/
/*!******************************************!*\
!*** ./node_modules/moment/locale/lv.js ***!
\******************************************/
/*!******************************************!*\
!*** ./node_modules/moment/locale/me.js ***!
\******************************************/
/*!******************************************!*\
!*** ./node_modules/moment/locale/mi.js ***!
\******************************************/
/*!******************************************!*\
!*** ./node_modules/moment/locale/mk.js ***!
\******************************************/
/*!******************************************!*\
!*** ./node_modules/moment/locale/ml.js ***!
\******************************************/
/*!******************************************!*\
!*** ./node_modules/moment/locale/mn.js ***!
\******************************************/
/*!******************************************!*\
!*** ./node_modules/moment/locale/mr.js ***!
\******************************************/
/*!******************************************!*\
!*** ./node_modules/moment/locale/ms.js ***!
\******************************************/
/*!******************************************!*\
!*** ./node_modules/moment/locale/mt.js ***!
\******************************************/
/*!******************************************!*\
!*** ./node_modules/moment/locale/my.js ***!
\******************************************/
/*!******************************************!*\
!*** ./node_modules/moment/locale/nb.js ***!
\******************************************/
/*!******************************************!*\
!*** ./node_modules/moment/locale/ne.js ***!
\******************************************/
/*!******************************************!*\
!*** ./node_modules/moment/locale/nl.js ***!
\******************************************/
/*!******************************************!*\
!*** ./node_modules/moment/locale/nn.js ***!
\******************************************/
/*!******************************************!*\
!*** ./node_modules/moment/locale/pl.js ***!
\******************************************/
/*!******************************************!*\
!*** ./node_modules/moment/locale/pt.js ***!
\******************************************/
/*!******************************************!*\
!*** ./node_modules/moment/locale/ro.js ***!
\******************************************/
/*!******************************************!*\
!*** ./node_modules/moment/locale/ru.js ***!
\******************************************/
/*!******************************************!*\
!*** ./node_modules/moment/locale/sd.js ***!
\******************************************/
/*!******************************************!*\
!*** ./node_modules/moment/locale/se.js ***!
\******************************************/
/*!******************************************!*\
!*** ./node_modules/moment/locale/si.js ***!
\******************************************/
/*!******************************************!*\
!*** ./node_modules/moment/locale/sk.js ***!
\******************************************/
/*!******************************************!*\
!*** ./node_modules/moment/locale/sl.js ***!
\******************************************/
/*!******************************************!*\
!*** ./node_modules/moment/locale/sq.js ***!
\******************************************/
/*!******************************************!*\
!*** ./node_modules/moment/locale/sr.js ***!
\******************************************/
/*!******************************************!*\
!*** ./node_modules/moment/locale/ss.js ***!
\******************************************/
/*!******************************************!*\
!*** ./node_modules/moment/locale/sv.js ***!
\******************************************/
/*!******************************************!*\
!*** ./node_modules/moment/locale/sw.js ***!
\******************************************/
/*!******************************************!*\
!*** ./node_modules/moment/locale/ta.js ***!
\******************************************/
/*!******************************************!*\
!*** ./node_modules/moment/locale/te.js ***!
\******************************************/
/*!******************************************!*\
!*** ./node_modules/moment/locale/tg.js ***!
\******************************************/
/*!******************************************!*\
!*** ./node_modules/moment/locale/th.js ***!
\******************************************/
/*!******************************************!*\
!*** ./node_modules/moment/locale/tk.js ***!
\******************************************/
/*!******************************************!*\
!*** ./node_modules/moment/locale/tr.js ***!
\******************************************/
/*!******************************************!*\
!*** ./node_modules/moment/locale/uk.js ***!
\******************************************/
/*!******************************************!*\
!*** ./node_modules/moment/locale/ur.js ***!
\******************************************/
/*!******************************************!*\
!*** ./node_modules/moment/locale/uz.js ***!
\******************************************/
/*!******************************************!*\
!*** ./node_modules/moment/locale/vi.js ***!
\******************************************/
/*!******************************************!*\
!*** ./node_modules/moment/locale/yo.js ***!
\******************************************/
/*!******************************************!*\ /*!******************************************!*\
!*** ./node_modules/react-dom/client.js ***! !*** ./node_modules/react-dom/client.js ***!
\******************************************/ \******************************************/
/*!*******************************************!*\
!*** ./node_modules/moment/locale/fil.js ***!
\*******************************************/
/*!*******************************************!*\
!*** ./node_modules/moment/locale/tet.js ***!
\*******************************************/
/*!*******************************************!*\
!*** ./node_modules/moment/locale/tlh.js ***!
\*******************************************/
/*!*******************************************!*\
!*** ./node_modules/moment/locale/tzl.js ***!
\*******************************************/
/*!*******************************************!*\
!*** ./node_modules/moment/locale/tzm.js ***!
\*******************************************/
/*!********************************************!*\ /*!********************************************!*\
!*** ./node_modules/shallowequal/index.js ***! !*** ./node_modules/shallowequal/index.js ***!
\********************************************/ \********************************************/
/*!*********************************************!*\
!*** ./node_modules/moment/locale/ar-dz.js ***!
\*********************************************/
/*!*********************************************!*\
!*** ./node_modules/moment/locale/ar-kw.js ***!
\*********************************************/
/*!*********************************************!*\
!*** ./node_modules/moment/locale/ar-ly.js ***!
\*********************************************/
/*!*********************************************!*\
!*** ./node_modules/moment/locale/ar-ma.js ***!
\*********************************************/
/*!*********************************************!*\
!*** ./node_modules/moment/locale/ar-sa.js ***!
\*********************************************/
/*!*********************************************!*\
!*** ./node_modules/moment/locale/ar-tn.js ***!
\*********************************************/
/*!*********************************************!*\
!*** ./node_modules/moment/locale/bn-bd.js ***!
\*********************************************/
/*!*********************************************!*\
!*** ./node_modules/moment/locale/de-at.js ***!
\*********************************************/
/*!*********************************************!*\
!*** ./node_modules/moment/locale/de-ch.js ***!
\*********************************************/
/*!*********************************************!*\
!*** ./node_modules/moment/locale/en-au.js ***!
\*********************************************/
/*!*********************************************!*\
!*** ./node_modules/moment/locale/en-ca.js ***!
\*********************************************/
/*!*********************************************!*\
!*** ./node_modules/moment/locale/en-gb.js ***!
\*********************************************/
/*!*********************************************!*\
!*** ./node_modules/moment/locale/en-ie.js ***!
\*********************************************/
/*!*********************************************!*\
!*** ./node_modules/moment/locale/en-il.js ***!
\*********************************************/
/*!*********************************************!*\
!*** ./node_modules/moment/locale/en-in.js ***!
\*********************************************/
/*!*********************************************!*\
!*** ./node_modules/moment/locale/en-nz.js ***!
\*********************************************/
/*!*********************************************!*\
!*** ./node_modules/moment/locale/en-sg.js ***!
\*********************************************/
/*!*********************************************!*\
!*** ./node_modules/moment/locale/es-do.js ***!
\*********************************************/
/*!*********************************************!*\
!*** ./node_modules/moment/locale/es-mx.js ***!
\*********************************************/
/*!*********************************************!*\
!*** ./node_modules/moment/locale/es-us.js ***!
\*********************************************/
/*!*********************************************!*\
!*** ./node_modules/moment/locale/fr-ca.js ***!
\*********************************************/
/*!*********************************************!*\
!*** ./node_modules/moment/locale/fr-ch.js ***!
\*********************************************/
/*!*********************************************!*\
!*** ./node_modules/moment/locale/hy-am.js ***!
\*********************************************/
/*!*********************************************!*\
!*** ./node_modules/moment/locale/it-ch.js ***!
\*********************************************/
/*!*********************************************!*\
!*** ./node_modules/moment/locale/ms-my.js ***!
\*********************************************/
/*!*********************************************!*\
!*** ./node_modules/moment/locale/nl-be.js ***!
\*********************************************/
/*!*********************************************!*\
!*** ./node_modules/moment/locale/pa-in.js ***!
\*********************************************/
/*!*********************************************!*\
!*** ./node_modules/moment/locale/pt-br.js ***!
\*********************************************/
/*!*********************************************!*\
!*** ./node_modules/moment/locale/tl-ph.js ***!
\*********************************************/
/*!*********************************************!*\
!*** ./node_modules/moment/locale/ug-cn.js ***!
\*********************************************/
/*!*********************************************!*\
!*** ./node_modules/moment/locale/zh-cn.js ***!
\*********************************************/
/*!*********************************************!*\
!*** ./node_modules/moment/locale/zh-hk.js ***!
\*********************************************/
/*!*********************************************!*\
!*** ./node_modules/moment/locale/zh-mo.js ***!
\*********************************************/
/*!*********************************************!*\
!*** ./node_modules/moment/locale/zh-tw.js ***!
\*********************************************/
/*!**********************************************!*\
!*** ./node_modules/moment/locale/oc-lnc.js ***!
\**********************************************/
/*!***********************************************!*\
!*** ./node_modules/moment/locale/sr-cyrl.js ***!
\***********************************************/
/*!***********************************************!*\
!*** ./node_modules/moment/locale/uz-latn.js ***!
\***********************************************/
/*!************************************************!*\
!*** ./node_modules/moment/locale/gom-deva.js ***!
\************************************************/
/*!************************************************!*\
!*** ./node_modules/moment/locale/gom-latn.js ***!
\************************************************/
/*!************************************************!*\
!*** ./node_modules/moment/locale/tzm-latn.js ***!
\************************************************/
/*!************************************************!*\
!*** ./node_modules/moment/locale/x-pseudo.js ***!
\************************************************/
/*!*************************************************!*\ /*!*************************************************!*\
!*** ./node_modules/react-router/dist/index.js ***! !*** ./node_modules/react-router/dist/index.js ***!
\*************************************************/ \*************************************************/
/*!***************************************************!*\
!*** ./node_modules/moment/locale/ sync ^\.\/.*$ ***!
\***************************************************/
/*!***************************************************!*\ /*!***************************************************!*\
!*** ./node_modules/styled-tools/dist/es/prop.js ***! !*** ./node_modules/styled-tools/dist/es/prop.js ***!
\***************************************************/ \***************************************************/
@ -50,6 +610,10 @@
!*** ./node_modules/styled-tools/dist/es/theme.js ***! !*** ./node_modules/styled-tools/dist/es/theme.js ***!
\****************************************************/ \****************************************************/
/*!*****************************************************!*\
!*** ./node_modules/css-loader/dist/runtime/api.js ***!
\*****************************************************/
/*!*****************************************************!*\ /*!*****************************************************!*\
!*** ./node_modules/react-router-dom/dist/index.js ***! !*** ./node_modules/react-router-dom/dist/index.js ***!
\*****************************************************/ \*****************************************************/
@ -102,10 +666,18 @@
!*** ./node_modules/scheduler/cjs/scheduler.development.js ***! !*** ./node_modules/scheduler/cjs/scheduler.development.js ***!
\*************************************************************/ \*************************************************************/
/*!**************************************************************!*\
!*** ./node_modules/css-loader/dist/runtime/noSourceMaps.js ***!
\**************************************************************/
/*!***************************************************************!*\ /*!***************************************************************!*\
!*** ./node_modules/react-loader-spinner/dist/esm/helpers.js ***! !*** ./node_modules/react-loader-spinner/dist/esm/helpers.js ***!
\***************************************************************/ \***************************************************************/
/*!***************************************************************!*\
!*** ./node_modules/style-loader/dist/runtime/styleDomAPI.js ***!
\***************************************************************/
/*!*****************************************************************!*\ /*!*****************************************************************!*\
!*** ./node_modules/@emotion/stylis/dist/stylis.browser.esm.js ***! !*** ./node_modules/@emotion/stylis/dist/stylis.browser.esm.js ***!
\*****************************************************************/ \*****************************************************************/
@ -150,6 +722,10 @@
!*** ./node_modules/react-loader-spinner/dist/esm/loader/Watch.js ***! !*** ./node_modules/react-loader-spinner/dist/esm/loader/Watch.js ***!
\********************************************************************/ \********************************************************************/
/*!********************************************************************!*\
!*** ./node_modules/style-loader/dist/runtime/insertBySelector.js ***!
\********************************************************************/
/*!*********************************************************************!*\ /*!*********************************************************************!*\
!*** ./node_modules/react-loader-spinner/dist/esm/loader/Blocks.js ***! !*** ./node_modules/react-loader-spinner/dist/esm/loader/Blocks.js ***!
\*********************************************************************/ \*********************************************************************/
@ -162,6 +738,10 @@
!*** ./node_modules/react-loader-spinner/dist/esm/loader/Vortex.js ***! !*** ./node_modules/react-loader-spinner/dist/esm/loader/Vortex.js ***!
\*********************************************************************/ \*********************************************************************/
/*!*********************************************************************!*\
!*** ./node_modules/style-loader/dist/runtime/styleTagTransform.js ***!
\*********************************************************************/
/*!**********************************************************************!*\ /*!**********************************************************************!*\
!*** ./node_modules/react-loader-spinner/dist/esm/loader/Circles.js ***! !*** ./node_modules/react-loader-spinner/dist/esm/loader/Circles.js ***!
\**********************************************************************/ \**********************************************************************/
@ -174,6 +754,10 @@
!*** ./node_modules/react-loader-spinner/dist/esm/loader/Discuss.js ***! !*** ./node_modules/react-loader-spinner/dist/esm/loader/Discuss.js ***!
\**********************************************************************/ \**********************************************************************/
/*!**********************************************************************!*\
!*** ./node_modules/style-loader/dist/runtime/insertStyleElement.js ***!
\**********************************************************************/
/*!***********************************************************************!*\ /*!***********************************************************************!*\
!*** ./node_modules/react-loader-spinner/dist/esm/loader/LineWave.js ***! !*** ./node_modules/react-loader-spinner/dist/esm/loader/LineWave.js ***!
\***********************************************************************/ \***********************************************************************/
@ -230,6 +814,14 @@
!*** ./node_modules/react-loader-spinner/dist/esm/loader/RotatingLines.js ***! !*** ./node_modules/react-loader-spinner/dist/esm/loader/RotatingLines.js ***!
\****************************************************************************/ \****************************************************************************/
/*!****************************************************************************!*\
!*** ./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js ***!
\****************************************************************************/
/*!*****************************************************************************!*\
!*** ./node_modules/css-loader/dist/cjs.js!./src/components/DataTables.css ***!
\*****************************************************************************/
/*!*****************************************************************************!*\ /*!*****************************************************************************!*\
!*** ./node_modules/react-loader-spinner/dist/esm/loader/CirclesWithBar.js ***! !*** ./node_modules/react-loader-spinner/dist/esm/loader/CirclesWithBar.js ***!
\*****************************************************************************/ \*****************************************************************************/
@ -258,6 +850,10 @@
!*** ./node_modules/hoist-non-react-statics/dist/hoist-non-react-statics.cjs.js ***! !*** ./node_modules/hoist-non-react-statics/dist/hoist-non-react-statics.cjs.js ***!
\**********************************************************************************/ \**********************************************************************************/
/*!**********************************************************************************!*\
!*** ./node_modules/style-loader/dist/runtime/setAttributesWithoutAttributes.js ***!
\**********************************************************************************/
/*!****************************************************************************************************!*\ /*!****************************************************************************************************!*\
!*** ./node_modules/styled-components/node_modules/@emotion/unitless/dist/unitless.browser.esm.js ***! !*** ./node_modules/styled-components/node_modules/@emotion/unitless/dist/unitless.browser.esm.js ***!
\****************************************************************************************************/ \****************************************************************************************************/

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -18,6 +18,6 @@
!*** ./src/components/Ratings.js ***! !*** ./src/components/Ratings.js ***!
\***********************************/ \***********************************/
/*!***********************************!*\ /*!*************************************!*\
!*** ./src/components/Sidebar.js ***! !*** ./src/components/Challenge.js ***!
\***********************************/ \*************************************/

View File

@ -20,7 +20,7 @@ urlpatterns = [
path('admin/', admin.site.urls), path('admin/', admin.site.urls),
path('api/', include('api.urls')), path('api/', include('api.urls')),
path('', include('frontend.urls')), path('', include('frontend.urls')),
path('booklist/', include('frontend.urls')), path('books/', include('frontend.urls')),
path('login/', include('frontend.urls')), path('login/', include('frontend.urls')),
path('manage/', include('frontend.urls')), path('manage/', include('frontend.urls')),
path('manage/challenges', include('frontend.urls')) path('manage/challenges', include('frontend.urls'))