Skip to content

Commit ff12a35

Browse files
committed
Add Copy for Sheets button (clipboard paste into Google Sheets)
CSV download doesn't open in Sheets on mobile. Added a "Copy for Sheets" button that copies tab-separated text to clipboard — paste directly into Google Sheets and it fills columns A (exercise) and B (sets x reps @RIR). Toast notification confirms copy. Fallback for older browsers via textarea select + execCommand. https://claude.ai/code/session_01Lo7Z7GoRzG6hZkKwrxnXVk
1 parent b4458a0 commit ff12a35

File tree

2 files changed

+54
-3
lines changed

2 files changed

+54
-3
lines changed

ironforge/templates/program.html

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,13 @@
190190
}
191191
.vol-change.up { background: rgba(76,175,80,0.15); color: var(--green); }
192192
.vol-change.down { background: rgba(91,155,213,0.15); color: var(--blue); }
193+
.copy-toast {
194+
position: fixed; bottom: 2rem; left: 50%; transform: translateX(-50%);
195+
background: var(--green); color: #fff; padding: 0.75rem 1.5rem;
196+
border-radius: 8px; font-size: 0.85rem; font-weight: 600;
197+
opacity: 0; transition: opacity 0.3s; pointer-events: none; z-index: 300;
198+
}
199+
.copy-toast.show { opacity: 1; }
193200
</style>
194201
</head>
195202
<body>
@@ -198,10 +205,13 @@
198205
<h1>YOUR PROGRAM</h1>
199206
<div class="header-actions">
200207
<a href="/" class="header-btn">New Program</a>
201-
<a href="/export.csv" class="header-btn export">Export CSV for Sheets</a>
208+
<button class="header-btn export" onclick="copyForSheets()">Copy for Sheets</button>
209+
<a href="/export.csv" class="header-btn">CSV</a>
202210
</div>
203211
</div>
204212

213+
<div class="copy-toast" id="copy-toast">Copied! Paste into Google Sheets</div>
214+
205215
<div class="container">
206216

207217
<!-- 1. LEVEL ASSESSMENT -->
@@ -375,13 +385,36 @@ <h1>YOUR PROGRAM</h1>
375385
}
376386

377387
function switchSession(tab, sessionIdx, weekIdx) {
378-
// Only affect tabs/content within the same week
379388
const weekContent = document.querySelector(`.week-content[data-week="${weekIdx}"]`);
380389
weekContent.querySelectorAll('.session-tab').forEach(t => t.classList.remove('active'));
381390
weekContent.querySelectorAll('.session-content').forEach(c => c.classList.remove('active'));
382391
tab.classList.add('active');
383392
weekContent.querySelector(`.session-content[data-session="${sessionIdx}"]`).classList.add('active');
384393
}
394+
395+
// Build tab-separated text for Google Sheets paste
396+
const SHEETS_DATA = {{ sheets_data | tojson }};
397+
398+
function copyForSheets() {
399+
navigator.clipboard.writeText(SHEETS_DATA).then(() => {
400+
const toast = document.getElementById('copy-toast');
401+
toast.classList.add('show');
402+
setTimeout(() => toast.classList.remove('show'), 2500);
403+
}).catch(() => {
404+
// Fallback: select a hidden textarea
405+
const ta = document.createElement('textarea');
406+
ta.value = SHEETS_DATA;
407+
ta.style.position = 'fixed';
408+
ta.style.opacity = '0';
409+
document.body.appendChild(ta);
410+
ta.select();
411+
document.execCommand('copy');
412+
document.body.removeChild(ta);
413+
const toast = document.getElementById('copy-toast');
414+
toast.classList.add('show');
415+
setTimeout(() => toast.classList.remove('show'), 2500);
416+
});
417+
}
385418
</script>
386419

387420
</body>

ironforge/web.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -288,12 +288,30 @@ def api_splits(days: int):
288288
return jsonify([{"key": s.key, "name": s.name, "rationale": s.rationale} for s in options])
289289

290290

291+
def _build_sheets_text(program: Program) -> str:
292+
"""Build tab-separated text that pastes cleanly into Google Sheets."""
293+
lines: list[str] = []
294+
for week in program.weeks:
295+
lines.append(f"Week {week.week_number}")
296+
lines.append("")
297+
for sess in week.sessions:
298+
lines.append(sess.day_label.replace(" — ", " - "))
299+
for ex in sess.exercises:
300+
load = ex.load
301+
prescription = f"{load.sets}x{load.rep_low}-{load.rep_high} @{load.rir} RIR"
302+
lines.append(f"{ex.exercise.name}\t{prescription}")
303+
lines.append("")
304+
return "\n".join(lines)
305+
306+
291307
@app.route("/generate", methods=["POST"])
292308
def generate():
293309
profile = _parse_profile(request.form)
294310
program = build_program(profile)
295311
_store_program(program)
296-
return render_template("program.html", program=program, profile=profile)
312+
sheets_data = _build_sheets_text(program)
313+
return render_template("program.html", program=program, profile=profile,
314+
sheets_data=sheets_data)
297315

298316

299317
@app.route("/export.csv")

0 commit comments

Comments
 (0)