Add service worker for push notifications, create calendar layout, and implement WLAN QR code page
- Implemented a service worker (sw.js) to handle push notifications with dynamic options and notification click events. - Created a calendar layout in test.html with a grid system for displaying events across days and times. - Developed a visually engaging WLAN QR code page (wlan.html) with animated backgrounds, particle effects, and tips for connecting to the network.
This commit is contained in:
418
public/ausbildung_quiz.html
Normal file
418
public/ausbildung_quiz.html
Normal file
@@ -0,0 +1,418 @@
|
||||
<!doctype html>
|
||||
<html lang="de">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
<title>Quiz: Ausbildung</title>
|
||||
<script src="/lib/pocketbase.umd.js"></script>
|
||||
<script defer src="https://analytics.fsae41.de/script.js" data-website-id="257da02e-d678-47b6-b036-e3bdabaf1405"></script>
|
||||
|
||||
<style>
|
||||
:root {
|
||||
--bg: #f7fafc;
|
||||
--card: #ffffff;
|
||||
--accent: #2563eb;
|
||||
--muted: #6b7280;
|
||||
--correct: #16a34a;
|
||||
--wrong: #ef4444
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: Inter, system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', Arial;
|
||||
background: var(--bg);
|
||||
padding: 18px
|
||||
}
|
||||
|
||||
.card {
|
||||
background: var(--card);
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 6px 18px rgba(15, 23, 42, 0.08);
|
||||
padding: 18px;
|
||||
max-width: 760px
|
||||
}
|
||||
|
||||
.q-head {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
align-items: flex-start
|
||||
}
|
||||
|
||||
.q-id {
|
||||
display: none
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin: 0 0 8px 0;
|
||||
font-size: 18px
|
||||
}
|
||||
|
||||
p.meta {
|
||||
margin: 0 0 14px 0;
|
||||
color: var(--muted)
|
||||
}
|
||||
|
||||
p.intro {
|
||||
margin-bottom: 12px;
|
||||
color: var(--muted)
|
||||
}
|
||||
|
||||
ul.answers {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
display: grid;
|
||||
gap: 10px
|
||||
}
|
||||
|
||||
ul.answers li {
|
||||
border: 1px solid #e6e9ef;
|
||||
border-radius: 10px;
|
||||
padding: 12px;
|
||||
cursor: pointer;
|
||||
transition: all .12s
|
||||
}
|
||||
|
||||
ul.answers li.selected {
|
||||
border-color: var(--accent);
|
||||
background: rgba(37, 99, 235, 0.06)
|
||||
}
|
||||
|
||||
ul.answers li:hover {
|
||||
transform: translateY(-2px)
|
||||
}
|
||||
|
||||
ul.answers li.correct {
|
||||
border-color: var(--correct);
|
||||
background: rgba(16, 185, 129, 0.06)
|
||||
}
|
||||
|
||||
ul.answers li.incorrect {
|
||||
border-color: var(--wrong);
|
||||
background: rgba(239, 68, 68, 0.06)
|
||||
}
|
||||
|
||||
.badge-container {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-bottom: 4px
|
||||
}
|
||||
|
||||
.badge {
|
||||
font-size: 12px;
|
||||
padding: 4px 8px;
|
||||
border-radius: 999px;
|
||||
background: #f1f5f9;
|
||||
color: var(--muted)
|
||||
}
|
||||
|
||||
.controls {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-top: 12px;
|
||||
flex-wrap: wrap
|
||||
}
|
||||
|
||||
.control-right {
|
||||
display: flex;
|
||||
gap: 8px
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 8px 12px;
|
||||
border-radius: 8px;
|
||||
border: 0;
|
||||
cursor: pointer
|
||||
}
|
||||
|
||||
.btn.primary {
|
||||
background: var(--accent);
|
||||
color: #fff
|
||||
}
|
||||
|
||||
.btn.ghost {
|
||||
background: transparent;
|
||||
border: 1px solid #e6e9ef
|
||||
}
|
||||
|
||||
.btn.danger {
|
||||
background: var(--wrong);
|
||||
color: #fff
|
||||
}
|
||||
|
||||
.hint {
|
||||
margin-top: 10px;
|
||||
font-size: 13px;
|
||||
color: var(--muted)
|
||||
}
|
||||
|
||||
#report-box {
|
||||
display: none;
|
||||
margin-top: 12px
|
||||
}
|
||||
|
||||
#report-box textarea {
|
||||
width: 100%;
|
||||
min-height: 80px;
|
||||
border: 1px solid #e6e9ef;
|
||||
border-radius: 8px;
|
||||
padding: 8px;
|
||||
font-family: inherit
|
||||
}
|
||||
|
||||
#report-box button {
|
||||
margin-top: 8px
|
||||
}
|
||||
|
||||
.dropdown-container {
|
||||
margin-bottom: 12px
|
||||
}
|
||||
|
||||
select#question-select {
|
||||
padding: 6px 10px;
|
||||
border-radius: 6px;
|
||||
border: 1px solid #e6e9ef;
|
||||
font-family: inherit
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="quiz-card" class="card" role="region" aria-label="Ausbildungsfrage">
|
||||
<div class="dropdown-container">
|
||||
<label for="question-select">Frage auswählen ID: </label>
|
||||
<select id="question-select"></select>
|
||||
</div>
|
||||
|
||||
<p class="intro" id="q-textIntro">Als Ausbilder setzen Sie zur Anleitung der Auszubildenden auch die
|
||||
Vier-Stufen-Methode ein.</p>
|
||||
<div class="q-head">
|
||||
<div class="q-id" id="q-id">ID 344</div>
|
||||
<div style="flex:1">
|
||||
<h2 id="q-text">Welches der folgenden Merkmale trifft auf die erste Stufe zu?</h2>
|
||||
<p class="meta" id="q-category">Kategorie: 3</p>
|
||||
<div class="badge-container">
|
||||
<p class="badge" id="q-type">1 Antwort richtig</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<p class="intro" id="answersIntro">Die Auszubildenden sollen…</p>
|
||||
<ul id="answers" class="answers" role="list">
|
||||
<!-- Antworten werden hier gerendert -->
|
||||
</ul>
|
||||
|
||||
<div class="controls">
|
||||
<button class="btn danger" id="report-error">Fehler melden</button>
|
||||
<button class="btn" id="last-q">Zurück</button>
|
||||
<div class="control-right">
|
||||
<button class="btn" id="next-q">Weiter</button>
|
||||
<button class="btn ghost" id="clear-selection">Auswahl zurücksetzen</button>
|
||||
<button class="btn primary" id="show-correct">Korrekte Antwort anzeigen</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="report-box" style="display: none;">
|
||||
<textarea id="report-text" placeholder="Bitte beschreiben Sie den Fehler..."></textarea>
|
||||
<button class="btn primary" id="send-report">Absenden</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const state = { item: null, selected: [], allQuestions: [] };
|
||||
|
||||
function populateDropdown() {
|
||||
const select = document.getElementById('question-select');
|
||||
select.innerHTML = '';
|
||||
state.allQuestions.forEach(q => {
|
||||
const option = document.createElement('option');
|
||||
option.value = q.id;
|
||||
option.textContent = `${q.id}`;
|
||||
option.selected = (state.item && state.item.id === q.id);
|
||||
select.appendChild(option);
|
||||
});
|
||||
select.addEventListener('change', () => {
|
||||
const selectedId = select.value;
|
||||
const question = state.allQuestions.find(q => q.id == selectedId);
|
||||
if (question) window.updateQuiz(question);
|
||||
});
|
||||
}
|
||||
|
||||
function shuffle(array) {
|
||||
for (let i = array.length - 1; i > 0; i--) {
|
||||
const j = Math.floor(Math.random() * (i + 1));
|
||||
[array[i], array[j]] = [array[j], array[i]];
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
function render(data) {
|
||||
state.item = data;
|
||||
state.selected = [];
|
||||
document.getElementById('q-id').textContent = 'ID ' + (data.id ?? '–');
|
||||
document.getElementById('q-text').textContent = data.text ?? '';
|
||||
document.getElementById('q-textIntro').innerHTML = data.textIntro || ' ';
|
||||
if (data.textIntro) {
|
||||
document.getElementById('q-textIntro').style.display = 'block';
|
||||
} else {
|
||||
document.getElementById('q-textIntro').style.display = 'none';
|
||||
}
|
||||
document.getElementById('q-category').textContent = 'Kategorie: ' + (data.category ?? '–');
|
||||
document.getElementById('answersIntro').innerHTML = data.answersIntro || ' ';
|
||||
if (data.answersIntro) {
|
||||
document.getElementById('answersIntro').style.display = 'block';
|
||||
} else {
|
||||
document.getElementById('answersIntro').style.display = 'none';
|
||||
}
|
||||
|
||||
const correctCount = (data.answers || []).filter(a => a.correct).length;
|
||||
document.getElementById('q-type').textContent = `${correctCount} Antwort${correctCount !== 1 ? 'en' : ''} richtig`;
|
||||
const answersEl = document.getElementById('answers');
|
||||
answersEl.innerHTML = '';
|
||||
let answers = [...(data.answers || [])];
|
||||
//answers = shuffle(answers);
|
||||
answers.forEach((a, i) => {
|
||||
const li = document.createElement('li');
|
||||
li.setAttribute('role', 'button');
|
||||
li.tabIndex = 0;
|
||||
li.dataset.index = i;
|
||||
li.dataset.answerId = a.id;
|
||||
li.innerHTML = `<div>${a.text}</div>`;
|
||||
li.addEventListener('click', () => toggleAnswer(i));
|
||||
li.addEventListener('keydown', (e) => { if (e.key === "Enter" || e.key === " ") toggleAnswer(i) });
|
||||
answersEl.appendChild(li);
|
||||
});
|
||||
const lis = document.querySelectorAll('#answers li');
|
||||
lis.forEach(li => { li.classList.remove('incorrect', 'correct', 'selected'); li.style.boxShadow = 'none' });
|
||||
state.item.shuffledAnswers = answers;
|
||||
}
|
||||
|
||||
function toggleAnswer(index) {
|
||||
const li = document.querySelectorAll('#answers li')[index];
|
||||
if (state.selected.includes(index)) {
|
||||
state.selected = state.selected.filter(i => i !== index);
|
||||
li.classList.remove('selected');
|
||||
} else {
|
||||
state.selected.push(index);
|
||||
li.classList.add('selected');
|
||||
}
|
||||
}
|
||||
|
||||
window.updateQuiz = function (data) {
|
||||
if (data && data.answers) {
|
||||
data.answers = data.answers.map(a => ({ id: a.id, text: a.text, correct: !!a.correct }));
|
||||
}
|
||||
render(data);
|
||||
};
|
||||
|
||||
window.showCorrect = function () {
|
||||
if (!state.item) return;
|
||||
const lis = document.querySelectorAll('#answers li');
|
||||
lis.forEach((li, i) => {
|
||||
const ans = state.item.answers[i];
|
||||
if (ans && ans.correct) {
|
||||
li.classList.add('correct');
|
||||
if (state.selected.includes(i)) li.classList.add('selected');
|
||||
} else if (state.selected.includes(i)) {
|
||||
li.classList.add('incorrect');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
window.setCorrect = function ({ index = null, answerId = null } = {}) {
|
||||
if (!state.item) return;
|
||||
|
||||
const answers = state.item.answers;
|
||||
if (index == null && answerId == null) return;
|
||||
answers.forEach((a, i) => a.correct = ((index != null && i === index) || (answerId != null && a.id === answerId)));
|
||||
|
||||
render(state.item);
|
||||
};
|
||||
|
||||
document.getElementById('show-correct').addEventListener('click', () => window.showCorrect());
|
||||
document.getElementById('clear-selection').addEventListener('click', () => {
|
||||
state.selected = [];
|
||||
const lis = document.querySelectorAll('#answers li');
|
||||
lis.forEach(li => { li.classList.remove('incorrect', 'correct', 'selected'); li.style.boxShadow = 'none' });
|
||||
});
|
||||
|
||||
const nextbt = document.getElementById("next-q");
|
||||
const lastbt = document.getElementById("last-q");
|
||||
|
||||
nextbt.addEventListener("click", () => {
|
||||
const select = document.getElementById('question-select');
|
||||
|
||||
// Nur weitergehen, wenn es noch ein nächstes Element gibt
|
||||
if (select.selectedIndex < select.options.length - 1) {
|
||||
select.selectedIndex++;
|
||||
const selectedId = select.value;
|
||||
const question = state.allQuestions.find(q => q.id == selectedId);
|
||||
if (question) window.updateQuiz(question);
|
||||
}
|
||||
})
|
||||
|
||||
lastbt.addEventListener("click", () => {
|
||||
const select = document.getElementById('question-select');
|
||||
|
||||
if (select.selectedIndex > 0) {
|
||||
select.selectedIndex--;
|
||||
const selectedId = select.value;
|
||||
const question = state.allQuestions.find(q => q.id == selectedId);
|
||||
if (question) window.updateQuiz(question);
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
|
||||
const reportBtn = document.getElementById('report-error');
|
||||
const reportBox = document.getElementById('report-box');
|
||||
const sendReport = document.getElementById('send-report');
|
||||
reportBtn.addEventListener('click', () => {
|
||||
reportBox.style.display = reportBox.style.display === 'none' ? 'block' : 'none';
|
||||
});
|
||||
|
||||
sendReport.addEventListener('click', async () => {
|
||||
const text = document.getElementById('report-text').value.trim();
|
||||
if (text) {
|
||||
console.log('Fehlerbericht gesendet:', text);
|
||||
alert('Vielen Dank für Ihre Rückmeldung!');
|
||||
const data = {
|
||||
"question": state.item.id,
|
||||
"text": text
|
||||
};
|
||||
|
||||
const record = await pb.collection('ADA_report').create(data);
|
||||
|
||||
document.getElementById('report-text').value = '';
|
||||
reportBox.style.display = 'none';
|
||||
} else {
|
||||
alert('Bitte geben Sie eine Fehlerbeschreibung ein.');
|
||||
}
|
||||
});
|
||||
|
||||
// Beispiel-Initialisierung mit mehreren Fragen
|
||||
state.allQuestions = [];
|
||||
|
||||
let pb = new PocketBase();
|
||||
|
||||
(async () => {
|
||||
const records = await pb.collection('ADA_question').getFullList();
|
||||
console.log(records);
|
||||
|
||||
records.forEach(r => {
|
||||
const item = r;
|
||||
state.allQuestions.push(item);
|
||||
});
|
||||
|
||||
let r = Math.floor(Math.random() * state.allQuestions.length);
|
||||
|
||||
window.updateQuiz(state.allQuestions[r]);
|
||||
|
||||
populateDropdown();
|
||||
})();
|
||||
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
Reference in New Issue
Block a user