Move Wettkampf question count to admin-configured setting per set
This commit is contained in:
+26
-5
@@ -59,19 +59,22 @@ function LoginPage({ onLogin }) {
|
||||
function ConfigPage({ config, setConfig, saveConfig }) {
|
||||
const [newSetName, setNewSetName] = useState('');
|
||||
const [newSetTableId, setNewSetTableId] = useState('');
|
||||
const [newSetWettkampfCount, setNewSetWettkampfCount] = useState('');
|
||||
const [msg, setMsg] = useState('');
|
||||
const [debugResult, setDebugResult] = useState(null);
|
||||
|
||||
const addSet = () => {
|
||||
if (!newSetName || !newSetTableId) return;
|
||||
const wkCount = parseInt(newSetWettkampfCount) || null;
|
||||
const updated = {
|
||||
...config,
|
||||
sets: [...(config.sets || []), { id: Date.now().toString(36), name: newSetName, tableId: parseInt(newSetTableId) }],
|
||||
sets: [...(config.sets || []), { id: Date.now().toString(36), name: newSetName, tableId: parseInt(newSetTableId), wettkampfCount: wkCount }],
|
||||
};
|
||||
setConfig(updated);
|
||||
saveConfig(updated);
|
||||
setNewSetName('');
|
||||
setNewSetTableId('');
|
||||
setNewSetWettkampfCount('');
|
||||
setMsg('Fragenset hinzugefügt!');
|
||||
setTimeout(() => setMsg(''), 3000);
|
||||
};
|
||||
@@ -82,6 +85,16 @@ function ConfigPage({ config, setConfig, saveConfig }) {
|
||||
saveConfig(updated);
|
||||
};
|
||||
|
||||
const updateSetWettkampfCount = (setId, val) => {
|
||||
const wkCount = parseInt(val) || null;
|
||||
const updated = {
|
||||
...config,
|
||||
sets: config.sets.map((s) => s.id === setId ? { ...s, wettkampfCount: wkCount } : s),
|
||||
};
|
||||
setConfig(updated);
|
||||
saveConfig(updated);
|
||||
};
|
||||
|
||||
const updateAnswersTable = (val) => {
|
||||
const updated = { ...config, answersTableId: val ? parseInt(val) : null };
|
||||
setConfig(updated);
|
||||
@@ -104,14 +117,22 @@ function ConfigPage({ config, setConfig, saveConfig }) {
|
||||
<h2 className="text-xl font-bold mb-4">📋 Fragensets</h2>
|
||||
{(config.sets || []).length === 0 && <p className="text-gray-500 mb-3">Noch keine Fragensets konfiguriert.</p>}
|
||||
{(config.sets || []).map((s) => (
|
||||
<div key={s.id} className="flex items-center justify-between py-2 border-b last:border-0">
|
||||
<div><span className="font-semibold">{s.name}</span> <span className="text-gray-400 text-sm">(Table ID: {s.tableId})</span></div>
|
||||
<button onClick={() => removeSet(s.id)} className="text-red-500 hover:text-red-700 text-sm">Entfernen</button>
|
||||
<div key={s.id} className="py-3 border-b last:border-0">
|
||||
<div className="flex items-center justify-between">
|
||||
<div><span className="font-semibold">{s.name}</span> <span className="text-gray-400 text-sm">(Table ID: {s.tableId})</span></div>
|
||||
<button onClick={() => removeSet(s.id)} className="text-red-500 hover:text-red-700 text-sm">Entfernen</button>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 mt-2">
|
||||
<span className="text-sm text-gray-500">⚔️ Wettkampf-Fragen:</span>
|
||||
<input className="w-20 p-1 border rounded text-center text-sm focus:border-emerald-500 outline-none" type="number" min="1" placeholder="—" value={s.wettkampfCount || ''} onChange={(e) => updateSetWettkampfCount(s.id, e.target.value)} />
|
||||
{s.wettkampfCount ? <span className="text-xs text-emerald-600">✓ aktiv</span> : <span className="text-xs text-gray-400">nicht konfiguriert</span>}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
<div className="mt-4 flex gap-2 flex-wrap">
|
||||
<input className="flex-1 min-w-0 p-2 border rounded-lg focus:border-red-500 outline-none" placeholder="Name" value={newSetName} onChange={(e) => setNewSetName(e.target.value)} />
|
||||
<input className="w-32 p-2 border rounded-lg focus:border-red-500 outline-none" placeholder="Table ID" type="number" value={newSetTableId} onChange={(e) => setNewSetTableId(e.target.value)} />
|
||||
<input className="w-28 p-2 border rounded-lg focus:border-red-500 outline-none" placeholder="Table ID" type="number" value={newSetTableId} onChange={(e) => setNewSetTableId(e.target.value)} />
|
||||
<input className="w-20 p-2 border rounded-lg focus:border-emerald-500 outline-none" placeholder="⚔️ Anz." type="number" min="1" value={newSetWettkampfCount} onChange={(e) => setNewSetWettkampfCount(e.target.value)} />
|
||||
<button onClick={addSet} className="px-4 py-2 bg-red-600 text-white rounded-lg font-bold hover:bg-red-700">+</button>
|
||||
</div>
|
||||
{msg && <p className="text-green-600 text-sm mt-2">{msg}</p>}
|
||||
|
||||
+14
-24
@@ -680,18 +680,20 @@ function WettkampfSelectPage({ navigate }) {
|
||||
const [sets, setSets] = useState([]);
|
||||
const [name, setName] = useState('');
|
||||
const [selectedSet, setSelectedSet] = useState(null);
|
||||
const [questionCount, setQuestionCount] = useState('');
|
||||
const [totalQuestions, setTotalQuestions] = useState(null);
|
||||
const [error, setError] = useState('');
|
||||
const [checking, setChecking] = useState(false);
|
||||
const [progressDialog, setProgressDialog] = useState(null);
|
||||
|
||||
useEffect(() => { api.get('/api/sets').then(setSets).catch((e) => setError(e.message)); }, []);
|
||||
useEffect(() => {
|
||||
api.get('/api/sets').then((allSets) => {
|
||||
// Only show sets with a configured wettkampfCount
|
||||
setSets(allSets.filter((s) => s.wettkampfCount && s.wettkampfCount > 0));
|
||||
}).catch((e) => setError(e.message));
|
||||
}, []);
|
||||
|
||||
const selectSet = async (set) => {
|
||||
setSelectedSet(set);
|
||||
setQuestionCount('');
|
||||
setTotalQuestions(null);
|
||||
try {
|
||||
const data = await api.get(`/api/sets/${set.id}/count`);
|
||||
setTotalQuestions(data.count);
|
||||
@@ -702,9 +704,7 @@ function WettkampfSelectPage({ navigate }) {
|
||||
|
||||
const startWettkampf = async () => {
|
||||
if (!name.trim()) return setError('Bitte Name eingeben');
|
||||
const count = parseInt(questionCount);
|
||||
if (!count || count < 1) return setError('Bitte Anzahl der Fragen eingeben');
|
||||
if (count > totalQuestions) return setError(`Maximal ${totalQuestions} Fragen verfügbar`);
|
||||
const count = selectedSet.wettkampfCount;
|
||||
setChecking(true);
|
||||
setError('');
|
||||
try {
|
||||
@@ -723,7 +723,7 @@ function WettkampfSelectPage({ navigate }) {
|
||||
const continueSession = () => {
|
||||
navigate('wettkampf-quiz', {
|
||||
setId: selectedSet.id, setName: selectedSet.name,
|
||||
name: name.trim(), questionCount: progressDialog.count,
|
||||
name: name.trim(), questionCount: selectedSet.wettkampfCount,
|
||||
continueSessionId: progressDialog.sessionId,
|
||||
priorAnswers: progressDialog.answers,
|
||||
});
|
||||
@@ -733,7 +733,7 @@ function WettkampfSelectPage({ navigate }) {
|
||||
const startFresh = () => {
|
||||
navigate('wettkampf-quiz', {
|
||||
setId: selectedSet.id, setName: selectedSet.name,
|
||||
name: name.trim(), questionCount: parseInt(questionCount),
|
||||
name: name.trim(), questionCount: selectedSet.wettkampfCount,
|
||||
});
|
||||
setProgressDialog(null);
|
||||
};
|
||||
@@ -761,11 +761,11 @@ function WettkampfSelectPage({ navigate }) {
|
||||
{!selectedSet ? (
|
||||
<>
|
||||
<h3 className="text-lg font-semibold mt-4">Fragenset wählen:</h3>
|
||||
{sets.length === 0 && <p className="text-gray-500">Keine Fragensets konfiguriert.</p>}
|
||||
{sets.length === 0 && <p className="text-gray-500">Keine Wettkampf-Sets konfiguriert. Bitte im Admin-Bereich konfigurieren.</p>}
|
||||
{sets.map((s) => (
|
||||
<button key={s.id} onClick={() => selectSet(s)}
|
||||
className="w-full py-4 bg-emerald-600 hover:bg-emerald-700 rounded-xl text-lg font-bold transition-all shadow-lg shadow-emerald-900/30">
|
||||
{s.name}
|
||||
{s.name} <span className="text-emerald-200 text-sm">({s.wettkampfCount} Fragen)</span>
|
||||
</button>
|
||||
))}
|
||||
</>
|
||||
@@ -775,24 +775,14 @@ function WettkampfSelectPage({ navigate }) {
|
||||
<div>
|
||||
<span className="text-sm text-gray-400">Fragenset:</span>
|
||||
<p className="font-bold text-emerald-400">{selectedSet.name}</p>
|
||||
<p className="text-sm text-gray-400">{selectedSet.wettkampfCount} Fragen{totalQuestions ? ` (von ${totalQuestions} gesamt)` : ''}</p>
|
||||
</div>
|
||||
<button onClick={() => { setSelectedSet(null); setTotalQuestions(null); setQuestionCount(''); }} className="text-sm text-gray-400 hover:text-white">Ändern</button>
|
||||
<button onClick={() => { setSelectedSet(null); setTotalQuestions(null); }} className="text-sm text-gray-400 hover:text-white">Ändern</button>
|
||||
</div>
|
||||
|
||||
{totalQuestions !== null && (
|
||||
<div className="space-y-2">
|
||||
<label className="block text-sm text-gray-400">Anzahl Fragen (max. {totalQuestions})</label>
|
||||
<input type="number" min="1" max={totalQuestions}
|
||||
className="w-full p-4 rounded-xl bg-gray-800 text-white text-center text-xl border-2 border-gray-700 focus:border-emerald-500 outline-none"
|
||||
placeholder={`1 – ${totalQuestions}`}
|
||||
value={questionCount}
|
||||
onChange={(e) => setQuestionCount(e.target.value)} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{error && <p className="text-red-400">{error}</p>}
|
||||
|
||||
<button onClick={startWettkampf} disabled={checking || !questionCount}
|
||||
<button onClick={startWettkampf} disabled={checking}
|
||||
className="w-full py-4 bg-emerald-600 hover:bg-emerald-700 disabled:opacity-40 rounded-xl text-xl font-bold transition-all shadow-lg shadow-emerald-900/30">
|
||||
{checking ? 'Prüfe...' : '⚔️ Wettkampf starten'}
|
||||
</button>
|
||||
|
||||
+1
-1
@@ -90,7 +90,7 @@ function rewriteImageUrls(rows) {
|
||||
|
||||
app.get('/api/sets', (req, res) => {
|
||||
const config = readConfig();
|
||||
res.json((config.sets || []).map((s) => ({ id: s.id, name: s.name })));
|
||||
res.json((config.sets || []).map((s) => ({ id: s.id, name: s.name, wettkampfCount: s.wettkampfCount || null })));
|
||||
});
|
||||
|
||||
app.get('/api/sets/:id/questions', async (req, res) => {
|
||||
|
||||
Reference in New Issue
Block a user