added stats to team page
This commit is contained in:
@@ -5,8 +5,10 @@ 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 "fmt"
|
||||
import "sort"
|
||||
import "time"
|
||||
|
||||
templ SeasonLeagueTeamDetailPage(twr *db.TeamWithRoster, fixtures []*db.Fixture, available []*db.Player, scheduleMap map[int]*db.FixtureSchedule) {
|
||||
templ SeasonLeagueTeamDetailPage(twr *db.TeamWithRoster, fixtures []*db.Fixture, available []*db.Player, scheduleMap map[int]*db.FixtureSchedule, resultMap map[int]*db.FixtureResult, record *db.TeamRecord, playerStats []*db.AggregatedPlayerStats) {
|
||||
{{
|
||||
team := twr.Team
|
||||
season := twr.Season
|
||||
@@ -54,11 +56,11 @@ templ SeasonLeagueTeamDetailPage(twr *db.TeamWithRoster, fixtures []*db.Fixture,
|
||||
<!-- Top row: Roster (left) + Fixtures (right) -->
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
@TeamRosterSection(twr, available)
|
||||
@teamFixturesPane(twr.Team, fixtures, scheduleMap)
|
||||
@teamFixturesPane(twr.Team, fixtures, scheduleMap, resultMap)
|
||||
</div>
|
||||
<!-- Stats below both -->
|
||||
<div class="mt-6">
|
||||
@teamStatsSection()
|
||||
@teamStatsSection(record, playerStats)
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -394,31 +396,68 @@ templ manageRosterModal(twr *db.TeamWithRoster, available []*db.Player, rosterPl
|
||||
</script>
|
||||
}
|
||||
|
||||
templ teamFixturesPane(team *db.Team, fixtures []*db.Fixture, scheduleMap map[int]*db.FixtureSchedule) {
|
||||
templ teamFixturesPane(team *db.Team, fixtures []*db.Fixture, scheduleMap map[int]*db.FixtureSchedule, resultMap map[int]*db.FixtureResult) {
|
||||
{{
|
||||
// Split fixtures into upcoming and completed
|
||||
var upcoming []*db.Fixture
|
||||
var completed []*db.Fixture
|
||||
for _, f := range fixtures {
|
||||
if _, hasResult := resultMap[f.ID]; hasResult {
|
||||
completed = append(completed, f)
|
||||
} else {
|
||||
upcoming = append(upcoming, f)
|
||||
}
|
||||
}
|
||||
// Sort completed by scheduled time descending (most recent first)
|
||||
sort.Slice(completed, func(i, j int) bool {
|
||||
ti := time.Time{}
|
||||
tj := time.Time{}
|
||||
if si, ok := scheduleMap[completed[i].ID]; ok && si.ScheduledTime != nil {
|
||||
ti = *si.ScheduledTime
|
||||
}
|
||||
if sj, ok := scheduleMap[completed[j].ID]; ok && sj.ScheduledTime != nil {
|
||||
tj = *sj.ScheduledTime
|
||||
}
|
||||
return ti.After(tj)
|
||||
})
|
||||
// Limit to 5 most recent results
|
||||
recentResults := completed
|
||||
if len(recentResults) > 5 {
|
||||
recentResults = recentResults[:5]
|
||||
}
|
||||
}}
|
||||
<section class="space-y-6">
|
||||
<!-- Results -->
|
||||
<div>
|
||||
<h2 class="text-2xl font-bold text-text mb-4">Results</h2>
|
||||
if len(recentResults) == 0 {
|
||||
<div class="bg-surface0 border border-surface1 rounded-lg p-8 text-center">
|
||||
<p class="text-subtext0 text-lg">No results yet.</p>
|
||||
<p class="text-subtext1 text-sm mt-2">Match results will appear here once games are played.</p>
|
||||
</div>
|
||||
} else {
|
||||
<div class="bg-surface0 border border-surface1 rounded-lg overflow-hidden divide-y divide-surface1">
|
||||
for _, fixture := range recentResults {
|
||||
@teamResultRow(team, fixture, resultMap)
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<!-- Upcoming -->
|
||||
<div>
|
||||
<h2 class="text-2xl font-bold text-text mb-4">Upcoming</h2>
|
||||
if len(fixtures) == 0 {
|
||||
if len(upcoming) == 0 {
|
||||
<div class="bg-surface0 border border-surface1 rounded-lg p-8 text-center">
|
||||
<p class="text-subtext0 text-lg">No upcoming fixtures.</p>
|
||||
</div>
|
||||
} else {
|
||||
<div class="bg-surface0 border border-surface1 rounded-lg overflow-hidden divide-y divide-surface1">
|
||||
for _, fixture := range fixtures {
|
||||
for _, fixture := range upcoming {
|
||||
@teamFixtureRow(team, fixture, scheduleMap)
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<!-- Results -->
|
||||
<div>
|
||||
<h2 class="text-2xl font-bold text-text mb-4">Results</h2>
|
||||
<div class="bg-surface0 border border-surface1 rounded-lg p-8 text-center">
|
||||
<p class="text-subtext0 text-lg">Results coming soon.</p>
|
||||
<p class="text-subtext1 text-sm mt-2">Match results will appear here once game data is recorded.</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
}
|
||||
|
||||
@@ -468,14 +507,135 @@ templ teamFixtureRow(team *db.Team, fixture *db.Fixture, scheduleMap map[int]*db
|
||||
</a>
|
||||
}
|
||||
|
||||
templ teamStatsSection() {
|
||||
templ teamResultRow(team *db.Team, fixture *db.Fixture, resultMap map[int]*db.FixtureResult) {
|
||||
{{
|
||||
isHome := fixture.HomeTeamID == team.ID
|
||||
var opponent string
|
||||
if isHome {
|
||||
opponent = fixture.AwayTeam.Name
|
||||
} else {
|
||||
opponent = fixture.HomeTeam.Name
|
||||
}
|
||||
res := resultMap[fixture.ID]
|
||||
won := (isHome && res.Winner == "home") || (!isHome && res.Winner == "away")
|
||||
lost := (isHome && res.Winner == "away") || (!isHome && res.Winner == "home")
|
||||
_ = lost
|
||||
}}
|
||||
<a
|
||||
href={ templ.SafeURL(fmt.Sprintf("/fixtures/%d", fixture.ID)) }
|
||||
class="px-4 py-3 flex items-center justify-between gap-3 hover:bg-surface1 transition hover:cursor-pointer block"
|
||||
>
|
||||
<div class="flex items-center gap-3 min-w-0">
|
||||
if won {
|
||||
<span class="text-xs font-bold px-2 py-0.5 bg-green/20 text-green rounded shrink-0">W</span>
|
||||
} else if lost {
|
||||
<span class="text-xs font-bold px-2 py-0.5 bg-red/20 text-red rounded shrink-0">L</span>
|
||||
} else {
|
||||
<span class="text-xs font-bold px-2 py-0.5 bg-surface1 text-subtext0 rounded shrink-0">D</span>
|
||||
}
|
||||
<span class="text-xs font-mono text-subtext0 bg-mantle px-2 py-0.5 rounded shrink-0">
|
||||
GW{ fmt.Sprint(*fixture.GameWeek) }
|
||||
</span>
|
||||
if isHome {
|
||||
<span class="text-xs px-2 py-0.5 bg-blue/20 text-blue rounded font-medium shrink-0">
|
||||
HOME
|
||||
</span>
|
||||
} else {
|
||||
<span class="text-xs px-2 py-0.5 bg-surface1 text-subtext0 rounded font-medium shrink-0">
|
||||
AWAY
|
||||
</span>
|
||||
}
|
||||
<span class="text-sm text-subtext0 shrink-0">vs</span>
|
||||
<span class="text-text font-medium truncate">
|
||||
{ opponent }
|
||||
</span>
|
||||
</div>
|
||||
<span class="flex items-center gap-2 shrink-0">
|
||||
if res.Winner == "home" {
|
||||
<span class="text-sm font-bold text-text">{ fmt.Sprint(res.HomeScore) }</span>
|
||||
<span class="text-xs text-subtext0">–</span>
|
||||
<span class="text-sm text-subtext0">{ fmt.Sprint(res.AwayScore) }</span>
|
||||
} else if res.Winner == "away" {
|
||||
<span class="text-sm text-subtext0">{ fmt.Sprint(res.HomeScore) }</span>
|
||||
<span class="text-xs text-subtext0">–</span>
|
||||
<span class="text-sm font-bold text-text">{ fmt.Sprint(res.AwayScore) }</span>
|
||||
} else {
|
||||
<span class="text-sm text-text">{ fmt.Sprint(res.HomeScore) }</span>
|
||||
<span class="text-xs text-subtext0">–</span>
|
||||
<span class="text-sm text-text">{ fmt.Sprint(res.AwayScore) }</span>
|
||||
}
|
||||
</span>
|
||||
</a>
|
||||
}
|
||||
|
||||
templ teamStatsSection(record *db.TeamRecord, playerStats []*db.AggregatedPlayerStats) {
|
||||
<section>
|
||||
<div class="mb-4">
|
||||
<h2 class="text-2xl font-bold text-text">Stats</h2>
|
||||
</div>
|
||||
<div class="bg-surface0 border border-surface1 rounded-lg p-8 text-center">
|
||||
<p class="text-subtext0 text-lg">Stats coming soon.</p>
|
||||
<p class="text-subtext1 text-sm mt-2">Team statistics will appear here once game data is available.</p>
|
||||
</div>
|
||||
if record.Played == 0 {
|
||||
<div class="bg-surface0 border border-surface1 rounded-lg p-8 text-center">
|
||||
<p class="text-subtext0 text-lg">No stats yet.</p>
|
||||
<p class="text-subtext1 text-sm mt-2">Team statistics will appear here once games are played.</p>
|
||||
</div>
|
||||
} else {
|
||||
<!-- Team Record Summary -->
|
||||
<div class="bg-surface0 border border-surface1 rounded-lg overflow-hidden mb-4">
|
||||
<div class="grid grid-cols-2 sm:grid-cols-4 lg:grid-cols-6 divide-x divide-surface1">
|
||||
@statCell("Played", fmt.Sprint(record.Played), "")
|
||||
@statCell("Record", fmt.Sprintf("%d-%d-%d", record.Wins, record.Losses, record.Draws), "")
|
||||
@statCell("Wins", fmt.Sprint(record.Wins), "text-green")
|
||||
@statCell("Losses", fmt.Sprint(record.Losses), "text-red")
|
||||
@statCell("GF", fmt.Sprint(record.GoalsFor), "")
|
||||
@statCell("GA", fmt.Sprint(record.GoalsAgainst), "")
|
||||
</div>
|
||||
</div>
|
||||
<!-- Player Stats Leaderboard -->
|
||||
if len(playerStats) > 0 {
|
||||
<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-left text-xs font-semibold text-text">Player</th>
|
||||
<th class="px-2 py-2 text-center text-xs font-semibold text-text" title="Games Played">GP</th>
|
||||
<th class="px-2 py-2 text-center text-xs font-semibold text-text" title="Score">SC</th>
|
||||
<th class="px-2 py-2 text-center text-xs font-semibold text-text" title="Goals">G</th>
|
||||
<th class="px-2 py-2 text-center text-xs font-semibold text-text" title="Assists">A</th>
|
||||
<th class="px-2 py-2 text-center text-xs font-semibold text-text" title="Saves">SV</th>
|
||||
<th class="px-2 py-2 text-center text-xs font-semibold text-text" title="Shots">SH</th>
|
||||
<th class="px-2 py-2 text-center text-xs font-semibold text-text" title="Blocks">BL</th>
|
||||
<th class="px-2 py-2 text-center text-xs font-semibold text-text" title="Passes">PA</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<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-2 py-2 text-center text-sm text-subtext0">{ fmt.Sprint(ps.GamesPlayed) }</td>
|
||||
<td class="px-2 py-2 text-center text-sm font-medium text-text">{ fmt.Sprint(ps.Score) }</td>
|
||||
<td class="px-2 py-2 text-center text-sm text-text">{ fmt.Sprint(ps.Goals) }</td>
|
||||
<td class="px-2 py-2 text-center text-sm text-text">{ fmt.Sprint(ps.Assists) }</td>
|
||||
<td class="px-2 py-2 text-center text-sm text-text">{ fmt.Sprint(ps.Saves) }</td>
|
||||
<td class="px-2 py-2 text-center text-sm text-text">{ fmt.Sprint(ps.Shots) }</td>
|
||||
<td class="px-2 py-2 text-center text-sm text-text">{ fmt.Sprint(ps.Blocks) }</td>
|
||||
<td class="px-2 py-2 text-center text-sm text-text">{ fmt.Sprint(ps.Passes) }</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</section>
|
||||
}
|
||||
|
||||
templ statCell(label string, value string, valueColor string) {
|
||||
<div class="px-4 py-3 text-center">
|
||||
<p class="text-xs text-subtext0 font-medium uppercase mb-1">{ label }</p>
|
||||
<p class={ "text-lg font-bold", templ.KV("text-text", valueColor == ""), templ.KV(valueColor, valueColor != "") }>
|
||||
{ value }
|
||||
</p>
|
||||
</div>
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user