f1b4b63324
- Add --primary-rgb, --transition-fast, --transition-normal CSS tokens to :root - Add --primary-rgb override in [data-theme="light"] section - Fix TS error: remove unused devUser prop from Route elements in App.tsx (API patching via window._devUser makes props redundant) - Fix TS error: remove unused 'api' import from DevPanel.tsx Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
103 lines
7.3 KiB
JavaScript
103 lines
7.3 KiB
JavaScript
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
import { useState, useEffect } from 'react';
|
|
import { api } from '../api/client';
|
|
import StatsRing from '../components/StatsRing';
|
|
import styles from './ProfilePage.module.css';
|
|
function initials(name) {
|
|
return name.split(' ').map(n => n[0]).join('').toUpperCase().slice(0, 2);
|
|
}
|
|
function mostCommonTip(tips) {
|
|
const counts = {};
|
|
for (const t of tips) {
|
|
const key = `${t.tip_home}:${t.tip_away}`;
|
|
counts[key] = (counts[key] ?? 0) + 1;
|
|
}
|
|
let best = '';
|
|
let max = 0;
|
|
for (const [key, count] of Object.entries(counts)) {
|
|
if (count > max) {
|
|
max = count;
|
|
best = key;
|
|
}
|
|
}
|
|
return best ? `${best} (${max}x getippt)` : '—';
|
|
}
|
|
function homeWinPct(tips) {
|
|
if (!tips.length)
|
|
return 0;
|
|
const homeWins = tips.filter(t => t.tip_home > t.tip_away).length;
|
|
return Math.round((homeWins / tips.length) * 100);
|
|
}
|
|
export default function ProfilePage() {
|
|
const [stats, setStats] = useState(null);
|
|
const [tips, setTips] = useState([]);
|
|
const [loading, setLoading] = useState(true);
|
|
const [teamEdit, setTeamEdit] = useState(false);
|
|
const [teamValue, setTeamValue] = useState('');
|
|
const [teamSaving, setTeamSaving] = useState(false);
|
|
const [teamMsg, setTeamMsg] = useState(null);
|
|
useEffect(() => {
|
|
Promise.all([
|
|
api.getMyStats(),
|
|
api.getMyTips(),
|
|
]).then(([s, t]) => {
|
|
setStats(s);
|
|
setTeamValue(s.team ?? '');
|
|
setTips(t.tips);
|
|
}).finally(() => setLoading(false));
|
|
}, []);
|
|
const saveTeam = async () => {
|
|
if (!teamValue.trim())
|
|
return;
|
|
setTeamSaving(true);
|
|
setTeamMsg(null);
|
|
try {
|
|
const res = await api.updateTeam(teamValue);
|
|
setStats(prev => prev ? { ...prev, team: res.team } : prev);
|
|
setTeamValue(res.team);
|
|
setTeamEdit(false);
|
|
setTeamMsg({ ok: true, text: 'Team gespeichert' });
|
|
}
|
|
catch (e) {
|
|
setTeamMsg({ ok: false, text: e.message });
|
|
}
|
|
finally {
|
|
setTeamSaving(false);
|
|
}
|
|
};
|
|
if (loading)
|
|
return _jsx("div", { className: styles.loading, children: _jsx("div", { className: styles.spinner }) });
|
|
if (!stats)
|
|
return _jsx("div", { className: styles.empty, children: "Profil nicht verf\u00FCgbar." });
|
|
const evaluatedTips = tips.filter(t => t.points !== null);
|
|
const recentTips = evaluatedTips.slice(0, 10);
|
|
const favTip = mostCommonTip(tips);
|
|
const homePct = homeWinPct(tips);
|
|
function pointBadgeClass(points) {
|
|
if (points === null)
|
|
return '';
|
|
if (points >= 3)
|
|
return styles.badgeExact;
|
|
if (points >= 1)
|
|
return styles.badgeTendency;
|
|
return styles.badgeWrong;
|
|
}
|
|
function pointLabel(points) {
|
|
if (points === null)
|
|
return '';
|
|
if (points >= 3)
|
|
return `${points} Pkt ✓✓`;
|
|
if (points >= 1)
|
|
return `${points} Pkt ✓`;
|
|
return `${points} Pkt ✗`;
|
|
}
|
|
return (_jsxs("div", { className: styles.page, children: [_jsxs("div", { className: `card ${styles.heroCard}`, children: [_jsx("div", { className: styles.avatar, children: initials(stats.fullName) }), _jsxs("div", { className: styles.heroInfo, children: [_jsx("h1", { className: `font-display ${styles.name}`, children: stats.fullName }), stats.rank && _jsxs("div", { className: styles.rankBadge, children: ["\uD83C\uDFC6 Platz ", stats.rank] }), _jsx("div", { className: styles.teamRow, children: teamEdit ? (_jsxs("div", { className: styles.teamEditRow, children: [_jsx("input", { className: styles.teamInput, value: teamValue, onChange: e => setTeamValue(e.target.value), placeholder: "z. B. Vertrieb S\u00FCd", maxLength: 80, autoFocus: true, onKeyDown: e => {
|
|
if (e.key === 'Enter')
|
|
saveTeam();
|
|
if (e.key === 'Escape')
|
|
setTeamEdit(false);
|
|
} }), _jsx("button", { className: styles.teamSaveBtn, onClick: saveTeam, disabled: teamSaving, children: teamSaving ? _jsx("span", { className: styles.spinnerSm }) : '✓' }), _jsx("button", { className: styles.teamCancelBtn, onClick: () => { setTeamEdit(false); setTeamValue(stats.team ?? ''); }, children: "\u2715" })] })) : (_jsx("button", { className: styles.teamBtn, onClick: () => setTeamEdit(true), children: stats.team
|
|
? _jsxs(_Fragment, { children: [_jsx("span", { className: styles.teamName, children: stats.team }), _jsx("span", { className: styles.teamEditHint, children: "bearbeiten" })] })
|
|
: _jsx("span", { className: styles.teamPlaceholder, children: "+ Team hinzuf\u00FCgen" }) })) }), teamMsg && (_jsx("div", { className: `${styles.teamMsg} ${teamMsg.ok ? styles.teamMsgOk : styles.teamMsgErr}`, children: teamMsg.text }))] })] }), _jsxs("div", { className: `card ${styles.ringCard}`, children: [_jsx("h2", { className: styles.sectionTitle, children: "Tipp-Statistik" }), _jsx(StatsRing, { exact: stats.exactCount, tendency: stats.tendencyCount, wrong: stats.wrongCount, total: stats.totalPoints }), stats.accuracy > 0 && (_jsxs("div", { className: styles.accuracyRow, children: [_jsx("span", { className: styles.accuracyLabel, children: "Trefferquote" }), _jsxs("span", { className: `font-display ${styles.accuracyVal}`, children: [stats.accuracy, "%"] })] }))] }), recentTips.length > 0 && (_jsxs("div", { className: `card ${styles.historyCard}`, children: [_jsx("h2", { className: styles.sectionTitle, children: "Letzte Tipps" }), _jsx("ul", { className: styles.tipList, children: recentTips.map((tip, i) => (_jsxs("li", { className: `${styles.tipRow} ${i % 2 === 1 ? styles.tipRowAlt : ''}`, children: [_jsxs("span", { className: styles.tipMatch, children: [tip.home_team_short, " vs ", tip.away_team_short] }), _jsxs("span", { className: styles.tipScore, children: ["Tipp: ", tip.tip_home, ":", tip.tip_away] }), _jsx("span", { className: `${styles.pointBadge} ${pointBadgeClass(tip.points)}`, children: pointLabel(tip.points) })] }, tip.match_id))) })] })), tips.length > 0 && (_jsxs("div", { className: styles.funStats, children: [_jsx("h2", { className: styles.sectionTitle, children: "Fun Facts" }), _jsxs("div", { className: styles.funGrid, children: [_jsxs("div", { className: `card ${styles.funCard}`, children: [_jsx("span", { className: styles.funIcon, children: "\uD83C\uDFAF" }), _jsx("span", { className: styles.funLabel, children: "Lieblings-Tipp" }), _jsx("span", { className: styles.funValue, children: favTip })] }), _jsxs("div", { className: `card ${styles.funCard}`, children: [_jsx("span", { className: styles.funIcon, children: "\uD83C\uDFE0" }), _jsx("span", { className: styles.funLabel, children: "Heimsiege getippt" }), _jsxs("span", { className: styles.funValue, children: [homePct, "%"] })] }), _jsxs("div", { className: `card ${styles.funCard}`, children: [_jsx("span", { className: styles.funIcon, children: "\uD83D\uDCCA" }), _jsx("span", { className: styles.funLabel, children: "Tipps abgegeben" }), _jsx("span", { className: styles.funValue, children: stats.tipsCount })] })] })] }))] }));
|
|
}
|