type = $type; $this->defaultTargetType = $defaultTargetType; } /** * Bridge landing spot: any change to the target selects or the radio * toggle in the parent form dispatches to us with the current shape. * Nullable values come through when the operator clears a select. */ #[On('checkout-target-selected')] public function targetSelected(?string $targetType, ?string $targetId): void { $this->targetType = in_array($targetType, self::TARGET_TYPES, true) ? $targetType : null; $this->targetId = $targetId !== null && $targetId !== '' ? (int) $targetId : null; } public function render(): View { return view('livewire.checkout-target-panel', [ 'items' => $this->items(), 'noun' => $this->itemNoun(), 'targetNoun' => $this->targetNoun(), ]); } private function items(): Collection { if ($this->targetType === null || $this->targetId === null) { return collect(); } $target = $this->resolveTarget(); if (! $target || ! Gate::allows('view', $target)) { return collect(); } // Not every (item, target) combo is a valid checkout path in the // schema — consumables only go to users, licenses only to users // or assets. Falling out to an empty collection is intentional: // the operator switched to a target type that this item can't // actually be checked out to, so there's nothing to show. // // Location targets use the polymorphic `assignedAssets` relation // (assets checked OUT to this location via assigned_to/ // assigned_type), NOT Location::assets() which is location_id-based // and means "assets physically at this location". Same story for // accessories: query the checkout pivot rather than the // location_id column. return match ("{$this->type}:{$this->targetType}") { 'assets:user' => $target->assets, 'licenses:user' => $target->licenses, 'accessories:user' => $target->accessories, 'consumables:user' => $target->consumables, 'assets:asset' => $target->assignedAssets, 'licenses:asset' => $target->licenses, 'accessories:asset' => $target->accessories, 'assets:location' => $target->assignedAssets, 'accessories:location' => Accessory::whereHas('checkouts', function ($q) { $q->where('assigned_type', Location::class) ->where('assigned_to', $this->targetId); })->get(), 'components:asset' => $target->components, default => collect(), }; } private function resolveTarget(): ?Model { return match ($this->targetType) { 'user' => User::find($this->targetId), 'asset' => Asset::find($this->targetId), 'location' => Location::find($this->targetId), default => null, }; } private function itemNoun(): string { return match ($this->type) { 'assets' => trans('general.assets'), 'licenses' => trans('general.licenses'), 'accessories' => trans('general.accessories'), 'consumables' => trans('general.consumables'), 'components' => trans('general.components'), }; } private function targetNoun(): string { return match ($this->targetType) { 'user' => trans('general.user'), 'asset' => trans('general.asset'), 'location' => trans('general.location'), default => trans('general.user'), }; } }