Files
sternwarte/sternwarte/intern/anmeld/js/anmeld.js
2026-01-14 19:05:10 +01:00

560 lines
21 KiB
JavaScript

// Vanilla JS Migration of anmeld.js (jQuery removed)
async function ladeAbsagegruende() {
const res = await fetch('data/absagegruende.txt');
const text = await res.text();
return text
.split('\n')
.map(line => line.trim())
.filter(line => line.length > 0);
}
document.addEventListener('DOMContentLoaded', async () => {
// URL-Parameter lesen
const urlParams = new URLSearchParams(window.location.search);
const query = {
typ: urlParams.get('typ') || 'regular',
storno: urlParams.get('storno') === 'true',
double: urlParams.get('double') === 'true',
name: urlParams.get('name') || 'Null',
datum: urlParams.get('datum') === 'true'
};
const absagegrundListe = await ladeAbsagegruende();
const fuehrung = query.typ === 'sonnen' ? 'Sonnenführung' : 'Sternführung';
let absagegrund = "";
const useDatum = query.datum
// Unified API endpoint
const ajaxURL = "../../DB4js_all.php";
const ANZAHL_DATES = query.typ === 'sonnen' ? 25 : 50;
bodytext = ""
betreff = ""
const TEXTE = {
absagebutton: (abg) => `Absage ${abg ? `wurde gesendet am ${abg}` : `senden`}`,
absagetext: "Absage an alle angemeldeten Besucher senden.",
bittegrund: "Die Führung wird abgesagt wegen:",
schonabgesagt: "Absage schon gesendet. Nochmal senden?",
subject: (useDatum, date) => `Absage der ${useDatum ? `${fuehrung} am ${date}` : `heutigen ${fuehrung}`}`,
bodytext: (useDatum, date) => `Sehr geehrte Dame, sehr geehrter Herr,
Sie haben sich über unsere Webseite www.sternwarte-welzheim.de zur ${useDatum ? `${fuehrung} am ${date}`: `heutigen ${fuehrung}`} angemeldet.
Aufgrund {absagegrund} können wir ${useDatum ? `am ${date}` : 'heute'} keine ${fuehrung} anbieten.
Bitte melden Sie sich für einen anderen Termin neu an.
Mit freundlichen Grüßen
Beobachtergruppe Sternwarte Welzheim`
,
};
const liste = {
emails: [],
ids: []
};
let abgesagt = null
let actualdate;
let isSmallScreen = false
const DateTime = luxon.DateTime
function $(selector) {
return document.querySelector(selector);
}
function $all(selector) {
return document.querySelectorAll(selector);
}
async function fetchFromDbase(body) {
body.typ = query.typ;
const response = await fetch(ajaxURL, {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(body)
});
return await response.json();
}
async function putToDbase(body) {
body.typ = query.typ;
const response = await fetch(ajaxURL, {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(body)
});
return await response.json();
}
function clearElement(el) {
el.innerHTML = '';
}
function appendHtml(el, html) {
el.insertAdjacentHTML('beforeend', html);
}
function setVisibility(id, visible) {
$(id).style.visibility = visible ? 'visible' : 'hidden';
}
async function storeAbsage(ids) {
if (!DateTime || typeof DateTime.now !== 'function') {
console.error('Luxon DateTime ist nicht verfügbar');
throw new Error('Datum kann nicht erstellt werden - Luxon nicht geladen');
}
const dt = DateTime.now()
const jetzt = dt.toFormat('yyyy-LL-dd HH:mm:ss')
if (!jetzt || jetzt === 'undefined' || jetzt.includes('undefined')) {
console.error('Ungültiges Datum generiert:', jetzt);
throw new Error('Ungültiges Datum erstellt');
}
const update = { cmd: 'UPDATE_TLN_BULK', field: 'abgesagt', ids: ids, values: [jetzt] };
await putToDbase(update);
abgesagt = jetzt
}
async function getDetailText(id) {
const nbr = parseInt(id);
const holit = await fetchFromDbase({cmd:'GET_TEILN_ID', id:nbr});
const entry = holit[0];
return `
<div class="det">${entry.name} ${entry.vorname}<br /></div>
<div class="det"><a href="mailto:${entry.email}">${entry.email}</a><br /></div>
<div class="det">${entry.plz} ${entry.stadt}<br /></div>
<div class="det">${entry.strasse}<br /></div>
<div class="det">${entry.telefon}<br /></div>
<div class="det">${entry.remarks}<br /></div>`;
}
// Teilnehmer aus 'anmeldungen' austragen und den count in 'fdatum1' anpassen
const austragen = async (teilnehmer) => {
let update = {cmd: 'DELETEONE', id: parseInt(teilnehmer.id)}
const erg1 = await putToDbase(update)
update = {cmd: 'UPDATECOUNT', date: parseInt(teilnehmer.fdatum), anzahl: parseInt(teilnehmer.anzahl)}
const erg2 = await putToDbase(update)
console.log("Storno Ergebisse: ",erg1,erg2)
showAktAnmeldungen(actualdate)
}
async function showAktAnmeldungen(date) {
actualdate = date;
liste.emails = [];
liste.ids = [];
let column = query.storno ? "col-2" : "col-3";
const anmeldungen = await fetchFromDbase({cmd:'GET_ANMELD', id:date});
let besucher = 0;
clearElement($('#tabAnmeld tbody'));
for (let e of anmeldungen) {
besucher += parseInt(e.anzahl);
liste.emails.push(e.email);
liste.ids.push(e.id);
abgesagt = e.abgesagt ? e.abgesagt.slice(0,16) : null
abgesagt = (abgesagt === '1900-01-01 00:00') ? null : abgesagt
// const selected = e.teilgenommen === "1" ? "checked" : "";
const row = document.createElement('tr');
row.classList.add('d-flex');
row.innerHTML = `
<td class="tdname col-6">${e.name} ${e.vorname}</td>
<td class=${column}>${e.anzahl}</td>`
if (query.storno) {
row.innerHTML += `<td class="col-2 tdstorno"><input type="checkbox" value="${e.id}" </td>`
}
row.querySelector('.tdname').addEventListener('click', async () => {
const detail = await getDetailText(e.id);
$('#detailtext').innerHTML = detail;
$('#detaildialog').showModal();
});
if(query.storno) {
row.querySelector('.tdstorno').addEventListener('click', function() {
const tr = this.closest("tr") // Finds the closest row <tr>
const index = tr.rowIndex-1
austragen(anmeldungen[index])
});
}
$('#tabAnmeld tbody').appendChild(row);
}
$('#absagen').innerHTML = TEXTE.absagebutton(abgesagt)
if (besucher !== 0) {
const sumRow = document.createElement('tr');
sumRow.id = 'summe';
sumRow.classList.add('d-flex');
sumRow.innerHTML = `
<td class="col-6">Summe der Anmeldungen</td>`
if (query.storno) {
sumRow.innerHTML += `<td style="text-align: center;" class="col-4">${besucher}</td>`;
} else {
sumRow.innerHTML += `<td class="col-3">${besucher}</td>`;
}
$('#tabAnmeld tbody').appendChild(sumRow);
}
// Add group participants line below table
let groupInfoDiv = $('#groupInfo');
if (!groupInfoDiv) {
groupInfoDiv = document.createElement('div');
groupInfoDiv.id = 'groupInfo';
groupInfoDiv.style.fontSize = '75%';
groupInfoDiv.style.textAlign = 'center';
groupInfoDiv.style.marginTop = '10px';
$('#tabAnmeld').parentNode.insertBefore(groupInfoDiv, $('#tabAnmeld').nextSibling);
}
groupInfoDiv.innerHTML = '';
if (query.typ === 'regular' && besucher !== 0) {
const groupName = await getGroupForDate(date);
if (groupName) {
const participants = await getGroupParticipants(groupName);
if (participants.length > 0) {
const participantNames = participants.map(p => `${p.vorname} `).join(', ');
groupInfoDiv.innerHTML = `${groupName} -> ${participantNames}`;
}
}
}
bodytext = TEXTE.bodytext(useDatum, DateTime.fromISO(date).setLocale("de").toFormat("cccc, 'den' d. LLLL yyyy"))
betreff = TEXTE.subject(useDatum, DateTime.fromISO(date).setLocale("de").toFormat("cccc, 'den' d. LLLL yyyy"))
console.log(betreff)
}
async function getGroupForDate(date) {
const dateInfo = await fetchFromDbase({cmd:'GET_DATES', anzahl: 1, date: date});
return dateInfo && dateInfo.length > 0 ? dateInfo[0].grp : null;
}
async function getGroupParticipants(groupName) {
const allBeos = await fetchFromDbase({cmd:'GET_BEOS', onlyguides: true, what: 'vorname,name,gruppe'});
return allBeos.filter(beo => {
if (!beo.gruppe) return false;
// Split gruppe by comma and check for exact match
// This handles cases like "Mo I, Mo II", "A, B", etc.
const groups = beo.gruppe.split(',').map(g => g.trim());
return groups.includes(groupName);
});
}
async function findName(name) {
const update = { cmd: 'GET_TEILN_NAME', name: name };
let erg = await putToDbase(update);
clearElement($('#tnames'));
appendHtml($('#tnames'), `
<thead><tr>
<th class="col-6">Name</th>
<th class="col-2">Anzahl</th>
<th class="col-2">Datum</th>
<th class="col-2">Finden</th>
</tr></thead><tbody>`);
for (let x of erg) {
appendHtml($('#tnames'), `
<tr>
<td class="tdname col-6">${x.name} ${x.vorname}</td>
<td class="col-2">${x.anzahl}</td>
<td class="col-2">${DateTime.fromISO(x.fdatum).toFormat('yyyy-LL-dd')}</td>
<td class="tdfind col-2"><button type="button" class="findit" value="${x.id}">🔍</button></td>
</tr>`);
}
appendHtml($('#tnames'), `</tbody>`);
$('#names').style.display = 'block';
$all('.findit').forEach(btn => {
btn.addEventListener('click', async () => {
const id = btn.value;
const detail = await getDetailText(id);
$('#detailtext').innerHTML = detail;
$('#detaildialog').showModal();
});
});
}
$('#absagen').addEventListener('click', () => {
$('#absagetext').innerHTML = abgesagt ? TEXTE.schonabgesagt : TEXTE.absagetext;
$('#bittegrund').innerHTML = TEXTE.bittegrund
$('#absagedialog').showModal();
const container = $('#absagegrund');
clearElement(container);
absagegrundListe.forEach((g, i) => {
const line = document.createElement('div');
line.className = 'absageline';
const radio = document.createElement('input');
radio.type = 'radio';
radio.name = 'grund';
radio.value = i;
radio.id = `grund${i}`;
radio.checked = i === 0; // Default to the first option
const label = document.createElement('label');
label.setAttribute('for', `grund${i}`);
const span = document.createElement('span');
span.textContent = g;
span.style.marginLeft = "20px"
line.appendChild(radio);
line.appendChild(span);
line.appendChild(document.createElement('br'));
container.appendChild(line);
});
});
$('#absagedialog-send').addEventListener('click', async () => {
const grundIndex = Array.from($all('input[name=grund]')).find(el => el.checked)?.value || 0;
absagegrund = absagegrundListe[grundIndex];
// Versuche das Absagedatum zu speichern, aber breche nicht ab wenn es fehlschlägt
let datumGespeichert = false;
try {
await storeAbsage(liste.ids);
datumGespeichert = true;
} catch (error) {
console.error('Fehler beim Speichern des Absagedatums:', error);
// Warnung anzeigen, aber weitermachen
console.warn('Absagedatum konnte nicht gespeichert werden, aber Mail wird trotzdem versendet');
}
// Laufschrift einschalten (falls regular)
if (query.typ !== 'sonnen') {
try {
await fetch('https://laufschrift.rexfue.de/switch/switch_on')
} catch (error) {
console.error('Laufschrift konnte nicht eingeschaltet werden:', error);
}
}
// Mail senden (wichtigster Teil!)
bodyText = bodytext.replace("{absagegrund}", absagegrund);
try {
const mailRet = await fetchFromDbase({
cmd: 'SENDMYMAIL',
to: ['rexfue@gmail.com'],
betreff: betreff,
body: bodyText,
bcc: liste.emails
});
if (mailRet.error) {
$('#errortext').innerHTML = mailRet.errortext
$('#errordialog-ok').addEventListener('click', () => $('#errordialog').close())
$('#errordialog').showModal();
return;
}
console.log("Mailret: ", mailRet, "Gesendet an: ", liste.emails)
// Zeige Warnung an, wenn Datum nicht gespeichert werden konnte
if (!datumGespeichert) {
$('#errortext').innerHTML = `<strong>Hinweis:</strong><br>Die Absage-Mail wurde erfolgreich versendet.<br><br>Das Absagedatum konnte jedoch nicht in der Datenbank gespeichert werden. Bitte notieren Sie manuell, dass die Absage gesendet wurde.`;
$('#errordialog-ok').addEventListener('click', () => $('#errordialog').close())
$('#errordialog').showModal();
}
$('#absagen').innerHTML = TEXTE.absagebutton(abgesagt)
$('#absagedialog').close();
} catch (error) {
console.error('Fehler beim Mail-Versand:', error);
$('#errortext').innerHTML = `<strong>Fehler beim Versenden der Absage-Mail:</strong><br>${error.message}`;
$('#errordialog-ok').addEventListener('click', () => $('#errordialog').close())
$('#errordialog').showModal();
$('#absagedialog').close();
}
});
$('#absagedialog-cancel').addEventListener('click', () => {
$('#absagedialog').close();
});
async function getDoubles(date) {
const update = { cmd: 'GET_ANMELDNEW', date: date, special: "alllater" };
let { data } = await putToDbase(update);
data.sort((a, b) => a.name.localeCompare(b.name));
data.push({name: '', vorname: '', email: ''});
let i;
for(i = 0; i < data.length - 1; i++) {
data[i].single = false;
if(data[i].name !== data[i+1].name) {
data[i].single = true;
} else {
while((i < data.length - 1) && (data[i].name === data[i+1].name)) {
if (data[i].strasse.slice(0,6).toUpperCase() !== data[i+1].strasse.slice(0,6).toUpperCase()
|| data[i].plz !== data[i+1].plz) {
data[i].single = true;
break;
} else {
i++;
}
}
}
}
data[i].single = true;
return data;
}
async function showDoubles(date) {
const erg = await getDoubles(date);
const container = $('#tnames');
clearElement(container);
appendHtml(container, `
<thead><tr>
<th class="col-2">Name</th>
<th class="col-2">Vorname</th>
<th class="col-3">Email</th>
<th class="col-1">Anz</th>
<th class="col-2">Datum</th>
<th class="col-2">Anmeldung</th>
</tr></thead><tbody>`);
for (let x of erg) {
if (x.single) continue;
appendHtml(container, `
<tr>
<td class="tdname col-2">${x.name}</td>
<td class="tdname col-2">${x.vorname}</td>
<td class="tdname col-3">${x.email}</td>
<td class="col-1">${x.anzahl}</td>
<td class="col-2">${DateTime.fromISO(x.fdatum).toFormat('yyyy-LL-dd')}</td>
<td class="col-2">${DateTime.fromISO(x.angemeldet).toFormat('yyyy-LL-dd')}</td>
</tr>`);
}
appendHtml(container, `</tbody>`);
$('#names').style.display = 'block';
}
function switchText() {
const thead = document.createElement('thead');
const row = document.createElement('tr');
let column = query.storno ? "col-2" : "col-3"
row.className = 'd-flex';
row.innerHTML = `
<th class="col-6">Name</th>
<th class=${column}>${isSmallScreen ? 'Anz' : 'Personen'}</th>`
if (query.storno) {
row.innerHTML += `<th class="col-2">Storno</th>`;
}
$('#versn').innerHTML = "Version: " + VERSION + ' vom ' + VDATE
thead.appendChild(row);
const table = $('#tabAnmeld');
clearElement(table); // Leert sowohl thead als auch tbody
table.appendChild(thead);
table.appendChild(document.createElement('tbody'));
}
// Aus dem rohen Führungsdatum von der Datenbank ein besser
// leserliches Format erzeugen
// Params:
// w -> Wochentag
// d -> Datum in DBase-Format
// t -> Uhrzeit
// Return
// neu formatierter Datums-String
const bauDate = (w, d, t) => {
const ds = String(d ?? '');
const ts = String(t ?? '');
const ws = String(w ?? '');
const dd = ds.replace(/^(\d{4})(\d{2})(\d{2})$/, '$3.$2.$1');
const hh = ts.substr(0, 2);
const ww = ws.substr(0, 2);
return `${ww}, ${dd} ${hh}:00`;
}
async function buildFuehrungsDates(dates, last) {
let n = 0;
let i = 0;
let summ = 0;
let select = `<select name='ftermin' id='ftermin'>`;
let selectedNext = false;
const today = DateTime.now().startOf('day');
for (let d of dates) {
let isselected = "";
const fday = DateTime.fromISO(d.datum);
if (!selectedNext && (isSameorAfter = fday >= today)) {
selectedNext = true;
isselected = "selected";
n = i;
}
const count = await fetchFromDbase({cmd: 'GET_COUNTS', fdate: d.datum});
const isFuehrung = (count && count > 0) ? '' : 'disabled';
let datstr
let grpstr
if (query.typ == 'regular') {
datstr = bauDate(d.wtag, d.datum, d.uhrzeit)
grpstr = `(${d.grp})`
} else {
datstr = bauDate('So', d.datum, '11')
grpstr = "&nbsp;"
}
if (isSmallScreen) {
select += `
<option value="${d.datum}" ${isselected} ${isFuehrung}> ${datstr} ${count || 0}B</option>`;
} else {
select += `
<option value="${d.datum}" ${isselected} ${isFuehrung}> ${datstr} ${grpstr} ${count || 0} Besucher</option>`;
}
i++;
summ += count ? parseInt(count) : 0;
if (d.datum >= last) break;
}
select += `</select>
<div id="gesamtsumme">Gesamtzahl der Anmeldungen (ab heute): ${summ}</div>`;
$('#auswahl').innerHTML = select;
$('#ftermin').addEventListener('change', async () => {
const date = $('#ftermin').value;
const index = $('#ftermin').selectedIndex;
setVisibility('#absagen', index === 0 || useDatum);
await showAktAnmeldungen(date);
});
return n;
}
async function main(n) {
let index = 0
const today = DateTime.now().minus({ days: 14 }).toFormat("yyyyLLdd");
const dates = await fetchFromDbase({cmd:'GET_DATES', anzahl:n, date: today});
const last = await fetchFromDbase({cmd:'GET_LASTANMELDUNG', date: today});
let mq = window.matchMedia('(max-width: 800px)');
isSmallScreen = mq.matches
switchText();
mq.addEventListener('change', async (e) => {
isSmallScreen = e.matches
switchText();
await buildFuehrungsDates(dates, last);
showAktAnmeldungen(actualdate);
});
index = await buildFuehrungsDates(dates, last);
await showAktAnmeldungen(dates[index].datum);
if (query.name !== 'Null') {
await findName(query.name);
}
if (query.double) {
await showDoubles(DateTime.now().toFormat("yyyyLLdd"));
}
}
main(ANZAHL_DATES).catch(console.error);
});