diff --git a/src/main/java/fr/titionfire/ffsaf/domain/service/ClubService.java b/src/main/java/fr/titionfire/ffsaf/domain/service/ClubService.java index e64f69a..82810c8 100644 --- a/src/main/java/fr/titionfire/ffsaf/domain/service/ClubService.java +++ b/src/main/java/fr/titionfire/ffsaf/domain/service/ClubService.java @@ -10,6 +10,7 @@ import fr.titionfire.ffsaf.data.repository.CombRepository; import fr.titionfire.ffsaf.net2.ServerCustom; import fr.titionfire.ffsaf.net2.data.SimpleClubModel; import fr.titionfire.ffsaf.net2.request.SReqClub; +import fr.titionfire.ffsaf.rest.data.ClubMapData; import fr.titionfire.ffsaf.rest.data.DeskMember; import fr.titionfire.ffsaf.rest.data.RenewAffData; import fr.titionfire.ffsaf.rest.data.SimpleClubList; @@ -25,6 +26,7 @@ import io.quarkus.hibernate.reactive.panache.common.WithSession; import io.quarkus.panache.common.Page; import io.quarkus.panache.common.Sort; import io.quarkus.vertx.VertxContextSupport; +import io.smallrye.mutiny.Multi; import io.smallrye.mutiny.Uni; import io.smallrye.mutiny.unchecked.Unchecked; import jakarta.enterprise.context.ApplicationScoped; @@ -121,7 +123,8 @@ public class ClubService { } public Uni getOfUser(SecurityCtx securityCtx) { - return combRepository.find("userId = ?1", securityCtx.getSubject()).firstResult().invoke(Unchecked.consumer(m -> { + return combRepository.find("userId = ?1", securityCtx.getSubject()).firstResult() + .invoke(Unchecked.consumer(m -> { if (m == null || m.getClub() == null) throw new DNotFoundException("Club non trouvé"); })) @@ -143,7 +146,8 @@ public class ClubService { TypeReference> typeRef = new TypeReference<>() { }; - return combRepository.find("userId = ?1", securityCtx.getSubject()).firstResult().invoke(Unchecked.consumer(m -> { + return combRepository.find("userId = ?1", securityCtx.getSubject()).firstResult() + .invoke(Unchecked.consumer(m -> { if (m == null || m.getClub() == null) throw new DNotFoundException("Club non trouvé"); if (!securityCtx.isInClubGroup(m.getClub().getId())) @@ -279,4 +283,31 @@ public class ClubService { .toList())) .map(o -> data); } + + public Uni> getMapData() { + return repository.list("international", false).toMulti().flatMap(list -> Multi.createFrom().iterable(list)) + .call(clubModel -> Mutiny.fetch(clubModel.getContact())) + .map(clubModel -> { + ClubMapData data = new ClubMapData(); + + data.setName(clubModel.getName()); + data.setUuid(clubModel.getClubId()); + if (clubModel.getTraining_location() != null) { + try { + MAPPER.readTree(clubModel.getTraining_location()).forEach(l -> { + ClubMapData.Location loc = new ClubMapData.Location(); + loc.setLat(l.get("lat").asDouble()); + loc.setLng(l.get("lng").asDouble()); + loc.setAddr(l.get("text").asText()); + data.training_location.add(loc); + }); + } catch (JsonProcessingException ignored) { + } + } + data.setTraining_day_time(clubModel.getTraining_day_time()); + data.setContact(clubModel.getContact()); + + return data; + }).collect().asList(); + } } 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 672f059..02b5ae9 100644 --- a/src/main/java/fr/titionfire/ffsaf/domain/service/MembreService.java +++ b/src/main/java/fr/titionfire/ffsaf/domain/service/MembreService.java @@ -41,6 +41,7 @@ import org.hibernate.reactive.mutiny.Mutiny; import java.io.*; import java.nio.file.Files; +import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.List; import java.util.Objects; @@ -438,11 +439,27 @@ public class MembreService { photoCell.setBorder(PdfPCell.NO_BORDER); memberTable.addCell(photoCell); + String[] cert; + if (licence.getCertificate() != null && !licence.getCertificate().isBlank()) { + cert = licence.getCertificate().split("¤"); + if (cert.length <= 1){ + cert = new String[]{licence.getCertificate(), "--"}; + }else{ + try { + cert[1] = sdf.format(new SimpleDateFormat("yyyy-MM-dd").parse(cert[1])); + } catch (ParseException e) { + cert[1] = "--"; + } + } + } else { + cert = new String[]{"--", "--"}; + } + // Adding member details memberTable.addCell(new Phrase("NOM : " + m.getLname().toUpperCase(), bodyFont)); memberTable.addCell(new Phrase("Prénom : " + m.getFname(), bodyFont)); memberTable.addCell(new Phrase("Licence n° : " + m.getLicence(), bodyFont)); - memberTable.addCell(new Phrase("Certificat médical par Dr " + licence.getCertificate(), bodyFont)); + memberTable.addCell(new Phrase("Certificat médical par " + cert[0] + ", le " + cert[1], bodyFont)); memberTable.addCell(new Phrase("")); // Empty cell for spacing document.add(memberTable); diff --git a/src/main/java/fr/titionfire/ffsaf/rest/ClubEndpoints.java b/src/main/java/fr/titionfire/ffsaf/rest/ClubEndpoints.java index e3e473c..3c49cd8 100644 --- a/src/main/java/fr/titionfire/ffsaf/rest/ClubEndpoints.java +++ b/src/main/java/fr/titionfire/ffsaf/rest/ClubEndpoints.java @@ -3,10 +3,7 @@ package fr.titionfire.ffsaf.rest; import fr.titionfire.ffsaf.data.model.ClubModel; import fr.titionfire.ffsaf.domain.service.ClubService; import fr.titionfire.ffsaf.net2.data.SimpleClubModel; -import fr.titionfire.ffsaf.rest.data.DeskMember; -import fr.titionfire.ffsaf.rest.data.RenewAffData; -import fr.titionfire.ffsaf.rest.data.SimpleClub; -import fr.titionfire.ffsaf.rest.data.SimpleClubList; +import fr.titionfire.ffsaf.rest.data.*; import fr.titionfire.ffsaf.rest.exception.DForbiddenException; import fr.titionfire.ffsaf.rest.exception.DInternalError; import fr.titionfire.ffsaf.rest.from.FullClubForm; @@ -315,4 +312,10 @@ public class ClubEndpoints { })); } + @GET + @Path("get_map_data") + public Uni> getMapData() { + return clubService.getMapData(); + } + } diff --git a/src/main/java/fr/titionfire/ffsaf/rest/data/ClubMapData.java b/src/main/java/fr/titionfire/ffsaf/rest/data/ClubMapData.java new file mode 100644 index 0000000..01c030c --- /dev/null +++ b/src/main/java/fr/titionfire/ffsaf/rest/data/ClubMapData.java @@ -0,0 +1,27 @@ +package fr.titionfire.ffsaf.rest.data; + +import fr.titionfire.ffsaf.utils.Contact; +import io.quarkus.runtime.annotations.RegisterForReflection; +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +@Data +@RegisterForReflection +public class ClubMapData { + public String name; + public String uuid; + public List training_location = new ArrayList<>(); + public String training_day_time; + public Map contact; + + @Data + @RegisterForReflection + public static class Location { + public double lat; + public double lng; + public String addr; + } +} diff --git a/src/main/java/fr/titionfire/ffsaf/utils/Utils.java b/src/main/java/fr/titionfire/ffsaf/utils/Utils.java index 709786a..b1a700c 100644 --- a/src/main/java/fr/titionfire/ffsaf/utils/Utils.java +++ b/src/main/java/fr/titionfire/ffsaf/utils/Utils.java @@ -11,7 +11,6 @@ import java.io.*; import java.net.URISyntaxException; import java.net.URLConnection; import java.nio.file.Files; -import java.util.Arrays; import java.util.Calendar; import java.util.Date; import java.util.concurrent.CompletableFuture; @@ -78,14 +77,7 @@ public class Utils { try (InputStream is = new BufferedInputStream(new ByteArrayInputStream(input))) { String mimeType = URLConnection.guessContentTypeFromStream(is); - System.out.println(mimeType); - /*try { - mimeType = Magic.getMagicMatch(input, false).getMimeType(); - } catch (MagicParseException | MagicMatchNotFoundException | MagicException e) { - mimeType = URLConnection.guessContentTypeFromStream(is); - }*/ String[] detectedExtensions = MimeTypes.findExtensionsByMimeTypes(mimeType, false); - System.out.println(Arrays.toString(detectedExtensions)); if (detectedExtensions.length == 0) throw new IOException("Fail to detect file extension for MIME type " + mimeType); diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index c0346aa..90c91bd 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -52,3 +52,6 @@ quarkus.http.auth.permission.authenticated.policy=authenticated quarkus.http.auth.permission.public.paths=/index.html quarkus.http.auth.permission.public.policy=permit quarkus.keycloak.admin-client.server-url=https://auth.safca.fr + + +quarkus.native.resources.includes=asset/** \ No newline at end of file diff --git a/src/main/webapp/index.html b/src/main/webapp/index.html index 95678f6..16586f2 100644 --- a/src/main/webapp/index.html +++ b/src/main/webapp/index.html @@ -2,9 +2,9 @@ - + - Vite + React + FFSAF Intranet @@ -23,7 +23,7 @@
-

Chargement de l'application FFSAF...

+

Chargement de l'intranet FFSAF...

diff --git a/src/main/webapp/src/assets/FFSSAF-bord-blanc-fond-transparent.webp b/src/main/webapp/public/FFSSAF-bord-blanc-fond-transparent.webp similarity index 100% rename from src/main/webapp/src/assets/FFSSAF-bord-blanc-fond-transparent.webp rename to src/main/webapp/public/FFSSAF-bord-blanc-fond-transparent.webp diff --git a/src/main/webapp/public/club-maps.html b/src/main/webapp/public/club-maps.html new file mode 100644 index 0000000..4e25ba3 --- /dev/null +++ b/src/main/webapp/public/club-maps.html @@ -0,0 +1,20 @@ + + + + + Où pratiquer le MSF ? + + + + + + +
+ + + + \ No newline at end of file diff --git a/src/main/webapp/public/club-maps.js b/src/main/webapp/public/club-maps.js new file mode 100644 index 0000000..bec2e5a --- /dev/null +++ b/src/main/webapp/public/club-maps.js @@ -0,0 +1,111 @@ +const api_url = "https://intra.ffsaf.fr"; + +let map = L.map('map').setView([46.631196, 2.456000], 6); + +L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', { + maxZoom: 19, + attribution: '© OpenStreetMap' +}).addTo(map); + +const getMeta = async (url) => { + const img = new Image(); + img.src = url; + await img.decode(); + return img +}; + +function timeNumberToSting(nbMin) { + return String(Math.floor(nbMin / 60)).padStart(2, '0') + ":" + String(nbMin % 60).padStart(2, '0') +} + +const sortHoraire = (a, b) => { + if (a.day === b.day) + return a.time_start - b.time_start; + return a.day - b.day; +} + +async function getData() { + const response = await fetch(`${api_url}/api/club/get_map_data`); + if (!response.ok) { + throw new Error(`Response status: ${response.status}`); + } + + const data = await response.json(); + for (const d of data) { + if (d.training_location.length === 0) + continue; + + let icon = null; + if (d.uuid !== null) { + const img = await getMeta(`${api_url}/api/club/${d.uuid}/logo`); + let ratio = img.naturalHeight / img.naturalWidth; + + icon = L.icon({ + iconUrl: `${api_url}/api/club/${d.uuid}/logo`, + + iconSize: [50, 50 * ratio], // size of the icon + //shadowSize: [50, 64], // size of the shadow + iconAnchor: [25, 50 * ratio], // point of the icon which will correspond to marker's location + //shadowAnchor: [4, 62], // the same for the shadow + popupAnchor: [0, -50 * ratio] // point from which the popup should open relative to the iconAnchor + }); + } + + for (const m of d.training_location) { + let marker; + if (icon === null) + marker = L.marker([m.lat, m.lng]); + else + marker = L.marker([m.lat, m.lng], {icon: icon}); + marker.addTo(map); + + const div_info = document.createElement("div"); + let tmp = document.createElement("h3"); + tmp.textContent = d.name; + div_info.appendChild(tmp); + + div_info.appendChild(document.createTextNode(`Adresse: `)); + div_info.appendChild(document.createElement("br")); + for (const m of d.training_location) { + div_info.appendChild(document.createTextNode(m.addr)); + div_info.appendChild(document.createElement("br")); + } + div_info.appendChild(document.createElement("br")); + + div_info.appendChild(document.createTextNode(`Horaire: `)); + div_info.appendChild(document.createElement("br")); + if (!d.training_day_time || JSON.parse(d.training_day_time).length === 0) { + div_info.appendChild(document.createTextNode("Pas d'horaire disponible")); + div_info.appendChild(document.createElement("br")); + } else { + for (const m of JSON.parse(d.training_day_time).sort(sortHoraire)) { + let days = ["Lundi", "Mardi", "Mercredi", "Jeudi", "Vendredi", "Samedi", "Dimanche"]; + + div_info.appendChild(document.createTextNode(`${days[m.day]}: ${timeNumberToSting(m.time_start)} - ${timeNumberToSting(m.time_end)}`)); + div_info.appendChild(document.createElement("br")); + } + } + div_info.appendChild(document.createElement("br")); + + div_info.appendChild(document.createTextNode(`Contact: `)); + div_info.appendChild(document.createElement("br")); + if (d.contact === null || Object.keys(d.contact).length === 0) { + div_info.appendChild(document.createTextNode("Pas de contact disponible")); + div_info.appendChild(document.createElement("br")); + } else { + for (const [key, value] of Object.entries(d.contact)) { + div_info.appendChild(document.createTextNode(`${key.charAt(0).toUpperCase() + key.slice(1).toLowerCase()}: ${value}`)); + div_info.appendChild(document.createElement("br")); + } + } + marker.bindPopup(div_info.innerHTML); + + if (icon !== null){ + marker.valueOf()._icon.style.backgroundColor = '#FFFFFF'; + marker.valueOf()._icon.style.borderRadius = '10px'; + } + } + } +} + +getData().then(() => console.log("end loading")); \ No newline at end of file diff --git a/src/main/webapp/public/home_back.jpg b/src/main/webapp/public/home_back.jpg new file mode 100644 index 0000000..331a76e Binary files /dev/null and b/src/main/webapp/public/home_back.jpg differ diff --git a/src/main/webapp/public/vite.svg b/src/main/webapp/public/vite.svg deleted file mode 100644 index e7b8dfb..0000000 --- a/src/main/webapp/public/vite.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/main/webapp/src/assets/react.svg b/src/main/webapp/src/assets/react.svg deleted file mode 100644 index 6c87de9..0000000 --- a/src/main/webapp/src/assets/react.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/main/webapp/src/components/Club/LocationEditor.jsx b/src/main/webapp/src/components/Club/LocationEditor.jsx index 086e82f..a1e99d1 100644 --- a/src/main/webapp/src/components/Club/LocationEditor.jsx +++ b/src/main/webapp/src/components/Club/LocationEditor.jsx @@ -98,15 +98,19 @@ export function LocationEditorModal({modal, sendData}) { return const delayDebounceFn = setTimeout(() => { - refresh(`https://api-adresse.data.gouv.fr/search/?q=${encodeURI(location)}&type=housenumber&autocomplete=1`) + refresh(`https://api-adresse.data.gouv.fr/search/?q=${encodeURI(location)}&type=housenumber&autocomplete=1&limit=5`) }, 500) return () => clearTimeout(delayDebounceFn) }, [location]); useEffect(() => { - if (data?.features?.length === 1) { - const {lat, lng} = convertLambert93ToLatLng(data.features[0].properties.x, data.features[0].properties.y) - setLocationObj({text: data.features[0].properties.label, lng: lng, lat: lat}) + if (data.features?.some((e) => e.properties.label === location)) { + data.features.forEach((d) => { + if (d.properties.label === location) { + const {lat, lng} = convertLambert93ToLatLng(d.properties.x, d.properties.y) + setLocationObj({text: d.properties.label, lng: lng, lat: lat}) + } + }) } else { setLocationObj({text: "", lng: undefined, lat: undefined}) } @@ -144,14 +148,17 @@ export function LocationEditorModal({modal, sendData}) {
- setLocation(e.target.value)}/> - - {data?.features && data.features.map((d, index) => { - return +
+
+
    + {data?.features && !data.features.some((e) => e.properties.label === location) && data.features.map((d, index) => { + return
  • setLocation(d.properties.label)}>{d.properties.label}
  • })} - +
@@ -166,7 +173,9 @@ export function LocationEditorModal({modal, sendData}) {
- +
diff --git a/src/main/webapp/src/components/Nav.jsx b/src/main/webapp/src/components/Nav.jsx index 4e5a01d..5f64f86 100644 --- a/src/main/webapp/src/components/Nav.jsx +++ b/src/main/webapp/src/components/Nav.jsx @@ -1,4 +1,3 @@ -import LogoIcon from '../assets/FFSSAF-bord-blanc-fond-transparent.webp' import './Nav.css' import {NavLink} from "react-router-dom"; import {useAuth} from "../hooks/useAuth.jsx"; @@ -9,8 +8,8 @@ export function Nav() { return