added attendance and fixed body composition
This commit is contained in:
parent
87051e179d
commit
125e1c34af
@ -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'));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
29
app/Models/Attendance.php
Normal file
29
app/Models/Attendance.php
Normal file
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class Attendance extends Model
|
||||
{
|
||||
protected $table = 'members_attendance';
|
||||
|
||||
protected $fillable = [
|
||||
'member_id',
|
||||
'session_type',
|
||||
'trainer_name',
|
||||
'session_datetime',
|
||||
'status',
|
||||
'notes',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'session_datetime' => 'datetime',
|
||||
];
|
||||
|
||||
public function member(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class, 'member_id');
|
||||
}
|
||||
}
|
||||
@ -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.
|
||||
|
||||
@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('attendance_records', function (Blueprint $table) {
|
||||
$table->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');
|
||||
}
|
||||
};
|
||||
@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('members_attendance', function (Blueprint $table) {
|
||||
$table->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');
|
||||
}
|
||||
};
|
||||
79
database/seeders/AttendanceSeeder.php
Normal file
79
database/seeders/AttendanceSeeder.php
Normal file
@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use App\Models\Attendance;
|
||||
use App\Models\User;
|
||||
use App\Models\Tenant;
|
||||
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
|
||||
use Illuminate\Database\Seeder;
|
||||
|
||||
class AttendanceSeeder extends Seeder
|
||||
{
|
||||
/**
|
||||
* Run the database seeds.
|
||||
*/
|
||||
public function run(): void
|
||||
{
|
||||
$user = User::where('email', 'test@example.com')->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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -65,5 +65,8 @@ class DatabaseSeeder extends Seeder
|
||||
'status' => 'pending',
|
||||
'due_date' => now()->addDays(15),
|
||||
]);
|
||||
|
||||
// Seed attendance data
|
||||
$this->call(AttendanceSeeder::class);
|
||||
}
|
||||
}
|
||||
|
||||
@ -431,8 +431,91 @@
|
||||
<div class="tab-pane fade" id="attendance" role="tabpanel">
|
||||
<div class="card shadow-sm border-0">
|
||||
<div class="card-body p-4">
|
||||
<h5 class="fw-bold mb-3"><i class="bi bi-calendar-check me-2"></i>Attendance Records</h5>
|
||||
<p class="text-muted">Attendance tracking coming soon...</p>
|
||||
<div class="d-flex align-items-center justify-content-between mb-4">
|
||||
<div>
|
||||
<h1 class="h3 fw-bold">Member Attendance</h1>
|
||||
<p class="text-muted">Track your gym session attendance and performance</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Summary Cards -->
|
||||
<div class="row g-4 mb-4">
|
||||
<div class="col-md-4">
|
||||
<div class="card shadow-sm bg-light">
|
||||
<div class="card-body text-center">
|
||||
<div class="display-4 fw-bold text-success mb-2">{{ $sessionsCompleted }}</div>
|
||||
<h6 class="card-title text-muted">Sessions Completed</h6>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="card shadow-sm bg-light">
|
||||
<div class="card-body text-center">
|
||||
<div class="display-4 fw-bold text-danger mb-2">{{ $noShows }}</div>
|
||||
<h6 class="card-title text-muted">No Shows</h6>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="card shadow-sm bg-light">
|
||||
<div class="card-body text-center">
|
||||
<div class="display-4 fw-bold text-primary mb-2">{{ $attendanceRate }}%</div>
|
||||
<h6 class="card-title text-muted">Attendance Rate</h6>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Attendance Table -->
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-header bg-light">
|
||||
<h6 class="card-title mb-0">Session History</h6>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover mb-0">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th class="border-0 fw-semibold">Date & Time</th>
|
||||
<th class="border-0 fw-semibold">Session Type</th>
|
||||
<th class="border-0 fw-semibold">Trainer Name</th>
|
||||
<th class="border-0 fw-semibold">Status</th>
|
||||
<th class="border-0 fw-semibold">Notes</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@forelse($attendanceRecords as $record)
|
||||
<tr>
|
||||
<td class="align-middle">
|
||||
<div class="fw-semibold">{{ $record->session_datetime->format('M j, Y') }}</div>
|
||||
<small class="text-muted">{{ $record->session_datetime->format('g:i A') }}</small>
|
||||
</td>
|
||||
<td class="align-middle">{{ $record->session_type }}</td>
|
||||
<td class="align-middle">{{ $record->trainer_name }}</td>
|
||||
<td class="align-middle">
|
||||
@if($record->status === 'completed')
|
||||
<span class="badge bg-success">Completed</span>
|
||||
@else
|
||||
<span class="badge bg-danger">No Show</span>
|
||||
@endif
|
||||
</td>
|
||||
<td class="align-middle">
|
||||
<small class="text-muted">{{ $record->notes ?: '-' }}</small>
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="5" class="text-center py-4">
|
||||
<i class="bi bi-calendar-check text-muted" style="font-size: 2rem;"></i>
|
||||
<p class="text-muted mt-2 mb-0">No attendance records found</p>
|
||||
</td>
|
||||
</tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -1352,7 +1435,7 @@
|
||||
r: {
|
||||
beginAtZero: true,
|
||||
ticks: {
|
||||
stepSize: 10
|
||||
display: false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user