Refactor and optimize: deduplicate, batch queries, fix bugs

- Remove unused DATABASE_PATH import from games.py
- Extract _new_game_context(db) helper to deduplicate GET/POST error path
- Extract _parse_hole_form() helper to deduplicate hole validation in create/edit course
- Batch N+1 player handicap and tee queries in create_game into two IN() queries
- Fix hole count import bug: use max() across all tees instead of first match
- Fix migration exception handling: re-raise unless "duplicate column name"
- Cache calcSkins() result in _skinsCache; invalidate on score/skip changes
- Collapse two identical Next buttons into one in new_game.html
- Fix stale skinDoubleBackNine checkbox: reset on mode change, use x-model

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Rolf
2026-03-23 22:52:45 +01:00
parent d81d6a2d3c
commit 0be6e908e6
5 changed files with 101 additions and 118 deletions
+7 -1
View File
@@ -307,6 +307,7 @@ function gameApp() {
touchStartX: 0,
dragSrc: null,
dragOver: null,
_skinsCache: null,
init() {
this.players = PLAYERS_DATA;
@@ -339,6 +340,7 @@ function gameApp() {
this.scores[h][uid] = s;
}
this.totals = msg.totals;
this._skinsCache = null;
}
};
this.ws.onclose = () => setTimeout(() => this.connectWs(), 2000);
@@ -358,6 +360,7 @@ function gameApp() {
const cur = this.scores[hole][uid].strokes;
this.scores[hole][uid].strokes = cur ? Math.max(1, cur + delta) : (HOLE_PARS[hole] || 4);
this.dirty.add(hole);
this._skinsCache = null;
},
setBingo(hole, uid) {
@@ -441,6 +444,7 @@ function gameApp() {
}
// Alpine reactivity workaround for Set
this.skipped = new Set(this.skipped);
this._skinsCache = null;
},
isSkipped(hole) {
@@ -503,6 +507,7 @@ function gameApp() {
touchDragEnd() { this.dropPlayer(this.dragOver ?? this.dragSrc); },
calcSkins() {
if (this._skinsCache) return this._skinsCache;
const skins = {};
const money = {};
PLAYERS_DATA.forEach(p => { skins[p.id] = 0; money[p.id] = 0; });
@@ -535,7 +540,8 @@ function gameApp() {
if (!SKINS_CARRYOVER) { pot = 0; moneyPot = 0; }
}
}
return { skins, money, holes, unclaimed: pot, unclaimedMoney: moneyPot };
this._skinsCache = { skins, money, holes, unclaimed: pot, unclaimedMoney: moneyPot };
return this._skinsCache;
},
isSkinWinner(hole, uid) {
+4 -5
View File
@@ -163,7 +163,7 @@
<div x-show="skinValue && holesMode === '18'">
<label style="display:flex; align-items:center; gap:0.5rem; cursor:pointer;">
<input type="checkbox" name="skin_double_back_nine" value="1" style="width:auto;"
@change="skinDoubleBackNine = $event.target.checked">
x-model="skinDoubleBackNine">
<span style="font-weight:400; color:var(--text-muted);">Double value on back nine (holes 1018)</span>
</label>
</div>
@@ -226,11 +226,11 @@
<span>18 holes</span>
</label>
<label class="radio-card" style="flex:1;">
<input type="radio" name="_holes_mode" value="front9" @change="holesMode = 'front9'">
<input type="radio" name="_holes_mode" value="front9" @change="holesMode = 'front9'; skinDoubleBackNine = false">
<span>Front 9</span>
</label>
<label class="radio-card" style="flex:1;">
<input type="radio" name="_holes_mode" value="back9" @change="holesMode = 'back9'">
<input type="radio" name="_holes_mode" value="back9" @change="holesMode = 'back9'; skinDoubleBackNine = false">
<span>Back 9</span>
</label>
</div>
@@ -268,8 +268,7 @@
</div>
<div style="position:sticky; bottom:calc(var(--nav-height) + env(safe-area-inset-bottom)); background:var(--bg); padding:0.75rem 0; margin-top:0.5rem;">
<button type="button" x-show="hasTees()" class="btn btn-primary" @click="step = 2" :disabled="!holesMode">Next →</button>
<button type="button" x-show="!hasTees()" class="btn btn-primary" @click="goToConfirm()" :disabled="!holesMode">Next →</button>
<button type="button" class="btn btn-primary" @click="hasTees() ? step = 2 : goToConfirm()" :disabled="!holesMode">Next →</button>
</div>
</div>