feat: club end main
This commit is contained in:
parent
e1a8c90f3e
commit
6a21bd4735
@ -28,8 +28,6 @@ public class ClubModel {
|
|||||||
|
|
||||||
String country;
|
String country;
|
||||||
|
|
||||||
String shieldURL;
|
|
||||||
|
|
||||||
//@Enumerated(EnumType.STRING)
|
//@Enumerated(EnumType.STRING)
|
||||||
@ElementCollection
|
@ElementCollection
|
||||||
@CollectionTable(name = "club_contact_mapping",
|
@CollectionTable(name = "club_contact_mapping",
|
||||||
@ -37,15 +35,19 @@ public class ClubModel {
|
|||||||
@MapKeyColumn(name = "contact_type")
|
@MapKeyColumn(name = "contact_type")
|
||||||
Map<Contact, String> contact;
|
Map<Contact, String> contact;
|
||||||
|
|
||||||
|
@Lob
|
||||||
|
@Column(length=4096)
|
||||||
String training_location;
|
String training_location;
|
||||||
|
|
||||||
|
@Lob
|
||||||
|
@Column(length=4096)
|
||||||
String training_day_time;
|
String training_day_time;
|
||||||
|
|
||||||
String contact_intern;
|
String contact_intern;
|
||||||
|
|
||||||
String RNA;
|
String RNA;
|
||||||
|
|
||||||
String SIRET;
|
Long SIRET;
|
||||||
|
|
||||||
String no_affiliation;
|
String no_affiliation;
|
||||||
|
|
||||||
|
|||||||
@ -18,13 +18,12 @@ public class ClubEntity {
|
|||||||
private String name;
|
private String name;
|
||||||
private String clubId;
|
private String clubId;
|
||||||
private String country;
|
private String country;
|
||||||
private String shieldURL;
|
|
||||||
private Map<Contact, String> contact;
|
private Map<Contact, String> contact;
|
||||||
private String training_location;
|
private String training_location;
|
||||||
private String training_day_time;
|
private String training_day_time;
|
||||||
private String contact_intern;
|
private String contact_intern;
|
||||||
private String RNA;
|
private String RNA;
|
||||||
private String SIRET;
|
private Long SIRET;
|
||||||
private String no_affiliation;
|
private String no_affiliation;
|
||||||
private boolean international;
|
private boolean international;
|
||||||
|
|
||||||
@ -38,7 +37,6 @@ public class ClubEntity {
|
|||||||
.name(model.getName())
|
.name(model.getName())
|
||||||
.clubId(model.getClubId())
|
.clubId(model.getClubId())
|
||||||
.country(model.getCountry())
|
.country(model.getCountry())
|
||||||
.shieldURL(model.getShieldURL())
|
|
||||||
.contact(model.getContact())
|
.contact(model.getContact())
|
||||||
.training_location(model.getTraining_location())
|
.training_location(model.getTraining_location())
|
||||||
.training_day_time(model.getTraining_day_time())
|
.training_day_time(model.getTraining_day_time())
|
||||||
|
|||||||
@ -1,9 +1,14 @@
|
|||||||
package fr.titionfire.ffsaf.domain.service;
|
package fr.titionfire.ffsaf.domain.service;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||||
|
import com.fasterxml.jackson.core.type.TypeReference;
|
||||||
import fr.titionfire.ffsaf.data.model.ClubModel;
|
import fr.titionfire.ffsaf.data.model.ClubModel;
|
||||||
import fr.titionfire.ffsaf.data.repository.ClubRepository;
|
import fr.titionfire.ffsaf.data.repository.ClubRepository;
|
||||||
|
import fr.titionfire.ffsaf.net2.ServerCustom;
|
||||||
import fr.titionfire.ffsaf.net2.data.SimpleClubModel;
|
import fr.titionfire.ffsaf.net2.data.SimpleClubModel;
|
||||||
|
import fr.titionfire.ffsaf.net2.request.SReqClub;
|
||||||
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.PageResult;
|
import fr.titionfire.ffsaf.utils.PageResult;
|
||||||
import io.quarkus.hibernate.reactive.panache.Panache;
|
import io.quarkus.hibernate.reactive.panache.Panache;
|
||||||
import io.quarkus.hibernate.reactive.panache.PanacheQuery;
|
import io.quarkus.hibernate.reactive.panache.PanacheQuery;
|
||||||
@ -19,8 +24,11 @@ import jakarta.ws.rs.BadRequestException;
|
|||||||
import org.hibernate.reactive.mutiny.Mutiny;
|
import org.hibernate.reactive.mutiny.Mutiny;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import static fr.titionfire.ffsaf.net2.Client_Thread.MAPPER;
|
||||||
|
|
||||||
@WithSession
|
@WithSession
|
||||||
@ApplicationScoped
|
@ApplicationScoped
|
||||||
public class ClubService {
|
public class ClubService {
|
||||||
@ -28,13 +36,18 @@ public class ClubService {
|
|||||||
@Inject
|
@Inject
|
||||||
ClubRepository repository;
|
ClubRepository repository;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
ServerCustom serverCustom;
|
||||||
|
|
||||||
public SimpleClubModel findByIdOptionalClub(long id) throws Throwable {
|
public SimpleClubModel findByIdOptionalClub(long id) throws Throwable {
|
||||||
return VertxContextSupport.subscribeAndAwait(() -> Panache.withTransaction(() -> repository.findById(id).map(SimpleClubModel::fromModel)));
|
return VertxContextSupport.subscribeAndAwait(
|
||||||
|
() -> Panache.withTransaction(() -> repository.findById(id).map(SimpleClubModel::fromModel)));
|
||||||
}
|
}
|
||||||
|
|
||||||
public Collection<SimpleClubModel> findAllClub() throws Throwable {
|
public Collection<SimpleClubModel> findAllClub() throws Throwable {
|
||||||
return VertxContextSupport.subscribeAndAwait(() -> Panache.withTransaction(
|
return VertxContextSupport.subscribeAndAwait(() -> Panache.withTransaction(
|
||||||
() -> repository.findAll().list().map(clubModels -> clubModels.stream().map(SimpleClubModel::fromModel).toList())));
|
() -> repository.findAll().list()
|
||||||
|
.map(clubModels -> clubModels.stream().map(SimpleClubModel::fromModel).toList())));
|
||||||
}
|
}
|
||||||
|
|
||||||
public Uni<List<ClubModel>> getAll() {
|
public Uni<List<ClubModel>> getAll() {
|
||||||
@ -88,15 +101,32 @@ public class ClubService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Uni<String> update(long id, FullClubForm input) {
|
public Uni<String> update(long id, FullClubForm input) {
|
||||||
/*return repository.findById(id)
|
return repository.findById(id).call(m -> Mutiny.fetch(m.getContact()))
|
||||||
.onItem().transformToUni(m -> {
|
.onItem().transformToUni(Unchecked.function(m -> {
|
||||||
|
TypeReference<HashMap<Contact, String>> typeRef = new TypeReference<>() {
|
||||||
|
};
|
||||||
|
|
||||||
m.setName(input.getName());
|
m.setName(input.getName());
|
||||||
m.setCountry(input.getCountry());
|
m.setCountry(input.getCountry());
|
||||||
m.setNo_affiliation(input.getNo_affiliation());
|
|
||||||
m.setShieldURL(input.getShieldURL());
|
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());
|
||||||
|
|
||||||
|
try {
|
||||||
|
m.setContact(MAPPER.readValue(input.getContact(), typeRef));
|
||||||
|
} catch (JsonProcessingException e) {
|
||||||
|
throw new BadRequestException();
|
||||||
|
}
|
||||||
|
}
|
||||||
return Panache.withTransaction(() -> repository.persist(m));
|
return Panache.withTransaction(() -> repository.persist(m));
|
||||||
});*/
|
}))
|
||||||
return Uni.createFrom().nullItem();
|
.invoke(membreModel -> SReqClub.sendIfNeed(serverCustom.clients,
|
||||||
|
SimpleClubModel.fromModel(membreModel)))
|
||||||
|
.map(__ -> "OK");
|
||||||
}
|
}
|
||||||
|
|
||||||
public Uni<Long> add(FullClubForm input) {
|
public Uni<Long> add(FullClubForm input) {
|
||||||
|
|||||||
@ -23,7 +23,7 @@ public class SimpleClubModel {
|
|||||||
if (model == null)
|
if (model == null)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
return new SimpleClubModel(model.getId(), model.getName(), model.getCountry(), model.getShieldURL(),
|
return new SimpleClubModel(model.getId(), model.getName(), model.getCountry(),
|
||||||
model.getNo_affiliation());
|
"/api/club/" + model.getClubId() + "/logo", model.getNo_affiliation());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -21,13 +21,12 @@ public class SimpleClub {
|
|||||||
private String clubId;
|
private String clubId;
|
||||||
private String name;
|
private String name;
|
||||||
private String country;
|
private String country;
|
||||||
private String shieldURL;
|
|
||||||
private Map<Contact, String> contact;
|
private Map<Contact, String> contact;
|
||||||
private String training_location;
|
private String training_location;
|
||||||
private String training_day_time;
|
private String training_day_time;
|
||||||
private String contact_intern;
|
private String contact_intern;
|
||||||
private String RNA;
|
private String RNA;
|
||||||
private String SIRET;
|
private Long SIRET;
|
||||||
private String no_affiliation;
|
private String no_affiliation;
|
||||||
private boolean international;
|
private boolean international;
|
||||||
private HashMap<String, String> contactMap = null;
|
private HashMap<String, String> contactMap = null;
|
||||||
@ -41,7 +40,6 @@ public class SimpleClub {
|
|||||||
.clubId(model.getClubId())
|
.clubId(model.getClubId())
|
||||||
.name(model.getName())
|
.name(model.getName())
|
||||||
.country(model.getCountry())
|
.country(model.getCountry())
|
||||||
.shieldURL(model.getShieldURL())
|
|
||||||
.contact(model.getContact())
|
.contact(model.getContact())
|
||||||
.training_location(model.getTraining_location())
|
.training_location(model.getTraining_location())
|
||||||
.training_day_time(model.getTraining_day_time())
|
.training_day_time(model.getTraining_day_time())
|
||||||
|
|||||||
@ -3,8 +3,10 @@ package fr.titionfire.ffsaf.rest.from;
|
|||||||
import jakarta.ws.rs.FormParam;
|
import jakarta.ws.rs.FormParam;
|
||||||
import jakarta.ws.rs.core.MediaType;
|
import jakarta.ws.rs.core.MediaType;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
import lombok.ToString;
|
||||||
import org.jboss.resteasy.reactive.PartType;
|
import org.jboss.resteasy.reactive.PartType;
|
||||||
|
|
||||||
|
@ToString
|
||||||
@Getter
|
@Getter
|
||||||
public class FullClubForm {
|
public class FullClubForm {
|
||||||
@FormParam("id")
|
@FormParam("id")
|
||||||
@ -13,6 +15,30 @@ public class FullClubForm {
|
|||||||
@FormParam("name")
|
@FormParam("name")
|
||||||
private String name = null;
|
private String name = null;
|
||||||
|
|
||||||
|
@FormParam("country")
|
||||||
|
private String country = null;
|
||||||
|
|
||||||
|
@FormParam("contact")
|
||||||
|
private String contact = null;
|
||||||
|
|
||||||
|
@FormParam("training_location")
|
||||||
|
private String training_location = null;
|
||||||
|
|
||||||
|
@FormParam("training_day_time")
|
||||||
|
private String training_day_time = null;
|
||||||
|
|
||||||
|
@FormParam("contact_intern")
|
||||||
|
private String contact_intern = null;
|
||||||
|
|
||||||
|
@FormParam("rna")
|
||||||
|
private String rna = null;
|
||||||
|
|
||||||
|
@FormParam("siret")
|
||||||
|
private Long siret = null;
|
||||||
|
|
||||||
|
@FormParam("international")
|
||||||
|
private boolean international = false;
|
||||||
|
|
||||||
@FormParam("status")
|
@FormParam("status")
|
||||||
@PartType(MediaType.APPLICATION_OCTET_STREAM)
|
@PartType(MediaType.APPLICATION_OCTET_STREAM)
|
||||||
private byte[] status = new byte[0];
|
private byte[] status = new byte[0];
|
||||||
|
|||||||
@ -54,7 +54,7 @@ public class Utils {
|
|||||||
|
|
||||||
File dirFile = new File(media, dir);
|
File dirFile = new File(media, dir);
|
||||||
if (!dirFile.exists())
|
if (!dirFile.exists())
|
||||||
if (dirFile.mkdirs())
|
if (!dirFile.mkdirs())
|
||||||
throw new IOException("Fail to create directory " + dir);
|
throw new IOException("Fail to create directory " + dir);
|
||||||
|
|
||||||
FilenameFilter filter = (directory, filename) -> filename.startsWith(String.valueOf(id));
|
FilenameFilter filter = (directory, filename) -> filename.startsWith(String.valueOf(id));
|
||||||
|
|||||||
79
src/main/webapp/src/components/Club/ContactEditor.jsx
Normal file
79
src/main/webapp/src/components/Club/ContactEditor.jsx
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
import {useEffect, useReducer, useState} from "react";
|
||||||
|
import {SimpleReducer} from "../../utils/SimpleReducer.jsx";
|
||||||
|
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
|
||||||
|
import {faAdd, faTrashCan} from "@fortawesome/free-solid-svg-icons";
|
||||||
|
|
||||||
|
export function ContactEditor({data}) {
|
||||||
|
const [state, dispatch] = useReducer(SimpleReducer, [])
|
||||||
|
const [out_data, setOutData] = useState({})
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
for (const key in data.contact) {
|
||||||
|
dispatch({type: 'UPDATE_OR_ADD', payload: {id: key, data: data.contact[key]}})
|
||||||
|
}
|
||||||
|
}, [data.contact]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let out_data2 = {}
|
||||||
|
state.forEach(d => {
|
||||||
|
if (d.data !== undefined)
|
||||||
|
out_data2[d.id] = d.data
|
||||||
|
})
|
||||||
|
setOutData(out_data2)
|
||||||
|
}, [state]);
|
||||||
|
|
||||||
|
return <div className="row mb-3">
|
||||||
|
<input name="contact" value={JSON.stringify(out_data)} readOnly hidden/>
|
||||||
|
<span className="input-group-text">Contacts</span>
|
||||||
|
<ul className="list-group form-control">
|
||||||
|
{state.map((d, index) => {
|
||||||
|
if (d.data === undefined)
|
||||||
|
return;
|
||||||
|
|
||||||
|
return <div key={index} 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 className="d-grid gap-2 d-md-flex justify-content-md-end">
|
||||||
|
<button className="btn btn-success" type="button"
|
||||||
|
onClick={() => {
|
||||||
|
let id = null;
|
||||||
|
for (let key in data.contactMap) {
|
||||||
|
let b = false;
|
||||||
|
for (let s of state) {
|
||||||
|
if (s.id === key && s.data !== undefined) b = true;
|
||||||
|
}
|
||||||
|
if (!b) {
|
||||||
|
id = key
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (id !== null)
|
||||||
|
dispatch({type: 'UPDATE_OR_ADD', payload: {id: id, data: ''}})
|
||||||
|
}}>
|
||||||
|
<FontAwesomeIcon icon={faAdd}/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
95
src/main/webapp/src/components/Club/HoraireEditor.jsx
Normal file
95
src/main/webapp/src/components/Club/HoraireEditor.jsx
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
import {useEffect, useReducer, useState} from "react";
|
||||||
|
import {SimpleReducer} from "../../utils/SimpleReducer.jsx";
|
||||||
|
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
|
||||||
|
import {faAdd, faTrashCan} from "@fortawesome/free-solid-svg-icons";
|
||||||
|
|
||||||
|
function timeNumberToSting(nbMin) {
|
||||||
|
return String(Math.floor(nbMin / 60)).padStart(2, '0') + ":" + String(nbMin % 60).padStart(2, '0')
|
||||||
|
}
|
||||||
|
|
||||||
|
function timeStringToNumber(time) {
|
||||||
|
let times = time.split(':');
|
||||||
|
return parseInt(times[0]) * 60 + parseInt(times[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function HoraireEditor({data}) {
|
||||||
|
const [state, dispatch] = useReducer(SimpleReducer, [])
|
||||||
|
const [out_data, setOutData] = useState({})
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (data.training_day_time === null)
|
||||||
|
return
|
||||||
|
JSON.parse(data.training_day_time).forEach((d, index) => {
|
||||||
|
dispatch({type: 'UPDATE_OR_ADD', payload: {id: index, data: d}})
|
||||||
|
})
|
||||||
|
}, [data.training_day_time]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setOutData(state.map(d => {
|
||||||
|
return {day: d.data.day, time_start: d.data.time_start, time_end: d.data.time_end}
|
||||||
|
}))
|
||||||
|
}, [state]);
|
||||||
|
|
||||||
|
const sortHoraire = (a, b) => {
|
||||||
|
if (a.data.day === b.data.day)
|
||||||
|
return a.data.time_start - b.data.time_start;
|
||||||
|
return a.data.day - b.data.day;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <div className="row mb-3">
|
||||||
|
<input name="training_day_time" value={JSON.stringify(out_data)} readOnly hidden/>
|
||||||
|
<span className="input-group-text">Horaires d'entrainements</span>
|
||||||
|
<ul className="list-group form-control">
|
||||||
|
{state.map((d, index) => {
|
||||||
|
return <div key={index} className="input-group">
|
||||||
|
<select className="form-select" aria-label="type" value={d.data.day}
|
||||||
|
onChange={(e) => {
|
||||||
|
d.data.day = Number(e.target.value)
|
||||||
|
dispatch({type: 'UPDATE_OR_ADD', payload: {id: d.id, data: d.data}})
|
||||||
|
dispatch({type: 'SORT', payload: sortHoraire})
|
||||||
|
}}>
|
||||||
|
<option value="0">Lundi</option>
|
||||||
|
<option value="1">Mardi</option>
|
||||||
|
<option value="2">Mercredi</option>
|
||||||
|
<option value="3">Jeudi</option>
|
||||||
|
<option value="4">Vendredi</option>
|
||||||
|
<option value="5">Samedi</option>
|
||||||
|
<option value="6">Dimanche</option>
|
||||||
|
</select>
|
||||||
|
<span className="input-group-text">de</span>
|
||||||
|
<input type="time" className="form-control" value={timeNumberToSting(d.data.time_start)} required
|
||||||
|
onChange={(e) => {
|
||||||
|
d.data.time_start = timeStringToNumber(e.target.value)
|
||||||
|
dispatch({type: 'UPDATE_OR_ADD', payload: {id: d.id, data: d.data}})
|
||||||
|
dispatch({type: 'SORT', payload: sortHoraire})
|
||||||
|
}}/>
|
||||||
|
<span className="input-group-text">à</span>
|
||||||
|
<input type="time" className="form-control" value={timeNumberToSting(d.data.time_end)} required
|
||||||
|
onChange={(e) => {
|
||||||
|
d.data.time_end = timeStringToNumber(e.target.value)
|
||||||
|
dispatch({type: 'UPDATE_OR_ADD', payload: {id: d.id, data: d.data}})
|
||||||
|
dispatch({type: 'SORT', payload: sortHoraire})
|
||||||
|
}}/>
|
||||||
|
<button className="btn btn-danger" type="button"
|
||||||
|
onClick={() => dispatch({type: 'REMOVE', payload: d.id})}>
|
||||||
|
<FontAwesomeIcon icon={faTrashCan}/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
})}
|
||||||
|
<div className="d-grid gap-2 d-md-flex justify-content-md-end">
|
||||||
|
<button className="btn btn-success" type="button"
|
||||||
|
disabled={state.length >= 21}
|
||||||
|
onClick={() => {
|
||||||
|
let maxId = 0;
|
||||||
|
state.forEach((d) => {
|
||||||
|
if (d.id > maxId)
|
||||||
|
maxId = d.id;
|
||||||
|
})
|
||||||
|
dispatch({type: 'UPDATE_OR_ADD', payload: {id: maxId + 1, data: {day: 6, time_start: 0, time_end: 0}}})
|
||||||
|
}}>
|
||||||
|
<FontAwesomeIcon icon={faAdd}/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
@ -1,10 +1,10 @@
|
|||||||
import {useEffect, useReducer, useRef, useState} from "react";
|
import {useEffect, useReducer, useRef, useState} from "react";
|
||||||
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
|
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
|
||||||
import {faPen, faTrashCan} from "@fortawesome/free-solid-svg-icons";
|
import {faAdd, faPen, faTrashCan} from "@fortawesome/free-solid-svg-icons";
|
||||||
import proj4 from "proj4";
|
import proj4 from "proj4";
|
||||||
import {useFetch} from "../../../hooks/useFetch.js";
|
import {useFetch} from "../../hooks/useFetch.js";
|
||||||
import {MapContainer, Marker, TileLayer} from "react-leaflet";
|
import {MapContainer, Marker, TileLayer} from "react-leaflet";
|
||||||
import {SimpleReducer} from "../../../utils/SimpleReducer.jsx";
|
import {SimpleReducer} from "../../utils/SimpleReducer.jsx";
|
||||||
|
|
||||||
export function LocationEditor({data, setModal, sendData}) {
|
export function LocationEditor({data, setModal, sendData}) {
|
||||||
const [state, dispatch] = useReducer(SimpleReducer, [])
|
const [state, dispatch] = useReducer(SimpleReducer, [])
|
||||||
@ -40,64 +40,46 @@ export function LocationEditor({data, setModal, sendData}) {
|
|||||||
}))
|
}))
|
||||||
}, [state]);
|
}, [state]);
|
||||||
|
|
||||||
return <div className="row">
|
return <div className="row mb-3">
|
||||||
<input name="training_location" value={JSON.stringify(out_data)} readOnly hidden/>
|
<input name="training_location" value={JSON.stringify(out_data)} readOnly hidden/>
|
||||||
<span className="input-group-text">Lieux d'entrainements</span>
|
<span className="input-group-text">Lieux d'entrainements</span>
|
||||||
<div className="input-group mb-3">
|
<ul className="list-group form-control">
|
||||||
<ul className="list-group form-control">
|
{state.map((d, index) => {
|
||||||
{state.map((d, index) => {
|
return <div key={index} className="input-group">
|
||||||
return <div key={index} className={"list-group-item d-flex justify-content-between align-items-start"}>
|
<input type="text" className="form-control" value={d.data.text} required
|
||||||
<div className="me-auto">{d.data.text}</div>
|
onChange={(e) => {
|
||||||
<button className="badge btn btn-primary rounded-pill" data-bs-toggle="modal"
|
}}/>
|
||||||
data-bs-target="#EditModal" onClick={e => {
|
|
||||||
e.preventDefault();
|
|
||||||
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>
|
<button className="btn btn-primary" data-bs-toggle="modal"
|
||||||
|
data-bs-target="#EditModal" onClick={e => {
|
||||||
|
e.preventDefault();
|
||||||
|
setModal(d);
|
||||||
|
}}>
|
||||||
|
<FontAwesomeIcon icon={faPen}/></button>
|
||||||
|
<button className="btn btn-danger"
|
||||||
|
onClick={() => dispatch({type: 'REMOVE', payload: d.id})}>
|
||||||
|
<FontAwesomeIcon icon={faTrashCan}/></button>
|
||||||
|
</div>
|
||||||
|
})}
|
||||||
|
<div className="d-grid gap-2 d-md-flex justify-content-md-end">
|
||||||
|
<button className="btn btn-success" data-bs-toggle="modal"
|
||||||
|
disabled={state.length >= 10}
|
||||||
|
data-bs-target="#EditModal" onClick={e => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
let maxId = 0;
|
||||||
|
state.forEach((d) => {
|
||||||
|
if (d.id > maxId)
|
||||||
|
maxId = d.id;
|
||||||
|
})
|
||||||
|
setModal({id: maxId + 1, data: {text: "", lat: undefined, lng: undefined}});
|
||||||
|
}}><FontAwesomeIcon icon={faAdd}/></button>
|
||||||
|
</div>
|
||||||
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
export function LocationEditorModal({modal, sendData}) {
|
export function LocationEditorModal({modal, sendData}) {
|
||||||
return <div className="modal fade" id="EditModal" tabIndex="-1" aria-labelledby="EditModalLabel"
|
|
||||||
aria-hidden="true">
|
|
||||||
<div className="modal-dialog">
|
|
||||||
<form onSubmit={e => sendData.current(e)}>
|
|
||||||
<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">
|
|
||||||
<LocationEditorModalBody modal={modal}/>
|
|
||||||
</div>
|
|
||||||
<div className="modal-footer">
|
|
||||||
<button type="submit" className="btn btn-primary" data-bs-dismiss="modal">Enregistrer</button>
|
|
||||||
<button type="reset" className="btn btn-secondary" data-bs-dismiss="modal">Annuler</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
proj4.defs("EPSG:9794", "+proj=lcc +lat_1=44 +lat_2=49 +lat_0=46.5 +lon_0=3 +x_0=700000 +y_0=6600000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs");
|
|
||||||
|
|
||||||
function convertLambert93ToLatLng(x, y) {
|
|
||||||
const lambertPoint = proj4.toPoint([x, y]);
|
|
||||||
const wgs84Point = proj4("EPSG:9794", "EPSG:4326", lambertPoint);
|
|
||||||
return {lat: wgs84Point.y, lng: wgs84Point.x};
|
|
||||||
}
|
|
||||||
|
|
||||||
function LocationEditorModalBody({modal}) {
|
|
||||||
const [location, setLocation] = useState("")
|
const [location, setLocation] = useState("")
|
||||||
const [locationObj, setLocationObj] = useState({text: "", lng: undefined, lat: undefined})
|
const [locationObj, setLocationObj] = useState({text: "", lng: undefined, lat: undefined})
|
||||||
const [mapPosition, setMapPosition] = useState([46.652195, 2.430226])
|
const [mapPosition, setMapPosition] = useState([46.652195, 2.430226])
|
||||||
@ -107,6 +89,7 @@ function LocationEditorModalBody({modal}) {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (modal.data !== undefined) {
|
if (modal.data !== undefined) {
|
||||||
setLocation(modal.data.text)
|
setLocation(modal.data.text)
|
||||||
|
setLocationObj(modal.data)
|
||||||
}
|
}
|
||||||
}, [modal])
|
}, [modal])
|
||||||
|
|
||||||
@ -143,34 +126,59 @@ function LocationEditorModalBody({modal}) {
|
|||||||
return () => clearTimeout(delayDebounceFn)
|
return () => clearTimeout(delayDebounceFn)
|
||||||
}, [locationObj, modal])
|
}, [locationObj, modal])
|
||||||
|
|
||||||
|
return <div className="modal fade" id="EditModal" tabIndex="-1" aria-labelledby="EditModalLabel"
|
||||||
|
aria-hidden="true">
|
||||||
|
<div className="modal-dialog">
|
||||||
|
<form onSubmit={e => sendData.current(e)}>
|
||||||
|
<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">
|
||||||
|
<input name="id" value={modal.id} readOnly hidden/>
|
||||||
|
<input name="loc_text" value={locationObj.text} readOnly hidden/>
|
||||||
|
<input name="loc_lat" value={locationObj.lat ? locationObj.lat : -142} readOnly hidden/>
|
||||||
|
<input name="loc_lng" value={locationObj.lng ? locationObj.lng : -142} readOnly hidden/>
|
||||||
|
<div className="row">
|
||||||
|
<div className="input-group mb-3">
|
||||||
|
<label className="input-group-text">Adresse</label>
|
||||||
|
<input className="form-control" aria-autocomplete="list" aria-expanded="true" autoComplete="true"
|
||||||
|
placeholder="Chercher une adresse..." aria-label="Recherche" list="addr" value={location}
|
||||||
|
onChange={e => setLocation(e.target.value)}/>
|
||||||
|
<datalist id="addr">
|
||||||
|
{data?.features && data.features.map((d, index) => {
|
||||||
|
return <option key={index}>{d.properties.label}</option>
|
||||||
|
})}
|
||||||
|
</datalist>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
return <>
|
<div className="row">
|
||||||
<input name="id" value={modal.id} readOnly hidden/>
|
<MapContainer ref={map} center={mapPosition} zoom={13} scrollWheelZoom={true} style={{height: "30em"}}>
|
||||||
<input name="loc_text" value={locationObj.text} readOnly hidden/>
|
<TileLayer
|
||||||
<input name="loc_lat" value={locationObj.lat ? locationObj.lat : -142} readOnly hidden/>
|
attribution='© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
|
||||||
<input name="loc_lng" value={locationObj.lng ? locationObj.lng : -142} readOnly hidden/>
|
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
|
||||||
<div className="row">
|
/>
|
||||||
<div className="input-group mb-3">
|
{locationObj.lat !== undefined && <Marker position={mapPosition}/>}
|
||||||
<label className="input-group-text">Adresse</label>
|
</MapContainer>
|
||||||
<input className="form-control" aria-autocomplete="list" aria-expanded="true" autoComplete="true"
|
</div>
|
||||||
placeholder="Chercher une adresse..." aria-label="Recherche" list="addr" value={location}
|
</div>
|
||||||
onChange={e => setLocation(e.target.value)}/>
|
<div className="modal-footer">
|
||||||
<datalist id="addr">
|
<button type="submit" className="btn btn-primary" data-bs-dismiss="modal" disabled={locationObj.lng === undefined}>Enregistrer</button>
|
||||||
{data?.features && data.features.map((d, index) => {
|
<button type="reset" className="btn btn-secondary" data-bs-dismiss="modal">Annuler</button>
|
||||||
return <option key={index}>{d.properties.label}</option>
|
</div>
|
||||||
})}
|
</div>
|
||||||
</datalist>
|
</form>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<div className="row">
|
}
|
||||||
<MapContainer ref={map} center={mapPosition} zoom={13} scrollWheelZoom={true} style={{height: "30em"}}>
|
|
||||||
<TileLayer
|
proj4.defs("EPSG:9794", "+proj=lcc +lat_1=44 +lat_2=49 +lat_0=46.5 +lon_0=3 +x_0=700000 +y_0=6600000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs");
|
||||||
attribution='© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
|
|
||||||
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
|
function convertLambert93ToLatLng(x, y) {
|
||||||
/>
|
const lambertPoint = proj4.toPoint([x, y]);
|
||||||
{locationObj.lat !== undefined && <Marker position={mapPosition}/>}
|
const wgs84Point = proj4("EPSG:9794", "EPSG:4326", lambertPoint);
|
||||||
</MapContainer>
|
return {lat: wgs84Point.y, lng: wgs84Point.x};
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
}
|
}
|
||||||
@ -6,13 +6,12 @@ import {apiAxios} from "../../../utils/Tools.js";
|
|||||||
import {ConfirmDialog} from "../../../components/ConfirmDialog.jsx";
|
import {ConfirmDialog} from "../../../components/ConfirmDialog.jsx";
|
||||||
import {AxiosError} from "../../../components/AxiosError.jsx";
|
import {AxiosError} from "../../../components/AxiosError.jsx";
|
||||||
import {AffiliationCard} from "./AffiliationCard.jsx";
|
import {AffiliationCard} from "./AffiliationCard.jsx";
|
||||||
import {CheckField, CountryList, TextField} from "../../../components/MemberCustomFiels.jsx";
|
import {CountryList, TextField} from "../../../components/MemberCustomFiels.jsx";
|
||||||
|
|
||||||
import {useEffect, useReducer, useRef, useState} from "react";
|
import {useRef, useState} from "react";
|
||||||
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
|
import {LocationEditor, LocationEditorModal} from "../../../components/Club/LocationEditor.jsx";
|
||||||
import {faPen, faTrashCan} from "@fortawesome/free-solid-svg-icons";
|
import {ContactEditor} from "../../../components/Club/ContactEditor.jsx";
|
||||||
import {SimpleReducer} from "../../../utils/SimpleReducer.jsx";
|
import {HoraireEditor} from "../../../components/Club/HoraireEditor.jsx";
|
||||||
import {LocationEditor, LocationEditorModal} from "./LocationEditor.jsx";
|
|
||||||
|
|
||||||
const vite_url = import.meta.env.VITE_URL;
|
const vite_url = import.meta.env.VITE_URL;
|
||||||
|
|
||||||
@ -78,7 +77,7 @@ function InformationForm({data}) {
|
|||||||
const formData = new FormData(event.target);
|
const formData = new FormData(event.target);
|
||||||
|
|
||||||
toast.promise(
|
toast.promise(
|
||||||
apiAxios.post(`/club/${data.id}`, formData),
|
apiAxios.put(`/club/${data.id}`, formData),
|
||||||
{
|
{
|
||||||
pending: "Enregistrement du club en cours",
|
pending: "Enregistrement du club en cours",
|
||||||
success: "Club enregistrée avec succès 🎉",
|
success: "Club enregistrée avec succès 🎉",
|
||||||
@ -90,46 +89,55 @@ function InformationForm({data}) {
|
|||||||
return <>
|
return <>
|
||||||
<form onSubmit={handleSubmit}>
|
<form onSubmit={handleSubmit}>
|
||||||
<div className="card mb-4">
|
<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">Licence n°{data.no_affiliation}</div>
|
||||||
<div className="card-body text-center">
|
<div className="card-body text-center">
|
||||||
|
|
||||||
<TextField name="clubId" text="ClubID" value={data.clubId} disabled={true}/>
|
<TextField name="clubId" text="ClubID" value={data.clubId} disabled={true}/>
|
||||||
<TextField name="name" text="Nom" value={data.name}/>
|
<TextField name="name" text="Nom" value={data.name}/>
|
||||||
<CountryList name="country" text="Pays" value={data.country}/>
|
<CountryList name="country" text="Pays" value={data.country}/>
|
||||||
|
|
||||||
<img
|
<img
|
||||||
src={`${vite_url}/api/club/${data.id}/logo`}
|
src={`${vite_url}/api/club/${data.clubId}/logo`}
|
||||||
alt="avatar"
|
alt="avatar"
|
||||||
className="img-fluid" style={{object_fit: 'contain', maxHeight: '15em'}}/>
|
className="img-fluid" style={{object_fit: 'contain', maxHeight: '15em'}}/>
|
||||||
|
|
||||||
<div className="mb-3">
|
<div className="mb-3">
|
||||||
<div className="input-group">
|
<div className="input-group">
|
||||||
<label className="input-group-text" htmlFor="url_photo">Blason</label>
|
<label className="input-group-text" htmlFor="logo">Blason</label>
|
||||||
<input type="file" className="form-control" id="url_photo" name="url_photo"
|
<input type="file" className="form-control" id="logo" name="logo"
|
||||||
accept=".jpg,.jpeg,.gif,.png,.svg"/>
|
accept=".jpg,.jpeg,.gif,.png,.svg"/>
|
||||||
</div>
|
</div>
|
||||||
<div className="form-text" id="url_photo">Laissez vide pour ne rien changer.</div>
|
<div className="form-text" id="logo">Laissez vide pour ne rien changer.</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="input-group mb-3">
|
<div className="input-group mb-3">
|
||||||
<div className="input-group-text">
|
<div className="input-group-text">
|
||||||
<input type="checkbox" id="inputGroupSelect01" className="form-check-input mt-0" name="international"
|
<input type="checkbox" className="form-check-input mt-0" name="international" id="international"
|
||||||
checked={switchOn} onChange={() => setSwitchOn(!switchOn)}/>
|
checked={switchOn} onChange={() => setSwitchOn(!switchOn)}/>
|
||||||
</div>
|
</div>
|
||||||
<label className="input-group-text" htmlFor="inputGroupSelect01">Club externe</label>
|
<label className="input-group-text" htmlFor="international">Club externe</label>
|
||||||
</div>
|
</div>
|
||||||
{!switchOn && <>
|
{!switchOn && <>
|
||||||
<TextField name="siret" text="SIRET" value={data.siret} type="number"/>
|
<TextField name="siret" text="SIRET" value={data.siret} type="number"/>
|
||||||
<TextField name="rna" text="RNA" value={data.rna} required={false}/>
|
<TextField name="rna" text="RNA" value={data.rna} required={false}/>
|
||||||
|
<TextField name="contact_intern" text="Contact interne" value={data.contact_intern} required={false}/>
|
||||||
|
|
||||||
|
<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=".jpg,.jpeg,.gif,.png,.svg"/>
|
||||||
|
</div>
|
||||||
|
<div className="form-text" id="status">Laissez vide pour ne rien changer.</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<ContactEditor data={data}/>
|
<ContactEditor data={data}/>
|
||||||
<LocationEditor data={data} setModal={setModal} sendData={locationModalCallback}/>
|
<LocationEditor data={data} setModal={setModal} sendData={locationModalCallback}/>
|
||||||
<HoraireEditor data={data}/>
|
<HoraireEditor data={data}/>
|
||||||
|
|
||||||
<TextField name="contact_intern" text="Contact interne" value={data.contact_intern} required={false}/>
|
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
<div className="row">
|
<div className="row mb-3">
|
||||||
<div className="d-grid gap-2 d-md-flex justify-content-md-end">
|
<div className="d-grid gap-2 d-md-flex justify-content-md-end">
|
||||||
<button type="submit" className="btn btn-primary">Enregistrer</button>
|
<button type="submit" className="btn btn-primary">Enregistrer</button>
|
||||||
</div>
|
</div>
|
||||||
@ -140,134 +148,3 @@ function InformationForm({data}) {
|
|||||||
<LocationEditorModal modal={modal} sendData={locationModalCallback}/>
|
<LocationEditorModal modal={modal} sendData={locationModalCallback}/>
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ContactEditor({data}) {
|
|
||||||
const [state, dispatch] = useReducer(SimpleReducer, [])
|
|
||||||
const [out_data, setOutData] = useState({})
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
for (const key in data.contact) {
|
|
||||||
dispatch({type: 'UPDATE_OR_ADD', payload: {id: key, data: data.contact[key]}})
|
|
||||||
}
|
|
||||||
}, [data.contact]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
let out_data2 = {}
|
|
||||||
state.forEach(d => {
|
|
||||||
if (d.data !== undefined)
|
|
||||||
out_data2[d.id] = d.data
|
|
||||||
})
|
|
||||||
setOutData(out_data2)
|
|
||||||
}, [state]);
|
|
||||||
|
|
||||||
return <div className="row">
|
|
||||||
<input name="contact" value={JSON.stringify(out_data)} readOnly hidden/>
|
|
||||||
<span className="input-group-text">Contacts</span>
|
|
||||||
<div className="input-group mb-3">
|
|
||||||
<ul className="list-group form-control">
|
|
||||||
{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>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
function timeNumberToSting(nbMin) {
|
|
||||||
return String(Math.floor(nbMin / 60)).padStart(2, '0') + ":" + String(nbMin % 60).padStart(2, '0')
|
|
||||||
}
|
|
||||||
|
|
||||||
function timeStringToNumber(time) {
|
|
||||||
let times = time.split(':');
|
|
||||||
return parseInt(times[0]) * 60 + parseInt(times[1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function HoraireEditor({data}) {
|
|
||||||
const [state, dispatch] = useReducer(SimpleReducer, [])
|
|
||||||
const [out_data, setOutData] = useState({})
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (data.training_day_time === null)
|
|
||||||
return
|
|
||||||
JSON.parse(data.training_day_time).forEach((d, index) => {
|
|
||||||
dispatch({type: 'UPDATE_OR_ADD', payload: {id: index, data: d}})
|
|
||||||
})
|
|
||||||
}, [data.training_day_time]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setOutData(state.map(d => {
|
|
||||||
return {day: d.data.day, time_start: d.data.time_start, time_end: d.data.time_end}
|
|
||||||
}))
|
|
||||||
}, [state]);
|
|
||||||
|
|
||||||
return <div className="row">
|
|
||||||
<input name="training_day_time" value={JSON.stringify(out_data)} readOnly hidden/>
|
|
||||||
<span className="input-group-text">Horaires d'entrainements</span>
|
|
||||||
<div className="input-group mb-3">
|
|
||||||
<ul className="list-group form-control">
|
|
||||||
{state.map((d, index) => {
|
|
||||||
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.data.day}
|
|
||||||
onChange={(e) => {
|
|
||||||
d.data.day = e.target.value
|
|
||||||
dispatch({type: 'UPDATE_OR_ADD', payload: {id: d.id, data: d.data}})
|
|
||||||
}}>
|
|
||||||
<option value="0">Lundi</option>
|
|
||||||
<option value="1">Mardi</option>
|
|
||||||
<option value="2">Mercredi</option>
|
|
||||||
<option value="3">Jeudi</option>
|
|
||||||
<option value="4">Vendredi</option>
|
|
||||||
<option value="5">Samedi</option>
|
|
||||||
<option value="6">Dimanche</option>
|
|
||||||
</select>
|
|
||||||
<span className="input-group-text">de</span>
|
|
||||||
<input type="time" className="form-control" defaultValue={timeNumberToSting(d.data.time_start)} required
|
|
||||||
onChange={(e) => {
|
|
||||||
d.data.time_start = timeStringToNumber(e.target.value)
|
|
||||||
dispatch({type: 'UPDATE_OR_ADD', payload: {id: d.id, data: d.data}})
|
|
||||||
}}/>
|
|
||||||
<span className="input-group-text">à</span>
|
|
||||||
<input type="time" className="form-control" defaultValue={timeNumberToSting(d.data.time_end)} required
|
|
||||||
onChange={(e) => {
|
|
||||||
d.data.time_end = timeStringToNumber(e.target.value)
|
|
||||||
dispatch({type: 'UPDATE_OR_ADD', payload: {id: d.id, data: d.data}})
|
|
||||||
}}/>
|
|
||||||
<button className="btn btn-danger" type="button"
|
|
||||||
onClick={() => dispatch({type: 'REMOVE', payload: d.id})}><FontAwesomeIcon
|
|
||||||
icon={faTrashCan}/>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
})}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
@ -18,6 +18,8 @@ export function SimpleReducer(datas, action) {
|
|||||||
datas[index] = action.payload
|
datas[index] = action.payload
|
||||||
return [...datas]
|
return [...datas]
|
||||||
}
|
}
|
||||||
|
case 'SORT':
|
||||||
|
return datas.sort(action.payload)
|
||||||
default:
|
default:
|
||||||
throw new Error()
|
throw new Error()
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user