web - refactor core modules, harden authorization, simplify endpoints
This commit is contained in:
@@ -0,0 +1,15 @@
|
||||
function openBsModal(userId) {
|
||||
var modal = document.getElementById('bsModal-' + userId);
|
||||
if (modal) modal.style.display = 'flex';
|
||||
}
|
||||
function closeBsModal(userId) {
|
||||
var modal = document.getElementById('bsModal-' + userId);
|
||||
if (modal) modal.style.display = 'none';
|
||||
}
|
||||
document.addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Escape') {
|
||||
document.querySelectorAll('.standard_overlay[id^="bsModal-"]').forEach(function(m) {
|
||||
m.style.display = 'none';
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,72 @@
|
||||
'use strict';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
document.querySelectorAll('[data-auth-toast]').forEach(function (toast) {
|
||||
requestAnimationFrame(function () {
|
||||
toast.classList.add('nak-toast--visible');
|
||||
});
|
||||
});
|
||||
|
||||
var otpRequestForm = document.getElementById('otpRequestForm');
|
||||
if (otpRequestForm) {
|
||||
otpRequestForm.addEventListener('submit', function () {
|
||||
var button = document.getElementById('requestBtn');
|
||||
if (button) {
|
||||
button.classList.add('is-loading');
|
||||
button.disabled = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
var signupForm = document.getElementById('signup');
|
||||
if (!signupForm || typeof window.JustValidate !== 'function') {
|
||||
return;
|
||||
}
|
||||
|
||||
var validation = new window.JustValidate('#signup');
|
||||
validation
|
||||
.addField('#first_name', [
|
||||
{ rule: 'required', errorMessage: 'Pflichtfeld' },
|
||||
{ rule: 'maxLength', value: 100, errorMessage: 'Zu lang' }
|
||||
])
|
||||
.addField('#last_name', [
|
||||
{ rule: 'required', errorMessage: 'Pflichtfeld' },
|
||||
{ rule: 'maxLength', value: 100, errorMessage: 'Zu lang' }
|
||||
])
|
||||
.addField('#email', [
|
||||
{ rule: 'required', errorMessage: 'Pflichtfeld' },
|
||||
{ rule: 'email', errorMessage: 'Ungültig' },
|
||||
{ rule: 'maxLength', value: 254, errorMessage: 'Zu lang' }
|
||||
])
|
||||
.addField('#phone', [
|
||||
{ rule: 'required', errorMessage: 'Pflichtfeld' },
|
||||
{
|
||||
validator: function (value) {
|
||||
var digits = String(value).replace(/\D+/g, '');
|
||||
return /^[1-9][0-9]{5,14}$/.test(digits);
|
||||
},
|
||||
errorMessage: 'Ohne führende 0, 6–15 Ziffern'
|
||||
}
|
||||
])
|
||||
.addField('#password', [
|
||||
{ rule: 'required', errorMessage: 'Pflichtfeld' },
|
||||
{
|
||||
validator: function (value) {
|
||||
return String(value).length >= 8 && /[a-z]/i.test(value) && /[0-9]/.test(value);
|
||||
},
|
||||
errorMessage: 'Mind. 8 Zeichen, Buchstabe und Zahl'
|
||||
}
|
||||
])
|
||||
.addField('#password_confirmation', [
|
||||
{ rule: 'required', errorMessage: 'Pflichtfeld' },
|
||||
{
|
||||
validator: function (value, fields) {
|
||||
return value === fields['#password'].elem.value;
|
||||
},
|
||||
errorMessage: 'Passwörter müssen übereinstimmen'
|
||||
}
|
||||
])
|
||||
.onSuccess(function () {
|
||||
signupForm.submit();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,601 @@
|
||||
/* ══════════════════════════════════════
|
||||
NAVIGATION
|
||||
══════════════════════════════════════ */
|
||||
var bsCurrentSection = 0;
|
||||
var bsTotalSections = 6; // 0=intro, 1-5=pillars
|
||||
var bsCurrentSub = {}; // track sub-steps per section
|
||||
|
||||
// Init sub-step tracking
|
||||
for (var i = 1; i <= 5; i++) bsCurrentSub[i] = 1;
|
||||
|
||||
function bsGoToSection(section) {
|
||||
var scoreEl = document.getElementById('bsSectionScore');
|
||||
if (scoreEl) scoreEl.style.display = 'none';
|
||||
|
||||
// Hide all sections
|
||||
for (var i = 0; i < bsTotalSections; i++) {
|
||||
var el = document.getElementById('bsSection' + i);
|
||||
if (el) el.style.display = 'none';
|
||||
}
|
||||
// Show target section
|
||||
var target = document.getElementById('bsSection' + section);
|
||||
if (target) target.style.display = '';
|
||||
|
||||
bsCurrentSection = section;
|
||||
bsUpdateMainProgress(section);
|
||||
|
||||
// Reset to first sub-step when entering a section
|
||||
if (section >= 1 && section <= 5) {
|
||||
bsCurrentSub[section] = 1;
|
||||
bsShowSubStep(section, 1);
|
||||
}
|
||||
|
||||
bsScrollToTop();
|
||||
}
|
||||
|
||||
function bsUpdateMainProgress(section) {
|
||||
var steps = document.querySelectorAll('#bsMainProgress .bs-progress-step');
|
||||
var lines = document.querySelectorAll('#bsMainProgress .bs-progress-line');
|
||||
steps.forEach(function(el, idx) {
|
||||
el.classList.remove('active', 'done');
|
||||
if (idx === section) el.classList.add('active');
|
||||
else if (idx < section) el.classList.add('done');
|
||||
});
|
||||
lines.forEach(function(el, idx) {
|
||||
el.classList.toggle('done', idx < section);
|
||||
});
|
||||
}
|
||||
|
||||
/* ── Sub-step navigation ── */
|
||||
function bsSubStep(section, step) {
|
||||
bsCurrentSub[section] = step;
|
||||
bsShowSubStep(section, step);
|
||||
bsScrollToTop();
|
||||
}
|
||||
|
||||
function bsShowSubStep(section, step) {
|
||||
// Count sub-steps
|
||||
var maxSub = document.querySelectorAll('#bsSection' + section + ' .bs-substep').length;
|
||||
for (var i = 1; i <= maxSub; i++) {
|
||||
var el = document.getElementById('bsSub' + section + '_' + i);
|
||||
if (el) el.style.display = (i === step) ? '' : 'none';
|
||||
}
|
||||
}
|
||||
|
||||
function bsScrollToTop() {
|
||||
window.scrollTo({top: 0, behavior: 'smooth'});
|
||||
var headerHeight = parseFloat(getComputedStyle(document.documentElement).getPropertyValue('--header-height')) || 64;
|
||||
var elemHeaderMenue = document.getElementById('header_menue');
|
||||
var elemHeaderTitle = document.getElementById('header_title');
|
||||
if (elemHeaderMenue) elemHeaderMenue.style.top = '0';
|
||||
if (elemHeaderTitle) elemHeaderTitle.style.top = headerHeight + 'px';
|
||||
}
|
||||
|
||||
/* ── Dropdown Matching ── */
|
||||
function bsCheckDropdownMatch() {
|
||||
var container = document.getElementById('match3');
|
||||
var rows = container.querySelectorAll('.bs-dropdown-match-row');
|
||||
var correct = 0;
|
||||
var total = rows.length;
|
||||
rows.forEach(function(row) {
|
||||
var select = row.querySelector('select');
|
||||
row.classList.remove('correct', 'wrong');
|
||||
if (select.value === select.dataset.correct) {
|
||||
row.classList.add('correct');
|
||||
correct++;
|
||||
} else {
|
||||
row.classList.add('wrong');
|
||||
}
|
||||
});
|
||||
var fb = document.getElementById('match3_fb');
|
||||
if (correct === total) {
|
||||
fb.className = 'bs-quiz-feedback show correct';
|
||||
fb.textContent = 'Richtig: Alle Zuordnungen korrekt! Gut gemacht!';
|
||||
} else {
|
||||
fb.className = 'bs-quiz-feedback show wrong';
|
||||
fb.textContent = correct + ' von ' + total + ' richtig. Bitte überprüfen Sie Ihre Auswahl.';
|
||||
}
|
||||
return correct === total;
|
||||
}
|
||||
|
||||
/* ══════════════════════════════════════
|
||||
FILL-IN-THE-BLANK
|
||||
══════════════════════════════════════ */
|
||||
function bsCheckFill(ids) {
|
||||
var correct = 0;
|
||||
var total = ids.length;
|
||||
|
||||
ids.forEach(function(id) {
|
||||
var input = document.getElementById(id);
|
||||
var answer = input.dataset.answer.toLowerCase().trim();
|
||||
var value = input.value.toLowerCase().trim();
|
||||
|
||||
input.classList.remove('correct', 'wrong');
|
||||
if (value === answer) {
|
||||
input.classList.add('correct');
|
||||
correct++;
|
||||
} else {
|
||||
input.classList.add('wrong');
|
||||
}
|
||||
});
|
||||
|
||||
// Find feedback element near the button
|
||||
var fb = document.getElementById('fill2_fb');
|
||||
if (fb) {
|
||||
fb.classList.remove('correct', 'wrong');
|
||||
fb.classList.add('show');
|
||||
if (correct === total) {
|
||||
fb.classList.add('correct');
|
||||
fb.textContent = 'Richtig: Alle Lücken richtig ausgefüllt!';
|
||||
} else {
|
||||
fb.classList.add('wrong');
|
||||
fb.textContent = correct + ' von ' + total + ' richtig. Korrekte Antworten: 112, Klixstraße 17, 13403';
|
||||
}
|
||||
}
|
||||
return correct === total;
|
||||
}
|
||||
|
||||
/* ══════════════════════════════════════
|
||||
MULTI-SELECT CHECKBOX QUIZ
|
||||
══════════════════════════════════════ */
|
||||
function bsToggleCheckbox(optionEl) {
|
||||
var cb = optionEl.querySelector('input[type="checkbox"]');
|
||||
cb.checked = !cb.checked;
|
||||
optionEl.classList.toggle('selected', cb.checked);
|
||||
}
|
||||
|
||||
function bsCheckMultiGeneric(quizId, correctMsg, wrongMsg) {
|
||||
var quiz = document.getElementById(quizId);
|
||||
var options = quiz.querySelectorAll('.bs-quiz-option');
|
||||
var allCorrect = true;
|
||||
|
||||
options.forEach(function(opt) {
|
||||
var cb = opt.querySelector('input[type="checkbox"]');
|
||||
var isCorrect = cb.dataset.correct === 'true';
|
||||
opt.classList.remove('correct', 'wrong');
|
||||
|
||||
if (isCorrect && cb.checked) {
|
||||
opt.classList.add('correct');
|
||||
} else if (isCorrect && !cb.checked) {
|
||||
opt.classList.add('correct');
|
||||
allCorrect = false;
|
||||
} else if (!isCorrect && cb.checked) {
|
||||
opt.classList.add('wrong');
|
||||
allCorrect = false;
|
||||
}
|
||||
});
|
||||
|
||||
var fb = document.getElementById(quizId + '_fb');
|
||||
fb.classList.remove('correct', 'wrong');
|
||||
fb.classList.add('show');
|
||||
if (allCorrect) {
|
||||
fb.classList.add('correct');
|
||||
fb.textContent = correctMsg;
|
||||
} else {
|
||||
fb.classList.add('wrong');
|
||||
fb.textContent = wrongMsg;
|
||||
}
|
||||
return allCorrect;
|
||||
}
|
||||
|
||||
/* ══════════════════════════════════════
|
||||
INDIVIDUAL QUIZ GATES
|
||||
══════════════════════════════════════ */
|
||||
var bsQuizPassed = {};
|
||||
var bsQuizAttempts = {};
|
||||
var bsQuizCorrect = {}; // true = correct, false = nach drei Versuchen beendet
|
||||
|
||||
var bsQuizGates = [
|
||||
{ section: 1, nextBtnId: 'bsNextS1Q', quizIds: ['quiz1_1', 'quiz1_2', 'quiz1_3'] },
|
||||
{ section: 2, nextBtnId: 'bsNextS2Q', quizIds: ['scenario2_1', 'fill2'] },
|
||||
{ section: 3, nextBtnId: 'bsNextS3Q', quizIds: ['match3', 'quiz3_1', 'quiz3_2'] },
|
||||
{ section: 4, nextBtnId: 'bsNextS4Q', quizIds: ['tf4', 'scenario4_1'] },
|
||||
{ section: 5, nextBtnId: 'bsNextS5P1', quizIds: ['bs5q1', 'bs5q2', 'bs5q3', 'bs5q4', 'bs5q5'] },
|
||||
{ section: 5, nextBtnId: 'bsNextS5P2', quizIds: ['bs5q6', 'bs5q7', 'bs5q8', 'bs5q9', 'bs5q10'] }
|
||||
];
|
||||
|
||||
var bsQuizMessages = {
|
||||
scenario2_1: [
|
||||
'Richtig! Nicht durch das verrauchte Treppenhaus gehen und keinen Löschversuch im Rauch unternehmen. Richtig ist: 112 anrufen, warnen, in einem rauchfreien Raum bleiben und sich am Fenster bemerkbar machen.',
|
||||
'Falsch. Gesucht sind die Verhaltensweisen, die man nicht tun sollte: durch das verrauchte Treppenhaus laufen oder im verrauchten Bereich selbst löschen.'
|
||||
],
|
||||
tf4: [
|
||||
'Richtig: Alle Antworten richtig! Ausgezeichnet!',
|
||||
'Nicht ganz richtig. Die korrekten Antworten sind markiert.'
|
||||
],
|
||||
scenario4_1: [
|
||||
'Richtig! Bei Elektrobränden niemals Wasser verwenden, Stromschlaggefahr! Der CO₂-Löscher ist die richtige Wahl.',
|
||||
'Falsch. Bei Elektrobränden darf kein Wasser verwendet werden (Stromschlaggefahr). Ein CO₂-Löscher ist hier das richtige Löschmittel.'
|
||||
],
|
||||
bs5q1: [
|
||||
'Richtig! Die Feuerlöscher befinden sich im Vorraum am seitlichen Ausgang, im Treppenhaus 1. OG und im hinteren Kirchenanbau neben der Toilette.',
|
||||
'Falsch: Nicht ganz. Die korrekten Standorte sind: Vorraum am seitlichen Ausgang, Treppenhaus 1. OG und hinterer Kirchenanbau neben der Toilette.'
|
||||
],
|
||||
bs5q2: [
|
||||
'Richtig! Alle drei Ausgänge stehen zur Verfügung: Haupteingang, hinterer Kirchenanbau und seitlicher Eingang. Achte auf die Stufen und den Gegenverkehr von der Empore!',
|
||||
'Falsch: Nicht ganz. Die korrekten Rettungswege sind: Haupteingang (Stufen!), hinterer Kirchenanbau (Stufen!) und seitlicher Eingang (Emporenverkehr!). Fenster sind keine Rettungswege.'
|
||||
],
|
||||
bs5q3: [
|
||||
'Richtig! Wir haben keine Brandmeldeanlage. Deshalb ist es besonders wichtig, dass alle Anwesenden wachsam sind und die Abläufe kennen.',
|
||||
'Falsch. Wir haben keine Brandmeldeanlage, daher müssen wir uns umso mehr auf geschultes Verhalten und aufmerksame Personen verlassen.'
|
||||
],
|
||||
bs5q4: [
|
||||
'Richtig! Die Adresse für den Notruf findest du innen an der rechten Tür des Haupteingangs. Merke dir diesen Ort!',
|
||||
'Falsch. Die Adresse befindet sich innen an der rechten Tür des Haupteingangs (Fluchttür), nicht auf dem Altar oder im Treppenhaus.'
|
||||
],
|
||||
bs5q5: [
|
||||
'Richtig! Unsere Kirchengemeinde fasst 180 bis 200 Personen.',
|
||||
'Falsch. Unsere Kirchengemeinde fasst 180 bis 200 Personen, nicht mehr.'
|
||||
],
|
||||
bs5q6: [
|
||||
'Richtig! Die Gefahrenstellen sind: Stufen zum Anbau, Stufen am Haupteingang und das Treppenhaus an der Empore (Gegenverkehr).',
|
||||
'Falsch: Nicht ganz. Die Gefahrenstellen sind: Stufen zwischen Kirchenschiff und Anbau, Stufen vor dem Haupteingang und das Treppenhaus an der Empore. Die Rampe ist keine Gefahrenstelle.'
|
||||
],
|
||||
bs5q7: [
|
||||
'Richtig! Die zuständige vBsU koordiniert, eine klare Zuständigkeit verhindert Chaos und widersprüchliche Anweisungen.',
|
||||
'Falsch. Nur die zuständige vBsU koordiniert die Aktionen. Das verhindert widersprüchliche Anweisungen und sorgt für eine geordnete Evakuierung.'
|
||||
],
|
||||
bs5q8: [
|
||||
'Richtig! Notleuchten befinden sich an sechs Standorten: Sakristei, unterm Altar, seitliche Klappen, Empore, Eltern-Kind-Raum und Vorraum.',
|
||||
'Falsch: Nicht ganz. Die Notleuchten befinden sich an: Sakristei, unterm Altar, letzte seitliche Klappen im Kirchenschiff, Empore (Tisch), Eltern-Kind-Raum (Schrank unter TV) und „Diakonie-Schrank" im Vorraum.'
|
||||
],
|
||||
bs5q9: [
|
||||
'Richtig! Der vorbereitete Text wird vom Pult aus verlesen, so erreicht die Information alle Anwesenden gleichzeitig und klar verständlich.',
|
||||
'Falsch. Die Räumung wird eingeleitet, indem der vorbereitete Text „zur Räumung des Gebäudes" vom Pult aus verlesen wird (rotes, laminiertes DIN-A4-Blatt). So wird sichergestellt, dass alle die gleiche, klare Ansage hören.'
|
||||
],
|
||||
bs5q10: [
|
||||
'Richtig! Anwesende Ersthelfer*innen übernehmen die weitere Erste Hilfe. Die vBsU koordiniert die Evakuierung, nicht die medizinische Versorgung.',
|
||||
'Falsch. Für die weitere Erste Hilfe sind die anwesenden Ersthelfer*innen zuständig, nicht die vBsU. Für weitergehende Erste Hilfe ist eine gesonderte Schulung notwendig. Die erste Hilfeleistung ist natürlich von jedem erwünscht und gesetzlich vorgeschrieben.'
|
||||
]
|
||||
};
|
||||
|
||||
function bsGetQuizGate(quizId) {
|
||||
return bsQuizGates.find(function(gate) {
|
||||
return gate.quizIds.includes(quizId);
|
||||
}) || null;
|
||||
}
|
||||
|
||||
|
||||
function bsGetAllQuizIds() {
|
||||
return bsQuizGates.reduce(function(ids, gate) { return ids.concat(gate.quizIds); }, []);
|
||||
}
|
||||
|
||||
function bsRenderQuizResult(quizId) {
|
||||
if (quizId === 'fill2') {
|
||||
return bsCheckFill(['fill2_1', 'fill2_2', 'fill2_3']);
|
||||
}
|
||||
if (quizId === 'match3') {
|
||||
return bsCheckDropdownMatch();
|
||||
}
|
||||
|
||||
var messages = bsQuizMessages[quizId] || ['Richtig!', 'Falsch: Leider falsch. Die richtige Antwort ist markiert.'];
|
||||
return bsCheckMultiGeneric(quizId, messages[0], messages[1]);
|
||||
}
|
||||
|
||||
function bsGetScoreStats() {
|
||||
var allIds = bsGetAllQuizIds();
|
||||
var total = allIds.length;
|
||||
var correct = 0;
|
||||
var wrong = 0;
|
||||
var open = 0;
|
||||
allIds.forEach(function(id) {
|
||||
if (bsQuizCorrect[id] === true) correct++;
|
||||
else if (bsQuizCorrect[id] === false) wrong++;
|
||||
else open++;
|
||||
});
|
||||
var percent = total > 0 ? Math.round((correct / total) * 100) : 0;
|
||||
return { correct: correct, wrong: wrong, open: open, total: total, percent: percent };
|
||||
}
|
||||
|
||||
function bsShowSectionScore(completedSection, nextAction) {
|
||||
var stats = bsGetScoreStats();
|
||||
var answered = stats.correct + stats.wrong;
|
||||
var maxReachable = stats.total > 0 ? 100 - Math.round((stats.wrong / stats.total) * 100) : 100;
|
||||
|
||||
var scoreEl = document.getElementById('bsSectionScore');
|
||||
var body = scoreEl.querySelector('.bs-body');
|
||||
|
||||
var barColor = maxReachable >= 70 ? 'var(--bs-green)' : 'var(--bs-red)';
|
||||
|
||||
body.innerHTML =
|
||||
'<div class="bs-chapter-title">Zwischenstand nach Säule ' + completedSection + '</div>' +
|
||||
'<div class="bs-score-bar-wrap">' +
|
||||
'<div class="bs-score-bar"><div class="bs-score-bar-fill" style="width:' + maxReachable + '%;background:' + barColor + '"></div></div>' +
|
||||
'<div class="bs-score-bar-label">Max. erreichbar: ' + maxReachable + '% (mindestens 70% erforderlich)</div>' +
|
||||
'</div>' +
|
||||
'<div class="bs-score-grid">' +
|
||||
'<div class="bs-score-card bs-score-correct"><div class="bs-score-card-num">' + stats.correct + '</div><div class="bs-score-card-label">Richtig</div></div>' +
|
||||
'<div class="bs-score-card bs-score-wrong"><div class="bs-score-card-num">' + stats.wrong + '</div><div class="bs-score-card-label">Falsch</div></div>' +
|
||||
'<div class="bs-score-card bs-score-open"><div class="bs-score-card-num">' + stats.open + '</div><div class="bs-score-card-label">Offen</div></div>' +
|
||||
'<div class="bs-score-card"><div class="bs-score-card-num">' + answered + '/' + stats.total + '</div><div class="bs-score-card-label">Gesamt</div></div>' +
|
||||
'</div>';
|
||||
|
||||
var actionsRight = scoreEl.querySelector('.bs-actions-right');
|
||||
actionsRight.innerHTML = '';
|
||||
|
||||
if (typeof nextAction === 'function') {
|
||||
var btn = document.createElement('button');
|
||||
btn.type = 'button';
|
||||
btn.className = 'bs-btn bs-btn-primary';
|
||||
btn.textContent = completedSection < 5 ? 'Weiter zu Säule ' + (completedSection + 1) + ' >' : 'Zum Ergebnis >';
|
||||
btn.onclick = nextAction;
|
||||
actionsRight.appendChild(btn);
|
||||
}
|
||||
|
||||
var actionsLeft = scoreEl.querySelector('.bs-actions-left');
|
||||
actionsLeft.innerHTML = '<button type="button" class="bs-btn" onclick="bsGoToSection(' + completedSection + ')">< Zurück zu Säule ' + completedSection + '</button>';
|
||||
|
||||
for (var i = 0; i < 6; i++) {
|
||||
var el = document.getElementById('bsSection' + i);
|
||||
if (el) el.style.display = 'none';
|
||||
}
|
||||
scoreEl.style.display = '';
|
||||
bsScrollToTop();
|
||||
}
|
||||
|
||||
function bsFinalResultAction() {
|
||||
document.getElementById('bsSectionScore').style.display = 'none';
|
||||
bsShowFinalResult();
|
||||
document.getElementById('bsSection5').style.display = '';
|
||||
bsCurrentSub[5] = 5;
|
||||
bsShowSubStep(5, 5);
|
||||
bsScrollToTop();
|
||||
}
|
||||
|
||||
function bsShowFinalResult() {
|
||||
var stats = bsGetScoreStats();
|
||||
var passed = stats.percent >= 70;
|
||||
|
||||
var section = document.getElementById('bsSub5_5');
|
||||
var body = section.querySelector('.bs-body');
|
||||
|
||||
var barColor = passed ? 'var(--bs-green)' : 'var(--bs-red)';
|
||||
|
||||
var html = '<div class="bs-chapter-title">' + (passed ? 'Schulung bestanden!' : 'Schulung nicht bestanden') + '</div>';
|
||||
|
||||
html += '<div class="bs-score-bar-wrap">' +
|
||||
'<div class="bs-score-bar"><div class="bs-score-bar-fill" style="width:' + stats.percent + '%;background:' + barColor + '"></div></div>' +
|
||||
'<div class="bs-score-bar-label">' + stats.percent + '% richtig (' + stats.correct + ' von ' + stats.total + ' Fragen)</div>' +
|
||||
'</div>';
|
||||
|
||||
html += '<div class="bs-score-grid">' +
|
||||
'<div class="bs-score-card bs-score-correct"><div class="bs-score-card-num">' + stats.correct + '</div><div class="bs-score-card-label">Richtig</div></div>' +
|
||||
'<div class="bs-score-card bs-score-wrong"><div class="bs-score-card-num">' + stats.wrong + '</div><div class="bs-score-card-label">Falsch</div></div>' +
|
||||
'</div>';
|
||||
|
||||
if (passed) {
|
||||
html += '<div class="bs-successbox" style="margin-top:20px;">' +
|
||||
'<strong>Herzlichen Glückwunsch!</strong> Du hast die Brandschutzunterweisung erfolgreich absolviert. ' +
|
||||
'Du kennst jetzt die Grundlagen der Brandverhütung, weißt, wie du dich im Brandfall verhältst, ' +
|
||||
'kennst die Aufgaben der vBsU, kannst einen Feuerlöscher richtig einsetzen und weißt, wo sich die wichtigsten ' +
|
||||
'Einrichtungen in unserer Kirchengemeinde befinden.' +
|
||||
'</div>';
|
||||
html += '<div style="margin-top:24px;padding:20px;background:var(--gray-50);border:1px solid var(--gray-200);">' +
|
||||
'<label style="display:flex;align-items:flex-start;gap:12px;cursor:pointer;">' +
|
||||
'<input type="checkbox" id="bsConfirmCheckbox" onchange="bsToggleConfirm()" style="width:20px;height:20px;margin-top:2px;">' +
|
||||
'<span style="font-size:var(--font-size-sm);color:var(--gray-800);line-height:1.6;">' +
|
||||
'Ich bestätige, dass ich die Inhalte der <a href="/intranet_repo/projects/brandschutz/Brandschutzordnung.pdf" target="_blank" onclick="event.stopPropagation();">Brandschutzordnung</a> verstanden und verinnerlicht habe. ' +
|
||||
'Ich bin bereit, die Aufgaben als vBsU zu übernehmen, sofern ich dazu eingeteilt werde.' +
|
||||
'</span>' +
|
||||
'</label>' +
|
||||
'</div>';
|
||||
html += '<button type="button" class="bs-btn bs-btn-primary bs-btn-locked" id="bsFinishBtn" onclick="bsFinishTraining()" style="margin-top:24px;width:100%;padding:16px;font-size:var(--font-size-md);">Schulung abschließen</button>';
|
||||
} else {
|
||||
html += '<div class="bs-warnbox" style="margin-top:20px;">' +
|
||||
'<div class="bs-warnbox-title">Leider nicht bestanden</div>' +
|
||||
'Du hast ' + stats.percent + '% der Fragen richtig beantwortet. Zum Bestehen sind mindestens 70% erforderlich. ' +
|
||||
'Bitte wiederhole die Schulung, um alle Inhalte zu verinnerlichen.' +
|
||||
'</div>';
|
||||
html += '<button type="button" class="bs-btn bs-btn-primary" onclick="bsRestartTraining()" style="margin-top:24px;width:100%;padding:16px;font-size:var(--font-size-md);">Schulung neu starten</button>';
|
||||
}
|
||||
|
||||
body.innerHTML = html;
|
||||
}
|
||||
|
||||
function bsSetQuizButtonState(quizId, state) {
|
||||
var btn = document.getElementById('bsCheck_' + quizId);
|
||||
if (!btn) return;
|
||||
btn.classList.remove('bs-btn-success', 'bs-btn-failed', 'bs-btn-check-disabled');
|
||||
if (state === 'passed') {
|
||||
btn.textContent = 'Richtig';
|
||||
btn.classList.add('bs-btn-success', 'bs-btn-check-disabled');
|
||||
btn.onclick = null;
|
||||
return;
|
||||
}
|
||||
if (state === 'failed') {
|
||||
btn.textContent = 'Falsch';
|
||||
btn.classList.add('bs-btn-failed', 'bs-btn-check-disabled');
|
||||
btn.onclick = null;
|
||||
return;
|
||||
}
|
||||
btn.textContent = 'Antwort prüfen';
|
||||
btn.onclick = function() { bsCheckIndividualQuiz(quizId); };
|
||||
}
|
||||
|
||||
function bsUpdatePageGate(nextBtnId, quizIds) {
|
||||
var nextBtn = document.getElementById(nextBtnId);
|
||||
if (!nextBtn) return;
|
||||
var allPassed = quizIds.every(function(id) { return !!bsQuizPassed[id]; });
|
||||
nextBtn.classList.toggle('bs-btn-locked', !allPassed);
|
||||
}
|
||||
|
||||
/* Validate quiz without marking options */
|
||||
function bsValidateQuiz(quizId) {
|
||||
var quiz = document.getElementById(quizId);
|
||||
if (!quiz) return false;
|
||||
|
||||
// Checkbox-based quizzes
|
||||
var cbOptions = quiz.querySelectorAll('.bs-quiz-option input[type="checkbox"]');
|
||||
if (cbOptions.length > 0) {
|
||||
var ok = true;
|
||||
cbOptions.forEach(function(cb) {
|
||||
var isCorrect = cb.dataset.correct === 'true';
|
||||
if ((isCorrect && !cb.checked) || (!isCorrect && cb.checked)) ok = false;
|
||||
});
|
||||
return ok;
|
||||
}
|
||||
|
||||
// Fill-in-the-blank
|
||||
var fills = quiz.querySelectorAll('.bs-fill-input');
|
||||
if (fills.length > 0) {
|
||||
var ok = true;
|
||||
fills.forEach(function(input) {
|
||||
if (input.value.toLowerCase().trim() !== input.dataset.answer.toLowerCase().trim()) ok = false;
|
||||
});
|
||||
return ok;
|
||||
}
|
||||
|
||||
// Dropdown matching
|
||||
var selects = quiz.querySelectorAll('.bs-dropdown-match-select');
|
||||
if (selects.length > 0) {
|
||||
var ok = true;
|
||||
selects.forEach(function(sel) {
|
||||
if (sel.value !== sel.dataset.correct) ok = false;
|
||||
});
|
||||
return ok;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function bsCheckIndividualQuiz(quizId) {
|
||||
var gate = bsGetQuizGate(quizId);
|
||||
if (!gate) return;
|
||||
|
||||
if (!bsQuizAttempts[quizId]) bsQuizAttempts[quizId] = 0;
|
||||
bsQuizAttempts[quizId]++;
|
||||
var attempt = bsQuizAttempts[quizId];
|
||||
var passed = bsValidateQuiz(quizId);
|
||||
|
||||
if (passed) {
|
||||
// Correct — show markings and lock
|
||||
bsRenderQuizResult(quizId);
|
||||
bsQuizPassed[quizId] = true;
|
||||
bsQuizCorrect[quizId] = true;
|
||||
bsSetQuizButtonState(quizId, 'passed');
|
||||
document.getElementById(quizId).classList.add('bs-quiz-locked');
|
||||
} else if (attempt >= 3) {
|
||||
// Third wrong attempt — show solution, lock as failed
|
||||
bsRenderQuizResult(quizId);
|
||||
bsQuizPassed[quizId] = true; // done (failed but completed)
|
||||
bsQuizCorrect[quizId] = false;
|
||||
bsSetQuizButtonState(quizId, 'failed');
|
||||
document.getElementById(quizId).classList.add('bs-quiz-locked');
|
||||
} else {
|
||||
// Wrong but retries left — show hint, keep selections
|
||||
var remaining = 3 - attempt;
|
||||
var fb = document.getElementById(quizId + '_fb');
|
||||
if (fb) {
|
||||
fb.classList.remove('correct', 'wrong');
|
||||
fb.classList.add('show', 'wrong');
|
||||
fb.textContent = 'Leider falsch. Du hast noch ' + remaining + (remaining === 1 ? ' Versuch.' : ' Versuche.');
|
||||
}
|
||||
}
|
||||
|
||||
bsUpdatePageGate(gate.nextBtnId, gate.quizIds);
|
||||
}
|
||||
|
||||
|
||||
function bsResetQuizContainer(containerId) {
|
||||
var container = document.getElementById(containerId);
|
||||
if (!container) return;
|
||||
container.querySelectorAll('input[type="checkbox"]').forEach(function(input) {
|
||||
input.checked = false;
|
||||
});
|
||||
container.querySelectorAll('.bs-quiz-option').forEach(function(opt) {
|
||||
opt.classList.remove('correct', 'wrong', 'selected');
|
||||
});
|
||||
container.querySelectorAll('select').forEach(function(select) {
|
||||
select.selectedIndex = 0;
|
||||
});
|
||||
container.querySelectorAll('.bs-dropdown-match-row').forEach(function(row) {
|
||||
row.classList.remove('correct', 'wrong');
|
||||
});
|
||||
container.querySelectorAll('input[type="text"]').forEach(function(input) {
|
||||
input.value = '';
|
||||
input.classList.remove('correct', 'wrong');
|
||||
});
|
||||
container.querySelectorAll('.bs-quiz-feedback, .bs-feedback').forEach(function(fb) {
|
||||
fb.classList.remove('show', 'correct', 'wrong');
|
||||
fb.textContent = '';
|
||||
});
|
||||
}
|
||||
|
||||
function bsUpdateChecklistNext(checklistId, buttonId) {
|
||||
const checklist = document.getElementById(checklistId);
|
||||
const button = document.getElementById(buttonId);
|
||||
|
||||
if (!checklist || !button) return;
|
||||
|
||||
const items = checklist.querySelectorAll('li');
|
||||
const checkedItems = checklist.querySelectorAll('li.checked');
|
||||
const allChecked = items.length > 0 && items.length === checkedItems.length;
|
||||
|
||||
button.classList.toggle('bs-btn-locked', !allChecked);
|
||||
}
|
||||
|
||||
function bsRestartTraining() {
|
||||
bsQuizPassed = {};
|
||||
bsQuizAttempts = {};
|
||||
bsQuizCorrect = {};
|
||||
|
||||
bsGetAllQuizIds().forEach(function(id) {
|
||||
bsResetQuizContainer(id);
|
||||
bsSetQuizButtonState(id, 'default');
|
||||
var quiz = document.getElementById(id);
|
||||
if (quiz) quiz.classList.remove('bs-quiz-locked');
|
||||
});
|
||||
|
||||
['bsNextS1Q', 'bsNextS2Q', 'bsNextS3Q', 'bsNextS4Q', 'bsNextS5P1', 'bsNextS5P2'].forEach(function(id) {
|
||||
var btn = document.getElementById(id);
|
||||
if (btn) btn.classList.add('bs-btn-locked');
|
||||
});
|
||||
|
||||
var checklist = document.getElementById('bsChecklist3_2');
|
||||
if (checklist) {
|
||||
checklist.querySelectorAll('li.checked').forEach(function(li) { li.classList.remove('checked'); });
|
||||
var clBtn = document.getElementById('bsNextSub3_2');
|
||||
if (clBtn) clBtn.classList.add('bs-btn-locked');
|
||||
}
|
||||
|
||||
bsGoToSection(0);
|
||||
}
|
||||
|
||||
/* ══════════════════════════════════════
|
||||
ABSCHLUSS, BESTÄTIGUNG
|
||||
══════════════════════════════════════ */
|
||||
function bsToggleConfirm() {
|
||||
var checkbox = document.getElementById('bsConfirmCheckbox');
|
||||
var finishBtn = document.getElementById('bsFinishBtn');
|
||||
if (checkbox.checked) {
|
||||
finishBtn.classList.remove('bs-btn-locked');
|
||||
} else {
|
||||
finishBtn.classList.add('bs-btn-locked');
|
||||
}
|
||||
}
|
||||
|
||||
function bsFinishTraining() {
|
||||
var checkbox = document.getElementById('bsConfirmCheckbox');
|
||||
var form = document.getElementById('bsCompleteForm');
|
||||
if (!checkbox || !checkbox.checked || !form) return;
|
||||
form.submit();
|
||||
}
|
||||
|
||||
/* ══════════════════════════════════════
|
||||
SHUFFLE QUIZ OPTIONS ON LOAD
|
||||
══════════════════════════════════════ */
|
||||
(function() {
|
||||
var quizBlocks = document.querySelectorAll('.bs-quiz-options');
|
||||
quizBlocks.forEach(function(container) {
|
||||
var options = Array.from(container.querySelectorAll(':scope > .bs-quiz-option'));
|
||||
if (options.length < 2) return;
|
||||
// Fisher-Yates shuffle
|
||||
for (var i = options.length - 1; i > 0; i--) {
|
||||
var j = Math.floor(Math.random() * (i + 1));
|
||||
var temp = options[i];
|
||||
options[i] = options[j];
|
||||
options[j] = temp;
|
||||
}
|
||||
options.forEach(function(opt) { container.appendChild(opt); });
|
||||
});
|
||||
})();
|
||||
@@ -0,0 +1,47 @@
|
||||
(function () {
|
||||
function switchTab(tabName, updateHash) {
|
||||
const tab = document.querySelector('.content-tab-bar__item[data-tab="' + tabName + '"]');
|
||||
const content = document.getElementById('tab-' + tabName);
|
||||
if (!tab || !content) return;
|
||||
|
||||
document.querySelectorAll('.content-tab-bar__item').forEach(function (item) {
|
||||
item.classList.toggle('active', item === tab);
|
||||
});
|
||||
document.querySelectorAll('.sb-tab-content').forEach(function (item) {
|
||||
item.classList.toggle('active', item === content);
|
||||
});
|
||||
|
||||
if (updateHash) {
|
||||
history.replaceState(null, '', '#' + tabName);
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
document.querySelectorAll('.content-tab-bar__item[data-tab]').forEach(function (tab) {
|
||||
tab.addEventListener('click', function () {
|
||||
switchTab(tab.dataset.tab, true);
|
||||
});
|
||||
});
|
||||
|
||||
const hash = window.location.hash.slice(1);
|
||||
if (hash) {
|
||||
switchTab(hash, false);
|
||||
}
|
||||
|
||||
document.querySelectorAll('[data-confirm]').forEach(function (button) {
|
||||
button.addEventListener('click', function (event) {
|
||||
if (!window.confirm(button.dataset.confirm || 'Wirklich fortfahren?')) {
|
||||
event.preventDefault();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
document.querySelectorAll('[data-upload-form]').forEach(function (form) {
|
||||
form.addEventListener('submit', function () {
|
||||
form.classList.add('sb-uploading');
|
||||
const button = form.querySelector('.sb-upload-button');
|
||||
if (button) button.disabled = true;
|
||||
});
|
||||
});
|
||||
});
|
||||
})();
|
||||
@@ -169,17 +169,13 @@ function showHideText(element) {
|
||||
|
||||
// Per Taste Overlay verlassen
|
||||
document.addEventListener('keydown', function(event) {
|
||||
const profileVisitOverlay = document.getElementById('profile_visit_overlay');
|
||||
const profileCalendarOverlay = document.getElementById('profile_calendar_overlay');
|
||||
const seelsorgeProfileOverlay = document.getElementById('profile_seelsorge_overlay');
|
||||
const addNewUser = document.getElementById('addNewUser');
|
||||
const showMessage = document.getElementById('showMessage');
|
||||
|
||||
if (event.key === 'Escape') {
|
||||
if (profileVisitOverlay && profileVisitOverlay.style.display === 'flex') {
|
||||
openCloseProfile(0, 'profile_visit', 'profile_visit_overlay');
|
||||
}
|
||||
if (profileCalendarOverlay && profileCalendarOverlay.style.display === 'flex') {
|
||||
openCloseProfile(0, 'profile_calendar', 'profile_calendar_overlay');
|
||||
if (seelsorgeProfileOverlay && seelsorgeProfileOverlay.style.display === 'flex') {
|
||||
openCloseProfile(0, 'profile_seelsorge', 'profile_seelsorge_overlay');
|
||||
}
|
||||
if (addNewUser && addNewUser.style.display === 'flex') {
|
||||
openCloseOverlay('addNewUser');
|
||||
|
||||
@@ -0,0 +1,173 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
var currentStep = 1;
|
||||
|
||||
function byId(id) {
|
||||
return document.getElementById(id);
|
||||
}
|
||||
|
||||
function selectedGroup(form) {
|
||||
return form.querySelector('input[name="group"]:checked');
|
||||
}
|
||||
|
||||
function showAlert(message) {
|
||||
window.alert(message);
|
||||
return false;
|
||||
}
|
||||
|
||||
function validateStepOne(form) {
|
||||
var selected = selectedGroup(form);
|
||||
if (!selected) {
|
||||
return showAlert('Bitte wählen Sie eine Terminart aus.');
|
||||
}
|
||||
|
||||
if (selected.dataset.kalliCustom === '1' && !byId('kalliUserInput').value.trim()) {
|
||||
return showAlert('Bitte geben Sie einen Titel für den benutzerdefinierten Termin ein.');
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function validateStepTwo() {
|
||||
var dateStart = byId('kalliDateS').value;
|
||||
var dateEnd = byId('kalliDateE').value;
|
||||
var timeStart = byId('kalliTimeS').value;
|
||||
var timeEnd = byId('kalliTimeE').value;
|
||||
|
||||
if (!dateStart) {
|
||||
return showAlert('Bitte geben Sie ein Datum ein.');
|
||||
}
|
||||
if (dateEnd && dateEnd < dateStart) {
|
||||
return showAlert('Das Enddatum darf nicht vor dem Startdatum liegen.');
|
||||
}
|
||||
if (timeStart && timeEnd && (!dateEnd || dateEnd === dateStart) && timeEnd < timeStart) {
|
||||
return showAlert('Die Endzeit darf am selben Tag nicht vor der Startzeit liegen.');
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function updateProgress(step) {
|
||||
document.querySelectorAll('.kalli-progress-step').forEach(function (element, index) {
|
||||
var elementStep = index + 1;
|
||||
element.classList.toggle('active', elementStep === step);
|
||||
element.classList.toggle('done', elementStep < step);
|
||||
});
|
||||
|
||||
document.querySelectorAll('.kalli-progress-line').forEach(function (element, index) {
|
||||
element.classList.toggle('done', index < step - 1);
|
||||
});
|
||||
}
|
||||
|
||||
function showHeader() {
|
||||
var rootStyle = getComputedStyle(document.documentElement);
|
||||
var headerHeight = parseFloat(rootStyle.getPropertyValue('--header-height')) || 64;
|
||||
var menu = byId('header_menue');
|
||||
var title = byId('header_title');
|
||||
|
||||
if (menu) menu.style.top = '0';
|
||||
if (title) title.style.top = headerHeight + 'px';
|
||||
}
|
||||
|
||||
function goToStep(step) {
|
||||
for (var index = 1; index <= 4; index++) {
|
||||
byId('kalliStep' + index).hidden = index !== step;
|
||||
}
|
||||
currentStep = step;
|
||||
updateProgress(step);
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
showHeader();
|
||||
}
|
||||
|
||||
function formatDate(isoDate) {
|
||||
if (!isoDate) return '–';
|
||||
var parts = isoDate.split('-');
|
||||
return parts.length === 3 ? parts[2] + '.' + parts[1] + '.' + parts[0] : isoDate;
|
||||
}
|
||||
|
||||
function showSummaryRow(rowId, valueId, value, displayValue) {
|
||||
var row = byId(rowId);
|
||||
row.hidden = !value;
|
||||
if (value) byId(valueId).textContent = displayValue;
|
||||
}
|
||||
|
||||
function buildSummary(form) {
|
||||
var selected = selectedGroup(form);
|
||||
var groupLabel = selected ? selected.value : '–';
|
||||
|
||||
if (selected && selected.dataset.kalliCustom === '1') {
|
||||
var customTitle = byId('kalliUserInput').value.trim();
|
||||
var visibility = selected.value === 'oeffentlich' ? 'Öffentlich' : 'Intern';
|
||||
groupLabel = customTitle ? customTitle + ' (' + visibility + ')' : groupLabel;
|
||||
}
|
||||
|
||||
byId('sumGroup').textContent = groupLabel;
|
||||
byId('sumDateS').textContent = formatDate(byId('kalliDateS').value);
|
||||
showSummaryRow('sumDateERow', 'sumDateE', byId('kalliDateE').value, formatDate(byId('kalliDateE').value));
|
||||
showSummaryRow('sumTimeSRow', 'sumTimeS', byId('kalliTimeS').value, byId('kalliTimeS').value);
|
||||
showSummaryRow('sumTimeERow', 'sumTimeE', byId('kalliTimeE').value, byId('kalliTimeE').value);
|
||||
showSummaryRow('sumTextRow', 'sumText', byId('kalliFurtherText').value.trim(), byId('kalliFurtherText').value.trim());
|
||||
showSummaryRow('sumLocRow', 'sumLoc', byId('kalliLocation').value.trim(), byId('kalliLocation').value.trim());
|
||||
showSummaryRow('sumRespRow', 'sumResp', byId('kalliResponsibility').value.trim(), byId('kalliResponsibility').value.trim());
|
||||
}
|
||||
|
||||
function validateForStep(form, nextStep) {
|
||||
if (nextStep === 2) return validateStepOne(form);
|
||||
if (nextStep === 3) return validateStepTwo();
|
||||
if (nextStep === 4) {
|
||||
if (!validateStepOne(form) || !validateStepTwo()) return false;
|
||||
buildSummary(form);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function initialise() {
|
||||
var form = byId('kalliForm');
|
||||
if (!form) return;
|
||||
|
||||
form.querySelectorAll('[data-kalli-next]').forEach(function (button) {
|
||||
button.addEventListener('click', function () {
|
||||
var nextStep = Number(button.dataset.kalliNext);
|
||||
if (validateForStep(form, nextStep)) goToStep(nextStep);
|
||||
});
|
||||
});
|
||||
|
||||
form.querySelectorAll('[data-kalli-prev]').forEach(function (button) {
|
||||
button.addEventListener('click', function () {
|
||||
goToStep(Number(button.dataset.kalliPrev));
|
||||
});
|
||||
});
|
||||
|
||||
form.addEventListener('submit', function (event) {
|
||||
if (currentStep !== 4) {
|
||||
event.preventDefault();
|
||||
var nextStep = Math.min(currentStep + 1, 4);
|
||||
if (validateForStep(form, nextStep)) goToStep(nextStep);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!validateStepOne(form)) {
|
||||
event.preventDefault();
|
||||
goToStep(1);
|
||||
return;
|
||||
}
|
||||
if (!validateStepTwo()) {
|
||||
event.preventDefault();
|
||||
goToStep(2);
|
||||
return;
|
||||
}
|
||||
|
||||
var submitButton = form.querySelector('button[type="submit"]');
|
||||
if (submitButton) submitButton.disabled = true;
|
||||
});
|
||||
|
||||
updateProgress(1);
|
||||
}
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', initialise);
|
||||
} else {
|
||||
initialise();
|
||||
}
|
||||
}());
|
||||
@@ -38,6 +38,11 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
// Zeige den entsprechenden Inhalt an
|
||||
var contentId = item.getAttribute('data-target');
|
||||
document.getElementById(contentId).classList.add('active');
|
||||
|
||||
const url = new URL(window.location.href);
|
||||
if (contentId === 'content_visit') url.searchParams.set('tab', 'visit');
|
||||
else url.searchParams.delete('tab');
|
||||
window.history.replaceState(null, '', url);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -68,130 +73,108 @@ function switchCalendar(type) {
|
||||
}
|
||||
|
||||
|
||||
// Tages-Modal oeffnen
|
||||
async function openDayModal(calendarDate) {
|
||||
if (!calendarDate) return;
|
||||
let _seelsorgeConfig = null;
|
||||
|
||||
function getSeelsorgeConfig() {
|
||||
if (_seelsorgeConfig) return _seelsorgeConfig;
|
||||
const element = document.getElementById('seelsorge-config');
|
||||
_seelsorgeConfig = element ? JSON.parse(element.textContent) : { csrfToken: '' };
|
||||
return _seelsorgeConfig;
|
||||
}
|
||||
|
||||
async function loadCalendarDay(calendarDate, action = 'view') {
|
||||
if (!calendarDate) return null;
|
||||
|
||||
const modal = document.getElementById('seelsorge-day-modal');
|
||||
const body = document.getElementById('modal-day-body');
|
||||
const title = document.getElementById('modal-day-title');
|
||||
const subtitle = document.getElementById('modal-day-subtitle');
|
||||
if (!modal || !body || !title || !subtitle) return null;
|
||||
|
||||
if (!modal || !body || !title || !subtitle) {
|
||||
console.error('openDayModal: Modal-Elemente nicht gefunden.');
|
||||
return;
|
||||
if (action === 'view') {
|
||||
title.textContent = 'Laden...';
|
||||
subtitle.innerHTML = '';
|
||||
}
|
||||
|
||||
// Modal sofort anzeigen mit Ladezustand
|
||||
title.textContent = 'Laden...';
|
||||
subtitle.innerHTML = '';
|
||||
body.innerHTML = '<div class="seelsorge-modal-loading"><div class="loading-spinner"></div></div>';
|
||||
body.dataset.calendarDate = calendarDate;
|
||||
modal.style.display = 'flex';
|
||||
|
||||
try {
|
||||
const response = await fetch('content/calendar_day.php', {
|
||||
const url = 'content/calendar_day.php?date=' + encodeURIComponent(calendarDate);
|
||||
const options = action === 'toggle'
|
||||
? {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
body: 'calendarDate=' + encodeURIComponent(calendarDate)
|
||||
});
|
||||
|
||||
const html = await response.text();
|
||||
|
||||
if (response.ok) {
|
||||
body.innerHTML = html;
|
||||
|
||||
// Metadaten aus dem eingebetteten JSON lesen
|
||||
const metaEl = body.querySelector('#modal-meta');
|
||||
if (metaEl) {
|
||||
const meta = JSON.parse(metaEl.textContent);
|
||||
title.textContent = meta.title;
|
||||
|
||||
let chips = '<span class="seelsorge-modal-chip">KW ' + meta.kw + '</span>';
|
||||
if (meta.feiertag) {
|
||||
chips += '<span class="seelsorge-modal-chip seelsorge-chip-holiday">' + meta.feiertag + '</span>';
|
||||
}
|
||||
subtitle.innerHTML = chips;
|
||||
metaEl.remove();
|
||||
}
|
||||
} else {
|
||||
// PHP-Fehler direkt im Modal anzeigen
|
||||
title.textContent = 'Fehler ' + response.status;
|
||||
body.innerHTML = '<div style="padding:16px;font-family:monospace;font-size:0.85em;color:#d21400;white-space:pre-wrap;word-break:break-word;background:#fff5f5;border:1px solid #fcc;">'
|
||||
+ (html || 'Keine Antwort vom Server.') + '</div>';
|
||||
body: new URLSearchParams({
|
||||
date: calendarDate,
|
||||
action: 'toggle',
|
||||
csrf_token: getSeelsorgeConfig().csrfToken
|
||||
})
|
||||
}
|
||||
: { method: 'GET' };
|
||||
|
||||
try {
|
||||
const response = await fetch(url, options);
|
||||
const html = await response.text();
|
||||
if (!response.ok) {
|
||||
throw new Error(html || ('HTTP Fehler: ' + response.status));
|
||||
}
|
||||
|
||||
body.innerHTML = html;
|
||||
const metaEl = body.querySelector('#modal-meta');
|
||||
if (!metaEl) throw new Error('Tagesdaten sind unvollständig.');
|
||||
|
||||
const meta = JSON.parse(metaEl.textContent);
|
||||
title.textContent = meta.title;
|
||||
let chips = '<span class="seelsorge-modal-chip">KW ' + meta.kw + '</span>';
|
||||
if (meta.feiertag) {
|
||||
chips += '<span class="seelsorge-modal-chip seelsorge-chip-holiday">' + escHtml(meta.feiertag) + '</span>';
|
||||
}
|
||||
subtitle.innerHTML = chips;
|
||||
metaEl.remove();
|
||||
return meta;
|
||||
} catch (error) {
|
||||
body.innerHTML = '<div class="seelsorge-modal-empty">Fehler beim Laden der Daten: ' + error.message + '</div>';
|
||||
title.textContent = 'Fehler';
|
||||
body.innerHTML = '<div class="seelsorge-modal-empty">' + escHtml(error.message || 'Fehler beim Laden der Daten.') + '</div>';
|
||||
console.error('Fehler beim Laden der Tagesdetails:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function openDayModal(calendarDate) {
|
||||
return loadCalendarDay(calendarDate, 'view');
|
||||
}
|
||||
|
||||
// Modal schliessen
|
||||
function closeDayModal() {
|
||||
const modal = document.getElementById('seelsorge-day-modal');
|
||||
if (modal) modal.style.display = 'none';
|
||||
}
|
||||
|
||||
// Backdrop-Klick schliesst Modal
|
||||
function closeDayModalOnBackdrop(event) {
|
||||
if (event.target === event.currentTarget) {
|
||||
closeDayModal();
|
||||
}
|
||||
if (event.target === event.currentTarget) closeDayModal();
|
||||
}
|
||||
|
||||
|
||||
// Verfuegbarkeit im Modal togglen (Modal bleibt offen)
|
||||
async function toggleAvailabilityModal(date) {
|
||||
const btn = document.getElementById('modal-toggle-btn');
|
||||
if (!btn) return;
|
||||
const button = document.getElementById('modal-toggle-btn');
|
||||
if (button) button.disabled = true;
|
||||
|
||||
const originalText = btn.textContent;
|
||||
btn.disabled = true;
|
||||
btn.textContent = 'Wird gespeichert\u2026';
|
||||
|
||||
try {
|
||||
const response = await fetch("toggle_availability.php", {
|
||||
method: "POST",
|
||||
headers: { "Content-type": "application/x-www-form-urlencoded" },
|
||||
body: "date=" + encodeURIComponent(date)
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.ok) {
|
||||
// Modal-Inhalt neu laden um aktuellen Stand zu zeigen
|
||||
await openDayModal(date);
|
||||
|
||||
// Kalender-Tageszelle visuell aktualisieren
|
||||
updateCalendarCell(date, data.nowAvailable, data.availCount);
|
||||
} else {
|
||||
btn.textContent = originalText;
|
||||
btn.disabled = false;
|
||||
console.error("Fehler:", data.error);
|
||||
}
|
||||
} catch (error) {
|
||||
btn.textContent = originalText;
|
||||
btn.disabled = false;
|
||||
console.error("Fehler beim Aktualisieren der Verfuegbarkeit:", error);
|
||||
}
|
||||
const meta = await loadCalendarDay(date, 'toggle');
|
||||
if (meta) updateCalendarCell(date, Number(meta.availCount) || 0);
|
||||
}
|
||||
|
||||
// Kalender-Tageszelle visuell aktualisieren ohne Page-Reload
|
||||
function updateCalendarCell(date, nowAvailable, availCount) {
|
||||
var cells = document.querySelectorAll('#calendar_day_item');
|
||||
function updateCalendarCell(date, availCount) {
|
||||
const cells = document.querySelectorAll(`[data-calendar-date="${date}"]`);
|
||||
cells.forEach(function(cell) {
|
||||
var onclick = cell.getAttribute('onclick') || '';
|
||||
if (onclick.indexOf(date) === -1) return;
|
||||
|
||||
// Alle Verfuegbarkeits-Klassen entfernen
|
||||
cell.classList.remove('available_single', 'available_multiple', 'normalday');
|
||||
cell.classList.remove(
|
||||
'available_single', 'available_multiple', 'normalday',
|
||||
'today', 'sunday', 'localholiday', 'saturday'
|
||||
);
|
||||
|
||||
if (availCount >= 2) {
|
||||
cell.classList.add('available_multiple');
|
||||
} else if (availCount === 1) {
|
||||
cell.classList.add('available_single');
|
||||
} else {
|
||||
cell.classList.add('normalday');
|
||||
cell.classList.add(cell.dataset.emptyClass || 'normalday');
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -201,26 +184,16 @@ function updateCalendarCell(date, nowAvailable, availCount) {
|
||||
var _lastCalendarDate = null;
|
||||
|
||||
function openCalendarProfile(userId) {
|
||||
// Datum merken, damit wir beim Schliessen zurueckkehren koennen
|
||||
var metaEl = document.getElementById('modal-meta');
|
||||
var bodyEl = document.getElementById('modal-day-body');
|
||||
if (!_lastCalendarDate && bodyEl) {
|
||||
// Datum aus dem Toggle-Button extrahieren
|
||||
var toggleBtn = bodyEl.querySelector('#modal-toggle-btn');
|
||||
if (toggleBtn) {
|
||||
var onclickAttr = toggleBtn.getAttribute('onclick') || '';
|
||||
var match = onclickAttr.match(/'(\d{4}-\d{2}-\d{2})'/);
|
||||
if (match) _lastCalendarDate = match[1];
|
||||
}
|
||||
}
|
||||
const body = document.getElementById('modal-day-body');
|
||||
_lastCalendarDate = body?.dataset.calendarDate || null;
|
||||
closeDayModal();
|
||||
openCloseProfile(userId, 'profile_calendar', 'profile_calendar_overlay');
|
||||
openCloseProfile(userId, 'profile_seelsorge', 'profile_seelsorge_overlay');
|
||||
}
|
||||
|
||||
// Globalen Listener fuer Kalender-Profil-Overlay: bei Schliessen Tagesmodal wieder oeffnen
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// MutationObserver auf das Kalender-Profil-Overlay
|
||||
var calOverlay = document.getElementById('profile_calendar_overlay');
|
||||
var calOverlay = document.getElementById('profile_seelsorge_overlay');
|
||||
if (calOverlay) {
|
||||
var observer = new MutationObserver(function(mutations) {
|
||||
mutations.forEach(function(m) {
|
||||
@@ -244,4 +217,297 @@ function openCalendarHelp() {
|
||||
function closeCalendarHelp() {
|
||||
var modal = document.getElementById('calendar-help-modal');
|
||||
if (modal) modal.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
// Besuchsstellen ------------------------------------------------------------
|
||||
let _visitConfig = null;
|
||||
|
||||
function getVisitConfig() {
|
||||
if (_visitConfig) return _visitConfig;
|
||||
const element = document.getElementById('visit-config');
|
||||
_visitConfig = element ? JSON.parse(element.textContent) : { entries: [], currentUserId: 0 };
|
||||
return _visitConfig;
|
||||
}
|
||||
|
||||
function openVisitProfile(userId) {
|
||||
closeHouseholdDetail();
|
||||
openCloseProfile(userId, 'profile_seelsorge', 'profile_seelsorge_overlay');
|
||||
}
|
||||
|
||||
// --- Detail Modal ---
|
||||
function openHouseholdDetail(data) {
|
||||
const overlay = document.getElementById('visitDetailOverlay');
|
||||
const title = document.getElementById('detailHeroTitle');
|
||||
const sub = document.getElementById('detailHeroSub');
|
||||
const body = document.getElementById('detailBody');
|
||||
|
||||
const members = data.members || [];
|
||||
const lastName = members[0]?.last_name || '';
|
||||
const firstNames = members.map(m => m.first_name).join(' & ');
|
||||
title.textContent = lastName + ', ' + firstNames;
|
||||
sub.textContent = data.address_street + ' · ' + data.address_plz;
|
||||
|
||||
let html = '';
|
||||
|
||||
// Members section
|
||||
html += '<div class="visit-detail-section-title">Haushaltsmitglieder</div>';
|
||||
members.forEach(m => {
|
||||
html += `<div class="visit-detail-member">
|
||||
<div class="visit-detail-member-info">
|
||||
${escHtml(m.first_name)} ${escHtml(m.last_name)}
|
||||
<span class="age">geb. ${escHtml(m.birth_formatted)} (${m.age} Jahre)</span>
|
||||
</div>
|
||||
<button class="visit-detail-member-edit" onclick="event.stopPropagation(); editMemberFromDetail(${m.besuch_id})" title="Bearbeiten"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" width="16" height="16"><path d="M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34c-.39-.39-1.02-.39-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z"/></svg></button>
|
||||
</div>`;
|
||||
});
|
||||
|
||||
// Info grid
|
||||
html += '<div class="visit-detail-section-title">Details</div>';
|
||||
html += '<div class="visit-detail-info-grid">';
|
||||
html += `<div class="visit-detail-info-item"><div class="visit-detail-info-label">Adresse</div><div class="visit-detail-info-value"><a href="${escHtml(data.address_link)}" target="_blank" style="color:var(--theme-color);text-decoration:none;">${escHtml(data.address_street)}, ${escHtml(data.address_plz)}</a></div></div>`;
|
||||
|
||||
const phone = members[0]?.phone || data.phone || '';
|
||||
const phoneFormatted = phone ? ('0' + phone) : '—';
|
||||
html += `<div class="visit-detail-info-item"><div class="visit-detail-info-label">Telefon</div><div class="visit-detail-info-value">${phone ? '<a href="tel:0'+escHtml(phone)+'" style="color:var(--theme-color);text-decoration:none;">'+escHtml(phoneFormatted)+'</a>' : '—'}</div></div>`;
|
||||
|
||||
const priestName = members[0]?.priest_name || '—';
|
||||
const priestId = members[0]?.priest_id || 0;
|
||||
const priestImg = members[0]?.priest_img || '/intranet_repo/general_files/profile_pictures/default.jpg';
|
||||
if (priestId > 0) {
|
||||
html += `<div class="visit-detail-info-item"><div class="visit-detail-info-label">Priester</div><div class="visit-detail-info-value">`;
|
||||
html += `<span style="display:inline-flex;align-items:center;gap:8px;cursor:pointer;" onclick="openVisitProfile(${priestId})">`;
|
||||
html += `<img src="${escHtml(priestImg)}" alt="" style="width:28px;height:28px;border-radius:50%;object-fit:cover;">`;
|
||||
html += `<span style="color:var(--theme-color);text-decoration:none;">${escHtml(priestName)}</span>`;
|
||||
html += `</span></div></div>`;
|
||||
} else {
|
||||
html += `<div class="visit-detail-info-item"><div class="visit-detail-info-label">Priester</div><div class="visit-detail-info-value">${escHtml(priestName)}</div></div>`;
|
||||
}
|
||||
|
||||
const comment = members.map(m => m.comment).filter(c => c).join('; ') || '—';
|
||||
html += `<div class="visit-detail-info-item"><div class="visit-detail-info-label">Hinweis</div><div class="visit-detail-info-value">${escHtml(comment)}</div></div>`;
|
||||
html += '</div>';
|
||||
|
||||
// Reservation info
|
||||
if (data.reservation) {
|
||||
html += '<div class="visit-detail-section-title">Geplanter Besuch</div>';
|
||||
const resName = data.reservation_name || ('Nutzer #' + data.reservation);
|
||||
const resImg = data.reservation_img || '/intranet_repo/general_files/profile_pictures/default.jpg';
|
||||
const resId = parseInt(data.reservation);
|
||||
html += '<div class="visit-detail-info-item" style="grid-column:1/-1;"><div class="visit-detail-info-label">Geplant von</div><div class="visit-detail-info-value">';
|
||||
html += `<span style="display:inline-flex;align-items:center;gap:8px;cursor:pointer;" onclick="openVisitProfile(${resId})">`;
|
||||
html += `<img src="${escHtml(resImg)}" alt="" style="width:28px;height:28px;border-radius:50%;object-fit:cover;">`;
|
||||
html += `<span style="color:var(--theme-color);text-decoration:none;">${escHtml(resName)}</span>`;
|
||||
html += `</span>`;
|
||||
html += '</div></div>';
|
||||
}
|
||||
|
||||
// Actions
|
||||
const firstBesuchId = members[0]?.besuch_id || 0;
|
||||
html += '<div class="visit-detail-actions">';
|
||||
if (!data.reservation) {
|
||||
html += `<button class="visit-action-btn visit-action-btn-primary" onclick="doReservation(${firstBesuchId})">Reservieren</button>`;
|
||||
} else if (parseInt(data.reservation) === getVisitConfig().currentUserId) {
|
||||
html += `<button class="visit-action-btn" style="color:hsl(145,55%,34%);border-color:hsl(145,55%,80%);background:hsl(145,55%,96%);" onclick="doComplete(${firstBesuchId})"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" width="16" height="16" style="vertical-align:middle;margin-right:4px;"><path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/></svg>Abschließen</button>`;
|
||||
}
|
||||
html += `<button class="visit-action-btn" onclick="doPrioritize(${firstBesuchId})">Priorisieren</button>`;
|
||||
html += '</div>';
|
||||
|
||||
body.innerHTML = html;
|
||||
overlay.style.display = 'flex';
|
||||
}
|
||||
|
||||
function closeHouseholdDetail() {
|
||||
document.getElementById('visitDetailOverlay').style.display = 'none';
|
||||
}
|
||||
|
||||
function escHtml(str) {
|
||||
if (!str) return '';
|
||||
const div = document.createElement('div');
|
||||
div.textContent = str;
|
||||
return div.innerHTML;
|
||||
}
|
||||
|
||||
function editMemberFromDetail(besuchId) {
|
||||
closeHouseholdDetail();
|
||||
const entry = getVisitConfig().entries.find(e => e.besuch_id === besuchId);
|
||||
if (entry) openVisitEditModalFromEntry(entry);
|
||||
}
|
||||
|
||||
function submitVisitAction(action, besuchId) {
|
||||
const form = document.getElementById('visitActionForm');
|
||||
const command = document.getElementById('visit_action_command');
|
||||
const id = document.getElementById('visit_action_id');
|
||||
if (!form || !command || !id) return;
|
||||
|
||||
command.value = action;
|
||||
id.value = besuchId;
|
||||
form.submit();
|
||||
}
|
||||
|
||||
function doReservation(besuchId) { submitVisitAction('reserve', besuchId); }
|
||||
function doComplete(besuchId) { submitVisitAction('complete', besuchId); }
|
||||
function doPrioritize(besuchId) { submitVisitAction('prioritize', besuchId); }
|
||||
|
||||
// --- Picker Modal ---
|
||||
function openVisitPickerModal() {
|
||||
renderVisitPickerList('');
|
||||
document.getElementById('visitPickerModalOverlay').style.display = 'flex';
|
||||
const inp = document.getElementById('visitPickerSearch');
|
||||
inp.value = '';
|
||||
setTimeout(() => inp.focus(), 30);
|
||||
}
|
||||
|
||||
function closeVisitPickerModal() {
|
||||
document.getElementById('visitPickerModalOverlay').style.display = 'none';
|
||||
}
|
||||
|
||||
function onVisitPickerSearchInput() {
|
||||
renderVisitPickerList(document.getElementById('visitPickerSearch').value || '');
|
||||
}
|
||||
|
||||
function normalizeStr(s) { return (s || '').toString().toLowerCase(); }
|
||||
|
||||
function matchesQuery(entry, query) {
|
||||
const q = normalizeStr(query).trim();
|
||||
if (!q) return true;
|
||||
const ln = normalizeStr(entry.last_name);
|
||||
const fn = normalizeStr(entry.first_name);
|
||||
const a = (ln + ' ' + fn).trim();
|
||||
const b = (fn + ' ' + ln).trim();
|
||||
const tokens = q.split(/\s+/).filter(Boolean);
|
||||
return tokens.every(t => a.includes(t) || b.includes(t));
|
||||
}
|
||||
|
||||
function renderVisitPickerList(query) {
|
||||
const list = document.getElementById('visitPickerList');
|
||||
list.innerHTML = '';
|
||||
if (!Array.isArray(getVisitConfig().entries) || getVisitConfig().entries.length === 0) {
|
||||
list.innerHTML = '<div style="opacity:.75;padding:8px;">Keine Einträge vorhanden.</div>';
|
||||
return;
|
||||
}
|
||||
const filtered = getVisitConfig().entries.filter(e => matchesQuery(e, query));
|
||||
if (filtered.length === 0) {
|
||||
list.innerHTML = '<div style="opacity:.75;padding:8px;">Keine Treffer.</div>';
|
||||
return;
|
||||
}
|
||||
filtered.forEach(entry => {
|
||||
const item = document.createElement('div');
|
||||
item.className = 'visit-picker-item';
|
||||
item.onclick = () => { closeVisitPickerModal(); openVisitEditModalFromEntry(entry); };
|
||||
const left = document.createElement('div');
|
||||
const name = document.createElement('div');
|
||||
name.className = 'visit-picker-name';
|
||||
name.textContent = (entry.last_name || '') + ', ' + (entry.first_name || '');
|
||||
left.appendChild(name);
|
||||
const sub = document.createElement('div');
|
||||
sub.className = 'visit-picker-sub';
|
||||
sub.textContent = (entry.address_street || '') + (entry.address_plz ? (' · ' + entry.address_plz) : '');
|
||||
left.appendChild(sub);
|
||||
const right = document.createElement('div');
|
||||
right.style.cssText = 'opacity:0.5;font-size:0.8em;font-weight:700;';
|
||||
right.textContent = '#' + entry.besuch_id;
|
||||
item.appendChild(left);
|
||||
item.appendChild(right);
|
||||
list.appendChild(item);
|
||||
});
|
||||
}
|
||||
|
||||
// --- Edit Modal ---
|
||||
const EMPTY_VISIT_ENTRY = {
|
||||
besuch_id: '',
|
||||
last_name: '',
|
||||
first_name: '',
|
||||
birth_date: '',
|
||||
address_street: '',
|
||||
address_plz: '',
|
||||
address_link: '',
|
||||
phone: '',
|
||||
comment: '',
|
||||
letzter_besuch: '2024-07-11',
|
||||
priority: 0,
|
||||
pr_bezirk: ''
|
||||
};
|
||||
|
||||
function openVisitEntryModal(entry = null, errorMessage = '') {
|
||||
const values = Object.assign({}, EMPTY_VISIT_ENTRY, entry || {});
|
||||
const isEdit = Number(values.besuch_id) > 0;
|
||||
|
||||
document.getElementById('visitModalTitle').innerText = isEdit ? 'Eintrag bearbeiten' : 'Neue Besuchsstelle anlegen';
|
||||
document.getElementById('modal_besuch_id').value = isEdit ? values.besuch_id : '';
|
||||
document.getElementById('modal_last_name').value = values.last_name || '';
|
||||
document.getElementById('modal_first_name').value = values.first_name || '';
|
||||
document.getElementById('modal_birth_date').value = values.birth_date || '';
|
||||
document.getElementById('modal_address_street').value = values.address_street || '';
|
||||
document.getElementById('modal_address_plz').value = values.address_plz || '';
|
||||
document.getElementById('modal_address_link').value = values.address_link || '';
|
||||
document.getElementById('modal_phone').value = values.phone || '';
|
||||
document.getElementById('modal_comment').value = values.comment || '';
|
||||
document.getElementById('modal_letzter_besuch').value = values.letzter_besuch || EMPTY_VISIT_ENTRY.letzter_besuch;
|
||||
document.getElementById('modal_priority').value = values.priority ?? 0;
|
||||
document.getElementById('modal_pr_bezirk').value = values.pr_bezirk ?? '';
|
||||
document.getElementById('modal_delete_btn').style.display = isEdit ? 'inline-block' : 'none';
|
||||
|
||||
const error = document.getElementById('visitFormError');
|
||||
if (error) {
|
||||
error.textContent = errorMessage;
|
||||
error.style.display = errorMessage ? 'block' : 'none';
|
||||
}
|
||||
showVisitModal();
|
||||
}
|
||||
|
||||
function openVisitEditModalFromEntry(entry) {
|
||||
openVisitEntryModal(entry);
|
||||
}
|
||||
|
||||
function openVisitAddModal() {
|
||||
openVisitEntryModal();
|
||||
}
|
||||
|
||||
function showVisitModal() {
|
||||
const overlay = document.getElementById('visitEntryModalOverlay');
|
||||
overlay.style.display = 'flex';
|
||||
overlay.setAttribute('aria-hidden', 'false');
|
||||
setTimeout(() => document.getElementById('modal_last_name')?.focus(), 30);
|
||||
}
|
||||
|
||||
function closeVisitModal() {
|
||||
const overlay = document.getElementById('visitEntryModalOverlay');
|
||||
if (overlay && overlay.contains(document.activeElement)) document.activeElement.blur();
|
||||
overlay.setAttribute('aria-hidden', 'true');
|
||||
overlay.style.display = 'none';
|
||||
}
|
||||
|
||||
// Close on backdrop/escape
|
||||
document.addEventListener('click', function(e) {
|
||||
const picker = document.getElementById('visitPickerModalOverlay');
|
||||
const edit = document.getElementById('visitEntryModalOverlay');
|
||||
if (picker && picker.style.display === 'flex' && e.target === picker) closeVisitPickerModal();
|
||||
if (edit && edit.style.display === 'flex' && e.target === edit) closeVisitModal();
|
||||
});
|
||||
|
||||
document.addEventListener('keydown', function(e) {
|
||||
if (e.key !== 'Escape') return;
|
||||
const detail = document.getElementById('visitDetailOverlay');
|
||||
const edit = document.getElementById('visitEntryModalOverlay');
|
||||
const picker = document.getElementById('visitPickerModalOverlay');
|
||||
if (edit && edit.style.display === 'flex') return closeVisitModal();
|
||||
if (picker && picker.style.display === 'flex') return closeVisitPickerModal();
|
||||
if (detail && detail.style.display === 'flex') return closeHouseholdDetail();
|
||||
});
|
||||
|
||||
function deleteVisitEntryFromModal() {
|
||||
const besuchId = document.getElementById('modal_besuch_id').value;
|
||||
if (besuchId && confirm('Eintrag wirklich löschen?')) {
|
||||
submitVisitAction('delete', besuchId);
|
||||
}
|
||||
}
|
||||
|
||||
// Server-seitige Validierungsfehler nach Post/Redirect/Get im Formular wieder anzeigen.
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const config = getVisitConfig();
|
||||
if (config.formError && config.formData) {
|
||||
openVisitEntryModal(config.formData, config.formError);
|
||||
} else if (config.formError) {
|
||||
alert(config.formError);
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user