wip: addr field
This commit is contained in:
parent
76d7a28678
commit
6c4b01590d
@ -5,6 +5,7 @@ import fr.titionfire.ffsaf.domain.service.ClubService;
|
|||||||
import fr.titionfire.ffsaf.net2.data.SimpleClubModel;
|
import fr.titionfire.ffsaf.net2.data.SimpleClubModel;
|
||||||
import fr.titionfire.ffsaf.rest.data.SimpleClub;
|
import fr.titionfire.ffsaf.rest.data.SimpleClub;
|
||||||
import fr.titionfire.ffsaf.rest.from.FullClubForm;
|
import fr.titionfire.ffsaf.rest.from.FullClubForm;
|
||||||
|
import fr.titionfire.ffsaf.utils.Contact;
|
||||||
import fr.titionfire.ffsaf.utils.GroupeUtils;
|
import fr.titionfire.ffsaf.utils.GroupeUtils;
|
||||||
import fr.titionfire.ffsaf.utils.PageResult;
|
import fr.titionfire.ffsaf.utils.PageResult;
|
||||||
import fr.titionfire.ffsaf.utils.Utils;
|
import fr.titionfire.ffsaf.utils.Utils;
|
||||||
@ -76,7 +77,9 @@ public class ClubEndpoints {
|
|||||||
@RolesAllowed({"federation_admin", "club_president", "club_secretaire", "club_respo_intra"})
|
@RolesAllowed({"federation_admin", "club_president", "club_secretaire", "club_respo_intra"})
|
||||||
@Produces(MediaType.APPLICATION_JSON)
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
public Uni<SimpleClub> getById(@PathParam("id") long id) {
|
public Uni<SimpleClub> getById(@PathParam("id") long id) {
|
||||||
return clubService.getById(id).onItem().invoke(checkPerm).map(SimpleClub::fromModel);
|
return clubService.getById(id).onItem().invoke(checkPerm).map(SimpleClub::fromModel).invoke(m -> {
|
||||||
|
m.setContactMap(Contact.toSite());
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@PUT
|
@PUT
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import lombok.Builder;
|
|||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.ToString;
|
import lombok.ToString;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
@ -29,6 +30,7 @@ public class SimpleClub {
|
|||||||
private String SIRET;
|
private String SIRET;
|
||||||
private String no_affiliation;
|
private String no_affiliation;
|
||||||
private boolean international;
|
private boolean international;
|
||||||
|
private HashMap<String, String> contactMap = null;
|
||||||
|
|
||||||
public static SimpleClub fromModel(ClubModel model) {
|
public static SimpleClub fromModel(ClubModel model) {
|
||||||
if (model == null)
|
if (model == null)
|
||||||
|
|||||||
@ -2,6 +2,9 @@ package fr.titionfire.ffsaf.utils;
|
|||||||
|
|
||||||
import io.quarkus.runtime.annotations.RegisterForReflection;
|
import io.quarkus.runtime.annotations.RegisterForReflection;
|
||||||
|
|
||||||
|
import javax.naming.ldap.HasControls;
|
||||||
|
import java.util.HashMap;
|
||||||
|
|
||||||
@RegisterForReflection
|
@RegisterForReflection
|
||||||
public enum Contact {
|
public enum Contact {
|
||||||
COURRIEL("Courriel"),
|
COURRIEL("Courriel"),
|
||||||
@ -20,8 +23,11 @@ public enum Contact {
|
|||||||
this.name = name;
|
this.name = name;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
public static HashMap<String, String> toSite() {
|
||||||
public String toString() {
|
HashMap<String, String> map = new HashMap<>();
|
||||||
return name;
|
for (Contact contact : Contact.values()) {
|
||||||
|
map.put(contact.toString(), contact.name);
|
||||||
|
}
|
||||||
|
return map;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
83
src/main/webapp/src/components/ListEditor.jsx
Normal file
83
src/main/webapp/src/components/ListEditor.jsx
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
import {useEffect, useReducer, useState} from "react";
|
||||||
|
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
|
||||||
|
import {faPen, faTrashCan} from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import {AxiosError} from "./AxiosError.jsx";
|
||||||
|
|
||||||
|
function SimpleReducer(datas, action) {
|
||||||
|
switch (action.type) {
|
||||||
|
case 'ADD':
|
||||||
|
return [
|
||||||
|
...datas,
|
||||||
|
action.payload
|
||||||
|
]
|
||||||
|
case 'REMOVE':
|
||||||
|
return datas.filter(data => data.id !== action.payload)
|
||||||
|
case 'UPDATE_OR_ADD':
|
||||||
|
const index = datas.findIndex(data => data.id === action.payload.id)
|
||||||
|
if (index === -1) {
|
||||||
|
return [
|
||||||
|
...datas,
|
||||||
|
action.payload
|
||||||
|
]
|
||||||
|
} else {
|
||||||
|
datas[index] = action.payload
|
||||||
|
return [...datas]
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
throw new Error()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ListEditorTest() {
|
||||||
|
const [html, dispatch] = ListEditor(ListHTML)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
dispatch({type: 'UPDATE_OR_ADD', payload: {id: 1, content: "data in"}})
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return html
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ListEditor(ListItem) {
|
||||||
|
const [modal, setModal] = useState({id: -1})
|
||||||
|
const [state, dispatch] = useReducer(SimpleReducer, [])
|
||||||
|
|
||||||
|
const sendAffiliation = (e) => {
|
||||||
|
|
||||||
|
dispatch({type: 'UPDATE_OR_ADD', payload: e})
|
||||||
|
}
|
||||||
|
|
||||||
|
return [<>
|
||||||
|
<ul className="list-group">
|
||||||
|
{state.map((d, index) => {
|
||||||
|
return <div key={index} className={"list-group-item d-flex justify-content-between align-items-start"}>
|
||||||
|
<ListItem data={d}/>
|
||||||
|
<button className="badge btn btn-primary rounded-pill" data-bs-toggle="modal"
|
||||||
|
data-bs-target="#EditModal" onClick={_ => setModal(d)}>
|
||||||
|
<FontAwesomeIcon icon={faPen}/></button>
|
||||||
|
<button className="badge btn btn-danger rounded-pill"
|
||||||
|
onClick={() => dispatch({type: 'REMOVE', payload: d.id})}>
|
||||||
|
<FontAwesomeIcon icon={faTrashCan}/></button>
|
||||||
|
</div>
|
||||||
|
})}
|
||||||
|
</ul>
|
||||||
|
<div className="modal fade" id="EditModal" tabIndex="-1" aria-labelledby="EditModalLabel"
|
||||||
|
aria-hidden="true">
|
||||||
|
<div className="modal-dialog">
|
||||||
|
<div className="modal-content">
|
||||||
|
<form onSubmit={e => sendAffiliation(e, dispatch)}>
|
||||||
|
<input name="id" value={modal.id} readOnly hidden/>
|
||||||
|
</form>
|
||||||
|
<ModalContent affiliation={modalAffiliation} dispatch={dispatch}/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
, dispatch]
|
||||||
|
}
|
||||||
|
|
||||||
|
function ListHTML({
|
||||||
|
data
|
||||||
|
}) {
|
||||||
|
return <div className="me-auto">{data.content}</div>
|
||||||
|
}
|
||||||
@ -4,9 +4,7 @@ import {useEffect, useReducer, useState} from "react";
|
|||||||
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
|
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
|
||||||
import {faPen} from "@fortawesome/free-solid-svg-icons";
|
import {faPen} from "@fortawesome/free-solid-svg-icons";
|
||||||
import {AxiosError} from "../../../components/AxiosError.jsx";
|
import {AxiosError} from "../../../components/AxiosError.jsx";
|
||||||
import {CheckField, TextField} from "../../../components/MemberCustomFiels.jsx";
|
|
||||||
import {apiAxios, getSaison} from "../../../utils/Tools.js";
|
import {apiAxios, getSaison} from "../../../utils/Tools.js";
|
||||||
import {Input} from "../../../components/Input.jsx";
|
|
||||||
import {toast} from "react-toastify";
|
import {toast} from "react-toastify";
|
||||||
|
|
||||||
function affiliationReducer(affiliation, action) {
|
function affiliationReducer(affiliation, action) {
|
||||||
|
|||||||
@ -9,9 +9,38 @@ import {AffiliationCard} from "./AffiliationCard.jsx";
|
|||||||
import {CheckField, CountryList, TextField} from "../../../components/MemberCustomFiels.jsx";
|
import {CheckField, CountryList, TextField} from "../../../components/MemberCustomFiels.jsx";
|
||||||
|
|
||||||
import {MapContainer, Marker, Popup, TileLayer, useMap} from 'react-leaflet'
|
import {MapContainer, Marker, Popup, TileLayer, useMap} from 'react-leaflet'
|
||||||
|
import {ListEditorTest} from "../../../components/ListEditor.jsx";
|
||||||
|
import {useEffect, useReducer, useState} from "react";
|
||||||
|
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
|
||||||
|
import {faPen, faTrashCan} from "@fortawesome/free-solid-svg-icons";
|
||||||
|
|
||||||
const vite_url = import.meta.env.VITE_URL;
|
const vite_url = import.meta.env.VITE_URL;
|
||||||
|
|
||||||
|
function SimpleReducer(datas, action) {
|
||||||
|
switch (action.type) {
|
||||||
|
case 'ADD':
|
||||||
|
return [
|
||||||
|
...datas,
|
||||||
|
action.payload
|
||||||
|
]
|
||||||
|
case 'REMOVE':
|
||||||
|
return datas.filter(data => data.id !== action.payload)
|
||||||
|
case 'UPDATE_OR_ADD':
|
||||||
|
const index = datas.findIndex(data => data.id === action.payload.id)
|
||||||
|
if (index === -1) {
|
||||||
|
return [
|
||||||
|
...datas,
|
||||||
|
action.payload
|
||||||
|
]
|
||||||
|
} else {
|
||||||
|
datas[index] = action.payload
|
||||||
|
return [...datas]
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
throw new Error()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function ClubPage() {
|
export function ClubPage() {
|
||||||
const {id} = useParams()
|
const {id} = useParams()
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
@ -41,15 +70,19 @@ export function ClubPage() {
|
|||||||
? <div>
|
? <div>
|
||||||
<div className="row">
|
<div className="row">
|
||||||
<div className="col-lg-8">
|
<div className="col-lg-8">
|
||||||
<LoadingProvider><InformationForm data={data}/></LoadingProvider>
|
<LoadingProvider>
|
||||||
|
<InformationForm data={data}/>
|
||||||
|
</LoadingProvider>
|
||||||
</div>
|
</div>
|
||||||
<div className="col-lg-4">
|
<div className="col-lg-4">
|
||||||
<LoadingProvider><AffiliationCard clubData={data}/></LoadingProvider>
|
<LoadingProvider><AffiliationCard clubData={data}/></LoadingProvider>
|
||||||
<div className="col" style={{textAlign: 'right', marginTop: '1em'}}>
|
<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 compte
|
<button className="btn btn-danger btn-sm" data-bs-toggle="modal"
|
||||||
|
data-bs-target="#confirm-delete">Supprimer le compte
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<ConfirmDialog title="Supprimer le compte" message="Êtes-vous sûr de vouloir supprimer ce compte ?"
|
<ConfirmDialog title="Supprimer le compte"
|
||||||
|
message="Êtes-vous sûr de vouloir supprimer ce compte ?"
|
||||||
onConfirm={handleRm}/>
|
onConfirm={handleRm}/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -83,18 +116,150 @@ function InformationForm({data}) {
|
|||||||
<div className="form-text" id="url_photo">Laissez vide pour ne rien changer.</div>
|
<div className="form-text" id="url_photo">Laissez vide pour ne rien changer.</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<TextField name="contact" text="Contact" value={data.contact}/>
|
<ContactEditor data={data}/>
|
||||||
|
<LocationEditor data={data}/>
|
||||||
|
|
||||||
<TextField name="training_location" text="Lieux d'entrainement" value={data.training_location}/>
|
<TextField name="training_location" text="Lieux d'entrainement" value={data.training_location}/>
|
||||||
<TextField name="training_day_time" text="Horaire d'entrainement" value={data.training_day_time}/>
|
<TextField name="training_day_time" text="Horaire d'entrainement" value={data.training_day_time}/>
|
||||||
<TextField name="contact_intern" text="Contact" value={"contact_intern"}/>
|
<TextField name="contact_intern" text="Contact" value={"contact_intern"}/>
|
||||||
<CheckField name="international" text="Club international" value={data.international}/>
|
<CheckField name="international" text="Club international" value={data.international}/>
|
||||||
|
|
||||||
|
|
||||||
<MainMap/>
|
<MainMap/>
|
||||||
</div>
|
</div>
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
// https://annuaire-entreprises.data.gouv.fr/entreprise/la-mesnie-des-chevaliers-de-st-georges-et-de-st-michel-500213731
|
|
||||||
|
export function LocationEditor({data}) {
|
||||||
|
const [modal, setModal] = useState({id: -1})
|
||||||
|
const [state, dispatch] = useReducer(SimpleReducer, [])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
JSON.parse(data.training_location).forEach((d, index) => {
|
||||||
|
dispatch({type: 'UPDATE_OR_ADD', payload: {id: index, data: d}})
|
||||||
|
})
|
||||||
|
}, [data.training_location]);
|
||||||
|
|
||||||
|
const sendAffiliation = (e) => {
|
||||||
|
|
||||||
|
dispatch({type: 'UPDATE_OR_ADD', payload: e})
|
||||||
|
}
|
||||||
|
|
||||||
|
return <>
|
||||||
|
<ul className="list-group">
|
||||||
|
{state.map((d, index) => {
|
||||||
|
return <div key={index} className={"list-group-item d-flex justify-content-between align-items-start"}>
|
||||||
|
<div className="me-auto">{d.data.text}</div>
|
||||||
|
<button className="badge btn btn-primary rounded-pill" data-bs-toggle="modal"
|
||||||
|
data-bs-target="#EditModal" onClick={_ => setModal(d)}>
|
||||||
|
<FontAwesomeIcon icon={faPen}/></button>
|
||||||
|
<button className="badge btn btn-danger rounded-pill"
|
||||||
|
onClick={() => dispatch({type: 'REMOVE', payload: d.id})}>
|
||||||
|
<FontAwesomeIcon icon={faTrashCan}/></button>
|
||||||
|
</div>
|
||||||
|
})}
|
||||||
|
</ul>
|
||||||
|
<div className="modal fade" id="EditModal" tabIndex="-1" aria-labelledby="EditModalLabel"
|
||||||
|
aria-hidden="true">
|
||||||
|
<div className="modal-dialog">
|
||||||
|
<div className="modal-content">
|
||||||
|
<div className="modal-header">
|
||||||
|
<h1 className="modal-title fs-5" id="EditModalLabel">Edition de l'adresse</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">
|
||||||
|
<form onSubmit={e => sendAffiliation(e, dispatch)}>
|
||||||
|
<input name="id" value={modal.id} readOnly hidden/>
|
||||||
|
</form>
|
||||||
|
<Autoc/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
|
||||||
|
function Autoc() {
|
||||||
|
const [location, setLocation] = useState("9 rue Gracchus")
|
||||||
|
const {
|
||||||
|
data,
|
||||||
|
error,
|
||||||
|
refresh
|
||||||
|
} = useFetch(`https://api-adresse.data.gouv.fr/search/?q=${encodeURI(location)}&type=housenumber&autocomplete=1`)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
refresh(`https://api-adresse.data.gouv.fr/search/?q=${encodeURI(location)}&type=housenumber&autocomplete=1`)
|
||||||
|
}, [location]);
|
||||||
|
|
||||||
|
return <>
|
||||||
|
<div className="form-group">
|
||||||
|
<label htmlFor="input-datalist">Timezone</label>
|
||||||
|
<input role="combobox" aria-autocomplete="list" aria-expanded="false" autoComplete="off"
|
||||||
|
placeholder="Chercher une adresse..." aria-label="Recherche"
|
||||||
|
className="form-control" list="addr" value={location}
|
||||||
|
onChange={e => setLocation(e.target.value)}/>
|
||||||
|
<datalist id="addr">
|
||||||
|
{data && data.features.map((d, index) => {
|
||||||
|
return <option key={index}>{d.properties.label}</option>
|
||||||
|
})}
|
||||||
|
</datalist>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ContactEditor({
|
||||||
|
data
|
||||||
|
}) {
|
||||||
|
const [state, dispatch] = useReducer(SimpleReducer, [])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
for (const key in data.contact) {
|
||||||
|
dispatch({type: 'UPDATE_OR_ADD', payload: {id: key, data: data.contact[key]}})
|
||||||
|
}
|
||||||
|
}, [data.contact]);
|
||||||
|
|
||||||
|
return <>
|
||||||
|
<ul className="list-group">
|
||||||
|
{state.map((d, index) => {
|
||||||
|
if (d.data === undefined)
|
||||||
|
return;
|
||||||
|
|
||||||
|
return <div key={index} className={"list-group-item d-flex justify-content-between align-items-start"}>
|
||||||
|
<div className="input-group">
|
||||||
|
<select className="form-select" aria-label="type" defaultValue={d.id}
|
||||||
|
onChange={(e) => {
|
||||||
|
dispatch({type: 'UPDATE_OR_ADD', payload: {id: d.id, data: undefined}})
|
||||||
|
dispatch({type: 'UPDATE_OR_ADD', payload: {id: e.target.value, data: d.data}})
|
||||||
|
}}>
|
||||||
|
{Object.keys(data.contactMap).map((key, _) => {
|
||||||
|
let b = false;
|
||||||
|
for (let s of state) {
|
||||||
|
if (s.id === key && s.data !== undefined) b = true;
|
||||||
|
}
|
||||||
|
return (<option key={key} value={key} disabled={b}>{data.contactMap[key]}</option>)
|
||||||
|
})}
|
||||||
|
</select>
|
||||||
|
<input type="text" className="form-control" defaultValue={d.data} required
|
||||||
|
onChange={(e) => {
|
||||||
|
dispatch({type: 'UPDATE_OR_ADD', payload: {id: d.id, data: e.target.value}})
|
||||||
|
}}/>
|
||||||
|
<button className="btn btn-danger" type="button"
|
||||||
|
onClick={() => dispatch({type: 'REMOVE', payload: d.id})}><FontAwesomeIcon
|
||||||
|
icon={faTrashCan}/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
})}
|
||||||
|
</ul>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://annuaire-entreprises.data.gouv.fr/entreprise/la-mesnie-des-chevaliers-de-st-georges-et-de-st-michel-500213731
|
||||||
const position = [51.505, -0.09]
|
const position = [51.505, -0.09]
|
||||||
|
|
||||||
function MainMap() {
|
function MainMap() {
|
||||||
function handleReturnCurrentPosition() {
|
function handleReturnCurrentPosition() {
|
||||||
console.log("I have clicked return button!!");
|
console.log("I have clicked return button!!");
|
||||||
@ -105,7 +270,7 @@ function MainMap() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<MapContainer center={position} zoom={13} scrollWheelZoom={false} style={{height: "30em", width: "50em"}}>
|
<MapContainer center={position} zoom={13} scrollWheelZoom={false} style={{height: "30em", width: "50em"}}>
|
||||||
<TileLayer
|
<TileLayer
|
||||||
attribution='© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
|
attribution='© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
|
||||||
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
|
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user