added better links to teams and players
This commit is contained in:
@@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
54
internal/view/component/links/links.templ
Normal file
54
internal/view/component/links/links.templ
Normal 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>
|
||||
}
|
||||
@@ -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">
|
||||
★
|
||||
@@ -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>
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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) }
|
||||
|
||||
@@ -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">
|
||||
★ 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>
|
||||
|
||||
@@ -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" {
|
||||
|
||||
Reference in New Issue
Block a user