From b0c49bc6e401e2ebf39ad333449ad09f2743d542 Mon Sep 17 00:00:00 2001 From: Ghassan Yusuf Date: Thu, 22 Jan 2026 17:50:19 +0300 Subject: [PATCH] latest updates --- TODO.md | 44 +-- app/Http/Controllers/FamilyController.php | 11 +- app/Http/Controllers/InvoiceController.php | 11 +- app/Models/User.php | 3 +- database/seeders/DatabaseSeeder.php | 3 + package-lock.json | 40 ++- package.json | 1 + resources/js/app.js | 2 + resources/views/clubs/explore.blade.php | 83 +++++- resources/views/family/dashboard.blade.php | 115 -------- resources/views/family/show.blade.php | 315 +++++++++++---------- routes/web.php | 2 +- 12 files changed, 312 insertions(+), 318 deletions(-) diff --git a/TODO.md b/TODO.md index ffc1eea..a46d586 100644 --- a/TODO.md +++ b/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 diff --git a/app/Http/Controllers/FamilyController.php b/app/Http/Controllers/FamilyController.php index e606afc..69647b2 100644 --- a/app/Http/Controllers/FamilyController.php +++ b/app/Http/Controllers/FamilyController.php @@ -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')); } /** diff --git a/app/Http/Controllers/InvoiceController.php b/app/Http/Controllers/InvoiceController.php index 4bf9dab..778e39b 100644 --- a/app/Http/Controllers/InvoiceController.php +++ b/app/Http/Controllers/InvoiceController.php @@ -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')); } diff --git a/app/Models/User.php b/app/Models/User.php index da9186d..06232b0 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -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. diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php index b28fd5a..df2f429 100644 --- a/database/seeders/DatabaseSeeder.php +++ b/database/seeders/DatabaseSeeder.php @@ -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 diff --git a/package-lock.json b/package-lock.json index 506d85b..1be44d8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index d6c4beb..c242851 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "vite": "^7.0.7" }, "dependencies": { + "chart.js": "^4.5.1", "jquery-cropbox": "github:acornejo/jquery-cropbox" } } diff --git a/resources/js/app.js b/resources/js/app.js index cbc6d56..8857701 100644 --- a/resources/js/app.js +++ b/resources/js/app.js @@ -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; diff --git a/resources/views/clubs/explore.blade.php b/resources/views/clubs/explore.blade.php index 114f7ba..901cf17 100644 --- a/resources/views/clubs/explore.blade.php +++ b/resources/views/clubs/explore.blade.php @@ -97,6 +97,8 @@ + + @@ -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) { `${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: '', + 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(`${club.club_name}
${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(`${club.club_name}
${club.owner_name || 'N/A'}`); + } + }); + } + clubs.forEach(club => { const card = document.createElement('div'); card.className = 'col-md-6 col-lg-4'; diff --git a/resources/views/family/dashboard.blade.php b/resources/views/family/dashboard.blade.php index 591a46d..6e4d597 100644 --- a/resources/views/family/dashboard.blade.php +++ b/resources/views/family/dashboard.blade.php @@ -8,122 +8,7 @@
- - @foreach($dependents as $relationship) diff --git a/resources/views/family/show.blade.php b/resources/views/family/show.blade.php index c6cad36..0abc067 100644 --- a/resources/views/family/show.blade.php +++ b/resources/views/family/show.blade.php @@ -90,7 +90,7 @@ 🥇 4 🥈 6 🥉 3 - 🎯 8 + 🎯 8 12
@@ -234,8 +234,8 @@