From 125e1c34afd10601322b79bf79634c42ee48c7dc Mon Sep 17 00:00:00 2001 From: GhassanYusuf Date: Sat, 24 Jan 2026 13:11:20 +0300 Subject: [PATCH] added attendance and fixed body composition --- app/Http/Controllers/FamilyController.php | 21 ++++- app/Models/Attendance.php | 29 ++++++ app/Models/User.php | 8 ++ ...094412_create_attendance_records_table.php | 35 ++++++++ ...095359_create_members_attendance_table.php | 33 +++++++ database/seeders/AttendanceSeeder.php | 79 ++++++++++++++++ database/seeders/DatabaseSeeder.php | 3 + resources/views/family/show.blade.php | 89 ++++++++++++++++++- 8 files changed, 293 insertions(+), 4 deletions(-) create mode 100644 app/Models/Attendance.php create mode 100644 database/migrations/2026_01_24_094412_create_attendance_records_table.php create mode 100644 database/migrations/2026_01_24_095359_create_members_attendance_table.php create mode 100644 database/seeders/AttendanceSeeder.php diff --git a/app/Http/Controllers/FamilyController.php b/app/Http/Controllers/FamilyController.php index 5324dbd..8e4ce1d 100644 --- a/app/Http/Controllers/FamilyController.php +++ b/app/Http/Controllers/FamilyController.php @@ -8,6 +8,7 @@ use App\Models\HealthRecord; use App\Models\Invoice; use App\Models\TournamentEvent; use App\Models\Goal; +use App\Models\Attendance; use App\Services\FamilyService; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; @@ -80,6 +81,13 @@ class FamilyController extends Controller $completedGoalsCount = $goals->where('status', 'completed')->count(); $successRate = $goals->count() > 0 ? round(($completedGoalsCount / $goals->count()) * 100) : 0; + // Fetch attendance data + $attendanceRecords = $user->attendanceRecords()->orderBy('session_datetime', 'desc')->get(); + $sessionsCompleted = $attendanceRecords->where('status', 'completed')->count(); + $noShows = $attendanceRecords->where('status', 'no_show')->count(); + $totalSessions = $attendanceRecords->count(); + $attendanceRate = $totalSessions > 0 ? round(($sessionsCompleted / $totalSessions) * 100, 1) : 0; + // Pass user directly and a flag to indicate it's the current user's profile return view('family.show', [ 'relationship' => (object)[ @@ -99,6 +107,10 @@ class FamilyController extends Controller 'activeGoalsCount' => $activeGoalsCount, 'completedGoalsCount' => $completedGoalsCount, 'successRate' => $successRate, + 'attendanceRecords' => $attendanceRecords, + 'sessionsCompleted' => $sessionsCompleted, + 'noShows' => $noShows, + 'attendanceRate' => $attendanceRate, ]); } @@ -289,7 +301,14 @@ class FamilyController extends Controller $completedGoalsCount = $goals->where('status', 'completed')->count(); $successRate = $goals->count() > 0 ? round(($completedGoalsCount / $goals->count()) * 100) : 0; - return view('family.show', compact('relationship', 'latestHealthRecord', 'healthRecords', 'comparisonRecords', 'invoices', 'tournamentEvents', 'awardCounts', 'sports', 'goals', 'activeGoalsCount', 'completedGoalsCount', 'successRate')); + // Fetch attendance data for the dependent + $attendanceRecords = $relationship->dependent->attendanceRecords()->orderBy('session_datetime', 'desc')->get(); + $sessionsCompleted = $attendanceRecords->where('status', 'completed')->count(); + $noShows = $attendanceRecords->where('status', 'no_show')->count(); + $totalSessions = $attendanceRecords->count(); + $attendanceRate = $totalSessions > 0 ? round(($sessionsCompleted / $totalSessions) * 100, 1) : 0; + + return view('family.show', compact('relationship', 'latestHealthRecord', 'healthRecords', 'comparisonRecords', 'invoices', 'tournamentEvents', 'awardCounts', 'sports', 'goals', 'activeGoalsCount', 'completedGoalsCount', 'successRate', 'attendanceRecords', 'sessionsCompleted', 'noShows', 'attendanceRate')); } /** diff --git a/app/Models/Attendance.php b/app/Models/Attendance.php new file mode 100644 index 0000000..6c39b08 --- /dev/null +++ b/app/Models/Attendance.php @@ -0,0 +1,29 @@ + 'datetime', + ]; + + public function member(): BelongsTo + { + return $this->belongsTo(User::class, 'member_id'); + } +} diff --git a/app/Models/User.php b/app/Models/User.php index 7fd5713..212eb05 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -242,6 +242,14 @@ class User extends Authenticatable return $this->hasMany(Goal::class); } + /** + * Get the attendance records for the user. + */ + public function attendanceRecords(): HasMany + { + return $this->hasMany(Attendance::class, 'member_id'); + } + /** * Send the email verification notification. * Override to prevent sending the default Laravel notification. diff --git a/database/migrations/2026_01_24_094412_create_attendance_records_table.php b/database/migrations/2026_01_24_094412_create_attendance_records_table.php new file mode 100644 index 0000000..fe5f2b3 --- /dev/null +++ b/database/migrations/2026_01_24_094412_create_attendance_records_table.php @@ -0,0 +1,35 @@ +id(); + $table->foreignId('user_id')->constrained()->onDelete('cascade'); + $table->foreignId('tenant_id')->constrained()->onDelete('cascade'); + $table->string('member_type')->default('Basic'); // Premium, Basic, Student, Family + $table->datetime('check_in_time'); + $table->datetime('check_out_time')->nullable(); + $table->string('duration')->nullable(); // e.g., "1h 45m" + $table->string('activity'); // e.g., "Gym Floor", "Yoga Class" + $table->enum('status', ['active', 'completed'])->default('active'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('attendance_records'); + } +}; diff --git a/database/migrations/2026_01_24_095359_create_members_attendance_table.php b/database/migrations/2026_01_24_095359_create_members_attendance_table.php new file mode 100644 index 0000000..84b979e --- /dev/null +++ b/database/migrations/2026_01_24_095359_create_members_attendance_table.php @@ -0,0 +1,33 @@ +id(); + $table->foreignId('member_id')->constrained('users')->onDelete('cascade'); + $table->string('session_type'); // e.g. Personal Training, Group Fitness, Yoga, Pilates + $table->string('trainer_name'); + $table->datetime('session_datetime'); + $table->enum('status', ['completed', 'no_show']); + $table->text('notes')->nullable(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('members_attendance'); + } +}; diff --git a/database/seeders/AttendanceSeeder.php b/database/seeders/AttendanceSeeder.php new file mode 100644 index 0000000..cbe834d --- /dev/null +++ b/database/seeders/AttendanceSeeder.php @@ -0,0 +1,79 @@ +first(); + + if (!$user) { + return; // Skip if test data not found + } + + $attendanceRecords = [ + [ + 'member_id' => $user->id, + 'session_type' => 'Personal Training', + 'trainer_name' => 'John Smith', + 'session_datetime' => now()->subDays(1)->setTime(10, 0), + 'status' => 'completed', + 'notes' => 'Great session, focused on upper body strength', + ], + [ + 'member_id' => $user->id, + 'session_type' => 'Group Fitness', + 'trainer_name' => 'Sarah Johnson', + 'session_datetime' => now()->subDays(2)->setTime(18, 30), + 'status' => 'completed', + 'notes' => 'High-intensity cardio workout', + ], + [ + 'member_id' => $user->id, + 'session_type' => 'Yoga', + 'trainer_name' => 'Mike Wilson', + 'session_datetime' => now()->subDays(3)->setTime(9, 0), + 'status' => 'no_show', + 'notes' => 'Member did not attend scheduled session', + ], + [ + 'member_id' => $user->id, + 'session_type' => 'Pilates', + 'trainer_name' => 'Emily Davis', + 'session_datetime' => now()->subDays(4)->setTime(17, 0), + 'status' => 'completed', + 'notes' => 'Core strengthening and flexibility', + ], + [ + 'member_id' => $user->id, + 'session_type' => 'Personal Training', + 'trainer_name' => 'John Smith', + 'session_datetime' => now()->subDays(5)->setTime(14, 0), + 'status' => 'completed', + 'notes' => 'Lower body strength training', + ], + [ + 'member_id' => $user->id, + 'session_type' => 'Group Fitness', + 'trainer_name' => 'Sarah Johnson', + 'session_datetime' => now()->subDays(6)->setTime(19, 0), + 'status' => 'no_show', + 'notes' => 'Late cancellation', + ], + ]; + + foreach ($attendanceRecords as $record) { + Attendance::create($record); + } + } +} diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php index df2f429..cca3102 100644 --- a/database/seeders/DatabaseSeeder.php +++ b/database/seeders/DatabaseSeeder.php @@ -65,5 +65,8 @@ class DatabaseSeeder extends Seeder 'status' => 'pending', 'due_date' => now()->addDays(15), ]); + + // Seed attendance data + $this->call(AttendanceSeeder::class); } } diff --git a/resources/views/family/show.blade.php b/resources/views/family/show.blade.php index 8f6f9f9..b3b53c7 100644 --- a/resources/views/family/show.blade.php +++ b/resources/views/family/show.blade.php @@ -431,8 +431,91 @@
-
Attendance Records
-

Attendance tracking coming soon...

+
+
+

Member Attendance

+

Track your gym session attendance and performance

+
+
+ + +
+
+
+
+
{{ $sessionsCompleted }}
+
Sessions Completed
+
+
+
+
+
+
+
{{ $noShows }}
+
No Shows
+
+
+
+
+
+
+
{{ $attendanceRate }}%
+
Attendance Rate
+
+
+
+
+ + +
+
+
Session History
+
+
+
+ + + + + + + + + + + + @forelse($attendanceRecords as $record) + + + + + + + + @empty + + + + @endforelse + +
Date & TimeSession TypeTrainer NameStatusNotes
+
{{ $record->session_datetime->format('M j, Y') }}
+ {{ $record->session_datetime->format('g:i A') }} +
{{ $record->session_type }}{{ $record->trainer_name }} + @if($record->status === 'completed') + Completed + @else + No Show + @endif + + {{ $record->notes ?: '-' }} +
+ +

No attendance records found

+
+
+
+
@@ -1352,7 +1435,7 @@ r: { beginAtZero: true, ticks: { - stepSize: 10 + display: false } } }