some more styling to the frontend

This commit is contained in:
jenz
2026-08-24 21:52:29 +02:00
parent 28b4e957ea
commit 09107047b9
17 changed files with 275 additions and 147 deletions
@@ -120,35 +120,61 @@ export async function getMapPopulationSeries(limit = 40, before?: string): Promi
}
export interface CountrySummary {
current_country: string;
current_country: string | null; // null represents the "Unknown" bucket
player_count: number;
total_minutes: number;
avg_minutes: number;
}
export async function getCountriesSummary(): Promise<CountrySummary[]> {
// Players with no resolved country are excluded — there's no meaningful
// "unknown" country page to select. Each player's playtime is the same
// "latest session counter snapshot" logic used everywhere else, wrapped
// in a derived table so it can be aggregated per country.
return query<CountrySummary>(
`SELECT p.current_country, COUNT(*) AS player_count,
SUM(pt.total_minutes) AS total_minutes,
AVG(pt.total_minutes) AS avg_minutes
FROM playtime_display_players p
JOIN (
SELECT p2.steamid,
(SELECT COALESCE(s.session_end_playtime_minutes, s.session_start_playtime_minutes)
FROM playtime_display_sessions s
WHERE s.steamid = p2.steamid
ORDER BY s.session_id DESC
LIMIT 1) AS total_minutes
FROM playtime_display_players p2
) pt ON pt.steamid = p.steamid
WHERE p.current_country IS NOT NULL
GROUP BY p.current_country
ORDER BY player_count DESC, p.current_country ASC`,
// "Unknown" covers both NULL and '' — the actual sentinel written by the
// plugin when GeoipCode2 can't resolve a country (private/local IP, GeoIP
// miss, etc.) is an empty string, not SQL NULL, since the upsert always
// writes some value rather than leaving the column untouched. NULL is
// also handled in case any row ever ends up there some other way (e.g.
// the column's own DEFAULT).
const UNKNOWN = `(p.current_country IS NULL OR p.current_country = '')`;
const KNOWN = `(p.current_country IS NOT NULL AND p.current_country != '')`;
const rows = await query<CountrySummary>(
`SELECT * FROM (
SELECT p.current_country, COUNT(*) AS player_count,
SUM(pt.total_minutes) AS total_minutes,
AVG(pt.total_minutes) AS avg_minutes
FROM playtime_display_players p
JOIN (
SELECT p2.steamid,
(SELECT COALESCE(s.session_end_playtime_minutes, s.session_start_playtime_minutes)
FROM playtime_display_sessions s
WHERE s.steamid = p2.steamid
ORDER BY s.session_id DESC
LIMIT 1) AS total_minutes
FROM playtime_display_players p2
) pt ON pt.steamid = p.steamid
WHERE ${KNOWN}
GROUP BY p.current_country
UNION ALL
SELECT NULL AS current_country, COUNT(*) AS player_count,
SUM(pt.total_minutes) AS total_minutes,
AVG(pt.total_minutes) AS avg_minutes
FROM playtime_display_players p
JOIN (
SELECT p2.steamid,
(SELECT COALESCE(s.session_end_playtime_minutes, s.session_start_playtime_minutes)
FROM playtime_display_sessions s
WHERE s.steamid = p2.steamid
ORDER BY s.session_id DESC
LIMIT 1) AS total_minutes
FROM playtime_display_players p2
) pt ON pt.steamid = p.steamid
WHERE ${UNKNOWN}
) t
ORDER BY player_count DESC, current_country IS NULL, current_country ASC`,
);
// Skip the Unknown bucket entirely if it's empty, rather than showing a
// dead "0 players" row.
return rows.filter((r) => r.current_country !== null || r.player_count > 0);
}
export interface CountryPlayer {
@@ -159,7 +185,7 @@ export interface CountryPlayer {
}
export async function getPlayersByCountry(
countryCode: string,
countryCode: string | null,
sort: 'playtime' | 'recent',
): Promise<CountryPlayer[]> {
const orderBy = sort === 'playtime' ? 'total_minutes DESC' : 'p.last_seen DESC';
@@ -172,10 +198,10 @@ export async function getPlayersByCountry(
LIMIT 1
) AS total_minutes
FROM playtime_display_players p
WHERE p.current_country = ?
WHERE ${countryCode === null ? "(p.current_country IS NULL OR p.current_country = '')" : 'p.current_country = ?'}
ORDER BY ${orderBy}
LIMIT 200`,
[countryCode],
countryCode === null ? [] : [countryCode],
);
}
@@ -208,6 +234,7 @@ export interface MapVoteRow {
result: string | null;
steamid: string;
current_name: string;
current_country: string | null;
vote_choice: string;
vote_weight: number;
}
@@ -273,7 +300,8 @@ export async function getPlayersPresentDuringMap(mapId: number): Promise<MapPres
export async function getMapVotes(mapId: number): Promise<MapVoteRow[]> {
return query<MapVoteRow>(
`SELECT e.vote_id, e.vote_time, e.result,
c.steamid, COALESCE(p.current_name, c.steamid) AS current_name, c.vote_choice, c.vote_weight
c.steamid, COALESCE(p.current_name, c.steamid) AS current_name, p.current_country,
c.vote_choice, c.vote_weight
FROM playtime_display_map_vote_events e
JOIN playtime_display_map_vote_choices c ON c.vote_id = e.vote_id
LEFT JOIN playtime_display_players p ON p.steamid = c.steamid
@@ -312,6 +340,7 @@ export async function getAdjacentMaps(
export interface CurrentPlayer {
steamid: string;
current_name: string;
current_country: string | null;
}
/**
@@ -342,7 +371,8 @@ export async function getCurrentPlayers(freshnessMinutes = 3): Promise<CurrentPl
// matching players row wasn't found, undercounting "currently online"
// for reasons unrelated to whether the player is actually connected.
return query<CurrentPlayer>(
`SELECT s.steamid, COALESCE(MAX(p.current_name), MAX(s.player_name)) AS current_name
`SELECT s.steamid, COALESCE(MAX(p.current_name), MAX(s.player_name)) AS current_name,
MAX(p.current_country) AS current_country
FROM playtime_display_sessions s
LEFT JOIN playtime_display_players p ON p.steamid = s.steamid
WHERE s.session_end_dt > NOW() - INTERVAL ? MINUTE
@@ -356,11 +386,13 @@ export async function getCurrentPlayers(freshnessMinutes = 3): Promise<CurrentPl
export interface PlayerAtTime {
steamid: string;
current_name: string;
current_country: string | null;
}
export async function getPlayersAtTime(atIso: string): Promise<PlayerAtTime[]> {
return query<PlayerAtTime>(
`SELECT s.steamid, COALESCE(MAX(p.current_name), MAX(s.player_name)) AS current_name
`SELECT s.steamid, COALESCE(MAX(p.current_name), MAX(s.player_name)) AS current_name,
MAX(p.current_country) AS current_country
FROM playtime_display_sessions s
LEFT JOIN playtime_display_players p ON p.steamid = s.steamid
WHERE s.session_start_dt <= ?