feat: implement trip incident dashboard, drop-off geofence validation, and calendar updates

- **Trip Incidents**: Created the `AdminTripController@incidents` API endpoint, built the `incidents.blade.php` view, enabled the incidents sidebar link, and added test coverage (`TripIncidentDashboardTest.php`).
- **Drop-off Points (LGU Geofence)**: Added a Barangay (LGU) dropdown to the drop-off point form. Integrated `Turf.js` to render the LGU boundary polygon on Leaflet and restrict pin placement (clicks, drags, and address search) to within the selected boundary.
- **Trip Calendar**: Changed the shortcut for opening the Daily Digest from Ctrl+Click to Shift+Click on calendar dates.
- **API fixes**: Corrected the Barangay fetch endpoint to `/api/v1/geo/barangays` in the Drop-off points view.
This commit is contained in:
Developer
2026-07-06 12:39:13 +08:00
parent 514c42ba88
commit 6f5327bbcd
10 changed files with 584 additions and 13 deletions

View File

@@ -5,11 +5,13 @@ namespace App\Http\Controllers\Api\V1\Admin;
use App\Http\Controllers\Api\V1\ApiController;
use App\Http\Requests\Trip\StoreTripRequest;
use App\Http\Resources\TripResource;
use App\Http\Resources\TripTimelineEventResource;
use App\Models\Route;
use App\Models\Trip;
use App\Models\TripStop;
use App\Models\CollectionTeam;
use App\Models\Truck;
use App\Models\TripTimelineEvent;
use App\Services\Trip\TripExecutor;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
@@ -260,6 +262,38 @@ class AdminTripController extends ApiController
]);
}
public function incidents(Request $request): JsonResponse
{
$request->validate([
'event_type' => ['nullable', 'string', 'in:incident_reported,breakdown'],
'date' => ['nullable', 'date'],
'per_page' => ['nullable', 'integer', 'min:1', 'max:500'],
]);
$perPage = (int) $request->input('per_page', 25);
$events = TripTimelineEvent::query()
->with(['trip.route', 'trip.team.driver', 'trip.truck', 'recordedBy'])
->whereIn('event_type', [
TripTimelineEvent::TYPE_INCIDENT_REPORTED,
TripTimelineEvent::TYPE_BREAKDOWN,
])
->when($request->filled('event_type'), fn ($q) => $q->where('event_type', $request->string('event_type')))
->when($request->filled('date'), fn ($q) => $q->whereDate('event_at', $request->string('date')))
->orderByDesc('event_at')
->paginate($perPage);
return $this->ok(
TripTimelineEventResource::collection($events),
null,
[
'page' => $events->currentPage(),
'per_page' => $events->perPage(),
'total' => $events->total(),
'last_page' => $events->lastPage(),
]
);
}
/**
* Returns human-readable conflict messages for the proposed trip.
* Cancelled trips don't conflict; everything else on the same date