Header menu via header.js
This commit is contained in:
117
frontend/public/admin/admin.js
Normal file
117
frontend/public/admin/admin.js
Normal file
@@ -0,0 +1,117 @@
|
||||
async function j(url, opts={}){
|
||||
const r = await fetch(url, opts);
|
||||
if (!r.ok) {
|
||||
const t = await r.text().catch(()=>'');
|
||||
throw new Error(`HTTP ${r.status} ${t}`);
|
||||
}
|
||||
const ct = r.headers.get('content-type')||'';
|
||||
return ct.includes('application/json') ? r.json() : r.text();
|
||||
}
|
||||
const $ = (s)=>document.querySelector(s);
|
||||
|
||||
async function loadTournaments(){
|
||||
const data = await j('/api/tournaments');
|
||||
const opts = ['<option value="">— choisir —</option>']
|
||||
.concat(data.map(t=>`<option value="${t.id}">#${t.id} — ${t.name}</option>`));
|
||||
$('#t-list').innerHTML = opts.join('');
|
||||
$('#p-tournament').innerHTML = opts.join('');
|
||||
$('#m-tournament').innerHTML = opts.join('');
|
||||
$('#tournaments-box').textContent = JSON.stringify(data, null, 2);
|
||||
const tid = Number($('#p-tournament').value || data[0]?.id || 0);
|
||||
if (tid) await refreshParticipants(tid);
|
||||
}
|
||||
|
||||
async function refreshParticipants(tid){
|
||||
try{
|
||||
const data = await j(`/api/tournaments/${tid}/participants`);
|
||||
$('#participants-box').textContent = JSON.stringify(data, null, 2);
|
||||
}catch(e){
|
||||
$('#participants-box').textContent = 'Erreur: '+e.message;
|
||||
}
|
||||
}
|
||||
|
||||
async function createTournament(e){
|
||||
e.preventDefault();
|
||||
const payload = {
|
||||
name: $('#t-name').value.trim(),
|
||||
location: $('#t-location').value.trim(),
|
||||
start_date: $('#t-start').value,
|
||||
end_date: $('#t-end').value
|
||||
};
|
||||
try{
|
||||
await j('/api/tournaments', {
|
||||
method:'POST',
|
||||
headers:{'Content-Type':'application/json'},
|
||||
body: JSON.stringify(payload)
|
||||
});
|
||||
$('#t-create-status').textContent = 'OK ✅';
|
||||
await loadTournaments();
|
||||
}catch(err){
|
||||
$('#t-create-status').textContent = err.message;
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteTournament(){
|
||||
const id = Number($('#t-list').value||'0');
|
||||
if (!id) return;
|
||||
if (!confirm('Supprimer le tournoi #' + id + ' ?')) return;
|
||||
try{
|
||||
await j(`/api/tournaments/${id}`, { method:'DELETE' });
|
||||
await loadTournaments();
|
||||
}catch(e){
|
||||
alert(e.message);
|
||||
}
|
||||
}
|
||||
|
||||
async function createParticipant(e){
|
||||
e.preventDefault();
|
||||
const tid = Number($('#p-tournament').value||'0');
|
||||
const name = $('#p-name').value.trim();
|
||||
if (!tid || !name) return;
|
||||
try{
|
||||
await j('/api/participants', {
|
||||
method:'POST',
|
||||
headers:{'Content-Type':'application/json'},
|
||||
body: JSON.stringify({ tournament_id: tid, full_name: name })
|
||||
});
|
||||
$('#p-create-status').textContent = 'OK ✅';
|
||||
await refreshParticipants(tid);
|
||||
}catch(e){
|
||||
$('#p-create-status').textContent = e.message;
|
||||
}
|
||||
}
|
||||
|
||||
async function scoreMatch(e){
|
||||
e.preventDefault();
|
||||
const id = Number($('#m-id').value||'0');
|
||||
const sa = Number($('#m-score-a').value||'0');
|
||||
const sb = Number($('#m-score-b').value||'0');
|
||||
const finished = $('#m-finished').checked;
|
||||
if (!id) return;
|
||||
try{
|
||||
await j(`/api/matches/${id}/score`, {
|
||||
method:'POST',
|
||||
headers:{'Content-Type':'application/json'},
|
||||
body: JSON.stringify({ score_a: sa, score_b: sb, finished })
|
||||
});
|
||||
$('#m-score-status').textContent = 'OK ✅';
|
||||
}catch(e){
|
||||
$('#m-score-status').textContent = e.message;
|
||||
}
|
||||
}
|
||||
|
||||
async function ping(){
|
||||
try{ await j('/api/health'); $('#health').textContent = 'OK ✅'; }
|
||||
catch(e){ $('#health').textContent = e.message; }
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
$('#t-create-form').addEventListener('submit', createTournament);
|
||||
$('#btn-del-t').addEventListener('click', deleteTournament);
|
||||
$('#btn-refresh-t').addEventListener('click', loadTournaments);
|
||||
$('#p-create-form').addEventListener('submit', createParticipant);
|
||||
$('#m-score-form').addEventListener('submit', scoreMatch);
|
||||
$('#btn-health').addEventListener('click', ping);
|
||||
loadTournaments();
|
||||
ping();
|
||||
});
|
||||
@@ -4,116 +4,112 @@
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Super Sunday — Admin</title>
|
||||
<link rel="stylesheet" href="/assets/style.css?v=10" />
|
||||
<link rel="stylesheet" href="/assets/style.css" />
|
||||
<link rel="stylesheet" href="/assets/patch-bubbles.css" />
|
||||
<style>
|
||||
.form-row{display:grid;grid-template-columns:1fr 1fr;gap:12px}
|
||||
.grid-2{display:grid;grid-template-columns:1fr 1fr;gap:16px}
|
||||
.actions{display:flex;gap:8px;flex-wrap:wrap;align-items:center}
|
||||
.muted-sm{color:#9fb0c7;font-size:13px}
|
||||
.toast{position:fixed;right:16px;bottom:16px;padding:12px 16px;
|
||||
background:#0f1b33;border:1px solid rgba(255,255,255,.2);color:#eaf3ff;
|
||||
border-radius:14px;box-shadow:0 8px 24px rgba(0,0,0,.35);display:none;min-width:260px}
|
||||
.toast.show{display:block;animation:toastIn .18s ease-out}
|
||||
@keyframes toastIn{from{transform:translateY(6px);opacity:.0}to{transform:translateY(0);opacity:1}}
|
||||
.spinner{display:inline-block;width:16px;height:16px;border-radius:50%;
|
||||
border:2px solid rgba(255,255,255,.25);border-top-color:#8fedff;animation:spin .6s linear infinite;margin-left:8px}
|
||||
@keyframes spin{to{transform:rotate(360deg)}}
|
||||
.help{font-size:12px;color:#cfe1ff;margin-top:4px}
|
||||
.sel{display:flex;gap:8px}
|
||||
.sel select{min-width:160px}
|
||||
.hide{display:none !important}
|
||||
.wrap{max-width:1100px;margin:0 auto}
|
||||
.grid{display:grid;grid-template-columns:repeat(12,1fr);gap:16px}
|
||||
.col-6{grid-column:span 6}
|
||||
.col-12{grid-column:span 12}
|
||||
.card{background:rgba(15,27,51,.7);border:1px solid rgba(255,255,255,.08);border-radius:16px;padding:16px}
|
||||
.card h2{font-size:20px;margin:0 0 10px}
|
||||
.row{display:flex;gap:8px;flex-wrap:wrap;margin:6px 0}
|
||||
.row > *{flex:1 1 180px}
|
||||
.muted{color:#a9bfd6}
|
||||
.btn{padding:10px 14px;border-radius:12px;border:1px solid rgba(255,255,255,.1);background:#1a2442;color:#fff;cursor:pointer}
|
||||
.btn:hover{filter:brightness(1.1)}
|
||||
input,select{background:#0f1b33;border:1px solid rgba(255,255,255,.12);border-radius:12px;padding:10px 12px;color:#fff}
|
||||
pre{white-space:pre-wrap;background:rgba(0,0,0,.3);padding:10px;border-radius:12px;border:1px solid rgba(255,255,255,.08);max-height:320px;overflow:auto}
|
||||
.status{min-height:24px}
|
||||
.danger{background:#3a1520;border-color:#ce4f76}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1>Admin — Super Sunday</h1>
|
||||
<nav>
|
||||
<a href="/">Accueil</a>
|
||||
<a href="/events">Événements</a>
|
||||
<a href="/admin" class="active">Admin</a>
|
||||
</nav>
|
||||
</header>
|
||||
<header>
|
||||
<h1>Admin</h1>
|
||||
<nav>
|
||||
<a href="/">Accueil</a>
|
||||
<a href="/events">Événements</a>
|
||||
<a class="active" href="/admin">Admin</a>
|
||||
<a href="/scoreboard">Classement</a>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<main class="container">
|
||||
<!-- Login -->
|
||||
<section id="loginSection" class="card">
|
||||
<h2 class="section-title">Connexion</h2>
|
||||
<div class="form-row">
|
||||
<input id="email" placeholder="Email (ex: admin@supersunday.local)" />
|
||||
<input id="password" type="password" placeholder="Mot de passe (ex: changeme)" />
|
||||
</div>
|
||||
<div class="actions">
|
||||
<button id="loginBtn" class="btn">Se connecter</button>
|
||||
<button id="logoutBtn" class="btn-outline">Se déconnecter</button>
|
||||
<span id="loginStatus" class="muted"></span>
|
||||
</div>
|
||||
<p class="muted-sm">Identifiants dans <code>backend/.env</code> (ADMIN_EMAIL / ADMIN_PASSWORD).</p>
|
||||
</section>
|
||||
|
||||
<section id="adminSection" class="grid-2 hide">
|
||||
<!-- Create tournament -->
|
||||
<div class="card">
|
||||
<h3 class="section-title">Créer un tournoi</h3>
|
||||
<input id="t_name" placeholder="Nom du tournoi" />
|
||||
<input id="t_location" placeholder="Lieu" />
|
||||
<div class="form-row">
|
||||
<input id="t_start" type="date" placeholder="Date début" />
|
||||
<input id="t_end" type="date" placeholder="Date fin" />
|
||||
</div>
|
||||
<div class="actions">
|
||||
<button id="createTournamentBtn" class="btn">Créer</button>
|
||||
<div id="createTournamentSpin" class="spinner hide"></div>
|
||||
</div>
|
||||
<div id="createTournamentErr" class="help"></div>
|
||||
</div>
|
||||
|
||||
<!-- Add participant -->
|
||||
<div class="card">
|
||||
<h3 class="section-title">Ajouter un joueur</h3>
|
||||
<div class="sel">
|
||||
<select id="p_tid"></select>
|
||||
<input id="p_fullname" placeholder="Nom complet" />
|
||||
</div>
|
||||
<div class="actions">
|
||||
<button id="addParticipantBtn" class="btn">Ajouter</button>
|
||||
<div id="addParticipantSpin" class="spinner hide"></div>
|
||||
</div>
|
||||
<div id="addParticipantErr" class="help"></div>
|
||||
</div>
|
||||
|
||||
<!-- Score match -->
|
||||
<div class="card">
|
||||
<h3 class="section-title">Scorer un match</h3>
|
||||
<div class="sel">
|
||||
<select id="s_tid"></select>
|
||||
<select id="s_mid"></select>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<input id="m_a" type="number" placeholder="Score A" min="0" />
|
||||
<input id="m_b" type="number" placeholder="Score B" min="0" />
|
||||
</div>
|
||||
<div class="actions">
|
||||
<label><input id="m_done" type="checkbox" /> Terminé</label>
|
||||
<button id="scoreBtn" class="btn">Valider</button>
|
||||
<div id="scoreSpin" class="spinner hide"></div>
|
||||
</div>
|
||||
<div id="scoreErr" class="help"></div>
|
||||
</div>
|
||||
|
||||
<!-- Quick view tournaments -->
|
||||
<div class="card">
|
||||
<h3 class="section-title">Tournois (aperçu)</h3>
|
||||
<div class="actions">
|
||||
<button id="refreshTournaments" class="btn-outline">Rafraîchir</button>
|
||||
</div>
|
||||
<div id="listTournaments" class="muted"></div>
|
||||
<main class="container wrap">
|
||||
<div class="grid">
|
||||
<section class="card col-12">
|
||||
<div class="row">
|
||||
<button class="btn" id="btn-health">Tester /api/health</button>
|
||||
<span id="health" class="status muted"></span>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<div id="toast" class="toast" role="status" aria-live="polite"></div>
|
||||
<section class="card col-6">
|
||||
<h2>Créer un tournoi</h2>
|
||||
<form id="t-create-form">
|
||||
<div class="row">
|
||||
<input id="t-name" placeholder="Nom" required />
|
||||
<input id="t-location" placeholder="Lieu" />
|
||||
</div>
|
||||
<div class="row">
|
||||
<label class="muted">Début<input id="t-start" type="date" required /></label>
|
||||
<label class="muted">Fin<input id="t-end" type="date" required /></label>
|
||||
</div>
|
||||
<div class="row">
|
||||
<button class="btn" type="submit">Créer</button>
|
||||
<span id="t-create-status" class="status muted"></span>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
<script type="module" src="/assets/api.js?v=10"></script>
|
||||
<script type="module" src="/assets/admin.ux.js?v=10"></script>
|
||||
<section class="card col-6">
|
||||
<h2>Tournois</h2>
|
||||
<div class="row">
|
||||
<select id="t-list"></select>
|
||||
<button class="btn" id="btn-refresh-t">Rafraîchir</button>
|
||||
<button class="btn danger" id="btn-del-t">Supprimer</button>
|
||||
</div>
|
||||
<pre id="tournaments-box" class="muted">—</pre>
|
||||
</section>
|
||||
|
||||
<section class="card col-6">
|
||||
<h2>Ajouter un joueur</h2>
|
||||
<form id="p-create-form">
|
||||
<div class="row">
|
||||
<select id="p-tournament"></select>
|
||||
<input id="p-name" placeholder="Nom complet" required />
|
||||
</div>
|
||||
<div class="row">
|
||||
<button class="btn" type="submit">Ajouter</button>
|
||||
<span id="p-create-status" class="status muted"></span>
|
||||
</div>
|
||||
</form>
|
||||
<pre id="participants-box" class="muted">—</pre>
|
||||
</section>
|
||||
|
||||
<section class="card col-6">
|
||||
<h2>Saisir un score</h2>
|
||||
<form id="m-score-form">
|
||||
<div class="row">
|
||||
<select id="m-tournament"></select>
|
||||
<input id="m-id" type="number" placeholder="Match ID" required />
|
||||
</div>
|
||||
<div class="row">
|
||||
<input id="m-score-a" type="number" placeholder="Jeux A" min="0" />
|
||||
<input id="m-score-b" type="number" placeholder="Jeux B" min="0" />
|
||||
<label class="muted"><input id="m-finished" type="checkbox" /> Terminé</label>
|
||||
</div>
|
||||
<div class="row">
|
||||
<button class="btn" type="submit">Enregistrer</button>
|
||||
<span id="m-score-status" class="status muted"></span>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<script type="module" src="/assets/bubbles.js"></script>
|
||||
<script type="module" src="/admin/admin.js"></script>
|
||||
<script type="module" src="/assets/header.js?v=1"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user