Skip to content

Commit 3d735b0

Browse files
committed
ScheduleDetail: Show the add rotation button in the timeline
1 parent 6cb09d8 commit 3d735b0

File tree

7 files changed

+128
-25
lines changed

7 files changed

+128
-25
lines changed

application/controllers/ScheduleController.php

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,6 @@ public function indexAction(): void
4242
null,
4343
Links::scheduleSettings($id),
4444
'cog'
45-
))->openInModal(),
46-
(new ButtonLink(
47-
$this->translate('Add Rotation'),
48-
Links::rotationAdd($id),
49-
'plus'
5045
))->openInModal()
5146
);
5247

library/Notifications/View/ScheduleRenderer.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ public function assembleCaption($item, HtmlDocument $caption, string $layout): v
4343
{
4444
// Number of days is set to 7, since default mode for schedule is week
4545
// and the start day should be the current day
46-
$timeline = (new Timeline((new DateTime())->setTime(0, 0), 7))
46+
$timeline = (new Timeline($item->id, (new DateTime())->setTime(0, 0), 7))
4747
->minimalLayout()
4848
->setStyle(
4949
(new Style())

library/Notifications/Widget/Detail/ScheduleDetail.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,11 @@ protected function assembleTimeline(Timeline $timeline): void
6868
*/
6969
protected function createTimeline(): Timeline
7070
{
71-
$timeline = new Timeline($this->controls->getStartDate(), $this->controls->getNumberOfDays());
71+
$timeline = new Timeline(
72+
$this->schedule->id,
73+
$this->controls->getStartDate(),
74+
$this->controls->getNumberOfDays()
75+
);
7276
$timeline->setStyle(
7377
(new Style())
7478
->setNonce(Csp::getStyleNonce())

library/Notifications/Widget/Timeline.php

Lines changed: 50 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,15 @@
1414
use Icinga\Module\Notifications\Widget\TimeGrid\Timescale;
1515
use Icinga\Module\Notifications\Widget\TimeGrid\Util;
1616
use Icinga\Module\Notifications\Widget\Timeline\Entry;
17+
use Icinga\Module\Notifications\Widget\Timeline\FakeEntry;
1718
use Icinga\Module\Notifications\Widget\Timeline\FutureEntry;
1819
use Icinga\Module\Notifications\Widget\Timeline\MinimalGrid;
1920
use Icinga\Module\Notifications\Widget\Timeline\Rotation;
2021
use IntlDateFormatter;
2122
use ipl\Html\Attributes;
2223
use ipl\Html\BaseHtmlElement;
2324
use ipl\Html\HtmlElement;
25+
use ipl\Html\TemplateString;
2426
use ipl\Html\Text;
2527
use ipl\I18n\Translation;
2628
use ipl\Web\Style;
@@ -42,6 +44,9 @@ class Timeline extends BaseHtmlElement implements EntryProvider
4244
/** @var array<int, Rotation> */
4345
protected $rotations = [];
4446

47+
/** @var int */
48+
protected int $scheduleId;
49+
4550
/** @var DateTime */
4651
protected $start;
4752

@@ -88,11 +93,13 @@ public function getStyle(): Style
8893
/**
8994
* Create a new Timeline
9095
*
96+
* @param int $scheduleId The schedule ID
9197
* @param DateTime $start The day the grid should start on
9298
* @param int $days Number of days to show on the grid
9399
*/
94-
public function __construct(DateTime $start, int $days)
100+
public function __construct(int $scheduleId, DateTime $start, int $days)
95101
{
102+
$this->scheduleId = $scheduleId;
96103
$this->start = $start;
97104
$this->days = $days;
98105
}
@@ -194,6 +201,14 @@ public function getEntries(): Traversable
194201
}
195202
}
196203

204+
if (! $this->minimalLayout) {
205+
// Always yield a fake entry to reserve the position for the add-rotation button
206+
yield (new FakeEntry())
207+
->setPosition($resultPosition++)
208+
->setStart($this->getGrid()->getGridStart())
209+
->setEnd($this->getGrid()->getGridEnd());
210+
}
211+
197212
$entryToCellsMap = new SplObjectStorage();
198213
foreach ($occupiedCells as $cell => $entry) {
199214
$cells = $entryToCellsMap[$entry] ?? [];
@@ -313,22 +328,18 @@ protected function assembleSidebarEntry(Rotation $rotation): BaseHtmlElement
313328

314329
protected function assemble()
315330
{
316-
if (empty($this->rotations)) {
317-
$emptyNotice = new HtmlElement(
331+
if ($this->minimalLayout && empty($this->rotations)) {
332+
$this->addHtml(new HtmlElement(
318333
'div',
319334
Attributes::create(['class' => 'empty-notice']),
320335
Text::create($this->translate('No rotations configured'))
321-
);
322-
323-
if ($this->minimalLayout) {
324-
$this->getAttributes()->add(['class' => 'minimal-layout']);
325-
$this->addHtml($emptyNotice);
326-
} else {
327-
$this->getGrid()->addToSideBar($emptyNotice);
328-
}
336+
));
329337
}
330338

331339
if (! $this->minimalLayout) {
340+
// We yield a fake overlay entry, so we also have to fake a sidebar entry
341+
$this->getGrid()->addToSideBar(new HtmlElement('div'));
342+
332343
$this->getGrid()->addToSideBar(
333344
new HtmlElement(
334345
'div',
@@ -369,7 +380,35 @@ protected function assemble()
369380
new HtmlElement('div', new Attributes(['class' => 'current-day']), $currentTime)
370381
);
371382

383+
if (empty($this->rotations)) {
384+
$newRotationMsg = $this->translate(
385+
'No rotations configured, yet. {{#button}}Add your first Rotation{{/button}}'
386+
);
387+
} else {
388+
$newRotationMsg = $this->translate(
389+
'{{#button}}Add another Rotation{{/button}} to override rotations above'
390+
);
391+
}
392+
372393
$this->getGrid()
394+
->addHtml(new HtmlElement(
395+
'div',
396+
new Attributes(['class' => 'new-rotation-container']),
397+
new HtmlElement(
398+
'div',
399+
Attributes::create(['class' => 'new-rotation-content']),
400+
TemplateString::create(
401+
$newRotationMsg,
402+
[
403+
'button' => (new Link(
404+
new Icon('circle-plus'),
405+
Links::rotationAdd($this->scheduleId),
406+
['class' => empty($this->rotations) ? 'btn-primary' : null]
407+
))->openInModal()
408+
]
409+
)
410+
)
411+
))
373412
->addHtml(new Timescale($this->days, $this->getStyle()))
374413
->addHtml($clock);
375414
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
3+
/* Icinga Notifications Web | (c) 2025 Icinga GmbH | GPLv2 */
4+
5+
namespace Icinga\Module\Notifications\Widget\Timeline;
6+
7+
use Icinga\Module\Notifications\Widget\TimeGrid\Entry;
8+
use ipl\Html\BaseHtmlElement;
9+
10+
/**
11+
* @internal Reserved for internal use.
12+
*/
13+
final class FakeEntry extends Entry
14+
{
15+
public function __construct()
16+
{
17+
parent::__construct(0);
18+
}
19+
20+
public function getColor(int $transparency): string
21+
{
22+
return '';
23+
}
24+
25+
protected function assembleContainer(BaseHtmlElement $container): void
26+
{
27+
}
28+
29+
public function renderUnwrapped(): string
30+
{
31+
return '';
32+
}
33+
}

public/css/schedule.less

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,6 @@
88
h2 {
99
display: inline;
1010
}
11-
12-
> a:last-of-type {
13-
float: right;
14-
}
1511
}
1612

1713
.schedule-detail {
@@ -37,7 +33,7 @@
3733
.from-scratch-hint {
3834
display: flex;
3935
align-items: center;
40-
font-size: 1.25em;
36+
font-size: 14/12em;
4137

4238
i.icon {
4339
float: left;

public/css/timeline.less

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -198,10 +198,31 @@
198198
}
199199
}
200200
}
201+
202+
.new-rotation-container {
203+
pointer-events: none;
204+
display: flex;
205+
flex-direction: column;
206+
justify-content: flex-end;
207+
grid-area: ~"3 / 1 / 4 / 3";
208+
padding-bottom: var(--stepRowHeight);
209+
210+
.new-rotation-content {
211+
pointer-events: all;
212+
z-index: 2; // Grid gaps must not bleed through, in day mode the time-hand must be below the button
213+
height: var(--stepRowHeight);
214+
display: flex;
215+
align-items: baseline;
216+
justify-content: center;
217+
align-content: center;
218+
flex-wrap: wrap;
219+
gap: .417em;
220+
}
221+
}
201222
}
202223
}
203224

204-
.timeline.minimal-layout{
225+
.timeline:has(.empty-notice) {
205226
position: relative;
206227

207228
.empty-notice {
@@ -271,9 +292,24 @@
271292
color: red;
272293
.user-select(none);
273294
}
295+
296+
.new-rotation-content {
297+
background: @gray-lighter;
298+
color: @text-color-light;
299+
300+
a {
301+
&:not(.btn-primary) {
302+
.button(@gray-lighter, @text-color-light);
303+
}
304+
305+
&.btn-primary {
306+
.button(@gray-lighter);
307+
}
308+
}
309+
}
274310
}
275311

276-
.timeline.minimal-layout .empty-notice {
312+
.timeline .empty-notice {
277313
font-size: 1.25em;
278314
}
279315

0 commit comments

Comments
 (0)