transferDepartmentAction(), $this->closeTicketAction(), DeleteAction::make(), ]; } protected function transferDepartmentAction(): Action { return Action::make('transferDepartment') ->label(__('feedback.transfer_department')) ->icon('heroicon-o-arrows-right-left') ->color('warning') ->visible(fn (): bool => auth()->user()->hasPermissionTo('transfer-department')) ->form([ Select::make('to_department_id') ->label(__('feedback.target_department')) ->options(Department::pluck('name', 'id')->toArray()) ->required() ->searchable(), Select::make('new_assignee_id') ->label(__('feedback.assign_to_optional')) ->options(function (\Filament\Forms\Get $get) { $deptId = $get('to_department_id'); if (! $deptId) { return []; } $dept = Department::with('manager')->find($deptId); return \App\Models\User::pluck('name', 'id')->toArray(); }) ->searchable() ->nullable(), Textarea::make('reason') ->label(__('feedback.reason')) ->required() ->rows(3), FileUpload::make('transfer_attachments') ->label(__('app.attachments')) ->multiple() ->disk('public') ->directory('feedback-attachments') ->acceptedFileTypes(['image/jpeg', 'image/png', 'application/pdf', 'video/mp4', 'video/quicktime']) ->maxSize(51200) // 50MB in KB ->columnSpanFull(), ]) ->action(function (array $data): void { $feedback = $this->getRecord(); $toDepartment = Department::findOrFail($data['to_department_id']); $sender = auth()->user(); $transferService = App::make(TransferService::class); $log = $transferService->transfer( $feedback, $toDepartment, $data['reason'], $sender, $data['new_assignee_id'] ?? null, ); if (! empty($data['transfer_attachments'])) { $disk = Storage::disk('public'); foreach ($data['transfer_attachments'] as $path) { if (! $disk->exists($path)) { continue; } if ($feedback->attachments()->where('path', $path)->exists()) { continue; } $feedback->attachments()->create([ 'uuid' => (string) \Illuminate\Support\Str::uuid(), 'user_id' => $sender->id, 'name' => basename($path), 'path' => $path, 'disk' => 'public', 'collection' => 'general', 'mime_type' => $this->safeMimeType($disk, $path), 'size' => $this->safeFileSize($disk, $path), ]); } } Notification::make() ->title(__('feedback.transferred_successfully')) ->success() ->send(); $this->redirect($this->getResource()::getUrl('edit', ['record' => $feedback])); }); } protected function closeTicketAction(): Action { return Action::make('closeTicket') ->label(__('feedback.close_ticket')) ->icon('heroicon-o-check-circle') ->color('success') ->visible(fn (): bool => $this->getRecord()->status !== 'closed' && auth()->user()->hasPermissionTo('close-ticket')) ->requiresConfirmation() ->modalHeading(__('feedback.close_ticket')) ->modalDescription(__('feedback.close_confirm')) ->modalSubmitActionLabel(__('feedback.close_submit')) ->action(function (): void { $feedback = $this->getRecord(); $actor = auth()->user(); $closingService = App::make(ClosingService::class); try { $closingService->close($feedback, $actor); Notification::make() ->title(__('feedback.closed_successfully')) ->success() ->send(); $this->redirect($this->getResource()::getUrl('edit', ['record' => $feedback])); } catch (\Symfony\Component\HttpKernel\Exception\HttpException $e) { Notification::make() ->title($e->getMessage()) ->danger() ->send(); } catch (\Illuminate\Validation\ValidationException $e) { Notification::make() ->title($e->getMessage()) ->danger() ->send(); } }); } public function getFooter(): ?View { return view('filament.resources.feedback.edit-footer'); } protected array $pendingAttachments = []; protected string $pendingCollection = 'general'; protected function mutateFormDataBeforeSave(array $data): array { if (($data['is_general'] ?? false) === true) { $data['customer_product_id'] = null; } $this->pendingAttachments = $data['attachments'] ?? []; $this->pendingCollection = $data['attachment_collection'] ?? 'general'; unset($data['is_general'], $data['attachment_collection'], $data['attachments']); $newStatus = $data['status'] ?? null; if ($newStatus === 'closed' && $this->getRecord()->status !== 'closed') { $closingService = App::make(ClosingService::class); $closingService->close($this->getRecord(), auth()->user()); } return $data; } protected function afterSave(): void { if (empty($this->pendingAttachments)) { return; } $disk = Storage::disk('public'); $feedback = $this->getRecord(); foreach ($this->pendingAttachments as $path) { if (! $disk->exists($path)) { continue; } if ($feedback->attachments()->where('path', $path)->exists()) { continue; } $feedback->attachments()->create([ 'uuid' => (string) \Illuminate\Support\Str::uuid(), 'user_id' => auth()->id(), 'name' => basename($path), 'path' => $path, 'disk' => 'public', 'collection' => $this->pendingCollection, 'mime_type' => $this->safeMimeType($disk, $path), 'size' => $this->safeFileSize($disk, $path), ]); } } protected function safeMimeType($disk, string $path): ?string { try { return $disk->mimeType($path); } catch (\Exception $e) { return null; } } protected function safeFileSize($disk, string $path): ?int { try { return $disk->size($path); } catch (\Exception $e) { return null; } } protected function mutateFormDataBeforeFill(array $data): array { $data['is_general'] = $data['customer_product_id'] === null; return $data; } }