series overview added
This commit is contained in:
319
internal/view/seasonsview/series_match_preview.templ
Normal file
319
internal/view/seasonsview/series_match_preview.templ
Normal file
@@ -0,0 +1,319 @@
|
||||
package seasonsview
|
||||
|
||||
import "git.haelnorr.com/h/oslstats/internal/db"
|
||||
import "git.haelnorr.com/h/oslstats/internal/view/component/links"
|
||||
import "fmt"
|
||||
import "sort"
|
||||
|
||||
// seriesMatchPreviewTab renders the full Match Preview tab for upcoming series.
|
||||
// Shows team standings comparison, recent form, and full rosters side-by-side.
|
||||
templ seriesMatchPreviewTab(
|
||||
series *db.PlayoffSeries,
|
||||
rosters map[string][]*db.PlayerWithPlayStatus,
|
||||
preview *db.MatchPreviewData,
|
||||
) {
|
||||
<div class="space-y-6">
|
||||
<!-- Team Comparison Header -->
|
||||
if preview != nil {
|
||||
@seriesPreviewHeader(series, preview)
|
||||
}
|
||||
|
||||
<!-- Form Guide (Last 5 Games) -->
|
||||
if preview != nil {
|
||||
@seriesPreviewFormGuide(series, preview)
|
||||
}
|
||||
|
||||
<!-- Team Rosters -->
|
||||
@seriesPreviewRosters(series, rosters)
|
||||
</div>
|
||||
}
|
||||
|
||||
// seriesPreviewHeader renders the broadcast-style team comparison with standings.
|
||||
templ seriesPreviewHeader(series *db.PlayoffSeries, preview *db.MatchPreviewData) {
|
||||
<div class="bg-mantle border border-surface1 rounded-lg overflow-hidden">
|
||||
<div class="bg-surface0 border-b border-surface1 px-6 py-3">
|
||||
<h2 class="text-lg font-bold text-text">Team Comparison</h2>
|
||||
</div>
|
||||
<div class="p-6">
|
||||
<!-- Team Names and VS -->
|
||||
<div class="flex items-center justify-center gap-4 sm:gap-8 mb-8">
|
||||
<!-- Team 1 -->
|
||||
<div class="flex flex-col items-center text-center flex-1">
|
||||
if series.Team1 != nil && series.Team1.Color != "" {
|
||||
<div
|
||||
class="w-14 h-14 rounded-full border-2 border-surface1 mb-3 shrink-0"
|
||||
style={ "background-color: " + templ.SafeCSS(series.Team1.Color) }
|
||||
></div>
|
||||
}
|
||||
<h3 class="text-xl sm:text-2xl font-bold text-text">
|
||||
if series.Team1 != nil {
|
||||
@links.TeamNameLinkInSeason(series.Team1, series.Bracket.Season, series.Bracket.League)
|
||||
} else {
|
||||
TBD
|
||||
}
|
||||
</h3>
|
||||
if series.Team1 != nil {
|
||||
<span class="text-subtext0 text-sm font-mono mt-1">{ series.Team1.ShortName }</span>
|
||||
}
|
||||
</div>
|
||||
<!-- VS Divider -->
|
||||
<div class="flex flex-col items-center shrink-0">
|
||||
<span class="text-3xl sm:text-4xl font-bold text-subtext0">VS</span>
|
||||
</div>
|
||||
<!-- Team 2 -->
|
||||
<div class="flex flex-col items-center text-center flex-1">
|
||||
if series.Team2 != nil && series.Team2.Color != "" {
|
||||
<div
|
||||
class="w-14 h-14 rounded-full border-2 border-surface1 mb-3 shrink-0"
|
||||
style={ "background-color: " + templ.SafeCSS(series.Team2.Color) }
|
||||
></div>
|
||||
}
|
||||
<h3 class="text-xl sm:text-2xl font-bold text-text">
|
||||
if series.Team2 != nil {
|
||||
@links.TeamNameLinkInSeason(series.Team2, series.Bracket.Season, series.Bracket.League)
|
||||
} else {
|
||||
TBD
|
||||
}
|
||||
</h3>
|
||||
if series.Team2 != nil {
|
||||
<span class="text-subtext0 text-sm font-mono mt-1">{ series.Team2.ShortName }</span>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<!-- Stats Comparison Grid -->
|
||||
{{
|
||||
homePos := ordinal(preview.HomePosition)
|
||||
awayPos := ordinal(preview.AwayPosition)
|
||||
if preview.HomePosition == 0 {
|
||||
homePos = "N/A"
|
||||
}
|
||||
if preview.AwayPosition == 0 {
|
||||
awayPos = "N/A"
|
||||
}
|
||||
}}
|
||||
<div class="space-y-0">
|
||||
@previewStatRow(
|
||||
homePos,
|
||||
"Position",
|
||||
awayPos,
|
||||
preview.HomePosition > 0 && preview.HomePosition < preview.AwayPosition,
|
||||
preview.AwayPosition > 0 && preview.AwayPosition < preview.HomePosition,
|
||||
)
|
||||
@previewStatRow(
|
||||
fmt.Sprint(preview.HomeRecord.Points),
|
||||
"Points",
|
||||
fmt.Sprint(preview.AwayRecord.Points),
|
||||
preview.HomeRecord.Points > preview.AwayRecord.Points,
|
||||
preview.AwayRecord.Points > preview.HomeRecord.Points,
|
||||
)
|
||||
@previewStatRow(
|
||||
fmt.Sprint(preview.HomeRecord.Played),
|
||||
"Played",
|
||||
fmt.Sprint(preview.AwayRecord.Played),
|
||||
false,
|
||||
false,
|
||||
)
|
||||
@previewStatRow(
|
||||
fmt.Sprint(preview.HomeRecord.Wins),
|
||||
"Wins",
|
||||
fmt.Sprint(preview.AwayRecord.Wins),
|
||||
preview.HomeRecord.Wins > preview.AwayRecord.Wins,
|
||||
preview.AwayRecord.Wins > preview.HomeRecord.Wins,
|
||||
)
|
||||
@previewStatRow(
|
||||
fmt.Sprint(preview.HomeRecord.OvertimeWins),
|
||||
"OT Wins",
|
||||
fmt.Sprint(preview.AwayRecord.OvertimeWins),
|
||||
preview.HomeRecord.OvertimeWins > preview.AwayRecord.OvertimeWins,
|
||||
preview.AwayRecord.OvertimeWins > preview.HomeRecord.OvertimeWins,
|
||||
)
|
||||
@previewStatRow(
|
||||
fmt.Sprint(preview.HomeRecord.OvertimeLosses),
|
||||
"OT Losses",
|
||||
fmt.Sprint(preview.AwayRecord.OvertimeLosses),
|
||||
preview.HomeRecord.OvertimeLosses < preview.AwayRecord.OvertimeLosses,
|
||||
preview.AwayRecord.OvertimeLosses < preview.HomeRecord.OvertimeLosses,
|
||||
)
|
||||
@previewStatRow(
|
||||
fmt.Sprint(preview.HomeRecord.Losses),
|
||||
"Losses",
|
||||
fmt.Sprint(preview.AwayRecord.Losses),
|
||||
preview.HomeRecord.Losses < preview.AwayRecord.Losses,
|
||||
preview.AwayRecord.Losses < preview.HomeRecord.Losses,
|
||||
)
|
||||
@previewStatRow(
|
||||
fmt.Sprint(preview.HomeRecord.GoalsFor),
|
||||
"Goals For",
|
||||
fmt.Sprint(preview.AwayRecord.GoalsFor),
|
||||
preview.HomeRecord.GoalsFor > preview.AwayRecord.GoalsFor,
|
||||
preview.AwayRecord.GoalsFor > preview.HomeRecord.GoalsFor,
|
||||
)
|
||||
@previewStatRow(
|
||||
fmt.Sprint(preview.HomeRecord.GoalsAgainst),
|
||||
"Goals Against",
|
||||
fmt.Sprint(preview.AwayRecord.GoalsAgainst),
|
||||
preview.HomeRecord.GoalsAgainst < preview.AwayRecord.GoalsAgainst,
|
||||
preview.AwayRecord.GoalsAgainst < preview.HomeRecord.GoalsAgainst,
|
||||
)
|
||||
{{
|
||||
homeDiff := preview.HomeRecord.GoalsFor - preview.HomeRecord.GoalsAgainst
|
||||
awayDiff := preview.AwayRecord.GoalsFor - preview.AwayRecord.GoalsAgainst
|
||||
}}
|
||||
@previewStatRow(
|
||||
fmt.Sprintf("%+d", homeDiff),
|
||||
"Goal Diff",
|
||||
fmt.Sprintf("%+d", awayDiff),
|
||||
homeDiff > awayDiff,
|
||||
awayDiff > homeDiff,
|
||||
)
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
// seriesPreviewFormGuide renders recent form for each team.
|
||||
templ seriesPreviewFormGuide(series *db.PlayoffSeries, preview *db.MatchPreviewData) {
|
||||
<div class="bg-mantle border border-surface1 rounded-lg overflow-hidden">
|
||||
<div class="bg-surface0 border-b border-surface1 px-6 py-3">
|
||||
<h2 class="text-lg font-bold text-text">Recent Form</h2>
|
||||
</div>
|
||||
<div class="p-6">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-8">
|
||||
<!-- Team 1 Form -->
|
||||
<div>
|
||||
<div class="flex items-center gap-2 mb-4">
|
||||
if series.Team1 != nil && series.Team1.Color != "" {
|
||||
<span
|
||||
class="w-3 h-3 rounded-full shrink-0"
|
||||
style={ "background-color: " + templ.SafeCSS(series.Team1.Color) }
|
||||
></span>
|
||||
}
|
||||
<h3 class="text-md font-bold text-text">{ seriesTeamName(series.Team1) }</h3>
|
||||
</div>
|
||||
if len(preview.HomeRecentGames) == 0 {
|
||||
<p class="text-subtext1 text-sm">No recent matches played</p>
|
||||
} else {
|
||||
<div class="flex items-center gap-1.5 mb-4">
|
||||
for _, g := range preview.HomeRecentGames {
|
||||
@gameOutcomeIcon(g)
|
||||
}
|
||||
</div>
|
||||
<div class="space-y-1.5">
|
||||
for i := len(preview.HomeRecentGames) - 1; i >= 0; i-- {
|
||||
@recentGameRow(preview.HomeRecentGames[i])
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<!-- Team 2 Form -->
|
||||
<div>
|
||||
<div class="flex items-center gap-2 mb-4">
|
||||
if series.Team2 != nil && series.Team2.Color != "" {
|
||||
<span
|
||||
class="w-3 h-3 rounded-full shrink-0"
|
||||
style={ "background-color: " + templ.SafeCSS(series.Team2.Color) }
|
||||
></span>
|
||||
}
|
||||
<h3 class="text-md font-bold text-text">{ seriesTeamName(series.Team2) }</h3>
|
||||
</div>
|
||||
if len(preview.AwayRecentGames) == 0 {
|
||||
<p class="text-subtext1 text-sm">No recent matches played</p>
|
||||
} else {
|
||||
<div class="flex items-center gap-1.5 mb-4">
|
||||
for _, g := range preview.AwayRecentGames {
|
||||
@gameOutcomeIcon(g)
|
||||
}
|
||||
</div>
|
||||
<div class="space-y-1.5">
|
||||
for i := len(preview.AwayRecentGames) - 1; i >= 0; i-- {
|
||||
@recentGameRow(preview.AwayRecentGames[i])
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
// seriesPreviewRosters renders team rosters side-by-side.
|
||||
templ seriesPreviewRosters(series *db.PlayoffSeries, rosters map[string][]*db.PlayerWithPlayStatus) {
|
||||
<div class="bg-mantle border border-surface1 rounded-lg overflow-hidden">
|
||||
<div class="bg-surface0 border-b border-surface1 px-6 py-3">
|
||||
<h2 class="text-lg font-bold text-text">Team Rosters</h2>
|
||||
</div>
|
||||
<div class="p-6">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
if series.Team1 != nil {
|
||||
@seriesPreviewRosterColumn(series.Team1, rosters["team1"], series.Bracket.Season, series.Bracket.League)
|
||||
}
|
||||
if series.Team2 != nil {
|
||||
@seriesPreviewRosterColumn(series.Team2, rosters["team2"], series.Bracket.Season, series.Bracket.League)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
templ seriesPreviewRosterColumn(
|
||||
team *db.Team,
|
||||
players []*db.PlayerWithPlayStatus,
|
||||
season *db.Season,
|
||||
league *db.League,
|
||||
) {
|
||||
{{
|
||||
var managers []*db.PlayerWithPlayStatus
|
||||
var roster []*db.PlayerWithPlayStatus
|
||||
for _, p := range players {
|
||||
if p.IsManager {
|
||||
managers = append(managers, p)
|
||||
} else {
|
||||
roster = append(roster, p)
|
||||
}
|
||||
}
|
||||
sort.Slice(roster, func(i, j int) bool {
|
||||
return roster[i].Player.DisplayName() < roster[j].Player.DisplayName()
|
||||
})
|
||||
}}
|
||||
<div>
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<div class="flex items-center gap-2">
|
||||
if team.Color != "" {
|
||||
<span
|
||||
class="w-3.5 h-3.5 rounded-full shrink-0 border border-surface1"
|
||||
style={ "background-color: " + templ.SafeCSS(team.Color) }
|
||||
></span>
|
||||
}
|
||||
<h3 class="text-md font-bold">
|
||||
@links.TeamNameLinkInSeason(team, season, league)
|
||||
</h3>
|
||||
</div>
|
||||
<span class="text-xs text-subtext0">
|
||||
{ fmt.Sprint(len(players)) } players
|
||||
</span>
|
||||
</div>
|
||||
if len(players) == 0 {
|
||||
<p class="text-subtext1 text-sm text-center py-4">No players on roster.</p>
|
||||
} else {
|
||||
<div class="space-y-1">
|
||||
for _, p := range managers {
|
||||
<div class="flex items-center gap-2 px-3 py-2 rounded-lg bg-surface0 border border-surface1">
|
||||
<span class="px-1.5 py-0.5 bg-yellow/20 text-yellow rounded text-xs font-medium shrink-0">
|
||||
★
|
||||
</span>
|
||||
<span class="text-sm font-medium">
|
||||
@links.PlayerLink(p.Player)
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
for _, p := range roster {
|
||||
<div class="flex items-center gap-2 px-3 py-2 rounded-lg hover:bg-surface0 transition">
|
||||
<span class="text-sm">
|
||||
@links.PlayerLink(p.Player)
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
Reference in New Issue
Block a user