Files
oslstats/internal/view/seasonsview/series_match_analysis.templ
2026-03-15 12:05:47 +11:00

252 lines
8.4 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package seasonsview
import "git.haelnorr.com/h/oslstats/internal/db"
import "git.haelnorr.com/h/oslstats/internal/view/component/links"
import "fmt"
// seriesMatchAnalysisTab renders the full Match Analysis tab for completed series.
// Shows final series score, individual match results, aggregated team stats,
// top performers, and league context.
templ seriesMatchAnalysisTab(
series *db.PlayoffSeries,
rosters map[string][]*db.PlayerWithPlayStatus,
preview *db.MatchPreviewData,
) {
<div class="space-y-6">
<!-- Final Series Score -->
@seriesAnalysisScoreHeader(series)
<!-- Individual Match Results -->
if len(series.Matches) > 0 {
@seriesAnalysisMatchResults(series)
}
<!-- League Context (from preview data) -->
if preview != nil {
@seriesAnalysisLeagueContext(series, preview)
}
</div>
}
// seriesAnalysisScoreHeader renders the final series score in a prominent display.
templ seriesAnalysisScoreHeader(series *db.PlayoffSeries) {
{{
team1Won := series.WinnerTeamID != nil && series.Team1ID != nil && *series.WinnerTeamID == *series.Team1ID
team2Won := series.WinnerTeamID != nil && series.Team2ID != nil && *series.WinnerTeamID == *series.Team2ID
}}
<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 Series Score</h2>
</div>
<div class="p-6">
<div class="flex items-center justify-center gap-6 sm:gap-10">
<!-- Team 1 -->
<div class="flex flex-col items-center text-center flex-1">
if series.Team1 != nil && series.Team1.Color != "" {
<div
class="w-12 h-12 rounded-full border-2 border-surface1 mb-2 shrink-0"
style={ "background-color: " + templ.SafeCSS(series.Team1.Color) }
></div>
}
<h3 class="text-lg sm:text-xl font-bold text-text mb-1">
if series.Team1 != nil {
@links.TeamNameLinkInSeason(series.Team1, series.Bracket.Season, series.Bracket.League)
} else {
TBD
}
</h3>
<span class={ "text-5xl sm:text-6xl font-bold", templ.KV("text-green", team1Won), templ.KV("text-text", !team1Won) }>
{ fmt.Sprint(series.Team1Wins) }
</span>
if team1Won {
<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>
</div>
<!-- Team 2 -->
<div class="flex flex-col items-center text-center flex-1">
if series.Team2 != nil && series.Team2.Color != "" {
<div
class="w-12 h-12 rounded-full border-2 border-surface1 mb-2 shrink-0"
style={ "background-color: " + templ.SafeCSS(series.Team2.Color) }
></div>
}
<h3 class="text-lg sm:text-xl font-bold text-text mb-1">
if series.Team2 != nil {
@links.TeamNameLinkInSeason(series.Team2, series.Bracket.Season, series.Bracket.League)
} else {
TBD
}
</h3>
<span class={ "text-5xl sm:text-6xl font-bold", templ.KV("text-green", team2Won), templ.KV("text-text", !team2Won) }>
{ fmt.Sprint(series.Team2Wins) }
</span>
if team2Won {
<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>
}
// seriesAnalysisMatchResults shows individual match results as a compact list.
templ seriesAnalysisMatchResults(series *db.PlayoffSeries) {
<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">Match Results</h2>
</div>
<div class="divide-y divide-surface1">
for _, match := range series.Matches {
@seriesAnalysisMatchRow(series, match)
}
</div>
</div>
}
templ seriesAnalysisMatchRow(series *db.PlayoffSeries, match *db.PlayoffMatch) {
{{
matchLabel := fmt.Sprintf("Game %d", match.MatchNumber)
isCompleted := match.Status == "completed"
}}
<div class="flex items-center justify-between px-6 py-3 hover:bg-surface0 transition-colors">
<div class="flex items-center gap-4">
<span class="text-sm font-medium text-subtext0 w-16">{ matchLabel }</span>
if isCompleted {
<span class="px-1.5 py-0.5 bg-green/20 text-green rounded text-xs">
Complete
</span>
} else {
<span class="px-1.5 py-0.5 bg-surface1 text-subtext0 rounded text-xs">
{ match.Status }
</span>
}
</div>
if match.FixtureID != nil {
<a
href={ templ.SafeURL(fmt.Sprintf("/fixtures/%d/overview", *match.FixtureID)) }
class="px-3 py-1 bg-surface1 hover:bg-surface2 text-text rounded text-xs
font-medium transition hover:cursor-pointer"
>
View Details
</a>
}
</div>
}
// seriesAnalysisLeagueContext shows how the teams sit in the league standings.
templ seriesAnalysisLeagueContext(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">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 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>
}
<span class="text-sm font-bold text-text">{ seriesTeamShortName(series.Team1) }</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">{ seriesTeamShortName(series.Team2) }</span>
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>
}
</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>
}