series overview added
This commit is contained in:
251
internal/view/seasonsview/series_match_analysis.templ
Normal file
251
internal/view/seasonsview/series_match_analysis.templ
Normal file
@@ -0,0 +1,251 @@
|
||||
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>
|
||||
}
|
||||
Reference in New Issue
Block a user