This commit is contained in:
Mark Gregory
2026-06-15 17:55:36 +01:00
parent 578a977c59
commit 0a9746fa77
25 changed files with 844 additions and 106 deletions
+186 -27
View File
@@ -28,20 +28,22 @@ final class SettingsManager extends Component
{
public string $section = 'statuses';
/** @var array<string, mixed> */
public array $statusForm = ['id' => null, 'name' => '', 'slug' => '', 'color' => 'zinc', 'sort_order' => 0, 'is_closed' => false];
public int|string|null $default_helpdesk_company_id = null;
/** @var array<string, mixed> */
public array $priorityForm = ['id' => null, 'name' => '', 'slug' => '', 'color' => 'zinc', 'sort_order' => 0];
public array $statusForm = ['id' => null, 'name' => '', 'slug' => '', 'color' => 'zinc', 'sort_order' => 0, 'is_closed' => false, 'is_default' => false];
/** @var array<string, mixed> */
public array $categoryForm = ['id' => null, 'company_id' => null, 'name' => '', 'slug' => '', 'color' => 'blue', 'description' => ''];
public array $priorityForm = ['id' => null, 'name' => '', 'slug' => '', 'color' => 'zinc', 'sort_order' => 0, 'is_default' => false];
/** @var array<string, mixed> */
public array $teamForm = ['id' => null, 'company_id' => null, 'name' => '', 'slug' => '', 'color' => 'indigo', 'description' => '', 'archived_at' => null];
public array $categoryForm = ['id' => null, 'company_id' => null, 'name' => '', 'slug' => '', 'color' => 'blue', 'description' => '', 'is_default' => false];
/** @var array<string, mixed> */
public array $projectForm = ['id' => null, 'company_id' => null, 'team_id' => null, 'title' => '', 'slug' => '', 'color' => 'blue', 'description' => '', 'archived_at' => null];
public array $teamForm = ['id' => null, 'company_id' => null, 'name' => '', 'slug' => '', 'color' => 'indigo', 'description' => '', 'is_default' => false, 'archived_at' => null];
/** @var array<string, mixed> */
public array $projectForm = ['id' => null, 'company_id' => null, 'team_id' => null, 'title' => '', 'slug' => '', 'color' => 'blue', 'description' => '', 'is_default' => false, 'archived_at' => null];
/** @var array<string, mixed> */
public array $boardForm = ['id' => null, 'name' => '', 'slug' => '', 'description' => '', 'is_default' => false, 'sort_order' => 0, 'status_ids' => []];
@@ -72,11 +74,12 @@ final class SettingsManager extends Component
public function mount(): void
{
$this->statusForm += ['id' => null, 'name' => '', 'slug' => '', 'color' => 'zinc', 'sort_order' => 0, 'is_closed' => false];
$this->priorityForm += ['id' => null, 'name' => '', 'slug' => '', 'color' => 'zinc', 'sort_order' => 0];
$this->categoryForm += ['id' => null, 'company_id' => null, 'name' => '', 'slug' => '', 'color' => 'blue', 'description' => ''];
$this->teamForm += ['id' => null, 'company_id' => null, 'name' => '', 'slug' => '', 'color' => 'indigo', 'description' => '', 'archived_at' => null];
$this->projectForm += ['id' => null, 'company_id' => null, 'team_id' => null, 'title' => '', 'slug' => '', 'color' => 'blue', 'description' => '', 'archived_at' => null];
$this->loadDefaultHelpdeskCompany();
$this->statusForm += ['id' => null, 'name' => '', 'slug' => '', 'color' => 'zinc', 'sort_order' => 0, 'is_closed' => false, 'is_default' => false];
$this->priorityForm += ['id' => null, 'name' => '', 'slug' => '', 'color' => 'zinc', 'sort_order' => 0, 'is_default' => false];
$this->categoryForm += ['id' => null, 'company_id' => null, 'name' => '', 'slug' => '', 'color' => 'blue', 'description' => '', 'is_default' => false];
$this->teamForm += ['id' => null, 'company_id' => null, 'name' => '', 'slug' => '', 'color' => 'indigo', 'description' => '', 'is_default' => false, 'archived_at' => null];
$this->projectForm += ['id' => null, 'company_id' => null, 'team_id' => null, 'title' => '', 'slug' => '', 'color' => 'blue', 'description' => '', 'is_default' => false, 'archived_at' => null];
$this->boardForm += ['id' => null, 'name' => '', 'slug' => '', 'description' => '', 'is_default' => false, 'sort_order' => 0, 'status_ids' => []];
$this->repositoryForm += ['id' => null, 'company_id' => null, 'project_id' => null, 'name' => '', 'provider' => 'git', 'remote_url' => '', 'default_branch' => 'main', 'webhook_secret' => ''];
$this->workflowForm += ['id' => null, 'name' => '', 'event' => 'ticket.created', 'subject' => '', 'html_body' => '', 'text_body' => '', 'to_role' => 'requester', 'cc' => '', 'bcc' => '', 'is_active' => true];
@@ -96,9 +99,16 @@ final class SettingsManager extends Component
'statusForm.color' => ['required', 'string', 'max:30'],
'statusForm.sort_order' => ['required', 'integer', 'min:0'],
'statusForm.is_closed' => ['boolean'],
'statusForm.is_default' => ['boolean'],
])['statusForm'];
$data['slug'] = $this->slug($data['slug'] ?: $data['name']);
$data = $this->guardDefaultColumn('helpdesk_statuses', $data);
if ((bool) ($data['is_default'] ?? false)) {
Status::query()->whereKeyNot($this->statusForm['id'])->update(['is_default' => false]);
}
Status::query()->updateOrCreate(['id' => $this->statusForm['id']], $data);
$this->resetStatusForm();
session()->flash('success', 'Status saved.');
@@ -106,7 +116,7 @@ final class SettingsManager extends Component
public function editStatus(int $id): void
{
$this->statusForm = Status::query()->findOrFail($id)->only(['id', 'name', 'slug', 'color', 'sort_order', 'is_closed']);
$this->statusForm = Status::query()->findOrFail($id)->only(['id', 'name', 'slug', 'color', 'sort_order', 'is_closed', 'is_default']);
$this->section = 'statuses';
}
@@ -118,7 +128,7 @@ final class SettingsManager extends Component
public function resetStatusForm(): void
{
$this->statusForm = ['id' => null, 'name' => '', 'slug' => '', 'color' => 'zinc', 'sort_order' => 0, 'is_closed' => false];
$this->statusForm = ['id' => null, 'name' => '', 'slug' => '', 'color' => 'zinc', 'sort_order' => 0, 'is_closed' => false, 'is_default' => false];
}
public function savePriority(): void
@@ -128,9 +138,15 @@ final class SettingsManager extends Component
'priorityForm.slug' => ['nullable', 'string', 'max:100'],
'priorityForm.color' => ['required', 'string', 'max:30'],
'priorityForm.sort_order' => ['required', 'integer', 'min:0'],
'priorityForm.is_default' => ['boolean'],
])['priorityForm'];
$data['slug'] = $this->slug($data['slug'] ?: $data['name']);
$data = $this->guardDefaultColumn('helpdesk_priorities', $data);
if ((bool) ($data['is_default'] ?? false)) {
Priority::query()->whereKeyNot($this->priorityForm['id'])->update(['is_default' => false]);
}
Priority::query()->updateOrCreate(['id' => $this->priorityForm['id']], $data);
$this->resetPriorityForm();
session()->flash('success', 'Priority saved.');
@@ -138,7 +154,7 @@ final class SettingsManager extends Component
public function editPriority(int $id): void
{
$this->priorityForm = Priority::query()->findOrFail($id)->only(['id', 'name', 'slug', 'color', 'sort_order']);
$this->priorityForm = Priority::query()->findOrFail($id)->only(['id', 'name', 'slug', 'color', 'sort_order', 'is_default']);
$this->section = 'priorities';
}
@@ -150,7 +166,7 @@ final class SettingsManager extends Component
public function resetPriorityForm(): void
{
$this->priorityForm = ['id' => null, 'name' => '', 'slug' => '', 'color' => 'zinc', 'sort_order' => 0];
$this->priorityForm = ['id' => null, 'name' => '', 'slug' => '', 'color' => 'zinc', 'sort_order' => 0, 'is_default' => false];
}
public function saveCategory(): void
@@ -161,9 +177,18 @@ final class SettingsManager extends Component
'categoryForm.slug' => ['nullable', 'string', 'max:140'],
'categoryForm.color' => ['required', 'string', 'max:30'],
'categoryForm.description' => ['nullable', 'string'],
'categoryForm.is_default' => ['boolean'],
])['categoryForm'];
$data['slug'] = $this->slug($data['slug'] ?: $data['name']);
$data = $this->guardDefaultColumn('helpdesk_categories', $data);
if ((bool) ($data['is_default'] ?? false)) {
Category::query()
->whereKeyNot($this->categoryForm['id'])
->where('company_id', $data['company_id'])
->update(['is_default' => false]);
}
Category::query()->updateOrCreate(['id' => $this->categoryForm['id']], $data);
$this->resetCategoryForm();
session()->flash('success', 'Category saved.');
@@ -171,7 +196,7 @@ final class SettingsManager extends Component
public function editCategory(int $id): void
{
$this->categoryForm = Category::query()->findOrFail($id)->only(['id', 'company_id', 'name', 'slug', 'color', 'description']);
$this->categoryForm = Category::query()->findOrFail($id)->only(['id', 'company_id', 'name', 'slug', 'color', 'description', 'is_default']);
$this->section = 'categories';
}
@@ -183,7 +208,7 @@ final class SettingsManager extends Component
public function resetCategoryForm(): void
{
$this->categoryForm = ['id' => null, 'company_id' => null, 'name' => '', 'slug' => '', 'color' => 'blue', 'description' => ''];
$this->categoryForm = ['id' => null, 'company_id' => null, 'name' => '', 'slug' => '', 'color' => 'blue', 'description' => '', 'is_default' => false];
}
public function saveTeam(): void
@@ -194,9 +219,18 @@ final class SettingsManager extends Component
'teamForm.slug' => ['nullable', 'string', 'max:140'],
'teamForm.color' => ['required', 'string', 'max:30'],
'teamForm.description' => ['nullable', 'string'],
'teamForm.is_default' => ['boolean'],
])['teamForm'];
$data['slug'] = $this->slug($data['slug'] ?: $data['name']);
$data = $this->guardDefaultColumn('helpdesk_teams', $data);
if ((bool) ($data['is_default'] ?? false)) {
Team::query()
->whereKeyNot($this->teamForm['id'])
->where('company_id', $data['company_id'])
->update(['is_default' => false]);
}
Team::query()->updateOrCreate(['id' => $this->teamForm['id']], $data);
$this->resetTeamForm();
session()->flash('success', 'Team saved.');
@@ -204,7 +238,7 @@ final class SettingsManager extends Component
public function editTeam(int $id): void
{
$this->teamForm = Team::query()->findOrFail($id)->only(['id', 'company_id', 'name', 'slug', 'color', 'description', 'archived_at']);
$this->teamForm = Team::query()->findOrFail($id)->only(['id', 'company_id', 'name', 'slug', 'color', 'description', 'is_default', 'archived_at']);
$this->section = 'teams';
}
@@ -216,7 +250,7 @@ final class SettingsManager extends Component
public function resetTeamForm(): void
{
$this->teamForm = ['id' => null, 'company_id' => null, 'name' => '', 'slug' => '', 'color' => 'indigo', 'description' => '', 'archived_at' => null];
$this->teamForm = ['id' => null, 'company_id' => null, 'name' => '', 'slug' => '', 'color' => 'indigo', 'description' => '', 'is_default' => false, 'archived_at' => null];
}
public function saveProject(): void
@@ -228,9 +262,18 @@ final class SettingsManager extends Component
'projectForm.slug' => ['nullable', 'string', 'max:180'],
'projectForm.color' => ['required', 'string', 'max:30'],
'projectForm.description' => ['nullable', 'string'],
'projectForm.is_default' => ['boolean'],
])['projectForm'];
$data['slug'] = $this->slug($data['slug'] ?: $data['title']);
$data = $this->guardDefaultColumn('helpdesk_projects', $data);
if ((bool) ($data['is_default'] ?? false)) {
Project::query()
->whereKeyNot($this->projectForm['id'])
->where('company_id', $data['company_id'])
->update(['is_default' => false]);
}
Project::query()->updateOrCreate(['id' => $this->projectForm['id']], $data);
$this->resetProjectForm();
session()->flash('success', 'Project saved.');
@@ -238,7 +281,7 @@ final class SettingsManager extends Component
public function editProject(int $id): void
{
$this->projectForm = Project::query()->findOrFail($id)->only(['id', 'company_id', 'team_id', 'title', 'slug', 'color', 'description', 'archived_at']);
$this->projectForm = Project::query()->findOrFail($id)->only(['id', 'company_id', 'team_id', 'title', 'slug', 'color', 'description', 'is_default', 'archived_at']);
$this->section = 'projects';
}
@@ -250,7 +293,7 @@ final class SettingsManager extends Component
public function resetProjectForm(): void
{
$this->projectForm = ['id' => null, 'company_id' => null, 'team_id' => null, 'title' => '', 'slug' => '', 'color' => 'blue', 'description' => '', 'archived_at' => null];
$this->projectForm = ['id' => null, 'company_id' => null, 'team_id' => null, 'title' => '', 'slug' => '', 'color' => 'blue', 'description' => '', 'is_default' => false, 'archived_at' => null];
}
@@ -614,15 +657,54 @@ final class SettingsManager extends Component
$this->checklistTemplateForm = ['id' => null, 'company_id' => null, 'category_id' => null, 'project_id' => null, 'name' => '', 'slug' => '', 'items_text' => '', 'is_active' => true];
}
public function saveDefaultHelpdeskCompany(): void
{
if (! Schema::hasTable('companies') || ! Schema::hasColumn('companies', 'is_default_helpdesk_company')) {
session()->flash('error', 'The default Helpdesk company column has not been migrated yet.');
return;
}
$this->validate([
'default_helpdesk_company_id' => ['nullable'],
]);
$companyId = filled($this->default_helpdesk_company_id)
? (int) $this->default_helpdesk_company_id
: null;
DB::table('companies')->update(['is_default_helpdesk_company' => false]);
if ($companyId !== null) {
DB::table('companies')
->where('id', $companyId)
->update(['is_default_helpdesk_company' => true]);
}
session()->flash('success', 'Default Helpdesk company saved.');
}
private function loadDefaultHelpdeskCompany(): void
{
if (! Schema::hasTable('companies') || ! Schema::hasColumn('companies', 'is_default_helpdesk_company')) {
return;
}
$this->default_helpdesk_company_id = DB::table('companies')
->where('is_default_helpdesk_company', true)
->value('id');
}
public function render(): View
{
return view('helpdesk::livewire.settings-manager', [
'companies' => $this->companies(),
'statuses' => Status::query()->withCount('tickets')->orderBy('sort_order')->orderBy('name')->get(),
'priorities' => Priority::query()->withCount('tickets')->orderBy('sort_order')->orderBy('name')->get(),
'categories' => Category::query()->withCount('tickets')->orderBy('name')->get(),
'teams' => Team::query()->withCount(['projects', 'tickets'])->orderBy('name')->get(),
'projects' => Project::query()->with(['team'])->withCount('tickets')->orderBy('title')->get(),
'statuses' => $this->orderedStatuses(),
'priorities' => $this->orderedPriorities(),
'categories' => $this->orderedCategories(),
'teams' => $this->orderedTeams(),
'projects' => $this->orderedProjects(),
'boards' => KanbanBoard::query()->with('statuses')->orderBy('sort_order')->orderBy('name')->get(),
'repositories' => GitRepository::query()->with(['project'])->withCount(['commits', 'webhookLogs'])->orderBy('name')->get(),
'workflowTemplates' => WorkflowTemplate::query()->orderBy('event')->orderBy('name')->get(),
@@ -635,6 +717,77 @@ final class SettingsManager extends Component
])->layout('layouts.app');
}
private function orderedStatuses(): mixed
{
return Status::query()
->withCount('tickets')
->when($this->hasDefaultColumn('helpdesk_statuses'), fn ($query) => $query->orderByDesc('is_default'))
->orderBy('sort_order')
->orderBy('name')
->get();
}
private function orderedPriorities(): mixed
{
return Priority::query()
->withCount('tickets')
->when($this->hasDefaultColumn('helpdesk_priorities'), fn ($query) => $query->orderByDesc('is_default'))
->orderBy('sort_order')
->orderBy('name')
->get();
}
private function orderedCategories(): mixed
{
return Category::query()
->withCount('tickets')
->when($this->hasDefaultColumn('helpdesk_categories'), fn ($query) => $query->orderByDesc('is_default'))
->orderBy('name')
->get();
}
private function orderedTeams(): mixed
{
return Team::query()
->withCount(['projects', 'tickets'])
->when($this->hasDefaultColumn('helpdesk_teams'), fn ($query) => $query->orderByDesc('is_default'))
->orderBy('name')
->get();
}
private function orderedProjects(): mixed
{
return Project::query()
->with(['team'])
->withCount('tickets')
->when($this->hasDefaultColumn('helpdesk_projects'), fn ($query) => $query->orderByDesc('is_default'))
->orderBy('title')
->get();
}
/**
* Remove is_default before save when the database has not been migrated yet.
*
* @param array<string, mixed> $data
* @return array<string, mixed>
*/
private function guardDefaultColumn(string $table, array $data): array
{
if (! $this->hasDefaultColumn($table)) {
unset($data['is_default']);
}
return $data;
}
private function hasDefaultColumn(string $table): bool
{
return Schema::hasTable($table) && Schema::hasColumn($table, 'is_default');
}
/** @return array<int, object> */
private function companies(): array
{
@@ -642,7 +795,13 @@ final class SettingsManager extends Component
return [];
}
return DB::table('companies')->select(['id', 'name'])->orderBy('name')->get()->all();
$columns = ['id', 'name'];
if (Schema::hasColumn('companies', 'is_default_helpdesk_company')) {
$columns[] = 'is_default_helpdesk_company';
}
return DB::table('companies')->select($columns)->orderBy('name')->get()->all();
}
private function slug(string $value): string
+216 -6
View File
@@ -16,7 +16,9 @@ use Modules\Helpdesk\Models\Category;
use Modules\Helpdesk\Models\Priority;
use Modules\Helpdesk\Models\Product;
use Modules\Helpdesk\Models\Project;
use Modules\Helpdesk\Models\Status;
use Modules\Helpdesk\Models\Team;
use Modules\Helpdesk\Models\Tag;
use Modules\Helpdesk\Services\TicketService;
final class TicketCreate extends Component
@@ -32,6 +34,9 @@ final class TicketCreate extends Component
#[Validate('required|string|min:10')]
public string $body = '';
#[Validate('nullable|integer|exists:helpdesk_statuses,id')]
public ?int $status_id = null;
#[Validate('required|integer|exists:helpdesk_priorities,id')]
public ?int $priority_id = null;
@@ -50,10 +55,23 @@ final class TicketCreate extends Component
#[Validate('nullable|date')]
public ?string $due_at = null;
/** @var array<int, int> */
#[Validate('array')]
public array $tag_ids = [];
/** @var array<int, TemporaryUploadedFile> */
#[Validate('array')]
public array $attachments = [];
/**
* Initialise configured Helpdesk defaults for a new ticket.
*/
public function mount(): void
{
$this->applyDefaults();
}
/**
* Store a new helpdesk ticket.
*/
@@ -63,34 +81,226 @@ final class TicketCreate extends Component
$this->validate([
'attachments.*' => 'file|max:'.config('helpdesk.max_upload_kb').'|mimes:'.implode(',', config('helpdesk.allowed_mimes')),
'tag_ids.*' => 'integer|exists:helpdesk_tags,id',
]);
$ticket = $tickets->create(Auth::user(), [
'company_id' => $this->company_id,
'subject' => $this->subject,
'body' => $this->body,
'status_id' => $this->status_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()->route('helpdesk.tickets.index');
return $this->redirectRoute('helpdesk.tickets.index', navigate: true);
}
public function render(): View
{
$this->applyDefaults();
return view('helpdesk::livewire.ticket-create', [
'priorities' => Priority::query()->orderBy('sort_order')->get(),
'categories' => Category::query()->orderBy('name')->get(),
'companies' => Schema::hasTable('companies') ? DB::table('companies')->select(['id', 'name'])->orderBy('name')->get() : collect(),
'teams' => Team::query()->whereNull('archived_at')->orderBy('name')->get(),
'projects' => Project::query()->whereNull('archived_at')->orderBy('title')->get(),
'statuses' => $this->statusOptions(),
'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 = $this->nullableInt(DB::table('companies')
->where('is_default_helpdesk_company', true)
->orderBy('name')
->value('id'));
}
if (blank($this->status_id) && $this->hasDefaultColumn('helpdesk_statuses')) {
$this->status_id = $this->nullableInt(Status::query()
->where('is_default', true)
->orderBy('sort_order')
->value('id'));
}
if (blank($this->priority_id) && $this->hasDefaultColumn('helpdesk_priorities')) {
$this->priority_id = $this->nullableInt(Priority::query()
->where('is_default', true)
->orderBy('sort_order')
->value('id'));
}
if (blank($this->category_id) && $this->hasDefaultColumn('helpdesk_categories')) {
$this->category_id = $this->nullableInt(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 = $this->nullableInt(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 = $this->nullableInt(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 status options with default records listed first.
*/
private function statusOptions(): mixed
{
return Status::query()
->when($this->hasDefaultColumn('helpdesk_statuses'), fn ($query) => $query->orderByDesc('is_default'))
->orderBy('sort_order')
->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()
->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();
}
/**
* Reload dependent defaults after the company changes.
*/
public function updatedCompanyId(mixed $value): void
{
$this->company_id = $this->nullableInt($value);
$this->category_id = null;
$this->team_id = null;
$this->project_id = null;
$this->applyDefaults();
}
/**
* Convert nullable database / Livewire values to nullable integers.
*/
private function nullableInt(mixed $value): ?int
{
if ($value === null || $value === '') {
return null;
}
return (int) $value;
}
/**
* 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');
}
}
+16 -2
View File
@@ -16,6 +16,7 @@ use Modules\Helpdesk\Models\Priority;
use Modules\Helpdesk\Models\Product;
use Modules\Helpdesk\Models\Project;
use Modules\Helpdesk\Models\Status;
use Modules\Helpdesk\Models\Tag;
use Modules\Helpdesk\Models\Team;
use Modules\Helpdesk\Models\Ticket;
use Modules\Helpdesk\Services\SlaService;
@@ -58,10 +59,15 @@ final class TicketEdit extends Component
#[Validate('nullable|date')]
public ?string $due_at = null;
/** @var array<int, int> */
#[Validate('array')]
public array $tag_ids = [];
public function mount(Ticket $ticket): void
{
$this->authorize('update', $ticket);
$this->ticket = $ticket->fresh(['status', 'priority', 'category', 'project', 'team', 'agent']) ?? $ticket;
$this->ticket = $ticket->fresh(['status', 'priority', 'category', 'project', 'team', 'agent', 'tags']) ?? $ticket;
$this->company_id = $this->ticket->company_id;
$this->subject = $this->ticket->subject;
@@ -74,6 +80,7 @@ final class TicketEdit extends Component
$this->team_id = $this->ticket->team_id;
$this->agent_id = $this->ticket->agent_id;
$this->due_at = $this->ticket->due_at?->format('Y-m-d');
$this->tag_ids = $this->ticket->tags->pluck('id')->map(fn ($id): int => (int) $id)->all();
}
public function save(WorkflowNotificationService $workflow, SlaService $sla): mixed
@@ -81,6 +88,10 @@ final class TicketEdit extends Component
$this->authorize('update', $this->ticket);
$this->validate();
$this->validate([
'tag_ids.*' => 'integer|exists:helpdesk_tags,id',
]);
$status = Status::query()->findOrFail($this->status_id);
$this->ticket->forceFill([
@@ -99,11 +110,13 @@ final class TicketEdit extends Component
'last_activity_at' => now(),
])->save();
$this->ticket->tags()->sync(array_values(array_unique(array_map('intval', $this->tag_ids))));
if ($status->is_closed) {
$sla->recordClosure($this->ticket);
}
$fresh = $this->ticket->fresh(['owner', 'agent', 'status', 'priority', 'category', 'project', 'team', 'product']) ?? $this->ticket;
$fresh = $this->ticket->fresh(['owner', 'agent', 'status', 'priority', 'category', 'project', 'team', 'product', 'tags']) ?? $this->ticket;
$workflow->dispatch($fresh, $status->is_closed ? 'ticket.closed' : 'ticket.updated', Auth::user());
session()->flash('success', 'Ticket ' . $fresh->reference . ' has been updated.');
@@ -121,6 +134,7 @@ final class TicketEdit extends Component
'teams' => Team::query()->whereNull('archived_at')->orderBy('name')->get(),
'projects' => Project::query()->whereNull('archived_at')->orderBy('title')->get(),
'products' => Product::query()->where('is_active', true)->orderBy('name')->get(),
'tags' => Tag::query()->orderBy('name')->get(),
'agents' => User::query()
->where(static function ($query): void {
$query->where('helpdesk_agent', true)->orWhere('helpdesk_admin', true);