Files
fitness/app/Http/Controllers/ProgramController.php
T
rootandClaude Sonnet 5 ebd0578dc9 Add multi-week workout programs
A Workout was a single flat session with no way to schedule several
across weeks/days as a structured plan (e.g. Push/Pull/Legs over 4
weeks) - every user started from a blank builder each time, and there
was no template concept at all.

Adds a Program (title, description, week count) that Workouts can
optionally belong to via nullable program_id/week_number/day_number
columns, reusing all existing workout and session-tracking
infrastructure rather than building a parallel system. The program show
page lays out the schedule week by week and derives progress (completed
vs total) from the user's existing WorkoutSession completions - no new
enrollment/progress-tracking concept needed. Deleting a program
unschedules its workouts (nullOnDelete) rather than destroying them.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-10 12:21:47 +03:00

103 lines
3.2 KiB
PHP

<?php
namespace App\Http\Controllers;
use App\Models\Program;
use App\Models\WorkoutSession;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\View\View;
class ProgramController extends Controller
{
public function index(): View
{
$programs = Auth::user()->programs()->withCount('workouts')->latest()->paginate(10);
return view('programs.index', compact('programs'));
}
public function create(): View
{
return view('programs.create');
}
public function store(Request $request): RedirectResponse
{
$data = $request->validate([
'title' => ['required', 'string', 'max:255'],
'description' => ['nullable', 'string'],
'weeks' => ['required', 'integer', 'min:1', 'max:52'],
]);
$program = Auth::user()->programs()->create($data);
return redirect()->route('programs.show', $program)->with('status', 'Program created');
}
public function show(Program $program): View
{
$this->authorizeProgram($program);
$program->load('workouts');
$completedWorkoutIds = WorkoutSession::query()
->where('user_id', Auth::id())
->whereIn('workout_id', $program->workouts->pluck('id'))
->whereNotNull('completed_at')
->pluck('workout_id')
->unique();
$workoutsByWeek = $program->workouts->groupBy('week_number');
$totalWorkouts = $program->workouts->count();
$completedCount = $completedWorkoutIds->count();
$progressPct = $totalWorkouts > 0 ? (int) round(($completedCount / $totalWorkouts) * 100) : 0;
return view('programs.show', [
'program' => $program,
'workoutsByWeek' => $workoutsByWeek,
'completedWorkoutIds' => $completedWorkoutIds,
'progressPct' => $progressPct,
'completedCount' => $completedCount,
'totalWorkouts' => $totalWorkouts,
]);
}
public function destroy(Program $program): RedirectResponse
{
$this->authorizeProgram($program);
$program->delete();
return redirect()->route('programs.index')->with('status', 'Program deleted');
}
public function addWorkout(Request $request, Program $program): RedirectResponse
{
$this->authorizeProgram($program);
$data = $request->validate([
'title' => ['required', 'string', 'max:255'],
'week_number' => ['required', 'integer', 'min:1', 'max:' . $program->weeks],
'day_number' => ['required', 'integer', 'min:1', 'max:14'],
'estimated_minutes' => ['nullable', 'integer', 'min:1', 'max:600'],
]);
$program->workouts()->create([
'user_id' => Auth::id(),
'title' => $data['title'],
'estimated_minutes' => $data['estimated_minutes'] ?? null,
'week_number' => $data['week_number'],
'day_number' => $data['day_number'],
]);
return back()->with('status', 'Workout added to program');
}
protected function authorizeProgram(Program $program): void
{
if ($program->user_id !== Auth::id()) {
abort(403);
}
}
}