950f51c61b
Success overlay with animated checkmark and 'Dein Tipp ist drin! 🎯' message. Haptic vibration on mobile. Auto-closes after 1.2s. - Add showSuccess state to TipModal - Trigger vibration feedback on successful submit - Display success overlay with popIn animation for checkmark - Auto-close modal after success animation completes - Add CSS animations (fadeIn, popIn) to TipModal.module.css Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
121 lines
7.6 KiB
JavaScript
121 lines
7.6 KiB
JavaScript
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
||
import { useState } from 'react';
|
||
import styles from './DevPanel.module.css';
|
||
const DEV_USERS = [
|
||
{ id: 1, name: 'Ronny M.', role: 'Editor' },
|
||
{ id: 2, name: 'Max M.', role: 'Viewer' },
|
||
{ id: 3, name: 'Anna S.', role: 'Viewer' },
|
||
];
|
||
const TIME_PRESETS = [
|
||
{ label: 'In 2 Std.', minutes: 120 },
|
||
{ label: 'In 10 Min.', minutes: 10 },
|
||
{ label: 'Jetzt +1 Min.', minutes: 1 },
|
||
{ label: 'Läuft (−30)', minutes: -30 },
|
||
{ label: 'Beendet (−120)', minutes: -120 },
|
||
];
|
||
const STATUS_PRESETS = [
|
||
{ label: 'TIMED', status: 'TIMED', scoreHome: null, scoreAway: null },
|
||
{ label: 'LIVE', status: 'IN_PLAY', scoreHome: 0, scoreAway: 0 },
|
||
{ label: 'Pause', status: 'PAUSED', scoreHome: 1, scoreAway: 0 },
|
||
{ label: '2:1 Fertig', status: 'FINISHED', scoreHome: 2, scoreAway: 1 },
|
||
{ label: '0:0 Fertig', status: 'FINISHED', scoreHome: 0, scoreAway: 0 },
|
||
];
|
||
export default function DevPanel({ currentUser, onUserChange, matches, onRefresh }) {
|
||
const [open, setOpen] = useState(false);
|
||
const [selectedMatch, setSelectedMatch] = useState('');
|
||
const [busy, setBusy] = useState(false);
|
||
const [log, setLog] = useState([]);
|
||
function addLog(msg) {
|
||
setLog(prev => [`${new Date().toLocaleTimeString('de-DE')} ${msg}`, ...prev].slice(0, 8));
|
||
}
|
||
async function applyTime(minutes) {
|
||
if (!selectedMatch)
|
||
return;
|
||
setBusy(true);
|
||
try {
|
||
await fetch(`/api/dev/match/${selectedMatch}/set-time`, {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({ minutesFromNow: minutes }),
|
||
});
|
||
addLog(`✓ Spiel #${selectedMatch}: Zeit → ${minutes > 0 ? `+${minutes}` : minutes} Min.`);
|
||
onRefresh();
|
||
}
|
||
catch (e) {
|
||
addLog(`✗ Fehler: ${e.message}`);
|
||
}
|
||
finally {
|
||
setBusy(false);
|
||
}
|
||
}
|
||
async function applyStatus(status, scoreHome, scoreAway) {
|
||
if (!selectedMatch)
|
||
return;
|
||
setBusy(true);
|
||
try {
|
||
await fetch(`/api/dev/match/${selectedMatch}/set-status`, {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({ status, scoreHome, scoreAway }),
|
||
});
|
||
addLog(`✓ Spiel #${selectedMatch}: Status → ${status}`);
|
||
onRefresh();
|
||
}
|
||
catch (e) {
|
||
addLog(`✗ Fehler: ${e.message}`);
|
||
}
|
||
finally {
|
||
setBusy(false);
|
||
}
|
||
}
|
||
async function resetTips() {
|
||
setBusy(true);
|
||
try {
|
||
await fetch('/api/dev/reset-tips', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({ userId: `dev-user-00${currentUser}` }),
|
||
});
|
||
addLog(`✓ Tipps von User ${currentUser} gelöscht`);
|
||
onRefresh();
|
||
}
|
||
catch (e) {
|
||
addLog(`✗ Fehler: ${e.message}`);
|
||
}
|
||
finally {
|
||
setBusy(false);
|
||
}
|
||
}
|
||
async function resetMatch(all) {
|
||
setBusy(true);
|
||
try {
|
||
const body = all ? {} : { matchId: selectedMatch };
|
||
const res = await fetch('/api/dev/reset-match', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify(body),
|
||
});
|
||
const data = await res.json();
|
||
if (all) {
|
||
addLog(`✓ ${data.count ?? 0} Spiele zurückgesetzt (TIMED)`);
|
||
}
|
||
else {
|
||
addLog(`✓ Spiel #${selectedMatch} zurückgesetzt (TIMED)`);
|
||
}
|
||
onRefresh();
|
||
}
|
||
catch (e) {
|
||
addLog(`✗ Fehler: ${e.message}`);
|
||
}
|
||
finally {
|
||
setBusy(false);
|
||
}
|
||
}
|
||
// Nur erste 20 Spiele zur Auswahl anbieten
|
||
const selectableMatches = matches.slice(0, 20);
|
||
return (_jsxs("div", { className: styles.wrap, children: [_jsxs("button", { className: styles.toggleBtn, onClick: () => setOpen(o => !o), children: [open ? '✕' : '🧪', " ", !open && _jsx("span", { className: styles.toggleLabel, children: "Dev" })] }), open && (_jsxs("div", { className: styles.panel, children: [_jsx("div", { className: styles.panelHeader, children: _jsx("span", { className: styles.panelTitle, children: "\uD83E\uDDEA Simulations-Modus" }) }), _jsxs("section", { className: styles.section, children: [_jsx("div", { className: styles.sectionLabel, children: "Aktiver User" }), _jsx("div", { className: styles.userButtons, children: DEV_USERS.map(u => (_jsxs("button", { className: `${styles.userBtn} ${currentUser === u.id ? styles.userBtnActive : ''}`, onClick: () => {
|
||
onUserChange(u.id);
|
||
addLog(`→ Wechsel zu User ${u.id}: ${u.name}`);
|
||
}, children: [_jsx("span", { className: styles.userInitial, children: u.name.charAt(0) }), _jsx("span", { className: styles.userName, children: u.name }), _jsx("span", { className: styles.userRole, children: u.role })] }, u.id))) })] }), _jsxs("section", { className: styles.section, children: [_jsx("div", { className: styles.sectionLabel, children: "Spiel ausw\u00E4hlen" }), _jsxs("select", { className: styles.select, value: selectedMatch, onChange: e => setSelectedMatch(e.target.value ? parseInt(e.target.value) : ''), children: [_jsx("option", { value: "", children: "\u2014 Spiel w\u00E4hlen \u2014" }), selectableMatches.map(m => (_jsxs("option", { value: m.id, children: ["#", m.id, " ", m.homeTeam.shortName, " vs ", m.awayTeam.shortName] }, m.id)))] })] }), _jsxs("section", { className: `${styles.section} ${!selectedMatch ? styles.sectionDisabled : ''}`, children: [_jsx("div", { className: styles.sectionLabel, children: "Ansto\u00DFzeit setzen" }), _jsx("div", { className: styles.presetGrid, children: TIME_PRESETS.map(p => (_jsx("button", { className: styles.presetBtn, onClick: () => applyTime(p.minutes), disabled: !selectedMatch || busy, children: p.label }, p.label))) })] }), _jsxs("section", { className: `${styles.section} ${!selectedMatch ? styles.sectionDisabled : ''}`, children: [_jsx("div", { className: styles.sectionLabel, children: "Status setzen" }), _jsx("div", { className: styles.presetGrid, children: STATUS_PRESETS.map(p => (_jsx("button", { className: `${styles.presetBtn} ${p.status === 'FINISHED' ? styles.presetBtnDanger : p.status === 'IN_PLAY' ? styles.presetBtnLive : ''}`, onClick: () => applyStatus(p.status, p.scoreHome, p.scoreAway), disabled: !selectedMatch || busy, children: p.label }, p.label))) })] }), _jsxs("section", { className: styles.section, children: [_jsx("div", { className: styles.sectionLabel, children: "Zur\u00FCcksetzen" }), _jsxs("div", { className: styles.resetGrid, children: [_jsx("button", { className: styles.resetBtn, onClick: () => resetMatch(false), disabled: !selectedMatch || busy, title: "Ausgew\u00E4hltes Spiel auf TIMED zur\u00FCcksetzen", children: "\u21BA Spiel zur\u00FCcksetzen" }), _jsx("button", { className: `${styles.resetBtn} ${styles.resetBtnAll}`, onClick: () => resetMatch(true), disabled: busy, title: "Alle laufenden/beendeten Spiele zur\u00FCcksetzen", children: "\u21BA Alle Spiele" }), _jsx("button", { className: `${styles.resetBtn} ${styles.resetBtnTips}`, onClick: resetTips, disabled: busy, children: "\uD83D\uDDD1 Tipps l\u00F6schen" })] })] }), log.length > 0 && (_jsx("div", { className: styles.log, children: log.map((l, i) => _jsx("div", { className: styles.logLine, children: l }, i)) }))] }))] }));
|
||
}
|