added better links to teams and players

This commit is contained in:
2026-03-06 21:03:51 +11:00
parent e99f10d0f4
commit 9884793bca
8 changed files with 117 additions and 43 deletions

View File

@@ -62,7 +62,7 @@ func SeasonLeagueTablePage(
if r.Method == "GET" {
renderSafely(seasonsview.SeasonLeagueTablePage(season, league, leaderboard), s, r, w)
} else {
renderSafely(seasonsview.SeasonLeagueTable(leaderboard), s, r, w)
renderSafely(seasonsview.SeasonLeagueTable(season, league, leaderboard), s, r, w)
}
})
}

View File

@@ -0,0 +1,54 @@
package links
import "git.haelnorr.com/h/oslstats/internal/db"
import "fmt"
// PlayerLink renders a player name as a clickable link to their profile page.
// The player's DisplayName() is used as the link text.
templ PlayerLink(player *db.Player) {
<a
href={ templ.SafeURL(fmt.Sprintf("/players/%d", player.ID)) }
class="text-text hover:text-blue transition"
>
{ player.DisplayName() }
</a>
}
// PlayerLinkFromStats renders a player name link using a player ID and name string.
// This is useful when only aggregated stats are available (no full Player object).
templ PlayerLinkFromStats(playerID int, playerName string) {
<a
href={ templ.SafeURL(fmt.Sprintf("/players/%d", playerID)) }
class="text-text hover:text-blue transition"
>
{ playerName }
</a>
}
// TeamLinkInSeason renders a team name as a clickable link to the team's
// season-specific detail page, with an optional color dot prefix.
templ TeamLinkInSeason(team *db.Team, season *db.Season, league *db.League) {
<a
href={ templ.SafeURL(fmt.Sprintf("/seasons/%s/leagues/%s/teams/%d", season.ShortName, league.ShortName, team.ID)) }
class="flex items-center gap-2 hover:text-blue transition"
>
if team.Color != "" {
<span
class="w-3 h-3 rounded-full shrink-0"
style={ "background-color: " + templ.SafeCSS(team.Color) }
></span>
}
<span class="text-sm font-medium">{ team.Name }</span>
</a>
}
// TeamNameLinkInSeason renders just the team name as a clickable link (no color dot).
// Useful where the color dot is already rendered separately or in inline contexts.
templ TeamNameLinkInSeason(team *db.Team, season *db.Season, league *db.League) {
<a
href={ templ.SafeURL(fmt.Sprintf("/seasons/%s/leagues/%s/teams/%d", season.ShortName, league.ShortName, team.ID)) }
class="hover:text-blue transition"
>
{ team.Name }
</a>
}

View File

@@ -4,6 +4,7 @@ import "git.haelnorr.com/h/oslstats/internal/db"
import "git.haelnorr.com/h/oslstats/internal/permissions"
import "git.haelnorr.com/h/oslstats/internal/contexts"
import "git.haelnorr.com/h/oslstats/internal/view/baseview"
import "git.haelnorr.com/h/oslstats/internal/view/component/links"
import "fmt"
import "sort"
import "strings"
@@ -147,8 +148,8 @@ templ fixtureOverviewTab(
}
<!-- Team Rosters -->
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
@fixtureTeamSection(fixture.HomeTeam, rosters["home"], "home", result)
@fixtureTeamSection(fixture.AwayTeam, rosters["away"], "away", result)
@fixtureTeamSection(fixture.HomeTeam, rosters["home"], "home", result, fixture.Season, fixture.League)
@fixtureTeamSection(fixture.AwayTeam, rosters["away"], "away", result, fixture.Season, fixture.League)
</div>
</div>
}
@@ -603,7 +604,7 @@ templ forfeitModal(fixture *db.Fixture) {
</div>
}
templ fixtureTeamSection(team *db.Team, players []*db.PlayerWithPlayStatus, side string, result *db.FixtureResult) {
templ fixtureTeamSection(team *db.Team, players []*db.PlayerWithPlayStatus, side string, result *db.FixtureResult, season *db.Season, league *db.League) {
{{
// Separate playing and bench players
var playing []*db.PlayerWithPlayStatus
@@ -640,8 +641,8 @@ templ fixtureTeamSection(team *db.Team, players []*db.PlayerWithPlayStatus, side
}}
<div class="bg-mantle border border-surface1 rounded-lg overflow-hidden">
<div class="bg-surface0 border-b border-surface1 px-4 py-3 flex items-center justify-between">
<h3 class="text-md font-bold text-text">
{ team.Name }
<h3 class="text-md font-bold">
@links.TeamNameLinkInSeason(team, season, league)
</h3>
if team.Color != "" {
<span
@@ -675,9 +676,9 @@ templ fixtureTeamSection(team *db.Team, players []*db.PlayerWithPlayStatus, side
<tbody class="divide-y divide-surface1">
for _, p := range playing {
<tr class="hover:bg-surface0 transition-colors">
<td class="px-3 py-2 text-sm text-text">
<td class="px-3 py-2 text-sm">
<span class="flex items-center gap-1.5">
{ p.Player.DisplayName() }
@links.PlayerLink(p.Player)
if p.IsManager {
<span class="px-1.5 py-0.5 bg-yellow/20 text-yellow rounded text-xs font-medium">
&#9733;
@@ -715,7 +716,7 @@ templ fixtureTeamSection(team *db.Team, players []*db.PlayerWithPlayStatus, side
for _, p := range bench {
<div class="flex items-center gap-2 px-2 py-1.5 rounded">
<span class="text-sm text-subtext1">
{ p.Player.DisplayName() }
@links.PlayerLink(p.Player)
</span>
if p.IsManager {
<span class="px-1.5 py-0.5 bg-yellow/20 text-yellow rounded text-xs font-medium">
@@ -737,8 +738,8 @@ templ fixtureTeamSection(team *db.Team, players []*db.PlayerWithPlayStatus, side
<div class="space-y-1">
for _, p := range playing {
<div class="flex items-center gap-2 px-2 py-1.5 rounded hover:bg-surface0 transition">
<span class="text-sm text-text">
{ p.Player.DisplayName() }
<span class="text-sm">
@links.PlayerLink(p.Player)
</span>
if p.IsManager {
<span class="px-1.5 py-0.5 bg-yellow/20 text-yellow rounded text-xs font-medium">
@@ -760,7 +761,7 @@ templ fixtureTeamSection(team *db.Team, players []*db.PlayerWithPlayStatus, side
for _, p := range bench {
<div class="flex items-center gap-2 px-2 py-1.5 rounded">
<span class="text-sm text-subtext1">
{ p.Player.DisplayName() }
@links.PlayerLink(p.Player)
</span>
if p.IsManager {
<span class="px-1.5 py-0.5 bg-yellow/20 text-yellow rounded text-xs font-medium">
@@ -840,7 +841,9 @@ templ fixtureFreeAgentSection(
for _, n := range homeNominated {
<div class="flex items-center justify-between px-2 py-1.5 rounded hover:bg-surface0 transition">
<span class="flex items-center gap-2">
<span class="text-sm text-text">{ n.Player.DisplayName() }</span>
<span class="text-sm">
@links.PlayerLink(n.Player)
</span>
<span class="px-1.5 py-0.5 bg-peach/20 text-peach rounded text-xs font-medium">
FA
</span>
@@ -875,7 +878,9 @@ templ fixtureFreeAgentSection(
for _, n := range awayNominated {
<div class="flex items-center justify-between px-2 py-1.5 rounded hover:bg-surface0 transition">
<span class="flex items-center gap-2">
<span class="text-sm text-text">{ n.Player.DisplayName() }</span>
<span class="text-sm">
@links.PlayerLink(n.Player)
</span>
<span class="px-1.5 py-0.5 bg-peach/20 text-peach rounded text-xs font-medium">
FA
</span>

View File

@@ -2,6 +2,7 @@ package seasonsview
import "git.haelnorr.com/h/oslstats/internal/db"
import "git.haelnorr.com/h/oslstats/internal/view/baseview"
import "git.haelnorr.com/h/oslstats/internal/view/component/links"
import "fmt"
templ FixtureReviewResultPage(
@@ -22,7 +23,13 @@ templ FixtureReviewResultPage(
<div>
<h1 class="text-2xl font-bold text-text mb-1">Review Match Result</h1>
<p class="text-sm text-subtext1">
{ fixture.HomeTeam.Name } vs { fixture.AwayTeam.Name }
<span>
@links.TeamNameLinkInSeason(fixture.HomeTeam, fixture.Season, fixture.League)
</span>
vs
<span>
@links.TeamNameLinkInSeason(fixture.AwayTeam, fixture.Season, fixture.League)
</span>
<span class="text-subtext0 ml-1">
Round { fmt.Sprint(fixture.Round) }
</span>
@@ -96,12 +103,16 @@ templ FixtureReviewResultPage(
<div class="p-6">
<div class="flex items-center justify-center gap-8 py-4">
<div class="text-center">
<p class="text-sm text-subtext0 mb-1">{ fixture.HomeTeam.Name }</p>
<p class="text-sm text-subtext0 mb-1">
@links.TeamNameLinkInSeason(fixture.HomeTeam, fixture.Season, fixture.League)
</p>
<p class="text-4xl font-bold text-text">{ fmt.Sprint(result.HomeScore) }</p>
</div>
<div class="text-2xl text-subtext0 font-light">—</div>
<div class="text-center">
<p class="text-sm text-subtext0 mb-1">{ fixture.AwayTeam.Name }</p>
<p class="text-sm text-subtext0 mb-1">
@links.TeamNameLinkInSeason(fixture.AwayTeam, fixture.Season, fixture.League)
</p>
<p class="text-4xl font-bold text-text">{ fmt.Sprint(result.AwayScore) }</p>
</div>
</div>
@@ -127,8 +138,8 @@ templ FixtureReviewResultPage(
</div>
<!-- Player Stats Tables -->
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-6">
@reviewTeamStats(fixture.HomeTeam, result, "home")
@reviewTeamStats(fixture.AwayTeam, result, "away")
@reviewTeamStats(fixture.HomeTeam, result, "home", fixture.Season, fixture.League)
@reviewTeamStats(fixture.AwayTeam, result, "away", fixture.Season, fixture.League)
</div>
<!-- Actions -->
<div class="bg-mantle border border-surface1 rounded-lg overflow-hidden">
@@ -164,7 +175,7 @@ templ FixtureReviewResultPage(
}
}
templ reviewTeamStats(team *db.Team, result *db.FixtureResult, side string) {
templ reviewTeamStats(team *db.Team, result *db.FixtureResult, side string, season *db.Season, league *db.League) {
{{
// Collect unique players for this team across all periods
// We'll show the period 3 (final/cumulative) stats
@@ -197,7 +208,7 @@ templ reviewTeamStats(team *db.Team, result *db.FixtureResult, side string) {
} else {
Away —
}
{ team.Name }
@links.TeamNameLinkInSeason(team, season, league)
</h3>
</div>
<div class="overflow-x-auto">
@@ -218,10 +229,12 @@ templ reviewTeamStats(team *db.Team, result *db.FixtureResult, side string) {
<tbody class="divide-y divide-surface1">
for _, ps := range finalStats {
<tr class="hover:bg-surface0 transition-colors">
<td class="px-3 py-2 text-sm text-text">
<td class="px-3 py-2 text-sm">
<span class="flex items-center gap-1.5">
{ ps.Username }
if ps.PlayerID == nil {
if ps.PlayerID != nil {
@links.PlayerLinkFromStats(*ps.PlayerID, ps.Username)
} else {
<span class="text-text">{ ps.Username }</span>
<span class="text-yellow text-xs" title="Unmapped player">?</span>
}
if ps.Stats.IsFreeAgent {

View File

@@ -3,6 +3,7 @@ package seasonsview
import "git.haelnorr.com/h/oslstats/internal/db"
import "git.haelnorr.com/h/oslstats/internal/permissions"
import "git.haelnorr.com/h/oslstats/internal/contexts"
import "git.haelnorr.com/h/oslstats/internal/view/component/links"
import "fmt"
templ SeasonLeagueFreeAgentsPage(season *db.Season, league *db.League, freeAgents []*db.SeasonLeagueFreeAgent, availablePlayers []*db.Player) {
@@ -53,9 +54,9 @@ templ SeasonLeagueFreeAgents(season *db.Season, league *db.League, freeAgents []
<tbody class="divide-y divide-surface1">
for _, fa := range freeAgents {
<tr class="hover:bg-surface1 transition-colors">
<td class="px-4 py-3 text-sm text-text">
<td class="px-4 py-3 text-sm">
<span class="flex items-center gap-2">
{ fa.Player.DisplayName() }
@links.PlayerLink(fa.Player)
<span class="px-1.5 py-0.5 bg-peach/20 text-peach rounded text-xs font-medium">
FREE AGENT
</span>

View File

@@ -1,15 +1,16 @@
package seasonsview
import "git.haelnorr.com/h/oslstats/internal/db"
import "git.haelnorr.com/h/oslstats/internal/view/component/links"
import "fmt"
templ SeasonLeagueTablePage(season *db.Season, league *db.League, leaderboard []*db.LeaderboardEntry) {
@SeasonLeagueLayout("table", season, league) {
@SeasonLeagueTable(leaderboard)
@SeasonLeagueTable(season, league, leaderboard)
}
}
templ SeasonLeagueTable(leaderboard []*db.LeaderboardEntry) {
templ SeasonLeagueTable(season *db.Season, league *db.League, leaderboard []*db.LeaderboardEntry) {
if len(leaderboard) == 0 {
<div class="bg-surface0 border border-surface1 rounded-lg p-8 text-center">
<p class="text-subtext0 text-lg">No teams in this league yet.</p>
@@ -43,7 +44,7 @@ templ SeasonLeagueTable(leaderboard []*db.LeaderboardEntry) {
</thead>
<tbody class="divide-y divide-surface1">
for _, entry := range leaderboard {
@leaderboardRow(entry)
@leaderboardRow(entry, season, league)
}
</tbody>
</table>
@@ -52,7 +53,7 @@ templ SeasonLeagueTable(leaderboard []*db.LeaderboardEntry) {
}
}
templ leaderboardRow(entry *db.LeaderboardEntry) {
templ leaderboardRow(entry *db.LeaderboardEntry, season *db.Season, league *db.League) {
{{
r := entry.Record
goalDiff := r.GoalsFor - r.GoalsAgainst
@@ -68,15 +69,7 @@ templ leaderboardRow(entry *db.LeaderboardEntry) {
{ fmt.Sprint(entry.Position) }
</td>
<td class="px-4 py-3">
<div class="flex items-center gap-2">
if entry.Team.Color != "" {
<span
class="w-3 h-3 rounded-full shrink-0"
style={ "background-color: " + templ.SafeCSS(entry.Team.Color) }
></span>
}
<span class="text-sm font-medium text-text">{ entry.Team.Name }</span>
</div>
@links.TeamLinkInSeason(entry.Team, season, league)
</td>
<td class="px-3 py-3 text-center text-sm text-subtext0">
{ fmt.Sprint(r.Played) }

View File

@@ -4,6 +4,7 @@ import "git.haelnorr.com/h/oslstats/internal/db"
import "git.haelnorr.com/h/oslstats/internal/permissions"
import "git.haelnorr.com/h/oslstats/internal/contexts"
import "git.haelnorr.com/h/oslstats/internal/view/baseview"
import "git.haelnorr.com/h/oslstats/internal/view/component/links"
import "fmt"
import "sort"
import "time"
@@ -154,7 +155,9 @@ templ TeamRosterSection(twr *db.TeamWithRoster, available []*db.Player) {
<div class="bg-surface0 border border-surface1 rounded-lg overflow-hidden divide-y divide-surface1">
if twr.Manager != nil {
<div class="px-4 py-3 flex items-center justify-between">
<span class="text-text font-medium">{ twr.Manager.Name }</span>
<span class="font-medium">
@links.PlayerLink(twr.Manager)
</span>
<span class="text-xs px-2 py-0.5 bg-yellow/20 text-yellow rounded font-medium">
&#9733; Manager
</span>
@@ -162,7 +165,7 @@ templ TeamRosterSection(twr *db.TeamWithRoster, available []*db.Player) {
}
for _, player := range rosterPlayers {
<div class="px-4 py-3">
<span class="text-text">{ player.Name }</span>
@links.PlayerLink(player)
</div>
}
</div>
@@ -680,7 +683,9 @@ templ playerStatsSection(playerStats []*db.AggregatedPlayerStats) {
<tbody class="divide-y divide-surface1">
for _, ps := range playerStats {
<tr class="hover:bg-surface1 transition-colors">
<td class="px-3 py-2 text-sm text-text">{ ps.PlayerName }</td>
<td class="px-3 py-2 text-sm">
@links.PlayerLinkFromStats(ps.PlayerID, ps.PlayerName)
</td>
<td class="px-2 py-2 text-center text-sm text-subtext0">{ fmt.Sprint(ps.GamesPlayed) }</td>
<td class="px-2 py-2 text-center text-sm text-subtext0">{ fmt.Sprint(ps.PeriodsPlayed) }</td>
<td class="px-2 py-2 text-center text-sm font-medium text-text">{ fmt.Sprint(ps.Score) }</td>

View File

@@ -1,6 +1,7 @@
package teamsview
import "git.haelnorr.com/h/oslstats/internal/db"
import "git.haelnorr.com/h/oslstats/internal/view/component/links"
import "fmt"
import "sort"
@@ -108,7 +109,9 @@ templ playerStatsTable(playerStats []*db.TeamAllTimePlayerStats, statType string
<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 text-text font-medium">{ ps.PlayerName }</td>
<td class="px-3 py-2 text-sm font-medium">
@links.PlayerLinkFromStats(ps.PlayerID, ps.PlayerName)
</td>
<td class="px-2 py-2 text-center text-sm text-subtext0">{ fmt.Sprint(ps.SeasonsPlayed) }</td>
<td class="px-2 py-2 text-center text-sm text-subtext0">{ fmt.Sprint(ps.PeriodsPlayed) }</td>
if statType == "goals" {