0% found this document useful (0 votes)
6 views7 pages

React Workout Tracker Component

The document is a React component for a Workout Tracker that allows users to log workouts, including details like date, workout name, sets, weight, and reps. It features local storage for saving entries, options to delete or clear all entries, and generates a report summarizing weekly workouts and top lifts. Users can also export their workout data as a CSV file.

Uploaded by

vobewo6116
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as TXT, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
6 views7 pages

React Workout Tracker Component

The document is a React component for a Workout Tracker that allows users to log workouts, including details like date, workout name, sets, weight, and reps. It features local storage for saving entries, options to delete or clear all entries, and generates a report summarizing weekly workouts and top lifts. Users can also export their workout data as a CSV file.

Uploaded by

vobewo6116
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as TXT, PDF, TXT or read online on Scribd

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>
);
}

You might also like