*/ public array $tag_ids = []; /** @var array */ public array $attachments = []; public bool $isSaving = false; /** * Validation rules are intentionally evaluated only when the user submits * the form, not while rich-text/select widgets are synchronising. * * @return array */ protected function rules(): array { return [ 'company_id' => ['nullable', 'integer'], 'subject' => ['required', 'string', 'max:200'], 'body' => ['required', 'string', 'min:10'], 'board_id' => ['nullable', 'integer', 'exists:helpdesk_kanban_boards,id'], 'priority_id' => ['required', 'integer', 'exists:helpdesk_priorities,id'], 'category_id' => ['nullable', 'integer', 'exists:helpdesk_categories,id'], 'project_id' => ['nullable', 'integer', 'exists:helpdesk_projects,id'], 'product_id' => ['nullable', 'integer', 'exists:helpdesk_products,id'], 'team_id' => ['nullable', 'integer', 'exists:helpdesk_teams,id'], 'due_at' => ['nullable', 'date'], 'tag_ids' => ['array'], 'tag_ids.*' => ['integer', 'exists:helpdesk_tags,id'], 'attachments' => ['array'], 'attachments.*' => $this->attachmentFileRules(), ]; } /** * Initialise configured Helpdesk defaults for a new ticket. */ public function mount(): void { $this->applyDefaults(); } /** * Store a new helpdesk ticket. */ public function save(TicketService $tickets) { if ($this->isSaving) { return null; } $this->isSaving = true; try { $this->body = trim($this->body); $this->validate(); $ticket = $tickets->create(Auth::user(), [ 'company_id' => $this->company_id, 'subject' => $this->subject, 'body' => $this->body, 'board_id' => $this->board_id, 'priority_id' => $this->priority_id, 'category_id' => $this->category_id, 'project_id' => $this->project_id, 'product_id' => $this->product_id, 'team_id' => $this->team_id, 'due_at' => $this->due_at, 'tag_ids' => $this->tag_ids, ], $this->attachments); session()->flash('success', 'Ticket ' . $ticket->reference . ' has been created.'); return redirect()->to(route('helpdesk.tickets.index')); } finally { $this->isSaving = false; } } /** * Clear the create form, including Select2 and Flatpickr browser widgets. */ public function clearForm(): void { $this->resetErrorBag(); $this->resetValidation(); $this->subject = ''; $this->body = ''; $this->tag_ids = []; $this->attachments = []; $this->dispatch('helpdesk-form-cleared'); $this->dispatch('helpdesk-editor-reset', model: 'body'); } public function render(): View { return view('helpdesk::livewire.ticket-create', [ 'boards' => $this->boardOptions(), 'priorities' => $this->priorityOptions(), 'categories' => $this->categoryOptions(), 'companies' => $this->companyOptions(), 'teams' => $this->teamOptions(), 'projects' => $this->projectOptions(), 'products' => Product::query()->where('is_active', true)->orderBy('name')->get(), 'tags' => Tag::query()->orderBy('name')->get(), ])->layout('layouts.app'); } /** * Apply default values from Helpdesk/company lookup configuration. */ private function applyDefaults(): void { if (blank($this->company_id) && Schema::hasTable('companies') && Schema::hasColumn('companies', 'is_default_helpdesk_company')) { $this->company_id = DB::table('companies') ->where('is_default_helpdesk_company', true) ->orderBy('name') ->value('id'); } if (blank($this->board_id) && Schema::hasTable('helpdesk_kanban_boards')) { $this->board_id = KanbanBoard::query() ->where('is_default', true) ->orderBy('sort_order') ->value('id') ?: KanbanBoard::query()->orderBy('sort_order')->value('id'); } if (blank($this->priority_id) && $this->hasDefaultColumn('helpdesk_priorities')) { $this->priority_id = Priority::query() ->where('is_default', true) ->orderBy('sort_order') ->value('id'); } if (blank($this->category_id) && $this->hasDefaultColumn('helpdesk_categories')) { $this->category_id = Category::query() ->where('is_default', true) ->when($this->company_id, fn ($query) => $query->where(function ($query): void { $query->where('company_id', $this->company_id) ->orWhereNull('company_id'); })) ->orderByRaw('company_id is null') ->orderBy('name') ->value('id'); } if (blank($this->team_id) && $this->hasDefaultColumn('helpdesk_teams')) { $this->team_id = Team::query() ->where('is_default', true) ->whereNull('archived_at') ->when($this->company_id, fn ($query) => $query->where(function ($query): void { $query->where('company_id', $this->company_id) ->orWhereNull('company_id'); })) ->orderByRaw('company_id is null') ->orderBy('name') ->value('id'); } if (blank($this->project_id) && $this->hasDefaultColumn('helpdesk_projects')) { $this->project_id = Project::query() ->where('is_default', true) ->whereNull('archived_at') ->when($this->company_id, fn ($query) => $query->where(function ($query): void { $query->where('company_id', $this->company_id) ->orWhereNull('company_id'); })) ->when($this->team_id, fn ($query) => $query->where(function ($query): void { $query->where('team_id', $this->team_id) ->orWhereNull('team_id'); })) ->orderByRaw('company_id is null') ->orderByRaw('team_id is null') ->orderBy('title') ->value('id'); } } /** * Return Kanban board options with the default board listed first. */ private function boardOptions(): mixed { return KanbanBoard::query() ->with('owner:id,name,email') ->orderByDesc('is_default') ->orderBy('sort_order') ->orderBy('name') ->get(); } /** * Return priority options with default records listed first. */ private function priorityOptions(): mixed { return Priority::query() ->when($this->hasDefaultColumn('helpdesk_priorities'), fn ($query) => $query->orderByDesc('is_default')) ->orderBy('sort_order') ->get(); } /** * Return category options with default records listed first. */ private function categoryOptions(): mixed { return Category::query() ->with('agents:id') ->when($this->hasDefaultColumn('helpdesk_categories'), fn ($query) => $query->orderByDesc('is_default')) ->orderBy('name') ->get(); } /** * Return team options with default records listed first. */ private function teamOptions(): mixed { return Team::query() ->whereNull('archived_at') ->when($this->hasDefaultColumn('helpdesk_teams'), fn ($query) => $query->orderByDesc('is_default')) ->orderBy('name') ->get(); } /** * Return project options with default records listed first. */ private function projectOptions(): mixed { return Project::query() ->whereNull('archived_at') ->when($this->hasDefaultColumn('helpdesk_projects'), fn ($query) => $query->orderByDesc('is_default')) ->orderBy('title') ->get(); } /** * Return company options with default flag support when available. */ private function companyOptions(): mixed { if (! Schema::hasTable('companies')) { return collect(); } $columns = ['id', 'name']; if (Schema::hasColumn('companies', 'is_default_helpdesk_company')) { $columns[] = 'is_default_helpdesk_company'; } return DB::table('companies') ->select($columns) ->orderByDesc(Schema::hasColumn('companies', 'is_default_helpdesk_company') ? 'is_default_helpdesk_company' : 'id') ->orderBy('name') ->get(); } /** * Check whether a Helpdesk lookup table has the default flag column. */ private function hasDefaultColumn(string $table): bool { return Schema::hasTable($table) && Schema::hasColumn($table, 'is_default'); } /** * Return Helpdesk attachment validation rules without relying on a newly autoloaded support class. * * Laravel validates source-code attachments by extension because browsers often report * PHP, JavaScript, RPG and other source files as text/plain or octet-stream. * * @return array */ private function attachmentFileRules(): array { return [ 'file', 'max:' . (int) config('helpdesk.max_upload_kb', 10240), 'extensions:' . implode(',', $this->allowedAttachmentExtensions()), ]; } /** * Return lower-case Helpdesk attachment extensions. * * @return array */ private function allowedAttachmentExtensions(): array { return collect(config('helpdesk.allowed_extensions', config('helpdesk.allowed_mimes', []))) ->filter(static fn (mixed $extension): bool => is_string($extension) && trim($extension) !== '') ->map(static fn (string $extension): string => strtolower(ltrim(trim($extension), '.'))) ->unique() ->values() ->all(); } }