feat: club
This commit is contained in:
parent
edcda185db
commit
d268461bfd
@ -308,7 +308,13 @@ public class AffiliationService {
|
||||
}
|
||||
}))
|
||||
.chain(club ->
|
||||
Panache.withTransaction(() -> repository.persist(new AffiliationModel(null, club, saison))))
|
||||
Panache.withTransaction(() -> repository.persist(new AffiliationModel(null, club, saison))
|
||||
.chain(c -> (club.getNo_affiliation() != null) ? Uni.createFrom().item(c) :
|
||||
sequenceRepository.getNextValueInTransaction(SequenceType.Affiliation)
|
||||
.invoke(club::setNo_affiliation)
|
||||
.chain(() -> clubRepository.persist(club))
|
||||
.map(o -> c)
|
||||
)))
|
||||
.map(SimpleAffiliation::fromModel);
|
||||
}
|
||||
|
||||
|
||||
@ -122,13 +122,15 @@ public class ClubService {
|
||||
|
||||
m.setName(input.getName());
|
||||
m.setCountry(input.getCountry());
|
||||
m.setInternational(input.isInternational());
|
||||
|
||||
if (!input.isInternational()) {
|
||||
m.setTraining_location(input.getTraining_location());
|
||||
m.setTraining_day_time(input.getTraining_day_time());
|
||||
m.setContact_intern(input.getContact_intern());
|
||||
m.setRNA(input.getRna());
|
||||
m.setSIRET(input.getSiret());
|
||||
if (input.getSiret() != null && !input.getSiret().isBlank())
|
||||
m.setSIRET(Long.parseLong(input.getSiret()));
|
||||
m.setAddress(input.getAddress());
|
||||
|
||||
try {
|
||||
@ -145,7 +147,37 @@ public class ClubService {
|
||||
}
|
||||
|
||||
public Uni<Long> add(FullClubForm input) {
|
||||
return Uni.createFrom().nullItem();
|
||||
TypeReference<HashMap<Contact, String>> typeRef = new TypeReference<>() {
|
||||
};
|
||||
|
||||
return Uni.createFrom().nullItem()
|
||||
.chain(() -> {
|
||||
ClubModel clubModel = new ClubModel();
|
||||
|
||||
clubModel.setName(input.getName());
|
||||
clubModel.setCountry(input.getCountry());
|
||||
clubModel.setInternational(input.isInternational());
|
||||
clubModel.setNo_affiliation(null);
|
||||
if (!input.isInternational()) {
|
||||
clubModel.setTraining_location(input.getTraining_location());
|
||||
clubModel.setTraining_day_time(input.getTraining_day_time());
|
||||
clubModel.setContact_intern(input.getContact_intern());
|
||||
clubModel.setRNA(input.getRna());
|
||||
if (input.getSiret() != null && !input.getSiret().isBlank())
|
||||
clubModel.setSIRET(Long.parseLong(input.getSiret()));
|
||||
clubModel.setAddress(input.getAddress());
|
||||
|
||||
try {
|
||||
clubModel.setContact(MAPPER.readValue(input.getContact(), typeRef));
|
||||
} catch (JsonProcessingException ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
return Panache.withTransaction(() -> repository.persist(clubModel));
|
||||
})
|
||||
.call(clubModel -> keycloakService.getGroupFromClub(clubModel)) // create group in keycloak
|
||||
.invoke(clubModel -> SReqClub.sendAddIfNeed(serverCustom.clients, SimpleClubModel.fromModel(clubModel)))
|
||||
.map(ClubModel::getId);
|
||||
}
|
||||
|
||||
public Uni<?> delete(long id) {
|
||||
@ -155,8 +187,10 @@ public class ClubService {
|
||||
combModel.setClub(null);
|
||||
combModel.setRole(RoleAsso.MEMBRE);
|
||||
}).toList())
|
||||
.call(list -> Uni.join().all(list.stream().filter(m -> m.getUserId() != null)
|
||||
.map(m -> keycloakService.clearUser(m.getUserId())).toList()).andCollectFailures())
|
||||
.call(list -> (list.isEmpty()) ? Uni.createFrom().voidItem() :
|
||||
Uni.join().all(list.stream().filter(m -> m.getUserId() != null)
|
||||
.map(m -> keycloakService.clearUser(m.getUserId())).toList())
|
||||
.andCollectFailures())
|
||||
.chain(list -> Panache.withTransaction(() -> combRepository.persist(list)))
|
||||
.map(o -> club)
|
||||
)
|
||||
|
||||
@ -24,6 +24,7 @@ import org.eclipse.microprofile.config.inject.ConfigProperty;
|
||||
import org.eclipse.microprofile.jwt.JsonWebToken;
|
||||
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@ -57,6 +58,14 @@ public class ClubEndpoints {
|
||||
return clubService.getAll().map(clubModels -> clubModels.stream().map(SimpleClubModel::fromModel).toList());
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/contact_type")
|
||||
@Authenticated
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public Uni<HashMap<String, String>> getConcatType() {
|
||||
return Uni.createFrom().item(Contact.toSite());
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/find")
|
||||
@RolesAllowed({"federation_admin"})
|
||||
@ -111,11 +120,12 @@ public class ClubEndpoints {
|
||||
});
|
||||
}
|
||||
|
||||
@POST
|
||||
@PUT
|
||||
@RolesAllowed({"federation_admin"})
|
||||
@Produces(MediaType.TEXT_PLAIN)
|
||||
@Consumes(MediaType.MULTIPART_FORM_DATA)
|
||||
public Uni<Long> addAdminClub(FullClubForm input) {
|
||||
System.out.println(input);
|
||||
return clubService.add(input)
|
||||
.invoke(Unchecked.consumer(id -> {
|
||||
if (id == null) throw new InternalError("Fail to create club data");
|
||||
|
||||
@ -37,7 +37,7 @@ public class FullClubForm {
|
||||
private String rna = null;
|
||||
|
||||
@FormParam("siret")
|
||||
private Long siret = null;
|
||||
private String siret = null;
|
||||
|
||||
@FormParam("international")
|
||||
private boolean international = false;
|
||||
|
||||
@ -70,7 +70,7 @@ export function ClubList() {
|
||||
}
|
||||
|
||||
return <>
|
||||
<h2>Club</h2>
|
||||
<h2>Club </h2>
|
||||
<div>
|
||||
<div className="row">
|
||||
<div className="col-lg-9">
|
||||
@ -85,8 +85,11 @@ export function ClubList() {
|
||||
</div>
|
||||
<div className="col-lg-3">
|
||||
<div className="mb-4">
|
||||
<button className="btn btn-primary" onClick={() => navigate("../affiliation/request")}>Demande en cours</button>
|
||||
<button className="btn btn-primary" onClick={() => navigate("new")}>Ajouter une affiliation</button>
|
||||
<div className="mb-2">
|
||||
<button className="btn btn-primary" onClick={() => navigate("../affiliation/request")}>Demande d'affiliation en cours
|
||||
</button>
|
||||
</div>
|
||||
<button className="btn btn-primary" onClick={() => navigate("new")}>Ajouter un club</button>
|
||||
</div>
|
||||
<div className="card mb-4">
|
||||
<div className="card-header">Filtre</div>
|
||||
|
||||
@ -22,7 +22,7 @@ export function ClubPage() {
|
||||
const navigate = useNavigate();
|
||||
|
||||
const setLoading = useLoadingSwitcher()
|
||||
const {data, error} = useFetch(`/club/${id}`, setLoading, 1)
|
||||
const {data, refresh, error} = useFetch(`/club/${id}`, setLoading, 1)
|
||||
|
||||
const handleRm = () => {
|
||||
toast.promise(
|
||||
@ -38,7 +38,7 @@ export function ClubPage() {
|
||||
}
|
||||
|
||||
return <>
|
||||
<h2>Page membre</h2>
|
||||
<h2>Page club</h2>
|
||||
<button type="button" className="btn btn-link" onClick={() => navigate("/admin/club")}>
|
||||
« retour
|
||||
</button>
|
||||
@ -92,7 +92,7 @@ function InformationForm({data}) {
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div className="card mb-4">
|
||||
<input name="id" value={data.id} readOnly hidden/>
|
||||
<div className="card-header">Licence n°{data.no_affiliation}</div>
|
||||
<div className="card-header">Affiliation n°{data.no_affiliation}</div>
|
||||
<div className="card-body text-center">
|
||||
|
||||
<TextField name="clubId" text="ClubID" value={data.clubId} disabled={true}/>
|
||||
@ -120,11 +120,12 @@ function InformationForm({data}) {
|
||||
</div>
|
||||
</div>
|
||||
{!switchOn && <>
|
||||
<TextField name="siret" text="SIRET" value={data.siret} type="number"/>
|
||||
<TextField name="siret" text="SIRET" value={data.siret} required={false} type="number"/>
|
||||
<TextField name="rna" text="RNA" value={data.rna} required={false}/>
|
||||
<TextField name="contact_intern" text="Contact interne" value={data.contact_intern} required={false}
|
||||
placeholder="example@test.com"/>
|
||||
<TextField name="address" text="Adresse administrative" value={data.address} placeholder="Adresse administrative"/>
|
||||
<TextField name="address" text="Adresse administrative" value={data.address} required={false}
|
||||
placeholder="Adresse administrative"/>
|
||||
|
||||
<div className="mb-3">
|
||||
<div className="input-group">
|
||||
|
||||
@ -1,16 +1,111 @@
|
||||
import {useNavigate} from "react-router-dom";
|
||||
import {LoadingProvider} from "../../../hooks/useLoading.jsx";
|
||||
import {useFetch} from "../../../hooks/useFetch.js";
|
||||
import {toast} from "react-toastify";
|
||||
import {apiAxios} from "../../../utils/Tools.js";
|
||||
import {CountryList, TextField} from "../../../components/MemberCustomFiels.jsx";
|
||||
|
||||
import {useRef, useState} from "react";
|
||||
import {LocationEditor, LocationEditorModal} from "../../../components/Club/LocationEditor.jsx";
|
||||
import {ContactEditor} from "../../../components/Club/ContactEditor.jsx";
|
||||
import {HoraireEditor} from "../../../components/Club/HoraireEditor.jsx";
|
||||
|
||||
export function NewClubPage() {
|
||||
const navigate = useNavigate();
|
||||
const navigate = useNavigate()
|
||||
|
||||
return <>
|
||||
<h2>Page affiliation</h2>
|
||||
<button type="button" className="btn btn-link" onClick={() => navigate("/admin/affiliation")}>
|
||||
<h2>Page nouveau club</h2>
|
||||
<button type="button" className="btn btn-link" onClick={() => navigate("/admin/club")}>
|
||||
« retour
|
||||
</button>
|
||||
<div>
|
||||
<div className="row">
|
||||
<div className="col-lg-9">
|
||||
<LoadingProvider>
|
||||
<InformationForm/>
|
||||
</LoadingProvider>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
}
|
||||
|
||||
function InformationForm() {
|
||||
const [switchOn, setSwitchOn] = useState(false);
|
||||
const [modal, setModal] = useState({id: -1})
|
||||
const locationModalCallback = useRef(null)
|
||||
const navigate = useNavigate()
|
||||
|
||||
const {data} = useFetch(`/club/contact_type`)
|
||||
|
||||
const handleSubmit = (event) => {
|
||||
event.preventDefault();
|
||||
|
||||
const formData = new FormData(event.target);
|
||||
|
||||
toast.promise(
|
||||
apiAxios.put(`/club`, formData),
|
||||
{
|
||||
pending: "Création du club en cours",
|
||||
success: "Club créé avec succès 🎉",
|
||||
error: "Échec de la création du club 😕"
|
||||
}
|
||||
).then(data => {
|
||||
navigate(`/admin/club/${data.data}`);
|
||||
})
|
||||
}
|
||||
|
||||
return <>
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div className="card mb-4">
|
||||
<div className="card-header">Nouveau club</div>
|
||||
<div className="card-body text-center">
|
||||
|
||||
<TextField name="name" text="Nom*"/>
|
||||
<CountryList name="country" text="Pays*" value={"fr"}/>
|
||||
|
||||
<div className="mb-3">
|
||||
<div className="input-group">
|
||||
<label className="input-group-text" htmlFor="logo">Blason</label>
|
||||
<input type="file" className="form-control" id="logo" name="logo"
|
||||
accept=".jpg,.jpeg,.gif,.png,.svg"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="input-group mb-3">
|
||||
<div className="input-group-text">
|
||||
<input type="checkbox" className="form-check-input mt-0" name="international" id="international"
|
||||
checked={switchOn} onChange={() => setSwitchOn(!switchOn)}/>
|
||||
<label className="input-group-text" htmlFor="international">Club externe</label>
|
||||
</div>
|
||||
</div>
|
||||
{!switchOn && <>
|
||||
<TextField name="siret" text="SIRET" required={false} type="number"/>
|
||||
<TextField name="rna" text="RNA" required={false}/>
|
||||
<TextField name="contact_intern" text="Contact interne" required={false} placeholder="example@test.com"/>
|
||||
<TextField name="address" text="Adresse administrative" required={false} placeholder="Adresse administrative"/>
|
||||
|
||||
<div className="mb-3">
|
||||
<div className="input-group">
|
||||
<label className="input-group-text" htmlFor="status">Status</label>
|
||||
<input type="file" className="form-control" id="status" name="status" accept=".pdf,.txt"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ContactEditor data={{contact: {}, contactMap: data}}/>
|
||||
<LocationEditor data={{training_location: null}} setModal={setModal} sendData={locationModalCallback}/>
|
||||
<HoraireEditor data={{training_day_time: null}}/>
|
||||
</>
|
||||
}
|
||||
</div>
|
||||
<div className="row mb-3">
|
||||
<div className="d-grid gap-2 d-md-flex justify-content-md-end">
|
||||
<button type="submit" className="btn btn-primary">Enregistrer</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<LocationEditorModal modal={modal} sendData={locationModalCallback}/>
|
||||
</>
|
||||
}
|
||||
|
||||
158
src/main/webapp/src/pages/club/club/ClubPage.jsx
Normal file
158
src/main/webapp/src/pages/club/club/ClubPage.jsx
Normal file
@ -0,0 +1,158 @@
|
||||
import {useNavigate, useParams} from "react-router-dom";
|
||||
import {LoadingProvider, useLoadingSwitcher} from "../../../hooks/useLoading.jsx";
|
||||
import {useFetch} from "../../../hooks/useFetch.js";
|
||||
import {toast} from "react-toastify";
|
||||
import {apiAxios} from "../../../utils/Tools.js";
|
||||
import {ConfirmDialog} from "../../../components/ConfirmDialog.jsx";
|
||||
import {AxiosError} from "../../../components/AxiosError.jsx";
|
||||
import {AffiliationCard} from "./AffiliationCard.jsx";
|
||||
import {CountryList, TextField} from "../../../components/MemberCustomFiels.jsx";
|
||||
|
||||
import {useRef, useState} from "react";
|
||||
import {LocationEditor, LocationEditorModal} from "../../../components/Club/LocationEditor.jsx";
|
||||
import {ContactEditor} from "../../../components/Club/ContactEditor.jsx";
|
||||
import {HoraireEditor} from "../../../components/Club/HoraireEditor.jsx";
|
||||
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
|
||||
import {faFilePdf} from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
const vite_url = import.meta.env.VITE_URL;
|
||||
|
||||
export function ClubPage() {
|
||||
const {id} = useParams()
|
||||
const navigate = useNavigate();
|
||||
|
||||
const setLoading = useLoadingSwitcher()
|
||||
const {data, error} = useFetch(`/club/${id}`, setLoading, 1)
|
||||
|
||||
const handleRm = () => {
|
||||
toast.promise(
|
||||
apiAxios.delete(`/club/${id}`),
|
||||
{
|
||||
pending: "Suppression du club en cours...",
|
||||
success: "Club supprimé avec succès 🎉",
|
||||
error: "Échec de la suppression du club 😕"
|
||||
}
|
||||
).then(_ => {
|
||||
navigate("/admin/club")
|
||||
})
|
||||
}
|
||||
|
||||
return <>
|
||||
<h2>Page club</h2>
|
||||
<button type="button" className="btn btn-link" onClick={() => navigate("/admin/club")}>
|
||||
« retour
|
||||
</button>
|
||||
{data
|
||||
? <div>
|
||||
<div className="row">
|
||||
<div className="col-lg-9">
|
||||
<LoadingProvider>
|
||||
<InformationForm data={data}/>
|
||||
</LoadingProvider>
|
||||
</div>
|
||||
<div className="col-lg-3">
|
||||
<LoadingProvider><AffiliationCard clubData={data}/></LoadingProvider>
|
||||
<div className="col" style={{textAlign: 'right', marginTop: '1em'}}>
|
||||
<button className="btn btn-danger btn-sm" data-bs-toggle="modal"
|
||||
data-bs-target="#confirm-delete">Supprimer le club
|
||||
</button>
|
||||
</div>
|
||||
<ConfirmDialog title="Supprimer le club"
|
||||
message="Êtes-vous sûr de vouloir supprimer ce club ?"
|
||||
onConfirm={handleRm}/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
: error && <AxiosError error={error}/>
|
||||
}
|
||||
</>
|
||||
}
|
||||
|
||||
function InformationForm({data}) {
|
||||
const [switchOn, setSwitchOn] = useState(data.international);
|
||||
const [modal, setModal] = useState({id: -1})
|
||||
const locationModalCallback = useRef(null)
|
||||
|
||||
const handleSubmit = (event) => {
|
||||
event.preventDefault();
|
||||
|
||||
const formData = new FormData(event.target);
|
||||
|
||||
toast.promise(
|
||||
apiAxios.put(`/club/${data.id}`, formData),
|
||||
{
|
||||
pending: "Enregistrement du club en cours",
|
||||
success: "Club enregistrée avec succès 🎉",
|
||||
error: "Échec de l'enregistrement du club 😕"
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
return <>
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div className="card mb-4">
|
||||
<input name="id" value={data.id} readOnly hidden/>
|
||||
<div className="card-header">Affiliation n°{data.no_affiliation}</div>
|
||||
<div className="card-body text-center">
|
||||
|
||||
<TextField name="clubId" text="ClubID" value={data.clubId} disabled={true}/>
|
||||
<TextField name="name" text="Nom" value={data.name}/>
|
||||
<CountryList name="country" text="Pays" value={data.country}/>
|
||||
|
||||
<img
|
||||
src={`${vite_url}/api/club/${data.clubId}/logo`}
|
||||
alt="avatar"
|
||||
className="img-fluid" style={{object_fit: 'contain', maxHeight: '15em'}}/>
|
||||
<div className="mb-3">
|
||||
<div className="input-group">
|
||||
<label className="input-group-text" htmlFor="logo">Blason</label>
|
||||
<input type="file" className="form-control" id="logo" name="logo"
|
||||
accept=".jpg,.jpeg,.gif,.png,.svg"/>
|
||||
</div>
|
||||
<div className="form-text" id="logo">Laissez vide pour ne rien changer.</div>
|
||||
</div>
|
||||
|
||||
<div className="input-group mb-3">
|
||||
<div className="input-group-text">
|
||||
<input type="checkbox" className="form-check-input mt-0" name="international" id="international"
|
||||
checked={switchOn} onChange={() => setSwitchOn(!switchOn)}/>
|
||||
<label className="input-group-text" htmlFor="international">Club externe</label>
|
||||
</div>
|
||||
</div>
|
||||
{!switchOn && <>
|
||||
<TextField name="siret" text="SIRET" value={data.siret} type="number"/>
|
||||
<TextField name="rna" text="RNA" value={data.rna} required={false}/>
|
||||
<TextField name="contact_intern" text="Contact interne" value={data.contact_intern} required={false}
|
||||
placeholder="example@test.com"/>
|
||||
<TextField name="address" text="Adresse administrative" value={data.address} placeholder="Adresse administrative"/>
|
||||
|
||||
<div className="mb-3">
|
||||
<div className="input-group">
|
||||
<label className="input-group-text" htmlFor="status">Status</label>
|
||||
<a href={`${vite_url}/api/club/${data.id}/status`} target='_blank'>
|
||||
<button className="btn btn-outline-secondary" type="button" id="button-addon1"
|
||||
onClick={e => null}><FontAwesomeIcon icon={faFilePdf}></FontAwesomeIcon>
|
||||
</button>
|
||||
</a>
|
||||
<input type="file" className="form-control" id="status" name="status" accept=".pdf,.txt"/>
|
||||
</div>
|
||||
<div className="form-text" id="status">Laissez vide pour ne rien changer.</div>
|
||||
</div>
|
||||
|
||||
<ContactEditor data={data}/>
|
||||
<LocationEditor data={data} setModal={setModal} sendData={locationModalCallback}/>
|
||||
<HoraireEditor data={data}/>
|
||||
</>
|
||||
}
|
||||
</div>
|
||||
<div className="row mb-3">
|
||||
<div className="d-grid gap-2 d-md-flex justify-content-md-end">
|
||||
<button type="submit" className="btn btn-primary">Enregistrer</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<LocationEditorModal modal={modal} sendData={locationModalCallback}/>
|
||||
</>
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user