Skip to content

Commit 697c03b

Browse files
author
Hatim EL OUFIR
committed
Kanban Board
1 parent d89b164 commit 697c03b

16 files changed

Lines changed: 338 additions & 57 deletions

File tree

app/Http/Livewire/Kanban.php

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
<?php
2+
3+
namespace App\Http\Livewire;
4+
5+
use App\Jobs\TicketUpdatedJob;
6+
use App\Models\Ticket;
7+
use Filament\Notifications\Notification;
8+
use Illuminate\Support\Collection;
9+
use Illuminate\Support\HtmlString;
10+
use Illuminate\Support\Str;
11+
use InvadersXX\FilamentKanbanBoard\Pages\FilamentKanbanBoard;
12+
13+
class Kanban extends FilamentKanbanBoard
14+
{
15+
protected static ?string $title = '';
16+
public bool $sortable = true;
17+
public bool $sortableBetweenStatuses = true;
18+
public bool $recordClickEnabled = true;
19+
20+
/**
21+
* Statuses list Definition
22+
*
23+
* @return Collection
24+
*/
25+
protected function statuses(): Collection
26+
{
27+
return collect(statuses_list_for_kanban());
28+
}
29+
30+
/**
31+
* Records list definitino
32+
*
33+
* @return Collection
34+
*/
35+
protected function records(): Collection
36+
{
37+
return Ticket::all()
38+
->map(function (Ticket $ticket) {
39+
$priority = config('system.priorities.' . $ticket->priority);
40+
$type = config('system.types.' . $ticket->type);
41+
return [
42+
'id' => $ticket->id,
43+
'title' => new HtmlString('
44+
<div class="w-full flex flex-col space-y-3">
45+
<div class="w-full flex items-center gap-2">
46+
<div title="' . $type['title'] . '" class="text-xs rounded-full w-6 h-6 flex items-center justify-center text-center ' . $type['text-color'] . ' ' . $type['bg-color'] . '">
47+
<i class="fa ' . $type['icon'] . '"></i>
48+
</div>
49+
<div title="' . $priority['title'] . '" class="text-xs rounded-full w-6 h-6 flex items-center justify-center text-center ' . $priority['text-color'] . ' ' . $priority['bg-color'] . '">
50+
<i class="fa ' . $priority['icon'] . '"></i>
51+
</div>
52+
<span class="text-sm font-normal" title="' . $ticket->title . '">' . Str::limit($ticket->title, 15) . '</span>
53+
</div>
54+
<div class="w-full text-xs font-light">
55+
' . Str::limit(htmlspecialchars(strip_tags($ticket->content))) . '
56+
</div>
57+
<div class="w-full flex items-center gap-1">
58+
'.
59+
($ticket->responsible ? '
60+
<img src="' . $ticket->responsible->avatar_url . '" alt="' . $ticket->responsible->name . '" class="rounded-full shadow" style="width: 20px; height: 20px;" />
61+
<span class="font-light text-xs">' . $ticket->responsible->name . '</span>
62+
' : '<span class="text-xs font-normal text-gray-400">' . __('Not assigned yet!') . '</span>')
63+
.'
64+
</div>
65+
</div>
66+
'),
67+
'status' => $ticket->status,
68+
];
69+
});
70+
}
71+
72+
/**
73+
* Customizing kanban board styles
74+
*
75+
* @return string[]
76+
*/
77+
protected function styles(): array
78+
{
79+
return [
80+
'wrapper' => 'w-full h-full flex space-x-4 overflow-x-auto',
81+
'kanbanWrapper' => 'h-full flex-1',
82+
'kanban' => 'border border-gray-150 flex flex-col h-full rounded',
83+
'kanbanHeader' => 'px-3 py-3 font-bold text-xs w-full border-b border-gray-150',
84+
'kanbanFooter' => '',
85+
'kanbanRecords' => 'space-y-4 p-3 flex-1 overflow-y-auto w-64',
86+
'record' => 'bg-white dark:bg-gray-800 p-4 border border-gray-150 rounded cursor-pointer w-62 hover:bg-gray-50 hover:shadow-lg',
87+
'recordContent' => 'w-full',
88+
];
89+
}
90+
91+
/**
92+
* Event launched when the record status is changed
93+
*
94+
* @param $recordId
95+
* @param $statusId
96+
* @param $fromOrderedIds
97+
* @param $toOrderedIds
98+
* @return void
99+
*/
100+
public function onStatusChanged($recordId, $statusId, $fromOrderedIds, $toOrderedIds): void
101+
{
102+
$ticket = Ticket::find($recordId);
103+
if ((has_all_permissions(auth()->user(), 'update-all-tickets') || (has_all_permissions(auth()->user(), 'update-own-tickets') && ($ticket->owner_id === auth()->user() || $ticket->responsible_id === auth()->user()->id))) && has_all_permissions(auth()->user(), 'change-status-tickets')) {
104+
$before = __(config('system.statuses.' . $ticket->status . '.title')) ?? '-';
105+
$ticket->status = $statusId;
106+
$ticket->save();
107+
Notification::make()
108+
->success()
109+
->title(__('Status updated'))
110+
->body(__('The ticket status has been successfully updated'))
111+
->send();
112+
TicketUpdatedJob::dispatch($ticket, __('Status'), $before, __(config('system.statuses.' . $ticket->status . '.title') ?? '-'));
113+
} else {
114+
Notification::make()
115+
->success()
116+
->title(__('Oops!'))
117+
->body(__("You don't have permissions to change this ticket status"))
118+
->send();
119+
}
120+
}
121+
122+
/**
123+
* Event launched when the record is clicked
124+
*
125+
* @param $recordId
126+
* @return void
127+
*/
128+
public function onRecordClick($recordId): void
129+
{
130+
$ticket = Ticket::find($recordId);
131+
$url = route('tickets.details', ['ticket' => $ticket, 'slug' => Str::slug($ticket->title)]);
132+
$this->dispatchBrowserEvent('open-ticket', ['url' => $url]);
133+
}
134+
}

app/Http/Livewire/TicketDetails/Priority.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ public function update(): void
6666
public function save(): void
6767
{
6868
$data = $this->form->getState();
69-
$before = config('system.priorities.' . $this->ticket->priority . '.title') ?? '-';
69+
$before = __(config('system.priorities.' . $this->ticket->priority . '.title')) ?? '-';
7070
$this->ticket->priority = $data['priority'];
7171
$this->ticket->save();
7272
Notification::make()
@@ -79,6 +79,6 @@ public function save(): void
7979
]);
8080
$this->updating = false;
8181
$this->emit('ticketSaved');
82-
TicketUpdatedJob::dispatch($this->ticket, __('Priority'), $before, (config('system.priorities.' . $this->ticket->priority . '.title') ?? '-'));
82+
TicketUpdatedJob::dispatch($this->ticket, __('Priority'), $before, __(config('system.priorities.' . $this->ticket->priority . '.title') ?? '-'));
8383
}
8484
}

app/Http/Livewire/TicketDetails/Status.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ public function update(): void
6565
public function save(): void
6666
{
6767
$data = $this->form->getState();
68-
$before = config('system.statuses.' . $this->ticket->status . '.title') ?? '-';
68+
$before = __(config('system.statuses.' . $this->ticket->status . '.title')) ?? '-';
6969
$this->ticket->status = $data['status'];
7070
$this->ticket->save();
7171
Notification::make()
@@ -78,6 +78,6 @@ public function save(): void
7878
]);
7979
$this->updating = false;
8080
$this->emit('ticketSaved');
81-
TicketUpdatedJob::dispatch($this->ticket, __('Status'), $before, (config('system.statuses.' . $this->ticket->status . '.title') ?? '-'));
81+
TicketUpdatedJob::dispatch($this->ticket, __('Status'), $before, __(config('system.statuses.' . $this->ticket->status . '.title') ?? '-'));
8282
}
8383
}

app/Http/Livewire/TicketDetails/Type.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ public function update(): void
6565
public function save(): void
6666
{
6767
$data = $this->form->getState();
68-
$before = config('system.types.' . $this->ticket->type . '.title') ?? '-';
68+
$before = __(config('system.types.' . $this->ticket->type . '.title')) ?? '-';
6969
$this->ticket->type = $data['type'];
7070
$this->ticket->save();
7171
Notification::make()
@@ -78,6 +78,6 @@ public function save(): void
7878
]);
7979
$this->updating = false;
8080
$this->emit('ticketSaved');
81-
TicketUpdatedJob::dispatch($this->ticket, __('Type'), $before, (config('system.types.' . $this->ticket->type . '.title') ?? '-'));
81+
TicketUpdatedJob::dispatch($this->ticket, __('Type'), $before, __(config('system.types.' . $this->ticket->type . '.title') ?? '-'));
8282
}
8383
}

app/View/Components/MainMenu.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,12 @@ public function __construct()
4040
'always_shown' => false,
4141
'show_notification_indicator' => false
4242
],
43+
'kanban' => [
44+
'title' => 'Kanban Board',
45+
'icon' => 'fa-clipboard-check',
46+
'always_shown' => false,
47+
'show_notification_indicator' => false
48+
],
4349
'administration' => [
4450
'title' => 'Administration',
4551
'icon' => 'fa-cogs',

app/helpers.php

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,27 @@ function statuses_list(): array
9898
}
9999
}
100100

101+
if (!function_exists('statuses_list_for_kanban')) {
102+
/**
103+
* Return statuses list as an array for kanban board
104+
*
105+
* @return array
106+
*/
107+
function statuses_list_for_kanban(): array
108+
{
109+
$statuses = [];
110+
foreach (config('system.statuses') as $key => $value) {
111+
$statuses[] = [
112+
'id' => $key,
113+
'title' => __($value['title']),
114+
'text-color' => $value['text-color'],
115+
'bg-color' => $value['bg-color'],
116+
];
117+
}
118+
return $statuses;
119+
}
120+
}
121+
101122
if (!function_exists('priorities_list')) {
102123
/**
103124
* Return priorities list as an array of KEY (priority id) => VALUE (priority title)

composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
"filament/forms": "^2.15",
1010
"filament/notifications": "^2.15",
1111
"guzzlehttp/guzzle": "^7.2",
12+
"invaders-xx/filament-kanban-board": "^0.2.6",
1213
"laravel/framework": "^9.19",
1314
"laravel/sanctum": "^3.0",
1415
"laravel/tinker": "^2.7",

0 commit comments

Comments
 (0)