diff --git a/src/main/java/fr/titionfire/ffsaf/domain/service/LicenceService.java b/src/main/java/fr/titionfire/ffsaf/domain/service/LicenceService.java index 15c9291..6843358 100644 --- a/src/main/java/fr/titionfire/ffsaf/domain/service/LicenceService.java +++ b/src/main/java/fr/titionfire/ffsaf/domain/service/LicenceService.java @@ -13,6 +13,7 @@ import io.smallrye.mutiny.unchecked.Unchecked; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; import jakarta.ws.rs.BadRequestException; +import org.eclipse.microprofile.jwt.JsonWebToken; import org.hibernate.reactive.mutiny.Mutiny; import java.util.List; @@ -32,6 +33,15 @@ public class LicenceService { return combRepository.findById(id).invoke(checkPerm).chain(combRepository -> Mutiny.fetch(combRepository.getLicences())); } + public Uni> getCurrentSaisonLicence(JsonWebToken idToken) { + if (idToken == null) + return repository.find("saison = ?1", Utils.getSaison()).list(); + + return combRepository.find("userId = ?1", idToken.getSubject()).firstResult().map(MembreModel::getClub) + .chain(clubModel -> combRepository.find("club = ?1", clubModel).list()) + .chain(membres -> repository.find("saison = ?1 AND membre IN ?2", Utils.getSaison(), membres).list()); + } + public Uni setLicence(long id, LicenceForm form) { if (form.getId() == -1) { return combRepository.findById(id).chain(combRepository -> { @@ -58,7 +68,7 @@ public class LicenceService { public Uni askLicence(long id, LicenceForm form, Consumer checkPerm) { return combRepository.findById(id).invoke(checkPerm).chain(membreModel -> { if (form.getId() == -1) { - return repository.find("saison = ?1", Utils.getSaison()).count().invoke(Unchecked.consumer(count -> { + return repository.find("saison = ?1 AND membre = ?2", Utils.getSaison(), membreModel).count().invoke(Unchecked.consumer(count -> { if (count > 0) throw new BadRequestException(); })).chain(__ -> combRepository.findById(id).chain(combRepository -> { diff --git a/src/main/java/fr/titionfire/ffsaf/domain/service/MembreService.java b/src/main/java/fr/titionfire/ffsaf/domain/service/MembreService.java index 0ac75b3..d059892 100644 --- a/src/main/java/fr/titionfire/ffsaf/domain/service/MembreService.java +++ b/src/main/java/fr/titionfire/ffsaf/domain/service/MembreService.java @@ -6,24 +6,28 @@ import fr.titionfire.ffsaf.data.repository.CombRepository; 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.SimpleMembre; import fr.titionfire.ffsaf.rest.from.ClubMemberForm; import fr.titionfire.ffsaf.rest.from.FullMemberForm; import fr.titionfire.ffsaf.utils.GroupeUtils; +import fr.titionfire.ffsaf.utils.PageResult; import fr.titionfire.ffsaf.utils.Pair; import fr.titionfire.ffsaf.utils.RoleAsso; import io.quarkus.hibernate.reactive.panache.Panache; +import io.quarkus.hibernate.reactive.panache.PanacheQuery; import io.quarkus.hibernate.reactive.panache.common.WithSession; +import io.quarkus.panache.common.Page; import io.quarkus.panache.common.Sort; +import io.quarkus.security.identity.SecurityIdentity; import io.quarkus.vertx.VertxContextSupport; import io.smallrye.mutiny.Uni; import io.smallrye.mutiny.unchecked.Unchecked; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; +import jakarta.ws.rs.BadRequestException; import jakarta.ws.rs.ForbiddenException; import org.eclipse.microprofile.jwt.JsonWebToken; -import java.util.List; - @WithSession @ApplicationScoped @@ -48,13 +52,48 @@ public class MembreService { return VertxContextSupport.subscribeAndAwait(() -> Panache.withTransaction(() -> repository.findById(id).map(SimpleCombModel::fromModel))); } - public Uni> getAll() { - return repository.listAll(Sort.ascending("fname", "lname")); + public Uni> searchAdmin(int limit, int page, String search, String club) { + if (search == null) + search = ""; + search = search + "%"; + + PanacheQuery query; + + if (club == null || club.isBlank()) + query = repository.find("(lname LIKE ?1 OR fname LIKE ?1)", + Sort.ascending("fname", "lname"), search).page(Page.ofSize(limit)); + else + query = repository.find("club.name LIKE ?2 AND (lname LIKE ?1 OR fname LIKE ?1)", + Sort.ascending("fname", "lname"), search, club + "%").page(Page.ofSize(limit)); + return getPageResult(query, limit, page); } - public Uni> getInClub(String subject) { + public Uni> search(int limit, int page, String search, String subject) { + if (search == null) + search = ""; + search = search + "%"; + String finalSearch = search; return repository.find("userId = ?1", subject).firstResult() - .chain(membreModel -> repository.find("club = ?1", membreModel.getClub()).list()); + .chain(membreModel -> { + PanacheQuery query = repository.find("club = ?1 AND (lname LIKE ?2 OR fname LIKE ?2)", + Sort.ascending("fname", "lname"), membreModel.getClub(), finalSearch).page(Page.ofSize(limit)); + return getPageResult(query, limit, page); + }); + } + + private Uni> getPageResult(PanacheQuery query, int limit, int page) { + return Uni.createFrom().item(new PageResult()) + .invoke(result -> result.setPage(page)) + .invoke(result -> result.setPage_size(limit)) + .call(result -> query.count().invoke(result::setResult_count)) + .call(result -> query.pageCount() + .invoke(Unchecked.consumer(pages -> { + if (page > pages) throw new BadRequestException(); + })) + .invoke(result::setPage_count)) + .call(result -> query.page(Page.of(page, limit)).list() + .map(membreModels -> membreModels.stream().map(SimpleMembre::fromModel).toList()) + .invoke(result::setResult)); } public Uni getById(long id) { @@ -89,7 +128,7 @@ public class MembreService { .map(__ -> "OK"); } - public Uni update(long id, ClubMemberForm membre, JsonWebToken idToken) { + public Uni update(long id, ClubMemberForm membre, JsonWebToken idToken, SecurityIdentity securityIdentity) { return repository.findById(id) .invoke(Unchecked.consumer(membreModel -> { if (!GroupeUtils.isInClubGroup(membreModel.getClub().getId(), idToken)) @@ -97,9 +136,9 @@ public class MembreService { })) .invoke(Unchecked.consumer(membreModel -> { RoleAsso source = RoleAsso.MEMBRE; - if (idToken.getGroups().contains("club_president")) source = RoleAsso.PRESIDENT; - else if (idToken.getGroups().contains("club_secretaire")) source = RoleAsso.SECRETAIRE; - else if (idToken.getGroups().contains("club_respo_intra")) source = RoleAsso.SECRETAIRE; + if (securityIdentity.getRoles().contains("club_president")) source = RoleAsso.PRESIDENT; + else if (securityIdentity.getRoles().contains("club_secretaire")) source = RoleAsso.SECRETAIRE; + else if (securityIdentity.getRoles().contains("club_respo_intra")) source = RoleAsso.SECRETAIRE; if (!membre.getRole().equals(membreModel.getRole()) && membre.getRole().level > source.level) throw new ForbiddenException(); })) diff --git a/src/main/java/fr/titionfire/ffsaf/rest/CombEndpoints.java b/src/main/java/fr/titionfire/ffsaf/rest/CombEndpoints.java index b27dd28..82a0209 100644 --- a/src/main/java/fr/titionfire/ffsaf/rest/CombEndpoints.java +++ b/src/main/java/fr/titionfire/ffsaf/rest/CombEndpoints.java @@ -6,9 +6,11 @@ import fr.titionfire.ffsaf.rest.data.SimpleMembre; import fr.titionfire.ffsaf.rest.from.ClubMemberForm; import fr.titionfire.ffsaf.rest.from.FullMemberForm; import fr.titionfire.ffsaf.utils.GroupeUtils; +import fr.titionfire.ffsaf.utils.PageResult; import fr.titionfire.ffsaf.utils.Pair; import io.quarkus.oidc.IdToken; import io.quarkus.security.Authenticated; +import io.quarkus.security.identity.SecurityIdentity; import io.smallrye.mutiny.Uni; import io.smallrye.mutiny.unchecked.Unchecked; import jakarta.annotation.security.RolesAllowed; @@ -26,7 +28,6 @@ import java.net.URI; import java.net.URISyntaxException; import java.net.URLConnection; import java.nio.file.Files; -import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Future; import java.util.function.Consumer; @@ -45,25 +46,38 @@ public class CombEndpoints { @IdToken JsonWebToken idToken; + @Inject + SecurityIdentity securityIdentity; + Consumer checkPerm = Unchecked.consumer(membreModel -> { - if (!idToken.getGroups().contains("federation_admin") && !GroupeUtils.isInClubGroup(membreModel.getClub().getId(), idToken)) + if (!securityIdentity.getRoles().contains("federation_admin") && !GroupeUtils.isInClubGroup(membreModel.getClub().getId(), idToken)) throw new ForbiddenException(); }); @GET - @Path("/all") - @RolesAllowed("federation_admin") + @Path("/find/admin") + @RolesAllowed({"federation_admin"}) @Produces(MediaType.APPLICATION_JSON) - public Uni> getAll() { - return membreService.getAll().map(membreModels -> membreModels.stream().map(SimpleMembre::fromModel).toList()); + public Uni> getFindAdmin(@QueryParam("limit") Integer limit, @QueryParam("page") Integer page, + @QueryParam("search") String search, @QueryParam("club") String club) { + if (limit == null) + limit = 50; + if (page == null || page < 1) + page = 1; + return membreService.searchAdmin(limit, page - 1, search, club); } @GET - @Path("/club") + @Path("/find/club") @RolesAllowed({"club_president", "club_secretaire", "club_respo_intra"}) @Produces(MediaType.APPLICATION_JSON) - public Uni> getClub() { - return membreService.getInClub(idToken.getSubject()).map(membreModels -> membreModels.stream().map(SimpleMembre::fromModel).toList()); + public Uni> getFindClub(@QueryParam("limit") Integer limit, @QueryParam("page") Integer page, + @QueryParam("search") String search) { + if (limit == null) + limit = 50; + if (page == null || page < 1) + page = 1; + return membreService.search(limit, page - 1, search, idToken.getSubject()); } @GET @@ -99,7 +113,7 @@ public class CombEndpoints { @Produces(MediaType.TEXT_PLAIN) @Consumes(MediaType.MULTIPART_FORM_DATA) public Uni setMembre(@PathParam("id") long id, ClubMemberForm input) { - return membreService.update(id, input, idToken) + return membreService.update(id, input, idToken, securityIdentity) .invoke(Unchecked.consumer(out -> { if (!out.equals("OK")) throw new InternalError("Fail to update data: " + out); })).chain(() -> { diff --git a/src/main/java/fr/titionfire/ffsaf/rest/CompteEndpoints.java b/src/main/java/fr/titionfire/ffsaf/rest/CompteEndpoints.java index 33dc88a..d05c38d 100644 --- a/src/main/java/fr/titionfire/ffsaf/rest/CompteEndpoints.java +++ b/src/main/java/fr/titionfire/ffsaf/rest/CompteEndpoints.java @@ -4,7 +4,7 @@ import fr.titionfire.ffsaf.domain.service.KeycloakService; import fr.titionfire.ffsaf.rest.from.MemberPermForm; import fr.titionfire.ffsaf.utils.GroupeUtils; import fr.titionfire.ffsaf.utils.Pair; -import io.quarkus.oidc.IdToken; +import io.quarkus.security.identity.SecurityIdentity; import io.smallrye.mutiny.Uni; import io.vertx.mutiny.core.Vertx; import jakarta.annotation.security.RolesAllowed; @@ -26,8 +26,7 @@ public class CompteEndpoints { JsonWebToken accessToken; @Inject - @IdToken - JsonWebToken idToken; + SecurityIdentity securityIdentity; @Inject Vertx vertx; @@ -37,8 +36,8 @@ public class CompteEndpoints { @RolesAllowed({"federation_admin", "club_president", "club_secretaire", "club_respo_intra"}) public Uni getCompte(@PathParam("id") String id) { return service.fetchCompte(id).call(pair -> vertx.getOrCreateContext().executeBlocking(() -> { - if (!idToken.getGroups().contains("federation_admin") && !pair.getKey().groups().stream().map(GroupRepresentation::getPath) - .anyMatch(s -> s.startsWith("/club/") && GroupeUtils.contains(s, accessToken))) + if (!securityIdentity.getRoles().contains("federation_admin") && pair.getKey().groups().stream().map(GroupRepresentation::getPath) + .noneMatch(s -> s.startsWith("/club/") && GroupeUtils.contains(s, accessToken))) throw new ForbiddenException(); return pair; })).map(Pair::getValue); diff --git a/src/main/java/fr/titionfire/ffsaf/rest/LicenceEndpoints.java b/src/main/java/fr/titionfire/ffsaf/rest/LicenceEndpoints.java index 7583287..9e4d069 100644 --- a/src/main/java/fr/titionfire/ffsaf/rest/LicenceEndpoints.java +++ b/src/main/java/fr/titionfire/ffsaf/rest/LicenceEndpoints.java @@ -6,6 +6,7 @@ import fr.titionfire.ffsaf.rest.data.SimpleLicence; import fr.titionfire.ffsaf.rest.from.LicenceForm; import fr.titionfire.ffsaf.utils.GroupeUtils; import io.quarkus.oidc.IdToken; +import io.quarkus.security.identity.SecurityIdentity; import io.smallrye.mutiny.Uni; import io.smallrye.mutiny.unchecked.Unchecked; import jakarta.annotation.security.RolesAllowed; @@ -27,8 +28,11 @@ public class LicenceEndpoints { @IdToken JsonWebToken idToken; + @Inject + SecurityIdentity securityIdentity; + Consumer checkPerm = Unchecked.consumer(membreModel -> { - if (!idToken.getGroups().contains("federation_admin") && !GroupeUtils.isInClubGroup(membreModel.getClub().getId(), idToken)) + if (!securityIdentity.getRoles().contains("federation_admin") && !GroupeUtils.isInClubGroup(membreModel.getClub().getId(), idToken)) throw new ForbiddenException(); }); @@ -40,6 +44,22 @@ public class LicenceEndpoints { return licenceService.getLicence(id, checkPerm).map(licenceModels -> licenceModels.stream().map(SimpleLicence::fromModel).toList()); } + @GET + @Path("current/admin") + @RolesAllowed({"federation_admin"}) + @Produces(MediaType.APPLICATION_JSON) + public Uni> getCurrentSaisonLicenceAdmin() { + return licenceService.getCurrentSaisonLicence(null).map(licenceModels -> licenceModels.stream().map(SimpleLicence::fromModel).toList()); + } + + @GET + @Path("current/club") + @RolesAllowed({"club_president", "club_secretaire", "club_respo_intra"}) + @Produces(MediaType.APPLICATION_JSON) + public Uni> getCurrentSaisonLicenceClub() { + return licenceService.getCurrentSaisonLicence(idToken).map(licenceModels -> licenceModels.stream().map(SimpleLicence::fromModel).toList()); + } + @POST @Path("{id}") @RolesAllowed("federation_admin") diff --git a/src/main/java/fr/titionfire/ffsaf/utils/PageResult.java b/src/main/java/fr/titionfire/ffsaf/utils/PageResult.java new file mode 100644 index 0000000..e8d2629 --- /dev/null +++ b/src/main/java/fr/titionfire/ffsaf/utils/PageResult.java @@ -0,0 +1,17 @@ +package fr.titionfire.ffsaf.utils; + +import io.quarkus.runtime.annotations.RegisterForReflection; +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +@Data +@RegisterForReflection +public class PageResult { + private int page; + private int page_size; + private int page_count; + private long result_count; + private List result = new ArrayList<>(); +} diff --git a/src/main/webapp/src/components/MemberCustomFiels.jsx b/src/main/webapp/src/components/MemberCustomFiels.jsx index 1b7eb64..6a00bce 100644 --- a/src/main/webapp/src/components/MemberCustomFiels.jsx +++ b/src/main/webapp/src/components/MemberCustomFiels.jsx @@ -77,4 +77,15 @@ export function CheckField({name, text, value, row = false}) { } -} \ No newline at end of file +} + +export const Checkbox = ({ label, value, onChange }) => { + const handleChange = () => { + onChange(!value); + }; + + return
+ + +
+}; \ No newline at end of file diff --git a/src/main/webapp/src/hooks/useFetch.js b/src/main/webapp/src/hooks/useFetch.js index f85bddf..99379fc 100644 --- a/src/main/webapp/src/hooks/useFetch.js +++ b/src/main/webapp/src/hooks/useFetch.js @@ -19,12 +19,16 @@ export function useFetch(url, setLoading = null, loadingLevel = 1, config = {}) const [data, setData] = useState(null) const [error, setErrors] = useState(null) - useEffect(() => { + const refresh = (url) => { stdAction(apiAxios.get(url, config), setData, setErrors, setLoading, loadingLevel) + } + + useEffect(() => { + refresh(url) }, []); return { - data, error + data, error, refresh } } diff --git a/src/main/webapp/src/pages/MemberList.jsx b/src/main/webapp/src/pages/MemberList.jsx new file mode 100644 index 0000000..8fbd771 --- /dev/null +++ b/src/main/webapp/src/pages/MemberList.jsx @@ -0,0 +1,234 @@ +import {useLoadingSwitcher} from "../hooks/useLoading.jsx"; +import {useFetch} from "../hooks/useFetch.js"; +import {AxiosError} from "../components/AxiosError.jsx"; +import {ThreeDots} from "react-loader-spinner"; +import {useEffect, useState} from "react"; +import {Input} from "../components/Input.jsx"; +import {useLocation, useNavigate} from "react-router-dom"; +import {Checkbox} from "../components/MemberCustomFiels.jsx"; +import axios from "axios"; +import {apiAxios} from "../utils/Tools.js"; +import {toast} from "react-toastify"; + +const removeDiacritics = str => { + return str + .normalize('NFD') + .replace(/[\u0300-\u036f]/g, '') +} + +export function MemberList({source}) { + const {hash} = useLocation(); + const navigate = useNavigate(); + let page = Number(hash.substring(1)); + page = (page > 0) ? page : 1; + + const [memberData, setMemberData] = useState([]); + const [licenceData, setLicenceData] = useState([]); + const [showLicenceState, setShowLicenceState] = useState(false); + const [clubFilter, setClubFilter] = useState(""); + const [lastSearch, setLastSearch] = useState(""); + + const setLoading = useLoadingSwitcher() + const {data, error, refresh} = useFetch(`/member/find/${source}?page=${page}`, setLoading, 1) + + + useEffect(() => { + refresh(`/member/find/${source}?page=${page}&search=${lastSearch}&club=${clubFilter}`); + }, [hash, clubFilter]); + + useEffect(() => { + if (!data) + return; + const data2 = []; + for (const e of data.result) { + data2.push({ + id: e.id, + fname: e.fname, + lname: e.lname, + club: e.club, + licence_number: e.licence, + licence: showLicenceState ? licenceData.find(licence => licence.membre === e.id) : null + }) + } + setMemberData(data2); + }, [data, licenceData]); + + useEffect(() => { + if (!showLicenceState) + return; + + toast.promise( + apiAxios.get(`/licence/current/${source}`), + { + pending: "Chargement des licences...", + success: "Licences chargées", + error: "Impossible de charger les licences" + }) + .then(data => { + setLicenceData(data.data); + }); + }, [showLicenceState]); + + const search = (search) => { + if (search === lastSearch) + return; + setLastSearch(search); + refresh(`/member/find/${source}?page=${page}&search=${search}&club=${clubFilter}`); + } + + return <> +
+
+
+ + {data + ? + : error + ? + : + } +
+
+
+ +
+
+
Filtre
+
+ +
+
+
+
+
+ +} + +function SearchBar({search}) { + const [searchInput, setSearchInput] = useState(""); + + const handelChange = (e) => { + setSearchInput(e.target.value); + } + + const handleKeyDown = (event) => { + if (event.key === 'Enter') { + searchMember(); + } + } + + const searchMember = () => { + search(removeDiacritics(searchInput)); + } + + useEffect(() => { + const delayDebounceFn = setTimeout(() => { + searchMember(); + }, 750) + return () => clearTimeout(delayDebounceFn) + }, [searchInput]) + + return
+
+ + +
+
+} + +function MakeCentralPanel({data, visibleMember, navigate, showLicenceState, page}) { + const pages = [] + for (let i = 1; i <= data.page_count; i++) { + pages.push(
  • + navigate("#" + i)}>{i} +
  • ); + } + + return <> +
    + Ligne {((page - 1) * data.page_size) + 1} à { + (page * data.page_size > data.result_count) ? data.result_count : (page * data.page_size)} (page {page} sur {data.page_count}) +
    + {visibleMember.map(member => ())} +
    +
    +
    + +
    + +} + +function MakeRow({member, showLicenceState, navigate}) { + const rowContent = <> +
    + {String(member.licence_number).padStart(5, '0')} +
    +
    {member.fname} {member.lname}
    +
    +
    + {member.club?.name || "Sans club"} + + + if (showLicenceState && member.licence != null) { + return
    navigate("" + member.id)}>{rowContent}
    + } else { + return
    navigate("" + member.id)}> + {rowContent} +
    + } +} + +let allClub = [] + +function FiltreBar({showLicenceState, setShowLicenceState, data, clubFilter, setClubFilter, source}) { + useEffect(() => { + if (!data) + return; + allClub.push(...data.result.map((e) => e.club?.name)) + allClub = allClub.filter((value, index, self) => self.indexOf(value) === index).filter(value => value != null).sort() + }, [data]); + + return
    +
    + +
    + {source !== "club" && +
    + +
    + } +
    +} + +function Def() { + return
    +
  • +
  • +
  • +
  • +
  • +
    +} \ No newline at end of file diff --git a/src/main/webapp/src/pages/admin/AdminRoot.jsx b/src/main/webapp/src/pages/admin/AdminRoot.jsx index 623032d..1ea1031 100644 --- a/src/main/webapp/src/pages/admin/AdminRoot.jsx +++ b/src/main/webapp/src/pages/admin/AdminRoot.jsx @@ -1,7 +1,7 @@ import {Outlet} from "react-router-dom"; import './AdminRoot.css' import {LoadingProvider} from "../../hooks/useLoading.jsx"; -import {MemberList} from "./MemberList.jsx"; +import {MemberList} from "../MemberList.jsx"; import {MemberPage} from "./member/MemberPage.jsx"; export function AdminRoot() { @@ -17,7 +17,7 @@ export function getAdminChildren () { return [ { path: 'member', - element: + element: }, { path: 'member/:id', diff --git a/src/main/webapp/src/pages/admin/MemberList.jsx b/src/main/webapp/src/pages/admin/MemberList.jsx deleted file mode 100644 index 5f4ed16..0000000 --- a/src/main/webapp/src/pages/admin/MemberList.jsx +++ /dev/null @@ -1,60 +0,0 @@ -import {useLoadingSwitcher} from "../../hooks/useLoading.jsx"; -import {useFetch} from "../../hooks/useFetch.js"; -import {AxiosError} from "../../components/AxiosError.jsx"; -import {ThreeDots} from "react-loader-spinner"; -import {useState} from "react"; -import {Input} from "../../components/Input.jsx"; -import {useNavigate} from "react-router-dom"; - -const removeDiacritics = str => { - return str - .normalize('NFD') - .replace(/[\u0300-\u036f]/g, '') -} - -export function MemberList() { - const setLoading = useLoadingSwitcher() - const {data, error} = useFetch(`/member/all`, setLoading, 1) - const [searchInput, setSearchInput] = useState(""); - const navigate = useNavigate(); - - const visibleMember = data ? data.filter(member => { - const lo = removeDiacritics(searchInput).toLowerCase() - return !searchInput - || (removeDiacritics(member.fname).toLowerCase().startsWith(lo) - || removeDiacritics(member.lname).toLowerCase().startsWith(lo)); - }) : []; - - return <> - - {data - ?
    - {visibleMember.map(member => ( - navigate("/admin/member/" + member.id)} - className="list-group-item list-group-item-action">{member.fname} {member.lname}))} -
    - : error - ? - : - } - -} - -function SearchBar({searchInput, onSearchInputChange}) { - return
    -
    - -
    -
    -} - -function Def() { - return
    -
  • -
  • -
  • -
  • -
  • -
    -} \ No newline at end of file diff --git a/src/main/webapp/src/pages/admin/member/MemberPage.jsx b/src/main/webapp/src/pages/admin/member/MemberPage.jsx index 77b85d3..badac6f 100644 --- a/src/main/webapp/src/pages/admin/member/MemberPage.jsx +++ b/src/main/webapp/src/pages/admin/member/MemberPage.jsx @@ -19,7 +19,7 @@ export function MemberPage() { return <>

    Page membre

    {data ?
    diff --git a/src/main/webapp/src/pages/club/ClubRoot.jsx b/src/main/webapp/src/pages/club/ClubRoot.jsx index 5c92a4a..5ddc95a 100644 --- a/src/main/webapp/src/pages/club/ClubRoot.jsx +++ b/src/main/webapp/src/pages/club/ClubRoot.jsx @@ -1,8 +1,8 @@ import {Outlet} from "react-router-dom"; import {LoadingProvider} from "../../hooks/useLoading.jsx"; -import {MemberList} from "./MemberList.jsx"; import {MemberPage} from "./member/MemberPage.jsx"; import {useAuth} from "../../hooks/useAuth.jsx"; +import {MemberList} from "../MemberList.jsx"; export function ClubRoot() { const {userinfo} = useAuth() @@ -29,7 +29,7 @@ export function getClubChildren() { return [ { path: 'member', - element: + element: }, { path: 'member/:id', diff --git a/src/main/webapp/src/pages/club/MemberList.jsx b/src/main/webapp/src/pages/club/MemberList.jsx deleted file mode 100644 index a486f6b..0000000 --- a/src/main/webapp/src/pages/club/MemberList.jsx +++ /dev/null @@ -1,60 +0,0 @@ -import {useLoadingSwitcher} from "../../hooks/useLoading.jsx"; -import {useFetch} from "../../hooks/useFetch.js"; -import {AxiosError} from "../../components/AxiosError.jsx"; -import {ThreeDots} from "react-loader-spinner"; -import {useState} from "react"; -import {Input} from "../../components/Input.jsx"; -import {useNavigate} from "react-router-dom"; - -const removeDiacritics = str => { - return str - .normalize('NFD') - .replace(/[\u0300-\u036f]/g, '') -} - -export function MemberList() { - const setLoading = useLoadingSwitcher() - const {data, error} = useFetch(`/member/club`, setLoading, 1) - const [searchInput, setSearchInput] = useState(""); - const navigate = useNavigate(); - - const visibleMember = data ? data.filter(member => { - const lo = removeDiacritics(searchInput).toLowerCase() - return !searchInput - || (removeDiacritics(member.fname).toLowerCase().startsWith(lo) - || removeDiacritics(member.lname).toLowerCase().startsWith(lo)); - }) : []; - - return <> - - {data - ?
    - {visibleMember.map(member => ( - navigate("/club/member/" + member.id)} - className="list-group-item list-group-item-action">{member.fname} {member.lname}))} -
    - : error - ? - : - } - -} - -function SearchBar({searchInput, onSearchInputChange}) { - return
    -
    - -
    -
    -} - -function Def() { - return
    -
  • -
  • -
  • -
  • -
  • -
    -} \ No newline at end of file diff --git a/src/main/webapp/src/pages/club/member/MemberPage.jsx b/src/main/webapp/src/pages/club/member/MemberPage.jsx index 3f34d2f..622fd5d 100644 --- a/src/main/webapp/src/pages/club/member/MemberPage.jsx +++ b/src/main/webapp/src/pages/club/member/MemberPage.jsx @@ -18,7 +18,7 @@ export function MemberPage() { return <>

    Page membre

    {data ?