Skip to content

Commit f15ad4b

Browse files
committed
Optimize Scrum / Kanban components
1 parent 257f1dd commit f15ad4b

4 files changed

Lines changed: 285 additions & 434 deletions

File tree

app/Filament/Pages/Kanban.php

Lines changed: 5 additions & 201 deletions
Original file line numberDiff line numberDiff line change
@@ -2,28 +2,18 @@
22

33
namespace App\Filament\Pages;
44

5+
use App\Helpers\KanbanScrumHelper;
56
use App\Models\Project;
6-
use App\Models\Ticket;
7-
use App\Models\TicketPriority;
8-
use App\Models\TicketStatus;
9-
use App\Models\TicketType;
10-
use App\Models\User;
117
use Filament\Facades\Filament;
12-
use Filament\Forms\Components\Grid;
13-
use Filament\Forms\Components\Placeholder;
14-
use Filament\Forms\Components\Select;
15-
use Filament\Forms\Components\Toggle;
168
use Filament\Forms\Concerns\InteractsWithForms;
179
use Filament\Forms\Contracts\HasForms;
1810
use Filament\Pages\Actions\Action;
1911
use Filament\Pages\Page;
2012
use Illuminate\Contracts\Support\Htmlable;
21-
use Illuminate\Support\Collection;
22-
use Illuminate\Support\HtmlString;
2313

2414
class Kanban extends Page implements HasForms
2515
{
26-
use InteractsWithForms;
16+
use InteractsWithForms, KanbanScrumHelper;
2717

2818
protected static ?string $navigationIcon = 'heroicon-o-view-boards';
2919

@@ -33,17 +23,6 @@ class Kanban extends Page implements HasForms
3323

3424
protected static bool $shouldRegisterNavigation = false;
3525

36-
public bool $sortable = true;
37-
38-
public Project|null $project = null;
39-
40-
public $users = [];
41-
public $types = [];
42-
public $priorities = [];
43-
public $includeNotAffectedTickets = false;
44-
45-
public bool $ticket = false;
46-
4726
protected $listeners = [
4827
'recordUpdated',
4928
'closeTicketDialog'
@@ -64,53 +43,6 @@ public function mount(Project $project)
6443
$this->form->fill();
6544
}
6645

67-
protected function getFormSchema(): array
68-
{
69-
return [
70-
Grid::make([
71-
'default' => 2,
72-
'md' => 6
73-
])
74-
->schema([
75-
Select::make('users')
76-
->label(__('Owners / Responsibles'))
77-
->multiple()
78-
->options(User::all()->pluck('name', 'id')),
79-
80-
Select::make('types')
81-
->label(__('Ticket types'))
82-
->multiple()
83-
->options(TicketType::all()->pluck('name', 'id')),
84-
85-
Select::make('priorities')
86-
->label(__('Ticket priorities'))
87-
->multiple()
88-
->options(TicketPriority::all()->pluck('name', 'id')),
89-
90-
Toggle::make('includeNotAffectedTickets')
91-
->label(__('Include not affected tickets'))
92-
->columnSpan(2),
93-
94-
Placeholder::make('search')
95-
->label(new HtmlString(' '))
96-
->content(new HtmlString('
97-
<button type="button"
98-
wire:click="filter" wire:loading.attr="disabled"
99-
class="bg-primary-500 px-3 py-2 text-white rounded hover:bg-primary-600
100-
disabled:bg-primary-300">
101-
' . __('Filter') . '
102-
</button>
103-
<button type="button"
104-
wire:click="resetFilters" wire:loading.attr="disabled"
105-
class="ml-2 bg-gray-800 px-3 py-2 text-white rounded hover:bg-gray-900
106-
disabled:bg-gray-300">
107-
' . __('Reset filters') . '
108-
</button>
109-
')),
110-
]),
111-
];
112-
}
113-
11446
protected function getActions(): array
11547
{
11648
return [
@@ -127,140 +59,12 @@ protected function getActions(): array
12759

12860
protected function getHeading(): string|Htmlable
12961
{
130-
$heading = '<div class="w-full flex flex-col gap-1">';
131-
$heading .= '<a href="' . route('filament.pages.board') . '"
132-
class="text-primary-500 text-xs font-medium hover:underline">';
133-
$heading .= __('Back to board');
134-
$heading .= '</a>';
135-
$heading .= '<div class="flex flex-col gap-1">';
136-
$heading .= '<span>' . __('Kanban');
137-
if ($this->project) {
138-
$heading .= ' - ' . $this->project->name . '</span>';
139-
} else {
140-
$heading .= '</span><span class="text-xs text-gray-400">'
141-
. __('Only default statuses are listed when no projects selected')
142-
. '</span>';
143-
}
144-
$heading .= '</div>';
145-
$heading .= '</div>';
146-
return new HtmlString($heading);
147-
}
148-
149-
public function getStatuses(): Collection
150-
{
151-
$query = TicketStatus::query();
152-
if ($this->project && $this->project->status_type === 'custom') {
153-
$query->where('project_id', $this->project->id);
154-
} else {
155-
$query->whereNull('project_id');
156-
}
157-
return $query->orderBy('order')
158-
->get()
159-
->map(function ($item) {
160-
$query = Ticket::query();
161-
if ($this->project) {
162-
$query->where('project_id', $this->project->id);
163-
}
164-
$query->where('status_id', $item->id);
165-
return [
166-
'id' => $item->id,
167-
'title' => $item->name,
168-
'color' => $item->color,
169-
'size' => $query->count(),
170-
'add_ticket' => $item->is_default && auth()->user()->can('Create ticket')
171-
];
172-
});
173-
}
174-
175-
public function getRecords(): Collection
176-
{
177-
$query = Ticket::query();
178-
$query->with(['project', 'owner', 'responsible', 'status', 'type', 'priority', 'epic']);
179-
if ($this->project) {
180-
$query->where('project_id', $this->project->id);
181-
} else {
182-
$query->whereHas('project', fn($query) => $query->where('status_type', 'default'));
183-
}
184-
if (sizeof($this->users)) {
185-
$query->where(function ($query) {
186-
return $query->whereIn('owner_id', $this->users)
187-
->orWhereIn('responsible_id', $this->users);
188-
});
189-
}
190-
if (sizeof($this->types)) {
191-
$query->whereIn('type_id', $this->types);
192-
}
193-
if (sizeof($this->priorities)) {
194-
$query->whereIn('priority_id', $this->priorities);
195-
}
196-
if ($this->includeNotAffectedTickets) {
197-
$query->orWhereNull('responsible_id');
198-
}
199-
$query->where(function ($query) {
200-
return $query->where('owner_id', auth()->user()->id)
201-
->orWhere('responsible_id', auth()->user()->id)
202-
->orWhereHas('project', function ($query) {
203-
return $query->where('owner_id', auth()->user()->id)
204-
->orWhereHas('users', function ($query) {
205-
return $query->where('users.id', auth()->user()->id);
206-
});
207-
});
208-
});
209-
return $query->get()
210-
->map(fn(Ticket $item) => [
211-
'id' => $item->id,
212-
'code' => $item->code,
213-
'title' => $item->name,
214-
'owner' => $item->owner,
215-
'type' => $item->type,
216-
'responsible' => $item->responsible,
217-
'project' => $item->project,
218-
'status' => $item->status->id,
219-
'priority' => $item->priority,
220-
'epic' => $item->epic,
221-
'relations' => $item->relations,
222-
'totalLoggedHours' => $item->totalLoggedSeconds ? $item->totalLoggedHours : null
223-
]);
224-
}
225-
226-
public function recordUpdated(int $record, int $newIndex, int $newStatus): void
227-
{
228-
$ticket = Ticket::find($record);
229-
if ($ticket) {
230-
$ticket->order = $newIndex;
231-
$ticket->status_id = $newStatus;
232-
$ticket->save();
233-
Filament::notify('success', __('Ticket updated'));
234-
}
235-
}
236-
237-
public function isMultiProject(): bool
238-
{
239-
return $this->project === null;
62+
return $this->kanbanHeading();
24063
}
24164

242-
public function filter(): void
243-
{
244-
$this->getRecords();
245-
}
246-
247-
public function resetFilters(): void
248-
{
249-
$this->form->fill();
250-
$this->filter();
251-
}
252-
253-
public function createTicket(): void
254-
{
255-
$this->ticket = true;
256-
}
257-
258-
public function closeTicketDialog(bool $refresh): void
65+
protected function getFormSchema(): array
25966
{
260-
$this->ticket = false;
261-
if ($refresh) {
262-
$this->filter();
263-
}
67+
return $this->formSchema();
26468
}
26569

26670
}

0 commit comments

Comments
 (0)