diff --git a/library/Icingadb/Widget/ItemList/HistoryObjectList.php b/library/Icingadb/Widget/ItemList/HistoryObjectList.php new file mode 100644 index 00000000..7dd4173f --- /dev/null +++ b/library/Icingadb/Widget/ItemList/HistoryObjectList.php @@ -0,0 +1,96 @@ + +// SPDX-License-Identifier: GPL-3.0-or-later + +namespace Icinga\Module\Icingadb\Widget\ItemList; + +use Icinga\Exception\NotImplementedError; +use Icinga\Module\Icingadb\Common\LoadMore; +use Icinga\Module\Icingadb\Model\History; +use Icinga\Module\Icingadb\Model\NotificationHistory; +use Icinga\Module\Icingadb\View\EventRenderer; +use Icinga\Module\Icingadb\View\NotificationRenderer; +use IntlDateFormatter; +use ipl\Html\Attributes; +use ipl\Html\HtmlDocument; +use ipl\Html\HtmlElement; +use ipl\Html\Text; +use ipl\Orm\Model; +use ipl\Orm\ResultSet; +use ipl\Web\Widget\ItemList; +use Locale; + +/** + * HistoryObjectList + * + * @template Item of NotificationHistory|History + * + * @extends ObjectList + */ +class HistoryObjectList extends ObjectList +{ + use LoadMore; + + /** @var ?int The timestamp of the recently added list item, used to detect day changes */ + protected ?int $previousTimeStamp = null; + + /** + * Create a list of History or NotificationHistory objects with a Load more link and add a separator between days. + * + * @param ResultSet $data + * @param ?int $previousTimeStamp The timestamp of the recently added list item, used to detect day changes + * @param bool $useRelativeTimestamps + */ + public function __construct(ResultSet $data, ?int $previousTimeStamp = null, bool $useRelativeTimestamps = false) + { + ItemList::__construct($data, function (Model $item) use ($useRelativeTimestamps) { + if ($item instanceof NotificationHistory) { + return new NotificationRenderer($useRelativeTimestamps); + } elseif ($item instanceof History) { + return new EventRenderer($useRelativeTimestamps); + } + + throw new NotImplementedError('Not implemented'); + }); + + $this->data = $this->getIterator($data); + $this->previousTimeStamp = $previousTimeStamp; + } + + protected function init(): void + { + parent::init(); + $formatter = new IntlDateFormatter( + Locale::getDefault(), + IntlDateFormatter::MEDIUM, + IntlDateFormatter::NONE + ); + + $this->on(ItemList::BEFORE_ITEM_ADD, function ($item, $data) use ($formatter) { + if ($data instanceof NotificationHistory) { + $timestamp = $data->send_time->getTimestamp(); + } else { + $timestamp = $data->event_time->getTimestamp(); + } + + if ( + $this->previousTimeStamp === null + || $formatter->format($this->previousTimeStamp) !== $formatter->format($timestamp) + ) { + $this->addHtml(new HtmlElement( + 'li', + new Attributes(['class' => ['day-separator']]), + new Text($formatter->format($timestamp)) + )); + } + + $this->previousTimeStamp = $timestamp; + }); + + $this->on( + HtmlDocument::ON_ASSEMBLED, + fn() => $this->loadMoreUrl->setParam('last-entry', $this->previousTimeStamp) + ); + } +} diff --git a/library/Icingadb/Widget/ItemList/LoadMoreObjectList.php b/library/Icingadb/Widget/ItemList/LoadMoreObjectList.php deleted file mode 100644 index 4d788c59..00000000 --- a/library/Icingadb/Widget/ItemList/LoadMoreObjectList.php +++ /dev/null @@ -1,45 +0,0 @@ - -// SPDX-License-Identifier: GPL-3.0-or-later - -namespace Icinga\Module\Icingadb\Widget\ItemList; - -use Icinga\Exception\NotImplementedError; -use Icinga\Module\Icingadb\Common\LoadMore; -use Icinga\Module\Icingadb\Model\History; -use Icinga\Module\Icingadb\Model\NotificationHistory; -use Icinga\Module\Icingadb\View\EventRenderer; -use Icinga\Module\Icingadb\View\NotificationRenderer; -use ipl\Orm\Model; -use ipl\Orm\ResultSet; -use ipl\Web\Widget\ItemList; - -/** - * LoadMoreObjectList - * - * Create a list of icingadb objects with Load more link - * - * @template Item of NotificationHistory|History - * - * @extends ObjectList - */ -class LoadMoreObjectList extends ObjectList -{ - use LoadMore; - - public function __construct(ResultSet $data) - { - ItemList::__construct($data, function (Model $item) { - if ($item instanceof NotificationHistory) { - return new NotificationRenderer(); - } elseif ($item instanceof History) { - return new EventRenderer(); - } - - throw new NotImplementedError('Not implemented'); - }); - - $this->data = $this->getIterator($data); - } -} diff --git a/public/css/list/item-list.less b/public/css/list/item-list.less index a92a7f13..b13b9d4d 100644 --- a/public/css/list/item-list.less +++ b/public/css/list/item-list.less @@ -42,6 +42,21 @@ > .page-separator + .list-item .main { border-top: none; } + + .interactive-time { + &:hover { + .rounded-corners(1em); + background: @gray-light; + } + + .user-select(none); + padding-left: 0.5em; + padding-right: 0.5em; + } + + .day-separator { + background-color: @gray-lighter; + } } // Layout @@ -80,4 +95,10 @@ } } } + + .day-separator { + display: flex; + justify-content: end; + padding-right: 1.5em; + } } diff --git a/public/js/action-list.js b/public/js/action-list.js index 8a47efc5..c44f4078 100644 --- a/public/js/action-list.js +++ b/public/js/action-list.js @@ -317,7 +317,9 @@ } _this.clearSelection(activeItems); - if (toActiveItem.classList.contains('page-separator')) { + if (toActiveItem.classList.contains('page-separator') + || toActiveItem.classList.contains('day-separator') + ) { toActiveItem = _this.getDirectionalNext(toActiveItem, event.key); } }