Remove manual course creation; import-only with per-tee stroke indices

Courses can now only be added via Golf Course API import, which
provides the full data model: per-tee slope/rating/gender/SI
(stroke_indices JSON) and front/back split ratings. Manual creation
required admins to enter SI by hand and didn't match the API model.

Changes:
- Remove GET/POST /admin/courses/new routes and new_course.html
- Remove _parse_hole_form helper (no longer needed)
- Edit course: par-only editing; stroke_index preserved from import,
  not exposed or overwritten in the admin form
- courses.html: replace "+ New course" with "+ Import course" button
- course_search.html: remove "+ Manual" shortcut
- edit_course.html: remove SI column from hole grid

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Rolf
2026-03-24 20:03:19 +01:00
parent a4ce8a9d50
commit 06995c5668
5 changed files with 16 additions and 182 deletions
-1
View File
@@ -15,7 +15,6 @@
<div style="display:flex; gap:0.5rem; justify-content:flex-end; margin-bottom:1rem;">
<a href="/admin/courses" class="btn" style="width:auto; padding:0.5rem 1rem; background:var(--surface2); color:var(--text);">← Courses</a>
<a href="/admin/courses/new" class="btn btn-primary" style="width:auto; padding:0.5rem 1rem;">+ Manual</a>
</div>
{% if not api_configured %}
+2 -3
View File
@@ -18,8 +18,7 @@
{% endif %}
<div style="display:flex; gap:0.5rem; justify-content:flex-end; margin-bottom:1rem;">
<a href="/admin/courses/search" class="btn" style="width:auto; padding:0.5rem 1rem; background:var(--surface2); color:var(--text);">Search & Import</a>
<a href="/admin/courses/new" class="btn btn-primary" style="width:auto; padding:0.5rem 1rem;">+ New course</a>
<a href="/admin/courses/search" class="btn btn-primary" style="width:auto; padding:0.5rem 1rem;">+ Import course</a>
</div>
{% if courses %}
@@ -50,7 +49,7 @@
<div class="empty-state">
<div class="empty-icon"></div>
<p>No courses yet.</p>
<a href="/admin/courses/new" class="btn btn-primary" style="margin-top:1rem; max-width:200px;">Add a course</a>
<a href="/admin/courses/search" class="btn btn-primary" style="margin-top:1rem; max-width:200px;">Import a course</a>
</div>
{% endif %}
</div>
-6
View File
@@ -58,12 +58,6 @@
<option value="4" {% if h.par == 4 %}selected{% endif %}>4</option>
<option value="5" {% if h.par == 5 %}selected{% endif %}>5</option>
</select>
<select name="si_{{ h.hole_number }}" class="hole-par-select">
<option value=""></option>
{% for s in range(1, course.holes + 1) %}
<option value="{{ s }}" {% if h.stroke_index == s %}selected{% endif %}>{{ s }}</option>
{% endfor %}
</select>
</div>
{% endfor %}
</div>
-81
View File
@@ -1,81 +0,0 @@
{% extends "base.html" %}
{% block title %}New Course Skins &amp; Pins{% endblock %}
{% block content %}
<div class="page">
<div class="page-header">
<a href="/admin/courses" style="color:var(--text-muted); font-size:0.9rem;">← Courses</a>
<h1 style="margin-top:0.25rem;">New course</h1>
</div>
<form method="post" action="/admin/courses/new"
x-data="{
holes: {{ (form.holes if form else 18)|int }},
initPars: {{ form.pars | tojson if (form and form.pars) else '{}' }},
initSIs: {{ form.stroke_indices | tojson if (form and form.stroke_indices) else '{}' }},
pars: {},
coursePar() { return Object.values(this.pars).reduce((a, b) => a + parseInt(b || 4), 0); },
initParState() { this.pars = {}; for (let i = 1; i <= this.holes; i++) this.pars[i] = this.initPars[String(i)] || 4; }
}"
x-init="initParState(); $watch('holes', () => initParState())">
<div class="card">
<div class="form-group">
<label for="name">Course name</label>
{% if errors and errors.name %}<div class="alert alert-error">{{ errors.name }}</div>{% endif %}
<input type="text" id="name" name="name"
value="{{ form.name if form else '' }}" placeholder="e.g. Augusta National" required>
</div>
<div class="form-group">
<label for="location">Location <span style="color:var(--text-muted); font-size:0.8rem;">(optional)</span></label>
<input type="text" id="location" name="location"
value="{{ form.location if form else '' }}" placeholder="e.g. Augusta, Georgia">
</div>
<div class="form-group">
<label>Holes</label>
{% if errors and errors.holes %}<div class="alert alert-error">{{ errors.holes }}</div>{% endif %}
<div style="display:flex; gap:0.75rem;">
<label class="radio-card">
<input type="radio" name="holes" value="9" x-model.number="holes">
<span>9 holes</span>
</label>
<label class="radio-card">
<input type="radio" name="holes" value="18" x-model.number="holes">
<span>18 holes</span>
</label>
</div>
</div>
</div>
<div class="card">
<h2 style="font-size:0.95rem; color:var(--text-muted); margin-bottom:0.75rem;">
Hole details
<span style="font-weight:400; font-size:0.85rem;">
— Course par: <strong x-text="coursePar()"></strong>
</span>
</h2>
<div class="hole-pars-grid">
<template x-for="n in Array.from({length: holes}, (_, i) => i + 1)" :key="n">
<div class="hole-par-cell">
<div class="hole-par-label" x-text="n"></div>
<select :name="'par_' + n" class="hole-par-select" x-model="pars[n]">
<option value="3">3</option>
<option value="4">4</option>
<option value="5">5</option>
</select>
<select :name="'si_' + n" class="hole-par-select">
<option value=""></option>
<template x-for="s in Array.from({length: holes}, (_, i) => i + 1)" :key="s">
<option :value="s" :selected="(initSIs[String(n)] || null) == s" x-text="s"></option>
</template>
</select>
</div>
</template>
</div>
</div>
<button type="submit" class="btn btn-primary">Create course</button>
</form>
</div>
{% endblock %}