dev #95
@ -41,7 +41,7 @@ public class LogModel {
|
||||
}
|
||||
|
||||
public enum ObjectType {
|
||||
Membre, Affiliation, Licence, Club, Competition, Register
|
||||
Membre, Affiliation, Licence, Club, Competition, Register, Selection
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -72,6 +72,10 @@ public class MembreModel implements LoggableModel, CombModel {
|
||||
@Schema(description = "Les licences du membre. (optionnel)")
|
||||
List<LicenceModel> licences;
|
||||
|
||||
@OneToMany(mappedBy = "membre", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
|
||||
@Schema(description = "Les séléctions du membre. (optionnel)")
|
||||
List<SelectionModel> selections;
|
||||
|
||||
@Override
|
||||
public String getObjectName() {
|
||||
return fname + " " + lname;
|
||||
|
||||
@ -0,0 +1,44 @@
|
||||
package fr.titionfire.ffsaf.data.model;
|
||||
|
||||
import fr.titionfire.ffsaf.utils.Categorie;
|
||||
import io.quarkus.runtime.annotations.RegisterForReflection;
|
||||
import jakarta.persistence.*;
|
||||
import lombok.*;
|
||||
import org.eclipse.microprofile.openapi.annotations.media.Schema;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@RegisterForReflection
|
||||
|
||||
@Entity
|
||||
@Table(name = "selection")
|
||||
public class SelectionModel implements LoggableModel {
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
@Schema(description = "L'identifiant de la séléction.")
|
||||
Long id;
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "membre", referencedColumnName = "id")
|
||||
@Schema(description = "Le membre de la séléction. (optionnel)")
|
||||
MembreModel membre;
|
||||
|
||||
@Schema(description = "La saison de la séléction.", examples = "2025")
|
||||
int saison;
|
||||
|
||||
@Schema(description = "Catégorie de la séléction.")
|
||||
Categorie categorie;
|
||||
|
||||
@Override
|
||||
public String getObjectName() {
|
||||
return "selection " + id.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public LogModel.ObjectType getObjectType() {
|
||||
return LogModel.ObjectType.Selection;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,9 @@
|
||||
package fr.titionfire.ffsaf.data.repository;
|
||||
|
||||
import fr.titionfire.ffsaf.data.model.SelectionModel;
|
||||
import io.quarkus.hibernate.reactive.panache.PanacheRepositoryBase;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
|
||||
@ApplicationScoped
|
||||
public class SelectionRepository implements PanacheRepositoryBase<SelectionModel, Long> {
|
||||
}
|
||||
@ -7,10 +7,7 @@ import fr.titionfire.ffsaf.data.repository.*;
|
||||
import fr.titionfire.ffsaf.net2.ServerCustom;
|
||||
import fr.titionfire.ffsaf.net2.data.SimpleCombModel;
|
||||
import fr.titionfire.ffsaf.net2.request.SReqComb;
|
||||
import fr.titionfire.ffsaf.rest.data.MeData;
|
||||
import fr.titionfire.ffsaf.rest.data.SimpleLicence;
|
||||
import fr.titionfire.ffsaf.rest.data.SimpleMembre;
|
||||
import fr.titionfire.ffsaf.rest.data.SimpleMembreInOutData;
|
||||
import fr.titionfire.ffsaf.rest.data.*;
|
||||
import fr.titionfire.ffsaf.rest.exception.DBadRequestException;
|
||||
import fr.titionfire.ffsaf.rest.exception.DForbiddenException;
|
||||
import fr.titionfire.ffsaf.rest.exception.DInternalError;
|
||||
@ -590,9 +587,12 @@ public class MembreService {
|
||||
MeData meData = new MeData();
|
||||
return repository.find("userId = ?1", subject).firstResult()
|
||||
.invoke(meData::setMembre)
|
||||
.chain(membreModel -> Mutiny.fetch(membreModel.getLicences()))
|
||||
.map(licences -> licences.stream().map(SimpleLicence::fromModel).toList())
|
||||
.invoke(meData::setLicences)
|
||||
.call(membreModel -> Mutiny.fetch(membreModel.getLicences())
|
||||
.map(licences -> licences.stream().map(SimpleLicence::fromModel).toList())
|
||||
.invoke(meData::setLicences))
|
||||
.call(membreModel -> Mutiny.fetch(membreModel.getSelections())
|
||||
.map(licences -> licences.stream().map(SimpleSelection::fromModel).toList())
|
||||
.invoke(meData::setSelections))
|
||||
.map(__ -> meData);
|
||||
}
|
||||
|
||||
|
||||
@ -0,0 +1,64 @@
|
||||
package fr.titionfire.ffsaf.domain.service;
|
||||
|
||||
import fr.titionfire.ffsaf.data.model.LogModel;
|
||||
import fr.titionfire.ffsaf.data.model.MembreModel;
|
||||
import fr.titionfire.ffsaf.data.model.SelectionModel;
|
||||
import fr.titionfire.ffsaf.data.repository.CombRepository;
|
||||
import fr.titionfire.ffsaf.data.repository.SelectionRepository;
|
||||
import fr.titionfire.ffsaf.rest.data.SimpleSelection;
|
||||
import io.quarkus.hibernate.reactive.panache.Panache;
|
||||
import io.quarkus.hibernate.reactive.panache.common.WithSession;
|
||||
import io.smallrye.mutiny.Uni;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.inject.Inject;
|
||||
import org.hibernate.reactive.mutiny.Mutiny;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@WithSession
|
||||
@ApplicationScoped
|
||||
public class SelectionService {
|
||||
|
||||
@Inject
|
||||
CombRepository combRepository;
|
||||
|
||||
@Inject
|
||||
SelectionRepository repository;
|
||||
|
||||
@Inject
|
||||
LoggerService ls;
|
||||
|
||||
public Uni<List<SelectionModel>> getSelection(long id, Consumer<MembreModel> checkPerm) {
|
||||
return combRepository.findById(id).invoke(checkPerm)
|
||||
.chain(combRepository -> Mutiny.fetch(combRepository.getSelections()));
|
||||
}
|
||||
|
||||
public Uni<SelectionModel> setSelection(long id, SimpleSelection data) {
|
||||
if (data.getId() == -1) {
|
||||
return combRepository.findById(id).chain(membreModel -> {
|
||||
SelectionModel model = new SelectionModel();
|
||||
|
||||
model.setMembre(membreModel);
|
||||
model.setSaison(data.getSaison());
|
||||
model.setCategorie(data.getCategorie());
|
||||
return Panache.withTransaction(() -> repository.persist(model))
|
||||
.call(licenceModel -> ls.logA(LogModel.ActionType.ADD, membreModel.getObjectName(),
|
||||
licenceModel));
|
||||
});
|
||||
} else {
|
||||
return repository.findById(data.getId()).chain(model -> {
|
||||
ls.logChange("Catégorie", model.getCategorie(), data.getCategorie(), model);
|
||||
model.setCategorie(data.getCategorie());
|
||||
return Panache.withTransaction(() -> repository.persist(model))
|
||||
.call(__ -> ls.append());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public Uni<Void> deleteSelection(long id) {
|
||||
return repository.findById(id)
|
||||
.call(model -> ls.logADelete(model))
|
||||
.chain(model -> Panache.withTransaction(() -> repository.delete(model)));
|
||||
}
|
||||
}
|
||||
@ -101,7 +101,7 @@ public class LicenceEndpoints {
|
||||
@RolesAllowed("federation_admin")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Consumes(MediaType.MULTIPART_FORM_DATA)
|
||||
@Operation(summary = "Créer une licence", description = "Créer unr licence en fonction de son identifiant et des " +
|
||||
@Operation(summary = "Créer une licence", description = "Créer une licence en fonction de son identifiant et des " +
|
||||
"informations fournies dans le formulaire (pour les administrateurs)")
|
||||
@APIResponses(value = {
|
||||
@APIResponse(responseCode = "200", description = "La licence a été mise à jour avec succès"),
|
||||
|
||||
@ -0,0 +1,85 @@
|
||||
package fr.titionfire.ffsaf.rest;
|
||||
|
||||
import fr.titionfire.ffsaf.data.model.MembreModel;
|
||||
import fr.titionfire.ffsaf.domain.service.SelectionService;
|
||||
import fr.titionfire.ffsaf.rest.data.SimpleSelection;
|
||||
import fr.titionfire.ffsaf.rest.exception.DForbiddenException;
|
||||
import fr.titionfire.ffsaf.utils.SecurityCtx;
|
||||
import io.smallrye.mutiny.Uni;
|
||||
import io.smallrye.mutiny.unchecked.Unchecked;
|
||||
import jakarta.annotation.security.RolesAllowed;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.ws.rs.*;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import org.eclipse.microprofile.openapi.annotations.Operation;
|
||||
import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;
|
||||
import org.eclipse.microprofile.openapi.annotations.responses.APIResponses;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@Path("api/selection")
|
||||
public class SelectionEndpoints {
|
||||
|
||||
@Inject
|
||||
SelectionService selectionService;
|
||||
|
||||
@Inject
|
||||
SecurityCtx securityCtx;
|
||||
|
||||
Consumer<MembreModel> checkPerm = Unchecked.consumer(membreModel -> {
|
||||
if (!securityCtx.roleHas("federation_admin") && !securityCtx.isInClubGroup(membreModel.getClub().getId()))
|
||||
throw new DForbiddenException();
|
||||
});
|
||||
|
||||
@GET
|
||||
@Path("{id}")
|
||||
@RolesAllowed({"federation_admin", "club_president", "club_secretaire", "club_tresorier", "club_respo_intra",
|
||||
"ffsaf_selectionneur"})
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Operation(summary = "Renvoie les séléctions d'un membre", description = "Renvoie les séléctions d'un membre en fonction " +
|
||||
"de son identifiant")
|
||||
@APIResponses(value = {
|
||||
@APIResponse(responseCode = "200", description = "La liste des séléctions du membre"),
|
||||
@APIResponse(responseCode = "403", description = "Accès refusé"),
|
||||
@APIResponse(responseCode = "404", description = "Le membre n'existe pas"),
|
||||
@APIResponse(responseCode = "500", description = "Erreur interne du serveur")
|
||||
})
|
||||
public Uni<List<SimpleSelection>> getSelection(@PathParam("id") long id) {
|
||||
return selectionService.getSelection(id, checkPerm)
|
||||
.map(selectionModels -> selectionModels.stream().map(SimpleSelection::fromModel).toList());
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("{id}")
|
||||
@RolesAllowed({"federation_admin", "ffsaf_selectionneur"})
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Operation(summary = "Créer une séléction", description = "Créer une séléction en fonction de son identifiant et des " +
|
||||
"informations fournies dans le formulaire (pour les administrateurs)")
|
||||
@APIResponses(value = {
|
||||
@APIResponse(responseCode = "200", description = "La séléction a été mise à jour avec succès"),
|
||||
@APIResponse(responseCode = "403", description = "Accès refusé"),
|
||||
@APIResponse(responseCode = "404", description = "La séléction n'existe pas"),
|
||||
@APIResponse(responseCode = "500", description = "Erreur interne du serveur")
|
||||
})
|
||||
public Uni<SimpleSelection> setSelection(@PathParam("id") long id, SimpleSelection data) {
|
||||
return selectionService.setSelection(id, data).map(SimpleSelection::fromModel);
|
||||
}
|
||||
|
||||
@DELETE
|
||||
@Path("{id}")
|
||||
@RolesAllowed({"federation_admin", "ffsaf_selectionneur"})
|
||||
@Produces(MediaType.TEXT_PLAIN)
|
||||
@Operation(summary = "Supprime une séléction", description = "Supprime une séléction en fonction de son identifiant " +
|
||||
"(pour les administrateurs)")
|
||||
@APIResponses(value = {
|
||||
@APIResponse(responseCode = "204", description = "La séléction a été supprimée avec succès"),
|
||||
@APIResponse(responseCode = "403", description = "Accès refusé"),
|
||||
@APIResponse(responseCode = "404", description = "La séléction n'existe pas"),
|
||||
@APIResponse(responseCode = "500", description = "Erreur interne du serveur")
|
||||
})
|
||||
public Uni<?> deleteSelection(@PathParam("id") long id) {
|
||||
return selectionService.deleteSelection(id);
|
||||
}
|
||||
}
|
||||
@ -44,6 +44,8 @@ public class MeData {
|
||||
private ResultPrivacy resultPrivacy;
|
||||
@Schema(description = "La liste des licences du membre.")
|
||||
private List<SimpleLicence> licences;
|
||||
@Schema(description = "La liste des séléctions du membre.")
|
||||
private List<SimpleSelection> selections;
|
||||
|
||||
public void setMembre(MembreModel membreModel) {
|
||||
this.id = membreModel.getId();
|
||||
|
||||
@ -0,0 +1,36 @@
|
||||
package fr.titionfire.ffsaf.rest.data;
|
||||
|
||||
import fr.titionfire.ffsaf.data.model.SelectionModel;
|
||||
import fr.titionfire.ffsaf.utils.Categorie;
|
||||
import io.quarkus.runtime.annotations.RegisterForReflection;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import org.eclipse.microprofile.openapi.annotations.media.Schema;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@RegisterForReflection
|
||||
public class SimpleSelection {
|
||||
@Schema(description = "ID de la séléction", examples = "1")
|
||||
Long id;
|
||||
@Schema(description = "ID du membre", examples = "1")
|
||||
Long membre;
|
||||
@Schema(description = "Saison de la séléction", examples = "2024")
|
||||
int saison;
|
||||
@Schema(description = "Catégorie de la séléction", examples = "JUNIOR")
|
||||
Categorie categorie;
|
||||
|
||||
public static SimpleSelection fromModel(SelectionModel model) {
|
||||
if (model == null)
|
||||
return null;
|
||||
|
||||
return new SimpleSelection.SimpleSelectionBuilder()
|
||||
.id(model.getId())
|
||||
.membre(model.getMembre().getId())
|
||||
.saison(model.getSaison())
|
||||
.categorie(model.getCategorie())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@ -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>;
|
||||
}
|
||||
|
||||
@ -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/>
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -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>;*/
|
||||
}
|
||||
|
||||
208
src/main/webapp/src/pages/admin/member/SelectCard.jsx
Normal file
208
src/main/webapp/src/pages/admin/member/SelectCard.jsx
Normal 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>;
|
||||
}
|
||||
@ -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>
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -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>;*/
|
||||
}
|
||||
|
||||
27
src/main/webapp/src/pages/club/member/SelectCard.jsx
Normal file
27
src/main/webapp/src/pages/club/member/SelectCard.jsx
Normal 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>;
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user