added log file uploading and match results
This commit is contained in:
@@ -5,18 +5,8 @@ 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 "time"
|
||||
|
||||
func formatScheduleTime(t *time.Time) string {
|
||||
if t == nil {
|
||||
return "No time set"
|
||||
}
|
||||
return t.Format("Mon 2 Jan 2006 at 3:04 PM")
|
||||
}
|
||||
|
||||
func formatHistoryTime(unix int64) string {
|
||||
return time.Unix(unix, 0).Format("2 Jan 2006 15:04")
|
||||
}
|
||||
import "sort"
|
||||
import "strings"
|
||||
|
||||
templ FixtureDetailPage(
|
||||
fixture *db.Fixture,
|
||||
@@ -24,11 +14,22 @@ templ FixtureDetailPage(
|
||||
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)) {
|
||||
<div class="max-w-screen-lg mx-auto px-4 py-8">
|
||||
@@ -67,17 +68,436 @@ templ FixtureDetailPage(
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Tab Navigation (hidden when only one tab) -->
|
||||
if !isFinalized {
|
||||
<nav class="bg-surface0 border-b border-surface1">
|
||||
<ul class="flex flex-wrap">
|
||||
@fixtureTabItem("overview", "Overview", activeTab, fixture)
|
||||
@fixtureTabItem("schedule", "Schedule", activeTab, fixture)
|
||||
</ul>
|
||||
</nav>
|
||||
}
|
||||
</div>
|
||||
<!-- Schedule Status + Actions -->
|
||||
<div class="space-y-6">
|
||||
@fixtureScheduleStatus(fixture, currentSchedule, canSchedule, canManage, userTeamID)
|
||||
@fixtureScheduleActions(fixture, currentSchedule, canSchedule, canManage, userTeamID)
|
||||
@fixtureScheduleHistory(fixture, history)
|
||||
</div>
|
||||
<!-- Tab Content -->
|
||||
if activeTab == "overview" {
|
||||
@fixtureOverviewTab(fixture, currentSchedule, result, rosters, canManage)
|
||||
} else if activeTab == "schedule" {
|
||||
@fixtureScheduleTab(fixture, currentSchedule, history, canSchedule, canManage, userTeamID)
|
||||
}
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}}
|
||||
<li class="inline-block">
|
||||
<a
|
||||
href={ templ.SafeURL(url) }
|
||||
class={ baseClasses, templ.KV(activeClasses, isActive), templ.KV(inactiveClasses, !isActive) }
|
||||
>
|
||||
{ label }
|
||||
</a>
|
||||
</li>
|
||||
}
|
||||
|
||||
// ==================== Overview Tab ====================
|
||||
templ fixtureOverviewTab(
|
||||
fixture *db.Fixture,
|
||||
currentSchedule *db.FixtureSchedule,
|
||||
result *db.FixtureResult,
|
||||
rosters map[string][]*db.PlayerWithPlayStatus,
|
||||
canManage bool,
|
||||
) {
|
||||
<div class="space-y-6">
|
||||
<!-- Result + Schedule Row -->
|
||||
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||||
<div class="lg:col-span-2">
|
||||
if result != nil {
|
||||
@fixtureResultDisplay(fixture, result)
|
||||
} else if canManage {
|
||||
@fixtureUploadPrompt(fixture)
|
||||
} else {
|
||||
<div class="bg-mantle border border-surface1 rounded-lg p-6 text-center">
|
||||
<p class="text-subtext1 text-sm">No result has been uploaded for this fixture yet.</p>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div>
|
||||
@fixtureScheduleSummary(fixture, currentSchedule, result)
|
||||
</div>
|
||||
</div>
|
||||
<!-- Team Rosters -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
@fixtureTeamSection(fixture.HomeTeam, rosters["home"], "home", result)
|
||||
@fixtureTeamSection(fixture.AwayTeam, rosters["away"], "away", result)
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
templ fixtureScheduleSummary(fixture *db.Fixture, schedule *db.FixtureSchedule, result *db.FixtureResult) {
|
||||
{{
|
||||
isPlayed := result != nil && result.Finalized
|
||||
}}
|
||||
<div class="bg-mantle border border-surface1 rounded-lg overflow-hidden h-full">
|
||||
<div class="bg-surface0 border-b border-surface1 px-4 py-3">
|
||||
<h2 class="text-lg font-bold text-text">Schedule</h2>
|
||||
</div>
|
||||
<div class="p-4 flex flex-col justify-center h-[calc(100%-3rem)]">
|
||||
if schedule == nil {
|
||||
<div class="text-center">
|
||||
<p class="text-subtext1 text-sm">No time scheduled</p>
|
||||
</div>
|
||||
} else if schedule.Status == db.ScheduleStatusAccepted && schedule.ScheduledTime != nil {
|
||||
<div class="text-center space-y-2">
|
||||
if isPlayed {
|
||||
<span class="px-2 py-0.5 bg-blue/20 text-blue rounded text-xs font-medium">
|
||||
Played
|
||||
</span>
|
||||
} else {
|
||||
<span class="px-2 py-0.5 bg-green/20 text-green rounded text-xs font-medium">
|
||||
Confirmed
|
||||
</span>
|
||||
}
|
||||
<p class="text-text font-medium">
|
||||
@localtime(schedule.ScheduledTime, "date")
|
||||
</p>
|
||||
<p class="text-text text-lg font-bold">
|
||||
@localtime(schedule.ScheduledTime, "time")
|
||||
</p>
|
||||
</div>
|
||||
} else if schedule.Status == db.ScheduleStatusPending && schedule.ScheduledTime != nil {
|
||||
<div class="text-center space-y-2">
|
||||
<span class="px-2 py-0.5 bg-yellow/20 text-yellow rounded text-xs font-medium">
|
||||
Proposed
|
||||
</span>
|
||||
<p class="text-text font-medium">
|
||||
@localtime(schedule.ScheduledTime, "date")
|
||||
</p>
|
||||
<p class="text-text text-lg font-bold">
|
||||
@localtime(schedule.ScheduledTime, "time")
|
||||
</p>
|
||||
<p class="text-subtext1 text-xs">Awaiting confirmation</p>
|
||||
</div>
|
||||
} else if schedule.Status == db.ScheduleStatusCancelled {
|
||||
<div class="text-center space-y-2">
|
||||
<span class="px-2 py-0.5 bg-red/20 text-red rounded text-xs font-medium">
|
||||
Forfeit
|
||||
</span>
|
||||
if schedule.RescheduleReason != nil {
|
||||
<p class="text-subtext1 text-xs">{ *schedule.RescheduleReason }</p>
|
||||
}
|
||||
</div>
|
||||
} else {
|
||||
<div class="text-center">
|
||||
<p class="text-subtext1 text-sm">No time confirmed</p>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
templ fixtureResultDisplay(fixture *db.Fixture, result *db.FixtureResult) {
|
||||
{{
|
||||
isOT := strings.EqualFold(result.EndReason, "Overtime")
|
||||
homeWon := result.Winner == "home"
|
||||
awayWon := result.Winner == "away"
|
||||
}}
|
||||
<div class="bg-mantle border border-surface1 rounded-lg overflow-hidden">
|
||||
<div class="bg-surface0 border-b border-surface1 px-4 py-3 flex items-center justify-between">
|
||||
<h2 class="text-lg font-bold text-text">Match Result</h2>
|
||||
<div class="flex items-center gap-2">
|
||||
if result.Finalized {
|
||||
<span class="px-2 py-0.5 bg-green/20 text-green rounded text-xs font-medium">
|
||||
Finalized
|
||||
</span>
|
||||
} else {
|
||||
<span class="px-2 py-0.5 bg-yellow/20 text-yellow rounded text-xs font-medium">
|
||||
Pending Review
|
||||
</span>
|
||||
}
|
||||
if !result.Finalized && result.TamperingDetected {
|
||||
<span class="px-2 py-0.5 bg-red/20 text-red rounded text-xs font-medium">
|
||||
Inconsistent Data
|
||||
</span>
|
||||
}
|
||||
if result.Finalized && result.TamperingDetected && result.TamperingReason != nil {
|
||||
<span class="relative group">
|
||||
<span class="text-yellow text-lg cursor-help">⚠</span>
|
||||
<span
|
||||
class="absolute right-0 top-full mt-1 w-72 bg-crust border border-surface1 rounded-lg
|
||||
p-3 text-xs text-subtext0 shadow-lg opacity-0 invisible
|
||||
group-hover:opacity-100 group-hover:visible transition-all z-50"
|
||||
>
|
||||
<span class="text-yellow font-semibold block mb-1">Inconsistent Data</span>
|
||||
{ *result.TamperingReason }
|
||||
</span>
|
||||
</span>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="p-6">
|
||||
if !result.Finalized && result.TamperingDetected && result.TamperingReason != nil {
|
||||
<div class="bg-red/10 border border-red/30 rounded-lg p-4 mb-4">
|
||||
<div class="flex items-center gap-2 mb-1">
|
||||
<span class="text-red font-medium text-sm">Warning: Inconsistent Data</span>
|
||||
</div>
|
||||
<p class="text-red/80 text-xs">{ *result.TamperingReason }</p>
|
||||
</div>
|
||||
}
|
||||
<!-- Score Display -->
|
||||
<div class="flex items-center justify-center gap-6 py-4">
|
||||
<div class="flex items-center gap-3">
|
||||
if homeWon {
|
||||
<span class="text-2xl">🏆</span>
|
||||
}
|
||||
if fixture.HomeTeam.Color != "" {
|
||||
<span
|
||||
class="px-2.5 py-1 rounded text-sm font-bold"
|
||||
style={ fmt.Sprintf("background-color: %s22; color: %s; border: 1px solid %s44",
|
||||
fixture.HomeTeam.Color, fixture.HomeTeam.Color, fixture.HomeTeam.Color) }
|
||||
>
|
||||
{ fixture.HomeTeam.ShortName }
|
||||
</span>
|
||||
} else {
|
||||
<span class="px-2.5 py-1 bg-surface1 text-text rounded text-sm font-bold">
|
||||
{ fixture.HomeTeam.ShortName }
|
||||
</span>
|
||||
}
|
||||
<span class="text-4xl font-bold text-text">{ fmt.Sprint(result.HomeScore) }</span>
|
||||
</div>
|
||||
<div class="flex flex-col items-center">
|
||||
<span class="text-4xl text-subtext0 font-light leading-none">–</span>
|
||||
if isOT {
|
||||
<span class="px-1.5 py-0.5 bg-peach/20 text-peach rounded text-xs font-semibold mt-1">
|
||||
OT
|
||||
</span>
|
||||
}
|
||||
</div>
|
||||
<div class="flex items-center gap-3">
|
||||
<span class="text-4xl font-bold text-text">{ fmt.Sprint(result.AwayScore) }</span>
|
||||
if fixture.AwayTeam.Color != "" {
|
||||
<span
|
||||
class="px-2.5 py-1 rounded text-sm font-bold"
|
||||
style={ fmt.Sprintf("background-color: %s22; color: %s; border: 1px solid %s44",
|
||||
fixture.AwayTeam.Color, fixture.AwayTeam.Color, fixture.AwayTeam.Color) }
|
||||
>
|
||||
{ fixture.AwayTeam.ShortName }
|
||||
</span>
|
||||
} else {
|
||||
<span class="px-2.5 py-1 bg-surface1 text-text rounded text-sm font-bold">
|
||||
{ fixture.AwayTeam.ShortName }
|
||||
</span>
|
||||
}
|
||||
if awayWon {
|
||||
<span class="text-2xl">🏆</span>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
templ fixtureUploadPrompt(fixture *db.Fixture) {
|
||||
<div class="bg-mantle border border-surface1 rounded-lg p-6 text-center">
|
||||
<div class="text-4xl mb-3">📋</div>
|
||||
<p class="text-lg text-text font-medium mb-2">No Result Uploaded</p>
|
||||
<p class="text-sm text-subtext1 mb-4">Upload match log files to record the result of this fixture.</p>
|
||||
<a
|
||||
href={ templ.SafeURL(fmt.Sprintf("/fixtures/%d/results/upload", fixture.ID)) }
|
||||
class="inline-block px-4 py-2 bg-blue hover:bg-blue/80 text-mantle rounded-lg
|
||||
font-medium transition hover:cursor-pointer"
|
||||
>
|
||||
Upload Match Logs
|
||||
</a>
|
||||
</div>
|
||||
}
|
||||
|
||||
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
|
||||
})
|
||||
}
|
||||
}}
|
||||
<div class="bg-mantle border border-surface1 rounded-lg overflow-hidden">
|
||||
<div class="bg-surface0 border-b border-surface1 px-4 py-3 flex items-center justify-between">
|
||||
<h3 class="text-md font-bold text-text">
|
||||
{ team.Name }
|
||||
</h3>
|
||||
if team.Color != "" {
|
||||
<span
|
||||
class="w-4 h-4 rounded-full border border-surface1"
|
||||
style={ fmt.Sprintf("background-color: %s", team.Color) }
|
||||
></span>
|
||||
}
|
||||
</div>
|
||||
if len(players) == 0 {
|
||||
<div class="p-4">
|
||||
<p class="text-subtext1 text-sm text-center py-2">No players on roster.</p>
|
||||
</div>
|
||||
} else if showStats {
|
||||
<!-- Stats table view for finalized results -->
|
||||
if len(playing) > 0 {
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full">
|
||||
<thead class="bg-surface0 border-b border-surface1">
|
||||
<tr>
|
||||
<th class="px-3 py-2 text-left text-xs font-semibold text-text">Player</th>
|
||||
<th class="px-2 py-2 text-center text-xs font-semibold text-text" title="Score">SC</th>
|
||||
<th class="px-2 py-2 text-center text-xs font-semibold text-text" title="Goals">G</th>
|
||||
<th class="px-2 py-2 text-center text-xs font-semibold text-text" title="Assists">A</th>
|
||||
<th class="px-2 py-2 text-center text-xs font-semibold text-text" title="Saves">SV</th>
|
||||
<th class="px-2 py-2 text-center text-xs font-semibold text-text" title="Shots">SH</th>
|
||||
<th class="px-2 py-2 text-center text-xs font-semibold text-text" title="Blocks">BL</th>
|
||||
<th class="px-2 py-2 text-center text-xs font-semibold text-text" title="Passes">PA</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-surface1">
|
||||
for _, p := range playing {
|
||||
<tr class="hover:bg-surface0 transition-colors">
|
||||
<td class="px-3 py-2 text-sm text-text">
|
||||
<span class="flex items-center gap-1.5">
|
||||
{ p.Player.DisplayName() }
|
||||
if p.IsManager {
|
||||
<span class="px-1.5 py-0.5 bg-yellow/20 text-yellow rounded text-xs font-medium">
|
||||
★
|
||||
</span>
|
||||
}
|
||||
</span>
|
||||
</td>
|
||||
if p.Stats != nil {
|
||||
<td class="px-2 py-2 text-center text-sm font-medium text-text">{ intPtrStr(p.Stats.Score) }</td>
|
||||
<td class="px-2 py-2 text-center text-sm text-text">{ intPtrStr(p.Stats.Goals) }</td>
|
||||
<td class="px-2 py-2 text-center text-sm text-text">{ intPtrStr(p.Stats.Assists) }</td>
|
||||
<td class="px-2 py-2 text-center text-sm text-text">{ intPtrStr(p.Stats.Saves) }</td>
|
||||
<td class="px-2 py-2 text-center text-sm text-text">{ intPtrStr(p.Stats.Shots) }</td>
|
||||
<td class="px-2 py-2 text-center text-sm text-text">{ intPtrStr(p.Stats.Blocks) }</td>
|
||||
<td class="px-2 py-2 text-center text-sm text-text">{ intPtrStr(p.Stats.Passes) }</td>
|
||||
} else {
|
||||
<td colspan="7" class="px-2 py-2 text-center text-xs text-subtext1">—</td>
|
||||
}
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
}
|
||||
if len(bench) > 0 {
|
||||
<div class="border-t border-surface1 px-4 py-3">
|
||||
<p class="text-xs text-subtext0 font-semibold uppercase mb-2">Bench</p>
|
||||
<div class="space-y-1">
|
||||
for _, p := range bench {
|
||||
<div class="flex items-center gap-2 px-2 py-1.5 rounded">
|
||||
<span class="text-sm text-subtext1">
|
||||
{ p.Player.DisplayName() }
|
||||
</span>
|
||||
if p.IsManager {
|
||||
<span class="px-1.5 py-0.5 bg-yellow/20 text-yellow rounded text-xs font-medium">
|
||||
★ Manager
|
||||
</span>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
} else {
|
||||
<!-- Simple list view (no result or pending review) -->
|
||||
<div class="p-4">
|
||||
if len(playing) > 0 {
|
||||
if result != nil {
|
||||
<p class="text-xs text-subtext0 font-semibold uppercase mb-2">Playing</p>
|
||||
}
|
||||
<div class="space-y-1">
|
||||
for _, p := range playing {
|
||||
<div class="flex items-center gap-2 px-2 py-1.5 rounded hover:bg-surface0 transition">
|
||||
<span class="text-sm text-text">
|
||||
{ p.Player.DisplayName() }
|
||||
</span>
|
||||
if p.IsManager {
|
||||
<span class="px-1.5 py-0.5 bg-yellow/20 text-yellow rounded text-xs font-medium">
|
||||
★ Manager
|
||||
</span>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
if result != nil && len(bench) > 0 {
|
||||
<p class="text-xs text-subtext0 font-semibold uppercase mt-4 mb-2">Bench</p>
|
||||
<div class="space-y-1">
|
||||
for _, p := range bench {
|
||||
<div class="flex items-center gap-2 px-2 py-1.5 rounded">
|
||||
<span class="text-sm text-subtext1">
|
||||
{ p.Player.DisplayName() }
|
||||
</span>
|
||||
if p.IsManager {
|
||||
<span class="px-1.5 py-0.5 bg-yellow/20 text-yellow rounded text-xs font-medium">
|
||||
★ Manager
|
||||
</span>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
// ==================== Schedule Tab ====================
|
||||
templ fixtureScheduleTab(
|
||||
fixture *db.Fixture,
|
||||
currentSchedule *db.FixtureSchedule,
|
||||
history []*db.FixtureSchedule,
|
||||
canSchedule bool,
|
||||
canManage bool,
|
||||
userTeamID int,
|
||||
) {
|
||||
<div class="space-y-6">
|
||||
@fixtureScheduleStatus(fixture, currentSchedule, canSchedule, canManage, userTeamID)
|
||||
@fixtureScheduleActions(fixture, currentSchedule, canSchedule, canManage, userTeamID)
|
||||
@fixtureScheduleHistory(fixture, history)
|
||||
</div>
|
||||
}
|
||||
|
||||
templ fixtureScheduleStatus(
|
||||
fixture *db.Fixture,
|
||||
current *db.FixtureSchedule,
|
||||
@@ -108,7 +528,8 @@ templ fixtureScheduleStatus(
|
||||
<div class="text-center py-4">
|
||||
<div class="text-4xl mb-3">⏳</div>
|
||||
<p class="text-lg text-text font-medium">
|
||||
Proposed: { formatScheduleTime(current.ScheduledTime) }
|
||||
Proposed:
|
||||
@localtime(current.ScheduledTime, "datetime")
|
||||
</p>
|
||||
<p class="text-sm text-subtext1 mt-1">
|
||||
Proposed by
|
||||
@@ -161,7 +582,8 @@ templ fixtureScheduleStatus(
|
||||
<div class="text-center py-4">
|
||||
<div class="text-4xl mb-3">✅</div>
|
||||
<p class="text-lg text-green font-medium">
|
||||
Confirmed: { formatScheduleTime(current.ScheduledTime) }
|
||||
Confirmed:
|
||||
@localtime(current.ScheduledTime, "datetime")
|
||||
</p>
|
||||
<p class="text-sm text-subtext1 mt-1">
|
||||
Both teams have agreed on this time.
|
||||
@@ -278,7 +700,19 @@ templ fixtureScheduleActions(
|
||||
class="space-y-4"
|
||||
>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-subtext0 mb-1">Date & Time</label>
|
||||
<label class="block text-sm font-medium text-subtext0 mb-1">
|
||||
Date & Time
|
||||
<span class="relative group inline-block ml-1">
|
||||
<span class="px-1.5 py-0.5 bg-blue/20 text-blue rounded text-xs font-medium cursor-help">AEST/AEDT</span>
|
||||
<span
|
||||
class="absolute left-0 top-full mt-1 w-56 bg-crust border border-surface1 rounded-lg
|
||||
p-2 text-xs text-subtext0 shadow-lg opacity-0 invisible
|
||||
group-hover:opacity-100 group-hover:visible transition-all z-50"
|
||||
>
|
||||
Enter the time in Australian Eastern time (AEST/AEDT). It will be displayed in each user's local timezone.
|
||||
</span>
|
||||
</span>
|
||||
</label>
|
||||
<input
|
||||
type="datetime-local"
|
||||
name="scheduled_time"
|
||||
@@ -311,7 +745,19 @@ templ fixtureScheduleActions(
|
||||
class="space-y-4"
|
||||
>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-subtext0 mb-1">New Date & Time</label>
|
||||
<label class="block text-sm font-medium text-subtext0 mb-1">
|
||||
New Date & Time
|
||||
<span class="relative group inline-block ml-1">
|
||||
<span class="px-1.5 py-0.5 bg-blue/20 text-blue rounded text-xs font-medium cursor-help">AEST/AEDT</span>
|
||||
<span
|
||||
class="absolute left-0 top-full mt-1 w-56 bg-crust border border-surface1 rounded-lg
|
||||
p-2 text-xs text-subtext0 shadow-lg opacity-0 invisible
|
||||
group-hover:opacity-100 group-hover:visible transition-all z-50"
|
||||
>
|
||||
Enter the time in Australian Eastern time (AEST/AEDT). It will be displayed in each user's local timezone.
|
||||
</span>
|
||||
</span>
|
||||
</label>
|
||||
<input
|
||||
type="datetime-local"
|
||||
name="scheduled_time"
|
||||
@@ -507,7 +953,7 @@ templ scheduleHistoryItem(schedule *db.FixtureSchedule, isCurrent bool) {
|
||||
</span>
|
||||
</div>
|
||||
<span class="text-xs text-subtext1">
|
||||
{ formatHistoryTime(schedule.CreatedAt) }
|
||||
@localtimeUnix(schedule.CreatedAt, "histdate")
|
||||
</span>
|
||||
</div>
|
||||
<div class="space-y-1">
|
||||
@@ -518,7 +964,9 @@ templ scheduleHistoryItem(schedule *db.FixtureSchedule, isCurrent bool) {
|
||||
if schedule.ScheduledTime != nil {
|
||||
<div class="flex items-center gap-2 text-sm">
|
||||
<span class="text-subtext0">Time:</span>
|
||||
<span class="text-text">{ formatScheduleTime(schedule.ScheduledTime) }</span>
|
||||
<span class="text-text">
|
||||
@localtime(schedule.ScheduledTime, "datetime")
|
||||
</span>
|
||||
</div>
|
||||
} else {
|
||||
<div class="flex items-center gap-2 text-sm">
|
||||
|
||||
Reference in New Issue
Block a user