612 lines
19 KiB
Plaintext
612 lines
19 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"
|
||
import "strings"
|
||
|
||
// teamAggStats holds aggregated stats for a single team in a fixture.
|
||
type teamAggStats struct {
|
||
Goals int
|
||
Assists int
|
||
PrimaryAssists int
|
||
SecondaryAssists int
|
||
Saves int
|
||
Shots int
|
||
Blocks int
|
||
Passes int
|
||
Turnovers int
|
||
Takeaways int
|
||
FaceoffsWon int
|
||
FaceoffsLost int
|
||
PostHits int
|
||
PossessionSec int
|
||
PlayersUsed int
|
||
}
|
||
|
||
func aggregateTeamStats(players []*db.PlayerWithPlayStatus) *teamAggStats {
|
||
agg := &teamAggStats{}
|
||
for _, p := range players {
|
||
if !p.Played || p.Stats == nil {
|
||
continue
|
||
}
|
||
agg.PlayersUsed++
|
||
if p.Stats.Goals != nil {
|
||
agg.Goals += *p.Stats.Goals
|
||
}
|
||
if p.Stats.Assists != nil {
|
||
agg.Assists += *p.Stats.Assists
|
||
}
|
||
if p.Stats.PrimaryAssists != nil {
|
||
agg.PrimaryAssists += *p.Stats.PrimaryAssists
|
||
}
|
||
if p.Stats.SecondaryAssists != nil {
|
||
agg.SecondaryAssists += *p.Stats.SecondaryAssists
|
||
}
|
||
if p.Stats.Saves != nil {
|
||
agg.Saves += *p.Stats.Saves
|
||
}
|
||
if p.Stats.Shots != nil {
|
||
agg.Shots += *p.Stats.Shots
|
||
}
|
||
if p.Stats.Blocks != nil {
|
||
agg.Blocks += *p.Stats.Blocks
|
||
}
|
||
if p.Stats.Passes != nil {
|
||
agg.Passes += *p.Stats.Passes
|
||
}
|
||
if p.Stats.Turnovers != nil {
|
||
agg.Turnovers += *p.Stats.Turnovers
|
||
}
|
||
if p.Stats.Takeaways != nil {
|
||
agg.Takeaways += *p.Stats.Takeaways
|
||
}
|
||
if p.Stats.FaceoffsWon != nil {
|
||
agg.FaceoffsWon += *p.Stats.FaceoffsWon
|
||
}
|
||
if p.Stats.FaceoffsLost != nil {
|
||
agg.FaceoffsLost += *p.Stats.FaceoffsLost
|
||
}
|
||
if p.Stats.PostHits != nil {
|
||
agg.PostHits += *p.Stats.PostHits
|
||
}
|
||
if p.Stats.PossessionTimeSec != nil {
|
||
agg.PossessionSec += *p.Stats.PossessionTimeSec
|
||
}
|
||
}
|
||
return agg
|
||
}
|
||
|
||
func formatPossession(seconds int) string {
|
||
m := seconds / 60
|
||
s := seconds % 60
|
||
return fmt.Sprintf("%d:%02d", m, s)
|
||
}
|
||
|
||
func faceoffPct(won, lost int) string {
|
||
total := won + lost
|
||
if total == 0 {
|
||
return "0%"
|
||
}
|
||
pct := float64(won) / float64(total) * 100
|
||
return fmt.Sprintf("%.0f%%", pct)
|
||
}
|
||
|
||
// fixtureMatchAnalysisTab renders the full Match Analysis tab for completed fixtures.
|
||
// Shows score, team stats comparison, match details, and top performers.
|
||
templ fixtureMatchAnalysisTab(
|
||
fixture *db.Fixture,
|
||
result *db.FixtureResult,
|
||
rosters map[string][]*db.PlayerWithPlayStatus,
|
||
preview *db.MatchPreviewData,
|
||
) {
|
||
<div class="space-y-6">
|
||
<!-- Score Display -->
|
||
@analysisScoreHeader(fixture, result)
|
||
|
||
<!-- Team Stats Comparison -->
|
||
@analysisTeamStatsComparison(fixture, rosters)
|
||
|
||
<!-- Top Performers -->
|
||
@analysisTopPerformers(fixture, rosters)
|
||
|
||
<!-- Standings Context (from preview data) -->
|
||
if preview != nil {
|
||
@analysisStandingsContext(fixture, preview)
|
||
}
|
||
</div>
|
||
}
|
||
|
||
// analysisScoreHeader renders the final score in a prominent broadcast-style display.
|
||
templ analysisScoreHeader(fixture *db.Fixture, result *db.FixtureResult) {
|
||
{{
|
||
isOT := strings.EqualFold(result.EndReason, "Overtime")
|
||
homeWon := result.Winner == "home"
|
||
awayWon := result.Winner == "away"
|
||
isForfeit := result.IsForfeit
|
||
}}
|
||
<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">Final Score</h2>
|
||
</div>
|
||
<div class="p-6">
|
||
if isForfeit {
|
||
@analysisForfeitDisplay(fixture, result)
|
||
} else {
|
||
<div class="flex items-center justify-center gap-6 sm:gap-10">
|
||
<!-- Home Team -->
|
||
<div class="flex flex-col items-center text-center flex-1">
|
||
if fixture.HomeTeam.Color != "" {
|
||
<div
|
||
class="w-12 h-12 rounded-full border-2 border-surface1 mb-2 shrink-0"
|
||
style={ "background-color: " + templ.SafeCSS(fixture.HomeTeam.Color) }
|
||
></div>
|
||
}
|
||
<h3 class="text-lg sm:text-xl font-bold text-text mb-1">
|
||
@links.TeamNameLinkInSeason(fixture.HomeTeam, fixture.Season, fixture.League)
|
||
</h3>
|
||
<span class={ "text-5xl sm:text-6xl font-bold", templ.KV("text-green", homeWon), templ.KV("text-text", !homeWon) }>
|
||
{ fmt.Sprint(result.HomeScore) }
|
||
</span>
|
||
if homeWon {
|
||
<span class="mt-2 px-3 py-1 bg-green/20 text-green rounded-full text-xs font-bold uppercase tracking-wider">Winner</span>
|
||
}
|
||
</div>
|
||
<!-- Divider -->
|
||
<div class="flex flex-col items-center shrink-0">
|
||
<span class="text-4xl text-subtext0 font-light">–</span>
|
||
if isOT {
|
||
<span class="mt-1 px-2 py-0.5 bg-peach/20 text-peach rounded text-xs font-bold">OT</span>
|
||
}
|
||
</div>
|
||
<!-- Away Team -->
|
||
<div class="flex flex-col items-center text-center flex-1">
|
||
if fixture.AwayTeam.Color != "" {
|
||
<div
|
||
class="w-12 h-12 rounded-full border-2 border-surface1 mb-2 shrink-0"
|
||
style={ "background-color: " + templ.SafeCSS(fixture.AwayTeam.Color) }
|
||
></div>
|
||
}
|
||
<h3 class="text-lg sm:text-xl font-bold text-text mb-1">
|
||
@links.TeamNameLinkInSeason(fixture.AwayTeam, fixture.Season, fixture.League)
|
||
</h3>
|
||
<span class={ "text-5xl sm:text-6xl font-bold", templ.KV("text-green", awayWon), templ.KV("text-text", !awayWon) }>
|
||
{ fmt.Sprint(result.AwayScore) }
|
||
</span>
|
||
if awayWon {
|
||
<span class="mt-2 px-3 py-1 bg-green/20 text-green rounded-full text-xs font-bold uppercase tracking-wider">Winner</span>
|
||
}
|
||
</div>
|
||
</div>
|
||
}
|
||
</div>
|
||
</div>
|
||
}
|
||
|
||
// analysisForfeitDisplay renders a forfeit result in the analysis header.
|
||
templ analysisForfeitDisplay(fixture *db.Fixture, result *db.FixtureResult) {
|
||
{{
|
||
isMutualForfeit := result.ForfeitType != nil && *result.ForfeitType == "mutual"
|
||
isOutrightForfeit := result.ForfeitType != nil && *result.ForfeitType == "outright"
|
||
forfeitTeamName := ""
|
||
winnerTeamName := ""
|
||
if isOutrightForfeit && result.ForfeitTeam != nil {
|
||
if *result.ForfeitTeam == "home" {
|
||
forfeitTeamName = fixture.HomeTeam.Name
|
||
winnerTeamName = fixture.AwayTeam.Name
|
||
} else {
|
||
forfeitTeamName = fixture.AwayTeam.Name
|
||
winnerTeamName = fixture.HomeTeam.Name
|
||
}
|
||
}
|
||
}}
|
||
<div class="flex flex-col items-center py-4 space-y-4">
|
||
if isMutualForfeit {
|
||
<span class="px-4 py-2 bg-peach/20 text-peach rounded-lg text-lg font-bold">MUTUAL FORFEIT</span>
|
||
<p class="text-sm text-subtext0">Both teams receive an overtime loss</p>
|
||
} else if isOutrightForfeit {
|
||
<span class="px-4 py-2 bg-red/20 text-red rounded-lg text-lg font-bold">FORFEIT</span>
|
||
<p class="text-sm text-subtext0">
|
||
{ forfeitTeamName } forfeited — { winnerTeamName } wins
|
||
</p>
|
||
}
|
||
if result.ForfeitReason != nil && *result.ForfeitReason != "" {
|
||
<div class="bg-surface0 border border-surface1 rounded-lg p-3 max-w-md w-full text-center">
|
||
<p class="text-xs text-subtext1 font-medium mb-1">Reason</p>
|
||
<p class="text-sm text-subtext0">{ *result.ForfeitReason }</p>
|
||
</div>
|
||
}
|
||
</div>
|
||
}
|
||
|
||
// analysisTeamStatsComparison renders aggregated team stats in the broadcast comparison layout.
|
||
templ analysisTeamStatsComparison(fixture *db.Fixture, rosters map[string][]*db.PlayerWithPlayStatus) {
|
||
{{
|
||
homeAgg := aggregateTeamStats(rosters["home"])
|
||
awayAgg := aggregateTeamStats(rosters["away"])
|
||
}}
|
||
<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 Statistics</h2>
|
||
</div>
|
||
<div class="p-6">
|
||
<!-- Team Name Headers -->
|
||
<div class="flex items-center mb-4">
|
||
<div class="flex-1 text-right pr-4">
|
||
<div class="flex items-center justify-end gap-2">
|
||
if fixture.HomeTeam.Color != "" {
|
||
<span
|
||
class="w-3 h-3 rounded-full shrink-0"
|
||
style={ "background-color: " + templ.SafeCSS(fixture.HomeTeam.Color) }
|
||
></span>
|
||
}
|
||
<span class="text-sm font-bold text-text">{ fixture.HomeTeam.ShortName }</span>
|
||
</div>
|
||
</div>
|
||
<div class="w-28 sm:w-36 text-center shrink-0"></div>
|
||
<div class="flex-1 text-left pl-4">
|
||
<div class="flex items-center gap-2">
|
||
<span class="text-sm font-bold text-text">{ fixture.AwayTeam.ShortName }</span>
|
||
if fixture.AwayTeam.Color != "" {
|
||
<span
|
||
class="w-3 h-3 rounded-full shrink-0"
|
||
style={ "background-color: " + templ.SafeCSS(fixture.AwayTeam.Color) }
|
||
></span>
|
||
}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<!-- Stats Rows -->
|
||
<div class="space-y-0">
|
||
@previewStatRow(
|
||
fmt.Sprint(homeAgg.Goals),
|
||
"Goals",
|
||
fmt.Sprint(awayAgg.Goals),
|
||
homeAgg.Goals > awayAgg.Goals,
|
||
awayAgg.Goals > homeAgg.Goals,
|
||
)
|
||
@previewStatRow(
|
||
fmt.Sprint(homeAgg.Assists),
|
||
"Assists",
|
||
fmt.Sprint(awayAgg.Assists),
|
||
homeAgg.Assists > awayAgg.Assists,
|
||
awayAgg.Assists > homeAgg.Assists,
|
||
)
|
||
@previewStatRow(
|
||
fmt.Sprint(homeAgg.Shots),
|
||
"Shots",
|
||
fmt.Sprint(awayAgg.Shots),
|
||
homeAgg.Shots > awayAgg.Shots,
|
||
awayAgg.Shots > homeAgg.Shots,
|
||
)
|
||
@previewStatRow(
|
||
fmt.Sprint(homeAgg.Saves),
|
||
"Saves",
|
||
fmt.Sprint(awayAgg.Saves),
|
||
homeAgg.Saves > awayAgg.Saves,
|
||
awayAgg.Saves > homeAgg.Saves,
|
||
)
|
||
@previewStatRow(
|
||
fmt.Sprint(homeAgg.Blocks),
|
||
"Blocks",
|
||
fmt.Sprint(awayAgg.Blocks),
|
||
homeAgg.Blocks > awayAgg.Blocks,
|
||
awayAgg.Blocks > homeAgg.Blocks,
|
||
)
|
||
@previewStatRow(
|
||
fmt.Sprint(homeAgg.Passes),
|
||
"Passes",
|
||
fmt.Sprint(awayAgg.Passes),
|
||
homeAgg.Passes > awayAgg.Passes,
|
||
awayAgg.Passes > homeAgg.Passes,
|
||
)
|
||
@previewStatRow(
|
||
fmt.Sprint(homeAgg.Takeaways),
|
||
"Takeaways",
|
||
fmt.Sprint(awayAgg.Takeaways),
|
||
homeAgg.Takeaways > awayAgg.Takeaways,
|
||
awayAgg.Takeaways > homeAgg.Takeaways,
|
||
)
|
||
@previewStatRow(
|
||
fmt.Sprint(homeAgg.Turnovers),
|
||
"Turnovers",
|
||
fmt.Sprint(awayAgg.Turnovers),
|
||
homeAgg.Turnovers < awayAgg.Turnovers,
|
||
awayAgg.Turnovers < homeAgg.Turnovers,
|
||
)
|
||
<!-- Faceoffs -->
|
||
{{
|
||
homeFO := homeAgg.FaceoffsWon + homeAgg.FaceoffsLost
|
||
awayFO := awayAgg.FaceoffsWon + awayAgg.FaceoffsLost
|
||
homeFOStr := fmt.Sprintf("%d/%d", homeAgg.FaceoffsWon, homeFO)
|
||
awayFOStr := fmt.Sprintf("%d/%d", awayAgg.FaceoffsWon, awayFO)
|
||
}}
|
||
@previewStatRow(
|
||
homeFOStr,
|
||
"Faceoffs Won",
|
||
awayFOStr,
|
||
homeAgg.FaceoffsWon > awayAgg.FaceoffsWon,
|
||
awayAgg.FaceoffsWon > homeAgg.FaceoffsWon,
|
||
)
|
||
@previewStatRow(
|
||
faceoffPct(homeAgg.FaceoffsWon, homeAgg.FaceoffsLost),
|
||
"Faceoff %",
|
||
faceoffPct(awayAgg.FaceoffsWon, awayAgg.FaceoffsLost),
|
||
homeAgg.FaceoffsWon * (awayAgg.FaceoffsWon + awayAgg.FaceoffsLost) > awayAgg.FaceoffsWon * (homeAgg.FaceoffsWon + homeAgg.FaceoffsLost),
|
||
awayAgg.FaceoffsWon * (homeAgg.FaceoffsWon + homeAgg.FaceoffsLost) > homeAgg.FaceoffsWon * (awayAgg.FaceoffsWon + awayAgg.FaceoffsLost),
|
||
)
|
||
@previewStatRow(
|
||
fmt.Sprint(homeAgg.PostHits),
|
||
"Post Hits",
|
||
fmt.Sprint(awayAgg.PostHits),
|
||
homeAgg.PostHits > awayAgg.PostHits,
|
||
awayAgg.PostHits > homeAgg.PostHits,
|
||
)
|
||
@previewStatRow(
|
||
formatPossession(homeAgg.PossessionSec),
|
||
"Possession",
|
||
formatPossession(awayAgg.PossessionSec),
|
||
homeAgg.PossessionSec > awayAgg.PossessionSec,
|
||
awayAgg.PossessionSec > homeAgg.PossessionSec,
|
||
)
|
||
@previewStatRow(
|
||
fmt.Sprint(homeAgg.PlayersUsed),
|
||
"Players Used",
|
||
fmt.Sprint(awayAgg.PlayersUsed),
|
||
false,
|
||
false,
|
||
)
|
||
</div>
|
||
</div>
|
||
</div>
|
||
}
|
||
|
||
// analysisTopPerformers shows the top players from each team based on score.
|
||
templ analysisTopPerformers(fixture *db.Fixture, rosters map[string][]*db.PlayerWithPlayStatus) {
|
||
{{
|
||
// Collect players who played and have stats, sorted by score descending
|
||
type scoredPlayer struct {
|
||
Player *db.Player
|
||
Stats *db.FixtureResultPlayerStats
|
||
IsManager bool
|
||
IsFreeAgent bool
|
||
}
|
||
|
||
collectTop := func(players []*db.PlayerWithPlayStatus, limit int) []*scoredPlayer {
|
||
var scored []*scoredPlayer
|
||
for _, p := range players {
|
||
if !p.Played || p.Stats == nil || p.Player == nil {
|
||
continue
|
||
}
|
||
scored = append(scored, &scoredPlayer{
|
||
Player: p.Player,
|
||
Stats: p.Stats,
|
||
IsManager: p.IsManager,
|
||
IsFreeAgent: p.IsFreeAgent,
|
||
})
|
||
}
|
||
sort.Slice(scored, func(i, j int) bool {
|
||
si, sj := 0, 0
|
||
if scored[i].Stats.Score != nil {
|
||
si = *scored[i].Stats.Score
|
||
}
|
||
if scored[j].Stats.Score != nil {
|
||
sj = *scored[j].Stats.Score
|
||
}
|
||
return si > sj
|
||
})
|
||
if len(scored) > limit {
|
||
scored = scored[:limit]
|
||
}
|
||
return scored
|
||
}
|
||
|
||
homeTop := collectTop(rosters["home"], 3)
|
||
awayTop := collectTop(rosters["away"], 3)
|
||
}}
|
||
if len(homeTop) > 0 || len(awayTop) > 0 {
|
||
<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">Top Performers</h2>
|
||
</div>
|
||
<div class="p-6">
|
||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||
<!-- Home Top Performers -->
|
||
<div>
|
||
<div class="flex items-center gap-2 mb-3">
|
||
if fixture.HomeTeam.Color != "" {
|
||
<span
|
||
class="w-3 h-3 rounded-full shrink-0"
|
||
style={ "background-color: " + templ.SafeCSS(fixture.HomeTeam.Color) }
|
||
></span>
|
||
}
|
||
<h3 class="text-md font-bold text-text">{ fixture.HomeTeam.Name }</h3>
|
||
</div>
|
||
<div class="space-y-2">
|
||
for i, p := range homeTop {
|
||
@topPerformerCard(p.Player, p.Stats, p.IsManager, p.IsFreeAgent, i+1)
|
||
}
|
||
</div>
|
||
</div>
|
||
<!-- Away Top Performers -->
|
||
<div>
|
||
<div class="flex items-center gap-2 mb-3">
|
||
if fixture.AwayTeam.Color != "" {
|
||
<span
|
||
class="w-3 h-3 rounded-full shrink-0"
|
||
style={ "background-color: " + templ.SafeCSS(fixture.AwayTeam.Color) }
|
||
></span>
|
||
}
|
||
<h3 class="text-md font-bold text-text">{ fixture.AwayTeam.Name }</h3>
|
||
</div>
|
||
<div class="space-y-2">
|
||
for i, p := range awayTop {
|
||
@topPerformerCard(p.Player, p.Stats, p.IsManager, p.IsFreeAgent, i+1)
|
||
}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
}
|
||
}
|
||
|
||
// topPerformerCard renders a single top performer card with key stats.
|
||
templ topPerformerCard(player *db.Player, stats *db.FixtureResultPlayerStats, isManager bool, isFreeAgent bool, rank int) {
|
||
{{
|
||
rankLabels := map[int]string{1: "🥇", 2: "🥈", 3: "🥉"}
|
||
rankLabel := rankLabels[rank]
|
||
}}
|
||
<div class="flex items-center gap-3 px-4 py-3 bg-surface0 border border-surface1 rounded-lg">
|
||
<span class="text-lg shrink-0">{ rankLabel }</span>
|
||
<div class="flex-1 min-w-0">
|
||
<div class="flex items-center gap-1.5">
|
||
<span class="text-sm font-medium truncate">
|
||
@links.PlayerLink(player)
|
||
</span>
|
||
if isManager {
|
||
<span class="px-1 py-0.5 bg-yellow/20 text-yellow rounded text-xs font-medium shrink-0">
|
||
★
|
||
</span>
|
||
}
|
||
if isFreeAgent {
|
||
<span class="px-1 py-0.5 bg-peach/20 text-peach rounded text-xs font-medium shrink-0">
|
||
FA
|
||
</span>
|
||
}
|
||
</div>
|
||
<div class="flex items-center gap-3 mt-1 text-xs text-subtext0">
|
||
if stats.Score != nil {
|
||
<span title="Score"><span class="font-semibold text-text">{ fmt.Sprint(*stats.Score) }</span> SC</span>
|
||
}
|
||
if stats.Goals != nil {
|
||
<span title="Goals"><span class="font-semibold text-text">{ fmt.Sprint(*stats.Goals) }</span> G</span>
|
||
}
|
||
if stats.Assists != nil {
|
||
<span title="Assists"><span class="font-semibold text-text">{ fmt.Sprint(*stats.Assists) }</span> A</span>
|
||
}
|
||
if stats.Saves != nil {
|
||
<span title="Saves"><span class="font-semibold text-text">{ fmt.Sprint(*stats.Saves) }</span> SV</span>
|
||
}
|
||
if stats.Shots != nil {
|
||
<span title="Shots"><span class="font-semibold text-text">{ fmt.Sprint(*stats.Shots) }</span> SH</span>
|
||
}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
}
|
||
|
||
// analysisStandingsContext shows how this result fits into the league standings.
|
||
templ analysisStandingsContext(fixture *db.Fixture, 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">League Context</h2>
|
||
</div>
|
||
<div class="p-6">
|
||
<!-- Team Name Headers -->
|
||
<div class="flex items-center mb-4">
|
||
<div class="flex-1 text-right pr-4">
|
||
<div class="flex items-center justify-end gap-2">
|
||
if fixture.HomeTeam.Color != "" {
|
||
<span
|
||
class="w-3 h-3 rounded-full shrink-0"
|
||
style={ "background-color: " + templ.SafeCSS(fixture.HomeTeam.Color) }
|
||
></span>
|
||
}
|
||
<span class="text-sm font-bold text-text">{ fixture.HomeTeam.ShortName }</span>
|
||
</div>
|
||
</div>
|
||
<div class="w-28 sm:w-36 text-center shrink-0"></div>
|
||
<div class="flex-1 text-left pl-4">
|
||
<div class="flex items-center gap-2">
|
||
<span class="text-sm font-bold text-text">{ fixture.AwayTeam.ShortName }</span>
|
||
if fixture.AwayTeam.Color != "" {
|
||
<span
|
||
class="w-3 h-3 rounded-full shrink-0"
|
||
style={ "background-color: " + templ.SafeCSS(fixture.AwayTeam.Color) }
|
||
></span>
|
||
}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="space-y-0">
|
||
{{
|
||
homePos := ordinal(preview.HomePosition)
|
||
awayPos := ordinal(preview.AwayPosition)
|
||
if preview.HomePosition == 0 {
|
||
homePos = "N/A"
|
||
}
|
||
if preview.AwayPosition == 0 {
|
||
awayPos = "N/A"
|
||
}
|
||
}}
|
||
@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.Sprintf("%d-%d-%d-%d",
|
||
preview.HomeRecord.Wins,
|
||
preview.HomeRecord.OvertimeWins,
|
||
preview.HomeRecord.OvertimeLosses,
|
||
preview.HomeRecord.Losses,
|
||
),
|
||
"Record",
|
||
fmt.Sprintf("%d-%d-%d-%d",
|
||
preview.AwayRecord.Wins,
|
||
preview.AwayRecord.OvertimeWins,
|
||
preview.AwayRecord.OvertimeLosses,
|
||
preview.AwayRecord.Losses,
|
||
),
|
||
false,
|
||
false,
|
||
)
|
||
{{
|
||
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,
|
||
)
|
||
<!-- Recent Form -->
|
||
if len(preview.HomeRecentGames) > 0 || len(preview.AwayRecentGames) > 0 {
|
||
<div class="flex items-center py-3 border-b border-surface1 last:border-b-0">
|
||
<div class="flex-1 flex justify-end pr-4">
|
||
<div class="flex items-center gap-1">
|
||
for _, g := range preview.HomeRecentGames {
|
||
@gameOutcomeIcon(g)
|
||
}
|
||
</div>
|
||
</div>
|
||
<div class="w-28 sm:w-36 text-center shrink-0">
|
||
<span class="text-xs font-semibold text-subtext0 uppercase tracking-wider">Form</span>
|
||
</div>
|
||
<div class="flex-1 flex pl-4">
|
||
<div class="flex items-center gap-1">
|
||
for _, g := range preview.AwayRecentGames {
|
||
@gameOutcomeIcon(g)
|
||
}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
}
|