From 0a9746fa77b652d5f23efe93c53ed78df5b3f550 Mon Sep 17 00:00:00 2001 From: Mark Gregory Date: Mon, 15 Jun 2026 17:55:36 +0100 Subject: [PATCH] updates --- ...efault_flags_to_helpdesk_lookup_tables.php | 6 +- ...esk_status_default_and_harden_defaults.php | 50 ++++ .../Seeders/HelpdeskDatabaseSeeder.php | 78 +++--- .../Docs/POST_INSTALL_DEFAULTS_AND_TAGS.md | 17 ++ Modules/Helpdesk/Livewire/SettingsManager.php | 213 ++++++++++++++--- Modules/Helpdesk/Livewire/TicketCreate.php | 222 +++++++++++++++++- Modules/Helpdesk/Livewire/TicketEdit.php | 18 +- Modules/Helpdesk/Models/Category.php | 4 +- Modules/Helpdesk/Models/Priority.php | 4 +- Modules/Helpdesk/Models/Project.php | 4 +- Modules/Helpdesk/Models/Status.php | 4 +- Modules/Helpdesk/Models/Team.php | 3 +- .../views/components/lookup-table.blade.php | 21 +- .../views/components/project-table.blade.php | 38 ++- .../views/components/team-table.blade.php | 38 ++- .../Helpdesk/Resources/views/index.blade.php | 2 +- .../views/livewire/kanban-board.blade.php | 2 +- .../views/livewire/settings-manager.blade.php | 48 ++++ .../views/livewire/ticket-create.blade.php | 76 +++++- .../views/livewire/ticket-edit.blade.php | 29 ++- .../views/livewire/ticket-index.blade.php | 2 +- .../views/partials/select2-loader.blade.php | 24 +- Modules/Helpdesk/Services/TicketService.php | 19 +- Modules/Helpdesk/changelog.yaml | 26 ++ Modules/Helpdesk/module.json | 2 +- 25 files changed, 844 insertions(+), 106 deletions(-) create mode 100644 Modules/Helpdesk/Database/Migrations/2026_06_15_151500_add_helpdesk_status_default_and_harden_defaults.php create mode 100644 Modules/Helpdesk/Docs/POST_INSTALL_DEFAULTS_AND_TAGS.md diff --git a/Modules/Helpdesk/Database/Migrations/2026_06_15_143824_default_flags_to_helpdesk_lookup_tables.php b/Modules/Helpdesk/Database/Migrations/2026_06_15_143824_default_flags_to_helpdesk_lookup_tables.php index f0d7a100..fa90d1ae 100644 --- a/Modules/Helpdesk/Database/Migrations/2026_06_15_143824_default_flags_to_helpdesk_lookup_tables.php +++ b/Modules/Helpdesk/Database/Migrations/2026_06_15_143824_default_flags_to_helpdesk_lookup_tables.php @@ -12,6 +12,7 @@ return new class extends Migration public function up(): void { foreach ([ + 'helpdesk_statuses', 'helpdesk_priorities', 'helpdesk_categories', 'helpdesk_projects', @@ -19,9 +20,7 @@ return new class extends Migration ] as $table) { if (! Schema::hasColumn($table, 'is_default')) { Schema::table($table, function (Blueprint $table): void { - $table->boolean('is_default') - ->default(false) - ->after('is_active'); + $table->boolean('is_default')->default(false); }); } } @@ -33,6 +32,7 @@ return new class extends Migration public function down(): void { foreach ([ + 'helpdesk_statuses', 'helpdesk_priorities', 'helpdesk_categories', 'helpdesk_projects', diff --git a/Modules/Helpdesk/Database/Migrations/2026_06_15_151500_add_helpdesk_status_default_and_harden_defaults.php b/Modules/Helpdesk/Database/Migrations/2026_06_15_151500_add_helpdesk_status_default_and_harden_defaults.php new file mode 100644 index 00000000..8e76ecc8 --- /dev/null +++ b/Modules/Helpdesk/Database/Migrations/2026_06_15_151500_add_helpdesk_status_default_and_harden_defaults.php @@ -0,0 +1,50 @@ +boolean('is_default')->default(false); + }); + } + } + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + foreach ([ + 'helpdesk_statuses', + 'helpdesk_priorities', + 'helpdesk_categories', + 'helpdesk_projects', + 'helpdesk_teams', + ] as $tableName) { + if (Schema::hasTable($tableName) && Schema::hasColumn($tableName, 'is_default')) { + Schema::table($tableName, static function (Blueprint $table): void { + $table->dropColumn('is_default'); + }); + } + } + } +}; diff --git a/Modules/Helpdesk/Database/Seeders/HelpdeskDatabaseSeeder.php b/Modules/Helpdesk/Database/Seeders/HelpdeskDatabaseSeeder.php index 3c880c53..b88e7929 100644 --- a/Modules/Helpdesk/Database/Seeders/HelpdeskDatabaseSeeder.php +++ b/Modules/Helpdesk/Database/Seeders/HelpdeskDatabaseSeeder.php @@ -5,7 +5,7 @@ declare(strict_types=1); namespace Modules\Helpdesk\Database\Seeders; use Illuminate\Database\Seeder; -use Illuminate\Support\Facades\DB; +use Illuminate\Support\Facades\Schema; use Illuminate\Support\Str; use Modules\Helpdesk\Models\CannedResponse; use Modules\Helpdesk\Models\Category; @@ -24,8 +24,6 @@ final class HelpdeskDatabaseSeeder extends Seeder { /** * Seed the Helpdesk module lookup data and default workflow templates. - * - * @return void */ public function run(): void { @@ -40,18 +38,19 @@ final class HelpdeskDatabaseSeeder extends Seeder /** * Seed ticket workflow statuses. - * - * @return void */ private function seedStatuses(): void { + + Status::query()->where('is_default', true)->update(['is_default' => false]); + foreach ([ - ['name' => 'New', 'slug' => 'new', 'color' => 'sky', 'sort_order' => 5, 'is_closed' => false], - ['name' => 'Open', 'slug' => 'open', 'color' => 'blue', 'sort_order' => 10, 'is_closed' => false], - ['name' => 'In Progress', 'slug' => 'in-progress', 'color' => 'amber', 'sort_order' => 20, 'is_closed' => false], - ['name' => 'Stalled', 'slug' => 'stalled', 'color' => 'rose', 'sort_order' => 30, 'is_closed' => false], - ['name' => 'On-Hold', 'slug' => 'on-hold', 'color' => 'violet', 'sort_order' => 40, 'is_closed' => false], - ['name' => 'Closed', 'slug' => 'closed', 'color' => 'emerald', 'sort_order' => 90, 'is_closed' => true], + ['name' => 'New', 'slug' => 'new', 'color' => 'sky', 'sort_order' => 5, 'is_closed' => false, 'is_default' => true], + ['name' => 'Open', 'slug' => 'open', 'color' => 'blue', 'sort_order' => 10, 'is_closed' => false, 'is_default' => false], + ['name' => 'In Progress', 'slug' => 'in-progress', 'color' => 'amber', 'sort_order' => 20, 'is_closed' => false, 'is_default' => false], + ['name' => 'Stalled', 'slug' => 'stalled', 'color' => 'rose', 'sort_order' => 30, 'is_closed' => false, 'is_default' => false], + ['name' => 'On-Hold', 'slug' => 'on-hold', 'color' => 'violet', 'sort_order' => 40, 'is_closed' => false, 'is_default' => false], + ['name' => 'Closed', 'slug' => 'closed', 'color' => 'emerald', 'sort_order' => 90, 'is_closed' => true, 'is_default' => false], ] as $row) { Status::query()->updateOrCreate(['slug' => $row['slug']], $row); } @@ -59,15 +58,15 @@ final class HelpdeskDatabaseSeeder extends Seeder /** * Seed ticket priorities. - * - * @return void */ private function seedPriorities(): void { + + Priority::query()->where('is_default', true)->update(['is_default' => false]); foreach ([ - ['name' => 'High', 'slug' => 'high', 'color' => 'red', 'sort_order' => 10], - ['name' => 'Medium', 'slug' => 'medium', 'color' => 'amber', 'sort_order' => 20], - ['name' => 'Low', 'slug' => 'low', 'color' => 'emerald', 'sort_order' => 30], + ['name' => 'High', 'slug' => 'high', 'color' => 'red', 'sort_order' => 10, 'is_default' => false], + ['name' => 'Medium', 'slug' => 'medium', 'color' => 'amber', 'sort_order' => 20, 'is_default' => true], + ['name' => 'Low', 'slug' => 'low', 'color' => 'emerald', 'sort_order' => 30, 'is_default' => false], ] as $row) { Priority::query()->updateOrCreate(['slug' => $row['slug']], $row); } @@ -75,16 +74,18 @@ final class HelpdeskDatabaseSeeder extends Seeder /** * Seed ticket categories. - * - * @return void */ private function seedCategories(): void { + Category::query() + ->whereNull('company_id') + ->where('is_default', true) + ->update(['is_default' => false]); foreach ([ - ['name' => 'Development', 'color' => 'indigo', 'description' => 'Software changes, enhancements and development work.'], - ['name' => 'Systems Deployment', 'color' => 'cyan', 'description' => 'Deployment, release, infrastructure and environment requests.'], - ['name' => 'Concepts', 'color' => 'purple', 'description' => 'Ideas, concepts, design discussion and future improvements.'], - ['name' => 'Bug', 'color' => 'rose', 'description' => 'Defects, regressions and incorrect behaviour.'], + ['name' => 'Development', 'color' => 'indigo', 'description' => 'Software changes, enhancements and development work.', 'is_default' => true], + ['name' => 'Systems Deployment', 'color' => 'cyan', 'description' => 'Deployment, release, infrastructure and environment requests.', 'is_default' => false], + ['name' => 'Concepts', 'color' => 'purple', 'description' => 'Ideas, concepts, design discussion and future improvements.', 'is_default' => false], + ['name' => 'Bug', 'color' => 'rose', 'description' => 'Defects, regressions and incorrect behaviour.', 'is_default' => false], ] as $row) { Category::query()->updateOrCreate( ['company_id' => null, 'slug' => Str::slug($row['name'])], @@ -95,11 +96,20 @@ final class HelpdeskDatabaseSeeder extends Seeder /** * Seed the default support team and a default project. - * - * @return void */ private function seedTeamsAndProjects(): void { + + Team::query() + ->whereNull('company_id') + ->where('is_default', true) + ->update(['is_default' => false]); + + Project::query() + ->whereNull('company_id') + ->where('is_default', true) + ->update(['is_default' => false]); + $team = Team::query()->updateOrCreate( ['company_id' => null, 'slug' => 'support'], [ @@ -108,6 +118,7 @@ final class HelpdeskDatabaseSeeder extends Seeder 'slug' => 'support', 'color' => 'blue', 'description' => 'Default support team for triage, ownership and ticket routing.', + 'is_default' => true, ] ); @@ -120,14 +131,13 @@ final class HelpdeskDatabaseSeeder extends Seeder 'slug' => 'support-desk', 'color' => 'indigo', 'description' => 'Default project for operational helpdesk tickets.', + 'is_default' => true, ] ); } /** * Seed the default configurable Kanban board. - * - * @return void */ private function seedKanbanBoards(): void { @@ -160,8 +170,6 @@ final class HelpdeskDatabaseSeeder extends Seeder /** * Seed default professional workflow email templates. - * - * @return void */ private function seedWorkflowTemplates(): void { @@ -235,7 +243,17 @@ final class HelpdeskDatabaseSeeder extends Seeder ['name' => 'Escalated', 'slug' => 'escalated', 'color' => 'red'], ['name' => 'Waiting on customer', 'slug' => 'waiting-on-customer', 'color' => 'amber'], ] as $tag) { - Tag::query()->updateOrCreate(['company_id' => null, 'slug' => $tag['slug']], $tag + ['company_id' => null]); + Tag::query()->updateOrCreate( + ['company_id' => null, 'slug' => $tag['slug']], + $tag + ['company_id' => null] + ); + } + + if (Schema::hasColumn('helpdesk_tags', 'is_default')) { + Tag::query() + ->whereNull('company_id') + ->where('is_default', true) + ->update(['is_default' => false]); } Product::query()->updateOrCreate( @@ -252,6 +270,4 @@ final class HelpdeskDatabaseSeeder extends Seeder ] ); } - } - diff --git a/Modules/Helpdesk/Docs/POST_INSTALL_DEFAULTS_AND_TAGS.md b/Modules/Helpdesk/Docs/POST_INSTALL_DEFAULTS_AND_TAGS.md new file mode 100644 index 00000000..c64b104c --- /dev/null +++ b/Modules/Helpdesk/Docs/POST_INSTALL_DEFAULTS_AND_TAGS.md @@ -0,0 +1,17 @@ +# Helpdesk defaults and tags update + +After copying this module update into the application, run: + +```bash +php artisan migrate +php artisan optimize:clear +php artisan view:clear +php artisan livewire:discover +php artisan octane:reload +``` + +If Octane is not running, skip `octane:reload`. + +The Helpdesk settings page now includes a Defaults tab for the default company. Statuses, priorities, categories, teams and projects have an `is_default` flag. Ticket creation reads those defaults in `TicketCreate::mount()` and `TicketService` reads the default status before falling back to the configured `new` status slug. + +Tickets support tags on create/edit through `tag_ids`, synced to `helpdesk_ticket_tag`. diff --git a/Modules/Helpdesk/Livewire/SettingsManager.php b/Modules/Helpdesk/Livewire/SettingsManager.php index 278c5352..9341af39 100644 --- a/Modules/Helpdesk/Livewire/SettingsManager.php +++ b/Modules/Helpdesk/Livewire/SettingsManager.php @@ -28,20 +28,22 @@ final class SettingsManager extends Component { public string $section = 'statuses'; - /** @var array */ - 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 */ - 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 */ - 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 */ - 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 */ - 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 */ + public array $projectForm = ['id' => null, 'company_id' => null, 'team_id' => null, 'title' => '', 'slug' => '', 'color' => 'blue', 'description' => '', 'is_default' => false, 'archived_at' => null]; /** @var array */ 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 $data + * @return array + */ + 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 */ 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 diff --git a/Modules/Helpdesk/Livewire/TicketCreate.php b/Modules/Helpdesk/Livewire/TicketCreate.php index 58e9ab74..df3065d3 100644 --- a/Modules/Helpdesk/Livewire/TicketCreate.php +++ b/Modules/Helpdesk/Livewire/TicketCreate.php @@ -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 */ + #[Validate('array')] + public array $tag_ids = []; + /** @var array */ #[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'); + } } diff --git a/Modules/Helpdesk/Livewire/TicketEdit.php b/Modules/Helpdesk/Livewire/TicketEdit.php index ab4c16a2..a8f1e9a8 100644 --- a/Modules/Helpdesk/Livewire/TicketEdit.php +++ b/Modules/Helpdesk/Livewire/TicketEdit.php @@ -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 */ + #[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); diff --git a/Modules/Helpdesk/Models/Category.php b/Modules/Helpdesk/Models/Category.php index 53e0c774..a9e5c632 100644 --- a/Modules/Helpdesk/Models/Category.php +++ b/Modules/Helpdesk/Models/Category.php @@ -17,10 +17,10 @@ final class Category extends Model protected $table = 'helpdesk_categories'; /** @var list */ - protected $fillable = ['company_id', 'name', 'slug', 'color', 'description']; + protected $fillable = ['company_id', 'name', 'slug', 'color', 'description', 'is_default']; /** @var array */ - protected $casts = ['company_id' => 'integer']; + protected $casts = ['company_id' => 'integer', 'is_default' => 'boolean']; /** @return HasMany */ public function tickets(): HasMany diff --git a/Modules/Helpdesk/Models/Priority.php b/Modules/Helpdesk/Models/Priority.php index caf3efc0..a1e2e451 100644 --- a/Modules/Helpdesk/Models/Priority.php +++ b/Modules/Helpdesk/Models/Priority.php @@ -15,10 +15,10 @@ final class Priority extends Model protected $table = 'helpdesk_priorities'; /** @var list */ - protected $fillable = ['name', 'slug', 'color', 'sort_order']; + protected $fillable = ['name', 'slug', 'color', 'sort_order', 'is_default']; /** @var array */ - protected $casts = ['sort_order' => 'integer']; + protected $casts = ['sort_order' => 'integer', 'is_default' => 'boolean']; /** @return HasMany */ public function tickets(): HasMany diff --git a/Modules/Helpdesk/Models/Project.php b/Modules/Helpdesk/Models/Project.php index eea3f35a..14e7c3ab 100644 --- a/Modules/Helpdesk/Models/Project.php +++ b/Modules/Helpdesk/Models/Project.php @@ -18,10 +18,10 @@ final class Project extends Model protected $table = 'helpdesk_projects'; /** @var list */ - protected $fillable = ['company_id', 'team_id', 'title', 'slug', 'color', 'description', 'archived_at']; + protected $fillable = ['company_id', 'team_id', 'title', 'slug', 'color', 'description', 'archived_at', 'is_default']; /** @var array */ - protected $casts = ['company_id' => 'integer', 'team_id' => 'integer', 'archived_at' => 'datetime']; + protected $casts = ['company_id' => 'integer', 'team_id' => 'integer', 'archived_at' => 'datetime', 'is_default' => 'boolean']; /** @return BelongsTo */ public function team(): BelongsTo diff --git a/Modules/Helpdesk/Models/Status.php b/Modules/Helpdesk/Models/Status.php index d91006df..6cdcb4c0 100644 --- a/Modules/Helpdesk/Models/Status.php +++ b/Modules/Helpdesk/Models/Status.php @@ -22,10 +22,10 @@ final class Status extends Model protected $table = 'helpdesk_statuses'; /** @var list */ - protected $fillable = ['name', 'slug', 'color', 'sort_order', 'is_closed']; + protected $fillable = ['name', 'slug', 'color', 'sort_order', 'is_closed', 'is_default']; /** @var array */ - protected $casts = ['is_closed' => 'boolean', 'sort_order' => 'integer']; + protected $casts = ['is_closed' => 'boolean', 'is_default' => 'boolean', 'sort_order' => 'integer']; /** @return HasMany */ public function tickets(): HasMany diff --git a/Modules/Helpdesk/Models/Team.php b/Modules/Helpdesk/Models/Team.php index c85c3bc3..9763b248 100644 --- a/Modules/Helpdesk/Models/Team.php +++ b/Modules/Helpdesk/Models/Team.php @@ -17,12 +17,13 @@ final class Team extends Model protected $table = 'helpdesk_teams'; /** @var list */ - protected $fillable = ['company_id', 'name', 'slug', 'color', 'description', 'archived_at']; + protected $fillable = ['company_id', 'name', 'slug', 'color', 'description', 'archived_at', 'is_default']; /** @var array */ protected $casts = [ 'company_id' => 'integer', 'archived_at' => 'datetime', + 'is_default' => 'boolean', ]; /** @return HasMany */ diff --git a/Modules/Helpdesk/Resources/views/components/lookup-table.blade.php b/Modules/Helpdesk/Resources/views/components/lookup-table.blade.php index 2c652685..26189af7 100644 --- a/Modules/Helpdesk/Resources/views/components/lookup-table.blade.php +++ b/Modules/Helpdesk/Resources/views/components/lookup-table.blade.php @@ -1,7 +1,14 @@
- + + + + + + + + @forelse ($items as $item) @@ -9,11 +16,19 @@ + - + @empty - + @endforelse
NameSlugColourTicketsActions
NameSlugColourDefaultTicketsActions
{{ $item->name }} {{ $item->slug }} {{ $item->color }} + + {{ ($item->is_default ?? false) ? 'Default' : 'No' }} + + {{ $item->tickets_count ?? 0 }}EditDelete + Edit + Delete +
No records yet.
No records yet.
diff --git a/Modules/Helpdesk/Resources/views/components/project-table.blade.php b/Modules/Helpdesk/Resources/views/components/project-table.blade.php index 402eb92e..92b7d688 100644 --- a/Modules/Helpdesk/Resources/views/components/project-table.blade.php +++ b/Modules/Helpdesk/Resources/views/components/project-table.blade.php @@ -1 +1,37 @@ -
@forelse ($items as $item)@empty@endforelse
ProjectTeamSlugColourTicketsActions
{{ $item->title }}{{ $item->team?->name ?? 'No team' }}{{ $item->slug }}{{ $item->color }}{{ $item->tickets_count }}EditDelete
No projects yet.
+
+ + + + + + + + + + + + + + @forelse ($items as $item) + + + + + + + + + + @empty + + @endforelse + +
ProjectTeamSlugColourDefaultTicketsActions
{{ $item->title }}{{ $item->team?->name ?? 'No team' }}{{ $item->slug }}{{ $item->color }} + + {{ ($item->is_default ?? false) ? 'Default' : 'No' }} + + {{ $item->tickets_count }} + Edit + Delete +
No projects yet.
+
diff --git a/Modules/Helpdesk/Resources/views/components/team-table.blade.php b/Modules/Helpdesk/Resources/views/components/team-table.blade.php index 6dc60692..61e73281 100644 --- a/Modules/Helpdesk/Resources/views/components/team-table.blade.php +++ b/Modules/Helpdesk/Resources/views/components/team-table.blade.php @@ -1 +1,37 @@ -
@forelse ($items as $item)@empty@endforelse
TeamSlugColourProjectsTicketsActions
{{ $item->name }}{{ $item->slug }}{{ $item->color }}{{ $item->projects_count }}{{ $item->tickets_count }}EditDelete
No teams yet.
+
+ + + + + + + + + + + + + + @forelse ($items as $item) + + + + + + + + + + @empty + + @endforelse + +
TeamSlugColourDefaultProjectsTicketsActions
{{ $item->name }}{{ $item->slug }}{{ $item->color }} + + {{ ($item->is_default ?? false) ? 'Default' : 'No' }} + + {{ $item->projects_count }}{{ $item->tickets_count }} + Edit + Delete +
No teams yet.
+
diff --git a/Modules/Helpdesk/Resources/views/index.blade.php b/Modules/Helpdesk/Resources/views/index.blade.php index 73a260dd..a16e7152 100644 --- a/Modules/Helpdesk/Resources/views/index.blade.php +++ b/Modules/Helpdesk/Resources/views/index.blade.php @@ -7,7 +7,7 @@

Helpdesk dashboard

Track open workload, closed tickets, response performance, project activity, priorities, Git commits and workflow communication from one professional support workspace.

- New ticket + New ticket Active tickets Kanban Settings diff --git a/Modules/Helpdesk/Resources/views/livewire/kanban-board.blade.php b/Modules/Helpdesk/Resources/views/livewire/kanban-board.blade.php index 660c2941..38ce8f27 100644 --- a/Modules/Helpdesk/Resources/views/livewire/kanban-board.blade.php +++ b/Modules/Helpdesk/Resources/views/livewire/kanban-board.blade.php @@ -9,7 +9,7 @@
- New ticket + New ticket Configure boards List view
diff --git a/Modules/Helpdesk/Resources/views/livewire/settings-manager.blade.php b/Modules/Helpdesk/Resources/views/livewire/settings-manager.blade.php index 83297772..0f16ca81 100644 --- a/Modules/Helpdesk/Resources/views/livewire/settings-manager.blade.php +++ b/Modules/Helpdesk/Resources/views/livewire/settings-manager.blade.php @@ -53,6 +53,7 @@ 'checklists' => ['label' => 'Checks', 'count' => $checklistTemplates->count()], 'tags' => ['label' => 'Tags', 'count' => $tags->count()], 'products' => ['label' => 'Products', 'count' => $products->count()], + 'defaults' => ['label' => 'Defaults', 'count' => 1], ] as $key => $item)
+ + @if ($section === 'defaults') + + +
+ + + @foreach ($companies as $company) + + @endforeach + + +
+ Save default company +
+
+
+ +
+

Lookup defaults

+

Set the default Status, Priority, Category, Team and Project from their own settings sections. Only one default per company/global scope is kept for each lookup type.

+
+
+ @endif + @if ($section === 'statuses') @@ -70,6 +98,10 @@ +
Save statusReset
@@ -85,6 +117,10 @@ +
Save priorityReset
@@ -101,6 +137,10 @@
+
Save categoryReset
@@ -117,6 +157,10 @@
+
Save teamReset
@@ -134,6 +178,10 @@
+
Save projectReset
diff --git a/Modules/Helpdesk/Resources/views/livewire/ticket-create.blade.php b/Modules/Helpdesk/Resources/views/livewire/ticket-create.blade.php index b208c704..532f233b 100644 --- a/Modules/Helpdesk/Resources/views/livewire/ticket-create.blade.php +++ b/Modules/Helpdesk/Resources/views/livewire/ticket-create.blade.php @@ -21,6 +21,41 @@ font-weight: 800 !important; z-index: 5; } + + .helpdesk-ticket-form input, + .helpdesk-ticket-form textarea, + .helpdesk-ticket-form select { + background-color: #09090b !important; + color: #f4f4f5 !important; + caret-color: #f4f4f5; + } + + .helpdesk-ticket-form input:focus, + .helpdesk-ticket-form textarea:focus, + .helpdesk-ticket-form select:focus { + background-color: #09090b !important; + color: #f4f4f5 !important; + } + + .helpdesk-ticket-form input:-webkit-autofill, + .helpdesk-ticket-form input:-webkit-autofill:hover, + .helpdesk-ticket-form input:-webkit-autofill:focus, + .helpdesk-ticket-form textarea:-webkit-autofill, + .helpdesk-ticket-form textarea:-webkit-autofill:hover, + .helpdesk-ticket-form textarea:-webkit-autofill:focus, + .helpdesk-ticket-form select:-webkit-autofill, + .helpdesk-ticket-form select:-webkit-autofill:hover, + .helpdesk-ticket-form select:-webkit-autofill:focus { + -webkit-text-fill-color: #f4f4f5 !important; + box-shadow: 0 0 0 1000px #09090b inset !important; + transition: background-color 9999s ease-in-out 0s; + } + + .helpdesk-ticket-form [wire\:loading], + .helpdesk-ticket-form [wire\:loading\.delay], + .helpdesk-ticket-form [wire\:loading\.remove] { + color: inherit; + } @include('helpdesk::partials.page-header', [ @@ -29,7 +64,7 @@ 'subtitle' => 'Raise a new support request with priority, product/service, project, category, team and full details.', ]) -
+

Ticket details

@@ -45,13 +80,15 @@ Subject * - + autocomplete="off" + /> @error('subject')

{{ $message }}

@@ -67,7 +104,7 @@ @@ -85,7 +122,7 @@ @@ -102,7 +139,7 @@ @@ -119,7 +156,7 @@ @@ -136,7 +173,7 @@ @@ -145,6 +182,25 @@ @enderror
+ +
+ + + + + @error('tag_ids') +

{{ $message }}

+ @enderror + + @error('tag_ids.*') +

{{ $message }}

+ @enderror +
+
diff --git a/Modules/Helpdesk/Resources/views/livewire/ticket-edit.blade.php b/Modules/Helpdesk/Resources/views/livewire/ticket-edit.blade.php index cdd06e58..9c04d8c6 100644 --- a/Modules/Helpdesk/Resources/views/livewire/ticket-edit.blade.php +++ b/Modules/Helpdesk/Resources/views/livewire/ticket-edit.blade.php @@ -64,13 +64,15 @@ Subject * - + autocomplete="off" + /> @error('subject')

{{ $message }}

@@ -194,6 +196,25 @@ @enderror
+ +
+ + + + + @error('tag_ids') +

{{ $message }}

+ @enderror + + @error('tag_ids.*') +

{{ $message }}

+ @enderror +
+
diff --git a/Modules/Helpdesk/Resources/views/livewire/ticket-index.blade.php b/Modules/Helpdesk/Resources/views/livewire/ticket-index.blade.php index 812fcae4..ae1249f4 100644 --- a/Modules/Helpdesk/Resources/views/livewire/ticket-index.blade.php +++ b/Modules/Helpdesk/Resources/views/livewire/ticket-index.blade.php @@ -9,7 +9,7 @@
Dashboard - New ticket + New ticket
diff --git a/Modules/Helpdesk/Resources/views/partials/select2-loader.blade.php b/Modules/Helpdesk/Resources/views/partials/select2-loader.blade.php index f3ac0950..2a30ad75 100644 --- a/Modules/Helpdesk/Resources/views/partials/select2-loader.blade.php +++ b/Modules/Helpdesk/Resources/views/partials/select2-loader.blade.php @@ -1,7 +1,8 @@ @once