Skip to content

Commit 3abb619

Browse files
authored
Merge pull request #39 from devaslanphp/dev
Scrum module
2 parents d2b649e + f15ad4b commit 3abb619

99 files changed

Lines changed: 2283 additions & 465 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

app/Filament/Pages/Board.php

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
<?php
2+
3+
namespace App\Filament\Pages;
4+
5+
use App\Models\Project;
6+
use Filament\Forms\Components\Card;
7+
use Filament\Forms\Components\Grid;
8+
use Filament\Forms\Components\Select;
9+
use Filament\Forms\Concerns\InteractsWithForms;
10+
use Filament\Forms\Contracts\HasForms;
11+
use Filament\Pages\Page;
12+
use Illuminate\Contracts\Support\Htmlable;
13+
14+
class Board extends Page implements HasForms
15+
{
16+
use InteractsWithForms;
17+
18+
protected static ?string $navigationIcon = 'heroicon-o-view-boards';
19+
20+
protected static string $view = 'filament.pages.board';
21+
22+
protected static ?string $slug = 'board';
23+
24+
protected static ?int $navigationSort = 4;
25+
26+
protected function getSubheading(): string|Htmlable|null
27+
{
28+
return __("In this section you can choose one of your projects to show it's Scrum or Kanban board");
29+
}
30+
31+
public function mount(): void
32+
{
33+
$this->form->fill();
34+
}
35+
36+
protected static function getNavigationLabel(): string
37+
{
38+
return __('Board');
39+
}
40+
41+
protected static function getNavigationGroup(): ?string
42+
{
43+
return __('Management');
44+
}
45+
46+
protected function getFormSchema(): array
47+
{
48+
return [
49+
Card::make()
50+
->schema([
51+
Grid::make()
52+
->columns(1)
53+
->schema([
54+
Select::make('project')
55+
->label(__('Project'))
56+
->required()
57+
->searchable()
58+
->reactive()
59+
->afterStateUpdated(fn () => $this->search())
60+
->helperText(__("Choose a project to show it's board"))
61+
->options(fn() => Project::where('owner_id', auth()->user()->id)
62+
->orWhereHas('users', function ($query) {
63+
return $query->where('users.id', auth()->user()->id);
64+
})->pluck('name', 'id')->toArray()),
65+
]),
66+
]),
67+
];
68+
}
69+
70+
public function search(): void
71+
{
72+
$data = $this->form->getState();
73+
$project = Project::find($data['project']);
74+
if ($project->type === "scrum") {
75+
$this->redirect(route('filament.pages.scrum/{project}', ['project' => $project]));
76+
} else {
77+
$this->redirect(route('filament.pages.kanban/{project}', ['project' => $project]));
78+
}
79+
}
80+
}

app/Filament/Pages/Kanban.php

Lines changed: 17 additions & 217 deletions
Original file line numberDiff line numberDiff line change
@@ -2,120 +2,47 @@
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

30-
protected static ?string $slug = 'kanban';
20+
protected static ?string $slug = 'kanban/{project}';
3121

3222
protected static string $view = 'filament.pages.kanban';
3323

34-
protected static ?int $navigationSort = 3;
35-
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;
24+
protected static bool $shouldRegisterNavigation = false;
4625

4726
protected $listeners = [
4827
'recordUpdated',
4928
'closeTicketDialog'
5029
];
5130

52-
public function mount()
31+
public function mount(Project $project)
5332
{
54-
if (request()->has('project')) {
55-
$this->project = Project::find(request()->get('project'));
56-
if (
57-
$this->project->owner_id != auth()->user()->id
58-
&&
59-
!$this->project->users->where('id', auth()->user()->id)->count()
60-
) {
61-
abort(403);
62-
}
33+
$this->project = $project;
34+
if ($this->project->type === 'scrum') {
35+
$this->redirect(route('filament.pages.scrum/{project}', ['project' => $project]));
36+
} elseif (
37+
$this->project->owner_id != auth()->user()->id
38+
&&
39+
!$this->project->users->where('id', auth()->user()->id)->count()
40+
) {
41+
abort(403);
6342
}
6443
$this->form->fill();
6544
}
6645

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

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

251-
public function resetFilters(): void
252-
{
253-
$this->form->fill();
254-
$this->filter();
255-
}
256-
257-
public function createTicket(): void
258-
{
259-
$this->ticket = true;
260-
}
261-
262-
public function closeTicketDialog(bool $refresh): void
65+
protected function getFormSchema(): array
26366
{
264-
$this->ticket = false;
265-
if ($refresh) {
266-
$this->filter();
267-
}
67+
return $this->formSchema();
26868
}
26969

27070
}

app/Filament/Pages/RoadMap.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ class RoadMap extends Page implements HasForms
2121

2222
protected static ?string $slug = 'road-map';
2323

24-
protected static ?int $navigationSort = 4;
24+
protected static ?int $navigationSort = 5;
2525

2626
public $project;
2727

@@ -31,7 +31,8 @@ class RoadMap extends Page implements HasForms
3131

3232
protected $listeners = [
3333
'closeEpicDialog' => 'closeDialog',
34-
'closeTicketDialog' => 'closeDialog'
34+
'closeTicketDialog' => 'closeDialog',
35+
'updateEpic'
3536
];
3637

3738
protected static function getNavigationLabel(): string

0 commit comments

Comments
 (0)