added the listing of the clubs but didnt succseed

This commit is contained in:
Ghassan Yusuf 2026-01-22 04:44:25 +03:00
parent 0efe1f69e5
commit 59ad30575c
12 changed files with 737 additions and 124 deletions

View File

@ -54,9 +54,9 @@ class ClubController extends Controller
'gps_lat' => (float) $club->gps_lat, 'gps_lat' => (float) $club->gps_lat,
'gps_long' => (float) $club->gps_long, 'gps_long' => (float) $club->gps_long,
'distance' => round($distance, 2), // distance in kilometers 'distance' => round($distance, 2), // distance in kilometers
'owner_name' => $club->owner->full_name, 'owner_name' => $club->owner ? $club->owner->full_name : 'N/A',
'owner_email' => $club->owner->email, 'owner_email' => $club->owner ? $club->owner->email : null,
'owner_mobile' => $club->owner->mobile, 'owner_mobile' => $club->owner ? $club->owner->mobile : null,
]; ];
}); });
@ -107,9 +107,7 @@ class ClubController extends Controller
*/ */
public function all() public function all()
{ {
$clubs = Tenant::whereNotNull('gps_lat') $clubs = Tenant::with('owner')
->whereNotNull('gps_long')
->with('owner')
->get() ->get()
->map(function ($club) { ->map(function ($club) {
return [ return [
@ -117,9 +115,9 @@ class ClubController extends Controller
'club_name' => $club->club_name, 'club_name' => $club->club_name,
'slug' => $club->slug, 'slug' => $club->slug,
'logo' => $club->logo, 'logo' => $club->logo,
'gps_lat' => (float) $club->gps_lat, 'gps_lat' => $club->gps_lat ? (float) $club->gps_lat : null,
'gps_long' => (float) $club->gps_long, 'gps_long' => $club->gps_long ? (float) $club->gps_long : null,
'owner_name' => $club->owner->full_name, 'owner_name' => $club->owner ? $club->owner->full_name : 'N/A',
]; ];
}); });

View File

@ -31,9 +31,8 @@ class FamilyController extends Controller
->sortBy(function($relationship) { ->sortBy(function($relationship) {
return $relationship->dependent->full_name; return $relationship->dependent->full_name;
}); });
$familyInvoices = $this->familyService->getFamilyInvoices($user->id);
return view('family.dashboard', compact('user', 'dependents', 'familyInvoices')); return view('family.dashboard', compact('user', 'dependents'));
} }
/** /**

View File

@ -34,7 +34,7 @@ class InvoiceController extends Controller
* @param int $id * @param int $id
* @return \Illuminate\View\View * @return \Illuminate\View\View
*/ */
public function show($id) public function show(Request $request, $id)
{ {
$user = Auth::user(); $user = Auth::user();
$invoice = Invoice::where('id', $id) $invoice = Invoice::where('id', $id)
@ -42,9 +42,30 @@ class InvoiceController extends Controller
->with(['student', 'tenant']) ->with(['student', 'tenant'])
->firstOrFail(); ->firstOrFail();
if ($request->ajax()) {
return response()->json(['html' => view('invoices._show_modal', compact('invoice'))->render()]);
}
return view('invoices.show', compact('invoice')); return view('invoices.show', compact('invoice'));
} }
/**
* Display the receipt for the specified invoice.
*
* @param int $id
* @return \Illuminate\View\View
*/
public function receipt($id)
{
$user = Auth::user();
$invoice = Invoice::where('id', $id)
->where('payer_user_id', $user->id)
->with(['student', 'tenant'])
->firstOrFail();
return view('invoices.receipt', compact('invoice'));
}
/** /**
* Process payment for the specified invoice. * Process payment for the specified invoice.
* *
@ -63,7 +84,7 @@ class InvoiceController extends Controller
'status' => 'paid' 'status' => 'paid'
]); ]);
return redirect()->route('invoices.show', $invoice->id) return redirect()->route('bills.show', $invoice->id)
->with('success', 'Payment processed successfully.'); ->with('success', 'Payment processed successfully.');
} }
@ -86,7 +107,7 @@ class InvoiceController extends Controller
]); ]);
} }
return redirect()->route('invoices.index') return redirect()->route('bills.index')
->with('success', 'All payments processed successfully.'); ->with('success', 'All payments processed successfully.');
} }
} }

View File

@ -3,6 +3,9 @@
namespace Database\Seeders; namespace Database\Seeders;
use App\Models\User; use App\Models\User;
use App\Models\Tenant;
use App\Models\Membership;
use App\Models\Invoice;
use Illuminate\Database\Console\Seeds\WithoutModelEvents; use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder; use Illuminate\Database\Seeder;
@ -17,11 +20,47 @@ class DatabaseSeeder extends Seeder
{ {
// User::factory(10)->create(); // User::factory(10)->create();
User::factory()->create([ $user = User::firstOrCreate([
'name' => 'Test User',
'email' => 'test@example.com', 'email' => 'test@example.com',
], [
'name' => 'Test User',
'full_name' => 'Test User', 'full_name' => 'Test User',
'mobile' => ['code' => '+1', 'number' => '1234567890'], 'mobile' => ['code' => '+1', 'number' => '1234567890'],
'password' => bcrypt('password'),
]);
// Create a tenant (club)
$tenant = Tenant::create([
'owner_user_id' => $user->id,
'club_name' => 'Test Club',
'slug' => 'test-club',
]);
// Create membership
Membership::create([
'user_id' => $user->id,
'tenant_id' => $tenant->id,
'status' => 'active',
]);
// Create a paid invoice
Invoice::create([
'tenant_id' => $tenant->id,
'student_user_id' => $user->id,
'payer_user_id' => $user->id,
'amount' => 50.00,
'status' => 'paid',
'due_date' => now()->addDays(30),
]);
// Create a pending invoice
Invoice::create([
'tenant_id' => $tenant->id,
'student_user_id' => $user->id,
'payer_user_id' => $user->id,
'amount' => 25.00,
'status' => 'pending',
'due_date' => now()->addDays(15),
]); ]);
} }
} }

View File

@ -89,8 +89,8 @@
<!-- Club cards will be inserted here --> <!-- Club cards will be inserted here -->
</div> </div>
</div> </div>
<div class="col-12 d-flex justify-content-center"> <div class="col-12 d-flex justify-content-center" id="noResultsContainer" style="display: none;">
<div id="noResults" class="d-flex flex-column align-items-center justify-content-center text-center" style="display: none; min-height: 400px;"> <div id="noResults" class="d-flex flex-column align-items-center justify-content-center text-center" style="min-height: 400px;">
<i class="bi bi-inbox" style="font-size: 4rem; color: #dee2e6;"></i> <i class="bi bi-inbox" style="font-size: 4rem; color: #dee2e6;"></i>
<h4 class="mt-3 text-muted">No Results Found</h4> <h4 class="mt-3 text-muted">No Results Found</h4>
<p class="text-muted">Try adjusting your search or location</p> <p class="text-muted">Try adjusting your search or location</p>
@ -284,6 +284,9 @@ document.addEventListener('DOMContentLoaded', function() {
startWatchingLocation(); startWatchingLocation();
} }
// Initial load: fetch all clubs since 'all' is default
fetchAllClubs();
// Category buttons // Category buttons
document.querySelectorAll('.category-btn').forEach(btn => { document.querySelectorAll('.category-btn').forEach(btn => {
btn.addEventListener('click', function() { btn.addEventListener('click', function() {
@ -295,7 +298,14 @@ document.addEventListener('DOMContentLoaded', function() {
this.classList.add('active', 'btn-primary'); this.classList.add('active', 'btn-primary');
currentCategory = this.dataset.category; currentCategory = this.dataset.category;
filterClubs(); if (currentCategory === 'all') {
fetchAllClubs();
} else if (userLocation) {
fetchNearbyClubs(userLocation.latitude, userLocation.longitude);
} else {
// If no location, show message or fetch all as fallback
fetchAllClubs();
}
}); });
}); });
@ -324,8 +334,12 @@ document.addEventListener('DOMContentLoaded', function() {
mapModal.hide(); mapModal.hide();
if (userLocation) { if (userLocation) {
if (currentCategory === 'all') {
fetchAllClubs();
} else {
fetchNearbyClubs(userLocation.latitude, userLocation.longitude); fetchNearbyClubs(userLocation.latitude, userLocation.longitude);
} }
}
}); });
}); });
@ -339,7 +353,11 @@ function startWatchingLocation() {
}; };
updateLocationDisplay(userLocation.latitude, userLocation.longitude); updateLocationDisplay(userLocation.latitude, userLocation.longitude);
// If current category is not 'all', fetch nearby clubs
if (currentCategory !== 'all') {
fetchNearbyClubs(userLocation.latitude, userLocation.longitude); fetchNearbyClubs(userLocation.latitude, userLocation.longitude);
}
// Stop watching after first successful location // Stop watching after first successful location
if (watchId) { if (watchId) {
@ -361,7 +379,10 @@ function startWatchingLocation() {
break; break;
} }
showAlert(errorMessage, 'danger'); showAlert(errorMessage, 'danger');
document.getElementById('loadingSpinner').style.display = 'none'; // If location fails and category is not 'all', perhaps fetch all as fallback
if (currentCategory !== 'all') {
fetchAllClubs();
}
}, },
{ {
enableHighAccuracy: true, enableHighAccuracy: true,
@ -454,19 +475,50 @@ function fetchNearbyClubs(lat, lng) {
}); });
} }
// Fetch all clubs
function fetchAllClubs() {
document.getElementById('loadingSpinner').style.display = 'block';
document.getElementById('clubsGrid').style.display = 'none';
fetch(`{{ route('clubs.all') }}`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': '{{ csrf_token() }}'
}
})
.then(response => response.json())
.then(data => {
document.getElementById('loadingSpinner').style.display = 'none';
document.getElementById('clubsGrid').style.display = 'block';
if (data.success) {
allClubs = data.clubs;
displayClubs(allClubs);
} else {
showAlert('Failed to fetch clubs', 'danger');
}
})
.catch(error => {
console.error('Error:', error);
document.getElementById('loadingSpinner').style.display = 'none';
showAlert('Error fetching clubs', 'danger');
});
}
// Display clubs as cards // Display clubs as cards
function displayClubs(clubs) { function displayClubs(clubs) {
const container = document.getElementById('clubsContainer'); const container = document.getElementById('clubsContainer');
const noResults = document.getElementById('noResults'); const noResultsContainer = document.getElementById('noResultsContainer');
container.innerHTML = ''; container.innerHTML = '';
if (clubs.length === 0) { if (clubs.length === 0) {
noResults.style.display = 'flex'; noResultsContainer.style.display = 'flex';
return; return;
} }
noResults.style.display = 'none'; noResultsContainer.style.display = 'none';
clubs.forEach(club => { clubs.forEach(club => {
const card = document.createElement('div'); const card = document.createElement('div');
@ -491,7 +543,7 @@ function displayClubs(clubs) {
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-navigation w-4 h-4"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-navigation w-4 h-4">
<polygon points="3 11 22 2 13 21 11 13 3 11"></polygon> <polygon points="3 11 22 2 13 21 11 13 3 11"></polygon>
</svg> </svg>
<span class="font-medium">${club.distance} km away</span> <span class="font-medium">${club.distance ? club.distance + ' km away' : 'Location available'}</span>
</div> </div>
<div class="flex items-center gap-1 text-sm text-muted-foreground"> <div class="flex items-center gap-1 text-sm text-muted-foreground">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-building w-4 h-4"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-building w-4 h-4">

View File

@ -4,11 +4,6 @@
<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">Family</h1> <h1 class="mb-0">Family</h1>
<div>
<a href="{{ route('invoices.index') }}" class="btn btn-outline-primary">
<i class="bi bi-receipt"></i> All Invoices
</a>
</div>
</div> </div>
<!-- Family Members Card Grid --> <!-- Family Members Card Grid -->
@ -101,9 +96,13 @@
<div class="d-flex justify-content-between align-items-center small mb-2"> <div class="d-flex justify-content-between align-items-center small mb-2">
<span class="text-muted fw-medium">Next Birthday</span> <span class="text-muted fw-medium">Next Birthday</span>
<span class="fw-semibold text-muted"> <span class="fw-semibold text-muted">
@if($user->birthdate)
{{ $user->birthdate->copy()->year(now()->year)->isFuture() {{ $user->birthdate->copy()->year(now()->year)->isFuture()
? $user->birthdate->copy()->year(now()->year)->diffForHumans(['parts' => 2, 'syntax' => \Carbon\CarbonInterface::DIFF_ABSOLUTE]) ? $user->birthdate->copy()->year(now()->year)->diffForHumans(['parts' => 2, 'syntax' => \Carbon\CarbonInterface::DIFF_ABSOLUTE])
: $user->birthdate->copy()->year(now()->year + 1)->diffForHumans(['parts' => 2, 'syntax' => \Carbon\CarbonInterface::DIFF_ABSOLUTE]) }} : $user->birthdate->copy()->year(now()->year + 1)->diffForHumans(['parts' => 2, 'syntax' => \Carbon\CarbonInterface::DIFF_ABSOLUTE]) }}
@else
N/A
@endif
</span> </span>
</div> </div>
<div class="d-flex justify-content-between align-items-center small"> <div class="d-flex justify-content-between align-items-center small">
@ -245,9 +244,13 @@
<div class="d-flex justify-content-between align-items-center small mb-2"> <div class="d-flex justify-content-between align-items-center small mb-2">
<span class="text-muted fw-medium">Next Birthday</span> <span class="text-muted fw-medium">Next Birthday</span>
<span class="fw-semibold text-muted"> <span class="fw-semibold text-muted">
@if($relationship->dependent->birthdate)
{{ $relationship->dependent->birthdate->copy()->year(now()->year)->isFuture() {{ $relationship->dependent->birthdate->copy()->year(now()->year)->isFuture()
? $relationship->dependent->birthdate->copy()->year(now()->year)->diffForHumans(['parts' => 2, 'syntax' => \Carbon\CarbonInterface::DIFF_ABSOLUTE]) ? $relationship->dependent->birthdate->copy()->year(now()->year)->diffForHumans(['parts' => 2, 'syntax' => \Carbon\CarbonInterface::DIFF_ABSOLUTE])
: $relationship->dependent->birthdate->copy()->year(now()->year + 1)->diffForHumans(['parts' => 2, 'syntax' => \Carbon\CarbonInterface::DIFF_ABSOLUTE]) }} : $relationship->dependent->birthdate->copy()->year(now()->year + 1)->diffForHumans(['parts' => 2, 'syntax' => \Carbon\CarbonInterface::DIFF_ABSOLUTE]) }}
@else
N/A
@endif
</span> </span>
</div> </div>
<div class="d-flex justify-content-between align-items-center small"> <div class="d-flex justify-content-between align-items-center small">
@ -283,68 +286,7 @@
</div> </div>
</div> </div>
<!-- Family Payments Table -->
<div class="card shadow-sm mb-4">
<div class="card-header bg-white">
<h4 class="mb-0">Family Payments</h4>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr>
<th>Student Name</th>
<th>Class/Package</th>
<th>Amount</th>
<th>Status</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
@forelse($familyInvoices as $invoice)
<tr>
<td>{{ $invoice->student->full_name }}</td>
<td>{{ $invoice->tenant->club_name }}</td>
<td>${{ number_format($invoice->amount, 2) }}</td>
<td>
@if($invoice->status === 'paid')
<span class="badge bg-success">Paid</span>
@elseif($invoice->status === 'pending')
<span class="badge bg-warning text-dark">Pending</span>
@else
<span class="badge bg-danger">Overdue</span>
@endif
</td>
<td>
<a href="{{ route('invoices.show', $invoice->id) }}" class="btn btn-sm btn-outline-primary">
<i class="bi bi-eye"></i> View
</a>
@if($invoice->status !== 'paid')
<a href="{{ route('invoices.pay', $invoice->id) }}" class="btn btn-sm btn-success">
<i class="bi bi-credit-card"></i> Pay
</a>
@endif
</td>
</tr>
@empty
<tr>
<td colspan="5" class="text-center py-4">
<p class="text-muted mb-0">No payments due at this time.</p>
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
</div>
<div class="card-footer bg-white d-flex justify-content-end">
@if(count($familyInvoices->where('status', '!=', 'paid')) > 0)
<a href="{{ route('invoices.pay-all') }}" class="btn btn-success">
<i class="bi bi-credit-card"></i> Pay All
</a>
@endif
</div>
</div>
</div> </div>
<!-- Add Family Member Modal --> <!-- Add Family Member Modal -->

View File

@ -0,0 +1,83 @@
@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
<div class="row mb-4">
<div class="col-md-6">
<h5>Billed To</h5>
<p class="mb-1">{{ Auth::user()->full_name }}</p>
<p class="mb-1">{{ Auth::user()->email }}</p>
@if(Auth::user()->mobile)
<p class="mb-0">{{ Auth::user()->mobile }}</p>
@endif
</div>
<div class="col-md-6 text-md-end">
<h5>Invoice Details</h5>
<p class="mb-1">Invoice #: {{ $invoice->id }}</p>
<p class="mb-1">Due Date: {{ $invoice->due_date->format('F j, Y') }}</p>
<p class="mb-0">
Status:
@if($invoice->status === 'paid')
<span class="badge bg-success">Paid</span>
@elseif($invoice->status === 'pending')
<span class="badge bg-warning text-dark">Pending</span>
@else
<span class="badge bg-danger">Overdue</span>
@endif
</p>
</div>
</div>
<div class="row mb-4">
<div class="col-md-6">
<h5>Club Information</h5>
<p class="mb-1">{{ $invoice->tenant->club_name }}</p>
<p class="mb-0">{{ $invoice->tenant->owner->full_name }} (Owner)</p>
</div>
<div class="col-md-6 text-md-end">
<h5>Student Information</h5>
<p class="mb-1">{{ $invoice->student->full_name }}</p>
<p class="mb-0">Age: {{ $invoice->student->age }} ({{ $invoice->student->life_stage }})</p>
</div>
</div>
<div class="table-responsive mb-4">
<table class="table table-bordered">
<thead class="table-light">
<tr>
<th>Description</th>
<th class="text-end">Amount</th>
</tr>
</thead>
<tbody>
<tr>
<td>Club Membership Fee - {{ $invoice->tenant->club_name }}</td>
<td class="text-end">${{ number_format($invoice->amount, 2) }}</td>
</tr>
</tbody>
<tfoot>
<tr>
<th>Total</th>
<th class="text-end">${{ number_format($invoice->amount, 2) }}</th>
</tr>
</tfoot>
</table>
</div>
<div class="d-flex justify-content-end">
@if($invoice->status !== 'paid')
<a href="{{ route('bills.pay', $invoice->id) }}" class="btn btn-success">
<i class="bi bi-credit-card"></i> Pay Now
</a>
@else
<button class="btn btn-outline-success me-2" disabled>
<i class="bi bi-check-circle"></i> Paid
</button>
<a href="{{ route('bills.receipt', $invoice->id) }}" class="btn btn-outline-primary" target="_blank">
<i class="bi bi-receipt"></i> View Receipt
</a>
@endif
</div>

View File

@ -3,21 +3,21 @@
@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 Invoices</h1> <h1 class="mb-0">My Bills</h1>
</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 d-flex justify-content-between align-items-center">
<h4 class="mb-0">All Invoices</h4> <h4 class="mb-0">All Bills</h4>
<div class="btn-group" role="group"> <div class="btn-group" role="group">
<button type="button" class="btn btn-outline-secondary btn-sm dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false"> <button type="button" class="btn btn-outline-secondary btn-sm dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">
<i class="bi bi-funnel"></i> Filter <i class="bi bi-funnel"></i> Filter
</button> </button>
<ul class="dropdown-menu dropdown-menu-end"> <ul class="dropdown-menu dropdown-menu-end">
<li><a class="dropdown-item" href="{{ route('invoices.index') }}">All</a></li> <li><a class="dropdown-item" href="{{ route('bills.index') }}">All</a></li>
<li><a class="dropdown-item" href="{{ route('invoices.index', ['status' => 'pending']) }}">Pending</a></li> <li><a class="dropdown-item" href="{{ route('bills.index', ['status' => 'pending']) }}">Pending</a></li>
<li><a class="dropdown-item" href="{{ route('invoices.index', ['status' => 'paid']) }}">Paid</a></li> <li><a class="dropdown-item" href="{{ route('bills.index', ['status' => 'paid']) }}">Paid</a></li>
<li><a class="dropdown-item" href="{{ route('invoices.index', ['status' => 'overdue']) }}">Overdue</a></li> <li><a class="dropdown-item" href="{{ route('bills.index', ['status' => 'overdue']) }}">Overdue</a></li>
</ul> </ul>
</div> </div>
</div> </div>
@ -62,13 +62,17 @@
<td>{{ $invoice->due_date->format('M j, Y') }}</td> <td>{{ $invoice->due_date->format('M j, Y') }}</td>
<td> <td>
<div class="btn-group" role="group"> <div class="btn-group" role="group">
<a href="{{ route('invoices.show', $invoice->id) }}" class="btn btn-sm btn-outline-primary"> <a href="#" class="btn btn-sm btn-outline-primary view-invoice" data-id="{{ $invoice->id }}">
<i class="bi bi-eye"></i> View <i class="bi bi-eye"></i> View
</a> </a>
@if($invoice->status !== 'paid') @if($invoice->status !== 'paid')
<a href="{{ route('invoices.pay', $invoice->id) }}" class="btn btn-sm btn-success"> <a href="{{ route('bills.pay', $invoice->id) }}" class="btn btn-sm btn-success">
<i class="bi bi-credit-card"></i> Pay <i class="bi bi-credit-card"></i> Pay
</a> </a>
@else
<a href="{{ route('bills.receipt', $invoice->id) }}" class="btn btn-sm btn-outline-info" target="_blank">
<i class="bi bi-receipt"></i> Receipt
</a>
@endif @endif
</div> </div>
</td> </td>
@ -80,18 +84,57 @@
@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="bi bi-receipt" style="font-size: 3rem;"></i>
<h4 class="mt-3">No Invoices Found</h4> <h4 class="mt-3">No Bills Found</h4>
<p class="text-muted">There are no invoices matching your criteria.</p> <p class="text-muted">There are no bills matching your criteria.</p>
</div> </div>
@endif @endif
</div> </div>
@if($invoices->where('status', '!=', 'paid')->count() > 0) @if($invoices->where('status', '!=', 'paid')->count() > 0)
<div class="card-footer bg-white d-flex justify-content-end"> <div class="card-footer bg-white d-flex justify-content-end">
<a href="{{ route('invoices.pay-all') }}" class="btn btn-success"> <a href="{{ route('bills.pay-all') }}" class="btn btn-success">
<i class="bi bi-credit-card"></i> Pay All <i class="bi bi-credit-card"></i> Pay All
</a> </a>
</div> </div>
@endif @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

@ -0,0 +1,429 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Receipt</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@400;600;700&display=swap');
:root {
--bahrain-red: #C41E3A;
--bahrain-white: #FFFFFF;
--text-color: #333;
--light-gray: #F2F2F2;
}
body {
font-family: 'Poppins', sans-serif;
background-color: #f0f0f0;
margin: 0;
padding: 10px;
color: var(--text-color);
}
.invoice-container {
max-width: 148mm;
width: 100%;
margin: 0 auto;
background-color: var(--bahrain-white);
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
position: relative;
border-radius: 6px;
}
.header {
background-color: var(--bahrain-red);
padding: 15px 20px;
display: flex;
justify-content: space-between;
align-items: center;
color: var(--bahrain-white);
margin-bottom: 5px;
}
.header-info {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 8px;
flex: 1;
margin-right: 20px;
}
.receipt-title-wrapper {
display: flex;
flex-direction: column;
gap: 5px;
padding-bottom: 8px;
border-bottom: 2px solid rgba(255, 255, 255, 0.5);
width: 100%;
}
.receipt-title-wrapper h1 {
font-size: 20px;
font-weight: 700;
line-height: 1;
margin: 0;
text-transform: uppercase;
}
.receipt-title-wrapper .slogan {
font-size: 13px;
font-weight: 600;
margin: 0;
}
.registration-info {
display: flex;
flex-direction: column;
gap: 5px;
margin-top: 0;
width: 100%;
}
.reg-row {
display: flex;
gap: 10px;
}
.white-box {
background-color: var(--bahrain-white);
color: var(--text-color);
padding: 5px 10px;
/* Changed border-radius to a high value for a pill shape */
border-radius: 999px;
font-size: 12px;
line-height: 1.2;
text-align: center;
display: flex;
align-items: center;
justify-content: center;
font-weight: 600;
}
/* Fixed widths for the white boxes */
.reg-row .white-box:first-child {
flex: 7 1 0%;
}
.reg-row .white-box:last-child {
flex: 3 1 0%;
}
.header img {
max-width: 120px;
height: auto;
}
.main-content {
padding: 10px 20px 30px;
}
.invoice-info {
display: flex;
flex-direction: column;
gap: 10px;
margin-bottom: 20px;
line-height: 1.6;
}
.info-details {
text-align: left;
}
@media (min-width: 500px) {
.invoice-info {
flex-direction: row;
justify-content: space-between;
}
.info-details {
text-align: right;
}
}
/* Styles for the info rows using Flexbox */
.info-row {
display: flex;
justify-content: space-between;
align-items: center;
background-color: var(--bahrain-red);
border-radius: 999px;
color: var(--bahrain-white);
font-weight: 700;
font-size: 14px;
padding: 5px 15px;
margin-left: auto;
width: 220px; /* Adjust width to fit content */
margin-bottom: 5px; /* Creates separation between rows */
}
.info-row span.white-pill {
background-color: var(--bahrain-white);
border-radius: 999px;
padding: 3px 10px;
color: var(--bahrain-red);
font-weight: 700;
text-align: center;
line-height: 1;
}
table {
width: 100%;
border-collapse: collapse;
margin-bottom: 20px;
text-align: left;
}
table thead {
background-color: var(--bahrain-red);
color: var(--bahrain-white);
}
table th, table td {
padding: 10px;
border-bottom: 1px solid #ddd;
font-size: 12px;
}
table tbody tr:nth-child(even) {
background-color: var(--light-gray);
}
/* Aligning text in the new 'Total' column */
table td:last-child {
text-align: right;
}
/* Aligning text in 'Price' and 'Qty' columns */
table td:nth-child(3),
table td:nth-child(4) {
text-align: right;
}
.sub-total-table {
width: 250px;
float: right;
border-collapse: collapse;
margin-bottom: 30px;
}
.sub-total-table td {
padding: 8px;
text-align: right;
font-size: 14px;
}
.sub-total-table tr:not(:last-child) {
background-color: var(--light-gray);
}
.sub-total-table .grand-total-row {
background-color: var(--bahrain-red) !important;
color: var(--bahrain-white);
font-weight: 700;
}
/* New styles for the conditional note */
.receipt-note {
background-color: #fff3cd; /* A light, noticeable color */
border-left: 5px solid #ffc107; /* A bright border */
padding: 10px 15px;
margin-top: 20px;
margin-bottom: 20px;
font-size: 14px;
color: #664d03;
line-height: 1.4;
display: none; /* Hide by default */
}
.receipt-note strong {
color: #333;
}
.contact-info {
display: flex;
flex-direction: column;
gap: 10px;
margin-bottom: 15px;
line-height: 1.4;
font-size: 12px;
}
.contact-info-item {
display: flex;
align-items: flex-start;
gap: 8px;
}
.contact-info-item .icon {
font-size: 16px;
color: var(--bahrain-red);
line-height: 1;
}
.footer {
background-color: var(--bahrain-red);
padding: 10px 20px;
text-align: center;
color: var(--bahrain-white);
font-size: 12px;
font-weight: 600;
border-bottom-left-radius: 6px;
border-bottom-right-radius: 6px;
}
</style>
</head>
<body>
<div class="invoice-container">
<div class="header">
<div class="header-info">
<div class="receipt-title-wrapper">
<h1>TAKEONE</h1>
<p class="slogan">Connect, Share, Thrive.</p>
</div>
<div class="registration-info">
<div class="reg-row">
<div class="white-box">REGISTRATION #</div>
<div class="white-box">001</div>
</div>
<div class="reg-row">
<div class="white-box">VAT REGISTRATION #</div>
<div class="white-box">N/A</div>
</div>
</div>
</div>
<img src="{{ asset('images/logo.png') }}" alt="Company Logo">
</div>
<div class="main-content">
<div class="invoice-info">
<div>
<p class="to"><B>RECEIPT TO :</B></p>
<p>
<strong>{{ $invoice->student->full_name }}</strong><br>
{{ $invoice->student->mobile_formatted ?: 'N/A' }}<br>
{{ $invoice->student->email ?: 'N/A' }}<br>
{{ $invoice->student->addresses ? implode(', ', array_filter($invoice->student->addresses)) : 'N/A' }}
</p>
</div>
<div class="info-details">
<div class="info-row">
<span>RECEIPT NO</span>
<span class="white-pill">{{ $invoice->id }}</span>
</div>
<div class="info-row">
<span>ISSUE DATE</span>
<span class="white-pill">{{ $invoice->created_at->format('d-m-Y') }}</span>
</div>
</div>
</div>
<table id="receipt-items">
<thead>
<tr>
<th>Item No</th>
<th>Description</th>
<th>Qty</th>
<th>Price</th>
<th>Total</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>Club Membership Fee - {{ $invoice->tenant->club_name }}</td>
<td>1</td>
<td>{{ number_format($invoice->amount, 2) }}</td>
<td></td>
</tr>
</tbody>
</table>
<div id="payment-note" class="receipt-note">
<p><strong>Note:</strong> This payment covers the membership fee for <strong>{{ $invoice->student->full_name }}</strong> at <strong>{{ $invoice->tenant->club_name }}</strong>.</p>
</div>
<table class="sub-total-table">
<tbody>
<tr>
<td>Sub Total:</td>
<td id="sub-total"></td>
</tr>
<tr>
<td>VAT (0%):</td>
<td id="vat"></td>
</tr>
<tr class="grand-total-row">
<td>Grand Total:</td>
<td id="grand-total"></td>
</tr>
</tbody>
</table>
<div class="contact-info">
<div class="contact-info-item">
<span class="icon">&#9742;</span>
<div>
+1 123 456 7890
</div>
</div>
<div class="contact-info-item">
<span class="icon">&#9993;</span>
<div>
support@takeone.com
</div>
</div>
<div class="contact-info-item">
<span class="icon">&#9906;</span>
<div>
123 Main St, City, Country
</div>
</div>
</div>
</div>
<div class="footer">
© 2025 TAKEONE. All Rights Reserved.
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', () => {
const tableBody = document.querySelector('#receipt-items tbody');
const subTotalElement = document.getElementById('sub-total');
const vatElement = document.getElementById('vat');
const grandTotalElement = document.getElementById('grand-total');
const paymentNote = document.getElementById('payment-note');
const vatRate = 0.0;
let subTotal = 0;
// --- CALCULATIONS ---
// Loop through each table row to calculate the total for each item
tableBody.querySelectorAll('tr').forEach(row => {
const qty = parseFloat(row.cells[2].textContent);
const price = parseFloat(row.cells[3].textContent);
const total = qty * price;
// Populate the 'Total' column
row.cells[4].textContent = total.toFixed(2) + ' USD';
// Add to the sub total
subTotal += total;
});
const vatAmount = subTotal * vatRate;
const grandTotal = subTotal + vatAmount;
// Update the sub total table
subTotalElement.textContent = subTotal.toFixed(2) + ' USD';
vatElement.textContent = vatAmount.toFixed(2) + ' USD';
grandTotalElement.textContent = grandTotal.toFixed(2) + ' USD';
// --- CONDITIONAL NOTE LOGIC ---
// Show the note
paymentNote.style.display = 'block';
});
</script>
</body>
</html>

View File

@ -7,8 +7,8 @@
<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 d-flex justify-content-between align-items-center">
<h4 class="mb-0">Invoice #{{ $invoice->id }}</h4> <h4 class="mb-0">Invoice #{{ $invoice->id }}</h4>
<a href="{{ route('invoices.index') }}" class="btn btn-outline-secondary btn-sm"> <a href="{{ route('bills.index') }}" class="btn btn-outline-secondary btn-sm">
<i class="bi bi-arrow-left"></i> Back to Invoices <i class="bi bi-arrow-left"></i> Back to Bills
</a> </a>
</div> </div>
<div class="card-body"> <div class="card-body">
@ -83,13 +83,16 @@
<div class="d-flex justify-content-end"> <div class="d-flex justify-content-end">
@if($invoice->status !== 'paid') @if($invoice->status !== 'paid')
<a href="{{ route('invoices.pay', $invoice->id) }}" class="btn btn-success"> <a href="{{ route('bills.pay', $invoice->id) }}" class="btn btn-success">
<i class="bi bi-credit-card"></i> Pay Now <i class="bi bi-credit-card"></i> Pay Now
</a> </a>
@else @else
<button class="btn btn-outline-success" disabled> <button class="btn btn-outline-success me-2" disabled>
<i class="bi bi-check-circle"></i> Paid <i class="bi bi-check-circle"></i> Paid
</button> </button>
<a href="{{ route('bills.receipt', $invoice->id) }}" class="btn btn-outline-primary" target="_blank">
<i class="bi bi-receipt"></i> View Receipt
</a>
@endif @endif
</div> </div>
</div> </div>

View File

@ -391,6 +391,9 @@
<a class="dropdown-item small" href="{{ route('family.dashboard') }}"> <a class="dropdown-item small" href="{{ route('family.dashboard') }}">
<i class="bi bi-people me-2"></i>My Family <i class="bi bi-people me-2"></i>My Family
</a> </a>
<a class="dropdown-item small" href="{{ route('bills.index') }}">
<i class="bi bi-receipt me-2"></i>My Bills
</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>Settings
</a> </a>

View File

@ -95,9 +95,10 @@ Route::middleware(['auth', 'verified'])->group(function () {
Route::post('/family/{id}/upload-picture', [FamilyController::class, 'uploadFamilyMemberPicture'])->name('family.upload-picture'); Route::post('/family/{id}/upload-picture', [FamilyController::class, 'uploadFamilyMemberPicture'])->name('family.upload-picture');
Route::delete('/family/{id}', [FamilyController::class, 'destroy'])->name('family.destroy'); Route::delete('/family/{id}', [FamilyController::class, 'destroy'])->name('family.destroy');
// Invoice routes // Bills routes
Route::get('/invoices', [InvoiceController::class, 'index'])->name('invoices.index'); Route::get('/bills', [InvoiceController::class, 'index'])->name('bills.index');
Route::get('/invoices/{id}', [InvoiceController::class, 'show'])->name('invoices.show'); Route::get('/bills/{id}', [InvoiceController::class, 'show'])->name('bills.show');
Route::get('/invoices/{id}/pay', [InvoiceController::class, 'pay'])->name('invoices.pay'); Route::get('/bills/{id}/receipt', [InvoiceController::class, 'receipt'])->name('bills.receipt');
Route::get('/invoices/pay-all', [InvoiceController::class, 'payAll'])->name('invoices.pay-all'); Route::get('/bills/{id}/pay', [InvoiceController::class, 'pay'])->name('bills.pay');
Route::get('/bills/pay-all', [InvoiceController::class, 'payAll'])->name('bills.pay-all');
}); });