diff --git a/Modules/Helpdesk/Database/Seeders/HelpdeskDatabaseSeeder.php b/Modules/Helpdesk/Database/Seeders/HelpdeskDatabaseSeeder.php index 6ba3eb7f..98c43917 100644 --- a/Modules/Helpdesk/Database/Seeders/HelpdeskDatabaseSeeder.php +++ b/Modules/Helpdesk/Database/Seeders/HelpdeskDatabaseSeeder.php @@ -30,17 +30,6 @@ final class HelpdeskDatabaseSeeder extends Seeder $this->seedTeamsAndProjects(); $this->seedKanbanBoards(); $this->seedWorkflowTemplates(); - DB::table('helpdesk_kanban_boards')->updateOrInsert( - ['slug' => 'default'], - [ - 'name' => 'Default Helpdesk Board', - 'description' => 'Standard support workflow board.', - 'is_default' => true, - 'sort_order' => 1, - 'created_at' => now(), - 'updated_at' => now(), - ] - ); } /** @@ -136,6 +125,8 @@ final class HelpdeskDatabaseSeeder extends Seeder */ private function seedKanbanBoards(): void { + KanbanBoard::query()->where('is_default', true)->update(['is_default' => false]); + $board = KanbanBoard::query()->updateOrCreate( ['slug' => 'standard-support'], [ diff --git a/Modules/Helpdesk/Livewire/KanbanBoard.php b/Modules/Helpdesk/Livewire/KanbanBoard.php index dcacc7ff..68b4800c 100644 --- a/Modules/Helpdesk/Livewire/KanbanBoard.php +++ b/Modules/Helpdesk/Livewire/KanbanBoard.php @@ -62,7 +62,11 @@ final class KanbanBoard extends Component ->get(); $board = $boards->firstWhere('id', $this->board_id) ?? $boards->first(); - $statuses = $board?->statuses ?? Status::query()->orderBy('sort_order')->orderBy('name')->get(); + $statuses = $board?->statuses ?? collect(); + + if ($statuses->isEmpty()) { + $statuses = Status::query()->orderBy('sort_order')->orderBy('name')->get(); + } return view('helpdesk::livewire.kanban-board', [ 'boards' => $boards, diff --git a/Modules/Helpdesk/Livewire/SettingsManager.php b/Modules/Helpdesk/Livewire/SettingsManager.php index f1d4b11f..ce97b14c 100644 --- a/Modules/Helpdesk/Livewire/SettingsManager.php +++ b/Modules/Helpdesk/Livewire/SettingsManager.php @@ -11,6 +11,7 @@ use Illuminate\Support\Str; use Livewire\Component; 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; @@ -42,6 +43,9 @@ final class SettingsManager extends Component /** @var array */ public array $workflowForm = ['id' => null, 'name' => '', 'event' => 'ticket.created', 'subject' => '', 'html_body' => '', 'text_body' => '', 'to_role' => 'requester', 'cc' => '', 'bcc' => '', 'is_active' => true]; + /** @var array */ + public array $boardForm = ['id' => null, 'name' => '', 'slug' => '', 'description' => '', 'is_default' => false, 'sort_order' => 0, 'status_ids' => []]; + public function saveStatus(): void { $data = $this->validate([ @@ -241,6 +245,65 @@ final class SettingsManager extends Component $this->repositoryForm = ['id' => null, 'company_id' => null, 'project_id' => null, 'name' => '', 'provider' => 'git', 'remote_url' => '', 'default_branch' => 'main', 'webhook_secret' => '']; } + + public function saveBoard(): void + { + $data = $this->validate([ + 'boardForm.name' => ['required', 'string', 'max:160'], + 'boardForm.slug' => ['nullable', 'string', 'max:180'], + 'boardForm.description' => ['nullable', 'string'], + 'boardForm.is_default' => ['boolean'], + 'boardForm.sort_order' => ['required', 'integer', 'min:0'], + 'boardForm.status_ids' => ['required', 'array', 'min:1'], + 'boardForm.status_ids.*' => ['integer', 'exists:helpdesk_statuses,id'], + ])['boardForm']; + + $statusIds = collect($data['status_ids']) + ->map(static fn (mixed $id): int => (int) $id) + ->filter() + ->unique() + ->values(); + + unset($data['status_ids']); + $data['slug'] = $this->slug($data['slug'] ?: $data['name']); + + if ((bool) $data['is_default']) { + KanbanBoard::query()->where('is_default', true)->update(['is_default' => false]); + } + + $board = KanbanBoard::query()->updateOrCreate(['id' => $this->boardForm['id']], $data); + + $sync = []; + foreach ($statusIds as $index => $statusId) { + $sync[$statusId] = ['sort_order' => ($index + 1) * 10]; + } + + $board->statuses()->sync($sync); + $this->resetBoardForm(); + $this->section = 'boards'; + session()->flash('success', 'Kanban board saved.'); + } + + public function editBoard(int $id): void + { + $board = KanbanBoard::query()->with('statuses')->findOrFail($id); + + $this->boardForm = $board->only(['id', 'name', 'slug', 'description', 'is_default', 'sort_order']); + $this->boardForm['status_ids'] = $board->statuses->pluck('id')->map(static fn (mixed $id): int => (int) $id)->all(); + $this->section = 'boards'; + } + + public function deleteBoard(int $id): void + { + KanbanBoard::query()->findOrFail($id)->delete(); + session()->flash('success', 'Kanban board deleted.'); + } + + public function resetBoardForm(): void + { + $this->boardForm = ['id' => null, 'name' => '', 'slug' => '', 'description' => '', 'is_default' => false, 'sort_order' => 0, 'status_ids' => []]; + } + public function saveWorkflowTemplate(): void { $data = $this->validate([ @@ -287,6 +350,7 @@ final class SettingsManager extends Component 'teams' => Team::query()->withCount(['projects', 'tickets'])->orderBy('name')->get(), 'projects' => Project::query()->with(['team'])->withCount('tickets')->orderBy('title')->get(), 'repositories' => GitRepository::query()->with(['project'])->withCount('commits')->orderBy('name')->get(), + 'boards' => KanbanBoard::query()->with('statuses')->withCount('statuses')->orderBy('sort_order')->orderBy('name')->get(), 'workflowTemplates' => WorkflowTemplate::query()->orderBy('event')->orderBy('name')->get(), ])->layout('layouts.app'); } diff --git a/Modules/Helpdesk/Livewire/TicketShow.php b/Modules/Helpdesk/Livewire/TicketShow.php index c4154fe7..0e5fe8fa 100644 --- a/Modules/Helpdesk/Livewire/TicketShow.php +++ b/Modules/Helpdesk/Livewire/TicketShow.php @@ -73,6 +73,7 @@ final class TicketShow extends Component $this->reset(['commentBody', 'internal', 'attachments']); $this->ticket = $this->freshTicket($this->ticket); + $this->dispatch('helpdesk-editor-reset', model: 'commentBody'); $this->dispatch('helpdesk-comment-added'); } @@ -87,8 +88,9 @@ final class TicketShow extends Component $tickets->comment($this->ticket, Auth::user(), $this->replyBody, $this->internal, [], $this->reply_parent_id); - $this->cancelReply(); + $this->reset(['reply_parent_id', 'replyBody']); $this->ticket = $this->freshTicket($this->ticket); + $this->dispatch('helpdesk-editor-reset', model: 'replyBody'); $this->dispatch('helpdesk-reply-added'); } @@ -144,39 +146,31 @@ final class TicketShow extends Component 'team', 'attachments.user', 'gitCommits.repository', - 'comments.attachments', - 'comments.replies.attachments', - 'comments.replies.replies.attachments', ]) ?? $ticket; - $comments = $ticket->comments - ->merge($ticket->comments->flatMap->replies) - ->merge($ticket->comments->flatMap->replies->flatMap->replies); + $comments = \Modules\Helpdesk\Models\Comment::query() + ->where('ticket_id', $ticket->id) + ->with(['user', 'attachments.user']) + ->oldest() + ->get(); - $users = User::query() - ->whereIn('id', $comments->pluck('user_id')->filter()->unique()) - ->get() - ->keyBy('id'); - - $this->attachCommentUsers($ticket->comments, $users); + $ticket->setRelation('comments', $this->buildCommentTree($comments->groupBy('parent_id'))); return $ticket; } /** - * Attach loaded users to comment hierarchy without triggering lazy loading. + * Build an unlimited nested comment tree from a flat eager-loaded collection. * - * @param Collection $comments - * @param Collection $users + * @param Collection> $grouped + * @return Collection */ - private function attachCommentUsers(Collection $comments, Collection $users): void + private function buildCommentTree(Collection $grouped, ?int $parentId = null): Collection { - foreach ($comments as $comment) { - $comment->setRelation('user', $users->get($comment->user_id)); + return $grouped->get($parentId, collect())->map(function ($comment) use ($grouped) { + $comment->setRelation('replies', $this->buildCommentTree($grouped, (int) $comment->id)); - if ($comment->relationLoaded('replies')) { - $this->attachCommentUsers($comment->replies, $users); - } - } + return $comment; + })->values(); } } diff --git a/Modules/Helpdesk/Policies/TicketPolicy.php b/Modules/Helpdesk/Policies/TicketPolicy.php index 39ad6f58..7314c065 100644 --- a/Modules/Helpdesk/Policies/TicketPolicy.php +++ b/Modules/Helpdesk/Policies/TicketPolicy.php @@ -28,7 +28,9 @@ final class TicketPolicy public function update(User $user, Ticket $ticket): bool { - return (bool) ($user->helpdesk_admin ?? false) || (int) $ticket->agent_id === (int) $user->id; + return (bool) ($user->helpdesk_admin ?? false) + || (bool) ($user->helpdesk_agent ?? false) + || (int) $ticket->agent_id === (int) $user->id; } public function comment(User $user, Ticket $ticket): bool diff --git a/Modules/Helpdesk/Resources/views/components/html-editor.blade.php b/Modules/Helpdesk/Resources/views/components/html-editor.blade.php index ec6df4b7..21fbc616 100644 --- a/Modules/Helpdesk/Resources/views/components/html-editor.blade.php +++ b/Modules/Helpdesk/Resources/views/components/html-editor.blade.php @@ -12,6 +12,7 @@
whereDoesntStartWith('wire:model')->merge(['class' => 'space-y-2']) }} + x-on:helpdesk-editor-reset.window="if ($event.detail.model === model) clear()" x-data="{ model: @js($model), value: @js($initialValue), @@ -26,6 +27,13 @@ document.execCommand(action, false, value); this.sync(); }, + clear() { + this.value = ''; + this.$refs.editor.innerHTML = ''; + if (this.model) { + this.$wire.set(this.model, '', false); + } + }, init() { this.$refs.editor.innerHTML = this.value || ''; } diff --git a/Modules/Helpdesk/Resources/views/components/lookup-table.blade.php b/Modules/Helpdesk/Resources/views/components/lookup-table.blade.php index a5f153bc..2c652685 100644 --- a/Modules/Helpdesk/Resources/views/components/lookup-table.blade.php +++ b/Modules/Helpdesk/Resources/views/components/lookup-table.blade.php @@ -10,7 +10,7 @@ {{ $item->slug }} {{ $item->color }} {{ $item->tickets_count ?? 0 }} - EditDelete + EditDelete @empty 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 21c8485d..402eb92e 100644 --- a/Modules/Helpdesk/Resources/views/components/project-table.blade.php +++ b/Modules/Helpdesk/Resources/views/components/project-table.blade.php @@ -1 +1 @@ -
@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
ProjectTeamSlugColourTicketsActions
{{ $item->title }}{{ $item->team?->name ?? 'No team' }}{{ $item->slug }}{{ $item->color }}{{ $item->tickets_count }}EditDelete
No projects yet.
diff --git a/Modules/Helpdesk/Resources/views/components/repository-table.blade.php b/Modules/Helpdesk/Resources/views/components/repository-table.blade.php index 068466df..83cb1d45 100644 --- a/Modules/Helpdesk/Resources/views/components/repository-table.blade.php +++ b/Modules/Helpdesk/Resources/views/components/repository-table.blade.php @@ -21,7 +21,7 @@ {{ $item->default_branch }} {{ $item->commits_count }} - Edit + Edit Delete diff --git a/Modules/Helpdesk/Resources/views/components/team-table.blade.php b/Modules/Helpdesk/Resources/views/components/team-table.blade.php index a923de3a..6dc60692 100644 --- a/Modules/Helpdesk/Resources/views/components/team-table.blade.php +++ b/Modules/Helpdesk/Resources/views/components/team-table.blade.php @@ -1 +1 @@ -
@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
TeamSlugColourProjectsTicketsActions
{{ $item->name }}{{ $item->slug }}{{ $item->color }}{{ $item->projects_count }}{{ $item->tickets_count }}EditDelete
No teams yet.
diff --git a/Modules/Helpdesk/Resources/views/livewire/kanban-board.blade.php b/Modules/Helpdesk/Resources/views/livewire/kanban-board.blade.php index 3d16ec73..6d44aaa9 100644 --- a/Modules/Helpdesk/Resources/views/livewire/kanban-board.blade.php +++ b/Modules/Helpdesk/Resources/views/livewire/kanban-board.blade.php @@ -1,4 +1,4 @@ -
+
@@ -36,11 +36,11 @@
-
+
@foreach ($statuses as $status) @php($tickets = $ticketsByStatus[$status->id] ?? collect()) -
+
@@ -52,16 +52,18 @@
@forelse ($tickets as $ticket)
@@ -106,55 +108,83 @@ @script @endscript
diff --git a/Modules/Helpdesk/Resources/views/livewire/settings-manager.blade.php b/Modules/Helpdesk/Resources/views/livewire/settings-manager.blade.php index 0141bf70..dad093e3 100644 --- a/Modules/Helpdesk/Resources/views/livewire/settings-manager.blade.php +++ b/Modules/Helpdesk/Resources/views/livewire/settings-manager.blade.php @@ -20,17 +20,18 @@ {{ session('success') }} @endif -
+
@foreach ([ 'statuses' => ['label' => 'Statuses', 'count' => $statuses->count()], 'priorities' => ['label' => 'Priorities', 'count' => $priorities->count()], 'categories' => ['label' => 'Categories', 'count' => $categories->count()], 'teams' => ['label' => 'Teams', 'count' => $teams->count()], 'projects' => ['label' => 'Projects', 'count' => $projects->count()], + 'boards' => ['label' => 'Boards', 'count' => $boards->count()], 'git' => ['label' => 'Git', 'count' => $repositories->count()], 'workflow' => ['label' => 'Workflow', 'count' => $workflowTemplates->count()], ] as $key => $item) - @@ -117,6 +118,78 @@ @endif + + @if ($section === 'boards') + + +
+ + + + +
+ +
+
+ +
+ @foreach ($statuses as $status) + + @endforeach +
+ @error('boardForm.status_ids')

{{ $message }}

@enderror +
+
+ Save board + Reset +
+ +
+ +
+ + + + + + + + + + + + @forelse ($boards as $boardItem) + + + + + + + + @empty + + @endforelse + +
BoardSlugColumnsDefaultActions
{{ $boardItem->name }}{{ $boardItem->slug }} +
+ @foreach ($boardItem->statuses as $status) + {{ $status->name }} + @endforeach +
+
{{ $boardItem->is_default ? 'Default' : 'No' }} + Edit + Delete +
No Kanban boards yet.
+
+
+ @endif + @if ($section === 'git') @@ -206,7 +279,7 @@ {{ $template->to_role }} {{ $template->is_active ? 'Active' : 'Inactive' }} - Edit + Edit Delete diff --git a/Modules/Helpdesk/Resources/views/livewire/ticket-edit.blade.php b/Modules/Helpdesk/Resources/views/livewire/ticket-edit.blade.php new file mode 100644 index 00000000..0b6ce785 --- /dev/null +++ b/Modules/Helpdesk/Resources/views/livewire/ticket-edit.blade.php @@ -0,0 +1,126 @@ +
+ @include('helpdesk::partials.page-header', [ + 'eyebrow' => 'Helpdesk', + 'title' => 'Edit Ticket', + 'subtitle' => 'Update assignment, status, priority, category, team, project, due date and ticket details.', + ]) + +
+
+
+
+
+

{{ $ticket->reference }}

+

Fields marked * are mandatory.

+
+ +
+
+ +
+
+ + + @error('subject')

{{ $message }}

@enderror +
+ + @if($companies->isNotEmpty()) +
+ + + @error('company_id')

{{ $message }}

@enderror +
+ @endif + +
+ + + @error('agent_id')

{{ $message }}

@enderror +
+ +
+ + + @error('status_id')

{{ $message }}

@enderror +
+ +
+ + + @error('priority_id')

{{ $message }}

@enderror +
+ +
+ + + @error('category_id')

{{ $message }}

@enderror +
+ +
+ + + @error('project_id')

{{ $message }}

@enderror +
+ +
+ + + @error('team_id')

{{ $message }}

@enderror +
+ +
+ + + @error('due_at')

{{ $message }}

@enderror +
+ +
+ +
+
+ +
+ Cancel + +
+
+
+ @include('helpdesk::partials.select2-loader') +
diff --git a/Modules/Helpdesk/Resources/views/livewire/ticket-show.blade.php b/Modules/Helpdesk/Resources/views/livewire/ticket-show.blade.php index 11ce9771..ad10aef7 100644 --- a/Modules/Helpdesk/Resources/views/livewire/ticket-show.blade.php +++ b/Modules/Helpdesk/Resources/views/livewire/ticket-show.blade.php @@ -74,7 +74,7 @@
- + Post reply Posting... diff --git a/Modules/Helpdesk/Resources/views/partials/comment-thread.blade.php b/Modules/Helpdesk/Resources/views/partials/comment-thread.blade.php index 40700cc0..b07a9232 100644 --- a/Modules/Helpdesk/Resources/views/partials/comment-thread.blade.php +++ b/Modules/Helpdesk/Resources/views/partials/comment-thread.blade.php @@ -44,11 +44,11 @@ @if (($reply_parent_id ?? null) === $comment->id)
- +
Cancel - Post reply + Post reply
@endif diff --git a/Modules/Helpdesk/Resources/views/partials/select2-loader.blade.php b/Modules/Helpdesk/Resources/views/partials/select2-loader.blade.php new file mode 100644 index 00000000..fb828c1f --- /dev/null +++ b/Modules/Helpdesk/Resources/views/partials/select2-loader.blade.php @@ -0,0 +1,30 @@ +@script + +@endscript diff --git a/Modules/Helpdesk/changelog.yaml b/Modules/Helpdesk/changelog.yaml index c6b5da50..066ab89b 100755 --- a/Modules/Helpdesk/changelog.yaml +++ b/Modules/Helpdesk/changelog.yaml @@ -53,3 +53,14 @@ versions: - Improved Helpdesk settings styling, blue edit actions, and a dedicated Kanban Boards CRUD section. - Marked mandatory ticket fields and enhanced create/edit ticket forms with Select2-capable dropdowns. - Kept module.json version in sync with changelog.yaml. + + "1.1.2": + changed: + - Restored ticket editing with a full Livewire edit view for assignment, status, priority, category, project, team, due date and details. + - Replaced the Kanban board drag behaviour with dependency-free native HTML5 drag and drop so cards move without external SortableJS. + - Added Kanban board CRUD to Helpdesk settings with status-column selection and default board support. + - Made Helpdesk settings edit actions blue and improved the statuses selector card styling. + - Added reusable Select2 progressive enhancement for Helpdesk dropdowns while preserving native Livewire behaviour when Select2 is unavailable. + - Made ticket reply buttons green and reset reply editors after posting. + - Rebuilt ticket comment loading as an unlimited nested tree with eager-loaded users and attachments. + - Kept module.json version in sync with changelog.yaml. diff --git a/Modules/Helpdesk/module.json b/Modules/Helpdesk/module.json index 655aef1f..0bbf6138 100644 --- a/Modules/Helpdesk/module.json +++ b/Modules/Helpdesk/module.json @@ -13,5 +13,5 @@ "Modules\\Helpdesk\\Providers\\HelpdeskServiceProvider" ], "files": [], - "version": "1.0.9" + "version": "1.1.2" }