320 lines
10 KiB
Plaintext
320 lines
10 KiB
Plaintext
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>
|
|
}
|