Repo rebuild
This commit is contained in:
@@ -0,0 +1,349 @@
|
||||
<?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.*' => $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<int, string>
|
||||
*/
|
||||
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<int, string>
|
||||
*/
|
||||
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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user