diff --git a/Modules/Documentation/Resources/views/categories/index.blade.php b/Modules/Documentation/Resources/views/categories/index.blade.php index 00bce3c5..bc9e3e05 100644 --- a/Modules/Documentation/Resources/views/categories/index.blade.php +++ b/Modules/Documentation/Resources/views/categories/index.blade.php @@ -16,7 +16,7 @@
Categories - Create category + New category
@@ -39,7 +39,7 @@ - + @empty diff --git a/Modules/Documentation/changelog.yaml b/Modules/Documentation/changelog.yaml index c17c3e18..21e5457b 100644 --- a/Modules/Documentation/changelog.yaml +++ b/Modules/Documentation/changelog.yaml @@ -1,3 +1,11 @@ +- version: 1.6.5 + date: '2026-06-16' + changes: + - Reworked Helpdesk action styling so header, support, settings, table, save, reset, edit and delete buttons use reliable coloured native styling. + - Replaced settings section card dynamic Tailwind colour classes with inline styles so card colours always render. + - Improved Kanban board dropdown styling. + - Hardened ticket create inputs and Select2 controls to prevent white flashes during save. + 1.6.1: date: 2026-06-15 changes: @@ -22,10 +30,3 @@ releases: date: 2026-06-15 changes: - Applied explicit coloured Documentation action buttons across pages and forms. - -1.6.3: - date: 2026-06-15 - changes: - - Coloured Documentation register/category edit actions as blue buttons. - - Standardised Create document and Create category actions as green buttons. - - Preserved existing Documentation CRUD, Markdown editor and attachment behaviour. diff --git a/Modules/Documentation/module.json b/Modules/Documentation/module.json index 00f69c29..21d1c09a 100644 --- a/Modules/Documentation/module.json +++ b/Modules/Documentation/module.json @@ -1,7 +1,7 @@ { "name": "Documentation", "alias": "documentation", - "version": "1.6.3", + "version": "1.6.5", "description": "Professional document management and knowledge-base module with WMS shell styling, categories, CRUD, publishing, Markdown editing/import support, code blocks, attachments, and Support navigation integration.", "keywords": [ "documentation", diff --git a/Modules/Helpdesk/Database/Migrations/2026_06_16_090000_add_single_board_assignment_and_owner_to_helpdesk.php b/Modules/Helpdesk/Database/Migrations/2026_06_16_090000_add_single_board_assignment_and_owner_to_helpdesk.php new file mode 100644 index 00000000..a79892f1 --- /dev/null +++ b/Modules/Helpdesk/Database/Migrations/2026_06_16_090000_add_single_board_assignment_and_owner_to_helpdesk.php @@ -0,0 +1,76 @@ +foreignId('owner_id') + ->nullable() + ->after('description') + ->constrained('users') + ->nullOnDelete(); + + $table->index('owner_id'); + }); + } + + if (Schema::hasTable('helpdesk_tickets') && ! Schema::hasColumn('helpdesk_tickets', 'board_id')) { + Schema::table('helpdesk_tickets', static function (Blueprint $table): void { + $table->foreignId('board_id') + ->nullable() + ->after('status_id') + ->constrained('helpdesk_kanban_boards') + ->nullOnDelete(); + + $table->index(['board_id', 'status_id']); + }); + } + + if (Schema::hasTable('helpdesk_tickets') && Schema::hasTable('helpdesk_kanban_boards') && Schema::hasColumn('helpdesk_tickets', 'board_id')) { + $defaultBoardId = DB::table('helpdesk_kanban_boards') + ->where('is_default', true) + ->orderBy('sort_order') + ->value('id') ?: DB::table('helpdesk_kanban_boards')->orderBy('sort_order')->value('id'); + + if ($defaultBoardId !== null) { + DB::table('helpdesk_tickets') + ->whereNull('board_id') + ->update(['board_id' => $defaultBoardId]); + } + } + } + + /** + * Reverse the migration. + */ + public function down(): void + { + if (Schema::hasTable('helpdesk_tickets') && Schema::hasColumn('helpdesk_tickets', 'board_id')) { + Schema::table('helpdesk_tickets', static function (Blueprint $table): void { + $table->dropForeign(['board_id']); + $table->dropIndex(['board_id', 'status_id']); + $table->dropColumn('board_id'); + }); + } + + if (Schema::hasTable('helpdesk_kanban_boards') && Schema::hasColumn('helpdesk_kanban_boards', 'owner_id')) { + Schema::table('helpdesk_kanban_boards', static function (Blueprint $table): void { + $table->dropForeign(['owner_id']); + $table->dropIndex(['owner_id']); + $table->dropColumn('owner_id'); + }); + } + } +}; diff --git a/Modules/Helpdesk/Livewire/KanbanBoard.php b/Modules/Helpdesk/Livewire/KanbanBoard.php index 30aceb38..cba72483 100644 --- a/Modules/Helpdesk/Livewire/KanbanBoard.php +++ b/Modules/Helpdesk/Livewire/KanbanBoard.php @@ -6,6 +6,7 @@ namespace Modules\Helpdesk\Livewire; use Illuminate\Contracts\View\View; use Illuminate\Database\Eloquent\Collection; +use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Auth; use Livewire\Component; use Modules\Helpdesk\Models\KanbanBoard as KanbanBoardModel; @@ -26,16 +27,23 @@ final class KanbanBoard extends Component } /** - * Move a ticket to another workflow status. + * Move a ticket to another workflow status on the active board. */ public function moveTicket(int|string $ticketId, int|string $statusId, WorkflowNotificationService $workflow): void { $ticket = Ticket::query()->visibleTo(Auth::user())->findOrFail((int) $ticketId); $status = Status::query()->findOrFail((int) $statusId); + $board = $this->activeBoard(); + $oldStatusName = $ticket->status?->name ?? 'previous status'; $this->authorize('update', $ticket); + if ($board !== null && ! $board->statuses()->whereKey($status->id)->exists()) { + $this->addStatusToBoard($board->id, $status->id); + } + $ticket->forceFill([ + 'board_id' => $board?->id, 'status_id' => $status->id, 'completed_at' => $status->is_closed ? now() : null, 'last_activity_at' => now(), @@ -47,7 +55,44 @@ final class KanbanBoard extends Component Auth::user() ); - session()->flash('success', 'Ticket moved to ' . $status->name . '.'); + $message = 'Ticket '.$ticket->reference.' moved from '.$oldStatusName.' to '.$status->name.'.'; + session()->flash('success', $message); + $this->dispatch('wms:toast', type: 'success', title: 'Ticket moved', message: $message); + } + + /** + * Move a ticket from its current board to another board. A ticket can only belong to one board. + */ + public function moveTicketToBoard(int|string $ticketId, int|string|null $boardId, WorkflowNotificationService $workflow): void + { + $ticket = Ticket::query()->with('board')->visibleTo(Auth::user())->findOrFail((int) $ticketId); + $board = KanbanBoardModel::query()->with(['statuses', 'owner:id,name,email'])->findOrFail((int) $boardId); + $oldBoardName = $ticket->board?->name ?? 'No board'; + + $this->authorize('update', $ticket); + + if (! $board->statuses->contains('id', $ticket->status_id)) { + $firstStatus = $board->statuses->first(); + + if ($firstStatus !== null) { + $ticket->status_id = $firstStatus->id; + $ticket->completed_at = $firstStatus->is_closed ? now() : null; + } else { + $this->addStatusToBoard($board->id, (int) $ticket->status_id); + } + } + + $ticket->forceFill([ + 'board_id' => $board->id, + 'last_activity_at' => now(), + ])->save(); + + $fresh = $ticket->fresh(['owner', 'agent', 'status', 'priority', 'category', 'project', 'team', 'board']) ?? $ticket; + $workflow->dispatch($fresh, 'ticket.updated', Auth::user()); + + $message = 'Ticket '.$fresh->reference.' moved from '.$oldBoardName.' to '.$board->name.'.'; + session()->flash('success', $message); + $this->dispatch('wms:toast', type: 'success', title: 'Board changed', message: $message); } /** @@ -56,7 +101,7 @@ final class KanbanBoard extends Component public function render(): View { $boards = KanbanBoardModel::query() - ->with('statuses') + ->with(['statuses', 'owner:id,name,email']) ->orderBy('sort_order') ->orderBy('name') ->get(); @@ -77,19 +122,48 @@ final class KanbanBoard extends Component } /** - * Get visible tickets grouped by status. + * Get visible tickets grouped by status for the active board only. * * @return array> */ private function ticketsByStatus(): array { + $board = $this->activeBoard(); + return Ticket::query() - ->with(['owner', 'agent', 'status', 'priority', 'category', 'project', 'team', 'product', 'tags', 'checklistItems']) + ->with(['owner', 'agent', 'status', 'priority', 'category', 'project', 'team', 'product', 'tags', 'checklistItems', 'board']) ->visibleTo(Auth::user()) + ->when($board !== null, fn ($query) => $query->where('board_id', $board->id)) ->latest('last_activity_at') ->limit(300) ->get() ->groupBy('status_id') ->all(); } + + /** + * Resolve the current board from the selected board ID. + */ + private function activeBoard(): ?KanbanBoardModel + { + if ($this->board_id !== null) { + return KanbanBoardModel::query()->find((int) $this->board_id); + } + + return KanbanBoardModel::query() + ->where('is_default', true) + ->orderBy('sort_order') + ->first() ?: KanbanBoardModel::query()->orderBy('sort_order')->first(); + } + + /** + * Attach a missing status to a board when a move requires it. + */ + private function addStatusToBoard(int $boardId, int $statusId): void + { + DB::table('helpdesk_kanban_board_statuses')->updateOrInsert( + ['board_id' => $boardId, 'status_id' => $statusId], + ['sort_order' => 999, 'updated_at' => now(), 'created_at' => now()] + ); + } } diff --git a/Modules/Helpdesk/Livewire/SettingsManager.php b/Modules/Helpdesk/Livewire/SettingsManager.php index 9341af39..e9d5d3c2 100644 --- a/Modules/Helpdesk/Livewire/SettingsManager.php +++ b/Modules/Helpdesk/Livewire/SettingsManager.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace Modules\Helpdesk\Livewire; +use App\Models\User; use Illuminate\Contracts\View\View; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Schema; @@ -46,7 +47,7 @@ final class SettingsManager extends Component 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' => []]; + public array $boardForm = ['id' => null, 'name' => '', 'slug' => '', 'description' => '', 'owner_id' => null, 'is_default' => false, 'sort_order' => 0, 'status_ids' => []]; /** @var array */ public array $repositoryForm = ['id' => null, 'company_id' => null, 'project_id' => null, 'name' => '', 'provider' => 'git', 'remote_url' => '', 'default_branch' => 'main', 'webhook_secret' => '']; @@ -80,7 +81,7 @@ final class SettingsManager extends Component $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->boardForm += ['id' => null, 'name' => '', 'slug' => '', 'description' => '', 'owner_id' => null, '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]; $this->cannedForm += ['id' => null, 'company_id' => null, 'category_id' => null, 'team_id' => null, 'name' => '', 'slug' => '', 'visibility' => 'agents', 'subject' => '', 'body' => '', 'is_active' => true, 'sort_order' => 0]; @@ -303,6 +304,7 @@ final class SettingsManager extends Component 'boardForm.name' => ['required', 'string', 'max:160'], 'boardForm.slug' => ['nullable', 'string', 'max:180'], 'boardForm.description' => ['nullable', 'string'], + 'boardForm.owner_id' => ['nullable', 'integer', 'exists:users,id'], 'boardForm.is_default' => ['boolean'], 'boardForm.sort_order' => ['required', 'integer', 'min:0'], 'boardForm.status_ids' => ['array'], @@ -312,6 +314,7 @@ final class SettingsManager extends Component $statusIds = collect($data['status_ids'] ?? [])->map(static fn ($id): int => (int) $id)->filter()->values(); unset($data['status_ids']); + $data['owner_id'] = blank($data['owner_id'] ?? null) ? null : (int) $data['owner_id']; $data['slug'] = $this->slug($data['slug'] ?: $data['name']); if ((bool) ($data['is_default'] ?? false)) { @@ -340,6 +343,7 @@ final class SettingsManager extends Component 'name' => $board->name, 'slug' => $board->slug, 'description' => $board->description, + 'owner_id' => $board->owner_id, 'is_default' => $board->is_default, 'sort_order' => $board->sort_order, 'status_ids' => $board->statuses->pluck('id')->map(static fn ($id): int => (int) $id)->all(), @@ -356,7 +360,7 @@ final class SettingsManager extends Component public function resetBoardForm(): void { - $this->boardForm = ['id' => null, 'name' => '', 'slug' => '', 'description' => '', 'is_default' => false, 'sort_order' => 0, 'status_ids' => []]; + $this->boardForm = ['id' => null, 'name' => '', 'slug' => '', 'description' => '', 'owner_id' => null, 'is_default' => false, 'sort_order' => 0, 'status_ids' => []]; } public function saveRepository(): void @@ -705,7 +709,8 @@ final class SettingsManager extends Component 'categories' => $this->orderedCategories(), 'teams' => $this->orderedTeams(), 'projects' => $this->orderedProjects(), - 'boards' => KanbanBoard::query()->with('statuses')->orderBy('sort_order')->orderBy('name')->get(), + 'boardOwners' => User::query()->where(static function ($query): void { $query->where('helpdesk_agent', true)->orWhere('helpdesk_admin', true); })->orderBy('name')->get(['id', 'name', 'email']), + 'boards' => KanbanBoard::query()->with(['statuses', 'owner:id,name,email'])->withCount('tickets')->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(), 'cannedResponses' => CannedResponse::query()->with(['category','team'])->orderBy('sort_order')->orderBy('name')->get(), diff --git a/Modules/Helpdesk/Livewire/TicketCreate.php b/Modules/Helpdesk/Livewire/TicketCreate.php index 5f1f0f75..8d9f6cf1 100644 --- a/Modules/Helpdesk/Livewire/TicketCreate.php +++ b/Modules/Helpdesk/Livewire/TicketCreate.php @@ -13,6 +13,7 @@ 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; @@ -33,6 +34,9 @@ final class TicketCreate extends Component #[Validate('required|string|min:10')] public string $body = ''; + #[Validate('nullable|integer|exists:helpdesk_kanban_boards,id')] + public ?int $board_id = null; + #[Validate('required|integer|exists:helpdesk_priorities,id')] public ?int $priority_id = null; @@ -84,6 +88,7 @@ final class TicketCreate extends Component '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, @@ -101,6 +106,7 @@ final class TicketCreate extends Component public function render(): View { return view('helpdesk::livewire.ticket-create', [ + 'boards' => $this->boardOptions(), 'priorities' => $this->priorityOptions(), 'categories' => $this->categoryOptions(), 'companies' => $this->companyOptions(), @@ -123,6 +129,14 @@ final class TicketCreate extends Component ->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) @@ -175,6 +189,20 @@ final class TicketCreate extends Component } + + /** + * 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. */ diff --git a/Modules/Helpdesk/Livewire/TicketEdit.php b/Modules/Helpdesk/Livewire/TicketEdit.php index a8f1e9a8..6bcf0719 100644 --- a/Modules/Helpdesk/Livewire/TicketEdit.php +++ b/Modules/Helpdesk/Livewire/TicketEdit.php @@ -12,6 +12,7 @@ use Illuminate\Support\Facades\Schema; use Livewire\Attributes\Validate; use Livewire\Component; 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; @@ -38,6 +39,9 @@ final class TicketEdit extends Component #[Validate('required|integer|exists:helpdesk_statuses,id')] public ?int $status_id = null; + #[Validate('nullable|integer|exists:helpdesk_kanban_boards,id')] + public ?int $board_id = null; + #[Validate('required|integer|exists:helpdesk_priorities,id')] public ?int $priority_id = null; @@ -73,6 +77,7 @@ final class TicketEdit extends Component $this->subject = $this->ticket->subject; $this->body = $this->ticket->body; $this->status_id = $this->ticket->status_id; + $this->board_id = $this->ticket->board_id; $this->priority_id = $this->ticket->priority_id; $this->category_id = $this->ticket->category_id; $this->project_id = $this->ticket->project_id; @@ -99,6 +104,7 @@ final class TicketEdit extends Component 'subject' => $this->subject, 'body' => $this->body, 'status_id' => $status->id, + 'board_id' => $this->board_id, 'priority_id' => $this->priority_id, 'category_id' => $this->category_id, 'project_id' => $this->project_id, @@ -127,6 +133,7 @@ final class TicketEdit extends Component public function render(): View { return view('helpdesk::livewire.ticket-edit', [ + 'boards' => KanbanBoard::query()->with('owner:id,name,email')->orderByDesc('is_default')->orderBy('sort_order')->orderBy('name')->get(), 'statuses' => Status::query()->orderBy('sort_order')->orderBy('name')->get(), 'priorities' => Priority::query()->orderBy('sort_order')->orderBy('name')->get(), 'categories' => Category::query()->orderBy('name')->get(), diff --git a/Modules/Helpdesk/Models/KanbanBoard.php b/Modules/Helpdesk/Models/KanbanBoard.php index ab77bc4c..5c29c470 100644 --- a/Modules/Helpdesk/Models/KanbanBoard.php +++ b/Modules/Helpdesk/Models/KanbanBoard.php @@ -6,7 +6,10 @@ namespace Modules\Helpdesk\Models; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; +use App\Models\User; +use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsToMany; +use Illuminate\Database\Eloquent\Relations\HasMany; /** * Configurable Helpdesk Kanban board. @@ -22,16 +25,30 @@ final class KanbanBoard extends Model 'name', 'slug', 'description', + 'owner_id', 'is_default', 'sort_order', ]; /** @var array */ protected $casts = [ + 'owner_id' => 'integer', 'is_default' => 'boolean', 'sort_order' => 'integer', ]; + /** @return BelongsTo */ + public function owner(): BelongsTo + { + return $this->belongsTo(User::class, 'owner_id'); + } + + /** @return HasMany */ + public function tickets(): HasMany + { + return $this->hasMany(Ticket::class, 'board_id'); + } + /** @return BelongsToMany */ public function statuses(): BelongsToMany { diff --git a/Modules/Helpdesk/Models/Ticket.php b/Modules/Helpdesk/Models/Ticket.php index 1efa5bb8..71e3cd1c 100644 --- a/Modules/Helpdesk/Models/Ticket.php +++ b/Modules/Helpdesk/Models/Ticket.php @@ -26,7 +26,7 @@ final class Ticket extends Model /** @var list */ protected $fillable = [ - 'reference', 'company_id', 'user_id', 'agent_id', 'status_id', 'priority_id', 'category_id', 'project_id', 'team_id', + 'reference', 'company_id', 'user_id', 'agent_id', 'status_id', 'board_id', 'priority_id', 'category_id', 'project_id', 'team_id', 'subject', 'body', 'due_at', 'completed_at', 'last_activity_at', 'archived_at', 'sla_policy_id', 'product_id', 'first_response_at', 'sla_first_response_due_at', 'sla_resolution_due_at', 'sla_first_response_breached_at', 'sla_resolution_breached_at', 'public_replies_count', 'time_spent_minutes', @@ -36,6 +36,7 @@ final class Ticket extends Model protected $casts = [ 'company_id' => 'integer', 'team_id' => 'integer', + 'board_id' => 'integer', 'due_at' => 'datetime', 'completed_at' => 'datetime', 'last_activity_at' => 'datetime', @@ -69,6 +70,12 @@ final class Ticket extends Model return $this->belongsTo(Status::class, 'status_id'); } + /** @return BelongsTo */ + public function board(): BelongsTo + { + return $this->belongsTo(KanbanBoard::class, 'board_id'); + } + /** @return BelongsTo */ public function priority(): BelongsTo { diff --git a/Modules/Helpdesk/Resources/views/components/lookup-table.blade.php b/Modules/Helpdesk/Resources/views/components/lookup-table.blade.php index a20153c4..d547eeb1 100644 --- a/Modules/Helpdesk/Resources/views/components/lookup-table.blade.php +++ b/Modules/Helpdesk/Resources/views/components/lookup-table.blade.php @@ -24,21 +24,8 @@ diff --git a/Modules/Helpdesk/Resources/views/components/project-table.blade.php b/Modules/Helpdesk/Resources/views/components/project-table.blade.php index 7cfea02d..a51487d5 100644 --- a/Modules/Helpdesk/Resources/views/components/project-table.blade.php +++ b/Modules/Helpdesk/Resources/views/components/project-table.blade.php @@ -1,14 +1,7 @@
{{ $category->sort_order }} {{ $category->is_active ? 'Active' : 'Inactive' }} - Edit + Edit
@csrf @method('DELETE') diff --git a/Modules/Documentation/Resources/views/documents/index.blade.php b/Modules/Documentation/Resources/views/documents/index.blade.php index 74489c36..c6738b19 100644 --- a/Modules/Documentation/Resources/views/documents/index.blade.php +++ b/Modules/Documentation/Resources/views/documents/index.blade.php @@ -38,7 +38,7 @@
Document register - Create document + New document
@@ -80,7 +80,7 @@
{{ ucfirst($document->status) }} {{ $document->version }} {{ $document->updated_at?->format('d M Y H:i') }}EditEdit
No documents found.
{{ $item->tickets_count ?? 0 }}
- - - + +
- - - - - - - - + @forelse ($items as $item) @@ -18,12 +11,7 @@ - + @empty diff --git a/Modules/Helpdesk/Resources/views/components/repository-table.blade.php b/Modules/Helpdesk/Resources/views/components/repository-table.blade.php index 9a9deea7..efd1ba5c 100644 --- a/Modules/Helpdesk/Resources/views/components/repository-table.blade.php +++ b/Modules/Helpdesk/Resources/views/components/repository-table.blade.php @@ -1,32 +1,19 @@
ProjectCompanyTeamDefaultTicketsActions
ProjectCompanyTeamDefaultTicketsActions
{{ $item->team?->name ?? 'No team' }} {{ ($item->is_default ?? false) ? 'Default' : 'No' }} {{ $item->tickets_count ?? 0 }} -
- - -
-
No projects yet.
- - - - - - - - + @forelse ($items as $item) - + - - + @empty - + @endforelse
RepositoryProviderProjectBranchCommitsActions
NameProviderProjectCommitsActions
{{ $item->name }}{{ $item->provider }}{{ strtoupper($item->provider) }} {{ $item->project?->title ?? 'No project' }}{{ $item->default_branch }} {{ $item->commits_count ?? 0 }} -
- - -
-
No repositories yet.
No repositories yet.
diff --git a/Modules/Helpdesk/Resources/views/components/team-table.blade.php b/Modules/Helpdesk/Resources/views/components/team-table.blade.php index 4c75d476..65d1bdb8 100644 --- a/Modules/Helpdesk/Resources/views/components/team-table.blade.php +++ b/Modules/Helpdesk/Resources/views/components/team-table.blade.php @@ -1,32 +1,19 @@
- - - - - - - - + @forelse ($items as $item) - - + @empty - + @endforelse
TeamCompanyColourDefaultTicketsActions
TeamCompanyDefaultTicketsActions
{{ $item->name }} {{ $item->company?->name ?? 'Global' }}{{ $item->color }} {{ ($item->is_default ?? false) ? 'Default' : 'No' }} {{ $item->tickets_count ?? 0 }} -
- - -
-
No teams yet.
No teams yet.
diff --git a/Modules/Helpdesk/Resources/views/index.blade.php b/Modules/Helpdesk/Resources/views/index.blade.php index 946e4fa2..19300061 100644 --- a/Modules/Helpdesk/Resources/views/index.blade.php +++ b/Modules/Helpdesk/Resources/views/index.blade.php @@ -1,4 +1,142 @@ -
+ + + +
@@ -7,19 +145,19 @@

Helpdesk dashboard

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

- + + New Ticket - + Active tickets - + Kanban - + Settings @@ -44,11 +182,11 @@ diff --git a/Modules/Helpdesk/Resources/views/livewire/dashboard-widget.blade.php b/Modules/Helpdesk/Resources/views/livewire/dashboard-widget.blade.php index 5d434425..6de2ee77 100644 --- a/Modules/Helpdesk/Resources/views/livewire/dashboard-widget.blade.php +++ b/Modules/Helpdesk/Resources/views/livewire/dashboard-widget.blade.php @@ -46,7 +46,7 @@

Recent commits

- Git setup + Git setup
@forelse ($recentCommits as $commit) @@ -67,7 +67,7 @@

Recent tickets

Latest support activity.

- View all + View all
@forelse ($recentTickets as $ticket) diff --git a/Modules/Helpdesk/Resources/views/livewire/kanban-board.blade.php b/Modules/Helpdesk/Resources/views/livewire/kanban-board.blade.php index 0ec8ee95..b7d59ed0 100644 --- a/Modules/Helpdesk/Resources/views/livewire/kanban-board.blade.php +++ b/Modules/Helpdesk/Resources/views/livewire/kanban-board.blade.php @@ -1,4 +1,142 @@ -
+ + + +
@@ -9,15 +147,15 @@
- + + New Ticket - + Configure boards - + List view @@ -36,9 +174,9 @@

{{ $board?->description ?: 'Use settings to configure which statuses appear as draggable columns.' }}

- @foreach ($boards as $availableBoard) - + @endforeach
@@ -98,6 +236,18 @@ @endif
+ +
+ + +
@if ($ticket->tags->isNotEmpty())
@foreach ($ticket->tags as $tag) @@ -118,7 +268,7 @@
Open @can('update', $ticket) - Edit + Edit @endcan
diff --git a/Modules/Helpdesk/Resources/views/livewire/settings-manager.blade.php b/Modules/Helpdesk/Resources/views/livewire/settings-manager.blade.php index ea6b6424..7cd25371 100644 --- a/Modules/Helpdesk/Resources/views/livewire/settings-manager.blade.php +++ b/Modules/Helpdesk/Resources/views/livewire/settings-manager.blade.php @@ -1,3 +1,141 @@ + + + @php /** * Keep the settings view safe during rolling module upgrades or Livewire navigate @@ -20,7 +158,7 @@ $products = collect($products ?? []); @endphp -
+
@@ -31,19 +169,19 @@
- + Helpdesk - + Tickets - + + New Ticket - + Kanban @@ -70,45 +208,30 @@ 'defaults' => ['label' => 'Defaults', 'count' => 1], ] as $key => $item) @php - $sectionTone = match ($key) { - 'statuses' => 'border-sky-400/70 bg-sky-600/15 text-sky-900 hover:bg-sky-600/25 dark:border-sky-500/50 dark:bg-sky-950/50 dark:text-sky-100 dark:hover:bg-sky-900/70', - 'priorities' => 'border-red-400/70 bg-red-600/15 text-red-900 hover:bg-red-600/25 dark:border-red-500/50 dark:bg-red-950/50 dark:text-red-100 dark:hover:bg-red-900/70', - 'categories' => 'border-indigo-400/70 bg-indigo-600/15 text-indigo-900 hover:bg-indigo-600/25 dark:border-indigo-500/50 dark:bg-indigo-950/50 dark:text-indigo-100 dark:hover:bg-indigo-900/70', - 'teams' => 'border-blue-400/70 bg-blue-600/15 text-blue-900 hover:bg-blue-600/25 dark:border-blue-500/50 dark:bg-blue-950/50 dark:text-blue-100 dark:hover:bg-blue-900/70', - 'projects' => 'border-cyan-400/70 bg-cyan-600/15 text-cyan-900 hover:bg-cyan-600/25 dark:border-cyan-500/50 dark:bg-cyan-950/50 dark:text-cyan-100 dark:hover:bg-cyan-900/70', - 'boards' => 'border-amber-400/70 bg-amber-600/15 text-amber-900 hover:bg-amber-600/25 dark:border-amber-500/50 dark:bg-amber-950/50 dark:text-amber-100 dark:hover:bg-amber-900/70', - 'git' => 'border-slate-400/70 bg-slate-600/15 text-slate-900 hover:bg-slate-600/25 dark:border-slate-500/50 dark:bg-slate-950/50 dark:text-slate-100 dark:hover:bg-slate-900/70', - 'workflow' => 'border-emerald-400/70 bg-emerald-600/15 text-emerald-900 hover:bg-emerald-600/25 dark:border-emerald-500/50 dark:bg-emerald-950/50 dark:text-emerald-100 dark:hover:bg-emerald-900/70', - 'canned' => 'border-teal-400/70 bg-teal-600/15 text-teal-900 hover:bg-teal-600/25 dark:border-teal-500/50 dark:bg-teal-950/50 dark:text-teal-100 dark:hover:bg-teal-900/70', - 'sla' => 'border-orange-400/70 bg-orange-600/15 text-orange-900 hover:bg-orange-600/25 dark:border-orange-500/50 dark:bg-orange-950/50 dark:text-orange-100 dark:hover:bg-orange-900/70', - 'automation' => 'border-purple-400/70 bg-purple-600/15 text-purple-900 hover:bg-purple-600/25 dark:border-purple-500/50 dark:bg-purple-950/50 dark:text-purple-100 dark:hover:bg-purple-900/70', - 'checklists' => 'border-lime-400/70 bg-lime-600/15 text-lime-900 hover:bg-lime-600/25 dark:border-lime-500/50 dark:bg-lime-950/50 dark:text-lime-100 dark:hover:bg-lime-900/70', - 'tags' => 'border-pink-400/70 bg-pink-600/15 text-pink-900 hover:bg-pink-600/25 dark:border-pink-500/50 dark:bg-pink-950/50 dark:text-pink-100 dark:hover:bg-pink-900/70', - 'products' => 'border-violet-400/70 bg-violet-600/15 text-violet-900 hover:bg-violet-600/25 dark:border-violet-500/50 dark:bg-violet-950/50 dark:text-violet-100 dark:hover:bg-violet-900/70', - 'defaults' => 'border-green-400/70 bg-green-600/15 text-green-900 hover:bg-green-600/25 dark:border-green-500/50 dark:bg-green-950/50 dark:text-green-100 dark:hover:bg-green-900/70', - default => 'border-zinc-400/70 bg-zinc-600/15 text-zinc-900 hover:bg-zinc-600/25 dark:border-zinc-500/50 dark:bg-zinc-950/50 dark:text-zinc-100 dark:hover:bg-zinc-900/70', + $sectionPalette = match ($key) { + 'statuses' => ['border' => '#38bdf8', 'bg' => 'rgba(14, 165, 233, .22)', 'active' => '#0284c7'], + 'priorities' => ['border' => '#f87171', 'bg' => 'rgba(239, 68, 68, .22)', 'active' => '#dc2626'], + 'categories' => ['border' => '#818cf8', 'bg' => 'rgba(99, 102, 241, .22)', 'active' => '#4f46e5'], + 'teams' => ['border' => '#60a5fa', 'bg' => 'rgba(37, 99, 235, .22)', 'active' => '#2563eb'], + 'projects' => ['border' => '#22d3ee', 'bg' => 'rgba(8, 145, 178, .22)', 'active' => '#0891b2'], + 'boards' => ['border' => '#fbbf24', 'bg' => 'rgba(245, 158, 11, .24)', 'active' => '#f59e0b'], + 'git' => ['border' => '#94a3b8', 'bg' => 'rgba(71, 85, 105, .28)', 'active' => '#475569'], + 'workflow' => ['border' => '#34d399', 'bg' => 'rgba(5, 150, 105, .22)', 'active' => '#059669'], + 'canned' => ['border' => '#2dd4bf', 'bg' => 'rgba(13, 148, 136, .22)', 'active' => '#0d9488'], + 'sla' => ['border' => '#fb923c', 'bg' => 'rgba(234, 88, 12, .22)', 'active' => '#ea580c'], + 'automation' => ['border' => '#c084fc', 'bg' => 'rgba(147, 51, 234, .22)', 'active' => '#9333ea'], + 'checklists' => ['border' => '#a3e635', 'bg' => 'rgba(101, 163, 13, .22)', 'active' => '#65a30d'], + 'tags' => ['border' => '#f472b6', 'bg' => 'rgba(219, 39, 119, .22)', 'active' => '#db2777'], + 'products' => ['border' => '#a78bfa', 'bg' => 'rgba(124, 58, 237, .22)', 'active' => '#7c3aed'], + 'defaults' => ['border' => '#4ade80', 'bg' => 'rgba(22, 163, 74, .22)', 'active' => '#16a34a'], + default => ['border' => '#71717a', 'bg' => 'rgba(63, 63, 70, .22)', 'active' => '#3f3f46'], }; - $sectionActiveTone = match ($key) { - 'statuses' => 'border-sky-300 bg-sky-600 text-white shadow-sky-950/20', - 'priorities' => 'border-red-300 bg-red-600 text-white shadow-red-950/20', - 'categories' => 'border-indigo-300 bg-indigo-600 text-white shadow-indigo-950/20', - 'teams' => 'border-blue-300 bg-blue-600 text-white shadow-blue-950/20', - 'projects' => 'border-cyan-300 bg-cyan-600 text-white shadow-cyan-950/20', - 'boards' => 'border-amber-300 bg-amber-500 text-white shadow-amber-950/20', - 'git' => 'border-slate-300 bg-slate-700 text-white shadow-slate-950/20', - 'workflow' => 'border-emerald-300 bg-emerald-600 text-white shadow-emerald-950/20', - 'canned' => 'border-teal-300 bg-teal-600 text-white shadow-teal-950/20', - 'sla' => 'border-orange-300 bg-orange-600 text-white shadow-orange-950/20', - 'automation' => 'border-purple-300 bg-purple-600 text-white shadow-purple-950/20', - 'checklists' => 'border-lime-300 bg-lime-600 text-white shadow-lime-950/20', - 'tags' => 'border-pink-300 bg-pink-600 text-white shadow-pink-950/20', - 'products' => 'border-violet-300 bg-violet-600 text-white shadow-violet-950/20', - 'defaults' => 'border-green-300 bg-green-600 text-white shadow-green-950/20', - default => 'border-blue-300 bg-blue-600 text-white shadow-blue-950/20', - }; + $sectionStyle = $section === $key + ? 'border-color: '.$sectionPalette['border'].'; background-color: '.$sectionPalette['active'].'; color: #ffffff; box-shadow: 0 16px 36px rgba(0,0,0,.32);' + : 'border-color: '.$sectionPalette['border'].'; background-color: '.$sectionPalette['bg'].'; color: #ffffff; box-shadow: 0 10px 24px rgba(0,0,0,.20);'; @endphp - @@ -130,7 +253,7 @@
- + Save default company
@@ -155,7 +278,7 @@ Default status for new tickets -
+
Save statusReset
@@ -174,7 +297,7 @@ Default for new tickets -
+
Save priorityReset
@@ -194,7 +317,7 @@ Default category for new tickets -
+
Save categoryReset
@@ -214,7 +337,7 @@ Default team for new tickets -
+
Save teamReset
@@ -235,7 +358,7 @@ Default project for new tickets -
+
Save projectReset
@@ -246,17 +369,23 @@ @if ($section === 'boards') -
+ + + + @foreach ($boardOwners as $owner) + + @endforeach +
-
+
@foreach ($statuses as $status) @@ -268,9 +397,9 @@ @endforeach
-
- - +
+ Save board + Reset
@@ -280,7 +409,9 @@ Board + Owner Columns + Tickets Default Actions @@ -292,6 +423,7 @@
{{ $boardItem->name }}
{{ $boardItem->slug }}
+ {{ $boardItem->owner?->name ?? 'Unassigned' }}
@foreach ($boardItem->statuses as $status) @@ -299,14 +431,15 @@ @endforeach
+ {{ $boardItem->tickets_count ?? 0 }} {{ $boardItem->is_default ? 'Default' : 'No' }} - - + Edit + Delete @empty - No Kanban boards yet. + No Kanban boards yet. @endforelse @@ -334,12 +467,12 @@ -
+
-
- - +
+ Save repository + Reset
@@ -368,18 +501,18 @@ -
+
-
+
-
+
-
- - +
+ Save workflow message + Reset
@@ -403,8 +536,8 @@ {{ $template->to_role }} {{ $template->is_active ? 'Active' : 'Inactive' }} - - + Edit + Delete @empty @@ -427,9 +560,9 @@ @foreach ($categories as $category)@endforeach @foreach ($teams as $team)@endforeach -
-
-
+
+
+
Save canned responseReset
@@ -437,7 +570,7 @@

{{ $response->name }}

{{ $response->category?->name ?? 'Any category' }} · {{ $response->team?->name ?? 'Any team' }}

{{ $response->is_active ? 'Active' : 'Inactive' }}

{{ strip_tags($response->body) }}

-
+
EditDelete
@empty
No canned responses yet.
@@ -459,10 +592,10 @@ -
+
Save SLA policyReset
-
@forelse ($slaPolicies as $policy)@empty@endforelse
PolicyTargetsApplies toActions
{{ $policy->name }} @if($policy->is_default)Default@endif{{ $policy->first_response_minutes }}m response · {{ $policy->resolution_minutes }}m close{{ $policy->priority?->name ?? 'Any priority' }} · {{ $policy->category?->name ?? 'Any category' }}
No SLA policies yet.
+
@forelse ($slaPolicies as $policy)@empty@endforelse
PolicyTargetsApplies toActions
{{ $policy->name }} @if($policy->is_default)Default@endif{{ $policy->first_response_minutes }}m response · {{ $policy->resolution_minutes }}m close{{ $policy->priority?->name ?? 'Any priority' }} · {{ $policy->category?->name ?? 'Any category' }}Edit Delete
No SLA policies yet.
@endif @@ -482,31 +615,31 @@ @foreach ($projects as $project)@endforeach @foreach ($statuses as $status)@endforeach -
+
Save automation ruleReset
-
@forelse ($automationRules as $rule)

{{ $rule->name }}

Sender: {{ $rule->match_sender ?: 'Any' }} · Subject: {{ $rule->match_subject ?: 'Any' }}

@empty
No automation rules yet.
@endforelse
+
@forelse ($automationRules as $rule)

{{ $rule->name }}

Sender: {{ $rule->match_sender ?: 'Any' }} · Subject: {{ $rule->match_subject ?: 'Any' }}

EditDelete
@empty
No automation rules yet.
@endforelse
@endif @if ($section === 'checklists') -
@foreach ($categories as $category)@endforeach@foreach ($projects as $project)@endforeach
-
@forelse ($checklistTemplates as $template)

{{ $template->name }}

{{ count($template->items ?? []) }} items

@empty
No checklist templates yet.
@endforelse
+
@foreach ($categories as $category)@endforeach@foreach ($projects as $project)@endforeach
Save checklistReset
+
@forelse ($checklistTemplates as $template)

{{ $template->name }}

{{ count($template->items ?? []) }} items

EditDelete
@empty
No checklist templates yet.
@endforelse
@endif @if ($section === 'tags') -
-
@forelse ($tags as $tag)
{{ $tag->name }}
@empty
No tags yet.
@endforelse
+
Save tagReset
+
@forelse ($tags as $tag)
{{ $tag->name }}
EditDelete
@empty
No tags yet.
@endforelse
@endif @if ($section === 'products') -
-
@forelse ($products as $product)

{{ $product->name }}

{{ ucfirst($product->type) }} · {{ $product->version ?: 'No version' }} · {{ $product->tickets_count }} tickets

@empty
No products or services yet.
@endforelse
+
Save product/serviceReset
+
@forelse ($products as $product)

{{ $product->name }}

{{ ucfirst($product->type) }} · {{ $product->version ?: 'No version' }} · {{ $product->tickets_count }} tickets

EditDelete
@empty
No products or services yet.
@endforelse
@endif diff --git a/Modules/Helpdesk/Resources/views/livewire/ticket-create.blade.php b/Modules/Helpdesk/Resources/views/livewire/ticket-create.blade.php index b8a50a81..76b23c46 100644 --- a/Modules/Helpdesk/Resources/views/livewire/ticket-create.blade.php +++ b/Modules/Helpdesk/Resources/views/livewire/ticket-create.blade.php @@ -1,4 +1,142 @@ -
+ + + +
+ +
@@ -8,11 +146,11 @@

Search, triage and manage support requests across companies, projects, teams, priorities and statuses.

- + Dashboard - + + New Ticket diff --git a/Modules/Helpdesk/Resources/views/livewire/ticket-show.blade.php b/Modules/Helpdesk/Resources/views/livewire/ticket-show.blade.php index 3c8f3d65..4df18c75 100644 --- a/Modules/Helpdesk/Resources/views/livewire/ticket-show.blade.php +++ b/Modules/Helpdesk/Resources/views/livewire/ticket-show.blade.php @@ -1,4 +1,142 @@ -
+ + + +
@@ -16,7 +154,7 @@ Back @can('update', $ticket) - + Edit @endcan @@ -68,7 +206,7 @@ @endforeach
- + Insert response
@endif @@ -88,10 +226,10 @@
- +
@@ -185,14 +323,14 @@ @endforeach - + Update status
@foreach ($priorities as $priority) @endforeach - + Update priority
@endcan diff --git a/Modules/Helpdesk/Resources/views/partials/comment-thread.blade.php b/Modules/Helpdesk/Resources/views/partials/comment-thread.blade.php index 9620c005..a050a725 100644 --- a/Modules/Helpdesk/Resources/views/partials/comment-thread.blade.php +++ b/Modules/Helpdesk/Resources/views/partials/comment-thread.blade.php @@ -39,7 +39,7 @@ @endif
- + Reply
@if (($reply_parent_id ?? null) === $comment->id) @@ -47,8 +47,8 @@
- - + Cancel + Post reply
@endif diff --git a/Modules/Helpdesk/Services/TicketService.php b/Modules/Helpdesk/Services/TicketService.php index f14cb90c..b4a569a6 100644 --- a/Modules/Helpdesk/Services/TicketService.php +++ b/Modules/Helpdesk/Services/TicketService.php @@ -9,6 +9,7 @@ use Illuminate\Http\UploadedFile; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Schema; use Modules\Helpdesk\Models\Comment; +use Modules\Helpdesk\Models\KanbanBoard; use Modules\Helpdesk\Models\Status; use Modules\Helpdesk\Models\Ticket; @@ -52,6 +53,14 @@ final class TicketService ]); } + $boardId = $data['board_id'] ?? null; + if (blank($boardId) && Schema::hasTable('helpdesk_kanban_boards')) { + $boardId = KanbanBoard::query() + ->where('is_default', true) + ->orderBy('sort_order') + ->value('id') ?: KanbanBoard::query()->orderBy('sort_order')->value('id'); + } + /** @var Ticket $ticket */ $ticket = Ticket::query()->create([ 'reference' => $this->references->next(), @@ -59,6 +68,7 @@ final class TicketService 'user_id' => $user->id, 'agent_id' => $this->assignment->selectAgentId($data['category_id'] ?? null), 'status_id' => $status->id, + 'board_id' => $boardId, 'priority_id' => $data['priority_id'], 'category_id' => $data['category_id'] ?? null, 'project_id' => $data['project_id'] ?? null, diff --git a/Modules/Helpdesk/Tests/Concerns/BuildsHelpdeskFixtures.php b/Modules/Helpdesk/Tests/Concerns/BuildsHelpdeskFixtures.php index 84648846..c1835b2b 100644 --- a/Modules/Helpdesk/Tests/Concerns/BuildsHelpdeskFixtures.php +++ b/Modules/Helpdesk/Tests/Concerns/BuildsHelpdeskFixtures.php @@ -10,6 +10,7 @@ use Illuminate\Support\Facades\Hash; use Illuminate\Support\Facades\Schema; use Modules\Helpdesk\Models\Category; use Modules\Helpdesk\Models\GitRepository; +use Modules\Helpdesk\Models\KanbanBoard; use Modules\Helpdesk\Models\Priority; use Modules\Helpdesk\Models\Project; use Modules\Helpdesk\Models\Status; @@ -137,6 +138,7 @@ trait BuildsHelpdeskFixtures 'user_id' => $owner->id, 'agent_id' => $agent?->id, 'status_id' => $this->ticketStatus()->id, + 'board_id' => $attributes['board_id'] ?? null, 'priority_id' => $this->priority()->id, 'category_id' => null, 'project_id' => null, @@ -156,8 +158,10 @@ trait BuildsHelpdeskFixtures $category = $this->category(['name' => 'Support', 'slug' => 'support']); $team = $this->team(['name' => 'Support Team', 'slug' => 'support-team']); $project = $this->project(['title' => 'WMS Core', 'slug' => 'wms-core', 'team_id' => $team->id]); + $board = KanbanBoard::query()->create(['name' => 'Standard Support', 'slug' => 'standard-support', 'description' => 'Default support board', 'is_default' => true, 'sort_order' => 1]); + $board->statuses()->sync([$open->id => ['sort_order' => 10], $closed->id => ['sort_order' => 20]]); - return compact('open', 'closed', 'normal', 'high', 'category', 'team', 'project'); + return compact('open', 'closed', 'normal', 'high', 'category', 'team', 'project', 'board'); } protected function ensureCompanyTableHasOne(): ?int diff --git a/Modules/Helpdesk/Tests/Feature/KanbanBoardTest.php b/Modules/Helpdesk/Tests/Feature/KanbanBoardTest.php index 7d311355..67fb7f10 100644 --- a/Modules/Helpdesk/Tests/Feature/KanbanBoardTest.php +++ b/Modules/Helpdesk/Tests/Feature/KanbanBoardTest.php @@ -7,6 +7,7 @@ namespace Modules\Helpdesk\Tests\Feature; use Illuminate\Foundation\Testing\RefreshDatabase; use Livewire\Livewire; use Modules\Helpdesk\Livewire\KanbanBoard; +use Modules\Helpdesk\Models\KanbanBoard as KanbanBoardModel; use Modules\Helpdesk\Tests\Concerns\BuildsHelpdeskFixtures; use Tests\TestCase; @@ -25,6 +26,7 @@ final class KanbanBoardTest extends TestCase 'owner' => $user, 'status_id' => $lookups['open']->id, 'priority_id' => $lookups['high']->id, + 'board_id' => $lookups['board']->id, 'subject' => 'Kanban visible ticket', ]); @@ -47,6 +49,7 @@ final class KanbanBoardTest extends TestCase 'owner' => $user, 'status_id' => $lookups['open']->id, 'priority_id' => $lookups['normal']->id, + 'board_id' => $lookups['board']->id, ]); $this->actingAs($user); @@ -58,6 +61,38 @@ final class KanbanBoardTest extends TestCase $ticket->refresh(); $this->assertSame($lookups['closed']->id, $ticket->status_id); + $this->assertSame($lookups['board']->id, $ticket->board_id); $this->assertNotNull($ticket->completed_at); } + + /** @test */ + public function kanban_ticket_can_be_moved_to_another_board_and_only_has_one_board(): void + { + $lookups = $this->seedLookupData(); + $user = $this->admin(); + $targetBoard = KanbanBoardModel::query()->create([ + 'name' => 'Development Board', + 'slug' => 'development-board', + 'is_default' => false, + 'sort_order' => 20, + ]); + $targetBoard->statuses()->sync([$lookups['open']->id => ['sort_order' => 10]]); + + $ticket = $this->ticket([ + 'owner' => $user, + 'status_id' => $lookups['open']->id, + 'priority_id' => $lookups['normal']->id, + 'board_id' => $lookups['board']->id, + ]); + + $this->actingAs($user); + + Livewire::test(KanbanBoard::class) + ->call('moveTicketToBoard', $ticket->id, $targetBoard->id) + ->assertHasNoErrors(); + + $ticket->refresh(); + + $this->assertSame($targetBoard->id, $ticket->board_id); + } } diff --git a/Modules/Helpdesk/changelog.yaml b/Modules/Helpdesk/changelog.yaml index 87028cd7..597c1c74 100644 --- a/Modules/Helpdesk/changelog.yaml +++ b/Modules/Helpdesk/changelog.yaml @@ -1,3 +1,21 @@ + +- version: 1.3.4 + date: 2026-06-16 + changes: + - Added single-board ticket assignment via helpdesk_tickets.board_id. + - Added Kanban board owner support. + - Added board-to-board ticket moves while enforcing one active board per ticket. + - Added status move alerts/toasts when tickets move between workflow statuses. + - Added create/edit ticket board selection and default-board assignment for new tickets. + +- version: 1.3.3 + date: '2026-06-16' + changes: + - Reworked Helpdesk action styling so header, support, settings, table, save, reset, edit and delete buttons use reliable coloured native styling. + - Replaced settings section card dynamic Tailwind colour classes with inline styles so card colours always render. + - Improved Kanban board dropdown styling. + - Hardened ticket create inputs and Select2 controls to prevent white flashes during save. + url: https://wms.vm versions: "1.0.0": @@ -141,11 +159,3 @@ versions: - Fixed malformed Helpdesk settings action buttons that caused Blade parse errors. - Applied explicit coloured action buttons across settings tables, settings forms and Kanban board configuration actions. - Kept module.json version in sync with changelog.yaml. - - - "1.3.1": - changed: - - Replaced malformed Flux settings action buttons with native coloured buttons to fix Blade parse errors and white edit/delete actions. - - Added coloured Helpdesk settings header actions for Tickets, + New Ticket and Kanban. - - Standardised support edit actions as blue and destructive actions as red. - - Kept module.json version in sync with changelog.yaml. diff --git a/Modules/Helpdesk/module.json b/Modules/Helpdesk/module.json index e9db97dd..f1f73f2c 100644 --- a/Modules/Helpdesk/module.json +++ b/Modules/Helpdesk/module.json @@ -22,5 +22,5 @@ "Modules\\Helpdesk\\Providers\\HelpdeskServiceProvider" ], "files": [], - "version": "1.3.1" + "version": "1.3.4" }