latest updates
This commit is contained in:
parent
36aea9a266
commit
b0c49bc6e4
44
TODO.md
44
TODO.md
@ -1,35 +1,9 @@
|
||||
# Health Section Dynamic Update TODO
|
||||
|
||||
## 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
|
||||
- [x] Add icon to Body Composition Analysis
|
||||
- [x] Add icon to Compare
|
||||
- [x] Change Health Tracking History to Health Tracking and add icon
|
||||
- [x] Add icon to Goals & Progress
|
||||
- [x] Add icon to Attendance Records
|
||||
- [x] Add icon to Tournament History
|
||||
- [x] Add icon to Event Participation
|
||||
- [x] Add icon to Affiliations & Badges
|
||||
- [x] Change Affiliations tab and header icon from bi-trophy to bi-diagram-3
|
||||
|
||||
@ -5,6 +5,7 @@ namespace App\Http\Controllers;
|
||||
use App\Models\User;
|
||||
use App\Models\UserRelationship;
|
||||
use App\Models\HealthRecord;
|
||||
use App\Models\Invoice;
|
||||
use App\Services\FamilyService;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
@ -28,6 +29,7 @@ class FamilyController extends Controller
|
||||
$user = Auth::user();
|
||||
$dependents = UserRelationship::where('guardian_user_id', $user->id)
|
||||
->with('dependent')
|
||||
->whereHas('dependent')
|
||||
->get()
|
||||
->sortBy(function($relationship) {
|
||||
return $relationship->dependent->full_name;
|
||||
@ -50,6 +52,9 @@ class FamilyController extends Controller
|
||||
$healthRecords = $user->healthRecords()->orderBy('recorded_at', 'desc')->paginate(10);
|
||||
$comparisonRecords = $user->healthRecords()->orderBy('recorded_at', 'desc')->take(2)->get();
|
||||
|
||||
// Fetch invoices
|
||||
$invoices = Invoice::where('student_user_id', $user->id)->orWhere('payer_user_id', $user->id)->with(['student', 'tenant'])->get();
|
||||
|
||||
// Pass user directly and a flag to indicate it's the current user's profile
|
||||
return view('family.show', [
|
||||
'relationship' => (object)[
|
||||
@ -61,6 +66,7 @@ class FamilyController extends Controller
|
||||
'latestHealthRecord' => $latestHealthRecord,
|
||||
'healthRecords' => $healthRecords,
|
||||
'comparisonRecords' => $comparisonRecords,
|
||||
'invoices' => $invoices,
|
||||
]);
|
||||
}
|
||||
|
||||
@ -225,7 +231,10 @@ class FamilyController extends Controller
|
||||
$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'));
|
||||
// Fetch invoices for the dependent
|
||||
$invoices = Invoice::where('student_user_id', $relationship->dependent->id)->orWhere('payer_user_id', $relationship->dependent->id)->with(['student', 'tenant'])->get();
|
||||
|
||||
return view('family.show', compact('relationship', 'latestHealthRecord', 'healthRecords', 'comparisonRecords', 'invoices'));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -53,9 +53,9 @@ class InvoiceController extends Controller
|
||||
* Display the receipt for the specified invoice.
|
||||
*
|
||||
* @param int $id
|
||||
* @return \Illuminate\View\View
|
||||
* @return \Illuminate\View\View|\Illuminate\Http\Response
|
||||
*/
|
||||
public function receipt($id)
|
||||
public function receipt(Request $request, $id)
|
||||
{
|
||||
$user = Auth::user();
|
||||
$invoice = Invoice::where('id', $id)
|
||||
@ -63,6 +63,13 @@ class InvoiceController extends Controller
|
||||
->with(['student', 'tenant'])
|
||||
->firstOrFail();
|
||||
|
||||
if ($request->has('download')) {
|
||||
$html = view('invoices.receipt', compact('invoice'))->render();
|
||||
return response($html)
|
||||
->header('Content-Type', 'text/html')
|
||||
->header('Content-Disposition', 'attachment; filename="receipt_' . $invoice->id . '.html"');
|
||||
}
|
||||
|
||||
return view('invoices.receipt', compact('invoice'));
|
||||
}
|
||||
|
||||
|
||||
@ -3,7 +3,6 @@
|
||||
namespace App\Models;
|
||||
|
||||
// use Illuminate\Contracts\Auth\MustVerifyEmail;
|
||||
use Illuminate\Auth\MustVerifyEmail;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||
@ -16,7 +15,7 @@ use Carbon\Carbon;
|
||||
class User extends Authenticatable
|
||||
{
|
||||
/** @use HasFactory<\Database\Factories\UserFactory> */
|
||||
use HasFactory, Notifiable, MustVerifyEmail, SoftDeletes;
|
||||
use HasFactory, Notifiable, SoftDeletes;
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
|
||||
@ -27,6 +27,7 @@ class DatabaseSeeder extends Seeder
|
||||
'full_name' => 'Test User',
|
||||
'mobile' => ['code' => '+1', 'number' => '1234567890'],
|
||||
'password' => bcrypt('password'),
|
||||
'email_verified_at' => now(),
|
||||
]);
|
||||
|
||||
// Create a tenant (club)
|
||||
@ -34,6 +35,8 @@ class DatabaseSeeder extends Seeder
|
||||
'owner_user_id' => $user->id,
|
||||
'club_name' => 'Test Club',
|
||||
'slug' => 'test-club',
|
||||
'gps_lat' => 25.276987, // Dubai latitude
|
||||
'gps_long' => 55.296249, // Dubai longitude
|
||||
]);
|
||||
|
||||
// Create membership
|
||||
|
||||
40
package-lock.json
generated
40
package-lock.json
generated
@ -5,6 +5,7 @@
|
||||
"packages": {
|
||||
"": {
|
||||
"dependencies": {
|
||||
"chart.js": "^4.5.1",
|
||||
"jquery-cropbox": "github:acornejo/jquery-cropbox"
|
||||
},
|
||||
"devDependencies": {
|
||||
@ -479,6 +480,12 @@
|
||||
"@jridgewell/sourcemap-codec": "^1.4.14"
|
||||
}
|
||||
},
|
||||
"node_modules/@kurkle/color": {
|
||||
"version": "0.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.4.tgz",
|
||||
"integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@rollup/rollup-android-arm-eabi": {
|
||||
"version": "4.55.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.55.2.tgz",
|
||||
@ -1158,6 +1165,18 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/chart.js": {
|
||||
"version": "4.5.1",
|
||||
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.5.1.tgz",
|
||||
"integrity": "sha512-GIjfiT9dbmHRiYi6Nl2yFCq7kkwdkp1W/lp2J99rX0yo9tgJGn3lKQATztIjb5tVtevcBtIdICNWqlq5+E8/Pw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@kurkle/color": "^0.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"pnpm": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/cliui": {
|
||||
"version": "8.0.1",
|
||||
"resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
|
||||
@ -1924,6 +1943,7 @@
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
|
||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
@ -2150,6 +2170,7 @@
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz",
|
||||
"integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"esbuild": "^0.27.0",
|
||||
"fdir": "^6.5.0",
|
||||
@ -2520,6 +2541,11 @@
|
||||
"@jridgewell/sourcemap-codec": "^1.4.14"
|
||||
}
|
||||
},
|
||||
"@kurkle/color": {
|
||||
"version": "0.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.4.tgz",
|
||||
"integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w=="
|
||||
},
|
||||
"@rollup/rollup-android-arm-eabi": {
|
||||
"version": "4.55.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.55.2.tgz",
|
||||
@ -2911,6 +2937,14 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"chart.js": {
|
||||
"version": "4.5.1",
|
||||
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.5.1.tgz",
|
||||
"integrity": "sha512-GIjfiT9dbmHRiYi6Nl2yFCq7kkwdkp1W/lp2J99rX0yo9tgJGn3lKQATztIjb5tVtevcBtIdICNWqlq5+E8/Pw==",
|
||||
"requires": {
|
||||
"@kurkle/color": "^0.3.0"
|
||||
}
|
||||
},
|
||||
"cliui": {
|
||||
"version": "8.0.1",
|
||||
"resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
|
||||
@ -3207,7 +3241,7 @@
|
||||
},
|
||||
"jquery-cropbox": {
|
||||
"version": "git+ssh://git@github.com/acornejo/jquery-cropbox.git#5d4ec5290849507ee27c11e8ae337090182ade31",
|
||||
"from": "jquery-cropbox@https://github.com/acornejo/jquery-cropbox.git"
|
||||
"from": "jquery-cropbox@github:acornejo/jquery-cropbox"
|
||||
},
|
||||
"laravel-vite-plugin": {
|
||||
"version": "2.1.0",
|
||||
@ -3362,7 +3396,8 @@
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
|
||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||
"dev": true
|
||||
"dev": true,
|
||||
"peer": true
|
||||
},
|
||||
"postcss": {
|
||||
"version": "8.5.6",
|
||||
@ -3517,6 +3552,7 @@
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz",
|
||||
"integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"requires": {
|
||||
"esbuild": "^0.27.0",
|
||||
"fdir": "^6.5.0",
|
||||
|
||||
@ -17,6 +17,7 @@
|
||||
"vite": "^7.0.7"
|
||||
},
|
||||
"dependencies": {
|
||||
"chart.js": "^4.5.1",
|
||||
"jquery-cropbox": "github:acornejo/jquery-cropbox"
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,7 +2,9 @@ import './bootstrap';
|
||||
import $ from 'jquery';
|
||||
import 'jquery-cropbox';
|
||||
import imageCompression from 'browser-image-compression';
|
||||
import Chart from 'chart.js/auto';
|
||||
|
||||
// Make jQuery globally available for jquery-cropbox
|
||||
window.$ = window.jQuery = $;
|
||||
window.imageCompression = imageCompression;
|
||||
window.Chart = Chart;
|
||||
|
||||
@ -97,6 +97,8 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Map Modal -->
|
||||
@ -317,15 +319,20 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
// Near Me button
|
||||
document.getElementById('nearMeBtn').addEventListener('click', function() {
|
||||
const mapModal = new bootstrap.Modal(document.getElementById('mapModal'));
|
||||
mapModal.show();
|
||||
const modalElement = document.getElementById('mapModal');
|
||||
|
||||
// Initialize map when modal is shown
|
||||
setTimeout(() => {
|
||||
modalElement.addEventListener('shown.bs.modal', function() {
|
||||
if (userLocation) {
|
||||
initMap(userLocation.latitude, userLocation.longitude);
|
||||
updateModalLocation(userLocation.latitude, userLocation.longitude);
|
||||
} else {
|
||||
// No location available, use default and let user drag
|
||||
initMap(25.276987, 55.296249); // Default to Dubai or any location
|
||||
updateModalLocation(25.276987, 55.296249);
|
||||
}
|
||||
}, 300);
|
||||
}, { once: true });
|
||||
|
||||
mapModal.show();
|
||||
});
|
||||
|
||||
// Apply Location button
|
||||
@ -354,10 +361,11 @@ function startWatchingLocation() {
|
||||
|
||||
updateLocationDisplay(userLocation.latitude, userLocation.longitude);
|
||||
|
||||
// If current category is not 'all', fetch nearby clubs
|
||||
if (currentCategory !== 'all') {
|
||||
fetchNearbyClubs(userLocation.latitude, userLocation.longitude);
|
||||
}
|
||||
// Fetch nearby clubs
|
||||
fetchNearbyClubs(userLocation.latitude, userLocation.longitude);
|
||||
|
||||
// Initialize page map
|
||||
initPageMap(userLocation.latitude, userLocation.longitude);
|
||||
|
||||
// Stop watching after first successful location
|
||||
if (watchId) {
|
||||
@ -398,6 +406,43 @@ function updateLocationDisplay(lat, lng) {
|
||||
`<i class="bi bi-geo-alt-fill me-1 fs-5"></i>${lat.toFixed(4)}, ${lng.toFixed(4)}`;
|
||||
}
|
||||
|
||||
// Initialize page map
|
||||
function initPageMap(lat, lng) {
|
||||
if (pageMap) {
|
||||
pageMap.remove();
|
||||
}
|
||||
|
||||
pageMap = L.map('pageMap').setView([lat, lng], 13);
|
||||
|
||||
L.tileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||
attribution: '© OpenStreetMap contributors',
|
||||
maxZoom: 19
|
||||
}).addTo(pageMap);
|
||||
|
||||
// Add user marker
|
||||
userMarker = L.marker([lat, lng], {
|
||||
icon: L.divIcon({
|
||||
className: 'user-location-marker',
|
||||
html: '<i class="bi bi-geo-alt-fill pulse-marker" style="font-size: 36px; color: #dc3545; filter: drop-shadow(0 3px 6px rgba(0,0,0,0.4));"></i>',
|
||||
iconSize: [36, 36],
|
||||
iconAnchor: [18, 36]
|
||||
})
|
||||
}).addTo(pageMap);
|
||||
|
||||
// Add club markers
|
||||
if (allClubs.length > 0) {
|
||||
allClubs.forEach(club => {
|
||||
if (club.gps_lat && club.gps_long) {
|
||||
L.marker([club.gps_lat, club.gps_long]).addTo(pageMap)
|
||||
.bindPopup(`<b>${club.club_name}</b><br>${club.owner_name || 'N/A'}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
setTimeout(() => pageMap.invalidateSize(), 100);
|
||||
document.getElementById('mapSection').style.display = 'block';
|
||||
}
|
||||
|
||||
// Initialize map in modal
|
||||
function initMap(lat, lng) {
|
||||
if (map) {
|
||||
@ -406,7 +451,7 @@ function initMap(lat, lng) {
|
||||
|
||||
map = L.map('map', { attributionControl: false }).setView([lat, lng], 13);
|
||||
|
||||
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||
L.tileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||
attribution: '© OpenStreetMap contributors',
|
||||
maxZoom: 19
|
||||
}).addTo(map);
|
||||
@ -423,7 +468,7 @@ function initMap(lat, lng) {
|
||||
}).addTo(map);
|
||||
|
||||
// Drag event
|
||||
userMarker.on('dragend', function(event) {
|
||||
userMarker.on('drag', function(event) {
|
||||
const position = event.target.getLatLng();
|
||||
userLocation = {
|
||||
latitude: position.lat,
|
||||
@ -520,6 +565,24 @@ function displayClubs(clubs) {
|
||||
|
||||
noResultsContainer.style.display = 'none';
|
||||
|
||||
// Update map markers if page map exists
|
||||
if (pageMap) {
|
||||
// Clear existing club markers (assuming user marker is the first)
|
||||
pageMap.eachLayer(function(layer) {
|
||||
if (layer instanceof L.Marker && layer !== userMarker) {
|
||||
pageMap.removeLayer(layer);
|
||||
}
|
||||
});
|
||||
|
||||
// Add new club markers
|
||||
clubs.forEach(club => {
|
||||
if (club.gps_lat && club.gps_long) {
|
||||
L.marker([club.gps_lat, club.gps_long]).addTo(pageMap)
|
||||
.bindPopup(`<b>${club.club_name}</b><br>${club.owner_name || 'N/A'}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
clubs.forEach(club => {
|
||||
const card = document.createElement('div');
|
||||
card.className = 'col-md-6 col-lg-4';
|
||||
|
||||
@ -8,122 +8,7 @@
|
||||
|
||||
<!-- Family Members Card Grid -->
|
||||
<div class="row row-cols-1 row-cols-md-2 row-cols-lg-4 g-4 mb-5">
|
||||
<!-- Current User Card -->
|
||||
<div class="col">
|
||||
<a href="{{ route('profile.show') }}" class="text-decoration-none">
|
||||
<div class="card h-100 shadow-sm border overflow-hidden d-flex flex-column family-card">
|
||||
<!-- Header with gradient background -->
|
||||
<div class="p-4 pb-3" style="background: linear-gradient(135deg, {{ $user->gender == 'm' ? 'rgba(147, 51, 234, 0.1) 0%, rgba(147, 51, 234, 0.05) 50%' : 'rgba(214, 51, 132, 0.1) 0%, rgba(214, 51, 132, 0.05) 50%' }}, transparent 100%);">
|
||||
<div class="d-flex align-items-start gap-3">
|
||||
<div class="position-relative">
|
||||
<div class="rounded-circle border border-4 border-white shadow" style="width: 80px; height: 80px; overflow: hidden; box-shadow: 0 0 0 2px {{ $user->gender == 'm' ? 'rgba(147, 51, 234, 0.3)' : 'rgba(214, 51, 132, 0.3)' }} !important;">
|
||||
@if($user->media_gallery[0] ?? false)
|
||||
<img src="{{ $user->media_gallery[0] }}" alt="{{ $user->full_name }}" class="w-100 h-100" style="object-fit: cover;">
|
||||
@else
|
||||
<div class="w-100 h-100 d-flex align-items-center justify-content-center text-white fw-bold fs-4" style="background: linear-gradient(135deg, {{ $user->gender == 'm' ? '#8b5cf6 0%, #7c3aed 100%' : '#d63384 0%, #a61e4d 100%' }});">
|
||||
{{ strtoupper(substr($user->full_name, 0, 1)) }}
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-grow-1 min-w-0">
|
||||
<h5 class="fw-bold mb-2 text-truncate">{{ $user->full_name }}</h5>
|
||||
<div class="d-flex flex-wrap gap-2">
|
||||
<span class="badge {{ $user->gender == 'm' ? 'bg-primary' : 'bg-danger' }}">Adult</span>
|
||||
<span class="badge bg-success">Active</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Contact Info -->
|
||||
<div class="px-4 py-3 bg-light border-top border-bottom">
|
||||
<div class="d-flex align-items-center gap-2 small mb-2">
|
||||
<i class="bi bi-telephone-fill {{ $user->gender == 'm' ? 'text-primary' : 'text-danger' }}"></i>
|
||||
<span class="fw-medium text-muted">{{ $user->mobile_formatted ?: 'Not provided' }}</span>
|
||||
</div>
|
||||
@if($user->email)
|
||||
<div class="d-flex align-items-center gap-2 small">
|
||||
<i class="bi bi-envelope-fill {{ $user->gender == 'm' ? 'text-primary' : 'text-danger' }}"></i>
|
||||
<span class="fw-medium text-muted text-truncate">{{ $user->email }}</span>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<!-- Details -->
|
||||
<div class="px-4 py-3 flex-grow-1">
|
||||
<div class="row g-3 mb-3">
|
||||
<div class="col-6">
|
||||
<div class="small text-muted text-uppercase fw-medium mb-1" style="font-size: 0.7rem; letter-spacing: 0.5px;">Gender</div>
|
||||
<div class="fw-semibold text-muted text-capitalize">{{ $user->gender == 'm' ? 'Male' : 'Female' }}</div>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<div class="small text-muted text-uppercase fw-medium mb-1" style="font-size: 0.7rem; letter-spacing: 0.5px;">Age</div>
|
||||
<div class="fw-semibold text-muted">{{ $user->age }} years</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row g-3 mb-3">
|
||||
<div class="col-6">
|
||||
<div class="small text-muted text-uppercase fw-medium mb-1" style="font-size: 0.7rem; letter-spacing: 0.5px;">Nationality</div>
|
||||
<div class="fw-semibold text-muted fs-5 nationality-display" data-iso3="{{ $user->nationality }}">{{ $user->nationality }}</div>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<div class="small text-muted text-uppercase fw-medium mb-1" style="font-size: 0.7rem; letter-spacing: 0.5px;">Horoscope</div>
|
||||
<div class="fw-semibold text-muted">
|
||||
@php
|
||||
$horoscopeSymbols = [
|
||||
'Aries' => '♈',
|
||||
'Taurus' => '♉',
|
||||
'Gemini' => '♊',
|
||||
'Cancer' => '♋',
|
||||
'Leo' => '♌',
|
||||
'Virgo' => '♍',
|
||||
'Libra' => '♎',
|
||||
'Scorpio' => '♏',
|
||||
'Sagittarius' => '♐',
|
||||
'Capricorn' => '♑',
|
||||
'Aquarius' => '♒',
|
||||
'Pisces' => '♓'
|
||||
];
|
||||
$horoscope = $user->horoscope ?? 'N/A';
|
||||
$symbol = $horoscopeSymbols[$horoscope] ?? '';
|
||||
@endphp
|
||||
{{ $symbol }} {{ $horoscope }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pt-2 border-top">
|
||||
<div class="d-flex justify-content-between align-items-center small mb-2">
|
||||
<span class="text-muted fw-medium">Next Birthday</span>
|
||||
<span class="fw-semibold text-muted">
|
||||
@if($user->birthdate)
|
||||
{{ $user->birthdate->copy()->year(now()->year)->isFuture()
|
||||
? $user->birthdate->copy()->year(now()->year)->diffForHumans(['parts' => 2, 'syntax' => \Carbon\CarbonInterface::DIFF_ABSOLUTE])
|
||||
: $user->birthdate->copy()->year(now()->year + 1)->diffForHumans(['parts' => 2, 'syntax' => \Carbon\CarbonInterface::DIFF_ABSOLUTE]) }}
|
||||
@else
|
||||
N/A
|
||||
@endif
|
||||
</span>
|
||||
</div>
|
||||
<div class="d-flex justify-content-between align-items-center small">
|
||||
<span class="text-muted fw-medium">Member Since</span>
|
||||
<span class="fw-semibold text-muted">{{ $user->created_at->format('d/m/Y') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
<!-- Guardian/Sponsor Info - Footer -->
|
||||
<div class="px-4 py-2 {{ $user->gender == 'm' ? 'bg-primary' : 'bg-danger' }} bg-opacity-10 border-top">
|
||||
<div class="d-flex align-items-center justify-content-center small">
|
||||
<span class="fw-medium text-white">
|
||||
GUARDIAN
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Dependents Cards -->
|
||||
@foreach($dependents as $relationship)
|
||||
|
||||
@ -90,7 +90,7 @@
|
||||
<a href="#" class="border bg-white rounded px-2 py-1 text-decoration-none" style="font-size: 1rem;">🥇 <span class="fw-semibold text-dark">4</span></a>
|
||||
<a href="#" class="border bg-white rounded px-2 py-1 text-decoration-none" style="font-size: 1rem;">🥈 <span class="fw-semibold text-dark">6</span></a>
|
||||
<a href="#" class="border bg-white rounded px-2 py-1 text-decoration-none" style="font-size: 1rem;">🥉 <span class="fw-semibold text-dark">3</span></a>
|
||||
<a href="#" class="border bg-white rounded px-2 py-1 text-decoration-none" style="font-size: 1rem;">🎯 <span class="fw-semibold text-dark">8</span></a>
|
||||
<a href="#goals" class="border bg-white rounded px-2 py-1 text-decoration-none" style="font-size: 1rem;" onclick="document.getElementById('goals-tab').click();">🎯 <span class="fw-semibold text-dark">8</span></a>
|
||||
<a href="#" class="border bg-white rounded px-2 py-1 text-decoration-none" style="font-size: 1rem;">⭐ <span class="fw-semibold text-dark">12</span></a>
|
||||
</div>
|
||||
|
||||
@ -234,8 +234,8 @@
|
||||
</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link text-dark" id="achievements-tab" data-bs-toggle="tab" data-bs-target="#achievements" type="button" role="tab">
|
||||
<i class="bi bi-trophy me-2"></i>Achievements
|
||||
<button class="nav-link text-dark" id="affiliations-tab" data-bs-toggle="tab" data-bs-target="#affiliations" type="button" role="tab">
|
||||
<i class="bi bi-diagram-3 me-2"></i>Affiliations
|
||||
</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
@ -279,27 +279,12 @@
|
||||
<div class="progress" style="height: 4px; background-color: #e9ecef;">
|
||||
<div class="progress-bar" role="progressbar" style="width: 85%; background: linear-gradient(90deg, #6f42c1 0%, #8b5cf6 100%);"></div>
|
||||
</div>
|
||||
<small class="text-muted">Sessions completed this year</small>
|
||||
<small class="text-muted" style="font-size: 0.75rem;">Sessions completed this year</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Total Revenue -->
|
||||
<div class="col-md-6">
|
||||
<div class="d-flex align-items-center gap-3 p-3 bg-light rounded">
|
||||
<div class="rounded-circle d-flex align-items-center justify-content-center" style="width: 48px; height: 48px; background-color: #10b981;">
|
||||
<i class="bi bi-currency-dollar text-white"></i>
|
||||
</div>
|
||||
<div class="flex-grow-1">
|
||||
<div class="small text-muted mb-1">Total Revenue</div>
|
||||
<div class="h4 fw-bold mb-2">$4250</div>
|
||||
<div class="progress" style="height: 4px; background-color: #e9ecef;">
|
||||
<div class="progress-bar" role="progressbar" style="width: 70%; background: linear-gradient(90deg, #6f42c1 0%, #10b981 100%);"></div>
|
||||
</div>
|
||||
<small class="text-muted">Revenue generated this year</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Attendance Rate -->
|
||||
<div class="col-md-6">
|
||||
@ -313,27 +298,12 @@
|
||||
<div class="progress" style="height: 4px; background-color: #e9ecef;">
|
||||
<div class="progress-bar" role="progressbar" style="width: 85%; background: linear-gradient(90deg, #6f42c1 0%, #10b981 100%);"></div>
|
||||
</div>
|
||||
<small class="text-muted">Average session attendance</small>
|
||||
<small class="text-muted" style="font-size: 0.75rem;">Average session attendance</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Member Since -->
|
||||
<div class="col-md-6">
|
||||
<div class="d-flex align-items-center gap-3 p-3 bg-light rounded">
|
||||
<div class="rounded-circle d-flex align-items-center justify-content-center" style="width: 48px; height: 48px; background-color: #f59e0b;">
|
||||
<i class="bi bi-calendar-check text-white"></i>
|
||||
</div>
|
||||
<div class="flex-grow-1">
|
||||
<div class="small text-muted mb-1">Member Since</div>
|
||||
<div class="h4 fw-bold mb-2">1.5</div>
|
||||
<div class="progress" style="height: 4px; background-color: #e9ecef;">
|
||||
<div class="progress-bar" role="progressbar" style="width: 30%; background: linear-gradient(90deg, #6f42c1 0%, #10b981 100%);"></div>
|
||||
</div>
|
||||
<small class="text-muted">Years of membership</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Achievements -->
|
||||
<div class="col-md-6">
|
||||
@ -347,7 +317,7 @@
|
||||
<div class="progress" style="height: 4px; background-color: #e9ecef;">
|
||||
<div class="progress-bar" role="progressbar" style="width: 40%; background: linear-gradient(90deg, #6f42c1 0%, #10b981 100%);"></div>
|
||||
</div>
|
||||
<small class="text-muted">Total badges earned</small>
|
||||
<small class="text-muted" style="font-size: 0.75rem;">Total badges earned</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -364,7 +334,7 @@
|
||||
<div class="progress" style="height: 4px; background-color: #e9ecef;">
|
||||
<div class="progress-bar" role="progressbar" style="width: 75%; background: linear-gradient(90deg, #6f42c1 0%, #10b981 100%);"></div>
|
||||
</div>
|
||||
<small class="text-muted">Current goals achieved</small>
|
||||
<small class="text-muted" style="font-size: 0.75rem;">Current goals achieved</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -373,15 +343,15 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Revenue Chart -->
|
||||
<!-- Self Investment Chart -->
|
||||
<div class="col-lg-6">
|
||||
<div class="card shadow-sm border-0 h-100">
|
||||
<div class="card-body p-4">
|
||||
<div class="d-flex align-items-center mb-2">
|
||||
<i class="bi bi-bar-chart-line text-primary me-2"></i>
|
||||
<h5 class="mb-0 fw-bold">Revenue Chart</h5>
|
||||
<h5 class="mb-0 fw-bold">Self Investment Chart</h5>
|
||||
</div>
|
||||
<p class="text-muted small mb-4">Revenue analytics over time</p>
|
||||
<p class="text-muted small mb-4">Self investment analytics over time</p>
|
||||
|
||||
<div class="d-flex align-items-center justify-content-center" style="min-height: 300px;">
|
||||
<div class="text-center">
|
||||
@ -420,88 +390,34 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@forelse($invoices as $invoice)
|
||||
<tr>
|
||||
<td class="small">2023-12-15</td>
|
||||
<td class="small text-primary">Package Payment</td>
|
||||
<td class="small">Premium Fitness + Personal Training</td>
|
||||
<td class="small text-muted">2023-12-15 to 2024-06-15</td>
|
||||
<td class="small">18/24</td>
|
||||
<td class="small fw-semibold" style="color: #10b981;">649.5 BHD</td>
|
||||
<td><span class="badge bg-success-subtle text-success small">✓ Paid</span></td>
|
||||
<td class="small">Credit Card</td>
|
||||
<td class="small">{{ $invoice->created_at->format('Y-m-d') }}</td>
|
||||
<td class="small text-primary">Invoice</td>
|
||||
<td class="small">{{ $invoice->tenant->club_name ?? 'N/A' }}</td>
|
||||
<td class="small text-muted">-</td>
|
||||
<td class="small">-</td>
|
||||
<td class="small fw-semibold" style="color: {{ $invoice->status == 'paid' ? '#10b981' : '#f59e0b' }};">{{ $invoice->amount }} BHD</td>
|
||||
<td>
|
||||
@if($invoice->status == 'paid')
|
||||
<span class="badge bg-success-subtle text-success small">✓ Paid</span>
|
||||
@elseif($invoice->status == 'due')
|
||||
<span class="badge bg-warning-subtle text-warning small">○ Due</span>
|
||||
@else
|
||||
<span class="badge bg-secondary-subtle text-secondary small">{{ ucfirst($invoice->status) }}</span>
|
||||
@endif
|
||||
</td>
|
||||
<td class="small">-</td>
|
||||
<td class="small">
|
||||
<i class="bi bi-file-earmark-text text-primary"></i>
|
||||
<i class="bi bi-download text-secondary ms-1"></i>
|
||||
<a href="{{ route('bills.receipt', $invoice->id) }}" target="_blank" title="View Receipt"><i class="bi bi-file-earmark-text text-primary"></i></a>
|
||||
<a href="{{ route('bills.receipt', $invoice->id) }}?download=1" download title="Download Receipt"><i class="bi bi-download text-secondary ms-1"></i></a>
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr>
|
||||
<td class="small">2024-02-15</td>
|
||||
<td class="small text-primary">Package Payment</td>
|
||||
<td class="small">Premium Fitness + Personal Training</td>
|
||||
<td class="small text-muted">2023-12-15 to 2024-06-15</td>
|
||||
<td class="small">18/24</td>
|
||||
<td class="small fw-semibold" style="color: #f59e0b;">649.5 BHD</td>
|
||||
<td><span class="badge bg-warning-subtle text-warning small">○ Due</span></td>
|
||||
<td class="small">Auto-pay</td>
|
||||
<td class="small">-</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="small">2023-06-15</td>
|
||||
<td class="small text-primary">Package Payment</td>
|
||||
<td class="small">Basic Gym Membership</td>
|
||||
<td class="small text-muted">2023-06-15 to 2023-12-15</td>
|
||||
<td class="small">Unlimited</td>
|
||||
<td class="small fw-semibold" style="color: #10b981;">599 BHD</td>
|
||||
<td><span class="badge bg-success-subtle text-success small">✓ Paid</span></td>
|
||||
<td class="small">Bank Transfer</td>
|
||||
<td class="small">
|
||||
<i class="bi bi-file-earmark-text text-primary"></i>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="small">2024-02-01</td>
|
||||
<td class="small text-primary">Package Payment</td>
|
||||
<td class="small">Nutrition Consultation Package</td>
|
||||
<td class="small text-muted">2024-02-01 to 2024-05-01</td>
|
||||
<td class="small">0/6</td>
|
||||
<td class="small fw-semibold" style="color: #f59e0b;">450 BHD</td>
|
||||
<td><span class="badge bg-warning-subtle text-warning small">○ Due</span></td>
|
||||
<td class="small">Credit Card</td>
|
||||
<td class="small">-</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="small">2024-01-08</td>
|
||||
<td class="small text-secondary">Service/Product</td>
|
||||
<td class="small">Personal Training Session - Paid</td>
|
||||
<td class="small">-</td>
|
||||
<td class="small">-</td>
|
||||
<td class="small fw-semibold" style="color: #10b981;">75 BHD</td>
|
||||
<td><span class="badge bg-success-subtle text-success small">✓ Paid</span></td>
|
||||
<td class="small">Credit Card</td>
|
||||
<td class="small">-</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="small">2024-01-05</td>
|
||||
<td class="small text-secondary">Service/Product</td>
|
||||
<td class="small">Protein Supplement - Paid</td>
|
||||
<td class="small">-</td>
|
||||
<td class="small">-</td>
|
||||
<td class="small fw-semibold" style="color: #10b981;">45 BHD</td>
|
||||
<td><span class="badge bg-success-subtle text-success small">✓ Paid</span></td>
|
||||
<td class="small">Cash</td>
|
||||
<td class="small">-</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="small">2024-01-01</td>
|
||||
<td class="small text-secondary">Service/Product</td>
|
||||
<td class="small">Monthly Membership - Due</td>
|
||||
<td class="small">-</td>
|
||||
<td class="small">-</td>
|
||||
<td class="small fw-semibold" style="color: #10b981;">99 BHD</td>
|
||||
<td><span class="badge bg-success-subtle text-success small">✓ Paid</span></td>
|
||||
<td class="small">Auto-pay</td>
|
||||
<td class="small">-</td>
|
||||
<td colspan="9" class="text-center text-muted small">No invoices found</td>
|
||||
</tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@ -515,7 +431,7 @@
|
||||
<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">Attendance Records</h5>
|
||||
<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>
|
||||
</div>
|
||||
@ -644,12 +560,10 @@
|
||||
<div class="col-lg-7">
|
||||
<div class="card shadow-sm border-0 h-100">
|
||||
<div class="card-body p-4">
|
||||
<h5 class="fw-bold mb-4">Body Composition Analysis</h5>
|
||||
<h5 class="fw-bold mb-4"><i class="bi bi-activity me-2"></i>Body Composition Analysis</h5>
|
||||
|
||||
<div class="text-center py-5">
|
||||
<i class="bi bi-radar text-muted" style="font-size: 3rem;"></i>
|
||||
<p class="text-muted mt-3 mb-1">Radar chart visualization coming soon...</p>
|
||||
<small class="text-muted">Chart will compare current vs previous body composition metrics</small>
|
||||
<div class="chart-container" style="position: relative; height: 500px; width: 100%;">
|
||||
<canvas id="radarChart" data-current="@json($comparisonRecords->first())" data-previous="@json($comparisonRecords->skip(1)->first())"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -659,7 +573,7 @@
|
||||
<div class="col-lg-5">
|
||||
<div class="card shadow-sm border-0 h-100">
|
||||
<div class="card-body p-4">
|
||||
<h5 class="fw-bold mb-4">Compare</h5>
|
||||
<h5 class="fw-bold mb-4"><i class="bi bi-bar-chart-line me-2"></i>Compare</h5>
|
||||
|
||||
@if($comparisonRecords->count() >= 2)
|
||||
@php
|
||||
@ -707,8 +621,8 @@
|
||||
<tr class="border-bottom">
|
||||
<th class="text-muted small fw-semibold">Metric</th>
|
||||
<th class="text-muted small fw-semibold text-end">Current</th>
|
||||
<th class="text-muted small fw-semibold text-center">Change</th>
|
||||
<th class="text-muted small fw-semibold text-end">Previous</th>
|
||||
<th class="text-muted small fw-semibold text-center">Change</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@ -722,62 +636,62 @@
|
||||
<tr data-metric="weight">
|
||||
<td class="small"><i class="bi bi-speedometer2 me-2"></i>Weight</td>
|
||||
<td class="small text-end fw-semibold">{{ $current->weight ?? 'N/A' }}kg</td>
|
||||
<td class="text-center">{!! $current->weight && $previous->weight ? getChangeIcon($current->weight, $previous->weight) : '-' !!}</td>
|
||||
<td class="small text-end text-muted">{{ $previous->weight ?? 'N/A' }}kg</td>
|
||||
<td class="text-center">{!! $current->weight && $previous->weight ? getChangeIcon($current->weight, $previous->weight) : '-' !!}</td>
|
||||
</tr>
|
||||
<tr data-metric="body_fat">
|
||||
<td class="small"><i class="bi bi-activity me-2"></i>Body Fat</td>
|
||||
<td class="small text-end fw-semibold">{{ $current->body_fat_percentage ?? 'N/A' }}%</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="small text-end text-muted">{{ $previous->body_fat_percentage ?? 'N/A' }}%</td>
|
||||
<td class="text-center">{!! $current->body_fat_percentage && $previous->body_fat_percentage ? getChangeIcon($current->body_fat_percentage, $previous->body_fat_percentage) : '-' !!}</td>
|
||||
</tr>
|
||||
<tr data-metric="bmi">
|
||||
<td class="small"><i class="bi bi-calculator me-2"></i>BMI</td>
|
||||
<td class="small text-end fw-semibold">{{ $current->bmi ?? 'N/A' }}</td>
|
||||
<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>
|
||||
<td class="text-center">{!! $current->bmi && $previous->bmi ? getChangeIcon($current->bmi, $previous->bmi) : '-' !!}</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>
|
||||
<td class="text-center">{!! $current->body_water_percentage && $previous->body_water_percentage ? getChangeIcon($current->body_water_percentage, $previous->body_water_percentage) : '-' !!}</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>
|
||||
<td class="text-center">{!! $current->muscle_mass && $previous->muscle_mass ? getChangeIcon($current->muscle_mass, $previous->muscle_mass) : '-' !!}</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>
|
||||
<td class="text-center">{!! $current->bone_mass && $previous->bone_mass ? getChangeIcon($current->bone_mass, $previous->bone_mass) : '-' !!}</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>
|
||||
<td class="text-center">{!! $current->visceral_fat && $previous->visceral_fat ? getChangeIcon($current->visceral_fat, $previous->visceral_fat) : '-' !!}</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>
|
||||
<td class="text-center">{!! $current->bmr && $previous->bmr ? getChangeIcon($current->bmr, $previous->bmr) : '-' !!}</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>
|
||||
<td class="text-center">{!! $current->protein_percentage && $previous->protein_percentage ? getChangeIcon($current->protein_percentage, $previous->protein_percentage) : '-' !!}</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>
|
||||
<td class="text-center">{!! $current->body_age && $previous->body_age ? getChangeIcon($current->body_age, $previous->body_age) : '-' !!}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
@ -796,7 +710,7 @@
|
||||
<!-- Health Tracking History -->
|
||||
<div class="card shadow-sm border-0">
|
||||
<div class="card-body p-4">
|
||||
<h5 class="fw-bold mb-4">Health Tracking History</h5>
|
||||
<h5 class="fw-bold mb-4"><i class="bi bi-heart-pulse me-2"></i>Health Tracking</h5>
|
||||
|
||||
@if($healthRecords->count() > 0)
|
||||
<div class="table-responsive">
|
||||
@ -858,18 +772,18 @@
|
||||
<div class="tab-pane fade" id="goals" role="tabpanel">
|
||||
<div class="card shadow-sm border-0">
|
||||
<div class="card-body p-4">
|
||||
<h5 class="fw-bold mb-3">Goals & Progress</h5>
|
||||
<h5 class="fw-bold mb-3"><i class="bi bi-bullseye me-2"></i>Goals & Progress</h5>
|
||||
<p class="text-muted">Goal tracking coming soon...</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Achievements Tab -->
|
||||
<div class="tab-pane fade" id="achievements" role="tabpanel">
|
||||
<!-- Affiliations Tab -->
|
||||
<div class="tab-pane fade" id="affiliations" role="tabpanel">
|
||||
<div class="card shadow-sm border-0">
|
||||
<div class="card-body p-4">
|
||||
<h5 class="fw-bold mb-3">Achievements & Badges</h5>
|
||||
<p class="text-muted">Achievement system coming soon...</p>
|
||||
<h5 class="fw-bold mb-3"><i class="bi bi-diagram-3 me-2"></i>Affiliations & Badges</h5>
|
||||
<p class="text-muted">Affiliation system coming soon...</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -878,7 +792,7 @@
|
||||
<div class="tab-pane fade" id="tournaments" role="tabpanel">
|
||||
<div class="card shadow-sm border-0">
|
||||
<div class="card-body p-4">
|
||||
<h5 class="fw-bold mb-3">Tournament History</h5>
|
||||
<h5 class="fw-bold mb-3"><i class="bi bi-award me-2"></i>Tournament History</h5>
|
||||
<p class="text-muted">Tournament records coming soon...</p>
|
||||
</div>
|
||||
</div>
|
||||
@ -888,7 +802,7 @@
|
||||
<div class="tab-pane fade" id="events" role="tabpanel">
|
||||
<div class="card shadow-sm border-0">
|
||||
<div class="card-body p-4">
|
||||
<h5 class="fw-bold mb-3">Event Participation</h5>
|
||||
<h5 class="fw-bold mb-3"><i class="bi bi-calendar-event me-2"></i>Event Participation</h5>
|
||||
<p class="text-muted">Event history coming soon...</p>
|
||||
</div>
|
||||
</div>
|
||||
@ -1026,6 +940,86 @@
|
||||
// Store health records data for dynamic comparison
|
||||
const healthRecordsData = @json($healthRecords->items());
|
||||
|
||||
// Radar chart variables
|
||||
let radarChart = null;
|
||||
const metricLabels = ['Weight', 'Body Fat', 'BMI', 'Body Water', 'Muscle Mass', 'Bone Mass', 'Visceral Fat', 'BMR', 'Protein', 'Body Age'];
|
||||
const metricKeys = ['weight', 'body_fat_percentage', 'bmi', 'body_water_percentage', 'muscle_mass', 'bone_mass', 'visceral_fat', 'bmr', 'protein_percentage', 'body_age'];
|
||||
|
||||
// Function to create/update radar chart
|
||||
function updateRadarChart(currentRecord, previousRecord) {
|
||||
console.log('updateRadarChart called', currentRecord, previousRecord);
|
||||
const ctx = document.getElementById('radarChart').getContext('2d');
|
||||
console.log('ctx', ctx);
|
||||
|
||||
const currentData = metricKeys.map(key => currentRecord ? (currentRecord[key] || 0) : 0);
|
||||
const previousData = metricKeys.map(key => previousRecord ? (previousRecord[key] || 0) : 0);
|
||||
console.log('currentData', currentData);
|
||||
console.log('previousData', previousData);
|
||||
console.log('window.Chart', window.Chart);
|
||||
|
||||
if (radarChart) {
|
||||
radarChart.destroy();
|
||||
}
|
||||
|
||||
radarChart = new window.Chart(ctx, {
|
||||
type: 'radar',
|
||||
data: {
|
||||
labels: metricLabels,
|
||||
datasets: [
|
||||
{
|
||||
label: 'From Record',
|
||||
data: previousData,
|
||||
borderColor: 'rgba(54, 162, 235, 1)',
|
||||
backgroundColor: 'rgba(54, 162, 235, 0.2)',
|
||||
pointBackgroundColor: 'rgba(54, 162, 235, 1)',
|
||||
pointBorderColor: '#fff',
|
||||
pointHoverBackgroundColor: '#fff',
|
||||
pointHoverBorderColor: 'rgba(54, 162, 235, 1)',
|
||||
},
|
||||
{
|
||||
label: 'To Record',
|
||||
data: currentData,
|
||||
borderColor: 'rgba(255, 99, 132, 1)',
|
||||
backgroundColor: 'rgba(255, 99, 132, 0.2)',
|
||||
pointBackgroundColor: 'rgba(255, 99, 132, 1)',
|
||||
pointBorderColor: '#fff',
|
||||
pointHoverBackgroundColor: '#fff',
|
||||
pointHoverBorderColor: 'rgba(255, 99, 132, 1)',
|
||||
}
|
||||
]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
legend: {
|
||||
position: 'top',
|
||||
},
|
||||
tooltip: {
|
||||
callbacks: {
|
||||
label: function(context) {
|
||||
let label = context.dataset.label || '';
|
||||
if (label) {
|
||||
label += ': ';
|
||||
}
|
||||
label += context.parsed.r;
|
||||
return label;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
scales: {
|
||||
r: {
|
||||
beginAtZero: true,
|
||||
ticks: {
|
||||
stepSize: 10
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Function to reset modal for adding new record
|
||||
function resetHealthModal() {
|
||||
document.getElementById('healthUpdateModalLabel').textContent = 'Add Health Update';
|
||||
@ -1115,6 +1109,9 @@
|
||||
updateTableRow('bmr', currentRecord.bmr, previousRecord.bmr);
|
||||
updateTableRow('protein', currentRecord.protein_percentage, previousRecord.protein_percentage);
|
||||
updateTableRow('body_age', currentRecord.body_age, previousRecord.body_age);
|
||||
|
||||
// Update the radar chart
|
||||
updateRadarChart(currentRecord, previousRecord);
|
||||
}
|
||||
|
||||
function calculateTimeDifference(date1, date2) {
|
||||
@ -1155,19 +1152,19 @@
|
||||
|
||||
// Update previous value
|
||||
if (metric === 'weight' || metric === 'muscle_mass' || metric === 'bone_mass') {
|
||||
cells[3].textContent = previousValue ? `${previousValue}kg` : 'N/A';
|
||||
cells[2].textContent = previousValue ? `${previousValue}kg` : 'N/A';
|
||||
} else if (metric === 'body_fat' || metric === 'body_water' || metric === 'protein') {
|
||||
cells[3].textContent = previousValue ? `${previousValue}%` : 'N/A';
|
||||
cells[2].textContent = previousValue ? `${previousValue}%` : 'N/A';
|
||||
} else if (metric === 'bmr') {
|
||||
cells[3].textContent = previousValue ? `${previousValue}cal` : 'N/A';
|
||||
cells[2].textContent = previousValue ? `${previousValue}cal` : 'N/A';
|
||||
} else if (metric === 'body_age') {
|
||||
cells[3].textContent = previousValue ? `${previousValue}yrs` : 'N/A';
|
||||
cells[2].textContent = previousValue ? `${previousValue}yrs` : 'N/A';
|
||||
} else {
|
||||
cells[3].textContent = previousValue || 'N/A';
|
||||
cells[2].textContent = previousValue || 'N/A';
|
||||
}
|
||||
|
||||
// Update change cell
|
||||
const changeCell = cells[2];
|
||||
const changeCell = cells[3];
|
||||
if (currentValue && previousValue) {
|
||||
const change = currentValue - previousValue;
|
||||
let arrow = '';
|
||||
@ -1195,7 +1192,25 @@
|
||||
|
||||
currentDateSelect.addEventListener('change', updateComparisonTable);
|
||||
previousDateSelect.addEventListener('change', updateComparisonTable);
|
||||
|
||||
// Initialize chart with default values
|
||||
updateComparisonTable();
|
||||
}
|
||||
|
||||
// Initialize radar chart with data from data attributes after a delay to ensure tab is shown
|
||||
setTimeout(() => {
|
||||
const radarCanvas = document.getElementById('radarChart');
|
||||
if (radarCanvas) {
|
||||
const currentData = radarCanvas.dataset.current ? JSON.parse(radarCanvas.dataset.current) : null;
|
||||
const previousData = radarCanvas.dataset.previous ? JSON.parse(radarCanvas.dataset.previous) : null;
|
||||
if (currentData && previousData) {
|
||||
updateRadarChart(currentData, previousData);
|
||||
} else {
|
||||
// Initialize with empty chart or message
|
||||
updateRadarChart(null, null);
|
||||
}
|
||||
}
|
||||
}, 1000);
|
||||
});
|
||||
</script>
|
||||
@endsection
|
||||
|
||||
@ -74,7 +74,7 @@ Route::post('/email/verification-notification', function (Request $request) {
|
||||
})->middleware(['auth', 'throttle:6,1'])->name('verification.send');
|
||||
|
||||
// Explore routes (accessible to authenticated users)
|
||||
Route::middleware(['auth', 'verified'])->group(function () {
|
||||
Route::middleware(['auth'])->group(function () {
|
||||
Route::get('/explore', [ClubController::class, 'index'])->name('clubs.explore');
|
||||
Route::get('/clubs/nearby', [ClubController::class, 'nearby'])->name('clubs.nearby');
|
||||
Route::get('/clubs/all', [ClubController::class, 'all'])->name('clubs.all');
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user