Skip to content

Commit

Permalink
Show additional daily appointments link (#124)
Browse files Browse the repository at this point in the history
fixes #109
  • Loading branch information
nilmerg authored Oct 19, 2023
2 parents 1be772f + 68ea489 commit 93bca74
Show file tree
Hide file tree
Showing 11 changed files with 407 additions and 51 deletions.
2 changes: 1 addition & 1 deletion library/Notifications/Model/Timeperiod.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public function createRelations(Relations $relations)
$relations->hasOne('parent', Schedule::class)
->setCandidateKey('owned_by_schedule_id')
->setJoinType('LEFT');
$relations->hasMany('entry', TimeperiodEntry::class)
$relations->hasMany('timeperiod_entry', TimeperiodEntry::class)
->setJoinType('LEFT');
}
}
34 changes: 31 additions & 3 deletions library/Notifications/Widget/Calendar.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use DateTime;
use Icinga\Module\Notifications\Widget\Calendar\BaseGrid;
use Icinga\Module\Notifications\Widget\Calendar\Controls;
use Icinga\Module\Notifications\Widget\Calendar\DayGrid;
use Icinga\Module\Notifications\Widget\Calendar\Entry;
use Icinga\Module\Notifications\Widget\Calendar\MonthGrid;
use Icinga\Module\Notifications\Widget\Calendar\Util;
Expand All @@ -31,6 +32,9 @@ class Calendar extends BaseHtmlElement
/** @var string Mode to show a specific calendar week */
public const MODE_WEEK = 'week';

/** @var string Mode to show only the day */
public const MODE_DAY = 'day';

protected $tag = 'div';

protected $defaultAttributes = ['class' => 'calendar'];
Expand All @@ -47,6 +51,9 @@ class Calendar extends BaseHtmlElement
/** @var Url */
protected $addEntryUrl;

/** @var Url */
protected $url;

public function setControls(Controls $controls): self
{
$this->controls = $controls;
Expand Down Expand Up @@ -75,6 +82,22 @@ public function getAddEntryUrl(): ?Url
return $this->addEntryUrl;
}

public function setUrl(?Url $url): self
{
$this->url = $url;

return $this;
}

public function prepareDayViewUrl(DateTime $date): Url
{
$url = clone $this->url;
return $url->overwriteParams([
'mode' => 'day',
'day' => $date->format('Y-m-d')
]);
}

protected function getModeStart(): DateTime
{
switch ($this->getControls()->getViewMode()) {
Expand All @@ -83,10 +106,13 @@ protected function getModeStart(): DateTime

return DateTime::createFromFormat('Y-m-d\TH:i:s', $month . '-01T00:00:00');
case self::MODE_WEEK:
default:
$week = $this->getControls()->getValue('week') ?: (new DateTime())->format('Y-\WW');

return (new DateTime())->setTimestamp(strtotime($week));
default:
$day = $this->getControls()->getValue('day') ?: (new DateTime())->format('Y-m-d');

return DateTime::createFromFormat('Y-m-d H:i:s', $day . ' 00:00:00');
}
}

Expand All @@ -95,8 +121,10 @@ public function getGrid(): BaseGrid
if ($this->grid === null) {
if ($this->getControls()->getViewMode() === self::MODE_MONTH) {
$this->grid = new MonthGrid($this, $this->getModeStart());
} else { // $mode === self::MODE_WEEK
} elseif ($this->getControls()->getViewMode() === self::MODE_WEEK) {
$this->grid = new WeekGrid($this, $this->getModeStart());
} else {
$this->grid = new DayGrid($this, $this->getModeStart());
}
}

Expand Down Expand Up @@ -124,7 +152,7 @@ public function getEntries(): Traversable
$length = $start->diff($end);

$visibleHours = Util::diffHours($start, $grid->getGridEnd());
$limit = (int) floor($visibleHours / (Util::diffHours($start, $end) ?: 0.5));
$limit = (int) ceil($visibleHours / (Util::diffHours($start, $end) ?: 0.5));
if ($limit > $visibleHours) {
$limit = $visibleHours;
}
Expand Down
47 changes: 42 additions & 5 deletions library/Notifications/Widget/Calendar/BaseGrid.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace Icinga\Module\Notifications\Widget\Calendar;

use DateInterval;
use DateTime;
use DateTimeInterface;
use Icinga\Module\Notifications\Widget\Calendar;
Expand Down Expand Up @@ -35,6 +36,9 @@ abstract class BaseGrid extends BaseHtmlElement
/** @var DateTime */
protected $end;

/** @var array Extra counts stored as [date1 => count1, date2 => count2]*/
protected $extraEntriesCount = [];

/**
* Create a new calendar
*
Expand Down Expand Up @@ -137,6 +141,18 @@ protected function createGridOverlay(): BaseHtmlElement
return $overlay;
}

/**
* Fetch the count of additional entries for the given date
*
* @param DateTime $date
*
* @return int
*/
public function getExtraEntryCount(DateTime $date): int
{
return $this->extraEntriesCount[$date->format('Y-m-d')] ?? 0;
}

protected function assembleGridOverlay(BaseHtmlElement $overlay): void
{
$style = (new Style())->setNonce(Csp::getStyleNonce());
Expand Down Expand Up @@ -218,21 +234,42 @@ protected function assembleGridOverlay(BaseHtmlElement $overlay): void
}
}

$this->extraEntriesCount = [];
foreach ($occupiedCells as $entry) {
$continuation = false;
$rows = $occupiedCells->getInfo();
foreach ($rows as $row => $hours) {
list($rowStart, $rowSpan) = $rowPlacements[spl_object_id($entry)][$row];
$colStart = min($hours);
$colEnd = max($hours);

// Calculate number of entries that are not displayed in the grid for each date
if ($rowStart > $row + $sectionsPerStep) {
// TODO: Register as +1
$startOffset = (int) (($row / $sectionsPerStep) * ($gridBorderAt / 48) + $colStart / 48);
$endOffset = (int) (($row / $sectionsPerStep) * ($gridBorderAt / 48) + $colEnd / 48);
$startDate = (clone $this->getGridStart())->add(new DateInterval("P$startOffset" . 'D'));
$duration = $endOffset - $startOffset;
for ($i = 0; $i <= $duration; $i++) {
$countIdx = $startDate->format('Y-m-d');
if (! isset($this->extraEntriesCount[$countIdx])) {
$this->extraEntriesCount[$countIdx] = 1;
} else {
$this->extraEntriesCount[$countIdx] += 1;
}

$startDate->add(new DateInterval('P1D'));
}

continue;
}

$rowEnd = $rowStart + $rowSpan;
$colStart = min($hours) + 1;
$colEnd = max($hours) + 2;
$gridArea = $this->getGridArea(
$rowStart,
$rowStart + $rowSpan,
$colStart + 1,
$colEnd + 2
);

$gridArea = $this->getGridArea($rowStart, $rowEnd, $colStart, $colEnd);
$entryClass = 'area-' . implode('-', $gridArea);

$style->add(".$entryClass", [
Expand Down
10 changes: 9 additions & 1 deletion library/Notifications/Widget/Calendar/Controls.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,18 +37,26 @@ protected function assemble()
]);
break;
case Calendar::MODE_WEEK:
default:
$this->addElement('input', 'week', [
'class' => 'autosubmit',
'type' => 'week',
'value' => (new DateTime())->format('Y-\WW'),
'label' => $this->translate('Calendar Week')
]);
break;
default:
$this->addElement('input', 'day', [
'class' => 'autosubmit',
'type' => 'date',
'value' => (new DateTime())->format('Y-m-d'),
'label' => $this->translate('Date')
]);
break;
}

$modeParam = 'mode';
$options = [
Calendar::MODE_DAY => $this->translate('Day'),
Calendar::MODE_WEEK => $this->translate('Week'),
Calendar::MODE_MONTH => $this->translate('Month')
];
Expand Down
125 changes: 125 additions & 0 deletions library/Notifications/Widget/Calendar/DayGrid.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
<?php

/* Icinga Notifications Web | (c) 2023 Icinga GmbH | GPLv2 */

namespace Icinga\Module\Notifications\Widget\Calendar;

use DateInterval;
use DateTime;
use InvalidArgumentException;
use ipl\Html\Attributes;
use ipl\Html\BaseHtmlElement;
use ipl\Html\HtmlElement;
use ipl\Html\Text;
use Traversable;

class DayGrid extends BaseGrid
{
public function setGridStart(DateTime $start): BaseGrid
{
if ($start->format('H:i:s') !== '00:00:00') {
throw new InvalidArgumentException('Start is not midnight');
}

return parent::setGridStart($start);
}

protected function getMaximumRowSpan(): int
{
return 28;
}

protected function calculateGridEnd(): DateTime
{
return (clone $this->getGridStart())->add(new DateInterval('P1D'));
}

protected function getNoOfVisuallyConnectedHours(): int
{
return 24;
}

protected function getGridArea(int $rowStart, int $rowEnd, int $colStart, int $colEnd): array
{
return [$colStart, $rowStart, $colEnd, $rowEnd];
}

protected function createGridSteps(): Traversable
{
$interval = new DateInterval('P1D');
$hourStartsAt = clone $this->getGridStart();
for ($i = 0; $i < 24; $i++) {
yield $hourStartsAt;

$hourStartsAt->add($interval);
}
}

protected function createHeader(): BaseHtmlElement
{
$header = new HtmlElement('div', Attributes::create(['class' => 'header']));
$dayNames = [
'Mon' => t('Mon', 'monday'),
'Tue' => t('Tue', 'tuesday'),
'Wed' => t('Wed', 'wednesday'),
'Thu' => t('Thu', 'thursday'),
'Fri' => t('Fri', 'friday'),
'Sat' => t('Sat', 'saturday'),
'Sun' => t('Sun', 'sunday')
];

$currentDay = clone $this->getGridStart();
$interval = new DateInterval('P1D');
$header->addHtml(new HtmlElement(
'div',
Attributes::create(['class' => 'column-title']),
new HtmlElement(
'span',
Attributes::create(['class' => 'day-name']),
Text::create($dayNames[$currentDay->format('D')])
),
new HtmlElement(
'span',
Attributes::create(['class' => 'day-number']),
Text::create($currentDay->format('d'))
)
));

return $header;
}

protected function createSidebar(): BaseHtmlElement
{
$sidebar = new HtmlElement('div', Attributes::create(['class' => 'sidebar']));

$time = (new DateTime())->setTime(0, 0);
$interval = new DateInterval('PT1H');
for ($i = 0; $i < 24; $i++) {
$sidebar->addHtml(new HtmlElement(
'div',
Attributes::create(['class' => 'row-title']),
new HtmlElement(
'span',
Attributes::create(['class' => 'hour']),
Text::create($time->format('H:i'))
)
));

$time->add($interval);
}

return $sidebar;
}

protected function assemble()
{
$this->getAttributes()->add('class', 'day');

$this->addHtml(
$this->createHeader(),
$this->createSidebar(),
$this->createGrid(),
$this->createGridOverlay()
);
}
}
Loading

0 comments on commit 93bca74

Please sign in to comment.