wip: club add map
This commit is contained in:
parent
6c4b01590d
commit
1b74c0a3bd
18
src/main/java/fr/titionfire/BlackPage.java
Normal file
18
src/main/java/fr/titionfire/BlackPage.java
Normal file
@ -0,0 +1,18 @@
|
||||
package fr.titionfire;
|
||||
|
||||
import jakarta.ws.rs.GET;
|
||||
import jakarta.ws.rs.Path;
|
||||
import jakarta.ws.rs.Produces;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
|
||||
@Path("/api")
|
||||
public class BlackPage {
|
||||
|
||||
@GET
|
||||
@Produces(MediaType.TEXT_PLAIN)
|
||||
public Response get() {
|
||||
return Response.noContent().build();
|
||||
}
|
||||
|
||||
}
|
||||
20
src/main/webapp/package-lock.json
generated
20
src/main/webapp/package-lock.json
generated
@ -16,6 +16,7 @@
|
||||
"axios": "^1.6.5",
|
||||
"browser-image-compression": "^2.0.2",
|
||||
"leaflet": "^1.9.4",
|
||||
"proj4": "^2.11.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-leaflet": "^4.2.1",
|
||||
@ -3184,6 +3185,11 @@
|
||||
"yallist": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/mgrs": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/mgrs/-/mgrs-1.0.0.tgz",
|
||||
"integrity": "sha512-awNbTOqCxK1DBGjalK3xqWIstBZgN6fxsMSiXLs9/spqWkF2pAhb2rrYCFSsr1/tT7PhcDGjZndG8SWYn0byYA=="
|
||||
},
|
||||
"node_modules/mime-db": {
|
||||
"version": "1.52.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||
@ -3503,6 +3509,15 @@
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/proj4": {
|
||||
"version": "2.11.0",
|
||||
"resolved": "https://registry.npmjs.org/proj4/-/proj4-2.11.0.tgz",
|
||||
"integrity": "sha512-SasuTkAx8HnWQHfIyhkdUNJorSJqINHAN3EyMWYiQRVorftz9DHz650YraFgczwgtHOxqnfuDxSNv3C8MUnHeg==",
|
||||
"dependencies": {
|
||||
"mgrs": "1.0.0",
|
||||
"wkt-parser": "^1.3.3"
|
||||
}
|
||||
},
|
||||
"node_modules/prop-types": {
|
||||
"version": "15.8.1",
|
||||
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
|
||||
@ -4423,6 +4438,11 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/wkt-parser": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/wkt-parser/-/wkt-parser-1.3.3.tgz",
|
||||
"integrity": "sha512-ZnV3yH8/k58ZPACOXeiHaMuXIiaTk1t0hSUVisbO0t4RjA5wPpUytcxeyiN2h+LZRrmuHIh/1UlrR9e7DHDvTw=="
|
||||
},
|
||||
"node_modules/wrappy": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||
|
||||
@ -18,6 +18,7 @@
|
||||
"axios": "^1.6.5",
|
||||
"browser-image-compression": "^2.0.2",
|
||||
"leaflet": "^1.9.4",
|
||||
"proj4": "^2.11.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-leaflet": "^4.2.1",
|
||||
|
||||
@ -10,37 +10,15 @@ import {CheckField, CountryList, TextField} from "../../../components/MemberCust
|
||||
|
||||
import {MapContainer, Marker, Popup, TileLayer, useMap} from 'react-leaflet'
|
||||
import {ListEditorTest} from "../../../components/ListEditor.jsx";
|
||||
import {useEffect, useReducer, useState} from "react";
|
||||
import {useEffect, useReducer, useRef, useState} from "react";
|
||||
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
|
||||
import {faPen, faTrashCan} from "@fortawesome/free-solid-svg-icons";
|
||||
import proj4 from "proj4";
|
||||
import {SimpleReducer} from "../../../utils/SimpleReducer.jsx";
|
||||
import {LocationEditor} from "./LocationEditor.jsx";
|
||||
|
||||
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() {
|
||||
const {id} = useParams()
|
||||
const navigate = useNavigate();
|
||||
@ -69,12 +47,12 @@ export function ClubPage() {
|
||||
{data
|
||||
? <div>
|
||||
<div className="row">
|
||||
<div className="col-lg-8">
|
||||
<div className="col-lg-9">
|
||||
<LoadingProvider>
|
||||
<InformationForm data={data}/>
|
||||
</LoadingProvider>
|
||||
</div>
|
||||
<div className="col-lg-4">
|
||||
<div className="col-lg-3">
|
||||
<LoadingProvider><AffiliationCard clubData={data}/></LoadingProvider>
|
||||
<div className="col" style={{textAlign: 'right', marginTop: '1em'}}>
|
||||
<button className="btn btn-danger btn-sm" data-bs-toggle="modal"
|
||||
@ -124,95 +102,11 @@ function InformationForm({data}) {
|
||||
<TextField name="contact_intern" text="Contact" value={"contact_intern"}/>
|
||||
<CheckField name="international" text="Club international" value={data.international}/>
|
||||
|
||||
|
||||
<MainMap/>
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
|
||||
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
|
||||
}) {
|
||||
export function ContactEditor({data}) {
|
||||
const [state, dispatch] = useReducer(SimpleReducer, [])
|
||||
|
||||
useEffect(() => {
|
||||
@ -221,73 +115,41 @@ export function ContactEditor({
|
||||
}
|
||||
}, [data.contact]);
|
||||
|
||||
return <>
|
||||
<ul className="list-group">
|
||||
{state.map((d, index) => {
|
||||
if (d.data === undefined)
|
||||
return;
|
||||
return <div className="row">
|
||||
<div className="input-group mb-3">
|
||||
<span className="input-group-text">Contact</span>
|
||||
<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>
|
||||
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>
|
||||
</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]
|
||||
|
||||
function MainMap() {
|
||||
function handleReturnCurrentPosition() {
|
||||
console.log("I have clicked return button!!");
|
||||
//const newCurrentPositionId = uuidv4();
|
||||
//setReturnCurrentPosition(newCurrentPositionId);
|
||||
//console.log(newCurrentPositionId);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<MapContainer center={position} zoom={13} scrollWheelZoom={false} style={{height: "30em", width: "50em"}}>
|
||||
<TileLayer
|
||||
attribution='© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
|
||||
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
|
||||
/>
|
||||
<Marker position={position}>
|
||||
<Popup>
|
||||
A pretty CSS3 popup. <br/> Easily customizable.
|
||||
</Popup>
|
||||
</Marker>
|
||||
</MapContainer>
|
||||
<button className="btn btn-primary" onClick={handleReturnCurrentPosition}>Return current position</button>
|
||||
<SearchBarMap/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
function SearchBarMap() {
|
||||
return <>
|
||||
</>
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
162
src/main/webapp/src/pages/admin/club/LocationEditor.jsx
Normal file
162
src/main/webapp/src/pages/admin/club/LocationEditor.jsx
Normal file
@ -0,0 +1,162 @@
|
||||
import {useEffect, useReducer, useRef, useState} from "react";
|
||||
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
|
||||
import {faPen, faTrashCan} from "@fortawesome/free-solid-svg-icons";
|
||||
import proj4 from "proj4";
|
||||
import {useFetch} from "../../../hooks/useFetch.js";
|
||||
import {MapContainer, Marker, TileLayer} from "react-leaflet";
|
||||
import {SimpleReducer} from "../../../utils/SimpleReducer.jsx";
|
||||
|
||||
export function LocationEditor({data}) {
|
||||
const [modal, setModal] = useState({id: -1})
|
||||
const [state, dispatch] = useReducer(SimpleReducer, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (data.training_location === null)
|
||||
return
|
||||
JSON.parse(data.training_location).forEach((d, index) => {
|
||||
dispatch({type: 'UPDATE_OR_ADD', payload: {id: index, data: d}})
|
||||
})
|
||||
}, [data.training_location]);
|
||||
|
||||
const sendAffiliation = (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const formData = new FormData(e.target);
|
||||
dispatch({
|
||||
type: 'UPDATE_OR_ADD', payload: {
|
||||
id: Number(formData.get('id')),
|
||||
data: {
|
||||
text: formData.get('loc_text'),
|
||||
lat: Number(formData.get('loc_lat')),
|
||||
lng: Number(formData.get('loc_lng'))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return <div className="row">
|
||||
<div className="input-group mb-3">
|
||||
<span className="input-group-text">Lieux d'entrainement</span>
|
||||
<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="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">
|
||||
<form onSubmit={e => sendAffiliation(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>
|
||||
</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 [locationObj, setLocationObj] = useState({text: "", lng: undefined, lat: undefined})
|
||||
const [mapPosition, setMapPosition] = useState([46.652195, 2.430226])
|
||||
const {data, error, refresh} = useFetch(``)
|
||||
const map = useRef(null)
|
||||
|
||||
useEffect(() => {
|
||||
if (modal.data !== undefined) {
|
||||
setLocation(modal.data.text)
|
||||
}
|
||||
}, [modal])
|
||||
|
||||
useEffect(() => {
|
||||
if (location.length < 3)
|
||||
return
|
||||
|
||||
const delayDebounceFn = setTimeout(() => {
|
||||
refresh(`https://api-adresse.data.gouv.fr/search/?q=${encodeURI(location)}&type=housenumber&autocomplete=1`)
|
||||
}, 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})
|
||||
} else {
|
||||
setLocationObj({text: "", lng: undefined, lat: undefined})
|
||||
}
|
||||
}, [data]);
|
||||
|
||||
useEffect(() => {
|
||||
if (locationObj.lat !== undefined) {
|
||||
setMapPosition([locationObj.lat, locationObj.lng])
|
||||
map.current?.setView([locationObj.lat, locationObj.lng], 19)
|
||||
} else {
|
||||
map.current?.setView([46.652195, 2.430226], 5)
|
||||
}
|
||||
|
||||
const delayDebounceFn = setTimeout(() => {
|
||||
map.current.invalidateSize(false);
|
||||
}, 300)
|
||||
return () => clearTimeout(delayDebounceFn)
|
||||
}, [locationObj, modal])
|
||||
|
||||
|
||||
return <>
|
||||
<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>
|
||||
|
||||
<div className="row">
|
||||
<MapContainer ref={map} center={mapPosition} zoom={13} scrollWheelZoom={true} style={{height: "30em"}}>
|
||||
<TileLayer
|
||||
attribution='© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
|
||||
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
|
||||
/>
|
||||
{locationObj.lat !== undefined && <Marker position={mapPosition}/>}
|
||||
</MapContainer>
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
24
src/main/webapp/src/utils/SimpleReducer.jsx
Normal file
24
src/main/webapp/src/utils/SimpleReducer.jsx
Normal file
@ -0,0 +1,24 @@
|
||||
export 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()
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user