Skip to content

Getting started with schedules #305

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 0 additions & 5 deletions application/controllers/ScheduleController.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,6 @@ public function indexAction(): void
null,
Links::scheduleSettings($id),
'cog'
))->openInModal(),
(new ButtonLink(
$this->translate('Add Rotation'),
Links::rotationAdd($id),
'plus'
))->openInModal()
);

Expand Down
43 changes: 40 additions & 3 deletions library/Notifications/Widget/TimeGrid/BaseGrid.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,15 @@
use DateInterval;
use DateTime;
use Generator;
use Icinga\Module\Notifications\Common\Links;
use ipl\Html\Attributes;
use ipl\Html\BaseHtmlElement;
use ipl\Html\HtmlElement;
use ipl\Html\Text;
use ipl\I18n\Translation;
use ipl\Web\Style;
use ipl\Web\Url;
use ipl\Web\Widget\ButtonLink;
use ipl\Web\Widget\Link;
use LogicException;
use SplObjectStorage;
Expand Down Expand Up @@ -470,8 +474,8 @@ final protected function yieldFixedEntries(Traversable $entries): Generator
yield $gridArea => $entry;
}

$this->style->addFor($this, [
'--primaryRows' => $lastRow === 1 ? 1 : $lastRow - $rowStartModifier + 1,
$this->style->addFor($this, [ // +1 to create extra row for the `add rotation` button
'--primaryRows' => $lastRow === 1 ? 1 : $lastRow - $rowStartModifier + 1 + 1,
'--rowsPerStep' => 1
]);
}
Expand All @@ -490,11 +494,44 @@ protected function assembleGridOverlay(BaseHtmlElement $overlay): void
$generator = $this->yieldFixedEntries($entries);
}

$addButtonCreated = false;
foreach ($generator as $gridArea => $entry) {
[$rowStart, $colStart, $rowEnd, $colEnd] = $gridArea;

if (! $addButtonCreated && $entry->getAttributes()->has('data-rotation-position')) {
$btn = new HtmlElement('div', new Attributes(['class' => 'btn-container']));

$btn->addHtml(
(new ButtonLink(
$this->translate('Add another Rotation'),
Links::rotationAdd(Url::fromRequest()->getParam('id')),
'plus'
))->openInModal(),
new HtmlElement(
'span',
new Attributes(['class' => 'hint']),
new Text($this->translate('to override rotations above'))
)
);

// take up the entire row
$this->style->addFor($btn, [
'grid-area' => sprintf('~"%d / %d / %d / %d"', $rowStart, 1, $rowEnd, -1)
]);

$overlay->addHtml($btn);
$addButtonCreated = true;
}

if ($addButtonCreated) { // result row must be below
$rowStart++;
$rowEnd++;
}

$this->style->addFor($entry, [
'--entry-bg' => $entry->getColor(10),
'--entry-border-color' => $entry->getColor(50),
'grid-area' => sprintf('~"%d / %d / %d / %d"', ...$gridArea)
'grid-area' => sprintf('~"%d / %d / %d / %d"', $rowStart, $colStart, $rowEnd, $colEnd)
]);

$overlay->addHtml($entry);
Expand Down
61 changes: 45 additions & 16 deletions library/Notifications/Widget/Timeline.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
use ipl\I18n\Translation;
use ipl\Web\Style;
use ipl\Web\Url;
use ipl\Web\Widget\ButtonLink;
use ipl\Web\Widget\Icon;
use ipl\Web\Widget\Link;
use Locale;
Expand Down Expand Up @@ -254,7 +255,7 @@ protected function getGrid()
$this->grid = (new DynamicGrid($this, $this->getStyle(), $this->start))->setDays($this->days);
}

if (! $this->minimalLayout) {
if (! $this->minimalLayout && $this->rotations) {
$rotations = $this->rotations;
usort($rotations, function (Rotation $a, Rotation $b) {
return $b->getPriority() <=> $a->getPriority();
Expand All @@ -266,6 +267,9 @@ protected function getGrid()
$this->grid->addToSideBar($this->assembleSidebarEntry($rotation));
}
}

// placeholder for new add button row
$this->grid->addToSideBar(new HtmlElement('div', new Attributes(['class' => 'placeholder'])));
}
}

Expand Down Expand Up @@ -303,21 +307,6 @@ protected function assembleSidebarEntry(Rotation $rotation): BaseHtmlElement

protected function assemble()
{
if (empty($this->rotations)) {
$emptyNotice = new HtmlElement(
'div',
Attributes::create(['class' => 'empty-notice']),
Text::create($this->translate('No rotations configured'))
);

if ($this->minimalLayout) {
$this->getAttributes()->add(['class' => 'minimal-layout']);
$this->addHtml($emptyNotice);
} else {
$this->getGrid()->addToSideBar($emptyNotice);
}
}

if (! $this->minimalLayout) {
$this->getGrid()->addToSideBar(
new HtmlElement(
Expand Down Expand Up @@ -364,6 +353,46 @@ protected function assemble()
->addHtml($clock);
}

if (! $this->rotations) {
$emptyNotice = new HtmlElement(
'div',
Attributes::create(['class' => 'empty-notice']),
Text::create($this->translate('No rotations configured, yet.'))
);

if ($this->minimalLayout) {
$this->getAttributes()->add(['class' => 'minimal-layout']);
$this->addHtml($emptyNotice);
} else {
$this->addHtml(new HtmlElement(
'div',
new Attributes(['class' => 'empty-state-notice']),
new Icon('info-circle'),
new HtmlElement(
'span',
null,
new Text($this->translate(
'With schedules Contacts can rotate in recurring shifts. You can add'
. ' multiple rotation layers to a schedule.'
))
)
));

$this->getGrid()
->addAttributes(['class' => 'empty'])
->addHtml(new HtmlElement(
'div',
new Attributes(['class' => 'btn-container']),
$emptyNotice,
(new ButtonLink(
$this->translate('Add your first Rotation'),
Links::rotationAdd(Url::fromRequest()->getParam('id')),
'plus'
))->openInModal()
));
}
}

$this->addHtml(
$this->getGrid(),
$this->getStyle()
Expand Down
2 changes: 1 addition & 1 deletion public/css/calendar.less
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@
.grid,
.overlay {
display: grid;
overflow: hidden;
// overflow: hidden; //TODO(sd): is this required?
grid-template-rows: repeat(~"calc(var(--primaryRows) * var(--rowsPerStep))", var(--stepRowHeight));
grid-template-columns: repeat(~"calc(var(--primaryColumns) * var(--columnsPerStep))", minmax(var(--minimumStepColumnWidth), 1fr));
border-width: 1px 0 0 1px;
Expand Down
26 changes: 15 additions & 11 deletions public/css/schedule.less
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,6 @@
h2 {
display: inline;
}

> a:last-of-type {
float: right;
}
}

.schedule-detail {
Expand Down Expand Up @@ -58,12 +54,20 @@

/* Design */

.schedule-detail .entry.highlighted {
outline: 2px solid var(--entry-border-color);
outline-offset: 1px;
}
.schedule {
.entry.highlighted {
outline: 2px solid var(--entry-border-color);
outline-offset: 1px;
}

.schedule-detail .step.highlighted {
background-color: @gray-lighter;
border-color: @gray-light;
.sidebar .row-title.highlighted,
.step.highlighted {
background-color: @gray-lighter;
border-color: @gray-light;
}

.sidebar .row-title.highlighted {
margin-top: -1px; // cover the border-top area
padding-top: 1px;
}
}
108 changes: 105 additions & 3 deletions public/css/timeline.less
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,27 @@
flex-direction: column;
overflow: hidden;

.empty-state-notice {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1em;
margin-bottom: 2em;
border: 1px solid @gray-light;
.rounded-corners();
color: @text-color-light;

.icon {
font-size: 1.75em;
}

> span {
padding-left: 0.5em;
flex-grow: 1;
text-align: center;
}
}

.time-grid {
--sidebarWidth: 12em;
--stepRowHeight: 4em;
Expand Down Expand Up @@ -44,6 +65,38 @@
}
}

.sidebar .placeholderXXX {
background-color: fade(@gray-light, 25%);
border-top: 1px solid @gray-lighter;
margin-top: -1px; // to cover the border-bottom area of the previous sibling
}

.btn-container {
display: flex;
justify-content: center;
align-items: center;
text-align: center;
height: var(--primaryRowHeight);
}

&:not(.empty) .btn-container {
margin-left: ~"calc(var(--sidebarWidth) * -1)"; // overlap sidebar's .placeholder
}

.overlay .btn-container {
pointer-events: all; // allow to click on the buttons

.button-link:not(:hover) {
color: @disabled-gray;
background: none;
}

.hint {
margin-left: .5em;
color: @text-color-light;
}
}

.overlay .entry {
margin-top: 1em;
margin-bottom: 1em;
Expand All @@ -68,8 +121,8 @@
position: absolute;
right: 0;
left: 0;
// -1 to exclude result row
top: ~"calc((var(--stepRowHeight) * calc(var(--primaryRows) - 1)) + var(--daysHeaderHeight))";
// -1 to exclude result row, max to have minimum 1 row
top: ~"calc(var(--stepRowHeight) * max(calc(var(--primaryRows) - 1), 1) + var(--daysHeaderHeight))";
}

.timescale {
Expand Down Expand Up @@ -204,6 +257,24 @@
}
}

.btn-container {
background-color: mix(@gray-light, @body-bg-color, 25%);

.empty-notice {
margin-right: 1em;
color: @text-color-light;
}

.button-link {
border: 1px solid;

&:hover {
color: @text-color-on-icinga-blue;
background-color: @icinga-blue;
}
}
}

.entry .icon {
font-size: .75em;
opacity: .8;
Expand Down Expand Up @@ -231,6 +302,31 @@
color: @text-color-light;
}

.time-grid.empty {
.btn-container {
grid-area: ~"3 / 1 / 3 / 3";
}

// .empty-state is placed under the .days-header, Everything below .empty-state element must slide down one row
// so the grid-row-start and grid-row-end is increased by 1
.sidebar {
grid-area: ~"4 / 1 / 5 / 2";
}

.grid,
.overlay {
grid-area: ~"4 / 2 / 5 / 3";
}

.timescale {
grid-area: ~"5 / 2 / 5 / 3";
}

.clock {
grid-area: ~"3 / 2 / 5 / 3";
}
}

#layout.twocols:not(.wide-layout) .days-header .column-title {
display: flex;
flex-direction: column;
Expand All @@ -239,6 +335,12 @@
padding-bottom: .25em;
}

#layout.twocols .schedule-detail .timescale:has(:nth-child(n+62)) { // month view (--timestampsPerDay * --primaryColumns = 62)
#layout.twocols .schedule .timescale:has(:nth-child(n+62)) { // month view (--timestampsPerDay * --primaryColumns = 62)
display: none;
}

@light-mode: {
.timeline .btn-container {
background-color: mix(#d0d3da, #F5F9FA, 25%); //@gray-light, @light-body-bg-color
}
};
Loading
Loading