Compare commits
7
Commits
d65f84c3e5
...
05f6b6386d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
05f6b6386d | ||
|
|
ebf52f3443 | ||
|
|
74769f057f | ||
|
|
ad0032bea1 | ||
|
|
935f5bd85b | ||
|
|
d7c491ec22 | ||
|
|
9f72bf3a6a |
No files matched your search
@@ -1 +1,3 @@
|
|||||||
.aider*
|
.aider*
|
||||||
|
backend/__pycache__/
|
||||||
|
backend/golf.db
|
||||||
+86
-54
@@ -7,7 +7,7 @@ import os
|
|||||||
|
|
||||||
DATABASE_URL = "sqlite:///./backend/golf.db"
|
DATABASE_URL = "sqlite:///./backend/golf.db"
|
||||||
engine = create_engine(DATABASE_URL, connect_args={"check_same_thread": False})
|
engine = create_engine(DATABASE_URL, connect_args={"check_same_thread": False})
|
||||||
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine, expire_on_commit=False)
|
||||||
Base = declarative_base()
|
Base = declarative_base()
|
||||||
|
|
||||||
class User(Base):
|
class User(Base):
|
||||||
@@ -28,6 +28,8 @@ class Hole(Base):
|
|||||||
number = Column(Integer, nullable=False)
|
number = Column(Integer, nullable=False)
|
||||||
par = Column(Integer, nullable=False)
|
par = Column(Integer, nullable=False)
|
||||||
yardage = Column(Integer, nullable=False)
|
yardage = Column(Integer, nullable=False)
|
||||||
|
yardage_meters = Column(Integer, default=0)
|
||||||
|
tee = Column(String, default="White")
|
||||||
|
|
||||||
class Round(Base):
|
class Round(Base):
|
||||||
__tablename__ = "rounds"
|
__tablename__ = "rounds"
|
||||||
@@ -35,6 +37,7 @@ class Round(Base):
|
|||||||
user_id = Column(Integer, ForeignKey("users.id"))
|
user_id = Column(Integer, ForeignKey("users.id"))
|
||||||
course_id = Column(Integer, ForeignKey("courses.id"))
|
course_id = Column(Integer, ForeignKey("courses.id"))
|
||||||
status = Column(String, default="in_progress")
|
status = Column(String, default="in_progress")
|
||||||
|
tee = Column(String, default="White")
|
||||||
|
|
||||||
class HoleScore(Base):
|
class HoleScore(Base):
|
||||||
__tablename__ = "hole_scores"
|
__tablename__ = "hole_scores"
|
||||||
@@ -46,6 +49,14 @@ class HoleScore(Base):
|
|||||||
completed = Column(Boolean, default=False)
|
completed = Column(Boolean, default=False)
|
||||||
fairway_hit = Column(Boolean, default=False)
|
fairway_hit = Column(Boolean, default=False)
|
||||||
gir = Column(Boolean, default=False)
|
gir = Column(Boolean, default=False)
|
||||||
|
putts_green = Column(Integer, default=0)
|
||||||
|
putts_off_green = Column(Integer, default=0)
|
||||||
|
penalty_water = Column(Boolean, default=False)
|
||||||
|
penalty_ob = Column(Boolean, default=False)
|
||||||
|
penalty_lost = Column(Boolean, default=False)
|
||||||
|
bunker_greenside = Column(Boolean, default=False)
|
||||||
|
bunker_fairway = Column(Boolean, default=False)
|
||||||
|
unfinished = Column(Boolean, default=False)
|
||||||
|
|
||||||
class Putt(Base):
|
class Putt(Base):
|
||||||
__tablename__ = "putts"
|
__tablename__ = "putts"
|
||||||
@@ -72,6 +83,9 @@ HoleScore.round = relationship("Round", back_populates="hole_scores")
|
|||||||
HoleScore.putts = relationship("Putt", back_populates="hole_score")
|
HoleScore.putts = relationship("Putt", back_populates="hole_score")
|
||||||
Putt.hole_score = relationship("HoleScore", back_populates="putts")
|
Putt.hole_score = relationship("HoleScore", back_populates="putts")
|
||||||
|
|
||||||
|
if os.path.exists("backend/golf.db"):
|
||||||
|
os.remove("backend/golf.db")
|
||||||
|
|
||||||
Base.metadata.create_all(bind=engine)
|
Base.metadata.create_all(bind=engine)
|
||||||
|
|
||||||
app = FastAPI()
|
app = FastAPI()
|
||||||
@@ -86,15 +100,15 @@ def init_mock():
|
|||||||
db.commit()
|
db.commit()
|
||||||
db.refresh(course)
|
db.refresh(course)
|
||||||
holes = [
|
holes = [
|
||||||
Hole(course_id=course.id, number=1, par=4, yardage=350),
|
Hole(course_id=course.id, number=1, par=4, yardage=350, yardage_meters=320, tee="White"),
|
||||||
Hole(course_id=course.id, number=2, par=3, yardage=180),
|
Hole(course_id=course.id, number=2, par=3, yardage=180, yardage_meters=165, tee="White"),
|
||||||
Hole(course_id=course.id, number=3, par=5, yardage=520),
|
Hole(course_id=course.id, number=3, par=5, yardage=520, yardage_meters=480, tee="Yellow"),
|
||||||
Hole(course_id=course.id, number=4, par=4, yardage=390),
|
Hole(course_id=course.id, number=4, par=4, yardage=390, yardage_meters=360, tee="White"),
|
||||||
Hole(course_id=course.id, number=5, par=3, yardage=160),
|
Hole(course_id=course.id, number=5, par=3, yardage=160, yardage_meters=145, tee="Red"),
|
||||||
Hole(course_id=course.id, number=6, par=4, yardage=410),
|
Hole(course_id=course.id, number=6, par=4, yardage=410, yardage_meters=375, tee="Blue"),
|
||||||
Hole(course_id=course.id, number=7, par=5, yardage=540),
|
Hole(course_id=course.id, number=7, par=5, yardage=540, yardage_meters=495, tee="Yellow"),
|
||||||
Hole(course_id=course.id, number=8, par=4, yardage=370),
|
Hole(course_id=course.id, number=8, par=4, yardage=370, yardage_meters=340, tee="White"),
|
||||||
Hole(course_id=course.id, number=9, par=3, yardage=190),
|
Hole(course_id=course.id, number=9, par=3, yardage=190, yardage_meters=175, tee="Red"),
|
||||||
]
|
]
|
||||||
db.add_all(holes)
|
db.add_all(holes)
|
||||||
db.commit()
|
db.commit()
|
||||||
@@ -119,21 +133,22 @@ def signup(req: SignupRequest):
|
|||||||
class StartRoundRequest(BaseModel):
|
class StartRoundRequest(BaseModel):
|
||||||
user_id: int
|
user_id: int
|
||||||
course_id: int
|
course_id: int
|
||||||
|
tee: str = "White"
|
||||||
|
|
||||||
@app.post("/round")
|
@app.post("/round")
|
||||||
def start_round(req: StartRoundRequest):
|
def start_round(req: StartRoundRequest):
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
round_obj = Round(user_id=req.user_id, course_id=req.course_id, status="in_progress")
|
round_obj = Round(user_id=req.user_id, course_id=req.course_id, status="in_progress", tee=req.tee)
|
||||||
db.add(round_obj)
|
db.add(round_obj)
|
||||||
db.commit()
|
db.commit()
|
||||||
db.refresh(round_obj)
|
db.refresh(round_obj)
|
||||||
holes = db.query(Hole).filter(Hole.course_id == req.course_id).all()
|
holes = db.query(Hole).filter(Hole.course_id == req.course_id).all()
|
||||||
for h in holes:
|
for h in holes:
|
||||||
hs = HoleScore(round_id=round_obj.id, hole_number=h.number, strokes=0, skipped=False, completed=False, fairway_hit=False, gir=False)
|
hs = HoleScore(round_id=round_obj.id, hole_number=h.number, strokes=0, skipped=False, completed=False, fairway_hit=False, gir=False, putts_green=0, putts_off_green=0, unfinished=False)
|
||||||
db.add(hs)
|
db.add(hs)
|
||||||
db.commit()
|
db.commit()
|
||||||
db.close()
|
db.close()
|
||||||
return {"id": round_obj.id, "status": round_obj.status}
|
return {"id": round_obj.id, "status": round_obj.status, "tee": round_obj.tee}
|
||||||
|
|
||||||
@app.get("/course")
|
@app.get("/course")
|
||||||
def get_course():
|
def get_course():
|
||||||
@@ -144,7 +159,7 @@ def get_course():
|
|||||||
return {
|
return {
|
||||||
"id": course.id,
|
"id": course.id,
|
||||||
"name": course.name,
|
"name": course.name,
|
||||||
"holes": [{"number": h.number, "par": h.par, "yardage": h.yardage} for h in holes]
|
"holes": [{"number": h.number, "par": h.par, "yardage": h.yardage, "yardage_meters": h.yardage_meters, "tee": h.tee} for h in holes]
|
||||||
}
|
}
|
||||||
|
|
||||||
@app.get("/round/{round_id}")
|
@app.get("/round/{round_id}")
|
||||||
@@ -160,12 +175,11 @@ def get_round(round_id: int):
|
|||||||
"user_id": round_obj.user_id,
|
"user_id": round_obj.user_id,
|
||||||
"course_id": round_obj.course_id,
|
"course_id": round_obj.course_id,
|
||||||
"status": round_obj.status,
|
"status": round_obj.status,
|
||||||
|
"tee": round_obj.tee,
|
||||||
"holes": []
|
"holes": []
|
||||||
}
|
}
|
||||||
for hs in hole_scores:
|
for hs in hole_scores:
|
||||||
putts = db.query(Putt).filter(Putt.hole_score_id == hs.id).all()
|
hole = db.query(Hole).filter(Hole.course_id == round_obj.course_id, Hole.number == hs.hole_number).first()
|
||||||
penalties = db.query(Penalty).filter(Penalty.hole_score_id == hs.id).all()
|
|
||||||
bunkers = db.query(Bunker).filter(Bunker.hole_score_id == hs.id).all()
|
|
||||||
result["holes"].append({
|
result["holes"].append({
|
||||||
"hole_score_id": hs.id,
|
"hole_score_id": hs.id,
|
||||||
"hole_number": hs.hole_number,
|
"hole_number": hs.hole_number,
|
||||||
@@ -174,21 +188,36 @@ def get_round(round_id: int):
|
|||||||
"completed": hs.completed,
|
"completed": hs.completed,
|
||||||
"fairway_hit": hs.fairway_hit,
|
"fairway_hit": hs.fairway_hit,
|
||||||
"gir": hs.gir,
|
"gir": hs.gir,
|
||||||
"putts": [{"location": p.location} for p in putts],
|
"unfinished": hs.unfinished,
|
||||||
"penalties": [{"type": p.type} for p in penalties],
|
"putts_green": hs.putts_green,
|
||||||
"bunkers": [{"location": b.location} for b in bunkers]
|
"putts_off_green": hs.putts_off_green,
|
||||||
|
"penalty_water": hs.penalty_water,
|
||||||
|
"penalty_ob": hs.penalty_ob,
|
||||||
|
"penalty_lost": hs.penalty_lost,
|
||||||
|
"bunker_greenside": hs.bunker_greenside,
|
||||||
|
"bunker_fairway": hs.bunker_fairway,
|
||||||
|
"hole_info": {
|
||||||
|
"par": hole.par if hole else None,
|
||||||
|
"yardage_meters": hole.yardage_meters if hole else 0,
|
||||||
|
"tee": round_obj.tee
|
||||||
|
}
|
||||||
})
|
})
|
||||||
db.close()
|
db.close()
|
||||||
return result
|
return result
|
||||||
|
|
||||||
class UpdateHoleRequest(BaseModel):
|
class UpdateHoleRequest(BaseModel):
|
||||||
strokes: int = 0
|
strokes: int = 1
|
||||||
skipped: bool = False
|
skipped: bool = False
|
||||||
completed: bool = False
|
completed: bool = False
|
||||||
fairway_hit: bool = False
|
fairway_hit: bool = False
|
||||||
putts: list = []
|
unfinished: bool = False
|
||||||
penalties: list = []
|
putts_green: int = 0
|
||||||
bunkers: list = []
|
putts_off_green: int = 0
|
||||||
|
penalty_water: bool = False
|
||||||
|
penalty_ob: bool = False
|
||||||
|
penalty_lost: bool = False
|
||||||
|
bunker_greenside: bool = False
|
||||||
|
bunker_fairway: bool = False
|
||||||
|
|
||||||
@app.post("/hole/{hole_score_id}")
|
@app.post("/hole/{hole_score_id}")
|
||||||
def update_hole(hole_score_id: int, req: UpdateHoleRequest):
|
def update_hole(hole_score_id: int, req: UpdateHoleRequest):
|
||||||
@@ -201,55 +230,58 @@ def update_hole(hole_score_id: int, req: UpdateHoleRequest):
|
|||||||
hs.skipped = req.skipped
|
hs.skipped = req.skipped
|
||||||
hs.completed = req.completed
|
hs.completed = req.completed
|
||||||
hs.fairway_hit = req.fairway_hit
|
hs.fairway_hit = req.fairway_hit
|
||||||
|
hs.unfinished = req.unfinished
|
||||||
|
hs.putts_green = req.putts_green
|
||||||
|
hs.putts_off_green = req.putts_off_green
|
||||||
|
hs.penalty_water = req.penalty_water
|
||||||
|
hs.penalty_ob = req.penalty_ob
|
||||||
|
hs.penalty_lost = req.penalty_lost
|
||||||
|
hs.bunker_greenside = req.bunker_greenside
|
||||||
|
hs.bunker_fairway = req.bunker_fairway
|
||||||
|
|
||||||
db.query(Putt).filter(Putt.hole_score_id == hole_score_id).delete()
|
|
||||||
db.query(Penalty).filter(Penalty.hole_score_id == hole_score_id).delete()
|
|
||||||
db.query(Bunker).filter(Bunker.hole_score_id == hole_score_id).delete()
|
|
||||||
|
|
||||||
for p in req.putts:
|
|
||||||
db.add(Putt(hole_score_id=hole_score_id, location=p.get("location", "green")))
|
|
||||||
for pen in req.penalties:
|
|
||||||
db.add(Penalty(hole_score_id=hole_score_id, type=pen.get("type", "water")))
|
|
||||||
for b in req.bunkers:
|
|
||||||
db.add(Bunker(hole_score_id=hole_score_id, location=b.get("location", "greenside")))
|
|
||||||
|
|
||||||
putts = db.query(Putt).filter(Putt.hole_score_id == hole_score_id).all()
|
|
||||||
putts_count = len(putts)
|
|
||||||
round_obj = db.query(Round).filter(Round.id == hs.round_id).first()
|
round_obj = db.query(Round).filter(Round.id == hs.round_id).first()
|
||||||
hole = db.query(Hole).filter(Hole.course_id == round_obj.course_id, Hole.number == hs.hole_number).first()
|
hole = db.query(Hole).filter(Hole.course_id == round_obj.course_id, Hole.number == hs.hole_number).first()
|
||||||
if hole:
|
if hole:
|
||||||
all_green = all(p.location == "green" for p in putts) if putts else True
|
total_putts = hs.putts_green + hs.putts_off_green
|
||||||
hs.gir = (hs.strokes - putts_count) <= (hole.par - 2) and all_green
|
all_green = hs.putts_off_green == 0
|
||||||
|
hs.gir = (not hs.unfinished) and (hs.strokes - total_putts) <= (hole.par - 2) and all_green
|
||||||
|
|
||||||
db.commit()
|
db.commit()
|
||||||
db.refresh(hs)
|
db.refresh(hs)
|
||||||
db.close()
|
db.close()
|
||||||
return {"hole_score_id": hs.id, "gir": hs.gir, "strokes": hs.strokes, "putts": putts_count}
|
return {
|
||||||
|
"hole_score_id": hs.id,
|
||||||
|
"gir": hs.gir,
|
||||||
|
"strokes": hs.strokes,
|
||||||
|
"putts_green": hs.putts_green,
|
||||||
|
"putts_off_green": hs.putts_off_green,
|
||||||
|
"unfinished": hs.unfinished
|
||||||
|
}
|
||||||
|
|
||||||
@app.get("/stats/{round_id}")
|
@app.get("/stats/{round_id}")
|
||||||
def get_stats(round_id: int):
|
def get_stats(round_id: int):
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
hs_list = db.query(HoleScore).filter(HoleScore.round_id == round_id).all()
|
hs_list = db.query(HoleScore).filter(HoleScore.round_id == round_id).all()
|
||||||
total_strokes = sum(hs.strokes for hs in hs_list if not hs.skipped)
|
total_strokes = sum(hs.strokes for hs in hs_list if not hs.skipped and not hs.unfinished)
|
||||||
total_putts = 0
|
total_putts = sum((hs.putts_green + hs.putts_off_green) for hs in hs_list if not hs.skipped and not hs.unfinished)
|
||||||
fairways = 0
|
fairways = sum(1 for hs in hs_list if hs.fairway_hit and not hs.skipped and not hs.unfinished)
|
||||||
girs = 0
|
girs = sum(1 for hs in hs_list if hs.gir and not hs.skipped and not hs.unfinished)
|
||||||
for hs in hs_list:
|
penalties = sum(
|
||||||
if hs.skipped:
|
(1 if hs.penalty_water else 0) + (1 if hs.penalty_ob else 0) + (1 if hs.penalty_lost else 0)
|
||||||
continue
|
for hs in hs_list if not hs.skipped and not hs.unfinished
|
||||||
putts = db.query(Putt).filter(Putt.hole_score_id == hs.id).all()
|
)
|
||||||
total_putts += len(putts)
|
bunker_greenside = sum(1 for hs in hs_list if hs.bunker_greenside and not hs.skipped and not hs.unfinished)
|
||||||
if hs.fairway_hit:
|
bunker_fairway = sum(1 for hs in hs_list if hs.bunker_fairway and not hs.skipped and not hs.unfinished)
|
||||||
fairways += 1
|
|
||||||
if hs.gir:
|
|
||||||
girs += 1
|
|
||||||
db.close()
|
db.close()
|
||||||
return {
|
return {
|
||||||
"total_strokes": total_strokes,
|
"total_strokes": total_strokes,
|
||||||
"total_putts": total_putts,
|
"total_putts": total_putts,
|
||||||
"fairways_hit": fairways,
|
"fairways_hit": fairways,
|
||||||
"gir": girs,
|
"gir": girs,
|
||||||
"holes_played": len([h for h in hs_list if not h.skipped])
|
"penalty_strokes": penalties,
|
||||||
|
"bunker_greenside": bunker_greenside,
|
||||||
|
"bunker_fairway": bunker_fairway,
|
||||||
|
"holes_played": len([h for h in hs_list if not h.skipped and not h.unfinished])
|
||||||
}
|
}
|
||||||
|
|
||||||
app.mount("/", StaticFiles(directory="frontend", html=True), name="static")
|
app.mount("/", StaticFiles(directory="frontend", html=True), name="static")
|
||||||
+98
-30
@@ -26,10 +26,11 @@ async function loadCourse() {
|
|||||||
async function startRound() {
|
async function startRound() {
|
||||||
const res = await fetch('/course');
|
const res = await fetch('/course');
|
||||||
const course = await res.json();
|
const course = await res.json();
|
||||||
|
const tee = document.getElementById('tee-select').value;
|
||||||
const res2 = await fetch('/round', {
|
const res2 = await fetch('/round', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {'Content-Type': 'application/json'},
|
headers: {'Content-Type': 'application/json'},
|
||||||
body: JSON.stringify({user_id: currentUser.id, course_id: course.id})
|
body: JSON.stringify({user_id: currentUser.id, course_id: course.id, tee: tee})
|
||||||
});
|
});
|
||||||
currentRound = await res2.json();
|
currentRound = await res2.json();
|
||||||
document.getElementById('start-section').style.display = 'none';
|
document.getElementById('start-section').style.display = 'none';
|
||||||
@@ -49,23 +50,64 @@ async function loadRound() {
|
|||||||
loadStats();
|
loadStats();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function saveDraft() {
|
||||||
|
const id = document.getElementById('hole-score-id').value;
|
||||||
|
if (!id) return;
|
||||||
|
const draft = {
|
||||||
|
strokes: document.getElementById('strokes').value,
|
||||||
|
unfinished: document.getElementById('unfinished').checked,
|
||||||
|
fairway: document.getElementById('fairway').checked,
|
||||||
|
putts_green: document.getElementById('putts-green').value,
|
||||||
|
putts_off_green: document.getElementById('putts-off-green').value,
|
||||||
|
pen_water: document.getElementById('pen-water').checked,
|
||||||
|
pen_ob: document.getElementById('pen-ob').checked,
|
||||||
|
pen_lost: document.getElementById('pen-lost').checked,
|
||||||
|
bunker_greenside: document.getElementById('bunker-greenside').checked,
|
||||||
|
bunker_fairway: document.getElementById('bunker-fairway').checked
|
||||||
|
};
|
||||||
|
localStorage.setItem('draft_' + id, JSON.stringify(draft));
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyDraft(hole) {
|
||||||
|
const draftRaw = localStorage.getItem('draft_' + hole.hole_score_id);
|
||||||
|
if (!draftRaw) return;
|
||||||
|
const d = JSON.parse(draftRaw);
|
||||||
|
document.getElementById('strokes').value = d.strokes !== undefined ? d.strokes : (hole.strokes || 1);
|
||||||
|
document.getElementById('unfinished').checked = d.unfinished || false;
|
||||||
|
document.getElementById('fairway').checked = d.fairway || false;
|
||||||
|
document.getElementById('putts-green').value = d.putts_green !== undefined ? d.putts_green : (hole.putts_green || 0);
|
||||||
|
document.getElementById('putts-off-green').value = d.putts_off_green !== undefined ? d.putts_off_green : (hole.putts_off_green || 0);
|
||||||
|
document.getElementById('pen-water').checked = d.pen_water || false;
|
||||||
|
document.getElementById('pen-ob').checked = d.pen_ob || false;
|
||||||
|
document.getElementById('pen-lost').checked = d.pen_lost || false;
|
||||||
|
document.getElementById('bunker-greenside').checked = d.bunker_greenside || false;
|
||||||
|
document.getElementById('bunker-fairway').checked = d.bunker_fairway || false;
|
||||||
|
}
|
||||||
|
|
||||||
function renderHole(hole) {
|
function renderHole(hole) {
|
||||||
document.getElementById('hole-score-id').value = hole.hole_score_id;
|
document.getElementById('hole-score-id').value = hole.hole_score_id;
|
||||||
document.getElementById('hole-label').textContent = `Hole ${hole.hole_number}`;
|
document.getElementById('hole-label').textContent = `Hole ${hole.hole_number}`;
|
||||||
document.getElementById('strokes').value = hole.strokes || 0;
|
document.getElementById('strokes').value = hole.strokes || 1;
|
||||||
document.getElementById('fairway').checked = hole.fairway_hit;
|
document.getElementById('unfinished').checked = hole.unfinished || false;
|
||||||
document.getElementById('skipped').checked = hole.skipped;
|
document.getElementById('fairway').checked = hole.fairway_hit || false;
|
||||||
document.getElementById('completed').checked = hole.completed;
|
|
||||||
document.getElementById('gir-display').textContent = hole.gir ? 'Yes' : 'No';
|
document.getElementById('gir-display').textContent = hole.gir ? 'Yes' : 'No';
|
||||||
|
|
||||||
const putts = hole.putts || [];
|
document.getElementById('putts-green').value = hole.putts_green || 0;
|
||||||
document.getElementById('putts-input').value = putts.map(p => p.location).join(',');
|
document.getElementById('putts-off-green').value = hole.putts_off_green || 0;
|
||||||
|
|
||||||
const pens = (hole.penalties || []).map(p => p.type).join(',');
|
document.getElementById('pen-water').checked = hole.penalty_water || false;
|
||||||
document.getElementById('penalties-input').value = pens;
|
document.getElementById('pen-ob').checked = hole.penalty_ob || false;
|
||||||
|
document.getElementById('pen-lost').checked = hole.penalty_lost || false;
|
||||||
|
|
||||||
const buns = (hole.bunkers || []).map(b => b.location).join(',');
|
document.getElementById('bunker-greenside').checked = hole.bunker_greenside || false;
|
||||||
document.getElementById('bunkers-input').value = buns;
|
document.getElementById('bunker-fairway').checked = hole.bunker_fairway || false;
|
||||||
|
|
||||||
|
applyDraft(hole);
|
||||||
|
|
||||||
|
const info = hole.hole_info || {};
|
||||||
|
document.getElementById('hole-info').innerHTML = `
|
||||||
|
<strong>Par ${info.par}</strong> | Tee: ${info.tee} | ${info.yardage_meters}m
|
||||||
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderNav(holes) {
|
function renderNav(holes) {
|
||||||
@@ -75,50 +117,73 @@ function renderNav(holes) {
|
|||||||
|
|
||||||
function prevHole() {
|
function prevHole() {
|
||||||
if (currentIndex > 0) {
|
if (currentIndex > 0) {
|
||||||
|
saveHole().then(() => {
|
||||||
currentIndex--;
|
currentIndex--;
|
||||||
const holes = currentData.holes.sort((a,b) => a.hole_number - b.hole_number);
|
loadRound();
|
||||||
renderHole(holes[currentIndex]);
|
});
|
||||||
renderNav(holes);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function nextHole() {
|
function nextHole() {
|
||||||
const holes = currentData.holes.sort((a,b) => a.hole_number - b.hole_number);
|
saveHole().then(() => {
|
||||||
if (currentIndex < holes.length - 1) {
|
|
||||||
currentIndex++;
|
currentIndex++;
|
||||||
renderHole(holes[currentIndex]);
|
loadRound();
|
||||||
renderNav(holes);
|
});
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function saveHole() {
|
async function saveHole() {
|
||||||
const id = document.getElementById('hole-score-id').value;
|
const id = document.getElementById('hole-score-id').value;
|
||||||
const strokes = parseInt(document.getElementById('strokes').value) || 0;
|
if (!id) return Promise.resolve();
|
||||||
|
const strokes = parseInt(document.getElementById('strokes').value) || 1;
|
||||||
|
const unfinished = document.getElementById('unfinished').checked;
|
||||||
const fairway = document.getElementById('fairway').checked;
|
const fairway = document.getElementById('fairway').checked;
|
||||||
const skipped = document.getElementById('skipped').checked;
|
|
||||||
const completed = document.getElementById('completed').checked;
|
|
||||||
|
|
||||||
const puttsStr = document.getElementById('putts-input').value;
|
const puttsGreen = parseInt(document.getElementById('putts-green').value) || 0;
|
||||||
const putts = puttsStr.split(',').filter(s => s.trim()).map(s => ({location: s.trim()}));
|
const puttsOffGreen = parseInt(document.getElementById('putts-off-green').value) || 0;
|
||||||
|
|
||||||
const pensStr = document.getElementById('penalties-input').value;
|
const penWater = document.getElementById('pen-water').checked;
|
||||||
const penalties = pensStr.split(',').filter(s => s.trim()).map(s => ({type: s.trim()}));
|
const penOb = document.getElementById('pen-ob').checked;
|
||||||
|
const penLost = document.getElementById('pen-lost').checked;
|
||||||
|
|
||||||
const bunsStr = document.getElementById('bunkers-input').value;
|
const bunkerGreenside = document.getElementById('bunker-greenside').checked;
|
||||||
const bunkers = bunsStr.split(',').filter(s => s.trim()).map(s => ({location: s.trim()}));
|
const bunkerFairway = document.getElementById('bunker-fairway').checked;
|
||||||
|
|
||||||
const res = await fetch(`/hole/${id}`, {
|
const res = await fetch(`/hole/${id}`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {'Content-Type': 'application/json'},
|
headers: {'Content-Type': 'application/json'},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
strokes, skipped, completed, fairway_hit: fairway,
|
strokes, skipped: false, completed: false, fairway_hit: fairway, unfinished,
|
||||||
putts, penalties, bunkers
|
putts_green: puttsGreen, putts_off_green: puttsOffGreen,
|
||||||
|
penalty_water: penWater, penalty_ob: penOb, penalty_lost: penLost,
|
||||||
|
bunker_greenside: bunkerGreenside, bunker_fairway: bunkerFairway
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
const result = await res.json();
|
const result = await res.json();
|
||||||
document.getElementById('gir-display').textContent = result.gir ? 'Yes' : 'No';
|
document.getElementById('gir-display').textContent = result.gir ? 'Yes' : 'No';
|
||||||
|
localStorage.removeItem('draft_' + id);
|
||||||
|
loadStats();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function skipHole() {
|
||||||
|
const id = document.getElementById('hole-score-id').value;
|
||||||
|
await fetch(`/hole/${id}`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {'Content-Type': 'application/json'},
|
||||||
|
body: JSON.stringify({
|
||||||
|
strokes: 0, skipped: true, completed: false, fairway_hit: false, unfinished: false,
|
||||||
|
putts_green: 0, putts_off_green: 0,
|
||||||
|
penalty_water: false, penalty_ob: false, penalty_lost: false,
|
||||||
|
bunker_greenside: false, bunker_fairway: false
|
||||||
|
})
|
||||||
|
});
|
||||||
|
localStorage.removeItem('draft_' + id);
|
||||||
|
const holes = currentData.holes.sort((a,b) => a.hole_number - b.hole_number);
|
||||||
|
if (currentIndex < holes.length - 1) {
|
||||||
|
currentIndex++;
|
||||||
loadRound();
|
loadRound();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function loadStats() {
|
async function loadStats() {
|
||||||
const res = await fetch(`/stats/${currentRound.id}`);
|
const res = await fetch(`/stats/${currentRound.id}`);
|
||||||
@@ -128,6 +193,9 @@ async function loadStats() {
|
|||||||
<p><strong>Total Putts:</strong> ${stats.total_putts}</p>
|
<p><strong>Total Putts:</strong> ${stats.total_putts}</p>
|
||||||
<p><strong>Fairways Hit:</strong> ${stats.fairways_hit}</p>
|
<p><strong>Fairways Hit:</strong> ${stats.fairways_hit}</p>
|
||||||
<p><strong>GIR:</strong> ${stats.gir}</p>
|
<p><strong>GIR:</strong> ${stats.gir}</p>
|
||||||
|
<p><strong>Penalty Strokes:</strong> ${stats.penalty_strokes}</p>
|
||||||
|
<p><strong>Bunker Greenside:</strong> ${stats.bunker_greenside}</p>
|
||||||
|
<p><strong>Bunker Fairway:</strong> ${stats.bunker_fairway}</p>
|
||||||
<p><strong>Holes Played:</strong> ${stats.holes_played}</p>
|
<p><strong>Holes Played:</strong> ${stats.holes_played}</p>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
+15
-9
@@ -11,6 +11,7 @@ label { display: inline-block; margin: 0.25rem 0; }
|
|||||||
input, select { padding: 0.25rem; }
|
input, select { padding: 0.25rem; }
|
||||||
button { padding: 0.4rem 0.8rem; margin-right: 0.5rem; cursor: pointer; }
|
button { padding: 0.4rem 0.8rem; margin-right: 0.5rem; cursor: pointer; }
|
||||||
#stats-display p { margin: 0.2rem 0; }
|
#stats-display p { margin: 0.2rem 0; }
|
||||||
|
#hole-info { font-size: 0.95rem; color: #555; margin-bottom: 0.5rem; }
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
@@ -26,6 +27,7 @@ button { padding: 0.4rem 0.8rem; margin-right: 0.5rem; cursor: pointer; }
|
|||||||
<section id="start-section" style="display:none;">
|
<section id="start-section" style="display:none;">
|
||||||
<h2>Course</h2>
|
<h2>Course</h2>
|
||||||
<p id="course-info"></p>
|
<p id="course-info"></p>
|
||||||
|
<label>Tee: <select id="tee-select"><option>White</option><option>Yellow</option><option>Blue</option><option>Red</option></select></label><br>
|
||||||
<button onclick="startRound()">Start Round</button>
|
<button onclick="startRound()">Start Round</button>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
@@ -35,17 +37,21 @@ button { padding: 0.4rem 0.8rem; margin-right: 0.5rem; cursor: pointer; }
|
|||||||
<button id="prev-btn" onclick="prevHole()">Prev</button>
|
<button id="prev-btn" onclick="prevHole()">Prev</button>
|
||||||
<strong id="hole-label">Hole 1</strong>
|
<strong id="hole-label">Hole 1</strong>
|
||||||
<button id="next-btn" onclick="nextHole()">Next</button>
|
<button id="next-btn" onclick="nextHole()">Next</button>
|
||||||
|
<button onclick="skipHole()">Skip</button>
|
||||||
</div>
|
</div>
|
||||||
<form id="hole-form" onsubmit="event.preventDefault(); saveHole();">
|
<div id="hole-info"></div>
|
||||||
|
<form id="hole-form" onsubmit="event.preventDefault();">
|
||||||
<input type="hidden" id="hole-score-id" />
|
<input type="hidden" id="hole-score-id" />
|
||||||
<label>Strokes: <input id="strokes" type="number" value="0" min="0" /></label><br>
|
<label>Strokes (1+): <input id="strokes" type="number" value="1" min="1" oninput="saveDraft()" /></label><br>
|
||||||
<label>Fairway Hit: <input id="fairway" type="checkbox" /></label><br>
|
<label>Unfinished (X): <input id="unfinished" type="checkbox" oninput="saveDraft()" /></label><br>
|
||||||
<label>Skipped: <input id="skipped" type="checkbox" /></label><br>
|
<label>Fairway Hit: <input id="fairway" type="checkbox" oninput="saveDraft()" /></label><br>
|
||||||
<label>Completed: <input id="completed" type="checkbox" /></label><br>
|
<label>Putts on Green: <input id="putts-green" type="number" value="0" min="0" oninput="saveDraft()" /></label><br>
|
||||||
<label>Putts (comma list): <input id="putts-input" type="text" placeholder="green,green" /></label><br>
|
<label>Putts Off Green: <input id="putts-off-green" type="number" value="0" min="0" oninput="saveDraft()" /></label><br>
|
||||||
<label>Penalties (comma list): <input id="penalties-input" type="text" placeholder="water" /></label><br>
|
<label>Penalty - Water: <input id="pen-water" type="checkbox" oninput="saveDraft()" /></label><br>
|
||||||
<label>Bunkers (comma list): <input id="bunkers-input" type="text" placeholder="greenside" /></label><br>
|
<label>Penalty - OB: <input id="pen-ob" type="checkbox" oninput="saveDraft()" /></label><br>
|
||||||
<button type="submit">Save Hole</button>
|
<label>Penalty - Lost Ball: <input id="pen-lost" type="checkbox" oninput="saveDraft()" /></label><br>
|
||||||
|
<label>Bunker - Greenside: <input id="bunker-greenside" type="checkbox" oninput="saveDraft()" /></label><br>
|
||||||
|
<label>Bunker - Fairway: <input id="bunker-fairway" type="checkbox" oninput="saveDraft()" /></label><br>
|
||||||
</form>
|
</form>
|
||||||
<p>GIR: <strong id="gir-display">No</strong></p>
|
<p>GIR: <strong id="gir-display">No</strong></p>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
Reference in new issue
Block a user