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 d059892..5eac2ce 100644 --- a/src/main/java/fr/titionfire/ffsaf/domain/service/MembreService.java +++ b/src/main/java/fr/titionfire/ffsaf/domain/service/MembreService.java @@ -1,5 +1,6 @@ package fr.titionfire.ffsaf.domain.service; +import fr.titionfire.ffsaf.data.model.ClubModel; import fr.titionfire.ffsaf.data.model.MembreModel; import fr.titionfire.ffsaf.data.repository.ClubRepository; import fr.titionfire.ffsaf.data.repository.CombRepository; @@ -9,10 +10,7 @@ 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 fr.titionfire.ffsaf.utils.*; import io.quarkus.hibernate.reactive.panache.Panache; import io.quarkus.hibernate.reactive.panache.PanacheQuery; import io.quarkus.hibernate.reactive.panache.common.WithSession; @@ -163,10 +161,47 @@ public class MembreService { .map(__ -> "OK"); } + public Uni add(FullMemberForm input) { + return clubRepository.findById(input.getClub()) + .chain(clubModel -> { + MembreModel model = getMembreModel(input, clubModel); + return Panache.withTransaction(() -> repository.persist(model)); + }) + .invoke(membreModel -> SReqComb.sendIfNeedAdd(serverCustom.clients, SimpleCombModel.fromModel(membreModel))) + .map(MembreModel::getId); + } + + public Uni add(FullMemberForm input, String subject) { + return repository.find("userId = ?1", subject).firstResult() + .chain(membreModel -> { + MembreModel model = getMembreModel(input, membreModel.getClub()); + model.setRole(RoleAsso.MEMBRE); + model.setGrade_arbitrage(GradeArbitrage.NA); + return Panache.withTransaction(() -> repository.persist(model)); + }) + .invoke(membreModel -> SReqComb.sendIfNeedAdd(serverCustom.clients, SimpleCombModel.fromModel(membreModel))) + .map(MembreModel::getId); + } + public Uni setUserId(Long id, String id1) { return repository.findById(id).chain(membreModel -> { membreModel.setUserId(id1); return Panache.withTransaction(() -> repository.persist(membreModel)); }); } + + private static MembreModel getMembreModel(FullMemberForm input, ClubModel clubModel) { + MembreModel model = new MembreModel(); + model.setFname(input.getFname()); + model.setLname(input.getLname()); + model.setEmail(input.getEmail()); + model.setGenre(input.getGenre()); + model.setCountry(input.getCountry()); + model.setBirth_date(input.getBirth_date()); + model.setCategorie(input.getCategorie()); + model.setClub(clubModel); + model.setRole(input.getRole()); + model.setGrade_arbitrage(input.getGrade_arbitrage()); + return model; + } } diff --git a/src/main/java/fr/titionfire/ffsaf/rest/CombEndpoints.java b/src/main/java/fr/titionfire/ffsaf/rest/CombEndpoints.java index 82a0209..c29d9bd 100644 --- a/src/main/java/fr/titionfire/ffsaf/rest/CombEndpoints.java +++ b/src/main/java/fr/titionfire/ffsaf/rest/CombEndpoints.java @@ -88,7 +88,7 @@ public class CombEndpoints { return membreService.getById(id).onItem().invoke(checkPerm).map(SimpleMembre::fromModel); } - @POST + @PUT @Path("{id}") @RolesAllowed({"federation_admin"}) @Produces(MediaType.TEXT_PLAIN) @@ -108,6 +108,22 @@ public class CombEndpoints { } @POST + @RolesAllowed({"federation_admin"}) + @Produces(MediaType.TEXT_PLAIN) + @Consumes(MediaType.MULTIPART_FORM_DATA) + public Uni addAdminMembre(FullMemberForm input) { + return membreService.add(input) + .invoke(Unchecked.consumer(id -> { + if (id == null) throw new InternalError("Fail to creat member data"); + })).call(id -> { + if (input.getPhoto_data().length > 0) + return Uni.createFrom().future(replacePhoto(id, input.getPhoto_data())); + else + return Uni.createFrom().nullItem(); + }); + } + + @PUT @Path("club/{id}") @RolesAllowed({"club_president", "club_secretaire", "club_respo_intra"}) @Produces(MediaType.TEXT_PLAIN) @@ -126,6 +142,23 @@ public class CombEndpoints { }); } + @POST + @Path("club") + @RolesAllowed({"club_president", "club_secretaire", "club_respo_intra"}) + @Produces(MediaType.TEXT_PLAIN) + @Consumes(MediaType.MULTIPART_FORM_DATA) + public Uni addMembre(FullMemberForm input) { + return membreService.add(input, idToken.getSubject()) + .invoke(Unchecked.consumer(id -> { + if (id == null) throw new InternalError("Fail to creat member data"); + })).call(id -> { + if (input.getPhoto_data().length > 0) + return Uni.createFrom().future(replacePhoto(id, input.getPhoto_data())); + else + return Uni.createFrom().nullItem(); + }); + } + private Future replacePhoto(long id, byte[] input) { return CompletableFuture.supplyAsync(() -> { try (InputStream is = new BufferedInputStream(new ByteArrayInputStream(input))) { diff --git a/src/main/webapp/src/pages/admin/AdminRoot.jsx b/src/main/webapp/src/pages/admin/AdminRoot.jsx index 1ea1031..e2860ed 100644 --- a/src/main/webapp/src/pages/admin/AdminRoot.jsx +++ b/src/main/webapp/src/pages/admin/AdminRoot.jsx @@ -3,6 +3,7 @@ import './AdminRoot.css' import {LoadingProvider} from "../../hooks/useLoading.jsx"; import {MemberList} from "../MemberList.jsx"; import {MemberPage} from "./member/MemberPage.jsx"; +import {NewMemberPage} from "./member/NewMemberPage.jsx"; export function AdminRoot() { return <> @@ -13,7 +14,7 @@ export function AdminRoot() { } -export function getAdminChildren () { +export function getAdminChildren() { return [ { path: 'member', @@ -23,6 +24,10 @@ export function getAdminChildren () { path: 'member/:id', element: }, + { + path: 'member/new', + element: + }, { path: 'b', element:
Admin B
diff --git a/src/main/webapp/src/pages/admin/member/InformationForm.jsx b/src/main/webapp/src/pages/admin/member/InformationForm.jsx index 265decc..034ef09 100644 --- a/src/main/webapp/src/pages/admin/member/InformationForm.jsx +++ b/src/main/webapp/src/pages/admin/member/InformationForm.jsx @@ -5,6 +5,27 @@ import imageCompression from "browser-image-compression"; import {BirthDayField, OptionField, TextField} from "../../../components/MemberCustomFiels.jsx"; import {ClubSelect} from "../../../components/ClubSelect.jsx"; +export function addPhoto(event, formData, send) { + const imageFile = event.target.url_photo.files[0]; + if (imageFile) { + console.log(`originalFile size ${imageFile.size / 1024 / 1024} MB`); + + const options = { + maxSizeMB: 1, + maxWidthOrHeight: 1920, + useWebWorker: true, + } + + imageCompression(imageFile, options).then(compressedFile => { + console.log(`compressedFile size ${compressedFile.size / 1024 / 1024} MB`); // smaller than maxSizeMB + formData.append("photo_data", compressedFile) + send(formData) + }); + } else { + send(formData) + } +} + export function InformationForm({data}) { const setLoading = useLoadingSwitcher() const handleSubmit = (event) => { @@ -25,7 +46,7 @@ export function InformationForm({data}) { formData.append("grade_arbitrage", event.target.grade_arbitrage?.value); const send = (formData_) => { - apiAxios.post(`/member/${data.id}`, formData_, { + apiAxios.put(`/member/${data.id}`, formData_, { headers: { 'Accept': '*/*', 'Content-Type': 'multipart/form-data', @@ -40,25 +61,7 @@ export function InformationForm({data}) { setLoading(0) }) } - - const imageFile = event.target.url_photo.files[0]; - if (imageFile) { - console.log(`originalFile size ${imageFile.size / 1024 / 1024} MB`); - - const options = { - maxSizeMB: 1, - maxWidthOrHeight: 1920, - useWebWorker: true, - } - - imageCompression(imageFile, options).then(compressedFile => { - console.log(`compressedFile size ${compressedFile.size / 1024 / 1024} MB`); // smaller than maxSizeMB - formData.append("photo_data", compressedFile) - send(formData) - }); - } else { - send(formData) - } + addPhoto(event, formData, send); } return
diff --git a/src/main/webapp/src/pages/admin/member/NewMemberPage.jsx b/src/main/webapp/src/pages/admin/member/NewMemberPage.jsx new file mode 100644 index 0000000..18fb3b7 --- /dev/null +++ b/src/main/webapp/src/pages/admin/member/NewMemberPage.jsx @@ -0,0 +1,107 @@ +import {useNavigate} from "react-router-dom"; +import {useLoadingSwitcher} from "../../../hooks/useLoading.jsx"; +import {apiAxios} from "../../../utils/Tools.js"; +import {toast} from "react-toastify"; +import {BirthDayField, OptionField, TextField} from "../../../components/MemberCustomFiels.jsx"; +import {ClubSelect} from "../../../components/ClubSelect.jsx"; +import {addPhoto} from "./InformationForm.jsx"; + +export function NewMemberPage() { + const navigate = useNavigate(); + + return <> +

Page membre

+ +
+
+ +
+
+ +} + +function Form() { + const navigate = useNavigate(); + const setLoading = useLoadingSwitcher() + + const handleSubmit = (event) => { + event.preventDefault(); + setLoading(1) + + const formData = new FormData(); + formData.append("id", -1); + formData.append("lname", event.target.lname?.value); + formData.append("fname", event.target.fname?.value); + formData.append("categorie", event.target.category?.value); + formData.append("club", event.target.club?.value); + formData.append("genre", event.target.genre?.value); + formData.append("country", event.target.country?.value); + formData.append("birth_date", new Date(event.target.birth_date?.value).toUTCString()); + formData.append("email", event.target.email?.value); + formData.append("role", event.target.role?.value); + formData.append("grade_arbitrage", event.target.grade_arbitrage?.value); + + const send = (formData_) => { + apiAxios.post(`/member`, formData_, { + headers: { + 'Accept': '*/*', + 'Content-Type': 'multipart/form-data', + } + }).then(data => { + toast.success('Profile crée avec succès 🎉'); + navigate(`/admin/member/${data.data}`) + }).catch(e => { + console.log(e.response) + toast.error('Échec de la création du profile 😕 (code: ' + e.response.status + ')'); + }).finally(() => { + if (setLoading) + setLoading(0) + }) + } + + addPhoto(event, formData, send); + } + + return +
+
Nouveau membre
+
+ + + + + + +
+ +
+ + +
+
+ + +
+
+
+
+ +
+
+
+
+
; +} diff --git a/src/main/webapp/src/pages/club/ClubRoot.jsx b/src/main/webapp/src/pages/club/ClubRoot.jsx index 5ddc95a..a59b0d3 100644 --- a/src/main/webapp/src/pages/club/ClubRoot.jsx +++ b/src/main/webapp/src/pages/club/ClubRoot.jsx @@ -3,6 +3,7 @@ import {LoadingProvider} from "../../hooks/useLoading.jsx"; import {MemberPage} from "./member/MemberPage.jsx"; import {useAuth} from "../../hooks/useAuth.jsx"; import {MemberList} from "../MemberList.jsx"; +import {NewMemberPage} from "./member/NewMemberPage.jsx"; export function ClubRoot() { const {userinfo} = useAuth() @@ -35,6 +36,10 @@ export function getClubChildren() { path: 'member/:id', element: }, + { + path: 'member/new', + element: + }, { path: 'b', element:
Club B
diff --git a/src/main/webapp/src/pages/club/member/InformationForm.jsx b/src/main/webapp/src/pages/club/member/InformationForm.jsx index 4fd541c..d84f34e 100644 --- a/src/main/webapp/src/pages/club/member/InformationForm.jsx +++ b/src/main/webapp/src/pages/club/member/InformationForm.jsx @@ -3,9 +3,8 @@ import {useLoadingSwitcher} from "../../../hooks/useLoading.jsx"; import {apiAxios} from "../../../utils/Tools.js"; import {toast} from "react-toastify"; -import imageCompression from "browser-image-compression"; import {BirthDayField, OptionField, TextField} from "../../../components/MemberCustomFiels.jsx"; -import {ClubSelect} from "../../../components/ClubSelect.jsx"; +import {addPhoto} from "../../admin/member/InformationForm.jsx"; export function InformationForm({data}) { const setLoading = useLoadingSwitcher() @@ -25,7 +24,7 @@ export function InformationForm({data}) { formData.append("role", event.target.role?.value); const send = (formData_) => { - apiAxios.post(`/member/club/${data.id}`, formData_, { + apiAxios.put(`/member/club/${data.id}`, formData_, { headers: { 'Accept': '*/*', 'Content-Type': 'multipart/form-data', @@ -40,23 +39,7 @@ export function InformationForm({data}) { setLoading(0) }) } - - const imageFile = event.target.url_photo.files[0]; - if (imageFile) { - console.log(`originalFile size ${imageFile.size / 1024 / 1024} MB`); - const options = { - maxSizeMB: 1, - maxWidthOrHeight: 1920, - useWebWorker: true, - } - imageCompression(imageFile, options).then(compressedFile => { - console.log(`compressedFile size ${compressedFile.size / 1024 / 1024} MB`); // smaller than maxSizeMB - formData.append("photo_data", compressedFile) - send(formData) - }); - } else { - send(formData) - } + addPhoto(event, formData, send); } return
@@ -79,7 +62,7 @@ export function InformationForm({data}) { PRESIDENT: 'Président', TRESORIER: 'Trésorier', SECRETAIRE: 'Secrétaire' - }}/> + }} disabled={true}/>
diff --git a/src/main/webapp/src/pages/club/member/NewMemberPage.jsx b/src/main/webapp/src/pages/club/member/NewMemberPage.jsx new file mode 100644 index 0000000..d5fd913 --- /dev/null +++ b/src/main/webapp/src/pages/club/member/NewMemberPage.jsx @@ -0,0 +1,92 @@ +import {useNavigate} from "react-router-dom"; +import {useLoadingSwitcher} from "../../../hooks/useLoading.jsx"; +import {apiAxios} from "../../../utils/Tools.js"; +import {toast} from "react-toastify"; +import {BirthDayField, OptionField, TextField} from "../../../components/MemberCustomFiels.jsx"; +import {ClubSelect} from "../../../components/ClubSelect.jsx"; +import {addPhoto} from "../../admin/member/InformationForm.jsx"; + +export function NewMemberPage() { + const navigate = useNavigate(); + + return <> +

Page membre

+ +
+
+ +
+
+ +} + +function Form() { + const navigate = useNavigate(); + const setLoading = useLoadingSwitcher() + + const handleSubmit = (event) => { + event.preventDefault(); + setLoading(1) + + const formData = new FormData(); + formData.append("id", -1); + formData.append("lname", event.target.lname?.value); + formData.append("fname", event.target.fname?.value); + formData.append("categorie", event.target.category?.value); + formData.append("genre", event.target.genre?.value); + formData.append("country", event.target.country?.value); + formData.append("birth_date", new Date(event.target.birth_date?.value).toUTCString()); + formData.append("email", event.target.email?.value); + + const send = (formData_) => { + apiAxios.post(`/member/club`, formData_, { + headers: { + 'Accept': '*/*', + 'Content-Type': 'multipart/form-data', + } + }).then(data => { + toast.success('Profile crée avec succès 🎉'); + navigate(`/club/member/${data.data}`) + }).catch(e => { + console.log(e.response) + toast.error('Échec de la création du profile 😕 (code: ' + e.response.status + ')'); + }).finally(() => { + if (setLoading) + setLoading(0) + }) + } + + addPhoto(event, formData, send); + } + + return +
+
Nouveau membre
+
+ + + + + + +
+
+ + +
+
+
+
+ +
+
+
+
+ ; +}