added affiliation tab card with its information
This commit is contained in:
1 parent
352d54a788
commit
c1fc28087e
14 files changed
+1493
-47
No files matched your search
@@ -9,6 +9,7 @@ use App\Models\Invoice;
|
||||
use App\Models\TournamentEvent;
|
||||
use App\Models\Goal;
|
||||
use App\Models\Attendance;
|
||||
use App\Models\ClubAffiliation;
|
||||
use App\Services\FamilyService;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
@@ -60,7 +61,7 @@ class FamilyController extends Controller
|
||||
|
||||
// Fetch tournament data
|
||||
$tournamentEvents = $user->tournamentEvents()
|
||||
->with(['performanceResults', 'notesMedia'])
|
||||
->with(['performanceResults', 'notesMedia', 'clubAffiliation'])
|
||||
->orderBy('date', 'desc')
|
||||
->get();
|
||||
|
||||
@@ -88,6 +89,24 @@ class FamilyController extends Controller
|
||||
$totalSessions = $attendanceRecords->count();
|
||||
$attendanceRate = $totalSessions > 0 ? round(($sessionsCompleted / $totalSessions) * 100, 1) : 0;
|
||||
|
||||
// Fetch affiliations data
|
||||
$clubAffiliations = $user->clubAffiliations()
|
||||
->with(['skillAcquisitions', 'affiliationMedia'])
|
||||
->orderBy('start_date', 'desc')
|
||||
->get();
|
||||
|
||||
// Add icon_class to media items for JavaScript
|
||||
$clubAffiliations->each(function($affiliation) {
|
||||
$affiliation->affiliationMedia->each(function($media) {
|
||||
$media->icon_class = $media->icon_class;
|
||||
});
|
||||
});
|
||||
|
||||
// Calculate summary stats
|
||||
$totalAffiliations = $clubAffiliations->count();
|
||||
$distinctSkills = $clubAffiliations->flatMap->skillAcquisitions->pluck('skill_name')->unique()->count();
|
||||
$totalMembershipDuration = $clubAffiliations->sum('duration_in_months');
|
||||
|
||||
// Pass user directly and a flag to indicate it's the current user's profile
|
||||
return view('family.show', [
|
||||
'relationship' => (object)[
|
||||
@@ -111,6 +130,10 @@ class FamilyController extends Controller
|
||||
'sessionsCompleted' => $sessionsCompleted,
|
||||
'noShows' => $noShows,
|
||||
'attendanceRate' => $attendanceRate,
|
||||
'clubAffiliations' => $clubAffiliations,
|
||||
'totalAffiliations' => $totalAffiliations,
|
||||
'distinctSkills' => $distinctSkills,
|
||||
'totalMembershipDuration' => $totalMembershipDuration,
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -280,7 +303,7 @@ class FamilyController extends Controller
|
||||
|
||||
// Fetch tournament data for the dependent
|
||||
$tournamentEvents = $relationship->dependent->tournamentEvents()
|
||||
->with(['performanceResults', 'notesMedia'])
|
||||
->with(['performanceResults', 'notesMedia', 'clubAffiliation'])
|
||||
->orderBy('date', 'desc')
|
||||
->get();
|
||||
|
||||
@@ -308,7 +331,46 @@ class FamilyController extends Controller
|
||||
$totalSessions = $attendanceRecords->count();
|
||||
$attendanceRate = $totalSessions > 0 ? round(($sessionsCompleted / $totalSessions) * 100, 1) : 0;
|
||||
|
||||
return view('family.show', compact('relationship', 'latestHealthRecord', 'healthRecords', 'comparisonRecords', 'invoices', 'tournamentEvents', 'awardCounts', 'sports', 'goals', 'activeGoalsCount', 'completedGoalsCount', 'successRate', 'attendanceRecords', 'sessionsCompleted', 'noShows', 'attendanceRate'));
|
||||
// Fetch affiliations data for the dependent
|
||||
$clubAffiliations = $relationship->dependent->clubAffiliations()
|
||||
->with(['skillAcquisitions', 'affiliationMedia'])
|
||||
->orderBy('start_date', 'desc')
|
||||
->get();
|
||||
|
||||
// Add icon_class to media items for JavaScript
|
||||
$clubAffiliations->each(function($affiliation) {
|
||||
$affiliation->affiliationMedia->each(function($media) {
|
||||
$media->icon_class = $media->icon_class;
|
||||
});
|
||||
});
|
||||
|
||||
// Calculate summary stats
|
||||
$totalAffiliations = $clubAffiliations->count();
|
||||
$distinctSkills = $clubAffiliations->flatMap->skillAcquisitions->pluck('skill_name')->unique()->count();
|
||||
$totalMembershipDuration = $clubAffiliations->sum('duration_in_months');
|
||||
|
||||
return view('family.show', [
|
||||
'relationship' => $relationship,
|
||||
'latestHealthRecord' => $latestHealthRecord,
|
||||
'healthRecords' => $healthRecords,
|
||||
'comparisonRecords' => $comparisonRecords,
|
||||
'invoices' => $invoices,
|
||||
'tournamentEvents' => $tournamentEvents,
|
||||
'awardCounts' => $awardCounts,
|
||||
'sports' => $sports,
|
||||
'goals' => $goals,
|
||||
'activeGoalsCount' => $activeGoalsCount,
|
||||
'completedGoalsCount' => $completedGoalsCount,
|
||||
'successRate' => $successRate,
|
||||
'attendanceRecords' => $attendanceRecords,
|
||||
'sessionsCompleted' => $sessionsCompleted,
|
||||
'noShows' => $noShows,
|
||||
'attendanceRate' => $attendanceRate,
|
||||
'clubAffiliations' => $clubAffiliations,
|
||||
'totalAffiliations' => $totalAffiliations,
|
||||
'distinctSkills' => $distinctSkills,
|
||||
'totalMembershipDuration' => $totalMembershipDuration,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -634,6 +696,81 @@ class FamilyController extends Controller
|
||||
return response()->json(['success' => true, 'message' => 'Goal updated successfully']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a new tournament participation record.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param int $id
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function storeTournament(Request $request, $id)
|
||||
{
|
||||
$user = Auth::user();
|
||||
|
||||
// Check if user is authorized to add tournament for this dependent
|
||||
if ($user->id !== (int)$id) {
|
||||
$relationship = UserRelationship::where('guardian_user_id', $user->id)
|
||||
->where('dependent_user_id', $id)
|
||||
->first();
|
||||
|
||||
if (!$relationship) {
|
||||
return response()->json(['success' => false, 'message' => 'Unauthorized'], 403);
|
||||
}
|
||||
}
|
||||
|
||||
// Validate the request
|
||||
$validated = $request->validate([
|
||||
'title' => 'required|string|max:255',
|
||||
'type' => 'required|in:championship,tournament,competition,exhibition',
|
||||
'sport' => 'required|string|max:100',
|
||||
'date' => 'required|date',
|
||||
'time' => 'nullable|date_format:H:i',
|
||||
'location' => 'nullable|string|max:255',
|
||||
'participants_count' => 'nullable|integer|min:1',
|
||||
'club_affiliation_id' => 'nullable|exists:club_affiliations,id',
|
||||
'performance_results' => 'nullable|array',
|
||||
'performance_results.*.medal_type' => 'nullable|in:special,1st,2nd,3rd',
|
||||
'performance_results.*.points' => 'nullable|numeric|min:0',
|
||||
'performance_results.*.description' => 'nullable|string|max:500',
|
||||
'notes_media' => 'nullable|array',
|
||||
'notes_media.*.note_text' => 'nullable|string|max:1000',
|
||||
'notes_media.*.media_link' => 'nullable|url',
|
||||
]);
|
||||
|
||||
// Create the tournament event
|
||||
$tournament = TournamentEvent::create([
|
||||
'user_id' => $id,
|
||||
'club_affiliation_id' => $validated['club_affiliation_id'] ?? null,
|
||||
'title' => $validated['title'],
|
||||
'type' => $validated['type'],
|
||||
'sport' => $validated['sport'],
|
||||
'date' => $validated['date'],
|
||||
'time' => $validated['time'],
|
||||
'location' => $validated['location'],
|
||||
'participants_count' => $validated['participants_count'],
|
||||
]);
|
||||
|
||||
// Create performance results
|
||||
if (isset($validated['performance_results'])) {
|
||||
foreach ($validated['performance_results'] as $resultData) {
|
||||
if (!empty($resultData['medal_type'])) {
|
||||
$tournament->performanceResults()->create($resultData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create notes and media
|
||||
if (isset($validated['notes_media'])) {
|
||||
foreach ($validated['notes_media'] as $noteData) {
|
||||
if (!empty($noteData['note_text']) || !empty($noteData['media_link'])) {
|
||||
$tournament->notesMedia()->create($noteData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return response()->json(['success' => true, 'message' => 'Tournament record added successfully']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified family member from storage.
|
||||
*
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class AffiliationMedia extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'club_affiliation_id',
|
||||
'media_type',
|
||||
'media_url',
|
||||
'title',
|
||||
'description',
|
||||
];
|
||||
|
||||
/**
|
||||
* Get the club affiliation that owns the media.
|
||||
*/
|
||||
public function clubAffiliation(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(ClubAffiliation::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the full URL for the media.
|
||||
*/
|
||||
public function getFullUrlAttribute(): string
|
||||
{
|
||||
if (filter_var($this->media_url, FILTER_VALIDATE_URL)) {
|
||||
return $this->media_url;
|
||||
}
|
||||
|
||||
return asset('storage/' . $this->media_url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get icon class for media type.
|
||||
*/
|
||||
public function getIconClassAttribute(): string
|
||||
{
|
||||
return match($this->media_type) {
|
||||
'certificate' => 'bi-file-earmark-text',
|
||||
'photo' => 'bi-image',
|
||||
'video' => 'bi-play-circle',
|
||||
'document' => 'bi-file-text',
|
||||
default => 'bi-file',
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
class ClubAffiliation extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'member_id',
|
||||
'club_name',
|
||||
'logo',
|
||||
'start_date',
|
||||
'end_date',
|
||||
'location',
|
||||
'coaches',
|
||||
'description',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'start_date' => 'date',
|
||||
'end_date' => 'date',
|
||||
'coaches' => 'array',
|
||||
];
|
||||
|
||||
/**
|
||||
* Get the member that owns the affiliation.
|
||||
*/
|
||||
public function member(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class, 'member_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the skills acquired during this affiliation.
|
||||
*/
|
||||
public function skillAcquisitions(): HasMany
|
||||
{
|
||||
return $this->hasMany(SkillAcquisition::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the media associated with this affiliation.
|
||||
*/
|
||||
public function affiliationMedia(): HasMany
|
||||
{
|
||||
return $this->hasMany(AffiliationMedia::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the duration of the affiliation in months.
|
||||
*/
|
||||
public function getDurationInMonthsAttribute(): int
|
||||
{
|
||||
$endDate = $this->end_date ?? now();
|
||||
return $this->start_date->diffInMonths($endDate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get formatted date range.
|
||||
*/
|
||||
public function getDateRangeAttribute(): string
|
||||
{
|
||||
$start = $this->start_date->format('M Y');
|
||||
$end = $this->end_date ? $this->end_date->format('M Y') : 'Present';
|
||||
return $start . ' – ' . $end;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get detailed formatted duration (years, months, days).
|
||||
*/
|
||||
public function getFormattedDurationAttribute(): string
|
||||
{
|
||||
$endDate = $this->end_date ?? now();
|
||||
$diff = $this->start_date->diff($endDate);
|
||||
|
||||
$parts = [];
|
||||
|
||||
if ($diff->y > 0) {
|
||||
$parts[] = $diff->y . ' year' . ($diff->y > 1 ? 's' : '');
|
||||
}
|
||||
if ($diff->m > 0) {
|
||||
$parts[] = $diff->m . ' month' . ($diff->m > 1 ? 's' : '');
|
||||
}
|
||||
if ($diff->d > 0) {
|
||||
$parts[] = $diff->d . ' day' . ($diff->d > 1 ? 's' : '');
|
||||
}
|
||||
|
||||
return implode(' ', $parts) ?: 'Same day';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class SkillAcquisition extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'club_affiliation_id',
|
||||
'skill_name',
|
||||
'icon',
|
||||
'duration_months',
|
||||
'proficiency_level',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'duration_months' => 'integer',
|
||||
];
|
||||
|
||||
/**
|
||||
* Get the club affiliation that owns the skill acquisition.
|
||||
*/
|
||||
public function clubAffiliation(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(ClubAffiliation::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get formatted duration.
|
||||
*/
|
||||
public function getFormattedDurationAttribute(): string
|
||||
{
|
||||
$months = $this->duration_months;
|
||||
if ($months < 12) {
|
||||
return $months . ' month' . ($months > 1 ? 's' : '');
|
||||
}
|
||||
|
||||
$years = floor($months / 12);
|
||||
$remainingMonths = $months % 12;
|
||||
|
||||
$result = $years . ' year' . ($years > 1 ? 's' : '');
|
||||
if ($remainingMonths > 0) {
|
||||
$result .= ' ' . $remainingMonths . ' month' . ($remainingMonths > 1 ? 's' : '');
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get proficiency level color for UI.
|
||||
*/
|
||||
public function getProficiencyColorAttribute(): string
|
||||
{
|
||||
return match($this->proficiency_level) {
|
||||
'beginner' => 'text-blue-500',
|
||||
'intermediate' => 'text-yellow-500',
|
||||
'advanced' => 'text-orange-500',
|
||||
'expert' => 'text-red-500',
|
||||
default => 'text-gray-500',
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ class TournamentEvent extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'user_id',
|
||||
'club_affiliation_id',
|
||||
'title',
|
||||
'type',
|
||||
'sport',
|
||||
@@ -29,6 +30,11 @@ class TournamentEvent extends Model
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
|
||||
public function clubAffiliation(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(ClubAffiliation::class);
|
||||
}
|
||||
|
||||
public function performanceResults(): HasMany
|
||||
{
|
||||
return $this->hasMany(PerformanceResult::class);
|
||||
|
||||
@@ -250,6 +250,14 @@ class User extends Authenticatable
|
||||
return $this->hasMany(Attendance::class, 'member_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the club affiliations for the user.
|
||||
*/
|
||||
public function clubAffiliations(): HasMany
|
||||
{
|
||||
return $this->hasMany(ClubAffiliation::class, 'member_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the email verification notification.
|
||||
* Override to prevent sending the default Laravel notification.
|
||||
|
||||
Reference in new issue
Block a user