added team overview

This commit is contained in:
2026-03-06 18:53:58 +11:00
parent baa15f03fa
commit c53fbac281
8 changed files with 535 additions and 10 deletions

View File

@@ -0,0 +1,81 @@
package teamsview
import "git.haelnorr.com/h/oslstats/internal/db"
import "git.haelnorr.com/h/oslstats/internal/view/baseview"
import "fmt"
templ DetailPage(team *db.Team, seasonInfos []*db.TeamSeasonInfo, playerStats []*db.TeamAllTimePlayerStats, activeTab string) {
@baseview.Layout(team.Name) {
<div class="max-w-screen-xl mx-auto px-4 py-8">
<div class="bg-mantle border border-surface1 rounded-lg overflow-hidden">
<!-- Header -->
<div class="bg-surface0 border-b border-surface1 px-6 py-8">
<div class="flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4">
<div class="flex items-center gap-4">
if team.Color != "" {
<div
class="w-12 h-12 rounded-full border-2 border-surface1 shrink-0"
style={ "background-color: " + templ.SafeCSS(team.Color) }
></div>
}
<div>
<h1 class="text-4xl font-bold text-text">{ team.Name }</h1>
<div class="flex items-center gap-2 mt-2 flex-wrap">
<span class="px-2 py-1 bg-mantle rounded text-subtext0 font-mono text-sm">
{ team.ShortName }
</span>
<span class="px-2 py-1 bg-mantle rounded text-subtext0 font-mono text-sm">
{ team.AltShortName }
</span>
</div>
</div>
</div>
<a
href="/teams"
class="rounded-lg px-4 py-2 hover:cursor-pointer text-center
bg-surface1 hover:bg-surface2 text-text transition"
>
Back to Teams
</a>
</div>
</div>
<!-- Tab Navigation -->
<nav class="bg-surface0 border-b border-surface1">
<ul class="flex flex-wrap">
@teamDetailTab("seasons", "Seasons", activeTab, team)
@teamDetailTab("stats", "Player Stats", activeTab, team)
</ul>
</nav>
</div>
<!-- Tab Content -->
<div class="mt-6">
if activeTab == "seasons" {
@TeamDetailSeasons(team, seasonInfos)
} else if activeTab == "stats" {
@TeamDetailPlayerStats(playerStats)
}
</div>
</div>
}
}
templ teamDetailTab(section string, label string, activeTab string, team *db.Team) {
{{
isActive := section == activeTab
baseClasses := "inline-block px-6 py-3 transition-colors cursor-pointer border-b-2"
activeClasses := "border-blue text-blue font-semibold"
inactiveClasses := "border-transparent text-subtext0 hover:text-text hover:border-surface2"
url := fmt.Sprintf("/teams/%d", team.ID)
if section != "seasons" {
url = fmt.Sprintf("/teams/%d?tab=%s", team.ID, section)
}
}}
<li class="inline-block">
<a
href={ templ.SafeURL(url) }
class={ baseClasses, templ.KV(activeClasses, isActive), templ.KV(inactiveClasses, !isActive) }
>
{ label }
</a>
</li>
}

View File

@@ -0,0 +1,127 @@
package teamsview
import "git.haelnorr.com/h/oslstats/internal/db"
import "fmt"
import "sort"
templ TeamDetailPlayerStats(playerStats []*db.TeamAllTimePlayerStats) {
if len(playerStats) == 0 {
<div class="bg-surface0 border border-surface1 rounded-lg p-8 text-center">
<p class="text-subtext0 text-lg">No player stats yet.</p>
<p class="text-subtext1 text-sm mt-2">Player statistics will appear here once games are played.</p>
</div>
} else {
<div x-data="{ activeView: 'goals' }">
<!-- Sub-view Tabs -->
<div class="flex gap-2 mb-4">
<button
@click="activeView = 'goals'"
:class="activeView === 'goals'
? 'bg-blue text-mantle'
: 'bg-surface0 border border-surface1 text-subtext0 hover:text-text hover:bg-surface1'"
class="px-4 py-2 rounded-lg font-medium text-sm transition hover:cursor-pointer"
>
Goals
</button>
<button
@click="activeView = 'assists'"
:class="activeView === 'assists'
? 'bg-blue text-mantle'
: 'bg-surface0 border border-surface1 text-subtext0 hover:text-text hover:bg-surface1'"
class="px-4 py-2 rounded-lg font-medium text-sm transition hover:cursor-pointer"
>
Assists
</button>
<button
@click="activeView = 'saves'"
:class="activeView === 'saves'
? 'bg-blue text-mantle'
: 'bg-surface0 border border-surface1 text-subtext0 hover:text-text hover:bg-surface1'"
class="px-4 py-2 rounded-lg font-medium text-sm transition hover:cursor-pointer"
>
Saves
</button>
</div>
<!-- Goals View -->
<div x-show="activeView === 'goals'">
@playerStatsTable(playerStats, "goals")
</div>
<!-- Assists View -->
<div x-show="activeView === 'assists'" style="display: none;">
@playerStatsTable(playerStats, "assists")
</div>
<!-- Saves View -->
<div x-show="activeView === 'saves'" style="display: none;">
@playerStatsTable(playerStats, "saves")
</div>
</div>
}
}
templ playerStatsTable(playerStats []*db.TeamAllTimePlayerStats, statType string) {
{{
// Make a copy so sorting doesn't affect other views
sorted := make([]*db.TeamAllTimePlayerStats, len(playerStats))
copy(sorted, playerStats)
switch statType {
case "goals":
sort.Slice(sorted, func(i, j int) bool {
return sorted[i].Goals > sorted[j].Goals
})
case "assists":
sort.Slice(sorted, func(i, j int) bool {
return sorted[i].Assists > sorted[j].Assists
})
case "saves":
sort.Slice(sorted, func(i, j int) bool {
return sorted[i].Saves > sorted[j].Saves
})
}
statLabel := "Goals"
statShort := "G"
if statType == "assists" {
statLabel = "Assists"
statShort = "A"
} else if statType == "saves" {
statLabel = "Saves"
statShort = "SV"
}
_ = statLabel
}}
<div class="bg-surface0 border border-surface1 rounded-lg overflow-hidden">
<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-2 py-2 text-center text-xs font-semibold text-text" title="Seasons Played">SZN</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-blue" title={ statLabel }>{ statShort }</th>
</tr>
</thead>
<tbody class="divide-y divide-surface1">
for i, ps := range sorted {
<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 text-text font-medium">{ 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" {
<td class="px-2 py-2 text-center text-sm font-bold text-blue">{ fmt.Sprint(ps.Goals) }</td>
} else if statType == "assists" {
<td class="px-2 py-2 text-center text-sm font-bold text-blue">{ fmt.Sprint(ps.Assists) }</td>
} else {
<td class="px-2 py-2 text-center text-sm font-bold text-blue">{ fmt.Sprint(ps.Saves) }</td>
}
</tr>
}
</tbody>
</table>
</div>
</div>
}

View File

@@ -0,0 +1,86 @@
package teamsview
import "git.haelnorr.com/h/oslstats/internal/db"
import "git.haelnorr.com/h/oslstats/internal/view/seasonsview"
import "fmt"
templ TeamDetailSeasons(team *db.Team, seasonInfos []*db.TeamSeasonInfo) {
if len(seasonInfos) == 0 {
<div class="bg-surface0 border border-surface1 rounded-lg p-8 text-center">
<p class="text-subtext0 text-lg">No season history yet.</p>
<p class="text-subtext1 text-sm mt-2">This team has not participated in any seasons.</p>
</div>
} else {
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
for _, info := range seasonInfos {
@teamSeasonCard(team, info)
}
</div>
}
}
templ teamSeasonCard(team *db.Team, info *db.TeamSeasonInfo) {
{{
detailURL := fmt.Sprintf(
"/seasons/%s/leagues/%s/teams/%d",
info.Season.ShortName, info.League.ShortName, team.ID,
)
}}
<a
href={ templ.SafeURL(detailURL) }
class="bg-mantle border border-surface1 rounded-lg overflow-hidden
hover:bg-surface0 transition hover:cursor-pointer block"
>
<!-- Card Header -->
<div class="bg-surface0 border-b border-surface1 px-4 py-3 flex items-center justify-between">
<div class="flex items-center gap-2">
<h3 class="text-lg font-bold text-text">{ info.Season.Name }</h3>
<span class="text-subtext0 text-sm">—</span>
<span class="text-subtext0 text-sm">{ info.League.Name }</span>
</div>
@seasonsview.StatusBadge(info.Season, true, true)
</div>
<!-- Card Body -->
<div class="p-4">
<!-- Position & Points Row -->
<div class="flex items-center justify-between mb-3">
<div class="flex items-center gap-3">
<!-- Position Badge -->
<div class="flex items-center gap-1.5">
<span class="text-xs text-subtext0 uppercase font-medium">Position</span>
<span class="text-2xl font-bold text-text">
{ fmt.Sprint(info.Position) }
</span>
<span class="text-sm text-subtext0">
/ { fmt.Sprint(info.TotalTeams) }
</span>
</div>
</div>
<!-- Points -->
<div class="text-right">
<span class="text-xs text-subtext0 uppercase font-medium">Points</span>
<p class="text-2xl font-bold text-blue">{ fmt.Sprint(info.Record.Points) }</p>
</div>
</div>
<!-- Record Row -->
<div class="grid grid-cols-4 gap-2 text-center">
<div class="bg-surface0 rounded px-2 py-1.5">
<p class="text-xs text-subtext0 font-medium">W</p>
<p class="text-sm font-bold text-green">{ fmt.Sprint(info.Record.Wins) }</p>
</div>
<div class="bg-surface0 rounded px-2 py-1.5">
<p class="text-xs text-subtext0 font-medium">OTW</p>
<p class="text-sm font-bold text-teal">{ fmt.Sprint(info.Record.OvertimeWins) }</p>
</div>
<div class="bg-surface0 rounded px-2 py-1.5">
<p class="text-xs text-subtext0 font-medium">OTL</p>
<p class="text-sm font-bold text-peach">{ fmt.Sprint(info.Record.OvertimeLosses) }</p>
</div>
<div class="bg-surface0 rounded px-2 py-1.5">
<p class="text-xs text-subtext0 font-medium">L</p>
<p class="text-sm font-bold text-red">{ fmt.Sprint(info.Record.Losses) }</p>
</div>
</div>
</div>
</a>
}

View File

@@ -7,6 +7,7 @@ import "git.haelnorr.com/h/oslstats/internal/view/sort"
import "git.haelnorr.com/h/oslstats/internal/contexts"
import "git.haelnorr.com/h/oslstats/internal/permissions"
import "github.com/uptrace/bun"
import "fmt"
templ ListPage(teams *db.List[db.Team]) {
@baseview.Layout("Teams") {
@@ -80,8 +81,10 @@ templ TeamsList(teams *db.List[db.Team]) {
<!-- Card grid -->
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
for _, t := range teams.Items {
<div
class="bg-mantle border border-surface1 rounded-lg p-6 hover:bg-surface0 transition-colors flex flex-col"
<a
href={ templ.SafeURL(fmt.Sprintf("/teams/%d", t.ID)) }
class="bg-mantle border border-surface1 rounded-lg p-6 hover:bg-surface0
transition-colors flex flex-col hover:cursor-pointer"
>
<!-- Header: Name with color indicator -->
<div class="flex justify-between items-start mb-3">
@@ -102,7 +105,7 @@ templ TeamsList(teams *db.List[db.Team]) {
{ t.AltShortName }
</span>
</div>
</div>
</a>
}
</div>
<!-- Pagination controls -->