resolveLinkedHost($request); $organization = $this->organization($request); $pending = Visit::owned($host->owner_ref) ->where('organization_id', $organization->id) ->where('host_id', $host->id) ->where('status', Visit::STATUS_WAITING) ->with('visitor') ->orderByDesc('updated_at') ->get() ->filter(fn (Visit $v) => $v->awaitingApproval()); $upcoming = Visit::owned($host->owner_ref) ->where('organization_id', $organization->id) ->where('host_id', $host->id) ->whereIn('status', [Visit::STATUS_SCHEDULED, Visit::STATUS_EXPECTED, Visit::STATUS_OVERDUE]) ->with('visitor') ->orderBy('scheduled_at') ->limit(10) ->get(); $recent = Visit::owned($host->owner_ref) ->where('organization_id', $organization->id) ->where('host_id', $host->id) ->whereIn('status', [Visit::STATUS_CHECKED_IN, Visit::STATUS_CHECKED_OUT, Visit::STATUS_CANCELLED]) ->with('visitor') ->orderByDesc('updated_at') ->limit(15) ->get(); return view('frontdesk.host-portal.index', compact('host', 'organization', 'pending', 'upcoming', 'recent')); } public function scheduleForm(Request $request): View { $host = $this->resolveLinkedHost($request); $organization = $this->organization($request); return view('frontdesk.host-portal.schedule', compact('host', 'organization')); } public function scheduleStore(Request $request, VisitScheduleService $scheduler): RedirectResponse { $host = $this->resolveLinkedHost($request); $organization = $this->organization($request); $validated = $request->validate([ 'full_name' => ['required', 'string', 'max:255'], 'company' => ['nullable', 'string', 'max:255'], 'phone' => ['nullable', 'string', 'max:50'], 'email' => ['nullable', 'email', 'max:255'], 'visitor_type' => ['required', 'string', 'in:'.implode(',', array_keys(config('frontdesk.visitor_types')))], 'purpose' => ['nullable', 'string', 'max:500'], 'expected_duration_minutes' => ['nullable', 'integer', 'min:5', 'max:480'], 'scheduled_at' => ['required', 'date'], 'notes' => ['nullable', 'string', 'max:2000'], ]); $scheduler->schedule( $host->owner_ref, $organization, [ ...$validated, 'host_id' => $host->id, 'branch_id' => $host->branch_id, ], $this->ownerRef($request), ); return redirect()->route('frontdesk.host.index')->with('success', 'Visit scheduled.'); } public function approve(Request $request, Visit $visit, VisitLifecycleService $lifecycle): RedirectResponse { $host = $this->resolveLinkedHost($request); $this->authorizeOwner($request, $visit); abort_unless($visit->host_id === $host->id, 403); abort_unless($visit->awaitingApproval(), 422); $lifecycle->approve($visit, $this->ownerRef($request)); return redirect()->route('frontdesk.host.index')->with('success', 'Visit approved.'); } public function toggleAvailability(Request $request): RedirectResponse { $host = $this->resolveLinkedHost($request); $host->update(['is_available' => ! $host->is_available]); return back()->with('success', $host->is_available ? 'You are now available.' : 'You are marked unavailable.'); } protected function resolveLinkedHost(Request $request): Host { $permissions = app(FrontdeskPermissions::class); $member = $this->member($request); abort_unless( $permissions->can($member, 'host.portal') || app(OrganizationResolver::class)->hostFor($request->user()) !== null, 403, ); $host = app(OrganizationResolver::class)->hostFor($request->user()); abort_unless($host, 403, 'No host profile linked to your account.'); $this->authorizeOwner($request, $host); return $host; } }