whereHas('shift.festivalSection', fn ($q) => $q->where('event_id', $event->id)) ->with(['person', 'shift.festivalSection', 'shift.timeSlot']); if ($request->filled('status')) { $status = ShiftAssignmentStatus::tryFrom($request->string('status')->toString()); if ($status !== null) { $query->where('status', $status); } } if ($request->filled('shift_id')) { $query->where('shift_id', $request->string('shift_id')->toString()); } if ($request->filled('person_id')) { $query->where('person_id', $request->string('person_id')->toString()); } if ($request->filled('section_id')) { $query->whereHas('shift', fn ($q) => $q->where('festival_section_id', $request->string('section_id')->toString())); } $assignments = $query->orderByDesc('created_at')->paginate(50); return ShiftAssignmentResource::collection($assignments); } public function approve(Event $event, ShiftAssignment $shiftAssignment): JsonResponse { Gate::authorize('approve', [$shiftAssignment, $event]); $shiftAssignment = $this->service->approve($shiftAssignment, request()->user()); return $this->success(new ShiftAssignmentResource($shiftAssignment->load(['person', 'shift.festivalSection', 'shift.timeSlot']))); } public function reject(RejectShiftAssignmentRequest $request, Event $event, ShiftAssignment $shiftAssignment): JsonResponse { Gate::authorize('reject', [$shiftAssignment, $event]); $shiftAssignment = $this->service->reject( $shiftAssignment, $request->user(), $request->validated('reason'), ); return $this->success(new ShiftAssignmentResource($shiftAssignment->load(['person', 'shift.festivalSection', 'shift.timeSlot']))); } public function cancel(Event $event, ShiftAssignment $shiftAssignment): JsonResponse { Gate::authorize('cancel', [$shiftAssignment, $event]); $shiftAssignment = $this->service->cancel($shiftAssignment, request()->user()); return $this->success(new ShiftAssignmentResource($shiftAssignment->load(['person', 'shift.festivalSection', 'shift.timeSlot']))); } public function bulkApprove(BulkApproveShiftAssignmentRequest $request, Event $event): JsonResponse { Gate::authorize('bulkApprove', [ShiftAssignment::class, $event]); $assignments = ShiftAssignment::whereIn('id', $request->validated('assignment_ids')) ->with('shift') ->get(); $results = $this->service->bulkApprove($assignments, $request->user()); return $this->success($results); } public function assignablePersons(Event $event, Shift $shift): JsonResponse { Gate::authorize('viewAny', [ShiftAssignment::class, $event]); $festivalEventId = $event->parent_event_id ?? $event->id; $timeSlotId = $shift->time_slot_id; $shiftId = $shift->id; // Get all conflict assignments for this time slot in one query $conflicts = ShiftAssignment::where('time_slot_id', $timeSlotId) ->whereIn('status', [ ShiftAssignmentStatus::PENDING_APPROVAL, ShiftAssignmentStatus::APPROVED, ]) ->with(['shift.festivalSection', 'shift.timeSlot']) ->get() ->keyBy('person_id'); // Get all assignments for THIS shift in one query $alreadyAssigned = ShiftAssignment::where('shift_id', $shiftId) ->whereNotIn('status', [ ShiftAssignmentStatus::REJECTED, ShiftAssignmentStatus::CANCELLED, ]) ->pluck('person_id') ->flip(); $persons = Person::where('event_id', $festivalEventId) ->where('status', PersonStatus::APPROVED) ->with('crowdType') ->orderBy('name') ->get() ->map(function (Person $person) use ($conflicts, $alreadyAssigned, $shiftId) { $isAlreadyAssigned = $alreadyAssigned->has($person->id); $conflict = $conflicts->get($person->id); $hasConflict = $conflict && $conflict->shift_id !== $shiftId; return [ 'id' => $person->id, 'name' => $person->name, 'email' => $person->email, 'status' => $person->status, 'crowd_type' => $person->crowdType ? [ 'id' => $person->crowdType->id, 'name' => $person->crowdType->name, 'system_type' => $person->crowdType->system_type, ] : null, 'is_available' => ! $hasConflict && ! $isAlreadyAssigned, 'already_assigned' => $isAlreadyAssigned, 'conflict' => $hasConflict ? [ 'section_name' => $conflict->shift->festivalSection->name, 'shift_title' => $conflict->shift->title ?? $conflict->shift->festivalSection->name, 'time_slot_name' => $conflict->shift->timeSlot->name, 'time' => $conflict->shift->timeSlot->start_time . '–' . $conflict->shift->timeSlot->end_time, ] : null, ]; }) ->sortBy([ ['already_assigned', 'asc'], ['is_available', 'desc'], ['name', 'asc'], ]) ->values(); return response()->json(['data' => $persons]); } }