From e15677a3f54c1c685cf0a9ccd2d78731fb940196 Mon Sep 17 00:00:00 2001 From: MrEgosar Date: Thu, 29 Oct 2026 09:29:07 +0100 Subject: [PATCH] =?UTF-8?q?=C3=9Cbersicht:=20laufende=20Matches=20als=20Ka?= =?UTF-8?q?rten=20mit=20Map,=20Stand=20und=20Serie?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/pages/Overview.jsx | 35 ++++++++++++++++++++++++++++++++++- client/src/styles.css | 20 ++++++++++++++++++++ server/src/matchService.js | 9 ++++++++- 3 files changed, 62 insertions(+), 2 deletions(-) diff --git a/client/src/pages/Overview.jsx b/client/src/pages/Overview.jsx index ac6ff62..c1abd8b 100644 --- a/client/src/pages/Overview.jsx +++ b/client/src/pages/Overview.jsx @@ -4,6 +4,7 @@ import { api } from '../lib/api.js'; import { FORMAT_LABEL, serverLabel, timeAgo } from '../lib/labels.js'; import { getAccess } from '../lib/session.js'; import { useOverview } from '../lib/useMatch.js'; +import MapImage from '../components/MapImage.jsx'; const GROUPS = [ { key: 'open', title: 'Offen', empty: 'Keine offenen Lobbys. Erstelle ein Match, um zu starten.' }, @@ -32,7 +33,11 @@ export default function Overview() { return (

{g.title} {list.length}

- {list.length === 0 ?

{g.empty}

: ( + {list.length === 0 ?

{g.empty}

: g.key === 'running' ? ( +
+ {list.map((m) => )} +
+ ) : ( @@ -79,6 +84,34 @@ function MatchRow({ match: m }) { ); } +/** Karte für ein laufendes Match: Map als Hintergrund, Stand groß, Serie und Status darunter. */ +function RunningCard({ match: m }) { + const server = serverLabel(m.server); + const map = m.currentMap; + const live = map && !map.finished; + const bo1 = m.format === 'bo1'; + return ( + + {map?.image && } +
+ {m.modeLabel}, {FORMAT_LABEL[m.format]} + {live ? Live : {m.statusLabel}} +
+
+ {m.teamA} + + {map ? <>{map.score.A}:{map.score.B} : vs.} + + {m.teamB} +
+
+ {map ? `Map ${map.number}: ${map.name}` : 'Veto läuft'} + {!bo1 && m.seriesScore && Serie {m.seriesScore.A}:{m.seriesScore.B}} +
+ + ); +} + function CodeEntry() { const [code, setCode] = useState(''); const [error, setError] = useState(null); diff --git a/client/src/styles.css b/client/src/styles.css index 4ecdb61..eb73b81 100644 --- a/client/src/styles.css +++ b/client/src/styles.css @@ -534,3 +534,23 @@ input:focus, select:focus { border-color: var(--link); outline: none; } .admin-badge { display: inline-block; margin-top: 6px; padding: 2px 10px; border-radius: 999px; font-size: .8rem; font-weight: 600; } .admin-badge.is-paused { background: color-mix(in srgb, var(--pending) 22%, transparent); color: var(--pending); border: 1px solid var(--pending); } .btn-warn { border-color: var(--pending); color: var(--pending); } +/* Laufende Matches in der Übersicht */ +.run-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); gap: 16px; } +.run-card { position: relative; display: flex; flex-direction: column; justify-content: space-between; gap: 10px; min-height: 190px; padding: 16px 18px; border-radius: var(--radius); border: 1px solid var(--line); background: var(--surface); color: var(--text); text-decoration: none; overflow: hidden; isolation: isolate; transition: transform .15s ease, border-color .15s ease; } +.run-card:hover { transform: translateY(-2px); border-color: var(--map); } +.run-card.is-live { box-shadow: inset 3px 0 0 var(--brand-red); } +.run-art { position: absolute; inset: 0; z-index: -1; opacity: .28; } +.run-art::after { background: linear-gradient(180deg, rgba(0,0,0,.2), rgba(0,0,0,.92)); } +.run-top { display: flex; justify-content: space-between; align-items: center; gap: 8px; font-size: .8rem; color: var(--muted); } +.run-live { animation: run-pulse 1.6s ease-in-out infinite; } +@keyframes run-pulse { 50% { opacity: .55; } } +.run-status { font-weight: 600; } +.run-teams { display: grid; grid-template-columns: 1fr auto 1fr; align-items: center; gap: 8px; text-align: center; } +.run-team { font: 700 1.25rem var(--display); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } +.run-team.a { color: var(--team-a); text-align: right; } +.run-team.b { color: var(--team-b); text-align: left; } +.run-score { font: 700 2.25rem var(--display); font-variant-numeric: tabular-nums; } +.run-score i { font-style: normal; color: var(--muted); padding: 0 4px; } +.run-vs { font-size: 1rem; color: var(--muted); font-style: normal; } +.run-foot { display: flex; justify-content: space-between; gap: 8px; font-size: .875rem; color: var(--muted); } +.run-series { font-variant-numeric: tabular-nums; } diff --git a/server/src/matchService.js b/server/src/matchService.js index b767756..171531f 100644 --- a/server/src/matchService.js +++ b/server/src/matchService.js @@ -791,7 +791,14 @@ export class MatchService extends EventEmitter { return { seriesScore: maps.length ? seriesScore(maps) : null, currentMap: current && (current.status === 'live' || current.status === 'done') - ? { number: current.number, name: this.mapName(m, current.map), score: { A: current.score_a, B: current.score_b }, finished: current.status === 'done' } + ? { + number: current.number, + name: this.mapName(m, current.map), + image: JSON.parse(m.maps).find((x) => x.id === current.map)?.image ?? null, + color: JSON.parse(m.maps).find((x) => x.id === current.map)?.color ?? null, + score: { A: current.score_a, B: current.score_b }, + finished: current.status === 'done', + } : null, id: m.id, teamA: m.team_a_name,