From b8137fed84bb0a28945b6d306b181540db508517 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Bia=C5=82czak?= Date: Wed, 12 Mar 2025 07:24:09 +0100 Subject: [PATCH 01/94] IBX-9060: Added renderAllNotificationsPageAction and markNotificationAsUnreadAction --- .../Controller/AllNotificationsController.php | 66 +++++++++++++++++++ .../Controller/NotificationController.php | 60 +++++++++++------ src/bundle/Resources/config/routing.yaml | 17 +++++ .../config/services/controllers.yaml | 6 ++ .../account/notifications/list_all.html.twig | 60 +++++++++++++++++ .../account/notifications/list_item.html.twig | 1 + 6 files changed, 189 insertions(+), 21 deletions(-) create mode 100644 src/bundle/Controller/AllNotificationsController.php create mode 100644 src/bundle/Resources/views/themes/admin/account/notifications/list_all.html.twig diff --git a/src/bundle/Controller/AllNotificationsController.php b/src/bundle/Controller/AllNotificationsController.php new file mode 100644 index 0000000000..5ab9edfefb --- /dev/null +++ b/src/bundle/Controller/AllNotificationsController.php @@ -0,0 +1,66 @@ +notificationService = $notificationService; + $this->registry = $registry; + $this->configResolver = $configResolver; + } + public function renderAllNotificationsPageAction(Request $request, int $page): Response + { + $pagerfanta = new Pagerfanta( + new NotificationAdapter($this->notificationService) + ); + $pagerfanta->setMaxPerPage($this->configResolver->getParameter('pagination.notification_limit')); + $pagerfanta->setCurrentPage(min($page, $pagerfanta->getNbPages())); + + $notifications = []; + foreach ($pagerfanta->getCurrentPageResults() as $notification) { + if ($this->registry->hasRenderer($notification->type)) { + $renderer = $this->registry->getRenderer($notification->type); + $notifications[] = $renderer->render($notification); + } + } + + return $this->forward( + NotificationController::class . '::renderNotificationsPageAction', + [ + 'page' => $page, + 'notifications' => $notifications, + 'template' => '@ibexadesign/account/notifications/list_all.html.twig', + 'render_all' => true, + ] + ); + } +} diff --git a/src/bundle/Controller/NotificationController.php b/src/bundle/Controller/NotificationController.php index 5cfb12cbaa..61f3608049 100644 --- a/src/bundle/Controller/NotificationController.php +++ b/src/bundle/Controller/NotificationController.php @@ -47,13 +47,6 @@ public function __construct( $this->configResolver = $configResolver; } - /** - * @param \Symfony\Component\HttpFoundation\Request $request - * @param int $offset - * @param int $limit - * - * @return \Symfony\Component\HttpFoundation\JsonResponse - */ public function getNotificationsAction(Request $request, int $offset, int $limit): JsonResponse { $response = new JsonResponse(); @@ -75,12 +68,7 @@ public function getNotificationsAction(Request $request, int $offset, int $limit return $response; } - /** - * @param int $page - * - * @return \Symfony\Component\HttpFoundation\Response - */ - public function renderNotificationsPageAction(int $page): Response + public function renderNotificationsPageAction(Request $request, int $page): Response { $pagerfanta = new Pagerfanta( new NotificationAdapter($this->notificationService) @@ -95,6 +83,7 @@ public function renderNotificationsPageAction(int $page): Response $notifications .= $renderer->render($notification); } } + $notifications = $request->attributes->get('notifications', $notifications); $routeGenerator = function ($page) { return $this->generateUrl('ibexa.notifications.render.page', [ @@ -104,13 +93,15 @@ public function renderNotificationsPageAction(int $page): Response $pagination = (new EzPagerfantaView(new EzPagerfantaTemplate($this->translator)))->render($pagerfanta, $routeGenerator); - return new Response($this->render('@ibexadesign/account/notifications/list.html.twig', [ + $template = $request->attributes->get('template', '@ibexadesign/account/notifications/list.html.twig'); + + return $this->render($template, [ 'page' => $page, 'pagination' => $pagination, 'notifications' => $notifications, 'notifications_count_interval' => $this->configResolver->getParameter('notification_count.interval'), 'pager' => $pagerfanta, - ])->getContent()); + ]); } /** @@ -139,13 +130,8 @@ public function countNotificationsAction(): JsonResponse * We're not able to establish two-way stream (it requires additional * server service for websocket connection), so * we need a way to mark notification * as read. AJAX call is fine. - * - * @param \Symfony\Component\HttpFoundation\Request $request - * @param mixed $notificationId - * - * @return \Symfony\Component\HttpFoundation\JsonResponse */ - public function markNotificationAsReadAction(Request $request, $notificationId): JsonResponse + public function markNotificationAsReadAction(Request $request, mixed $notificationId): JsonResponse { $response = new JsonResponse(); @@ -176,6 +162,38 @@ public function markNotificationAsReadAction(Request $request, $notificationId): return $response; } + + public function markNotificationAsUnreadAction(Request $request, mixed $notificationId): JsonResponse + { + $response = new JsonResponse(); + + try { + $notification = $this->notificationService->getNotification((int)$notificationId); + + $this->notificationService->markNotificationAsUnread($notification); + + $data = ['status' => 'success']; + + if ($this->registry->hasRenderer($notification->type)) { + $url = $this->registry->getRenderer($notification->type)->generateUrl($notification); + + if ($url) { + $data['redirect'] = $url; + } + } + + $response->setData($data); + } catch (\Exception $exception) { + $response->setData([ + 'status' => 'failed', + 'error' => $exception->getMessage(), + ]); + + $response->setStatusCode(404); + } + + return $response; + } } class_alias(NotificationController::class, 'EzSystems\EzPlatformAdminUiBundle\Controller\NotificationController'); diff --git a/src/bundle/Resources/config/routing.yaml b/src/bundle/Resources/config/routing.yaml index 18263e88b9..105ebcdeca 100644 --- a/src/bundle/Resources/config/routing.yaml +++ b/src/bundle/Resources/config/routing.yaml @@ -912,6 +912,15 @@ ibexa.notifications.render.page: requirements: page: '\d+' +ibexa.notifications.render.all: + path: /notifications/render/all/{page} + defaults: + _controller: 'Ibexa\Bundle\AdminUi\Controller\AllNotificationsController::renderAllNotificationsPageAction' + page: 1 + methods: [ GET ] + requirements: + page: '\d+' + ibexa.notifications.count: path: /notifications/count defaults: @@ -926,6 +935,14 @@ ibexa.notifications.mark_as_read: requirements: notificationId: '\d+' +ibexa.notifications.mark_as_unread: + path: /notification/unread/{notificationId} + defaults: + _controller: 'Ibexa\Bundle\AdminUi\Controller\NotificationController::markNotificationAsUnreadAction' + methods: [ GET ] + requirements: + notificationId: '\d+' + ibexa.asset.upload_image: path: /asset/image options: diff --git a/src/bundle/Resources/config/services/controllers.yaml b/src/bundle/Resources/config/services/controllers.yaml index e6384a6886..c9add9d9d8 100644 --- a/src/bundle/Resources/config/services/controllers.yaml +++ b/src/bundle/Resources/config/services/controllers.yaml @@ -104,6 +104,12 @@ services: tags: - controller.service_arguments + Ibexa\Bundle\AdminUi\Controller\AllNotificationsController: + parent: Ibexa\Contracts\AdminUi\Controller\Controller + autowire: true + tags: + - controller.service_arguments + Ibexa\Bundle\AdminUi\Controller\ObjectStateController: parent: Ibexa\Contracts\AdminUi\Controller\Controller autowire: true diff --git a/src/bundle/Resources/views/themes/admin/account/notifications/list_all.html.twig b/src/bundle/Resources/views/themes/admin/account/notifications/list_all.html.twig new file mode 100644 index 0000000000..f159e031a8 --- /dev/null +++ b/src/bundle/Resources/views/themes/admin/account/notifications/list_all.html.twig @@ -0,0 +1,60 @@ +{% trans_default_domain 'ibexa_notifications' %} + +{% embed '@ibexadesign/ui/component/table/table.html.twig' with { + head_cols: [ + { content: 'notification.type'|trans|desc('Type') }, + { content: 'notification.description'|trans|desc('Description') }, + { content: 'notification.date'|trans|desc('Date') }, + ], + class: 'ibexa-table--notifications', + attr: { + 'data-notifications': path('ibexa.notifications.render.page'), + 'data-notifications-count': path('ibexa.notifications.count'), + 'data-notifications-count-interval': notifications_count_interval, + 'data-notifications-total': pager.nbResults, + } +} %} + {% block tbody %} + {% if pager.count is same as(0) %} + {% include '@ibexadesign/ui/component/table/empty_table_body_row.html.twig' with { + colspan: 3, + empty_table_info_text: 'bookmark.list.empty'|trans|desc('You have no notifications.'), + } %} + {% else %} + {% block tbody_not_empty %} + {% for renderedNotification in notifications %} + {{ renderedNotification|raw }} + {% endfor %} + {% endblock %} + {% endif %} + {% endblock %} +{% endembed %} + +{% if pager.haveToPaginate %} + +{% endif %} + diff --git a/src/bundle/Resources/views/themes/admin/account/notifications/list_item.html.twig b/src/bundle/Resources/views/themes/admin/account/notifications/list_item.html.twig index fe93cc00c0..aa9d6bb0bd 100644 --- a/src/bundle/Resources/views/themes/admin/account/notifications/list_item.html.twig +++ b/src/bundle/Resources/views/themes/admin/account/notifications/list_item.html.twig @@ -46,6 +46,7 @@ attr: { 'data-notification-id': notification.id, 'data-notification-read': path('ibexa.notifications.mark_as_read', { 'notificationId': notification.id }), + 'data-notification-unread': path('ibexa.notifications.mark_as_unread', { 'notificationId': notification.id }), } } %} {% block body_row_cells %} From b510b9e5cf72a2156d6edf1e5875870b4c117256 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Bia=C5=82czak?= Date: Fri, 14 Mar 2025 14:09:08 +0100 Subject: [PATCH 02/94] IBX-9060: Moved pagination rendering to Twig template and simplified controller logic --- .../Controller/NotificationController.php | 10 ------ .../account/notifications/list.html.twig | 31 ++++++++++++++----- .../account/notifications/list_all.html.twig | 12 +++---- 3 files changed, 29 insertions(+), 24 deletions(-) diff --git a/src/bundle/Controller/NotificationController.php b/src/bundle/Controller/NotificationController.php index 61f3608049..075ad50093 100644 --- a/src/bundle/Controller/NotificationController.php +++ b/src/bundle/Controller/NotificationController.php @@ -85,19 +85,9 @@ public function renderNotificationsPageAction(Request $request, int $page): Resp } $notifications = $request->attributes->get('notifications', $notifications); - $routeGenerator = function ($page) { - return $this->generateUrl('ibexa.notifications.render.page', [ - 'page' => $page, - ]); - }; - - $pagination = (new EzPagerfantaView(new EzPagerfantaTemplate($this->translator)))->render($pagerfanta, $routeGenerator); - $template = $request->attributes->get('template', '@ibexadesign/account/notifications/list.html.twig'); return $this->render($template, [ - 'page' => $page, - 'pagination' => $pagination, 'notifications' => $notifications, 'notifications_count_interval' => $this->configResolver->getParameter('notification_count.interval'), 'pager' => $pagerfanta, diff --git a/src/bundle/Resources/views/themes/admin/account/notifications/list.html.twig b/src/bundle/Resources/views/themes/admin/account/notifications/list.html.twig index 54b684a274..c915efa478 100644 --- a/src/bundle/Resources/views/themes/admin/account/notifications/list.html.twig +++ b/src/bundle/Resources/views/themes/admin/account/notifications/list.html.twig @@ -30,13 +30,28 @@ {% if pager.haveToPaginate %}
-
- {{ 'pagination.viewing'|trans({ - '%viewing%': pager.currentPageResults|length, - '%total%': pager.nbResults}, 'ibexa_pagination')|desc('Viewing %viewing% out of %total% items')|raw }} -
-
- {{ pagination|raw }} -
+
{% endif %} diff --git a/src/bundle/Resources/views/themes/admin/account/notifications/list_all.html.twig b/src/bundle/Resources/views/themes/admin/account/notifications/list_all.html.twig index f159e031a8..ef21ad1874 100644 --- a/src/bundle/Resources/views/themes/admin/account/notifications/list_all.html.twig +++ b/src/bundle/Resources/views/themes/admin/account/notifications/list_all.html.twig @@ -8,7 +8,7 @@ ], class: 'ibexa-table--notifications', attr: { - 'data-notifications': path('ibexa.notifications.render.page'), + 'data-notifications': path('ibexa.notifications.render.all'), 'data-notifications-count': path('ibexa.notifications.count'), 'data-notifications-count-interval': notifications_count_interval, 'data-notifications-total': pager.nbResults, @@ -31,11 +31,11 @@ {% endembed %} {% if pager.haveToPaginate %} - + {% endif %} From 6ce7874ada2f5a61807e21a50a7002ed74127467 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Bia=C5=82czak?= Date: Wed, 26 Mar 2025 07:03:37 +0100 Subject: [PATCH 03/94] IBX-9060: Added delete action and delete multiple notifications --- .../Controller/AllNotificationsController.php | 38 -------- .../Controller/NotificationController.php | 89 ++++++++++++++++--- src/bundle/Resources/config/routing.yaml | 14 +++ .../Notification/NotificationRemoveData.php | 29 ++++++ src/lib/Form/Factory/FormFactory.php | 17 ++++ .../Notification/NotificationRemoveType.php | 47 ++++++++++ 6 files changed, 183 insertions(+), 51 deletions(-) create mode 100644 src/lib/Form/Data/Notification/NotificationRemoveData.php create mode 100644 src/lib/Form/Type/Notification/NotificationRemoveType.php diff --git a/src/bundle/Controller/AllNotificationsController.php b/src/bundle/Controller/AllNotificationsController.php index 5ab9edfefb..3fbdacb257 100644 --- a/src/bundle/Controller/AllNotificationsController.php +++ b/src/bundle/Controller/AllNotificationsController.php @@ -8,56 +8,18 @@ namespace Ibexa\Bundle\AdminUi\Controller; -use Ibexa\AdminUi\Pagination\Pagerfanta\NotificationAdapter; use Ibexa\Contracts\AdminUi\Controller\Controller; -use Ibexa\Contracts\Core\Repository\NotificationService; -use Ibexa\Contracts\Core\SiteAccess\ConfigResolverInterface; -use Ibexa\Core\Notification\Renderer\Registry; -use Pagerfanta\Pagerfanta; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; class AllNotificationsController extends Controller { - /** @var \Ibexa\Contracts\Core\Repository\NotificationService */ - protected $notificationService; - - /** @var \Ibexa\Core\Notification\Renderer\Registry */ - protected $registry; - - /** @var \Ibexa\Contracts\Core\SiteAccess\ConfigResolverInterface */ - private $configResolver; - - public function __construct( - NotificationService $notificationService, - Registry $registry, - ConfigResolverInterface $configResolver - ) { - $this->notificationService = $notificationService; - $this->registry = $registry; - $this->configResolver = $configResolver; - } public function renderAllNotificationsPageAction(Request $request, int $page): Response { - $pagerfanta = new Pagerfanta( - new NotificationAdapter($this->notificationService) - ); - $pagerfanta->setMaxPerPage($this->configResolver->getParameter('pagination.notification_limit')); - $pagerfanta->setCurrentPage(min($page, $pagerfanta->getNbPages())); - - $notifications = []; - foreach ($pagerfanta->getCurrentPageResults() as $notification) { - if ($this->registry->hasRenderer($notification->type)) { - $renderer = $this->registry->getRenderer($notification->type); - $notifications[] = $renderer->render($notification); - } - } - return $this->forward( NotificationController::class . '::renderNotificationsPageAction', [ 'page' => $page, - 'notifications' => $notifications, 'template' => '@ibexadesign/account/notifications/list_all.html.twig', 'render_all' => true, ] diff --git a/src/bundle/Controller/NotificationController.php b/src/bundle/Controller/NotificationController.php index 075ad50093..004a8fb35e 100644 --- a/src/bundle/Controller/NotificationController.php +++ b/src/bundle/Controller/NotificationController.php @@ -8,9 +8,10 @@ namespace Ibexa\Bundle\AdminUi\Controller; +use Ibexa\AdminUi\Form\Data\Notification\NotificationRemoveData; +use Ibexa\AdminUi\Form\Factory\FormFactory; +use Ibexa\AdminUi\Form\SubmitHandler; use Ibexa\AdminUi\Pagination\Pagerfanta\NotificationAdapter; -use Ibexa\Bundle\AdminUi\View\EzPagerfantaView; -use Ibexa\Bundle\AdminUi\View\Template\EzPagerfantaTemplate; use Ibexa\Contracts\AdminUi\Controller\Controller; use Ibexa\Contracts\Core\Repository\NotificationService; use Ibexa\Contracts\Core\SiteAccess\ConfigResolverInterface; @@ -35,16 +36,26 @@ class NotificationController extends Controller /** @var \Ibexa\Contracts\Core\SiteAccess\ConfigResolverInterface */ private $configResolver; + /** @var \Ibexa\AdminUi\Form\Factory\FormFactory */ + private $formFactory; + + /** @var \Ibexa\AdminUi\Form\SubmitHandler */ + private $submitHandler; + public function __construct( NotificationService $notificationService, Registry $registry, TranslatorInterface $translator, - ConfigResolverInterface $configResolver + ConfigResolverInterface $configResolver, + FormFactory $formFactory, + SubmitHandler $submitHandler ) { $this->notificationService = $notificationService; $this->registry = $registry; $this->translator = $translator; $this->configResolver = $configResolver; + $this->formFactory = $formFactory; + $this->submitHandler = $submitHandler; } public function getNotificationsAction(Request $request, int $offset, int $limit): JsonResponse @@ -76,24 +87,39 @@ public function renderNotificationsPageAction(Request $request, int $page): Resp $pagerfanta->setMaxPerPage($this->configResolver->getParameter('pagination.notification_limit')); $pagerfanta->setCurrentPage(min($page, $pagerfanta->getNbPages())); - $notifications = ''; + $notifications = []; foreach ($pagerfanta->getCurrentPageResults() as $notification) { if ($this->registry->hasRenderer($notification->type)) { $renderer = $this->registry->getRenderer($notification->type); - $notifications .= $renderer->render($notification); + $notifications[] = $renderer->render($notification); } } - $notifications = $request->attributes->get('notifications', $notifications); $template = $request->attributes->get('template', '@ibexadesign/account/notifications/list.html.twig'); + $deleteNotificationsForm = $this->formFactory->deleteNotification( + $this->createNotificationRemoveData($pagerfanta) + ); + return $this->render($template, [ 'notifications' => $notifications, 'notifications_count_interval' => $this->configResolver->getParameter('notification_count.interval'), 'pager' => $pagerfanta, + 'form_remove' => $deleteNotificationsForm->createView(), ]); } + private function createNotificationRemoveData(Pagerfanta $pagerfanta): NotificationRemoveData + { + $notificationIds = []; + + foreach ($pagerfanta->getCurrentPageResults() as $notification) { + $notificationIds[$notification->id] = false; + } + + return new NotificationRemoveData($notificationIds); + } + /** * @return \Symfony\Component\HttpFoundation\JsonResponse */ @@ -164,15 +190,29 @@ public function markNotificationAsUnreadAction(Request $request, mixed $notifica $data = ['status' => 'success']; - if ($this->registry->hasRenderer($notification->type)) { - $url = $this->registry->getRenderer($notification->type)->generateUrl($notification); + $response->setData($data); + } catch (\Exception $exception) { + $response->setData([ + 'status' => 'failed', + 'error' => $exception->getMessage(), + ]); - if ($url) { - $data['redirect'] = $url; - } - } + $response->setStatusCode(404); + } - $response->setData($data); + return $response; + } + + public function deleteNotificationAction(Request $request, mixed $notificationId): JsonResponse + { + $response = new JsonResponse(); + + try { + $notification = $this->notificationService->getNotification((int)$notificationId); + + $this->notificationService->deleteNotification($notification); + + $response->setData(['status' => 'success']); } catch (\Exception $exception) { $response->setData([ 'status' => 'failed', @@ -184,6 +224,29 @@ public function markNotificationAsUnreadAction(Request $request, mixed $notifica return $response; } + + public function deleteNotificationsAction(Request $request): Response + { + $form = $this->formFactory->deleteNotification(); + $form->handleRequest($request); + + if ($form->isSubmitted()) { + $result = $this->submitHandler->handle($form, function (NotificationRemoveData $data) { + foreach (array_keys($data->getNotifications()) as $id) { + $notification = $this->notificationService->getNotification((int)$id); + $this->notificationService->deleteNotification($notification); + } + + return $this->redirectToRoute('ibexa.notifications.render.all'); + }); + + if ($result instanceof Response) { + return $result; + } + } + + return $this->redirectToRoute('ibexa.notifications.render.all'); + } } class_alias(NotificationController::class, 'EzSystems\EzPlatformAdminUiBundle\Controller\NotificationController'); diff --git a/src/bundle/Resources/config/routing.yaml b/src/bundle/Resources/config/routing.yaml index 105ebcdeca..24edc5d33a 100644 --- a/src/bundle/Resources/config/routing.yaml +++ b/src/bundle/Resources/config/routing.yaml @@ -943,6 +943,14 @@ ibexa.notifications.mark_as_unread: requirements: notificationId: '\d+' +ibexa.notifications.delete: + path: /notification/delete/{notificationId} + defaults: + _controller: 'Ibexa\Bundle\AdminUi\Controller\NotificationController::deleteNotificationAction' + methods: [ GET ] + requirements: + notificationId: '\d+' + ibexa.asset.upload_image: path: /asset/image options: @@ -951,6 +959,12 @@ ibexa.asset.upload_image: _controller: 'Ibexa\Bundle\AdminUi\Controller\AssetController::uploadImageAction' methods: [POST] +ibexa.notifications.delete_multiple: + path: /notification/delete-multiple + defaults: + _controller: 'Ibexa\Bundle\AdminUi\Controller\NotificationController::deleteNotificationsAction' + methods: [ POST ] + # # Permissions # diff --git a/src/lib/Form/Data/Notification/NotificationRemoveData.php b/src/lib/Form/Data/Notification/NotificationRemoveData.php new file mode 100644 index 0000000000..8b6338ba37 --- /dev/null +++ b/src/lib/Form/Data/Notification/NotificationRemoveData.php @@ -0,0 +1,29 @@ +notifications = $notifications; + } + + public function getNotifications(): array + { + return $this->notifications; + } + + public function setNotifications(array $notifications): void + { + $this->notifications = $notifications; + } +} diff --git a/src/lib/Form/Factory/FormFactory.php b/src/lib/Form/Factory/FormFactory.php index 9ba2df1596..19fb4a9e55 100644 --- a/src/lib/Form/Factory/FormFactory.php +++ b/src/lib/Form/Factory/FormFactory.php @@ -36,6 +36,7 @@ use Ibexa\AdminUi\Form\Data\Location\LocationTrashData; use Ibexa\AdminUi\Form\Data\Location\LocationUpdateData; use Ibexa\AdminUi\Form\Data\Location\LocationUpdateVisibilityData; +use Ibexa\AdminUi\Form\Data\Notification\NotificationRemoveData; use Ibexa\AdminUi\Form\Data\ObjectState\ObjectStateGroupCreateData; use Ibexa\AdminUi\Form\Data\ObjectState\ObjectStateGroupDeleteData; use Ibexa\AdminUi\Form\Data\ObjectState\ObjectStateGroupsDeleteData; @@ -92,6 +93,7 @@ use Ibexa\AdminUi\Form\Type\Location\LocationTrashType; use Ibexa\AdminUi\Form\Type\Location\LocationUpdateType; use Ibexa\AdminUi\Form\Type\Location\LocationUpdateVisibilityType; +use Ibexa\AdminUi\Form\Type\Notification\NotificationRemoveType; use Ibexa\AdminUi\Form\Type\ObjectState\ObjectStateGroupCreateType; use Ibexa\AdminUi\Form\Type\ObjectState\ObjectStateGroupDeleteType; use Ibexa\AdminUi\Form\Type\ObjectState\ObjectStateGroupsDeleteType; @@ -1064,6 +1066,21 @@ public function removeContentDraft( return $this->formFactory->createNamed($name, ContentRemoveType::class, $data); } + /** + * @param \Ibexa\AdminUi\Form\Data\Notification\NotificationRemoveData|null $data + * @param string|null $name + * + * @return \Symfony\Component\Form\FormInterface + */ + public function deleteNotification( + NotificationRemoveData $data = null, + ?string $name = null + ): FormInterface { + $name = $name ?: StringUtil::fqcnToBlockPrefix(NotificationRemoveType::class); + + return $this->formFactory->createNamed($name, NotificationRemoveType::class, $data); + } + /** * @param \Ibexa\AdminUi\Form\Data\URLWildcard\URLWildcardData|null $data * @param string|null $name diff --git a/src/lib/Form/Type/Notification/NotificationRemoveType.php b/src/lib/Form/Type/Notification/NotificationRemoveType.php new file mode 100644 index 0000000000..23bc79998e --- /dev/null +++ b/src/lib/Form/Type/Notification/NotificationRemoveType.php @@ -0,0 +1,47 @@ +add( + 'notifications', + CollectionType::class, + [ + 'entry_type' => CheckboxType::class, + 'required' => false, + 'allow_add' => true, + 'entry_options' => ['label' => false], + 'label' => false, + ] + ); + + $builder->add( + 'remove', + SubmitType::class + ); + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'data_class' => NotificationRemoveData::class, + ]); + } +} From c23d4941204cf5afb8770ceaf9a4a0ebf9847e76 Mon Sep 17 00:00:00 2001 From: Aleksandra Bozek Date: Fri, 21 Mar 2025 16:26:35 +0100 Subject: [PATCH 04/94] IBX-9060: notification views changes --- .../Resources/encore/ibexa.js.config.js | 2 + .../js/scripts/admin.notifications.modal.js | 134 +++++++++++--- .../public/js/scripts/sidebar/side.panel.js | 83 +++++++++ .../public/scss/_header-user-menu.scss | 17 +- .../public/scss/_notifications-modal.scss | 167 ++++++++++++++---- .../Resources/public/scss/_notifications.scss | 7 + .../Resources/public/scss/_side-panel.scss | 64 +++++++ src/bundle/Resources/public/scss/ibexa.scss | 1 + .../translations/ibexa_admin_ui.en.xliff | 5 + .../translations/ibexa_notifications.en.xliff | 64 +++++-- .../Resources/translations/messages.en.xliff | 25 +-- .../account/notifications/list.html.twig | 45 +---- .../account/notifications/list_all.html.twig | 111 ++++++------ .../account/notifications/list_item.html.twig | 75 ++++++-- .../notifications/list_item_all.html.twig | 125 +++++++++++++ .../notifications/list_item_deleted.html.twig | 19 +- .../account/notifications/modal.html.twig | 24 --- .../notifications/side-panel.html.twig | 40 +++++ .../component/side-panel/side_panel.html.twig | 49 +++++ .../views/themes/admin/ui/menu/user.html.twig | 9 +- 20 files changed, 845 insertions(+), 221 deletions(-) create mode 100644 src/bundle/Resources/public/js/scripts/sidebar/side.panel.js create mode 100644 src/bundle/Resources/public/scss/_side-panel.scss create mode 100644 src/bundle/Resources/views/themes/admin/account/notifications/list_item_all.html.twig delete mode 100644 src/bundle/Resources/views/themes/admin/account/notifications/modal.html.twig create mode 100644 src/bundle/Resources/views/themes/admin/account/notifications/side-panel.html.twig create mode 100644 src/bundle/Resources/views/themes/admin/ui/component/side-panel/side_panel.html.twig diff --git a/src/bundle/Resources/encore/ibexa.js.config.js b/src/bundle/Resources/encore/ibexa.js.config.js index b0e571de94..53e910a5c2 100644 --- a/src/bundle/Resources/encore/ibexa.js.config.js +++ b/src/bundle/Resources/encore/ibexa.js.config.js @@ -38,6 +38,7 @@ const layout = [ path.resolve(__dirname, '../public/js/scripts/admin.prevent.click.js'), path.resolve(__dirname, '../public/js/scripts/admin.picker.js'), path.resolve(__dirname, '../public/js/scripts/admin.notifications.modal.js'), + path.resolve(__dirname, '../public/js/scripts/sidebar/side.panel.js'), path.resolve(__dirname, '../public/js/scripts/admin.location.add.translation.js'), path.resolve(__dirname, '../public/js/scripts/admin.form.autosubmit.js'), path.resolve(__dirname, '../public/js/scripts/admin.anchor.navigation'), @@ -199,6 +200,7 @@ module.exports = (Encore) => { path.resolve(__dirname, '../public/js/scripts/fieldType/base/multi-input-field.js'), ...fieldTypes, path.resolve(__dirname, '../public/js/scripts/sidebar/extra.actions.js'), + path.resolve(__dirname, '../public/js/scripts/sidebar/side.panel.js'), path.resolve(__dirname, '../public/js/scripts/edit.header.js'), ]) .addEntry('ibexa-admin-ui-settings-datetime-format-update-js', [ diff --git a/src/bundle/Resources/public/js/scripts/admin.notifications.modal.js b/src/bundle/Resources/public/js/scripts/admin.notifications.modal.js index efdaa755b6..256efca1a4 100644 --- a/src/bundle/Resources/public/js/scripts/admin.notifications.modal.js +++ b/src/bundle/Resources/public/js/scripts/admin.notifications.modal.js @@ -3,34 +3,56 @@ let getNotificationsStatusErrorShowed = false; let lastFailedCountFetchNotificationNode = null; const SELECTOR_MODAL_ITEM = '.ibexa-notifications-modal__item'; - const SELECTOR_MODAL_RESULTS = '.ibexa-notifications-modal__results'; - const SELECTOR_MODAL_TITLE = '.modal-title'; + const SELECTOR_MODAL_RESULTS = '.ibexa-notifications-modal__type-content'; + const SELECTOR_GO_TO_NOTIFICATION = '.ibexa-notification-viewAll__show'; + const SELECTOR_TOGGLE_NOTIFICATION = '.ibexa-notification-viewAll__mail'; + const SELECTOR_MODAL_TITLE = '.ibexa-side-panel__header'; const SELECTOR_DESC_TEXT = '.description__text'; - const SELECTOR_TABLE = '.ibexa-table--notifications'; + const SELECTOR_LIST = '.ibexa-list--notifications'; const CLASS_ELLIPSIS = 'description__text--ellipsis'; const CLASS_PAGINATION_LINK = 'page-link'; const CLASS_MODAL_LOADING = 'ibexa-notifications-modal--loading'; const INTERVAL = 30000; - const modal = doc.querySelector('.ibexa-notifications-modal'); + const panel = doc.querySelector('.ibexa-notifications-modal'); + const popupBtns = [...doc.querySelectorAll('.ibexa-multilevel-popup-menu__item-content')]; + const SELECTOR_MORE_ACTION = '.ibexa-notifications-modal--more'; const { showErrorNotification, showWarningNotification } = ibexa.helpers.notification; const { getJsonFromResponse, getTextFromResponse } = ibexa.helpers.request; - const markAsRead = (notification, response) => { - if (response.status === 'success') { - notification.classList.add('ibexa-notifications-modal__item--read'); - } - - if (response.redirect) { - global.location = response.redirect; - } - }; - const handleNotificationClick = (notification) => { - const notificationReadLink = notification.dataset.notificationRead; + const handleNotificationClick = (notification, isToggle = false) => { + const notificationRow = notification.closest('.ibexa-table__row'); + const isRead = notification.classList.contains('ibexa-notifications-modal__item--read'); + const notificationReadLink = isToggle && isRead ? notificationRow.dataset.notificationUnread : notificationRow.dataset.notificationRead; const request = new Request(notificationReadLink, { mode: 'cors', credentials: 'same-origin', }); + + fetch(request).then(getJsonFromResponse).then((response) => { + if (response.status === 'success') { + notification.classList.toggle('ibexa-notifications-modal__item--read', !isRead); + + if(isToggle) { + notification.querySelector('.ibexa-table__cell .ibexa-notification-viewAll__mail-open')?.classList.toggle('ibexa-notification-viewAll__icon-hidden'); + notification.querySelector('.ibexa-table__cell .ibexa-notification-viewAll__mail-closed')?.classList.toggle('ibexa-notification-viewAll__icon-hidden'); + + const statusText = isRead ? Translator.trans( + /*@Desc("Unread")*/ 'notification.unread', + {}, + 'ibexa_notifications', + ) : Translator.trans( + /*@Desc("Read")*/ 'notification.read', + {}, + 'ibexa_notifications', + ); + notification.closest('.ibexa-table__row').querySelector('.ibexa-notification-viewAll__read').innerHTML = statusText; + return; + } - fetch(request).then(getJsonFromResponse).then(markAsRead.bind(null, notification)).catch(showErrorNotification); + if (response.redirect) { + global.location = response.redirect; + } + } + }).catch(showErrorNotification); }; const handleTableClick = (event) => { if (event.target.classList.contains('description__read-more')) { @@ -47,8 +69,26 @@ handleNotificationClick(notification); }; + + const initNotificationPopup = () => { + //TODO: init popups + // const notificationsTable = panel.querySelector(SELECTOR_LIST); + // const popups = [...panel.querySelectorAll('.ibexa-multilevel-popup-menu:not(.ibexa-multilevel-popup-menu--custom-init)')]; + // popups.forEach(function (popupBtn) { + // const multilevelPopupMenu = new ibexa.core.MultilevelPopupMenu({ + // container: popupBtn, + // triggerElement: popupBtn, + // // referenceElement: this.container, + // initialBranchPlacement: popupBtn.dataset?.initialBranchPlacement, + // // initialBranchFallbackPlacements: ['bottom-end', 'top-end', 'top-start'], + // // onTopBranchOpened: this.handlePopupOpened, + // // onTopBranchClosed: this.handlePopupClosed, + // }); + // multilevelPopupMenu.init(); + // }); + } const getNotificationsStatus = () => { - const notificationsTable = modal.querySelector(SELECTOR_TABLE); + const notificationsTable = panel.querySelector(SELECTOR_LIST); const notificationsStatusLink = notificationsTable.dataset.notificationsCount; const request = new Request(notificationsStatusLink, { mode: 'cors', @@ -93,19 +133,20 @@ getNotificationsStatusErrorShowed = true; }; const updateModalTitleTotalInfo = (notificationsCount) => { - const modalTitle = modal.querySelector(SELECTOR_MODAL_TITLE); + const modalTitle = panel.querySelector(SELECTOR_MODAL_TITLE); modalTitle.dataset.notificationsTotal = `(${notificationsCount})`; }; const updatePendingNotificationsView = (notificationsInfo) => { const noticeDot = doc.querySelector('.ibexa-header-user-menu__notice-dot'); + noticeDot.dataset.count = notificationsInfo.pending; noticeDot.classList.toggle('ibexa-header-user-menu__notice-dot--no-notice', notificationsInfo.pending === 0); }; const setPendingNotificationCount = (notificationsInfo) => { updatePendingNotificationsView(notificationsInfo); - const notificationsTable = modal.querySelector(SELECTOR_TABLE); + const notificationsTable = panel.querySelectzor(SELECTOR_LIST); const notificationsTotal = notificationsInfo.total; const notificationsTotalOld = parseInt(notificationsTable.dataset.notificationsTotal, 10); @@ -116,13 +157,13 @@ } }; const showNotificationPage = (pageHtml) => { - const modalResults = modal.querySelector(SELECTOR_MODAL_RESULTS); + const modalResults = panel.querySelector(SELECTOR_MODAL_RESULTS); modalResults.innerHTML = pageHtml; toggleLoading(false); }; const toggleLoading = (show) => { - modal.classList.toggle(CLASS_MODAL_LOADING, show); + panel.classList.toggle(CLASS_MODAL_LOADING, show); }; const fetchNotificationPage = (link) => { if (!link) { @@ -160,15 +201,60 @@ fetchNotificationPage(notificationsPageLink); }; - if (!modal) { + if (!panel) { return; } - const notificationsTable = modal.querySelector(SELECTOR_TABLE); + const initTooltipIfOverflow = (popup) => { + const label = popup.querySelector('.ibexa-btn__label'); + const popupContainer = popup.closest('.ibexa-multilevel-popup-menu__group'); + + if (label.scrollWidth < popupContainer.offsetWidth) { + return; + } + + popup.title = label.textContent; + ibexa.helpers.tooltips.parse(popup); + }; + const handleMoreActionBtnClick =(btn) => { + const noticeId = btn.closest('.ibexa-notifications-modal__item').dataset.notificationId; + popupBtns.forEach(function (popupBtn) { + const actionGroup = popupBtn.closest('.ibexa-multilevel-popup-menu__group'); + + if(actionGroup.dataset.groupId === noticeId) { + return initTooltipIfOverflow(popupBtn); + }; + }); + //event.removeEventListener('click', handleMoreActionBtnClick); + }; + + const handleNotificationActionClick =(event, isToggle = false) => { + const notification = event.target.closest(SELECTOR_MODAL_ITEM); + + if (!notification) { + return + } + + handleNotificationClick(notification, isToggle); + } + const initStatusIcons = () => { + doc.querySelectorAll(SELECTOR_MODAL_ITEM).forEach((item) => { + const isRead = item.classList.contains('ibexa-notifications-modal__item--read'); + + item.querySelector(`.ibexa-table__cell .ibexa-notification-viewAll__mail-closed`)?.classList.toggle('ibexa-notification-viewAll__icon-hidden', !isRead); + item.querySelector(`.ibexa-table__cell .ibexa-notification-viewAll__mail-open`)?.classList.toggle('ibexa-notification-viewAll__icon-hidden', isRead); + }, false); + }; + + initStatusIcons(); + const notificationsTable = panel.querySelector(SELECTOR_LIST); currentPageLink = notificationsTable.dataset.notifications; const interval = Number.parseInt(notificationsTable.dataset.notificationsCountInterval, 10) || INTERVAL; - modal.querySelectorAll(SELECTOR_MODAL_RESULTS).forEach((link) => link.addEventListener('click', handleModalResultsClick, false)); + panel.querySelectorAll(SELECTOR_MODAL_RESULTS).forEach((link) => link.addEventListener('click', handleModalResultsClick, false)); + panel.querySelectorAll(SELECTOR_MORE_ACTION).forEach((btn) => btn.addEventListener('click', () => handleMoreActionBtnClick(btn))); + doc.querySelectorAll(SELECTOR_GO_TO_NOTIFICATION).forEach((link) => link.addEventListener('click', handleNotificationActionClick, false)); + doc.querySelectorAll(SELECTOR_TOGGLE_NOTIFICATION).forEach((link) => link.addEventListener('click', (event) => handleNotificationActionClick(event, true), false)); const getNotificationsStatusLoop = () => { getNotificationsStatus().finally(() => { diff --git a/src/bundle/Resources/public/js/scripts/sidebar/side.panel.js b/src/bundle/Resources/public/js/scripts/sidebar/side.panel.js new file mode 100644 index 0000000000..44c0c05043 --- /dev/null +++ b/src/bundle/Resources/public/js/scripts/sidebar/side.panel.js @@ -0,0 +1,83 @@ +(function (global, doc, ibexa) { + const CLASS_HIDDEN = 'ibexa-side-panel--hidden'; + const closeBtns = doc.querySelectorAll( + '.ibexa-side-panel .ibexa-btn--close, .ibexa-side-panel .ibexa-side-panel__btn--cancel', + ); + const btns = [...doc.querySelectorAll('.ibexa-btn--side-panel-actions')]; + const backdrop = new ibexa.core.Backdrop(); + const haveHiddenPart = (element) => element.classList.contains(CLASS_HIDDEN); + const removeBackdrop = () => { + backdrop.hide(); + doc.body.classList.remove('ibexa-scroll-disabled'); + }; + const allActivityBtn = doc?.querySelector('.ibexa-notifications__view-all-btn'); + + const closeExtraActions = (actions) => { + actions.classList.add(CLASS_HIDDEN); + + doc.body.dispatchEvent(new CustomEvent('ibexa-side-panel:after-close')); + + removeBackdrop(); + }; + const toggleExtraActionsWidget = (event) => { + const actions = doc.querySelector(`.ibexa-side-panel[data-actions="create"]`); + const isHidden = haveHiddenPart(actions); + const detectClickOutside = (event) => { + if (event.target.classList.contains('ibexa-backdrop')) { + closeExtraActions(actions); + doc.body.removeEventListener('click', detectClickOutside, false); + } + }; + + actions.classList.toggle(CLASS_HIDDEN, !isHidden); + + if (!actions.classList.contains(CLASS_HIDDEN)) { + backdrop.show(); + doc.body.addEventListener('click', detectClickOutside, false); + doc.body.classList.add('ibexa-scroll-disabled'); + } else { + doc.body.removeEventListener('click', detectClickOutside); + removeBackdrop(); + } + }; + + const hideMenu = (btn) => { + const menuBranch = btn.closest('.ibexa-multilevel-popup-menu__branch'); + + if (!menuBranch?.menuInstanceElement) { + return; + } + + const menuInstance = ibexa.helpers.objectInstances.getInstance(menuBranch.menuInstanceElement); + + menuInstance.closeMenu(); + }; + const goToActivityLog = () => { + window.location.href = Routing.generate('ibexa.notifications.render.all'); + }; + + btns.forEach((btn) => { + const { dataset } = btn; + + btn.addEventListener( + 'click', + () => { + toggleExtraActionsWidget(dataset); + hideMenu(btn); + }, + false, + ); + }); + doc.body.addEventListener('ibexa-side-panel', (event) => toggleExtraActionsWidget(event), false); + closeBtns.forEach((closeBtn) => + closeBtn.addEventListener( + 'click', + (event) => { + closeExtraActions(event.currentTarget.closest('.ibexa-side-panel')); + }, + false, + ), + ); + allActivityBtn?.addEventListener('click', goToActivityLog, false); + +})(window, window.document, window.ibexa); diff --git a/src/bundle/Resources/public/scss/_header-user-menu.scss b/src/bundle/Resources/public/scss/_header-user-menu.scss index c4f0ba370c..a52972e23e 100644 --- a/src/bundle/Resources/public/scss/_header-user-menu.scss +++ b/src/bundle/Resources/public/scss/_header-user-menu.scss @@ -21,7 +21,7 @@ padding: calculateRem(16px) calculateRem(24px); border-bottom: calculateRem(1px) solid $ibexa-color-light; color: $ibexa-color-dark-400; - font-size: $ibexa-text-font-size-small; + font-size: $ibexa-text-font-size-large; } .ibexa-focus-mode-form { @@ -86,15 +86,24 @@ } &__notice-dot { - width: calculateRem(6px); - height: calculateRem(6px); + width: calculateRem(12px); + height: calculateRem(12px); border-radius: 50%; background: $ibexa-color-danger; opacity: 1; cursor: pointer; position: absolute; - left: calculateRem(10px); + left: calculateRem(8px); top: 0; + font-size: 10px; + color: white; + + &::after { + display: flex; + justify-content: center; + content: attr(data-count); + font-size: 8px; + } &--no-notice { opacity: 0; diff --git a/src/bundle/Resources/public/scss/_notifications-modal.scss b/src/bundle/Resources/public/scss/_notifications-modal.scss index 6a0fa1cffa..19f41bda2c 100644 --- a/src/bundle/Resources/public/scss/_notifications-modal.scss +++ b/src/bundle/Resources/public/scss/_notifications-modal.scss @@ -1,63 +1,96 @@ .ibexa-notifications-modal { cursor: auto; + &__footer { + display: flex; + justify-content: flex-end; + padding: calculateRem(8px); - .modal-dialog { - max-width: 60vw; - } - - .modal-header { - .modal-title { - &::after { - content: attr(data-notifications-total); - } - } } .table { table-layout: fixed; white-space: normal; margin-bottom: 0; + .ibexa-table__row .ibexa-table__cell{ + height: 115px; + padding-right: 0; + border-radius: 0; - th { - border: none; - color: $ibexa-color-dark-300; - border-top: calculateRem(1px) solid $ibexa-color-light; - border-bottom: calculateRem(1px) solid $ibexa-color-light; } - tr { - background-color: $ibexa-color-white; - cursor: pointer; - } } - + &__list { + max-height: 90vh; + overflow-y: auto; + } &__type { .type__icon { @include type-icon; + margin-left: calculateRem(16px); } .type__text { margin-left: 1rem; } } - + &__item--date { + font-size: $ibexa-text-font-size-small; + color: $ibexa-color-light-700; + } &__type-content { - display: flex; - align-items: center; + width: 100%; + font-size: 14px; + p { + margin-bottom: 0; + } } + &__item { + position: relative; + border: 1px solid $ibexa-color-light; + border-bottom: none; + &--wrapper { + display: flex; + td { + background-color: $ibexa-color-light-300 !important;//TODO:! + } + } + &:first-of-type { + border-top: none; + } + .description__text { + font-size: $ibexa-text-font-size-medium; + &--permanently-deleted { + color:$ibexa-color-danger-500; + font-size: $ibexa-text-font-size-medium; + } + } + + + } + &__notice-dot { + width: calculateRem(8px); + height: calculateRem(8px); + border-radius: 50%; + background: $ibexa-color-danger; + opacity: 1; + position: absolute; + left: calculateRem(10px); + top: calculateRem(16px); + font-size: 10px; + color: white; + } &__item--read { color: $ibexa-color-dark-300; - .type__icon { - @include type-icon-read; + .ibexa-notifications-modal__notice-dot, + .ibexa-notification-viewAll__notice-dot { + background: $ibexa-color-dark-300 } } - - &__item--permanently-deleted { - .type__text, - .description__text { - font-style: italic; + &__item--read-wrapper { + td { + background-color: white !important; } } @@ -128,3 +161,77 @@ } } } +//TODO: needs cleaning +.ibexa-notification-viewAll { + display: flex; + align-items: center; + gap: calculateRem(4px); + flex-wrap: nowrap; + white-space: nowrap; + + &__status-icon { + position: relative; + margin-right: calculateRem(12px); + } + + &__status { + position: relative; + } + + &__read { + margin-left: calculateRem(10px); + } + + &__date { + white-space: nowrap; + overflow: hidden; + } + + &__show, + &__mail { + cursor: pointer; + } + + &__notice-dot { + width: calculateRem(8px); + height: calculateRem(8px); + border-radius: 50%; + background: $ibexa-color-danger; + opacity: 1; + position: absolute; + top: calculateRem(8px); + font-size: 10px; + color: white; + + &--small { + width: calculateRem(6px); + height: calculateRem(6px); + } + } + + &__details { + display: flex; + gap: calculateRem(4px); + + .ibexa-notifications-modal__description { + display: flex; + } + } + + &__cell-wrapper { + font-size: 14px; + + p { + margin-bottom: 0; + } + + .ibexa-table__cell.ibexa-table__cell { + padding: calculateRem(12px) 0 calculateRem(12px) 0; + + } + } + + &__icon-hidden { + display: none; + } +} \ No newline at end of file diff --git a/src/bundle/Resources/public/scss/_notifications.scss b/src/bundle/Resources/public/scss/_notifications.scss index aaafd53efb..d9349ee5f4 100644 --- a/src/bundle/Resources/public/scss/_notifications.scss +++ b/src/bundle/Resources/public/scss/_notifications.scss @@ -5,3 +5,10 @@ width: calculateRem(400px); z-index: 50000; } + +.ibexa-notification-list { + &__mark-all-read { + display: flex; + justify-content: flex-end; + } +} \ No newline at end of file diff --git a/src/bundle/Resources/public/scss/_side-panel.scss b/src/bundle/Resources/public/scss/_side-panel.scss new file mode 100644 index 0000000000..1efdce56d8 --- /dev/null +++ b/src/bundle/Resources/public/scss/_side-panel.scss @@ -0,0 +1,64 @@ +.ibexa-side-panel { + background-color: $ibexa-color-white; + padding: calculateRem(8px) 0; + width: calculateRem(516px); + + &__header { + padding: 0 calculateRem(32px) calculateRem(8px); + font-weight: bold; + border-bottom: calculateRem(1px) solid $ibexa-color-light; + display: flex; + flex-wrap: wrap; + justify-content: space-between; + align-items: center; + } + + &__content { + max-height: calc(100% - #{calculateRem(60px)}); + overflow: auto; + } + + &__footer { + display: flex; + align-items: center; + box-shadow: 0 0 calculateRem(16px) 0 rgba($ibexa-color-dark, 0.16); + z-index: 1000; + width: 100%; + + .ibexa-btn { + margin-right: calculateRem(16px); + } + } + + &--hidden { + display: none; + } +} + +.ibexa-extra-actions-container { + &__backdrop { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + z-index: 150; + background-color: rgba($ibexa-color-dark, 0.4); + } + + .ibexa-side-panel { + height: calc(100vh - #{calculateRem(73px)}); + position: fixed; + top: calculateRem(73px); + right: 0; + z-index: 200; + transform: translate(0, 0) scaleX(1); + transform-origin: right center; + transition: $ibexa-admin-widget-open-transition; + + &--hidden { + transform: translate(calc(100%), 0) scaleX(0); + transition: $ibexa-admin-widget-close-transition; + } + } +} diff --git a/src/bundle/Resources/public/scss/ibexa.scss b/src/bundle/Resources/public/scss/ibexa.scss index bb74981f5f..49c8d2e965 100644 --- a/src/bundle/Resources/public/scss/ibexa.scss +++ b/src/bundle/Resources/public/scss/ibexa.scss @@ -81,6 +81,7 @@ @import 'dashboard'; @import 'picker'; @import 'notifications-modal'; +@import 'side-panel'; @import 'admin.section-view'; @import 'content-tree'; @import 'flatpickr'; diff --git a/src/bundle/Resources/translations/ibexa_admin_ui.en.xliff b/src/bundle/Resources/translations/ibexa_admin_ui.en.xliff index 8272a323fb..e7ea82e501 100644 --- a/src/bundle/Resources/translations/ibexa_admin_ui.en.xliff +++ b/src/bundle/Resources/translations/ibexa_admin_ui.en.xliff @@ -56,6 +56,11 @@ Select language key: edit_translation.list.title + + Cancel + Cancel + key: side_panel.btn.cancel_label + Removed '%languageCode%' translation from '%name%'. Removed '%languageCode%' translation from '%name%'. diff --git a/src/bundle/Resources/translations/ibexa_notifications.en.xliff b/src/bundle/Resources/translations/ibexa_notifications.en.xliff index 04f5c4acc1..83814e0030 100644 --- a/src/bundle/Resources/translations/ibexa_notifications.en.xliff +++ b/src/bundle/Resources/translations/ibexa_notifications.en.xliff @@ -11,35 +11,75 @@ Notifications key: ibexa_notifications + + Mark all as read + Mark all as read + key: ibexa_notifications.btn.mark_all_as_read + View Notifications View Notifications key: menu.notification - - Date - Date - key: notification.date + + Title + Title + key: notification.Title + + + Date and time + Date and time + key: notification.datetime + + + Delete + Delete + key: notification.delete + + + Mark as read + Mark as read + key: notification.mark_as_read - - Description - Description - key: notification.description + + Mark as unread + Mark as unread + key: notification.mark_as_unread + + + The Content item is no longer available + The Content item is no longer available + key: notification.no_longer_available Deleted Deleted key: notification.permanently_deleted + + Read + Read + key: notification.read + + + Status + Status + key: notification.status + + + Title: + Title: + key: notification.title + Sent to Trash Sent to Trash key: notification.trashed - - Type - Type - key: notification.type + + Unread + Unread + key: notification.unread Cannot update notifications diff --git a/src/bundle/Resources/translations/messages.en.xliff b/src/bundle/Resources/translations/messages.en.xliff index e03fd25686..15a46e2a99 100644 --- a/src/bundle/Resources/translations/messages.en.xliff +++ b/src/bundle/Resources/translations/messages.en.xliff @@ -463,6 +463,16 @@ We’ve sent to your email account a link to reset your password. key: ibexa.forgot_user_password.success.alert + + Notifications + Notifications + key: ibexa_notifications + + + Mark all as read + Mark all as read + key: ibexa_notifications.btn.mark_all_as_read + Delete Delete @@ -508,16 +518,6 @@ Password key: my_account_settings.password.title - - The Content item is no longer available - The Content item is no longer available - key: notification.no_longer_available - - - Title: - Title: - key: notification.title - Delete Delete @@ -593,6 +593,11 @@ Do you want to delete the Section(s)? key: section.modal.message + + View all + View all + key: side_panel.view_all + Swap Locations Swap Locations diff --git a/src/bundle/Resources/views/themes/admin/account/notifications/list.html.twig b/src/bundle/Resources/views/themes/admin/account/notifications/list.html.twig index c915efa478..302fead545 100644 --- a/src/bundle/Resources/views/themes/admin/account/notifications/list.html.twig +++ b/src/bundle/Resources/views/themes/admin/account/notifications/list.html.twig @@ -1,12 +1,8 @@ {% trans_default_domain 'ibexa_notifications' %} {% embed '@ibexadesign/ui/component/table/table.html.twig' with { - head_cols: [ - { content: 'notification.type'|trans|desc('Type') }, - { content: 'notification.description'|trans|desc('Description') }, - { content: 'notification.date'|trans|desc('Date') }, - ], - class: 'ibexa-table--notifications', + head_cols: [], + class: 'ibexa-list--notifications ibexa-notifications-modal__list', attr: { 'data-notifications': path('ibexa.notifications.render.page'), 'data-notifications-count': path('ibexa.notifications.count'), @@ -16,42 +12,9 @@ } %} {% block tbody %} {% if pager.count is same as(0) %} - {% include '@ibexadesign/ui/component/table/empty_table_body_row.html.twig' with { - colspan: 3, - empty_table_info_text: 'bookmark.list.empty'|trans|desc('You have no notifications.'), - } %} + {{ 'bookmark.list.empty'|trans|desc('You have no notifications.') }} {% else %} - {% block tbody_not_empty %} - {{ notifications|raw }} - {% endblock %} + {{ notifications|raw }} {% endif %} {% endblock %} {% endembed %} - -{% if pager.haveToPaginate %} -
- -
-{% endif %} diff --git a/src/bundle/Resources/views/themes/admin/account/notifications/list_all.html.twig b/src/bundle/Resources/views/themes/admin/account/notifications/list_all.html.twig index ef21ad1874..48515a603c 100644 --- a/src/bundle/Resources/views/themes/admin/account/notifications/list_all.html.twig +++ b/src/bundle/Resources/views/themes/admin/account/notifications/list_all.html.twig @@ -1,60 +1,67 @@ +{% extends "@ibexadesign/ui/layout.html.twig" %} +{% import '@ibexadesign/ui/component/macros.html.twig' as html %} +{% from '@ibexadesign/ui/component/macros.html.twig' import results_headline %} + {% trans_default_domain 'ibexa_notifications' %} -{% embed '@ibexadesign/ui/component/table/table.html.twig' with { - head_cols: [ - { content: 'notification.type'|trans|desc('Type') }, - { content: 'notification.description'|trans|desc('Description') }, - { content: 'notification.date'|trans|desc('Date') }, - ], - class: 'ibexa-table--notifications', - attr: { - 'data-notifications': path('ibexa.notifications.render.all'), - 'data-notifications-count': path('ibexa.notifications.count'), - 'data-notifications-count-interval': notifications_count_interval, - 'data-notifications-total': pager.nbResults, - } -} %} - {% block tbody %} - {% if pager.count is same as(0) %} - {% include '@ibexadesign/ui/component/table/empty_table_body_row.html.twig' with { - colspan: 3, - empty_table_info_text: 'bookmark.list.empty'|trans|desc('You have no notifications.'), - } %} - {% else %} - {% block tbody_not_empty %} - {% for renderedNotification in notifications %} - {{ renderedNotification|raw }} - {% endfor %} - {% endblock %} - {% endif %} - {% endblock %} -{% endembed %} +{% block title %}{{ 'ibexa_notifications'|trans|desc('Notifications') }}{% endblock %} -{% if pager.haveToPaginate %} -
-
    - {% if pager.hasPreviousPage %} -
  • - - {{ 'pagination.previous'|trans({}, 'ibexa_pagination') }} - -
  • - {% endif %} +{% block header %} +
    + +
    + + {% include '@ibexadesign/ui/page_title.html.twig' with { + title: 'ibexa_notifications'|trans|desc('Notifications'), + } %} +{% endblock %} - {% for i in 1..pager.nbPages %} -
  • - {{ i }} -
  • - {% endfor %} +{% block content %} +
    +
    + {% embed '@ibexadesign/ui/component/table/table.html.twig' with { + headline: custom_results_headline ?? results_headline(pager.getNbResults()), + head_cols: [ + { has_checkbox: true }, + { content: 'notification.Title'|trans|desc('Title') }, + { content: 'notification.status'|trans|desc('Status') }, + { content: 'notification.datetime'|trans|desc('Date and time') }, + ], + class: 'ibexa-table--notifications', + attr: { + 'data-notifications': path('ibexa.notifications.render.page'), + 'data-notifications-count': path('ibexa.notifications.count'), + 'data-notifications-count-interval': notifications_count_interval, + 'data-notifications-total': pager.nbResults, + }, + } %} + {% block tbody %} + {% if pager.count is same as(0) %} + {% include '@ibexadesign/ui/component/table/empty_table_body_row.html.twig' with { + colspan: 3, + empty_table_info_text: 'bookmark.list.empty'|trans|desc('You have no notifications.'), + } %} + {% else %} + {% block tbody_not_empty %} + {{ notifications|raw }} + {% endblock %} + {% endif %} + {% endblock %} + {% endembed %} - {% if pager.hasNextPage %} -
  • - - {{ 'pagination.next'|trans({}, 'ibexa_pagination') }} - -
  • + {% if pager.haveToPaginate %} +
    + {% include '@ibexadesign/ui/pagination.html.twig' with { + pager, + 'paginaton_params': { + 'routeName': 'ibexa.notifications.render.all', + } + } %} +
    {% endif %} -
+
-{% endif %} +{% endblock %} diff --git a/src/bundle/Resources/views/themes/admin/account/notifications/list_item.html.twig b/src/bundle/Resources/views/themes/admin/account/notifications/list_item.html.twig index aa9d6bb0bd..de6e3f96d0 100644 --- a/src/bundle/Resources/views/themes/admin/account/notifications/list_item.html.twig +++ b/src/bundle/Resources/views/themes/admin/account/notifications/list_item.html.twig @@ -1,7 +1,7 @@ {% trans_default_domain 'ibexa_notifications' %} {% if wrapper_class_list is not defined %} - {% set wrapper_class_list = 'ibexa-notifications-modal__item' ~ (notification.isPending == 0 ? ' ibexa-notifications-modal__item--read') %} + {% set wrapper_class_list = 'ibexa-notifications-modal__item ibexa-notifications-modal__item--wrapper' ~ (notification.isPending == 0 ? ' ibexa-notifications-modal__item--read ibexa-notifications-modal__item--read-wrapper') %} {% endif %} {% set icon %} @@ -23,12 +23,7 @@ {% set message %} {% block message %} - {% embed '@ibexadesign/ui/component/table/table_body_cell.html.twig' with { class: 'ibexa-notifications-modal__description' } %} - {% block content %} -

{{ 'notification.title'|trans|desc('Title:') }} {{ title }}

-

{{ message }}

- {% endblock %} - {% endembed %} +

{{ message }}

{% endblock %} {% endset %} @@ -41,24 +36,76 @@ {% endblock %} {% endset %} +{% set popup_items = [] %} + +{% set action_btns = { + markAs: { + label: notification.isPending == 0 ? 'notification.mark_as_unread'|trans|desc('Mark as unread') : 'notification.mark_as_read'|trans|desc('Mark as read'), + action_attr: { class: 'ibexa-notifications-modal--markAs', id: notification.id }, + }, + delete: { + label: 'notification.delete'|trans|desc('Delete'), + action_attr: { class: 'ibexa-notifications-modal--delete', id: notification.id }, + }, +} %} + +{% for key in action_btns|keys %} + {% set item = action_btns[key] %} + {% if item is not null %} + {% set popup_items = popup_items|merge([item]) %} + {% endif %} +{% endfor %} + {% embed '@ibexadesign/ui/component/table/table_body_row.html.twig' with { class: wrapper_class_list ~ (wrapper_additional_classes is defined ? ' ' ~ wrapper_additional_classes), attr: { 'data-notification-id': notification.id, 'data-notification-read': path('ibexa.notifications.mark_as_read', { 'notificationId': notification.id }), - 'data-notification-unread': path('ibexa.notifications.mark_as_unread', { 'notificationId': notification.id }), } } %} {% block body_row_cells %} - {% embed '@ibexadesign/ui/component/table/table_body_cell.html.twig' with { class: 'ibexa-notifications-modal__type' } %} + {% embed '@ibexadesign/ui/component/table/table_body_cell.html.twig' %} + {% block content %} +
+ +
{{ icon }}
+
+ {% endblock %} + {% endembed %} + {% embed '@ibexadesign/ui/component/table/table_body_cell.html.twig' with { class: 'ibexa-notifications-modal__type-content' } %} + {% block content %} + {{ notification_type }} + {{ message }} +
+ {{ notification.created|ibexa_short_datetime }} +
+ {% endblock %} + {% endembed %} + {% embed '@ibexadesign/ui/component/table/table_body_cell.html.twig' %} {% block content %} -
- {{ icon }} - {{ notification_type }} +
+ + {{ include('@ibexadesign/ui/component/multilevel_popup_menu/multilevel_popup_menu.html.twig', { + groups: [ + { + id: notification.id, + items: popup_items, + }, + ], + attr: { + 'data-trigger-element-selector': '.ibexa-notifications-modal--more', + 'data-initial-branch-placement': 'bottom-end', + }, + branch_attr: { + 'class': 'ibexa-notification-actions-popup-menu', + } + }) }}
{% endblock %} {% endembed %} - {{ message }} - {{ date }} {% endblock %} {% endembed %} diff --git a/src/bundle/Resources/views/themes/admin/account/notifications/list_item_all.html.twig b/src/bundle/Resources/views/themes/admin/account/notifications/list_item_all.html.twig new file mode 100644 index 0000000000..77289cbd3a --- /dev/null +++ b/src/bundle/Resources/views/themes/admin/account/notifications/list_item_all.html.twig @@ -0,0 +1,125 @@ +{% import "@ibexadesign/ui/component/macros.html.twig" as html %} + +{% trans_default_domain 'ibexa_notifications' %} + +{% set is_read = notification.isPending == 0 %} + +{% if wrapper_class_list is not defined %} + {% set wrapper_class_list = 'ibexa-notifications-modal__item' ~ (is_read ? ' ibexa-notifications-modal__item--read') %} +{% endif %} + +{% set icon %} + + + + + +{% endset %} + +{% set date %} + {% block date %} + {{ notification.created|ibexa_short_datetime }} + {% endblock %} +{% endset %} + +{% set notification_type %} + {% block notification_type %} + + {% endblock %} +{% endset %} + +{% set message %} + {% block message %} +

{{ message }}

+ {% endblock %} +{% endset %} + +{% set status %} +
+ + + {{is_read ? 'notification.read'|trans|desc('Read') : 'notification.unread'|trans|desc('Unread')}} + +
+{% endset %} + +{% set icon_show %} + + + +{% endset %} + +{% set icon_mail_open %} + + + +{% endset %} + +{% set icon_mail %} + + + +{% endset %} + +{% embed '@ibexadesign/ui/component/table/table_body_row.html.twig' with { + class: wrapper_class_list ~ (wrapper_additional_classes is defined ? ' ' ~ wrapper_additional_classes), + attr: { + 'data-notification-id': notification.id, + 'data-notification-read': path('ibexa.notifications.mark_as_read', { 'notificationId': notification.id }), + 'data-notification-unread': path('ibexa.notifications.mark_as_unread', { 'notificationId': notification.id }), +} +} %} + {% block body_row_cells %} + {% embed '@ibexadesign/ui/component/table/table_body_cell.html.twig' %} + {% block content %} + {# TODO: to handle #} +
+ +
+ {% endblock %} + {% endembed %} + {% embed '@ibexadesign/ui/component/table/table_body_cell.html.twig' with { class: 'ibexa-notification-viewAll__cell-wrapper' } %} + {% block content %} +
+
+ +
{{ icon }}
+
+
+ {{ notification_type }} + {{ message }} +
+
+ {% endblock %} + {% endembed %} + {% embed '@ibexadesign/ui/component/table/table_body_cell.html.twig' %} + {% block content %} + {{ status }} + {% endblock %} + {% endembed %} + {% embed '@ibexadesign/ui/component/table/table_body_cell.html.twig' %} + {% block content %} + {{ date }} + {% endblock %} + {% endembed %} + {% embed '@ibexadesign/ui/component/table/table_body_cell.html.twig' %} + {% block content %} + {{ icon_show }} + {% endblock %} + {% endembed %} + {% embed '@ibexadesign/ui/component/table/table_body_cell.html.twig' %} + {% block content %} +
+ {{ icon_mail_open }} +
+
+ {{ icon_mail }} +
+ {% endblock %} + {% endembed %} + {% endblock %} +{% endembed %} + diff --git a/src/bundle/Resources/views/themes/admin/account/notifications/list_item_deleted.html.twig b/src/bundle/Resources/views/themes/admin/account/notifications/list_item_deleted.html.twig index 97a9a41d33..5f9df1272e 100644 --- a/src/bundle/Resources/views/themes/admin/account/notifications/list_item_deleted.html.twig +++ b/src/bundle/Resources/views/themes/admin/account/notifications/list_item_deleted.html.twig @@ -2,19 +2,28 @@ {% trans_default_domain 'ibexa_notifications' %} -{% set wrapper_additional_classes = 'ibexa-notifications-modal__item--permanently-deleted' %} +{% set wrapper_additional_classes = 'ibexa-notifications-modal__item' %} + +{% block icon %} + + + + + +{% endblock %} {% block notification_type %} - + {{ 'notification.permanently_deleted'|trans|desc('Deleted')}} {% endblock %} {% block message %} - {% embed '@ibexadesign/ui/component/table/table_body_cell.html.twig' with { class: 'ibexa-notifications-modal__description' } %} {% block content %} -

{{ 'notification.title'|trans|desc('Title:') }} {{ title }}

+

+ {{ 'notification.title'|trans|desc('Title:') }} + {{ title }} +

{{ 'notification.no_longer_available'|trans|desc('The Content item is no longer available')}}

{% endblock %} - {% endembed %} {% endblock %} diff --git a/src/bundle/Resources/views/themes/admin/account/notifications/modal.html.twig b/src/bundle/Resources/views/themes/admin/account/notifications/modal.html.twig deleted file mode 100644 index cf339a35d4..0000000000 --- a/src/bundle/Resources/views/themes/admin/account/notifications/modal.html.twig +++ /dev/null @@ -1,24 +0,0 @@ -{% trans_default_domain 'ibexa_notifications' %} - -{% embed '@ibexadesign/ui/component/modal/modal.html.twig' with { - title: 'ibexa_notifications'|trans|desc('Notifications'), - class: 'ibexa-notifications-modal', - no_header_border: true, - id: 'view-notifications', - attr_close_btn: { - 'data-notifications-total': '', - }, -} %} - {% block body_content %} -
- - - -
-
- {{ render(controller('Ibexa\\Bundle\\AdminUi\\Controller\\NotificationController::renderNotificationsPageAction', { - 'page': 1, - })) }} -
- {% endblock %} -{% endembed %} diff --git a/src/bundle/Resources/views/themes/admin/account/notifications/side-panel.html.twig b/src/bundle/Resources/views/themes/admin/account/notifications/side-panel.html.twig new file mode 100644 index 0000000000..666e3df5b7 --- /dev/null +++ b/src/bundle/Resources/views/themes/admin/account/notifications/side-panel.html.twig @@ -0,0 +1,40 @@ +{% embed '@ibexadesign/ui/component/side-panel/side_panel.html.twig' with { + title: 'ibexa_notifications'|trans|desc('Notifications'), + attr: { + 'data-actions': "create", + class: 'ibexa-notifications-modal ibexa-scroll-disabled', + id: 'view-notifications', + }, +}%} + {% block header %} +
+ {{ 'ibexa_notifications'|trans|desc('Notifications')}} + +
+ {% endblock %} + + {% block content %} +
+
+ + + +
+
+ {{ render(controller('Ibexa\\Bundle\\AdminUi\\Controller\\NotificationController::renderNotificationsPageAction', { + 'page': 1, + })) }} +
+
+ {% endblock %} + + {% block footer %} + + {% endblock %} +{% endembed %} \ No newline at end of file diff --git a/src/bundle/Resources/views/themes/admin/ui/component/side-panel/side_panel.html.twig b/src/bundle/Resources/views/themes/admin/ui/component/side-panel/side_panel.html.twig new file mode 100644 index 0000000000..77a6b82261 --- /dev/null +++ b/src/bundle/Resources/views/themes/admin/ui/component/side-panel/side_panel.html.twig @@ -0,0 +1,49 @@ +{% import '@ibexadesign/ui/component/macros.html.twig' as html %} + +{% trans_default_domain 'ibexa_admin_ui' %} + +{% set config_panel_main_class = 'ibexa-side-panel ibexa-side-panel--hidden' %} +{% set config_panel_footer_class = 'ibexa-side-panel__footer' ~ (footer_class is defined ? footer_class ~ '')%} + +{% set attr = attr|default({})|merge({ + class: (' ' + ~ class|default('') ~ ' ' + ~ attr.class|default(''))|trim ~ ' ' ~ config_panel_main_class, +}) %} + +{% if id is defined %} + {% set attr = attr|default({})|merge({ + id, + }) %} +{% endif %} + +
+ {% block panel %} +
+ {% block header %} + +

{{ title }}

+ {% endblock %} + {% block content %}{% endblock %} + + +
+ {% endblock %} +
+ diff --git a/src/bundle/Resources/views/themes/admin/ui/menu/user.html.twig b/src/bundle/Resources/views/themes/admin/ui/menu/user.html.twig index c09f260385..ae29934c2c 100644 --- a/src/bundle/Resources/views/themes/admin/ui/menu/user.html.twig +++ b/src/bundle/Resources/views/themes/admin/ui/menu/user.html.twig @@ -6,8 +6,7 @@
@@ -26,9 +25,9 @@ - - {{ include('@ibexadesign/account/notifications/modal.html.twig') }} - +
+ {{ include('@ibexadesign/account/notifications/side-panel.html.twig') }} +
{% block current_user %} From 3dd59ad3c980e412c51799f5f9c86f6c8fac1be3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Bia=C5=82czak?= Date: Thu, 3 Apr 2025 13:46:03 +0200 Subject: [PATCH 05/94] IBX-9060: Added search form and query support to notifications page --- .../Controller/NotificationController.php | 24 ++++++++++++- src/bundle/Form/Data/SearchQueryData.php | 24 +++++++++++++ src/bundle/Form/Type/SearchType.php | 34 +++++++++++++++++++ .../Pagerfanta/NotificationAdapter.php | 23 +++++-------- 4 files changed, 90 insertions(+), 15 deletions(-) create mode 100644 src/bundle/Form/Data/SearchQueryData.php create mode 100644 src/bundle/Form/Type/SearchType.php diff --git a/src/bundle/Controller/NotificationController.php b/src/bundle/Controller/NotificationController.php index 004a8fb35e..d611ebafba 100644 --- a/src/bundle/Controller/NotificationController.php +++ b/src/bundle/Controller/NotificationController.php @@ -12,11 +12,14 @@ use Ibexa\AdminUi\Form\Factory\FormFactory; use Ibexa\AdminUi\Form\SubmitHandler; use Ibexa\AdminUi\Pagination\Pagerfanta\NotificationAdapter; +use Ibexa\Bundle\AdminUi\Form\Data\SearchQueryData; +use Ibexa\Bundle\AdminUi\Form\Type\SearchType; use Ibexa\Contracts\AdminUi\Controller\Controller; use Ibexa\Contracts\Core\Repository\NotificationService; use Ibexa\Contracts\Core\SiteAccess\ConfigResolverInterface; use Ibexa\Core\Notification\Renderer\Registry; use Pagerfanta\Pagerfanta; +use Symfony\Component\Form\FormInterface; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; @@ -81,8 +84,16 @@ public function getNotificationsAction(Request $request, int $offset, int $limit public function renderNotificationsPageAction(Request $request, int $page): Response { + $searchForm = $this->createSearchForm(); + $searchForm->handleRequest($request); + + if ($searchForm->isSubmitted() && $searchForm->isValid()) { + $data = $searchForm->getData(); + $query = $this->buildQuery($data); + } + $pagerfanta = new Pagerfanta( - new NotificationAdapter($this->notificationService) + new NotificationAdapter($this->notificationService, $query ?? null) ); $pagerfanta->setMaxPerPage($this->configResolver->getParameter('pagination.notification_limit')); $pagerfanta->setCurrentPage(min($page, $pagerfanta->getNbPages())); @@ -105,10 +116,21 @@ public function renderNotificationsPageAction(Request $request, int $page): Resp 'notifications' => $notifications, 'notifications_count_interval' => $this->configResolver->getParameter('notification_count.interval'), 'pager' => $pagerfanta, + 'search_form' => $searchForm->createView(), 'form_remove' => $deleteNotificationsForm->createView(), ]); } + private function buildQuery(SearchQueryData $data): ?string + { + return $data->getQuery(); + } + + private function createSearchForm(): FormInterface + { + return $this->createForm(SearchType::class); + } + private function createNotificationRemoveData(Pagerfanta $pagerfanta): NotificationRemoveData { $notificationIds = []; diff --git a/src/bundle/Form/Data/SearchQueryData.php b/src/bundle/Form/Data/SearchQueryData.php new file mode 100644 index 0000000000..f250e414ba --- /dev/null +++ b/src/bundle/Form/Data/SearchQueryData.php @@ -0,0 +1,24 @@ +query; + } + + public function setQuery(?string $query): void + { + $this->query = $query; + } +} diff --git a/src/bundle/Form/Type/SearchType.php b/src/bundle/Form/Type/SearchType.php new file mode 100644 index 0000000000..3997473571 --- /dev/null +++ b/src/bundle/Form/Type/SearchType.php @@ -0,0 +1,34 @@ +add('query', TextType::class, [ + 'required' => false, + ]); + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'method' => 'GET', + 'csrf_protection' => false, + 'data_class' => SearchQueryData::class, + ]); + } +} diff --git a/src/lib/Pagination/Pagerfanta/NotificationAdapter.php b/src/lib/Pagination/Pagerfanta/NotificationAdapter.php index ec6f6ab6e5..94f6631e99 100644 --- a/src/lib/Pagination/Pagerfanta/NotificationAdapter.php +++ b/src/lib/Pagination/Pagerfanta/NotificationAdapter.php @@ -17,19 +17,18 @@ */ class NotificationAdapter implements AdapterInterface { - /** @var \Ibexa\Contracts\Core\Repository\NotificationService */ - private $notificationService; + private NotificationService $notificationService; - /** @var int */ - private $nbResults; + private ?string $query; + + private int $nbResults; - /** - * @param \Ibexa\Contracts\Core\Repository\NotificationService $notificationService - */ public function __construct( - NotificationService $notificationService + NotificationService $notificationService, + ?string $query = null ) { $this->notificationService = $notificationService; + $this->query = $query; } /** @@ -39,11 +38,7 @@ public function __construct( */ public function getNbResults(): int { - if ($this->nbResults !== null) { - return $this->nbResults; - } - - return $this->nbResults = $this->notificationService->getNotificationCount(); + return $this->nbResults ?? ($this->nbResults = $this->notificationService->getNotificationCount($this->query)); } /** @@ -56,7 +51,7 @@ public function getNbResults(): int */ public function getSlice($offset, $length): NotificationList { - $notifications = $this->notificationService->loadNotifications($offset, $length); + $notifications = $this->notificationService->loadNotifications($offset, $length, $this->query); if (null === $this->nbResults) { $this->nbResults = $notifications->totalCount; From 2f7208c3dc6506fd6bcb32b819e952a55bc4d119 Mon Sep 17 00:00:00 2001 From: Dariusz Szut Date: Fri, 28 Mar 2025 14:13:36 +0100 Subject: [PATCH 06/94] IBX-9060: Fixed modal --- .../js/scripts/admin.notifications.modal.js | 134 +++++++----------- .../public/js/scripts/sidebar/side.panel.js | 73 +++------- .../public/scss/_notifications-modal.scss | 23 +++ .../Resources/public/scss/_side-panel.scss | 43 ++---- .../account/notifications/list.html.twig | 4 +- .../account/notifications/list_item.html.twig | 6 +- ...e-panel.html.twig => side_panel.html.twig} | 10 +- .../side_panel.html.twig | 4 +- .../views/themes/admin/ui/menu/user.html.twig | 8 +- 9 files changed, 125 insertions(+), 180 deletions(-) rename src/bundle/Resources/views/themes/admin/account/notifications/{side-panel.html.twig => side_panel.html.twig} (77%) rename src/bundle/Resources/views/themes/admin/ui/component/{side-panel => side_panel}/side_panel.html.twig (95%) diff --git a/src/bundle/Resources/public/js/scripts/admin.notifications.modal.js b/src/bundle/Resources/public/js/scripts/admin.notifications.modal.js index 256efca1a4..4f69cb3a35 100644 --- a/src/bundle/Resources/public/js/scripts/admin.notifications.modal.js +++ b/src/bundle/Resources/public/js/scripts/admin.notifications.modal.js @@ -14,45 +14,45 @@ const CLASS_MODAL_LOADING = 'ibexa-notifications-modal--loading'; const INTERVAL = 30000; const panel = doc.querySelector('.ibexa-notifications-modal'); - const popupBtns = [...doc.querySelectorAll('.ibexa-multilevel-popup-menu__item-content')]; - const SELECTOR_MORE_ACTION = '.ibexa-notifications-modal--more'; const { showErrorNotification, showWarningNotification } = ibexa.helpers.notification; const { getJsonFromResponse, getTextFromResponse } = ibexa.helpers.request; const handleNotificationClick = (notification, isToggle = false) => { const notificationRow = notification.closest('.ibexa-table__row'); const isRead = notification.classList.contains('ibexa-notifications-modal__item--read'); - const notificationReadLink = isToggle && isRead ? notificationRow.dataset.notificationUnread : notificationRow.dataset.notificationRead; + const notificationReadLink = + isToggle && isRead ? notificationRow.dataset.notificationUnread : notificationRow.dataset.notificationRead; const request = new Request(notificationReadLink, { mode: 'cors', credentials: 'same-origin', }); - - fetch(request).then(getJsonFromResponse).then((response) => { - if (response.status === 'success') { - notification.classList.toggle('ibexa-notifications-modal__item--read', !isRead); - - if(isToggle) { - notification.querySelector('.ibexa-table__cell .ibexa-notification-viewAll__mail-open')?.classList.toggle('ibexa-notification-viewAll__icon-hidden'); - notification.querySelector('.ibexa-table__cell .ibexa-notification-viewAll__mail-closed')?.classList.toggle('ibexa-notification-viewAll__icon-hidden'); - - const statusText = isRead ? Translator.trans( - /*@Desc("Unread")*/ 'notification.unread', - {}, - 'ibexa_notifications', - ) : Translator.trans( - /*@Desc("Read")*/ 'notification.read', - {}, - 'ibexa_notifications', - ); - notification.closest('.ibexa-table__row').querySelector('.ibexa-notification-viewAll__read').innerHTML = statusText; - return; - } - if (response.redirect) { - global.location = response.redirect; + fetch(request) + .then(getJsonFromResponse) + .then((response) => { + if (response.status === 'success') { + notification.classList.toggle('ibexa-notifications-modal__item--read', !isRead); + + if (isToggle) { + notification + .querySelector('.ibexa-table__cell .ibexa-notification-viewAll__mail-open') + ?.classList.toggle('ibexa-notification-viewAll__icon-hidden'); + notification + .querySelector('.ibexa-table__cell .ibexa-notification-viewAll__mail-closed') + ?.classList.toggle('ibexa-notification-viewAll__icon-hidden'); + + const statusText = isRead + ? Translator.trans(/*@Desc("Unread")*/ 'notification.unread', {}, 'ibexa_notifications') + : Translator.trans(/*@Desc("Read")*/ 'notification.read', {}, 'ibexa_notifications'); + notification.closest('.ibexa-table__row').querySelector('.ibexa-notification-viewAll__read').innerHTML = statusText; + return; + } + + if (response.redirect) { + global.location = response.redirect; + } } - } - }).catch(showErrorNotification); + }) + .catch(showErrorNotification); }; const handleTableClick = (event) => { if (event.target.classList.contains('description__read-more')) { @@ -70,23 +70,6 @@ handleNotificationClick(notification); }; - const initNotificationPopup = () => { - //TODO: init popups - // const notificationsTable = panel.querySelector(SELECTOR_LIST); - // const popups = [...panel.querySelectorAll('.ibexa-multilevel-popup-menu:not(.ibexa-multilevel-popup-menu--custom-init)')]; - // popups.forEach(function (popupBtn) { - // const multilevelPopupMenu = new ibexa.core.MultilevelPopupMenu({ - // container: popupBtn, - // triggerElement: popupBtn, - // // referenceElement: this.container, - // initialBranchPlacement: popupBtn.dataset?.initialBranchPlacement, - // // initialBranchFallbackPlacements: ['bottom-end', 'top-end', 'top-start'], - // // onTopBranchOpened: this.handlePopupOpened, - // // onTopBranchClosed: this.handlePopupClosed, - // }); - // multilevelPopupMenu.init(); - // }); - } const getNotificationsStatus = () => { const notificationsTable = panel.querySelector(SELECTOR_LIST); const notificationsStatusLink = notificationsTable.dataset.notificationsCount; @@ -107,12 +90,6 @@ }) .catch(onGetNotificationsStatusFailure); }; - - /** - * Handle a failure while getting notifications status - * - * @method onGetNotificationsStatusFailure - */ const onGetNotificationsStatusFailure = (error) => { if (lastFailedCountFetchNotificationNode && doc.contains(lastFailedCountFetchNotificationNode)) { return; @@ -146,7 +123,7 @@ const setPendingNotificationCount = (notificationsInfo) => { updatePendingNotificationsView(notificationsInfo); - const notificationsTable = panel.querySelectzor(SELECTOR_LIST); + const notificationsTable = panel.querySelector(SELECTOR_LIST); const notificationsTotal = notificationsInfo.total; const notificationsTotalOld = parseInt(notificationsTable.dataset.notificationsTotal, 10); @@ -205,56 +182,43 @@ return; } - const initTooltipIfOverflow = (popup) => { - const label = popup.querySelector('.ibexa-btn__label'); - const popupContainer = popup.closest('.ibexa-multilevel-popup-menu__group'); - - if (label.scrollWidth < popupContainer.offsetWidth) { - return; - } - - popup.title = label.textContent; - ibexa.helpers.tooltips.parse(popup); - }; - const handleMoreActionBtnClick =(btn) => { - const noticeId = btn.closest('.ibexa-notifications-modal__item').dataset.notificationId; - popupBtns.forEach(function (popupBtn) { - const actionGroup = popupBtn.closest('.ibexa-multilevel-popup-menu__group'); - - if(actionGroup.dataset.groupId === noticeId) { - return initTooltipIfOverflow(popupBtn); - }; - }); - //event.removeEventListener('click', handleMoreActionBtnClick); - }; - - const handleNotificationActionClick =(event, isToggle = false) => { + const handleNotificationActionClick = (event, isToggle = false) => { const notification = event.target.closest(SELECTOR_MODAL_ITEM); if (!notification) { - return + return; } handleNotificationClick(notification, isToggle); - } + }; const initStatusIcons = () => { doc.querySelectorAll(SELECTOR_MODAL_ITEM).forEach((item) => { const isRead = item.classList.contains('ibexa-notifications-modal__item--read'); - - item.querySelector(`.ibexa-table__cell .ibexa-notification-viewAll__mail-closed`)?.classList.toggle('ibexa-notification-viewAll__icon-hidden', !isRead); - item.querySelector(`.ibexa-table__cell .ibexa-notification-viewAll__mail-open`)?.classList.toggle('ibexa-notification-viewAll__icon-hidden', isRead); + + item.querySelector(`.ibexa-table__cell .ibexa-notification-viewAll__mail-closed`)?.classList.toggle( + 'ibexa-notification-viewAll__icon-hidden', + !isRead, + ); + item.querySelector(`.ibexa-table__cell .ibexa-notification-viewAll__mail-open`)?.classList.toggle( + 'ibexa-notification-viewAll__icon-hidden', + isRead, + ); }, false); }; - initStatusIcons(); + initStatusIcons(); + const notificationsTable = panel.querySelector(SELECTOR_LIST); currentPageLink = notificationsTable.dataset.notifications; const interval = Number.parseInt(notificationsTable.dataset.notificationsCountInterval, 10) || INTERVAL; panel.querySelectorAll(SELECTOR_MODAL_RESULTS).forEach((link) => link.addEventListener('click', handleModalResultsClick, false)); - panel.querySelectorAll(SELECTOR_MORE_ACTION).forEach((btn) => btn.addEventListener('click', () => handleMoreActionBtnClick(btn))); - doc.querySelectorAll(SELECTOR_GO_TO_NOTIFICATION).forEach((link) => link.addEventListener('click', handleNotificationActionClick, false)); - doc.querySelectorAll(SELECTOR_TOGGLE_NOTIFICATION).forEach((link) => link.addEventListener('click', (event) => handleNotificationActionClick(event, true), false)); + doc.querySelectorAll(SELECTOR_GO_TO_NOTIFICATION).forEach((link) => + link.addEventListener('click', handleNotificationActionClick, false), + ); + doc.querySelectorAll(SELECTOR_TOGGLE_NOTIFICATION).forEach((link) => + link.addEventListener('click', (event) => handleNotificationActionClick(event, true), false), + ); const getNotificationsStatusLoop = () => { getNotificationsStatus().finally(() => { diff --git a/src/bundle/Resources/public/js/scripts/sidebar/side.panel.js b/src/bundle/Resources/public/js/scripts/sidebar/side.panel.js index 44c0c05043..53d17fa014 100644 --- a/src/bundle/Resources/public/js/scripts/sidebar/side.panel.js +++ b/src/bundle/Resources/public/js/scripts/sidebar/side.panel.js @@ -1,83 +1,56 @@ (function (global, doc, ibexa) { const CLASS_HIDDEN = 'ibexa-side-panel--hidden'; - const closeBtns = doc.querySelectorAll( + const sidePanelCloseBtns = doc.querySelectorAll( '.ibexa-side-panel .ibexa-btn--close, .ibexa-side-panel .ibexa-side-panel__btn--cancel', ); - const btns = [...doc.querySelectorAll('.ibexa-btn--side-panel-actions')]; + const sidePanelTriggers = [...doc.querySelectorAll('.ibexa-side-panel-trigger')]; const backdrop = new ibexa.core.Backdrop(); - const haveHiddenPart = (element) => element.classList.contains(CLASS_HIDDEN); const removeBackdrop = () => { backdrop.hide(); doc.body.classList.remove('ibexa-scroll-disabled'); }; - const allActivityBtn = doc?.querySelector('.ibexa-notifications__view-all-btn'); - - const closeExtraActions = (actions) => { - actions.classList.add(CLASS_HIDDEN); - - doc.body.dispatchEvent(new CustomEvent('ibexa-side-panel:after-close')); - - removeBackdrop(); + const showBackdrop = () => { + backdrop.show(); + doc.body.classList.add('ibexa-scroll-disabled'); }; - const toggleExtraActionsWidget = (event) => { - const actions = doc.querySelector(`.ibexa-side-panel[data-actions="create"]`); - const isHidden = haveHiddenPart(actions); - const detectClickOutside = (event) => { + const toggleSidePanelVisibility = (sidePanel) => { + const shouldBeVisible = sidePanel.classList.contains(CLASS_HIDDEN); + const handleClickOutside = (event) => { if (event.target.classList.contains('ibexa-backdrop')) { - closeExtraActions(actions); - doc.body.removeEventListener('click', detectClickOutside, false); + sidePanel.classList.add(CLASS_HIDDEN); + doc.body.removeEventListener('click', handleClickOutside, false); + removeBackdrop(); } }; - actions.classList.toggle(CLASS_HIDDEN, !isHidden); + sidePanel.classList.toggle(CLASS_HIDDEN, !shouldBeVisible); - if (!actions.classList.contains(CLASS_HIDDEN)) { - backdrop.show(); - doc.body.addEventListener('click', detectClickOutside, false); - doc.body.classList.add('ibexa-scroll-disabled'); + if (shouldBeVisible) { + doc.body.addEventListener('click', handleClickOutside, false); + showBackdrop(); } else { - doc.body.removeEventListener('click', detectClickOutside); + doc.body.removeEventListener('click', handleClickOutside, false); removeBackdrop(); } }; - const hideMenu = (btn) => { - const menuBranch = btn.closest('.ibexa-multilevel-popup-menu__branch'); - - if (!menuBranch?.menuInstanceElement) { - return; - } - - const menuInstance = ibexa.helpers.objectInstances.getInstance(menuBranch.menuInstanceElement); - - menuInstance.closeMenu(); - }; - const goToActivityLog = () => { - window.location.href = Routing.generate('ibexa.notifications.render.all'); - }; - - btns.forEach((btn) => { - const { dataset } = btn; - - btn.addEventListener( + sidePanelTriggers.forEach((trigger) => { + trigger.addEventListener( 'click', - () => { - toggleExtraActionsWidget(dataset); - hideMenu(btn); + (event) => { + toggleSidePanelVisibility(doc.querySelector(event.currentTarget.dataset.sidePanelSelector)); }, false, ); }); - doc.body.addEventListener('ibexa-side-panel', (event) => toggleExtraActionsWidget(event), false); - closeBtns.forEach((closeBtn) => + + sidePanelCloseBtns.forEach((closeBtn) => closeBtn.addEventListener( 'click', (event) => { - closeExtraActions(event.currentTarget.closest('.ibexa-side-panel')); + toggleSidePanelVisibility(event.currentTarget.closest('.ibexa-side-panel')); }, false, ), ); - allActivityBtn?.addEventListener('click', goToActivityLog, false); - })(window, window.document, window.ibexa); diff --git a/src/bundle/Resources/public/scss/_notifications-modal.scss b/src/bundle/Resources/public/scss/_notifications-modal.scss index 19f41bda2c..cbdea6d13c 100644 --- a/src/bundle/Resources/public/scss/_notifications-modal.scss +++ b/src/bundle/Resources/public/scss/_notifications-modal.scss @@ -11,6 +11,7 @@ table-layout: fixed; white-space: normal; margin-bottom: 0; + .ibexa-table__row .ibexa-table__cell{ height: 115px; padding-right: 0; @@ -19,10 +20,12 @@ } } + &__list { max-height: 90vh; overflow-y: auto; } + &__type { .type__icon { @include type-icon; @@ -33,30 +36,37 @@ margin-left: 1rem; } } + &__item--date { font-size: $ibexa-text-font-size-small; color: $ibexa-color-light-700; } + &__type-content { width: 100%; font-size: 14px; + p { margin-bottom: 0; } } + &__item { position: relative; border: 1px solid $ibexa-color-light; border-bottom: none; + &--wrapper { display: flex; td { background-color: $ibexa-color-light-300 !important;//TODO:! } } + &:first-of-type { border-top: none; } + .description__text { font-size: $ibexa-text-font-size-medium; @@ -68,6 +78,7 @@ } + &__notice-dot { width: calculateRem(8px); height: calculateRem(8px); @@ -80,6 +91,7 @@ font-size: 10px; color: white; } + &__item--read { color: $ibexa-color-dark-300; @@ -88,6 +100,7 @@ background: $ibexa-color-dark-300 } } + &__item--read-wrapper { td { background-color: white !important; @@ -160,6 +173,16 @@ height: 2rem; } } + + &__actions { + .ibexa-icon { + margin-right: 0; + } + + .ibexa-btn { + margin-right: calculateRem(8px); + } + } } //TODO: needs cleaning .ibexa-notification-viewAll { diff --git a/src/bundle/Resources/public/scss/_side-panel.scss b/src/bundle/Resources/public/scss/_side-panel.scss index 1efdce56d8..82ee287080 100644 --- a/src/bundle/Resources/public/scss/_side-panel.scss +++ b/src/bundle/Resources/public/scss/_side-panel.scss @@ -2,9 +2,14 @@ background-color: $ibexa-color-white; padding: calculateRem(8px) 0; width: calculateRem(516px); + height: calc(100vh - #{calculateRem(73px)}); + position: fixed; + top: calculateRem(73px); + right: 0; + z-index: 200; &__header { - padding: 0 calculateRem(32px) calculateRem(8px); + padding: calculateRem(8px) calculateRem(32px) calculateRem(16px); font-weight: bold; border-bottom: calculateRem(1px) solid $ibexa-color-light; display: flex; @@ -23,42 +28,20 @@ align-items: center; box-shadow: 0 0 calculateRem(16px) 0 rgba($ibexa-color-dark, 0.16); z-index: 1000; - width: 100%; + width: calculateRem(516px); + position: fixed; + bottom: 0; .ibexa-btn { margin-right: calculateRem(16px); } + + .ibexa-notifications-modal__footer { + width: calculateRem(516px); + } } &--hidden { display: none; } } - -.ibexa-extra-actions-container { - &__backdrop { - position: fixed; - top: 0; - left: 0; - right: 0; - bottom: 0; - z-index: 150; - background-color: rgba($ibexa-color-dark, 0.4); - } - - .ibexa-side-panel { - height: calc(100vh - #{calculateRem(73px)}); - position: fixed; - top: calculateRem(73px); - right: 0; - z-index: 200; - transform: translate(0, 0) scaleX(1); - transform-origin: right center; - transition: $ibexa-admin-widget-open-transition; - - &--hidden { - transform: translate(calc(100%), 0) scaleX(0); - transition: $ibexa-admin-widget-close-transition; - } - } -} diff --git a/src/bundle/Resources/views/themes/admin/account/notifications/list.html.twig b/src/bundle/Resources/views/themes/admin/account/notifications/list.html.twig index 302fead545..f756ea8ace 100644 --- a/src/bundle/Resources/views/themes/admin/account/notifications/list.html.twig +++ b/src/bundle/Resources/views/themes/admin/account/notifications/list.html.twig @@ -14,7 +14,9 @@ {% if pager.count is same as(0) %} {{ 'bookmark.list.empty'|trans|desc('You have no notifications.') }} {% else %} - {{ notifications|raw }} + {% for notification in notifications %} + {{ notification|raw }} + {% endfor %} {% endif %} {% endblock %} {% endembed %} diff --git a/src/bundle/Resources/views/themes/admin/account/notifications/list_item.html.twig b/src/bundle/Resources/views/themes/admin/account/notifications/list_item.html.twig index de6e3f96d0..07fc10cc65 100644 --- a/src/bundle/Resources/views/themes/admin/account/notifications/list_item.html.twig +++ b/src/bundle/Resources/views/themes/admin/account/notifications/list_item.html.twig @@ -83,8 +83,8 @@ {% endembed %} {% embed '@ibexadesign/ui/component/table/table_body_cell.html.twig' %} {% block content %} -
-
@@ -31,10 +31,10 @@ {% endblock %} {% block footer %} - {% endblock %} {% endembed %} \ No newline at end of file diff --git a/src/bundle/Resources/views/themes/admin/ui/component/side-panel/side_panel.html.twig b/src/bundle/Resources/views/themes/admin/ui/component/side_panel/side_panel.html.twig similarity index 95% rename from src/bundle/Resources/views/themes/admin/ui/component/side-panel/side_panel.html.twig rename to src/bundle/Resources/views/themes/admin/ui/component/side_panel/side_panel.html.twig index 77a6b82261..ca594d3dc2 100644 --- a/src/bundle/Resources/views/themes/admin/ui/component/side-panel/side_panel.html.twig +++ b/src/bundle/Resources/views/themes/admin/ui/component/side_panel/side_panel.html.twig @@ -17,9 +17,9 @@ }) %} {% endif %} -
+
{% block panel %} -
+
{% block header %} -
- {{ include('@ibexadesign/account/notifications/side-panel.html.twig') }} +
+ {{ include('@ibexadesign/account/notifications/side_panel.html.twig') }}
From b327aa2bd0fdbcb8d6cdddb79bf04d3e1f44fa66 Mon Sep 17 00:00:00 2001 From: Dariusz Szut Date: Mon, 31 Mar 2025 15:33:52 +0200 Subject: [PATCH 07/94] IBX-9060: Fixed all list --- .../Resources/encore/ibexa.js.config.js | 4 +- .../js/scripts/admin.multilevel.popup.menu.js | 34 +++++-- .../js/scripts/admin.notifications.list.js | 80 ++++++++++++++++ .../js/scripts/admin.notifications.modal.js | 91 +++++-------------- .../public/scss/_notifications-modal.scss | 24 ++--- .../account/notifications/list_all.html.twig | 8 +- .../notifications/list_item_all.html.twig | 24 ++--- .../notifications/list_item_deleted.html.twig | 2 +- 8 files changed, 159 insertions(+), 108 deletions(-) create mode 100644 src/bundle/Resources/public/js/scripts/admin.notifications.list.js diff --git a/src/bundle/Resources/encore/ibexa.js.config.js b/src/bundle/Resources/encore/ibexa.js.config.js index 53e910a5c2..531bbc5f46 100644 --- a/src/bundle/Resources/encore/ibexa.js.config.js +++ b/src/bundle/Resources/encore/ibexa.js.config.js @@ -200,7 +200,6 @@ module.exports = (Encore) => { path.resolve(__dirname, '../public/js/scripts/fieldType/base/multi-input-field.js'), ...fieldTypes, path.resolve(__dirname, '../public/js/scripts/sidebar/extra.actions.js'), - path.resolve(__dirname, '../public/js/scripts/sidebar/side.panel.js'), path.resolve(__dirname, '../public/js/scripts/edit.header.js'), ]) .addEntry('ibexa-admin-ui-settings-datetime-format-update-js', [ @@ -254,5 +253,6 @@ module.exports = (Encore) => { path.resolve(__dirname, '../public/js/scripts/admin.location.tab.js'), path.resolve(__dirname, '../public/js/scripts/admin.location.adaptive.tabs.js'), ]) - .addEntry('ibexa-admin-ui-edit-base-js', [path.resolve(__dirname, '../public/js/scripts/edit.header.js')]); + .addEntry('ibexa-admin-ui-edit-base-js', [path.resolve(__dirname, '../public/js/scripts/edit.header.js')]) + .addEntry('ibexa-admin-notifications-list-js', [path.resolve(__dirname, '../public/js/scripts/admin.notifications.list.js')]); }; diff --git a/src/bundle/Resources/public/js/scripts/admin.multilevel.popup.menu.js b/src/bundle/Resources/public/js/scripts/admin.multilevel.popup.menu.js index 9093770d6f..ea93681155 100644 --- a/src/bundle/Resources/public/js/scripts/admin.multilevel.popup.menu.js +++ b/src/bundle/Resources/public/js/scripts/admin.multilevel.popup.menu.js @@ -1,15 +1,29 @@ (function (global, doc, ibexa) { - const multilevelPopupMenusContainers = doc.querySelectorAll( - '.ibexa-multilevel-popup-menu:not(.ibexa-multilevel-popup-menu--custom-init)', - ); + const initMutlilevelPopupMenus = (container) => { + const multilevelPopupMenusContainers = container.querySelectorAll( + '.ibexa-multilevel-popup-menu:not(.ibexa-multilevel-popup-menu--custom-init)', + ); + + multilevelPopupMenusContainers.forEach((multilevelPopupMenusContainer) => { + const multilevelPopupMenu = new ibexa.core.MultilevelPopupMenu({ + container: multilevelPopupMenusContainer, + triggerElement: doc.querySelector(multilevelPopupMenusContainer.dataset.triggerElementSelector), + initialBranchPlacement: multilevelPopupMenusContainer.dataset.initialBranchPlacement, + }); - multilevelPopupMenusContainers.forEach((container) => { - const multilevelPopupMenu = new ibexa.core.MultilevelPopupMenu({ - container, - triggerElement: doc.querySelector(container.dataset.triggerElementSelector), - initialBranchPlacement: container.dataset.initialBranchPlacement, + multilevelPopupMenu.init(); }); + }; + + initMutlilevelPopupMenus(doc); - multilevelPopupMenu.init(); - }); + doc.body.addEventListener( + 'ibexa-multilevel-popup-menu:init', + (event) => { + const { container } = event.detail; + + initMutlilevelPopupMenus(container); + }, + false, + ); })(window, window.document, window.ibexa); diff --git a/src/bundle/Resources/public/js/scripts/admin.notifications.list.js b/src/bundle/Resources/public/js/scripts/admin.notifications.list.js new file mode 100644 index 0000000000..1f95e9db11 --- /dev/null +++ b/src/bundle/Resources/public/js/scripts/admin.notifications.list.js @@ -0,0 +1,80 @@ +(function (global, doc, ibexa, Translator) { + const SELECTOR_MODAL_ITEM = '.ibexa-notifications-modal__item'; + const SELECTOR_GO_TO_NOTIFICATION = '.ibexa-notification-view-all__show'; + const SELECTOR_TOGGLE_NOTIFICATION = '.ibexa-notification-view-all__mail'; + const { showErrorNotification } = ibexa.helpers.notification; + const { getJsonFromResponse } = ibexa.helpers.request; + const handleNotificationClick = (notification, isToggle = false) => { + const notificationRow = notification.closest('.ibexa-table__row'); + const isRead = notification.classList.contains('ibexa-notifications-modal__item--read'); + const notificationReadLink = + isToggle && isRead ? notificationRow.dataset.notificationUnread : notificationRow.dataset.notificationRead; + const request = new Request(notificationReadLink, { + mode: 'cors', + credentials: 'same-origin', + }); + + fetch(request) + .then(getJsonFromResponse) + .then((response) => { + if (response.status === 'success') { + notification.classList.toggle('ibexa-notifications-modal__item--read', !isRead); + + if (isToggle) { + notification + .querySelector('.ibexa-table__cell .ibexa-notification-view-all__mail-open') + ?.classList.toggle('ibexa-notification-view-all__icon-hidden'); + notification + .querySelector('.ibexa-table__cell .ibexa-notification-view-all__mail-closed') + ?.classList.toggle('ibexa-notification-view-all__icon-hidden'); + + const statusText = isRead + ? Translator.trans(/*@Desc("Unread")*/ 'notification.unread', {}, 'ibexa_notifications') + : Translator.trans(/*@Desc("Read")*/ 'notification.read', {}, 'ibexa_notifications'); + notification.closest('.ibexa-table__row').querySelector('.ibexa-notification-view-all__read').innerHTML = + statusText; + + return; + } + + if (response.redirect) { + global.location = response.redirect; + } + } + }) + .catch(showErrorNotification); + }; + + const handleNotificationActionClick = (event, isToggle = false) => { + const notification = event.target.closest(SELECTOR_MODAL_ITEM); + + if (!notification) { + return; + } + + handleNotificationClick(notification, isToggle); + }; + const initStatusIcons = () => { + doc.querySelectorAll(SELECTOR_MODAL_ITEM).forEach((item) => { + const isRead = item.classList.contains('ibexa-notifications-modal__item--read'); + + item.querySelector(`.ibexa-table__cell .ibexa-notification-view-all__mail-closed`)?.classList.toggle( + 'ibexa-notification-view-all__icon-hidden', + !isRead, + ); + item.querySelector(`.ibexa-table__cell .ibexa-notification-view-all__mail-open`)?.classList.toggle( + 'ibexa-notification-view-all__icon-hidden', + isRead, + ); + }, false); + }; + + initStatusIcons(); + + doc.querySelectorAll(SELECTOR_GO_TO_NOTIFICATION).forEach((link) => + link.addEventListener('click', handleNotificationActionClick, false), + ); + doc.querySelectorAll(SELECTOR_TOGGLE_NOTIFICATION).forEach((link) => + link.addEventListener('click', (event) => handleNotificationActionClick(event, true), false), + ); +})(window, window.document, window.ibexa, window.Translator); diff --git a/src/bundle/Resources/public/js/scripts/admin.notifications.modal.js b/src/bundle/Resources/public/js/scripts/admin.notifications.modal.js index 4f69cb3a35..688b29e601 100644 --- a/src/bundle/Resources/public/js/scripts/admin.notifications.modal.js +++ b/src/bundle/Resources/public/js/scripts/admin.notifications.modal.js @@ -3,9 +3,7 @@ let getNotificationsStatusErrorShowed = false; let lastFailedCountFetchNotificationNode = null; const SELECTOR_MODAL_ITEM = '.ibexa-notifications-modal__item'; - const SELECTOR_MODAL_RESULTS = '.ibexa-notifications-modal__type-content'; - const SELECTOR_GO_TO_NOTIFICATION = '.ibexa-notification-viewAll__show'; - const SELECTOR_TOGGLE_NOTIFICATION = '.ibexa-notification-viewAll__mail'; + const SELECTOR_MODAL_RESULTS = '.ibexa-notifications-modal__results .ibexa-scrollable-wrapper'; const SELECTOR_MODAL_TITLE = '.ibexa-side-panel__header'; const SELECTOR_DESC_TEXT = '.description__text'; const SELECTOR_LIST = '.ibexa-list--notifications'; @@ -16,43 +14,23 @@ const panel = doc.querySelector('.ibexa-notifications-modal'); const { showErrorNotification, showWarningNotification } = ibexa.helpers.notification; const { getJsonFromResponse, getTextFromResponse } = ibexa.helpers.request; - const handleNotificationClick = (notification, isToggle = false) => { - const notificationRow = notification.closest('.ibexa-table__row'); - const isRead = notification.classList.contains('ibexa-notifications-modal__item--read'); - const notificationReadLink = - isToggle && isRead ? notificationRow.dataset.notificationUnread : notificationRow.dataset.notificationRead; + const markAsRead = (notification, response) => { + if (response.status === 'success') { + notification.classList.add('ibexa-notifications-modal__item--read'); + } + + if (response.redirect) { + global.location = response.redirect; + } + }; + const handleNotificationClick = (notification) => { + const notificationReadLink = notification.dataset.notificationRead; const request = new Request(notificationReadLink, { mode: 'cors', credentials: 'same-origin', }); - fetch(request) - .then(getJsonFromResponse) - .then((response) => { - if (response.status === 'success') { - notification.classList.toggle('ibexa-notifications-modal__item--read', !isRead); - - if (isToggle) { - notification - .querySelector('.ibexa-table__cell .ibexa-notification-viewAll__mail-open') - ?.classList.toggle('ibexa-notification-viewAll__icon-hidden'); - notification - .querySelector('.ibexa-table__cell .ibexa-notification-viewAll__mail-closed') - ?.classList.toggle('ibexa-notification-viewAll__icon-hidden'); - - const statusText = isRead - ? Translator.trans(/*@Desc("Unread")*/ 'notification.unread', {}, 'ibexa_notifications') - : Translator.trans(/*@Desc("Read")*/ 'notification.read', {}, 'ibexa_notifications'); - notification.closest('.ibexa-table__row').querySelector('.ibexa-notification-viewAll__read').innerHTML = statusText; - return; - } - - if (response.redirect) { - global.location = response.redirect; - } - } - }) - .catch(showErrorNotification); + fetch(request).then(getJsonFromResponse).then(markAsRead.bind(null, notification)).catch(showErrorNotification); }; const handleTableClick = (event) => { if (event.target.classList.contains('description__read-more')) { @@ -138,6 +116,12 @@ modalResults.innerHTML = pageHtml; toggleLoading(false); + + doc.body.dispatchEvent( + new CustomEvent('ibexa-multilevel-popup-menu:init', { + detail: { container: modalResults }, + }), + ); }; const toggleLoading = (show) => { panel.classList.toggle(CLASS_MODAL_LOADING, show); @@ -162,6 +146,11 @@ }; const handleModalResultsClick = (event) => { const isPaginationBtn = event.target.classList.contains(CLASS_PAGINATION_LINK); + const isActionBtn = event.target.closest('.ibexa-notifications-modal__actions'); + + if (isActionBtn) { + return; + } if (isPaginationBtn) { handleNotificationsPageChange(event); @@ -182,43 +171,11 @@ return; } - const handleNotificationActionClick = (event, isToggle = false) => { - const notification = event.target.closest(SELECTOR_MODAL_ITEM); - - if (!notification) { - return; - } - - handleNotificationClick(notification, isToggle); - }; - const initStatusIcons = () => { - doc.querySelectorAll(SELECTOR_MODAL_ITEM).forEach((item) => { - const isRead = item.classList.contains('ibexa-notifications-modal__item--read'); - - item.querySelector(`.ibexa-table__cell .ibexa-notification-viewAll__mail-closed`)?.classList.toggle( - 'ibexa-notification-viewAll__icon-hidden', - !isRead, - ); - item.querySelector(`.ibexa-table__cell .ibexa-notification-viewAll__mail-open`)?.classList.toggle( - 'ibexa-notification-viewAll__icon-hidden', - isRead, - ); - }, false); - }; - - initStatusIcons(); - const notificationsTable = panel.querySelector(SELECTOR_LIST); currentPageLink = notificationsTable.dataset.notifications; const interval = Number.parseInt(notificationsTable.dataset.notificationsCountInterval, 10) || INTERVAL; panel.querySelectorAll(SELECTOR_MODAL_RESULTS).forEach((link) => link.addEventListener('click', handleModalResultsClick, false)); - doc.querySelectorAll(SELECTOR_GO_TO_NOTIFICATION).forEach((link) => - link.addEventListener('click', handleNotificationActionClick, false), - ); - doc.querySelectorAll(SELECTOR_TOGGLE_NOTIFICATION).forEach((link) => - link.addEventListener('click', (event) => handleNotificationActionClick(event, true), false), - ); const getNotificationsStatusLoop = () => { getNotificationsStatus().finally(() => { diff --git a/src/bundle/Resources/public/scss/_notifications-modal.scss b/src/bundle/Resources/public/scss/_notifications-modal.scss index cbdea6d13c..9610bbd7b6 100644 --- a/src/bundle/Resources/public/scss/_notifications-modal.scss +++ b/src/bundle/Resources/public/scss/_notifications-modal.scss @@ -4,7 +4,6 @@ display: flex; justify-content: flex-end; padding: calculateRem(8px); - } .table { @@ -12,13 +11,11 @@ white-space: normal; margin-bottom: 0; - .ibexa-table__row .ibexa-table__cell{ + .ibexa-table__row .ibexa-table__cell { height: 115px; padding-right: 0; border-radius: 0; - } - } &__list { @@ -59,24 +56,22 @@ &--wrapper { display: flex; td { - background-color: $ibexa-color-light-300 !important;//TODO:! + background-color: $ibexa-color-light-300 !important; //TODO:! } } &:first-of-type { border-top: none; - } + } .description__text { font-size: $ibexa-text-font-size-medium; &--permanently-deleted { - color:$ibexa-color-danger-500; + color: $ibexa-color-danger-500; font-size: $ibexa-text-font-size-medium; } } - - } &__notice-dot { @@ -96,8 +91,8 @@ color: $ibexa-color-dark-300; .ibexa-notifications-modal__notice-dot, - .ibexa-notification-viewAll__notice-dot { - background: $ibexa-color-dark-300 + .ibexa-notification-view-all__notice-dot { + background: $ibexa-color-dark-300; } } @@ -174,7 +169,7 @@ } } - &__actions { + &__actions { .ibexa-icon { margin-right: 0; } @@ -185,7 +180,7 @@ } } //TODO: needs cleaning -.ibexa-notification-viewAll { +.ibexa-notification-view-all { display: flex; align-items: center; gap: calculateRem(4px); @@ -250,11 +245,10 @@ .ibexa-table__cell.ibexa-table__cell { padding: calculateRem(12px) 0 calculateRem(12px) 0; - } } &__icon-hidden { display: none; } -} \ No newline at end of file +} diff --git a/src/bundle/Resources/views/themes/admin/account/notifications/list_all.html.twig b/src/bundle/Resources/views/themes/admin/account/notifications/list_all.html.twig index 48515a603c..df05321b9e 100644 --- a/src/bundle/Resources/views/themes/admin/account/notifications/list_all.html.twig +++ b/src/bundle/Resources/views/themes/admin/account/notifications/list_all.html.twig @@ -1,4 +1,5 @@ {% extends "@ibexadesign/ui/layout.html.twig" %} + {% import '@ibexadesign/ui/component/macros.html.twig' as html %} {% from '@ibexadesign/ui/component/macros.html.twig' import results_headline %} @@ -45,7 +46,9 @@ } %} {% else %} {% block tbody_not_empty %} - {{ notifications|raw }} + {% for notification in notifications %} + {{ notification|raw }} + {% endfor %} {% endblock %} {% endif %} {% endblock %} @@ -63,5 +66,8 @@ {% endif %}
+{% endblock %} +{% block javascripts %} + {{ encore_entry_script_tags('ibexa-admin-notifications-list-js', null, 'ibexa') }} {% endblock %} diff --git a/src/bundle/Resources/views/themes/admin/account/notifications/list_item_all.html.twig b/src/bundle/Resources/views/themes/admin/account/notifications/list_item_all.html.twig index 77289cbd3a..71b97238c8 100644 --- a/src/bundle/Resources/views/themes/admin/account/notifications/list_item_all.html.twig +++ b/src/bundle/Resources/views/themes/admin/account/notifications/list_item_all.html.twig @@ -18,7 +18,7 @@ {% set date %} {% block date %} - {{ notification.created|ibexa_short_datetime }} + {{ notification.created|ibexa_short_datetime }} {% endblock %} {% endset %} @@ -35,16 +35,16 @@ {% endset %} {% set status %} -
- - +
+ + {{is_read ? 'notification.read'|trans|desc('Read') : 'notification.unread'|trans|desc('Unread')}}
{% endset %} {% set icon_show %} - + {% endset %} @@ -81,14 +81,14 @@
{% endblock %} {% endembed %} - {% embed '@ibexadesign/ui/component/table/table_body_cell.html.twig' with { class: 'ibexa-notification-viewAll__cell-wrapper' } %} + {% embed '@ibexadesign/ui/component/table/table_body_cell.html.twig' with { class: 'ibexa-notification-view-all__cell-wrapper' } %} {% block content %} -
-
- +
+
+
{{ icon }}
-
+
{{ notification_type }} {{ message }}
@@ -112,10 +112,10 @@ {% endembed %} {% embed '@ibexadesign/ui/component/table/table_body_cell.html.twig' %} {% block content %} -
+
{{ icon_mail_open }}
-
+
{{ icon_mail }}
{% endblock %} diff --git a/src/bundle/Resources/views/themes/admin/account/notifications/list_item_deleted.html.twig b/src/bundle/Resources/views/themes/admin/account/notifications/list_item_deleted.html.twig index 5f9df1272e..2bd69ed6bc 100644 --- a/src/bundle/Resources/views/themes/admin/account/notifications/list_item_deleted.html.twig +++ b/src/bundle/Resources/views/themes/admin/account/notifications/list_item_deleted.html.twig @@ -1,4 +1,4 @@ -{% extends '@ibexadesign/account/notifications/list_item.html.twig' %} +{% extends template_to_extend %} {% trans_default_domain 'ibexa_notifications' %} From a0c2a2572af18b36049c3be59f5a3c6e5fe064e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Bia=C5=82czak?= Date: Fri, 4 Apr 2025 07:08:00 +0200 Subject: [PATCH 08/94] IBX-9060: Added mark all as read --- .../Controller/NotificationController.php | 49 ++++++++++++------- src/bundle/Resources/config/routing.yaml | 6 +++ 2 files changed, 38 insertions(+), 17 deletions(-) diff --git a/src/bundle/Controller/NotificationController.php b/src/bundle/Controller/NotificationController.php index d611ebafba..eb5780df2f 100644 --- a/src/bundle/Controller/NotificationController.php +++ b/src/bundle/Controller/NotificationController.php @@ -8,6 +8,7 @@ namespace Ibexa\Bundle\AdminUi\Controller; +use Exception; use Ibexa\AdminUi\Form\Data\Notification\NotificationRemoveData; use Ibexa\AdminUi\Form\Factory\FormFactory; use Ibexa\AdminUi\Form\SubmitHandler; @@ -27,23 +28,17 @@ class NotificationController extends Controller { - /** @var \Ibexa\Contracts\Core\Repository\NotificationService */ - protected $notificationService; + protected NotificationService $notificationService; - /** @var \Ibexa\Core\Notification\Renderer\Registry */ - protected $registry; + protected Registry $registry; - /** @var \Symfony\Contracts\Translation\TranslatorInterface */ - protected $translator; + protected TranslatorInterface $translator; - /** @var \Ibexa\Contracts\Core\SiteAccess\ConfigResolverInterface */ - private $configResolver; + private ConfigResolverInterface $configResolver; - /** @var \Ibexa\AdminUi\Form\Factory\FormFactory */ - private $formFactory; + private FormFactory $formFactory; - /** @var \Ibexa\AdminUi\Form\SubmitHandler */ - private $submitHandler; + private SubmitHandler $submitHandler; public function __construct( NotificationService $notificationService, @@ -72,7 +67,7 @@ public function getNotificationsAction(Request $request, int $offset, int $limit 'total' => $notificationList->totalCount, 'notifications' => $notificationList->items, ]); - } catch (\Exception $exception) { + } catch (Exception $exception) { $response->setData([ 'status' => 'failed', 'error' => $exception->getMessage(), @@ -154,7 +149,7 @@ public function countNotificationsAction(): JsonResponse 'pending' => $this->notificationService->getPendingNotificationCount(), 'total' => $this->notificationService->getNotificationCount(), ]); - } catch (\Exception $exception) { + } catch (Exception $exception) { $response->setData([ 'status' => 'failed', 'error' => $exception->getMessage(), @@ -189,7 +184,7 @@ public function markNotificationAsReadAction(Request $request, mixed $notificati } $response->setData($data); - } catch (\Exception $exception) { + } catch (Exception $exception) { $response->setData([ 'status' => 'failed', 'error' => $exception->getMessage(), @@ -201,6 +196,26 @@ public function markNotificationAsReadAction(Request $request, mixed $notificati return $response; } + public function markAllNotificationsAsReadAction(Request $request): JsonResponse + { + $response = new JsonResponse(); + + try { + $notifications = $this->notificationService->loadNotifications(0, PHP_INT_MAX)->items; + + foreach ($notifications as $notification) { + $this->notificationService->markNotificationAsRead($notification); + } + + return $response->setData(['status' => 'success']); + } catch (Exception $exception) { + return $response->setData([ + 'status' => 'failed', + 'error' => $exception->getMessage(), + ])->setStatusCode(404); + } + } + public function markNotificationAsUnreadAction(Request $request, mixed $notificationId): JsonResponse { $response = new JsonResponse(); @@ -213,7 +228,7 @@ public function markNotificationAsUnreadAction(Request $request, mixed $notifica $data = ['status' => 'success']; $response->setData($data); - } catch (\Exception $exception) { + } catch (Exception $exception) { $response->setData([ 'status' => 'failed', 'error' => $exception->getMessage(), @@ -235,7 +250,7 @@ public function deleteNotificationAction(Request $request, mixed $notificationId $this->notificationService->deleteNotification($notification); $response->setData(['status' => 'success']); - } catch (\Exception $exception) { + } catch (Exception $exception) { $response->setData([ 'status' => 'failed', 'error' => $exception->getMessage(), diff --git a/src/bundle/Resources/config/routing.yaml b/src/bundle/Resources/config/routing.yaml index 24edc5d33a..0f7069a0bb 100644 --- a/src/bundle/Resources/config/routing.yaml +++ b/src/bundle/Resources/config/routing.yaml @@ -935,6 +935,12 @@ ibexa.notifications.mark_as_read: requirements: notificationId: '\d+' +ibexa.notifications.mark_all_as_read: + path: /notifications/read-all + defaults: + _controller: 'Ibexa\Bundle\AdminUi\Controller\NotificationController::markAllNotificationsAsReadAction' + methods: [ GET ] + ibexa.notifications.mark_as_unread: path: /notification/unread/{notificationId} defaults: From d9ff38a050465f78c497bae93ef23146dc6667b9 Mon Sep 17 00:00:00 2001 From: Dariusz Szut Date: Mon, 7 Apr 2025 14:04:44 +0200 Subject: [PATCH 09/94] IBX-9060: Delete, mark as notifications --- src/bundle/Resources/config/routing.yaml | 17 ++- .../js/scripts/admin.notifications.modal.js | 108 +++++++++++++++++- .../public/scss/_notifications-modal.scss | 28 +++-- .../translations/ibexa_notifications.en.xliff | 15 +++ .../account/notifications/list.html.twig | 2 +- .../account/notifications/list_item.html.twig | 39 +++---- 6 files changed, 163 insertions(+), 46 deletions(-) diff --git a/src/bundle/Resources/config/routing.yaml b/src/bundle/Resources/config/routing.yaml index 0f7069a0bb..749a9cf1de 100644 --- a/src/bundle/Resources/config/routing.yaml +++ b/src/bundle/Resources/config/routing.yaml @@ -917,7 +917,7 @@ ibexa.notifications.render.all: defaults: _controller: 'Ibexa\Bundle\AdminUi\Controller\AllNotificationsController::renderAllNotificationsPageAction' page: 1 - methods: [ GET ] + methods: [GET] requirements: page: '\d+' @@ -929,6 +929,8 @@ ibexa.notifications.count: ibexa.notifications.mark_as_read: path: /notification/read/{notificationId} + options: + expose: true defaults: _controller: 'Ibexa\Bundle\AdminUi\Controller\NotificationController::markNotificationAsReadAction' methods: [GET] @@ -939,21 +941,25 @@ ibexa.notifications.mark_all_as_read: path: /notifications/read-all defaults: _controller: 'Ibexa\Bundle\AdminUi\Controller\NotificationController::markAllNotificationsAsReadAction' - methods: [ GET ] + methods: [GET] ibexa.notifications.mark_as_unread: path: /notification/unread/{notificationId} + options: + expose: true defaults: _controller: 'Ibexa\Bundle\AdminUi\Controller\NotificationController::markNotificationAsUnreadAction' - methods: [ GET ] + methods: [GET] requirements: notificationId: '\d+' ibexa.notifications.delete: path: /notification/delete/{notificationId} + options: + expose: true defaults: _controller: 'Ibexa\Bundle\AdminUi\Controller\NotificationController::deleteNotificationAction' - methods: [ GET ] + methods: [GET] requirements: notificationId: '\d+' @@ -969,7 +975,7 @@ ibexa.notifications.delete_multiple: path: /notification/delete-multiple defaults: _controller: 'Ibexa\Bundle\AdminUi\Controller\NotificationController::deleteNotificationsAction' - methods: [ POST ] + methods: [POST] # # Permissions @@ -1001,7 +1007,6 @@ ibexa.permission.limitation.language.content_read: requirements: contentInfoId: \d+ - ### Focus Mode ibexa.focus_mode.change: diff --git a/src/bundle/Resources/public/js/scripts/admin.notifications.modal.js b/src/bundle/Resources/public/js/scripts/admin.notifications.modal.js index 688b29e601..54619d9254 100644 --- a/src/bundle/Resources/public/js/scripts/admin.notifications.modal.js +++ b/src/bundle/Resources/public/js/scripts/admin.notifications.modal.js @@ -1,4 +1,4 @@ -(function (global, doc, ibexa, Translator) { +(function (global, doc, ibexa, Translator, Routing) { let currentPageLink = null; let getNotificationsStatusErrorShowed = false; let lastFailedCountFetchNotificationNode = null; @@ -14,7 +14,7 @@ const panel = doc.querySelector('.ibexa-notifications-modal'); const { showErrorNotification, showWarningNotification } = ibexa.helpers.notification; const { getJsonFromResponse, getTextFromResponse } = ibexa.helpers.request; - const markAsRead = (notification, response) => { + const handleNotificationClickRequest = (notification, response) => { if (response.status === 'success') { notification.classList.add('ibexa-notifications-modal__item--read'); } @@ -30,7 +30,7 @@ credentials: 'same-origin', }); - fetch(request).then(getJsonFromResponse).then(markAsRead.bind(null, notification)).catch(showErrorNotification); + fetch(request).then(getJsonFromResponse).then(handleNotificationClickRequest.bind(null, notification)).catch(showErrorNotification); }; const handleTableClick = (event) => { if (event.target.classList.contains('description__read-more')) { @@ -111,11 +111,109 @@ fetchNotificationPage(currentPageLink); } }; + const markAsRead = ({ currentTarget }) => { + const { notificationId } = currentTarget.dataset; + const markAsReadLink = Routing.generate('ibexa.notifications.mark_as_read', { notificationId }); + + fetch(markAsReadLink, { mode: 'same-origin', credentials: 'same-origin' }) + .then(ibexa.helpers.request.getJsonFromResponse) + .then((response) => { + if (response.status === 'success') { + const notification = doc.querySelector(`.ibexa-notifications-modal__item[data-notification-id="${notificationId}"]`); + const menuBranch = currentTarget.closest('.ibexa-multilevel-popup-menu__branch'); + const menuInstance = ibexa.helpers.objectInstances.getInstance(menuBranch.menuInstanceElement); + + menuInstance.closeMenu(); + + notification.classList.add('ibexa-notifications-modal__item--read'); + } + }) + .catch(() => { + const message = Translator.trans( + /* @Desc("Cannot mark notification as read") */ 'notifications.modal.message.error.mark_as_read', + {}, + 'ibexa_notifications', + ); + + showErrorNotification(message); + }); + }; + const markAsUnread = ({ currentTarget }) => { + const { notificationId } = currentTarget.dataset; + const markAsUnreadLink = Routing.generate('ibexa.notifications.mark_as_unread', { notificationId }); + + fetch(markAsUnreadLink, { mode: 'same-origin', credentials: 'same-origin' }) + .then(ibexa.helpers.request.getJsonFromResponse) + .then((response) => { + if (response.status === 'success') { + const notification = doc.querySelector(`.ibexa-notifications-modal__item[data-notification-id="${notificationId}"]`); + const menuBranch = currentTarget.closest('.ibexa-multilevel-popup-menu__branch'); + const menuInstance = ibexa.helpers.objectInstances.getInstance(menuBranch.menuInstanceElement); + + menuInstance.closeMenu(); + notification.classList.remove('ibexa-notifications-modal__item--read'); + } + }) + .catch(() => { + const message = Translator.trans( + /* @Desc("Cannot mark notification as unread") */ 'notifications.modal.message.error.mark_as_unread', + {}, + 'ibexa_notifications', + ); + + showErrorNotification(message); + }); + }; + const deleteNotification = ({ currentTarget }) => { + const { notificationId } = currentTarget.dataset; + const deleteLink = Routing.generate('ibexa.notifications.delete', { notificationId }); + + fetch(deleteLink, { mode: 'same-origin', credentials: 'same-origin' }) + .then(ibexa.helpers.request.getJsonFromResponse) + .then((response) => { + if (response.status === 'success') { + const notification = doc.querySelector(`.ibexa-notifications-modal__item[data-notification-id="${notificationId}"]`); + const menuBranch = currentTarget.closest('.ibexa-multilevel-popup-menu__branch'); + const menuInstance = ibexa.helpers.objectInstances.getInstance(menuBranch.menuInstanceElement); + + menuInstance.closeMenu(); + notification.remove(); + } + }) + .catch(() => { + const message = Translator.trans( + /* @Desc("Cannot delete notification") */ 'notifications.modal.message.error.delete', + {}, + 'ibexa_notifications', + ); + + showErrorNotification(message); + }); + }; + const attachActionsListeners = () => { + const attachListener = (node, callback) => node.addEventListener('click', callback, false); + const markAsReadButtons = doc.querySelectorAll('.ibexa-notifications-modal--mark-as-read'); + const markAsUnreadButtons = doc.querySelectorAll('.ibexa-notifications-modal--mark-as-unread'); + const deleteButtons = doc.querySelectorAll('.ibexa-notifications-modal--delete'); + + markAsReadButtons.forEach((markAsReadButton) => { + attachListener(markAsReadButton, markAsRead); + }); + + markAsUnreadButtons.forEach((markAsUnreadButton) => { + attachListener(markAsUnreadButton, markAsUnread); + }); + + deleteButtons.forEach((deleteButton) => { + attachListener(deleteButton, deleteNotification); + }); + }; const showNotificationPage = (pageHtml) => { const modalResults = panel.querySelector(SELECTOR_MODAL_RESULTS); modalResults.innerHTML = pageHtml; toggleLoading(false); + attachActionsListeners(); doc.body.dispatchEvent( new CustomEvent('ibexa-multilevel-popup-menu:init', { @@ -154,6 +252,7 @@ if (isPaginationBtn) { handleNotificationsPageChange(event); + return; } @@ -184,4 +283,5 @@ }; getNotificationsStatusLoop(); -})(window, window.document, window.ibexa, window.Translator); + attachActionsListeners(); +})(window, window.document, window.ibexa, window.Translator, window.Routing); diff --git a/src/bundle/Resources/public/scss/_notifications-modal.scss b/src/bundle/Resources/public/scss/_notifications-modal.scss index 9610bbd7b6..ff87ac2474 100644 --- a/src/bundle/Resources/public/scss/_notifications-modal.scss +++ b/src/bundle/Resources/public/scss/_notifications-modal.scss @@ -11,10 +11,19 @@ white-space: normal; margin-bottom: 0; - .ibexa-table__row .ibexa-table__cell { - height: 115px; - padding-right: 0; - border-radius: 0; + .ibexa-table__row { + .ibexa-table__cell { + height: 115px; + padding-right: 0; + border-radius: 0; + background-color: $ibexa-color-light-300; + } + + &.ibexa-notifications-modal__item--read { + .ibexa-table__cell { + background-color: $ibexa-color-white; + } + } } } @@ -55,9 +64,6 @@ &--wrapper { display: flex; - td { - background-color: $ibexa-color-light-300 !important; //TODO:! - } } &:first-of-type { @@ -88,20 +94,12 @@ } &__item--read { - color: $ibexa-color-dark-300; - .ibexa-notifications-modal__notice-dot, .ibexa-notification-view-all__notice-dot { background: $ibexa-color-dark-300; } } - &__item--read-wrapper { - td { - background-color: white !important; - } - } - &__description { .description__title { margin-bottom: 0; diff --git a/src/bundle/Resources/translations/ibexa_notifications.en.xliff b/src/bundle/Resources/translations/ibexa_notifications.en.xliff index 83814e0030..9380169930 100644 --- a/src/bundle/Resources/translations/ibexa_notifications.en.xliff +++ b/src/bundle/Resources/translations/ibexa_notifications.en.xliff @@ -86,6 +86,21 @@ Cannot update notifications key: notifications.modal.message.error + + Cannot delete notification + Cannot delete notification + key: notifications.modal.message.error.delete + + + Cannot mark notification as read + Cannot mark notification as read + key: notifications.modal.message.error.mark_as_read + + + Cannot mark notification as unread + Cannot mark notification as unread + key: notifications.modal.message.error.mark_as_unread + diff --git a/src/bundle/Resources/views/themes/admin/account/notifications/list.html.twig b/src/bundle/Resources/views/themes/admin/account/notifications/list.html.twig index f756ea8ace..dc2b5f0330 100644 --- a/src/bundle/Resources/views/themes/admin/account/notifications/list.html.twig +++ b/src/bundle/Resources/views/themes/admin/account/notifications/list.html.twig @@ -2,7 +2,7 @@ {% embed '@ibexadesign/ui/component/table/table.html.twig' with { head_cols: [], - class: 'ibexa-list--notifications ibexa-notifications-modal__list', + class: 'ibexa-table--not-striped ibexa-list--notifications ibexa-notifications-modal__list', attr: { 'data-notifications': path('ibexa.notifications.render.page'), 'data-notifications-count': path('ibexa.notifications.count'), diff --git a/src/bundle/Resources/views/themes/admin/account/notifications/list_item.html.twig b/src/bundle/Resources/views/themes/admin/account/notifications/list_item.html.twig index 07fc10cc65..aa5ba88da2 100644 --- a/src/bundle/Resources/views/themes/admin/account/notifications/list_item.html.twig +++ b/src/bundle/Resources/views/themes/admin/account/notifications/list_item.html.twig @@ -1,7 +1,7 @@ {% trans_default_domain 'ibexa_notifications' %} {% if wrapper_class_list is not defined %} - {% set wrapper_class_list = 'ibexa-notifications-modal__item ibexa-notifications-modal__item--wrapper' ~ (notification.isPending == 0 ? ' ibexa-notifications-modal__item--read ibexa-notifications-modal__item--read-wrapper') %} + {% set wrapper_class_list = 'ibexa-notifications-modal__item ibexa-notifications-modal__item--wrapper' ~ (notification.isPending == 0 ? ' ibexa-notifications-modal__item--read') %} {% endif %} {% set icon %} @@ -38,23 +38,22 @@ {% set popup_items = [] %} -{% set action_btns = { - markAs: { - label: notification.isPending == 0 ? 'notification.mark_as_unread'|trans|desc('Mark as unread') : 'notification.mark_as_read'|trans|desc('Mark as read'), - action_attr: { class: 'ibexa-notifications-modal--markAs', id: notification.id }, - }, - delete: { - label: 'notification.delete'|trans|desc('Delete'), - action_attr: { class: 'ibexa-notifications-modal--delete', id: notification.id }, - }, -} %} +{% if notification.isPending == 0 %} + {% set popup_items = popup_items|merge([{ + label: 'notification.mark_as_unread'|trans|desc('Mark as unread'), + action_attr: { class: 'ibexa-notifications-modal--mark-as-unread', 'data-notification-id': notification.id }, + }]) %} +{% else %} + {% set popup_items = popup_items|merge([{ + label: 'notification.mark_as_read'|trans|desc('Mark as read'), + action_attr: { class: 'ibexa-notifications-modal--mark-as-read', 'data-notification-id': notification.id }, + }]) %} +{% endif %} -{% for key in action_btns|keys %} - {% set item = action_btns[key] %} - {% if item is not null %} - {% set popup_items = popup_items|merge([item]) %} - {% endif %} -{% endfor %} +{% set popup_items = popup_items|merge([{ + label: 'notification.delete'|trans|desc('Delete'), + action_attr: { class: 'ibexa-notifications-modal--delete', 'data-notification-id': notification.id }, +}]) %} {% embed '@ibexadesign/ui/component/table/table_body_row.html.twig' with { class: wrapper_class_list ~ (wrapper_additional_classes is defined ? ' ' ~ wrapper_additional_classes), @@ -64,7 +63,7 @@ } } %} {% block body_row_cells %} - {% embed '@ibexadesign/ui/component/table/table_body_cell.html.twig' %} + {% embed '@ibexadesign/ui/component/table/table_body_cell.html.twig' with { class: '' } %} {% block content %}
@@ -81,7 +80,7 @@
{% endblock %} {% endembed %} - {% embed '@ibexadesign/ui/component/table/table_body_cell.html.twig' %} + {% embed '@ibexadesign/ui/component/table/table_body_cell.html.twig' with { class: '' } %} {% block content %}
From 84153c7c410d2c225647481e73f036ed140f7ed5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Bia=C5=82czak?= Date: Thu, 10 Apr 2025 08:38:29 +0200 Subject: [PATCH 11/94] IBX-9060: Fixed notification query parameters to use arrays instead of strings --- .../Controller/NotificationController.php | 50 ++++++++++++++++--- src/bundle/Form/Data/SearchQueryData.php | 36 +++++++++++-- src/bundle/Form/Type/SearchType.php | 28 +++++++++-- .../Pagerfanta/NotificationAdapter.php | 9 ++-- 4 files changed, 101 insertions(+), 22 deletions(-) diff --git a/src/bundle/Controller/NotificationController.php b/src/bundle/Controller/NotificationController.php index eb5780df2f..40bde8195c 100644 --- a/src/bundle/Controller/NotificationController.php +++ b/src/bundle/Controller/NotificationController.php @@ -8,6 +8,7 @@ namespace Ibexa\Bundle\AdminUi\Controller; +use DateTimeInterface; use Exception; use Ibexa\AdminUi\Form\Data\Notification\NotificationRemoveData; use Ibexa\AdminUi\Form\Factory\FormFactory; @@ -79,16 +80,27 @@ public function getNotificationsAction(Request $request, int $offset, int $limit public function renderNotificationsPageAction(Request $request, int $page): Response { - $searchForm = $this->createSearchForm(); + $notificationTypes = array_unique( + array_map( + fn($notification) => $notification->type, + $this->notificationService->loadNotifications(0, PHP_INT_MAX)->items + ) + ); + sort($notificationTypes); + + $searchForm = $this->createForm(SearchType::class, null, [ + 'notification_types' => $notificationTypes, + ]); $searchForm->handleRequest($request); + $query = []; if ($searchForm->isSubmitted() && $searchForm->isValid()) { $data = $searchForm->getData(); $query = $this->buildQuery($data); } $pagerfanta = new Pagerfanta( - new NotificationAdapter($this->notificationService, $query ?? null) + new NotificationAdapter($this->notificationService, $query) ); $pagerfanta->setMaxPerPage($this->configResolver->getParameter('pagination.notification_limit')); $pagerfanta->setCurrentPage(min($page, $pagerfanta->getNbPages())); @@ -116,16 +128,38 @@ public function renderNotificationsPageAction(Request $request, int $page): Resp ]); } - private function buildQuery(SearchQueryData $data): ?string + private function buildQuery(SearchQueryData $data): array { - return $data->getQuery(); - } + $query = []; - private function createSearchForm(): FormInterface - { - return $this->createForm(SearchType::class); + if ($data->getType()) { + $query['type'] = $data->getType(); + } + + if (!empty($data->getStatuses())) { + $query['status'] = []; + if (in_array('read', $data->getStatuses(), true)) { + $query['status'][] = 'read'; + } + if (in_array('unread', $data->getStatuses(), true)) { + $query['status'][] = 'unread'; + } + } + + $range = $data->getCreatedRange(); + if ($range !== null) { + if ($range->getMin() instanceof DateTimeInterface) { + $query['created_from'] = $range->getMin()->getTimestamp(); + } + if ($range->getMax() instanceof DateTimeInterface) { + $query['created_to'] = $range->getMax()->getTimestamp(); + } + } + + return $query; } + private function createNotificationRemoveData(Pagerfanta $pagerfanta): NotificationRemoveData { $notificationIds = []; diff --git a/src/bundle/Form/Data/SearchQueryData.php b/src/bundle/Form/Data/SearchQueryData.php index f250e414ba..a80bd5da3b 100644 --- a/src/bundle/Form/Data/SearchQueryData.php +++ b/src/bundle/Form/Data/SearchQueryData.php @@ -8,17 +8,43 @@ namespace Ibexa\Bundle\AdminUi\Form\Data; +use Ibexa\AdminUi\Form\Data\DateRangeData; + final class SearchQueryData { - private ?string $query = null; + private array $statuses = []; + private ?string $type = null; + + private ?DateRangeData $createdRange = null; + + + public function getStatuses(): array + { + return $this->statuses; + } + + public function setStatuses(array $statuses): void + { + $this->statuses = $statuses; + } + + public function getType(): ?string + { + return $this->type; + } + + public function setType(?string $type): void + { + $this->type = $type; + } - public function getQuery(): ?string + public function getCreatedRange(): ?DateRangeData { - return $this->query; + return $this->createdRange; } - public function setQuery(?string $query): void + public function setCreatedRange(?DateRangeData $createdRange): void { - $this->query = $query; + $this->createdRange = $createdRange; } } diff --git a/src/bundle/Form/Type/SearchType.php b/src/bundle/Form/Type/SearchType.php index 3997473571..e73abc2423 100644 --- a/src/bundle/Form/Type/SearchType.php +++ b/src/bundle/Form/Type/SearchType.php @@ -8,9 +8,10 @@ namespace Ibexa\Bundle\AdminUi\Form\Type; +use Ibexa\AdminUi\Form\Type\DateRangeType; use Ibexa\Bundle\AdminUi\Form\Data\SearchQueryData; use Symfony\Component\Form\AbstractType; -use Symfony\Component\Form\Extension\Core\Type\TextType; +use Symfony\Component\Form\Extension\Core\Type\ChoiceType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolver; @@ -18,9 +19,27 @@ final class SearchType extends AbstractType { public function buildForm(FormBuilderInterface $builder, array $options): void { - $builder->add('query', TextType::class, [ - 'required' => false, - ]); + $builder + ->add('type', ChoiceType::class, [ + 'required' => false, + 'choices' => array_combine($options['notification_types'], $options['notification_types']), + 'placeholder' => 'All types', + 'label' => 'Type', + ]) + ->add('statuses', ChoiceType::class, [ + 'choices' => [ + 'Read' => 'read', + 'Unread' => 'unread', + ], + 'expanded' => true, + 'multiple' => true, + 'required' => false, + 'label' => 'Status', + ]) + ->add('createdRange', DateRangeType::class, [ + 'required' => false, + 'label' => 'Date and time', + ]); } public function configureOptions(OptionsResolver $resolver): void @@ -29,6 +48,7 @@ public function configureOptions(OptionsResolver $resolver): void 'method' => 'GET', 'csrf_protection' => false, 'data_class' => SearchQueryData::class, + 'notification_types' => [], ]); } } diff --git a/src/lib/Pagination/Pagerfanta/NotificationAdapter.php b/src/lib/Pagination/Pagerfanta/NotificationAdapter.php index 94f6631e99..b6d8f3aed8 100644 --- a/src/lib/Pagination/Pagerfanta/NotificationAdapter.php +++ b/src/lib/Pagination/Pagerfanta/NotificationAdapter.php @@ -19,13 +19,13 @@ class NotificationAdapter implements AdapterInterface { private NotificationService $notificationService; - private ?string $query; + private array $query; private int $nbResults; public function __construct( NotificationService $notificationService, - ?string $query = null + array $query = [] ) { $this->notificationService = $notificationService; $this->query = $query; @@ -53,9 +53,8 @@ public function getSlice($offset, $length): NotificationList { $notifications = $this->notificationService->loadNotifications($offset, $length, $this->query); - if (null === $this->nbResults) { - $this->nbResults = $notifications->totalCount; - } + $this->nbResults ??= $notifications->totalCount; + return $notifications; } From df1c3310cb2d13d9c1cf4e6b77a635a580792a13 Mon Sep 17 00:00:00 2001 From: Aleksandra Bozek Date: Thu, 24 Apr 2025 16:45:33 +0200 Subject: [PATCH 12/94] IBX-9060: Notification list filter --- .../config/ezplatform_default_settings.yaml | 2 +- .../Resources/encore/ibexa.js.config.js | 5 +- .../js/scripts/admin.notifications.filters.js | 107 ++++++++++++ .../js/scripts/admin.notifications.list.js | 73 ++++++++ .../js/scripts/admin.notifications.modal.js | 8 +- .../Resources/public/scss/_list-filters.scss | 94 +++++++++++ .../public/scss/_notifications-modal.scss | 43 +++-- .../Resources/public/scss/_notifications.scss | 56 +++++++ src/bundle/Resources/public/scss/ibexa.scss | 1 + .../translations/ibexa_notifications.en.xliff | 45 +++++ .../account/notifications/filters.html.twig | 44 +++++ .../filters/filter_item.html.twig | 19 +++ .../filters/form_fields.html.twig | 55 ++++++ .../account/notifications/list.html.twig | 2 +- .../account/notifications/list_all.html.twig | 158 +++++++++++++----- .../notifications/list_item_all.html.twig | 21 ++- .../notifications/side_panel.html.twig | 5 +- 17 files changed, 665 insertions(+), 73 deletions(-) create mode 100644 src/bundle/Resources/public/js/scripts/admin.notifications.filters.js create mode 100644 src/bundle/Resources/public/scss/_list-filters.scss create mode 100644 src/bundle/Resources/views/themes/admin/account/notifications/filters.html.twig create mode 100644 src/bundle/Resources/views/themes/admin/account/notifications/filters/filter_item.html.twig create mode 100644 src/bundle/Resources/views/themes/admin/account/notifications/filters/form_fields.html.twig diff --git a/src/bundle/Resources/config/ezplatform_default_settings.yaml b/src/bundle/Resources/config/ezplatform_default_settings.yaml index 7c6cbb3174..b3ca07df9e 100644 --- a/src/bundle/Resources/config/ezplatform_default_settings.yaml +++ b/src/bundle/Resources/config/ezplatform_default_settings.yaml @@ -22,7 +22,7 @@ parameters: ibexa.site_access.config.admin_group.pagination.content_role_limit: 5 ibexa.site_access.config.admin_group.pagination.content_policy_limit: 5 ibexa.site_access.config.admin_group.pagination.bookmark_limit: 10 - ibexa.site_access.config.admin_group.pagination.notification_limit: 5 + ibexa.site_access.config.admin_group.pagination.notification_limit: 10 ibexa.site_access.config.admin_group.pagination.user_settings_limit: 10 ibexa.site_access.config.admin_group.pagination.content_draft_limit: 10 ibexa.site_access.config.admin_group.pagination.location_limit: 10 diff --git a/src/bundle/Resources/encore/ibexa.js.config.js b/src/bundle/Resources/encore/ibexa.js.config.js index 531bbc5f46..28b0b11034 100644 --- a/src/bundle/Resources/encore/ibexa.js.config.js +++ b/src/bundle/Resources/encore/ibexa.js.config.js @@ -254,5 +254,8 @@ module.exports = (Encore) => { path.resolve(__dirname, '../public/js/scripts/admin.location.adaptive.tabs.js'), ]) .addEntry('ibexa-admin-ui-edit-base-js', [path.resolve(__dirname, '../public/js/scripts/edit.header.js')]) - .addEntry('ibexa-admin-notifications-list-js', [path.resolve(__dirname, '../public/js/scripts/admin.notifications.list.js')]); + .addEntry('ibexa-admin-notifications-list-js', [ + path.resolve(__dirname, '../public/js/scripts/admin.notifications.list.js'), + path.resolve(__dirname, '../public/js/scripts/admin.notifications.filters.js'), + ]) }; diff --git a/src/bundle/Resources/public/js/scripts/admin.notifications.filters.js b/src/bundle/Resources/public/js/scripts/admin.notifications.filters.js new file mode 100644 index 0000000000..df16a895b0 --- /dev/null +++ b/src/bundle/Resources/public/js/scripts/admin.notifications.filters.js @@ -0,0 +1,107 @@ +(function (global, doc) { + const searchForm = doc.querySelector('.ibexa-al-list-search-form'); + const filtersContainerNode = doc.querySelector('.ibexa-list-filters'); + const applyFiltersBtn = filtersContainerNode.querySelector('.ibexa-btn--apply'); + const clearFiltersBtn = filtersContainerNode.querySelector('.ibexa-btn--clear'); + const statusFilterNode = filtersContainerNode.querySelector('.ibexa-list-filters__item--statuses'); + const typeFilterNode = filtersContainerNode.querySelector('.ibexa-list-filters__item--type'); + const datetimeFilterNodes = filtersContainerNode.querySelectorAll('.ibexa-list-filters__item--date-time .ibexa-picker'); + + const clearFilter = (filterNode) => { + if (!filterNode) { + return; + } + + const sourceSelect = filterNode.querySelector('.ibexa-list-filters__item-content .ibexa-dropdown__source .ibexa-input--select'); + const sourceSelectOptions = sourceSelect?.querySelectorAll('option'); + const checkboxes = filterNode.querySelectorAll( + '.ibexa-list-filters__item-content .ibexa-input--checkbox:not([name="dropdown-checkbox"])', + ); + const timePicker = filterNode.querySelector('.ibexa-date-time-picker__input'); + + if (sourceSelect) { + sourceSelectOptions.forEach((option) => (option.selected = false)); + + if (isNodeTimeFilter(filterNode)) { + sourceSelectOptions[0].selected = true; + } + } else if (checkboxes.length) { + checkboxes.forEach((checkbox) => (checkbox.checked = false)); + } else if (timePicker.value.length) { + const timePicker = filterNode.querySelector('.ibexa-date-time-picker__input'); + const formInput = filterNode.querySelector('.ibexa-picker__form-input'); + + timePicker.value = ''; + formInput.value = ''; + + timePicker.dispatchEvent(new Event('input')); + formInput.dispatchEvent(new Event('input')); + } + + searchForm.submit(); + }; + const attachFilterEvent = (filterNode) => { + if (!filterNode) { + return; + } + + const sourceSelect = filterNode.querySelector('.ibexa-list-filters__item-content .ibexa-dropdown__source .ibexa-input--select'); + const checkboxes = filterNode.querySelectorAll( + '.ibexa-list-filters__item-content .ibexa-input--checkbox:not([name="dropdown-checkbox"])', + ); + const picker = filterNode.querySelector( + '.ibexa-input--date', + ); + picker?.addEventListener('change', filterChange, false); + sourceSelect?.addEventListener('change', filterChange, false); + checkboxes.forEach((checkbox) => { + checkbox.addEventListener('change', filterChange, false); + }); + }; + const isNodeTimeFilter = (filterNode) => { + return filterNode.classList.contains('ibexa-picker'); + }; + const hasFilterValue = (filterNode) => { + if (!filterNode) { + return; + } + + const select = filterNode.querySelector('.ibexa-dropdown__source .ibexa-input--select'); + const checkedCheckboxes = filterNode.querySelectorAll('.ibexa-input--checkbox:checked'); + + if (isNodeTimeFilter(filterNode)) { + const timePicker = filterNode.querySelector('.ibexa-date-time-picker__input'); + + return !!timePicker.dataset.timestamp; + } + + return !!(select?.value || checkedCheckboxes?.length); + }; + const isSomeFilterSet = () => { + const hasStatusFilterValue = hasFilterValue(statusFilterNode); + const hasTypeFilterValue = hasFilterValue(typeFilterNode); + const hasDatetimeFilterValue = [...datetimeFilterNodes].some((input) => hasFilterValue(input)); + + return hasStatusFilterValue || hasTypeFilterValue || hasDatetimeFilterValue; + }; + const attachInitEvents = () => { + attachFilterEvent(statusFilterNode); + attachFilterEvent(typeFilterNode); + datetimeFilterNodes.forEach((input) => attachFilterEvent(input)); + }; + const filterChange = () => { + const hasFiltersSetValue = isSomeFilterSet(); + + applyFiltersBtn.disabled = false; + clearFiltersBtn.disabled = !hasFiltersSetValue; + }; + const clearAllFilters = () => { + clearFilter(statusFilterNode); + clearFilter(typeFilterNode); + datetimeFilterNodes.forEach((input) => clearFilter(input)); + }; + + attachInitEvents(); + + clearFiltersBtn.addEventListener('click', clearAllFilters, false); +})(window, window.document); diff --git a/src/bundle/Resources/public/js/scripts/admin.notifications.list.js b/src/bundle/Resources/public/js/scripts/admin.notifications.list.js index 7e670a2408..c792babfb1 100644 --- a/src/bundle/Resources/public/js/scripts/admin.notifications.list.js +++ b/src/bundle/Resources/public/js/scripts/admin.notifications.list.js @@ -5,6 +5,9 @@ const { showErrorNotification } = ibexa.helpers.notification; const { getJsonFromResponse } = ibexa.helpers.request; const markAllAsReadBtn = doc.querySelector('.ibexa-notification-list__mark-all-read'); + const markAsReadBtn = doc.querySelector('.ibexa-notification-list__btn--mark-as-read'); + const deleteBtn = doc.querySelector('.ibexa-notification-list__btn--delete'); + const checkboxes = [...doc.querySelectorAll('.ibexa-notification-list .ibexa-table__cell--has-checkbox .ibexa-input--checkbox')]; const markAllAsRead = () => { const markAllAsReadLink = Routing.generate('ibexa.notifications.mark_all_as_read'); @@ -25,6 +28,53 @@ showErrorNotification(message); }); }; + + //TODO: + const markSelectedAsRead = () => { + const checkedElements = doc.querySelectorAll('.ibexa-notification-list__mark-row-checkbox:checked'); + const notificationIds = [...checkedElements].map((element) => element.dataset.notificationId); + const bulkOperations = getBulkOperations(notificationIds); + const request = new Request('/api/ibexa/v2/bulk', { + method: 'POST', + headers: { + Accept: 'application/vnd.ibexa.api.BulkOperationResponse+json', + 'Content-Type': 'application/vnd.ibexa.api.BulkOperation+json', + }, + body: JSON.stringify({ + bulkOperations: { + operations: bulkOperations, + }, + }), + mode: 'same-origin', + credentials: 'include', + }); + const errorMessage = Translator.trans( + /*@Desc("Cannot mark selected notifications as read")*/ 'notifications.modal.message.error.mark_selected_as_read', + {}, + 'ibexa_notifications', + ); + + fetch(request) + .then(getJsonFromResponse) + .catch(() => ibexa.helpers.notification.showErrorNotification(errorMessage)); + }; + const getBulkOperations = (notificationIds) => notificationIds.reduce((total, notificationId) => { + const markAsReadLink = Routing.generate('ibexa.notifications.mark_as_read', { notificationId }); + + total[markAsReadLink] = { + uri: markAsReadLink, + method: 'GET', + mode: 'same-origin', + headers: { + Accept: 'application/vnd.ibexa.api.ContentType+json', + 'X-Requested-With': 'XMLHttpRequest', + }, + credentials: 'same-origin' + }; + + return total; + }, {}); + const handleNotificationClick = (notification, isToggle = false) => { const notificationRow = notification.closest('.ibexa-table__row'); const isRead = notification.classList.contains('ibexa-notifications-modal__item--read'); @@ -99,4 +149,27 @@ link.addEventListener('click', (event) => handleNotificationActionClick(event, true), false), ); markAllAsReadBtn.addEventListener('click', markAllAsRead, false); + markAsReadBtn.addEventListener('click', markSelectedAsRead, false); + + + const toggleActionButtonState = () => { + const checkedNotifications = checkboxes.filter((el) => el.checked); + const isAnythingSelected = checkedNotifications.length > 0; + const unreadLabel = Translator.trans(/* @Desc("Unread") */ 'notification.unread',{},'ibexa_notifications'); + + deleteBtn.disabled = !isAnythingSelected; + markAsReadBtn.disabled = !isAnythingSelected || !checkedNotifications.every((checkbox) => + checkbox.closest('.ibexa-table__row').querySelector('.ibexa-notification-view-all__read').innerText === unreadLabel + ); + }; + const handleCheckboxChange = (checkbox) => { + const checkboxFormId = checkbox.dataset?.formRemoveId; + const formRemoveCheckbox = doc.getElementById(checkboxFormId); + if (formRemoveCheckbox) { + formRemoveCheckbox.checked = checkbox.checked; + } + toggleActionButtonState(); + }; + + checkboxes.forEach((checkbox) => checkbox.addEventListener('change', () => handleCheckboxChange(checkbox), false)); })(window, window.document, window.ibexa, window.Translator, window.Routing); diff --git a/src/bundle/Resources/public/js/scripts/admin.notifications.modal.js b/src/bundle/Resources/public/js/scripts/admin.notifications.modal.js index 2d65141dc3..d7e6bb7421 100644 --- a/src/bundle/Resources/public/js/scripts/admin.notifications.modal.js +++ b/src/bundle/Resources/public/js/scripts/admin.notifications.modal.js @@ -89,8 +89,14 @@ }; const updateModalTitleTotalInfo = (notificationsCount) => { const modalTitle = panel.querySelector(SELECTOR_MODAL_TITLE); - + const modalFooter = panel.querySelector('.ibexa-notifications__view-all-btn--count'); + modalFooter.textContent = ` (${notificationsCount})`; modalTitle.dataset.notificationsTotal = `(${notificationsCount})`; + + if (notificationsCount < 10) { + panel.querySelector('.ibexa-notifications-modal__count').textContent = `(${notificationsCount})`; + } + }; const updatePendingNotificationsView = (notificationsInfo) => { const noticeDot = doc.querySelector('.ibexa-header-user-menu__notice-dot'); diff --git a/src/bundle/Resources/public/scss/_list-filters.scss b/src/bundle/Resources/public/scss/_list-filters.scss new file mode 100644 index 0000000000..9450ab775f --- /dev/null +++ b/src/bundle/Resources/public/scss/_list-filters.scss @@ -0,0 +1,94 @@ +.ibexa-list-filters { + $self: &; + + overflow: hidden; + + &__header { + display: flex; + align-items: center; + justify-content: space-between; + padding: calculateRem(16px); + } + + &__title { + margin: 0; + padding: 0; + font-weight: 600; + } + + .accordion-item { + background: transparent; + + #{$self} { + &__item-header-btn { + justify-content: space-between; + font-size: $ibexa-text-font-size-medium; + font-weight: 600; + transition: all $ibexa-admin-transition-duration $ibexa-admin-transition; + border-top-color: $ibexa-color-light; + border-bottom-color: transparent; + border-style: solid; + border-width: calculateRem(1px) 0; + background: transparent; + + .ibexa-icon--toggle { + transition: var(--bs-accordion-btn-icon-transition); + } + + &:not(.collapsed) { + border-bottom-color: $ibexa-color-light; + + .ibexa-icon--toggle { + transform: var(--bs-accordion-btn-icon-transform); + } + } + + &::after { + display: none; + } + } + } + + &:last-of-type { + #{$self} { + &__item-header-btn { + border-bottom-color: $ibexa-color-light; + + &.collapsed { + border-bottom-color: transparent; + } + } + } + } + } + + &__item { + .ibexa-label { + margin: 0; + padding: 0; + } + + &--date-time { + #{$self} { + &__item-content { + padding-bottom: calculateRem(48px); + } + } + + .form-group:first-child { + padding-bottom: calculateRem(16px); + } + .ibexa-date-time-picker { + width: 100%; + } + } + + &--hidden { + display: none; + } + } + + &__item-content { + padding: calculateRem(24px) calculateRem(16px); + } +} diff --git a/src/bundle/Resources/public/scss/_notifications-modal.scss b/src/bundle/Resources/public/scss/_notifications-modal.scss index ff87ac2474..11d7c9defd 100644 --- a/src/bundle/Resources/public/scss/_notifications-modal.scss +++ b/src/bundle/Resources/public/scss/_notifications-modal.scss @@ -4,6 +4,11 @@ display: flex; justify-content: flex-end; padding: calculateRem(8px); + background-color: $ibexa-color-white; + } + &__results { + max-height: calc(100vh - #{calculateRem(200px)}); + overflow-y: auto; } .table { @@ -27,11 +32,6 @@ } } - &__list { - max-height: 90vh; - overflow-y: auto; - } - &__type { .type__icon { @include type-icon; @@ -43,11 +43,6 @@ } } - &__item--date { - font-size: $ibexa-text-font-size-small; - color: $ibexa-color-light-700; - } - &__type-content { width: 100%; font-size: 14px; @@ -80,6 +75,11 @@ } } + &__item--date { + font-size: $ibexa-text-font-size-small; + color: $ibexa-color-light-700; + } + &__notice-dot { width: calculateRem(8px); height: calculateRem(8px); @@ -90,7 +90,7 @@ left: calculateRem(10px); top: calculateRem(16px); font-size: 10px; - color: white; + color: $ibexa-color-white; } &__item--read { @@ -177,13 +177,13 @@ } } } -//TODO: needs cleaning + .ibexa-notification-view-all { display: flex; align-items: center; gap: calculateRem(4px); flex-wrap: nowrap; - white-space: nowrap; + overflow: hidden; &__status-icon { position: relative; @@ -217,7 +217,7 @@ position: absolute; top: calculateRem(8px); font-size: 10px; - color: white; + color: $ibexa-color-white; &--small { width: calculateRem(6px); @@ -228,9 +228,21 @@ &__details { display: flex; gap: calculateRem(4px); + min-width: 0; + max-width: 100%; + overflow: hidden; .ibexa-notifications-modal__description { display: flex; + max-width: 100%; + } + + .description__text { + flex: 1; + min-width: 0; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } } @@ -240,6 +252,9 @@ p { margin-bottom: 0; } + padding-left: 0; + min-width: 0; + max-width: 100%; .ibexa-table__cell.ibexa-table__cell { padding: calculateRem(12px) 0 calculateRem(12px) 0; diff --git a/src/bundle/Resources/public/scss/_notifications.scss b/src/bundle/Resources/public/scss/_notifications.scss index d9349ee5f4..d8c4d542d8 100644 --- a/src/bundle/Resources/public/scss/_notifications.scss +++ b/src/bundle/Resources/public/scss/_notifications.scss @@ -7,8 +7,64 @@ } .ibexa-notification-list { + display: flex; + align-items: strech; + margin-bottom: calculateRem(48px); + + .ibexa-table__header-cell { + padding: calculateRem(16px) calculateRem(8px); + &:first-child { + padding-left: calculateRem(16px); + } + &:last-child { + padding-right: calculateRem(16px); + } + } + .ibexa-table__cell { + padding: calculateRem(8px); + &:first-child { + padding-left: calculateRem(16px); + } + &:last-child { + padding-right: calculateRem(16px); + } + } + .ibexa-container { + padding: 0 calculateRem(16px) calculateRem(16px); + margin-bottom: 0; + } + + .ibexa-table-header { + padding: calculateRem(8px) 0; + &__headline{ + font-size: $ibexa-text-font-size-extra-large; + } + } + &__mark-all-read { display: flex; justify-content: flex-end; } + + &__data-grid-wrapper { + width: calc(100% - #{calculateRem(269px)}); + border-radius: $ibexa-border-radius 0 0 $ibexa-border-radius; + border: calculateRem(1px) solid $ibexa-color-light; + border-right: none; + } + + &__filters-wrapper { + width: calculateRem(275px); + border-radius: 0 $ibexa-border-radius $ibexa-border-radius 0; + border: calculateRem(1px) solid $ibexa-color-light; + background-color: $ibexa-color-white; + } + + &__table-btns { + font-size: $ibexa-text-font-size-medium; + } + + &__hidden-btn { + display: none; + } } \ No newline at end of file diff --git a/src/bundle/Resources/public/scss/ibexa.scss b/src/bundle/Resources/public/scss/ibexa.scss index 49c8d2e965..34c3e97ab6 100644 --- a/src/bundle/Resources/public/scss/ibexa.scss +++ b/src/bundle/Resources/public/scss/ibexa.scss @@ -108,6 +108,7 @@ @import 'grid-view'; @import 'grid-view-item'; @import 'list-search'; +@import 'list-filters'; @import 'search-links-form'; @import 'custom-url-form'; @import 'details'; diff --git a/src/bundle/Resources/translations/ibexa_notifications.en.xliff b/src/bundle/Resources/translations/ibexa_notifications.en.xliff index 9380169930..f2410607c8 100644 --- a/src/bundle/Resources/translations/ibexa_notifications.en.xliff +++ b/src/bundle/Resources/translations/ibexa_notifications.en.xliff @@ -6,6 +6,36 @@ The source node in most cases contains the sample message as written by the developer. If it looks like a dot-delimitted string such as "form.label.firstname", then the developer has not provided a default message. + + Apply + Apply + key: ibexa.notifications.search_form.apply + + + Clear + Clear + key: ibexa.notifications.search_form.clear + + + Date and Time + Date and Time + key: ibexa.notifications.search_form.label.date_and_time + + + Status + Status + key: ibexa.notifications.search_form.label.status + + + Type + Type + key: ibexa.notifications.search_form.label.type + + + Filters + Filters + key: ibexa.notifications.search_form.title + Notifications Notifications @@ -81,6 +111,11 @@ Unread key: notification.unread + + Are you sure you want to permanently delete the selected notification(s)? + Are you sure you want to permanently delete the selected notification(s)? + key: notifications.list.action.remove.confirmation.text + Cannot update notifications Cannot update notifications @@ -91,6 +126,11 @@ Cannot delete notification key: notifications.modal.message.error.delete + + Cannot mark all notifications as read + Cannot mark all notifications as read + key: notifications.modal.message.error.mark_all_as_read + Cannot mark notification as read Cannot mark notification as read @@ -101,6 +141,11 @@ Cannot mark notification as unread key: notifications.modal.message.error.mark_as_unread + + Cannot mark selected notifications as read + Cannot mark selected notifications as read + key: notifications.modal.message.error.mark_selected_as_read + diff --git a/src/bundle/Resources/views/themes/admin/account/notifications/filters.html.twig b/src/bundle/Resources/views/themes/admin/account/notifications/filters.html.twig new file mode 100644 index 0000000000..6b57b8f82c --- /dev/null +++ b/src/bundle/Resources/views/themes/admin/account/notifications/filters.html.twig @@ -0,0 +1,44 @@ +{% trans_default_domain 'ibexa_notifications' %} + +{% form_theme search_form with '@ibexadesign/account/notifications/filters/form_fields.html.twig' %} + +{% set is_some_filter_set = + (search_form.statuses is defined and search_form.statuses.vars.value|length) or + search_form.type.vars.value|length or + search_form.createdRange.vars.value != 0 +%} + +
+
+ {# TODO: collapse #} + {# #} +

{{ 'ibexa.notifications.search_form.title'|trans()|desc('Filters') }}

+
+ + +
+
+ +
+ {{ form_row(search_form.type) }} + {% if search_form.statuses is defined %}{{ form_row(search_form.statuses) }}{% endif %} + {{ form_row(search_form.createdRange) }} + {{ form_rest(search_form) }} +
+
diff --git a/src/bundle/Resources/views/themes/admin/account/notifications/filters/filter_item.html.twig b/src/bundle/Resources/views/themes/admin/account/notifications/filters/filter_item.html.twig new file mode 100644 index 0000000000..1e156aecb0 --- /dev/null +++ b/src/bundle/Resources/views/themes/admin/account/notifications/filters/filter_item.html.twig @@ -0,0 +1,19 @@ +
+ + + +
+ {% block content %} + {% endblock %} +
+
diff --git a/src/bundle/Resources/views/themes/admin/account/notifications/filters/form_fields.html.twig b/src/bundle/Resources/views/themes/admin/account/notifications/filters/form_fields.html.twig new file mode 100644 index 0000000000..0115e3fd98 --- /dev/null +++ b/src/bundle/Resources/views/themes/admin/account/notifications/filters/form_fields.html.twig @@ -0,0 +1,55 @@ +{% extends '@ibexadesign/ui/form_fields.html.twig' %} + +{% trans_default_domain 'ibexa_notifications' %} + +{% block _search_type_row %} + {% embed '@ibexadesign/account/notifications/filters/filter_item.html.twig' with { + target_id: form.vars.id|slug, + extra_class: 'ibexa-list-filters__item--type', + label: 'ibexa.notifications.search_form.label.type'|trans|desc('Type'), + } %} + {% block content %} + {{ form_widget(form) }} + {% endblock %} + {% endembed %} +{% endblock _search_type_row %} + +{% block _search_statuses_row %} + {% embed '@ibexadesign/account/notifications/filters/filter_item.html.twig' with { + target_id: form.vars.id|slug, + extra_class: 'ibexa-list-filters__item--statuses', + label: 'ibexa.notifications.search_form.label.status'|trans|desc('Status'), + } %} + {% block content %} + {{ form_widget(form) }} + {% endblock %} + {% endembed %} +{% endblock _search_statuses_row %} + +{% block _search_createdRange_row %} + {% embed '@ibexadesign/account/notifications/filters/filter_item.html.twig' with { + target_id: form.vars.id|slug, + extra_class: 'ibexa-list-filters__item--date-time', + label: 'ibexa.notifications.search_form.label.date_and_time'|trans|desc('Date and Time'), + } %} + {% block content %} +
+ {{ form_label(form.children.min) }} + {{ form_widget(form.children.min, { + attr: { + 'data-seconds': 0, + } + }) }} +
+ +
+ {{ form_label(form.children.max) }} + {{ form_widget(form.children.max, { + attr: { + 'data-seconds': 0, + } + }) }} +
+ {% endblock %} + {% endembed %} +{% endblock _search_createdRange_row %} diff --git a/src/bundle/Resources/views/themes/admin/account/notifications/list.html.twig b/src/bundle/Resources/views/themes/admin/account/notifications/list.html.twig index dc2b5f0330..6d248f01c9 100644 --- a/src/bundle/Resources/views/themes/admin/account/notifications/list.html.twig +++ b/src/bundle/Resources/views/themes/admin/account/notifications/list.html.twig @@ -2,7 +2,7 @@ {% embed '@ibexadesign/ui/component/table/table.html.twig' with { head_cols: [], - class: 'ibexa-table--not-striped ibexa-list--notifications ibexa-notifications-modal__list', + class: 'ibexa-table--not-striped ibexa-list--notifications', attr: { 'data-notifications': path('ibexa.notifications.render.page'), 'data-notifications-count': path('ibexa.notifications.count'), diff --git a/src/bundle/Resources/views/themes/admin/account/notifications/list_all.html.twig b/src/bundle/Resources/views/themes/admin/account/notifications/list_all.html.twig index df05321b9e..d96cd9b808 100644 --- a/src/bundle/Resources/views/themes/admin/account/notifications/list_all.html.twig +++ b/src/bundle/Resources/views/themes/admin/account/notifications/list_all.html.twig @@ -4,6 +4,8 @@ {% from '@ibexadesign/ui/component/macros.html.twig' import results_headline %} {% trans_default_domain 'ibexa_notifications' %} +{% form_theme form_remove '@ibexadesign/ui/form_fields.html.twig' %} +{% import _self as macros %} {% block title %}{{ 'ibexa_notifications'|trans|desc('Notifications') }}{% endblock %} @@ -20,54 +22,120 @@ {% endblock %} {% block content %} -
-
- {% embed '@ibexadesign/ui/component/table/table.html.twig' with { - headline: custom_results_headline ?? results_headline(pager.getNbResults()), - head_cols: [ - { has_checkbox: true }, - { content: 'notification.Title'|trans|desc('Title') }, - { content: 'notification.status'|trans|desc('Status') }, - { content: 'notification.datetime'|trans|desc('Date and time') }, - ], - class: 'ibexa-table--notifications', - attr: { - 'data-notifications': path('ibexa.notifications.render.page'), - 'data-notifications-count': path('ibexa.notifications.count'), - 'data-notifications-count-interval': notifications_count_interval, - 'data-notifications-total': pager.nbResults, - }, - } %} - {% block tbody %} - {% if pager.count is same as(0) %} - {% include '@ibexadesign/ui/component/table/empty_table_body_row.html.twig' with { - colspan: 3, - empty_table_info_text: 'bookmark.list.empty'|trans|desc('You have no notifications.'), + {{ form_start(form_remove, { + 'action': path('ibexa.notifications.delete_multiple'), + 'attr': { + 'class': 'ibexa-toggle-btn-state ibexa-notification-list__hidden-btn', + 'data-toggle-button-id': '#confirm-' ~ form_remove.remove.vars.id + } + }) }} + {% for row in form_remove.notifications %} + {{ form_widget(row, { + 'attr': { + 'hidden': true + } + }) }} + {% endfor %} + {{ form_widget(form_remove.remove, { + 'attr': { + 'hidden': true + } + }) }} + {{ form_end(form_remove) }} + {{ form_start(search_form, { + attr: { class: 'ibexa-al-list-search-form' } + }) }} +
+
+ {% embed '@ibexadesign/ui/component/table/table.html.twig' with { + headline: custom_results_headline ?? results_headline(pager.getNbResults()), + head_cols: [ + { has_checkbox: true }, + { content: 'notification.Title'|trans|desc('Title') }, + { content: 'notification.status'|trans|desc('Status') }, + { content: 'notification.datetime'|trans|desc('Date and time') }, + ], + class: 'ibexa-table--notifications', + actions: macros.table_header_tools(form_remove), + is_scrollable: false, + show_head_cols_if_empty: true, + attr: { + 'data-notifications': path('ibexa.notifications.render.page'), + 'data-notifications-count': path('ibexa.notifications.count'), + 'data-notifications-count-interval': notifications_count_interval, + 'data-notifications-total': pager.nbResults, + }, + } %} + {% block tbody %} + {% if pager.count is same as(0) %} + {% include '@ibexadesign/ui/component/table/empty_table_body_row.html.twig' with { + colspan: 3, + empty_table_info_text: 'bookmark.list.empty'|trans|desc('You have no notifications.'), + } %} + {% else %} + {% block tbody_not_empty %} + {% for notification in notifications %} + {{ notification|raw }} + {% endfor %} + {% endblock %} + {% endif %} + {% endblock %} + {% endembed %} + {% if pager.haveToPaginate %} +
+ {% include '@ibexadesign/ui/pagination.html.twig' with { + pager, + 'paginaton_params': { + 'routeName': 'ibexa.notifications.render.all', + } } %} - {% else %} - {% block tbody_not_empty %} - {% for notification in notifications %} - {{ notification|raw }} - {% endfor %} - {% endblock %} - {% endif %} - {% endblock %} - {% endembed %} - - {% if pager.haveToPaginate %} -
- {% include '@ibexadesign/ui/pagination.html.twig' with { - pager, - 'paginaton_params': { - 'routeName': 'ibexa.notifications.render.all', - } - } %} -
- {% endif %} -
-
+
+ {% endif %} + +
+ {% include '@ibexadesign/account/notifications/filters.html.twig' %} +
+
+ {{ form_end(search_form) }} {% endblock %} {% block javascripts %} {{ encore_entry_script_tags('ibexa-admin-notifications-list-js', null, 'ibexa') }} {% endblock %} +{% macro table_header_tools(form) %} + {% set modal_data_target = 'modal-' ~ form.remove.vars.id %} +
+ + +
+ {% include '@ibexadesign/ui/modal/bulk_delete_confirmation.html.twig' with { + 'id': modal_data_target, + 'message': 'notifications.list.action.remove.confirmation.text'|trans|desc('Are you sure you want to permanently delete the selected notification(s)?'), + 'data_click': '#' ~ form.remove.vars.id, + } %} +{% endmacro %} \ No newline at end of file diff --git a/src/bundle/Resources/views/themes/admin/account/notifications/list_item_all.html.twig b/src/bundle/Resources/views/themes/admin/account/notifications/list_item_all.html.twig index 71b97238c8..a537e624ea 100644 --- a/src/bundle/Resources/views/themes/admin/account/notifications/list_item_all.html.twig +++ b/src/bundle/Resources/views/themes/admin/account/notifications/list_item_all.html.twig @@ -9,11 +9,13 @@ {% endif %} {% set icon %} - - - - - + {% block icon %} + + + + + + {% endblock %} {% endset %} {% set date %} @@ -37,7 +39,7 @@ {% set status %}
- + {{is_read ? 'notification.read'|trans|desc('Read') : 'notification.unread'|trans|desc('Unread')}}
@@ -70,13 +72,14 @@ } } %} {% block body_row_cells %} - {% embed '@ibexadesign/ui/component/table/table_body_cell.html.twig' %} + {% embed '@ibexadesign/ui/component/table/table_body_cell.html.twig' with { class: 'ibexa-table__cell--has-checkbox' }%} {% block content %} - {# TODO: to handle #}
{% endblock %} diff --git a/src/bundle/Resources/views/themes/admin/account/notifications/side_panel.html.twig b/src/bundle/Resources/views/themes/admin/account/notifications/side_panel.html.twig index 0bfa2083ad..509e2d8c98 100644 --- a/src/bundle/Resources/views/themes/admin/account/notifications/side_panel.html.twig +++ b/src/bundle/Resources/views/themes/admin/account/notifications/side_panel.html.twig @@ -8,7 +8,9 @@ }%} {% block header %}
- {{ 'ibexa_notifications'|trans|desc('Notifications')}} + {{ 'ibexa_notifications'|trans|desc('Notifications')}} + (10) + @@ -34,6 +36,7 @@ {% endblock %} From bae168d67bb1ca09ad7b7d388530ba3d43505480 Mon Sep 17 00:00:00 2001 From: Aleksandra Bozek Date: Mon, 28 Apr 2025 16:50:59 +0200 Subject: [PATCH 13/94] IBX-9060: Collapsable sidebar --- .../Resources/encore/ibexa.js.config.js | 1 + .../js/scripts/admin.notifications.list.js | 8 +++-- .../js/scripts/core/sidebar.collapse.js | 17 ++++++++++ .../Resources/public/scss/_list-filters.scss | 5 +-- .../public/scss/_notifications-modal.scss | 11 ++++--- .../Resources/public/scss/_notifications.scss | 14 ++++++-- .../translations/ibexa_notifications.en.xliff | 5 +++ .../account/notifications/filters.html.twig | 16 ++++++--- .../notifications/list_item_all.html.twig | 33 +++++++++++++------ .../notifications/list_item_deleted.html.twig | 2 +- 10 files changed, 85 insertions(+), 27 deletions(-) create mode 100644 src/bundle/Resources/public/js/scripts/core/sidebar.collapse.js diff --git a/src/bundle/Resources/encore/ibexa.js.config.js b/src/bundle/Resources/encore/ibexa.js.config.js index 28b0b11034..15a305daa3 100644 --- a/src/bundle/Resources/encore/ibexa.js.config.js +++ b/src/bundle/Resources/encore/ibexa.js.config.js @@ -255,6 +255,7 @@ module.exports = (Encore) => { ]) .addEntry('ibexa-admin-ui-edit-base-js', [path.resolve(__dirname, '../public/js/scripts/edit.header.js')]) .addEntry('ibexa-admin-notifications-list-js', [ + path.resolve(__dirname, '../public/js/scripts/core/sidebar.collapse.js'), path.resolve(__dirname, '../public/js/scripts/admin.notifications.list.js'), path.resolve(__dirname, '../public/js/scripts/admin.notifications.filters.js'), ]) diff --git a/src/bundle/Resources/public/js/scripts/admin.notifications.list.js b/src/bundle/Resources/public/js/scripts/admin.notifications.list.js index c792babfb1..2995828660 100644 --- a/src/bundle/Resources/public/js/scripts/admin.notifications.list.js +++ b/src/bundle/Resources/public/js/scripts/admin.notifications.list.js @@ -102,13 +102,17 @@ const statusText = isRead ? Translator.trans(/*@Desc("Unread")*/ 'notification.unread', {}, 'ibexa_notifications') : Translator.trans(/*@Desc("Read")*/ 'notification.read', {}, 'ibexa_notifications'); - notification.closest('.ibexa-table__row').querySelector('.ibexa-notification-view-all__read').innerHTML = + + notificationRow.querySelectorAll('.ibexa-notification-view-all__notice-dot').forEach((noticeDot) => { + noticeDot.setAttribute('data-is-read', (!isRead).toString()); + }); + notificationRow.querySelector('.ibexa-notification-view-all__read').innerHTML = statusText; return; } - if (response.redirect) { + if (!isToggle && response.redirect) { global.location = response.redirect; } } diff --git a/src/bundle/Resources/public/js/scripts/core/sidebar.collapse.js b/src/bundle/Resources/public/js/scripts/core/sidebar.collapse.js new file mode 100644 index 0000000000..a41b0d91bf --- /dev/null +++ b/src/bundle/Resources/public/js/scripts/core/sidebar.collapse.js @@ -0,0 +1,17 @@ +(function (global, doc) { + const sidebar = doc.querySelector('.ibexa-notification-list__filters-wrapper'); + const toggleBtn = sidebar.querySelector('.ibexa-list-filters__expand-btn'); + const toggleCollapseIcon = toggleBtn.querySelector('.ibexa-list-filters__collapse-icon'); + const toggleExpandIcon = toggleBtn.querySelector('.ibexa-list-filters__expand-icon'); + + const toggleSidebar = () => { + const isExpanded = toggleBtn.getAttribute('aria-expanded') === 'true'; + + sidebar.classList.toggle('ibexa-notification-list__filters-wrapper--collapsed', !isExpanded); + toggleBtn.setAttribute('aria-expanded', (!isExpanded).toString()); + toggleExpandIcon.toggleAttribute('hidden', isExpanded); + toggleCollapseIcon.toggleAttribute('hidden', !isExpanded); + }; + + toggleBtn.addEventListener('click', toggleSidebar, false); +})(window, window.document, window.bootstrap, window.Translator); diff --git a/src/bundle/Resources/public/scss/_list-filters.scss b/src/bundle/Resources/public/scss/_list-filters.scss index 9450ab775f..65ba02f56b 100644 --- a/src/bundle/Resources/public/scss/_list-filters.scss +++ b/src/bundle/Resources/public/scss/_list-filters.scss @@ -7,12 +7,13 @@ display: flex; align-items: center; justify-content: space-between; - padding: calculateRem(16px); + padding: calculateRem(11px) calculateRem(16px); + min-width: calculateRem(275px); } &__title { margin: 0; - padding: 0; + padding: 0 0 0 calculateRem(8px); font-weight: 600; } diff --git a/src/bundle/Resources/public/scss/_notifications-modal.scss b/src/bundle/Resources/public/scss/_notifications-modal.scss index 11d7c9defd..7996b285cf 100644 --- a/src/bundle/Resources/public/scss/_notifications-modal.scss +++ b/src/bundle/Resources/public/scss/_notifications-modal.scss @@ -203,11 +203,6 @@ overflow: hidden; } - &__show, - &__mail { - cursor: pointer; - } - &__notice-dot { width: calculateRem(8px); height: calculateRem(8px); @@ -223,6 +218,12 @@ width: calculateRem(6px); height: calculateRem(6px); } + &[data-is-read="true"] { + background: $ibexa-color-dark-300; + } + &[data-is-read="false"] { + background: $ibexa-color-danger; + } } &__details { diff --git a/src/bundle/Resources/public/scss/_notifications.scss b/src/bundle/Resources/public/scss/_notifications.scss index d8c4d542d8..a7f047979b 100644 --- a/src/bundle/Resources/public/scss/_notifications.scss +++ b/src/bundle/Resources/public/scss/_notifications.scss @@ -47,17 +47,27 @@ } &__data-grid-wrapper { - width: calc(100% - #{calculateRem(269px)}); border-radius: $ibexa-border-radius 0 0 $ibexa-border-radius; border: calculateRem(1px) solid $ibexa-color-light; border-right: none; } &__filters-wrapper { - width: calculateRem(275px); + width: calculateRem(360px); border-radius: 0 $ibexa-border-radius $ibexa-border-radius 0; border: calculateRem(1px) solid $ibexa-color-light; background-color: $ibexa-color-white; + transition: width 0.3s ease; + + &--collapsed { + width: calculateRem(64px); + margin-right: 0; + + .ibexa-list-filters__header > *:not(.ibexa-list-filters__expand-btn), + .ibexa-list-filters__items { + display: none; + } + } } &__table-btns { diff --git a/src/bundle/Resources/translations/ibexa_notifications.en.xliff b/src/bundle/Resources/translations/ibexa_notifications.en.xliff index f2410607c8..723b5f4934 100644 --- a/src/bundle/Resources/translations/ibexa_notifications.en.xliff +++ b/src/bundle/Resources/translations/ibexa_notifications.en.xliff @@ -66,6 +66,11 @@ Delete key: notification.delete + + Go to content + Go to content + key: notification.go_to_content + Mark as read Mark as read diff --git a/src/bundle/Resources/views/themes/admin/account/notifications/filters.html.twig b/src/bundle/Resources/views/themes/admin/account/notifications/filters.html.twig index 6b57b8f82c..c8664f01b5 100644 --- a/src/bundle/Resources/views/themes/admin/account/notifications/filters.html.twig +++ b/src/bundle/Resources/views/themes/admin/account/notifications/filters.html.twig @@ -10,13 +10,19 @@
- {# TODO: collapse #} - {# +

{{ 'ibexa.notifications.search_form.title'|trans()|desc('Filters') }}

{% endblock %} {% endembed %} {% embed '@ibexadesign/ui/component/table/table_body_cell.html.twig' %} {% block content %} -
- {{ icon_mail_open }} -
-
- {{ icon_mail }} -
+ + {% endblock %} {% endembed %} {% endblock %} diff --git a/src/bundle/Resources/views/themes/admin/account/notifications/list_item_deleted.html.twig b/src/bundle/Resources/views/themes/admin/account/notifications/list_item_deleted.html.twig index 2bd69ed6bc..36bfb33da8 100644 --- a/src/bundle/Resources/views/themes/admin/account/notifications/list_item_deleted.html.twig +++ b/src/bundle/Resources/views/themes/admin/account/notifications/list_item_deleted.html.twig @@ -24,6 +24,6 @@ {{ 'notification.title'|trans|desc('Title:') }} {{ title }}

-

{{ 'notification.no_longer_available'|trans|desc('The Content item is no longer available')}}

+

{{ 'notification.no_longer_available'|trans({}, 'ibexa_notifications')|desc('The Content item is no longer available')}}

{% endblock %} {% endblock %} From 4ec884554d983f3a42750182722c04fb0542be3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Bia=C5=82czak?= Date: Tue, 29 Apr 2025 09:29:45 +0200 Subject: [PATCH 14/94] IBX-9060: Added bulk mark-as-read support and unify selection form handling --- .../Controller/NotificationController.php | 61 +++++++++++++------ src/bundle/Form/Data/SearchQueryData.php | 2 +- src/bundle/Resources/config/routing.yaml | 6 ++ ...Data.php => NotificationSelectionData.php} | 2 +- src/lib/Form/Factory/FormFactory.php | 27 ++++++-- ...Type.php => NotificationSelectionType.php} | 6 +- .../Pagerfanta/NotificationAdapter.php | 1 - 7 files changed, 74 insertions(+), 31 deletions(-) rename src/lib/Form/Data/Notification/{NotificationRemoveData.php => NotificationSelectionData.php} (95%) rename src/lib/Form/Type/Notification/{NotificationRemoveType.php => NotificationSelectionType.php} (87%) diff --git a/src/bundle/Controller/NotificationController.php b/src/bundle/Controller/NotificationController.php index 40bde8195c..5039f437ce 100644 --- a/src/bundle/Controller/NotificationController.php +++ b/src/bundle/Controller/NotificationController.php @@ -10,7 +10,7 @@ use DateTimeInterface; use Exception; -use Ibexa\AdminUi\Form\Data\Notification\NotificationRemoveData; +use Ibexa\AdminUi\Form\Data\Notification\NotificationSelectionData; use Ibexa\AdminUi\Form\Factory\FormFactory; use Ibexa\AdminUi\Form\SubmitHandler; use Ibexa\AdminUi\Pagination\Pagerfanta\NotificationAdapter; @@ -21,7 +21,6 @@ use Ibexa\Contracts\Core\SiteAccess\ConfigResolverInterface; use Ibexa\Core\Notification\Renderer\Registry; use Pagerfanta\Pagerfanta; -use Symfony\Component\Form\FormInterface; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; @@ -80,10 +79,12 @@ public function getNotificationsAction(Request $request, int $offset, int $limit public function renderNotificationsPageAction(Request $request, int $page): Response { + $allNotifications = $this->notificationService->loadNotifications(0, PHP_INT_MAX)->items; + $notificationTypes = array_unique( array_map( - fn($notification) => $notification->type, - $this->notificationService->loadNotifications(0, PHP_INT_MAX)->items + static fn($notification) => $notification->type, + $allNotifications ) ); sort($notificationTypes); @@ -95,8 +96,7 @@ public function renderNotificationsPageAction(Request $request, int $page): Resp $query = []; if ($searchForm->isSubmitted() && $searchForm->isValid()) { - $data = $searchForm->getData(); - $query = $this->buildQuery($data); + $query = $this->buildQuery($searchForm->getData()); } $pagerfanta = new Pagerfanta( @@ -108,23 +108,24 @@ public function renderNotificationsPageAction(Request $request, int $page): Resp $notifications = []; foreach ($pagerfanta->getCurrentPageResults() as $notification) { if ($this->registry->hasRenderer($notification->type)) { - $renderer = $this->registry->getRenderer($notification->type); - $notifications[] = $renderer->render($notification); + $notifications[] = $this->registry->getRenderer($notification->type)->render($notification); } } - $template = $request->attributes->get('template', '@ibexadesign/account/notifications/list.html.twig'); + $formData = $this->createNotificationSelectionData($pagerfanta); - $deleteNotificationsForm = $this->formFactory->deleteNotification( - $this->createNotificationRemoveData($pagerfanta) - ); + $deleteForm = $this->formFactory->deleteNotification($formData); + $markAsReadForm = $this->formFactory->markNotificationAsRead($formData); + + $template = $request->attributes->get('template', '@ibexadesign/account/notifications/list.html.twig'); return $this->render($template, [ 'notifications' => $notifications, 'notifications_count_interval' => $this->configResolver->getParameter('notification_count.interval'), 'pager' => $pagerfanta, 'search_form' => $searchForm->createView(), - 'form_remove' => $deleteNotificationsForm->createView(), + 'form_remove' => $deleteForm->createView(), + 'form_mark_as_read' => $markAsReadForm->createView(), ]); } @@ -159,16 +160,15 @@ private function buildQuery(SearchQueryData $data): array return $query; } - - private function createNotificationRemoveData(Pagerfanta $pagerfanta): NotificationRemoveData + private function createNotificationSelectionData(Pagerfanta $pagerfanta): NotificationSelectionData { - $notificationIds = []; + $notifications = []; foreach ($pagerfanta->getCurrentPageResults() as $notification) { - $notificationIds[$notification->id] = false; + $notifications[$notification->id] = false; } - return new NotificationRemoveData($notificationIds); + return new NotificationSelectionData($notifications); } /** @@ -230,6 +230,29 @@ public function markNotificationAsReadAction(Request $request, mixed $notificati return $response; } + public function markNotificationsAsReadAction(Request $request): Response + { + $form = $this->formFactory->markNotificationAsRead(); + $form->handleRequest($request); + + if ($form->isSubmitted()) { + $result = $this->submitHandler->handle($form, function (NotificationSelectionData $data) { + foreach (array_keys($data->getNotifications()) as $id) { + $notification = $this->notificationService->getNotification((int)$id); + $this->notificationService->markNotificationAsRead($notification); + } + + return $this->redirectToRoute('ibexa.notifications.render.all'); + }); + + if ($result instanceof Response) { + return $result; + } + } + + return $this->redirectToRoute('ibexa.notifications.render.all'); + } + public function markAllNotificationsAsReadAction(Request $request): JsonResponse { $response = new JsonResponse(); @@ -302,7 +325,7 @@ public function deleteNotificationsAction(Request $request): Response $form->handleRequest($request); if ($form->isSubmitted()) { - $result = $this->submitHandler->handle($form, function (NotificationRemoveData $data) { + $result = $this->submitHandler->handle($form, function (NotificationSelectionData $data) { foreach (array_keys($data->getNotifications()) as $id) { $notification = $this->notificationService->getNotification((int)$id); $this->notificationService->deleteNotification($notification); diff --git a/src/bundle/Form/Data/SearchQueryData.php b/src/bundle/Form/Data/SearchQueryData.php index a80bd5da3b..3dd2c0421f 100644 --- a/src/bundle/Form/Data/SearchQueryData.php +++ b/src/bundle/Form/Data/SearchQueryData.php @@ -13,11 +13,11 @@ final class SearchQueryData { private array $statuses = []; + private ?string $type = null; private ?DateRangeData $createdRange = null; - public function getStatuses(): array { return $this->statuses; diff --git a/src/bundle/Resources/config/routing.yaml b/src/bundle/Resources/config/routing.yaml index 57e3bcb90b..c2b4373f12 100644 --- a/src/bundle/Resources/config/routing.yaml +++ b/src/bundle/Resources/config/routing.yaml @@ -937,6 +937,12 @@ ibexa.notifications.mark_as_read: requirements: notificationId: '\d+' +ibexa.notifications.mark_multiple_as_read: + path: /notification/mark-multiple-as-read + defaults: + _controller: 'Ibexa\Bundle\AdminUi\Controller\NotificationController::markNotificationsAsReadAction' + methods: [ POST ] + ibexa.notifications.mark_all_as_read: path: /notifications/read-all options: diff --git a/src/lib/Form/Data/Notification/NotificationRemoveData.php b/src/lib/Form/Data/Notification/NotificationSelectionData.php similarity index 95% rename from src/lib/Form/Data/Notification/NotificationRemoveData.php rename to src/lib/Form/Data/Notification/NotificationSelectionData.php index 8b6338ba37..9594d551cc 100644 --- a/src/lib/Form/Data/Notification/NotificationRemoveData.php +++ b/src/lib/Form/Data/Notification/NotificationSelectionData.php @@ -8,7 +8,7 @@ namespace Ibexa\AdminUi\Form\Data\Notification; -class NotificationRemoveData +class NotificationSelectionData { private array $notifications; diff --git a/src/lib/Form/Factory/FormFactory.php b/src/lib/Form/Factory/FormFactory.php index 19fb4a9e55..88c579093a 100644 --- a/src/lib/Form/Factory/FormFactory.php +++ b/src/lib/Form/Factory/FormFactory.php @@ -36,7 +36,7 @@ use Ibexa\AdminUi\Form\Data\Location\LocationTrashData; use Ibexa\AdminUi\Form\Data\Location\LocationUpdateData; use Ibexa\AdminUi\Form\Data\Location\LocationUpdateVisibilityData; -use Ibexa\AdminUi\Form\Data\Notification\NotificationRemoveData; +use Ibexa\AdminUi\Form\Data\Notification\NotificationSelectionData; use Ibexa\AdminUi\Form\Data\ObjectState\ObjectStateGroupCreateData; use Ibexa\AdminUi\Form\Data\ObjectState\ObjectStateGroupDeleteData; use Ibexa\AdminUi\Form\Data\ObjectState\ObjectStateGroupsDeleteData; @@ -93,7 +93,7 @@ use Ibexa\AdminUi\Form\Type\Location\LocationTrashType; use Ibexa\AdminUi\Form\Type\Location\LocationUpdateType; use Ibexa\AdminUi\Form\Type\Location\LocationUpdateVisibilityType; -use Ibexa\AdminUi\Form\Type\Notification\NotificationRemoveType; +use Ibexa\AdminUi\Form\Type\Notification\NotificationSelectionType; use Ibexa\AdminUi\Form\Type\ObjectState\ObjectStateGroupCreateType; use Ibexa\AdminUi\Form\Type\ObjectState\ObjectStateGroupDeleteType; use Ibexa\AdminUi\Form\Type\ObjectState\ObjectStateGroupsDeleteType; @@ -1067,18 +1067,33 @@ public function removeContentDraft( } /** - * @param \Ibexa\AdminUi\Form\Data\Notification\NotificationRemoveData|null $data + * @param \Ibexa\AdminUi\Form\Data\Notification\NotificationSelectionData|null $data * @param string|null $name * * @return \Symfony\Component\Form\FormInterface */ public function deleteNotification( - NotificationRemoveData $data = null, + NotificationSelectionData $data = null, ?string $name = null ): FormInterface { - $name = $name ?: StringUtil::fqcnToBlockPrefix(NotificationRemoveType::class); + $name = $name ?: StringUtil::fqcnToBlockPrefix(NotificationSelectionType::class); - return $this->formFactory->createNamed($name, NotificationRemoveType::class, $data); + return $this->formFactory->createNamed($name, NotificationSelectionType::class, $data); + } + + /** + * @param \Ibexa\AdminUi\Form\Data\Notification\NotificationSelectionData|null $data + * @param string|null $name + * + * @return \Symfony\Component\Form\FormInterface + */ + public function markNotificationAsRead( + NotificationSelectionData $data = null, + ?string $name = null + ): FormInterface { + $name = $name ?: StringUtil::fqcnToBlockPrefix(NotificationSelectionType::class); + + return $this->formFactory->createNamed($name, NotificationSelectionType::class, $data); } /** diff --git a/src/lib/Form/Type/Notification/NotificationRemoveType.php b/src/lib/Form/Type/Notification/NotificationSelectionType.php similarity index 87% rename from src/lib/Form/Type/Notification/NotificationRemoveType.php rename to src/lib/Form/Type/Notification/NotificationSelectionType.php index 23bc79998e..ad50787444 100644 --- a/src/lib/Form/Type/Notification/NotificationRemoveType.php +++ b/src/lib/Form/Type/Notification/NotificationSelectionType.php @@ -8,7 +8,7 @@ namespace Ibexa\AdminUi\Form\Type\Notification; -use Ibexa\AdminUi\Form\Data\Notification\NotificationRemoveData; +use Ibexa\AdminUi\Form\Data\Notification\NotificationSelectionData; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\Extension\Core\Type\CheckboxType; use Symfony\Component\Form\Extension\Core\Type\CollectionType; @@ -16,7 +16,7 @@ use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolver; -class NotificationRemoveType extends AbstractType +class NotificationSelectionType extends AbstractType { public function buildForm(FormBuilderInterface $builder, array $options): void { @@ -41,7 +41,7 @@ public function buildForm(FormBuilderInterface $builder, array $options): void public function configureOptions(OptionsResolver $resolver): void { $resolver->setDefaults([ - 'data_class' => NotificationRemoveData::class, + 'data_class' => NotificationSelectionData::class, ]); } } diff --git a/src/lib/Pagination/Pagerfanta/NotificationAdapter.php b/src/lib/Pagination/Pagerfanta/NotificationAdapter.php index b6d8f3aed8..002226c925 100644 --- a/src/lib/Pagination/Pagerfanta/NotificationAdapter.php +++ b/src/lib/Pagination/Pagerfanta/NotificationAdapter.php @@ -55,7 +55,6 @@ public function getSlice($offset, $length): NotificationList $this->nbResults ??= $notifications->totalCount; - return $notifications; } } From 92eaf96467855dde148cd09572a37fd5b31cb24b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Bia=C5=82czak?= Date: Tue, 29 Apr 2025 13:08:24 +0200 Subject: [PATCH 15/94] IBX-9060: Added submit buttons to notification selection forms --- src/bundle/Controller/NotificationController.php | 4 ++++ .../Form/Type/Notification/NotificationSelectionType.php | 6 ------ 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/bundle/Controller/NotificationController.php b/src/bundle/Controller/NotificationController.php index 5039f437ce..2a7eced360 100644 --- a/src/bundle/Controller/NotificationController.php +++ b/src/bundle/Controller/NotificationController.php @@ -21,6 +21,7 @@ use Ibexa\Contracts\Core\SiteAccess\ConfigResolverInterface; use Ibexa\Core\Notification\Renderer\Registry; use Pagerfanta\Pagerfanta; +use Symfony\Component\Form\Extension\Core\Type\SubmitType; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; @@ -115,7 +116,10 @@ public function renderNotificationsPageAction(Request $request, int $page): Resp $formData = $this->createNotificationSelectionData($pagerfanta); $deleteForm = $this->formFactory->deleteNotification($formData); + $deleteForm->add('remove', SubmitType::class); + $markAsReadForm = $this->formFactory->markNotificationAsRead($formData); + $markAsReadForm->add('mark_as_read', SubmitType::class); $template = $request->attributes->get('template', '@ibexadesign/account/notifications/list.html.twig'); diff --git a/src/lib/Form/Type/Notification/NotificationSelectionType.php b/src/lib/Form/Type/Notification/NotificationSelectionType.php index ad50787444..277a4d04ee 100644 --- a/src/lib/Form/Type/Notification/NotificationSelectionType.php +++ b/src/lib/Form/Type/Notification/NotificationSelectionType.php @@ -12,7 +12,6 @@ use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\Extension\Core\Type\CheckboxType; use Symfony\Component\Form\Extension\Core\Type\CollectionType; -use Symfony\Component\Form\Extension\Core\Type\SubmitType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolver; @@ -31,11 +30,6 @@ public function buildForm(FormBuilderInterface $builder, array $options): void 'label' => false, ] ); - - $builder->add( - 'remove', - SubmitType::class - ); } public function configureOptions(OptionsResolver $resolver): void From b3d8ffe09316b7867c8aa98fbe3660654752f154 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Bia=C5=82czak?= Date: Wed, 30 Apr 2025 12:24:56 +0200 Subject: [PATCH 16/94] IBX-9060: Refactored routes for marking notifications as read --- .../Controller/NotificationController.php | 41 +++++++++++-------- src/bundle/Resources/config/routing.yaml | 2 +- src/lib/Form/Factory/FormFactory.php | 15 ------- 3 files changed, 24 insertions(+), 34 deletions(-) diff --git a/src/bundle/Controller/NotificationController.php b/src/bundle/Controller/NotificationController.php index 2a7eced360..197dc0327c 100644 --- a/src/bundle/Controller/NotificationController.php +++ b/src/bundle/Controller/NotificationController.php @@ -20,6 +20,7 @@ use Ibexa\Contracts\Core\Repository\NotificationService; use Ibexa\Contracts\Core\SiteAccess\ConfigResolverInterface; use Ibexa\Core\Notification\Renderer\Registry; +use InvalidArgumentException; use Pagerfanta\Pagerfanta; use Symfony\Component\Form\Extension\Core\Type\SubmitType; use Symfony\Component\HttpFoundation\JsonResponse; @@ -118,9 +119,6 @@ public function renderNotificationsPageAction(Request $request, int $page): Resp $deleteForm = $this->formFactory->deleteNotification($formData); $deleteForm->add('remove', SubmitType::class); - $markAsReadForm = $this->formFactory->markNotificationAsRead($formData); - $markAsReadForm->add('mark_as_read', SubmitType::class); - $template = $request->attributes->get('template', '@ibexadesign/account/notifications/list.html.twig'); return $this->render($template, [ @@ -129,7 +127,6 @@ public function renderNotificationsPageAction(Request $request, int $page): Resp 'pager' => $pagerfanta, 'search_form' => $searchForm->createView(), 'form_remove' => $deleteForm->createView(), - 'form_mark_as_read' => $markAsReadForm->createView(), ]); } @@ -234,27 +231,35 @@ public function markNotificationAsReadAction(Request $request, mixed $notificati return $response; } - public function markNotificationsAsReadAction(Request $request): Response + public function markNotificationsAsReadAction(Request $request): JsonResponse { - $form = $this->formFactory->markNotificationAsRead(); - $form->handleRequest($request); + $response = new JsonResponse(); - if ($form->isSubmitted()) { - $result = $this->submitHandler->handle($form, function (NotificationSelectionData $data) { - foreach (array_keys($data->getNotifications()) as $id) { - $notification = $this->notificationService->getNotification((int)$id); - $this->notificationService->markNotificationAsRead($notification); - } + try { + $ids = $request->toArray()['ids'] ?? []; - return $this->redirectToRoute('ibexa.notifications.render.all'); - }); + if (!is_array($ids) || empty($ids)) { + throw new InvalidArgumentException('Missing or invalid "ids" parameter.'); + } - if ($result instanceof Response) { - return $result; + foreach ($ids as $id) { + $notification = $this->notificationService->getNotification((int)$id); + $this->notificationService->markNotificationAsRead($notification); } + + $response->setData([ + 'status' => 'success', + 'redirect' => $this->generateUrl('ibexa.notifications.render.all'), + ]); + } catch (Exception $exception) { + $response->setData([ + 'status' => 'failed', + 'error' => $exception->getMessage(), + ]); + $response->setStatusCode(400); } - return $this->redirectToRoute('ibexa.notifications.render.all'); + return $response; } public function markAllNotificationsAsReadAction(Request $request): JsonResponse diff --git a/src/bundle/Resources/config/routing.yaml b/src/bundle/Resources/config/routing.yaml index c2b4373f12..581016d658 100644 --- a/src/bundle/Resources/config/routing.yaml +++ b/src/bundle/Resources/config/routing.yaml @@ -938,7 +938,7 @@ ibexa.notifications.mark_as_read: notificationId: '\d+' ibexa.notifications.mark_multiple_as_read: - path: /notification/mark-multiple-as-read + path: /notifications/read defaults: _controller: 'Ibexa\Bundle\AdminUi\Controller\NotificationController::markNotificationsAsReadAction' methods: [ POST ] diff --git a/src/lib/Form/Factory/FormFactory.php b/src/lib/Form/Factory/FormFactory.php index 88c579093a..d389505ce9 100644 --- a/src/lib/Form/Factory/FormFactory.php +++ b/src/lib/Form/Factory/FormFactory.php @@ -1081,21 +1081,6 @@ public function deleteNotification( return $this->formFactory->createNamed($name, NotificationSelectionType::class, $data); } - /** - * @param \Ibexa\AdminUi\Form\Data\Notification\NotificationSelectionData|null $data - * @param string|null $name - * - * @return \Symfony\Component\Form\FormInterface - */ - public function markNotificationAsRead( - NotificationSelectionData $data = null, - ?string $name = null - ): FormInterface { - $name = $name ?: StringUtil::fqcnToBlockPrefix(NotificationSelectionType::class); - - return $this->formFactory->createNamed($name, NotificationSelectionType::class, $data); - } - /** * @param \Ibexa\AdminUi\Form\Data\URLWildcard\URLWildcardData|null $data * @param string|null $name From d2a966daefee2d311ba48fde6cf9f4466ddaa1dc Mon Sep 17 00:00:00 2001 From: Aleksandra Bozek Date: Tue, 29 Apr 2025 14:31:05 +0200 Subject: [PATCH 17/94] Refacotr & style fixes --- .../Resources/encore/ibexa.js.config.js | 2 +- .../js/scripts/admin.notifications.filters.js | 7 ++--- .../js/scripts/admin.notifications.list.js | 28 ++++++++++--------- .../js/scripts/admin.notifications.modal.js | 1 - .../js/scripts/core/sidebar.collapse.js | 10 +++---- .../Resources/public/scss/_list-filters.scss | 16 +++++++++-- .../public/scss/_notifications-modal.scss | 6 ++-- .../Resources/public/scss/_notifications.scss | 20 ++++--------- .../account/notifications/list_all.html.twig | 2 +- 9 files changed, 46 insertions(+), 46 deletions(-) diff --git a/src/bundle/Resources/encore/ibexa.js.config.js b/src/bundle/Resources/encore/ibexa.js.config.js index 15a305daa3..abe2a4bc81 100644 --- a/src/bundle/Resources/encore/ibexa.js.config.js +++ b/src/bundle/Resources/encore/ibexa.js.config.js @@ -258,5 +258,5 @@ module.exports = (Encore) => { path.resolve(__dirname, '../public/js/scripts/core/sidebar.collapse.js'), path.resolve(__dirname, '../public/js/scripts/admin.notifications.list.js'), path.resolve(__dirname, '../public/js/scripts/admin.notifications.filters.js'), - ]) + ]); }; diff --git a/src/bundle/Resources/public/js/scripts/admin.notifications.filters.js b/src/bundle/Resources/public/js/scripts/admin.notifications.filters.js index df16a895b0..d877bd5be9 100644 --- a/src/bundle/Resources/public/js/scripts/admin.notifications.filters.js +++ b/src/bundle/Resources/public/js/scripts/admin.notifications.filters.js @@ -1,5 +1,5 @@ (function (global, doc) { - const searchForm = doc.querySelector('.ibexa-al-list-search-form'); + const searchForm = doc.querySelector('.ibexa-list-search-form'); const filtersContainerNode = doc.querySelector('.ibexa-list-filters'); const applyFiltersBtn = filtersContainerNode.querySelector('.ibexa-btn--apply'); const clearFiltersBtn = filtersContainerNode.querySelector('.ibexa-btn--clear'); @@ -28,7 +28,6 @@ } else if (checkboxes.length) { checkboxes.forEach((checkbox) => (checkbox.checked = false)); } else if (timePicker.value.length) { - const timePicker = filterNode.querySelector('.ibexa-date-time-picker__input'); const formInput = filterNode.querySelector('.ibexa-picker__form-input'); timePicker.value = ''; @@ -49,9 +48,7 @@ const checkboxes = filterNode.querySelectorAll( '.ibexa-list-filters__item-content .ibexa-input--checkbox:not([name="dropdown-checkbox"])', ); - const picker = filterNode.querySelector( - '.ibexa-input--date', - ); + const picker = filterNode.querySelector('.ibexa-input--date'); picker?.addEventListener('change', filterChange, false); sourceSelect?.addEventListener('change', filterChange, false); checkboxes.forEach((checkbox) => { diff --git a/src/bundle/Resources/public/js/scripts/admin.notifications.list.js b/src/bundle/Resources/public/js/scripts/admin.notifications.list.js index 2995828660..da8e7ba03f 100644 --- a/src/bundle/Resources/public/js/scripts/admin.notifications.list.js +++ b/src/bundle/Resources/public/js/scripts/admin.notifications.list.js @@ -58,9 +58,10 @@ .then(getJsonFromResponse) .catch(() => ibexa.helpers.notification.showErrorNotification(errorMessage)); }; - const getBulkOperations = (notificationIds) => notificationIds.reduce((total, notificationId) => { + const getBulkOperations = (notificationIds) => + notificationIds.reduce((total, notificationId) => { const markAsReadLink = Routing.generate('ibexa.notifications.mark_as_read', { notificationId }); - + total[markAsReadLink] = { uri: markAsReadLink, method: 'GET', @@ -69,9 +70,9 @@ Accept: 'application/vnd.ibexa.api.ContentType+json', 'X-Requested-With': 'XMLHttpRequest', }, - credentials: 'same-origin' + credentials: 'same-origin', }; - + return total; }, {}); @@ -106,8 +107,7 @@ notificationRow.querySelectorAll('.ibexa-notification-view-all__notice-dot').forEach((noticeDot) => { noticeDot.setAttribute('data-is-read', (!isRead).toString()); }); - notificationRow.querySelector('.ibexa-notification-view-all__read').innerHTML = - statusText; + notificationRow.querySelector('.ibexa-notification-view-all__read').innerHTML = statusText; return; } @@ -155,16 +155,18 @@ markAllAsReadBtn.addEventListener('click', markAllAsRead, false); markAsReadBtn.addEventListener('click', markSelectedAsRead, false); - const toggleActionButtonState = () => { const checkedNotifications = checkboxes.filter((el) => el.checked); const isAnythingSelected = checkedNotifications.length > 0; - const unreadLabel = Translator.trans(/* @Desc("Unread") */ 'notification.unread',{},'ibexa_notifications'); + const unreadLabel = Translator.trans(/* @Desc("Unread") */ 'notification.unread', {}, 'ibexa_notifications'); deleteBtn.disabled = !isAnythingSelected; - markAsReadBtn.disabled = !isAnythingSelected || !checkedNotifications.every((checkbox) => - checkbox.closest('.ibexa-table__row').querySelector('.ibexa-notification-view-all__read').innerText === unreadLabel - ); + markAsReadBtn.disabled = + !isAnythingSelected || + !checkedNotifications.every( + (checkbox) => + checkbox.closest('.ibexa-table__row').querySelector('.ibexa-notification-view-all__read').innerText === unreadLabel, + ); }; const handleCheckboxChange = (checkbox) => { const checkboxFormId = checkbox.dataset?.formRemoveId; @@ -172,8 +174,8 @@ if (formRemoveCheckbox) { formRemoveCheckbox.checked = checkbox.checked; } - toggleActionButtonState(); + toggleActionButtonState(); }; - + checkboxes.forEach((checkbox) => checkbox.addEventListener('change', () => handleCheckboxChange(checkbox), false)); })(window, window.document, window.ibexa, window.Translator, window.Routing); diff --git a/src/bundle/Resources/public/js/scripts/admin.notifications.modal.js b/src/bundle/Resources/public/js/scripts/admin.notifications.modal.js index d7e6bb7421..7fd903826f 100644 --- a/src/bundle/Resources/public/js/scripts/admin.notifications.modal.js +++ b/src/bundle/Resources/public/js/scripts/admin.notifications.modal.js @@ -96,7 +96,6 @@ if (notificationsCount < 10) { panel.querySelector('.ibexa-notifications-modal__count').textContent = `(${notificationsCount})`; } - }; const updatePendingNotificationsView = (notificationsInfo) => { const noticeDot = doc.querySelector('.ibexa-header-user-menu__notice-dot'); diff --git a/src/bundle/Resources/public/js/scripts/core/sidebar.collapse.js b/src/bundle/Resources/public/js/scripts/core/sidebar.collapse.js index a41b0d91bf..d5a19aa5a1 100644 --- a/src/bundle/Resources/public/js/scripts/core/sidebar.collapse.js +++ b/src/bundle/Resources/public/js/scripts/core/sidebar.collapse.js @@ -1,16 +1,16 @@ (function (global, doc) { - const sidebar = doc.querySelector('.ibexa-notification-list__filters-wrapper'); + const sidebar = doc.querySelector('.ibexa-list-filters__sidebar'); const toggleBtn = sidebar.querySelector('.ibexa-list-filters__expand-btn'); const toggleCollapseIcon = toggleBtn.querySelector('.ibexa-list-filters__collapse-icon'); const toggleExpandIcon = toggleBtn.querySelector('.ibexa-list-filters__expand-icon'); const toggleSidebar = () => { const isExpanded = toggleBtn.getAttribute('aria-expanded') === 'true'; - - sidebar.classList.toggle('ibexa-notification-list__filters-wrapper--collapsed', !isExpanded); + + sidebar.classList.toggle('ibexa-list-filters__sidebar--collapsed', isExpanded); toggleBtn.setAttribute('aria-expanded', (!isExpanded).toString()); - toggleExpandIcon.toggleAttribute('hidden', isExpanded); - toggleCollapseIcon.toggleAttribute('hidden', !isExpanded); + toggleExpandIcon.toggleAttribute('hidden', !isExpanded); + toggleCollapseIcon.toggleAttribute('hidden', isExpanded); }; toggleBtn.addEventListener('click', toggleSidebar, false); diff --git a/src/bundle/Resources/public/scss/_list-filters.scss b/src/bundle/Resources/public/scss/_list-filters.scss index 65ba02f56b..006614eb89 100644 --- a/src/bundle/Resources/public/scss/_list-filters.scss +++ b/src/bundle/Resources/public/scss/_list-filters.scss @@ -1,13 +1,13 @@ .ibexa-list-filters { $self: &; - + overflow: hidden; &__header { display: flex; align-items: center; justify-content: space-between; - padding: calculateRem(11px) calculateRem(16px); + padding: calculateRem(11px) calculateRem(12px); min-width: calculateRem(275px); } @@ -17,6 +17,18 @@ font-weight: 600; } + &__sidebar { + &--collapsed { + width: calculateRem(68px); + margin-right: 0; + + .ibexa-list-filters__header > *:not(.ibexa-list-filters__expand-btn), + .ibexa-list-filters__items { + display: none; + } + } + } + .accordion-item { background: transparent; diff --git a/src/bundle/Resources/public/scss/_notifications-modal.scss b/src/bundle/Resources/public/scss/_notifications-modal.scss index 7996b285cf..7dc74ad0bc 100644 --- a/src/bundle/Resources/public/scss/_notifications-modal.scss +++ b/src/bundle/Resources/public/scss/_notifications-modal.scss @@ -79,7 +79,7 @@ font-size: $ibexa-text-font-size-small; color: $ibexa-color-light-700; } - + &__notice-dot { width: calculateRem(8px); height: calculateRem(8px); @@ -218,10 +218,10 @@ width: calculateRem(6px); height: calculateRem(6px); } - &[data-is-read="true"] { + &[data-is-read='true'] { background: $ibexa-color-dark-300; } - &[data-is-read="false"] { + &[data-is-read='false'] { background: $ibexa-color-danger; } } diff --git a/src/bundle/Resources/public/scss/_notifications.scss b/src/bundle/Resources/public/scss/_notifications.scss index a7f047979b..6f0eb3c5a1 100644 --- a/src/bundle/Resources/public/scss/_notifications.scss +++ b/src/bundle/Resources/public/scss/_notifications.scss @@ -36,11 +36,11 @@ .ibexa-table-header { padding: calculateRem(8px) 0; - &__headline{ - font-size: $ibexa-text-font-size-extra-large; + &__headline { + font-size: $ibexa-text-font-size-extra-large; } } - + &__mark-all-read { display: flex; justify-content: flex-end; @@ -58,23 +58,13 @@ border: calculateRem(1px) solid $ibexa-color-light; background-color: $ibexa-color-white; transition: width 0.3s ease; - - &--collapsed { - width: calculateRem(64px); - margin-right: 0; - - .ibexa-list-filters__header > *:not(.ibexa-list-filters__expand-btn), - .ibexa-list-filters__items { - display: none; - } - } } &__table-btns { font-size: $ibexa-text-font-size-medium; } - + &__hidden-btn { display: none; } -} \ No newline at end of file +} diff --git a/src/bundle/Resources/views/themes/admin/account/notifications/list_all.html.twig b/src/bundle/Resources/views/themes/admin/account/notifications/list_all.html.twig index d96cd9b808..91239ae32c 100644 --- a/src/bundle/Resources/views/themes/admin/account/notifications/list_all.html.twig +++ b/src/bundle/Resources/views/themes/admin/account/notifications/list_all.html.twig @@ -92,7 +92,7 @@
{% endif %} -
+
{% include '@ibexadesign/account/notifications/filters.html.twig' %}
From 8b0e7d8f6affc632634f574aac73ed650a448641 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Bia=C5=82czak?= Date: Mon, 5 May 2025 08:54:21 +0200 Subject: [PATCH 18/94] IBX-9060: Moved submit button definition back to SelectionType --- src/bundle/Controller/NotificationController.php | 3 --- .../Form/Type/Notification/NotificationSelectionType.php | 6 ++++++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/bundle/Controller/NotificationController.php b/src/bundle/Controller/NotificationController.php index 197dc0327c..657d3343ce 100644 --- a/src/bundle/Controller/NotificationController.php +++ b/src/bundle/Controller/NotificationController.php @@ -22,7 +22,6 @@ use Ibexa\Core\Notification\Renderer\Registry; use InvalidArgumentException; use Pagerfanta\Pagerfanta; -use Symfony\Component\Form\Extension\Core\Type\SubmitType; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; @@ -115,9 +114,7 @@ public function renderNotificationsPageAction(Request $request, int $page): Resp } $formData = $this->createNotificationSelectionData($pagerfanta); - $deleteForm = $this->formFactory->deleteNotification($formData); - $deleteForm->add('remove', SubmitType::class); $template = $request->attributes->get('template', '@ibexadesign/account/notifications/list.html.twig'); diff --git a/src/lib/Form/Type/Notification/NotificationSelectionType.php b/src/lib/Form/Type/Notification/NotificationSelectionType.php index 277a4d04ee..ad50787444 100644 --- a/src/lib/Form/Type/Notification/NotificationSelectionType.php +++ b/src/lib/Form/Type/Notification/NotificationSelectionType.php @@ -12,6 +12,7 @@ use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\Extension\Core\Type\CheckboxType; use Symfony\Component\Form\Extension\Core\Type\CollectionType; +use Symfony\Component\Form\Extension\Core\Type\SubmitType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolver; @@ -30,6 +31,11 @@ public function buildForm(FormBuilderInterface $builder, array $options): void 'label' => false, ] ); + + $builder->add( + 'remove', + SubmitType::class + ); } public function configureOptions(OptionsResolver $resolver): void From 38f9411c90d145603111a654d5ae8c8c75efd7f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Bia=C5=82czak?= Date: Mon, 5 May 2025 12:00:06 +0200 Subject: [PATCH 19/94] IBX-9060: Allowed custom submit button name in NotificationSelectionType form --- src/lib/Form/Factory/FormFactory.php | 10 ++++++++-- .../Type/Notification/NotificationSelectionType.php | 3 ++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/lib/Form/Factory/FormFactory.php b/src/lib/Form/Factory/FormFactory.php index d389505ce9..a28932a475 100644 --- a/src/lib/Form/Factory/FormFactory.php +++ b/src/lib/Form/Factory/FormFactory.php @@ -1074,11 +1074,17 @@ public function removeContentDraft( */ public function deleteNotification( NotificationSelectionData $data = null, - ?string $name = null + ?string $name = null, + string $submitName = 'remove' ): FormInterface { $name = $name ?: StringUtil::fqcnToBlockPrefix(NotificationSelectionType::class); - return $this->formFactory->createNamed($name, NotificationSelectionType::class, $data); + return $this->formFactory->createNamed( + $name, + NotificationSelectionType::class, + $data, + ['submit_name' => $submitName] + ); } /** diff --git a/src/lib/Form/Type/Notification/NotificationSelectionType.php b/src/lib/Form/Type/Notification/NotificationSelectionType.php index ad50787444..4ae0ee8faa 100644 --- a/src/lib/Form/Type/Notification/NotificationSelectionType.php +++ b/src/lib/Form/Type/Notification/NotificationSelectionType.php @@ -33,7 +33,7 @@ public function buildForm(FormBuilderInterface $builder, array $options): void ); $builder->add( - 'remove', + $options['submit_name'], SubmitType::class ); } @@ -42,6 +42,7 @@ public function configureOptions(OptionsResolver $resolver): void { $resolver->setDefaults([ 'data_class' => NotificationSelectionData::class, + 'submit_name' => 'submit', ]); } } From 3e5b9ab7606a567f157b5a0bb9e852f5ff48abfb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Bia=C5=82czak?= Date: Thu, 8 May 2025 10:19:55 +0200 Subject: [PATCH 20/94] IBX-9060: Refactored notification query building --- .../Controller/NotificationController.php | 36 +++++++++++-------- .../Pagerfanta/NotificationAdapter.php | 5 +-- 2 files changed, 25 insertions(+), 16 deletions(-) diff --git a/src/bundle/Controller/NotificationController.php b/src/bundle/Controller/NotificationController.php index 657d3343ce..3b18f45b80 100644 --- a/src/bundle/Controller/NotificationController.php +++ b/src/bundle/Controller/NotificationController.php @@ -18,6 +18,7 @@ use Ibexa\Bundle\AdminUi\Form\Type\SearchType; use Ibexa\Contracts\AdminUi\Controller\Controller; use Ibexa\Contracts\Core\Repository\NotificationService; +use Ibexa\Contracts\Core\Repository\Values\Notification\Query\Criterion\NotificationQuery; use Ibexa\Contracts\Core\SiteAccess\ConfigResolverInterface; use Ibexa\Core\Notification\Renderer\Registry; use InvalidArgumentException; @@ -95,15 +96,18 @@ public function renderNotificationsPageAction(Request $request, int $page): Resp ]); $searchForm->handleRequest($request); - $query = []; + $query = new NotificationQuery(); if ($searchForm->isSubmitted() && $searchForm->isValid()) { $query = $this->buildQuery($searchForm->getData()); } + $query->offset = ($page - 1) * $this->configResolver->getParameter('pagination.notification_limit'); + $query->limit = $this->configResolver->getParameter('pagination.notification_limit'); + $pagerfanta = new Pagerfanta( new NotificationAdapter($this->notificationService, $query) ); - $pagerfanta->setMaxPerPage($this->configResolver->getParameter('pagination.notification_limit')); + $pagerfanta->setMaxPerPage($query->limit); $pagerfanta->setCurrentPage(min($page, $pagerfanta->getNbPages())); $notifications = []; @@ -127,35 +131,39 @@ public function renderNotificationsPageAction(Request $request, int $page): Resp ]); } - private function buildQuery(SearchQueryData $data): array + private function buildQuery(SearchQueryData $data): NotificationQuery { - $query = []; + $criteria = []; if ($data->getType()) { - $query['type'] = $data->getType(); + $criteria[] = new Criterion\Type($data->getType()); } if (!empty($data->getStatuses())) { - $query['status'] = []; + $statuses = []; if (in_array('read', $data->getStatuses(), true)) { - $query['status'][] = 'read'; + $statuses[] = 'read'; } if (in_array('unread', $data->getStatuses(), true)) { - $query['status'][] = 'unread'; + $statuses[] = 'unread'; + } + + if (!empty($statuses)) { + $criteria[] = new Criterion\Status($statuses); } } $range = $data->getCreatedRange(); if ($range !== null) { - if ($range->getMin() instanceof DateTimeInterface) { - $query['created_from'] = $range->getMin()->getTimestamp(); - } - if ($range->getMax() instanceof DateTimeInterface) { - $query['created_to'] = $range->getMax()->getTimestamp(); + $min = $range->getMin() instanceof DateTimeInterface ? $range->getMin() : null; + $max = $range->getMax() instanceof DateTimeInterface ? $range->getMax() : null; + + if ($min !== null || $max !== null) { + $criteria[] = new Criterion\DateCreated($min, $max); } } - return $query; + return new NotificationQuery($criteria); } private function createNotificationSelectionData(Pagerfanta $pagerfanta): NotificationSelectionData diff --git a/src/lib/Pagination/Pagerfanta/NotificationAdapter.php b/src/lib/Pagination/Pagerfanta/NotificationAdapter.php index 002226c925..f0faaeb496 100644 --- a/src/lib/Pagination/Pagerfanta/NotificationAdapter.php +++ b/src/lib/Pagination/Pagerfanta/NotificationAdapter.php @@ -9,6 +9,7 @@ use Ibexa\Contracts\Core\Repository\NotificationService; use Ibexa\Contracts\Core\Repository\Values\Notification\NotificationList; +use Ibexa\Contracts\Core\Repository\Values\Notification\Query\Criterion\NotificationQuery; use Pagerfanta\Adapter\AdapterInterface; /** @@ -19,13 +20,13 @@ class NotificationAdapter implements AdapterInterface { private NotificationService $notificationService; - private array $query; + private NotificationQuery $query; private int $nbResults; public function __construct( NotificationService $notificationService, - array $query = [] + NotificationQuery $query ) { $this->notificationService = $notificationService; $this->query = $query; From 554a121779032d0062869ada4f6c81a9f0caef02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Bia=C5=82czak?= Date: Wed, 26 Mar 2025 07:03:37 +0100 Subject: [PATCH 21/94] IBX-9060: Added delete action and delete multiple notifications --- src/bundle/Resources/config/routing.yaml | 8 ++++ .../Notification/NotificationRemoveData.php | 29 ++++++++++++ .../Notification/NotificationRemoveType.php | 47 +++++++++++++++++++ 3 files changed, 84 insertions(+) create mode 100644 src/lib/Form/Data/Notification/NotificationRemoveData.php create mode 100644 src/lib/Form/Type/Notification/NotificationRemoveType.php diff --git a/src/bundle/Resources/config/routing.yaml b/src/bundle/Resources/config/routing.yaml index 581016d658..eff0227904 100644 --- a/src/bundle/Resources/config/routing.yaml +++ b/src/bundle/Resources/config/routing.yaml @@ -971,6 +971,14 @@ ibexa.notifications.delete: requirements: notificationId: '\d+' +ibexa.notifications.delete: + path: /notification/delete/{notificationId} + defaults: + _controller: 'Ibexa\Bundle\AdminUi\Controller\NotificationController::deleteNotificationAction' + methods: [ GET ] + requirements: + notificationId: '\d+' + ibexa.asset.upload_image: path: /asset/image options: diff --git a/src/lib/Form/Data/Notification/NotificationRemoveData.php b/src/lib/Form/Data/Notification/NotificationRemoveData.php new file mode 100644 index 0000000000..8b6338ba37 --- /dev/null +++ b/src/lib/Form/Data/Notification/NotificationRemoveData.php @@ -0,0 +1,29 @@ +notifications = $notifications; + } + + public function getNotifications(): array + { + return $this->notifications; + } + + public function setNotifications(array $notifications): void + { + $this->notifications = $notifications; + } +} diff --git a/src/lib/Form/Type/Notification/NotificationRemoveType.php b/src/lib/Form/Type/Notification/NotificationRemoveType.php new file mode 100644 index 0000000000..23bc79998e --- /dev/null +++ b/src/lib/Form/Type/Notification/NotificationRemoveType.php @@ -0,0 +1,47 @@ +add( + 'notifications', + CollectionType::class, + [ + 'entry_type' => CheckboxType::class, + 'required' => false, + 'allow_add' => true, + 'entry_options' => ['label' => false], + 'label' => false, + ] + ); + + $builder->add( + 'remove', + SubmitType::class + ); + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'data_class' => NotificationRemoveData::class, + ]); + } +} From de04bebfd3ff9b5e08ef3e0b8007a14f6fa93d41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Bia=C5=82czak?= Date: Thu, 3 Apr 2025 13:46:03 +0200 Subject: [PATCH 22/94] IBX-9060: Added search form and query support to notifications page --- src/bundle/Controller/NotificationController.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/bundle/Controller/NotificationController.php b/src/bundle/Controller/NotificationController.php index 3b18f45b80..43f190ae58 100644 --- a/src/bundle/Controller/NotificationController.php +++ b/src/bundle/Controller/NotificationController.php @@ -23,6 +23,7 @@ use Ibexa\Core\Notification\Renderer\Registry; use InvalidArgumentException; use Pagerfanta\Pagerfanta; +use Symfony\Component\Form\FormInterface; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; From 31c82657c77bb2c8eb43536404387828206d7869 Mon Sep 17 00:00:00 2001 From: Dariusz Szut Date: Mon, 7 Apr 2025 14:04:44 +0200 Subject: [PATCH 23/94] IBX-9060: Delete, mark as notifications --- src/bundle/Resources/config/routing.yaml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/bundle/Resources/config/routing.yaml b/src/bundle/Resources/config/routing.yaml index eff0227904..18a6fc6096 100644 --- a/src/bundle/Resources/config/routing.yaml +++ b/src/bundle/Resources/config/routing.yaml @@ -973,9 +973,11 @@ ibexa.notifications.delete: ibexa.notifications.delete: path: /notification/delete/{notificationId} + options: + expose: true defaults: _controller: 'Ibexa\Bundle\AdminUi\Controller\NotificationController::deleteNotificationAction' - methods: [ GET ] + methods: [GET] requirements: notificationId: '\d+' From dfb616f93545bd01c25d0e6077a822c3adbc5eb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Bia=C5=82czak?= Date: Tue, 29 Apr 2025 09:29:45 +0200 Subject: [PATCH 24/94] IBX-9060: Added bulk mark-as-read support and unify selection form handling --- .../Controller/NotificationController.php | 1 - .../Notification/NotificationRemoveData.php | 29 ------------ .../Notification/NotificationRemoveType.php | 47 ------------------- 3 files changed, 77 deletions(-) delete mode 100644 src/lib/Form/Data/Notification/NotificationRemoveData.php delete mode 100644 src/lib/Form/Type/Notification/NotificationRemoveType.php diff --git a/src/bundle/Controller/NotificationController.php b/src/bundle/Controller/NotificationController.php index 43f190ae58..3b18f45b80 100644 --- a/src/bundle/Controller/NotificationController.php +++ b/src/bundle/Controller/NotificationController.php @@ -23,7 +23,6 @@ use Ibexa\Core\Notification\Renderer\Registry; use InvalidArgumentException; use Pagerfanta\Pagerfanta; -use Symfony\Component\Form\FormInterface; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; diff --git a/src/lib/Form/Data/Notification/NotificationRemoveData.php b/src/lib/Form/Data/Notification/NotificationRemoveData.php deleted file mode 100644 index 8b6338ba37..0000000000 --- a/src/lib/Form/Data/Notification/NotificationRemoveData.php +++ /dev/null @@ -1,29 +0,0 @@ -notifications = $notifications; - } - - public function getNotifications(): array - { - return $this->notifications; - } - - public function setNotifications(array $notifications): void - { - $this->notifications = $notifications; - } -} diff --git a/src/lib/Form/Type/Notification/NotificationRemoveType.php b/src/lib/Form/Type/Notification/NotificationRemoveType.php deleted file mode 100644 index 23bc79998e..0000000000 --- a/src/lib/Form/Type/Notification/NotificationRemoveType.php +++ /dev/null @@ -1,47 +0,0 @@ -add( - 'notifications', - CollectionType::class, - [ - 'entry_type' => CheckboxType::class, - 'required' => false, - 'allow_add' => true, - 'entry_options' => ['label' => false], - 'label' => false, - ] - ); - - $builder->add( - 'remove', - SubmitType::class - ); - } - - public function configureOptions(OptionsResolver $resolver): void - { - $resolver->setDefaults([ - 'data_class' => NotificationRemoveData::class, - ]); - } -} From 566e5bee3383becf8b44fa45a9a271f5bde0c7af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Bia=C5=82czak?= Date: Tue, 29 Apr 2025 13:08:24 +0200 Subject: [PATCH 25/94] IBX-9060: Added submit buttons to notification selection forms --- src/bundle/Controller/NotificationController.php | 1 + src/lib/Form/Type/Notification/NotificationSelectionType.php | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bundle/Controller/NotificationController.php b/src/bundle/Controller/NotificationController.php index 3b18f45b80..f882992e26 100644 --- a/src/bundle/Controller/NotificationController.php +++ b/src/bundle/Controller/NotificationController.php @@ -23,6 +23,7 @@ use Ibexa\Core\Notification\Renderer\Registry; use InvalidArgumentException; use Pagerfanta\Pagerfanta; +use Symfony\Component\Form\Extension\Core\Type\SubmitType; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; diff --git a/src/lib/Form/Type/Notification/NotificationSelectionType.php b/src/lib/Form/Type/Notification/NotificationSelectionType.php index 4ae0ee8faa..89124bf49b 100644 --- a/src/lib/Form/Type/Notification/NotificationSelectionType.php +++ b/src/lib/Form/Type/Notification/NotificationSelectionType.php @@ -12,7 +12,6 @@ use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\Extension\Core\Type\CheckboxType; use Symfony\Component\Form\Extension\Core\Type\CollectionType; -use Symfony\Component\Form\Extension\Core\Type\SubmitType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolver; From cd72fa9a20f22aa84b004bc5321d12791d762a12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Bia=C5=82czak?= Date: Mon, 5 May 2025 08:54:21 +0200 Subject: [PATCH 26/94] IBX-9060: Moved submit button definition back to SelectionType --- src/bundle/Controller/NotificationController.php | 1 - src/lib/Form/Type/Notification/NotificationSelectionType.php | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bundle/Controller/NotificationController.php b/src/bundle/Controller/NotificationController.php index f882992e26..3b18f45b80 100644 --- a/src/bundle/Controller/NotificationController.php +++ b/src/bundle/Controller/NotificationController.php @@ -23,7 +23,6 @@ use Ibexa\Core\Notification\Renderer\Registry; use InvalidArgumentException; use Pagerfanta\Pagerfanta; -use Symfony\Component\Form\Extension\Core\Type\SubmitType; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; diff --git a/src/lib/Form/Type/Notification/NotificationSelectionType.php b/src/lib/Form/Type/Notification/NotificationSelectionType.php index 89124bf49b..4ae0ee8faa 100644 --- a/src/lib/Form/Type/Notification/NotificationSelectionType.php +++ b/src/lib/Form/Type/Notification/NotificationSelectionType.php @@ -12,6 +12,7 @@ use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\Extension\Core\Type\CheckboxType; use Symfony\Component\Form\Extension\Core\Type\CollectionType; +use Symfony\Component\Form\Extension\Core\Type\SubmitType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolver; From 5ba887303f5377924a1545e86f4087e7a96ac25c Mon Sep 17 00:00:00 2001 From: Aleksandra Bozek Date: Mon, 5 May 2025 12:39:08 +0200 Subject: [PATCH 27/94] Mark multiple as read & notification list view expansion --- src/bundle/Resources/config/routing.yaml | 4 +- .../js/scripts/admin.notifications.list.js | 67 +++++++++---------- .../js/scripts/admin.notifications.modal.js | 2 +- .../public/scss/_notifications-modal.scss | 12 ++-- .../Resources/public/scss/_notifications.scss | 13 +++- .../account/notifications/list_all.html.twig | 12 ++-- .../notifications/list_item_all.html.twig | 2 +- .../notifications/side_panel.html.twig | 4 +- 8 files changed, 64 insertions(+), 52 deletions(-) diff --git a/src/bundle/Resources/config/routing.yaml b/src/bundle/Resources/config/routing.yaml index 18a6fc6096..82f98fc125 100644 --- a/src/bundle/Resources/config/routing.yaml +++ b/src/bundle/Resources/config/routing.yaml @@ -939,9 +939,11 @@ ibexa.notifications.mark_as_read: ibexa.notifications.mark_multiple_as_read: path: /notifications/read + options: + expose: true defaults: _controller: 'Ibexa\Bundle\AdminUi\Controller\NotificationController::markNotificationsAsReadAction' - methods: [ POST ] + methods: [POST] ibexa.notifications.mark_all_as_read: path: /notifications/read-all diff --git a/src/bundle/Resources/public/js/scripts/admin.notifications.list.js b/src/bundle/Resources/public/js/scripts/admin.notifications.list.js index da8e7ba03f..1881e19d09 100644 --- a/src/bundle/Resources/public/js/scripts/admin.notifications.list.js +++ b/src/bundle/Resources/public/js/scripts/admin.notifications.list.js @@ -29,52 +29,41 @@ }); }; - //TODO: const markSelectedAsRead = () => { - const checkedElements = doc.querySelectorAll('.ibexa-notification-list__mark-row-checkbox:checked'); - const notificationIds = [...checkedElements].map((element) => element.dataset.notificationId); - const bulkOperations = getBulkOperations(notificationIds); - const request = new Request('/api/ibexa/v2/bulk', { + const selectedNotifications = [...checkboxes] + .filter((checkbox) => checkbox.checked) + .map((checkbox) => checkbox.dataset.notificationId); + + const markAsReadLink = Routing.generate('ibexa.notifications.mark_multiple_as_read'); + const request = new Request(markAsReadLink, { method: 'POST', headers: { - Accept: 'application/vnd.ibexa.api.BulkOperationResponse+json', - 'Content-Type': 'application/vnd.ibexa.api.BulkOperation+json', + 'Content-Type': 'application/json', }, + mode: 'same-origin', + credentials: 'same-origin', body: JSON.stringify({ - bulkOperations: { - operations: bulkOperations, - }, + ids: selectedNotifications, }), - mode: 'same-origin', - credentials: 'include', }); - const errorMessage = Translator.trans( - /*@Desc("Cannot mark selected notifications as read")*/ 'notifications.modal.message.error.mark_selected_as_read', - {}, - 'ibexa_notifications', - ); fetch(request) .then(getJsonFromResponse) - .catch(() => ibexa.helpers.notification.showErrorNotification(errorMessage)); + .then((response) => { + if (response.status === 'success') { + global.location.reload(); + } + }) + .catch(() => { + const message = Translator.trans( + /* @Desc("Cannot mark notifications as read") */ + 'notifications.modal.message.error.mark_as_read', + {}, + 'ibexa_notifications', + ); + showErrorNotification(message); + }); }; - const getBulkOperations = (notificationIds) => - notificationIds.reduce((total, notificationId) => { - const markAsReadLink = Routing.generate('ibexa.notifications.mark_as_read', { notificationId }); - - total[markAsReadLink] = { - uri: markAsReadLink, - method: 'GET', - mode: 'same-origin', - headers: { - Accept: 'application/vnd.ibexa.api.ContentType+json', - 'X-Requested-With': 'XMLHttpRequest', - }, - credentials: 'same-origin', - }; - - return total; - }, {}); const handleNotificationClick = (notification, isToggle = false) => { const notificationRow = notification.closest('.ibexa-table__row'); @@ -169,11 +158,15 @@ ); }; const handleCheckboxChange = (checkbox) => { - const checkboxFormId = checkbox.dataset?.formRemoveId; - const formRemoveCheckbox = doc.getElementById(checkboxFormId); + const checkboxFormId = checkbox.dataset?.formCheckboxId; + const formRemoveCheckbox = doc.querySelector( + `[data-toggle-button-id="#confirm-notification_selection_remove"] input#${checkboxFormId}`, + ); + if (formRemoveCheckbox) { formRemoveCheckbox.checked = checkbox.checked; } + toggleActionButtonState(); }; diff --git a/src/bundle/Resources/public/js/scripts/admin.notifications.modal.js b/src/bundle/Resources/public/js/scripts/admin.notifications.modal.js index 7fd903826f..795ec5fa2d 100644 --- a/src/bundle/Resources/public/js/scripts/admin.notifications.modal.js +++ b/src/bundle/Resources/public/js/scripts/admin.notifications.modal.js @@ -89,7 +89,7 @@ }; const updateModalTitleTotalInfo = (notificationsCount) => { const modalTitle = panel.querySelector(SELECTOR_MODAL_TITLE); - const modalFooter = panel.querySelector('.ibexa-notifications__view-all-btn--count'); + const modalFooter = panel.querySelector('.ibexa-notifications-modal__view-all-btn--count'); modalFooter.textContent = ` (${notificationsCount})`; modalTitle.dataset.notificationsTotal = `(${notificationsCount})`; diff --git a/src/bundle/Resources/public/scss/_notifications-modal.scss b/src/bundle/Resources/public/scss/_notifications-modal.scss index 7dc74ad0bc..23d25fb26c 100644 --- a/src/bundle/Resources/public/scss/_notifications-modal.scss +++ b/src/bundle/Resources/public/scss/_notifications-modal.scss @@ -55,16 +55,12 @@ &__item { position: relative; border: 1px solid $ibexa-color-light; - border-bottom: none; + border-top: none; &--wrapper { display: flex; } - &:first-of-type { - border-top: none; - } - .description__text { font-size: $ibexa-text-font-size-medium; @@ -93,6 +89,12 @@ color: $ibexa-color-white; } + &__view-all-btn { + &--count { + padding-left: calculateRem(2px); + } + } + &__item--read { .ibexa-notifications-modal__notice-dot, .ibexa-notification-view-all__notice-dot { diff --git a/src/bundle/Resources/public/scss/_notifications.scss b/src/bundle/Resources/public/scss/_notifications.scss index 6f0eb3c5a1..df052d545f 100644 --- a/src/bundle/Resources/public/scss/_notifications.scss +++ b/src/bundle/Resources/public/scss/_notifications.scss @@ -8,9 +8,17 @@ .ibexa-notification-list { display: flex; - align-items: strech; + align-items: stretch; margin-bottom: calculateRem(48px); + &__container { + .container.container { + @media (min-width: 1200px) { + max-width: calculateRem(2000px); + } + } + } + .ibexa-table__header-cell { padding: calculateRem(16px) calculateRem(8px); &:first-child { @@ -20,6 +28,7 @@ padding-right: calculateRem(16px); } } + .ibexa-table__cell { padding: calculateRem(8px); &:first-child { @@ -29,6 +38,7 @@ padding-right: calculateRem(16px); } } + .ibexa-container { padding: 0 calculateRem(16px) calculateRem(16px); margin-bottom: 0; @@ -47,6 +57,7 @@ } &__data-grid-wrapper { + width: 100%; border-radius: $ibexa-border-radius 0 0 $ibexa-border-radius; border: calculateRem(1px) solid $ibexa-color-light; border-right: none; diff --git a/src/bundle/Resources/views/themes/admin/account/notifications/list_all.html.twig b/src/bundle/Resources/views/themes/admin/account/notifications/list_all.html.twig index 91239ae32c..fd55252cc7 100644 --- a/src/bundle/Resources/views/themes/admin/account/notifications/list_all.html.twig +++ b/src/bundle/Resources/views/themes/admin/account/notifications/list_all.html.twig @@ -7,6 +7,8 @@ {% form_theme form_remove '@ibexadesign/ui/form_fields.html.twig' %} {% import _self as macros %} +{% block main_container_class %}{{ parent() }} ibexa-notification-list__container {% endblock %} + {% block title %}{{ 'ibexa_notifications'|trans|desc('Notifications') }}{% endblock %} {% block header %} @@ -43,10 +45,10 @@ }) }} {{ form_end(form_remove) }} {{ form_start(search_form, { - attr: { class: 'ibexa-al-list-search-form' } + attr: { class: 'ibexa-list-search-form' } }) }}
-
+
{% embed '@ibexadesign/ui/component/table/table.html.twig' with { headline: custom_results_headline ?? results_headline(pager.getNbResults()), head_cols: [ @@ -97,15 +99,17 @@
{{ form_end(search_form) }} + {{dump()}} {% endblock %} {% block javascripts %} {{ encore_entry_script_tags('ibexa-admin-notifications-list-js', null, 'ibexa') }} {% endblock %} -{% macro table_header_tools(form) %} +{% macro table_header_tools(form,) %} {% set modal_data_target = 'modal-' ~ form.remove.vars.id %}
{{ form_end(search_form) }} - {{dump()}} {% endblock %} {% block javascripts %} diff --git a/src/bundle/Resources/views/themes/admin/account/notifications/side_panel.html.twig b/src/bundle/Resources/views/themes/admin/account/notifications/side_panel.html.twig index 121bf80df2..48dda19da0 100644 --- a/src/bundle/Resources/views/themes/admin/account/notifications/side_panel.html.twig +++ b/src/bundle/Resources/views/themes/admin/account/notifications/side_panel.html.twig @@ -18,26 +18,26 @@ {% endblock %} {% block content %} -
-
- - - +
+
+ + + +
+
+ {{ render(controller('Ibexa\\Bundle\\AdminUi\\Controller\\NotificationController::renderNotificationsPageAction', { + 'page': 1, + })) }} +
-
- {{ render(controller('Ibexa\\Bundle\\AdminUi\\Controller\\NotificationController::renderNotificationsPageAction', { - 'page': 1, - })) }} -
-
{% endblock %} {% block footer %} - + {% endblock %} -{% endembed %} \ No newline at end of file +{% endembed %} diff --git a/src/bundle/Resources/views/themes/admin/ui/component/modal/modal.html.twig b/src/bundle/Resources/views/themes/admin/ui/component/modal/modal.html.twig index 5b669cb280..2a81adcf57 100644 --- a/src/bundle/Resources/views/themes/admin/ui/component/modal/modal.html.twig +++ b/src/bundle/Resources/views/themes/admin/ui/component/modal/modal.html.twig @@ -23,9 +23,7 @@ }) %} {% if id is defined %} - {% set attr = attr|default({})|merge({ - id, - }) %} + {% set attr = attr|default({})|merge({ id }) %} {% endif %} {% if has_static_backdrop|default(false) %} diff --git a/src/bundle/Resources/views/themes/admin/ui/component/side_panel/side_panel.html.twig b/src/bundle/Resources/views/themes/admin/ui/component/side_panel/side_panel.html.twig index ca594d3dc2..e6386049b0 100644 --- a/src/bundle/Resources/views/themes/admin/ui/component/side_panel/side_panel.html.twig +++ b/src/bundle/Resources/views/themes/admin/ui/component/side_panel/side_panel.html.twig @@ -12,38 +12,36 @@ }) %} {% if id is defined %} - {% set attr = attr|default({})|merge({ - id, - }) %} + {% set attr = attr|default({})|merge({ id }) %} {% endif %}
{% block panel %} -
- {% block header %} - -

{{ title }}

- {% endblock %} - {% block content %}{% endblock %} - - {% endblock %}
From 7a5dd6944e1f826da15b5a82328385dd07c1e98a Mon Sep 17 00:00:00 2001 From: barbaragr Date: Tue, 6 May 2025 15:38:53 +0200 Subject: [PATCH 29/94] [Behat] Fix for quickReview.feature --- src/lib/Behat/Component/UserNotificationPopup.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/Behat/Component/UserNotificationPopup.php b/src/lib/Behat/Component/UserNotificationPopup.php index 91953b5269..c33d5e7be4 100644 --- a/src/lib/Behat/Component/UserNotificationPopup.php +++ b/src/lib/Behat/Component/UserNotificationPopup.php @@ -48,7 +48,7 @@ public function verifyIsLoaded(): void protected function specifyLocators(): array { return [ - new VisibleCSSLocator('notificationsPopupTitle', '#view-notifications .modal-title'), + new VisibleCSSLocator('notificationsPopupTitle', '.ibexa-side-panel__header'), new VisibleCSSLocator('notificationItem', '.ibexa-notifications-modal__item'), new VisibleCSSLocator('notificationType', '.ibexa-notifications-modal__type'), new VisibleCSSLocator('notificationDescription', '.ibexa-notifications-modal__description'), From 565fc08399fe0a0bde70631bcfab9e60d4ac3ea3 Mon Sep 17 00:00:00 2001 From: Aleksandra Bozek Date: Wed, 7 May 2025 10:45:46 +0200 Subject: [PATCH 30/94] Fixed typo --- .../public/js/scripts/admin.multilevel.popup.menu.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/bundle/Resources/public/js/scripts/admin.multilevel.popup.menu.js b/src/bundle/Resources/public/js/scripts/admin.multilevel.popup.menu.js index ea93681155..998cad485a 100644 --- a/src/bundle/Resources/public/js/scripts/admin.multilevel.popup.menu.js +++ b/src/bundle/Resources/public/js/scripts/admin.multilevel.popup.menu.js @@ -1,5 +1,5 @@ (function (global, doc, ibexa) { - const initMutlilevelPopupMenus = (container) => { + const initMultilevelPopupMenus = (container) => { const multilevelPopupMenusContainers = container.querySelectorAll( '.ibexa-multilevel-popup-menu:not(.ibexa-multilevel-popup-menu--custom-init)', ); @@ -15,14 +15,14 @@ }); }; - initMutlilevelPopupMenus(doc); + initMultilevelPopupMenus(doc); doc.body.addEventListener( 'ibexa-multilevel-popup-menu:init', (event) => { const { container } = event.detail; - initMutlilevelPopupMenus(container); + initMultilevelPopupMenus(container); }, false, ); From b6e562f4ce9f7087aeee0c046de37215a5ccd124 Mon Sep 17 00:00:00 2001 From: tomaszszopinski Date: Wed, 7 May 2025 12:06:43 +0200 Subject: [PATCH 31/94] [Behat] Adjusted notification assertion --- src/lib/Behat/Component/UserNotificationPopup.php | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/lib/Behat/Component/UserNotificationPopup.php b/src/lib/Behat/Component/UserNotificationPopup.php index c33d5e7be4..7a585c6017 100644 --- a/src/lib/Behat/Component/UserNotificationPopup.php +++ b/src/lib/Behat/Component/UserNotificationPopup.php @@ -24,7 +24,11 @@ public function clickNotification(string $expectedType, string $expectedDescript continue; } - $description = $notification->find($this->getLocator('notificationDescription'))->getText(); + $notificationTitle = $this->getHTMLPage()->setTimeout(3)->find($this->getLocator('notificationDescriptionTitle'))->getText(); + $notificationText = $this->getHTMLPage()->setTimeout(3)->find($this->getLocator('notificationDescriptionText'))->getText(); + + $description = sprintf('%s %s', $notificationTitle, $notificationText); + if ($description !== $expectedDescription) { continue; } @@ -50,8 +54,9 @@ protected function specifyLocators(): array return [ new VisibleCSSLocator('notificationsPopupTitle', '.ibexa-side-panel__header'), new VisibleCSSLocator('notificationItem', '.ibexa-notifications-modal__item'), - new VisibleCSSLocator('notificationType', '.ibexa-notifications-modal__type'), - new VisibleCSSLocator('notificationDescription', '.ibexa-notifications-modal__description'), + new VisibleCSSLocator('notificationType', '.ibexa-notifications-modal__type-content > strong > span'), + new VisibleCSSLocator('notificationDescriptionTitle', '.ibexa-notifications-modal__type-content > p.description__title'), + new VisibleCSSLocator('notificationDescriptionText', '.ibexa-notifications-modal__type-content > p.description__text'), ]; } } From 19fee17482f672463ca37d21e41bed650598650a Mon Sep 17 00:00:00 2001 From: Aleksandra Bozek Date: Thu, 8 May 2025 09:10:05 +0100 Subject: [PATCH 32/94] Style fixes --- src/bundle/Resources/public/scss/_notifications-modal.scss | 6 +++--- .../admin/account/notifications/list_item_deleted.html.twig | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/bundle/Resources/public/scss/_notifications-modal.scss b/src/bundle/Resources/public/scss/_notifications-modal.scss index 557cce25ca..1fe4cc001d 100644 --- a/src/bundle/Resources/public/scss/_notifications-modal.scss +++ b/src/bundle/Resources/public/scss/_notifications-modal.scss @@ -83,8 +83,8 @@ background: $ibexa-color-danger; opacity: 1; position: absolute; - left: calculateRem(10px); - top: calculateRem(16px); + left: calculateRem(16px); + top: calculateRem(22px); color: $ibexa-color-white; } @@ -105,7 +105,7 @@ .description__title { margin-bottom: 0; - &__item { + &--item { display: inline-block; vertical-align: top; max-width: 100%; diff --git a/src/bundle/Resources/views/themes/admin/account/notifications/list_item_deleted.html.twig b/src/bundle/Resources/views/themes/admin/account/notifications/list_item_deleted.html.twig index 36bfb33da8..46db86596b 100644 --- a/src/bundle/Resources/views/themes/admin/account/notifications/list_item_deleted.html.twig +++ b/src/bundle/Resources/views/themes/admin/account/notifications/list_item_deleted.html.twig @@ -22,7 +22,7 @@ {% block content %}

{{ 'notification.title'|trans|desc('Title:') }} - {{ title }} + {{ title }}

{{ 'notification.no_longer_available'|trans({}, 'ibexa_notifications')|desc('The Content item is no longer available')}}

{% endblock %} From b690b3ad784ba12e9e46036a6539dc6e042b5019 Mon Sep 17 00:00:00 2001 From: Aleksandra Bozek Date: Thu, 8 May 2025 13:05:16 +0100 Subject: [PATCH 33/94] Build fix --- src/bundle/Resources/config/routing.yaml | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/bundle/Resources/config/routing.yaml b/src/bundle/Resources/config/routing.yaml index 82f98fc125..a61c5d242f 100644 --- a/src/bundle/Resources/config/routing.yaml +++ b/src/bundle/Resources/config/routing.yaml @@ -973,16 +973,6 @@ ibexa.notifications.delete: requirements: notificationId: '\d+' -ibexa.notifications.delete: - path: /notification/delete/{notificationId} - options: - expose: true - defaults: - _controller: 'Ibexa\Bundle\AdminUi\Controller\NotificationController::deleteNotificationAction' - methods: [GET] - requirements: - notificationId: '\d+' - ibexa.asset.upload_image: path: /asset/image options: From 937cfd6fce55833b325292f80bfcf8473dc87ab5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Bia=C5=82czak?= Date: Fri, 9 May 2025 08:37:29 +0200 Subject: [PATCH 34/94] IBX-9060: Fixed loadNotifications --- src/bundle/Controller/NotificationController.php | 5 ++++- src/lib/Pagination/Pagerfanta/NotificationAdapter.php | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/bundle/Controller/NotificationController.php b/src/bundle/Controller/NotificationController.php index 3b18f45b80..2288fb6fd1 100644 --- a/src/bundle/Controller/NotificationController.php +++ b/src/bundle/Controller/NotificationController.php @@ -18,6 +18,7 @@ use Ibexa\Bundle\AdminUi\Form\Type\SearchType; use Ibexa\Contracts\AdminUi\Controller\Controller; use Ibexa\Contracts\Core\Repository\NotificationService; +use Ibexa\Contracts\Core\Repository\Values\Notification\Query\Criterion; use Ibexa\Contracts\Core\Repository\Values\Notification\Query\Criterion\NotificationQuery; use Ibexa\Contracts\Core\SiteAccess\ConfigResolverInterface; use Ibexa\Core\Notification\Renderer\Registry; @@ -81,7 +82,9 @@ public function getNotificationsAction(Request $request, int $offset, int $limit public function renderNotificationsPageAction(Request $request, int $page): Response { - $allNotifications = $this->notificationService->loadNotifications(0, PHP_INT_MAX)->items; + $allNotifications = $this->notificationService->loadNotifications( + new NotificationQuery([], 0, PHP_INT_MAX) + )->items; $notificationTypes = array_unique( array_map( diff --git a/src/lib/Pagination/Pagerfanta/NotificationAdapter.php b/src/lib/Pagination/Pagerfanta/NotificationAdapter.php index f0faaeb496..ab48926e97 100644 --- a/src/lib/Pagination/Pagerfanta/NotificationAdapter.php +++ b/src/lib/Pagination/Pagerfanta/NotificationAdapter.php @@ -52,7 +52,7 @@ public function getNbResults(): int */ public function getSlice($offset, $length): NotificationList { - $notifications = $this->notificationService->loadNotifications($offset, $length, $this->query); + $notifications = $this->notificationService->loadNotifications($this->query); $this->nbResults ??= $notifications->totalCount; From 814c3489f195461adace5665a83092b459790838 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Bia=C5=82czak?= Date: Fri, 9 May 2025 08:58:28 +0200 Subject: [PATCH 35/94] IBX-9060: Fixed POST method --- src/bundle/Form/Type/SearchType.php | 2 +- src/bundle/Resources/config/routing.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/bundle/Form/Type/SearchType.php b/src/bundle/Form/Type/SearchType.php index e73abc2423..9b7a00a838 100644 --- a/src/bundle/Form/Type/SearchType.php +++ b/src/bundle/Form/Type/SearchType.php @@ -45,7 +45,7 @@ public function buildForm(FormBuilderInterface $builder, array $options): void public function configureOptions(OptionsResolver $resolver): void { $resolver->setDefaults([ - 'method' => 'GET', + 'method' => 'POST', 'csrf_protection' => false, 'data_class' => SearchQueryData::class, 'notification_types' => [], diff --git a/src/bundle/Resources/config/routing.yaml b/src/bundle/Resources/config/routing.yaml index a61c5d242f..98df700bae 100644 --- a/src/bundle/Resources/config/routing.yaml +++ b/src/bundle/Resources/config/routing.yaml @@ -917,7 +917,7 @@ ibexa.notifications.render.all: defaults: _controller: 'Ibexa\Bundle\AdminUi\Controller\AllNotificationsController::renderAllNotificationsPageAction' page: 1 - methods: [GET] + methods: [ GET, POST ] requirements: page: '\d+' From 376a2e4a4531a7654ab6b02ff1d0357585cedfbf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Bia=C5=82czak?= Date: Fri, 9 May 2025 10:31:05 +0200 Subject: [PATCH 36/94] IBX-9060: Fixed loadNotifications --- src/bundle/Controller/NotificationController.php | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/bundle/Controller/NotificationController.php b/src/bundle/Controller/NotificationController.php index 2288fb6fd1..bce29cd1fa 100644 --- a/src/bundle/Controller/NotificationController.php +++ b/src/bundle/Controller/NotificationController.php @@ -64,7 +64,9 @@ public function getNotificationsAction(Request $request, int $offset, int $limit $response = new JsonResponse(); try { - $notificationList = $this->notificationService->loadNotifications($offset, $limit); + $notificationList = $this->notificationService->loadNotifications( + new NotificationQuery([], $offset, $limit) + ); $response->setData([ 'pending' => $this->notificationService->getPendingNotificationCount(), 'total' => $notificationList->totalCount, @@ -86,12 +88,7 @@ public function renderNotificationsPageAction(Request $request, int $page): Resp new NotificationQuery([], 0, PHP_INT_MAX) )->items; - $notificationTypes = array_unique( - array_map( - static fn($notification) => $notification->type, - $allNotifications - ) - ); + $notificationTypes = array_unique(array_column($allNotifications, 'type')); sort($notificationTypes); $searchForm = $this->createForm(SearchType::class, null, [ @@ -275,7 +272,9 @@ public function markAllNotificationsAsReadAction(Request $request): JsonResponse $response = new JsonResponse(); try { - $notifications = $this->notificationService->loadNotifications(0, PHP_INT_MAX)->items; + $notifications = $this->notificationService->loadNotifications( + new NotificationQuery([], 0, PHP_INT_MAX) + )->items; foreach ($notifications as $notification) { $this->notificationService->markNotificationAsRead($notification); From 6b8752f6674e9b9009b50b8bc907b8670eb0a4ed Mon Sep 17 00:00:00 2001 From: Aleksandra Bozek Date: Fri, 9 May 2025 11:35:47 +0100 Subject: [PATCH 37/94] Fixed dateRange filter status check --- .../views/themes/admin/account/notifications/filters.html.twig | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/bundle/Resources/views/themes/admin/account/notifications/filters.html.twig b/src/bundle/Resources/views/themes/admin/account/notifications/filters.html.twig index c8664f01b5..fbbdba085c 100644 --- a/src/bundle/Resources/views/themes/admin/account/notifications/filters.html.twig +++ b/src/bundle/Resources/views/themes/admin/account/notifications/filters.html.twig @@ -5,7 +5,8 @@ {% set is_some_filter_set = (search_form.statuses is defined and search_form.statuses.vars.value|length) or search_form.type.vars.value|length or - search_form.createdRange.vars.value != 0 + (search_form.createdRange.vars.value is not null and (search_form.createdRange.vars.value.min is not null or + search_form.createdRange.vars.value.max is not null)) %}
From 15e928995e7630afe951ca9d2ee8e631f9564305 Mon Sep 17 00:00:00 2001 From: Aleksandra Bozek Date: Fri, 9 May 2025 11:36:21 +0100 Subject: [PATCH 38/94] temp: do not merge --- dependencies.json | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 dependencies.json diff --git a/dependencies.json b/dependencies.json new file mode 100644 index 0000000000..0ae273b138 --- /dev/null +++ b/dependencies.json @@ -0,0 +1,11 @@ +{ + "recipesEndpoint": "", + "packages": [ + { + "requirement": "dev-ibx_9060-notification-filtering as 4.6.x-dev", + "repositoryUrl": "https://github.com/ibexa/core", + "package": "ibexa/core", + "shouldBeAddedAsVCS": false + } + ] +} \ No newline at end of file From 820b1801cc06ae53e3ded6fc69db3833777c3dae Mon Sep 17 00:00:00 2001 From: Aleksandra Bozek Date: Fri, 9 May 2025 15:13:05 +0100 Subject: [PATCH 39/94] Review fixes pt 2 --- .../js/scripts/admin.notifications.filters.js | 6 ++-- .../js/scripts/admin.notifications.list.js | 14 +++++---- .../public/scss/_header-user-menu.scss | 2 +- .../public/scss/_notifications-modal.scss | 30 +++++++++++-------- .../Resources/public/scss/_notifications.scss | 5 ++++ .../account/notifications/list_all.html.twig | 8 +++-- .../account/notifications/list_item.html.twig | 6 ++-- .../notifications/list_item_all.html.twig | 5 ++-- .../notifications/list_item_deleted.html.twig | 2 +- .../notifications/side_panel.html.twig | 4 ++- .../component/side_panel/side_panel.html.twig | 2 +- 11 files changed, 52 insertions(+), 32 deletions(-) diff --git a/src/bundle/Resources/public/js/scripts/admin.notifications.filters.js b/src/bundle/Resources/public/js/scripts/admin.notifications.filters.js index 331f965ad0..643013b8cd 100644 --- a/src/bundle/Resources/public/js/scripts/admin.notifications.filters.js +++ b/src/bundle/Resources/public/js/scripts/admin.notifications.filters.js @@ -20,7 +20,9 @@ const timePicker = filterNode.querySelector('.ibexa-date-time-picker__input'); if (sourceSelect) { - sourceSelectOptions.forEach((option) => (option.selected = false)); + sourceSelectOptions.forEach((option) => { + option.selected = false; + }); if (isNodeTimeFilter(filterNode)) { sourceSelectOptions[0].selected = true; @@ -78,7 +80,7 @@ const isSomeFilterSet = () => { const hasStatusFilterValue = hasFilterValue(statusFilterNode); const hasTypeFilterValue = hasFilterValue(typeFilterNode); - const hasDatetimeFilterValue = [...datetimeFilterNodes].some((input) => hasFilterValue(input)); + const hasDatetimeFilterValue = [...datetimeFilterNodes].some(hasFilterValue); return hasStatusFilterValue || hasTypeFilterValue || hasDatetimeFilterValue; }; diff --git a/src/bundle/Resources/public/js/scripts/admin.notifications.list.js b/src/bundle/Resources/public/js/scripts/admin.notifications.list.js index aa3bf83a1a..9fe7e77b85 100644 --- a/src/bundle/Resources/public/js/scripts/admin.notifications.list.js +++ b/src/bundle/Resources/public/js/scripts/admin.notifications.list.js @@ -7,7 +7,9 @@ const markAllAsReadBtn = doc.querySelector('.ibexa-notification-list__mark-all-read'); const markAsReadBtn = doc.querySelector('.ibexa-notification-list__btn--mark-as-read'); const deleteBtn = doc.querySelector('.ibexa-notification-list__btn--delete'); - const checkboxes = [...doc.querySelectorAll('.ibexa-notification-list .ibexa-table__cell--has-checkbox .ibexa-input--checkbox')]; + const notificationsCheckboxes = [ + ...doc.querySelectorAll('.ibexa-notification-list .ibexa-table__cell--has-checkbox .ibexa-input--checkbox'), + ]; const markAllAsRead = () => { const markAllAsReadLink = Routing.generate('ibexa.notifications.mark_all_as_read'); @@ -30,7 +32,7 @@ }; const markSelectedAsRead = () => { - const selectedNotifications = [...checkboxes] + const selectedNotifications = [...notificationsCheckboxes] .filter((checkbox) => checkbox.checked) .map((checkbox) => checkbox.dataset.notificationId); @@ -145,16 +147,16 @@ markAsReadBtn.addEventListener('click', markSelectedAsRead, false); const toggleActionButtonState = () => { - const checkedNotifications = checkboxes.filter((el) => el.checked); + const checkedNotifications = notificationsCheckboxes.filter((el) => el.checked); const isAnythingSelected = checkedNotifications.length > 0; - const unreadLabel = Translator.trans(/* @Desc("Unread") */ 'notification.unread', {}, 'ibexa_notifications'); deleteBtn.disabled = !isAnythingSelected; markAsReadBtn.disabled = !isAnythingSelected || !checkedNotifications.every( (checkbox) => - checkbox.closest('.ibexa-table__row').querySelector('.ibexa-notification-view-all__read').innerText === unreadLabel, + checkbox.closest('.ibexa-table__row').querySelector('.ibexa-notification-view-all__notice-dot').dataset.isRead === + 'false', ); }; const handleCheckboxChange = (checkbox) => { @@ -170,5 +172,5 @@ toggleActionButtonState(); }; - checkboxes.forEach((checkbox) => checkbox.addEventListener('change', () => handleCheckboxChange(checkbox), false)); + notificationsCheckboxes.forEach((checkbox) => checkbox.addEventListener('change', () => handleCheckboxChange(checkbox), false)); })(window, window.document, window.ibexa, window.Translator, window.Routing); diff --git a/src/bundle/Resources/public/scss/_header-user-menu.scss b/src/bundle/Resources/public/scss/_header-user-menu.scss index a4cc8c280a..8aad9d108d 100644 --- a/src/bundle/Resources/public/scss/_header-user-menu.scss +++ b/src/bundle/Resources/public/scss/_header-user-menu.scss @@ -95,7 +95,7 @@ position: absolute; left: calculateRem(8px); top: 0; - color: white; + color: $ibexa-color-white; &::after { display: flex; diff --git a/src/bundle/Resources/public/scss/_notifications-modal.scss b/src/bundle/Resources/public/scss/_notifications-modal.scss index 1fe4cc001d..e0f1aec1cd 100644 --- a/src/bundle/Resources/public/scss/_notifications-modal.scss +++ b/src/bundle/Resources/public/scss/_notifications-modal.scss @@ -104,15 +104,15 @@ &__description { .description__title { margin-bottom: 0; + } - &--item { - display: inline-block; - vertical-align: top; - max-width: 100%; - overflow: hidden; - text-overflow: ellipsis; - font-weight: bold; - } + .description__title-item { + display: inline-block; + vertical-align: top; + max-width: 100%; + overflow: hidden; + text-overflow: ellipsis; + font-weight: bold; } .description__text { @@ -218,9 +218,11 @@ width: calculateRem(6px); height: calculateRem(6px); } + &[data-is-read='true'] { background: $ibexa-color-dark-300; } + &[data-is-read='false'] { background: $ibexa-color-danger; } @@ -233,6 +235,10 @@ max-width: 100%; overflow: hidden; + .type__text { + white-space: nowrap; + } + .ibexa-notifications-modal__description { display: flex; max-width: 100%; @@ -249,16 +255,16 @@ &__cell-wrapper { font-size: $ibexa-text-font-size-medium; + padding-left: 0; + min-width: 0; + max-width: 100%; p { margin-bottom: 0; } - padding-left: 0; - min-width: 0; - max-width: 100%; .ibexa-table__cell.ibexa-table__cell { - padding: calculateRem(12px) 0 calculateRem(12px) 0; + padding: calculateRem(12px) 0; } } diff --git a/src/bundle/Resources/public/scss/_notifications.scss b/src/bundle/Resources/public/scss/_notifications.scss index df052d545f..a372ef2fa7 100644 --- a/src/bundle/Resources/public/scss/_notifications.scss +++ b/src/bundle/Resources/public/scss/_notifications.scss @@ -21,9 +21,11 @@ .ibexa-table__header-cell { padding: calculateRem(16px) calculateRem(8px); + &:first-child { padding-left: calculateRem(16px); } + &:last-child { padding-right: calculateRem(16px); } @@ -31,9 +33,11 @@ .ibexa-table__cell { padding: calculateRem(8px); + &:first-child { padding-left: calculateRem(16px); } + &:last-child { padding-right: calculateRem(16px); } @@ -46,6 +50,7 @@ .ibexa-table-header { padding: calculateRem(8px) 0; + &__headline { font-size: $ibexa-text-font-size-extra-large; } diff --git a/src/bundle/Resources/views/themes/admin/account/notifications/list_all.html.twig b/src/bundle/Resources/views/themes/admin/account/notifications/list_all.html.twig index 3d62364fd6..5829e7d13f 100644 --- a/src/bundle/Resources/views/themes/admin/account/notifications/list_all.html.twig +++ b/src/bundle/Resources/views/themes/admin/account/notifications/list_all.html.twig @@ -1,11 +1,11 @@ {% extends "@ibexadesign/ui/layout.html.twig" %} {% import '@ibexadesign/ui/component/macros.html.twig' as html %} +{% import _self as macros %} {% from '@ibexadesign/ui/component/macros.html.twig' import results_headline %} {% trans_default_domain 'ibexa_notifications' %} {% form_theme form_remove '@ibexadesign/ui/form_fields.html.twig' %} -{% import _self as macros %} {% block main_container_class %}{{ parent() }} ibexa-notification-list__container {% endblock %} @@ -104,13 +104,15 @@ {% block javascripts %} {{ encore_entry_script_tags('ibexa-admin-notifications-list-js', null, 'ibexa') }} {% endblock %} + {% macro table_header_tools(form,) %} {% set modal_data_target = 'modal-' ~ form.remove.vars.id %} +
{% endblock %} {% endembed %} - {% embed '@ibexadesign/ui/component/table/table_body_cell.html.twig' with { class: '' } %} + {% embed '@ibexadesign/ui/component/table/table_body_cell.html.twig' %} {% block content %}
- {% endblock %} diff --git a/src/bundle/Resources/views/themes/admin/account/notifications/list_item_deleted.html.twig b/src/bundle/Resources/views/themes/admin/account/notifications/list_item_deleted.html.twig index 46db86596b..64930fc793 100644 --- a/src/bundle/Resources/views/themes/admin/account/notifications/list_item_deleted.html.twig +++ b/src/bundle/Resources/views/themes/admin/account/notifications/list_item_deleted.html.twig @@ -22,7 +22,7 @@ {% block content %}

{{ 'notification.title'|trans|desc('Title:') }} - {{ title }} + {{ title }}

{{ 'notification.no_longer_available'|trans({}, 'ibexa_notifications')|desc('The Content item is no longer available')}}

{% endblock %} diff --git a/src/bundle/Resources/views/themes/admin/account/notifications/side_panel.html.twig b/src/bundle/Resources/views/themes/admin/account/notifications/side_panel.html.twig index 48dda19da0..6abd1ae9cb 100644 --- a/src/bundle/Resources/views/themes/admin/account/notifications/side_panel.html.twig +++ b/src/bundle/Resources/views/themes/admin/account/notifications/side_panel.html.twig @@ -1,3 +1,5 @@ +{% set max_visible_notifications_count = 10 %} + {% embed '@ibexadesign/ui/component/side_panel/side_panel.html.twig' with { title: 'ibexa_notifications'|trans|desc('Notifications'), attr: { @@ -9,7 +11,7 @@ {% block header %}
{{ 'ibexa_notifications'|trans|desc('Notifications')}} - (10) + ({{max_visible_notifications_count}}) @@ -25,7 +25,7 @@ {% block content %} {{ form_start(form_remove, { - 'action': path('ibexa.notifications.delete_multiple'), + action: path('ibexa.notifications.delete_multiple'), 'attr': { 'class': 'ibexa-toggle-btn-state ibexa-notification-list__hidden-btn', 'data-toggle-button-id': '#confirm-' ~ form_remove.remove.vars.id diff --git a/src/bundle/Resources/views/themes/admin/ui/component/side_panel/side_panel.html.twig b/src/bundle/Resources/views/themes/admin/ui/component/side_panel/side_panel.html.twig index 4d90c0d980..9e1457101b 100644 --- a/src/bundle/Resources/views/themes/admin/ui/component/side_panel/side_panel.html.twig +++ b/src/bundle/Resources/views/themes/admin/ui/component/side_panel/side_panel.html.twig @@ -6,9 +6,8 @@ {% set config_panel_footer_class = 'ibexa-side-panel__footer' ~ (footer_class is defined ? footer_class ~ '')%} {% set attr = attr|default({})|merge({ - class: (' ' - ~ class|default('') ~ ' ' - ~ attr.class|default(''))|trim ~ ' ' ~ config_panel_main_class, + ~ attr.class|default(''))|trim ~ ' ' + ~ config_panel_main_class, }) %} {% if id is defined %} From 6bd4adecb291c873ef87fdfd4847e4b7f94b7a87 Mon Sep 17 00:00:00 2001 From: Aleksandra Bozek Date: Wed, 14 May 2025 08:50:15 +0100 Subject: [PATCH 41/94] Review fixes pt 4 --- .../Resources/encore/ibexa.js.config.js | 2 +- .../js/scripts/admin.notifications.filters.js | 38 ++++++++++++------- ...=> admin.notifications.filters.sidebar.js} | 2 +- .../Resources/public/scss/_notifications.scss | 24 ++++-------- .../component/side_panel/side_panel.html.twig | 11 ++++-- 5 files changed, 41 insertions(+), 36 deletions(-) rename src/bundle/Resources/public/js/scripts/{core/sidebar.collapse.js => admin.notifications.filters.sidebar.js} (92%) diff --git a/src/bundle/Resources/encore/ibexa.js.config.js b/src/bundle/Resources/encore/ibexa.js.config.js index abe2a4bc81..447c7a322e 100644 --- a/src/bundle/Resources/encore/ibexa.js.config.js +++ b/src/bundle/Resources/encore/ibexa.js.config.js @@ -255,7 +255,7 @@ module.exports = (Encore) => { ]) .addEntry('ibexa-admin-ui-edit-base-js', [path.resolve(__dirname, '../public/js/scripts/edit.header.js')]) .addEntry('ibexa-admin-notifications-list-js', [ - path.resolve(__dirname, '../public/js/scripts/core/sidebar.collapse.js'), + path.resolve(__dirname, '../public/js/scripts/admin.notifications.filters.sidebar.js'), path.resolve(__dirname, '../public/js/scripts/admin.notifications.list.js'), path.resolve(__dirname, '../public/js/scripts/admin.notifications.filters.js'), ]); diff --git a/src/bundle/Resources/public/js/scripts/admin.notifications.filters.js b/src/bundle/Resources/public/js/scripts/admin.notifications.filters.js index 643013b8cd..f83f4c53f7 100644 --- a/src/bundle/Resources/public/js/scripts/admin.notifications.filters.js +++ b/src/bundle/Resources/public/js/scripts/admin.notifications.filters.js @@ -13,18 +13,18 @@ } const sourceSelect = filterNode.querySelector('.ibexa-list-filters__item-content .ibexa-dropdown__source .ibexa-input--select'); - const sourceSelectOptions = sourceSelect?.querySelectorAll('option'); const checkboxes = filterNode.querySelectorAll( '.ibexa-list-filters__item-content .ibexa-input--checkbox:not([name="dropdown-checkbox"])', ); const timePicker = filterNode.querySelector('.ibexa-date-time-picker__input'); if (sourceSelect) { + const sourceSelectOptions = sourceSelect.querySelectorAll('option'); sourceSelectOptions.forEach((option) => { option.selected = false; }); - if (isNodeTimeFilter(filterNode)) { + if (isTimeFilterNode(filterNode)) { sourceSelectOptions[0].selected = true; } } else if (checkboxes.length) { @@ -41,24 +41,36 @@ searchForm.submit(); }; - const attachFilterEvents = (filterNode) => { + const attachStatusFilterEvents = (filterNode) => { if (!filterNode) { return; } - const sourceSelect = filterNode.querySelector('.ibexa-list-filters__item-content .ibexa-dropdown__source .ibexa-input--select'); const checkboxes = filterNode.querySelectorAll( '.ibexa-list-filters__item-content .ibexa-input--checkbox:not([name="dropdown-checkbox"])', ); - const picker = filterNode.querySelector('.ibexa-input--date'); - - picker?.addEventListener('change', filterChange, false); - sourceSelect?.addEventListener('change', filterChange, false); checkboxes.forEach((checkbox) => { checkbox.addEventListener('change', filterChange, false); }); }; - const isNodeTimeFilter = (filterNode) => { + const attachTypeFilterEvents = (filterNode) => { + if (!filterNode) { + return; + } + + const sourceSelect = filterNode.querySelector('.ibexa-list-filters__item-content .ibexa-dropdown__source .ibexa-input--select'); + sourceSelect?.addEventListener('change', filterChange, false); + }; + const attachDateFilterEvents = (filterNode) => { + if (!filterNode) { + return; + } + + const picker = filterNode.querySelector('.ibexa-input--date'); + picker?.addEventListener('change', filterChange, false); + }; + + const isTimeFilterNode = (filterNode) => { return filterNode.classList.contains('ibexa-picker'); }; const hasFilterValue = (filterNode) => { @@ -69,7 +81,7 @@ const select = filterNode.querySelector('.ibexa-dropdown__source .ibexa-input--select'); const checkedCheckboxes = filterNode.querySelectorAll('.ibexa-input--checkbox:checked'); - if (isNodeTimeFilter(filterNode)) { + if (isTimeFilterNode(filterNode)) { const timePicker = filterNode.querySelector('.ibexa-date-time-picker__input'); return !!timePicker.dataset.timestamp; @@ -85,9 +97,9 @@ return hasStatusFilterValue || hasTypeFilterValue || hasDatetimeFilterValue; }; const attachInitEvents = () => { - attachFilterEvents(statusFilterNode); - attachFilterEvents(typeFilterNode); - datetimeFilterNodes.forEach((input) => attachFilterEvents(input)); + attachStatusFilterEvents(statusFilterNode); + attachTypeFilterEvents(typeFilterNode); + datetimeFilterNodes.forEach((input) => attachDateFilterEvents(input)); }; const filterChange = () => { const hasFiltersSetValue = isSomeFilterSet(); diff --git a/src/bundle/Resources/public/js/scripts/core/sidebar.collapse.js b/src/bundle/Resources/public/js/scripts/admin.notifications.filters.sidebar.js similarity index 92% rename from src/bundle/Resources/public/js/scripts/core/sidebar.collapse.js rename to src/bundle/Resources/public/js/scripts/admin.notifications.filters.sidebar.js index d5a19aa5a1..2bd689df8d 100644 --- a/src/bundle/Resources/public/js/scripts/core/sidebar.collapse.js +++ b/src/bundle/Resources/public/js/scripts/admin.notifications.filters.sidebar.js @@ -14,4 +14,4 @@ }; toggleBtn.addEventListener('click', toggleSidebar, false); -})(window, window.document, window.bootstrap, window.Translator); +})(window, window.document); diff --git a/src/bundle/Resources/public/scss/_notifications.scss b/src/bundle/Resources/public/scss/_notifications.scss index c9c56158d7..c6ba73af27 100644 --- a/src/bundle/Resources/public/scss/_notifications.scss +++ b/src/bundle/Resources/public/scss/_notifications.scss @@ -19,20 +19,9 @@ } } - .ibexa-table__header-cell { - padding: calculateRem(16px) calculateRem(8px); - - &:first-child { - padding-left: calculateRem(16px); - } - - &:last-child { - padding-right: calculateRem(16px); - } - } - + .ibexa-table__header-cell, .ibexa-table__cell { - padding: calculateRem(8px); + padding: calculateRem(16px) calculateRem(8px); &:first-child { padding-left: calculateRem(16px); @@ -55,10 +44,11 @@ font-size: $ibexa-text-font-size-extra-large; } } - - &__mark-all-read { - display: flex; - justify-content: flex-end; + &__btn { + &--mark-all-as-read { + display: flex; + justify-content: flex-end; + } } &__data-grid-wrapper { diff --git a/src/bundle/Resources/views/themes/admin/ui/component/side_panel/side_panel.html.twig b/src/bundle/Resources/views/themes/admin/ui/component/side_panel/side_panel.html.twig index 9e1457101b..495bc7ab56 100644 --- a/src/bundle/Resources/views/themes/admin/ui/component/side_panel/side_panel.html.twig +++ b/src/bundle/Resources/views/themes/admin/ui/component/side_panel/side_panel.html.twig @@ -3,11 +3,14 @@ {% trans_default_domain 'ibexa_admin_ui' %} {% set config_panel_main_class = 'ibexa-side-panel ibexa-side-panel--hidden' %} -{% set config_panel_footer_class = 'ibexa-side-panel__footer' ~ (footer_class is defined ? footer_class ~ '')%} +{% set attr_footer = attr_footer|default({})|merge({ + class: ('ibexa-side-panel__footer' + ~ (footer_class is defined ? footer_class ~ ''))|trim, +}) %} + {% set attr = attr|default({})|merge({ - ~ attr.class|default(''))|trim ~ ' ' - ~ config_panel_main_class, + class: attr.class|default('')|trim ~ ' ' ~ config_panel_main_class, }) %} {% if id is defined %} @@ -30,7 +33,7 @@ {% endblock %} {% block content %}{% endblock %} -