added stat leaderboards
This commit is contained in:
@@ -437,6 +437,222 @@ func GetAggregatedPlayerStatsForTeam(
|
||||
return stats, nil
|
||||
}
|
||||
|
||||
// LeagueTopGoalScorer holds aggregated goal scoring stats for a player in a season-league.
|
||||
type LeagueTopGoalScorer struct {
|
||||
PlayerID int `bun:"player_id"`
|
||||
PlayerName string `bun:"player_name"`
|
||||
TeamID int `bun:"team_id"`
|
||||
TeamName string `bun:"team_name"`
|
||||
TeamColor string `bun:"team_color"`
|
||||
Goals int `bun:"total_goals"`
|
||||
PeriodsPlayed int `bun:"total_periods_played"`
|
||||
Shots int `bun:"total_shots"`
|
||||
}
|
||||
|
||||
// GetTopGoalScorers returns the top goal scorers for a season-league,
|
||||
// sorted by goals DESC, periods ASC, shots ASC.
|
||||
// Stats are combined across all teams a player may have played on,
|
||||
// and the player's current roster team is shown.
|
||||
func GetTopGoalScorers(
|
||||
ctx context.Context,
|
||||
tx bun.Tx,
|
||||
seasonID, leagueID int,
|
||||
) ([]*LeagueTopGoalScorer, error) {
|
||||
if seasonID == 0 {
|
||||
return nil, errors.New("seasonID not provided")
|
||||
}
|
||||
if leagueID == 0 {
|
||||
return nil, errors.New("leagueID not provided")
|
||||
}
|
||||
|
||||
var stats []*LeagueTopGoalScorer
|
||||
err := tx.NewRaw(`
|
||||
SELECT
|
||||
agg.player_id,
|
||||
agg.player_name,
|
||||
COALESCE(tr.team_id, 0) AS team_id,
|
||||
COALESCE(t.name, '') AS team_name,
|
||||
COALESCE(t.color, '') AS team_color,
|
||||
agg.total_goals,
|
||||
agg.total_periods_played,
|
||||
agg.total_shots
|
||||
FROM (
|
||||
SELECT
|
||||
frps.player_id AS player_id,
|
||||
COALESCE(p.name, frps.player_username) AS player_name,
|
||||
COALESCE(SUM(frps.goals), 0) AS total_goals,
|
||||
COALESCE(SUM(frps.periods_played), 0) AS total_periods_played,
|
||||
COALESCE(SUM(frps.shots), 0) AS total_shots
|
||||
FROM fixture_result_player_stats frps
|
||||
JOIN fixture_results fr ON fr.id = frps.fixture_result_id
|
||||
JOIN fixtures f ON f.id = fr.fixture_id
|
||||
LEFT JOIN players p ON p.id = frps.player_id
|
||||
WHERE fr.finalized = true
|
||||
AND f.season_id = ?
|
||||
AND f.league_id = ?
|
||||
AND frps.period_num = 3
|
||||
AND frps.player_id IS NOT NULL
|
||||
GROUP BY frps.player_id, COALESCE(p.name, frps.player_username)
|
||||
ORDER BY total_goals DESC, total_periods_played ASC, total_shots ASC
|
||||
LIMIT 10
|
||||
) agg
|
||||
LEFT JOIN team_rosters tr
|
||||
ON tr.player_id = agg.player_id
|
||||
AND tr.season_id = ?
|
||||
AND tr.league_id = ?
|
||||
LEFT JOIN teams t ON t.id = tr.team_id
|
||||
ORDER BY agg.total_goals DESC, agg.total_periods_played ASC, agg.total_shots ASC
|
||||
`, seasonID, leagueID, seasonID, leagueID).Scan(ctx, &stats)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "tx.NewRaw")
|
||||
}
|
||||
return stats, nil
|
||||
}
|
||||
|
||||
// LeagueTopAssister holds aggregated assist stats for a player in a season-league.
|
||||
type LeagueTopAssister struct {
|
||||
PlayerID int `bun:"player_id"`
|
||||
PlayerName string `bun:"player_name"`
|
||||
TeamID int `bun:"team_id"`
|
||||
TeamName string `bun:"team_name"`
|
||||
TeamColor string `bun:"team_color"`
|
||||
Assists int `bun:"total_assists"`
|
||||
PeriodsPlayed int `bun:"total_periods_played"`
|
||||
PrimaryAssists int `bun:"total_primary_assists"`
|
||||
}
|
||||
|
||||
// GetTopAssisters returns the top assisters for a season-league,
|
||||
// sorted by assists DESC, periods ASC, primary assists DESC.
|
||||
// Stats are combined across all teams a player may have played on,
|
||||
// and the player's current roster team is shown.
|
||||
func GetTopAssisters(
|
||||
ctx context.Context,
|
||||
tx bun.Tx,
|
||||
seasonID, leagueID int,
|
||||
) ([]*LeagueTopAssister, error) {
|
||||
if seasonID == 0 {
|
||||
return nil, errors.New("seasonID not provided")
|
||||
}
|
||||
if leagueID == 0 {
|
||||
return nil, errors.New("leagueID not provided")
|
||||
}
|
||||
|
||||
var stats []*LeagueTopAssister
|
||||
err := tx.NewRaw(`
|
||||
SELECT
|
||||
agg.player_id,
|
||||
agg.player_name,
|
||||
COALESCE(tr.team_id, 0) AS team_id,
|
||||
COALESCE(t.name, '') AS team_name,
|
||||
COALESCE(t.color, '') AS team_color,
|
||||
agg.total_assists,
|
||||
agg.total_periods_played,
|
||||
agg.total_primary_assists
|
||||
FROM (
|
||||
SELECT
|
||||
frps.player_id AS player_id,
|
||||
COALESCE(p.name, frps.player_username) AS player_name,
|
||||
COALESCE(SUM(frps.assists), 0) AS total_assists,
|
||||
COALESCE(SUM(frps.periods_played), 0) AS total_periods_played,
|
||||
COALESCE(SUM(frps.primary_assists), 0) AS total_primary_assists
|
||||
FROM fixture_result_player_stats frps
|
||||
JOIN fixture_results fr ON fr.id = frps.fixture_result_id
|
||||
JOIN fixtures f ON f.id = fr.fixture_id
|
||||
LEFT JOIN players p ON p.id = frps.player_id
|
||||
WHERE fr.finalized = true
|
||||
AND f.season_id = ?
|
||||
AND f.league_id = ?
|
||||
AND frps.period_num = 3
|
||||
AND frps.player_id IS NOT NULL
|
||||
GROUP BY frps.player_id, COALESCE(p.name, frps.player_username)
|
||||
ORDER BY total_assists DESC, total_periods_played ASC, total_primary_assists DESC
|
||||
LIMIT 10
|
||||
) agg
|
||||
LEFT JOIN team_rosters tr
|
||||
ON tr.player_id = agg.player_id
|
||||
AND tr.season_id = ?
|
||||
AND tr.league_id = ?
|
||||
LEFT JOIN teams t ON t.id = tr.team_id
|
||||
ORDER BY agg.total_assists DESC, agg.total_periods_played ASC, agg.total_primary_assists DESC
|
||||
`, seasonID, leagueID, seasonID, leagueID).Scan(ctx, &stats)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "tx.NewRaw")
|
||||
}
|
||||
return stats, nil
|
||||
}
|
||||
|
||||
// LeagueTopSaver holds aggregated save stats for a player in a season-league.
|
||||
type LeagueTopSaver struct {
|
||||
PlayerID int `bun:"player_id"`
|
||||
PlayerName string `bun:"player_name"`
|
||||
TeamID int `bun:"team_id"`
|
||||
TeamName string `bun:"team_name"`
|
||||
TeamColor string `bun:"team_color"`
|
||||
Saves int `bun:"total_saves"`
|
||||
PeriodsPlayed int `bun:"total_periods_played"`
|
||||
Blocks int `bun:"total_blocks"`
|
||||
}
|
||||
|
||||
// GetTopSavers returns the top savers for a season-league,
|
||||
// sorted by saves DESC, periods ASC, blocks DESC.
|
||||
// Stats are combined across all teams a player may have played on,
|
||||
// and the player's current roster team is shown.
|
||||
func GetTopSavers(
|
||||
ctx context.Context,
|
||||
tx bun.Tx,
|
||||
seasonID, leagueID int,
|
||||
) ([]*LeagueTopSaver, error) {
|
||||
if seasonID == 0 {
|
||||
return nil, errors.New("seasonID not provided")
|
||||
}
|
||||
if leagueID == 0 {
|
||||
return nil, errors.New("leagueID not provided")
|
||||
}
|
||||
|
||||
var stats []*LeagueTopSaver
|
||||
err := tx.NewRaw(`
|
||||
SELECT
|
||||
agg.player_id,
|
||||
agg.player_name,
|
||||
COALESCE(tr.team_id, 0) AS team_id,
|
||||
COALESCE(t.name, '') AS team_name,
|
||||
COALESCE(t.color, '') AS team_color,
|
||||
agg.total_saves,
|
||||
agg.total_periods_played,
|
||||
agg.total_blocks
|
||||
FROM (
|
||||
SELECT
|
||||
frps.player_id AS player_id,
|
||||
COALESCE(p.name, frps.player_username) AS player_name,
|
||||
COALESCE(SUM(frps.saves), 0) AS total_saves,
|
||||
COALESCE(SUM(frps.periods_played), 0) AS total_periods_played,
|
||||
COALESCE(SUM(frps.blocks), 0) AS total_blocks
|
||||
FROM fixture_result_player_stats frps
|
||||
JOIN fixture_results fr ON fr.id = frps.fixture_result_id
|
||||
JOIN fixtures f ON f.id = fr.fixture_id
|
||||
LEFT JOIN players p ON p.id = frps.player_id
|
||||
WHERE fr.finalized = true
|
||||
AND f.season_id = ?
|
||||
AND f.league_id = ?
|
||||
AND frps.period_num = 3
|
||||
AND frps.player_id IS NOT NULL
|
||||
GROUP BY frps.player_id, COALESCE(p.name, frps.player_username)
|
||||
ORDER BY total_saves DESC, total_periods_played ASC, total_blocks DESC
|
||||
LIMIT 10
|
||||
) agg
|
||||
LEFT JOIN team_rosters tr
|
||||
ON tr.player_id = agg.player_id
|
||||
AND tr.season_id = ?
|
||||
AND tr.league_id = ?
|
||||
LEFT JOIN teams t ON t.id = tr.team_id
|
||||
ORDER BY agg.total_saves DESC, agg.total_periods_played ASC, agg.total_blocks DESC
|
||||
`, seasonID, leagueID, seasonID, leagueID).Scan(ctx, &stats)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "tx.NewRaw")
|
||||
}
|
||||
return stats, nil
|
||||
}
|
||||
|
||||
// TeamRecord holds win/loss/draw record and goal totals for a team.
|
||||
type TeamRecord struct {
|
||||
Played int
|
||||
|
||||
@@ -2189,11 +2189,21 @@
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
}
|
||||
}
|
||||
.lg\:flex-row {
|
||||
@media (width >= 64rem) {
|
||||
flex-direction: row;
|
||||
}
|
||||
}
|
||||
.lg\:items-end {
|
||||
@media (width >= 64rem) {
|
||||
align-items: flex-end;
|
||||
}
|
||||
}
|
||||
.lg\:items-start {
|
||||
@media (width >= 64rem) {
|
||||
align-items: flex-start;
|
||||
}
|
||||
}
|
||||
.lg\:justify-between {
|
||||
@media (width >= 64rem) {
|
||||
justify-content: space-between;
|
||||
|
||||
@@ -22,6 +22,9 @@ func SeasonLeagueStatsPage(
|
||||
leagueStr := r.PathValue("league_short_name")
|
||||
|
||||
var sl *db.SeasonLeague
|
||||
var topGoals []*db.LeagueTopGoalScorer
|
||||
var topAssists []*db.LeagueTopAssister
|
||||
var topSaves []*db.LeagueTopSaver
|
||||
|
||||
if ok := conn.WithReadTx(s, w, r, func(ctx context.Context, tx bun.Tx) (bool, error) {
|
||||
var err error
|
||||
@@ -33,15 +36,31 @@ func SeasonLeagueStatsPage(
|
||||
}
|
||||
return false, errors.Wrap(err, "db.GetSeasonLeague")
|
||||
}
|
||||
|
||||
topGoals, err = db.GetTopGoalScorers(ctx, tx, sl.SeasonID, sl.LeagueID)
|
||||
if err != nil {
|
||||
return false, errors.Wrap(err, "db.GetTopGoalScorers")
|
||||
}
|
||||
|
||||
topAssists, err = db.GetTopAssisters(ctx, tx, sl.SeasonID, sl.LeagueID)
|
||||
if err != nil {
|
||||
return false, errors.Wrap(err, "db.GetTopAssisters")
|
||||
}
|
||||
|
||||
topSaves, err = db.GetTopSavers(ctx, tx, sl.SeasonID, sl.LeagueID)
|
||||
if err != nil {
|
||||
return false, errors.Wrap(err, "db.GetTopSavers")
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}); !ok {
|
||||
return
|
||||
}
|
||||
|
||||
if r.Method == "GET" {
|
||||
renderSafely(seasonsview.SeasonLeagueStatsPage(sl.Season, sl.League), s, r, w)
|
||||
renderSafely(seasonsview.SeasonLeagueStatsPage(sl.Season, sl.League, topGoals, topAssists, topSaves), s, r, w)
|
||||
} else {
|
||||
renderSafely(seasonsview.SeasonLeagueStats(), s, r, w)
|
||||
renderSafely(seasonsview.SeasonLeagueStats(sl.Season, sl.League, topGoals, topAssists, topSaves), s, r, w)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,15 +1,227 @@
|
||||
package seasonsview
|
||||
|
||||
import "git.haelnorr.com/h/oslstats/internal/db"
|
||||
import "git.haelnorr.com/h/oslstats/internal/view/component/links"
|
||||
import "fmt"
|
||||
|
||||
templ SeasonLeagueStatsPage(season *db.Season, league *db.League) {
|
||||
templ SeasonLeagueStatsPage(
|
||||
season *db.Season,
|
||||
league *db.League,
|
||||
topGoals []*db.LeagueTopGoalScorer,
|
||||
topAssists []*db.LeagueTopAssister,
|
||||
topSaves []*db.LeagueTopSaver,
|
||||
) {
|
||||
@SeasonLeagueLayout("stats", season, league) {
|
||||
@SeasonLeagueStats()
|
||||
@SeasonLeagueStats(season, league, topGoals, topAssists, topSaves)
|
||||
}
|
||||
}
|
||||
|
||||
templ SeasonLeagueStats() {
|
||||
<div class="bg-surface0 border border-surface1 rounded-lg p-8 text-center">
|
||||
<p class="text-subtext0 text-lg">Coming Soon...</p>
|
||||
templ SeasonLeagueStats(
|
||||
season *db.Season,
|
||||
league *db.League,
|
||||
topGoals []*db.LeagueTopGoalScorer,
|
||||
topAssists []*db.LeagueTopAssister,
|
||||
topSaves []*db.LeagueTopSaver,
|
||||
) {
|
||||
if len(topGoals) == 0 && len(topAssists) == 0 && len(topSaves) == 0 {
|
||||
<div class="bg-surface0 border border-surface1 rounded-lg p-8 text-center">
|
||||
<p class="text-subtext0 text-lg">No stats available yet.</p>
|
||||
<p class="text-subtext1 text-sm mt-2">Player statistics will appear here once games are finalized.</p>
|
||||
</div>
|
||||
} else {
|
||||
<!-- Triangle layout: two side-by-side on wide screens, saves centered below -->
|
||||
<div class="flex flex-col items-center gap-6">
|
||||
<!-- Top row: Goals and Assists side by side when room allows -->
|
||||
<div class="flex flex-col lg:flex-row gap-6 w-full justify-center items-center lg:items-start">
|
||||
@topGoalScorersTable(season, league, topGoals)
|
||||
@topAssistersTable(season, league, topAssists)
|
||||
</div>
|
||||
<!-- Bottom row: Saves centered -->
|
||||
@topSaversTable(season, league, topSaves)
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
templ topGoalScorersTable(season *db.Season, league *db.League, goals []*db.LeagueTopGoalScorer) {
|
||||
<div class="bg-surface0 border border-surface1 rounded-lg overflow-hidden w-full max-w-lg">
|
||||
<div class="bg-mantle border-b border-surface1 px-4 py-3">
|
||||
<h3 class="text-sm font-semibold text-text">
|
||||
Top Goal Scorers
|
||||
</h3>
|
||||
</div>
|
||||
<!-- Sorting key -->
|
||||
<div class="bg-mantle border-b border-surface1 px-4 py-1.5 flex items-center gap-3 text-xs text-subtext0">
|
||||
<span class="font-semibold text-subtext1">Sort:</span>
|
||||
<span>G ↓</span>
|
||||
<span>PP ↑</span>
|
||||
<span>SH ↑</span>
|
||||
</div>
|
||||
if len(goals) == 0 {
|
||||
<div class="p-6 text-center">
|
||||
<p class="text-subtext0 text-sm">No goal data available yet.</p>
|
||||
</div>
|
||||
} else {
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full">
|
||||
<thead class="bg-mantle border-b border-surface1">
|
||||
<tr>
|
||||
<th class="px-3 py-2 text-center text-xs font-semibold text-subtext0 w-10">#</th>
|
||||
<th class="px-3 py-2 text-left text-xs font-semibold text-text">Player</th>
|
||||
<th class="px-3 py-2 text-left text-xs font-semibold text-text">Team</th>
|
||||
<th class="px-2 py-2 text-center text-xs font-semibold text-blue" title="Goals">G</th>
|
||||
<th class="px-2 py-2 text-center text-xs font-semibold text-text" title="Periods Played">PP</th>
|
||||
<th class="px-2 py-2 text-center text-xs font-semibold text-text" title="Shots">SH</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-surface1">
|
||||
for i, gs := range goals {
|
||||
<tr class="hover:bg-surface1 transition-colors">
|
||||
<td class="px-3 py-2 text-center text-sm font-medium text-subtext0">
|
||||
{ fmt.Sprint(i + 1) }
|
||||
</td>
|
||||
<td class="px-3 py-2 text-sm font-medium">
|
||||
@links.PlayerLinkFromStats(gs.PlayerID, gs.PlayerName)
|
||||
</td>
|
||||
<td class="px-3 py-2 text-sm">
|
||||
@teamColorName(gs.TeamID, gs.TeamName, gs.TeamColor, season, league)
|
||||
</td>
|
||||
<td class="px-2 py-2 text-center text-sm font-bold text-blue">{ fmt.Sprint(gs.Goals) }</td>
|
||||
<td class="px-2 py-2 text-center text-sm text-subtext0">{ fmt.Sprint(gs.PeriodsPlayed) }</td>
|
||||
<td class="px-2 py-2 text-center text-sm text-subtext0">{ fmt.Sprint(gs.Shots) }</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
templ topAssistersTable(season *db.Season, league *db.League, assists []*db.LeagueTopAssister) {
|
||||
<div class="bg-surface0 border border-surface1 rounded-lg overflow-hidden w-full max-w-lg">
|
||||
<div class="bg-mantle border-b border-surface1 px-4 py-3">
|
||||
<h3 class="text-sm font-semibold text-text">
|
||||
Top Assisters
|
||||
</h3>
|
||||
</div>
|
||||
<!-- Sorting key -->
|
||||
<div class="bg-mantle border-b border-surface1 px-4 py-1.5 flex items-center gap-3 text-xs text-subtext0">
|
||||
<span class="font-semibold text-subtext1">Sort:</span>
|
||||
<span>A ↓</span>
|
||||
<span>PP ↑</span>
|
||||
<span>PA ↓</span>
|
||||
</div>
|
||||
if len(assists) == 0 {
|
||||
<div class="p-6 text-center">
|
||||
<p class="text-subtext0 text-sm">No assist data available yet.</p>
|
||||
</div>
|
||||
} else {
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full">
|
||||
<thead class="bg-mantle border-b border-surface1">
|
||||
<tr>
|
||||
<th class="px-3 py-2 text-center text-xs font-semibold text-subtext0 w-10">#</th>
|
||||
<th class="px-3 py-2 text-left text-xs font-semibold text-text">Player</th>
|
||||
<th class="px-3 py-2 text-left text-xs font-semibold text-text">Team</th>
|
||||
<th class="px-2 py-2 text-center text-xs font-semibold text-blue" title="Assists">A</th>
|
||||
<th class="px-2 py-2 text-center text-xs font-semibold text-text" title="Periods Played">PP</th>
|
||||
<th class="px-2 py-2 text-center text-xs font-semibold text-text" title="Primary Assists">PA</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-surface1">
|
||||
for i, as := range assists {
|
||||
<tr class="hover:bg-surface1 transition-colors">
|
||||
<td class="px-3 py-2 text-center text-sm font-medium text-subtext0">
|
||||
{ fmt.Sprint(i + 1) }
|
||||
</td>
|
||||
<td class="px-3 py-2 text-sm font-medium">
|
||||
@links.PlayerLinkFromStats(as.PlayerID, as.PlayerName)
|
||||
</td>
|
||||
<td class="px-3 py-2 text-sm">
|
||||
@teamColorName(as.TeamID, as.TeamName, as.TeamColor, season, league)
|
||||
</td>
|
||||
<td class="px-2 py-2 text-center text-sm font-bold text-blue">{ fmt.Sprint(as.Assists) }</td>
|
||||
<td class="px-2 py-2 text-center text-sm text-subtext0">{ fmt.Sprint(as.PeriodsPlayed) }</td>
|
||||
<td class="px-2 py-2 text-center text-sm text-subtext0">{ fmt.Sprint(as.PrimaryAssists) }</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
templ topSaversTable(season *db.Season, league *db.League, saves []*db.LeagueTopSaver) {
|
||||
<div class="bg-surface0 border border-surface1 rounded-lg overflow-hidden w-full max-w-lg">
|
||||
<div class="bg-mantle border-b border-surface1 px-4 py-3">
|
||||
<h3 class="text-sm font-semibold text-text">
|
||||
Top Saves
|
||||
</h3>
|
||||
</div>
|
||||
<!-- Sorting key -->
|
||||
<div class="bg-mantle border-b border-surface1 px-4 py-1.5 flex items-center gap-3 text-xs text-subtext0">
|
||||
<span class="font-semibold text-subtext1">Sort:</span>
|
||||
<span>SV ↓</span>
|
||||
<span>PP ↑</span>
|
||||
<span>BLK ↓</span>
|
||||
</div>
|
||||
if len(saves) == 0 {
|
||||
<div class="p-6 text-center">
|
||||
<p class="text-subtext0 text-sm">No save data available yet.</p>
|
||||
</div>
|
||||
} else {
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full">
|
||||
<thead class="bg-mantle border-b border-surface1">
|
||||
<tr>
|
||||
<th class="px-3 py-2 text-center text-xs font-semibold text-subtext0 w-10">#</th>
|
||||
<th class="px-3 py-2 text-left text-xs font-semibold text-text">Player</th>
|
||||
<th class="px-3 py-2 text-left text-xs font-semibold text-text">Team</th>
|
||||
<th class="px-2 py-2 text-center text-xs font-semibold text-blue" title="Saves">SV</th>
|
||||
<th class="px-2 py-2 text-center text-xs font-semibold text-text" title="Periods Played">PP</th>
|
||||
<th class="px-2 py-2 text-center text-xs font-semibold text-text" title="Blocks">BLK</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-surface1">
|
||||
for i, sv := range saves {
|
||||
<tr class="hover:bg-surface1 transition-colors">
|
||||
<td class="px-3 py-2 text-center text-sm font-medium text-subtext0">
|
||||
{ fmt.Sprint(i + 1) }
|
||||
</td>
|
||||
<td class="px-3 py-2 text-sm font-medium">
|
||||
@links.PlayerLinkFromStats(sv.PlayerID, sv.PlayerName)
|
||||
</td>
|
||||
<td class="px-3 py-2 text-sm">
|
||||
@teamColorName(sv.TeamID, sv.TeamName, sv.TeamColor, season, league)
|
||||
</td>
|
||||
<td class="px-2 py-2 text-center text-sm font-bold text-blue">{ fmt.Sprint(sv.Saves) }</td>
|
||||
<td class="px-2 py-2 text-center text-sm text-subtext0">{ fmt.Sprint(sv.PeriodsPlayed) }</td>
|
||||
<td class="px-2 py-2 text-center text-sm text-subtext0">{ fmt.Sprint(sv.Blocks) }</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
templ teamColorName(teamID int, teamName string, teamColor string, season *db.Season, league *db.League) {
|
||||
if teamID > 0 && teamName != "" {
|
||||
<a
|
||||
href={ templ.SafeURL(fmt.Sprintf("/seasons/%s/leagues/%s/teams/%d", season.ShortName, league.ShortName, teamID)) }
|
||||
class="flex items-center gap-2 hover:text-blue transition"
|
||||
>
|
||||
if teamColor != "" {
|
||||
<span
|
||||
class="w-3 h-3 rounded-full shrink-0"
|
||||
style={ "background-color: " + templ.SafeCSS(teamColor) }
|
||||
></span>
|
||||
}
|
||||
<span class="text-sm font-medium">{ teamName }</span>
|
||||
</a>
|
||||
} else {
|
||||
<span class="text-sm text-subtext0 italic">—</span>
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user