Rename MC to MultipleChoice, implement SingleChoice option, and update documentation
This commit is contained in:
+18
-18
@@ -58,7 +58,7 @@ Für jedes Fragenset wird eine eigene Tabelle erstellt. Der Aufbau ist immer ide
|
|||||||
| Nr. | Feldname | Feldtyp | Optionen / Hinweis |
|
| Nr. | Feldname | Feldtyp | Optionen / Hinweis |
|
||||||
|-----|--------------------|------------------|---------------------------------------------------|
|
|-----|--------------------|------------------|---------------------------------------------------|
|
||||||
| 1 | `Frage` | Long text | Der vollständige Fragetext |
|
| 1 | `Frage` | Long text | Der vollständige Fragetext |
|
||||||
| 2 | `Typ` | Single select | Optionen anlegen: `MC`, `Wahr/Falsch`, `Freitext` |
|
| 2 | `Typ` | Single select | Optionen anlegen: `MultipleChoice`, `SingleChoice`, `Wahr/Falsch`, `Freitext` |
|
||||||
| 3 | `Bild` | File | Optional — Bild zur Frage hochladen |
|
| 3 | `Bild` | File | Optional — Bild zur Frage hochladen |
|
||||||
| 4 | `Antwort A` | Single line text | Bei Wahr/Falsch: `Wahr` |
|
| 4 | `Antwort A` | Single line text | Bei Wahr/Falsch: `Wahr` |
|
||||||
| 5 | `Antwort B` | Single line text | Bei Wahr/Falsch: `Falsch` |
|
| 5 | `Antwort B` | Single line text | Bei Wahr/Falsch: `Falsch` |
|
||||||
@@ -75,8 +75,8 @@ Für jedes Fragenset wird eine eigene Tabelle erstellt. Der Aufbau ist immer ide
|
|||||||
|
|
||||||
| Fragetyp | Beispiel-Wert | Erklärung |
|
| Fragetyp | Beispiel-Wert | Erklärung |
|
||||||
|----------------|---------------|------------------------------------------------|
|
|----------------|---------------|------------------------------------------------|
|
||||||
| MC (1 richtig) | `A` | Nur der Buchstabe der richtigen Antwort |
|
| SingleChoice | `A` | Nur der Buchstabe der richtigen Antwort |
|
||||||
| MC (mehrere) | `A,C` | Kommagetrennt, ohne Leerzeichen |
|
| MultipleChoice | `A,C` | Kommagetrennt, ohne Leerzeichen |
|
||||||
| Wahr/Falsch | `A` | `A` = Wahr, `B` = Falsch |
|
| Wahr/Falsch | `A` | `A` = Wahr, `B` = Falsch |
|
||||||
| Freitext | `Berlin` | Das erwartete Wort (Groß/Kleinschreibung egal) |
|
| Freitext | `Berlin` | Das erwartete Wort (Groß/Kleinschreibung egal) |
|
||||||
|
|
||||||
@@ -100,23 +100,23 @@ Die App normalisiert Eingaben automatisch:
|
|||||||
|
|
||||||
## 5. Beispielfragen
|
## 5. Beispielfragen
|
||||||
|
|
||||||
### MC mit 4 Antworten
|
### SingleChoice mit 4 Antworten
|
||||||
|
|
||||||
| Frage | Typ | Antwort A | Antwort B | Antwort C | Antwort D | Richtige Antwort | Kategorie |
|
| Frage | Typ | Antwort A | Antwort B | Antwort C | Antwort D | Richtige Antwort | Kategorie |
|
||||||
|-------------------------------------------------|-----|-----------|-----------|-----------|-----------|------------------|-----------|
|
|-------------------------------------------------|--------------|-----------|-----------|-----------|-----------|------------------|-----------|
|
||||||
| Wie heißt die Hauptstadt von Baden-Württemberg? | MC | Stuttgart | Karlsruhe | Freiburg | Mannheim | A | Geografie |
|
| Wie heißt die Hauptstadt von Baden-Württemberg? | SingleChoice | Stuttgart | Karlsruhe | Freiburg | Mannheim | A | Geografie |
|
||||||
|
|
||||||
### MC mit mehreren richtigen Antworten
|
### MultipleChoice mit mehreren richtigen Antworten
|
||||||
|
|
||||||
| Frage | Typ | Antwort A | Antwort B | Antwort C | Antwort D | Richtige Antwort | Kategorie |
|
| Frage | Typ | Antwort A | Antwort B | Antwort C | Antwort D | Richtige Antwort | Kategorie |
|
||||||
|----------------------------------|-----|-----------|-----------|-----------|-----------|------------------|-----------|
|
|----------------------------------|----------------|-----------|-----------|-----------|-----------|------------------|-----------|
|
||||||
| Welche sind Programmiersprachen? | MC | Python | HTML | Java | Photoshop | A,C | IT |
|
| Welche sind Programmiersprachen? | MultipleChoice | Python | HTML | Java | Photoshop | A,C | IT |
|
||||||
|
|
||||||
### MC mit 3 Antworten
|
### SingleChoice mit 3 Antworten
|
||||||
|
|
||||||
| Frage | Typ | Antwort A | Antwort B | Antwort C | Antwort D | Richtige Antwort | Kategorie |
|
| Frage | Typ | Antwort A | Antwort B | Antwort C | Antwort D | Richtige Antwort | Kategorie |
|
||||||
|-----------------------------------|-----|-----------|-----------|-----------|-----------|------------------|-----------|
|
|-----------------------------------|--------------|-----------|-----------|-----------|-----------|------------------|-----------|
|
||||||
| Welche Farbe hat ein Stoppschild? | MC | Rot | Blau | Grün | *(leer)* | A | Verkehr |
|
| Welche Farbe hat ein Stoppschild? | SingleChoice | Rot | Blau | Grün | *(leer)* | A | Verkehr |
|
||||||
|
|
||||||
### Wahr/Falsch
|
### Wahr/Falsch
|
||||||
|
|
||||||
@@ -132,9 +132,9 @@ Die App normalisiert Eingaben automatisch:
|
|||||||
|
|
||||||
### Mit Bild
|
### Mit Bild
|
||||||
|
|
||||||
| Frage | Typ | Bild | Antwort A | Antwort B | Antwort C | Antwort D | Richtige Antwort | Kategorie |
|
| Frage | Typ | Bild | Antwort A | Antwort B | Antwort C | Antwort D | Richtige Antwort | Kategorie |
|
||||||
|-----------------------------------|-----|--------------------|-----------|-----------|---------------|------------|------------------|-----------|
|
|-----------------------------------|--------------|--------------------|-----------|-----------|---------------|------------|------------------|-----------|
|
||||||
| Was zeigt dieses Verkehrszeichen? | MC | *(Bild hochladen)* | Vorfahrt | Stopp | Einbahnstraße | Parkverbot | B | Verkehr |
|
| Was zeigt dieses Verkehrszeichen? | SingleChoice | *(Bild hochladen)* | Vorfahrt | Stopp | Einbahnstraße | Parkverbot | B | Verkehr |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
+14
-14
@@ -90,8 +90,8 @@ function Leaderboard({ entries, highlight, compact }) {
|
|||||||
const ANSWER_COLORS = { A: 'answer-a', B: 'answer-b', C: 'answer-c', D: 'answer-d' };
|
const ANSWER_COLORS = { A: 'answer-a', B: 'answer-b', C: 'answer-c', D: 'answer-d' };
|
||||||
|
|
||||||
function AnswerGrid({ question, selected, onToggle, disabled, feedback }) {
|
function AnswerGrid({ question, selected, onToggle, disabled, feedback }) {
|
||||||
const typ = (question.typ || 'MC').toLowerCase();
|
const typ = (question.typ || 'MultipleChoice').toLowerCase();
|
||||||
const isWF = typ === 'wahr/falsch';
|
const isSingle = typ === 'wahr/falsch' || typ === 'singlechoice';
|
||||||
|
|
||||||
if (typ === 'freitext') {
|
if (typ === 'freitext') {
|
||||||
if (feedback) {
|
if (feedback) {
|
||||||
@@ -132,7 +132,7 @@ function AnswerGrid({ question, selected, onToggle, disabled, feedback }) {
|
|||||||
<button key={a.key} className={cls} disabled={disabled || !!feedback}
|
<button key={a.key} className={cls} disabled={disabled || !!feedback}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (disabled || feedback) return;
|
if (disabled || feedback) return;
|
||||||
if (isWF) onToggle([a.key]);
|
if (isSingle) onToggle([a.key]);
|
||||||
else onToggle(isSelected ? selected.filter((s) => s !== a.key) : [...selected, a.key]);
|
else onToggle(isSelected ? selected.filter((s) => s !== a.key) : [...selected, a.key]);
|
||||||
}}>
|
}}>
|
||||||
{icon}{a.text}
|
{icon}{a.text}
|
||||||
@@ -503,10 +503,10 @@ function SoloQuizPage({ params, navigate }) {
|
|||||||
if (!questions.length) return <div className="min-h-screen flex items-center justify-center animate-pulse text-xl text-amber-400">Lade Fragen...</div>;
|
if (!questions.length) return <div className="min-h-screen flex items-center justify-center animate-pulse text-xl text-amber-400">Lade Fragen...</div>;
|
||||||
|
|
||||||
const q = questions[index];
|
const q = questions[index];
|
||||||
const typ = fieldVal(q['Typ'], 'MC').toLowerCase();
|
const typ = fieldVal(q['Typ'], 'MultipleChoice').toLowerCase();
|
||||||
const sanitized = {
|
const sanitized = {
|
||||||
frage: q['Frage'] || '(Kein Fragetext)',
|
frage: q['Frage'] || '(Kein Fragetext)',
|
||||||
typ: fieldVal(q['Typ'], 'MC'),
|
typ: fieldVal(q['Typ'], 'MultipleChoice'),
|
||||||
bild: q['Bild'] && Array.isArray(q['Bild']) && q['Bild'].length ? q['Bild'][0].url : null,
|
bild: q['Bild'] && Array.isArray(q['Bild']) && q['Bild'].length ? q['Bild'][0].url : null,
|
||||||
antworten: shuffledAnswers,
|
antworten: shuffledAnswers,
|
||||||
};
|
};
|
||||||
@@ -838,7 +838,7 @@ function WettkampfQuizPage({ params, navigate }) {
|
|||||||
// Detect all unique types dynamically
|
// Detect all unique types dynamically
|
||||||
const typeGroups = {};
|
const typeGroups = {};
|
||||||
shuffled.forEach((q) => {
|
shuffled.forEach((q) => {
|
||||||
const typ = fieldVal(q['Typ'], 'MC');
|
const typ = fieldVal(q['Typ'], 'MultipleChoice');
|
||||||
if (!typeGroups[typ]) typeGroups[typ] = [];
|
if (!typeGroups[typ]) typeGroups[typ] = [];
|
||||||
typeGroups[typ].push(q);
|
typeGroups[typ].push(q);
|
||||||
});
|
});
|
||||||
@@ -909,7 +909,7 @@ function WettkampfQuizPage({ params, navigate }) {
|
|||||||
if (questions.length === 0) return;
|
if (questions.length === 0) return;
|
||||||
const map = {};
|
const map = {};
|
||||||
questions.forEach((q) => {
|
questions.forEach((q) => {
|
||||||
const typ = fieldVal(q['Typ'], 'MC').toLowerCase();
|
const typ = fieldVal(q['Typ'], 'MultipleChoice').toLowerCase();
|
||||||
if (typ !== 'freitext') {
|
if (typ !== 'freitext') {
|
||||||
const antworten = [];
|
const antworten = [];
|
||||||
['A', 'B', 'C', 'D'].forEach((k) => { if (q[`Antwort ${k}`]) antworten.push({ key: k, text: q[`Antwort ${k}`] }); });
|
['A', 'B', 'C', 'D'].forEach((k) => { if (q[`Antwort ${k}`]) antworten.push({ key: k, text: q[`Antwort ${k}`] }); });
|
||||||
@@ -1026,7 +1026,7 @@ function WettkampfQuizPage({ params, navigate }) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const typ = fieldVal(question['Typ'], 'MC').toLowerCase();
|
const typ = fieldVal(question['Typ'], 'MultipleChoice').toLowerCase();
|
||||||
const rawCorrect = (question['Richtige Antwort'] || '').trim();
|
const rawCorrect = (question['Richtige Antwort'] || '').trim();
|
||||||
let ok = false;
|
let ok = false;
|
||||||
|
|
||||||
@@ -1136,13 +1136,13 @@ function WettkampfQuizPage({ params, navigate }) {
|
|||||||
|
|
||||||
const currentAnswer = answers[q?.id];
|
const currentAnswer = answers[q?.id];
|
||||||
const currentSelected = currentAnswer?.selected || [];
|
const currentSelected = currentAnswer?.selected || [];
|
||||||
const typ = fieldVal(q['Typ'], 'MC').toLowerCase();
|
const typ = fieldVal(q['Typ'], 'MultipleChoice').toLowerCase();
|
||||||
const isWF = typ === 'wahr/falsch';
|
const isSingle = typ === 'wahr/falsch' || typ === 'singlechoice';
|
||||||
const currentShuffledAnswers = shuffledAnswersMap[q?.id] || [];
|
const currentShuffledAnswers = shuffledAnswersMap[q?.id] || [];
|
||||||
|
|
||||||
const sanitized = {
|
const sanitized = {
|
||||||
frage: q['Frage'] || '(Kein Fragetext)',
|
frage: q['Frage'] || '(Kein Fragetext)',
|
||||||
typ: fieldVal(q['Typ'], 'MC'),
|
typ: fieldVal(q['Typ'], 'MultipleChoice'),
|
||||||
bild: q['Bild'] && Array.isArray(q['Bild']) && q['Bild'].length ? q['Bild'][0].url : null,
|
bild: q['Bild'] && Array.isArray(q['Bild']) && q['Bild'].length ? q['Bild'][0].url : null,
|
||||||
antworten: currentShuffledAnswers,
|
antworten: currentShuffledAnswers,
|
||||||
};
|
};
|
||||||
@@ -1240,7 +1240,7 @@ function WettkampfQuizPage({ params, navigate }) {
|
|||||||
return (
|
return (
|
||||||
<button key={a.key} className={cls}
|
<button key={a.key} className={cls}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (isWF) handleSelect(q.id, [a.key]);
|
if (isSingle) handleSelect(q.id, [a.key]);
|
||||||
else handleSelect(q.id, isSelected ? currentSelected.filter((s) => s !== a.key) : [...currentSelected, a.key]);
|
else handleSelect(q.id, isSelected ? currentSelected.filter((s) => s !== a.key) : [...currentSelected, a.key]);
|
||||||
}}>
|
}}>
|
||||||
{a.text}
|
{a.text}
|
||||||
@@ -1304,7 +1304,7 @@ function WettkampfResultPage({ params, navigate }) {
|
|||||||
// Resolve correct answers for a question
|
// Resolve correct answers for a question
|
||||||
const resolveCorrectKeys = (question) => {
|
const resolveCorrectKeys = (question) => {
|
||||||
const rawCorrect = (question['Richtige Antwort'] || '').trim();
|
const rawCorrect = (question['Richtige Antwort'] || '').trim();
|
||||||
const typ = fieldVal(question['Typ'], 'MC').toLowerCase();
|
const typ = fieldVal(question['Typ'], 'MultipleChoice').toLowerCase();
|
||||||
if (typ === 'freitext') return { keys: [], text: rawCorrect };
|
if (typ === 'freitext') return { keys: [], text: rawCorrect };
|
||||||
|
|
||||||
const parts = rawCorrect.split(',').map((s) => s.trim()).filter(Boolean);
|
const parts = rawCorrect.split(',').map((s) => s.trim()).filter(Boolean);
|
||||||
@@ -1326,7 +1326,7 @@ function WettkampfResultPage({ params, navigate }) {
|
|||||||
const QuestionReview = ({ result }) => {
|
const QuestionReview = ({ result }) => {
|
||||||
const question = qMap[result.questionId];
|
const question = qMap[result.questionId];
|
||||||
if (!question) return null;
|
if (!question) return null;
|
||||||
const typ = fieldVal(question['Typ'], 'MC').toLowerCase();
|
const typ = fieldVal(question['Typ'], 'MultipleChoice').toLowerCase();
|
||||||
const userAnswer = answers[result.questionId];
|
const userAnswer = answers[result.questionId];
|
||||||
const selected = userAnswer?.selected || [];
|
const selected = userAnswer?.selected || [];
|
||||||
const { keys: correctKeys, text: correctText } = resolveCorrectKeys(question);
|
const { keys: correctKeys, text: correctText } = resolveCorrectKeys(question);
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ function extractFieldValue(field, fallback) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getTyp(question) {
|
function getTyp(question) {
|
||||||
return extractFieldValue(question['Typ'], 'MC').toLowerCase();
|
return extractFieldValue(question['Typ'], 'MultipleChoice').toLowerCase();
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkFreitext(userAnswer, correctAnswer) {
|
function checkFreitext(userAnswer, correctAnswer) {
|
||||||
@@ -134,7 +134,7 @@ function sanitizeQuestion(question) {
|
|||||||
const sanitized = {
|
const sanitized = {
|
||||||
id: question.id,
|
id: question.id,
|
||||||
frage: question['Frage'] || '',
|
frage: question['Frage'] || '',
|
||||||
typ: extractFieldValue(question['Typ'], 'MC'),
|
typ: extractFieldValue(question['Typ'], 'MultipleChoice'),
|
||||||
bild: null,
|
bild: null,
|
||||||
antworten: [],
|
antworten: [],
|
||||||
kategorie: extractFieldValue(question['Kategorie'], ''),
|
kategorie: extractFieldValue(question['Kategorie'], ''),
|
||||||
|
|||||||
Reference in New Issue
Block a user