feat(portal): shift claiming and my-shifts for volunteer portal

Backend: PortalShiftController with 4 endpoints (available-shifts,
my-shifts, claim, cancel) delegating to ShiftAssignmentService.
24 PHPUnit tests covering happy paths, auth, conflicts, and edge cases.

Frontend: claim-shifts and my-shifts pages with TanStack Query
composable, conflict detection, confirmation dialogs, and cancel flow.
Navigation and dashboard cards wired up for approved volunteers.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-13 08:47:12 +02:00
parent 0d5523dbfe
commit 5173f7297f
10 changed files with 1790 additions and 9 deletions

View File

@@ -31,6 +31,7 @@ use App\Http\Controllers\Api\V1\PublicRegistrationDataController;
use App\Http\Controllers\Api\V1\PortalTokenController;
use App\Http\Controllers\Api\V1\PasswordResetController;
use App\Http\Controllers\Api\V1\PortalMeController;
use App\Http\Controllers\Api\V1\Portal\PortalShiftController;
use App\Http\Controllers\Api\V1\UserOrganisationTagController;
use App\Models\FestivalSection;
use App\Models\Organisation;
@@ -79,6 +80,10 @@ Route::middleware('auth:sanctum')->group(function () {
// Portal (authenticated)
Route::get('portal/me', [PortalMeController::class, 'index']);
Route::get('portal/events/{event}/available-shifts', [PortalShiftController::class, 'availableShifts']);
Route::get('portal/events/{event}/my-shifts', [PortalShiftController::class, 'myShifts']);
Route::post('portal/events/{event}/shifts/{shift}/claim', [PortalShiftController::class, 'claim']);
Route::post('portal/events/{event}/assignments/{shiftAssignment}/cancel', [PortalShiftController::class, 'cancel']);
// Organisations
Route::apiResource('organisations', OrganisationController::class)