252 lines
8.4 KiB
Plaintext
252 lines
8.4 KiB
Plaintext
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>
|
||
}
|