|
|
|
|
@@ -8,7 +8,7 @@ import "fmt"
|
|
|
|
|
import "sort"
|
|
|
|
|
import "time"
|
|
|
|
|
|
|
|
|
|
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) {
|
|
|
|
|
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, position int, totalTeams int) {
|
|
|
|
|
{{
|
|
|
|
|
team := twr.Team
|
|
|
|
|
season := twr.Season
|
|
|
|
|
@@ -62,14 +62,48 @@ templ SeasonLeagueTeamDetailPage(twr *db.TeamWithRoster, fixtures []*db.Fixture,
|
|
|
|
|
</div>
|
|
|
|
|
<!-- Content -->
|
|
|
|
|
<div class="bg-crust p-6">
|
|
|
|
|
<!-- Top row: Roster (left) + Fixtures (right) -->
|
|
|
|
|
{{
|
|
|
|
|
// 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]
|
|
|
|
|
}
|
|
|
|
|
}}
|
|
|
|
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
|
|
|
|
<!-- Top Left: Team Standing -->
|
|
|
|
|
@teamRecordCard(record, position, totalTeams)
|
|
|
|
|
<!-- Top Right: Results -->
|
|
|
|
|
@teamResultsSection(twr.Team, recentResults, resultMap)
|
|
|
|
|
<!-- Bottom Left: Roster -->
|
|
|
|
|
@TeamRosterSection(twr, available)
|
|
|
|
|
@teamFixturesPane(twr.Team, fixtures, scheduleMap, resultMap)
|
|
|
|
|
<!-- Bottom Right: Upcoming -->
|
|
|
|
|
@teamUpcomingSection(twr.Team, upcoming, scheduleMap)
|
|
|
|
|
</div>
|
|
|
|
|
<!-- Stats below both -->
|
|
|
|
|
<!-- Player Stats (full width) -->
|
|
|
|
|
<div class="mt-6">
|
|
|
|
|
@teamStatsSection(record, playerStats)
|
|
|
|
|
@playerStatsSection(playerStats)
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
@@ -405,68 +439,45 @@ templ manageRosterModal(twr *db.TeamWithRoster, available []*db.Player, rosterPl
|
|
|
|
|
</script>
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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>
|
|
|
|
|
}
|
|
|
|
|
templ teamResultsSection(team *db.Team, recentResults []*db.Fixture, resultMap map[int]*db.FixtureResult) {
|
|
|
|
|
<section>
|
|
|
|
|
<div class="flex justify-between items-center mb-4">
|
|
|
|
|
<h2 class="text-2xl font-bold text-text">
|
|
|
|
|
Results
|
|
|
|
|
<span class="text-sm font-normal text-subtext0">(last 5)</span>
|
|
|
|
|
</h2>
|
|
|
|
|
</div>
|
|
|
|
|
<!-- Upcoming -->
|
|
|
|
|
<div>
|
|
|
|
|
<h2 class="text-2xl font-bold text-text mb-4">Upcoming</h2>
|
|
|
|
|
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 upcoming {
|
|
|
|
|
@teamFixtureRow(team, fixture, scheduleMap)
|
|
|
|
|
}
|
|
|
|
|
</div>
|
|
|
|
|
}
|
|
|
|
|
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>
|
|
|
|
|
}
|
|
|
|
|
</section>
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
templ teamUpcomingSection(team *db.Team, upcoming []*db.Fixture, scheduleMap map[int]*db.FixtureSchedule) {
|
|
|
|
|
<section>
|
|
|
|
|
<div class="flex justify-between items-center mb-4">
|
|
|
|
|
<h2 class="text-2xl font-bold text-text">Upcoming</h2>
|
|
|
|
|
</div>
|
|
|
|
|
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 upcoming {
|
|
|
|
|
@teamFixtureRow(team, fixture, scheduleMap)
|
|
|
|
|
}
|
|
|
|
|
</div>
|
|
|
|
|
}
|
|
|
|
|
</section>
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -595,69 +606,96 @@ templ teamResultRow(team *db.Team, fixture *db.Fixture, resultMap map[int]*db.Fi
|
|
|
|
|
</a>
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
templ teamStatsSection(record *db.TeamRecord, playerStats []*db.AggregatedPlayerStats) {
|
|
|
|
|
templ teamRecordCard(record *db.TeamRecord, position int, totalTeams int) {
|
|
|
|
|
<section>
|
|
|
|
|
<div class="mb-4">
|
|
|
|
|
<h2 class="text-2xl font-bold text-text">Stats</h2>
|
|
|
|
|
<div class="flex justify-between items-center mb-4">
|
|
|
|
|
<h2 class="text-2xl font-bold text-text">Standing</h2>
|
|
|
|
|
</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>
|
|
|
|
|
<p class="text-subtext0 text-lg">No games played yet.</p>
|
|
|
|
|
</div>
|
|
|
|
|
} else {
|
|
|
|
|
<!-- Team Record Summary -->
|
|
|
|
|
<div class="bg-surface0 border border-surface1 rounded-lg overflow-hidden mb-4">
|
|
|
|
|
<div class="grid grid-cols-4 sm:grid-cols-4 lg:grid-cols-8 divide-x divide-surface1">
|
|
|
|
|
@statCell("Played", fmt.Sprint(record.Played), "")
|
|
|
|
|
<div class="bg-surface0 border border-surface1 rounded-lg overflow-hidden">
|
|
|
|
|
<!-- Position & Points Header -->
|
|
|
|
|
<div class="flex items-center justify-between px-6 py-5 border-b border-surface1">
|
|
|
|
|
<div class="flex items-center gap-3">
|
|
|
|
|
<span class="text-4xl font-bold text-text">{ ordinal(position) }</span>
|
|
|
|
|
<div>
|
|
|
|
|
<p class="text-xs text-subtext0 uppercase font-medium">Position</p>
|
|
|
|
|
<p class="text-sm text-subtext1">of { fmt.Sprint(totalTeams) } teams</p>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="text-right">
|
|
|
|
|
<p class="text-xs text-subtext0 uppercase font-medium">Points</p>
|
|
|
|
|
<p class="text-3xl font-bold text-blue">{ fmt.Sprint(record.Points) }</p>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<!-- Record Grid -->
|
|
|
|
|
<div class="grid grid-cols-4 divide-x divide-surface1">
|
|
|
|
|
@statCell("W", fmt.Sprint(record.Wins), "text-green")
|
|
|
|
|
@statCell("OTW", fmt.Sprint(record.OvertimeWins), "text-teal")
|
|
|
|
|
@statCell("OTL", fmt.Sprint(record.OvertimeLosses), "text-peach")
|
|
|
|
|
@statCell("L", fmt.Sprint(record.Losses), "text-red")
|
|
|
|
|
</div>
|
|
|
|
|
<!-- Goals Row -->
|
|
|
|
|
<div class="grid grid-cols-3 divide-x divide-surface1 border-t border-surface1">
|
|
|
|
|
@statCell("Played", fmt.Sprint(record.Played), "")
|
|
|
|
|
@statCell("GF", fmt.Sprint(record.GoalsFor), "")
|
|
|
|
|
@statCell("GA", fmt.Sprint(record.GoalsAgainst), "")
|
|
|
|
|
@statCell("PTS", fmt.Sprint(record.Points), "text-blue")
|
|
|
|
|
</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="Periods Played">PP</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>
|
|
|
|
|
}
|
|
|
|
|
</section>
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
templ playerStatsSection(playerStats []*db.AggregatedPlayerStats) {
|
|
|
|
|
<section>
|
|
|
|
|
<div class="flex justify-between items-center mb-4">
|
|
|
|
|
<h2 class="text-2xl font-bold text-text">Player Stats</h2>
|
|
|
|
|
</div>
|
|
|
|
|
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 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="Periods Played">PP</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 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>
|
|
|
|
|
<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>
|
|
|
|
|
</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 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>
|
|
|
|
|
<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>
|
|
|
|
|
}
|
|
|
|
|
</tbody>
|
|
|
|
|
</table>
|
|
|
|
|
</div>
|
|
|
|
|
}
|
|
|
|
|
</div>
|
|
|
|
|
}
|
|
|
|
|
</section>
|
|
|
|
|
}
|
|
|
|
|
|