Syncronisation de la page d'accueil avec l'api

This commit is contained in:
Quentin Roussel
2023-03-20 21:39:45 +01:00
parent 226f8617d6
commit 4b0f2f237f
11 changed files with 274 additions and 133 deletions

View File

@@ -116,8 +116,7 @@ CREATE TABLE `sources` (
INSERT INTO `sources` (`id`, `nom`) VALUES INSERT INTO `sources` (`id`, `nom`) VALUES
(1, 'website'), (1, 'website'),
(2, 'borne'), (2, 'borne');
(3, 'instagram');
-- -------------------------------------------------------- -- --------------------------------------------------------
@@ -180,11 +179,12 @@ CREATE TABLE `stats_autres_semaine` (
CREATE TABLE `stats_general_annee` ( CREATE TABLE `stats_general_annee` (
`id` int NOT NULL, `id` int NOT NULL,
`date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, `date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`moyenne_globale` float NOT NULL, `nb_avis` int NOT NULL,
`moyenne_site` float NOT NULL, `moyenne_globale` float DEFAULT NULL,
`moyenne_borne` float NOT NULL, `moyenne_site` float DEFAULT NULL,
`dist_age` text NOT NULL COMMENT 'Distribution de l''age des auteurs', `moyenne_borne` float DEFAULT NULL,
`dist_sexe` text NOT NULL COMMENT 'Distribution du sexe des auteurs' `dist_age` text DEFAULT NULL COMMENT 'Distribution de l''age des auteurs',
`dist_sexe` text DEFAULT NULL COMMENT 'Distribution du sexe des auteurs'
) ; ) ;
-- -------------------------------------------------------- -- --------------------------------------------------------
@@ -196,11 +196,12 @@ CREATE TABLE `stats_general_annee` (
CREATE TABLE `stats_general_jour` ( CREATE TABLE `stats_general_jour` (
`id` int NOT NULL, `id` int NOT NULL,
`date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, `date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`nb_avis` int NOT NULL,
`moyenne_globale` float DEFAULT NULL, `moyenne_globale` float DEFAULT NULL,
`moyenne_site` float DEFAULT NULL, `moyenne_site` float DEFAULT NULL,
`moyenne_borne` float DEFAULT NULL, `moyenne_borne` float DEFAULT NULL,
`dist_age` text COMMENT 'Distribution de l''age des auteurs', `dist_age` text DEFAULT NULL COMMENT 'Distribution de l''age des auteurs',
`dist_sexe` text COMMENT 'Distribution du sexe des auteurs' `dist_sexe` text DEFAULT NULL COMMENT 'Distribution du sexe des auteurs'
) ; ) ;
-- -------------------------------------------------------- -- --------------------------------------------------------
@@ -212,11 +213,12 @@ CREATE TABLE `stats_general_jour` (
CREATE TABLE `stats_general_mois` ( CREATE TABLE `stats_general_mois` (
`id` int NOT NULL, `id` int NOT NULL,
`date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, `date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`moyenne_globale` float NOT NULL, `nb_avis` int NOT NULL,
`moyenne_site` float NOT NULL, `moyenne_globale` float DEFAULT NULL,
`moyenne_borne` float NOT NULL, `moyenne_site` float DEFAULT NULL,
`dist_age` text NOT NULL COMMENT 'Distribution de l''age des auteurs', `moyenne_borne` float DEFAULT NULL,
`dist_sexe` text NOT NULL COMMENT 'Distribution du sexe des auteurs' `dist_age` text DEFAULT NULL COMMENT 'Distribution de l''age des auteurs',
`dist_sexe` text DEFAULT NULL COMMENT 'Distribution du sexe des auteurs'
) ; ) ;
-- -------------------------------------------------------- -- --------------------------------------------------------
@@ -228,11 +230,12 @@ CREATE TABLE `stats_general_mois` (
CREATE TABLE `stats_general_semaine` ( CREATE TABLE `stats_general_semaine` (
`id` int NOT NULL, `id` int NOT NULL,
`date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, `date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`moyenne_globale` float NOT NULL, `nb_avis` int NOT NULL,
`moyenne_site` float NOT NULL, `moyenne_globale` float DEFAULT NULL,
`moyenne_borne` float NOT NULL, `moyenne_site` float DEFAULT NULL,
`dist_age` text NOT NULL COMMENT 'Distribution de l''age des auteurs', `moyenne_borne` float DEFAULT NULL,
`dist_sexe` text NOT NULL COMMENT 'Distribution du sexe des auteurs' `dist_age` text DEFAULT NULL COMMENT 'Distribution de l''age des auteurs',
`dist_sexe` text DEFAULT NULL COMMENT 'Distribution du sexe des auteurs'
) ; ) ;
-- --

View File

@@ -55,44 +55,44 @@ services:
restart: always restart: always
#Serveur web de l'interface de la borne #Serveur web de l'interface de la borne
interface_borne: # interface_borne:
image: httpd:latest # image: httpd:latest
volumes: # volumes:
- ./interface_borne:/usr/local/apache2/htdocs/ # - ./interface_borne:/usr/local/apache2/htdocs/
container_name: interface_borne # container_name: interface_borne
ports: # ports:
- 8888:80 # - 8888:80
#Serveur web de l'interface admin # #Serveur web de l'interface admin
interface_admin: # interface_admin:
image: httpd:latest # image: httpd:latest
volumes: # volumes:
- ./interface_admin:/usr/local/apache2/htdocs/ # - ./interface_admin:/usr/local/apache2/htdocs/
container_name: interface_admin # container_name: interface_admin
ports: # ports:
- 80:80 # - 80:80
#Backend de la borne : scripts pythons de reconnaissances video et audio # #Backend de la borne : scripts pythons de reconnaissances video et audio
#Envoient les infos a l'interface de la borne par websocket pour mettre a jour l'interface rapidement # #Envoient les infos a l'interface de la borne par websocket pour mettre a jour l'interface rapidement
#Met a jour les avis en faisant des requêtes a l'API # #Met a jour les avis en faisant des requêtes a l'API
backend_reconnaissance: # backend_reconnaissance:
build: ./backend_reconnaissance # build: ./backend_reconnaissance
container_name: backend_reconnaissance # container_name: backend_reconnaissance
restart: always # restart: always
devices: # devices:
- /dev/video3:/dev/video0 # - /dev/video3:/dev/video0
environment: # environment:
- PORT=5000 # - PORT=5000
- HOST=backend_reconnaissance # - HOST=backend_reconnaissance
ports: # ports:
#Ce container est le serveur websocker dont le client est l'interface de la borne qui tourne dans le navigateur # #Ce container est le serveur websocker dont le client est l'interface de la borne qui tourne dans le navigateur
- 5000:5000 # - 5000:5000
video_loopback: # video_loopback:
build: ./video_loopback # build: ./video_loopback
container_name: video_loopback # container_name: video_loopback
restart: always # restart: always
devices: # devices:
- /dev/video0:/dev/video0 # - /dev/video0:/dev/video0
- /dev/video2:/dev/video1 # - /dev/video2:/dev/video1
- /dev/video3:/dev/video2 # - /dev/video3:/dev/video2

View File

@@ -1,4 +1,4 @@
import { useEffect } from "react"; import { useEffect, useState } from "react";
import { api } from "../config/reviewsApi"; import { api } from "../config/reviewsApi";
export default function useStats(limit, interval) { export default function useStats(limit, interval) {
@@ -7,7 +7,7 @@ export default function useStats(limit, interval) {
const [error, setError] = useState(false); const [error, setError] = useState(false);
async function fetchData(limit, interval) { async function fetchData(limit, interval) {
const response = await fetch("http://" + api.HOST + `/get_stats?interval=${interval}&limit=${limit}`) const response = await fetch("http://" + api.HOST + `/borne/get_stats?interval=${interval}&limit=${limit}`)
if(response.ok) { if(response.ok) {
const data = await response.json(); const data = await response.json();
setStats(data); setStats(data);
@@ -20,7 +20,7 @@ export default function useStats(limit, interval) {
useEffect(() => { useEffect(() => {
fetchData(limit, interval); fetchData(limit, interval);
}) }, [])
return {stats, loading, error}; return {stats, loading, error};
} }

View File

@@ -12,6 +12,7 @@
"@next/font": "13.1.6", "@next/font": "13.1.6",
"bootstrap": "^5.2.3", "bootstrap": "^5.2.3",
"chart.js": "^4.2.0", "chart.js": "^4.2.0",
"date-fns": "^2.29.3",
"eslint": "8.33.0", "eslint": "8.33.0",
"eslint-config-next": "13.1.6", "eslint-config-next": "13.1.6",
"next": "13.1.6", "next": "13.1.6",

View File

@@ -57,8 +57,8 @@ export default function AvisListPage() {
<div className='d-flex flex-row justify-content-around col-md-6'> <div className='d-flex flex-row justify-content-around col-md-6'>
<div>Min : {minGrade}/10</div> <div>Min : {minGrade}/10</div>
<div className={styles.sliderContainer}> <div className={styles.sliderContainer}>
<input type="range" value={minGrade} onChange={(e) => setMinGrade(e.target.value)} min="0" max="10" step="1" className={styles.slider}></input> <input type="range" value={minGrade} onChange={(e) => setMinGrade(Number(e.target.value))} min="0" max="10" step="1" className={styles.slider}></input>
<input type="range" value={maxGrade} onChange={(e) => setMaxGrade(e.target.value)} min="0" max="10" step="1" className={styles.slider}></input> <input type="range" value={maxGrade} onChange={(e) => setMaxGrade(Number(e.target.value))} min="0" max="10" step="1" className={styles.slider}></input>
</div> </div>
<div>Max : {maxGrade}/10</div> <div>Max : {maxGrade}/10</div>
</div> </div>

View File

@@ -3,74 +3,132 @@ import { Card, Container } from 'react-bootstrap'
import ComparativeBarChart from '../components/ComparativeBarChart' import ComparativeBarChart from '../components/ComparativeBarChart'
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import styles from "../styles/Home.module.css" import styles from "../styles/Home.module.css"
import useStats from '../hooks/stats'
import getDay from 'date-fns/getDay'
import getWeek from '../util'
export default function Home() { export default function Home() {
const [datasets, setDatasets] = useState(null); const [datasets, setDatasets] = useState(null);
const [averages, setAverages] = useState(null); const [averages, setAverages] = useState(null);
const [differences, setDifferences] = useState(null); const [differences, setDifferences] = useState(null);
useEffect(() => { useEffect(() => {
if (datasets) { if (datasets) {
let newAverages = [] let newAverages = []
let newDifferences = [] let newDifferences = []
for (let i = 0; i < datasets.length; i++) { for (let i = 0; i < datasets.length; i++) {
newAverages[i] = datasets[i].current.reduce((a, b) => a + b) / datasets[i].current.length let currentEntriesCount = 0;
newDifferences[i] = newAverages[i] - datasets[i].previous.reduce((a, b) => a + b) / datasets[i].previous.length let previousEntriesCount = 0;
for(let x of datasets[i].current) {
if(x != null) {
currentEntriesCount++;
}
}
for(let x of datasets[i].previous) {
if(x != null) {
previousEntriesCount++;
}
}
if(currentEntriesCount != 0) {
newAverages[i] = datasets[i].current.reduce((a, b) => a + b) / currentEntriesCount;
if(previousEntriesCount > 0) {
newDifferences[i] = newAverages[i] - datasets[i].previous.reduce((a, b) => a + b) / datasets[i].previous.length
}else {
newDifferences[i] = newAverages[i]
}
}else {
newDifferences[i] = 0;
newAverages[i] = 0;
}
} }
setAverages(newAverages); setAverages(newAverages);
setDifferences(newDifferences); setDifferences(newDifferences);
} }
}, [datasets]); }, [datasets]);
useEffect(() => setDatasets([ const { stats, loading, error } = useStats(14, "jour");
{ title: "Nombre d'avis", current: [3, 2, 3, 4, 5, 6, 7], previous: [7, 6, 5, 4, 3, 2, 1] },
{ title: "Notes moyennes", current: [1, 2, 3, 4, 5, 6, 7], previous: [7, 6, 5, 4, 3, 2, 1] } useEffect(() => {
]), []); console.log(getDay(new Date("2023-03-20T14:00:00.000Z")))
})
useEffect(() => {
if(!error && !loading) {
let reviewCount = [null,null,null,null,null,null,null];
let reviewCountPrev = [null,null,null,null,null,null,null]
let reviewAvg = [null,null,null,null,null,null,null]
let reviewAvgPrev = [null,null,null,null,null,null,null]
for(let i = 0; i < stats.length; i++) {
let date = new Date(Date.parse(stats[i].date))
let now = new Date();
let day = (date.getDay() - 1) % 7;
let week = getWeek(date, 1);
let thisWeek = getWeek(now, 1);
console.log({date,week,thisWeek,day})
if(week == thisWeek) {
reviewCount[day] = stats[i].nb_avis;
reviewAvg[day] = stats[i].moyenne_globale;
}else if(week = thisWeek - 1){
reviewAvgPrev[day] = stats[i].moyenne_globale;
reviewCountPrev[day] = stats[i].nb_avis;
}
}
setDatasets([
{ title: "Nombre d'avis", current: reviewCount, previous: reviewCountPrev },
{ title: "Notes moyennes", current: reviewAvg, previous: reviewAvgPrev }
])
}
}, [stats]);
function dataVisualizer(title, current, previous, average, difference) { function dataVisualizer(title, current, previous, average, difference) {
return <div key={title}> return <div key={title}>
<h3>{title}</h3> <h3>{title}</h3>
<Card className={styles.averageCard}> <Card className={styles.averageCard}>
<Card.Title>Moyenne</Card.Title> <Card.Title>Moyenne</Card.Title>
<Card.Body className={styles.averageCardBody}> <Card.Body className={styles.averageCardBody}>
<div <div
className={styles.averageMainValue} className={styles.averageMainValue}
> >
{Math.round(average * 1e2) / 1e2} {Math.round(average * 1e2) / 1e2}
</div> </div>
<div <div
className={[styles.averageCardSecondaryValue, difference >= 0 ? styles.averagePositive : styles.averageNegative].join(' ')} className={[styles.averageCardSecondaryValue, difference >= 0 ? styles.averagePositive : styles.averageNegative].join(' ')}
> >
{(difference >= 0 ? "+" : "-") + Math.round(difference * 1e2) / 1e2} {(difference >= 0 ? "+" : "-") + Math.round(difference * 1e2) / 1e2}
</div> </div>
</Card.Body> </Card.Body>
</Card> </Card>
<ComparativeBarChart <ComparativeBarChart
xlabels={["lundi", "mardi", "mercredi", "jeudi", "vendredi", "samedi", "dimanche"]} xlabels={["lundi", "mardi", "mercredi", "jeudi", "vendredi", "samedi", "dimanche"]}
label0="Cette semaine" label0="Cette semaine"
label1="La semaine dernière" label1="La semaine dernière"
data0={current} data0={current}
data1={previous} data1={previous}
/> />
<hr /> <hr />
</div> </div>
} }
return ( return (
<> <>
<Head> <Head>
<title>Create Next App</title> <title>Create Next App</title>
<meta name="description" content="Page d'accueil" /> <meta name="description" content="Page d'accueil" />
</Head> </Head>
<Container fluid> <Container fluid>
<Card> <Card>
<Card.Header as="h2">Vos performances cette semaine</Card.Header> <Card.Header as="h2">Vos performances cette semaine</Card.Header>
<Card.Body> <Card.Body>
{datasets && averages && differences && datasets.map((set, i) => dataVisualizer(set.title, set.current, set.previous, averages[i], differences[i]))} {datasets && averages && differences && datasets.map((set, i) => dataVisualizer(set.title, set.current, set.previous, averages[i], differences[i]))}
</Card.Body> </Card.Body>
<div className='col col-12 col-lg-8 mx-auto'> <div className='col col-12 col-lg-8 mx-auto'>
</div> </div>
</Card> </Card>
</Container> </Container>
</> </>
) )
} }

View File

@@ -0,0 +1,34 @@
/**
* Returns the week number for this date. dowOffset is the day of week the week
* "starts" on for your locale - it can be from 0 to 6. If dowOffset is 1 (Monday),
* the week returned is the ISO 8601 week number.
* @param int dowOffset
* @return int
*/
export default function getWeek (date,dowOffset) {
/*getWeek() was developed by Nick Baicoianu at MeanFreePath: http://www.meanfreepath.com */
dowOffset = typeof(dowOffset) == 'number' ? dowOffset : 0; //default dowOffset to zero
var newYear = new Date(date.getFullYear(),0,1);
var day = newYear.getDay() - dowOffset; //the day of week the year begins on
day = (day >= 0 ? day : day + 7);
var daynum = Math.floor((date.getTime() - newYear.getTime() -
(date.getTimezoneOffset()-newYear.getTimezoneOffset())*60000)/86400000) + 1;
var weeknum;
//if the year starts before the middle of a week
if(day < 4) {
weeknum = Math.floor((daynum+day-1)/7) + 1;
if(weeknum > 52) {
nYear = new Date(date.getFullYear() + 1,0,1);
nday = nYear.getDay() - dowOffset;
nday = nday >= 0 ? nday : nday + 7;
/*if the next year starts before the middle of
the week, it is week #1 of that year*/
weeknum = nday < 4 ? 1 : 53;
}
}
else {
weeknum = Math.floor((daynum+day-1)/7);
}
return weeknum;
};

View File

@@ -143,13 +143,23 @@ const getStats = (interval, limit = 10) => {
Ces fonction sont des handlers pour les routes express, elles sont appelées par les routes et renvoient les données au format JSON Ces fonction sont des handlers pour les routes express, elles sont appelées par les routes et renvoient les données au format JSON
*/ */
export const handleGetLastReviews = (req, res) => { export const handleGetLastReviews = (req, res) => {
getLastReviews(req.query.limit) if (req.query.limit) {
.then((reviews) => { getLastReviews(Number(req.query.limit))
res.send(reviews); .then((reviews) => {
}) res.send(reviews);
.catch((err) => { })
res.status(500).send("Error: " + err.message); .catch((err) => {
}); res.status(500).send("Error: " + err.message);
});
} else {
getLastReviews()
.then((reviews) => {
res.send(reviews);
})
.catch((err) => {
res.status(500).send("Error: " + err.message);
});
}
} }
export const handleGetReview = (req, res) => { export const handleGetReview = (req, res) => {
@@ -176,13 +186,23 @@ export const handleGetCriteres = (req, res) => {
export const handleGetNotesAutres = (req, res) => { export const handleGetNotesAutres = (req, res) => {
if (req.query.critere) { if (req.query.critere) {
getNotesAutresFromCritere(req.query.critere, req.query.limit) if (req.query.limit) {
.then((notes) => { getNotesAutresFromCritere(req.query.critere, Number(req.query.limit))
res.send(notes); .then((notes) => {
}) res.send(notes);
.catch((err) => { })
res.status(500).send("Error: " + err.message); .catch((err) => {
}); res.status(500).send("Error: " + err.message);
});
} else {
getNotesAutresFromCritere(req.query.critere)
.then((notes) => {
res.send(notes);
})
.catch((err) => {
res.status(500).send("Error: " + err.message);
});
}
} else if (req.query.id) { } else if (req.query.id) {
getNotesAutresFromReview(req.query.id) getNotesAutresFromReview(req.query.id)
.then((notes) => { .then((notes) => {
@@ -197,11 +217,21 @@ export const handleGetNotesAutres = (req, res) => {
} }
export const handleGetStats = (req, res) => { export const handleGetStats = (req, res) => {
getStats(req.query.interval, req.query.limit) if (req.query.limit) {
.then((stats) => { getStats(req.query.interval, Number(req.query.limit))
res.send(stats); .then((stats) => {
}) res.send(stats);
.catch((err) => { })
res.status(500).send("Error: " + err.message); .catch((err) => {
}); res.status(500).send("Error: " + err.message);
});
} else {
getStats(req.query.interval)
.then((stats) => {
res.send(stats);
})
.catch((err) => {
res.status(500).send("Error: " + err.message);
});
}
} }

View File

@@ -3,7 +3,7 @@ import express from 'express';
import bodyParser from 'body-parser'; import bodyParser from 'body-parser';
import { addReviewFromRequest } from './borne/post_handler.js'; import { addReviewFromRequest } from './borne/post_handler.js';
import { addSocialReviewFromRequest } from './reseaux_sociaux/post_handler.js'; import { addSocialReviewFromRequest } from './reseaux_sociaux/post_handler.js';
import { startCronJobs } from './stats/update_stats.js'; import { startCronJobs, manualUpdateStats } from './stats/update_stats.js';
import * as borneHandler from './borne/get_handler.js'; import * as borneHandler from './borne/get_handler.js';
import cors from "cors"; import cors from "cors";
@@ -20,6 +20,10 @@ app.get('/borne/get_criteres', borneHandler.handleGetCriteres);
app.get('/borne/notes_autres', borneHandler.handleGetNotesAutres); app.get('/borne/notes_autres', borneHandler.handleGetNotesAutres);
app.get('/borne/get_stats', borneHandler.handleGetStats); app.get('/borne/get_stats', borneHandler.handleGetStats);
app.get('/update_stats', (req, res) => {
manualUpdateStats();
res.send("OK");
})
startCronJobs(); startCronJobs();

View File

@@ -8,6 +8,10 @@ SET @date_limite = DATE_ADD(NOW(), INTERVAL -DAY_COUNT_DELAY DAY);
On récupère les notes notes moyennes sur la periode, en séparant global, borne et site On récupère les notes notes moyennes sur la periode, en séparant global, borne et site
*/ */
SELECT @nb_avis:=COUNT(*)
FROM borne_avis
WHERE borne_avis.date > @date_limite;
SELECT @moyenne_globale:=AVG(note_principale) SELECT @moyenne_globale:=AVG(note_principale)
FROM borne_avis FROM borne_avis
WHERE borne_avis.date > @date_limite; WHERE borne_avis.date > @date_limite;
@@ -38,7 +42,7 @@ SELECT @stats_a:=COUNT(*) FROM borne_avis
SET @dist_sexe = CONCAT(@stats_f,",",@stats_h,",",@stats_a); SET @dist_sexe = CONCAT(@stats_f,",",@stats_h,",",@stats_a);
INSERT INTO STATS_GENERAL_TABLE_NAME (moyenne_globale, moyenne_borne, moyenne_site, dist_sexe) VALUES (@moyenne_globale, @moyenne_borne, @moyenne_site, @dist_sexe); INSERT INTO STATS_GENERAL_TABLE_NAME (moyenne_globale, nb_avis, moyenne_borne, moyenne_site, dist_sexe) VALUES (@moyenne_globale, @nb_avis, @moyenne_borne, @moyenne_site, @dist_sexe);
INSERT INTO STATS_AUTRES_TABLE_NAME (critere_id, note) INSERT INTO STATS_AUTRES_TABLE_NAME (critere_id, note)
SELECT critere_id, AVG(note) as moyenne FROM borne_notes_autre SELECT critere_id, AVG(note) as moyenne FROM borne_notes_autre

View File

@@ -67,3 +67,10 @@ export const startCronJobs = () => {
) )
console.log("All cronjobs initiated") console.log("All cronjobs initiated")
} }
export function manualUpdateStats() {
computeStats(1, "stats_general_jour", "stats_autres_jour");
computeStats(7, "stats_general_semaine", "stats_autres_jour");
computeStats(30, "stats_general_mois", "stats_autres_mois");
computeStats(365, "stats_general_annee", "stats_autres_annee");
}