mirror of
https://github.com/guyg1474/hcrb.git
synced 2026-10-16 07:13:28 +02:00
Erste Version: HCRB Spieltag-Kalender und Fahrtplaner
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
commit
10400eee59
30 files changed
+3771
No files matched your search
@@ -0,0 +1,216 @@
|
||||
import { $, api, esc, sortByName, seatsLabel, showError, renderHeader } from './common.js';
|
||||
|
||||
renderHeader('gruppe');
|
||||
|
||||
let members = [];
|
||||
let points = [];
|
||||
const form = $('#member-form');
|
||||
|
||||
$('#member-hascar').addEventListener('change', (e) => {
|
||||
$('#member-seats-wrap').hidden = !e.target.checked;
|
||||
});
|
||||
|
||||
// Auswahl "fährt am liebsten mit bei" – alle anderen Mitglieder
|
||||
function fillRidesWith(selfId, selected) {
|
||||
$('#member-rideswith').innerHTML = '<option value="">(keine Vorliebe)</option>'
|
||||
+ sortByName(members.filter((m) => m.id !== selfId))
|
||||
.map((m) => `<option value="${m.id}" ${m.id === selected ? 'selected' : ''}>${esc(m.name)}</option>`).join('');
|
||||
}
|
||||
|
||||
// Auswahl Standard-Treffpunkt
|
||||
function fillMemberPoint(selected) {
|
||||
$('#member-point').innerHTML = '<option value="">(Treffpunkt der Fahrt)</option>'
|
||||
+ points.map((p) => `<option value="${p.id}" ${p.id === selected ? 'selected' : ''}>${esc(p.name)}</option>`).join('');
|
||||
}
|
||||
|
||||
function resetForm() {
|
||||
form.reset();
|
||||
fillRidesWith();
|
||||
fillMemberPoint();
|
||||
$('#member-id').value = '';
|
||||
$('#member-seats-wrap').hidden = true;
|
||||
$('#member-cancel').hidden = true;
|
||||
$('#member-form-title').textContent = 'Mitglied hinzufügen';
|
||||
}
|
||||
|
||||
$('#member-cancel').addEventListener('click', resetForm);
|
||||
|
||||
form.addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
const body = {
|
||||
name: $('#member-name').value.trim(),
|
||||
external: $('#member-external').checked,
|
||||
hasCar: $('#member-hascar').checked,
|
||||
seats: $('#member-seats').value,
|
||||
alwaysDrives: $('#member-always').checked,
|
||||
ridesWith: $('#member-rideswith').value || null,
|
||||
meetingPoint: $('#member-point').value || null,
|
||||
};
|
||||
const id = $('#member-id').value;
|
||||
try {
|
||||
if (id) await api('PUT', `/members/${id}`, body);
|
||||
else await api('POST', '/members', body);
|
||||
resetForm();
|
||||
await load();
|
||||
} catch (err) {
|
||||
showError(err);
|
||||
}
|
||||
});
|
||||
|
||||
function edit(id) {
|
||||
const m = members.find((x) => x.id === id);
|
||||
$('#member-id').value = m.id;
|
||||
$('#member-name').value = m.name;
|
||||
$('#member-external').checked = !!m.external;
|
||||
fillMemberPoint(m.meetingPoint);
|
||||
$('#member-hascar').checked = m.hasCar;
|
||||
$('#member-seats').value = m.seats || 4;
|
||||
$('#member-always').checked = !!m.alwaysDrives;
|
||||
fillRidesWith(m.id, m.ridesWith);
|
||||
$('#member-seats-wrap').hidden = !m.hasCar;
|
||||
$('#member-cancel').hidden = false;
|
||||
$('#member-form-title').textContent = 'Mitglied bearbeiten';
|
||||
$('#member-name').focus();
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
}
|
||||
|
||||
async function remove(id) {
|
||||
const m = members.find((x) => x.id === id);
|
||||
if (!confirm(`${m.name} wirklich aus der Gruppe löschen?`)) return;
|
||||
try {
|
||||
await api('DELETE', `/members/${id}`);
|
||||
await load();
|
||||
} catch (err) {
|
||||
showError(err);
|
||||
}
|
||||
}
|
||||
|
||||
function render() {
|
||||
if (!$('#member-id').value) fillRidesWith();
|
||||
const nameOf = (id) => members.find((m) => m.id === id)?.name;
|
||||
const pointOf = (id) => points.find((p) => p.id === id)?.name;
|
||||
$('#members-empty').hidden = members.length > 0;
|
||||
$('#members-list').innerHTML = sortByName(members).map((m) => `
|
||||
<li>
|
||||
<span class="name">${esc(m.name)}${m.external ? ' <span class="badge">Extern</span>' : ''}</span>
|
||||
${m.hasCar && pointOf(m.meetingPoint) ? `<span class="meta">ab ${esc(pointOf(m.meetingPoint))}</span>` : ''}
|
||||
${nameOf(m.ridesWith) ? `<span class="meta">fährt gern bei ${esc(nameOf(m.ridesWith))} mit</span>` : ''}
|
||||
<span class="badge">${m.hasCar ? `Auto · ${seatsLabel(m.seats)}${m.alwaysDrives ? ' · fährt immer' : ''}` : 'kein Auto'}</span>
|
||||
<span class="actions">
|
||||
<button class="link" data-edit="${m.id}">Bearbeiten</button>
|
||||
<button class="link danger" data-del="${m.id}">Löschen</button>
|
||||
</span>
|
||||
</li>`).join('');
|
||||
}
|
||||
|
||||
$('#members-list').addEventListener('click', (e) => {
|
||||
if (e.target.dataset.edit) edit(e.target.dataset.edit);
|
||||
if (e.target.dataset.del) remove(e.target.dataset.del);
|
||||
});
|
||||
|
||||
$('#export-btn').addEventListener('click', () => {
|
||||
const blob = new Blob([JSON.stringify(members, null, 2)], { type: 'application/json' });
|
||||
const a = document.createElement('a');
|
||||
a.href = URL.createObjectURL(blob);
|
||||
a.download = 'hcrb-gruppe.json';
|
||||
a.click();
|
||||
URL.revokeObjectURL(a.href);
|
||||
});
|
||||
|
||||
$('#import-file').addEventListener('change', async (e) => {
|
||||
const file = e.target.files[0];
|
||||
e.target.value = '';
|
||||
if (!file) return;
|
||||
let data;
|
||||
try {
|
||||
data = JSON.parse(await file.text());
|
||||
if (!Array.isArray(data)) throw new Error();
|
||||
} catch {
|
||||
alert('Die Datei konnte nicht gelesen werden.');
|
||||
return;
|
||||
}
|
||||
if (!confirm(`${data.length} Mitglieder importieren? Die aktuelle Gruppe wird ersetzt.`)) return;
|
||||
try {
|
||||
await api('POST', '/members/import', data);
|
||||
await load();
|
||||
} catch (err) {
|
||||
showError(err);
|
||||
}
|
||||
});
|
||||
|
||||
// ---------- Treffpunkte ----------
|
||||
const pointForm = $('#point-form');
|
||||
|
||||
function resetPointForm() {
|
||||
pointForm.reset();
|
||||
$('#point-id').value = '';
|
||||
$('#point-cancel').hidden = true;
|
||||
$('#point-form-title').textContent = 'Treffpunkt hinzufügen';
|
||||
}
|
||||
|
||||
$('#point-cancel').addEventListener('click', resetPointForm);
|
||||
|
||||
pointForm.addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
const body = { name: $('#point-name').value.trim(), address: $('#point-address').value.trim() };
|
||||
const id = $('#point-id').value;
|
||||
try {
|
||||
if (id) await api('PUT', `/meeting-points/${id}`, body);
|
||||
else await api('POST', '/meeting-points', body);
|
||||
resetPointForm();
|
||||
await loadPoints();
|
||||
} catch (err) {
|
||||
showError(err);
|
||||
}
|
||||
});
|
||||
|
||||
function renderPoints() {
|
||||
$('#points-empty').hidden = points.length > 0;
|
||||
$('#points-list').innerHTML = points.map((p) => `
|
||||
<li>
|
||||
<span class="name">${esc(p.name)}</span>
|
||||
<span class="meta">${esc(p.address)}</span>
|
||||
<span class="actions">
|
||||
<button class="link" data-edit="${p.id}">Bearbeiten</button>
|
||||
<button class="link danger" data-del="${p.id}">Löschen</button>
|
||||
</span>
|
||||
</li>`).join('');
|
||||
}
|
||||
|
||||
$('#points-list').addEventListener('click', async (e) => {
|
||||
const { edit: editId, del } = e.target.dataset;
|
||||
if (editId) {
|
||||
const p = points.find((x) => x.id === editId);
|
||||
$('#point-id').value = p.id;
|
||||
$('#point-name').value = p.name;
|
||||
$('#point-address').value = p.address;
|
||||
$('#point-cancel').hidden = false;
|
||||
$('#point-form-title').textContent = 'Treffpunkt bearbeiten';
|
||||
$('#point-name').focus();
|
||||
}
|
||||
if (del) {
|
||||
const p = points.find((x) => x.id === del);
|
||||
if (!confirm(`Treffpunkt „${p.name}“ wirklich löschen?`)) return;
|
||||
try {
|
||||
await api('DELETE', `/meeting-points/${del}`);
|
||||
await loadPoints();
|
||||
} catch (err) {
|
||||
showError(err);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
async function loadPoints() {
|
||||
points = await api('GET', '/meeting-points');
|
||||
renderPoints();
|
||||
const editing = members.find((m) => m.id === $('#member-id').value);
|
||||
fillMemberPoint(editing ? $('#member-point').value : undefined);
|
||||
render();
|
||||
}
|
||||
|
||||
async function load() {
|
||||
members = await api('GET', '/members');
|
||||
render();
|
||||
}
|
||||
|
||||
load().then(loadPoints).catch(showError);
|
||||
Reference in new issue
Block a user