ffsaf-site/src/main/webapp/src/pages/competition/CompetitionEdit.jsx

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>
}