fixed the health update

This commit is contained in:
Ghassan Yusuf 2026-01-22 12:17:43 +03:00
parent 59ad30575c
commit 36aea9a266
7 changed files with 861 additions and 240 deletions

38
TODO.md
View File

@ -1,3 +1,35 @@
- [x] Add the :root CSS variables block to the <style> in resources/views/layouts/app.blade.php # Health Section Dynamic Update TODO
- [x] Update existing custom styles in the layout to use the new CSS variables (e.g., background-color: hsl(var(--primary)))
- [x] Verify that the colors are applied correctly in the views ## Completed
- [x] Create HealthRecord model
- [x] Create migration for health_records table
- [x] Add healthRecords relationship to User model
- [x] Update FamilyController show/profile methods to fetch health data
- [x] Update show.blade.php health tab with dynamic data
- [x] Replace hardcoded metrics with latest record data
- [x] Add date dropdowns for comparison (From/To labels)
- [x] Update comparison table with dynamic changes and colored arrows
- [x] Update history table with paginated data
- [x] Run migration
- [x] Handle no health records case
- [x] Add health update modal
- [x] Create modal HTML with form (defaults to current date)
- [x] Add JavaScript to trigger modal
- [x] Add route and controller method for storing
- [x] Handle form submission with validation (at least one metric required)
- [x] Add flash message display
- [x] Auto-activate health tab after saving
- [x] Handle self-profile health updates (no relationship check needed)
- [x] Add edit functionality for health records
- [x] Add hover effect with floating pencil icon on history table rows
- [x] Add JavaScript to populate modal for editing
- [x] Add route and controller method for updating
- [x] Handle form submission for updates with validation
- [x] Update modal title and button text for edit mode
## Testing
- [x] Test dynamic display with sample data
- [x] Test modal submission and tab activation
- [x] Test dynamic comparison dropdowns with live updates, colored arrows, and time difference calculation
- [x] Test pagination in history table
- [x] Test edit functionality with hover pencil icon and modal population

View File

@ -4,6 +4,7 @@ namespace App\Http\Controllers;
use App\Models\User; use App\Models\User;
use App\Models\UserRelationship; use App\Models\UserRelationship;
use App\Models\HealthRecord;
use App\Services\FamilyService; use App\Services\FamilyService;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
@ -44,6 +45,11 @@ class FamilyController extends Controller
{ {
$user = Auth::user(); $user = Auth::user();
// Fetch health data
$latestHealthRecord = $user->healthRecords()->latest('recorded_at')->first();
$healthRecords = $user->healthRecords()->orderBy('recorded_at', 'desc')->paginate(10);
$comparisonRecords = $user->healthRecords()->orderBy('recorded_at', 'desc')->take(2)->get();
// Pass user directly and a flag to indicate it's the current user's profile // Pass user directly and a flag to indicate it's the current user's profile
return view('family.show', [ return view('family.show', [
'relationship' => (object)[ 'relationship' => (object)[
@ -51,7 +57,10 @@ class FamilyController extends Controller
'relationship_type' => 'self', 'relationship_type' => 'self',
'guardian_user_id' => $user->id, 'guardian_user_id' => $user->id,
'dependent_user_id' => $user->id, 'dependent_user_id' => $user->id,
] ],
'latestHealthRecord' => $latestHealthRecord,
'healthRecords' => $healthRecords,
'comparisonRecords' => $comparisonRecords,
]); ]);
} }
@ -211,7 +220,12 @@ class FamilyController extends Controller
->with('dependent') ->with('dependent')
->firstOrFail(); ->firstOrFail();
return view('family.show', compact('relationship')); // Fetch health data for the dependent
$latestHealthRecord = $relationship->dependent->healthRecords()->latest('recorded_at')->first();
$healthRecords = $relationship->dependent->healthRecords()->orderBy('recorded_at', 'desc')->paginate(10);
$comparisonRecords = $relationship->dependent->healthRecords()->orderBy('recorded_at', 'desc')->take(2)->get();
return view('family.show', compact('relationship', 'latestHealthRecord', 'healthRecords', 'comparisonRecords'));
} }
/** /**
@ -352,6 +366,151 @@ class FamilyController extends Controller
], 400); ], 400);
} }
/**
* Store a health record for the specified family member.
*
* @param \Illuminate\Http\Request $request
* @param int $id
* @return \Illuminate\Http\RedirectResponse
*/
public function storeHealth(Request $request, $id)
{
$validated = $request->validate([
'recorded_at' => 'required|date',
'weight' => 'nullable|numeric|min:0|max:999.9',
'body_fat_percentage' => 'nullable|numeric|min:0|max:100',
'bmi' => 'nullable|numeric|min:0|max:100',
'body_water_percentage' => 'nullable|numeric|min:0|max:100',
'muscle_mass' => 'nullable|numeric|min:0|max:999.9',
'bone_mass' => 'nullable|numeric|min:0|max:999.9',
'visceral_fat' => 'nullable|integer|min:0|max:50',
'bmr' => 'nullable|integer|min:0|max:10000',
'protein_percentage' => 'nullable|numeric|min:0|max:100',
'body_age' => 'nullable|integer|min:0|max:150',
]);
// Check that at least one metric is provided besides the date
$metrics = array_filter([
$validated['weight'] ?? null,
$validated['body_fat_percentage'] ?? null,
$validated['bmi'] ?? null,
$validated['body_water_percentage'] ?? null,
$validated['muscle_mass'] ?? null,
$validated['bone_mass'] ?? null,
$validated['visceral_fat'] ?? null,
$validated['bmr'] ?? null,
$validated['protein_percentage'] ?? null,
$validated['body_age'] ?? null,
]);
if (empty($metrics)) {
return redirect()->back()
->with('error', 'Please provide at least one health metric besides the date.');
}
$user = Auth::user();
// For self profile, allow without relationship check
if ($id == $user->id) {
$dependent = $user;
} else {
// Verify the family member belongs to the authenticated user
$relationship = UserRelationship::where('guardian_user_id', $user->id)
->where('dependent_user_id', $id)
->firstOrFail();
$dependent = User::findOrFail($id);
}
// Check for duplicate date
$existing = $dependent->healthRecords()->where('recorded_at', $validated['recorded_at'])->first();
if ($existing) {
return redirect()->back()
->with('error', 'A health record already exists for this date. Please choose a different date.');
}
$dependent->healthRecords()->create($validated);
return redirect()->back()->withFragment('health')
->with('success', 'Health record added successfully.');
}
/**
* Update a health record for the specified family member.
*
* @param \Illuminate\Http\Request $request
* @param int $id
* @param int $recordId
* @return \Illuminate\Http\RedirectResponse
*/
public function updateHealth(Request $request, $id, $recordId)
{
$validated = $request->validate([
'recorded_at' => 'required|date',
'weight' => 'nullable|numeric|min:0|max:999.9',
'body_fat_percentage' => 'nullable|numeric|min:0|max:100',
'bmi' => 'nullable|numeric|min:0|max:100',
'body_water_percentage' => 'nullable|numeric|min:0|max:100',
'muscle_mass' => 'nullable|numeric|min:0|max:999.9',
'bone_mass' => 'nullable|numeric|min:0|max:999.9',
'visceral_fat' => 'nullable|integer|min:0|max:50',
'bmr' => 'nullable|integer|min:0|max:10000',
'protein_percentage' => 'nullable|numeric|min:0|max:100',
'body_age' => 'nullable|integer|min:0|max:150',
]);
// Check that at least one metric is provided besides the date
$metrics = array_filter([
$validated['weight'] ?? null,
$validated['body_fat_percentage'] ?? null,
$validated['bmi'] ?? null,
$validated['body_water_percentage'] ?? null,
$validated['muscle_mass'] ?? null,
$validated['bone_mass'] ?? null,
$validated['visceral_fat'] ?? null,
$validated['bmr'] ?? null,
$validated['protein_percentage'] ?? null,
$validated['body_age'] ?? null,
]);
if (empty($metrics)) {
return redirect()->back()
->with('error', 'Please provide at least one health metric besides the date.');
}
$user = Auth::user();
// For self profile, allow without relationship check
if ($id == $user->id) {
$dependent = $user;
} else {
// Verify the family member belongs to the authenticated user
$relationship = UserRelationship::where('guardian_user_id', $user->id)
->where('dependent_user_id', $id)
->firstOrFail();
$dependent = User::findOrFail($id);
}
// Find the health record
$healthRecord = $dependent->healthRecords()->findOrFail($recordId);
// Check for duplicate date (excluding current record)
$existing = $dependent->healthRecords()
->where('recorded_at', $validated['recorded_at'])
->where('id', '!=', $recordId)
->first();
if ($existing) {
return redirect()->back()
->with('error', 'A health record already exists for this date. Please choose a different date.');
}
$healthRecord->update($validated);
return redirect()->back()->withFragment('health')
->with('success', 'Health record updated successfully.');
}
/** /**
* Remove the specified family member from storage. * Remove the specified family member from storage.
* *

View File

@ -0,0 +1,45 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class HealthRecord extends Model
{
use HasFactory;
protected $fillable = [
'user_id',
'recorded_at',
'weight',
'body_fat_percentage',
'bmi',
'body_water_percentage',
'muscle_mass',
'bone_mass',
'visceral_fat',
'bmr',
'protein_percentage',
'body_age',
];
protected $casts = [
'recorded_at' => 'date',
'weight' => 'decimal:2',
'body_fat_percentage' => 'decimal:2',
'bmi' => 'decimal:2',
'body_water_percentage' => 'decimal:2',
'muscle_mass' => 'decimal:2',
'bone_mass' => 'decimal:2',
'visceral_fat' => 'integer',
'bmr' => 'integer',
'protein_percentage' => 'decimal:2',
'body_age' => 'integer',
];
public function user()
{
return $this->belongsTo(User::class);
}
}

View File

@ -219,6 +219,14 @@ class User extends Authenticatable
return $this->hasMany(Invoice::class, 'payer_user_id'); return $this->hasMany(Invoice::class, 'payer_user_id');
} }
/**
* Get the health records for the user.
*/
public function healthRecords(): HasMany
{
return $this->hasMany(HealthRecord::class);
}
/** /**
* Send the email verification notification. * Send the email verification notification.
* Override to prevent sending the default Laravel notification. * Override to prevent sending the default Laravel notification.

View File

@ -0,0 +1,41 @@
<?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('health_records', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->constrained()->onDelete('cascade');
$table->date('recorded_at');
$table->decimal('weight', 5, 2)->nullable();
$table->decimal('body_fat_percentage', 5, 2)->nullable();
$table->decimal('bmi', 5, 2)->nullable();
$table->decimal('body_water_percentage', 5, 2)->nullable();
$table->decimal('muscle_mass', 5, 2)->nullable();
$table->decimal('bone_mass', 5, 2)->nullable();
$table->integer('visceral_fat')->nullable();
$table->integer('bmr')->nullable();
$table->decimal('protein_percentage', 5, 2)->nullable();
$table->integer('body_age')->nullable();
$table->timestamps();
$table->unique(['user_id', 'recorded_at']);
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('health_records');
}
};

View File

@ -1,7 +1,34 @@
@extends('layouts.app') @extends('layouts.app')
@php
function calculateTimeDifference($date1, $date2) {
$diff = $date1->diff($date2);
$parts = [];
if ($diff->y > 0) $parts[] = $diff->y . ' year' . ($diff->y > 1 ? 's' : '');
if ($diff->m > 0) $parts[] = $diff->m . ' month' . ($diff->m > 1 ? 's' : '');
if ($diff->d > 0) $parts[] = $diff->d . ' day' . ($diff->d > 1 ? 's' : '');
return implode(' ', $parts) ?: 'Same day';
}
@endphp
@section('content') @section('content')
<div class="container py-4"> <div class="container py-4">
<!-- Flash Messages -->
@if(session('success'))
<div class="alert alert-success alert-dismissible fade show" role="alert">
{{ session('success') }}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
@endif
@if(session('error'))
<div class="alert alert-danger alert-dismissible fade show" role="alert">
{{ session('error') }}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
@endif
<!-- Header --> <!-- Header -->
<div class="d-flex justify-content-between align-items-center mb-4"> <div class="d-flex justify-content-between align-items-center mb-4">
<div> <div>
@ -35,15 +62,15 @@
<i class="bi bi-lightning me-1"></i>Action <i class="bi bi-lightning me-1"></i>Action
</button> </button>
<ul class="dropdown-menu dropdown-menu-end" aria-labelledby="actionDropdown"> <ul class="dropdown-menu dropdown-menu-end" aria-labelledby="actionDropdown">
<li><a class="dropdown-item" href="#"><i class="bi bi-trophy me-2"></i>Add Achievement</a></li>
<li><a class="dropdown-item" href="#"><i class="bi bi-calendar-check me-2"></i>Add Attendance Record</a></li>
<li><a class="dropdown-item" href="#"><i class="bi bi-calendar-event me-2"></i>Add Event Participation</a></li>
<li><a class="dropdown-item" href="#" data-bs-target="#healthUpdateModal"><i class="bi bi-heart-pulse me-2"></i>Add Health Update</a></li>
<li><a class="dropdown-item" href="#"><i class="bi bi-award me-2"></i>Add Tournament Participation</a></li>
<li><a class="dropdown-item" href="@if($relationship->relationship_type == 'self'){{ route('profile.edit') }}@else{{ route('family.edit', $relationship->dependent->id) }}@endif"> <li><a class="dropdown-item" href="@if($relationship->relationship_type == 'self'){{ route('profile.edit') }}@else{{ route('family.edit', $relationship->dependent->id) }}@endif">
<i class="bi bi-pencil me-2"></i>Edit Info <i class="bi bi-pencil me-2"></i>Edit Info
</a></li> </a></li>
<li><a class="dropdown-item" href="#"><i class="bi bi-calendar-check me-2"></i>Add Attendance Record</a></li>
<li><a class="dropdown-item" href="#"><i class="bi bi-heart-pulse me-2"></i>Add Health Update</a></li>
<li><a class="dropdown-item" href="#"><i class="bi bi-bullseye me-2"></i>Set a Goal</a></li> <li><a class="dropdown-item" href="#"><i class="bi bi-bullseye me-2"></i>Set a Goal</a></li>
<li><a class="dropdown-item" href="#"><i class="bi bi-trophy me-2"></i>Add Achievement</a></li>
<li><a class="dropdown-item" href="#"><i class="bi bi-award me-2"></i>Add Tournament Participation</a></li>
<li><a class="dropdown-item" href="#"><i class="bi bi-calendar-event me-2"></i>Add Event Participation</a></li>
</ul> </ul>
</div> </div>
</div> </div>
@ -507,93 +534,106 @@
<div class="d-flex justify-content-between align-items-center mb-4"> <div class="d-flex justify-content-between align-items-center mb-4">
<p class="text-muted small mb-0">Monitor health metrics and progress over time</p> <p class="text-muted small mb-0">Monitor health metrics and progress over time</p>
@php @if($latestHealthRecord)
$latestDate = \Carbon\Carbon::parse('2024-01-08'); @php
$now = \Carbon\Carbon::now(); $latestDate = $latestHealthRecord->recorded_at;
$diff = $latestDate->diff($now); $now = \Carbon\Carbon::now();
@endphp $diff = $latestDate->diff($now);
@endphp
<div class="d-flex align-items-center gap-3"> <div class="d-flex align-items-center gap-3">
<div class="d-flex align-items-center gap-2"> <div class="d-flex align-items-center gap-2">
<i class="bi bi-calendar-event text-primary"></i> <i class="bi bi-calendar-event text-primary"></i>
<span class="fw-semibold">Snapshot Date:</span> <span class="fw-semibold">Snapshot Date:</span>
<span class="text-muted">{{ $latestDate->format('F j, Y') }}</span> <span class="text-muted">{{ $latestDate->format('F j, Y') }}</span>
</div>
<div class="vr"></div>
<div class="d-flex align-items-center gap-2">
<i class="bi bi-clock-history text-primary"></i>
<span class="fw-semibold">Time Since:</span>
<span class="text-muted">
@if($diff->y > 0)
{{ $diff->y }} {{ $diff->y == 1 ? 'year' : 'years' }}
@endif
@if($diff->m > 0)
{{ $diff->m }} {{ $diff->m == 1 ? 'month' : 'months' }}
@endif
@if($diff->d > 0)
{{ $diff->d }} {{ $diff->d == 1 ? 'day' : 'days' }}
@endif
ago
</span>
</div>
</div> </div>
<div class="vr"></div> @else
<div class="d-flex align-items-center gap-2"> <div class="text-muted small">No health records available</div>
<i class="bi bi-clock-history text-primary"></i> @endif
<span class="fw-semibold">Time Since:</span>
<span class="text-muted">
@if($diff->y > 0)
{{ $diff->y }} {{ $diff->y == 1 ? 'year' : 'years' }}
@endif
@if($diff->m > 0)
{{ $diff->m }} {{ $diff->m == 1 ? 'month' : 'months' }}
@endif
@if($diff->d > 0)
{{ $diff->d }} {{ $diff->d == 1 ? 'day' : 'days' }}
@endif
ago
</span>
</div>
</div>
</div> </div>
<!-- Health Metrics Cards --> <!-- Health Metrics Cards -->
<div class="row g-3"> <div class="row g-3">
<!-- Weight --> @if($latestHealthRecord)
<div class="col-md-2"> <!-- Weight -->
<div class="text-center p-3 bg-light rounded"> <div class="col-md-2">
<i class="bi bi-speedometer2 text-purple mb-2" style="font-size: 1.5rem; color: #8b5cf6;"></i> <div class="text-center p-3 bg-light rounded">
<div class="h4 fw-bold mb-0">75</div> <i class="bi bi-speedometer2 text-purple mb-2" style="font-size: 1.5rem; color: #8b5cf6;"></i>
<small class="text-muted">Weight (kg)</small> <div class="h4 fw-bold mb-0">{{ $latestHealthRecord->weight ?? 'N/A' }}</div>
<small class="text-muted">Weight (kg)</small>
</div>
</div> </div>
</div>
<!-- Body Fat --> <!-- Body Fat -->
<div class="col-md-2"> <div class="col-md-2">
<div class="text-center p-3 bg-light rounded"> <div class="text-center p-3 bg-light rounded">
<i class="bi bi-activity text-warning mb-2" style="font-size: 1.5rem;"></i> <i class="bi bi-activity text-warning mb-2" style="font-size: 1.5rem;"></i>
<div class="h4 fw-bold mb-0">12.8%</div> <div class="h4 fw-bold mb-0">{{ $latestHealthRecord->body_fat_percentage ?? 'N/A' }}%</div>
<small class="text-muted">Body Fat</small> <small class="text-muted">Body Fat</small>
</div>
</div> </div>
</div>
<!-- Body Water --> <!-- Body Water -->
<div class="col-md-2"> <div class="col-md-2">
<div class="text-center p-3 bg-light rounded"> <div class="text-center p-3 bg-light rounded">
<i class="bi bi-droplet text-info mb-2" style="font-size: 1.5rem;"></i> <i class="bi bi-droplet text-info mb-2" style="font-size: 1.5rem;"></i>
<div class="h4 fw-bold mb-0">68.2%</div> <div class="h4 fw-bold mb-0">{{ $latestHealthRecord->body_water_percentage ?? 'N/A' }}%</div>
<small class="text-muted">Body Water</small> <small class="text-muted">Body Water</small>
</div>
</div> </div>
</div>
<!-- Muscle Mass --> <!-- Muscle Mass -->
<div class="col-md-2"> <div class="col-md-2">
<div class="text-center p-3 bg-light rounded"> <div class="text-center p-3 bg-light rounded">
<i class="bi bi-heart text-success mb-2" style="font-size: 1.5rem;"></i> <i class="bi bi-heart text-success mb-2" style="font-size: 1.5rem;"></i>
<div class="h4 fw-bold mb-0">70.5</div> <div class="h4 fw-bold mb-0">{{ $latestHealthRecord->muscle_mass ?? 'N/A' }}</div>
<small class="text-muted">Muscle Mass</small> <small class="text-muted">Muscle Mass</small>
</div>
</div> </div>
</div>
<!-- Bone Mass --> <!-- Bone Mass -->
<div class="col-md-2"> <div class="col-md-2">
<div class="text-center p-3 bg-light rounded"> <div class="text-center p-3 bg-light rounded">
<i class="bi bi-capsule text-secondary mb-2" style="font-size: 1.5rem;"></i> <i class="bi bi-capsule text-secondary mb-2" style="font-size: 1.5rem;"></i>
<div class="h4 fw-bold mb-0">3.7</div> <div class="h4 fw-bold mb-0">{{ $latestHealthRecord->bone_mass ?? 'N/A' }}</div>
<small class="text-muted">Bone Mass</small> <small class="text-muted">Bone Mass</small>
</div>
</div> </div>
</div>
<!-- BMR --> <!-- BMR -->
<div class="col-md-2"> <div class="col-md-2">
<div class="text-center p-3 bg-light rounded"> <div class="text-center p-3 bg-light rounded">
<i class="bi bi-lightning text-danger mb-2" style="font-size: 1.5rem;"></i> <i class="bi bi-lightning text-danger mb-2" style="font-size: 1.5rem;"></i>
<div class="h4 fw-bold mb-0">1895</div> <div class="h4 fw-bold mb-0">{{ $latestHealthRecord->bmr ?? 'N/A' }}</div>
<small class="text-muted">BMR (cal)</small> <small class="text-muted">BMR (cal)</small>
</div>
</div> </div>
</div> @else
<div class="col-12">
<div class="text-center py-4">
<i class="bi bi-heart-pulse text-muted" style="font-size: 3rem;"></i>
<p class="text-muted mt-3">No health metrics available</p>
</div>
</div>
@endif
</div> </div>
</div> </div>
</div> </div>
@ -621,80 +661,133 @@
<div class="card-body p-4"> <div class="card-body p-4">
<h5 class="fw-bold mb-4">Compare</h5> <h5 class="fw-bold mb-4">Compare</h5>
<div class="table-responsive"> @if($comparisonRecords->count() >= 2)
<table class="table table-sm align-middle"> @php
<thead> $current = $comparisonRecords->first();
<tr class="border-bottom"> $previous = $comparisonRecords->skip(1)->first();
<th class="text-muted small fw-semibold">Metric</th> @endphp
<th class="text-muted small fw-semibold text-end">Current</th>
<th class="text-muted small fw-semibold text-center">Change</th> <div class="mb-3">
<th class="text-muted small fw-semibold text-end">Previous</th> <div class="row g-2">
</tr> <div class="col-6 text-center">
</thead> <label class="form-label fw-bold">From</label>
<tbody> <select class="form-select form-select-sm" id="currentDate">
<tr> @foreach($healthRecords as $record)
<td class="small"><i class="bi bi-speedometer2 me-2"></i>Weight</td> <option value="{{ $record->id }}" {{ $record->id == $current->id ? 'selected' : '' }}>
<td class="small text-end fw-semibold">75kg</td> {{ $record->recorded_at->format('M j, Y') }}
<td class="text-center"><i class="bi bi-arrow-down text-success"></i></td> </option>
<td class="small text-end text-muted">77kg</td> @endforeach
</tr> </select>
<tr> </div>
<td class="small"><i class="bi bi-activity me-2"></i>Body Fat</td> <div class="col-6 text-center">
<td class="small text-end fw-semibold">12.8%</td> <label class="form-label fw-bold">To</label>
<td class="text-center"><i class="bi bi-arrow-down text-success"></i></td> <select class="form-select form-select-sm" id="previousDate">
<td class="small text-end text-muted">14.5%</td> @foreach($healthRecords as $record)
</tr> <option value="{{ $record->id }}" {{ $record->id == $previous->id ? 'selected' : '' }}>
<tr> {{ $record->recorded_at->format('M j, Y') }}
<td class="small"><i class="bi bi-calculator me-2"></i>BMI</td> </option>
<td class="small text-end fw-semibold">22.5</td> @endforeach
<td class="text-center"><i class="bi bi-arrow-down text-success"></i></td> </select>
<td class="small text-end text-muted">23.2</td> </div>
</tr> </div>
<tr> <div class="mt-3">
<td class="small"><i class="bi bi-droplet me-2"></i>Body Water</td> <div class="alert alert-secondary text-center py-2" id="timeDifference">
<td class="small text-end fw-semibold">68.2%</td> @if($current && $previous)
<td class="text-center"><i class="bi bi-arrow-up text-success"></i></td> <strong>Time between records:</strong> {{ calculateTimeDifference($current->recorded_at, $previous->recorded_at) }}
<td class="small text-end text-muted">65.8%</td> @else
</tr> Select dates to see time difference
<tr> @endif
<td class="small"><i class="bi bi-heart me-2"></i>Muscle Mass</td> </div>
<td class="small text-end fw-semibold">70.5kg</td> </div>
<td class="text-center"><i class="bi bi-arrow-up text-success"></i></td> </div>
<td class="small text-end text-muted">68.2kg</td>
</tr> <div class="table-responsive">
<tr> <table class="table table-sm align-middle">
<td class="small"><i class="bi bi-capsule me-2"></i>Bone Mass</td> <thead>
<td class="small text-end fw-semibold">3.7kg</td> <tr class="border-bottom">
<td class="text-center"><i class="bi bi-arrow-up text-success"></i></td> <th class="text-muted small fw-semibold">Metric</th>
<td class="small text-end text-muted">3.6kg</td> <th class="text-muted small fw-semibold text-end">Current</th>
</tr> <th class="text-muted small fw-semibold text-center">Change</th>
<tr> <th class="text-muted small fw-semibold text-end">Previous</th>
<td class="small"><i class="bi bi-activity me-2"></i>Visceral Fat</td> </tr>
<td class="small text-end fw-semibold">3</td> </thead>
<td class="text-center"><i class="bi bi-arrow-down text-success"></i></td> <tbody>
<td class="small text-end text-muted">4</td> @php
</tr> function getChangeIcon($current, $previous) {
<tr> if ($current > $previous) return '<i class="bi bi-arrow-up text-success"></i>';
<td class="small"><i class="bi bi-lightning me-2"></i>BMR</td> if ($current < $previous) return '<i class="bi bi-arrow-down text-danger"></i>';
<td class="small text-end fw-semibold">1895cal</td> return '<i class="bi bi-dash text-muted"></i>';
<td class="text-center"><i class="bi bi-arrow-up text-success"></i></td> }
<td class="small text-end text-muted">1865cal</td> @endphp
</tr> <tr data-metric="weight">
<tr> <td class="small"><i class="bi bi-speedometer2 me-2"></i>Weight</td>
<td class="small"><i class="bi bi-heart-pulse me-2"></i>Protein</td> <td class="small text-end fw-semibold">{{ $current->weight ?? 'N/A' }}kg</td>
<td class="small text-end fw-semibold">19.8%</td> <td class="text-center">{!! $current->weight && $previous->weight ? getChangeIcon($current->weight, $previous->weight) : '-' !!}</td>
<td class="text-center"><i class="bi bi-arrow-up text-success"></i></td> <td class="small text-end text-muted">{{ $previous->weight ?? 'N/A' }}kg</td>
<td class="small text-end text-muted">18.9%</td> </tr>
</tr> <tr data-metric="body_fat">
<tr> <td class="small"><i class="bi bi-activity me-2"></i>Body Fat</td>
<td class="small"><i class="bi bi-calendar-heart me-2"></i>Body Age</td> <td class="small text-end fw-semibold">{{ $current->body_fat_percentage ?? 'N/A' }}%</td>
<td class="small text-end fw-semibold">25yrs</td> <td class="text-center">{!! $current->body_fat_percentage && $previous->body_fat_percentage ? getChangeIcon($current->body_fat_percentage, $previous->body_fat_percentage) : '-' !!}</td>
<td class="text-center"><i class="bi bi-arrow-down text-success"></i></td> <td class="small text-end text-muted">{{ $previous->body_fat_percentage ?? 'N/A' }}%</td>
<td class="small text-end text-muted">27yrs</td> </tr>
</tr> <tr data-metric="bmi">
</tbody> <td class="small"><i class="bi bi-calculator me-2"></i>BMI</td>
</table> <td class="small text-end fw-semibold">{{ $current->bmi ?? 'N/A' }}</td>
</div> <td class="text-center">{!! $current->bmi && $previous->bmi ? getChangeIcon($current->bmi, $previous->bmi) : '-' !!}</td>
<td class="small text-end text-muted">{{ $previous->bmi ?? 'N/A' }}</td>
</tr>
<tr data-metric="body_water">
<td class="small"><i class="bi bi-droplet me-2"></i>Body Water</td>
<td class="small text-end fw-semibold">{{ $current->body_water_percentage ?? 'N/A' }}%</td>
<td class="text-center">{!! $current->body_water_percentage && $previous->body_water_percentage ? getChangeIcon($current->body_water_percentage, $previous->body_water_percentage) : '-' !!}</td>
<td class="small text-end text-muted">{{ $previous->body_water_percentage ?? 'N/A' }}%</td>
</tr>
<tr data-metric="muscle_mass">
<td class="small"><i class="bi bi-heart me-2"></i>Muscle Mass</td>
<td class="small text-end fw-semibold">{{ $current->muscle_mass ?? 'N/A' }}kg</td>
<td class="text-center">{!! $current->muscle_mass && $previous->muscle_mass ? getChangeIcon($current->muscle_mass, $previous->muscle_mass) : '-' !!}</td>
<td class="small text-end text-muted">{{ $previous->muscle_mass ?? 'N/A' }}kg</td>
</tr>
<tr data-metric="bone_mass">
<td class="small"><i class="bi bi-capsule me-2"></i>Bone Mass</td>
<td class="small text-end fw-semibold">{{ $current->bone_mass ?? 'N/A' }}kg</td>
<td class="text-center">{!! $current->bone_mass && $previous->bone_mass ? getChangeIcon($current->bone_mass, $previous->bone_mass) : '-' !!}</td>
<td class="small text-end text-muted">{{ $previous->bone_mass ?? 'N/A' }}kg</td>
</tr>
<tr data-metric="visceral_fat">
<td class="small"><i class="bi bi-activity me-2"></i>Visceral Fat</td>
<td class="small text-end fw-semibold">{{ $current->visceral_fat ?? 'N/A' }}</td>
<td class="text-center">{!! $current->visceral_fat && $previous->visceral_fat ? getChangeIcon($current->visceral_fat, $previous->visceral_fat) : '-' !!}</td>
<td class="small text-end text-muted">{{ $previous->visceral_fat ?? 'N/A' }}</td>
</tr>
<tr data-metric="bmr">
<td class="small"><i class="bi bi-lightning me-2"></i>BMR</td>
<td class="small text-end fw-semibold">{{ $current->bmr ?? 'N/A' }}cal</td>
<td class="text-center">{!! $current->bmr && $previous->bmr ? getChangeIcon($current->bmr, $previous->bmr) : '-' !!}</td>
<td class="small text-end text-muted">{{ $previous->bmr ?? 'N/A' }}cal</td>
</tr>
<tr data-metric="protein">
<td class="small"><i class="bi bi-heart-pulse me-2"></i>Protein</td>
<td class="small text-end fw-semibold">{{ $current->protein_percentage ?? 'N/A' }}%</td>
<td class="text-center">{!! $current->protein_percentage && $previous->protein_percentage ? getChangeIcon($current->protein_percentage, $previous->protein_percentage) : '-' !!}</td>
<td class="small text-end text-muted">{{ $previous->protein_percentage ?? 'N/A' }}%</td>
</tr>
<tr data-metric="body_age">
<td class="small"><i class="bi bi-calendar-heart me-2"></i>Body Age</td>
<td class="small text-end fw-semibold">{{ $current->body_age ?? 'N/A' }}yrs</td>
<td class="text-center">{!! $current->body_age && $previous->body_age ? getChangeIcon($current->body_age, $previous->body_age) : '-' !!}</td>
<td class="small text-end text-muted">{{ $previous->body_age ?? 'N/A' }}yrs</td>
</tr>
</tbody>
</table>
</div>
@else
<div class="text-center py-4">
<i class="bi bi-bar-chart-line text-muted" style="font-size: 3rem;"></i>
<p class="text-muted mt-3">Need at least 2 health records to compare</p>
</div>
@endif
</div> </div>
</div> </div>
</div> </div>
@ -705,92 +798,58 @@
<div class="card-body p-4"> <div class="card-body p-4">
<h5 class="fw-bold mb-4">Health Tracking History</h5> <h5 class="fw-bold mb-4">Health Tracking History</h5>
<div class="table-responsive"> @if($healthRecords->count() > 0)
<table class="table table-hover align-middle"> <div class="table-responsive">
<thead class="table-light"> <table class="table table-hover align-middle">
<tr> <thead class="table-light">
<th class="text-muted small fw-semibold">Date</th> <tr>
<th class="text-muted small fw-semibold text-center"><i class="bi bi-speedometer2 me-1"></i>Weight (kg)</th> <th class="text-muted small fw-semibold">Date</th>
<th class="text-muted small fw-semibold text-center"><i class="bi bi-activity me-1"></i>Body Fat %</th> <th class="text-muted small fw-semibold text-center"><i class="bi bi-speedometer2 me-1"></i>Weight (kg)</th>
<th class="text-muted small fw-semibold text-center"><i class="bi bi-calculator me-1"></i>BMI</th> <th class="text-muted small fw-semibold text-center"><i class="bi bi-activity me-1"></i>Body Fat %</th>
<th class="text-muted small fw-semibold text-center"><i class="bi bi-droplet me-1"></i>Body Water %</th> <th class="text-muted small fw-semibold text-center"><i class="bi bi-calculator me-1"></i>BMI</th>
<th class="text-muted small fw-semibold text-center"><i class="bi bi-heart me-1"></i>Muscle Mass (kg)</th> <th class="text-muted small fw-semibold text-center"><i class="bi bi-droplet me-1"></i>Body Water %</th>
<th class="text-muted small fw-semibold text-center"><i class="bi bi-capsule me-1"></i>Bone Mass (kg)</th> <th class="text-muted small fw-semibold text-center"><i class="bi bi-heart me-1"></i>Muscle Mass (kg)</th>
<th class="text-muted small fw-semibold text-center"><i class="bi bi-activity me-1"></i>Visceral Fat</th> <th class="text-muted small fw-semibold text-center"><i class="bi bi-capsule me-1"></i>Bone Mass (kg)</th>
<th class="text-muted small fw-semibold text-center"><i class="bi bi-lightning me-1"></i>BMR</th> <th class="text-muted small fw-semibold text-center"><i class="bi bi-activity me-1"></i>Visceral Fat</th>
<th class="text-muted small fw-semibold text-center"><i class="bi bi-heart-pulse me-1"></i>Protein %</th> <th class="text-muted small fw-semibold text-center"><i class="bi bi-lightning me-1"></i>BMR</th>
<th class="text-muted small fw-semibold text-center"><i class="bi bi-calendar-heart me-1"></i>Body Age</th> <th class="text-muted small fw-semibold text-center"><i class="bi bi-heart-pulse me-1"></i>Protein %</th>
</tr> <th class="text-muted small fw-semibold text-center"><i class="bi bi-calendar-heart me-1"></i>Body Age</th>
</thead> </tr>
<tbody> </thead>
<tr> <tbody>
<td class="small fw-semibold">Jan 08, 2024</td> @foreach($healthRecords as $record)
<td class="small text-center">75</td> <tr data-record-id="{{ $record->id }}" class="position-relative history-row">
<td class="small text-center">12.8</td> <td class="small fw-semibold">{{ $record->recorded_at->format('M j, Y') }}</td>
<td class="small text-center">22.5</td> <td class="small text-center">{{ $record->weight ?? '-' }}</td>
<td class="small text-center">68.2</td> <td class="small text-center">{{ $record->body_fat_percentage ?? '-' }}</td>
<td class="small text-center">70.5</td> <td class="small text-center">{{ $record->bmi ?? '-' }}</td>
<td class="small text-center">3.7</td> <td class="small text-center">{{ $record->body_water_percentage ?? '-' }}</td>
<td class="small text-center">3</td> <td class="small text-center">{{ $record->muscle_mass ?? '-' }}</td>
<td class="small text-center">1895</td> <td class="small text-center">{{ $record->bone_mass ?? '-' }}</td>
<td class="small text-center">19.8</td> <td class="small text-center">{{ $record->visceral_fat ?? '-' }}</td>
<td class="small text-center">25</td> <td class="small text-center">{{ $record->bmr ?? '-' }}</td>
</tr> <td class="small text-center">{{ $record->protein_percentage ?? '-' }}</td>
<tr> <td class="small text-center">{{ $record->body_age ?? '-' }}</td>
<td class="small fw-semibold">Jan 01, 2024</td> <td class="position-absolute top-50 end-0 translate-middle-y opacity-0 edit-record-btn" style="cursor: pointer; right: 10px;">
<td class="small text-center">77</td> <i class="bi bi-pencil text-primary" style="font-size: 1.2rem;"></i>
<td class="small text-center">14.5</td> </td>
<td class="small text-center">23.2</td> </tr>
<td class="small text-center">65.8</td> @endforeach
<td class="small text-center">68.2</td> </tbody>
<td class="small text-center">3.6</td> </table>
<td class="small text-center">4</td> </div>
<td class="small text-center">1865</td>
<td class="small text-center">18.9</td> <!-- Pagination -->
<td class="small text-center">27</td> <div class="d-flex justify-content-center mt-4">
</tr> {{ $healthRecords->links() }}
<tr> </div>
<td class="small fw-semibold">Dec 25, 2023</td> @else
<td class="small text-center">79</td> <div class="text-center py-5">
<td class="small text-center">16.8</td> <i class="bi bi-clipboard-data text-muted" style="font-size: 3rem;"></i>
<td class="small text-center">23.9</td> <p class="text-muted mt-3">No health records found</p>
<td class="small text-center">63.2</td> <small class="text-muted">Health tracking data will appear here once records are added</small>
<td class="small text-center">66.7</td> </div>
<td class="small text-center">3.5</td> @endif
<td class="small text-center">5</td>
<td class="small text-center">1845</td>
<td class="small text-center">18.2</td>
<td class="small text-center">29</td>
</tr>
<tr>
<td class="small fw-semibold">Dec 18, 2023</td>
<td class="small text-center">82</td>
<td class="small text-center">19.2</td>
<td class="small text-center">24.6</td>
<td class="small text-center">60.8</td>
<td class="small text-center">64.7</td>
<td class="small text-center">3.4</td>
<td class="small text-center">7</td>
<td class="small text-center">1820</td>
<td class="small text-center">17.6</td>
<td class="small text-center">31</td>
</tr>
<tr>
<td class="small fw-semibold">Dec 11, 2023</td>
<td class="small text-center">84</td>
<td class="small text-center">21.5</td>
<td class="small text-center">25.2</td>
<td class="small text-center">58.5</td>
<td class="small text-center">63</td>
<td class="small text-center">3.4</td>
<td class="small text-center">8</td>
<td class="small text-center">1795</td>
<td class="small text-center">17.1</td>
<td class="small text-center">33</td>
</tr>
</tbody>
</table>
</div>
</div> </div>
</div> </div>
</div> </div>
@ -837,6 +896,79 @@
</div> </div>
</div> </div>
<!-- Health Update Modal -->
<div class="modal fade" id="healthUpdateModal" tabindex="-1" aria-labelledby="healthUpdateModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="healthUpdateModalLabel">Add Health Update</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<form id="healthUpdateForm" method="POST" action="{{ route('family.store-health', $relationship->dependent->id) }}">
@csrf
<div class="modal-body">
<div class="row g-3">
<div class="col-md-6">
<label for="recorded_at" class="form-label">Date <span class="text-danger">*</span></label>
<input type="date" class="form-control" id="recorded_at" name="recorded_at" value="{{ \Carbon\Carbon::now()->format('Y-m-d') }}" required>
</div>
<div class="col-md-6">
<label for="weight" class="form-label">Weight (kg)</label>
<input type="number" step="0.1" class="form-control" id="weight" name="weight">
</div>
<div class="col-md-6">
<label for="body_fat_percentage" class="form-label">Body Fat (%)</label>
<input type="number" step="0.1" class="form-control" id="body_fat_percentage" name="body_fat_percentage">
</div>
<div class="col-md-6">
<label for="bmi" class="form-label">BMI</label>
<input type="number" step="0.1" class="form-control" id="bmi" name="bmi">
</div>
<div class="col-md-6">
<label for="body_water_percentage" class="form-label">Body Water (%)</label>
<input type="number" step="0.1" class="form-control" id="body_water_percentage" name="body_water_percentage">
</div>
<div class="col-md-6">
<label for="muscle_mass" class="form-label">Muscle Mass (kg)</label>
<input type="number" step="0.1" class="form-control" id="muscle_mass" name="muscle_mass">
</div>
<div class="col-md-6">
<label for="bone_mass" class="form-label">Bone Mass (kg)</label>
<input type="number" step="0.1" class="form-control" id="bone_mass" name="bone_mass">
</div>
<div class="col-md-6">
<label for="visceral_fat" class="form-label">Visceral Fat</label>
<input type="number" class="form-control" id="visceral_fat" name="visceral_fat">
</div>
<div class="col-md-6">
<label for="bmr" class="form-label">BMR (cal)</label>
<input type="number" class="form-control" id="bmr" name="bmr">
</div>
<div class="col-md-6">
<label for="protein_percentage" class="form-label">Protein (%)</label>
<input type="number" step="0.1" class="form-control" id="protein_percentage" name="protein_percentage">
</div>
<div class="col-md-6">
<label for="body_age" class="form-label">Body Age (years)</label>
<input type="number" class="form-control" id="body_age" name="body_age">
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="submit" class="btn btn-primary">Save Health Update</button>
</div>
</form>
</div>
</div>
</div>
<style>
.history-row:hover .edit-record-btn {
opacity: 1 !important;
}
</style>
<script> <script>
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function() {
// Load countries from JSON file // Load countries from JSON file
@ -862,6 +994,208 @@
}); });
}) })
.catch(error => console.error('Error loading countries:', error)); .catch(error => console.error('Error loading countries:', error));
// Handle Add Health Update click
document.querySelector('a[href="#"][data-bs-target="#healthUpdateModal"]').addEventListener('click', function(e) {
e.preventDefault();
resetHealthModal();
const modal = new bootstrap.Modal(document.getElementById('healthUpdateModal'));
modal.show();
});
// Handle Edit Health Record click
document.addEventListener('click', function(e) {
if (e.target.closest('.edit-record-btn')) {
e.preventDefault();
const recordId = e.target.closest('tr').getAttribute('data-record-id');
populateHealthModalForEdit(recordId);
const modal = new bootstrap.Modal(document.getElementById('healthUpdateModal'));
modal.show();
}
});
// Activate health tab if URL has #health
if (window.location.hash === '#health') {
const healthTab = document.querySelector('#health-tab');
if (healthTab) {
const tab = new bootstrap.Tab(healthTab);
tab.show();
}
}
// Store health records data for dynamic comparison
const healthRecordsData = @json($healthRecords->items());
// Function to reset modal for adding new record
function resetHealthModal() {
document.getElementById('healthUpdateModalLabel').textContent = 'Add Health Update';
document.getElementById('healthUpdateForm').action = '{{ route("family.store-health", $relationship->dependent->id) }}';
document.getElementById('healthUpdateForm').method = 'POST';
document.getElementById('recorded_at').value = '{{ \Carbon\Carbon::now()->format("Y-m-d") }}';
document.getElementById('weight').value = '';
document.getElementById('body_fat_percentage').value = '';
document.getElementById('bmi').value = '';
document.getElementById('body_water_percentage').value = '';
document.getElementById('muscle_mass').value = '';
document.getElementById('bone_mass').value = '';
document.getElementById('visceral_fat').value = '';
document.getElementById('bmr').value = '';
document.getElementById('protein_percentage').value = '';
document.getElementById('body_age').value = '';
document.querySelector('#healthUpdateForm button[type="submit"]').textContent = 'Save Health Update';
}
// Function to populate modal for editing
function populateHealthModalForEdit(recordId) {
const record = healthRecordsData.find(r => r.id == recordId);
if (!record) return;
document.getElementById('healthUpdateModalLabel').textContent = 'Edit Health Update';
document.getElementById('healthUpdateForm').action = '{{ route("family.update-health", ["id" => $relationship->dependent->id, "recordId" => "__RECORD_ID__"]) }}'.replace('__RECORD_ID__', recordId);
document.getElementById('healthUpdateForm').method = 'POST';
// Add method spoofing for PUT
let methodInput = document.querySelector('#healthUpdateForm input[name="_method"]');
if (!methodInput) {
methodInput = document.createElement('input');
methodInput.type = 'hidden';
methodInput.name = '_method';
document.getElementById('healthUpdateForm').appendChild(methodInput);
}
methodInput.value = 'PUT';
document.getElementById('recorded_at').value = record.recorded_at.split('T')[0];
document.getElementById('weight').value = record.weight || '';
document.getElementById('body_fat_percentage').value = record.body_fat_percentage || '';
document.getElementById('bmi').value = record.bmi || '';
document.getElementById('body_water_percentage').value = record.body_water_percentage || '';
document.getElementById('muscle_mass').value = record.muscle_mass || '';
document.getElementById('bone_mass').value = record.bone_mass || '';
document.getElementById('visceral_fat').value = record.visceral_fat || '';
document.getElementById('bmr').value = record.bmr || '';
document.getElementById('protein_percentage').value = record.protein_percentage || '';
document.getElementById('body_age').value = record.body_age || '';
document.querySelector('#healthUpdateForm button[type="submit"]').textContent = 'Update Health Update';
}
// Handle comparison dropdown changes
const currentDateSelect = document.getElementById('currentDate');
const previousDateSelect = document.getElementById('previousDate');
if (currentDateSelect && previousDateSelect) {
function updateComparisonTable() {
const currentId = currentDateSelect.value;
const previousId = previousDateSelect.value;
if (!currentId || !previousId) {
document.getElementById('timeDifference').innerHTML = 'Select dates to see time difference';
return;
}
const currentRecord = healthRecordsData.find(r => r.id == currentId);
const previousRecord = healthRecordsData.find(r => r.id == previousId);
if (!currentRecord || !previousRecord) {
document.getElementById('timeDifference').innerHTML = 'Select dates to see time difference';
return;
}
// Update time difference
const timeDiff = calculateTimeDifference(currentRecord.recorded_at, previousRecord.recorded_at);
document.getElementById('timeDifference').innerHTML = `<strong>Time between records:</strong> ${timeDiff}`;
// Update the table rows
updateTableRow('weight', currentRecord.weight, previousRecord.weight);
updateTableRow('body_fat', currentRecord.body_fat_percentage, previousRecord.body_fat_percentage);
updateTableRow('bmi', currentRecord.bmi, previousRecord.bmi);
updateTableRow('body_water', currentRecord.body_water_percentage, previousRecord.body_water_percentage);
updateTableRow('muscle_mass', currentRecord.muscle_mass, previousRecord.muscle_mass);
updateTableRow('bone_mass', currentRecord.bone_mass, previousRecord.bone_mass);
updateTableRow('visceral_fat', currentRecord.visceral_fat, previousRecord.visceral_fat);
updateTableRow('bmr', currentRecord.bmr, previousRecord.bmr);
updateTableRow('protein', currentRecord.protein_percentage, previousRecord.protein_percentage);
updateTableRow('body_age', currentRecord.body_age, previousRecord.body_age);
}
function calculateTimeDifference(date1, date2) {
const d1 = new Date(date1);
const d2 = new Date(date2);
const diff = Math.abs(d1 - d2);
const years = Math.floor(diff / (1000 * 60 * 60 * 24 * 365));
const months = Math.floor((diff % (1000 * 60 * 60 * 24 * 365)) / (1000 * 60 * 60 * 24 * 30));
const days = Math.floor((diff % (1000 * 60 * 60 * 24 * 30)) / (1000 * 60 * 60 * 24));
const parts = [];
if (years > 0) parts.push(`${years} year${years > 1 ? 's' : ''}`);
if (months > 0) parts.push(`${months} month${months > 1 ? 's' : ''}`);
if (days > 0) parts.push(`${days} day${days > 1 ? 's' : ''}`);
return parts.length > 0 ? parts.join(' ') : 'Same day';
}
function updateTableRow(metric, currentValue, previousValue) {
const row = document.querySelector(`tr[data-metric="${metric}"]`);
if (!row) return;
const cells = row.querySelectorAll('td');
if (cells.length >= 4) {
// Update current value
if (metric === 'weight' || metric === 'muscle_mass' || metric === 'bone_mass') {
cells[1].textContent = currentValue ? `${currentValue}kg` : 'N/A';
} else if (metric === 'body_fat' || metric === 'body_water' || metric === 'protein') {
cells[1].textContent = currentValue ? `${currentValue}%` : 'N/A';
} else if (metric === 'bmr') {
cells[1].textContent = currentValue ? `${currentValue}cal` : 'N/A';
} else if (metric === 'body_age') {
cells[1].textContent = currentValue ? `${currentValue}yrs` : 'N/A';
} else {
cells[1].textContent = currentValue || 'N/A';
}
// Update previous value
if (metric === 'weight' || metric === 'muscle_mass' || metric === 'bone_mass') {
cells[3].textContent = previousValue ? `${previousValue}kg` : 'N/A';
} else if (metric === 'body_fat' || metric === 'body_water' || metric === 'protein') {
cells[3].textContent = previousValue ? `${previousValue}%` : 'N/A';
} else if (metric === 'bmr') {
cells[3].textContent = previousValue ? `${previousValue}cal` : 'N/A';
} else if (metric === 'body_age') {
cells[3].textContent = previousValue ? `${previousValue}yrs` : 'N/A';
} else {
cells[3].textContent = previousValue || 'N/A';
}
// Update change cell
const changeCell = cells[2];
if (currentValue && previousValue) {
const change = currentValue - previousValue;
let arrow = '';
let colorClass = 'text-muted';
if (change > 0) {
arrow = '↑';
colorClass = 'text-danger';
} else if (change < 0) {
arrow = '↓';
colorClass = 'text-success';
} else {
arrow = '—';
colorClass = 'text-muted';
}
changeCell.innerHTML = `<span class="${colorClass}">${arrow} ${Math.abs(change).toFixed(1)}</span>`;
} else {
changeCell.textContent = '-';
}
}
}
currentDateSelect.addEventListener('change', updateComparisonTable);
previousDateSelect.addEventListener('change', updateComparisonTable);
}
}); });
</script> </script>
@endsection @endsection

View File

@ -92,6 +92,8 @@ Route::middleware(['auth', 'verified'])->group(function () {
Route::get('/family/{id}', [FamilyController::class, 'show'])->name('family.show'); Route::get('/family/{id}', [FamilyController::class, 'show'])->name('family.show');
Route::get('/family/{id}/edit', [FamilyController::class, 'edit'])->name('family.edit'); Route::get('/family/{id}/edit', [FamilyController::class, 'edit'])->name('family.edit');
Route::put('/family/{id}', [FamilyController::class, 'update'])->name('family.update'); Route::put('/family/{id}', [FamilyController::class, 'update'])->name('family.update');
Route::post('/family/{id}/health', [FamilyController::class, 'storeHealth'])->name('family.store-health');
Route::put('/family/{id}/health/{recordId}', [FamilyController::class, 'updateHealth'])->name('family.update-health');
Route::post('/family/{id}/upload-picture', [FamilyController::class, 'uploadFamilyMemberPicture'])->name('family.upload-picture'); Route::post('/family/{id}/upload-picture', [FamilyController::class, 'uploadFamilyMemberPicture'])->name('family.upload-picture');
Route::delete('/family/{id}', [FamilyController::class, 'destroy'])->name('family.destroy'); Route::delete('/family/{id}', [FamilyController::class, 'destroy'])->name('family.destroy');