diff --git a/app.js b/app.js index 6e7536b..3371d20 100644 --- a/app.js +++ b/app.js @@ -782,17 +782,16 @@ function reorderExercise(a, model, fromKey, toKey) { scheduleAutosave(model); } -function attachDragReorder(a, model, ex, block) { - block.draggable = true; - block.title = t("reorder_hint"); - block.addEventListener("dragstart", ev => { +function attachDragReorder(a, model, ex, block, grip) { + grip.draggable = true; + grip.addEventListener("dragstart", ev => { app.dragKey = ex.key; try { ev.dataTransfer = ev.dataTransfer || {}; ev.dataTransfer.setData("text/plain", ex.key); } catch (_) {} }); - block.addEventListener("dragend", () => { + grip.addEventListener("dragend", () => { app.dragKey = null; for (const b of $$(".ex-block.drag-over")) b.classList.remove("drag-over"); }); @@ -850,6 +849,14 @@ function buildExBlock(a, model, ex, removable) { model.exByKey[ex.key] = ex; const head = el("div", "ex-head"); + const grip = el("span", "ex-grip", ""); + grip.innerHTML = ''; + grip.title = t("reorder_hint"); + grip.setAttribute("aria-label", t("reorder_hint")); + head.appendChild(grip); const infoBtn = el("button", "icon-btn", "?"); infoBtn.type = "button"; infoBtn.setAttribute("aria-label", t("info_aria") + ex.name); @@ -878,7 +885,7 @@ function buildExBlock(a, model, ex, removable) { block.appendChild(el("div", "set-rows")); renderExRows(a, model, ex); - attachDragReorder(a, model, ex, block); + attachDragReorder(a, model, ex, block, grip); return block; } diff --git a/server.py b/server.py index fa8c9b9..dbba503 100644 --- a/server.py +++ b/server.py @@ -434,6 +434,26 @@ def fetch_session(user_id, sid): return d +def last_exercise_order(user_id, activity, day): + """Uebungs-Reihenfolge der letzten abgeschlossenen Session (Tag/Kategorie).""" + con = db() + clause, extra = _user_clause(user_id) + rows = con.execute( + f"SELECT fields FROM sessions WHERE activity=? AND day=? AND status='done' " + f"AND {clause} ORDER BY date DESC, id DESC LIMIT 10", + [activity, day] + extra).fetchall() + con.close() + for r in rows: + try: + f = json.loads(r["fields"] or "{}") + except Exception: + continue + order = f.get("exerciseOrder") + if isinstance(order, list) and order: + return order + return None + + def create_session(user_id, data): activity = _clean_str(data.get("activity")) if not activity: @@ -441,12 +461,15 @@ def create_session(user_id, data): sdate = _clean_str(data.get("date")) or date.today().isoformat() day = _clean_str(data.get("day")) start = _clean_str(data.get("start_time")) or datetime.now().strftime("%H:%M") + # Reihenfolge der Uebungen aus der letzten abgeschlossenen Session uebernehmen + order = last_exercise_order(user_id, activity, day) + fields = json.dumps({"exerciseOrder": order}) if order else "{}" con = db() cur = con.execute( - "INSERT INTO sessions(activity,day,date,start_time,status,created_at,user_id) " - "VALUES(?,?,?,?,?,?,?)", + "INSERT INTO sessions(activity,day,date,start_time,status,created_at,user_id,fields) " + "VALUES(?,?,?,?,?,?,?,?)", (activity, day, sdate, start, "active", - datetime.now().isoformat(timespec="seconds"), user_id)) + datetime.now().isoformat(timespec="seconds"), user_id, fields)) con.commit() sid = cur.lastrowid con.close() diff --git a/styles.css b/styles.css index d0f3d1c..2ac27e1 100644 --- a/styles.css +++ b/styles.css @@ -400,9 +400,18 @@ h3 { font-size: 15px; font-weight: 700; } .ex-block { border: 2px solid var(--border); margin: 0; - cursor: grab; } +.ex-grip { + display: inline-flex; + align-items: center; + color: var(--muted); + cursor: grab; + user-select: none; +} + +.ex-grip:active { cursor: grabbing; } + .ex-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(460px, 100%), 1fr)); diff --git a/tests/test_api.py b/tests/test_api.py index 3230531..eed4328 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -212,6 +212,15 @@ def main(): check("Statistik: bestes Gewicht je Termin", s0["weight"] == 22 and s1["weight"] == 20, stats) + # Uebungs-Reihenfolge der letzten abgeschlossenen Session uebernehmen + order = ["Push/Schrägbank KH (je Hand)", "Push/Bankdrücken KH (je Hand)"] + req("PATCH", f"/api/sessions/{pid}", + {"meta": {"fields": {"exerciseOrder": order}}}) + st, ns = req("POST", "/api/sessions", {"activity": "gym", "day": "Push"}) + check("Uebungs-Reihenfolge beim Start uebernommen", + ns["fields"].get("exerciseOrder") == order, ns["fields"]) + req("DELETE", f"/api/sessions/{ns['id']}") + st, exp = req("GET", "/api/export?format=json") check("Export JSON Format", st == 200 and exp["format"] == "trainingsplan-export-v2" diff --git a/tests/test_ui.js b/tests/test_ui.js index 6e6ca60..6781e4e 100644 --- a/tests/test_ui.js +++ b/tests/test_ui.js @@ -238,8 +238,8 @@ async function main() { check("Hinweiszeile ausgeblendet", !gymCard().querySelector(".ex-block .ex-hint")); - // --- Uebungen per Drag & Drop sortieren -------------------------------- - gymCard().querySelectorAll(".ex-block")[0] + // --- Uebungen per Drag & Drop (Griff) sortieren ------------------------ + gymCard().querySelectorAll(".ex-block")[0].querySelector(".ex-grip") .dispatchEvent(new window.Event("dragstart", { bubbles: true })); const target = gymCard().querySelectorAll(".ex-block")[1]; target.dispatchEvent(new window.Event("drop", { bubbles: true }));