added free agents
This commit is contained in:
@@ -17,6 +17,8 @@ templ FixtureDetailPage(
|
||||
result *db.FixtureResult,
|
||||
rosters map[string][]*db.PlayerWithPlayStatus,
|
||||
activeTab string,
|
||||
nominatedFreeAgents []*db.FixtureFreeAgent,
|
||||
availableFreeAgents []*db.SeasonLeagueFreeAgent,
|
||||
) {
|
||||
{{
|
||||
permCache := contexts.Permissions(ctx)
|
||||
@@ -78,10 +80,10 @@ templ FixtureDetailPage(
|
||||
</nav>
|
||||
}
|
||||
</div>
|
||||
<!-- Tab Content -->
|
||||
if activeTab == "overview" {
|
||||
@fixtureOverviewTab(fixture, currentSchedule, result, rosters, canManage)
|
||||
} else if activeTab == "schedule" {
|
||||
<!-- Tab Content -->
|
||||
if activeTab == "overview" {
|
||||
@fixtureOverviewTab(fixture, currentSchedule, result, rosters, canManage, canSchedule, userTeamID, nominatedFreeAgents, availableFreeAgents)
|
||||
} else if activeTab == "schedule" {
|
||||
@fixtureScheduleTab(fixture, currentSchedule, history, canSchedule, canManage, userTeamID)
|
||||
}
|
||||
</div>
|
||||
@@ -116,6 +118,10 @@ templ fixtureOverviewTab(
|
||||
result *db.FixtureResult,
|
||||
rosters map[string][]*db.PlayerWithPlayStatus,
|
||||
canManage bool,
|
||||
canSchedule bool,
|
||||
userTeamID int,
|
||||
nominatedFreeAgents []*db.FixtureFreeAgent,
|
||||
availableFreeAgents []*db.SeasonLeagueFreeAgent,
|
||||
) {
|
||||
<div class="space-y-6">
|
||||
<!-- Result + Schedule Row -->
|
||||
@@ -135,6 +141,10 @@ templ fixtureOverviewTab(
|
||||
@fixtureScheduleSummary(fixture, currentSchedule, result)
|
||||
</div>
|
||||
</div>
|
||||
<!-- Free Agent Nominations (hidden when result is finalized) -->
|
||||
if (result == nil || !result.Finalized) && (canSchedule || canManage || len(nominatedFreeAgents) > 0) {
|
||||
@fixtureFreeAgentSection(fixture, canSchedule, canManage, userTeamID, nominatedFreeAgents, availableFreeAgents)
|
||||
}
|
||||
<!-- Team Rosters -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
@fixtureTeamSection(fixture.HomeTeam, rosters["home"], "home", result)
|
||||
@@ -400,6 +410,11 @@ templ fixtureTeamSection(team *db.Team, players []*db.PlayerWithPlayStatus, side
|
||||
★
|
||||
</span>
|
||||
}
|
||||
if p.IsFreeAgent {
|
||||
<span class="px-1.5 py-0.5 bg-peach/20 text-peach rounded text-xs font-medium">
|
||||
FREE AGENT
|
||||
</span>
|
||||
}
|
||||
</span>
|
||||
</td>
|
||||
if p.Stats != nil {
|
||||
@@ -456,6 +471,11 @@ templ fixtureTeamSection(team *db.Team, players []*db.PlayerWithPlayStatus, side
|
||||
★ Manager
|
||||
</span>
|
||||
}
|
||||
if p.IsFreeAgent {
|
||||
<span class="px-1.5 py-0.5 bg-peach/20 text-peach rounded text-xs font-medium">
|
||||
FREE AGENT
|
||||
</span>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
@@ -482,6 +502,256 @@ templ fixtureTeamSection(team *db.Team, players []*db.PlayerWithPlayStatus, side
|
||||
</div>
|
||||
}
|
||||
|
||||
// ==================== Free Agent Section ====================
|
||||
templ fixtureFreeAgentSection(
|
||||
fixture *db.Fixture,
|
||||
canSchedule bool,
|
||||
canManage bool,
|
||||
userTeamID int,
|
||||
nominated []*db.FixtureFreeAgent,
|
||||
available []*db.SeasonLeagueFreeAgent,
|
||||
) {
|
||||
{{
|
||||
// Split nominated by team
|
||||
homeNominated := []*db.FixtureFreeAgent{}
|
||||
awayNominated := []*db.FixtureFreeAgent{}
|
||||
for _, n := range nominated {
|
||||
if n.TeamID == fixture.HomeTeamID {
|
||||
homeNominated = append(homeNominated, n)
|
||||
} else {
|
||||
awayNominated = append(awayNominated, n)
|
||||
}
|
||||
}
|
||||
|
||||
// Filter available: exclude already nominated players
|
||||
nominatedIDs := map[int]bool{}
|
||||
for _, n := range nominated {
|
||||
nominatedIDs[n.PlayerID] = true
|
||||
}
|
||||
filteredAvailable := []*db.SeasonLeagueFreeAgent{}
|
||||
for _, fa := range available {
|
||||
if !nominatedIDs[fa.PlayerID] {
|
||||
filteredAvailable = append(filteredAvailable, fa)
|
||||
}
|
||||
}
|
||||
|
||||
// Can the user nominate?
|
||||
canNominate := (canSchedule || canManage) && len(filteredAvailable) > 0
|
||||
}}
|
||||
<div class="bg-mantle border border-surface1 rounded-lg overflow-hidden" x-data="{ showNominateModal: false, selectedPlayerId: '', selectedTeamId: '' }">
|
||||
<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">Free Agent Nominations</h2>
|
||||
if canNominate {
|
||||
<button
|
||||
@click="showNominateModal = true"
|
||||
class="rounded-lg px-3 py-1.5 hover:cursor-pointer text-center text-xs
|
||||
bg-peach hover:bg-peach/75 text-mantle transition"
|
||||
>
|
||||
Nominate Free Agent
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
<div class="p-4">
|
||||
if len(nominated) == 0 {
|
||||
<p class="text-subtext1 text-sm text-center py-2">No free agents nominated for this fixture.</p>
|
||||
} else {
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<!-- Home team nominations -->
|
||||
<div>
|
||||
<p class="text-xs text-subtext0 font-semibold uppercase mb-2">{ fixture.HomeTeam.Name }</p>
|
||||
if len(homeNominated) == 0 {
|
||||
<p class="text-subtext1 text-xs italic">None</p>
|
||||
} else {
|
||||
<div class="space-y-1">
|
||||
for _, n := range homeNominated {
|
||||
<div class="flex items-center justify-between px-2 py-1.5 rounded hover:bg-surface0 transition">
|
||||
<span class="flex items-center gap-2">
|
||||
<span class="text-sm text-text">{ n.Player.DisplayName() }</span>
|
||||
<span class="px-1.5 py-0.5 bg-peach/20 text-peach rounded text-xs font-medium">
|
||||
FA
|
||||
</span>
|
||||
</span>
|
||||
if canManage || (canSchedule && userTeamID == fixture.HomeTeamID) {
|
||||
<form
|
||||
hx-post={ fmt.Sprintf("/fixtures/%d/free-agents/%d/remove", fixture.ID, n.PlayerID) }
|
||||
hx-swap="none"
|
||||
class="inline"
|
||||
>
|
||||
<button
|
||||
type="submit"
|
||||
class="px-2 py-0.5 text-xs bg-red/20 hover:bg-red/40 text-red rounded
|
||||
transition hover:cursor-pointer"
|
||||
>
|
||||
Remove
|
||||
</button>
|
||||
</form>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<!-- Away team nominations -->
|
||||
<div>
|
||||
<p class="text-xs text-subtext0 font-semibold uppercase mb-2">{ fixture.AwayTeam.Name }</p>
|
||||
if len(awayNominated) == 0 {
|
||||
<p class="text-subtext1 text-xs italic">None</p>
|
||||
} else {
|
||||
<div class="space-y-1">
|
||||
for _, n := range awayNominated {
|
||||
<div class="flex items-center justify-between px-2 py-1.5 rounded hover:bg-surface0 transition">
|
||||
<span class="flex items-center gap-2">
|
||||
<span class="text-sm text-text">{ n.Player.DisplayName() }</span>
|
||||
<span class="px-1.5 py-0.5 bg-peach/20 text-peach rounded text-xs font-medium">
|
||||
FA
|
||||
</span>
|
||||
</span>
|
||||
if canManage || (canSchedule && userTeamID == fixture.AwayTeamID) {
|
||||
<form
|
||||
hx-post={ fmt.Sprintf("/fixtures/%d/free-agents/%d/remove", fixture.ID, n.PlayerID) }
|
||||
hx-swap="none"
|
||||
class="inline"
|
||||
>
|
||||
<button
|
||||
type="submit"
|
||||
class="px-2 py-0.5 text-xs bg-red/20 hover:bg-red/40 text-red rounded
|
||||
transition hover:cursor-pointer"
|
||||
>
|
||||
Remove
|
||||
</button>
|
||||
</form>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<!-- Nominate Modal -->
|
||||
if canNominate {
|
||||
<div
|
||||
x-show="showNominateModal"
|
||||
@keydown.escape.window="showNominateModal = false"
|
||||
class="fixed inset-0 z-50 overflow-y-auto"
|
||||
style="display: none;"
|
||||
>
|
||||
<div
|
||||
class="fixed inset-0 bg-crust/80 transition-opacity"
|
||||
@click="showNominateModal = false"
|
||||
></div>
|
||||
<div class="flex min-h-full items-center justify-center p-4">
|
||||
<div
|
||||
class="relative bg-mantle border-2 border-surface1 rounded-xl shadow-xl max-w-md w-full p-6"
|
||||
@click.stop
|
||||
>
|
||||
<h3 class="text-2xl font-bold text-text mb-4">Nominate Free Agent</h3>
|
||||
<form
|
||||
hx-post={ fmt.Sprintf("/fixtures/%d/free-agents/nominate", fixture.ID) }
|
||||
hx-swap="none"
|
||||
>
|
||||
if canManage && !canSchedule {
|
||||
<!-- Manager (not on either team): show team selector -->
|
||||
<div class="mb-4">
|
||||
<label for="fa_team_id" class="block text-sm font-medium mb-2">Nominating Team</label>
|
||||
<select
|
||||
id="fa_team_id"
|
||||
name="team_id"
|
||||
x-model="selectedTeamId"
|
||||
required
|
||||
class="w-full py-3 px-4 rounded-lg text-sm bg-base border-2 border-overlay0
|
||||
focus:border-blue outline-none"
|
||||
>
|
||||
<option value="">Choose a team...</option>
|
||||
<option value={ fmt.Sprint(fixture.HomeTeamID) }>
|
||||
{ fixture.HomeTeam.Name }
|
||||
</option>
|
||||
<option value={ fmt.Sprint(fixture.AwayTeamID) }>
|
||||
{ fixture.AwayTeam.Name }
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
} else if canManage && canSchedule {
|
||||
<!-- Manager who is also on a team: show team selector pre-filled -->
|
||||
<div class="mb-4">
|
||||
<label for="fa_team_id" class="block text-sm font-medium mb-2">Nominating Team</label>
|
||||
<select
|
||||
id="fa_team_id"
|
||||
name="team_id"
|
||||
x-model="selectedTeamId"
|
||||
x-init={ fmt.Sprintf("selectedTeamId = '%d'", userTeamID) }
|
||||
required
|
||||
class="w-full py-3 px-4 rounded-lg text-sm bg-base border-2 border-overlay0
|
||||
focus:border-blue outline-none"
|
||||
>
|
||||
<option value="">Choose a team...</option>
|
||||
<option value={ fmt.Sprint(fixture.HomeTeamID) }>
|
||||
{ fixture.HomeTeam.Name }
|
||||
</option>
|
||||
<option value={ fmt.Sprint(fixture.AwayTeamID) }>
|
||||
{ fixture.AwayTeam.Name }
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
} else {
|
||||
<!-- Regular team manager: fixed to their team -->
|
||||
<input type="hidden" name="team_id" value={ fmt.Sprint(userTeamID) }/>
|
||||
}
|
||||
<div class="mb-4">
|
||||
<label for="fa_player_id" class="block text-sm font-medium mb-2">Select Free Agent</label>
|
||||
<select
|
||||
id="fa_player_id"
|
||||
name="player_id"
|
||||
x-model="selectedPlayerId"
|
||||
required
|
||||
class="w-full py-3 px-4 rounded-lg text-sm bg-base border-2 border-overlay0
|
||||
focus:border-blue outline-none"
|
||||
>
|
||||
<option value="">Choose a free agent...</option>
|
||||
for _, fa := range filteredAvailable {
|
||||
<option value={ fmt.Sprint(fa.PlayerID) }>
|
||||
{ fa.Player.DisplayName() }
|
||||
</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
<div class="flex gap-3 justify-end">
|
||||
<button
|
||||
type="button"
|
||||
@click="showNominateModal = false"
|
||||
class="px-4 py-2 rounded-lg bg-surface0 hover:bg-surface1 text-text transition hover:cursor-pointer"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
if canManage {
|
||||
<button
|
||||
type="submit"
|
||||
:disabled="!selectedPlayerId || !selectedTeamId"
|
||||
class="px-4 py-2 rounded-lg bg-peach hover:bg-peach/75 text-mantle transition
|
||||
disabled:bg-peach/40 disabled:cursor-not-allowed hover:cursor-pointer"
|
||||
>
|
||||
Nominate
|
||||
</button>
|
||||
} else {
|
||||
<button
|
||||
type="submit"
|
||||
:disabled="!selectedPlayerId"
|
||||
class="px-4 py-2 rounded-lg bg-peach hover:bg-peach/75 text-mantle transition
|
||||
disabled:bg-peach/40 disabled:cursor-not-allowed hover:cursor-pointer"
|
||||
>
|
||||
Nominate
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
// ==================== Schedule Tab ====================
|
||||
templ fixtureScheduleTab(
|
||||
fixture *db.Fixture,
|
||||
|
||||
@@ -8,6 +8,7 @@ templ FixtureReviewResultPage(
|
||||
fixture *db.Fixture,
|
||||
result *db.FixtureResult,
|
||||
unmappedPlayers []string,
|
||||
unnominatedFreeAgents []FreeAgentWarning,
|
||||
) {
|
||||
{{
|
||||
backURL := fmt.Sprintf("/fixtures/%d", fixture.ID)
|
||||
@@ -37,38 +38,56 @@ templ FixtureReviewResultPage(
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Warnings Section -->
|
||||
if result.TamperingDetected || len(unmappedPlayers) > 0 {
|
||||
<div class="space-y-4 mb-6">
|
||||
if result.TamperingDetected && result.TamperingReason != nil {
|
||||
<div class="bg-red/10 border border-red/30 rounded-lg p-4">
|
||||
<div class="flex items-center gap-2 mb-2">
|
||||
<!-- Warnings Section -->
|
||||
if result.TamperingDetected || len(unmappedPlayers) > 0 || len(unnominatedFreeAgents) > 0 {
|
||||
<div class="space-y-4 mb-6">
|
||||
if result.TamperingDetected && result.TamperingReason != nil {
|
||||
<div class="bg-red/10 border border-red/30 rounded-lg p-4">
|
||||
<div class="flex items-center gap-2 mb-2">
|
||||
<span class="text-red font-bold text-sm">⚠ Inconsistent Data Detected</span>
|
||||
</div>
|
||||
<p class="text-red/80 text-sm">{ *result.TamperingReason }</p>
|
||||
<p class="text-red/60 text-xs mt-2">
|
||||
This does not block finalization but should be reviewed carefully.
|
||||
</p>
|
||||
<p class="text-red/80 text-sm">{ *result.TamperingReason }</p>
|
||||
<p class="text-red/60 text-xs mt-2">
|
||||
This does not block finalization but should be reviewed carefully.
|
||||
</p>
|
||||
</div>
|
||||
}
|
||||
if len(unnominatedFreeAgents) > 0 {
|
||||
<div class="bg-yellow/10 border border-yellow/30 rounded-lg p-4">
|
||||
<div class="flex items-center gap-2 mb-2">
|
||||
<span class="text-yellow font-bold text-sm">⚠ Free Agent Nomination Issues</span>
|
||||
</div>
|
||||
}
|
||||
if len(unmappedPlayers) > 0 {
|
||||
<div class="bg-yellow/10 border border-yellow/30 rounded-lg p-4">
|
||||
<div class="flex items-center gap-2 mb-2">
|
||||
<span class="text-yellow font-bold text-sm">⚠ Unmapped Players</span>
|
||||
</div>
|
||||
<p class="text-yellow/80 text-sm mb-2">
|
||||
The following players could not be matched to registered players.
|
||||
They may be free agents or have unregistered Slapshot IDs.
|
||||
</p>
|
||||
<ul class="list-disc list-inside text-yellow/70 text-xs space-y-0.5">
|
||||
for _, p := range unmappedPlayers {
|
||||
<li>{ p }</li>
|
||||
}
|
||||
</ul>
|
||||
<p class="text-yellow/80 text-sm mb-2">
|
||||
The following free agents have nomination issues that should be reviewed before finalizing.
|
||||
</p>
|
||||
<ul class="list-disc list-inside text-yellow/70 text-xs space-y-0.5">
|
||||
for _, fa := range unnominatedFreeAgents {
|
||||
<li>
|
||||
<span class="text-yellow font-medium">{ fa.Name }</span>
|
||||
<span class="text-yellow/60"> — { fa.Reason }</span>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
}
|
||||
if len(unmappedPlayers) > 0 {
|
||||
<div class="bg-yellow/10 border border-yellow/30 rounded-lg p-4">
|
||||
<div class="flex items-center gap-2 mb-2">
|
||||
<span class="text-yellow font-bold text-sm">⚠ Unmapped Players</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
<p class="text-yellow/80 text-sm mb-2">
|
||||
The following players could not be matched to registered players.
|
||||
They may be free agents or have unregistered Slapshot IDs.
|
||||
</p>
|
||||
<ul class="list-disc list-inside text-yellow/70 text-xs space-y-0.5">
|
||||
for _, p := range unmappedPlayers {
|
||||
<li>{ p }</li>
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
<!-- Score Overview -->
|
||||
<div class="bg-mantle border border-surface1 rounded-lg overflow-hidden mb-6">
|
||||
<div class="bg-surface0 border-b border-surface1 px-4 py-3">
|
||||
@@ -199,10 +218,17 @@ templ reviewTeamStats(team *db.Team, result *db.FixtureResult, side string) {
|
||||
for _, ps := range finalStats {
|
||||
<tr class="hover:bg-surface0 transition-colors">
|
||||
<td class="px-3 py-2 text-sm text-text">
|
||||
{ ps.Username }
|
||||
if ps.PlayerID == nil {
|
||||
<span class="text-yellow text-xs ml-1" title="Unmapped player">?</span>
|
||||
}
|
||||
<span class="flex items-center gap-1.5">
|
||||
{ ps.Username }
|
||||
if ps.PlayerID == nil {
|
||||
<span class="text-yellow text-xs" title="Unmapped player">?</span>
|
||||
}
|
||||
if ps.Stats.IsFreeAgent {
|
||||
<span class="px-1.5 py-0.5 bg-peach/20 text-peach rounded text-xs font-medium">
|
||||
FREE AGENT
|
||||
</span>
|
||||
}
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-2 py-2 text-center text-sm text-text">{ intPtrStr(ps.Stats.Goals) }</td>
|
||||
<td class="px-2 py-2 text-center text-sm text-text">{ intPtrStr(ps.Stats.Assists) }</td>
|
||||
|
||||
168
internal/view/seasonsview/season_league_free_agents.templ
Normal file
168
internal/view/seasonsview/season_league_free_agents.templ
Normal file
@@ -0,0 +1,168 @@
|
||||
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 "fmt"
|
||||
|
||||
templ SeasonLeagueFreeAgentsPage(season *db.Season, league *db.League, freeAgents []*db.SeasonLeagueFreeAgent, availablePlayers []*db.Player) {
|
||||
@SeasonLeagueLayout("free-agents", season, league) {
|
||||
@SeasonLeagueFreeAgents(season, league, freeAgents, availablePlayers)
|
||||
}
|
||||
}
|
||||
|
||||
templ SeasonLeagueFreeAgents(season *db.Season, league *db.League, freeAgents []*db.SeasonLeagueFreeAgent, availablePlayers []*db.Player) {
|
||||
{{
|
||||
permCache := contexts.Permissions(ctx)
|
||||
canAdd := permCache.HasPermission(permissions.FreeAgentsAdd)
|
||||
canRemove := permCache.HasPermission(permissions.FreeAgentsRemove)
|
||||
}}
|
||||
<div x-data="{ showAddModal: false, selectedPlayerId: '' }">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<h2 class="text-2xl font-bold text-text">Free Agents ({ fmt.Sprint(len(freeAgents)) })</h2>
|
||||
if canAdd {
|
||||
<button
|
||||
@click="showAddModal = true"
|
||||
class="rounded-lg px-4 py-2 hover:cursor-pointer text-center text-sm
|
||||
bg-green hover:bg-green/75 text-mantle transition"
|
||||
>
|
||||
Add Free Agent
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
if len(freeAgents) == 0 {
|
||||
<div class="bg-surface0 border border-surface1 rounded-lg p-8 text-center">
|
||||
<p class="text-subtext0 text-lg">No free agents registered in this league yet.</p>
|
||||
if canAdd {
|
||||
<p class="text-subtext1 text-sm mt-2">Click "Add Free Agent" to register a player.</p>
|
||||
}
|
||||
</div>
|
||||
} else {
|
||||
<div class="bg-surface0 border border-surface1 rounded-lg overflow-hidden">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full">
|
||||
<thead class="bg-mantle border-b border-surface1">
|
||||
<tr>
|
||||
<th class="px-4 py-3 text-left text-sm font-semibold text-text">Player</th>
|
||||
<th class="px-4 py-3 text-left text-sm font-semibold text-text">Registered By</th>
|
||||
if canRemove {
|
||||
<th class="px-4 py-3 text-right text-sm font-semibold text-text">Actions</th>
|
||||
}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-surface1">
|
||||
for _, fa := range freeAgents {
|
||||
<tr class="hover:bg-surface1 transition-colors">
|
||||
<td class="px-4 py-3 text-sm text-text">
|
||||
<span class="flex items-center gap-2">
|
||||
{ fa.Player.DisplayName() }
|
||||
<span class="px-1.5 py-0.5 bg-peach/20 text-peach rounded text-xs font-medium">
|
||||
FREE AGENT
|
||||
</span>
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-4 py-3 text-sm text-subtext0">
|
||||
if fa.RegisteredBy != nil {
|
||||
{ fa.RegisteredBy.Username }
|
||||
}
|
||||
</td>
|
||||
if canRemove {
|
||||
<td class="px-4 py-3 text-right">
|
||||
<form
|
||||
hx-post={ fmt.Sprintf("/seasons/%s/leagues/%s/free-agents/unregister", season.ShortName, league.ShortName) }
|
||||
hx-swap="none"
|
||||
class="inline"
|
||||
>
|
||||
<input type="hidden" name="player_id" value={ fmt.Sprint(fa.PlayerID) }/>
|
||||
<button
|
||||
type="submit"
|
||||
class="px-3 py-1 text-xs bg-red/20 hover:bg-red/40 text-red rounded
|
||||
transition hover:cursor-pointer"
|
||||
>
|
||||
Remove
|
||||
</button>
|
||||
</form>
|
||||
</td>
|
||||
}
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
if canAdd {
|
||||
@addFreeAgentModal(season, league, availablePlayers)
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
templ addFreeAgentModal(season *db.Season, league *db.League, availablePlayers []*db.Player) {
|
||||
<div
|
||||
x-show="showAddModal"
|
||||
@keydown.escape.window="showAddModal = false"
|
||||
class="fixed inset-0 z-50 overflow-y-auto"
|
||||
style="display: none;"
|
||||
>
|
||||
<!-- Backdrop -->
|
||||
<div
|
||||
class="fixed inset-0 bg-crust/80 transition-opacity"
|
||||
@click="showAddModal = false"
|
||||
></div>
|
||||
<!-- Modal -->
|
||||
<div class="flex min-h-full items-center justify-center p-4">
|
||||
<div
|
||||
class="relative bg-mantle border-2 border-surface1 rounded-xl shadow-xl max-w-md w-full p-6"
|
||||
@click.stop
|
||||
>
|
||||
<h3 class="text-2xl font-bold text-text mb-4">Add Free Agent</h3>
|
||||
<form
|
||||
hx-post={ fmt.Sprintf("/seasons/%s/leagues/%s/free-agents/register", season.ShortName, league.ShortName) }
|
||||
hx-swap="none"
|
||||
>
|
||||
if len(availablePlayers) == 0 {
|
||||
<p class="text-subtext0 mb-4">No players available to register as free agents. All players are either on a team or already registered.</p>
|
||||
} else {
|
||||
<div class="mb-4">
|
||||
<label for="player_id" class="block text-sm font-medium mb-2">Select Player</label>
|
||||
<select
|
||||
id="player_id"
|
||||
name="player_id"
|
||||
x-model="selectedPlayerId"
|
||||
required
|
||||
class="w-full py-3 px-4 rounded-lg text-sm bg-base border-2 border-overlay0
|
||||
focus:border-blue outline-none"
|
||||
>
|
||||
<option value="">Choose a player...</option>
|
||||
for _, player := range availablePlayers {
|
||||
<option value={ fmt.Sprint(player.ID) }>
|
||||
{ player.DisplayName() }
|
||||
</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
}
|
||||
<div class="flex gap-3 justify-end">
|
||||
<button
|
||||
type="button"
|
||||
@click="showAddModal = false"
|
||||
class="px-4 py-2 rounded-lg bg-surface0 hover:bg-surface1 text-text transition hover:cursor-pointer"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
if len(availablePlayers) > 0 {
|
||||
<button
|
||||
type="submit"
|
||||
:disabled="!selectedPlayerId"
|
||||
class="px-4 py-2 rounded-lg bg-green hover:bg-green/75 text-mantle transition
|
||||
disabled:bg-green/40 disabled:cursor-not-allowed hover:cursor-pointer"
|
||||
>
|
||||
Register Free Agent
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@@ -120,6 +120,7 @@ templ SeasonLeagueLayout(activeSection string, season *db.Season, league *db.Lea
|
||||
@leagueNavItem("table", "Table", activeSection, season, league)
|
||||
@leagueNavItem("fixtures", "Fixtures", activeSection, season, league)
|
||||
@leagueNavItem("teams", "Teams", activeSection, season, league)
|
||||
@leagueNavItem("free-agents", "Free Agents", activeSection, season, league)
|
||||
@leagueNavItem("stats", "Stats", activeSection, season, league)
|
||||
@leagueNavItem("finals", "Finals", activeSection, season, league)
|
||||
</ul>
|
||||
|
||||
7
internal/view/seasonsview/types.go
Normal file
7
internal/view/seasonsview/types.go
Normal file
@@ -0,0 +1,7 @@
|
||||
package seasonsview
|
||||
|
||||
// FreeAgentWarning holds information about a free agent nomination issue for display.
|
||||
type FreeAgentWarning struct {
|
||||
Name string
|
||||
Reason string
|
||||
}
|
||||
Reference in New Issue
Block a user