affiliations

This commit is contained in:
ghassan committed 2026-01-27 16:34:46 +03:00
1 parent 4f3f9c9aad
commit 0b5b3dd3ee
177 files changed
+9660 -8531

No files matched your search

@@ -0,0 +1,121 @@
@extends('layouts.admin-club')
@section('club-admin-content')
<div>
<div class="d-flex justify-content-between align-items-center mb-4">
<div>
<h2 class="h3 fw-bold mb-1">Activities</h2>
<p class="text-muted mb-0">Manage club activities and classes</p>
</div>
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addActivityModal">
<i class="bi bi-plus-lg me-2"></i>Add Activity
</button>
</div>
@if(isset($activities) && count($activities) > 0)
<div class="card border-0 shadow-sm">
<div class="table-responsive">
<table class="table table-hover mb-0">
<thead class="bg-light">
<tr>
<th>Activity</th>
<th>Duration</th>
<th>Instructor</th>
<th>Status</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
@foreach($activities as $activity)
<tr>
<td>
<div class="d-flex align-items-center gap-3">
<div class="rounded bg-primary bg-opacity-10 p-2">
<i class="bi bi-activity text-primary"></i>
</div>
<div>
<p class="fw-semibold mb-0">{{ $activity->name }}</p>
<p class="text-muted small mb-0">{{ Str::limit($activity->description, 50) }}</p>
</div>
</div>
</td>
<td>{{ $activity->duration ?? 'N/A' }} min</td>
<td>{{ $activity->instructor->name ?? 'Not assigned' }}</td>
<td>
@if($activity->is_active ?? true)
<span class="badge bg-success">Active</span>
@else
<span class="badge bg-secondary">Inactive</span>
@endif
</td>
<td>
<div class="d-flex gap-1">
<button class="btn btn-sm btn-outline-primary">
<i class="bi bi-pencil"></i>
</button>
<button class="btn btn-sm btn-outline-danger">
<i class="bi bi-trash"></i>
</button>
</div>
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
@else
<div class="card border-0 shadow-sm">
<div class="card-body text-center py-5">
<i class="bi bi-activity text-muted" style="font-size: 4rem;"></i>
<h5 class="mt-3 mb-2">No activities yet</h5>
<p class="text-muted mb-3">Add activities and classes for your members</p>
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addActivityModal">
<i class="bi bi-plus-lg me-2"></i>Add Activity
</button>
</div>
</div>
@endif
</div>
<!-- Add Activity Modal -->
<div class="modal fade" id="addActivityModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header border-0">
<h5 class="modal-title">Add Activity</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<form action="{{ route('admin.club.activities.store', $club->id) }}" method="POST">
@csrf
<div class="mb-3">
<label class="form-label">Activity Name</label>
<input type="text" name="name" class="form-control" required>
</div>
<div class="mb-3">
<label class="form-label">Description</label>
<textarea name="description" class="form-control" rows="3"></textarea>
</div>
<div class="row g-3">
<div class="col-6">
<label class="form-label">Duration (minutes)</label>
<input type="number" name="duration" class="form-control" value="60">
</div>
<div class="col-6">
<label class="form-label">Instructor</label>
<select name="instructor_id" class="form-select">
<option value="">Select instructor</option>
@foreach($instructors ?? [] as $instructor)
<option value="{{ $instructor->id }}">{{ $instructor->name }}</option>
@endforeach
</select>
</div>
</div>
<button type="submit" class="btn btn-primary w-100 mt-3">Add Activity</button>
</form>
</div>
</div>
</div>
</div>
@endsection
@@ -0,0 +1,207 @@
@extends('layouts.admin-club')
@section('club-admin-content')
<div>
<div class="d-flex justify-content-between align-items-center mb-4">
<div>
<h2 class="h3 fw-bold mb-1">Analytics</h2>
<p class="text-muted mb-0">Track your club's performance</p>
</div>
<div class="d-flex gap-2">
<select class="form-select" style="width: auto;">
<option value="7">Last 7 days</option>
<option value="30" selected>Last 30 days</option>
<option value="90">Last 90 days</option>
<option value="365">Last year</option>
</select>
<button class="btn btn-outline-primary">
<i class="bi bi-download me-2"></i>Export
</button>
</div>
</div>
<!-- Key Metrics -->
<div class="row g-3 mb-4">
<div class="col-6 col-lg-3">
<div class="card border-0 shadow-sm">
<div class="card-body">
<p class="text-muted small mb-1">New Members</p>
<div class="d-flex align-items-center gap-2">
<h4 class="fw-bold mb-0">{{ $analytics['new_members'] ?? 0 }}</h4>
<span class="badge bg-success-subtle text-success">
<i class="bi bi-arrow-up"></i> {{ $analytics['new_members_change'] ?? 0 }}%
</span>
</div>
</div>
</div>
</div>
<div class="col-6 col-lg-3">
<div class="card border-0 shadow-sm">
<div class="card-body">
<p class="text-muted small mb-1">Retention Rate</p>
<div class="d-flex align-items-center gap-2">
<h4 class="fw-bold mb-0">{{ $analytics['retention_rate'] ?? 0 }}%</h4>
<span class="badge bg-success-subtle text-success">
<i class="bi bi-arrow-up"></i> {{ $analytics['retention_change'] ?? 0 }}%
</span>
</div>
</div>
</div>
</div>
<div class="col-6 col-lg-3">
<div class="card border-0 shadow-sm">
<div class="card-body">
<p class="text-muted small mb-1">Avg. Revenue/Member</p>
<div class="d-flex align-items-center gap-2">
<h4 class="fw-bold mb-0">{{ $club->currency ?? 'BHD' }} {{ number_format($analytics['avg_revenue'] ?? 0, 2) }}</h4>
</div>
</div>
</div>
</div>
<div class="col-6 col-lg-3">
<div class="card border-0 shadow-sm">
<div class="card-body">
<p class="text-muted small mb-1">Check-ins</p>
<div class="d-flex align-items-center gap-2">
<h4 class="fw-bold mb-0">{{ $analytics['total_checkins'] ?? 0 }}</h4>
<span class="badge bg-info-subtle text-info">
<i class="bi bi-arrow-up"></i> {{ $analytics['checkins_change'] ?? 0 }}%
</span>
</div>
</div>
</div>
</div>
</div>
<div class="row g-4">
<!-- Membership Growth Chart -->
<div class="col-lg-8">
<div class="card border-0 shadow-sm h-100">
<div class="card-header bg-white border-0">
<h5 class="fw-semibold mb-0">Membership Growth</h5>
</div>
<div class="card-body">
<canvas id="membershipChart" height="250"></canvas>
</div>
</div>
</div>
<!-- Popular Packages -->
<div class="col-lg-4">
<div class="card border-0 shadow-sm h-100">
<div class="card-header bg-white border-0">
<h5 class="fw-semibold mb-0">Popular Packages</h5>
</div>
<div class="card-body">
@if(isset($popularPackages) && count($popularPackages) > 0)
@foreach($popularPackages as $package)
<div class="d-flex justify-content-between align-items-center mb-3">
<div>
<p class="fw-semibold mb-0">{{ $package->name }}</p>
<p class="text-muted small mb-0">{{ $package->subscriptions_count }} subscriptions</p>
</div>
<div class="text-end">
<p class="fw-bold text-primary mb-0">{{ $package->percentage ?? 0 }}%</p>
</div>
</div>
<div class="progress mb-3" style="height: 6px;">
<div class="progress-bar bg-primary" style="width: {{ $package->percentage ?? 0 }}%"></div>
</div>
@endforeach
@else
<div class="text-center py-4">
<p class="text-muted mb-0">No package data available</p>
</div>
@endif
</div>
</div>
</div>
<!-- Activity Breakdown -->
<div class="col-lg-6">
<div class="card border-0 shadow-sm">
<div class="card-header bg-white border-0">
<h5 class="fw-semibold mb-0">Activity Breakdown</h5>
</div>
<div class="card-body">
<canvas id="activityChart" height="200"></canvas>
</div>
</div>
</div>
<!-- Peak Hours -->
<div class="col-lg-6">
<div class="card border-0 shadow-sm">
<div class="card-header bg-white border-0">
<h5 class="fw-semibold mb-0">Peak Hours</h5>
</div>
<div class="card-body">
<canvas id="peakHoursChart" height="200"></canvas>
</div>
</div>
</div>
</div>
</div>
@push('scripts')
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
// Membership Growth Chart
new Chart(document.getElementById('membershipChart'), {
type: 'line',
data: {
labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
datasets: [{
label: 'Members',
data: @json($analytics['monthly_members'] ?? [0,0,0,0,0,0,0,0,0,0,0,0]),
borderColor: 'hsl(355, 84%, 44%)',
backgroundColor: 'hsla(355, 84%, 44%, 0.1)',
fill: true,
tension: 0.3
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: { legend: { display: false } }
}
});
// Activity Chart
new Chart(document.getElementById('activityChart'), {
type: 'doughnut',
data: {
labels: @json($analytics['activity_labels'] ?? ['No data']),
datasets: [{
data: @json($analytics['activity_data'] ?? [100]),
backgroundColor: ['#CE1126', '#3b82f6', '#22c55e', '#f59e0b', '#8b5cf6']
}]
},
options: {
responsive: true,
maintainAspectRatio: false
}
});
// Peak Hours Chart
new Chart(document.getElementById('peakHoursChart'), {
type: 'bar',
data: {
labels: ['6am', '8am', '10am', '12pm', '2pm', '4pm', '6pm', '8pm', '10pm'],
datasets: [{
label: 'Check-ins',
data: @json($analytics['hourly_checkins'] ?? [0,0,0,0,0,0,0,0,0]),
backgroundColor: 'hsl(355, 84%, 44%)'
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: { legend: { display: false } }
}
});
});
</script>
@endpush
@endsection
@@ -0,0 +1,215 @@
@extends('layouts.admin-club')
@section('club-admin-content')
<div class="space-y-6">
<!-- Header -->
<div>
<h2 class="h3 fw-bold mb-2">Dashboard Overview</h2>
<p class="text-muted">Welcome to {{ $club->club_name }} management</p>
</div>
<!-- Stats Cards -->
<div class="row g-3">
<!-- Active Members -->
<div class="col-6 col-md-4 col-lg">
<div class="card h-100 border-0 shadow-sm" style="cursor: pointer;" onclick="window.location='{{ route('admin.club.members', $club->id) }}'">
<div class="card-body">
<div class="d-flex align-items-center justify-content-between mb-2">
<span class="text-muted small">Active Members</span>
<div class="rounded p-2" style="background-color: hsl(var(--primary) / 0.1);">
<i class="bi bi-people text-primary"></i>
</div>
</div>
<h3 class="fw-bold text-primary mb-1">{{ $stats['members'] ?? 0 }}</h3>
<p class="text-muted small mb-0">Members with valid packages</p>
</div>
</div>
</div>
<!-- Activities -->
<div class="col-6 col-md-4 col-lg">
<div class="card h-100 border-0 shadow-sm" style="cursor: pointer;" onclick="window.location='{{ route('admin.club.activities', $club->id) }}'">
<div class="card-body">
<div class="d-flex align-items-center justify-content-between mb-2">
<span class="text-muted small">Activities</span>
<div class="rounded p-2" style="background-color: hsl(var(--warning) / 0.1);">
<i class="bi bi-activity text-warning"></i>
</div>
</div>
<h3 class="fw-bold text-warning mb-1">{{ $stats['activities'] ?? 0 }}</h3>
<p class="text-muted small mb-0">Available activities</p>
</div>
</div>
</div>
<!-- Packages -->
<div class="col-6 col-md-4 col-lg">
<div class="card h-100 border-0 shadow-sm" style="cursor: pointer;" onclick="window.location='{{ route('admin.club.packages', $club->id) }}'">
<div class="card-body">
<div class="d-flex align-items-center justify-content-between mb-2">
<span class="text-muted small">Packages</span>
<div class="rounded p-2" style="background-color: hsl(var(--success) / 0.1);">
<i class="bi bi-box text-success"></i>
</div>
</div>
<h3 class="fw-bold text-success mb-1">{{ $stats['packages'] ?? 0 }}</h3>
<p class="text-muted small mb-0">Available packages</p>
</div>
</div>
</div>
<!-- Trainers -->
<div class="col-6 col-md-4 col-lg">
<div class="card h-100 border-0 shadow-sm" style="cursor: pointer;" onclick="window.location='{{ route('admin.club.instructors', $club->id) }}'">
<div class="card-body">
<div class="d-flex align-items-center justify-content-between mb-2">
<span class="text-muted small">Trainers</span>
<div class="rounded p-2" style="background-color: hsl(var(--info) / 0.1);">
<i class="bi bi-person-badge text-info"></i>
</div>
</div>
<h3 class="fw-bold text-info mb-1">{{ $stats['instructors'] ?? 0 }}</h3>
<p class="text-muted small mb-0">Available trainers</p>
</div>
</div>
</div>
<!-- Rating -->
<div class="col-6 col-md-4 col-lg">
<div class="card h-100 border-0 shadow-sm" style="cursor: pointer;" onclick="window.location='{{ route('admin.club.details', $club->id) }}'">
<div class="card-body">
<div class="d-flex align-items-center justify-content-between mb-2">
<span class="text-muted small">Member Rating</span>
<div class="rounded p-2" style="background-color: hsl(35 90% 55% / 0.1);">
<i class="bi bi-star-fill" style="color: hsl(35, 90%, 55%);"></i>
</div>
</div>
<div class="d-flex align-items-center gap-2">
<h3 class="fw-bold mb-1" style="color: hsl(35, 90%, 55%);">{{ number_format($stats['rating'] ?? 0, 1) }}</h3>
<i class="bi bi-star-fill" style="color: hsl(35, 90%, 55%);"></i>
</div>
<p class="text-muted small mb-0">Average from reviews</p>
</div>
</div>
</div>
</div>
<!-- Financial Overview Chart -->
<div class="card border-0 shadow-sm">
<div class="card-header bg-white border-0 pb-0">
<h5 class="fw-bold mb-1">Financial Overview (Last 12 Months)</h5>
<p class="text-muted small">Monthly income, expenses, and profit trends</p>
</div>
<div class="card-body">
<canvas id="financialChart" height="300"></canvas>
</div>
</div>
<!-- Expiring Subscriptions -->
@if(isset($expiringSubscriptions) && count($expiringSubscriptions) > 0)
<div class="card border-0 shadow-sm border-start border-warning border-4">
<div class="card-header bg-white border-0">
<h5 class="fw-bold mb-0">
<i class="bi bi-exclamation-triangle text-warning me-2"></i>
Expiring Subscriptions
</h5>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr>
<th>Member</th>
<th>Package</th>
<th>Expires</th>
<th>Action</th>
</tr>
</thead>
<tbody>
@foreach($expiringSubscriptions as $subscription)
<tr>
<td>{{ $subscription->user->full_name ?? 'N/A' }}</td>
<td>{{ $subscription->package->name ?? 'N/A' }}</td>
<td>{{ $subscription->end_date ? $subscription->end_date->format('M d, Y') : 'N/A' }}</td>
<td>
<button class="btn btn-sm btn-outline-primary">Renew</button>
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
</div>
@endif
</div>
@push('scripts')
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
const ctx = document.getElementById('financialChart').getContext('2d');
// Sample data - replace with actual data from controller
const monthlyData = @json($monthlyFinancials ?? []);
const labels = monthlyData.map(d => d.month) || ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
const incomeData = monthlyData.map(d => d.income) || [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
const expensesData = monthlyData.map(d => d.expenses) || [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
const profitData = monthlyData.map(d => d.profit) || [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
new Chart(ctx, {
type: 'line',
data: {
labels: labels,
datasets: [
{
label: 'Income',
data: incomeData,
borderColor: '#22c55e',
backgroundColor: 'rgba(34, 197, 94, 0.1)',
borderWidth: 2,
tension: 0.3
},
{
label: 'Expenses',
data: expensesData,
borderColor: '#ef4444',
backgroundColor: 'rgba(239, 68, 68, 0.1)',
borderWidth: 2,
tension: 0.3
},
{
label: 'Profit',
data: profitData,
borderColor: '#3b82f6',
backgroundColor: 'rgba(59, 130, 246, 0.1)',
borderWidth: 2,
tension: 0.3
}
]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
position: 'bottom'
}
},
scales: {
y: {
beginAtZero: true,
ticks: {
callback: function(value) {
return '{{ $club->currency ?? "BHD" }} ' + value.toLocaleString();
}
}
}
}
}
});
});
</script>
@endpush
@endsection
@@ -0,0 +1,127 @@
@extends('layouts.admin-club')
@section('club-admin-content')
<div class="space-y-4">
<div class="d-flex justify-content-between align-items-center mb-4">
<div>
<h2 class="h3 fw-bold mb-1">Club Details</h2>
<p class="text-muted mb-0">Manage your club information</p>
</div>
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#editClubModal">
<i class="bi bi-pencil me-2"></i>Edit Details
</button>
</div>
<div class="row g-4">
<!-- Basic Info Card -->
<div class="col-lg-8">
<div class="card border-0 shadow-sm">
<div class="card-header bg-white border-0">
<h5 class="fw-semibold mb-0">Basic Information</h5>
</div>
<div class="card-body">
<div class="row g-3">
<div class="col-md-6">
<label class="form-label text-muted small">Club Name</label>
<p class="fw-semibold mb-0">{{ $club->club_name }}</p>
</div>
<div class="col-md-6">
<label class="form-label text-muted small">Slug</label>
<p class="fw-semibold mb-0">{{ $club->slug }}</p>
</div>
<div class="col-12">
<label class="form-label text-muted small">Description</label>
<p class="mb-0">{{ $club->description ?? 'No description provided' }}</p>
</div>
<div class="col-md-6">
<label class="form-label text-muted small">Email</label>
<p class="mb-0">{{ $club->email ?? 'Not set' }}</p>
</div>
<div class="col-md-6">
<label class="form-label text-muted small">Phone</label>
<p class="mb-0">
@if($club->phone)
{{ is_array($club->phone) ? implode(', ', $club->phone) : $club->phone }}
@else
Not set
@endif
</p>
</div>
<div class="col-12">
<label class="form-label text-muted small">Address</label>
<p class="mb-0">{{ $club->address ?? 'Not set' }}</p>
</div>
</div>
</div>
</div>
</div>
<!-- Logo & Settings Card -->
<div class="col-lg-4">
<div class="card border-0 shadow-sm mb-4">
<div class="card-header bg-white border-0">
<h5 class="fw-semibold mb-0">Club Logo</h5>
</div>
<div class="card-body text-center">
@if($club->logo)
<img src="{{ asset('storage/' . $club->logo) }}" alt="{{ $club->club_name }}" class="img-fluid rounded mb-3" style="max-height: 150px;">
@else
<div class="bg-light rounded d-flex align-items-center justify-content-center mb-3" style="height: 150px;">
<i class="bi bi-building text-muted" style="font-size: 3rem;"></i>
</div>
@endif
<button class="btn btn-outline-primary btn-sm">
<i class="bi bi-upload me-1"></i>Change Logo
</button>
</div>
</div>
<div class="card border-0 shadow-sm">
<div class="card-header bg-white border-0">
<h5 class="fw-semibold mb-0">Settings</h5>
</div>
<div class="card-body">
<div class="mb-3">
<label class="form-label text-muted small">Currency</label>
<p class="fw-semibold mb-0">{{ $club->currency ?? 'BHD' }}</p>
</div>
<div class="mb-3">
<label class="form-label text-muted small">Timezone</label>
<p class="fw-semibold mb-0">{{ $club->timezone ?? 'Asia/Bahrain' }}</p>
</div>
<div class="mb-0">
<label class="form-label text-muted small">Enrollment Fee</label>
<p class="fw-semibold mb-0">{{ $club->currency ?? 'BHD' }} {{ number_format($club->enrollment_fee ?? 0, 2) }}</p>
</div>
</div>
</div>
</div>
<!-- Location Card -->
<div class="col-12">
<div class="card border-0 shadow-sm">
<div class="card-header bg-white border-0">
<h5 class="fw-semibold mb-0">Location</h5>
</div>
<div class="card-body">
<div class="row g-3">
<div class="col-md-6">
<label class="form-label text-muted small">GPS Latitude</label>
<p class="fw-semibold mb-0">{{ $club->gps_lat ?? 'Not set' }}</p>
</div>
<div class="col-md-6">
<label class="form-label text-muted small">GPS Longitude</label>
<p class="fw-semibold mb-0">{{ $club->gps_long ?? 'Not set' }}</p>
</div>
</div>
@if($club->gps_lat && $club->gps_long)
<div class="mt-3">
<div id="clubMap" style="height: 300px; border-radius: 0.5rem;"></div>
</div>
@endif
</div>
</div>
</div>
</div>
</div>
@endsection
@@ -0,0 +1,108 @@
@extends('layouts.admin-club')
@section('club-admin-content')
<div>
<div class="d-flex justify-content-between align-items-center mb-4">
<div>
<h2 class="h3 fw-bold mb-1">Facilities</h2>
<p class="text-muted mb-0">Manage your club facilities</p>
</div>
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addFacilityModal">
<i class="bi bi-plus-lg me-2"></i>Add Facility
</button>
</div>
@if(isset($facilities) && count($facilities) > 0)
<div class="row g-4">
@foreach($facilities as $facility)
<div class="col-md-6 col-lg-4">
<div class="card border-0 shadow-sm h-100">
@if($facility->image)
<img src="{{ asset('storage/' . $facility->image) }}" alt="{{ $facility->name }}" class="card-img-top" style="height: 180px; object-fit: cover;">
@else
<div class="bg-light d-flex align-items-center justify-content-center" style="height: 180px;">
<i class="bi bi-geo-alt text-muted" style="font-size: 3rem;"></i>
</div>
@endif
<div class="card-body">
<h5 class="fw-semibold mb-2">{{ $facility->name }}</h5>
<p class="text-muted small mb-3">{{ Str::limit($facility->description, 100) }}</p>
<div class="d-flex gap-2 flex-wrap">
<x-takeone-cropper
id="facility_{{ $facility->id }}"
:width="400"
:height="300"
shape="square"
folder="clubs/{{ $club->id }}/facilities"
filename="facility_{{ $facility->id }}"
uploadUrl="{{ route('admin.club.facilities.upload-image', [$club->id, $facility->id]) }}"
:currentImage="$facility->image ? asset('storage/' . $facility->image) : ''"
buttonText="Change Image"
buttonClass="btn btn-sm btn-outline-success flex-grow-1"
/>
<button class="btn btn-sm btn-outline-danger">
<i class="bi bi-trash"></i>
</button>
</div>
</div>
</div>
</div>
@endforeach
</div>
@else
<div class="card border-0 shadow-sm">
<div class="card-body text-center py-5">
<i class="bi bi-geo-alt text-muted" style="font-size: 4rem;"></i>
<h5 class="mt-3 mb-2">No facilities yet</h5>
<p class="text-muted mb-3">Add facilities to showcase what your club offers</p>
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addFacilityModal">
<i class="bi bi-plus-lg me-2"></i>Add Facility
</button>
</div>
</div>
@endif
</div>
<!-- Add Facility Modal -->
<div class="modal fade" id="addFacilityModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header border-0">
<h5 class="modal-title">Add Facility</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<form action="{{ route('admin.club.facilities.store', $club->id) }}" method="POST">
@csrf
<div class="mb-3">
<label class="form-label">Facility Name</label>
<input type="text" name="name" class="form-control" required>
</div>
<div class="mb-3">
<label class="form-label">Description</label>
<textarea name="description" class="form-control" rows="3"></textarea>
</div>
<div class="mb-3">
<label class="form-label d-block">Image</label>
<x-takeone-cropper
id="add_facility_image"
mode="form"
inputName="image"
:width="400"
:height="300"
:previewWidth="200"
:previewHeight="150"
shape="square"
folder="clubs/{{ $club->id }}/facilities"
filename="facility_{{ time() }}"
buttonText="Select Image"
buttonClass="btn btn-outline-success btn-sm"
/>
</div>
<button type="submit" class="btn btn-primary w-100">Add Facility</button>
</form>
</div>
</div>
</div>
</div>
@endsection
@@ -0,0 +1,188 @@
@extends('layouts.admin-club')
@section('club-admin-content')
<div>
<div class="d-flex flex-column flex-md-row justify-content-between align-items-start align-items-md-center gap-3 mb-4">
<div>
<h2 class="h3 fw-bold mb-1">Financials</h2>
<p class="text-muted mb-0">Track income, expenses, and transactions</p>
</div>
<div class="d-flex gap-2">
<button class="btn btn-outline-primary" data-bs-toggle="modal" data-bs-target="#addExpenseModal">
<i class="bi bi-dash-circle me-2"></i>Add Expense
</button>
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addIncomeModal">
<i class="bi bi-plus-circle me-2"></i>Add Income
</button>
</div>
</div>
<!-- Summary Cards -->
<div class="row g-3 mb-4">
<div class="col-6 col-lg-3">
<div class="card border-0 shadow-sm h-100">
<div class="card-body">
<p class="text-muted small mb-1">Total Income</p>
<h4 class="fw-bold text-success mb-0">{{ $club->currency ?? 'BHD' }} {{ number_format($summary['total_income'] ?? 0, 2) }}</h4>
</div>
</div>
</div>
<div class="col-6 col-lg-3">
<div class="card border-0 shadow-sm h-100">
<div class="card-body">
<p class="text-muted small mb-1">Total Expenses</p>
<h4 class="fw-bold text-danger mb-0">{{ $club->currency ?? 'BHD' }} {{ number_format($summary['total_expenses'] ?? 0, 2) }}</h4>
</div>
</div>
</div>
<div class="col-6 col-lg-3">
<div class="card border-0 shadow-sm h-100">
<div class="card-body">
<p class="text-muted small mb-1">Net Profit</p>
<h4 class="fw-bold text-primary mb-0">{{ $club->currency ?? 'BHD' }} {{ number_format($summary['net_profit'] ?? 0, 2) }}</h4>
</div>
</div>
</div>
<div class="col-6 col-lg-3">
<div class="card border-0 shadow-sm h-100">
<div class="card-body">
<p class="text-muted small mb-1">Pending Payments</p>
<h4 class="fw-bold text-warning mb-0">{{ $club->currency ?? 'BHD' }} {{ number_format($summary['pending'] ?? 0, 2) }}</h4>
</div>
</div>
</div>
</div>
<!-- Transactions Table -->
<div class="card border-0 shadow-sm">
<div class="card-header bg-white border-0 d-flex justify-content-between align-items-center">
<h5 class="fw-semibold mb-0">Recent Transactions</h5>
<div class="d-flex gap-2">
<select class="form-select form-select-sm" style="width: auto;">
<option value="">All Types</option>
<option value="income">Income</option>
<option value="expense">Expense</option>
<option value="refund">Refund</option>
</select>
<input type="month" class="form-control form-control-sm" style="width: auto;">
</div>
</div>
@if(isset($transactions) && count($transactions) > 0)
<div class="table-responsive">
<table class="table table-hover mb-0">
<thead class="bg-light">
<tr>
<th>Date</th>
<th>Description</th>
<th>Type</th>
<th>Amount</th>
<th>Status</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
@foreach($transactions as $transaction)
<tr>
<td>{{ $transaction->transaction_date ? $transaction->transaction_date->format('M d, Y') : 'N/A' }}</td>
<td>{{ $transaction->description ?? 'N/A' }}</td>
<td>
@if($transaction->type === 'income')
<span class="badge bg-success">Income</span>
@elseif($transaction->type === 'expense')
<span class="badge bg-danger">Expense</span>
@else
<span class="badge bg-warning text-dark">{{ ucfirst($transaction->type) }}</span>
@endif
</td>
<td class="{{ $transaction->type === 'income' ? 'text-success' : 'text-danger' }} fw-semibold">
{{ $transaction->type === 'income' ? '+' : '-' }}{{ $club->currency ?? 'BHD' }} {{ number_format($transaction->amount, 2) }}
</td>
<td>
@if($transaction->status === 'paid')
<span class="badge bg-success">Paid</span>
@elseif($transaction->status === 'pending')
<span class="badge bg-warning text-dark">Pending</span>
@else
<span class="badge bg-secondary">{{ ucfirst($transaction->status) }}</span>
@endif
</td>
<td>
<button class="btn btn-sm btn-outline-secondary">
<i class="bi bi-eye"></i>
</button>
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
@else
<div class="card-body text-center py-5">
<i class="bi bi-currency-dollar text-muted" style="font-size: 4rem;"></i>
<h5 class="mt-3 mb-2">No transactions yet</h5>
<p class="text-muted">Transactions will appear here as they occur</p>
</div>
@endif
</div>
</div>
<!-- Add Income Modal -->
<div class="modal fade" id="addIncomeModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header border-0">
<h5 class="modal-title">Add Income</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<form action="{{ route('admin.club.financials.income', $club->id) }}" method="POST">
@csrf
<div class="mb-3">
<label class="form-label">Description</label>
<input type="text" name="description" class="form-control" required>
</div>
<div class="mb-3">
<label class="form-label">Amount ({{ $club->currency ?? 'BHD' }})</label>
<input type="number" name="amount" class="form-control" step="0.01" required>
</div>
<div class="mb-3">
<label class="form-label">Date</label>
<input type="date" name="transaction_date" class="form-control" value="{{ date('Y-m-d') }}" required>
</div>
<button type="submit" class="btn btn-success w-100">Add Income</button>
</form>
</div>
</div>
</div>
</div>
<!-- Add Expense Modal -->
<div class="modal fade" id="addExpenseModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header border-0">
<h5 class="modal-title">Add Expense</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<form action="{{ route('admin.club.financials.expense', $club->id) }}" method="POST">
@csrf
<div class="mb-3">
<label class="form-label">Description</label>
<input type="text" name="description" class="form-control" required>
</div>
<div class="mb-3">
<label class="form-label">Amount ({{ $club->currency ?? 'BHD' }})</label>
<input type="number" name="amount" class="form-control" step="0.01" required>
</div>
<div class="mb-3">
<label class="form-label">Date</label>
<input type="date" name="transaction_date" class="form-control" value="{{ date('Y-m-d') }}" required>
</div>
<button type="submit" class="btn btn-danger w-100">Add Expense</button>
</form>
</div>
</div>
</div>
</div>
@endsection
@@ -0,0 +1,76 @@
@extends('layouts.admin-club')
@section('club-admin-content')
<div>
<div class="d-flex justify-content-between align-items-center mb-4">
<div>
<h2 class="h3 fw-bold mb-1">Gallery</h2>
<p class="text-muted mb-0">Manage your club photos and media</p>
</div>
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#uploadImageModal">
<i class="bi bi-plus-lg me-2"></i>Add Images
</button>
</div>
@if(isset($images) && count($images) > 0)
<div class="row g-3">
@foreach($images as $image)
<div class="col-6 col-md-4 col-lg-3">
<div class="card border-0 shadow-sm h-100">
<div class="position-relative">
<img src="{{ asset('storage/' . $image->path) }}" alt="{{ $image->caption ?? 'Gallery image' }}" class="card-img-top" style="height: 200px; object-fit: cover;">
<div class="position-absolute top-0 end-0 p-2">
<button class="btn btn-sm btn-danger rounded-circle" onclick="deleteImage({{ $image->id }})">
<i class="bi bi-trash"></i>
</button>
</div>
</div>
@if($image->caption)
<div class="card-body py-2">
<p class="small mb-0">{{ $image->caption }}</p>
</div>
@endif
</div>
</div>
@endforeach
</div>
@else
<div class="card border-0 shadow-sm">
<div class="card-body text-center py-5">
<i class="bi bi-images text-muted" style="font-size: 4rem;"></i>
<h5 class="mt-3 mb-2">No images yet</h5>
<p class="text-muted mb-3">Upload photos to showcase your club</p>
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#uploadImageModal">
<i class="bi bi-plus-lg me-2"></i>Add Images
</button>
</div>
</div>
@endif
</div>
<!-- Upload Modal -->
<div class="modal fade" id="uploadImageModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header border-0">
<h5 class="modal-title">Upload Images</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<form action="{{ route('admin.club.gallery.upload', $club->id) }}" method="POST" enctype="multipart/form-data">
@csrf
<div class="mb-3">
<label class="form-label">Select Images</label>
<input type="file" name="images[]" class="form-control" multiple accept="image/*">
</div>
<div class="mb-3">
<label class="form-label">Caption (optional)</label>
<input type="text" name="caption" class="form-control" placeholder="Enter caption...">
</div>
<button type="submit" class="btn btn-primary w-100">Upload</button>
</form>
</div>
</div>
</div>
</div>
@endsection
@@ -0,0 +1,115 @@
@extends('layouts.admin-club')
@section('club-admin-content')
<div>
<div class="d-flex justify-content-between align-items-center mb-4">
<div>
<h2 class="h3 fw-bold mb-1">Instructors</h2>
<p class="text-muted mb-0">Manage your club instructors and trainers</p>
</div>
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addInstructorModal">
<i class="bi bi-plus-lg me-2"></i>Add Instructor
</button>
</div>
@if(isset($instructors) && count($instructors) > 0)
<div class="row g-4">
@foreach($instructors as $instructor)
<div class="col-md-6 col-lg-4">
<div class="card border-0 shadow-sm h-100">
<div class="card-body text-center">
@if($instructor->photo)
<img src="{{ asset('storage/' . $instructor->photo) }}" alt="{{ $instructor->name }}" class="rounded-circle mb-3" style="width: 100px; height: 100px; object-fit: cover;">
@else
<div class="rounded-circle bg-primary d-inline-flex align-items-center justify-content-center mb-3" style="width: 100px; height: 100px;">
<span class="text-white fw-bold" style="font-size: 2rem;">{{ strtoupper(substr($instructor->name, 0, 1)) }}</span>
</div>
@endif
<h5 class="fw-semibold mb-1">{{ $instructor->name }}</h5>
<p class="text-muted small mb-2">{{ $instructor->specialization ?? 'Trainer' }}</p>
@if($instructor->bio)
<p class="text-muted small mb-3">{{ Str::limit($instructor->bio, 80) }}</p>
@endif
<div class="d-flex gap-2 justify-content-center flex-wrap">
<x-takeone-cropper
id="instructor_{{ $instructor->id }}"
:width="200"
:height="200"
shape="circle"
folder="clubs/{{ $club->id }}/instructors"
filename="instructor_{{ $instructor->id }}"
uploadUrl="{{ route('admin.club.instructors.upload-photo', [$club->id, $instructor->id]) }}"
:currentImage="$instructor->photo ? asset('storage/' . $instructor->photo) : ''"
buttonText="Change Photo"
buttonClass="btn btn-sm btn-outline-success"
/>
<button class="btn btn-sm btn-outline-danger">
<i class="bi bi-trash"></i>
</button>
</div>
</div>
</div>
</div>
@endforeach
</div>
@else
<div class="card border-0 shadow-sm">
<div class="card-body text-center py-5">
<i class="bi bi-people text-muted" style="font-size: 4rem;"></i>
<h5 class="mt-3 mb-2">No instructors yet</h5>
<p class="text-muted mb-3">Add instructors to your club</p>
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addInstructorModal">
<i class="bi bi-plus-lg me-2"></i>Add Instructor
</button>
</div>
</div>
@endif
</div>
<!-- Add Instructor Modal -->
<div class="modal fade" id="addInstructorModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header border-0">
<h5 class="modal-title">Add Instructor</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<form action="{{ route('admin.club.instructors.store', $club->id) }}" method="POST">
@csrf
<div class="mb-3">
<label class="form-label">Name</label>
<input type="text" name="name" class="form-control" required>
</div>
<div class="mb-3">
<label class="form-label">Specialization</label>
<input type="text" name="specialization" class="form-control" placeholder="e.g., Yoga, CrossFit, Swimming">
</div>
<div class="mb-3">
<label class="form-label">Bio</label>
<textarea name="bio" class="form-control" rows="3"></textarea>
</div>
<div class="mb-3 text-center">
<label class="form-label d-block">Photo</label>
<x-takeone-cropper
id="add_instructor_photo"
mode="form"
inputName="photo"
:width="200"
:height="200"
:previewWidth="120"
:previewHeight="120"
shape="circle"
folder="clubs/{{ $club->id }}/instructors"
filename="instructor_{{ time() }}"
buttonText="Select Photo"
buttonClass="btn btn-outline-success btn-sm"
/>
</div>
<button type="submit" class="btn btn-primary w-100">Add Instructor</button>
</form>
</div>
</div>
</div>
</div>
@endsection
@@ -0,0 +1,187 @@
@extends('layouts.admin-club')
@section('club-admin-content')
<div>
<div class="d-flex flex-column flex-md-row justify-content-between align-items-start align-items-md-center gap-3 mb-4">
<div>
<h2 class="h3 fw-bold mb-1">Members</h2>
<p class="text-muted mb-0">Manage club members and subscriptions</p>
</div>
<div class="d-flex gap-2">
<button class="btn btn-outline-primary" data-bs-toggle="modal" data-bs-target="#walkInModal">
<i class="bi bi-person-walking me-2"></i>Walk-in
</button>
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addMemberModal">
<i class="bi bi-plus-lg me-2"></i>Add Member
</button>
</div>
</div>
<!-- Search & Filter -->
<div class="card border-0 shadow-sm mb-4">
<div class="card-body">
<div class="row g-3">
<div class="col-md-6">
<div class="input-group">
<span class="input-group-text bg-white border-end-0">
<i class="bi bi-search text-muted"></i>
</span>
<input type="text" class="form-control border-start-0" placeholder="Search members..." id="searchMembers">
</div>
</div>
<div class="col-md-3">
<select class="form-select" id="filterStatus">
<option value="">All Status</option>
<option value="active">Active</option>
<option value="expired">Expired</option>
<option value="pending">Pending</option>
</select>
</div>
<div class="col-md-3">
<select class="form-select" id="filterPackage">
<option value="">All Packages</option>
@foreach($packages ?? [] as $package)
<option value="{{ $package->id }}">{{ $package->name }}</option>
@endforeach
</select>
</div>
</div>
</div>
</div>
<!-- Members Table -->
@if(isset($members) && count($members) > 0)
<div class="card border-0 shadow-sm">
<div class="table-responsive">
<table class="table table-hover mb-0">
<thead class="bg-light">
<tr>
<th>Member</th>
<th>Package</th>
<th>Start Date</th>
<th>End Date</th>
<th>Status</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
@foreach($members as $member)
<tr>
<td>
<div class="d-flex align-items-center gap-3">
@if($member->user->profile_picture)
<img src="{{ asset('storage/' . $member->user->profile_picture) }}" alt="" class="rounded-circle" style="width: 40px; height: 40px; object-fit: cover;">
@else
<div class="rounded-circle bg-primary d-flex align-items-center justify-content-center" style="width: 40px; height: 40px;">
<span class="text-white fw-bold">{{ strtoupper(substr($member->user->full_name ?? 'M', 0, 1)) }}</span>
</div>
@endif
<div>
<p class="fw-semibold mb-0">{{ $member->user->full_name ?? 'N/A' }}</p>
<p class="text-muted small mb-0">{{ $member->user->email ?? '' }}</p>
</div>
</div>
</td>
<td>{{ $member->subscription->package->name ?? 'No package' }}</td>
<td>{{ $member->subscription->start_date ? $member->subscription->start_date->format('M d, Y') : 'N/A' }}</td>
<td>{{ $member->subscription->end_date ? $member->subscription->end_date->format('M d, Y') : 'N/A' }}</td>
<td>
@if($member->status === 'active')
<span class="badge bg-success">Active</span>
@elseif($member->status === 'expired')
<span class="badge bg-danger">Expired</span>
@else
<span class="badge bg-warning text-dark">{{ ucfirst($member->status) }}</span>
@endif
</td>
<td>
<div class="dropdown">
<button class="btn btn-sm btn-outline-secondary dropdown-toggle" data-bs-toggle="dropdown">
Actions
</button>
<ul class="dropdown-menu dropdown-menu-end">
<li><a class="dropdown-item" href="#"><i class="bi bi-eye me-2"></i>View</a></li>
<li><a class="dropdown-item" href="#"><i class="bi bi-pencil me-2"></i>Edit</a></li>
<li><a class="dropdown-item" href="#"><i class="bi bi-arrow-repeat me-2"></i>Renew</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item text-danger" href="#"><i class="bi bi-trash me-2"></i>Remove</a></li>
</ul>
</div>
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
@if($members instanceof \Illuminate\Pagination\LengthAwarePaginator)
<div class="card-footer bg-white border-0">
{{ $members->links() }}
</div>
@endif
</div>
@else
<div class="card border-0 shadow-sm">
<div class="card-body text-center py-5">
<i class="bi bi-people text-muted" style="font-size: 4rem;"></i>
<h5 class="mt-3 mb-2">No members yet</h5>
<p class="text-muted mb-3">Start adding members to your club</p>
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addMemberModal">
<i class="bi bi-plus-lg me-2"></i>Add Member
</button>
</div>
</div>
@endif
</div>
<!-- Add Member Modal -->
<div class="modal fade" id="addMemberModal" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header border-0">
<h5 class="modal-title">Add New Member</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<form action="{{ route('admin.club.members.store', $club->id) }}" method="POST">
@csrf
<div class="row g-3">
<div class="col-md-6">
<label class="form-label">Full Name</label>
<input type="text" name="full_name" class="form-control" required>
</div>
<div class="col-md-6">
<label class="form-label">Email</label>
<input type="email" name="email" class="form-control">
</div>
<div class="col-md-6">
<label class="form-label">Mobile</label>
<input type="text" name="mobile" class="form-control">
</div>
<div class="col-md-6">
<label class="form-label">Package</label>
<select name="package_id" class="form-select" required>
<option value="">Select package</option>
@foreach($packages ?? [] as $package)
<option value="{{ $package->id }}">{{ $package->name }} - {{ $club->currency ?? 'BHD' }} {{ number_format($package->price, 2) }}</option>
@endforeach
</select>
</div>
<div class="col-md-6">
<label class="form-label">Start Date</label>
<input type="date" name="start_date" class="form-control" value="{{ date('Y-m-d') }}" required>
</div>
<div class="col-md-6">
<label class="form-label">Payment Status</label>
<select name="payment_status" class="form-select">
<option value="paid">Paid</option>
<option value="pending">Pending</option>
</select>
</div>
</div>
<button type="submit" class="btn btn-primary w-100 mt-4">Add Member</button>
</form>
</div>
</div>
</div>
</div>
@endsection
@@ -0,0 +1,106 @@
@extends('layouts.admin-club')
@section('club-admin-content')
<div>
<div class="d-flex justify-content-between align-items-center mb-4">
<div>
<h2 class="h3 fw-bold mb-1">Messages</h2>
<p class="text-muted mb-0">Communicate with your members</p>
</div>
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#newMessageModal">
<i class="bi bi-plus-lg me-2"></i>New Message
</button>
</div>
<div class="row g-4">
<!-- Conversations List -->
<div class="col-lg-4">
<div class="card border-0 shadow-sm h-100">
<div class="card-header bg-white border-0">
<input type="text" class="form-control" placeholder="Search conversations...">
</div>
<div class="card-body p-0" style="max-height: 500px; overflow-y: auto;">
@if(isset($conversations) && count($conversations) > 0)
@foreach($conversations as $conversation)
<div class="d-flex align-items-center gap-3 p-3 border-bottom conversation-item {{ $conversation->unread ? 'bg-light' : '' }}" style="cursor: pointer;">
@if($conversation->user->profile_picture)
<img src="{{ asset('storage/' . $conversation->user->profile_picture) }}" alt="" class="rounded-circle" style="width: 45px; height: 45px; object-fit: cover;">
@else
<div class="rounded-circle bg-primary d-flex align-items-center justify-content-center" style="width: 45px; height: 45px;">
<span class="text-white fw-bold">{{ strtoupper(substr($conversation->user->full_name ?? 'U', 0, 1)) }}</span>
</div>
@endif
<div class="flex-grow-1 min-width-0">
<div class="d-flex justify-content-between align-items-center">
<p class="fw-semibold mb-0 text-truncate">{{ $conversation->user->full_name ?? 'Unknown' }}</p>
<small class="text-muted">{{ $conversation->last_message_at ? $conversation->last_message_at->diffForHumans(null, true) : '' }}</small>
</div>
<p class="text-muted small mb-0 text-truncate">{{ $conversation->last_message ?? 'No messages yet' }}</p>
</div>
@if($conversation->unread_count > 0)
<span class="badge bg-primary rounded-pill">{{ $conversation->unread_count }}</span>
@endif
</div>
@endforeach
@else
<div class="text-center py-5">
<i class="bi bi-chat-dots text-muted" style="font-size: 3rem;"></i>
<p class="text-muted mt-2 mb-0">No conversations yet</p>
</div>
@endif
</div>
</div>
</div>
<!-- Chat Area -->
<div class="col-lg-8">
<div class="card border-0 shadow-sm h-100">
<div class="card-body d-flex flex-column" style="min-height: 500px;">
<div class="text-center my-auto">
<i class="bi bi-chat-square-text text-muted" style="font-size: 4rem;"></i>
<h5 class="mt-3 mb-2">Select a conversation</h5>
<p class="text-muted">Choose a conversation from the list to start messaging</p>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- New Message Modal -->
<div class="modal fade" id="newMessageModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header border-0">
<h5 class="modal-title">New Message</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<form action="{{ route('admin.club.messages.send', $club->id) }}" method="POST">
@csrf
<div class="mb-3">
<label class="form-label">To</label>
<select name="recipient_id" class="form-select" required>
<option value="">Select member...</option>
@foreach($members ?? [] as $member)
<option value="{{ $member->user_id }}">{{ $member->user->full_name ?? 'Unknown' }}</option>
@endforeach
</select>
</div>
<div class="mb-3">
<label class="form-label">Message</label>
<textarea name="message" class="form-control" rows="4" required></textarea>
</div>
<button type="submit" class="btn btn-primary w-100">Send Message</button>
</form>
</div>
</div>
</div>
</div>
<style>
.conversation-item:hover {
background-color: hsl(var(--muted)) !important;
}
</style>
@endsection
@@ -0,0 +1,116 @@
@extends('layouts.admin-club')
@section('club-admin-content')
<div>
<div class="d-flex justify-content-between align-items-center mb-4">
<div>
<h2 class="h3 fw-bold mb-1">Packages</h2>
<p class="text-muted mb-0">Manage membership packages</p>
</div>
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addPackageModal">
<i class="bi bi-plus-lg me-2"></i>Add Package
</button>
</div>
@if(isset($packages) && count($packages) > 0)
<div class="row g-4">
@foreach($packages as $package)
<div class="col-md-6 col-lg-4">
<div class="card border-0 shadow-sm h-100">
<div class="card-body">
<div class="d-flex justify-content-between align-items-start mb-3">
<div>
<h5 class="fw-semibold mb-1">{{ $package->name }}</h5>
<span class="badge bg-primary">{{ $package->duration_days ?? 30 }} days</span>
</div>
@if($package->is_popular ?? false)
<span class="badge bg-warning text-dark">Popular</span>
@endif
</div>
<p class="text-muted small mb-3">{{ Str::limit($package->description, 100) }}</p>
<div class="mb-3">
<span class="h4 fw-bold text-primary">{{ $club->currency ?? 'BHD' }} {{ number_format($package->price, 2) }}</span>
<span class="text-muted">/ {{ $package->duration_days ?? 30 }} days</span>
</div>
@if($package->features)
<ul class="list-unstyled small mb-3">
@foreach(json_decode($package->features, true) ?? [] as $feature)
<li class="mb-1">
<i class="bi bi-check-circle text-success me-2"></i>{{ $feature }}
</li>
@endforeach
</ul>
@endif
<div class="d-flex gap-2">
<button class="btn btn-sm btn-outline-primary flex-grow-1">
<i class="bi bi-pencil me-1"></i>Edit
</button>
<button class="btn btn-sm btn-outline-danger">
<i class="bi bi-trash"></i>
</button>
</div>
</div>
</div>
</div>
@endforeach
</div>
@else
<div class="card border-0 shadow-sm">
<div class="card-body text-center py-5">
<i class="bi bi-box text-muted" style="font-size: 4rem;"></i>
<h5 class="mt-3 mb-2">No packages yet</h5>
<p class="text-muted mb-3">Create membership packages for your club</p>
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addPackageModal">
<i class="bi bi-plus-lg me-2"></i>Add Package
</button>
</div>
</div>
@endif
</div>
<!-- Add Package Modal -->
<div class="modal fade" id="addPackageModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header border-0">
<h5 class="modal-title">Add Package</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<form action="{{ route('admin.club.packages.store', $club->id) }}" method="POST">
@csrf
<div class="mb-3">
<label class="form-label">Package Name</label>
<input type="text" name="name" class="form-control" required>
</div>
<div class="mb-3">
<label class="form-label">Description</label>
<textarea name="description" class="form-control" rows="2"></textarea>
</div>
<div class="row g-3">
<div class="col-6">
<label class="form-label">Price ({{ $club->currency ?? 'BHD' }})</label>
<input type="number" name="price" class="form-control" step="0.01" required>
</div>
<div class="col-6">
<label class="form-label">Duration (days)</label>
<input type="number" name="duration_days" class="form-control" value="30" required>
</div>
</div>
<div class="mb-3 mt-3">
<div class="form-check">
<input type="checkbox" name="is_popular" class="form-check-input" id="isPopular">
<label class="form-check-label" for="isPopular">Mark as Popular</label>
</div>
</div>
<button type="submit" class="btn btn-primary w-100">Add Package</button>
</form>
</div>
</div>
</div>
</div>
@endsection
+152
View File
@@ -0,0 +1,152 @@
@extends('layouts.admin-club')
@section('club-admin-content')
<div>
<div class="d-flex justify-content-between align-items-center mb-4">
<div>
<h2 class="h3 fw-bold mb-1">Roles & Permissions</h2>
<p class="text-muted mb-0">Manage club staff roles and access</p>
</div>
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addRoleModal">
<i class="bi bi-plus-lg me-2"></i>Add Staff
</button>
</div>
<!-- Staff with Roles -->
@if(isset($staffMembers) && count($staffMembers) > 0)
<div class="card border-0 shadow-sm">
<div class="table-responsive">
<table class="table table-hover mb-0">
<thead class="bg-light">
<tr>
<th>Staff Member</th>
<th>Role</th>
<th>Added</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
@foreach($staffMembers as $staff)
<tr>
<td>
<div class="d-flex align-items-center gap-3">
@if($staff->user->profile_picture)
<img src="{{ asset('storage/' . $staff->user->profile_picture) }}" alt="" class="rounded-circle" style="width: 40px; height: 40px; object-fit: cover;">
@else
<div class="rounded-circle bg-primary d-flex align-items-center justify-content-center" style="width: 40px; height: 40px;">
<span class="text-white fw-bold">{{ strtoupper(substr($staff->user->full_name ?? 'S', 0, 1)) }}</span>
</div>
@endif
<div>
<p class="fw-semibold mb-0">{{ $staff->user->full_name ?? 'N/A' }}</p>
<p class="text-muted small mb-0">{{ $staff->user->email ?? '' }}</p>
</div>
</div>
</td>
<td>
@if($staff->role === 'admin')
<span class="badge bg-danger">Admin</span>
@elseif($staff->role === 'instructor')
<span class="badge bg-info">Instructor</span>
@else
<span class="badge bg-secondary">{{ ucfirst($staff->role) }}</span>
@endif
</td>
<td>{{ $staff->created_at ? $staff->created_at->format('M d, Y') : 'N/A' }}</td>
<td>
<div class="d-flex gap-1">
<button class="btn btn-sm btn-outline-primary">
<i class="bi bi-pencil"></i>
</button>
<button class="btn btn-sm btn-outline-danger">
<i class="bi bi-trash"></i>
</button>
</div>
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
@else
<div class="card border-0 shadow-sm">
<div class="card-body text-center py-5">
<i class="bi bi-shield-check text-muted" style="font-size: 4rem;"></i>
<h5 class="mt-3 mb-2">No staff roles assigned</h5>
<p class="text-muted mb-3">Add staff members and assign roles</p>
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addRoleModal">
<i class="bi bi-plus-lg me-2"></i>Add Staff
</button>
</div>
</div>
@endif
<!-- Available Roles Info -->
<div class="card border-0 shadow-sm mt-4">
<div class="card-header bg-white border-0">
<h5 class="fw-semibold mb-0">Available Roles</h5>
</div>
<div class="card-body">
<div class="row g-3">
<div class="col-md-4">
<div class="border rounded p-3">
<div class="d-flex align-items-center gap-2 mb-2">
<span class="badge bg-danger">Admin</span>
</div>
<p class="small text-muted mb-0">Full access to all club settings, members, and financials</p>
</div>
</div>
<div class="col-md-4">
<div class="border rounded p-3">
<div class="d-flex align-items-center gap-2 mb-2">
<span class="badge bg-info">Instructor</span>
</div>
<p class="small text-muted mb-0">Can manage activities, view members, and track attendance</p>
</div>
</div>
<div class="col-md-4">
<div class="border rounded p-3">
<div class="d-flex align-items-center gap-2 mb-2">
<span class="badge bg-secondary">Staff</span>
</div>
<p class="small text-muted mb-0">Limited access to member check-in and basic operations</p>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Add Staff Role Modal -->
<div class="modal fade" id="addRoleModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header border-0">
<h5 class="modal-title">Add Staff Member</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<form action="{{ route('admin.club.roles.store', $club->id) }}" method="POST">
@csrf
<div class="mb-3">
<label class="form-label">Search User</label>
<input type="text" class="form-control" placeholder="Search by email or name..." id="searchUser">
<input type="hidden" name="user_id" id="selectedUserId">
</div>
<div class="mb-3">
<label class="form-label">Role</label>
<select name="role" class="form-select" required>
<option value="">Select role</option>
<option value="admin">Admin</option>
<option value="instructor">Instructor</option>
<option value="staff">Staff</option>
</select>
</div>
<button type="submit" class="btn btn-primary w-100">Assign Role</button>
</form>
</div>
</div>
</div>
</div>
@endsection
@@ -26,7 +26,7 @@
data-club-name="{{ $club->club_name }}"
data-club-address="{{ $club->address ?? '' }}"
data-club-owner="{{ $club->owner->full_name ?? '' }}">
<a href="{{ route('admin.platform.clubs.edit', $club) }}" class="text-decoration-none">
<a href="{{ route('admin.club.dashboard', $club) }}" class="text-decoration-none">
<div class="card border shadow-sm overflow-hidden club-card" style="border-radius: 0; cursor: pointer; transition: all 0.3s ease;">
<!-- Cover Image -->
<div class="position-relative overflow-hidden" style="height: 192px;">
@@ -11,7 +11,7 @@
<h5 class="mb-0"><i class="bi bi-building me-2"></i>Club Information</h5>
</div>
<div class="card-body">
<form action="{{ route('admin.platform.clubs.store') }}" method="POST" enctype="multipart/form-data">
<form action="{{ route('admin.platform.clubs.store') }}" method="POST">
@csrf
<!-- Owner Selection -->
@@ -70,45 +70,50 @@
</div>
<div class="col-md-6">
<label for="phone_number" class="form-label">Phone Number</label>
<div class="input-group">
<select class="form-select" name="phone_code" style="max-width: 120px;">
<option value="+973" {{ old('phone_code') == '+973' ? 'selected' : '' }}>+973 (BH)</option>
<option value="+966" {{ old('phone_code') == '+966' ? 'selected' : '' }}>+966 (SA)</option>
<option value="+971" {{ old('phone_code') == '+971' ? 'selected' : '' }}>+971 (AE)</option>
<option value="+965" {{ old('phone_code') == '+965' ? 'selected' : '' }}>+965 (KW)</option>
</select>
<input type="text" class="form-control" name="phone_number" value="{{ old('phone_number') }}" placeholder="12345678">
</div>
<x-country-code-dropdown
name="phone_code"
id="phone_code"
:value="old('phone_code', '+973')"
:required="false"
:error="$errors->first('phone_code')">
<input type="text"
class="form-control @error('phone_number') is-invalid @enderror"
name="phone_number"
id="phone_number"
value="{{ old('phone_number') }}"
placeholder="12345678">
</x-country-code-dropdown>
@error('phone_number')
<div class="invalid-feedback d-block">{{ $message }}</div>
@enderror
</div>
</div>
<div class="row mb-3">
<div class="col-md-4">
<label for="currency" class="form-label">Currency</label>
<select class="form-select" id="currency" name="currency">
<option value="BHD" {{ old('currency', 'BHD') == 'BHD' ? 'selected' : '' }}>BHD - Bahrain Dinar</option>
<option value="SAR" {{ old('currency') == 'SAR' ? 'selected' : '' }}>SAR - Saudi Riyal</option>
<option value="AED" {{ old('currency') == 'AED' ? 'selected' : '' }}>AED - UAE Dirham</option>
<option value="KWD" {{ old('currency') == 'KWD' ? 'selected' : '' }}>KWD - Kuwaiti Dinar</option>
</select>
<x-currency-dropdown
name="currency"
id="currency"
:value="old('currency', 'BHD')"
:required="false"
:error="$errors->first('currency')" />
</div>
<div class="col-md-4">
<label for="timezone" class="form-label">Timezone</label>
<select class="form-select" id="timezone" name="timezone">
<option value="Asia/Bahrain" {{ old('timezone', 'Asia/Bahrain') == 'Asia/Bahrain' ? 'selected' : '' }}>Asia/Bahrain</option>
<option value="Asia/Riyadh" {{ old('timezone') == 'Asia/Riyadh' ? 'selected' : '' }}>Asia/Riyadh</option>
<option value="Asia/Dubai" {{ old('timezone') == 'Asia/Dubai' ? 'selected' : '' }}>Asia/Dubai</option>
<option value="Asia/Kuwait" {{ old('timezone') == 'Asia/Kuwait' ? 'selected' : '' }}>Asia/Kuwait</option>
</select>
<x-timezone-dropdown
name="timezone"
id="timezone"
:value="old('timezone', 'Asia/Bahrain')"
:required="false"
:error="$errors->first('timezone')" />
</div>
<div class="col-md-4">
<label for="country" class="form-label">Country</label>
<select class="form-select" id="country" name="country">
<option value="BH" {{ old('country', 'BH') == 'BH' ? 'selected' : '' }}>Bahrain</option>
<option value="SA" {{ old('country') == 'SA' ? 'selected' : '' }}>Saudi Arabia</option>
<option value="AE" {{ old('country') == 'AE' ? 'selected' : '' }}>United Arab Emirates</option>
<option value="KW" {{ old('country') == 'KW' ? 'selected' : '' }}>Kuwait</option>
</select>
<x-nationality-dropdown
name="country"
id="country"
label="Country"
:value="old('country', 'Bahrain')"
:required="false"
:error="$errors->first('country')" />
</div>
</div>
@@ -144,21 +149,47 @@
<h6 class="border-bottom pb-2 mb-3 mt-4">Branding</h6>
<div class="row mb-3">
<div class="col-md-6">
<label for="logo" class="form-label">Club Logo</label>
<input type="file" class="form-control @error('logo') is-invalid @enderror" id="logo" name="logo" accept="image/*">
<div class="col-md-6 text-center">
<label class="form-label d-block">Club Logo</label>
<x-takeone-cropper
id="create_club_logo"
mode="form"
inputName="logo"
:width="200"
:height="200"
:previewWidth="150"
:previewHeight="150"
shape="square"
folder="clubs/logos"
filename="logo_{{ time() }}"
buttonText="Select Logo"
buttonClass="btn btn-outline-success btn-sm"
/>
@error('logo')
<div class="invalid-feedback">{{ $message }}</div>
<div class="text-danger small mt-1">{{ $message }}</div>
@enderror
<small class="text-muted">Recommended: Square image, max 2MB</small>
<small class="text-muted d-block mt-2">Recommended: Square image, max 2MB</small>
</div>
<div class="col-md-6">
<label for="cover_image" class="form-label">Cover Image</label>
<input type="file" class="form-control @error('cover_image') is-invalid @enderror" id="cover_image" name="cover_image" accept="image/*">
<div class="col-md-6 text-center">
<label class="form-label d-block">Cover Image</label>
<x-takeone-cropper
id="create_club_cover"
mode="form"
inputName="cover_image"
:width="600"
:height="200"
:previewWidth="250"
:previewHeight="83"
shape="square"
folder="clubs/covers"
filename="cover_{{ time() }}"
buttonText="Select Cover"
buttonClass="btn btn-outline-success btn-sm"
/>
@error('cover_image')
<div class="invalid-feedback">{{ $message }}</div>
<div class="text-danger small mt-1">{{ $message }}</div>
@enderror
<small class="text-muted">Recommended: 1200x400px, max 2MB</small>
<small class="text-muted d-block mt-2">Recommended: 1200x400px, max 2MB</small>
</div>
</div>
@@ -401,5 +432,94 @@ $(document).ready(function() {
return null;
}
});
// Auto-fill currency, timezone, and phone code when country is selected
let countriesData = null;
// Load countries data once
fetch('/data/countries.json')
.then(response => response.json())
.then(countries => {
countriesData = countries;
})
.catch(error => console.error('Error loading countries for auto-fill:', error));
// Watch for changes on the country hidden input
const countryInput = document.getElementById('country');
if (countryInput) {
// Use MutationObserver to detect value changes on hidden input
const observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
if (mutation.type === 'attributes' && mutation.attributeName === 'value') {
autoFillFromCountry(countryInput.value);
}
});
});
observer.observe(countryInput, { attributes: true });
// Also listen for direct changes
countryInput.addEventListener('change', function() {
autoFillFromCountry(this.value);
});
// Check periodically for value changes (fallback)
let lastCountryValue = countryInput.value;
setInterval(function() {
if (countryInput.value !== lastCountryValue) {
lastCountryValue = countryInput.value;
autoFillFromCountry(countryInput.value);
}
}, 500);
}
function autoFillFromCountry(countryName) {
if (!countriesData || !countryName) return;
// Find the country in our data
const country = countriesData.find(c => c.name.toLowerCase() === countryName.toLowerCase());
if (!country) return;
// Update currency dropdown
const currencySelect = document.getElementById('currency');
if (currencySelect && country.currency) {
currencySelect.value = country.currency;
// Trigger change event for Select2
if (typeof $ !== 'undefined' && $.fn.select2) {
$(currencySelect).trigger('change');
}
}
// Update timezone dropdown
const timezoneSelect = document.getElementById('timezone');
if (timezoneSelect && country.timezone) {
timezoneSelect.value = country.timezone;
// Trigger change event for Select2
if (typeof $ !== 'undefined' && $.fn.select2) {
$(timezoneSelect).trigger('change');
}
}
// Update phone country code dropdown
const phoneCodeInput = document.getElementById('phone_code');
const phoneCodeFlag = document.getElementById('phone_codeSelectedFlag');
const phoneCodeCountry = document.getElementById('phone_codeSelectedCountry');
if (phoneCodeInput && country.call_code) {
phoneCodeInput.value = country.call_code;
if (phoneCodeFlag) {
// Convert ISO2 to flag emoji
const flagEmoji = country.iso2
.toUpperCase()
.split('')
.map(char => String.fromCodePoint(127397 + char.charCodeAt(0)))
.join('');
phoneCodeFlag.textContent = flagEmoji;
}
if (phoneCodeCountry) {
phoneCodeCountry.textContent = `${country.name} (${country.call_code})`;
}
}
console.log(`Auto-filled from country: ${countryName} -> Currency: ${country.currency}, Timezone: ${country.timezone}, Phone: ${country.call_code}`);
}
</script>
@endpush
@@ -12,7 +12,7 @@
<span class="badge bg-secondary">{{ $club->slug }}</span>
</div>
<div class="card-body">
<form action="{{ route('admin.platform.clubs.update', $club) }}" method="POST" enctype="multipart/form-data">
<form action="{{ route('admin.platform.clubs.update', $club) }}" method="POST">
@csrf
@method('PUT')
@@ -146,33 +146,45 @@
<h6 class="border-bottom pb-2 mb-3 mt-4">Branding</h6>
<div class="row mb-3">
<div class="col-md-6">
<label for="logo" class="form-label">Club Logo</label>
@if($club->logo)
<div class="mb-2">
<img src="{{ asset('storage/' . $club->logo) }}" alt="Current Logo" class="img-thumbnail" style="max-width: 100px;">
<small class="d-block text-muted">Current logo</small>
</div>
@endif
<input type="file" class="form-control @error('logo') is-invalid @enderror" id="logo" name="logo" accept="image/*">
@error('logo')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
<small class="text-muted">Leave empty to keep current logo</small>
<div class="col-md-6 text-center">
<label class="form-label d-block">Club Logo</label>
<x-image-upload
id="club_logo"
name="logo"
:width="200"
:height="200"
shape="square"
folder="clubs/{{ $club->id }}/logos"
filename="logo_{{ $club->id }}"
uploadUrl="{{ route('admin.platform.clubs.upload-logo', $club) }}"
:currentImage="$club->logo ? asset('storage/' . $club->logo) : ''"
placeholder="No logo"
placeholderIcon="bi-building"
buttonText="Change Logo"
buttonClass="btn btn-success btn-sm"
:rounded="false"
/>
<small class="text-muted d-block mt-2">Recommended: Square image, max 2MB</small>
</div>
<div class="col-md-6">
<label for="cover_image" class="form-label">Cover Image</label>
@if($club->cover_image)
<div class="mb-2">
<img src="{{ asset('storage/' . $club->cover_image) }}" alt="Current Cover" class="img-thumbnail" style="max-width: 200px;">
<small class="d-block text-muted">Current cover image</small>
</div>
@endif
<input type="file" class="form-control @error('cover_image') is-invalid @enderror" id="cover_image" name="cover_image" accept="image/*">
@error('cover_image')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
<small class="text-muted">Leave empty to keep current cover</small>
<div class="col-md-6 text-center">
<label class="form-label d-block">Cover Image</label>
<x-image-upload
id="club_cover"
name="cover_image"
:width="600"
:height="200"
shape="square"
folder="clubs/{{ $club->id }}/covers"
filename="cover_{{ $club->id }}"
uploadUrl="{{ route('admin.platform.clubs.upload-cover', $club) }}"
:currentImage="$club->cover_image ? asset('storage/' . $club->cover_image) : ''"
placeholder="No cover"
placeholderIcon="bi-image"
buttonText="Change Cover"
buttonClass="btn btn-success btn-sm"
:rounded="false"
/>
<small class="text-muted d-block mt-2">Recommended: 1200x400px, max 2MB</small>
</div>
</div>
+17 -101
View File
@@ -376,75 +376,23 @@
</div>
<!-- Gender -->
<div class="mb-3">
<label for="gender" class="form-label">Gender</label>
<select id="gender" class="form-select @error('gender') is-invalid @enderror"
name="gender" required>
<option value="">Select Gender</option>
<option value="m" {{ old('gender') == 'm' ? 'selected' : '' }}>♂️ Male</option>
<option value="f" {{ old('gender') == 'f' ? 'selected' : '' }}>♀️ Female</option>
</select>
@error('gender')
<span class="invalid-feedback" role="alert">
<strong>{{ $message }}</strong>
</span>
@enderror
</div>
<x-gender-dropdown
name="gender"
id="gender"
:value="old('gender')"
:required="true"
:error="$errors->first('gender')" />
<!-- Birthdate -->
<div class="mb-3">
<label for="birthdate" class="form-label">Birthdate</label>
<div class="row g-2">
<div class="col-4">
<select id="birth_day" class="form-select @error('birthdate') is-invalid @enderror" required>
<option value="">Day</option>
@for($day = 1; $day <= 31; $day++)
<option value="{{ str_pad($day, 2, '0', STR_PAD_LEFT) }}" {{ old('birth_day') == str_pad($day, 2, '0', STR_PAD_LEFT) ? 'selected' : '' }}>
{{ $day }}
</option>
@endfor
</select>
</div>
<div class="col-4">
<select id="birth_month" class="form-select @error('birthdate') is-invalid @enderror" required>
<option value="">Month</option>
<option value="01" {{ old('birth_month') == '01' ? 'selected' : '' }}>January</option>
<option value="02" {{ old('birth_month') == '02' ? 'selected' : '' }}>February</option>
<option value="03" {{ old('birth_month') == '03' ? 'selected' : '' }}>March</option>
<option value="04" {{ old('birth_month') == '04' ? 'selected' : '' }}>April</option>
<option value="05" {{ old('birth_month') == '05' ? 'selected' : '' }}>May</option>
<option value="06" {{ old('birth_month') == '06' ? 'selected' : '' }}>June</option>
<option value="07" {{ old('birth_month') == '07' ? 'selected' : '' }}>July</option>
<option value="08" {{ old('birth_month') == '08' ? 'selected' : '' }}>August</option>
<option value="09" {{ old('birth_month') == '09' ? 'selected' : '' }}>September</option>
<option value="10" {{ old('birth_month') == '10' ? 'selected' : '' }}>October</option>
<option value="11" {{ old('birth_month') == '11' ? 'selected' : '' }}>November</option>
<option value="12" {{ old('birth_month') == '12' ? 'selected' : '' }}>December</option>
</select>
</div>
<div class="col-4">
<select id="birth_year" class="form-select @error('birthdate') is-invalid @enderror" required>
<option value="">Year</option>
@php
$currentYear = date('Y');
$startYear = $currentYear - 10; // Start from 10 years ago
$endYear = 1900;
@endphp
@for($year = $startYear; $year >= $endYear; $year--)
<option value="{{ $year }}" {{ old('birth_year') == $year ? 'selected' : '' }}>
{{ $year }}
</option>
@endfor
</select>
</div>
</div>
<input type="hidden" id="birthdate" name="birthdate" value="{{ old('birthdate') }}">
@error('birthdate')
<span class="invalid-feedback d-block" role="alert">
<strong>{{ $message }}</strong>
</span>
@enderror
</div>
<x-birthdate-dropdown
name="birthdate"
id="birthdate"
label="Birthdate"
:value="old('birthdate')"
:required="true"
:min-age="10"
:max-age="120"
:error="$errors->first('birthdate')" />
<!-- Nationality -->
<div class="mb-3">
@@ -467,39 +415,6 @@
<script>
document.addEventListener('DOMContentLoaded', function() {
// Combine birth date dropdowns into hidden field
const birthDay = document.getElementById('birth_day');
const birthMonth = document.getElementById('birth_month');
const birthYear = document.getElementById('birth_year');
const birthdateHidden = document.getElementById('birthdate');
function updateBirthdate() {
const day = birthDay.value;
const month = birthMonth.value;
const year = birthYear.value;
if (day && month && year) {
birthdateHidden.value = `${year}-${month}-${day}`;
} else {
birthdateHidden.value = '';
}
}
// Update hidden field when any dropdown changes
birthDay.addEventListener('change', updateBirthdate);
birthMonth.addEventListener('change', updateBirthdate);
birthYear.addEventListener('change', updateBirthdate);
// Initialize from old value if exists
if (birthdateHidden.value) {
const parts = birthdateHidden.value.split('-');
if (parts.length === 3) {
birthYear.value = parts[0];
birthMonth.value = parts[1];
birthDay.value = parts[2];
}
}
// Error handler
window.onerror = function(message, source, lineno, colno, error) {
console.error('JavaScript Error:', message);
@@ -509,12 +424,13 @@
};
});
// Form submission handler
// Form submission handler (for debugging)
document.getElementById('registrationForm').addEventListener('submit', function(e) {
console.log('Form submitting...');
console.log('Country code:', document.getElementById('country_code').value);
console.log('Nationality:', document.getElementById('nationality').value);
console.log('Mobile number:', document.getElementById('mobile_number').value);
console.log('Gender:', document.getElementById('gender').value);
console.log('Birthdate:', document.getElementById('birthdate').value);
});
</script>
@@ -0,0 +1,131 @@
@props([
'name' => 'date',
'id' => 'date',
'value' => '',
'required' => false,
'error' => null,
'label' => 'Date',
'minYear' => null,
'maxYear' => null,
'minAge' => null,
'maxAge' => null
])
@php
$currentYear = date('Y');
// Calculate year range - support both direct year values and age-based calculation
if ($minYear !== null && $maxYear !== null) {
// Direct year range specified
$startYear = $maxYear;
$endYear = $minYear;
} elseif ($minAge !== null && $maxAge !== null) {
// Age-based calculation (for birthdates)
$startYear = $currentYear - $minAge;
$endYear = $currentYear - $maxAge;
} else {
// Default: current year to 100 years ago
$startYear = $currentYear;
$endYear = $currentYear - 100;
}
// Parse existing value
$selectedDay = '';
$selectedMonth = '';
$selectedYear = '';
if ($value) {
$parts = explode('-', $value);
if (count($parts) === 3) {
$selectedYear = $parts[0];
$selectedMonth = $parts[1];
$selectedDay = $parts[2];
}
}
@endphp
<div class="mb-3">
<label class="form-label">{{ $label }}@if($required) <span class="text-danger">*</span>@endif</label>
<div class="row g-2">
<div class="col-4">
<select id="{{ $id }}_day"
class="form-select @if($error) is-invalid @endif"
{{ $required ? 'required' : '' }}
onchange="updateDate_{{ $id }}()">
<option value="">Day</option>
@for($day = 1; $day <= 31; $day++)
@php $dayPadded = str_pad($day, 2, '0', STR_PAD_LEFT); @endphp
<option value="{{ $dayPadded }}" {{ $selectedDay == $dayPadded ? 'selected' : '' }}>
{{ $day }}
</option>
@endfor
</select>
</div>
<div class="col-4">
<select id="{{ $id }}_month"
class="form-select @if($error) is-invalid @endif"
{{ $required ? 'required' : '' }}
onchange="updateDate_{{ $id }}()">
<option value="">Month</option>
<option value="01" {{ $selectedMonth == '01' ? 'selected' : '' }}>January</option>
<option value="02" {{ $selectedMonth == '02' ? 'selected' : '' }}>February</option>
<option value="03" {{ $selectedMonth == '03' ? 'selected' : '' }}>March</option>
<option value="04" {{ $selectedMonth == '04' ? 'selected' : '' }}>April</option>
<option value="05" {{ $selectedMonth == '05' ? 'selected' : '' }}>May</option>
<option value="06" {{ $selectedMonth == '06' ? 'selected' : '' }}>June</option>
<option value="07" {{ $selectedMonth == '07' ? 'selected' : '' }}>July</option>
<option value="08" {{ $selectedMonth == '08' ? 'selected' : '' }}>August</option>
<option value="09" {{ $selectedMonth == '09' ? 'selected' : '' }}>September</option>
<option value="10" {{ $selectedMonth == '10' ? 'selected' : '' }}>October</option>
<option value="11" {{ $selectedMonth == '11' ? 'selected' : '' }}>November</option>
<option value="12" {{ $selectedMonth == '12' ? 'selected' : '' }}>December</option>
</select>
</div>
<div class="col-4">
<select id="{{ $id }}_year"
class="form-select @if($error) is-invalid @endif"
{{ $required ? 'required' : '' }}
onchange="updateDate_{{ $id }}()">
<option value="">Year</option>
@for($year = $startYear; $year >= $endYear; $year--)
<option value="{{ $year }}" {{ $selectedYear == $year ? 'selected' : '' }}>
{{ $year }}
</option>
@endfor
</select>
</div>
</div>
<input type="hidden" id="{{ $id }}" name="{{ $name }}" value="{{ $value }}">
@if($error)
<span class="invalid-feedback d-block" role="alert">
<strong>{{ $error }}</strong>
</span>
@endif
</div>
@once
@push('scripts')
<script>
document.addEventListener('DOMContentLoaded', function() {
// Initialize all date dropdown components
document.querySelectorAll('[id$="_day"]').forEach(function(daySelect) {
const baseId = daySelect.id.replace('_day', '');
if (document.getElementById(baseId + '_month') && document.getElementById(baseId + '_year')) {
// Component exists, initialize it
window['updateDate_' + baseId] = function() {
const day = document.getElementById(baseId + '_day').value;
const month = document.getElementById(baseId + '_month').value;
const year = document.getElementById(baseId + '_year').value;
const hidden = document.getElementById(baseId);
if (day && month && year) {
hidden.value = `${year}-${month}-${day}`;
} else {
hidden.value = '';
}
};
}
});
});
</script>
@endpush
@endonce
@@ -22,7 +22,7 @@
onkeydown="event.stopPropagation()"
onkeyup="event.stopPropagation()">
<div class="country-list" id="{{ $id }}List" style="max-height: 300px; overflow-y: auto;">
<div class="country-list country-code-dropdown-list" id="{{ $id }}List" data-component-id="{{ $id }}" style="max-height: 300px; overflow-y: auto;">
<!-- Countries will be populated by JavaScript -->
</div>
</div>
@@ -162,9 +162,9 @@
}
}
// Initialize only country code dropdowns (not nationality dropdowns)
document.querySelectorAll('[id$="country_codeList"]').forEach(function(listElement) {
const componentId = listElement.id.replace('List', '');
// Initialize all country code dropdowns
document.querySelectorAll('.country-code-dropdown-list').forEach(function(listElement) {
const componentId = listElement.getAttribute('data-component-id');
initializeCountryDropdown(componentId, countries);
});
@@ -3,8 +3,10 @@
<div class="mb-3">
<label for="{{ $id }}" class="form-label">Currency</label>
<select id="{{ $id }}"
class="form-select currency-select @error($name) is-invalid @enderror"
class="form-select currency-dropdown-select @error($name) is-invalid @enderror"
name="{{ $name }}"
data-component-id="{{ $id }}"
data-initial-value="{{ $value }}"
{{ $required ? 'required' : '' }}>
<option value="">Select Currency</option>
</select>
@@ -23,60 +25,60 @@
fetch('/data/countries.json')
.then(response => response.json())
.then(countries => {
const selectElement = document.getElementById('{{ $id }}');
if (!selectElement) return;
// Initialize all currency dropdowns
document.querySelectorAll('.currency-dropdown-select').forEach(function(selectElement) {
const initialValue = selectElement.getAttribute('data-initial-value');
// Get unique currencies
const uniqueCurrencies = {};
countries.forEach(country => {
if (!uniqueCurrencies[country.currency]) {
uniqueCurrencies[country.currency] = {
currency: country.currency,
currency_symbol: country.currency_symbol,
flag: country.flag,
name: country.name
};
}
}
});
// Populate dropdown
Object.values(uniqueCurrencies).forEach(currencyData => {
const option = document.createElement('option');
option.value = currencyData.currency;
option.textContent = `${currencyData.name} (${currencyData.currency} ${currencyData.currency_symbol})`;
option.setAttribute('data-flag', currencyData.flag);
selectElement.appendChild(option);
});
// Set initial value if provided
const initialValue = '{{ $value }}';
if (initialValue) {
selectElement.value = initialValue;
}
// Initialize Select2 for searchable dropdown
if (typeof $ !== 'undefined' && $.fn.select2) {
$(selectElement).select2({
templateResult: function(state) {
if (!state.id) {
return state.text;
}
const option = $(state.element);
const flagCode = option.data('flag');
return $(`<span><span class="fi fi-${flagCode} me-2"></span>${state.text}</span>`);
},
templateSelection: function(state) {
if (!state.id) {
return state.text;
}
const option = $(state.element);
const flagCode = option.data('flag');
return $(`<span><span class="fi fi-${flagCode} me-2"></span>${state.text}</span>`);
},
width: '100%'
// Get unique currencies
const uniqueCurrencies = {};
countries.forEach(country => {
if (country.currency && !uniqueCurrencies[country.currency]) {
uniqueCurrencies[country.currency] = {
currency: country.currency,
currency_symbol: country.currency_symbol || '',
flag: country.flag,
name: country.name
};
}
});
}
// Populate dropdown
Object.values(uniqueCurrencies).forEach(currencyData => {
const option = document.createElement('option');
option.value = currencyData.currency;
option.textContent = `${currencyData.currency} - ${currencyData.name} ${currencyData.currency_symbol}`;
option.setAttribute('data-flag', currencyData.flag);
selectElement.appendChild(option);
});
// Set initial value if provided
if (initialValue) {
selectElement.value = initialValue;
}
// Initialize Select2 for searchable dropdown
if (typeof $ !== 'undefined' && $.fn.select2) {
$(selectElement).select2({
templateResult: function(state) {
if (!state.id) {
return state.text;
}
const option = $(state.element);
const flagCode = option.data('flag');
return $(`<span><span class="fi fi-${flagCode} me-2"></span>${state.text}</span>`);
},
templateSelection: function(state) {
if (!state.id) {
return state.text;
}
const option = $(state.element);
const flagCode = option.data('flag');
return $(`<span><span class="fi fi-${flagCode} me-2"></span>${state.text}</span>`);
},
width: '100%'
});
}
});
})
.catch(error => console.error('Error loading countries:', error));
});
@@ -0,0 +1,18 @@
@props(['name' => 'gender', 'id' => 'gender', 'value' => '', 'required' => false, 'error' => null])
<div class="mb-3">
<label for="{{ $id }}" class="form-label">Gender</label>
<select id="{{ $id }}"
class="form-select @if($error) is-invalid @endif"
name="{{ $name }}"
{{ $required ? 'required' : '' }}>
<option value="">Select Gender</option>
<option value="m" {{ $value == 'm' ? 'selected' : '' }}>Male</option>
<option value="f" {{ $value == 'f' ? 'selected' : '' }}>Female</option>
</select>
@if($error)
<span class="invalid-feedback" role="alert">
<strong>{{ $error }}</strong>
</span>
@endif
</div>
@@ -0,0 +1,84 @@
@props([
'id' => 'image_upload_' . uniqid(),
'name' => 'image',
'width' => 300,
'height' => 300,
'shape' => 'square',
'folder' => 'uploads',
'filename' => 'image_' . time(),
'uploadUrl' => '',
'currentImage' => '',
'placeholder' => 'No image',
'placeholderIcon' => 'bi-image',
'buttonText' => 'Change Photo',
'buttonClass' => 'btn btn-success',
'previewClass' => '',
'showPreview' => true,
'rounded' => false
])
@php
$cacheBuster = $currentImage ? '?v=' . time() : '';
$imageUrl = $currentImage ? $currentImage . $cacheBuster : '';
$borderRadius = $rounded ? '50%' : '8px';
$previewWidth = $width . 'px';
$previewHeight = $height . 'px';
@endphp
<div class="image-upload-component text-center {{ $previewClass }}">
@if($showPreview)
<div class="image-preview mb-3">
@if($imageUrl)
<img src="{{ $imageUrl }}"
alt="Preview"
id="{{ $id }}_preview"
class="image-upload-preview"
style="width: {{ $previewWidth }}; height: {{ $previewHeight }}; object-fit: cover; border: 3px solid #dee2e6; border-radius: {{ $borderRadius }};">
@else
<div class="image-placeholder"
id="{{ $id }}_placeholder"
style="width: {{ $previewWidth }}; height: {{ $previewHeight }}; background-color: #f0f0f0; border: 3px solid #dee2e6; border-radius: {{ $borderRadius }}; display: flex; align-items: center; justify-content: center; margin: 0 auto;">
<div class="text-center">
<i class="bi {{ $placeholderIcon }}" style="font-size: 60px; color: #dee2e6;"></i>
<p class="text-muted mt-2 mb-0">{{ $placeholder }}</p>
</div>
</div>
@endif
</div>
@endif
<x-takeone-cropper
:id="$id"
:width="$width"
:height="$height"
:shape="$shape"
:folder="$folder"
:filename="$filename"
:uploadUrl="$uploadUrl"
:currentImage="$currentImage"
:buttonText="$buttonText"
:buttonClass="$buttonClass"
/>
</div>
@once
@push('styles')
<style>
.image-upload-component {
display: inline-block;
}
.image-upload-preview {
transition: opacity 0.3s ease;
}
.image-upload-preview:hover {
opacity: 0.9;
}
.image-placeholder {
transition: all 0.3s ease;
}
.image-placeholder:hover {
background-color: #e9ecef !important;
}
</style>
@endpush
@endonce
@@ -26,7 +26,7 @@
onkeydown="event.stopPropagation()"
onkeyup="event.stopPropagation()">
<div class="country-list" id="{{ $id }}List" style="max-height: 300px; overflow-y: auto;">
<div class="country-list nationality-dropdown-list" id="{{ $id }}List" data-component-id="{{ $id }}" style="max-height: 300px; overflow-y: auto;">
<!-- Countries will be populated by JavaScript -->
</div>
</div>
@@ -72,9 +72,9 @@
fetch('/data/countries.json')
.then(response => response.json())
.then(countries => {
// Initialize only nationality dropdowns (not country code dropdowns)
document.querySelectorAll('[id$="nationalityList"]').forEach(function(listElement) {
const componentId = listElement.id.replace('List', '');
// Initialize all nationality/country dropdowns by finding elements with data attribute
document.querySelectorAll('.nationality-dropdown-list').forEach(function(listElement) {
const componentId = listElement.getAttribute('data-component-id');
initializeNationalityDropdown(componentId, countries);
});
})
@@ -3,8 +3,10 @@
<div class="mb-3">
<label for="{{ $id }}" class="form-label">Timezone</label>
<select id="{{ $id }}"
class="form-select timezone-select @error($name) is-invalid @enderror"
class="form-select timezone-dropdown-select @error($name) is-invalid @enderror"
name="{{ $name }}"
data-component-id="{{ $id }}"
data-initial-value="{{ $value }}"
{{ $required ? 'required' : '' }}>
<option value="">Select Timezone</option>
</select>
@@ -23,59 +25,59 @@
fetch('/data/countries.json')
.then(response => response.json())
.then(countries => {
const selectElement = document.getElementById('{{ $id }}');
if (!selectElement) return;
// Initialize all timezone dropdowns
document.querySelectorAll('.timezone-dropdown-select').forEach(function(selectElement) {
const initialValue = selectElement.getAttribute('data-initial-value');
// Get unique timezones
const uniqueTimezones = {};
countries.forEach(country => {
if (!uniqueTimezones[country.timezone]) {
uniqueTimezones[country.timezone] = {
timezone: country.timezone,
flag: country.flag,
name: country.name
};
}
}
});
// Populate dropdown
Object.values(uniqueTimezones).forEach(timezoneData => {
const option = document.createElement('option');
option.value = timezoneData.timezone;
option.textContent = `${timezoneData.name} (${timezoneData.timezone})`;
option.setAttribute('data-flag', timezoneData.flag);
selectElement.appendChild(option);
});
// Set initial value if provided
const initialValue = '{{ $value }}';
if (initialValue) {
selectElement.value = initialValue;
}
// Initialize Select2 for searchable dropdown
if (typeof $ !== 'undefined' && $.fn.select2) {
$(selectElement).select2({
templateResult: function(state) {
if (!state.id) {
return state.text;
}
const option = $(state.element);
const flagCode = option.data('flag');
return $(`<span><span class="fi fi-${flagCode} me-2"></span>${state.text}</span>`);
},
templateSelection: function(state) {
if (!state.id) {
return state.text;
}
const option = $(state.element);
const flagCode = option.data('flag');
return $(`<span><span class="fi fi-${flagCode} me-2"></span>${state.text}</span>`);
},
width: '100%'
// Get unique timezones
const uniqueTimezones = {};
countries.forEach(country => {
if (country.timezone && !uniqueTimezones[country.timezone]) {
uniqueTimezones[country.timezone] = {
timezone: country.timezone,
flag: country.flag,
name: country.name
};
}
});
}
// Populate dropdown
Object.values(uniqueTimezones).forEach(timezoneData => {
const option = document.createElement('option');
option.value = timezoneData.timezone;
option.textContent = `${timezoneData.name} (${timezoneData.timezone})`;
option.setAttribute('data-flag', timezoneData.flag);
selectElement.appendChild(option);
});
// Set initial value if provided
if (initialValue) {
selectElement.value = initialValue;
}
// Initialize Select2 for searchable dropdown
if (typeof $ !== 'undefined' && $.fn.select2) {
$(selectElement).select2({
templateResult: function(state) {
if (!state.id) {
return state.text;
}
const option = $(state.element);
const flagCode = option.data('flag');
return $(`<span><span class="fi fi-${flagCode} me-2"></span>${state.text}</span>`);
},
templateSelection: function(state) {
if (!state.id) {
return state.text;
}
const option = $(state.element);
const flagCode = option.data('flag');
return $(`<span><span class="fi fi-${flagCode} me-2"></span>${state.text}</span>`);
},
width: '100%'
});
}
});
})
.catch(error => console.error('Error loading countries:', error));
});
@@ -0,0 +1,227 @@
@props(['position' => 'top-right'])
@once
@push('styles')
<style>
/* Toast Notification Styles */
.toast-container {
position: fixed;
z-index: 9999;
pointer-events: none;
}
.toast-container.top-right {
top: 20px;
right: 20px;
}
.toast-container.top-left {
top: 20px;
left: 20px;
}
.toast-container.top-center {
top: 20px;
left: 50%;
transform: translateX(-50%);
}
.toast-container.bottom-right {
bottom: 20px;
right: 20px;
}
.toast-container.bottom-left {
bottom: 20px;
left: 20px;
}
.toast-container.bottom-center {
bottom: 20px;
left: 50%;
transform: translateX(-50%);
}
.toast-notification {
display: flex;
align-items: center;
gap: 12px;
padding: 16px 20px;
border-radius: 12px;
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.2);
min-width: 300px;
max-width: 400px;
margin-bottom: 10px;
pointer-events: auto;
animation: toastSlideIn 0.3s ease-out;
}
.toast-notification.hiding {
animation: toastFadeOut 0.3s ease-in forwards;
}
.toast-notification.success {
background: linear-gradient(135deg, #10b981 0%, #059669 100%);
color: white;
}
.toast-notification.error {
background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
color: white;
}
.toast-notification.warning {
background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%);
color: white;
}
.toast-notification.info {
background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%);
color: white;
}
.toast-notification .toast-icon {
width: 32px;
height: 32px;
border-radius: 50%;
background: rgba(255, 255, 255, 0.2);
display: flex;
align-items: center;
justify-content: center;
font-size: 16px;
flex-shrink: 0;
}
.toast-notification .toast-content {
flex: 1;
}
.toast-notification .toast-title {
font-weight: 600;
font-size: 15px;
margin-bottom: 2px;
}
.toast-notification .toast-message {
font-size: 13px;
opacity: 0.9;
}
.toast-notification .toast-close {
background: rgba(255, 255, 255, 0.2);
border: none;
color: white;
cursor: pointer;
padding: 4px 8px;
font-size: 16px;
line-height: 1;
border-radius: 6px;
transition: background 0.2s;
}
.toast-notification .toast-close:hover {
background: rgba(255, 255, 255, 0.3);
}
.toast-notification .toast-progress {
position: absolute;
bottom: 0;
left: 0;
height: 3px;
background: rgba(255, 255, 255, 0.4);
border-radius: 0 0 12px 12px;
animation: toastProgress 3s linear forwards;
}
@keyframes toastSlideIn {
from {
transform: translateX(100%);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
@keyframes toastFadeOut {
from {
opacity: 1;
transform: translateX(0);
}
to {
opacity: 0;
transform: translateX(100%);
}
}
@keyframes toastProgress {
from { width: 100%; }
to { width: 0%; }
}
</style>
@endpush
@push('scripts')
<script>
const Toast = {
container: null,
position: '{{ $position }}',
init() {
if (!this.container) {
this.container = document.createElement('div');
this.container.className = `toast-container ${this.position}`;
document.body.appendChild(this.container);
}
return this.container;
},
show(type, title, message, duration = 3000) {
const container = this.init();
const icons = {
success: '<i class="bi bi-check-lg"></i>',
error: '<i class="bi bi-x-lg"></i>',
warning: '<i class="bi bi-exclamation-lg"></i>',
info: '<i class="bi bi-info-lg"></i>'
};
const toast = document.createElement('div');
toast.className = `toast-notification ${type}`;
toast.style.position = 'relative';
toast.innerHTML = `
<span class="toast-icon">${icons[type] || icons.info}</span>
<div class="toast-content">
<div class="toast-title">${title}</div>
<div class="toast-message">${message}</div>
</div>
<button class="toast-close" aria-label="Close">×</button>
<div class="toast-progress" style="animation-duration: ${duration}ms"></div>
`;
// Close button handler
toast.querySelector('.toast-close').addEventListener('click', () => {
this.hide(toast);
});
container.appendChild(toast);
// Auto remove
if (duration > 0) {
setTimeout(() => this.hide(toast), duration);
}
return toast;
},
hide(toast) {
if (!toast || !toast.parentElement) return;
toast.classList.add('hiding');
setTimeout(() => {
if (toast.parentElement) toast.remove();
}, 300);
},
success(title, message, duration) {
return this.show('success', title, message, duration);
},
error(title, message, duration) {
return this.show('error', title, message, duration);
},
warning(title, message, duration) {
return this.show('warning', title, message, duration);
},
info(title, message, duration) {
return this.show('info', title, message, duration);
}
};
// Global function for backward compatibility
function showToast(type, title, message, duration = 3000) {
return Toast.show(type, title, message, duration);
}
</script>
@endpush
@endonce
+30 -98
View File
@@ -30,44 +30,43 @@
<div class="mb-3">
<label for="mobile" class="form-label">Mobile Number</label>
<div class="input-group" onclick="event.stopPropagation()">
<button class="btn btn-outline-secondary dropdown-toggle country-dropdown-btn d-flex align-items-center" type="button" id="country_codeDropdown" data-bs-toggle="dropdown" aria-expanded="false">
<span class="fi fi-bh me-2" id="country_codeSelectedFlag"></span>
<span class="country-label" id="country_codeSelectedCountry">{{ old('mobile_code', '+973') }}</span>
</button>
<div class="dropdown-menu p-2" aria-labelledby="country_codeDropdown" style="min-width: 200px;" onclick="event.stopPropagation()">
<input type="text" class="form-control form-control-sm mb-2" placeholder="Search country..." id="country_codeSearch" onkeydown="event.stopPropagation()">
<div class="country-list" id="country_codeList" style="max-height: 300px; overflow-y: auto;"></div>
</div>
<input type="hidden" id="country_code" name="mobile_code" value="{{ old('mobile_code', '+973') }}">
<input id="mobile_number" type="tel" class="form-control @error('mobile') is-invalid @enderror" name="mobile" value="{{ old('mobile') }}" autocomplete="tel" placeholder="Phone number">
</div>
<x-country-code-dropdown
name="mobile_code"
id="country_code"
:value="old('mobile_code', '+973')"
:required="false"
:error="$errors->first('mobile_code')">
<input id="mobile_number" type="tel"
class="form-control @error('mobile') is-invalid @enderror"
name="mobile"
value="{{ old('mobile') }}"
autocomplete="tel"
placeholder="Phone number">
</x-country-code-dropdown>
@error('mobile')
<div class="invalid-feedback d-block">{{ $message }}</div>
@enderror
@error('mobile_code')
<div class="invalid-feedback d-block">{{ $message }}</div>
@enderror
</div>
<div class="row mb-3">
<div class="col-md-6">
<label for="gender" class="form-label">Gender</label>
<select class="form-select @error('gender') is-invalid @enderror" id="gender" name="gender" required>
<option value="">Select Gender</option>
<option value="m" {{ old('gender') == 'm' ? 'selected' : '' }}>Male</option>
<option value="f" {{ old('gender') == 'f' ? 'selected' : '' }}>Female</option>
</select>
@error('gender')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
<x-gender-dropdown
name="gender"
id="gender"
:value="old('gender')"
:required="true"
:error="$errors->first('gender')" />
</div>
<div class="col-md-6">
<label for="birthdate" class="form-label">Birthdate</label>
<input type="date" class="form-control @error('birthdate') is-invalid @enderror" id="birthdate" name="birthdate" value="{{ old('birthdate') }}" required>
@error('birthdate')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
<x-birthdate-dropdown
name="birthdate"
id="birthdate"
label="Birthdate"
:value="old('birthdate')"
:required="true"
:min-age="0"
:max-age="120"
:error="$errors->first('birthdate')" />
</div>
</div>
@@ -91,7 +90,7 @@
@enderror
</div>
<div class="col-md-6">
<x-country-dropdown
<x-nationality-dropdown
name="nationality"
id="nationality"
:value="old('nationality')"
@@ -205,6 +204,7 @@
</div>
</div>
@push('scripts')
<script>
document.addEventListener('DOMContentLoaded', function() {
let socialLinkIndex = {{ count($formLinks ?? []) }};
@@ -271,74 +271,6 @@ document.addEventListener('DOMContentLoaded', function() {
}
});
</script>
@push('scripts')
<script>
document.addEventListener('DOMContentLoaded', function() {
// Load countries for mobile code dropdown
fetch('/data/countries.json')
.then(response => response.json())
.then(countries => {
const listElement = document.getElementById('country_codeList');
const selectedFlag = document.getElementById('country_codeSelectedFlag');
const selectedCountry = document.getElementById('country_codeSelectedCountry');
const hiddenInput = document.getElementById('country_code');
const searchInput = document.getElementById('country_codeSearch');
if (!listElement) return;
// Populate dropdown
countries.forEach(country => {
const button = document.createElement('button');
button.className = 'dropdown-item d-flex align-items-center';
button.type = 'button';
button.setAttribute('data-country-code', country.call_code);
button.setAttribute('data-country-name', country.name);
button.setAttribute('data-flag-code', country.flag);
button.setAttribute('data-search', `${country.name.toLowerCase()} ${country.call_code}`);
button.innerHTML = `<span class="fi fi-${country.flag.toLowerCase()} me-2"></span><span>${country.name} (${country.call_code})</span>`;
button.addEventListener('click', function() {
const code = this.getAttribute('data-country-code');
const flag = this.getAttribute('data-flag-code');
const name = this.getAttribute('data-country-name');
selectedFlag.className = `fi fi-${flag.toLowerCase()} me-2`;
selectedCountry.textContent = code;
hiddenInput.value = code;
// Close dropdown
const dropdown = bootstrap.Dropdown.getInstance(document.getElementById('country_codeDropdown'));
if (dropdown) dropdown.hide();
});
listElement.appendChild(button);
});
// Set initial value
const initialValue = '{{ old('mobile_code', '+973') }}';
if (initialValue) {
hiddenInput.value = initialValue;
selectedCountry.textContent = initialValue;
const country = countries.find(c => c.call_code === initialValue);
if (country) {
selectedFlag.className = `fi fi-${country.flag.toLowerCase()} me-2`;
}
}
// Search functionality
searchInput.addEventListener('input', function() {
const searchTerm = this.value.toLowerCase();
const items = listElement.querySelectorAll('.dropdown-item');
items.forEach(item => {
const searchData = item.getAttribute('data-search');
if (searchData.includes(searchTerm)) {
item.style.display = '';
} else {
item.style.display = 'none';
}
});
});
})
.catch(error => console.error('Error loading countries:', error));
});
</script>
@endpush
@endsection
+8 -404
View File
@@ -160,223 +160,22 @@
<!-- Add New Family Member Card -->
<div class="col">
<div class="card h-100 shadow-sm border-dashed add-card">
<div class="card-body text-center text-decoration-none d-flex flex-column justify-content-center align-items-center" style="height: 100%; cursor: pointer;" data-bs-toggle="modal" data-bs-target="#addFamilyMemberModal">
<div class="mb-3">
<i class="bi bi-plus-circle" style="font-size: 3rem;"></i>
<a href="{{ route('family.create') }}" class="text-decoration-none">
<div class="card h-100 shadow-sm border-dashed add-card">
<div class="card-body text-center d-flex flex-column justify-content-center align-items-center" style="height: 100%; cursor: pointer;">
<div class="mb-3">
<i class="bi bi-plus-circle" style="font-size: 3rem;"></i>
</div>
<h5 class="card-title text-muted">Add Family Member</h5>
</div>
<h5 class="card-title text-muted">Add Family Member</h5>
</div>
</div>
</a>
</div>
</div>
</div>
<!-- Add Family Member Modal -->
<div class="modal fade" id="addFamilyMemberModal" tabindex="-1" aria-labelledby="addFamilyMemberModalLabel" aria-hidden="true" data-bs-focus="false">
<div class="modal-dialog modal-xl">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="addFamilyMemberModalLabel">Add Family Member</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<form id="addFamilyMemberForm" method="POST" action="{{ route('family.store') }}">
@csrf
<div class="mb-3">
<label for="full_name" class="form-label">Full Name</label>
<input type="text" class="form-control @error('full_name') is-invalid @enderror" id="full_name" name="full_name" value="{{ old('full_name') }}" required>
@error('full_name')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
<div class="mb-3">
<label for="email" class="form-label">Email Address <span class="text-muted">(Optional for children)</span></label>
<input type="email" class="form-control @error('email') is-invalid @enderror" id="email" name="email" value="{{ old('email') }}">
@error('email')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
<div class="mb-3">
<label for="mobile" class="form-label">Mobile Number</label>
<div class="input-group" onclick="event.stopPropagation()">
<button class="btn btn-outline-secondary dropdown-toggle country-dropdown-btn d-flex align-items-center" type="button" id="country_codeDropdown" data-bs-toggle="dropdown" data-bs-auto-close="false" aria-expanded="false">
<span class="fi fi-bh me-2" id="country_codeSelectedFlag"></span>
<span class="country-label" id="country_codeSelectedCountry">{{ old('mobile_code', '+973') }}</span>
</button>
<div class="dropdown-menu p-2" aria-labelledby="country_codeDropdown" style="min-width: 200px;" onclick="event.stopPropagation()">
<input type="text" class="form-control form-control-sm mb-2" placeholder="Search country..." id="country_codeSearch" onkeydown="event.stopPropagation()" tabindex="0">
<div class="country-list" id="country_codeList" style="max-height: 300px; overflow-y: auto;"></div>
</div>
<input type="hidden" id="country_code" name="mobile_code" value="{{ old('mobile_code', '+973') }}">
<input id="mobile_number" type="tel" class="form-control @error('mobile') is-invalid @enderror" name="mobile" value="{{ old('mobile') }}" autocomplete="tel" placeholder="Phone number">
</div>
@error('mobile')
<div class="invalid-feedback d-block">{{ $message }}</div>
@enderror
@error('mobile_code')
<div class="invalid-feedback d-block">{{ $message }}</div>
@enderror
</div>
<div class="row mb-3">
<div class="col-md-6">
<label for="gender" class="form-label">Gender</label>
<select class="form-select @error('gender') is-invalid @enderror" id="gender" name="gender" required>
<option value="">Select Gender</option>
<option value="m" {{ old('gender') == 'm' ? 'selected' : '' }}>Male</option>
<option value="f" {{ old('gender') == 'f' ? 'selected' : '' }}>Female</option>
</select>
@error('gender')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
<div class="col-md-6">
<label for="birthdate" class="form-label">Birthdate</label>
<input type="date" class="form-control @error('birthdate') is-invalid @enderror" id="birthdate" name="birthdate" value="{{ old('birthdate') }}" required>
@error('birthdate')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
</div>
<div class="row mb-3">
<div class="col-md-6">
<label for="blood_type" class="form-label">Blood Type</label>
<select class="form-select @error('blood_type') is-invalid @enderror" id="blood_type" name="blood_type">
<option value="">Select Blood Type</option>
<option value="A+" {{ old('blood_type') == 'A+' ? 'selected' : '' }}>A+</option>
<option value="A-" {{ old('blood_type') == 'A-' ? 'selected' : '' }}>A-</option>
<option value="B+" {{ old('blood_type') == 'B+' ? 'selected' : '' }}>B+</option>
<option value="B-" {{ old('blood_type') == 'B-' ? 'selected' : '' }}>B-</option>
<option value="AB+" {{ old('blood_type') == 'AB+' ? 'selected' : '' }}>AB+</option>
<option value="AB-" {{ old('blood_type') == 'AB-' ? 'selected' : '' }}>AB-</option>
<option value="O+" {{ old('blood_type') == 'O+' ? 'selected' : '' }}>O+</option>
<option value="O-" {{ old('blood_type') == 'O-' ? 'selected' : '' }}>O-</option>
<option value="Unknown" {{ old('blood_type') == 'Unknown' ? 'selected' : '' }}>Unknown</option>
</select>
@error('blood_type')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
<div class="col-md-6">
<x-country-dropdown
name="nationality"
id="nationality"
:value="old('nationality')"
:required="true"
:error="$errors->first('nationality')" />
</div>
</div>
<div class="mb-3">
<h5 class="form-label d-flex justify-content-between align-items-center">
Social Media Links
<button type="button" class="btn btn-outline-primary btn-sm" id="addSocialLink">
<i class="bi bi-plus"></i> Add Link
</button>
</h5>
<div id="socialLinksContainer">
@php
$existingLinks = old('social_links', []);
if (!is_array($existingLinks)) {
$existingLinks = [];
}
// Convert associative array to array of arrays for form display
$formLinks = [];
foreach ($existingLinks as $platform => $url) {
$formLinks[] = ['platform' => $platform, 'url' => $url];
}
@endphp
@foreach($formLinks as $index => $link)
<div class="social-link-row mb-3 d-flex align-items-end">
<div class="me-2 flex-grow-1">
<label class="form-label">Platform</label>
<select class="form-select platform-select" name="social_links[{{ $index }}][platform]" required>
<option value="">Select Platform</option>
<option value="facebook" {{ ($link['platform'] ?? '') == 'facebook' ? 'selected' : '' }}>Facebook</option>
<option value="twitter" {{ ($link['platform'] ?? '') == 'twitter' ? 'selected' : '' }}>Twitter/X</option>
<option value="instagram" {{ ($link['platform'] ?? '') == 'instagram' ? 'selected' : '' }}>Instagram</option>
<option value="linkedin" {{ ($link['platform'] ?? '') == 'linkedin' ? 'selected' : '' }}>LinkedIn</option>
<option value="youtube" {{ ($link['platform'] ?? '') == 'youtube' ? 'selected' : '' }}>YouTube</option>
<option value="tiktok" {{ ($link['platform'] ?? '') == 'tiktok' ? 'selected' : '' }}>TikTok</option>
<option value="snapchat" {{ ($link['platform'] ?? '') == 'snapchat' ? 'selected' : '' }}>Snapchat</option>
<option value="whatsapp" {{ ($link['platform'] ?? '') == 'whatsapp' ? 'selected' : '' }}>WhatsApp</option>
<option value="telegram" {{ ($link['platform'] ?? '') == 'telegram' ? 'selected' : '' }}>Telegram</option>
<option value="discord" {{ ($link['platform'] ?? '') == 'discord' ? 'selected' : '' }}>Discord</option>
<option value="reddit" {{ ($link['platform'] ?? '') == 'reddit' ? 'selected' : '' }}>Reddit</option>
<option value="pinterest" {{ ($link['platform'] ?? '') == 'pinterest' ? 'selected' : '' }}>Pinterest</option>
<option value="twitch" {{ ($link['platform'] ?? '') == 'twitch' ? 'selected' : '' }}>Twitch</option>
<option value="github" {{ ($link['platform'] ?? '') == 'github' ? 'selected' : '' }}>GitHub</option>
<option value="spotify" {{ ($link['platform'] ?? '') == 'spotify' ? 'selected' : '' }}>Spotify</option>
<option value="skype" {{ ($link['platform'] ?? '') == 'skype' ? 'selected' : '' }}>Skype</option>
<option value="slack" {{ ($link['platform'] ?? '') == 'slack' ? 'selected' : '' }}>Slack</option>
<option value="medium" {{ ($link['platform'] ?? '') == 'medium' ? 'selected' : '' }}>Medium</option>
<option value="vimeo" {{ ($link['platform'] ?? '') == 'vimeo' ? 'selected' : '' }}>Vimeo</option>
<option value="messenger" {{ ($link['platform'] ?? '') == 'messenger' ? 'selected' : '' }}>Messenger</option>
<option value="wechat" {{ ($link['platform'] ?? '') == 'wechat' ? 'selected' : '' }}>WeChat</option>
<option value="line" {{ ($link['platform'] ?? '') == 'line' ? 'selected' : '' }}>Line</option>
</select>
</div>
<div class="me-2 flex-grow-1">
<label class="form-label">URL</label>
<input type="url" class="form-control" name="social_links[{{ $index }}][url]" value="{{ $link['url'] ?? '' }}" placeholder="https://example.com/username" required>
</div>
<div class="mb-0">
<button type="button" class="btn btn-outline-danger btn-sm remove-social-link">
<i class="bi bi-trash"></i>
</button>
</div>
</div>
@endforeach
</div>
</div>
<div class="mb-3">
<label for="motto" class="form-label">Personal Motto</label>
<textarea class="form-control @error('motto') is-invalid @enderror" id="motto" name="motto" rows="3" placeholder="Enter personal motto or quote...">{{ old('motto') }}</textarea>
<div class="form-text">Share a personal motto or quote that inspires them.</div>
@error('motto')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
<div class="row mb-3">
<div class="col-md-6">
<label for="relationship_type" class="form-label">Relationship</label>
<select class="form-select @error('relationship_type') is-invalid @enderror" id="relationship_type" name="relationship_type" required>
<option value="">Select Relationship</option>
<option value="son" {{ old('relationship_type') == 'son' ? 'selected' : '' }}>Son</option>
<option value="daughter" {{ old('relationship_type') == 'daughter' ? 'selected' : '' }}>Daughter</option>
<option value="spouse" {{ old('relationship_type') == 'spouse' ? 'selected' : '' }}>Wife</option>
<option value="sponsor" {{ old('relationship_type') == 'sponsor' ? 'selected' : '' }}>Sponsor</option>
<option value="other" {{ old('relationship_type') == 'other' ? 'selected' : '' }}>Other</option>
</select>
@error('relationship_type')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
</div>
<div class="mb-3 form-check">
<input type="checkbox" class="form-check-input" id="is_billing_contact" name="is_billing_contact" value="1" {{ old('is_billing_contact') ? 'checked' : '' }}>
<label class="form-check-label" for="is_billing_contact">Is Billing Contact</label>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="submit" form="addFamilyMemberForm" class="btn btn-primary">Add Family Member</button>
</div>
</div>
</div>
</div>
<style>
.border-dashed {
border-style: dashed !important;
@@ -425,16 +224,6 @@
color: #0d6efd;
transition: color 0.3s ease-in-out;
}
/* Fix Select2 dropdown in modal */
.select2-container--open {
z-index: 1060;
}
/* Fix custom dropdown in modal */
.dropdown-menu {
z-index: 1060;
}
</style>
<script>
@@ -462,191 +251,6 @@
});
})
.catch(error => console.error('Error loading countries:', error));
// Social links script for modal
let socialLinkIndex = 0;
// Add new social link row
document.addEventListener('click', function(e) {
if (e.target && e.target.id === 'addSocialLink') {
addSocialLinkRow();
}
});
// Remove social link row
document.addEventListener('click', function(e) {
if (e.target.classList.contains('remove-social-link') || e.target.closest('.remove-social-link')) {
e.target.closest('.social-link-row').remove();
}
});
function addSocialLinkRow(platform = '', url = '') {
const container = document.getElementById('socialLinksContainer');
if (!container) return;
const row = document.createElement('div');
row.className = 'social-link-row mb-3 d-flex align-items-end';
row.innerHTML = `
<div class="me-2 flex-grow-1">
<label class="form-label">Platform</label>
<select class="form-select platform-select" name="social_links[${socialLinkIndex}][platform]" required>
<option value="">Select Platform</option>
<option value="facebook" ${platform === 'facebook' ? 'selected' : ''}>Facebook</option>
<option value="twitter" ${platform === 'twitter' ? 'selected' : ''}>Twitter/X</option>
<option value="instagram" ${platform === 'instagram' ? 'selected' : ''}>Instagram</option>
<option value="linkedin" ${platform === 'linkedin' ? 'selected' : ''}>LinkedIn</option>
<option value="youtube" ${platform === 'youtube' ? 'selected' : ''}>YouTube</option>
<option value="tiktok" ${platform === 'tiktok' ? 'selected' : ''}>TikTok</option>
<option value="snapchat" ${platform === 'snapchat' ? 'selected' : ''}>Snapchat</option>
<option value="whatsapp" ${platform === 'whatsapp' ? 'selected' : ''}>WhatsApp</option>
<option value="telegram" ${platform === 'telegram' ? 'selected' : ''}>Telegram</option>
<option value="discord" ${platform === 'discord' ? 'selected' : ''}>Discord</option>
<option value="reddit" ${platform === 'reddit' ? 'selected' : ''}>Reddit</option>
<option value="pinterest" ${platform === 'pinterest' ? 'selected' : ''}>Pinterest</option>
<option value="twitch" ${platform === 'twitch' ? 'selected' : ''}>Twitch</option>
<option value="github" ${platform === 'github' ? 'selected' : ''}>GitHub</option>
<option value="spotify" ${platform === 'spotify' ? 'selected' : ''}>Spotify</option>
<option value="skype" ${platform === 'skype' ? 'selected' : ''}>Skype</option>
<option value="slack" ${platform === 'slack' ? 'selected' : ''}>Slack</option>
<option value="medium" ${platform === 'medium' ? 'selected' : ''}>Medium</option>
<option value="vimeo" ${platform === 'vimeo' ? 'selected' : ''}>Vimeo</option>
<option value="messenger" ${platform === 'messenger' ? 'selected' : ''}>Messenger</option>
<option value="wechat" ${platform === 'wechat' ? 'selected' : ''}>WeChat</option>
<option value="line" ${platform === 'line' ? 'selected' : ''}>Line</option>
</select>
</div>
<div class="me-2 flex-grow-1">
<label class="form-label">URL</label>
<input type="url" class="form-control" name="social_links[${socialLinkIndex}][url]" value="${url}" placeholder="https://example.com/username" required>
</div>
<div class="mb-0">
<button type="button" class="btn btn-outline-danger btn-sm remove-social-link">
<i class="bi bi-trash"></i>
</button>
</div>
`;
container.appendChild(row);
socialLinkIndex++;
}
// Mobile dropdown script for modal
fetch('/data/countries.json')
.then(response => response.json())
.then(countries => {
const listElement = document.getElementById('country_codeList');
const selectedFlag = document.getElementById('country_codeSelectedFlag');
const selectedCountry = document.getElementById('country_codeSelectedCountry');
const hiddenInput = document.getElementById('country_code');
const searchInput = document.getElementById('country_codeSearch');
if (!listElement) return;
// Populate dropdown
countries.forEach(country => {
const button = document.createElement('button');
button.className = 'dropdown-item d-flex align-items-center';
button.type = 'button';
button.setAttribute('data-country-code', country.call_code);
button.setAttribute('data-country-name', country.name);
button.setAttribute('data-flag-code', country.flag);
button.setAttribute('data-search', `${country.name.toLowerCase()} ${country.call_code}`);
button.innerHTML = `<span class="fi fi-${country.flag.toLowerCase()} me-2"></span><span>${country.name} (${country.call_code})</span>`;
button.addEventListener('click', function() {
const code = this.getAttribute('data-country-code');
const flag = this.getAttribute('data-flag-code');
const name = this.getAttribute('data-country-name');
if (selectedFlag) selectedFlag.className = `fi fi-${flag.toLowerCase()} me-2`;
if (selectedCountry) selectedCountry.textContent = code;
if (hiddenInput) hiddenInput.value = code;
// Close dropdown
const dropdown = bootstrap.Dropdown.getInstance(document.getElementById('country_codeDropdown'));
if (dropdown) dropdown.hide();
});
listElement.appendChild(button);
});
// Set initial value
const initialValue = '{{ old('mobile_code', '+973') }}';
if (initialValue && selectedCountry && hiddenInput) {
hiddenInput.value = initialValue;
selectedCountry.textContent = initialValue;
const country = countries.find(c => c.call_code === initialValue);
if (country && selectedFlag) {
selectedFlag.className = `fi fi-${country.flag.toLowerCase()} me-2`;
}
}
// Search functionality
if (searchInput) {
searchInput.addEventListener('input', function() {
const searchTerm = this.value.toLowerCase();
const items = listElement.querySelectorAll('.dropdown-item');
items.forEach(item => {
const searchData = item.getAttribute('data-search');
if (searchData.includes(searchTerm)) {
item.classList.remove('d-none');
} else {
item.classList.add('d-none');
}
});
});
}
})
.catch(error => console.error('Error loading countries for mobile:', error));
// Initialize nationality dropdown when modal is shown
$('#addFamilyMemberModal').on('shown.bs.modal', function () {
const selectElement = document.getElementById('nationality');
if (selectElement && !$(selectElement).hasClass('select2-hidden-accessible')) {
// Load countries and initialize Select2
fetch('/data/countries.json')
.then(response => response.json())
.then(countries => {
// Clear existing options except the first one
while (selectElement.options.length > 1) {
selectElement.remove(1);
}
// Populate dropdown
countries.forEach(country => {
const option = document.createElement('option');
option.value = country.iso3;
option.textContent = country.name;
option.setAttribute('data-flag', country.flag);
selectElement.appendChild(option);
});
// Set initial value if provided
const initialValue = '{{ old('nationality') }}';
if (initialValue) {
selectElement.value = initialValue;
}
// Initialize Select2
$(selectElement).select2({
templateResult: function(state) {
if (!state.id) {
return state.text;
}
const option = $(state.element);
const flagCode = option.data('flag');
return $(`<span><span class="fi fi-${flagCode} me-2"></span>${state.text}</span>`);
},
templateSelection: function(state) {
if (!state.id) {
return state.text;
}
const option = $(state.element);
const flagCode = option.data('flag');
return $(`<span><span class="fi fi-${flagCode} me-2"></span>${state.text}</span>`);
},
width: '100%'
});
})
.catch(error => console.error('Error loading countries:', error));
}
});
});
</script>
@endsection
@@ -0,0 +1,906 @@
@php
// Helper function to calculate age at a specific date with detailed format
function calculateAgeAtDate($birthdate, $date) {
if (!$birthdate || !$date) return null;
$birth = \Carbon\Carbon::parse($birthdate);
$targetDate = \Carbon\Carbon::parse($date);
$diff = $birth->diff($targetDate);
$parts = [];
if ($diff->y > 0) $parts[] = $diff->y . ' year' . ($diff->y > 1 ? 's' : '');
if ($diff->m > 0) $parts[] = $diff->m . ' month' . ($diff->m > 1 ? 's' : '');
if ($diff->d > 0) $parts[] = $diff->d . ' day' . ($diff->d > 1 ? 's' : '');
return implode(' ', $parts) ?: 'Same day';
}
@endphp
<div class="card shadow-sm border-0">
<div class="card-body p-4">
<!-- Header with Filter -->
<div class="d-flex justify-content-between align-items-center mb-4">
<div>
<h5 class="fw-bold mb-1"><i class="bi bi-diagram-3 me-2"></i>Club Affiliations & Skills Journey</h5>
<p class="text-muted small mb-0">Complete history of club memberships, skills acquired, and instructors</p>
</div>
<div class="d-flex gap-2">
<select class="form-select form-select-sm" id="skillFilter" style="width: 200px;">
<option value="all">All Skills</option>
@foreach($allSkills ?? [] as $skill)
<option value="{{ $skill }}">{{ $skill }}</option>
@endforeach
</select>
<button class="btn btn-sm btn-outline-secondary" id="resetFilters">
<i class="bi bi-arrow-clockwise"></i> Reset
</button>
</div>
</div>
@if($clubAffiliations->count() > 0)
<!-- Summary Stats -->
<div class="row g-3 mb-4">
<div class="col-md-3">
<div class="card shadow-sm h-100" style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border: none;">
<div class="card-body text-center text-white p-3">
<i class="bi bi-building display-5 mb-2"></i>
<h3 class="fw-bold mb-1">{{ $totalAffiliations }}</h3>
<small class="opacity-75">Total Clubs</small>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card shadow-sm h-100" style="background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); border: none;">
<div class="card-body text-center text-white p-3">
<i class="bi bi-star-fill display-5 mb-2"></i>
<h3 class="fw-bold mb-1">{{ $distinctSkills }}</h3>
<small class="opacity-75">Unique Skills</small>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card shadow-sm h-100" style="background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%); border: none;">
<div class="card-body text-center text-white p-3">
<i class="bi bi-calendar-check display-5 mb-2"></i>
<h3 class="fw-bold mb-1">{{ floor($totalMembershipDuration / 12) }}y {{ $totalMembershipDuration % 12 }}m</h3>
<small class="opacity-75">Total Training</small>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card shadow-sm h-100" style="background: linear-gradient(135deg, #fa709a 0%, #fee140 100%); border: none;">
<div class="card-body text-center text-white p-3">
<i class="bi bi-people-fill display-5 mb-2"></i>
<h3 class="fw-bold mb-1">{{ $totalInstructors ?? 0 }}</h3>
<small class="opacity-75">Instructors</small>
</div>
</div>
</div>
</div>
<!-- Timeline -->
<div class="row">
<div class="col-12">
<div class="card shadow-sm border-0">
<div class="card-header" style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border: none;">
<h6 class="card-title mb-0 text-white">
<i class="bi bi-clock-history me-2"></i>Membership Timeline
</h6>
</div>
<div class="card-body p-4" style="max-height: 800px; overflow-y: auto;">
<div class="timeline-enhanced">
@foreach($clubAffiliations as $index => $affiliation)
@php
$ageAtStart = calculateAgeAtDate($user->birthdate, $affiliation->start_date);
$ageAtEnd = $affiliation->end_date ? calculateAgeAtDate($user->birthdate, $affiliation->end_date) : null;
$isOngoing = !$affiliation->end_date;
// Get all skills for this affiliation
$affiliationSkills = $affiliation->skillAcquisitions ?? collect();
$skillNames = $affiliationSkills->pluck('skill_name')->unique()->implode(',');
@endphp
<div class="timeline-item-enhanced mb-4" data-affiliation-id="{{ $affiliation->id }}" data-skills="{{ $skillNames }}">
<!-- Timeline Marker -->
<div class="timeline-marker-enhanced {{ $isOngoing ? 'pulse' : '' }}"></div>
<!-- Affiliation Card -->
<div class="affiliation-card-enhanced card border-0 shadow-sm">
<!-- Card Header with Gradient -->
<div class="card-header border-0 p-3" style="background: linear-gradient(135deg, {{ $index % 4 == 0 ? '#667eea 0%, #764ba2' : ($index % 4 == 1 ? '#f093fb 0%, #f5576c' : ($index % 4 == 2 ? '#4facfe 0%, #00f2fe' : '#fa709a 0%, #fee140')) }} 100%);">
<div class="d-flex align-items-center">
@if($affiliation->logo)
<img src="{{ asset('storage/' . $affiliation->logo) }}" alt="{{ $affiliation->club_name }}" class="rounded-circle me-3" style="width: 50px; height: 50px; object-fit: cover; border: 3px solid white;">
@else
<div class="rounded-circle bg-white d-flex align-items-center justify-content-center me-3" style="width: 50px; height: 50px;">
<i class="bi bi-building" style="font-size: 1.5rem; color: #667eea;"></i>
</div>
@endif
<div class="flex-grow-1 text-white">
<h5 class="mb-1 fw-bold">{{ $affiliation->club_name }}</h5>
<div class="d-flex gap-3 flex-wrap">
<small class="opacity-90">
<i class="bi bi-calendar-event me-1"></i>{{ $affiliation->start_date->format('M Y') }} - {{ $isOngoing ? 'Present' : $affiliation->end_date->format('M Y') }}
</small>
<small class="opacity-90">
<i class="bi bi-hourglass-split me-1"></i>{{ $affiliation->formatted_duration }}
</small>
@if($ageAtStart)
<small class="opacity-90">
<i class="bi bi-person me-1"></i>Age: {{ $ageAtStart }}{{ $ageAtEnd && $ageAtEnd != $ageAtStart ? " to $ageAtEnd" : '' }}
</small>
@endif
</div>
</div>
@if($isOngoing)
<span class="badge bg-success">
<i class="bi bi-circle-fill me-1" style="font-size: 0.5rem;"></i>Active
</span>
@endif
</div>
</div>
<!-- Card Body -->
<div class="card-body p-3">
@if($affiliation->location)
<div class="mb-3">
<i class="bi bi-geo-alt text-primary me-2"></i>
<span class="text-muted">{{ $affiliation->location }}</span>
</div>
@endif
<!-- Skills Acquired as Badges -->
@if($affiliationSkills->count() > 0)
<div class="mb-3">
<h6 class="fw-bold mb-2">
<i class="bi bi-star-fill me-2 text-warning"></i>Skills Acquired ({{ $affiliationSkills->count() }})
</h6>
<div class="d-flex gap-2 flex-wrap">
@foreach($affiliationSkills as $skill)
<span class="badge skill-badge bg-{{ $skill->proficiency_level == 'expert' ? 'danger' : ($skill->proficiency_level == 'advanced' ? 'warning' : ($skill->proficiency_level == 'intermediate' ? 'info' : 'secondary')) }}"
data-bs-toggle="tooltip"
data-bs-placement="top"
data-bs-html="true"
title="<strong>{{ $skill->skill_name }}</strong><br>
Proficiency: {{ ucfirst($skill->proficiency_level) }}<br>
Duration: {{ $skill->formatted_duration }}<br>
@if($skill->instructor)Instructor: {{ $skill->instructor->user->full_name ?? 'Unknown' }}<br>@endif
Started: {{ $skill->start_date->format('M Y') }}">
<i class="bi bi-star-fill me-1"></i>{{ $skill->skill_name }}
<span class="badge bg-white text-dark ms-1" style="font-size: 0.65rem;">{{ ucfirst($skill->proficiency_level) }}</span>
</span>
@endforeach
</div>
</div>
@endif
<!-- Training Packages -->
@if($affiliation->subscriptions && $affiliation->subscriptions->count() > 0)
<div class="mb-3">
<h6 class="fw-bold mb-2">
<i class="bi bi-box-seam me-2 text-primary"></i>Training Packages ({{ $affiliation->subscriptions->count() }})
</h6>
<div class="d-flex gap-2 flex-wrap">
@foreach($affiliation->subscriptions as $subIndex => $subscription)
@if($subscription->package)
<button type="button" class="btn btn-sm btn-outline-primary package-card-btn"
data-bs-toggle="modal"
data-bs-target="#packageModal_{{ $affiliation->id }}_{{ $subscription->id }}">
<i class="bi bi-box me-1"></i>{{ $subscription->package->name }}
</button>
@endif
@endforeach
</div>
</div>
@endif
<!-- Instructors -->
@php
$instructors = $affiliationSkills->pluck('instructor')->filter()->unique('id');
@endphp
@if($instructors->count() > 0)
<div class="mb-2">
<h6 class="fw-bold mb-2">
<i class="bi bi-people-fill me-2 text-success"></i>Instructors ({{ $instructors->count() }})
</h6>
<div class="d-flex gap-2 flex-wrap">
@foreach($instructors as $instructor)
<div class="instructor-badge" role="button"
data-bs-toggle="modal"
data-bs-target="#instructorModal_{{ $instructor->id }}">
<div class="d-flex align-items-center gap-2 p-2 bg-light rounded">
<div class="rounded-circle bg-success text-white d-flex align-items-center justify-content-center" style="width: 32px; height: 32px; font-size: 0.8rem;">
{{ strtoupper(substr($instructor->user->full_name ?? 'I', 0, 1)) }}
</div>
<div>
<div class="fw-semibold small">{{ $instructor->user->full_name ?? 'Unknown' }}</div>
<div class="text-muted" style="font-size: 0.7rem;">{{ $instructor->role ?? 'Instructor' }}</div>
</div>
</div>
</div>
@endforeach
</div>
</div>
@endif
</div>
</div>
</div>
@endforeach
</div>
<!-- Instructor Modals -->
@php
$allInstructors = collect();
foreach($clubAffiliations as $aff) {
$affInstructors = $aff->skillAcquisitions->pluck('instructor')->filter()->unique('id');
$allInstructors = $allInstructors->merge($affInstructors);
}
$allInstructors = $allInstructors->unique('id');
@endphp
@foreach($allInstructors as $instructor)
<div class="modal fade" id="instructorModal_{{ $instructor->id }}" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered modal-dialog-scrollable" style="max-width: 600px;">
<div class="modal-content">
<div class="modal-header" style="background: linear-gradient(135deg, #11998e 0%, #38ef7d 100%);">
<h5 class="modal-title text-white">
<i class="bi bi-person-badge me-2"></i>Instructor Profile
</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body" style="max-height: 70vh; overflow-y: auto;">
<div class="text-center mb-4">
<!-- Profile Picture -->
<div class="mb-3">
@if($instructor->user->profile_picture)
<img src="{{ asset('storage/' . $instructor->user->profile_picture) }}"
alt="{{ $instructor->user->full_name }}"
class="rounded-circle"
style="width: 100px; height: 100px; object-fit: cover; border: 4px solid #11998e;">
@else
<div class="rounded-circle mx-auto d-flex align-items-center justify-content-center text-white"
style="width: 100px; height: 100px; font-size: 2.5rem; background: linear-gradient(135deg, #11998e 0%, #38ef7d 100%);">
{{ strtoupper(substr($instructor->user->full_name ?? 'I', 0, 1)) }}
</div>
@endif
</div>
<!-- Name & Role -->
<h5 class="fw-bold mb-1">{{ $instructor->user->full_name ?? 'Unknown Instructor' }}</h5>
<p class="text-muted mb-2">{{ $instructor->role ?? 'Instructor' }}</p>
<!-- Average Rating -->
@php
$avgRating = $instructor->reviews()->avg('rating') ?? 0;
$reviewCount = $instructor->reviews()->count();
@endphp
<div class="mb-3">
<div class="d-flex justify-content-center align-items-center gap-2">
<div class="stars-display">
@for($i = 1; $i <= 5; $i++)
<i class="bi bi-star{{ $i <= round($avgRating) ? '-fill' : '' }} text-warning"></i>
@endfor
</div>
<span class="text-muted small">({{ number_format($avgRating, 1) }} / {{ $reviewCount }} {{ $reviewCount == 1 ? 'review' : 'reviews' }})</span>
</div>
</div>
</div>
<!-- Quick Stats -->
<div class="row g-2 mb-3">
@php
$instructorSkills = \App\Models\SkillAcquisition::where('instructor_id', $instructor->id)->get();
$studentsCount = $instructorSkills->pluck('clubAffiliation.member_id')->unique()->count();
$skillsTaught = $instructorSkills->pluck('skill_name')->unique();
@endphp
<div class="col-6">
<div class="card bg-light border-0">
<div class="card-body p-2">
<div class="h4 mb-0 text-primary">{{ $studentsCount }}</div>
<small class="text-muted">Students</small>
</div>
</div>
</div>
<div class="col-6">
<div class="card bg-light border-0">
<div class="card-body p-2">
<div class="h4 mb-0 text-success">{{ $skillsTaught->count() }}</div>
<small class="text-muted">Skills</small>
</div>
</div>
</div>
</div>
<!-- Skills Taught -->
@if($skillsTaught->count() > 0)
<div class="mb-3 text-start">
<label class="text-muted small fw-semibold mb-2">Specializes In:</label>
<div class="d-flex gap-1 flex-wrap">
@foreach($skillsTaught as $skill)
<span class="badge bg-success">{{ $skill }}</span>
@endforeach
</div>
</div>
@endif
<!-- Contact Info -->
@if($instructor->user->email)
<div class="mb-2 text-start">
<small class="text-muted">
<i class="bi bi-envelope me-1"></i>{{ $instructor->user->email }}
</small>
</div>
@endif
@if($instructor->user->mobile)
<div class="mb-3 text-start">
<small class="text-muted">
<i class="bi bi-phone me-1"></i>{{ $instructor->user->mobile }}
</small>
</div>
@endif
<!-- Reviews Section -->
<div class="mt-4">
<h6 class="fw-bold mb-3">
<i class="bi bi-chat-left-text me-2"></i>Reviews
</h6>
<!-- Add/Edit Review Form -->
@php
$userReview = $instructor->reviews()->where('reviewer_user_id', auth()->id())->first();
@endphp
<div class="card bg-light mb-3" id="reviewForm_{{ $instructor->id }}">
<div class="card-body">
<form class="instructor-review-form" data-instructor-id="{{ $instructor->id }}" data-review-id="{{ $userReview->id ?? '' }}">
@csrf
<div class="mb-3">
<label class="form-label small fw-semibold">Your Rating</label>
<div class="star-rating" data-rating="{{ $userReview->rating ?? 0 }}">
@for($i = 1; $i <= 5; $i++)
<i class="bi bi-star{{ $userReview && $i <= $userReview->rating ? '-fill' : '' }} star-input" data-value="{{ $i }}"></i>
@endfor
</div>
<input type="hidden" name="rating" value="{{ $userReview->rating ?? 0 }}" required>
</div>
<div class="mb-3">
<label class="form-label small fw-semibold">Your Review</label>
<textarea name="comment" class="form-control form-control-sm" rows="3" placeholder="Share your experience...">{{ $userReview->comment ?? '' }}</textarea>
</div>
<button type="submit" class="btn btn-success btn-sm w-100">
<i class="bi bi-{{ $userReview ? 'pencil' : 'plus-circle' }} me-1"></i>
{{ $userReview ? 'Update Review' : 'Submit Review' }}
</button>
</form>
</div>
</div>
<!-- Reviews List -->
<div class="reviews-list" id="reviewsList_{{ $instructor->id }}">
@foreach($instructor->reviews()->with('reviewer')->latest()->get() as $review)
<div class="card mb-2">
<div class="card-body p-3">
<div class="d-flex align-items-start mb-2">
<div class="flex-grow-1">
<div class="fw-semibold small">{{ $review->reviewer->full_name }}</div>
<div class="stars-display small">
@for($i = 1; $i <= 5; $i++)
<i class="bi bi-star{{ $i <= $review->rating ? '-fill' : '' }} text-warning"></i>
@endfor
</div>
</div>
<small class="text-muted">
{{ $review->wasUpdated() ? 'Updated ' : '' }}{{ $review->wasUpdated() ? $review->updated_at->diffForHumans() : $review->reviewed_at->diffForHumans() }}
</small>
</div>
@if($review->comment)
<p class="mb-0 small text-muted">{{ $review->comment }}</p>
@endif
</div>
</div>
@endforeach
</div>
</div>
</div>
<div class="modal-footer">
<a href="{{ route('family.show', $instructor->user_id) }}" class="btn btn-primary btn-sm">
<i class="bi bi-person-lines-fill me-1"></i>View Full Profile
</a>
<button type="button" class="btn btn-secondary btn-sm" data-bs-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
@endforeach
<!-- Package Modals (Outside Timeline Loop) -->
@foreach($clubAffiliations as $affiliation)
@foreach($affiliation->subscriptions as $subIndex => $subscription)
@if($subscription->package)
<div class="modal fade" id="packageModal_{{ $affiliation->id }}_{{ $subscription->id }}" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered modal-dialog-scrollable">
<div class="modal-content">
<div class="modal-header" style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);">
<h5 class="modal-title text-white">
<i class="bi bi-box-seam me-2"></i>{{ $subscription->package->name }}
</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="mb-3">
<label class="text-muted small fw-semibold">Subscription Period</label>
<div>
<i class="bi bi-calendar-range me-2 text-primary"></i>
{{ $subscription->start_date->format('M d, Y') }} - {{ $subscription->end_date->format('M d, Y') }}
</div>
@php
$duration = $subscription->start_date->diff($subscription->end_date);
$durationParts = [];
if ($duration->y > 0) $durationParts[] = $duration->y . ' year' . ($duration->y > 1 ? 's' : '');
if ($duration->m > 0) $durationParts[] = $duration->m . ' month' . ($duration->m > 1 ? 's' : '');
if ($duration->d > 0) $durationParts[] = $duration->d . ' day' . ($duration->d > 1 ? 's' : '');
$durationText = implode(' ', $durationParts) ?: 'Same day';
@endphp
<small class="text-muted">
<i class="bi bi-hourglass-split me-1"></i>Duration: {{ $durationText }}
</small>
</div>
@if($subscription->package->description)
<div class="mb-3">
<label class="text-muted small fw-semibold">Description</label>
<p class="mb-0">{{ $subscription->package->description }}</p>
</div>
@endif
@if($subscription->package->price)
<div class="mb-3">
<label class="text-muted small fw-semibold">Price</label>
<div class="h5 mb-0 text-success">
<i class="bi bi-currency-dollar"></i>{{ number_format($subscription->package->price, 2) }}
</div>
</div>
@endif
@if($subscription->package->packageActivities && $subscription->package->packageActivities->count() > 0)
<div class="mb-3">
<label class="text-muted small fw-semibold">Activities & Skills Included</label>
<div class="list-group">
@foreach($subscription->package->packageActivities as $pkgActivity)
@if($pkgActivity->activity)
<div class="list-group-item">
<div class="d-flex align-items-start mb-2">
<i class="bi bi-check-circle-fill text-success me-2 mt-1"></i>
<div class="flex-grow-1">
<div class="fw-semibold">{{ $pkgActivity->activity->name }}</div>
@if($pkgActivity->activity->description)
<small class="text-muted d-block mb-2">{{ $pkgActivity->activity->description }}</small>
@endif
@php
// Get skills taught in this activity
$activitySkills = \App\Models\SkillAcquisition::where('activity_id', $pkgActivity->activity_id)
->where('club_affiliation_id', $affiliation->id)
->get();
@endphp
@if($activitySkills->count() > 0)
<div class="mb-2">
<small class="text-muted d-block mb-1">Skills Practiced:</small>
<div class="d-flex gap-1 flex-wrap">
@foreach($activitySkills as $actSkill)
<span class="badge bg-{{ $actSkill->proficiency_level == 'expert' ? 'danger' : ($actSkill->proficiency_level == 'advanced' ? 'warning' : ($actSkill->proficiency_level == 'intermediate' ? 'info' : 'secondary')) }}" style="font-size: 0.7rem;">
<i class="bi bi-star-fill me-1"></i>{{ $actSkill->skill_name }}
</span>
@endforeach
</div>
</div>
@endif
</div>
@if($pkgActivity->instructor && $pkgActivity->instructor->user)
<div class="text-end">
<small class="text-muted">
<i class="bi bi-person-badge"></i>
{{ $pkgActivity->instructor->user->full_name }}
</small>
</div>
@endif
</div>
</div>
@endif
@endforeach
</div>
</div>
@endif
@php
// Check if this package was subscribed to multiple times
$samePackageSubscriptions = $affiliation->subscriptions
->where('package_id', $subscription->package_id)
->where('id', '!=', $subscription->id);
@endphp
@if($samePackageSubscriptions->count() > 0)
<div class="mb-3">
<label class="text-muted small fw-semibold">
<i class="bi bi-arrow-repeat me-1"></i>Other Subscriptions to This Package
</label>
<div class="alert alert-info mb-0" style="font-size: 0.85rem;">
<div class="fw-semibold mb-1">You subscribed to this package {{ $samePackageSubscriptions->count() + 1 }} times:</div>
<ul class="mb-0 ps-3">
<li class="text-primary fw-semibold">
{{ $subscription->start_date->format('M d, Y') }} - {{ $subscription->end_date->format('M d, Y') }} (Current)
</li>
@foreach($samePackageSubscriptions as $otherSub)
<li>
{{ $otherSub->start_date->format('M d, Y') }} - {{ $otherSub->end_date->format('M d, Y') }}
@php
$gap = $subscription->start_date->diffInMonths($otherSub->start_date);
@endphp
@if($gap > 0)
<small class="text-muted">({{ abs($gap) }} months {{ $subscription->start_date->gt($otherSub->start_date) ? 'before' : 'after' }} current)</small>
@endif
</li>
@endforeach
</ul>
</div>
</div>
@endif
<div class="mb-0">
<label class="text-muted small fw-semibold">Status</label>
<div>
<span class="badge bg-{{ $subscription->status == 'active' ? 'success' : 'secondary' }}">
{{ ucfirst($subscription->status) }}
</span>
<span class="badge bg-{{ $subscription->payment_status == 'paid' ? 'success' : 'warning' }} ms-2">
Payment: {{ ucfirst($subscription->payment_status) }}
</span>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
@endif
@endforeach
@endforeach
</div>
</div>
</div>
</div>
@else
<div class="text-center py-5">
<i class="bi bi-diagram-3 text-muted" style="font-size: 3rem;"></i>
<h5 class="text-muted mt-3 mb-2">No Affiliations Yet</h5>
<p class="text-muted mb-0">Club affiliations and skills will appear here once added</p>
</div>
@endif
</div>
</div>
<style>
/* Enhanced Timeline Styles */
.timeline-enhanced {
position: relative;
padding-left: 40px;
}
.timeline-enhanced::before {
content: '';
position: absolute;
left: 20px;
top: 0;
bottom: 0;
width: 3px;
background: linear-gradient(180deg, #667eea 0%, #764ba2 100%);
}
.timeline-item-enhanced {
position: relative;
animation: fadeInUp 0.5s ease-out;
}
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.timeline-marker-enhanced {
position: absolute;
left: -28px;
top: 25px;
width: 16px;
height: 16px;
border-radius: 50%;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border: 3px solid #fff;
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.2);
z-index: 1;
}
.timeline-marker-enhanced.pulse {
animation: pulse 2s infinite;
}
@keyframes pulse {
0%, 100% {
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.2);
}
50% {
box-shadow: 0 0 0 8px rgba(102, 126, 234, 0.4);
}
}
.affiliation-card-enhanced {
transition: all 0.3s ease;
margin-left: 10px;
}
.affiliation-card-enhanced:hover {
transform: translateX(5px);
box-shadow: 0 8px 16px rgba(0,0,0,0.15) !important;
}
.skill-badge {
padding: 0.5rem 0.75rem;
font-size: 0.85rem;
font-weight: 500;
transition: all 0.2s ease;
cursor: pointer;
}
.skill-badge:hover {
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0,0,0,0.2);
}
.package-card-btn {
transition: all 0.2s ease;
}
.package-card-btn:hover {
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
}
.modal-dialog {
max-width: 600px;
}
.modal-body {
max-height: 60vh;
overflow-y: auto;
}
.instructor-badge {
transition: all 0.2s ease;
cursor: pointer;
}
.instructor-badge:hover {
transform: scale(1.05);
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
}
.instructor-badge:active {
transform: scale(0.98);
}
/* Star Rating Styles */
.star-rating {
font-size: 1.5rem;
cursor: pointer;
}
.star-rating .star-input {
color: #ddd;
transition: color 0.2s ease;
}
.star-rating .star-input:hover,
.star-rating .star-input.active {
color: #ffc107;
}
.star-rating .bi-star-fill {
color: #ffc107;
}
.stars-display {
font-size: 1rem;
}
.stars-display .bi-star-fill {
color: #ffc107;
}
.stars-display .bi-star {
color: #ddd;
}
/* Filter transition */
.timeline-item-enhanced.filtered-out {
display: none;
animation: fadeOut 0.3s ease-out;
}
@keyframes fadeOut {
from {
opacity: 1;
transform: translateY(0);
}
to {
opacity: 0;
transform: translateY(-20px);
}
}
</style>
<script>
document.addEventListener('DOMContentLoaded', function() {
const skillFilter = document.getElementById('skillFilter');
const resetButton = document.getElementById('resetFilters');
const timelineItems = document.querySelectorAll('.timeline-item-enhanced');
// Initialize Bootstrap tooltips
const tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'));
tooltipTriggerList.map(function (tooltipTriggerEl) {
return new bootstrap.Tooltip(tooltipTriggerEl);
});
// Star Rating Functionality
document.querySelectorAll('.star-rating').forEach(ratingContainer => {
const stars = ratingContainer.querySelectorAll('.star-input');
const ratingInput = ratingContainer.parentElement.querySelector('input[name="rating"]');
stars.forEach((star, index) => {
star.addEventListener('click', function() {
const value = this.getAttribute('data-value');
ratingInput.value = value;
// Update star display
stars.forEach((s, i) => {
if (i < value) {
s.classList.remove('bi-star');
s.classList.add('bi-star-fill');
} else {
s.classList.remove('bi-star-fill');
s.classList.add('bi-star');
}
});
});
star.addEventListener('mouseenter', function() {
const value = this.getAttribute('data-value');
stars.forEach((s, i) => {
if (i < value) {
s.classList.add('active');
} else {
s.classList.remove('active');
}
});
});
});
ratingContainer.addEventListener('mouseleave', function() {
stars.forEach(s => s.classList.remove('active'));
});
});
// Review Form Submission
document.querySelectorAll('.instructor-review-form').forEach(form => {
form.addEventListener('submit', function(e) {
e.preventDefault();
const instructorId = this.getAttribute('data-instructor-id');
const reviewId = this.getAttribute('data-review-id');
const formData = new FormData(this);
const data = {
rating: formData.get('rating'),
comment: formData.get('comment')
};
// Validate rating
if (!data.rating || data.rating == 0) {
alert('Please select a rating');
return;
}
const url = reviewId
? `/instructor/reviews/${reviewId}`
: `/instructor/${instructorId}/reviews`;
const method = reviewId ? 'PUT' : 'POST';
fetch(url, {
method: method,
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').getAttribute('content')
},
body: JSON.stringify(data)
})
.then(response => response.json())
.then(result => {
if (result.success) {
// Show success message
showAlert(result.message, 'success');
// Reload the page to show updated reviews
setTimeout(() => {
window.location.reload();
}, 1000);
} else {
showAlert(result.message || 'Error submitting review', 'danger');
}
})
.catch(error => {
console.error('Error:', error);
showAlert('Error submitting review. Please try again.', 'danger');
});
});
});
function showAlert(message, type) {
const alertDiv = document.createElement('div');
alertDiv.className = `alert alert-${type} alert-dismissible fade show position-fixed`;
alertDiv.style.cssText = 'top: 20px; right: 20px; z-index: 9999; min-width: 300px;';
alertDiv.innerHTML = `
${message}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
`;
document.body.appendChild(alertDiv);
setTimeout(() => {
if (alertDiv.parentNode) {
alertDiv.remove();
}
}, 5000);
}
if (skillFilter) {
skillFilter.addEventListener('change', function() {
const selectedSkill = this.value;
timelineItems.forEach(item => {
const itemSkills = item.getAttribute('data-skills');
if (selectedSkill === 'all') {
item.classList.remove('filtered-out');
item.style.display = '';
} else {
if (itemSkills && itemSkills.includes(selectedSkill)) {
item.classList.remove('filtered-out');
item.style.display = '';
} else {
item.classList.add('filtered-out');
setTimeout(() => {
item.style.display = 'none';
}, 300);
}
}
});
});
}
if (resetButton) {
resetButton.addEventListener('click', function() {
if (skillFilter) {
skillFilter.value = 'all';
skillFilter.dispatchEvent(new Event('change'));
}
});
}
});
</script>
+56 -122
View File
@@ -10,34 +10,26 @@
</div>
<div class="card-body">
<!-- Profile Picture Section -->
<div class="mb-4 text-center">
<div class="mb-3">
@if($user->profile_picture && file_exists(public_path('storage/' . $user->profile_picture)))
<img src="{{ asset('storage/' . $user->profile_picture) }}"
alt="Profile Picture"
style="width: 300px; height: 400px; object-fit: cover; border: 3px solid #dee2e6; border-radius: 8px;">
@elseif(file_exists(public_path('storage/images/profiles/profile_' . $user->id . '.png')))
<img src="{{ asset('storage/images/profiles/profile_' . $user->id . '.png') }}"
alt="Profile Picture"
style="width: 300px; height: 400px; object-fit: cover; border: 3px solid #dee2e6; border-radius: 8px;">
@elseif(file_exists(public_path('storage/images/profiles/profile_' . $user->id . '.jpg')))
<img src="{{ asset('storage/images/profiles/profile_' . $user->id . '.jpg') }}"
alt="Profile Picture"
style="width: 300px; height: 400px; object-fit: cover; border: 3px solid #dee2e6; border-radius: 8px;">
@elseif(file_exists(public_path('storage/images/profiles/profile_' . $user->id . '.jpeg')))
<img src="{{ asset('storage/images/profiles/profile_' . $user->id . '.jpeg') }}"
alt="Profile Picture"
style="width: 300px; height: 400px; object-fit: cover; border: 3px solid #dee2e6; border-radius: 8px;">
@else
<div style="width: 300px; height: 400px; background-color: #f0f0f0; border: 3px solid #dee2e6; border-radius: 8px; display: flex; align-items: center; justify-content: center; margin: 0 auto;">
<div class="text-center">
<i class="bi bi-person-circle" style="font-size: 100px; color: #dee2e6;"></i>
<p class="text-muted mt-2">No profile picture</p>
</div>
</div>
@endif
</div>
<x-takeone-cropper
@php
$currentProfileImage = '';
// Check user's profile_picture field first (set by upload controller)
if ($user->profile_picture && file_exists(public_path('storage/' . $user->profile_picture))) {
$currentProfileImage = asset('storage/' . $user->profile_picture);
} else {
// Fallback: check for files with common extensions
$extensions = ['png', 'jpg', 'jpeg', 'webp'];
foreach ($extensions as $ext) {
$path = 'storage/images/profiles/profile_' . $user->id . '.' . $ext;
if (file_exists(public_path($path))) {
$currentProfileImage = asset($path);
break;
}
}
}
@endphp
<div class="mb-4">
<x-image-upload
id="profile_picture"
width="300"
height="400"
@@ -45,6 +37,10 @@
folder="images/profiles"
filename="profile_{{ $user->id }}"
uploadUrl="{{ route('profile.upload-picture') }}"
currentImage="{{ $currentProfileImage }}"
placeholder="No profile picture"
placeholderIcon="bi-person-circle"
buttonText="Change Photo"
/>
</div>
@@ -69,45 +65,44 @@
</div>
<div class="mb-3">
<label for="mobile" class="form-label">Mobile Number</label>
<div class="input-group" onclick="event.stopPropagation()">
<button class="btn btn-outline-secondary dropdown-toggle country-dropdown-btn d-flex align-items-center" type="button" id="country_codeDropdown" data-bs-toggle="dropdown" aria-expanded="false">
<span class="fi fi-bh me-2" id="country_codeSelectedFlag"></span>
<span class="country-label" id="country_codeSelectedCountry">{{ old('mobile_code', $user->mobile_code ?? '+973') }}</span>
</button>
<div class="dropdown-menu p-2" aria-labelledby="country_codeDropdown" style="min-width: 200px;" onclick="event.stopPropagation()">
<input type="text" class="form-control form-control-sm mb-2" placeholder="Search country..." id="country_codeSearch" onkeydown="event.stopPropagation()">
<div class="country-list" id="country_codeList" style="max-height: 300px; overflow-y: auto;"></div>
</div>
<input type="hidden" id="country_code" name="mobile_code" value="{{ old('mobile_code', $user->mobile['code'] ?? '+973') }}" required="">
<input id="mobile_number" type="tel" class="form-control @error('mobile') is-invalid @enderror" name="mobile" value="{{ old('mobile', $user->mobile['number'] ?? '') }}" required="" autocomplete="tel" placeholder="Phone number">
</div>
<label for="mobile_number" class="form-label">Mobile Number</label>
<x-country-code-dropdown
name="mobile_code"
id="country_code"
:value="old('mobile_code', $user->mobile['code'] ?? '+973')"
:required="true"
:error="$errors->first('mobile_code')">
<input id="mobile_number" type="tel"
class="form-control @error('mobile') is-invalid @enderror"
name="mobile"
value="{{ old('mobile', $user->mobile['number'] ?? '') }}"
required autocomplete="tel"
placeholder="Phone number">
</x-country-code-dropdown>
@error('mobile')
<div class="invalid-feedback d-block">{{ $message }}</div>
@enderror
@error('mobile_code')
<div class="invalid-feedback d-block">{{ $message }}</div>
@enderror
</div>
<div class="row mb-3">
<div class="col-md-6">
<label for="gender" class="form-label">Gender</label>
<select class="form-select @error('gender') is-invalid @enderror" id="gender" name="gender" required>
<option value="">Select Gender</option>
<option value="m" {{ old('gender', $user->gender) == 'm' ? 'selected' : '' }}>Male</option>
<option value="f" {{ old('gender', $user->gender) == 'f' ? 'selected' : '' }}>Female</option>
</select>
@error('gender')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
<x-gender-dropdown
name="gender"
id="gender"
:value="old('gender', $user->gender)"
:required="true"
:error="$errors->first('gender')" />
</div>
<div class="col-md-6">
<label for="birthdate" class="form-label">Birthdate</label>
<input type="date" class="form-control @error('birthdate') is-invalid @enderror" id="birthdate" name="birthdate" value="{{ old('birthdate', $user->birthdate->format('Y-m-d')) }}" required>
@error('birthdate')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
<x-birthdate-dropdown
name="birthdate"
id="birthdate"
label="Birthdate"
:value="old('birthdate', $user->birthdate?->format('Y-m-d'))"
:required="true"
:min-age="10"
:max-age="120"
:error="$errors->first('birthdate')" />
</div>
</div>
@@ -131,7 +126,7 @@
@enderror
</div>
<div class="col-md-6">
<x-country-dropdown
<x-nationality-dropdown
name="nationality"
id="nationality"
:value="old('nationality', $user->nationality)"
@@ -227,69 +222,6 @@
@push('scripts')
<script>
document.addEventListener('DOMContentLoaded', function() {
// Load countries for mobile code dropdown
fetch('/data/countries.json')
.then(response => response.json())
.then(countries => {
const listElement = document.getElementById('country_codeList');
const selectedFlag = document.getElementById('country_codeSelectedFlag');
const selectedCountry = document.getElementById('country_codeSelectedCountry');
const hiddenInput = document.getElementById('country_code');
const searchInput = document.getElementById('country_codeSearch');
if (!listElement) return;
// Populate dropdown
countries.forEach(country => {
const button = document.createElement('button');
button.className = 'dropdown-item d-flex align-items-center';
button.type = 'button';
button.setAttribute('data-country-code', country.call_code);
button.setAttribute('data-country-name', country.name);
button.setAttribute('data-flag-code', country.flag);
button.setAttribute('data-search', `${country.name.toLowerCase()} ${country.call_code}`);
button.innerHTML = `<span class="fi fi-${country.flag.toLowerCase()} me-2"></span><span>${country.name} (${country.call_code})</span>`;
button.addEventListener('click', function() {
const code = this.getAttribute('data-country-code');
const flag = this.getAttribute('data-flag-code');
const name = this.getAttribute('data-country-name');
selectedFlag.className = `fi fi-${flag.toLowerCase()} me-2`;
selectedCountry.textContent = code;
hiddenInput.value = code;
// Close dropdown
const dropdown = bootstrap.Dropdown.getInstance(document.getElementById('country_codeDropdown'));
if (dropdown) dropdown.hide();
});
listElement.appendChild(button);
});
// Set initial value
const initialValue = '{{ old('mobile_code', $user->mobile['code'] ?? '+973') }}';
if (initialValue) {
hiddenInput.value = initialValue;
selectedCountry.textContent = initialValue;
const country = countries.find(c => c.call_code === initialValue);
if (country) {
selectedFlag.className = `fi fi-${country.flag.toLowerCase()} me-2`;
}
}
// Search functionality
searchInput.addEventListener('input', function() {
const searchTerm = this.value.toLowerCase();
const items = listElement.querySelectorAll('.dropdown-item');
items.forEach(item => {
const searchData = item.getAttribute('data-search');
if (searchData.includes(searchTerm)) {
item.style.display = '';
} else {
item.style.display = 'none';
}
});
});
})
.catch(error => console.error('Error loading countries:', error));
let socialLinkIndex = {{ count($formLinks ?? []) }};
// Add new social link row
@@ -355,4 +287,6 @@ document.addEventListener('DOMContentLoaded', function() {
});
</script>
@endpush
@stack('styles')
@endsection
+111 -221
View File
@@ -988,129 +988,13 @@
<!-- 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"><i class="bi bi-diagram-3 me-2"></i>Affiliations & Skills</h5>
@if($clubAffiliations->count() > 0)
<!-- Summary Stats -->
<div class="row g-3 mb-4">
<div class="col-md-4">
<div class="card shadow-sm bg-primary text-white">
<div class="card-body text-center">
<i class="bi bi-building display-4 mb-2"></i>
<h4 class="fw-bold mb-1">{{ $totalAffiliations }}</h4>
<small>Total Affiliations</small>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card shadow-sm bg-success text-white">
<div class="card-body text-center">
<i class="bi bi-star display-4 mb-2"></i>
<h4 class="fw-bold mb-1">{{ $distinctSkills }}</h4>
<small>Distinct Skills</small>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card shadow-sm bg-info text-white">
<div class="card-body text-center">
<i class="bi bi-calendar-check display-4 mb-2"></i>
<h4 class="fw-bold mb-1">{{ floor($totalMembershipDuration / 12) }}y {{ $totalMembershipDuration % 12 }}m</h4>
<small>Total Membership</small>
</div>
</div>
</div>
</div>
<!-- Timeline and Skills Wheel Container -->
<div class="row">
<!-- Timeline -->
<div class="col-lg-6 mb-4">
<div class="card shadow-sm">
<div class="card-header bg-light">
<h6 class="card-title mb-0"><i class="bi bi-timeline me-2"></i>Club Affiliations Timeline</h6>
</div>
<div class="card-body" style="max-height: 600px; overflow-y: auto;">
<div class="timeline">
@foreach($clubAffiliations as $index => $affiliation)
<div class="timeline-item mb-4 position-relative" data-affiliation-id="{{ $affiliation->id }}">
<div class="timeline-marker bg-primary"></div>
<div class="timeline-content card border {{ $index === 0 ? 'border-primary' : '' }} affiliation-card" style="cursor: pointer;" data-affiliation-id="{{ $affiliation->id }}">
<div class="card-body p-3">
<div class="d-flex align-items-center mb-2">
@if($affiliation->logo)
<img src="{{ asset('storage/' . $affiliation->logo) }}" alt="{{ $affiliation->club_name }}" class="me-3 rounded" style="width: 40px; height: 40px; object-fit: cover;">
@else
<div class="bg-primary text-white rounded d-flex align-items-center justify-content-center me-3" style="width: 40px; height: 40px;">
<i class="bi bi-building"></i>
</div>
@endif
<div class="flex-grow-1">
<h6 class="mb-0 fw-bold">{{ $affiliation->club_name }}</h6>
<small class="text-muted">{{ $affiliation->date_range }}</small>
<br>
<span class="badge bg-info text-dark small">{{ $affiliation->formatted_duration }}</span>
</div>
</div>
@if($affiliation->location)
<div class="text-muted small mb-1">
<i class="bi bi-geo-alt me-1"></i>{{ $affiliation->location }}
</div>
@endif
<div class="text-muted small">
<i class="bi bi-star me-1"></i>{{ $affiliation->skillAcquisitions->count() }} skills acquired
</div>
</div>
</div>
</div>
@endforeach
</div>
</div>
</div>
</div>
<!-- Skills Wheel and Details -->
<div class="col-lg-6">
<!-- Skills Wheel -->
<div class="card shadow-sm mb-4">
<div class="card-header bg-light">
<h6 class="card-title mb-0"><i class="bi bi-pie-chart me-2"></i>Skills Wheel</h6>
</div>
<div class="card-body">
<div class="text-center">
<canvas id="skillsChart" width="300" height="300"></canvas>
<p id="noSkillsMessage" class="text-muted mt-3 d-none">Select an affiliation to view skills</p>
</div>
</div>
</div>
<!-- Affiliation Details -->
<div class="card shadow-sm">
<div class="card-header bg-light">
<h6 class="card-title mb-0"><i class="bi bi-info-circle me-2"></i>Affiliation Details</h6>
</div>
<div class="card-body" id="affiliationDetails">
<p class="text-muted">Select an affiliation from the timeline to view details</p>
</div>
</div>
</div>
</div>
@else
<div class="text-center py-5">
<i class="bi bi-diagram-3 text-muted" style="font-size: 3rem;"></i>
<h5 class="text-muted mt-3 mb-2">No Affiliations Yet</h5>
<p class="text-muted mb-0">Club affiliations and skills will appear here once added</p>
</div>
@endif
</div>
</div>
@include('family.partials.affiliations-enhanced')
</div>
<!-- Tournaments Tab -->
<div class="tab-pane fade" id="tournaments" role="tabpanel">
<div class="card shadow-sm border-0">
<!-- Tournament & Event Participation Card -->
<div class="card shadow-sm border-0 mb-4">
<div class="card-body p-4">
<!-- Section Title & Subtitle -->
<div class="d-flex justify-content-between align-items-center mb-4">
@@ -1131,7 +1015,7 @@
</div>
<!-- Award Summary Cards -->
<div class="row g-3 mb-4" id="awardCards">
<div class="row g-3" id="awardCards">
<div class="col-md-3">
<div class="card shadow-sm border-0 text-center" style="background: linear-gradient(135deg, #FFD700 0%, #FFA500 100%);">
<div class="card-body p-3">
@@ -1169,114 +1053,114 @@
</div>
</div>
</div>
</div>
</div>
<!-- Tournament History Table -->
<div class="card shadow-sm border-0">
<div class="card-body p-4">
<h6 class="fw-bold mb-3"><i class="bi bi-list-ul me-2"></i>Tournament & Championships History</h6>
<!-- Tournament & Championships History Card -->
<div class="card shadow-sm border-0">
<div class="card-body p-4">
<h6 class="fw-bold mb-3"><i class="bi bi-list-ul me-2"></i>Tournament & Championships History</h6>
@if($tournamentEvents->count() > 0)
<div class="table-responsive">
<table class="table table-hover align-middle" id="tournamentsTable">
<thead class="table-light">
<tr>
<th class="text-muted small fw-semibold">Tournament Details</th>
<th class="text-muted small fw-semibold">Club Affiliation</th>
<th class="text-muted small fw-semibold">Performance & Result</th>
<th class="text-muted small fw-semibold">Notes & Media</th>
</tr>
</thead>
<tbody>
@foreach($tournamentEvents as $event)
<tr data-sport="{{ $event->sport }}">
<td>
<div class="fw-bold">{{ $event->title }}</div>
<div class="d-flex gap-2 mt-1 flex-wrap">
<span class="badge bg-{{ $event->type == 'championship' ? 'primary' : 'secondary' }} small">{{ ucfirst($event->type) }}</span>
<span class="badge bg-info small">{{ $event->sport }}</span>
</div>
<div class="text-muted small mt-1">
<i class="bi bi-calendar-event me-1"></i>{{ $event->date->format('M j, Y') }}
@if($event->time)
<i class="bi bi-clock me-1 ms-2"></i>{{ $event->time->format('H:i') }}
@if($tournamentEvents->count() > 0)
<div class="table-responsive">
<table class="table table-hover align-middle" id="tournamentsTable">
<thead class="table-light">
<tr>
<th class="text-muted small fw-semibold">Tournament Details</th>
<th class="text-muted small fw-semibold">Club Affiliation</th>
<th class="text-muted small fw-semibold">Performance & Result</th>
<th class="text-muted small fw-semibold">Notes & Media</th>
</tr>
</thead>
<tbody>
@foreach($tournamentEvents as $event)
<tr data-sport="{{ $event->sport }}">
<td>
<div class="fw-bold">{{ $event->title }}</div>
<div class="d-flex gap-2 mt-1 flex-wrap">
<span class="badge bg-{{ $event->type == 'championship' ? 'primary' : 'secondary' }} small">{{ ucfirst($event->type) }}</span>
<span class="badge bg-info small">{{ $event->sport }}</span>
</div>
<div class="text-muted small mt-1">
<i class="bi bi-calendar-event me-1"></i>{{ $event->date->format('M j, Y') }}
@if($event->time)
<i class="bi bi-clock me-1 ms-2"></i>{{ $event->time->format('H:i') }}
@endif
@if($event->location)
<i class="bi bi-geo-alt me-1 ms-2"></i>{{ $event->location }}
@endif
@if($event->participants_count)
<i class="bi bi-people me-1 ms-2"></i>{{ $event->participants_count }} participants
@endif
</div>
</td>
<td>
@if($event->clubAffiliation)
<div>
<div class="small fw-semibold">{{ $event->clubAffiliation->club_name }}</div>
<div class="text-muted small">{{ $event->clubAffiliation->location }}</div>
</div>
@else
<span class="text-muted small">Individual</span>
@endif
</td>
<td>
@if($event->performanceResults->count() > 0)
@foreach($event->performanceResults as $result)
<div class="d-flex align-items-center gap-2 mb-1">
@if($result->medal_type == '1st')
<i class="bi bi-award-fill text-warning"></i>
<span class="badge bg-warning text-dark small">1st Place</span>
@elseif($result->medal_type == '2nd')
<i class="bi bi-award-fill text-secondary"></i>
<span class="badge bg-secondary small">2nd Place</span>
@elseif($result->medal_type == '3rd')
<i class="bi bi-award-fill" style="color: #CD7F32;"></i>
<span class="badge" style="background-color: #CD7F32; color: white;" small>3rd Place</span>
@elseif($result->medal_type == 'special')
<i class="bi bi-trophy-fill text-warning"></i>
<span class="badge bg-warning text-dark small">Special Award</span>
@endif
@if($event->location)
<i class="bi bi-geo-alt me-1 ms-2"></i>{{ $event->location }}
@endif
@if($event->participants_count)
<i class="bi bi-people me-1 ms-2"></i>{{ $event->participants_count }} participants
@if($result->points)
<small class="text-muted">{{ $result->points }} pts</small>
@endif
</div>
</td>
<td>
@if($event->clubAffiliation)
<div>
<div class="small fw-semibold">{{ $event->clubAffiliation->club_name }}</div>
<div class="text-muted small">{{ $event->clubAffiliation->location }}</div>
</div>
@else
<span class="text-muted small">Individual</span>
@if($result->description)
<small class="text-muted">{{ $result->description }}</small>
@endif
</td>
<td>
@if($event->performanceResults->count() > 0)
@foreach($event->performanceResults as $result)
<div class="d-flex align-items-center gap-2 mb-1">
@if($result->medal_type == '1st')
<i class="bi bi-award-fill text-warning"></i>
<span class="badge bg-warning text-dark small">1st Place</span>
@elseif($result->medal_type == '2nd')
<i class="bi bi-award-fill text-secondary"></i>
<span class="badge bg-secondary small">2nd Place</span>
@elseif($result->medal_type == '3rd')
<i class="bi bi-award-fill" style="color: #CD7F32;"></i>
<span class="badge" style="background-color: #CD7F32; color: white;" small>3rd Place</span>
@elseif($result->medal_type == 'special')
<i class="bi bi-trophy-fill text-warning"></i>
<span class="badge bg-warning text-dark small">Special Award</span>
@endif
@if($result->points)
<small class="text-muted">{{ $result->points }} pts</small>
@endif
</div>
@if($result->description)
<small class="text-muted">{{ $result->description }}</small>
@endif
@endforeach
@else
<span class="text-muted small">No results recorded</span>
@endforeach
@else
<span class="text-muted small">No results recorded</span>
@endif
</td>
<td>
@if($event->notesMedia->count() > 0)
@foreach($event->notesMedia as $note)
@if($note->note_text)
<p class="mb-1 small">{{ $note->note_text }}</p>
@endif
</td>
<td>
@if($event->notesMedia->count() > 0)
@foreach($event->notesMedia as $note)
@if($note->note_text)
<p class="mb-1 small">{{ $note->note_text }}</p>
@endif
@if($note->media_link)
<a href="{{ $note->media_link }}" target="_blank" class="btn btn-sm btn-outline-primary small">
<i class="bi bi-image me-1"></i>View Media
</a>
@endif
@endforeach
@else
<span class="text-muted small">No notes available</span>
@if($note->media_link)
<a href="{{ $note->media_link }}" target="_blank" class="btn btn-sm btn-outline-primary small">
<i class="bi bi-image me-1"></i>View Media
</a>
@endif
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
@else
<div class="text-center py-5">
<i class="bi bi-trophy text-muted" style="font-size: 3rem;"></i>
<p class="text-muted mt-3">No tournament records found</p>
<small class="text-muted">Tournament participation will appear here once records are added</small>
</div>
@endif
@endforeach
@else
<span class="text-muted small">No notes available</span>
@endif
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
@else
<div class="text-center py-5">
<i class="bi bi-trophy text-muted" style="font-size: 3rem;"></i>
<p class="text-muted mt-3">No tournament records found</p>
<small class="text-muted">Tournament participation will appear here once records are added</small>
</div>
@endif
</div>
</div>
</div>
@@ -1461,8 +1345,14 @@
</select>
</div>
<div class="col-md-6">
<label for="tournament_date" class="form-label">Date <span class="text-danger">*</span></label>
<input type="date" class="form-control" id="tournament_date" name="date" required>
<x-birthdate-dropdown
name="date"
id="tournament_date"
label="Date"
:required="true"
:min-year="2000"
:max-year="date('Y')"
:error="$errors->first('date')" />
</div>
<div class="col-md-6">
<label for="tournament_time" class="form-label">Time</label>
@@ -0,0 +1,282 @@
@extends('layouts.app')
@section('content')
<style>
/* Club Admin Layout - BOXED with Sidebar */
.club-admin-wrapper {
max-width: 1400px;
margin: 20px auto;
padding: 0 15px;
display: flex;
gap: 20px;
}
.club-admin-sidebar {
width: 280px;
min-width: 280px;
background-color: hsl(var(--muted) / 0.3);
padding: 1.5rem;
border-radius: 0.75rem;
height: fit-content;
position: sticky;
top: 92px;
}
.club-logo-container {
width: 100%;
padding: 1rem;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 1rem;
}
.club-logo-container img {
max-width: 100%;
max-height: 120px;
object-fit: contain;
}
.club-name-header {
font-size: 1.125rem;
font-weight: 700;
color: hsl(var(--foreground));
padding: 0 0.5rem 1rem;
text-align: center;
}
.sidebar-action-buttons {
display: flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
padding-bottom: 1rem;
margin-bottom: 1rem;
border-bottom: 1px solid hsl(var(--border));
}
.sidebar-action-btn {
width: 36px;
height: 36px;
border-radius: 0.5rem;
display: flex;
align-items: center;
justify-content: center;
background-color: transparent;
border: none;
color: hsl(var(--foreground));
cursor: pointer;
transition: all 0.2s;
}
.sidebar-action-btn:hover {
background-color: hsl(var(--accent));
}
.club-admin-sidebar nav {
display: flex;
flex-direction: column;
gap: 0.25rem;
}
.club-nav-item {
display: flex;
align-items: center;
gap: 0.75rem;
padding: 0.75rem 1rem;
border-radius: 0.5rem;
transition: all 0.2s;
text-decoration: none;
color: hsl(var(--foreground));
font-size: 0.875rem;
font-weight: 500;
}
.club-nav-item:hover {
background-color: hsl(var(--muted));
text-decoration: none;
color: hsl(var(--foreground));
}
.club-nav-item.active {
background-color: hsl(var(--primary));
color: white;
box-shadow: var(--shadow-primary);
}
.club-nav-item i {
width: 20px;
font-size: 1.1rem;
}
.club-nav-divider {
border-top: 1px solid hsl(var(--border));
margin: 0.75rem 0;
}
.club-admin-content {
flex: 1;
min-width: 0;
}
/* Mobile Sidebar Toggle */
.mobile-sidebar-toggle {
display: none;
position: sticky;
top: 72px;
z-index: 40;
background-color: hsl(var(--background));
border-bottom: 1px solid hsl(var(--border));
padding: 1rem;
}
.mobile-sidebar-toggle button {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem 1rem;
border: 1px solid hsl(var(--border));
border-radius: 0.5rem;
background: white;
font-weight: 600;
cursor: pointer;
}
@media (max-width: 992px) {
.club-admin-wrapper {
flex-direction: column;
}
.club-admin-sidebar {
display: none;
width: 100%;
position: relative;
top: 0;
}
.club-admin-sidebar.show {
display: block;
}
.mobile-sidebar-toggle {
display: block;
}
}
</style>
<!-- Mobile Sidebar Toggle -->
<div class="mobile-sidebar-toggle">
<button onclick="document.querySelector('.club-admin-sidebar').classList.toggle('show')">
<i class="bi bi-list"></i>
<span>{{ $club->club_name ?? 'Club Menu' }}</span>
</button>
</div>
<!-- Club Admin Wrapper -->
<div class="club-admin-wrapper">
<!-- Sidebar -->
<aside class="club-admin-sidebar">
@if(isset($club) && $club->logo)
<div class="club-logo-container">
<img src="{{ asset('storage/' . $club->logo) }}" alt="{{ $club->club_name }}">
</div>
@else
<h2 class="club-name-header">{{ $club->club_name ?? 'Club Admin' }}</h2>
@endif
<!-- Action Buttons -->
<div class="sidebar-action-buttons">
<a href="{{ route('admin.platform.clubs') }}" class="sidebar-action-btn" title="Back to Clubs">
<i class="bi bi-arrow-left"></i>
</a>
<a href="{{ route('clubs.explore') }}" class="sidebar-action-btn" title="Preview Club" target="_blank">
<i class="bi bi-eye"></i>
</a>
<button class="sidebar-action-btn" title="Send Notification">
<i class="bi bi-send"></i>
</button>
</div>
<nav>
@php
$clubId = $club->id ?? null;
$currentRoute = request()->route()->getName();
@endphp
<a href="{{ route('admin.club.dashboard', $clubId) }}"
class="club-nav-item {{ $currentRoute === 'admin.club.dashboard' ? 'active' : '' }}">
<i class="bi bi-speedometer2"></i>
<span>Dashboard</span>
</a>
<a href="{{ route('admin.club.details', $clubId) }}"
class="club-nav-item {{ $currentRoute === 'admin.club.details' ? 'active' : '' }}">
<i class="bi bi-building"></i>
<span>Club Details</span>
</a>
<a href="{{ route('admin.club.gallery', $clubId) }}"
class="club-nav-item {{ $currentRoute === 'admin.club.gallery' ? 'active' : '' }}">
<i class="bi bi-images"></i>
<span>Gallery</span>
</a>
<a href="{{ route('admin.club.facilities', $clubId) }}"
class="club-nav-item {{ $currentRoute === 'admin.club.facilities' ? 'active' : '' }}">
<i class="bi bi-geo-alt"></i>
<span>Facilities</span>
</a>
<a href="{{ route('admin.club.instructors', $clubId) }}"
class="club-nav-item {{ $currentRoute === 'admin.club.instructors' ? 'active' : '' }}">
<i class="bi bi-people"></i>
<span>Instructors</span>
</a>
<a href="{{ route('admin.club.activities', $clubId) }}"
class="club-nav-item {{ $currentRoute === 'admin.club.activities' ? 'active' : '' }}">
<i class="bi bi-activity"></i>
<span>Activities</span>
</a>
<a href="{{ route('admin.club.packages', $clubId) }}"
class="club-nav-item {{ $currentRoute === 'admin.club.packages' ? 'active' : '' }}">
<i class="bi bi-box"></i>
<span>Packages</span>
</a>
<a href="{{ route('admin.club.members', $clubId) }}"
class="club-nav-item {{ $currentRoute === 'admin.club.members' ? 'active' : '' }}">
<i class="bi bi-person-plus"></i>
<span>Members</span>
</a>
<a href="{{ route('admin.club.roles', $clubId) }}"
class="club-nav-item {{ $currentRoute === 'admin.club.roles' ? 'active' : '' }}">
<i class="bi bi-shield-check"></i>
<span>Roles</span>
</a>
<a href="{{ route('admin.club.financials', $clubId) }}"
class="club-nav-item {{ $currentRoute === 'admin.club.financials' ? 'active' : '' }}">
<i class="bi bi-currency-dollar"></i>
<span>Financials</span>
</a>
<a href="{{ route('admin.club.messages', $clubId) }}"
class="club-nav-item {{ $currentRoute === 'admin.club.messages' ? 'active' : '' }}">
<i class="bi bi-chat-dots"></i>
<span>Messages</span>
</a>
<a href="{{ route('admin.club.analytics', $clubId) }}"
class="club-nav-item {{ $currentRoute === 'admin.club.analytics' ? 'active' : '' }}">
<i class="bi bi-bar-chart"></i>
<span>Analytics</span>
</a>
</nav>
</aside>
<!-- Content -->
<main class="club-admin-content">
@yield('club-admin-content')
</main>
</div>
@endsection
+1 -1
View File
@@ -206,7 +206,7 @@
right: -2px;
width: 10px;
height: 10px;
background-color: #28a745;
background-color: hsl(var(--success));
border-radius: 50%;
border: 2px solid white;
}
+187 -39
View File
@@ -5,9 +5,19 @@
$shape = $attributes->get('shape', 'circle');
$folder = $attributes->get('folder', 'uploads');
$filename = $attributes->get('filename', 'cropped_' . time());
$uploadUrl = $attributes->get('uploadUrl', route('profile.upload-picture'));
$uploadUrl = $attributes->get('uploadUrl', '');
$currentImage = $attributes->get('currentImage', '');
$buttonText = $attributes->get('buttonText', 'Change Photo');
$buttonClass = $attributes->get('buttonClass', 'btn btn-success px-4 fw-bold shadow-sm');
$mode = $attributes->get('mode', 'ajax'); // 'ajax' or 'form'
$inputName = $attributes->get('inputName', 'image'); // For form mode - the hidden input name
$previewWidth = $attributes->get('previewWidth', $width);
$previewHeight = $attributes->get('previewHeight', $height);
$showPreview = $attributes->get('showPreview', $mode === 'form'); // Show preview by default in form mode
@endphp
<x-toast-notification />
@once
<link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/cropme.min.css">
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
@@ -32,12 +42,78 @@
}
.form-range::-webkit-slider-thumb { background: #198754; }
.form-range::-moz-range-thumb { background: #198754; }
.cropper-preview-container {
position: relative;
display: inline-block;
}
.cropper-preview-placeholder {
display: flex;
align-items: center;
justify-content: center;
background-color: #f0f0f0;
border: 2px dashed #dee2e6;
color: #6c757d;
}
.cropper-preview-image {
object-fit: cover;
border: 2px solid #dee2e6;
}
.cropper-remove-btn {
position: absolute;
top: -8px;
right: -8px;
width: 24px;
height: 24px;
border-radius: 50%;
background: #dc3545;
color: white;
border: none;
font-size: 14px;
line-height: 1;
cursor: pointer;
display: none;
}
.cropper-preview-container.has-image .cropper-remove-btn {
display: flex;
align-items: center;
justify-content: center;
}
</style>
@endonce
<button type="button" class="btn btn-success px-4 fw-bold shadow-sm" data-bs-toggle="modal" data-bs-target="#cropperModal_{{ $id }}">
Change Photo
@if($mode === 'form')
{{-- Form mode: Show preview and hidden input --}}
<div class="cropper-form-wrapper text-center" id="wrapper_{{ $id }}">
<div class="cropper-preview-container mb-2" id="previewContainer_{{ $id }}">
@if($currentImage)
<img src="{{ $currentImage }}"
id="preview_{{ $id }}"
class="cropper-preview-image"
style="width: {{ $previewWidth }}px; height: {{ $previewHeight }}px; border-radius: {{ $shape === 'circle' ? '50%' : '8px' }};">
<button type="button" class="cropper-remove-btn" id="removeBtn_{{ $id }}" onclick="removeImage_{{ $id }}()">
<i class="bi bi-x"></i>
</button>
@else
<div id="preview_{{ $id }}"
class="cropper-preview-placeholder"
style="width: {{ $previewWidth }}px; height: {{ $previewHeight }}px; border-radius: {{ $shape === 'circle' ? '50%' : '8px' }};">
<i class="bi bi-image" style="font-size: 2rem;"></i>
</div>
@endif
</div>
<input type="hidden" name="{{ $inputName }}" id="hiddenInput_{{ $id }}" value="">
<input type="hidden" name="{{ $inputName }}_folder" value="{{ $folder }}">
<input type="hidden" name="{{ $inputName }}_filename" value="{{ $filename }}">
<button type="button" class="{{ $buttonClass }}" data-bs-toggle="modal" data-bs-target="#cropperModal_{{ $id }}">
<i class="bi bi-camera me-2"></i>{{ $buttonText }}
</button>
</div>
@else
{{-- AJAX mode: Just the button --}}
<button type="button" class="{{ $buttonClass }}" data-bs-toggle="modal" data-bs-target="#cropperModal_{{ $id }}">
<i class="bi bi-camera me-2"></i>{{ $buttonText }}
</button>
@endif
@push('modals')
<div class="modal fade" id="cropperModal_{{ $id }}" tabindex="-1" aria-hidden="true" data-bs-backdrop="static">
@@ -64,7 +140,11 @@
<div class="d-grid gap-2 mt-2">
<button type="button" class="btn btn-success btn-lg fw-bold py-3" id="save_{{ $id }}">
Crop & Save Image
@if($mode === 'form')
Crop & Apply
@else
Crop & Save Image
@endif
</button>
</div>
</div>
@@ -78,6 +158,8 @@ $(function() {
const el_{{ $id }} = document.getElementById("box_{{ $id }}");
const zoomMin_{{ $id }} = 0.01;
const zoomMax_{{ $id }} = 3;
const currentImage_{{ $id }} = '{{ $currentImage }}';
const mode_{{ $id }} = '{{ $mode }}';
function applyTransform_{{ $id }}(instance) {
if (!instance.properties.image) return;
@@ -86,29 +168,40 @@ $(function() {
p.image.style.transform = t;
}
function initCropper_{{ $id }}(imageUrl) {
if (cropper_{{ $id }}) cropper_{{ $id }}.destroy();
cropper_{{ $id }} = new Cropme(el_{{ $id }}, {
container: { width: '100%', height: 400 },
viewport: {
width: {{ $width }},
height: {{ $height }},
type: '{{ $shape }}',
border: { enable: true, width: 2, color: '#fff' }
},
transformOrigin: 'viewport',
zoom: { min: zoomMin_{{ $id }}, max: zoomMax_{{ $id }}, enable: true, mouseWheel: true, slider: false },
rotation: { enable: true, slider: false }
});
cropper_{{ $id }}.bind({ url: imageUrl }).then(() => {
$('#zoom_{{ $id }}').val(0);
$('#rot_{{ $id }}').val(0);
});
}
// Load current image when modal opens
$('#cropperModal_{{ $id }}').on('shown.bs.modal', function() {
if (currentImage_{{ $id }} && !cropper_{{ $id }}) {
initCropper_{{ $id }}(currentImage_{{ $id }});
}
});
$('#input_{{ $id }}').on('change', function() {
if (this.files && this.files[0]) {
const reader = new FileReader();
reader.onload = function(event) {
if (cropper_{{ $id }}) cropper_{{ $id }}.destroy();
cropper_{{ $id }} = new Cropme(el_{{ $id }}, {
container: { width: '100%', height: 400 },
viewport: {
width: {{ $width }},
height: {{ $height }},
type: '{{ $shape }}',
border: { enable: true, width: 2, color: '#fff' }
},
transformOrigin: 'viewport',
zoom: { min: zoomMin_{{ $id }}, max: zoomMax_{{ $id }}, enable: true, mouseWheel: true, slider: false },
rotation: { enable: true, slider: false }
});
cropper_{{ $id }}.bind({ url: event.target.result }).then(() => {
$('#zoom_{{ $id }}').val(0);
$('#rot_{{ $id }}').val(0);
});
initCropper_{{ $id }}(event.target.result);
};
reader.readAsDataURL(this.files[0]);
}
@@ -131,26 +224,81 @@ $(function() {
$('#save_{{ $id }}').on('click', function() {
if (!cropper_{{ $id }}) return;
const btn = $(this);
btn.prop('disabled', true).text('Uploading...');
cropper_{{ $id }}.crop({ type: 'base64' }).then(base64 => {
$.post("{{ $uploadUrl }}", {
_token: "{{ csrf_token() }}",
image: base64,
folder: '{{ $folder }}',
filename: '{{ $filename }}'
}).done((res) => {
alert('Saved successfully!');
if (mode_{{ $id }} === 'form') {
// Form mode: Store base64 in hidden input and update preview
btn.prop('disabled', true).text('Processing...');
cropper_{{ $id }}.crop({ type: 'base64' }).then(base64 => {
// Store in hidden input
$('#hiddenInput_{{ $id }}').val(base64);
// Update preview
const previewContainer = $('#previewContainer_{{ $id }}');
const borderRadius = '{{ $shape }}' === 'circle' ? '50%' : '8px';
previewContainer.html(`
<img src="${base64}"
id="preview_{{ $id }}"
class="cropper-preview-image"
style="width: {{ $previewWidth }}px; height: {{ $previewHeight }}px; border-radius: ${borderRadius};">
<button type="button" class="cropper-remove-btn" id="removeBtn_{{ $id }}" onclick="removeImage_{{ $id }}()">
<i class="bi bi-x"></i>
</button>
`);
previewContainer.addClass('has-image');
// Close modal
$('#cropperModal_{{ $id }}').modal('hide');
// Reload page to show new image
location.reload();
}).fail((err) => {
alert('Upload failed: ' + (err.responseJSON?.message || 'Unknown error'));
}).always(() => {
btn.prop('disabled', false).text('Crop & Save Image');
btn.prop('disabled', false).text('Crop & Apply');
});
});
} else {
// AJAX mode: Upload to server
btn.prop('disabled', true).text('Uploading...');
cropper_{{ $id }}.crop({ type: 'base64' }).then(base64 => {
$.post("{{ $uploadUrl }}", {
_token: "{{ csrf_token() }}",
image: base64,
folder: '{{ $folder }}',
filename: '{{ $filename }}'
}).done((res) => {
$('#cropperModal_{{ $id }}').modal('hide');
Toast.success('Photo Updated!', 'Your image has been saved successfully.');
// Reload page after toast shows
setTimeout(() => location.reload(), 1500);
}).fail((err) => {
Toast.error('Upload Failed', err.responseJSON?.message || 'An error occurred while uploading.');
}).always(() => {
btn.prop('disabled', false).text('Crop & Save Image');
});
});
}
});
// Remove image function for form mode
window.removeImage_{{ $id }} = function() {
const previewContainer = $('#previewContainer_{{ $id }}');
const borderRadius = '{{ $shape }}' === 'circle' ? '50%' : '8px';
// Clear hidden input
$('#hiddenInput_{{ $id }}').val('');
// Reset preview to placeholder
previewContainer.html(`
<div id="preview_{{ $id }}"
class="cropper-preview-placeholder"
style="width: {{ $previewWidth }}px; height: {{ $previewHeight }}px; border-radius: ${borderRadius};">
<i class="bi bi-image" style="font-size: 2rem;"></i>
</div>
`);
previewContainer.removeClass('has-image');
};
// Initialize remove button visibility if current image exists
@if($currentImage && $mode === 'form')
$('#previewContainer_{{ $id }}').addClass('has-image');
@endif
});
</script>
@endpush