diff --git a/app/Http/Controllers/Api/V1/Payment/PaymentController.php b/app/Http/Controllers/Api/V1/Payment/PaymentController.php
index b57c577..83374b8 100644
--- a/app/Http/Controllers/Api/V1/Payment/PaymentController.php
+++ b/app/Http/Controllers/Api/V1/Payment/PaymentController.php
@@ -81,6 +81,53 @@ class PaymentController extends ApiController
]);
}
+ public function adminIndex(Request $request): JsonResponse
+ {
+ $request->validate([
+ 'status' => ['nullable', 'in:pending,processing,paid,failed,refunded'],
+ 'purpose' => ['nullable', 'in:resident_code_purchase,store_inventory_purchase'],
+ 'q' => ['nullable', 'string', 'max:100'],
+ 'per_page' => ['nullable', 'integer', 'min:1', 'max:100'],
+ ]);
+ $perPage = (int) $request->input('per_page', 25);
+
+ $payments = Payment::query()
+ ->with('payer')
+ ->when($request->filled('status'), fn ($q) => $q->where('status', $request->string('status')))
+ ->when($request->filled('purpose'), fn ($q) => $q->where('purpose', $request->string('purpose')))
+ ->when($request->filled('q'), function ($q) use ($request) {
+ $term = '%'.$request->string('q').'%';
+ $q->where(fn ($qq) => $qq->where('uuid', 'like', $term)
+ ->orWhereHas('payer', fn ($p) => $p->where('email', 'like', $term)->orWhere('phone', 'like', $term)));
+ })
+ ->orderByDesc('id')
+ ->paginate($perPage);
+
+ return $this->ok(
+ $payments->getCollection()->map(fn (Payment $p) => [
+ 'id' => $p->uuid,
+ 'payer' => [
+ 'name' => $p->payer?->full_name,
+ 'email' => $p->payer?->email,
+ ],
+ 'purpose' => $p->purpose,
+ 'amount_centavos' => $p->amount_centavos,
+ 'currency' => $p->currency,
+ 'provider' => $p->provider,
+ 'status' => $p->status,
+ 'paid_at' => $p->paid_at?->toIso8601String(),
+ 'created_at' => $p->created_at?->toIso8601String(),
+ ])->all(),
+ null,
+ [
+ 'page' => $payments->currentPage(),
+ 'per_page' => $payments->perPage(),
+ 'total' => $payments->total(),
+ 'last_page' => $payments->lastPage(),
+ ],
+ );
+ }
+
/**
* Admin manually marks a payment paid (used for cash-paid resident
* purchases or when the manual driver is in effect). Triggers the
diff --git a/resources/views/admin/dashboard.blade.php b/resources/views/admin/dashboard.blade.php
index cd3ac72..bde4613 100644
--- a/resources/views/admin/dashboard.blade.php
+++ b/resources/views/admin/dashboard.blade.php
@@ -47,15 +47,15 @@
- Backend modules 8 / 13
+ Backend modules 13 / 13
- Tests passing 139
+ Tests passing 171
- Operations / Live tracking / Finance ship with later modules.
+ Reverb live broadcast and FCM push are deferred — polling drives the live map for now.
diff --git a/resources/views/admin/finance.blade.php b/resources/views/admin/finance.blade.php
new file mode 100644
index 0000000..b9d6234
--- /dev/null
+++ b/resources/views/admin/finance.blade.php
@@ -0,0 +1,108 @@
+@extends('admin.layouts.app', ['pageTitle' => 'Finance'])
+
+@section('page')
+
+
+
+
+
+ All statuses
+ Pending
+ Processing
+ Paid
+ Failed
+ Refunded
+
+
+ All purposes
+ Resident code purchase
+ Store wholesale
+
+
+ Apply
+
+
+
+
+
+
+ Payment
+ Payer
+ Purpose
+ Amount
+ Provider
+ Status
+ Actions
+
+
+
+ Loading…
+
+
+
+
+
+
+@endsection
diff --git a/resources/views/admin/live-tracking.blade.php b/resources/views/admin/live-tracking.blade.php
new file mode 100644
index 0000000..5e22807
--- /dev/null
+++ b/resources/views/admin/live-tracking.blade.php
@@ -0,0 +1,126 @@
+@extends('admin.layouts.app', ['pageTitle' => 'Live Tracking'])
+
+@section('page')
+
+
+
+
+
+
+@endsection
diff --git a/resources/views/admin/partials/sidebar.blade.php b/resources/views/admin/partials/sidebar.blade.php
index 25b968c..cc25830 100644
--- a/resources/views/admin/partials/sidebar.blade.php
+++ b/resources/views/admin/partials/sidebar.blade.php
@@ -3,20 +3,22 @@
$isActive = fn ($path) => str_starts_with($current, ltrim($path, '/'));
$groups = [
'Operations' => [
- ['href' => '/admin/operations', 'label' => 'Today\'s Trips', 'disabled' => true, 'icon' => 'truck'],
+ ['href' => '/admin/trips', 'label' => 'Trips', 'icon' => 'truck'],
+ ['href' => '/admin/operations/live', 'label' => 'Live Tracking', 'icon' => 'map'],
['href' => '/admin/operations/calendar', 'label' => 'Trip Calendar', 'disabled' => true, 'icon' => 'calendar'],
- ['href' => '/admin/operations/live', 'label' => 'Live Tracking', 'disabled' => true, 'icon' => 'map'],
['href' => '/admin/operations/incidents', 'label' => 'Incidents', 'disabled' => true, 'icon' => 'alert'],
],
'Planning' => [
['href' => '/admin/routes', 'label' => 'Routes', 'icon' => 'route'],
- ['href' => '/admin/teams', 'label' => 'Teams', 'disabled' => true, 'icon' => 'users'],
+ ['href' => '/admin/teams', 'label' => 'Teams', 'icon' => 'users'],
+ ['href' => '/admin/trucks', 'label' => 'Trucks', 'icon' => 'truck'],
['href' => '/admin/drop-off-points', 'label' => 'Drop-off Points', 'icon' => 'pin'],
['href' => '/admin/dumpsites', 'label' => 'Dumpsites', 'icon' => 'trash'],
],
'People' => [
['href' => '/admin/users', 'label' => 'All Users', 'icon' => 'people'],
['href' => '/admin/households', 'label' => 'Households', 'icon' => 'home'],
+ ['href' => '/admin/partner-stores', 'label' => 'Partner Stores', 'icon' => 'cash'],
],
'QR Management' => [
['href' => '/admin/qr-batches', 'label' => 'Code Batches', 'icon' => 'qr'],
@@ -26,13 +28,13 @@
['href' => '/admin/service-areas', 'label' => 'Service Areas', 'icon' => 'globe'],
],
'Finance' => [
- ['href' => '/admin/finance', 'label' => 'Sales & Payouts', 'disabled' => true, 'icon' => 'cash'],
+ ['href' => '/admin/finance', 'label' => 'Payments', 'icon' => 'cash'],
],
'Reports' => [
- ['href' => '/admin/reports', 'label' => 'Reports', 'disabled' => true, 'icon' => 'chart'],
+ ['href' => '/admin/reports', 'label' => 'Reports', 'icon' => 'chart'],
],
'Settings' => [
- ['href' => '/admin/settings', 'label' => 'System Config', 'disabled' => true, 'icon' => 'cog'],
+ ['href' => '/admin/settings', 'label' => 'System Config', 'icon' => 'cog'],
],
];
@@ -55,9 +57,7 @@
'cog' => ' ',
];
- $renderIcon = function ($name) use ($icons) {
- return $icons[$name] ?? $icons['cog'];
- };
+ $renderIcon = fn ($name) => $icons[$name] ?? $icons['cog'];
@endphp
diff --git a/resources/views/admin/settings.blade.php b/resources/views/admin/settings.blade.php
new file mode 100644
index 0000000..a5b52e2
--- /dev/null
+++ b/resources/views/admin/settings.blade.php
@@ -0,0 +1,104 @@
+@extends('admin.layouts.app', ['pageTitle' => 'Settings'])
+
+@section('page')
+
+
+
+
+
Notification preferences
+
+
+
+
+
System
+
+ User roles, notification templates, audit logs, and feature flags
+ will land here as the platform grows. For now, system config is
+ managed via config/*.php
+ and .env.
+
+
+
+
+
+@endsection
diff --git a/routes/api.php b/routes/api.php
index dcc7b38..9cf5cdb 100644
--- a/routes/api.php
+++ b/routes/api.php
@@ -91,7 +91,12 @@ Route::middleware(['auth:sanctum', 'role:driver'])->prefix('driver')->name('api.
// Admin live tracking
Route::middleware(['auth:sanctum', 'role:admin'])->prefix('admin/live')->name('api.v1.admin.live.')->group(function () {
Route::get('/trucks', [AdminLiveTrackingController::class, 'trucks'])->name('trucks');
- Route::post('/payments/{payment}/mark-paid', [PaymentController::class, 'adminMarkPaid'])->name('payments.mark-paid');
+});
+
+// Admin payments
+Route::middleware(['auth:sanctum', 'role:admin'])->prefix('admin/payments')->name('api.v1.admin.payments.')->group(function () {
+ Route::get('/', [PaymentController::class, 'adminIndex'])->name('index');
+ Route::post('/{payment}/mark-paid', [PaymentController::class, 'adminMarkPaid'])->name('mark-paid');
});
Route::prefix('admin/users')
diff --git a/routes/web.php b/routes/web.php
index 41b0703..f835b72 100644
--- a/routes/web.php
+++ b/routes/web.php
@@ -18,30 +18,24 @@ Route::prefix('admin')->group(function () {
Route::view('/dumpsites', 'admin.dumpsites')->name('admin.dumpsites');
Route::view('/routes', 'admin.routes')->name('admin.routes');
Route::view('/users', 'admin.users')->name('admin.users');
-
- // New pages from Modules 9–13
Route::view('/teams', 'admin.teams')->name('admin.teams');
Route::view('/trucks', 'admin.trucks')->name('admin.trucks');
Route::view('/trips', 'admin.trips')->name('admin.trips');
Route::view('/partner-stores', 'admin.partner-stores')->name('admin.partner-stores');
Route::view('/reports', 'admin.reports')->name('admin.reports');
- // Stubs that still need backend work
+ // New from this batch
+ Route::view('/operations/live', 'admin.live-tracking')->name('admin.live-tracking');
+ Route::view('/finance', 'admin.finance')->name('admin.finance');
+ Route::view('/settings', 'admin.settings')->name('admin.settings');
+
+ // Still stubs — UI work needed (backends are ready, just no page yet)
Route::view('/operations/calendar', 'admin.coming-soon', [
- 'title' => 'Trip Calendar', 'description' => 'Drag-to-create week/month calendar — UI work pending.',
- ]);
- Route::view('/operations/live', 'admin.coming-soon', [
- 'title' => 'Live Tracking', 'module' => 'Module 13 — Reverb live tracking (deferred)',
- 'description' => 'Real-time truck positions on a map. Requires Reverb + driver GPS broadcast.',
+ 'title' => 'Trip Calendar',
+ 'description' => 'Drag-to-create week/month calendar — UI work pending. Use the Trips list for now.',
]);
Route::view('/operations/incidents', 'admin.coming-soon', [
- 'title' => 'Incidents', 'description' => 'Filtered view of trip incident events — UI work pending.',
- ]);
- Route::view('/finance', 'admin.coming-soon', [
- 'title' => 'Finance', 'module' => 'Module 13 — PayMongo payments (deferred)',
- 'description' => 'Sales, commissions, and payouts. Needs PayMongo integration.',
- ]);
- Route::view('/settings', 'admin.coming-soon', [
- 'title' => 'Settings', 'description' => 'User roles, notification templates, system config, audit logs.',
+ 'title' => 'Incidents',
+ 'description' => 'Filtered view of trip incident events — UI work pending. Visible inline in each Trip detail.',
]);
});
diff --git a/tests/Feature/Api/V1/Payment/PaymentFlowTest.php b/tests/Feature/Api/V1/Payment/PaymentFlowTest.php
index 5f833e7..ad60365 100644
--- a/tests/Feature/Api/V1/Payment/PaymentFlowTest.php
+++ b/tests/Feature/Api/V1/Payment/PaymentFlowTest.php
@@ -68,7 +68,7 @@ class PaymentFlowTest extends TestCase
]);
Sanctum::actingAs($admin);
- $response = $this->postJson("/api/v1/admin/live/payments/{$payment->uuid}/mark-paid");
+ $response = $this->postJson("/api/v1/admin/payments/{$payment->uuid}/mark-paid");
$response->assertOk()->assertJsonPath('data.status', 'paid');