* Removed the live body binding causing premature $commit. * Details editor still syncs before submit. * Added duplicate-submit guard. * Should no longer need clicking Save/Create twice. * Append-only changelog. * module.json version 1.9.42.
331 lines
11 KiB
PHP
331 lines
11 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace Modules\Helpdesk\Livewire;
|
|
|
|
use Illuminate\Contracts\View\View;
|
|
use Illuminate\Support\Facades\Auth;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\Schema;
|
|
use Livewire\Component;
|
|
use Livewire\Features\SupportFileUploads\TemporaryUploadedFile;
|
|
use Livewire\WithFileUploads;
|
|
use Modules\Helpdesk\Models\Category;
|
|
use Modules\Helpdesk\Models\KanbanBoard;
|
|
use Modules\Helpdesk\Models\Priority;
|
|
use Modules\Helpdesk\Models\Product;
|
|
use Modules\Helpdesk\Models\Project;
|
|
use Modules\Helpdesk\Models\Tag;
|
|
use Modules\Helpdesk\Models\Team;
|
|
use Modules\Helpdesk\Services\TicketService;
|
|
|
|
final class TicketCreate extends Component
|
|
{
|
|
use WithFileUploads;
|
|
|
|
public ?int $company_id = null;
|
|
|
|
public string $subject = '';
|
|
|
|
public string $body = '';
|
|
|
|
public ?int $board_id = null;
|
|
|
|
public ?int $priority_id = null;
|
|
|
|
public ?int $category_id = null;
|
|
|
|
public ?int $project_id = null;
|
|
|
|
public ?int $product_id = null;
|
|
|
|
public ?int $team_id = null;
|
|
|
|
public ?string $due_at = null;
|
|
|
|
/** @var array<int, int> */
|
|
public array $tag_ids = [];
|
|
|
|
/** @var array<int, TemporaryUploadedFile> */
|
|
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<string, mixed>
|
|
*/
|
|
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.*' => [
|
|
'file',
|
|
'max:'.config('helpdesk.max_upload_kb'),
|
|
'mimes:'.implode(',', config('helpdesk.allowed_mimes')),
|
|
],
|
|
];
|
|
}
|
|
|
|
/**
|
|
* 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 $this->redirectRoute('helpdesk.tickets.index', navigate: true);
|
|
} 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->company_id = null;
|
|
$this->board_id = null;
|
|
$this->priority_id = null;
|
|
$this->category_id = null;
|
|
$this->project_id = null;
|
|
$this->product_id = null;
|
|
$this->team_id = null;
|
|
$this->due_at = null;
|
|
$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');
|
|
}
|
|
}
|