feat: add selection
All checks were successful
Deploy Production Server / if_merged (pull_request) Successful in 6m50s

This commit is contained in:
2026-01-05 17:56:37 +01:00
parent 0757ae7198
commit 13650b27c1
20 changed files with 507 additions and 41 deletions

View File

@@ -14,7 +14,7 @@ import {
} from "@fortawesome/free-solid-svg-icons";
import {CheckField} from "../components/MemberCustomFiels.jsx";
import {toast} from "react-toastify";
import {apiAxios} from "../utils/Tools.js";
import {apiAxios, getCatName} from "../utils/Tools.js";
import {useEffect, useState} from "react";
const vite_url = import.meta.env.VITE_URL;
@@ -40,7 +40,7 @@ export function MePage() {
<LoadingProvider><LicenceCard userData={data}/></LoadingProvider>
</div>
<div className="col-md-6">
<LoadingProvider><SelectCard/></LoadingProvider>
<LoadingProvider><SelectCard userData={data}/></LoadingProvider>
</div>
</div>
</div>
@@ -93,10 +93,17 @@ function PhotoCard({data}) {
</div>;
}
function SelectCard() {
function SelectCard({userData}) {
return <div className="card mb-4 mb-md-0">
<div className="card-header">Sélection en équipe de France</div>
<div className="card-body">
<ul className="list-group">
{userData?.selections && userData.selections.sort((a, b) => b.saison - a.saison).map((selection, index) => {
return <div key={index} className="list-group-item d-flex justify-content-between align-items-start">
<div className="me-auto">{selection?.saison}-{selection?.saison + 1} en {getCatName(selection?.categorie)}</div>
</div>
})}
</ul>
</div>
</div>;
}

View File

@@ -100,7 +100,7 @@ function InformationForm({data}) {
}
return <>
<form onSubmit={handleSubmit}>
<form onSubmit={handleSubmit} autoComplete="off">
<div className="card mb-4">
<input name="id" value={data.id} readOnly hidden/>
<input name="clubId" value={data.clubId} readOnly hidden/>

View File

@@ -60,7 +60,7 @@ function InformationForm() {
}
return <>
<form onSubmit={handleSubmit}>
<form onSubmit={handleSubmit} autoComplete="off">
<div className="card mb-4">
<div className="card-header">Nouveau club</div>
<div className="card-body text-center">

View File

@@ -79,7 +79,7 @@ export function InformationForm({data}) {
addPhoto(event, formData, send);
}
return <form onSubmit={handleSubmit}>
return <form onSubmit={handleSubmit} autoComplete="off">
<div className="card mb-4">
<div className="card-header">Information</div>
<div className="card-body">

View File

@@ -11,6 +11,7 @@ import {apiAxios, errFormater} from "../../../utils/Tools.js";
import {ConfirmDialog} from "../../../components/ConfirmDialog.jsx";
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
import {faFilePdf} from "@fortawesome/free-solid-svg-icons";
import {SelectCard} from "./SelectCard.jsx";
const vite_url = import.meta.env.VITE_URL;
@@ -58,7 +59,7 @@ export function MemberPage() {
<LoadingProvider><LicenceCard userData={data}/></LoadingProvider>
</div>
<div className="col-md-6">
<LoadingProvider><SelectCard/></LoadingProvider>
<LoadingProvider><SelectCard userData={data}/></LoadingProvider>
</div>
</div>
<div className="col" style={{textAlign: 'right', marginTop: '1em'}}>
@@ -94,14 +95,3 @@ function PhotoCard({data}) {
</div>
</div>;
}
function SelectCard() {
return <></>
/*return <div className="card mb-4 mb-md-0">
<div className="card-header">Sélection en équipe de France</div>
<div className="card-body">
<p className="mb-1">Web Design</p>
</div>
</div>;*/
}

View File

@@ -0,0 +1,208 @@
import {useLoadingSwitcher} from "../../../hooks/useLoading.jsx";
import {useFetch} from "../../../hooks/useFetch.js";
import {useEffect, useReducer, useState} from "react";
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
import {faEuroSign, faPen} from "@fortawesome/free-solid-svg-icons";
import {AxiosError} from "../../../components/AxiosError.jsx";
import {apiAxios, CatList, errFormater, getCatName, getSaison} from "../../../utils/Tools.js";
import {toast} from "react-toastify";
function selectionReducer(selections, action) {
switch (action.type) {
case 'ADD':
return [
...selections,
action.payload
]
case 'REMOVE':
return selections.filter(selection => selection.id !== action.payload)
case 'UPDATE_OR_ADD':
const index = selections.findIndex(selection => selection.id === action.payload.id)
if (index === -1) {
return [
...selections,
action.payload
]
} else {
selections[index] = action.payload
return [...selections]
}
case 'SORT':
return selections.sort((a, b) => b.saison - a.saison)
default:
throw new Error()
}
}
export function SelectCard({userData}) {
const setLoading = useLoadingSwitcher()
const {data, error} = useFetch(`/selection/${userData.id}`, setLoading, 1)
const [modalSelection, setModal] = useState({id: -1, membre: userData.id})
const [selections, dispatch] = useReducer(selectionReducer, [])
useEffect(() => {
if (!data) return
for (const dataKey of data) {
dispatch({type: 'UPDATE_OR_ADD', payload: dataKey})
}
dispatch({type: 'SORT'})
}, [data]);
return <div className="card mb-4 mb-md-0">
<div className="card-header container-fluid">
<div className="row">
<div className="col">Sélection en équipe de France</div>
<div className="col" style={{textAlign: 'right'}}>
<button className="btn btn-primary btn-sm" data-bs-toggle="modal" data-bs-target="#SelectionModal"
onClick={() => setModal({id: -1, membre: userData.id, categorie: userData.categorie})}>Ajouter
</button>
</div>
</div>
</div>
<div className="card-body">
<ul className="list-group">
{selections.map((selection, index) => {
return <div key={index} className="list-group-item d-flex justify-content-between align-items-start">
<div className="me-auto">{selection?.saison}-{selection?.saison + 1} en {getCatName(selection?.categorie)}</div>
<button className="badge btn btn-primary rounded-pill" data-bs-toggle="modal"
data-bs-target="#SelectionModal" onClick={_ => setModal(selection)}>
<FontAwesomeIcon icon={faPen}/></button>
</div>
})}
{error && <AxiosError error={error}/>}
</ul>
</div>
<div className="modal fade" id="SelectionModal" tabIndex="-1" aria-labelledby="SelectionModalLabel"
aria-hidden="true">
<div className="modal-dialog">
<div className="modal-content">
<ModalContent selection={modalSelection} dispatch={dispatch}/>
</div>
</div>
</div>
</div>;
}
function sendSelection(event, dispatch) {
event.preventDefault();
const formData = new FormData(event.target);
formData.set('selection', event.target.selection?.value?.length > 0 ? event.target.selection?.value : null)
formData.set('categorie', event.target.categorie?.value)
toast.promise(
apiAxios.post(`/selection/${event.target.membre.value}`, {
id: event.target.id.value,
membre: event.target.membre.value,
saison: event.target.saison.value,
categorie: event.target.categorie.value,
}),
{
pending: "Enregistrement de la séléction en cours",
success: "Séléction enregistrée avec succès 🎉",
error: {
render({data}) {
return errFormater(data, "Échec de l'enregistrement de la séléction")
}
}
}
).then(data => {
dispatch({type: 'UPDATE_OR_ADD', payload: data.data})
dispatch({type: 'SORT'})
})
}
function removeSelection(id, dispatch) {
toast.promise(
apiAxios.delete(`/selection/${id}`),
{
pending: "Suppression de la séléction en cours",
success: "Séléction supprimée avec succès 🎉",
error: {
render({data}) {
return errFormater(data, "Échec de la suppression de la séléction")
}
}
}
).then(_ => {
dispatch({type: 'REMOVE', payload: id})
})
}
function ModalContent({selection, dispatch}) {
const [saison, setSaison] = useState(0)
const [cat, setCat] = useState("")
const [isNew, setNew] = useState(true)
const setSeason = (event) => {
setSaison(Number(event.target.value))
}
const handleCatChange = (event) => {
setCat(event.target.value);
}
useEffect(() => {
if (selection.id !== -1) {
setNew(false)
setSaison(selection.saison)
} else {
setNew(true)
setSaison(getSaison())
}
setCat(selection.categorie)
}, [selection]);
return <form onSubmit={e => sendSelection(e, dispatch)}>
<input name="id" value={selection.id} readOnly hidden/>
<input name="membre" value={selection.membre} readOnly hidden/>
<div className="modal-header">
<h1 className="modal-title fs-5" id="SelectionModalLabel">Edition de la séléction</h1>
<button type="button" className="btn-close" data-bs-dismiss="modal"
aria-label="Close"></button>
</div>
<div className="modal-body">
<div className="input-group mb-3 justify-content-md-center">
{isNew
? <input type="number" className="form-control" placeholder="Saison" name="saison"
aria-label="Saison" aria-describedby="basic-addon2" value={saison} onChange={setSeason}/>
: <><span className="input-group-text" id="basic-addon2">{saison}</span>
<input name="saison" value={saison} readOnly hidden/></>}
<span className="input-group-text" id="basic-addon2">-</span>
<span className="input-group-text" id="basic-addon2">{saison + 1}</span>
</div>
<div className="input-group mb-3">
<label className="input-group-text" htmlFor="inputGroupSelect01">Catégorie</label>
<select className="form-select" id="inputGroupSelect01" value={cat} onChange={handleCatChange} name="categorie" required>
<option>Choisir...</option>
{CatList.map((cat) => {
return (<option key={cat} value={cat}>{getCatName(cat)}</option>)
})}
</select>
</div>
</div>
<div className="modal-footer">
<button type="submit" className="btn btn-primary" data-bs-dismiss="modal">Enregistrer</button>
<button type="reset" className="btn btn-secondary" data-bs-dismiss="modal">Annuler</button>
{isNew || <button type="button" className="btn btn-danger" data-bs-dismiss="modal"
onClick={() => removeSelection(selection.id, dispatch)}>Supprimer</button>}
</div>
</form>
}
function RadioGroupeOnOff({value, onChange, name, text}) {
return <div className="btn-group input-group mb-3 justify-content-md-center" role="group"
aria-label="Basic radio toggle button group">
<span className="input-group-text">{text}</span>
<input type="radio" className="btn-check" id={"btnradio1" + name} autoComplete="off"
value="false" checked={value === false} onChange={onChange}/>
<label className="btn btn-outline-primary" htmlFor={"btnradio1" + name}>Non</label>
<input type="radio" className="btn-check" name={name} id={"btnradio2" + name} autoComplete="off"
value="true" checked={value === true} onChange={onChange}/>
<label className="btn btn-outline-primary" htmlFor={"btnradio2" + name}>Oui</label>
</div>;
}

View File

@@ -67,7 +67,7 @@ function InformationForm({data}) {
}
return <>
<form onSubmit={handleSubmit}>
<form onSubmit={handleSubmit} autoComplete="off">
<div className="card mb-4">
<input name="id" value={data.id} readOnly hidden/>
<div className="card-header">Affiliation n°{data.no_affiliation}</div>

View File

@@ -42,7 +42,7 @@ export function InformationForm({data}) {
addPhoto(event, formData, send);
}
return <form onSubmit={handleSubmit}>
return <form onSubmit={handleSubmit} autoComplete="off">
<div className="card mb-4">
<div className="card-header">Information</div>
<div className="card-body">

View File

@@ -10,6 +10,7 @@ import {apiAxios, errFormater} from "../../../utils/Tools.js";
import {toast} from "react-toastify";
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
import {faFilePdf} from "@fortawesome/free-solid-svg-icons";
import {SelectCard} from "./SelectCard.jsx";
const vite_url = import.meta.env.VITE_URL;
@@ -56,7 +57,7 @@ export function MemberPage() {
<LoadingProvider><LicenceCard userData={data}/></LoadingProvider>
</div>
<div className="col-md-6">
<LoadingProvider><SelectCard/></LoadingProvider>
<LoadingProvider><SelectCard userData={data}/></LoadingProvider>
</div>
</div>
{data.licence == null &&
@@ -93,14 +94,3 @@ function PhotoCard({data}) {
</div>
</div>;
}
function SelectCard() {
return <></>
/*return <div className="card mb-4 mb-md-0">
<div className="card-header">Sélection en équipe de France</div>
<div className="card-body">
<p className="mb-1">Soon</p>
</div>
</div>;*/
}

View File

@@ -0,0 +1,27 @@
import {useLoadingSwitcher} from "../../../hooks/useLoading.jsx";
import {useFetch} from "../../../hooks/useFetch.js";
import {getCatName} from "../../../utils/Tools.js";
import {AxiosError} from "../../../components/AxiosError.jsx";
export function SelectCard({userData}) {
const setLoading = useLoadingSwitcher()
const {data, error} = useFetch(`/selection/${userData.id}`, setLoading, 1)
return <div className="card mb-4 mb-md-0">
<div className="card-header container-fluid">
<div className="row">
<div className="col">Sélection en équipe de France</div>
</div>
</div>
<div className="card-body">
<ul className="list-group">
{data && data.sort((a, b) => b.saison - a.saison).map((selection, index) => {
return <div key={index} className="list-group-item d-flex justify-content-between align-items-start">
<div className="me-auto">{selection?.saison}-{selection?.saison + 1} en {getCatName(selection?.categorie)}</div>
</div>
})}
{error && <AxiosError error={error}/>}
</ul>
</div>
</div>;
}