Skip to content
Snippets Groups Projects
Commit 60682715 authored by Antoine Gaudron-Desjardins's avatar Antoine Gaudron-Desjardins
Browse files

improve front

parent 570d0a54
Branches
No related tags found
1 merge request!28improve front
Pipeline #43930 passed with warnings
Showing
with 37484 additions and 37277 deletions
......@@ -14,7 +14,10 @@ from db import models, schemas
def get_waiting_time(place: str, db: Session):
""" Get the last estimated waiting time for the given place """
db_record = db.query(models.Records).filter(models.Records.place == place).order_by(models.Records.date.desc()).first()
if db_record.waiting_time is not None:
return db_record.waiting_time
else:
raise Exception
def get_stats(place: str, weekday: int, min_time_hour: int, min_time_mn: int, max_time_hour: int, max_time_mn: int, interval: timedelta, db: Session):
......@@ -66,12 +69,12 @@ def get_stats(place: str, weekday: int, min_time_hour: int, min_time_mn: int, ma
def get_comments(place: str, page: int, db: Session):
""" Get the 10 last comments for the given place """
if page == 0:
comments = db.query(models.Comments).order_by(models.Comments.date.desc(), models.Comments.id.desc()).all()
comments = db.query(models.Comments).order_by(models.Comments.published_at.desc(), models.Comments.id.desc()).all()
else:
comments = db.query(
models.Comments).filter(
models.Comments.place == place).order_by(
models.Comments.date.desc(),
models.Comments.published_at.desc(),
models.Comments.id.desc()).slice(
(page -
1) *
......@@ -84,7 +87,7 @@ def get_comments(place: str, page: int, db: Session):
def create_comment(place: str, new_comments: schemas.CommentBase, db: Session):
""" Add a new comment to the database """
date = datetime.now(tz=pytz.timezone("Europe/Paris"))
db_comment = models.Comments(**new_comments.dict(), date=date, place=place)
db_comment = models.Comments(**new_comments.dict(), published_at=date, place=place)
db.add(db_comment)
db.commit()
db.refresh(db_comment)
......@@ -104,7 +107,7 @@ def delete_comment(id: int, db: Session):
def get_news(place: str, db: Session):
""" Get the news for the given place """
news = db.query(models.News).filter(models.News.place == place).order_by(models.News.date.desc()).all()
news = db.query(models.News).filter(models.News.place == place).order_by(models.News.published_at.desc()).all()
return news
......
......@@ -22,14 +22,14 @@ class Comments(Base):
__tablename__ = "comments"
id = Column(Integer, primary_key=True, index=True)
comment = Column(Text)
date = Column(DateTime)
content = Column(Text)
published_at = Column(DateTime)
place = Column(String(10))
class News(Base):
"""Records sql table model"""
__tablename__ = "News sql table model"
"""News sql table model"""
__tablename__ = "news"
id = Column(Integer, primary_key=True, index=True)
title = Column(String(50))
......
......@@ -24,13 +24,13 @@ class Record(RecordBase):
class CommentBase(BaseModel):
"""Comments base schema"""
comment: str = Field(..., title="Content of the comment posted")
content: str = Field(..., title="Content of the comment posted")
class Comment(CommentBase):
"""Database comments base schema"""
id: int
date: datetime = Field(..., title="Publication date of the comment")
published_at: datetime = Field(..., title="Publication date of the comment")
place: str = Field(..., title="Name of the RU corresponding the comment")
class Config:
......
......@@ -14,9 +14,9 @@ async def get_news(place: str, db: Session = Depends(get_db)):
return crud.get_news(place, db)
@router.post('/{place}/news', response_model=schemas.News)
async def create_news(place: str, news: schemas.NewsBase, db: Session = Depends(get_db)):
return crud.create_news(place, news, db)
@router.post('/news', response_model=schemas.News)
async def create_news(news: schemas.NewsBase, db: Session = Depends(get_db)):
return crud.create_news(news, db)
@router.delete('/news/{id}', response_model=None)
......
......@@ -18,6 +18,7 @@
"react-bootstrap": "^2.4.0",
"react-dom": "^18.1.0",
"react-helmet": "^6.1.0",
"react-icons": "^4.4.0",
"react-router-dom": "^6.3.0",
"react-scripts": "5.0.1",
"react-spring": "^9.4.5",
......@@ -16661,6 +16662,14 @@
"react": ">=16.3.0"
}
},
"node_modules/react-icons": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/react-icons/-/react-icons-4.4.0.tgz",
"integrity": "sha512-fSbvHeVYo/B5/L4VhB7sBA1i2tS8MkT0Hb9t2H1AVPkwGfVHLJCqyr2Py9dKMxsyM63Eng1GkdZfbWj+Fmv8Rg==",
"peerDependencies": {
"react": "*"
}
},
"node_modules/react-is": {
"version": "17.0.2",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
......@@ -33425,6 +33434,12 @@
"react-side-effect": "^2.1.0"
}
},
"react-icons": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/react-icons/-/react-icons-4.4.0.tgz",
"integrity": "sha512-fSbvHeVYo/B5/L4VhB7sBA1i2tS8MkT0Hb9t2H1AVPkwGfVHLJCqyr2Py9dKMxsyM63Eng1GkdZfbWj+Fmv8Rg==",
"requires": {}
},
"react-is": {
"version": "17.0.2",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
......@@ -13,6 +13,7 @@
"react-bootstrap": "^2.4.0",
"react-dom": "^18.1.0",
"react-helmet": "^6.1.0",
"react-icons": "^4.4.0",
"react-router-dom": "^6.3.0",
"react-scripts": "5.0.1",
"react-spring": "^9.4.5",
......
import React, { useEffect, useRef, useState } from "react";
import axios from "axios";
import { BiSend } from "react-icons/bi";
import "../styles/Comments.css";
export default function Comments({ place }) {
const [comments, setComments] = useState([]);
export default function Messages({ place, infos }) {
const [messages, setMessages] = useState([]);
const [newComment, setNewComment] = useState("");
const [loading, setLoading] = useState(true);
const input = useRef();
const chat = useRef();
......@@ -13,13 +15,17 @@ export default function Comments({ place }) {
if (newComment.replace(/\s/g, "").length) {
ev.preventDefault();
axios
.post(`${process.env.REACT_APP_BASE_URL_BACK}/${place}/comments`, {
comment: newComment,
.post(`${process.env.REACT_APP_BASE_URL_BACK}/${encodeURIComponent(place)}/comments`, {
content: newComment,
})
.then((res) => {
let update = comments.map((_, index) => (index ? comments[index - 1] : res.data));
update.push(comments[comments.length - 1]);
setComments(update);
if (messages.length) {
let update = messages.map((_, index) => (index ? messages[index - 1] : res.data));
update.push(messages[messages.length - 1]);
setMessages(update);
} else {
setMessages([res.data]);
}
updateValue("");
})
.catch((e) => {
......@@ -39,11 +45,19 @@ export default function Comments({ place }) {
useEffect(() => {
axios
.get(`${process.env.REACT_APP_BASE_URL_BACK}/${place}/comments`)
.get(
`${process.env.REACT_APP_BASE_URL_BACK}/${encodeURIComponent(place)}/${
infos ? "news" : "comments"
}`,
)
.then((res) => {
setComments(res.data);
setMessages(res.data);
setLoading(false);
})
.catch((e) => console.log(e));
.catch((e) => {
console.log(e);
setLoading(false);
});
}, []);
useEffect(() => {
......@@ -53,32 +67,59 @@ export default function Comments({ place }) {
}
}, [chat.current]);
useEffect(() => {
if (input.current) {
input.current.style.height = "";
input.current.style.height = `${input.current.scrollHeight + 5}px`;
}
}, [newComment]);
return (
<div className="comments-side-bar">
<div className="comments-title">{infos ? "Infos" : "Commentaire"}</div>
<div ref={chat} className={`comments-scroll-bar ${infos && "infos-scroll-bar"}`}>
{!messages.length ? (
loading ? (
<div className={`comments-loading ${infos && "infos-loading"}`}>Chargement...</div>
) : (
<div className="no-comments">
<div>
Il n&apos;y a{" "}
{infos
? `aucune information particulière concernant ${place}`
: `aucun commentaire sur ${place}`}
</div>
</div>
)
) : (
messages.map((message, index) => {
let [date, hour] = message.published_at.split("T");
let [year, month, day] = date.split("-");
return (
<div id="comments-side-bar">
<div ref={chat} id="comments-scroll-bar">
{comments.map((comment, index) => (
<div key={index} className="comment">
<div className="comment-content">{comment.comment}</div>
<div className="comment-content">{message.content}</div>
<div className="comment-date">
{comment.date
.split("T")
.reduce((date, hours) => ${hours.substring(0, 5)} le ${date}`)}
{${hour.substring(0, 5)} le ${day}/${month}/${year}`}
</div>
</div>
))}
);
})
)}
</div>
<div id="comment-input-container">
{!infos && (
<div className="comment-input-container">
<textarea
id="comments-input"
className="comments-input"
ref={input}
value={newComment}
onChange={(ev) => updateValue(ev.target.value)}
placeholder="Ajouter un commentaire..."
placeholder="Ajouter un commentaire"
/>
<button id="comment-input-button" onClick={Submit}>
Envoyer
<button className="comment-input-button" onClick={Submit}>
<BiSend />
</button>
</div>
)}
</div>
);
}
......@@ -11,15 +11,10 @@ import {
} from "recharts";
import "../styles/Graph.css";
export default function DailyGraph({
place,
day,
min_time_hour,
min_time_mn,
max_time_hour,
max_time_mn,
interval,
}) {
export default function DailyGraph({ place }) {
const [day, min_time_hour, min_time_mn, max_time_hour, max_time_mn, interval] = [
3, 12, 0, 12, 40, 300,
];
const url =
process.env.REACT_APP_BASE_URL_BACK +
"/" +
......@@ -57,10 +52,7 @@ export default function DailyGraph({
};
return (
<>
<div className="graph-title">
Temps d&apos;attente pour le prochain créneau d&apos;ouverture
</div>
<div style={{ height: "60%", padding: "3rem" }}>
<div className="graph">
<ResponsiveContainer width="100%" height="100%">
<AreaChart
......@@ -110,6 +102,7 @@ export default function DailyGraph({
</AreaChart>
</ResponsiveContainer>
</div>
</>
<div className="graph-title">Temps d&apos;attente estimé depuis l&apos;ouverture</div>
</div>
);
}
import React from "react";
import { Link } from "react-router-dom";
import { Nav, Navbar, NavLink } from "react-bootstrap";
export default function Header() {
import "../styles/Header.css";
export default function Header({ selection, setSelection }) {
return (
<Navbar collapseOnSelect expand="sm" bg="dark" variant="dark">
<Navbar.Toggle
aria-controls="navbarScroll"
data-bs-toggle="collapse"
data-bs-target="#navbarScroll"
/>
<Navbar.Collapse id="navbarScroll">
<Nav>
<NavLink eventKey="1" as={Link} to="/">
Accueil
</NavLink>
<NavLink eventKey="2" as={Link} to="/eiffel">
RU Eiffel
</NavLink>
</Nav>
</Navbar.Collapse>
</Navbar>
<div id="header-container">
<div id="header-restaurant-status">
{selection
? `${selection.name} : actuellement ${selection.status ? "ouvert" : "fermé"}`
: "Accueil"}
</div>
<Link id="header-home-link" to="/" onClick={() => setSelection(null)}>
<h2>Eatfast</h2>
</Link>
<div id="header-timetable">{selection && `horaires : ${selection.timetable}`}</div>
</div>
);
}
import React from "react";
import React, { useState } from "react";
import axios from "axios";
import "../styles/WaitingTime.css";
export default function WaitingTime({ place }) {
const url = process.env.REACT_APP_BASE_URL_BACK + "/" + place + "/waiting_time";
const [post, setPost] = React.useState(null);
const [post, setPost] = useState(null);
React.useEffect(() => {
axios.get(url).then((response) => {
axios
.get(`${process.env.REACT_APP_BASE_URL_BACK}/${encodeURIComponent(place)}/waiting_time`)
.then((response) => {
if (response.data < 60) {
setPost(0);
} else {
setPost(Math.round(response.data / 60));
}
});
}, [url]);
}, []);
return (
<div className="parent">
<div id="waiting-time-parent">
{post ? (
<div className="waiting-time">
Temps d&apos;attente estimé à <b>{post} minutes</b>.
<div id="waiting-time-display">
<div>Temps d&apos;attente estimé:</div>
<div className="waiting-time-minutes">
<b id="waiting-time-number">{post}</b> minutes
</div>
</div>
) : (
<div>Pas de données...</div>
<div className="waiting-time-minutes">Pas de données...</div>
)}
</div>
);
......
import React from "react";
import ReactDOM from "react-dom";
import React, { useEffect, useState } from "react";
import { createRoot } from "react-dom/client";
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import axios from "axios";
import { Header, Footer } from "./components";
import { HomePage, Eiffel, NotFoundPage } from "./views";
import { Footer, Header } from "./components";
import { HomePage, RestaurantPage, NotFoundPage, DetailsPage } from "./views";
import "bootstrap/dist/css/bootstrap.min.css";
import "./styles/index.css";
export default function App() {
const [restaurantsList, setRestaurantsList] = useState([]);
const [selection, setSelection] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
axios
.get(`${process.env.REACT_APP_BASE_URL_BACK}/restaurants`)
.then((res) => {
setRestaurantsList(res.data);
setLoading(false);
})
.catch((e) => {
console.log(e);
setLoading(false);
});
setRestaurantsList([
{ name: "RU Eiffel", status: true, waiting_time: 17, timetable: "11h30-14h / 18h30-21h" },
{ name: "RU Bréguet", status: true, waiting_time: 25, timetable: "11h30-14h" },
{ name: "Lieu de vie", status: false, waiting_time: null, timetable: "11h30-14h / 18h30-21h" },
{ name: "Caféteria Eiffel", status: false, waiting_time: null, timetable: "18h30-21h" },
{ name: "Caféteria Bréguet", status: true, waiting_time: null, timetable: "18h30-21h" },
]);
}, []);
useEffect(() => {
let path = window.location.pathname.split("/");
if (restaurantsList && path.length >= 2 && path[1]) {
let name = decodeURIComponent(path[1]);
let filter = restaurantsList.filter((restaurant) => restaurant.name == name);
if (filter) {
setSelection(filter[0]);
}
}
}, [restaurantsList]);
return (
<div className="app">
<Router>
<Header />
<Header {...{ selection, setSelection }} />
<div className="page">
<Routes>
<Route exact path="/" element={<HomePage />} />
<Route path="/eiffel" element={<Eiffel />} />
<Route path="/*" element={<NotFoundPage />} />
<Route
exact
path="/"
element={<HomePage {...{ restaurantsList, setSelection, loading }} />}
/>
<Route
path="/:restaurant"
element={<RestaurantPage {...{ selection, setSelection }} />}
>
<Route path="details" element={<DetailsPage selection={selection} />} />
</Route>
<Route path="*" element={<NotFoundPage />} />
</Routes>
</div>
<Footer />
</Router>
<Footer />
</div>
);
}
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById("root"),
);
const container = document.getElementById("root");
const root = createRoot(container);
root.render(<App />);
#comments-input {
.comments-title {
font-size: 1.5rem;
}
.comments-input {
resize: none;
display : block;
height: 2.4rem;
......@@ -10,18 +14,29 @@
border-radius: 0.5rem;
}
#comments-side-bar {
.comments-input::placeholder {
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
.comments-side-bar {
width: 25%;
display: flex;
flex-direction: column;
justify-content: space-between;
}
#comments-scroll-bar {
.comments-scroll-bar {
flex: 1;
display: flex;
flex-direction: column-reverse;
overflow-y: scroll;
overflow-y: auto;
}
.infos-scroll-bar {
direction: rtl;
flex-direction: column;
}
.comment {
......@@ -45,20 +60,48 @@
margin-left: 0.2rem;
}
#comment-input-container {
.comment-input-container {
display: flex;
width: 100%;
background-color: rgb(59, 137, 255);
padding-left: 1rem;
}
#comment-input-button {
.comment-input-button {
display: flex;
justify-content: center;
align-items: center;
background-color: rgb(17, 2, 145);
color: white;
height: 2.4rem;
width: 2.4rem;
border-radius: 0.5rem;
padding: 0.2rem;
font-size: 1.5rem;
}
#comment-input-button:hover {
background-color: rgb(20, 0, 196);
.comment-input-button:hover {
background-color: rgb(18, 0, 177);
}
.no-comments {
height: 100%;
display: flex;
align-items: center;
padding: 1rem;
}
.comments-loading {
margin-top: 2rem;
height: 100%;
}
.infos-loading {
direction: ltr;
}
@media only screen and (max-device-width: 600px) {
.comments-side-bar {
width: 0px;
overflow: hidden;
}
}
\ No newline at end of file
#header-container {
display: flex;
justify-content: space-between;
align-items: baseline;
padding-left: 1rem;
padding-right: 1rem;
background-color: rgb(33, 37, 41);
}
#header-home-link {
text-decoration: none;
color: inherit;
}
#header-restaurant-status {
flex: 1;
text-align: left;
font-size: 1.2rem;
}
#header-timetable {
flex: 1;
text-align: right;
font-weight: lighter;
}
\ No newline at end of file
#home-container {
margin-top: 2rem;
display: flex;
flex-direction: column;
align-content: center;
}
#home-table {
margin-top: 2rem;
width: fit-content;
align-self: center;
}
.home-table-row {
height: 3rem;
}
.home-restaurant-name {
display: flex;
flex-direction: row-reverse;
justify-content: left;
height: 3rem;
align-items: center;
padding-right: 5rem;
}
.home-link-item {
color: inherit;
font-weight: bold;
text-decoration: none;
white-space: nowrap;
}
.home-link-item:hover {
color: inherit;
}
.home-link-item:hover + span {
color: rgb(45, 45, 45);
}
.home-arrow {
margin-right: 1rem;
}
.home-restaurant-status {
white-space: nowrap;
padding-right: 5rem;
text-align: left;
}
.home-waiting-time {
white-space: nowrap;
text-align: left;
}
\ No newline at end of file
.parent{
#waiting-time-parent {
display: flex;
flex-direction: column;
justify-content: center;
margin-top: 5%;
margin-bottom: 5%;
}
.waiting-time{
display: block;
margin-top: 50px;
margin-bottom: 50px;
font-size: large;
border: solid;
border-radius: 20px;
border-width: 2px;
padding: 10px;
max-width: 500px;
justify-content: center;
padding-left: 30px;
padding-right: 30px;
#waiting-time-display {
font-size: 1.4rem;
}
.waiting-time-minutes {
font-size: 1.8rem;
display: inline;
vertical-align: baseline;
}
#waiting-time-number {
font-size: 2.5rem;
font-weight: bold;
}
\ No newline at end of file
body, html {
color: white;
text-align: center;
background: linear-gradient(to right, #B06AB3, #4568DC);
/* background: linear-gradient(to right, #B06AB3, #4568DC); */
background-color: rgb(93, 114, 154);
}
.app{
......@@ -14,5 +15,7 @@ body, html {
.page{
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
}
.eiffel-container {
.restaurant-container {
display: flex;
height: 100%;
justify-content: space-between;
justify-content: center;
}
#eiffel-main-page {
width: 70%;
#restaurant-main-page {
width: 50%;
flex-direction: column;
align-content: center;
padding-left: 2rem
}
@media only screen and (max-device-width: 600px) {
#restaurant-main-page {
width: 100%;
}
}
\ No newline at end of file
import React from "react";
export default function Details() {
return(
<div>details page</div>
)
}
\ No newline at end of file
import React from "react";
import { DailyGraph, Timetable, WaitingTime, Comments } from "../components";
import "../styles/eiffel.css";
export default function Eiffel() {
return (
<div className="eiffel-container">
<div className="eiffel-container" id="eiffel-main-page">
<h2>RU Eiffel</h2>
<WaitingTime place="eiffel" />
<DailyGraph
place="eiffel"
day={3}
min_time_hour={12}
min_time_mn={0}
max_time_hour={12}
max_time_mn={40}
interval={300}
/>
<Timetable
schedule={{
LundiMidi: "11h30 - 14h",
LundiSoir: "18h30 - 21h",
MardiMidi: "11h30 - 14h",
MardiSoir: "18h30 - 21h",
MercrediMidi: "11h30 - 14h",
MercrediSoir: "18h30 - 21h",
JeudiMidi: "11h30 - 14h",
JeudiSoir: "18h30 - 21h",
VendrediMidi: "11h30 - 14h",
VendrediSoir: "18h30 - 21h",
}}
/>
</div>
<Comments place={"eiffel"} />
</div>
);
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment