Skip to content

Commit 08cf86f

Browse files
Schedule detail: Introduce Timescale
1 parent 7cc3eb0 commit 08cf86f

File tree

3 files changed

+148
-1
lines changed

3 files changed

+148
-1
lines changed
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
<?php
2+
3+
/* Icinga Notifications Web | (c) 2025 Icinga GmbH | GPLv2 */
4+
5+
namespace Icinga\Module\Notifications\Widget\TimeGrid;
6+
7+
use DateTime;
8+
use IntlDateFormatter;
9+
use ipl\Html\Attributes;
10+
use ipl\Html\BaseHtmlElement;
11+
use ipl\Html\HtmlElement;
12+
use ipl\Html\Text;
13+
use ipl\I18n\Translation;
14+
use ipl\Web\Style;
15+
use Locale;
16+
17+
class Timescale extends BaseHtmlElement
18+
{
19+
use Translation;
20+
21+
protected $tag = 'div';
22+
23+
protected $defaultAttributes = ['class' => 'timescale'];
24+
25+
/** @var int The number of days shown */
26+
protected $days;
27+
28+
/** @var Style */
29+
protected $style;
30+
31+
/**
32+
* Create a new Timescale
33+
*
34+
* @param int $days
35+
* @param Style $style
36+
*/
37+
public function __construct(int $days, Style $style)
38+
{
39+
$this->days = $days;
40+
$this->style = $style;
41+
}
42+
43+
public function assemble(): void
44+
{
45+
switch (true) {
46+
case $this->days === 1:
47+
$timestampPerDay = 24;
48+
break;
49+
case $this->days <= 7:
50+
$timestampPerDay = 3;
51+
break;
52+
case $this->days <= 14:
53+
$timestampPerDay = 2;
54+
break;
55+
default:
56+
$timestampPerDay = 1;
57+
}
58+
59+
$this->style->addFor($this, ['--timestampsPerDay' => $timestampPerDay]);
60+
61+
$dateFormatter = new IntlDateFormatter(
62+
Locale::getDefault(),
63+
IntlDateFormatter::NONE,
64+
IntlDateFormatter::SHORT
65+
);
66+
67+
$timeIntervals = 24 / $timestampPerDay;
68+
69+
$time = new DateTime();
70+
$dayTimestamps = [];
71+
for ($i = 0; $i < $timestampPerDay; $i++) {
72+
// am-pm is separated by non-breaking whitespace
73+
$parts = preg_split('/\s/u', $dateFormatter->format($time->setTime($i * $timeIntervals, 0)));
74+
75+
$dayTimestamps[$i] = new HtmlElement('span', new Attributes(['class' => 'timestamp']), new Text($parts[0]));
76+
77+
if (isset($parts[1])) {
78+
$dayTimestamps[$i]->addHtml(
79+
new HtmlElement('span', new Attributes(['class' => 'am-pm']), new Text($parts[1]))
80+
);
81+
}
82+
}
83+
84+
$allTimestamps = array_merge(...array_fill(0, $this->days, $dayTimestamps));
85+
86+
$lastPosition = count($allTimestamps) - 1;
87+
88+
$fakeLast = clone $allTimestamps[$lastPosition];
89+
$allTimestamps[$lastPosition] = $fakeLast->addAttributes(['class' => 'last']);
90+
// added the class to add them into the same cell
91+
$first = clone $allTimestamps[0];
92+
$allTimestamps[] = $first->addAttributes(['class' => 'midnight']);
93+
94+
$this->addHtml(...$allTimestamps);
95+
}
96+
}

library/Notifications/Widget/Timeline.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
use Icinga\Module\Notifications\Widget\TimeGrid\DynamicGrid;
1212
use Icinga\Module\Notifications\Widget\TimeGrid\EntryProvider;
1313
use Icinga\Module\Notifications\Widget\TimeGrid\GridStep;
14+
use Icinga\Module\Notifications\Widget\TimeGrid\Timescale;
1415
use Icinga\Module\Notifications\Widget\Timeline\Entry;
1516
use Icinga\Module\Notifications\Widget\Timeline\MinimalGrid;
1617
use Icinga\Module\Notifications\Widget\Timeline\Rotation;
@@ -312,6 +313,9 @@ protected function assemble()
312313
Text::create($this->translate('Result'))
313314
)
314315
);
316+
317+
$this->getGrid()
318+
->addHtml(new Timescale($this->days, $this->getStyle()));
315319
}
316320

317321
$this->addHtml(

public/css/timeline.less

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
--stepRowHeight: 4em;
1010
--primaryRowHeight: 4em;
1111
position: relative;
12+
margin-right: 1em; // make midnight timestamp visible
1213

1314
.time-grid-header {
1415
box-sizing: border-box;
@@ -51,10 +52,48 @@
5152
display: block;
5253
border-top: 1px solid black;
5354
position: absolute;
54-
bottom: var(--stepRowHeight);
55+
bottom: ~"calc(var(--stepRowHeight) + 3em)"; // 3em .timescale height
5556
right: 0;
5657
left: 0;
5758
}
59+
60+
.timescale {
61+
display: grid;
62+
grid-template-columns: repeat(~"calc(var(--primaryColumns) * var(--timestampsPerDay))", 1fr);
63+
border-left: 1px solid transparent; // this is required to maintain the grid layout, otherwise timestamp::before is not positioned correctly
64+
grid-area: ~"4 / 2 / 4 / 3";
65+
height: 3em;
66+
67+
.timestamp {
68+
text-align: center;
69+
position: relative;
70+
transform: translateX(~"calc(-50% - 1px)"); // 1px border-left of .timescale
71+
padding-top: 0.5em;
72+
font-size: .75em;
73+
74+
&.last,
75+
&.midnight {
76+
grid-area: ~"1 / -2 / 2 / end";
77+
}
78+
79+
&.midnight {
80+
transform: translateX(~"calc(50% - 1px)"); // 1px border-left of .timescale
81+
}
82+
83+
.am-pm {
84+
display: block;
85+
}
86+
87+
&:after {
88+
content: '';
89+
position: absolute;
90+
top: 0;
91+
left: 50%;
92+
width: 1px;
93+
height: 0.5em; // padding of .timestamp
94+
}
95+
}
96+
}
5897
}
5998
}
6099

@@ -103,6 +142,14 @@
103142
font-size: .75em;
104143
opacity: .8;
105144
}
145+
146+
.timescale .timestamp {
147+
color: @gray-semilight;
148+
149+
&:after {
150+
background: @gray-lighter;
151+
}
152+
}
106153
}
107154

108155
.timeline.minimal-layout .empty-notice {

0 commit comments

Comments
 (0)