done lots of work till now

This commit is contained in:
Ghassan Yusuf 2026-01-28 03:37:51 +03:00
parent a7434e33d7
commit c87c4bfd6d
10 changed files with 256 additions and 116 deletions

30
TODO.md
View File

@ -1,9 +1,21 @@
- [x] Add icon to Body Composition Analysis # Bills Page Modifications - Completed
- [x] Add icon to Compare
- [x] Change Health Tracking History to Health Tracking and add icon ## Tasks Completed
- [x] Add icon to Goals & Progress - [x] Add back button to the right side of the title
- [x] Add icon to Attendance Records - Added a back button using `url()->previous()` to return to the previous page
- [x] Add icon to Tournament History - [x] Separate the buttons inside the All Bills card
- [x] Add icon to Event Participation - Removed `btn-group` and made buttons individual with `d-flex gap-2`
- [x] Add icon to Affiliations & Badges - [x] Add date picker start date and end date to filter results
- [x] Change Affiliations tab and header icon from bi-trophy to bi-diagram-3 - Added form with start_date and end_date inputs
- Updated InvoiceController to handle date filtering on due_date
- Form submits GET request to same route with query params
## Files Modified
- `app/Http/Controllers/InvoiceController.php`: Added filtering logic for status and date range
- `resources/views/invoices/index.blade.php`: Updated UI with back button, separated buttons, and date filters
## Technical Details
- Back button uses Laravel's `url()->previous()` helper
- Date filters apply to `due_date` field in database
- Status filtering maintained existing functionality
- Form uses GET method to allow bookmarkable/filtered URLs

View File

@ -56,6 +56,11 @@ class RegisteredUserController extends Controller
'nationality' => $request->nationality, 'nationality' => $request->nationality,
]); ]);
// Assign super-admin role to the first registered user
if (User::count() === 1) {
$user->assignRole('super-admin');
}
event(new Registered($user)); event(new Registered($user));
// Send welcome email // Send welcome email

View File

@ -18,12 +18,26 @@ class InvoiceController extends Controller
* *
* @return \Illuminate\View\View * @return \Illuminate\View\View
*/ */
public function index() public function index(Request $request)
{ {
$user = Auth::user(); $user = Auth::user();
$invoices = Invoice::where('payer_user_id', $user->id) $query = Invoice::where('payer_user_id', $user->id)
->with(['student', 'tenant']) ->with(['student', 'tenant']);
->get();
// Filter by status
if ($request->has('status') && in_array($request->status, ['pending', 'paid'])) {
$query->where('status', $request->status);
}
// Filter by date range
if ($request->has('start_date') && $request->start_date) {
$query->where('due_date', '>=', $request->start_date);
}
if ($request->has('end_date') && $request->end_date) {
$query->where('due_date', '<=', $request->end_date);
}
$invoices = $query->get();
return view('invoices.index', compact('invoices')); return view('invoices.index', compact('invoices'));
} }

View File

@ -667,6 +667,58 @@ class MemberController extends Controller
return response()->json(['success' => true, 'message' => 'Goal updated successfully']); return response()->json(['success' => true, 'message' => 'Goal updated successfully']);
} }
/**
* Confirm and remove the specified member from storage.
*
* @param \Illuminate\Http\Request $request
* @param int $id
* @return \Illuminate\Http\RedirectResponse
*/
public function confirmDelete(Request $request, $id)
{
$validated = $request->validate([
'confirm_name' => 'required|string',
]);
$user = Auth::user();
// Check if user is super-admin
$isSuperAdmin = $user->hasRole('super-admin');
// Prevent deleting own account
if ($user->id == $id) {
return redirect()->back()
->with('error', 'You cannot delete your own account.');
}
// For regular users, verify family relationship exists
if (!$isSuperAdmin) {
UserRelationship::where('guardian_user_id', $user->id)
->where('dependent_user_id', $id)
->firstOrFail();
}
$member = User::findOrFail($id);
// Verify the confirmation name matches
if ($validated['confirm_name'] !== $member->full_name) {
return redirect()->back()
->with('error', 'Confirmation name does not match. Account deletion cancelled.');
}
$memberName = $member->full_name;
$member->delete();
// Redirect based on user type
if ($isSuperAdmin) {
return redirect()->route('admin.platform.members')
->with('success', $memberName . ' has been removed successfully.');
}
return redirect()->route('members.index')
->with('success', 'Member removed successfully.');
}
/** /**
* Remove the specified member from storage. * Remove the specified member from storage.
* *

View File

@ -102,7 +102,10 @@
<div class="row g-3 mb-3"> <div class="row g-3 mb-3">
<div class="col-6"> <div class="col-6">
<div class="small text-muted text-uppercase fw-medium mb-1" style="font-size: 0.7rem; letter-spacing: 0.5px;">Gender</div> <div class="small text-muted text-uppercase fw-medium mb-1" style="font-size: 0.7rem; letter-spacing: 0.5px;">Gender</div>
<div class="fw-semibold text-muted text-capitalize">{{ $member->gender == 'm' ? 'Male' : 'Female' }}</div> <div class="fw-semibold text-muted text-capitalize">
<i class="bi {{ $member->gender == 'm' ? 'bi-man text-primary' : 'bi-woman text-danger' }} me-1"></i>
{{ $member->gender == 'm' ? 'Male' : 'Female' }}
</div>
</div> </div>
<div class="col-6"> <div class="col-6">
<div class="small text-muted text-uppercase fw-medium mb-1" style="font-size: 0.7rem; letter-spacing: 0.5px;">Age</div> <div class="small text-muted text-uppercase fw-medium mb-1" style="font-size: 0.7rem; letter-spacing: 0.5px;">Age</div>

View File

@ -3,32 +3,36 @@
@section('content') @section('content')
<div class="container py-4"> <div class="container py-4">
<div class="d-flex justify-content-between align-items-center mb-4"> <div class="d-flex justify-content-between align-items-center mb-4">
<h1 class="mb-0">My Bills</h1> <div>
<h1 class="mb-0">Payments & Subscriptions</h1>
<p class="text-muted mb-0">Manage your club membership payments, subscriptions, and billing history</p>
</div>
<a href="{{ url()->previous() }}" class="btn btn-outline-secondary">
<i class="fas fa-arrow-left"></i> Back
</a>
</div> </div>
<div class="card shadow-sm"> <div class="card shadow-sm">
<div class="card-header bg-white d-flex justify-content-between align-items-center"> <div class="card-header bg-white">
<div class="d-flex justify-content-between align-items-center">
<h4 class="mb-0">All Bills</h4> <h4 class="mb-0">All Bills</h4>
<div class="btn-group" role="group"> <div class="d-flex align-items-center gap-3">
<button type="button" class="btn btn-outline-secondary btn-sm dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false"> <form method="GET" action="{{ route('bills.index') }}" class="d-flex gap-2 align-items-center">
<i class="bi bi-funnel"></i> Filter <label for="start_date" class="form-label mb-0 me-1">From:</label>
</button> <input type="date" name="start_date" id="start_date" class="form-control form-control-sm" value="{{ request('start_date') }}">
<ul class="dropdown-menu dropdown-menu-end"> <label for="end_date" class="form-label mb-0 me-1 ms-2">To:</label>
<li><a class="dropdown-item" href="{{ route('bills.index') }}">All</a></li> <input type="date" name="end_date" id="end_date" class="form-control form-control-sm" value="{{ request('end_date') }}">
<li><a class="dropdown-item" href="{{ route('bills.index', ['status' => 'pending']) }}">Pending</a></li> <button type="submit" class="btn btn-primary btn-sm ms-2">Filter</button>
<li><a class="dropdown-item" href="{{ route('bills.index', ['status' => 'paid']) }}">Paid</a></li> </form>
<li><a class="dropdown-item" href="{{ route('bills.index', ['status' => 'overdue']) }}">Overdue</a></li> <div class="d-flex gap-2">
</ul> <a href="{{ route('bills.index') }}" class="btn btn-outline-secondary {{ !request('status') ? 'active' : '' }}">All</a>
<a href="{{ route('bills.index', ['status' => 'pending']) }}" class="btn btn-warning {{ request('status') === 'pending' ? 'active' : '' }}">Pending</a>
<a href="{{ route('bills.index', ['status' => 'paid']) }}" class="btn btn-success {{ request('status') === 'paid' ? 'active' : '' }}">Paid</a>
</div>
</div>
</div> </div>
</div> </div>
<div class="card-body"> <div class="card-body">
@if(session('success'))
<div class="alert alert-success alert-dismissible fade show" role="alert">
{{ session('success') }}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
@endif
@if($invoices->count() > 0) @if($invoices->count() > 0)
<div class="table-responsive"> <div class="table-responsive">
<table class="table table-hover"> <table class="table table-hover">
@ -36,7 +40,6 @@
<tr> <tr>
<th>Invoice #</th> <th>Invoice #</th>
<th>Student</th> <th>Student</th>
<th>Club</th>
<th>Amount</th> <th>Amount</th>
<th>Status</th> <th>Status</th>
<th>Due Date</th> <th>Due Date</th>
@ -47,94 +50,36 @@
@foreach($invoices as $invoice) @foreach($invoices as $invoice)
<tr> <tr>
<td>{{ $invoice->id }}</td> <td>{{ $invoice->id }}</td>
<td>{{ $invoice->student->full_name }}</td> <td>{{ $invoice->student_user->full_name ?? 'N/A' }}</td>
<td>{{ $invoice->tenant->club_name }}</td>
<td>${{ number_format($invoice->amount, 2) }}</td> <td>${{ number_format($invoice->amount, 2) }}</td>
<td> <td>
@if($invoice->status === 'paid') <span class="badge bg-{{ $invoice->status === 'paid' ? 'success' : 'warning' }}">
<span class="badge bg-success">Paid</span> {{ ucfirst($invoice->status) }}
@elseif($invoice->status === 'pending') </span>
<span class="badge bg-warning text-dark">Pending</span>
@else
<span class="badge bg-danger">Overdue</span>
@endif
</td> </td>
<td>{{ $invoice->due_date->format('M j, Y') }}</td> <td>{{ $invoice->due_date->format('M d, Y') }}</td>
<td> <td>
<div class="btn-group" role="group"> <a href="{{ route('bills.show', $invoice->id) }}" class="btn btn-sm btn-outline-primary">View</a>
<a href="#" class="btn btn-sm btn-outline-primary view-invoice" data-id="{{ $invoice->id }}"> @if($invoice->status === 'pending')
<i class="bi bi-eye"></i> View <a href="{{ route('bills.pay', $invoice->id) }}" class="btn btn-sm btn-success">Pay Now</a>
</a>
@if($invoice->status !== 'paid')
<a href="{{ route('bills.pay', $invoice->id) }}" class="btn btn-sm btn-success">
<i class="bi bi-credit-card"></i> Pay
</a>
@else @else
<a href="{{ route('bills.receipt', $invoice->id) }}" class="btn btn-sm btn-outline-info" target="_blank"> <a href="{{ route('bills.receipt', $invoice->id) }}" class="btn btn-sm btn-outline-secondary">Receipt</a>
<i class="bi bi-receipt"></i> Receipt
</a>
@endif @endif
</div>
</td> </td>
</tr> </tr>
@endforeach @endforeach
</tbody> </tbody>
</table> </table>
</div> </div>
{{ $invoices->links() }}
@else @else
<div class="text-center py-5"> <div class="text-center py-5">
<i class="bi bi-receipt" style="font-size: 3rem;"></i> <i class="fas fa-receipt fa-3x text-muted mb-3"></i>
<h4 class="mt-3">No Bills Found</h4> <h5 class="text-muted">No invoices found</h5>
<p class="text-muted">There are no bills matching your criteria.</p> <p class="text-muted">You don't have any invoices yet.</p>
</div> </div>
@endif @endif
</div> </div>
@if($invoices->where('status', '!=', 'paid')->count() > 0)
<div class="card-footer bg-white d-flex justify-content-end">
<a href="{{ route('bills.pay-all') }}" class="btn btn-success">
<i class="bi bi-credit-card"></i> Pay All
</a>
</div>
@endif
</div> </div>
</div> </div>
<!-- Invoice Modal -->
<div class="modal fade" id="invoiceModal" tabindex="-1" aria-labelledby="invoiceModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="invoiceModalLabel">Invoice Details</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body" id="invoiceModalBody">
<!-- Content will be loaded here -->
</div>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
document.querySelectorAll('.view-invoice').forEach(function(button) {
button.addEventListener('click', function(e) {
e.preventDefault();
var invoiceId = this.getAttribute('data-id');
fetch('/bills/' + invoiceId, {
method: 'GET',
headers: {
'X-Requested-With': 'XMLHttpRequest',
'Accept': 'application/json',
}
})
.then(response => response.json())
.then(data => {
document.getElementById('invoiceModalBody').innerHTML = data.html;
var modal = new bootstrap.Modal(document.getElementById('invoiceModal'));
modal.show();
});
});
});
});
</script>
@endsection @endsection

View File

@ -397,22 +397,22 @@
<h6 class="dropdown-header small"><strong>{{ Auth::user()->full_name }}</strong><br><small>{{ Auth::user()->email }}</small></h6> <h6 class="dropdown-header small"><strong>{{ Auth::user()->full_name }}</strong><br><small>{{ Auth::user()->email }}</small></h6>
<div class="dropdown-divider"></div> <div class="dropdown-divider"></div>
<a class="dropdown-item small" href="{{ route('member.show', Auth::id()) }}"> <a class="dropdown-item small" href="{{ route('member.show', Auth::id()) }}">
<i class="bi bi-person me-2"></i>My Profile <i class="bi bi-person me-2"></i>Profile
</a> </a>
<a class="dropdown-item small" href="#"> <a class="dropdown-item small" href="#">
<i class="bi bi-diagram-3 me-2"></i>Affiliations <i class="bi bi-diagram-3 me-2"></i>Affiliations
</a> </a>
<a class="dropdown-item small" href="#"> <a class="dropdown-item small" href="#">
<i class="bi bi-calendar-event me-2"></i>My Sessions <i class="bi bi-calendar-event me-2"></i>Sessions
</a> </a>
<a class="dropdown-item small" href="{{ route('members.index') }}"> <a class="dropdown-item small" href="{{ route('members.index') }}">
<i class="bi bi-people me-2"></i>Members <i class="bi bi-people me-2"></i>Family
</a> </a>
<a class="dropdown-item small" href="{{ route('bills.index') }}"> <a class="dropdown-item small" href="{{ route('bills.index') }}">
<i class="bi bi-receipt me-2"></i>My Bills <i class="bi bi-receipt me-2"></i>Payments & Subscriptions
</a> </a>
<a class="dropdown-item small" href="#"> <a class="dropdown-item small" href="#">
<i class="bi bi-gear me-2"></i>Settings <i class="bi bi-gear me-2"></i>Manage Business
</a> </a>
<div class="dropdown-divider"></div> <div class="dropdown-divider"></div>
@if(Auth::user()->isSuperAdmin()) @if(Auth::user()->isSuperAdmin())

View File

@ -3,7 +3,10 @@
@section('content') @section('content')
<div class="container py-4"> <div class="container py-4">
<div class="d-flex justify-content-between align-items-center mb-4"> <div class="d-flex justify-content-between align-items-center mb-4">
<h1 class="mb-0">Members</h1> <div>
<h1 class="mb-1">Family Members</h1>
<p class="text-muted mb-0">Manage and view your family members</p>
</div>
</div> </div>
<!-- Family Members Card Grid --> <!-- Family Members Card Grid -->
@ -88,11 +91,17 @@
<div class="row g-3 mb-3"> <div class="row g-3 mb-3">
<div class="col-6"> <div class="col-6">
<div class="small text-muted text-uppercase fw-medium mb-1" style="font-size: 0.7rem; letter-spacing: 0.5px;">Gender</div> <div class="small text-muted text-uppercase fw-medium mb-1" style="font-size: 0.7rem; letter-spacing: 0.5px;">Gender</div>
<div class="fw-semibold text-muted text-capitalize">{{ $relationship->dependent->gender == 'm' ? 'Male' : 'Female' }}</div> <div class="fw-semibold text-muted text-capitalize">
<i class="bi {{ $relationship->dependent->gender == 'm' ? 'bi-man text-primary' : 'bi-woman text-danger' }} me-1"></i>
{{ $relationship->dependent->gender == 'm' ? 'Male' : 'Female' }}
</div>
</div> </div>
<div class="col-6"> <div class="col-6">
<div class="small text-muted text-uppercase fw-medium mb-1" style="font-size: 0.7rem; letter-spacing: 0.5px;">Age</div> <div class="small text-muted text-uppercase fw-medium mb-1" style="font-size: 0.7rem; letter-spacing: 0.5px;">Age</div>
<div class="fw-semibold text-muted">{{ $relationship->dependent->age }} years</div> <div class="fw-semibold text-muted">
<i class="bi bi-cake text-warning me-1"></i>
{{ $relationship->dependent->age }} years
</div>
</div> </div>
</div> </div>
<div class="row g-3 mb-3"> <div class="row g-3 mb-3">

View File

@ -22,6 +22,11 @@
<h2 class="fw-bold mb-1">Member Profile</h2> <h2 class="fw-bold mb-1">Member Profile</h2>
<p class="text-muted mb-0">Comprehensive member information and analytics</p> <p class="text-muted mb-0">Comprehensive member information and analytics</p>
</div> </div>
<div>
<button onclick="window.history.back()" class="btn btn-outline-primary">
<i class="bi bi-arrow-left me-2"></i>Back
</button>
</div>
</div> </div>
<!-- Profile Card --> <!-- Profile Card -->
@ -58,6 +63,10 @@
<i class="bi bi-pencil me-2"></i>Edit Info <i class="bi bi-pencil me-2"></i>Edit Info
</a></li> </a></li>
<li><a class="dropdown-item" href="#"><i class="bi bi-bullseye me-2"></i>Set a Goal</a></li> <li><a class="dropdown-item" href="#"><i class="bi bi-bullseye me-2"></i>Set a Goal</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item text-danger" href="#" data-bs-toggle="modal" data-bs-target="#deleteAccountModal">
<i class="bi bi-trash me-2"></i>Delete Account
</a></li>
</ul> </ul>
</div> </div>
</div> </div>
@ -2480,6 +2489,51 @@ document.addEventListener('DOMContentLoaded', function() {
}); });
}); });
// Delete Account Modal functionality
const deleteAccountModal = document.getElementById('deleteAccountModal');
const confirmNameInput = document.getElementById('confirmName');
const deleteAccountBtn = document.getElementById('deleteAccountBtn');
const expectedName = '{{ $relationship->dependent->full_name }}';
if (deleteAccountModal && confirmNameInput && deleteAccountBtn) {
// Function to check if confirmation name matches
function checkConfirmationName() {
const enteredName = confirmNameInput.value.trim();
const matches = enteredName === expectedName;
deleteAccountBtn.disabled = !matches;
// Add visual feedback
if (matches) {
confirmNameInput.classList.remove('is-invalid');
confirmNameInput.classList.add('is-valid');
} else {
confirmNameInput.classList.remove('is-valid');
if (enteredName.length > 0) {
confirmNameInput.classList.add('is-invalid');
} else {
confirmNameInput.classList.remove('is-invalid');
}
}
}
// Listen for input changes
confirmNameInput.addEventListener('input', checkConfirmationName);
// Reset modal when opened
deleteAccountModal.addEventListener('show.bs.modal', function() {
confirmNameInput.value = '';
confirmNameInput.classList.remove('is-valid', 'is-invalid');
deleteAccountBtn.disabled = true;
});
// Reset modal when closed
deleteAccountModal.addEventListener('hidden.bs.modal', function() {
confirmNameInput.value = '';
confirmNameInput.classList.remove('is-valid', 'is-invalid');
deleteAccountBtn.disabled = true;
});
}
// Handle form submission // Handle form submission
document.getElementById('tournamentParticipationForm').addEventListener('submit', function(e) { document.getElementById('tournamentParticipationForm').addEventListener('submit', function(e) {
e.preventDefault(); e.preventDefault();
@ -2547,4 +2601,49 @@ document.addEventListener('DOMContentLoaded', function() {
:relationship="$relationship" :relationship="$relationship"
/> />
<!-- Delete Account Modal -->
<div class="modal fade" id="deleteAccountModal" tabindex="-1" aria-labelledby="deleteAccountModalLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content border-danger">
<div class="modal-header bg-danger text-white">
<h5 class="modal-title" id="deleteAccountModalLabel">
<i class="bi bi-exclamation-triangle-fill me-2"></i>Delete Account
</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<form id="deleteAccountForm" method="POST" action="{{ $relationship->relationship_type === 'admin_view' ? route('admin.platform.members.destroy', $relationship->dependent->id) : route('member.confirm-delete', $relationship->dependent->id) }}">
@csrf
@method('DELETE')
<div class="modal-body">
<div class="text-center mb-4">
<i class="bi bi-exclamation-triangle-fill text-danger" style="font-size: 3rem;"></i>
</div>
<div class="alert alert-danger">
<strong>Warning!</strong> This action cannot be undone. This will permanently delete the account for <strong>{{ $relationship->dependent->full_name }}</strong> and remove all associated data.
</div>
<p class="text-muted small mb-3">
To confirm deletion, please type the full name of the account holder below:
</p>
<div class="mb-3">
<label for="confirmName" class="form-label fw-semibold">Type "{{ $relationship->dependent->full_name }}" to confirm:</label>
<input type="text" class="form-control" id="confirmName" name="confirm_name" required>
<div class="form-text text-muted">
This action will soft delete the account. The account can be restored by an administrator if needed.
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="submit" class="btn btn-danger" id="deleteAccountBtn" disabled>
<i class="bi bi-trash me-2"></i>Delete Account
</button>
</div>
</form>
</div>
</div>
</div>
@endsection @endsection

View File

@ -165,6 +165,7 @@ Route::middleware(['auth', 'verified'])->group(function () {
Route::get('/member/{id}', [MemberController::class, 'show'])->name('member.show'); Route::get('/member/{id}', [MemberController::class, 'show'])->name('member.show');
Route::get('/member/{id}/edit', [MemberController::class, 'edit'])->name('member.edit'); Route::get('/member/{id}/edit', [MemberController::class, 'edit'])->name('member.edit');
Route::put('/member/{id}', [MemberController::class, 'update'])->name('member.update'); Route::put('/member/{id}', [MemberController::class, 'update'])->name('member.update');
Route::delete('/member/{id}/confirm-delete', [MemberController::class, 'confirmDelete'])->name('member.confirm-delete');
Route::delete('/member/{id}', [MemberController::class, 'destroy'])->name('member.destroy'); Route::delete('/member/{id}', [MemberController::class, 'destroy'])->name('member.destroy');
Route::post('/member/{id}/upload-picture', [MemberController::class, 'uploadPicture'])->name('member.upload-picture'); Route::post('/member/{id}/upload-picture', [MemberController::class, 'uploadPicture'])->name('member.upload-picture');
Route::post('/member/{id}/health', [MemberController::class, 'storeHealth'])->name('member.store-health'); Route::post('/member/{id}/health', [MemberController::class, 'storeHealth'])->name('member.store-health');