Skip to content

Commit c38c8e6

Browse files
committed
Update scrum page
1 parent e701ead commit c38c8e6

13 files changed

Lines changed: 245 additions & 72 deletions

File tree

app/Filament/Pages/Board.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
use Filament\Forms\Concerns\InteractsWithForms;
1010
use Filament\Forms\Contracts\HasForms;
1111
use Filament\Pages\Page;
12+
use Illuminate\Contracts\Support\Htmlable;
1213

1314
class Board extends Page implements HasForms
1415
{
@@ -22,6 +23,11 @@ class Board extends Page implements HasForms
2223

2324
protected static ?int $navigationSort = 4;
2425

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+
2531
public function mount(): void
2632
{
2733
$this->form->fill();

app/Filament/Pages/Scrum.php

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,14 +114,22 @@ class="ml-2 bg-gray-800 px-3 py-2 text-white rounded hover:bg-gray-900
114114
protected function getActions(): array
115115
{
116116
return [
117+
Action::make('manage-sprints')
118+
->button()
119+
->visible(fn () => $this->project->currentSprint && auth()->user()->can('update', $this->project))
120+
->label(__('Manage sprints'))
121+
->color('primary')
122+
->url(route('filament.resources.projects.edit', $this->project)),
123+
117124
Action::make('refresh')
118125
->button()
126+
->visible(fn () => $this->project->currentSprint)
119127
->label(__('Refresh'))
120128
->color('secondary')
121129
->action(function () {
122130
$this->getRecords();
123131
Filament::notify('success', __('Kanban board updated'));
124-
})
132+
}),
125133
];
126134
}
127135

@@ -133,7 +141,7 @@ class="text-primary-500 text-xs font-medium hover:underline">';
133141
$heading .= __('Back to board');
134142
$heading .= '</a>';
135143
$heading .= '<div class="flex flex-col gap-1">';
136-
$heading .= '<span>' . __('Kanban');
144+
$heading .= '<span>' . __('Scrum');
137145
if ($this->project) {
138146
$heading .= ' - ' . $this->project->name . '</span>';
139147
} else {
@@ -146,6 +154,38 @@ class="text-primary-500 text-xs font-medium hover:underline">';
146154
return new HtmlString($heading);
147155
}
148156

157+
protected function getSubheading(): string|Htmlable|null
158+
{
159+
if ($this->project?->currentSprint) {
160+
return new HtmlString(
161+
'<div class="w-full flex flex-col gap-1">'
162+
. '<div class="w-full flex items-center gap-2">'
163+
. '<span class="bg-danger-500 px-2 py-1 rounded text-white text-sm">'
164+
. $this->project->currentSprint->name
165+
. '</span>'
166+
. '<span class="text-xs text-gray-400">'
167+
. __('Started at:') . ' ' . $this->project->currentSprint->started_at->format(__('Y-m-d')) . ' - '
168+
. __('Ends at:') . ' ' . $this->project->currentSprint->ends_at->format(__('Y-m-d')) . ' - '
169+
. ($this->project->currentSprint->remaining ?
170+
(
171+
__('Remaining:') . ' ' . $this->project->currentSprint->remaining . ' ' . __('days'))
172+
: ''
173+
)
174+
. '</span>'
175+
. '</div>'
176+
. ($this->project->nextSprint ? '<span class="text-xs text-primary-500 font-medium">'
177+
. __('Next sprint:') . ' ' . $this->project->nextSprint->name . ' - '
178+
. __('Starts at:') . ' ' . $this->project->nextSprint->starts_at->format(__('Y-m-d'))
179+
. ' (' . __('in') . ' ' . $this->project->nextSprint->starts_at->diffForHumans() . ')'
180+
. '</span>'
181+
. '</span>' : '')
182+
. '</div>'
183+
);
184+
} else {
185+
return null;
186+
}
187+
}
188+
149189
public function getStatuses(): Collection
150190
{
151191
$query = TicketStatus::query();
@@ -175,6 +215,7 @@ public function getStatuses(): Collection
175215
public function getRecords(): Collection
176216
{
177217
$query = Ticket::query();
218+
$query->where('sprint_id', $this->project->currentSprint->id);
178219
$query->with(['project', 'owner', 'responsible', 'status', 'type', 'priority', 'epic']);
179220
if ($this->project) {
180221
$query->where('project_id', $this->project->id);

app/Filament/Resources/ProjectResource/Pages/EditProject.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,21 @@ class EditProject extends EditRecord
1313
protected function getActions(): array
1414
{
1515
return [
16+
Actions\Action::make('kanban')
17+
->label(
18+
fn ()
19+
=> ($this->record->type === 'scrum' ? __('Scrum board') : __('Kanban board'))
20+
)
21+
->icon('heroicon-o-view-boards')
22+
->color('secondary')
23+
->url(function () {
24+
if ($this->record->type === 'scrum') {
25+
return route('filament.pages.scrum/{project}', ['project' => $this->record->id]);
26+
} else {
27+
return route('filament.pages.kanban/{project}', ['project' => $this->record->id]);
28+
}
29+
}),
30+
1631
Actions\ViewAction::make(),
1732
Actions\DeleteAction::make(),
1833
];

app/Filament/Resources/ProjectResource/Pages/ViewProject.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,21 @@ class ViewProject extends ViewRecord
1313
protected function getActions(): array
1414
{
1515
return [
16+
Actions\Action::make('kanban')
17+
->label(
18+
fn ()
19+
=> ($this->record->type === 'scrum' ? __('Scrum board') : __('Kanban board'))
20+
)
21+
->icon('heroicon-o-view-boards')
22+
->color('secondary')
23+
->url(function () {
24+
if ($this->record->type === 'scrum') {
25+
return route('filament.pages.scrum/{project}', ['project' => $this->record->id]);
26+
} else {
27+
return route('filament.pages.kanban/{project}', ['project' => $this->record->id]);
28+
}
29+
}),
30+
1631
Actions\EditAction::make(),
1732
];
1833
}

app/Filament/Resources/ProjectResource/RelationManagers/SprintsRelationManager.php

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
use Closure;
99
use Filament\Facades\Filament;
1010
use Filament\Forms;
11+
use Filament\Notifications\Actions\Action;
12+
use Filament\Notifications\Notification;
1113
use Filament\Resources\Form;
1214
use Filament\Resources\RelationManagers\RelationManager;
1315
use Filament\Resources\Table;
@@ -108,7 +110,7 @@ public static function table(Table $table): Table
108110

109111
Tables\Columns\TextColumn::make('remaining')
110112
->label(__('Remaining'))
111-
->suffix(fn ($record) => $record->remaining ? (' ' . __('days')) : '')
113+
->suffix(fn($record) => $record->remaining ? (' ' . __('days')) : '')
112114
->sortable()
113115
->searchable(),
114116

@@ -133,8 +135,34 @@ public static function table(Table $table): Table
133135
->button()
134136
->icon('heroicon-o-play')
135137
->action(function ($record) {
136-
$record->started_at = now();
138+
$now = now();
139+
Sprint::where('project_id', $record->project_id)
140+
->where('id', '<>', $record->id)
141+
->whereNotNull('started_at')
142+
->whereNull('ended_at')
143+
->update(['ended_at' => $now]);
144+
$record->started_at = $now;
137145
$record->save();
146+
Notification::make('sprint_started')
147+
->success()
148+
->body(__('Sprint started at') . ' ' . $now)
149+
->actions([
150+
Action::make('board')
151+
->color('secondary')
152+
->button()
153+
->label(
154+
fn ()
155+
=> ($record->project->type === 'scrum' ? __('Scrum board') : __('Kanban board'))
156+
)
157+
->url(function () use ($record) {
158+
if ($record->project->type === 'scrum') {
159+
return route('filament.pages.scrum/{project}', ['project' => $record->project->id]);
160+
} else {
161+
return route('filament.pages.kanban/{project}', ['project' => $record->project->id]);
162+
}
163+
}),
164+
])
165+
->send();
138166
}),
139167

140168
Tables\Actions\Action::make('stop')
@@ -145,8 +173,14 @@ public static function table(Table $table): Table
145173
->button()
146174
->icon('heroicon-o-pause')
147175
->action(function ($record) {
148-
$record->ended_at = now();
176+
$now = now();
177+
$record->ended_at = $now;
149178
$record->save();
179+
180+
Notification::make('sprint_started')
181+
->success()
182+
->body(__('Sprint ended at') . ' ' . $now)
183+
->send();
150184
}),
151185

152186
Tables\Actions\Action::make('tickets')

app/Models/Project.php

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,4 +105,31 @@ public function cover(): Attribute
105105
'https://ui-avatars.com/api/?background=3f84f3&color=ffffff&name=' . $this->name
106106
);
107107
}
108+
109+
public function currentSprint(): Attribute
110+
{
111+
return new Attribute(
112+
get: fn() => $this->sprints()
113+
->whereNotNull('started_at')
114+
->whereNull('ended_at')
115+
->first()
116+
);
117+
}
118+
119+
public function nextSprint(): Attribute
120+
{
121+
return new Attribute(
122+
get: function () {
123+
if ($this->currentSprint) {
124+
return $this->sprints()
125+
->whereNull('started_at')
126+
->whereNull('ended_at')
127+
->where('starts_at', '>=', $this->currentSprint->ends_at)
128+
->orderBy('starts_at')
129+
->first();
130+
}
131+
return null;
132+
}
133+
);
134+
}
108135
}

app/Models/Sprint.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ public function remaining(): Attribute
6262
return new Attribute(
6363
get: function () {
6464
if ($this->starts_at && $this->ends_at && $this->started_at && !$this->ended_at) {
65-
return $this->ends_at->diffInDays(now());
65+
return $this->ends_at->diffInDays(now()) + 1;
6666
}
6767
return null;
6868
}

lang/fr.json

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -218,5 +218,17 @@
218218
"Choose tickets to associate to this sprint": "Choisissez les tickets à associer à ce sprint",
219219
"Tickets associated with sprint": "Tickets associés au sprint",
220220
"Associated tickets": "Tickets associés",
221-
"days": "jours"
221+
"days": "jours",
222+
"In this section you can choose one of your projects to show it's Scrum or Kanban board": "Dans cette section vous pouvez choisir l'un de vos projets pour afficher son tableau Scrum ou Kanban",
223+
"No active sprint for this project!": "Aucun sprint actif pour ce projet !",
224+
"Manage sprints": "Gérer les sprints",
225+
"Click the below button to manage project's sprints": "Cliquez sur le bouton ci-dessous pour gérer les sprints du projet",
226+
"Remaining": "Restant",
227+
"Remaining:": "Restant :",
228+
"If you think a sprint should be started, please contact an administrator": "Si vous pensez qu'un sprint doit être en cours, veuillez contacter un administrateur",
229+
"in": "dans",
230+
"Next sprint:": "Sprint suivant :",
231+
"Sprint ended at": "Sprint terminé le",
232+
"Sprint started at": "Sprint commencé le",
233+
"Started at:": "Commencé le :"
222234
}

resources/css/filament.scss

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,6 @@
66

77
.filament-header-heading {
88
display: block;
9-
text-transform: lowercase;
10-
}
11-
12-
.filament-header-heading:first-letter {
13-
text-transform: uppercase;
149
}
1510

1611
.ticket-comment {

resources/css/kanban.scss

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,17 @@
4040
}
4141

4242
.record-title {
43-
@apply text-base text-gray-600 hover:cursor-pointer hover:underline;
43+
@apply text-base text-gray-600 hover:cursor-pointer;
44+
45+
.code {
46+
@apply text-xs font-medium text-gray-400;
47+
}
48+
49+
&:hover {
50+
.title {
51+
@apply underline;
52+
}
53+
}
4454
}
4555
}
4656

@@ -49,10 +59,6 @@
4959

5060
.record-type-code {
5161
@apply flex items-center gap-2;
52-
53-
span {
54-
@apply text-xs font-medium text-gray-400;
55-
}
5662
}
5763

5864
.avatar {

0 commit comments

Comments
 (0)