components([ Section::make(__('app.customer_product')) ->description(__('app.select_customer_product')) ->icon('heroicon-o-user-group') ->schema([ Select::make('customer_id') ->label(__('app.widget_customer')) ->relationship('customer', 'name') ->searchable() ->required() ->live() ->columnSpan(1), Toggle::make('is_general') ->label(__('app.general_feedback')) ->default(false) ->live() ->columnSpan(1), Select::make('product_id_virtual') ->label(__('app.product')) ->searchable() ->options(fn ($get): array => self::getProductOptionsForCustomer($get('customer_id'))) ->getSearchResultsUsing(function (string $search, $get): array { $customerId = $get('customer_id'); if (! $customerId) { return []; } $keyword = mb_strtolower($search); $allIds = self::getProductIdsForCustomer($customerId); if ($allIds->isEmpty()) { return []; } return \App\Models\Product::whereIn('id', $allIds) ->whereRaw('LOWER(name) LIKE ?', ["%{$keyword}%"]) ->limit(50) ->pluck('name', 'id') ->toArray(); }) ->getOptionLabelUsing(fn ($value): ?string => \App\Models\Product::find($value)?->name ) ->visible(fn ($get): bool => ! $get('is_general')) ->nullable() ->live() ->columnSpan(1), Select::make('contract_id') ->label(__('app.contract')) ->visible(fn ($get): bool => ! $get('is_general') && filled($get('product_id_virtual'))) ->options(function ($get): array { $productId = $get('product_id_virtual'); if (! $productId) { return []; } return Contract::where('product_id', $productId) ->where('status', 'active') ->with('customer') ->get() ->mapWithKeys(fn ($c) => [ $c->id => sprintf('%s — %s (%s)', \App\Enums\ContractType::from($c->type)->label(), $c->customer->name, $c->start_date?->format('d/m/Y') ?? 'N/A', ), ]) ->toArray(); }) ->afterStateHydrated(function ($component, $state, $get): void { if (filled($state)) { return; } $productId = $get('product_id_virtual'); if (! $productId) { return; } $activeContract = Contract::where('product_id', $productId) ->where('status', 'active') ->first(); if ($activeContract) { $component->state($activeContract->id); } }) ->nullable() ->searchable(), ]) ->columns(2), Section::make(__('app.feedback_details')) ->description(__('app.describe_feedback')) ->icon('heroicon-o-chat-bubble-left-right') ->schema([ Select::make('feedback_channel_id') ->label(__('app.channel')) ->relationship('feedbackChannel', 'name') ->required() ->preload() ->columnSpan(1), TextInput::make('title') ->label(__('app.blade_title')) ->required() ->maxLength(255) ->columnSpan(1), Textarea::make('content') ->label(__('app.description')) ->required() ->rows(6) ->columnSpanFull(), Select::make('tags') ->relationship('tags', 'name') ->multiple() ->preload() ->columnSpanFull() ->createOptionForm([ TextInput::make('name')->label(__('app.name'))->required(), TextInput::make('slug')->label(__('app.slug'))->required(), ColorPicker::make('color')->label(__('app.color'))->default('#3b82f6'), ]), ]) ->columns(2), Section::make(__('app.assignment_status')) ->description(__('app.assign_handler')) ->icon('heroicon-o-arrow-path-rounded-square') ->schema([ Select::make('status') ->label(__('app.status')) ->options([ 'pending' => __('app.status_pending'), 'processing' => __('app.status_processing'), 'resolved' => __('app.status_resolved'), 'closed' => __('app.status_closed'), ]) ->default('pending') ->required() ->columnSpan(1), Select::make('assigned_to') ->label(__('app.assigned_to')) ->relationship('assignedTo', 'name') ->searchable() ->nullable() ->columnSpan(1), Select::make('current_department_id') ->label(__('app.department')) ->options(Department::pluck('name', 'id')->toArray()) ->searchable() ->nullable() ->columnSpan(1), Toggle::make('is_escalated') ->label(__('app.escalated')) ->visible(fn (): bool => auth()->user()?->hasPermissionTo('close-ticket') ?? false) ->columnSpan(1), ]) ->columns(2), Section::make(__('app.attachments')) ->description(__('app.upload_files')) ->icon('heroicon-o-paper-clip') ->schema([ Select::make('attachment_collection') ->label(__('app.collection')) ->options([ 'general' => __('app.collection_general'), 'proof_of_resolution' => __('app.collection_proof_of_resolution'), 'customer_evidence' => __('app.collection_customer_evidence'), ]) ->default('general') ->columnSpan(1), FileUpload::make('attachments') ->label(__('app.files')) ->multiple() ->disk('public') ->directory('feedback-attachments') ->acceptedFileTypes(['image/jpeg', 'image/png', 'application/pdf', 'video/mp4', 'video/quicktime']) ->maxSize(51200) ->columnSpan(1), ]) ->columns(2), ]); } private static function getProductOptionsForCustomer(?int $customerId): array { $ids = self::getProductIdsForCustomer($customerId); return \App\Models\Product::whereIn('id', $ids) ->limit(100) ->pluck('name', 'id') ->toArray(); } private static function getProductIdsForCustomer(?int $customerId): \Illuminate\Support\Collection { if (! $customerId) { return collect(); } $ownedIds = Contract::where('customer_id', $customerId) ->where('category', 'ownership') ->where('status', 'active') ->pluck('product_id'); $customer = \App\Models\Customer::find($customerId); $repIds = collect(); if ($customer?->email) { $repIds = Contract::where('representative_email', $customer->email) ->where('status', 'active') ->pluck('product_id'); } return $ownedIds->merge($repIds)->unique(); } }