frontend: FunctionsList et début de /admin
This commit is contained in:
@ -1,5 +1,6 @@
|
||||
import Link from 'next/link';
|
||||
import { useState, forwardRef } from 'react';
|
||||
import date from 'date-and-time';
|
||||
import Loader from '../Loader';
|
||||
import './FunctionCard.css';
|
||||
import { API_URL } from '../../utils/config';
|
||||
@ -16,8 +17,18 @@ const FunctionCard = forwardRef((props, ref) => {
|
||||
|
||||
return (
|
||||
<Link
|
||||
href={isFormOrArticle ? "/functions/[slug]" : `/functions/${props.slug}`}
|
||||
as={`/functions/${props.slug}`}
|
||||
{
|
||||
...(props.isAdmin) ?
|
||||
{
|
||||
href: "/admin/[slug]",
|
||||
as: `/admin/${props.slug}`
|
||||
}
|
||||
:
|
||||
{
|
||||
href: (isFormOrArticle) ? "/functions/[slug]" : `/functions/${props.slug}`,
|
||||
as: `/functions/${props.slug}`
|
||||
}
|
||||
}
|
||||
>
|
||||
{/* FunctionCard a une hauteur pendant chargement */}
|
||||
<div ref={ref} style={isLoading ? { height: "360px", justifyContent: "center" } : null} className={"FunctionCard col-sm-24 col-md-10 col-xl-7"}>
|
||||
@ -31,8 +42,8 @@ const FunctionCard = forwardRef((props, ref) => {
|
||||
<p className="FunctionCard__description">{props.description}</p>
|
||||
</div>
|
||||
<div className="FunctionCard__info">
|
||||
<p className="FunctionCard__category" style={{ backgroundColor: props.category.color }}>{props.category.name}</p>
|
||||
<p className="FunctionCard__publication-date">{props.publicationDate}</p>
|
||||
<p className="FunctionCard__category" style={{ backgroundColor: props.categorie.color }}>{props.categorie.name}</p>
|
||||
<p className="FunctionCard__publication-date">{date.format(new Date(props.createdAt), 'DD/MM/YYYY', true)}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
40
website/components/FunctionsList/FunctionsList.css
Normal file
40
website/components/FunctionsList/FunctionsList.css
Normal file
@ -0,0 +1,40 @@
|
||||
.Functions__title {
|
||||
padding: 20px 0 20px 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.Functions__form-control {
|
||||
display: block;
|
||||
height: calc(1.5em + .75rem + 2px);
|
||||
padding: .375rem .75rem;
|
||||
font-size: 1rem;
|
||||
font-weight: 400;
|
||||
line-height: 1.5;
|
||||
color: #495057;
|
||||
background-color: #fff;
|
||||
background-clip: padding-box;
|
||||
border: 1px solid #ced4da;
|
||||
border-radius: .5em;
|
||||
}
|
||||
.Functions__search-container {
|
||||
margin-bottom: 50px;
|
||||
}
|
||||
.Functions__select-option {
|
||||
color: rgb(221, 220, 220);
|
||||
}
|
||||
.Functions__search-input {
|
||||
width: 40%;
|
||||
}
|
||||
/* col-sm */
|
||||
@media (max-width: 576px) {
|
||||
.Functions__search-container {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
.Functions__select {
|
||||
width: 90%;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.Functions__search-input {
|
||||
width: 90%;
|
||||
}
|
||||
}
|
100
website/components/FunctionsList/FunctionsList.js
Normal file
100
website/components/FunctionsList/FunctionsList.js
Normal file
@ -0,0 +1,100 @@
|
||||
import { useState, useEffect, useRef, useCallback } from 'react';
|
||||
import { useRouter } from 'next/router';
|
||||
import FunctionCard from '../FunctionCard/FunctionCard';
|
||||
import Loader from '../Loader';
|
||||
import api from '../../utils/api';
|
||||
import useAPI from '../../hooks/useAPI';
|
||||
import './FunctionsList.css';
|
||||
|
||||
const FunctionsList = (props) => {
|
||||
|
||||
const { categoryId } = useRouter().query;
|
||||
|
||||
// State de recherche et de catégories
|
||||
const [, categories] = useAPI('/categories');
|
||||
const [inputSearch, setInputSearch] = useState({ search: "", selectedCategory: categoryId || "0" });
|
||||
|
||||
// State pour afficher les fonctions
|
||||
const [functionsData, setFunctionsData] = useState({ hasMore: true, rows: [] });
|
||||
const [isLoadingFunctions, setLoadingFunctions] = useState(true);
|
||||
const [pageFunctions, setPageFunctions] = useState(1);
|
||||
|
||||
// Récupère la catégorie avec la query categoryId
|
||||
useEffect(() => {
|
||||
if (categoryId) {
|
||||
handleChange({ target: { name: "selectedCategory", value: categoryId } });
|
||||
}
|
||||
}, [categoryId]);
|
||||
|
||||
// Récupère les fonctions si la page change
|
||||
useEffect(() => {
|
||||
getFunctionsData().then((data) => setFunctionsData({
|
||||
hasMore: data.hasMore,
|
||||
rows: [...functionsData.rows, ...data.rows]
|
||||
}));
|
||||
}, [pageFunctions]);
|
||||
|
||||
// Récupère les fonctions si la catégorie/recherche change
|
||||
useEffect(() => {
|
||||
getFunctionsData().then((data) => setFunctionsData(data));
|
||||
}, [inputSearch.selectedCategory, inputSearch.search]);
|
||||
|
||||
// Permet la pagination au scroll
|
||||
const observer = useRef();
|
||||
const lastFunctionCardRef = useCallback((node) => {
|
||||
if (isLoadingFunctions) return;
|
||||
if (observer.current) observer.current.disconnect();
|
||||
observer.current = new IntersectionObserver((entries) => {
|
||||
if (entries[0].isIntersecting && functionsData.hasMore) {
|
||||
setPageFunctions(pageFunctions + 1);
|
||||
}
|
||||
}, { threshold: 1 });
|
||||
if (node) observer.current.observe(node);
|
||||
}, [isLoadingFunctions, functionsData.hasMore]);
|
||||
|
||||
const getFunctionsData = () => {
|
||||
setLoadingFunctions(true);
|
||||
return new Promise(async (next) => {
|
||||
const result = await api.get(`/functions?page=${pageFunctions}&limit=10&categoryId=${inputSearch.selectedCategory}&search=${inputSearch.search}`);
|
||||
setLoadingFunctions(false);
|
||||
next(result.data);
|
||||
});
|
||||
}
|
||||
|
||||
const handleChange = (event) => {
|
||||
const inputSearchNew = { ...inputSearch };
|
||||
inputSearchNew[event.target.name] = event.target.value;
|
||||
setInputSearch(inputSearchNew);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="container text-center">
|
||||
<div className="row justify-content-center">
|
||||
{props.children}
|
||||
</div>
|
||||
|
||||
<div className="Functions__search-container row justify-content-center">
|
||||
<select name="selectedCategory" value={inputSearch.selectedCategory} onChange={handleChange} className="Functions__select Functions__form-control">
|
||||
<option value="0">Toutes catégories</option>
|
||||
{categories.map((category) => (
|
||||
<option key={category.id} value={category.id} className="Functions__select-option" style={{ backgroundColor: category.color }}>{category.name}</option>
|
||||
))}
|
||||
</select>
|
||||
<input value={inputSearch.search} onChange={handleChange} type="search" className="Functions__form-control Functions__search-input" name="search" id="search" placeholder="🔎 Rechercher..."></input>
|
||||
</div>
|
||||
|
||||
<div className="row justify-content-center">
|
||||
{functionsData.rows.map((currentFunction, index) => {
|
||||
// Si c'est le dernier élément
|
||||
if (functionsData.rows.length === index + 1) {
|
||||
return <FunctionCard isAdmin={props.isAdmin} key={currentFunction.id} ref={lastFunctionCardRef} { ...currentFunction } />;
|
||||
}
|
||||
return <FunctionCard isAdmin={props.isAdmin} key={currentFunction.id} { ...currentFunction } />;
|
||||
})}
|
||||
</div>
|
||||
{isLoadingFunctions && <Loader />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default FunctionsList;
|
@ -37,7 +37,6 @@ export default function Header() {
|
||||
<ul className={`navbar__list ${(isActive) ? "navbar__list-active" : ""}`}>
|
||||
<NavigationLink name="Accueil" path="/" />
|
||||
<NavigationLink name="Fonctions" path="/functions" />
|
||||
|
||||
{
|
||||
(!isAuth) ?
|
||||
<Fragment>
|
||||
@ -58,6 +57,10 @@ export default function Header() {
|
||||
</li>
|
||||
</Fragment>
|
||||
}
|
||||
{
|
||||
(isAuth && user.isAdmin) &&
|
||||
<NavigationLink name="Admin" path="/admin" />
|
||||
}
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
|
Reference in New Issue
Block a user