package seasonsview
import "git.haelnorr.com/h/oslstats/internal/db"
import "git.haelnorr.com/h/oslstats/internal/permissions"
import "git.haelnorr.com/h/oslstats/internal/contexts"
import "git.haelnorr.com/h/oslstats/internal/view/baseview"
import "fmt"
import "sort"
import "strings"
templ FixtureDetailPage(
fixture *db.Fixture,
currentSchedule *db.FixtureSchedule,
history []*db.FixtureSchedule,
canSchedule bool,
userTeamID int,
result *db.FixtureResult,
rosters map[string][]*db.PlayerWithPlayStatus,
activeTab string,
) {
{{
permCache := contexts.Permissions(ctx)
canManage := permCache.HasPermission(permissions.FixturesManage)
backURL := fmt.Sprintf("/seasons/%s/leagues/%s/fixtures", fixture.Season.ShortName, fixture.League.ShortName)
isFinalized := result != nil && result.Finalized
if activeTab == "" {
activeTab = "overview"
}
// Force overview if schedule tab is hidden (result finalized)
if isFinalized && activeTab == "schedule" {
activeTab = "overview"
}
}}
@baseview.Layout(fmt.Sprintf("%s vs %s", fixture.HomeTeam.Name, fixture.AwayTeam.Name)) {
{ fixture.HomeTeam.Name }
vs
{ fixture.AwayTeam.Name }
Round { fmt.Sprint(fixture.Round) }
if fixture.GameWeek != nil {
Game Week { fmt.Sprint(*fixture.GameWeek) }
}
{ fixture.Season.Name } β { fixture.League.Name }
Back to Fixtures
if !isFinalized {
}
if activeTab == "overview" {
@fixtureOverviewTab(fixture, currentSchedule, result, rosters, canManage)
} else if activeTab == "schedule" {
@fixtureScheduleTab(fixture, currentSchedule, history, canSchedule, canManage, userTeamID)
}
}
}
templ fixtureTabItem(section string, label string, activeTab string, fixture *db.Fixture) {
{{
isActive := section == activeTab
baseClasses := "inline-block px-6 py-3 transition-colors cursor-pointer border-b-2"
activeClasses := "border-blue text-blue font-semibold"
inactiveClasses := "border-transparent text-subtext0 hover:text-text hover:border-surface2"
url := fmt.Sprintf("/fixtures/%d", fixture.ID)
if section != "overview" {
url = fmt.Sprintf("/fixtures/%d?tab=%s", fixture.ID, section)
}
}}
{ label }
}
// ==================== Overview Tab ====================
templ fixtureOverviewTab(
fixture *db.Fixture,
currentSchedule *db.FixtureSchedule,
result *db.FixtureResult,
rosters map[string][]*db.PlayerWithPlayStatus,
canManage bool,
) {
if result != nil {
@fixtureResultDisplay(fixture, result)
} else if canManage {
@fixtureUploadPrompt(fixture)
} else {
No result has been uploaded for this fixture yet.
}
@fixtureScheduleSummary(fixture, currentSchedule, result)
@fixtureTeamSection(fixture.HomeTeam, rosters["home"], "home", result)
@fixtureTeamSection(fixture.AwayTeam, rosters["away"], "away", result)
}
templ fixtureScheduleSummary(fixture *db.Fixture, schedule *db.FixtureSchedule, result *db.FixtureResult) {
{{
isPlayed := result != nil && result.Finalized
}}
Schedule
if schedule == nil {
} else if schedule.Status == db.ScheduleStatusAccepted && schedule.ScheduledTime != nil {
if isPlayed {
Played
} else {
Confirmed
}
@localtime(schedule.ScheduledTime, "date")
@localtime(schedule.ScheduledTime, "time")
} else if schedule.Status == db.ScheduleStatusPending && schedule.ScheduledTime != nil {
Proposed
@localtime(schedule.ScheduledTime, "date")
@localtime(schedule.ScheduledTime, "time")
Awaiting confirmation
} else if schedule.Status == db.ScheduleStatusCancelled {
Forfeit
if schedule.RescheduleReason != nil {
{ *schedule.RescheduleReason }
}
} else {
}
}
templ fixtureResultDisplay(fixture *db.Fixture, result *db.FixtureResult) {
{{
isOT := strings.EqualFold(result.EndReason, "Overtime")
homeWon := result.Winner == "home"
awayWon := result.Winner == "away"
}}
Match Result
if result.Finalized {
Finalized
} else {
Pending Review
}
if !result.Finalized && result.TamperingDetected {
Inconsistent Data
}
if result.Finalized && result.TamperingDetected && result.TamperingReason != nil {
⚠
Inconsistent Data
{ *result.TamperingReason }
}
if !result.Finalized && result.TamperingDetected && result.TamperingReason != nil {
Warning: Inconsistent Data
{ *result.TamperingReason }
}
if homeWon {
🏆
}
if fixture.HomeTeam.Color != "" {
{ fixture.HomeTeam.ShortName }
} else {
{ fixture.HomeTeam.ShortName }
}
{ fmt.Sprint(result.HomeScore) }
β
if isOT {
OT
}
{ fmt.Sprint(result.AwayScore) }
if fixture.AwayTeam.Color != "" {
{ fixture.AwayTeam.ShortName }
} else {
{ fixture.AwayTeam.ShortName }
}
if awayWon {
🏆
}
}
templ fixtureUploadPrompt(fixture *db.Fixture) {
π
No Result Uploaded
Upload match log files to record the result of this fixture.
Upload Match Logs
}
templ fixtureTeamSection(team *db.Team, players []*db.PlayerWithPlayStatus, side string, result *db.FixtureResult) {
{{
// Separate playing and bench players
var playing []*db.PlayerWithPlayStatus
var bench []*db.PlayerWithPlayStatus
for _, p := range players {
if result == nil || p.Played {
playing = append(playing, p)
} else {
bench = append(bench, p)
}
}
showStats := result != nil && result.Finalized
if showStats {
// Sort playing players by score descending
sort.Slice(playing, func(i, j int) bool {
si, sj := 0, 0
if playing[i].Stats != nil && playing[i].Stats.Score != nil {
si = *playing[i].Stats.Score
}
if playing[j].Stats != nil && playing[j].Stats.Score != nil {
sj = *playing[j].Stats.Score
}
return si > sj
})
} else {
// Sort with managers first
sort.SliceStable(playing, func(i, j int) bool {
return playing[i].IsManager && !playing[j].IsManager
})
sort.SliceStable(bench, func(i, j int) bool {
return bench[i].IsManager && !bench[j].IsManager
})
}
}}
{ team.Name }
if team.Color != "" {
}
if len(players) == 0 {
} else if showStats {
if len(playing) > 0 {
| Player |
SC |
G |
A |
SV |
SH |
BL |
PA |
for _, p := range playing {
|
{ p.Player.DisplayName() }
if p.IsManager {
★
}
|
if p.Stats != nil {
{ intPtrStr(p.Stats.Score) } |
{ intPtrStr(p.Stats.Goals) } |
{ intPtrStr(p.Stats.Assists) } |
{ intPtrStr(p.Stats.Saves) } |
{ intPtrStr(p.Stats.Shots) } |
{ intPtrStr(p.Stats.Blocks) } |
{ intPtrStr(p.Stats.Passes) } |
} else {
β |
}
}
}
if len(bench) > 0 {
Bench
for _, p := range bench {
{ p.Player.DisplayName() }
if p.IsManager {
★ Manager
}
}
}
} else {
if len(playing) > 0 {
if result != nil {
Playing
}
for _, p := range playing {
{ p.Player.DisplayName() }
if p.IsManager {
★ Manager
}
}
}
if result != nil && len(bench) > 0 {
Bench
for _, p := range bench {
{ p.Player.DisplayName() }
if p.IsManager {
★ Manager
}
}
}
}
}
// ==================== Schedule Tab ====================
templ fixtureScheduleTab(
fixture *db.Fixture,
currentSchedule *db.FixtureSchedule,
history []*db.FixtureSchedule,
canSchedule bool,
canManage bool,
userTeamID int,
) {
@fixtureScheduleStatus(fixture, currentSchedule, canSchedule, canManage, userTeamID)
@fixtureScheduleActions(fixture, currentSchedule, canSchedule, canManage, userTeamID)
@fixtureScheduleHistory(fixture, history)
}
templ fixtureScheduleStatus(
fixture *db.Fixture,
current *db.FixtureSchedule,
canSchedule bool,
canManage bool,
userTeamID int,
) {
Schedule Status
if current == nil {
π
No time scheduled
if canSchedule {
Use the form to propose a time for this fixture.
} else {
A team manager needs to propose a time for this fixture.
}
} else if current.Status == db.ScheduleStatusPending && current.ScheduledTime != nil {
β³
Proposed:
@localtime(current.ScheduledTime, "datetime")
Proposed by
{ current.ProposedBy.Name }
β awaiting response from the other team
if canSchedule && userTeamID != current.ProposedByTeamID {
}
if canSchedule && userTeamID == current.ProposedByTeamID {
}
} else if current.Status == db.ScheduleStatusAccepted {
β
Confirmed:
@localtime(current.ScheduledTime, "datetime")
Both teams have agreed on this time.
} else if current.Status == db.ScheduleStatusRejected {
β
Proposal Rejected
The proposed time was rejected. A new time needs to be proposed.
} else if current.Status == db.ScheduleStatusCancelled {
π«
Fixture Forfeited
if current.RescheduleReason != nil {
{ *current.RescheduleReason }
}
} else if current.Status == db.ScheduleStatusRescheduled {
π
Rescheduled
if current.RescheduleReason != nil {
Reason: { *current.RescheduleReason }
}
A new time needs to be proposed.
} else if current.Status == db.ScheduleStatusPostponed {
βΈοΈ
Postponed
if current.RescheduleReason != nil {
Reason: { *current.RescheduleReason }
}
A new time needs to be proposed.
} else if current.Status == db.ScheduleStatusWithdrawn {
β©οΈ
Proposal Withdrawn
The proposed time was withdrawn. A new time needs to be proposed.
}
}
templ fixtureScheduleActions(
fixture *db.Fixture,
current *db.FixtureSchedule,
canSchedule bool,
canManage bool,
userTeamID int,
) {
{{
// Determine what actions are available
showPropose := false
showReschedule := false
showPostpone := false
showCancel := false
if canSchedule {
if current == nil {
showPropose = true
} else if current.Status == db.ScheduleStatusRejected {
showPropose = true
} else if current.Status == db.ScheduleStatusRescheduled {
showPropose = true
} else if current.Status == db.ScheduleStatusPostponed {
showPropose = true
} else if current.Status == db.ScheduleStatusWithdrawn {
showPropose = true
} else if current.Status == db.ScheduleStatusAccepted {
showReschedule = true
showPostpone = true
}
}
if canManage && current != nil && !current.Status.IsTerminal() {
showCancel = true
}
}}
if showPropose || showReschedule || showPostpone || showCancel {
if showPropose {
}
if showReschedule {
}
if showPostpone {
Postpone
@rescheduleReasonSelect(fixture)
}
if showCancel {
Declare Forfeit
This action is irreversible. Declaring a forfeit will permanently cancel the fixture schedule.
@forfeitReasonSelect(fixture)
}
} else {
if !canSchedule && !canManage {
Only team managers can manage fixture scheduling.
}
}
}
templ forfeitReasonSelect(fixture *db.Fixture) {
}
templ rescheduleReasonSelect(fixture *db.Fixture) {
}
templ fixtureScheduleHistory(fixture *db.Fixture, history []*db.FixtureSchedule) {
Schedule History
if len(history) == 0 {
No scheduling activity yet.
} else {
for i := len(history) - 1; i >= 0; i-- {
@scheduleHistoryItem(history[i], i == len(history)-1)
}
}
}
templ scheduleHistoryItem(schedule *db.FixtureSchedule, isCurrent bool) {
{{
statusColor := "text-subtext0"
statusBg := "bg-surface1"
statusLabel := string(schedule.Status)
switch schedule.Status {
case db.ScheduleStatusPending:
statusColor = "text-blue"
statusBg = "bg-blue/20"
statusLabel = "Pending"
case db.ScheduleStatusAccepted:
statusColor = "text-green"
statusBg = "bg-green/20"
statusLabel = "Accepted"
case db.ScheduleStatusRejected:
statusColor = "text-red"
statusBg = "bg-red/20"
statusLabel = "Rejected"
case db.ScheduleStatusRescheduled:
statusColor = "text-yellow"
statusBg = "bg-yellow/20"
statusLabel = "Rescheduled"
case db.ScheduleStatusPostponed:
statusColor = "text-peach"
statusBg = "bg-peach/20"
statusLabel = "Postponed"
case db.ScheduleStatusCancelled:
statusColor = "text-red"
statusBg = "bg-red/20"
statusLabel = "Cancelled"
case db.ScheduleStatusWithdrawn:
statusColor = "text-subtext0"
statusBg = "bg-surface1"
statusLabel = "Withdrawn"
}
}}
if isCurrent {
CURRENT
}
{ statusLabel }
@localtimeUnix(schedule.CreatedAt, "histdate")
Proposed by:
{ schedule.ProposedBy.Name }
if schedule.ScheduledTime != nil {
Time:
@localtime(schedule.ScheduledTime, "datetime")
} else {
Time:
No time set
}
if schedule.AcceptedBy != nil {
Accepted by:
{ schedule.AcceptedBy.Name }
}
if schedule.RescheduleReason != nil {
Reason:
{ *schedule.RescheduleReason }
}
}