import React, { useEffect, useState } from "react";
// Workout Tracker - Single-file React component
// Tailwind CSS required in project to render correctly.
// Save as e.g. [Link] and import into your app.
export default function WorkoutTracker() {
const STORAGE_KEY = "workout-tracker-data-v1";
const [entries, setEntries] = useState(() => {
try {
const raw = [Link](STORAGE_KEY);
return raw ? [Link](raw) : [];
} catch (e) {
return [];
}
});
const [form, setForm] = useState({
date: new Date().toISOString().slice(0, 10),
workout: "",
sets: [{ weight: "", reps: "" }],
dayLabel: "",
currentWeight: "",
steps: "",
notes: "",
});
useEffect(() => {
[Link](STORAGE_KEY, [Link](entries));
}, [entries]);
function addSetRow() {
setForm((f) => ({ ...f, sets: [...[Link], { weight: "", reps: "" }] }));
}
function removeSetRow(i) {
setForm((f) => ({ ...f, sets: [Link]((_, idx) => idx !== i) }));
}
function updateSet(i, key, value) {
setForm((f) => {
const sets = [Link]((s, idx) => (idx === i ? { ...s, [key]: value } :
s));
return { ...f, sets };
});
}
function saveEntry(e) {
[Link]();
if (![Link]()) return alert("Please enter a workout name");
const entry = {
id: [Link](),
date: [Link],
workout: [Link](),
dayLabel: [Link](),
sets: [Link]((s) => ({ weight: [Link](), reps:
[Link]() })),
currentWeight: [Link](),
steps: [Link](),
notes: [Link](),
};
setEntries((p) => [entry, ...p]);
// Reset workout fields but keep date
setForm((f) => ({ ...f, workout: "", sets: [{ weight: "", reps: "" }],
dayLabel: "", currentWeight: "", steps: "", notes: "" }));
}
function deleteEntry(id) {
// Prevent button from acting like a form submit
// (Some browsers treat button inside form as submit)
event?.preventDefault?.();
if (!confirm("Delete this entry?")) return;
setEntries((p) => [Link]((x) => [Link] !== id));
}(id) {
if (!confirm("Delete this entry?")) return;
setEntries((p) => [Link]((x) => [Link] !== id));
}
function clearAll() {
if (!confirm("Clear all saved entries?")) return;
setEntries([]);
}
// Simple report computations
function computeReport() {
if (![Link]) return null;
// group by week (ISO week not necessary here) - we'll group by calendar week
starting Monday
const byWeek = {};
[Link]((en) => {
const dt = new Date([Link] + "T00:00:00");
// get year-week key
const weekStart = new Date(dt);
const day = [Link]();
const diffToMonday = ((day + 6) % 7); // 0->Monday
[Link]([Link]() - diffToMonday);
const key = [Link]().slice(0, 10);
if (!byWeek[key]) byWeek[key] = [];
byWeek[key].push(en);
});
// aggregate
const weeks = [Link](byWeek).map(([weekStart, list]) => {
let totalSteps = 0;
let totalEntries = [Link];
let maxWeight = 0;
let totalReps = 0;
let setsCount = 0;
[Link]((en) => {
if ([Link]) totalSteps += Number([Link]) || 0;
[Link]((s) => {
const w = Number([Link]) || 0;
const r = Number([Link]) || 0;
if (w > maxWeight) maxWeight = w;
totalReps += r;
setsCount += r ? 1 : 0;
});
});
return {
weekStart,
totalEntries,
totalSteps,
maxWeight,
avgRepsPerSet: setsCount ? [Link]((totalReps / setsCount) * 100) /
100 : 0,
};
});
// top lifts
const topLifts = [];
[Link]((en) => {
[Link]((s) => {
[Link]({ date: [Link], workout: [Link], weight:
Number([Link]) || 0, reps: Number([Link]) || 0 });
});
});
[Link]((a, b) => [Link] - [Link]);
return { weeks, topLifts: [Link](0, 10) };
}
const report = computeReport();
// CSV export
function exportCSV(e) {
e?.preventDefault?.();
try {
const header = ["id", "date", "workout", "day", "set_index", "weight",
"reps", "currentWeight", "steps", "notes"];
const rows = [[Link](",")];
[Link]((en) => {
[Link]((s, idx) => {
const row = [[Link], [Link], `"${[Link](/"/g, '""') }"`,
[Link], idx + 1, [Link], [Link], [Link], [Link], `"$
{([Link]||"").replace(/"/g, '""') }"`];
[Link]([Link](","));
});
});
const csv = [Link]("
");
const blob = new Blob([csv], { type: "text/csv" });
const url = [Link](blob);
const a = [Link]("a");
[Link] = url;
[Link] = `workout-tracker-${new Date().toISOString().slice(0,10)}.csv`;
[Link](a);
[Link]();
[Link]();
[Link](url);
} catch (err) {
alert("Export failed: " + [Link]);
}
}() {
const header = ["id", "date", "workout", "day", "set_index", "weight", "reps",
"currentWeight", "steps", "notes"];
const rows = [[Link](",")];
[Link]((en) => {
[Link]((s, idx) => {
const row = [[Link], [Link], `"${[Link](/"/g, '""') }"`,
[Link], idx + 1, [Link], [Link], [Link], [Link], `"$
{([Link]||"").replace(/"/g, '""') }"`];
[Link]([Link](","));
});
});
const csv = [Link]("\n");
const blob = new Blob([csv], { type: "text/csv" });
const url = [Link](blob);
const a = [Link]("a");
[Link] = url;
[Link] = `workout-tracker-${new Date().toISOString().slice(0,10)}.csv`;
[Link](a);
[Link]();
[Link]();
[Link](url);
}
return (
<div className="min-h-screen bg-gray-900 text-gray-100 p-6">
<div className="max-w-4xl mx-auto">
<header className="flex items-center justify-between mb-6">
<h1 className="text-2xl font-bold">Workout Tracker</h1>
<div className="space-x-2">
<button onClick={exportCSV} className="px-3 py-1 rounded bg-indigo-600
hover:bg-indigo-500">Export CSV</button>
<button onClick={clearAll} className="px-3 py-1 rounded bg-red-600
hover:bg-red-500">Clear All</button>
</div>
</header>
<form onSubmit={saveEntry} className="bg-gray-800 p-4 rounded-lg shadow-md
mb-6">
<div className="grid grid-cols-1 md:grid-cols-3 gap-3">
<div>
<label className="text-sm">Date</label>
<input type="date" value={[Link]} onChange={(e) =>
setForm({ ...form, date: [Link] })} className="w-full mt-1 p-2 rounded bg-
gray-900 text-gray-100" />
</div>
<div className="md:col-span-2">
<label className="text-sm">Workout name</label>
<input placeholder="e.g. Chest + Triceps" value={[Link]}
onChange={(e) => setForm({ ...form, workout: [Link] })} className="w-full
mt-1 p-2 rounded bg-gray-900 text-gray-100" />
</div>
<div>
<label className="text-sm">Day (optional)</label>
<input placeholder="Upper/Lower/Legs" value={[Link]}
onChange={(e) => setForm({ ...form, dayLabel: [Link] })} className="w-full
mt-1 p-2 rounded bg-gray-900 text-gray-100" />
</div>
<div>
<label className="text-sm">Current body weight (optional)</label>
<input placeholder="kg" value={[Link]} onChange={(e) =>
setForm({ ...form, currentWeight: [Link] })} className="w-full mt-1 p-2
rounded bg-gray-900 text-gray-100" />
</div>
<div>
<label className="text-sm">Steps walked (optional)</label>
<input placeholder="steps" value={[Link]} onChange={(e) =>
setForm({ ...form, steps: [Link] })} className="w-full mt-1 p-2 rounded bg-
gray-900 text-gray-100" />
</div>
<div className="md:col-span-3">
<label className="text-sm">Notes (optional)</label>
<input placeholder="any notes" value={[Link]} onChange={(e) =>
setForm({ ...form, notes: [Link] })} className="w-full mt-1 p-2 rounded bg-
gray-900 text-gray-100" />
</div>
</div>
<section className="mt-4">
<h3 className="font-semibold mb-2">Sets (enter weight & reps for each
set)</h3>
<div className="space-y-2">
{[Link]((s, i) => (
<div key={i} className="grid grid-cols-12 gap-2 items-center">
<div className="col-span-5">
<input placeholder="weight (kg)" value={[Link]} onChange={(e)
=> updateSet(i, "weight", [Link])} className="w-full p-2 rounded bg-gray-
900 text-gray-100" />
</div>
<div className="col-span-5">
<input placeholder="reps" value={[Link]} onChange={(e) =>
updateSet(i, "reps", [Link])} className="w-full p-2 rounded bg-gray-900
text-gray-100" />
</div>
<div className="col-span-2 flex gap-2">
<button type="button" onClick={() => removeSetRow(i)}
className="px-2 py-1 rounded bg-red-600">Remove</button>
{i === [Link] - 1 && (
<button type="button" onClick={addSetRow} className="px-2 py-
1 rounded bg-green-600">Add</button>
)}
</div>
</div>
))}
</div>
</section>
<div className="mt-4 flex justify-end">
<button type="submit" className="px-4 py-2 rounded bg-indigo-600
hover:bg-indigo-500">Save Entry</button>
</div>
</form>
<main className="grid grid-cols-1 md:grid-cols-3 gap-4">
<section className="md:col-span-2 bg-gray-800 p-4 rounded-lg shadow">
<h2 className="font-semibold mb-3">Entries</h2>
{[Link] === 0 && <div className="text-sm text-gray-400">No
entries yet — add your first workout above.</div>}
<div className="space-y-3">
{[Link]((en) => (
<div key={[Link]} className="border border-gray-700 rounded p-3">
<div className="flex justify-between items-start">
<div>
<div className="text-sm text-gray-300">{[Link]} ·
{[Link] || ""}</div>
<div className="text-lg font-medium">{[Link]}</div>
</div>
<div className="text-right">
<div className="text-sm">Body: {[Link] ||
"-"}</div>
<div className="text-sm">Steps: {[Link] || "-"}</div>
</div>
</div>
<div className="mt-2">
<table className="w-full text-sm">
<thead>
<tr className="text-left
text-gray-400"><th>Set</th><th>Weight</th><th>Reps</th></tr>
</thead>
<tbody>
{[Link]((s, idx) => (
<tr key={idx}><td>{idx + 1}</td><td>{[Link] ||
"-"}</td><td>{[Link] || "-"}</td></tr>
))}
</tbody>
</table>
</div>
{[Link] && <div className="mt-2 text-sm text-gray-300">Note:
{[Link]}</div>}
<div className="mt-3 flex justify-end gap-2">
<button onClick={() => deleteEntry([Link])} className="px-2 py-1
rounded bg-red-600">Delete</button>
</div>
</div>
))}
</div>
</section>
<aside className="bg-gray-800 p-4 rounded-lg shadow">
<h2 className="font-semibold mb-3">Report</h2>
{!report && <div className="text-sm text-gray-400">No data to report
yet — save some entries.</div>}
{report && (
<div className="space-y-3 text-sm">
<div>
<div className="text-xs text-gray-400">Recent weekly summaries
(week start)</div>
<div className="mt-2 space-y-2">
{[Link](0,5).map((w) => (
<div key={[Link]} className="p-2 bg-gray-900 rounded">
<div className="text-sm">Week of
<strong>{[Link]}</strong></div>
<div className="text-xs text-gray-300">Entries:
{[Link]} · Steps: {[Link]} · Max weight: {[Link]}kg · Avg
reps/set: {[Link]}</div>
</div>
))}
</div>
</div>
<div>
<div className="text-xs text-gray-400">Top lifts (by
weight)</div>
<ol className="mt-2 list-decimal ml-5 text-xs text-gray-300">
{[Link]((t, i) => (
<li key={i}>{[Link]}kg · {[Link]} reps · {[Link]} ·
{[Link]}</li>
))}
</ol>
</div>
<div>
<div className="text-xs text-gray-400">Quick actions</div>
<div className="mt-2 flex flex-col gap-2">
<button onClick={exportCSV} className="px-3 py-1 rounded bg-
indigo-600">Export CSV</button>
<button onClick={() => alert('You can enhance reports by
requesting custom summaries (e.g. monthly, by-exercise, bodyweight progression).')}
className="px-3 py-1 rounded bg-green-600">Request custom report</button>
</div>
</div>
</div>
)}
<div className="mt-4 text-xs text-gray-500">Data stored locally in your
browser (localStorage).</div>
</aside>
</main>
<footer className="mt-6 text-center text-xs text-gray-500">Made with ☕ —
edit the component to tailor fields, units or visuals.</footer>
</div>
</div>
);
}