418 lines
22 KiB
JavaScript
418 lines
22 KiB
JavaScript
import {useNavigate, useParams} from "react-router-dom";
|
|
import {useLoadingSwitcher} from "../../hooks/useLoading.jsx";
|
|
import {useFetch} from "../../hooks/useFetch.js";
|
|
import {AxiosError} from "../../components/AxiosError.jsx";
|
|
import {CheckField, OptionField, TextField} from "../../components/MemberCustomFiels.jsx";
|
|
import {ClubSelect} from "../../components/ClubSelect.jsx";
|
|
import {ConfirmDialog} from "../../components/ConfirmDialog.jsx";
|
|
import {toast} from "react-toastify";
|
|
import {apiAxios, getToastMessage} from "../../utils/Tools.js";
|
|
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";
|
|
import {Trans, useTranslation} from "react-i18next";
|
|
|
|
export function CompetitionEdit() {
|
|
const {id} = useParams()
|
|
const navigate = useNavigate();
|
|
const {t} = useTranslation();
|
|
|
|
const setLoading = useLoadingSwitcher()
|
|
const {data, refresh, error} = useFetch(`/competition/${id}?light=false`, setLoading, 1)
|
|
|
|
const handleRm = () => {
|
|
toast.promise(
|
|
apiAxios.delete(`/competition/${id}`), getToastMessage("comp.toast.del")
|
|
).then(_ => {
|
|
navigate("/competition")
|
|
})
|
|
}
|
|
|
|
useEffect(() => {
|
|
refresh(`/competition/${id}?light=false`)
|
|
}, [id]);
|
|
|
|
return <>
|
|
<button type="button" className="btn btn-link" onClick={() => navigate("/competition")}>
|
|
{t('back')}
|
|
</button>
|
|
<div>
|
|
{data
|
|
? <div className="">
|
|
|
|
<Content data={data} refresh={refresh}/>
|
|
|
|
{data.id !== null && <button style={{marginBottom: "1.5em", width: "100%"}} className="btn btn-primary"
|
|
onClick={_ => navigate(`/competition/${data.id}/register?type=${data.registerMode}`)}>
|
|
{t('comp.modifierLesParticipants')}</button>}
|
|
|
|
{data.id !== null && (data.system === "SAFCA" || data.system === "INTERNAL") &&
|
|
<ContentSAFCAAndInternal data2={data} type={data.system}/>}
|
|
|
|
{data.id !== null && <>
|
|
<div className="col" style={{textAlign: 'right', marginTop: '1em'}}>
|
|
<button className="btn btn-danger btn-sm" data-bs-toggle="modal"
|
|
data-bs-target="#confirm-delete">{t('comp.supprimerLaCompétition')}
|
|
</button>
|
|
</div>
|
|
<ConfirmDialog title={t('comp.supprimerLaCompétition')}
|
|
message={t('comp.supprimerLaCompétition.msg')}
|
|
onConfirm={handleRm}/>
|
|
</>}
|
|
</div>
|
|
: error && <AxiosError error={error}/>
|
|
}
|
|
</div>
|
|
</>
|
|
}
|
|
|
|
function ContentSAFCAAndInternal({data2, type = "SAFCA"}) {
|
|
const getDataPath = type === "SAFCA" ? `/competition/${data2.id}/safcaData` : `/competition/${data2.id}/internalData`
|
|
const setDataPath = type === "SAFCA" ? "/competition/safcaData" : "/competition/internalData"
|
|
|
|
const setLoading = useLoadingSwitcher()
|
|
const {data, error} = useFetch(getDataPath, setLoading, 1)
|
|
const {t} = useTranslation();
|
|
|
|
const [state, dispatch] = useReducer(SimpleReducer, [])
|
|
const [state2, dispatch2] = useReducer(SimpleReducer, [])
|
|
|
|
useEffect(() => {
|
|
if (data === null)
|
|
return
|
|
if (data.admin !== null) {
|
|
let index = 0
|
|
for (const d of data.admin) {
|
|
dispatch({type: 'UPDATE_OR_ADD', payload: {id: index, data: d}})
|
|
index++
|
|
}
|
|
}
|
|
if (data.table !== null) {
|
|
let index = 0
|
|
for (const d of data.table) {
|
|
dispatch2({type: 'UPDATE_OR_ADD', payload: {id: index, data: d}})
|
|
index++
|
|
}
|
|
}
|
|
}, [data]);
|
|
|
|
const handleSubmit = (event) => {
|
|
event.preventDefault();
|
|
|
|
const out = {}
|
|
out['id'] = (data2.id === "") ? null : data2.id
|
|
out['show_blason'] = event.target.show_blason.checked
|
|
out['show_flag'] = event.target.show_flag.checked
|
|
out['admin'] = state.map(d => d.data)
|
|
out['table'] = state2.map(d => d.data)
|
|
|
|
toast.promise(apiAxios.post(setDataPath, out), getToastMessage("comp.toast.params"))
|
|
}
|
|
|
|
return <>
|
|
{data ?
|
|
<form onSubmit={handleSubmit}>
|
|
<input name="id" value={data2.id || ""} readOnly hidden/>
|
|
<div className="card mb-4">
|
|
<div className="card-header">{t('configuration')}</div>
|
|
<div className="card-body text-center">
|
|
|
|
<div className="input-group mb-1">
|
|
<div className="input-group-text">
|
|
<input className="form-check-input mt-0" type="checkbox" aria-label={t('comp.aff.blason')}
|
|
defaultChecked={data.show_blason} name="show_blason"/>
|
|
</div>
|
|
<span className="input-group-text">{t('comp.aff.blason')}</span>
|
|
</div>
|
|
|
|
<div className="input-group mb-3">
|
|
<div className="input-group-text">
|
|
<input className="form-check-input mt-0" type="checkbox" aria-label={t('comp.aff.flag')}
|
|
defaultChecked={data.show_flag} name="show_flag"/>
|
|
</div>
|
|
<span className="input-group-text">{t('comp.aff.flag')}</span>
|
|
</div>
|
|
|
|
<span className="input-group-text">{t('administrateur')}</span>
|
|
<ul className="list-group form-control">
|
|
{state.map((d, index) => {
|
|
return <div key={index} className="input-group">
|
|
<input type="text" className="form-control" value={d.data} required
|
|
onChange={(e) => {
|
|
dispatch({type: 'UPDATE_OR_ADD', payload: {id: d.id, data: e.target.value}})
|
|
}}/>
|
|
<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" onClick={e => {
|
|
e.preventDefault();
|
|
|
|
let maxId = 0;
|
|
state.forEach((d) => {
|
|
if (d.id > maxId)
|
|
maxId = d.id;
|
|
})
|
|
|
|
dispatch({type: 'UPDATE_OR_ADD', payload: {id: maxId + 1, data: ""}})
|
|
}}><FontAwesomeIcon icon={faAdd}/></button>
|
|
</div>
|
|
</ul>
|
|
|
|
<div className="mb-3"></div>
|
|
<span className="input-group-text">{t('secrétariatsDeLice')}</span>
|
|
<ul className="list-group form-control">
|
|
{state2.map((d, index) => {
|
|
return <div key={index} className="input-group">
|
|
<input type="text" className="form-control" value={d.data} required
|
|
onChange={(e) => {
|
|
dispatch2({type: 'UPDATE_OR_ADD', payload: {id: d.id, data: e.target.value}})
|
|
}}/>
|
|
<button className="btn btn-danger"
|
|
onClick={() => dispatch2({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" onClick={e => {
|
|
e.preventDefault();
|
|
|
|
let maxId = 0;
|
|
state2.forEach((d) => {
|
|
if (d.id > maxId)
|
|
maxId = d.id;
|
|
})
|
|
|
|
dispatch2({type: 'UPDATE_OR_ADD', payload: {id: maxId + 1, data: ""}})
|
|
}}><FontAwesomeIcon icon={faAdd}/></button>
|
|
</div>
|
|
</ul>
|
|
</div>
|
|
<div className="row mb-3">
|
|
<div className="d-grid gap-2 d-md-flex justify-content-md-end">
|
|
<button type="submit" className="btn btn-primary">{t('button.enregistrer')}</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
: error && <AxiosError error={error}/>}
|
|
</>
|
|
}
|
|
|
|
function Content({data}) {
|
|
const navigate = useNavigate();
|
|
const [registerMode, setRegisterMode] = useState(data.registerMode || "FREE");
|
|
const {t} = useTranslation();
|
|
|
|
const handleSubmit = (event) => {
|
|
event.preventDefault();
|
|
|
|
let err = false;
|
|
|
|
const out = {}
|
|
out['id'] = (data.id === "") ? null : data.id
|
|
out['name'] = event.target.name?.value
|
|
out['date'] = event.target.date?.value
|
|
out['toDate'] = event.target.toDate?.value
|
|
out['system'] = event.target.system?.value
|
|
out['club'] = event.target.club?.value
|
|
out['owner'] = event.target.owner?.value
|
|
out['description'] = event.target.description?.value
|
|
out['adresse'] = event.target.adresse?.value
|
|
out['publicVisible'] = event.target.publicVisible?.checked
|
|
|
|
out['startRegister'] = event.target.startRegister?.value
|
|
out['endRegister'] = event.target.endRegister?.value
|
|
out['registerMode'] = registerMode
|
|
|
|
if (out['registerMode'] === "HELLOASSO") {
|
|
out['data3'] = event.target.data3?.value
|
|
|
|
const url = event.target.helloassoUrl?.value
|
|
|
|
if (!url || !event.target.data3?.value) {
|
|
toast.error(t('comp.ha.error1'))
|
|
err = true;
|
|
} else {
|
|
const regex = /\/associations\/([^/]+)\/evenements\/([^/]+)/;
|
|
const match = url.match(regex);
|
|
|
|
if (match) {
|
|
out['data1'] = match[1]
|
|
out['data2'] = match[2]
|
|
} else {
|
|
toast.error(t('comp.ha.error2'))
|
|
err = true;
|
|
}
|
|
}
|
|
|
|
out['data4'] = event.target.data4?.value
|
|
if (!out['data4']) {
|
|
toast.error(t('comp.ha.error3'))
|
|
err = true;
|
|
}
|
|
}
|
|
|
|
if (out['date'] > out['toDate']) {
|
|
toast.error(t('comp.error1'))
|
|
err = true;
|
|
}
|
|
|
|
if ((out['registerMode'] === "FREE" || out['registerMode'] === "CLUB_ADMIN") && (!out['startRegister'] || !out['endRegister'])) {
|
|
toast.error(t('comp.error2'))
|
|
err = true;
|
|
}
|
|
if ((out['registerMode'] === "FREE" || out['registerMode'] === "CLUB_ADMIN") && out['startRegister'] > out['endRegister']) {
|
|
toast.error(t('comp.error3'))
|
|
err = true;
|
|
}
|
|
|
|
if (err) {
|
|
return;
|
|
}
|
|
|
|
toast.promise(
|
|
apiAxios.post(`/competition`, out), getToastMessage("comp.toast.save")
|
|
).then(data => {
|
|
console.log(data.data)
|
|
if (data.data.id !== undefined)
|
|
navigate("/competition/" + data.data.id)
|
|
})
|
|
}
|
|
|
|
return <form onSubmit={handleSubmit}>
|
|
<div className="card mb-4">
|
|
<input name="id" value={data.id || ""} readOnly hidden/>
|
|
<div className="card-header">{data.id ? t('comp.editionCompétition') : t('comp.créationCompétition')}</div>
|
|
<div className="card-body text-center">
|
|
|
|
<div className="accordion" id="accordionExample">
|
|
<div className="accordion-item">
|
|
<h2 className="accordion-header">
|
|
<button className="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseOne"
|
|
aria-expanded="false" aria-controls="collapseOne">
|
|
{t('comp.informationsTechniques')}
|
|
</button>
|
|
</h2>
|
|
<div id="collapseOne" className="accordion-collapse collapse" data-bs-parent="#accordionExample">
|
|
<div className="accordion-body">
|
|
<TextField name="uuid" text="UUID" value={data.uuid} disabled={true}/>
|
|
<OptionField name="system" text="System" value={data.system} values={{SAFCA: 'SAFCA', INTERNAL: "Intranet"}}
|
|
disabled={data.id !== null}/>
|
|
{data.id !== null &&
|
|
<div className="row">
|
|
<ClubSelect defaultValue={data.club} name="club" na={false} disabled={true}/>
|
|
</div>
|
|
}
|
|
{data.id !== null && <TextField name="owner" text="Propriétaire" value={data.owner}/>}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div className="accordion-item">
|
|
<h2 className="accordion-header">
|
|
<button className="accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#collapseTwo"
|
|
aria-expanded="true" aria-controls="collapseTwo">
|
|
{t('comp.informationsGénéralesSurLaCompétition')}
|
|
</button>
|
|
</h2>
|
|
<div id="collapseTwo" className="accordion-collapse collapse show" data-bs-parent="#accordionExample">
|
|
<div className="accordion-body">
|
|
<TextField name="name" text={t('nom') + "*"} value={data.name}/>
|
|
<div className="input-group mb-3">
|
|
<span className="input-group-text" id="date">{t('date')}*</span>
|
|
<span className="input-group-text" id="date">{t('du')}</span>
|
|
<input type="date" className="form-control" placeholder="jj/mm/aaaa" aria-label="date"
|
|
name="date" aria-describedby="date" defaultValue={data.date ? data.date.split('T')[0] : ''} required/>
|
|
<span className="input-group-text" id="date">{t('au')}</span>
|
|
<input type="date" className="form-control" placeholder="jj/mm/aaaa" aria-label="toDate"
|
|
name="toDate" aria-describedby="toDate" defaultValue={data.toDate ? data.toDate.split('T')[0] : ''}
|
|
required/>
|
|
</div>
|
|
<TextField name="description" text={t('description')} value={data.description} required={false}/>
|
|
<TextField name="adresse" text={t('adresse')} value={data.adresse} required={false}/>
|
|
<CheckField name="publicVisible" text={t('comp.text2')}
|
|
value={data.publicVisible} row={true}/>
|
|
<small>{t('comp.text3')}</small>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="accordion-item">
|
|
<h2 className="accordion-header">
|
|
<button className="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseThree"
|
|
aria-expanded="false" aria-controls="collapseThree">
|
|
{t('comp.informationsSurLeModeDinscription')}
|
|
</button>
|
|
</h2>
|
|
<div id="collapseThree" className="accordion-collapse collapse" data-bs-parent="#accordionExample">
|
|
<div className="accordion-body">
|
|
|
|
<div className="row">
|
|
<div className="input-group mb-3">
|
|
<label className="input-group-text">{t('comp.quiPeutInscrire')}</label>
|
|
<select className="form-select" value={registerMode} onChange={event => setRegisterMode(event.target.value)}>
|
|
<option value="FREE">{t('comp.tousLesMembresDeLaFfsaf')}</option>
|
|
<option value="CLUB_ADMIN">{t('comp.responsablesEtBureauxDesAssociations')}</option>
|
|
<option value="ADMIN">{t('comp.uniquementLesAdministrateursDeLaCompétition')}</option>
|
|
<option value="HELLOASSO">{t('comp.billetterieHelloasso')}</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="input-group mb-3"
|
|
style={{display: registerMode === "FREE" || registerMode === "CLUB_ADMIN" ? "flex" : "none"}}>
|
|
<span className="input-group-text" id="startRegister">{t('comp.dateDinscription')}</span>
|
|
<span className="input-group-text" id="startRegister">{t('du')}</span>
|
|
<input type="datetime-local" className="form-control" placeholder="jj/mm/aaaa" aria-label="date"
|
|
name="startRegister" aria-describedby="startRegister"
|
|
defaultValue={data.startRegister ? data.startRegister.substring(0, 16) : ''}/>
|
|
<span className="input-group-text" id="endRegister">{t('au')}</span>
|
|
<input type="datetime-local" className="form-control" placeholder="jj/mm/aaaa" aria-label="endRegister"
|
|
name="endRegister" aria-describedby="endRegister"
|
|
defaultValue={data.endRegister ? data.endRegister.substring(0, 16) : ''}/>
|
|
</div>
|
|
|
|
<div style={{display: registerMode === "HELLOASSO" ? "initial" : "none"}}>
|
|
<span style={{textAlign: "left"}}>
|
|
<div>{t('comp.ha.text1')}</div>
|
|
<ul>
|
|
<li><Trans i18nKey="comp.ha.text2"></Trans>
|
|
<img src="/img/HA-help-4.png" alt="" className="img-fluid" style={{objectFit: "contain"}}/></li>
|
|
<li><Trans i18nKey="comp.ha.text3"></Trans>
|
|
<img src="/img/HA-help-3.png" className="img-fluid" alt="..." style={{object_fit: 'contain'}}/>
|
|
<img src="/img/HA-help-2.png" className="img-fluid" alt="..." style={{object_fit: 'contain'}}/></li>
|
|
<li><Trans i18nKey="comp.ha.text4"></Trans></li>
|
|
</ul>
|
|
</span>
|
|
|
|
<TextField name="helloassoUrl" text={t('comp.ha.text5')} required={false}
|
|
value={data.data1 && data.data2 && `https://www.helloasso.com/associations/${data.data1}/evenements/${data.data2}` || ""}
|
|
placeholder="https://www.helloasso.com/associations/nom-asso-sur-helloasso/evenements/nom-billetterie"/>
|
|
|
|
<TextField name="data3" text={t('comp.ha.tarifsHelloasso')}
|
|
value={data.data3 || ""} required={false}
|
|
placeholder="Tarif1;Tarif2;Tarif3"/>
|
|
|
|
<TextField name="data4" text={t('comp.ha.emailDeRéceptionDesInscriptionséchoué')}
|
|
value={data.data4 || ""} required={false}/>
|
|
<small>{t('comp.ha.text6')}</small>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
|
|
<div className="row mb-3">
|
|
<div className="d-grid gap-2 d-md-flex justify-content-md-end">
|
|
<button type="submit" className="btn btn-primary">{t('button.enregistrer')}</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
}
|