diff --git a/TODO_goals.md b/TODO_goals.md new file mode 100644 index 0000000..923fdfd --- /dev/null +++ b/TODO_goals.md @@ -0,0 +1,58 @@ +# Goal Tracking Implementation - Completed Tasks + +## โ Completed +- [x] Created Goal model with all required fields +- [x] Created migration for goals table +- [x] Added relationships between User and Goal models +- [x] Updated FamilyController to fetch goals data for both profile() and show($id) methods +- [x] Implemented UI for goals tab with: + - Section title and subtitle + - Summary cards (Active Goals, Completed Goals, Success Rate) + - Current goals list with progress bars, dates, and status badges + - **Wrapped entire content in a card container** (consistent with other tabs) +- [x] Created GoalSeeder with sample data +- [x] Ran migration and seeder successfully + +## ๐ฏ Features Implemented +- **Backend Models**: Goal model with fields for title, description, dates, progress values, status, priority, unit, icon_type +- **Database**: goals table with proper relationships and constraints +- **Controller Logic**: Fetches goals, calculates active/completed counts and success rate +- **UI Components**: + - Summary cards with gradients and icons + - Goal cards with progress indicators (purple to green gradient) + - Status badges (Active/Completed, High/Medium/Low priority) + - Responsive design for different screen sizes + - **Interactive filtering**: Click summary cards to filter goals by status + - **Visual feedback**: Active filter highlighted with border and shadow + - **Title click**: Click title to show all goals + - **Edit functionality**: Circle edit button on active goal cards (only for profile owner/guardian) + - **Edit modal**: Modal with progress input, status selector, and live progress preview + - **Consistent card layout**: Both Goals and Tournaments sections wrapped in cards for uniform design +- **Sample Data**: 4 sample goals per user (Weight Loss, Bench Press, 5K Running, Daily Steps) +- **Auto-calculated Success Rate**: (Completed Goals / Total Goals) * 100 +- **Authorization**: Edit buttons only appear for profile owners and guardians +- **AJAX Updates**: Goals update via AJAX without page refresh + +## ๐ Next Steps (Optional Enhancements) +- [ ] Add goal creation functionality +- [ ] Add goal deletion functionality +- [ ] Implement progress history tracking +- [ ] Add goal categories or types +- [ ] Create ProgressHistory model for tracking progress over time +- [ ] Add notifications for goal deadlines or achievements +- [ ] Implement goal sharing between family members +- [ ] Add charts/visualizations for goal progress trends + +## ๐งช Testing +- Migration: โ Created and ran successfully +- Seeder: โ Populated sample data +- Routes: โ Profile route exists +- Models: โ Relationships and accessors working +- Views: โ Blade templates updated with goals data + +## ๐ Notes +- Goals are user-specific (each user has their own goals) +- Progress percentage calculated automatically in Goal model +- UI uses Bootstrap classes and custom gradients +- Icons use Bootstrap Icons (bi-bullseye, bi-dumbbell, bi-clock) +- Responsive grid layout (col-lg-6 for goal cards) diff --git a/TODO_height_bmi.md b/TODO_height_bmi.md new file mode 100644 index 0000000..e3f9aa6 --- /dev/null +++ b/TODO_height_bmi.md @@ -0,0 +1,29 @@ +# TODO: Add Height Field to Health Records with Auto BMI Calculation + +## Task +Add height field to health record modals (add and edit/update) with auto BMI calculation when height is set. BMI calculation: weight(kg) / (height(m)^2). If height not set, BMI not calculated. + +## Current State +- health_records table needs height column +- modals in resources/views/family/show.blade.php need height input and BMI auto-calc JS + +## Required Changes +1. โ Database Migration: Add height column to health_records table +2. โ Model Updates: Add height to fillable and casts in HealthRecord model +3. โ Controller Validation: Add height validation to storeHealth and updateHealth methods +4. โ View Updates: Add height input to add/edit modals, update table headers and rows +5. โ JavaScript Updates: Add BMI auto-calculation, update modal reset/populate functions, update comparison table and radar chart +6. โ Test the functionality + +## Files Modified +- database/migrations/2026_01_24_084323_add_height_to_health_records_table.php +- app/Models/HealthRecord.php +- app/Http/Controllers/FamilyController.php +- resources/views/family/show.blade.php + +## Followup Steps +- Run the application and navigate to /profile +- Switch to Health tab +- Test adding/editing health records with height and weight +- Verify BMI auto-calculates correctly +- Check comparison table and radar chart include height diff --git a/TODO_radar_chart.md b/TODO_radar_chart.md new file mode 100644 index 0000000..e5f6ff3 --- /dev/null +++ b/TODO_radar_chart.md @@ -0,0 +1,32 @@ +# TODO: Implement Chart.js Radar Area Chart for Body Composition Analysis + +## Task +Update the radar chart in the Body Composition Analysis section of the profile page (http://localhost:8000/profile) to match the Chart.js v4 sample from https://www.chartjs.org/docs/latest/samples/area/radar.html + +## Current State +- Chart.js v4.5.1 is installed and imported +- A radar chart is already implemented in resources/views/family/show.blade.php +- The chart compares current and previous health records for body composition metrics + +## Required Changes +1. โ Update the `updateRadarChart` function in resources/views/family/show.blade.php to match the sample: + - โ Add `fill: true` to both datasets + - โ Add `elements: { line: { borderWidth: 3 } }` to options + - โ Ensure proper Chart.js v4 syntax + +2. โ Test the chart functionality: + - Server started on http://127.0.0.1:8000 + - Navigate to /profile and switch to Health tab to verify chart + - Test dropdown selections for data comparison + - Check responsive behavior + +## Files to Edit +- โ resources/views/family/show.blade.php (updateRadarChart function) +- โ resources/views/layouts/app.blade.php (add Vite directive) +- โ Built Vite assets with Chart.js + +## Followup Steps +- โ Run the application and navigate to /profile +- โ Switch to Health tab +- โ Verify the radar chart displays correctly with area fill (added dummy data for testing) +- โ Test changing comparison records via dropdowns (may need real data for this) diff --git a/TODO_tournaments.md b/TODO_tournaments.md new file mode 100644 index 0000000..b51a246 --- /dev/null +++ b/TODO_tournaments.md @@ -0,0 +1,28 @@ +# Tournament Tab Implementation + +## Completed Tasks +- [x] Create migrations for tournament_events, performance_results, notes_media tables +- [x] Create TournamentEvent, PerformanceResult, NotesMedia models with relationships +- [x] Add tournamentEvents relationship to User model +- [x] Update FamilyController to fetch tournament data and calculate award counts +- [x] Update profile view with tournaments tab UI including: + - Section title with trophy icon and subtitle + - Filter dropdown for sports + - Award summary cards (Special, 1st, 2nd, 3rd place) + - Tournament history table with 3 columns (Details, Performance, Notes/Media) +- [x] Add JavaScript for filtering by sport and updating award counts +- [x] Create TournamentSeeder with sample data +- [x] Run migrations and seeder + +## Features Implemented +- Backend models with proper relationships +- Award count calculation from performance results +- Dynamic filtering by sport with JS +- Responsive UI with Bootstrap styling +- Icons for medals and awards +- Table displaying tournament details, results, and media links + +## Testing +- Server running on http://0.0.0.0:8000 +- Sample data seeded +- Profile page at /profile should show tournaments tab diff --git a/app/Http/Controllers/FamilyController.php b/app/Http/Controllers/FamilyController.php index 69647b2..5324dbd 100644 --- a/app/Http/Controllers/FamilyController.php +++ b/app/Http/Controllers/FamilyController.php @@ -6,6 +6,8 @@ use App\Models\User; use App\Models\UserRelationship; use App\Models\HealthRecord; use App\Models\Invoice; +use App\Models\TournamentEvent; +use App\Models\Goal; use App\Services\FamilyService; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; @@ -55,6 +57,29 @@ class FamilyController extends Controller // Fetch invoices $invoices = Invoice::where('student_user_id', $user->id)->orWhere('payer_user_id', $user->id)->with(['student', 'tenant'])->get(); + // Fetch tournament data + $tournamentEvents = $user->tournamentEvents() + ->with(['performanceResults', 'notesMedia']) + ->orderBy('date', 'desc') + ->get(); + + // Calculate award counts + $awardCounts = [ + 'special' => $tournamentEvents->flatMap->performanceResults->where('medal_type', 'special')->count(), + '1st' => $tournamentEvents->flatMap->performanceResults->where('medal_type', '1st')->count(), + '2nd' => $tournamentEvents->flatMap->performanceResults->where('medal_type', '2nd')->count(), + '3rd' => $tournamentEvents->flatMap->performanceResults->where('medal_type', '3rd')->count(), + ]; + + // Get unique sports for filter + $sports = $tournamentEvents->pluck('sport')->unique()->sort()->values(); + + // Fetch goals data + $goals = $user->goals()->orderBy('created_at', 'desc')->get(); + $activeGoalsCount = $goals->where('status', 'active')->count(); + $completedGoalsCount = $goals->where('status', 'completed')->count(); + $successRate = $goals->count() > 0 ? round(($completedGoalsCount / $goals->count()) * 100) : 0; + // Pass user directly and a flag to indicate it's the current user's profile return view('family.show', [ 'relationship' => (object)[ @@ -67,6 +92,13 @@ class FamilyController extends Controller 'healthRecords' => $healthRecords, 'comparisonRecords' => $comparisonRecords, 'invoices' => $invoices, + 'tournamentEvents' => $tournamentEvents, + 'awardCounts' => $awardCounts, + 'sports' => $sports, + 'goals' => $goals, + 'activeGoalsCount' => $activeGoalsCount, + 'completedGoalsCount' => $completedGoalsCount, + 'successRate' => $successRate, ]); } @@ -234,7 +266,30 @@ class FamilyController extends Controller // 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')); + // Fetch tournament data for the dependent + $tournamentEvents = $relationship->dependent->tournamentEvents() + ->with(['performanceResults', 'notesMedia']) + ->orderBy('date', 'desc') + ->get(); + + // Calculate award counts + $awardCounts = [ + 'special' => $tournamentEvents->flatMap->performanceResults->where('medal_type', 'special')->count(), + '1st' => $tournamentEvents->flatMap->performanceResults->where('medal_type', '1st')->count(), + '2nd' => $tournamentEvents->flatMap->performanceResults->where('medal_type', '2nd')->count(), + '3rd' => $tournamentEvents->flatMap->performanceResults->where('medal_type', '3rd')->count(), + ]; + + // Get unique sports for filter + $sports = $tournamentEvents->pluck('sport')->unique()->sort()->values(); + + // Fetch goals data for the dependent + $goals = $relationship->dependent->goals()->orderBy('created_at', 'desc')->get(); + $activeGoalsCount = $goals->where('status', 'active')->count(); + $completedGoalsCount = $goals->where('status', 'completed')->count(); + $successRate = $goals->count() > 0 ? round(($completedGoalsCount / $goals->count()) * 100) : 0; + + return view('family.show', compact('relationship', 'latestHealthRecord', 'healthRecords', 'comparisonRecords', 'invoices', 'tournamentEvents', 'awardCounts', 'sports', 'goals', 'activeGoalsCount', 'completedGoalsCount', 'successRate')); } /** @@ -386,6 +441,7 @@ class FamilyController extends Controller { $validated = $request->validate([ 'recorded_at' => 'required|date', + 'height' => 'nullable|numeric|min:50|max:250', 'weight' => 'nullable|numeric|min:0|max:999.9', 'body_fat_percentage' => 'nullable|numeric|min:0|max:100', 'bmi' => 'nullable|numeric|min:0|max:100', @@ -456,6 +512,7 @@ class FamilyController extends Controller { $validated = $request->validate([ 'recorded_at' => 'required|date', + 'height' => 'nullable|numeric|min:50|max:250', 'weight' => 'nullable|numeric|min:0|max:999.9', 'body_fat_percentage' => 'nullable|numeric|min:0|max:100', 'bmi' => 'nullable|numeric|min:0|max:100', @@ -520,6 +577,44 @@ class FamilyController extends Controller ->with('success', 'Health record updated successfully.'); } + /** + * Update the specified goal. + * + * @param \Illuminate\Http\Request $request + * @param int $goalId + * @return \Illuminate\Http\JsonResponse + */ + public function updateGoal(Request $request, $goalId) + { + $user = Auth::user(); + + // Find the goal + $goal = Goal::findOrFail($goalId); + + // Check if user is authorized to update this goal + if ($goal->user_id !== $user->id) { + // Check if user is guardian of the goal owner + $relationship = UserRelationship::where('guardian_user_id', $user->id) + ->where('dependent_user_id', $goal->user_id) + ->first(); + + if (!$relationship) { + return response()->json(['success' => false, 'message' => 'Unauthorized'], 403); + } + } + + // Validate the request + $validated = $request->validate([ + 'current_progress_value' => 'required|numeric|min:0', + 'status' => 'required|in:active,completed', + ]); + + // Update the goal + $goal->update($validated); + + return response()->json(['success' => true, 'message' => 'Goal updated successfully']); + } + /** * Remove the specified family member from storage. * diff --git a/app/Models/Goal.php b/app/Models/Goal.php new file mode 100644 index 0000000..492997c --- /dev/null +++ b/app/Models/Goal.php @@ -0,0 +1,49 @@ + 'date', + 'target_date' => 'date', + 'current_progress_value' => 'decimal:2', + 'target_value' => 'decimal:2', + ]; + + public function user(): BelongsTo + { + return $this->belongsTo(User::class); + } + + public function getProgressPercentageAttribute(): float + { + if ($this->target_value == 0) { + return 0; + } + + return min(100, ($this->current_progress_value / $this->target_value) * 100); + } + + public function getIsCompletedAttribute(): bool + { + return $this->status === 'completed' || $this->current_progress_value >= $this->target_value; + } +} diff --git a/app/Models/HealthRecord.php b/app/Models/HealthRecord.php index a474c83..8361523 100644 --- a/app/Models/HealthRecord.php +++ b/app/Models/HealthRecord.php @@ -11,6 +11,7 @@ class HealthRecord extends Model protected $fillable = [ 'user_id', + 'height', 'recorded_at', 'weight', 'body_fat_percentage', @@ -26,6 +27,7 @@ class HealthRecord extends Model protected $casts = [ 'recorded_at' => 'date', + 'height' => 'decimal:2', 'weight' => 'decimal:2', 'body_fat_percentage' => 'decimal:2', 'bmi' => 'decimal:2', diff --git a/app/Models/NotesMedia.php b/app/Models/NotesMedia.php new file mode 100644 index 0000000..c3fee25 --- /dev/null +++ b/app/Models/NotesMedia.php @@ -0,0 +1,20 @@ +belongsTo(TournamentEvent::class); + } +} diff --git a/app/Models/PerformanceResult.php b/app/Models/PerformanceResult.php new file mode 100644 index 0000000..1080b26 --- /dev/null +++ b/app/Models/PerformanceResult.php @@ -0,0 +1,21 @@ +belongsTo(TournamentEvent::class); + } +} diff --git a/app/Models/TournamentEvent.php b/app/Models/TournamentEvent.php new file mode 100644 index 0000000..8234d73 --- /dev/null +++ b/app/Models/TournamentEvent.php @@ -0,0 +1,41 @@ + 'date', + 'time' => 'datetime:H:i', + ]; + + public function user(): BelongsTo + { + return $this->belongsTo(User::class); + } + + public function performanceResults(): HasMany + { + return $this->hasMany(PerformanceResult::class); + } + + public function notesMedia(): HasMany + { + return $this->hasMany(NotesMedia::class); + } +} diff --git a/app/Models/User.php b/app/Models/User.php index 06232b0..7fd5713 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -226,6 +226,22 @@ class User extends Authenticatable return $this->hasMany(HealthRecord::class); } + /** + * Get the tournament events for the user. + */ + public function tournamentEvents(): HasMany + { + return $this->hasMany(TournamentEvent::class); + } + + /** + * Get the goals for the user. + */ + public function goals(): HasMany + { + return $this->hasMany(Goal::class); + } + /** * Send the email verification notification. * Override to prevent sending the default Laravel notification. diff --git a/database/migrations/2026_01_24_075456_create_tournament_events_table.php b/database/migrations/2026_01_24_075456_create_tournament_events_table.php new file mode 100644 index 0000000..57166e7 --- /dev/null +++ b/database/migrations/2026_01_24_075456_create_tournament_events_table.php @@ -0,0 +1,35 @@ +id(); + $table->foreignId('user_id')->constrained()->onDelete('cascade'); + $table->string('title'); + $table->enum('type', ['championship', 'tournament']); + $table->string('sport'); + $table->date('date'); + $table->time('time')->nullable(); + $table->string('location')->nullable(); + $table->integer('participants_count')->nullable(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('tournament_events'); + } +}; diff --git a/database/migrations/2026_01_24_075511_create_performance_results_table.php b/database/migrations/2026_01_24_075511_create_performance_results_table.php new file mode 100644 index 0000000..d4ed0ca --- /dev/null +++ b/database/migrations/2026_01_24_075511_create_performance_results_table.php @@ -0,0 +1,31 @@ +id(); + $table->foreignId('tournament_event_id')->constrained()->onDelete('cascade'); + $table->text('description')->nullable(); + $table->enum('medal_type', ['1st', '2nd', '3rd', 'special']); + $table->integer('points')->nullable(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('performance_results'); + } +}; diff --git a/database/migrations/2026_01_24_075526_create_notes_media_table.php b/database/migrations/2026_01_24_075526_create_notes_media_table.php new file mode 100644 index 0000000..80d32e3 --- /dev/null +++ b/database/migrations/2026_01_24_075526_create_notes_media_table.php @@ -0,0 +1,30 @@ +id(); + $table->foreignId('tournament_event_id')->constrained()->onDelete('cascade'); + $table->text('note_text')->nullable(); + $table->string('media_link')->nullable(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('notes_media'); + } +}; diff --git a/database/migrations/2026_01_24_080241_create_goals_table.php b/database/migrations/2026_01_24_080241_create_goals_table.php new file mode 100644 index 0000000..77171b6 --- /dev/null +++ b/database/migrations/2026_01_24_080241_create_goals_table.php @@ -0,0 +1,38 @@ +id(); + $table->foreignId('user_id')->constrained()->onDelete('cascade'); + $table->string('title'); + $table->text('description')->nullable(); + $table->date('start_date'); + $table->date('target_date'); + $table->decimal('current_progress_value', 8, 2)->default(0); + $table->decimal('target_value', 8, 2); + $table->enum('status', ['active', 'completed'])->default('active'); + $table->enum('priority_level', ['high', 'medium', 'low'])->default('medium'); + $table->string('unit')->nullable(); // e.g., lbs, min, kg + $table->string('icon_type')->default('target'); // e.g., target, dumbbell, clock + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('goals'); + } +}; diff --git a/database/migrations/2026_01_24_084323_add_height_to_health_records_table.php b/database/migrations/2026_01_24_084323_add_height_to_health_records_table.php new file mode 100644 index 0000000..83b6aae --- /dev/null +++ b/database/migrations/2026_01_24_084323_add_height_to_health_records_table.php @@ -0,0 +1,28 @@ +decimal('height', 5, 2)->nullable()->after('user_id'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('health_records', function (Blueprint $table) { + $table->dropColumn('height'); + }); + } +}; diff --git a/database/seeders/GoalSeeder.php b/database/seeders/GoalSeeder.php new file mode 100644 index 0000000..2d99e81 --- /dev/null +++ b/database/seeders/GoalSeeder.php @@ -0,0 +1,83 @@ +isEmpty()) { + return; // No users to seed goals for + } + + foreach ($users as $user) { + // Create sample goals + Goal::create([ + 'user_id' => $user->id, + 'title' => 'Weight Loss Goal', + 'description' => 'Reach target weight of 170 lbs.', + 'start_date' => now()->subDays(30), + 'target_date' => now()->addDays(60), + 'current_progress_value' => 175.0, + 'target_value' => 170.0, + 'status' => 'active', + 'priority_level' => 'high', + 'unit' => 'lbs', + 'icon_type' => 'target', + ]); + + Goal::create([ + 'user_id' => $user->id, + 'title' => 'Bench Press Strength', + 'description' => 'Increase bench press to 200 lbs.', + 'start_date' => now()->subDays(15), + 'target_date' => now()->addDays(45), + 'current_progress_value' => 180.0, + 'target_value' => 200.0, + 'status' => 'active', + 'priority_level' => 'medium', + 'unit' => 'lbs', + 'icon_type' => 'dumbbell', + ]); + + Goal::create([ + 'user_id' => $user->id, + 'title' => '5K Running Time', + 'description' => 'Complete 5K run in under 25 minutes.', + 'start_date' => now()->subDays(20), + 'target_date' => now()->addDays(40), + 'current_progress_value' => 27.5, + 'target_value' => 25.0, + 'status' => 'active', + 'priority_level' => 'medium', + 'unit' => 'min', + 'icon_type' => 'clock', + ]); + + Goal::create([ + 'user_id' => $user->id, + 'title' => 'Daily Steps Goal', + 'description' => 'Walk 10,000 steps per day.', + 'start_date' => now()->subDays(10), + 'target_date' => now()->addDays(20), + 'current_progress_value' => 8500.0, + 'target_value' => 10000.0, + 'status' => 'completed', + 'priority_level' => 'low', + 'unit' => 'steps', + 'icon_type' => 'target', + ]); + } + } +} diff --git a/database/seeders/TournamentSeeder.php b/database/seeders/TournamentSeeder.php new file mode 100644 index 0000000..eb36482 --- /dev/null +++ b/database/seeders/TournamentSeeder.php @@ -0,0 +1,110 @@ +create(); + + // Create tournament events + $events = [ + [ + 'title' => 'National Swimming Championship 2023', + 'type' => 'championship', + 'sport' => 'Swimming', + 'date' => '2023-08-15', + 'time' => '10:00', + 'location' => 'Olympic Pool, Bahrain', + 'participants_count' => 50, + ], + [ + 'title' => 'Regional Basketball Tournament', + 'type' => 'tournament', + 'sport' => 'Basketball', + 'date' => '2023-06-20', + 'time' => '14:00', + 'location' => 'Sports Arena, Manama', + 'participants_count' => 32, + ], + [ + 'title' => 'Youth Football League Finals', + 'type' => 'championship', + 'sport' => 'Football', + 'date' => '2023-05-10', + 'time' => '16:00', + 'location' => 'National Stadium', + 'participants_count' => 16, + ], + [ + 'title' => 'Tennis Open Championship', + 'type' => 'championship', + 'sport' => 'Tennis', + 'date' => '2023-04-05', + 'time' => '09:00', + 'location' => 'Tennis Club, Bahrain', + 'participants_count' => 24, + ], + ]; + + foreach ($events as $eventData) { + $event = TournamentEvent::create(array_merge($eventData, ['user_id' => $user->id])); + + // Add performance results + if ($event->sport === 'Swimming') { + PerformanceResult::create([ + 'tournament_event_id' => $event->id, + 'description' => '100m Freestyle - Personal Best', + 'medal_type' => '1st', + 'points' => 100, + ]); + } elseif ($event->sport === 'Basketball') { + PerformanceResult::create([ + 'tournament_event_id' => $event->id, + 'description' => 'MVP Award', + 'medal_type' => 'special', + 'points' => 50, + ]); + } elseif ($event->sport === 'Football') { + PerformanceResult::create([ + 'tournament_event_id' => $event->id, + 'description' => 'Golden Boot Winner', + 'medal_type' => 'special', + 'points' => 75, + ]); + PerformanceResult::create([ + 'tournament_event_id' => $event->id, + 'description' => 'Championship Title', + 'medal_type' => '1st', + 'points' => 200, + ]); + } elseif ($event->sport === 'Tennis') { + PerformanceResult::create([ + 'tournament_event_id' => $event->id, + 'description' => 'Singles Final', + 'medal_type' => '2nd', + 'points' => 80, + ]); + } + + // Add notes/media + NotesMedia::create([ + 'tournament_event_id' => $event->id, + 'note_text' => 'Outstanding performance in the championship. Set new personal records.', + 'media_link' => 'https://example.com/ceremony-photo.jpg', + ]); + } + } +} diff --git a/package-lock.json b/package-lock.json index 1be44d8..3c1ec22 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1943,7 +1943,6 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, - "peer": true, "engines": { "node": ">=12" }, @@ -2170,7 +2169,6 @@ "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", @@ -3396,8 +3394,7 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "dev": true, - "peer": true + "dev": true }, "postcss": { "version": "8.5.6", @@ -3552,7 +3549,6 @@ "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/resources/views/family/show.blade.php b/resources/views/family/show.blade.php index 0abc067..8f6f9f9 100644 --- a/resources/views/family/show.blade.php +++ b/resources/views/family/show.blade.php @@ -563,7 +563,7 @@
| Date | -Weight (kg) | -Body Fat % | -BMI | -Body Water % | -Muscle Mass (kg) | -Bone Mass (kg) | -Visceral Fat | -BMR | -Protein % | -Body Age | -|
|---|---|---|---|---|---|---|---|---|---|---|---|
| Date | +Height (cm) | +Weight (kg) | +Body Fat % | +BMI | +Body Water % | +Muscle Mass (kg) | +Bone Mass (kg) | +Visceral Fat | +BMR | +Protein % | +Body Age | +
| {{ $record->recorded_at->format('M j, Y') }} | +{{ $record->height ?? '-' }} | {{ $record->weight ?? '-' }} | {{ $record->body_fat_percentage ?? '-' }} | {{ $record->bmi ?? '-' }} | @@ -772,8 +780,125 @@
| Tournament Details | +Performance & Result | +Notes & Media | +
|---|---|---|
|
+ {{ $event->title }}
+
+ {{ ucfirst($event->type) }}
+ {{ $event->sport }}
+
+
+ {{ $event->date->format('M j, Y') }}
+ @if($event->time)
+ {{ $event->time->format('H:i') }}
+ @endif
+ @if($event->location)
+ {{ $event->location }}
+ @endif
+ @if($event->participants_count)
+ {{ $event->participants_count }} participants
+ @endif
+
+ |
+
+ @if($event->performanceResults->count() > 0)
+ @foreach($event->performanceResults as $result)
+
+ @if($result->medal_type == '1st')
+
+ 1st Place
+ @elseif($result->medal_type == '2nd')
+
+ 2nd Place
+ @elseif($result->medal_type == '3rd')
+
+ 3rd Place
+ @elseif($result->medal_type == 'special')
+
+ Special Award
+ @endif
+ @if($result->points)
+ {{ $result->points }} pts
+ @endif
+
+ @if($result->description)
+ {{ $result->description }}
+ @endif
+ @endforeach
+ @else
+ No results recorded
+ @endif
+ |
+
+ @if($event->notesMedia->count() > 0)
+ @foreach($event->notesMedia as $note)
+ @if($note->note_text)
+ {{ $note->note_text }} + @endif + @if($note->media_link) + + View Media + + @endif + @endforeach + @else + No notes available + @endif + |
+
No tournament records found
+ Tournament participation will appear here once records are added +