feat: smart assign person dialog with conflict details and assignable-persons endpoint

Add GET /events/{event}/shifts/{shift}/assignable-persons endpoint that
returns approved persons with availability status, conflict details, and
already-assigned flags. Improve ShiftAssignmentService conflict errors to
include section name, time slot, and time range. Replace both assign
dialogs with a new AssignPersonDialog featuring search, crowd type
filtering, availability toggle, and inline conflict warnings.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-10 20:32:31 +02:00
parent c220446920
commit 968e17c6d6
10 changed files with 1872 additions and 13 deletions

View File

@@ -24,7 +24,7 @@ final class ShiftAssignmentService
$this->validateShiftIsOpen($shift);
$this->validatePersonApproved($person);
$this->validateClaimCapacity($shift);
$this->validateNoConflict($shift, $person);
$this->validateNoConflict($shift, $person, isClaim: true);
$autoApprove = $shift->festivalSection->crew_auto_accepts;
$status = $autoApprove
@@ -323,20 +323,30 @@ final class ShiftAssignmentService
/**
* @throws ValidationException
*/
private function validateNoConflict(Shift $shift, Person $person): void
private function validateNoConflict(Shift $shift, Person $person, bool $isClaim = false): void
{
if ($shift->allow_overlap) {
return;
}
$conflict = ShiftAssignment::where('person_id', $person->id)
$existingAssignment = ShiftAssignment::where('person_id', $person->id)
->where('time_slot_id', $shift->time_slot_id)
->active()
->exists();
->with(['shift.festivalSection', 'shift.timeSlot'])
->first();
if ($existingAssignment) {
$section = $existingAssignment->shift->festivalSection->name;
$timeSlot = $existingAssignment->shift->timeSlot->name;
$time = $existingAssignment->shift->timeSlot->start_time
. '' . $existingAssignment->shift->timeSlot->end_time;
$message = $isClaim
? "Je bent al ingepland bij \"{$section}\" voor {$timeSlot} ({$time})."
: "Deze persoon is al ingepland bij \"{$section}\" voor {$timeSlot} ({$time}).";
if ($conflict) {
throw ValidationException::withMessages([
'person_id' => ['Deze persoon is al ingepland voor dit tijdslot.'],
'person_id' => [$message],
]);
}
}