im actually goated
This commit is contained in:
@@ -489,9 +489,6 @@
|
|||||||
.w-20 {
|
.w-20 {
|
||||||
width: calc(var(--spacing) * 20);
|
width: calc(var(--spacing) * 20);
|
||||||
}
|
}
|
||||||
.w-24 {
|
|
||||||
width: calc(var(--spacing) * 24);
|
|
||||||
}
|
|
||||||
.w-26 {
|
.w-26 {
|
||||||
width: calc(var(--spacing) * 26);
|
width: calc(var(--spacing) * 26);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package handlers
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
@@ -69,6 +70,7 @@ func UpdateFixtures(
|
|||||||
leagueShortName := getter.String("league_short_name").TrimSpace().Required().Value
|
leagueShortName := getter.String("league_short_name").TrimSpace().Required().Value
|
||||||
allocations := getter.GetMaps("allocations")
|
allocations := getter.GetMaps("allocations")
|
||||||
if !getter.ValidateAndNotify(s, w, r) {
|
if !getter.ValidateAndNotify(s, w, r) {
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
updates, err := mapUpdates(allocations)
|
updates, err := mapUpdates(allocations)
|
||||||
@@ -91,6 +93,7 @@ func UpdateFixtures(
|
|||||||
}
|
}
|
||||||
var valid bool
|
var valid bool
|
||||||
fixtures, valid = updateFixtures(fixtures, updates)
|
fixtures, valid = updateFixtures(fixtures, updates)
|
||||||
|
fmt.Println(len(fixtures))
|
||||||
if !valid {
|
if !valid {
|
||||||
notify.Warn(s, w, r, "Invalid game weeks", "A game week is missing or has no games", nil)
|
notify.Warn(s, w, r, "Invalid game weeks", "A game week is missing or has no games", nil)
|
||||||
return false, nil
|
return false, nil
|
||||||
@@ -158,10 +161,27 @@ func updateFixtures(fixtures []*db.Fixture, updates map[int]int) ([]*db.Fixture,
|
|||||||
gameWeeks := map[int]int{}
|
gameWeeks := map[int]int{}
|
||||||
for _, fixture := range fixtures {
|
for _, fixture := range fixtures {
|
||||||
if gameWeek, exists := updates[fixture.ID]; exists {
|
if gameWeek, exists := updates[fixture.ID]; exists {
|
||||||
fixture.GameWeek = &gameWeek
|
var newValue *int
|
||||||
updated = append(updated, fixture)
|
var oldValue int
|
||||||
|
if fixture.GameWeek != nil {
|
||||||
|
oldValue = *fixture.GameWeek
|
||||||
|
} else {
|
||||||
|
oldValue = 0
|
||||||
|
}
|
||||||
|
if gameWeek == 0 {
|
||||||
|
newValue = nil
|
||||||
|
} else {
|
||||||
|
newValue = &gameWeek
|
||||||
|
}
|
||||||
|
if gameWeek != oldValue {
|
||||||
|
fixture.GameWeek = newValue
|
||||||
|
updated = append(updated, fixture)
|
||||||
|
}
|
||||||
|
// fuck i hate pointers sometimes
|
||||||
|
}
|
||||||
|
if fixture.GameWeek != nil {
|
||||||
|
gameWeeks[*fixture.GameWeek]++
|
||||||
}
|
}
|
||||||
gameWeeks[*fixture.GameWeek]++
|
|
||||||
}
|
}
|
||||||
for i := range len(gameWeeks) {
|
for i := range len(gameWeeks) {
|
||||||
count, exists := gameWeeks[i+1]
|
count, exists := gameWeeks[i+1]
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package validation
|
|||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"git.haelnorr.com/h/golib/hws"
|
"git.haelnorr.com/h/golib/hws"
|
||||||
@@ -32,18 +31,24 @@ func (f *FormGetter) GetList(key string) []string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (f *FormGetter) GetMaps(key string) []map[string]string {
|
func (f *FormGetter) GetMaps(key string) []map[string]string {
|
||||||
var result []map[string]string
|
results := map[string]map[string]string{}
|
||||||
for key, values := range f.r.Form {
|
re := regexp.MustCompile(key + "\\[([0-9]+)\\]\\[([a-zA-Z_]+)\\]")
|
||||||
re := regexp.MustCompile(key + "\\[([0-9]+)\\]\\[([a-zA-Z]+)\\]")
|
for k, v := range f.r.Form {
|
||||||
matches := re.FindStringSubmatch(key)
|
matches := re.FindStringSubmatch(k)
|
||||||
if len(matches) >= 3 {
|
if len(matches) >= 3 {
|
||||||
index, _ := strconv.Atoi(matches[1])
|
realKey := matches[1]
|
||||||
for index >= len(result) {
|
field := matches[2]
|
||||||
result = append(result, map[string]string{})
|
value := strings.Join(v, ",")
|
||||||
|
if _, exists := results[realKey]; !exists {
|
||||||
|
results[realKey] = map[string]string{}
|
||||||
}
|
}
|
||||||
result[index][matches[2]] = values[0]
|
results[realKey][field] = value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
result := []map[string]string{}
|
||||||
|
for _, v := range results {
|
||||||
|
result = append(result, v)
|
||||||
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -60,18 +60,19 @@ templ SeasonLeagueManageFixtures(season *db.Season, league *db.League, fixtures
|
|||||||
<div class="flex items-center gap-3 mb-6">
|
<div class="flex items-center gap-3 mb-6">
|
||||||
<!-- Generate -->
|
<!-- Generate -->
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<input
|
<label class="text-sm text-subtext0">Round:</label>
|
||||||
type="number"
|
<select
|
||||||
x-model.number="generateRounds"
|
x-model.number="generateRounds"
|
||||||
min="1"
|
class="py-2 px-3 rounded-lg text-sm bg-base border-2 border-overlay0
|
||||||
max="20"
|
|
||||||
placeholder="Rounds"
|
|
||||||
class="w-24 py-2 px-3 rounded-lg text-sm bg-base border-2 border-overlay0
|
|
||||||
focus:border-blue outline-none text-text"
|
focus:border-blue outline-none text-text"
|
||||||
/>
|
>
|
||||||
|
<template x-for="r in availableRounds" :key="r">
|
||||||
|
<option :value="r" x-text="r"></option>
|
||||||
|
</template>
|
||||||
|
</select>
|
||||||
<button
|
<button
|
||||||
@click="generate()"
|
@click="generate()"
|
||||||
:disabled="isGenerating || generateRounds < 1"
|
:disabled="isGenerating || availableRounds.length === 0"
|
||||||
class="rounded-lg px-4 py-2 hover:cursor-pointer text-center text-sm
|
class="rounded-lg px-4 py-2 hover:cursor-pointer text-center text-sm
|
||||||
bg-blue hover:bg-blue/80 text-mantle transition
|
bg-blue hover:bg-blue/80 text-mantle transition
|
||||||
disabled:bg-blue/40 disabled:cursor-not-allowed"
|
disabled:bg-blue/40 disabled:cursor-not-allowed"
|
||||||
@@ -79,32 +80,40 @@ templ SeasonLeagueManageFixtures(season *db.Season, league *db.League, fixtures
|
|||||||
<span x-text="isGenerating ? 'Generating...' : 'Generate Round'"></span>
|
<span x-text="isGenerating ? 'Generating...' : 'Generate Round'"></span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<!-- Clear All -->
|
<!-- Delete All -->
|
||||||
<button
|
<button
|
||||||
x-show="allFixtures.length > 0"
|
x-show="allFixtures.length > 0"
|
||||||
@click="clearAll()"
|
@click="deleteAll()"
|
||||||
class="rounded-lg px-4 py-2 hover:cursor-pointer text-center text-sm
|
class="rounded-lg px-4 py-2 hover:cursor-pointer text-center text-sm
|
||||||
bg-red hover:bg-red/80 text-mantle transition"
|
bg-red hover:bg-red/80 text-mantle transition"
|
||||||
>
|
>
|
||||||
Clear All
|
Delete All
|
||||||
</button>
|
</button>
|
||||||
<!-- Save -->
|
<!-- Save / Reset -->
|
||||||
<button
|
<div x-show="unsavedChanges" class="flex items-center gap-2 ml-auto">
|
||||||
x-show="unsavedChanges"
|
<span
|
||||||
@click="save()"
|
x-show="!canSave()"
|
||||||
:disabled="isSaving || !canSave()"
|
class="text-yellow text-xs"
|
||||||
class="rounded-lg px-4 py-2 hover:cursor-pointer text-center text-sm
|
>
|
||||||
bg-green hover:bg-green/75 text-mantle transition
|
All game weeks must have at least 1 fixture
|
||||||
disabled:bg-green/40 disabled:cursor-not-allowed ml-auto"
|
</span>
|
||||||
>
|
<button
|
||||||
<span x-text="isSaving ? 'Saving...' : 'Save'"></span>
|
@click="reset()"
|
||||||
</button>
|
class="rounded-lg px-4 py-2 hover:cursor-pointer text-center text-sm
|
||||||
<span
|
bg-surface1 hover:bg-surface2 text-text transition"
|
||||||
x-show="unsavedChanges && !canSave()"
|
>
|
||||||
class="text-yellow text-xs ml-2"
|
Reset
|
||||||
>
|
</button>
|
||||||
All game weeks must have at least 1 fixture
|
<button
|
||||||
</span>
|
@click="save()"
|
||||||
|
:disabled="isSaving || !canSave()"
|
||||||
|
class="rounded-lg px-4 py-2 hover:cursor-pointer text-center text-sm
|
||||||
|
bg-green hover:bg-green/75 text-mantle transition
|
||||||
|
disabled:bg-green/40 disabled:cursor-not-allowed"
|
||||||
|
>
|
||||||
|
<span x-text="isSaving ? 'Saving...' : 'Save'"></span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- Main content panels -->
|
<!-- Main content panels -->
|
||||||
<div class="grid grid-cols-1 lg:grid-cols-3 gap-4">
|
<div class="grid grid-cols-1 lg:grid-cols-3 gap-4">
|
||||||
@@ -114,7 +123,7 @@ templ SeasonLeagueManageFixtures(season *db.Season, league *db.League, fixtures
|
|||||||
<div x-show="selectedGameWeek === null">
|
<div x-show="selectedGameWeek === null">
|
||||||
<h3 class="text-lg font-bold text-text mb-4">Game Weeks</h3>
|
<h3 class="text-lg font-bold text-text mb-4">Game Weeks</h3>
|
||||||
<div x-show="allGameWeekNumbers.length === 0" class="text-subtext0 text-sm italic mb-4">
|
<div x-show="allGameWeekNumbers.length === 0" class="text-subtext0 text-sm italic mb-4">
|
||||||
No game weeks yet. Add one to start allocating fixtures.
|
No game weeks yet. Generate fixtures to get started.
|
||||||
</div>
|
</div>
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<template x-for="week in allGameWeekNumbers" :key="week">
|
<template x-for="week in allGameWeekNumbers" :key="week">
|
||||||
@@ -271,206 +280,272 @@ templ SeasonLeagueManageFixtures(season *db.Season, league *db.League, fixtures
|
|||||||
</div>
|
</div>
|
||||||
<!-- Alpine.js component -->
|
<!-- Alpine.js component -->
|
||||||
<script>
|
<script>
|
||||||
document.addEventListener('alpine:init', () => {
|
document.addEventListener("alpine:init", () => {
|
||||||
Alpine.data('fixturesManager', (initialFixtures, seasonShortName, leagueShortName) => ({
|
Alpine.data(
|
||||||
allFixtures: initialFixtures || [],
|
"fixturesManager",
|
||||||
seasonShortName: seasonShortName,
|
(initialFixtures, seasonShortName, leagueShortName) => ({
|
||||||
leagueShortName: leagueShortName,
|
allFixtures: initialFixtures || [],
|
||||||
|
_initialFixtures: JSON.parse(JSON.stringify(initialFixtures || [])),
|
||||||
|
seasonShortName: seasonShortName,
|
||||||
|
leagueShortName: leagueShortName,
|
||||||
|
|
||||||
// UI state
|
// UI state
|
||||||
selectedGameWeek: null,
|
selectedGameWeek: null,
|
||||||
unsavedChanges: false,
|
unsavedChanges: false,
|
||||||
isSaving: false,
|
isSaving: false,
|
||||||
isGenerating: false,
|
isGenerating: false,
|
||||||
generateRounds: 1,
|
generateRounds: null,
|
||||||
|
|
||||||
// Drag state
|
init() {
|
||||||
draggedFixture: null,
|
this.generateRounds =
|
||||||
dropTarget: null,
|
this.availableRounds.length > 0 ? this.availableRounds[0] : 1;
|
||||||
|
},
|
||||||
|
|
||||||
// Computed
|
// Drag state
|
||||||
get unallocatedFixtures() {
|
draggedFixture: null,
|
||||||
return this.allFixtures
|
dropTarget: null,
|
||||||
.filter(f => f.gameWeek === null)
|
|
||||||
.sort((a, b) => a.round - b.round || a.id - b.id);
|
|
||||||
},
|
|
||||||
|
|
||||||
get allGameWeekNumbers() {
|
// Computed
|
||||||
const weeks = new Set();
|
get unallocatedFixtures() {
|
||||||
for (const f of this.allFixtures) {
|
return this.allFixtures
|
||||||
if (f.gameWeek !== null) {
|
.filter((f) => f.gameWeek === null)
|
||||||
weeks.add(f.gameWeek);
|
.sort((a, b) => a.round - b.round || a.id - b.id);
|
||||||
}
|
},
|
||||||
}
|
|
||||||
// Also include manually added empty weeks
|
|
||||||
for (const w of this._emptyWeeks || []) {
|
|
||||||
weeks.add(w);
|
|
||||||
}
|
|
||||||
return [...weeks].sort((a, b) => a - b);
|
|
||||||
},
|
|
||||||
|
|
||||||
// Track empty weeks that user created
|
get allGameWeekNumbers() {
|
||||||
_emptyWeeks: [],
|
const weeks = new Set();
|
||||||
|
for (const f of this.allFixtures) {
|
||||||
getGameWeekFixtures(week) {
|
if (f.gameWeek !== null) {
|
||||||
return this.allFixtures
|
weeks.add(f.gameWeek);
|
||||||
.filter(f => f.gameWeek === week)
|
|
||||||
.sort((a, b) => a.round - b.round || a.id - b.id);
|
|
||||||
},
|
|
||||||
|
|
||||||
getFixtureCount(week) {
|
|
||||||
return this.allFixtures.filter(f => f.gameWeek === week).length;
|
|
||||||
},
|
|
||||||
|
|
||||||
getPreview(week) {
|
|
||||||
return this.getGameWeekFixtures(week).slice(0, 3);
|
|
||||||
},
|
|
||||||
|
|
||||||
// Game week management
|
|
||||||
addGameWeek() {
|
|
||||||
const existing = this.allGameWeekNumbers;
|
|
||||||
const next = existing.length > 0 ? Math.max(...existing) + 1 : 1;
|
|
||||||
this._emptyWeeks.push(next);
|
|
||||||
this.unsavedChanges = true;
|
|
||||||
},
|
|
||||||
|
|
||||||
deleteGameWeek(week) {
|
|
||||||
if (this.getFixtureCount(week) > 0) return;
|
|
||||||
this._emptyWeeks = this._emptyWeeks.filter(w => w !== week);
|
|
||||||
if (this.selectedGameWeek === week) {
|
|
||||||
this.selectedGameWeek = null;
|
|
||||||
}
|
|
||||||
this.unsavedChanges = true;
|
|
||||||
},
|
|
||||||
|
|
||||||
selectGameWeek(week) {
|
|
||||||
this.selectedGameWeek = week;
|
|
||||||
},
|
|
||||||
|
|
||||||
backToList() {
|
|
||||||
this.selectedGameWeek = null;
|
|
||||||
},
|
|
||||||
|
|
||||||
// Drag and drop
|
|
||||||
onDragStart(event, fixture) {
|
|
||||||
this.draggedFixture = fixture;
|
|
||||||
event.dataTransfer.effectAllowed = 'move';
|
|
||||||
event.dataTransfer.setData('text/plain', fixture.id);
|
|
||||||
},
|
|
||||||
|
|
||||||
onDragEnd() {
|
|
||||||
this.draggedFixture = null;
|
|
||||||
this.dropTarget = null;
|
|
||||||
},
|
|
||||||
|
|
||||||
onDrop(target) {
|
|
||||||
if (!this.draggedFixture) return;
|
|
||||||
|
|
||||||
const fixture = this.allFixtures.find(f => f.id === this.draggedFixture.id);
|
|
||||||
if (!fixture) return;
|
|
||||||
|
|
||||||
if (target === 'unallocated') {
|
|
||||||
fixture.gameWeek = null;
|
|
||||||
} else {
|
|
||||||
fixture.gameWeek = target;
|
|
||||||
// Remove from empty weeks if it now has fixtures
|
|
||||||
this._emptyWeeks = this._emptyWeeks.filter(w => w !== target);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.unsavedChanges = true;
|
|
||||||
this.draggedFixture = null;
|
|
||||||
this.dropTarget = null;
|
|
||||||
},
|
|
||||||
|
|
||||||
unallocateFixture(fixture) {
|
|
||||||
const f = this.allFixtures.find(ff => ff.id === fixture.id);
|
|
||||||
if (f) {
|
|
||||||
const oldWeek = f.gameWeek;
|
|
||||||
f.gameWeek = null;
|
|
||||||
// If the old week is now empty, track it
|
|
||||||
if (oldWeek !== null && this.getFixtureCount(oldWeek) === 0) {
|
|
||||||
this._emptyWeeks.push(oldWeek);
|
|
||||||
}
|
|
||||||
this.unsavedChanges = true;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// Validation
|
|
||||||
canSave() {
|
|
||||||
const weeks = this.allGameWeekNumbers;
|
|
||||||
if (weeks.length === 0) return false;
|
|
||||||
for (const week of weeks) {
|
|
||||||
if (this.getFixtureCount(week) === 0) return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
|
|
||||||
// Server actions
|
|
||||||
generate() {
|
|
||||||
if (this.generateRounds < 1) return;
|
|
||||||
this.isGenerating = true;
|
|
||||||
|
|
||||||
const form = new FormData();
|
|
||||||
form.append('season_short_name', this.seasonShortName);
|
|
||||||
form.append('league_short_name', this.leagueShortName);
|
|
||||||
form.append('round', this.generateRounds);
|
|
||||||
|
|
||||||
htmx.ajax('POST', '/fixtures/generate', {
|
|
||||||
target: '#manage-fixtures-content',
|
|
||||||
swap: 'outerHTML',
|
|
||||||
values: Object.fromEntries(form)
|
|
||||||
}).finally(() => {
|
|
||||||
this.isGenerating = false;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
save() {
|
|
||||||
if (!this.canSave()) return;
|
|
||||||
this.isSaving = true;
|
|
||||||
|
|
||||||
const form = new FormData();
|
|
||||||
form.append('season_short_name', this.seasonShortName);
|
|
||||||
form.append('league_short_name', this.leagueShortName);
|
|
||||||
|
|
||||||
this.allFixtures.forEach((f, i) => {
|
|
||||||
form.append('allocations[' + i + '][id]', f.id);
|
|
||||||
form.append('allocations[' + i + '][game_week]', f.gameWeek !== null ? f.gameWeek : 0);
|
|
||||||
});
|
|
||||||
|
|
||||||
fetch('/fixtures/update-game-weeks', {
|
|
||||||
method: 'POST',
|
|
||||||
body: form
|
|
||||||
}).then(response => {
|
|
||||||
if (response.ok) {
|
|
||||||
this.unsavedChanges = false;
|
|
||||||
}
|
|
||||||
this.isSaving = false;
|
|
||||||
}).catch(() => {
|
|
||||||
this.isSaving = false;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
clearAll() {
|
|
||||||
const seasonShort = this.seasonShortName;
|
|
||||||
const leagueShort = this.leagueShortName;
|
|
||||||
|
|
||||||
window.dispatchEvent(new CustomEvent('confirm-action', {
|
|
||||||
detail: {
|
|
||||||
title: 'Clear All Fixtures',
|
|
||||||
message: 'This will delete all fixtures for this league. This action cannot be undone.',
|
|
||||||
action: () => {
|
|
||||||
htmx.ajax('DELETE',
|
|
||||||
'/seasons/' + seasonShort + '/leagues/' + leagueShort + '/fixtures',
|
|
||||||
{
|
|
||||||
target: '#manage-fixtures-content',
|
|
||||||
swap: 'outerHTML'
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}));
|
// Also include manually added empty weeks
|
||||||
},
|
for (const w of this._emptyWeeks || []) {
|
||||||
}));
|
weeks.add(w);
|
||||||
|
}
|
||||||
|
return [...weeks].sort((a, b) => a - b);
|
||||||
|
},
|
||||||
|
|
||||||
|
get existingRounds() {
|
||||||
|
const rounds = new Set();
|
||||||
|
for (const f of this.allFixtures) {
|
||||||
|
rounds.add(f.round);
|
||||||
|
}
|
||||||
|
return rounds;
|
||||||
|
},
|
||||||
|
|
||||||
|
get availableRounds() {
|
||||||
|
const taken = this.existingRounds;
|
||||||
|
const maxTaken = taken.size > 0 ? Math.max(...taken) : 0;
|
||||||
|
const limit = maxTaken + 5;
|
||||||
|
const available = [];
|
||||||
|
for (let i = 1; i <= limit; i++) {
|
||||||
|
if (!taken.has(i)) available.push(i);
|
||||||
|
}
|
||||||
|
return available;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Track empty weeks that user created
|
||||||
|
// Default to [1] if no fixtures have game weeks assigned
|
||||||
|
_emptyWeeks: (initialFixtures || []).some(
|
||||||
|
(f) => f.gameWeek !== null,
|
||||||
|
)
|
||||||
|
? []
|
||||||
|
: [1],
|
||||||
|
|
||||||
|
getGameWeekFixtures(week) {
|
||||||
|
return this.allFixtures
|
||||||
|
.filter((f) => f.gameWeek === week)
|
||||||
|
.sort((a, b) => a.round - b.round || a.id - b.id);
|
||||||
|
},
|
||||||
|
|
||||||
|
getFixtureCount(week) {
|
||||||
|
return this.allFixtures.filter((f) => f.gameWeek === week).length;
|
||||||
|
},
|
||||||
|
|
||||||
|
getPreview(week) {
|
||||||
|
return this.getGameWeekFixtures(week).slice(0, 3);
|
||||||
|
},
|
||||||
|
|
||||||
|
// Game week management
|
||||||
|
addGameWeek() {
|
||||||
|
const existing = this.allGameWeekNumbers;
|
||||||
|
const next = existing.length > 0 ? Math.max(...existing) + 1 : 1;
|
||||||
|
this._emptyWeeks.push(next);
|
||||||
|
this.unsavedChanges = true;
|
||||||
|
},
|
||||||
|
|
||||||
|
deleteGameWeek(week) {
|
||||||
|
if (this.getFixtureCount(week) > 0) return;
|
||||||
|
this._emptyWeeks = this._emptyWeeks.filter((w) => w !== week);
|
||||||
|
if (this.selectedGameWeek === week) {
|
||||||
|
this.selectedGameWeek = null;
|
||||||
|
}
|
||||||
|
this.unsavedChanges = true;
|
||||||
|
},
|
||||||
|
|
||||||
|
selectGameWeek(week) {
|
||||||
|
this.selectedGameWeek = week;
|
||||||
|
},
|
||||||
|
|
||||||
|
backToList() {
|
||||||
|
this.selectedGameWeek = null;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Drag and drop
|
||||||
|
onDragStart(event, fixture) {
|
||||||
|
this.draggedFixture = fixture;
|
||||||
|
event.dataTransfer.effectAllowed = "move";
|
||||||
|
event.dataTransfer.setData("text/plain", fixture.id);
|
||||||
|
},
|
||||||
|
|
||||||
|
onDragEnd() {
|
||||||
|
this.draggedFixture = null;
|
||||||
|
this.dropTarget = null;
|
||||||
|
},
|
||||||
|
|
||||||
|
onDrop(target) {
|
||||||
|
if (!this.draggedFixture) return;
|
||||||
|
|
||||||
|
const fixture = this.allFixtures.find(
|
||||||
|
(f) => f.id === this.draggedFixture.id,
|
||||||
|
);
|
||||||
|
if (!fixture) return;
|
||||||
|
|
||||||
|
if (target === "unallocated") {
|
||||||
|
fixture.gameWeek = null;
|
||||||
|
} else {
|
||||||
|
fixture.gameWeek = target;
|
||||||
|
// Remove from empty weeks if it now has fixtures
|
||||||
|
this._emptyWeeks = this._emptyWeeks.filter((w) => w !== target);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.unsavedChanges = true;
|
||||||
|
this.draggedFixture = null;
|
||||||
|
this.dropTarget = null;
|
||||||
|
},
|
||||||
|
|
||||||
|
unallocateFixture(fixture) {
|
||||||
|
const f = this.allFixtures.find((ff) => ff.id === fixture.id);
|
||||||
|
if (f) {
|
||||||
|
const oldWeek = f.gameWeek;
|
||||||
|
f.gameWeek = null;
|
||||||
|
// If the old week is now empty, track it
|
||||||
|
if (oldWeek !== null && this.getFixtureCount(oldWeek) === 0) {
|
||||||
|
this._emptyWeeks.push(oldWeek);
|
||||||
|
}
|
||||||
|
this.unsavedChanges = true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Validation
|
||||||
|
canSave() {
|
||||||
|
const weeks = this.allGameWeekNumbers;
|
||||||
|
if (weeks.length === 0) return false;
|
||||||
|
for (const week of weeks) {
|
||||||
|
if (this.getFixtureCount(week) === 0) return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
|
||||||
|
reset() {
|
||||||
|
this.allFixtures = JSON.parse(
|
||||||
|
JSON.stringify(this._initialFixtures),
|
||||||
|
);
|
||||||
|
this._emptyWeeks = this._initialFixtures.some(
|
||||||
|
(f) => f.gameWeek !== null,
|
||||||
|
)
|
||||||
|
? []
|
||||||
|
: [1];
|
||||||
|
this.selectedGameWeek = null;
|
||||||
|
this.unsavedChanges = false;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Server actions
|
||||||
|
generate() {
|
||||||
|
if (this.generateRounds < 1) return;
|
||||||
|
this.isGenerating = true;
|
||||||
|
|
||||||
|
const form = new FormData();
|
||||||
|
form.append("season_short_name", this.seasonShortName);
|
||||||
|
form.append("league_short_name", this.leagueShortName);
|
||||||
|
form.append("round", this.generateRounds);
|
||||||
|
|
||||||
|
htmx
|
||||||
|
.ajax("POST", "/fixtures/generate", {
|
||||||
|
target: "#manage-fixtures-content",
|
||||||
|
swap: "outerHTML",
|
||||||
|
values: Object.fromEntries(form),
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
this.isGenerating = false;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
save() {
|
||||||
|
if (!this.canSave()) return;
|
||||||
|
this.isSaving = true;
|
||||||
|
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append("season_short_name", this.seasonShortName);
|
||||||
|
params.append("league_short_name", this.leagueShortName);
|
||||||
|
|
||||||
|
this.allFixtures.forEach((f, i) => {
|
||||||
|
params.append("allocations[" + i + "][id]", f.id);
|
||||||
|
params.append(
|
||||||
|
"allocations[" + i + "][game_week]",
|
||||||
|
f.gameWeek !== null ? f.gameWeek : 0,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
fetch("/fixtures/update-game-weeks", {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/x-www-form-urlencoded",
|
||||||
|
},
|
||||||
|
body: params.toString(),
|
||||||
|
})
|
||||||
|
.then((response) => {
|
||||||
|
if (response.ok) {
|
||||||
|
this.unsavedChanges = false;
|
||||||
|
}
|
||||||
|
this.isSaving = false;
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
this.isSaving = false;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
deleteAll() {
|
||||||
|
const seasonShort = this.seasonShortName;
|
||||||
|
const leagueShort = this.leagueShortName;
|
||||||
|
|
||||||
|
window.dispatchEvent(
|
||||||
|
new CustomEvent("confirm-action", {
|
||||||
|
detail: {
|
||||||
|
title: "Delete All Fixtures",
|
||||||
|
message:
|
||||||
|
"This will delete all fixtures for this league. This action cannot be undone.",
|
||||||
|
action: () => {
|
||||||
|
htmx.ajax(
|
||||||
|
"DELETE",
|
||||||
|
"/seasons/" +
|
||||||
|
seasonShort +
|
||||||
|
"/leagues/" +
|
||||||
|
leagueShort +
|
||||||
|
"/fixtures",
|
||||||
|
{
|
||||||
|
target: "#manage-fixtures-content",
|
||||||
|
swap: "outerHTML",
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user