mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-06-07 18:44:08 +00:00
added unread and read all function with endpoints for notifications
This commit is contained in:
parent
2b09e1459c
commit
2d67843901
7
.changes/unreleased/Feature-20240705-152111.yaml
Normal file
7
.changes/unreleased/Feature-20240705-152111.yaml
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
kind: Feature
|
||||||
|
body: |+
|
||||||
|
Add the possibility to mark all notifications as read
|
||||||
|
|
||||||
|
time: 2024-07-05T15:21:11.730543489+02:00
|
||||||
|
custom:
|
||||||
|
Issue: "273"
|
@ -104,4 +104,38 @@ class NotificationApiController
|
|||||||
|
|
||||||
return new JsonResponse(null, JsonResponse::HTTP_ACCEPTED, [], false);
|
return new JsonResponse(null, JsonResponse::HTTP_ACCEPTED, [], false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Route("/mark/allread", name="chill_api_main_notification_mark_allread", methods={"POST"})
|
||||||
|
*/
|
||||||
|
public function markAllRead(): JsonResponse
|
||||||
|
{
|
||||||
|
$user = $this->security->getUser();
|
||||||
|
|
||||||
|
if (!$user instanceof User) {
|
||||||
|
throw new \RuntimeException('Invalid user');
|
||||||
|
}
|
||||||
|
|
||||||
|
$modifiedNotificationIds = $this->notificationRepository->markAllNotificationAsReadForUser($user);
|
||||||
|
|
||||||
|
return new JsonResponse($modifiedNotificationIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Route("/mark/undoallread", name="chill_api_main_notification_mark_undoallread", methods={"POST"})
|
||||||
|
*/
|
||||||
|
public function undoAllRead(Request $request): JsonResponse
|
||||||
|
{
|
||||||
|
$user = $this->security->getUser();
|
||||||
|
|
||||||
|
if (!$user instanceof User) {
|
||||||
|
throw new \RuntimeException('Invalid user');
|
||||||
|
}
|
||||||
|
|
||||||
|
$ids = json_decode($request->getContent(), true, 512, JSON_THROW_ON_ERROR);
|
||||||
|
|
||||||
|
$touchedIds = $this->notificationRepository->markAllNotificationAsUnreadForUser($user, $ids);
|
||||||
|
|
||||||
|
return new JsonResponse($touchedIds);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -193,13 +193,17 @@ class NotificationController extends AbstractController
|
|||||||
$this->denyAccessUnlessGranted('IS_AUTHENTICATED_REMEMBERED');
|
$this->denyAccessUnlessGranted('IS_AUTHENTICATED_REMEMBERED');
|
||||||
$currentUser = $this->security->getUser();
|
$currentUser = $this->security->getUser();
|
||||||
|
|
||||||
|
if (!$currentUser instanceof User) {
|
||||||
|
throw new AccessDeniedHttpException('Only regular user should access this page');
|
||||||
|
}
|
||||||
|
|
||||||
$notificationsNbr = $this->notificationRepository->countAllForAttendee($currentUser);
|
$notificationsNbr = $this->notificationRepository->countAllForAttendee($currentUser);
|
||||||
$paginator = $this->paginatorFactory->create($notificationsNbr);
|
$paginator = $this->paginatorFactory->create($notificationsNbr);
|
||||||
|
|
||||||
$notifications = $this->notificationRepository->findAllForAttendee(
|
$notifications = $this->notificationRepository->findAllForAttendee(
|
||||||
$currentUser,
|
$currentUser,
|
||||||
$limit = $paginator->getItemsPerPage(),
|
$paginator->getItemsPerPage(),
|
||||||
$offset = $paginator->getCurrentPage()->getFirstItemNumber()
|
$paginator->getCurrentPage()->getFirstItemNumber()
|
||||||
);
|
);
|
||||||
|
|
||||||
return $this->render('@ChillMain/Notification/list.html.twig', [
|
return $this->render('@ChillMain/Notification/list.html.twig', [
|
||||||
|
@ -13,6 +13,8 @@ namespace Chill\MainBundle\Repository;
|
|||||||
|
|
||||||
use Chill\MainBundle\Entity\Notification;
|
use Chill\MainBundle\Entity\Notification;
|
||||||
use Chill\MainBundle\Entity\User;
|
use Chill\MainBundle\Entity\User;
|
||||||
|
use Doctrine\DBAL\Connection;
|
||||||
|
use Doctrine\DBAL\Result;
|
||||||
use Doctrine\DBAL\Statement;
|
use Doctrine\DBAL\Statement;
|
||||||
use Doctrine\DBAL\Types\Types;
|
use Doctrine\DBAL\Types\Types;
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
@ -81,10 +83,7 @@ final class NotificationRepository implements ObjectRepository
|
|||||||
$results->free();
|
$results->free();
|
||||||
} else {
|
} else {
|
||||||
$wheres = [];
|
$wheres = [];
|
||||||
foreach ([
|
foreach ([['relatedEntityClass' => $relatedEntityClass, 'relatedEntityId' => $relatedEntityId], ...$more] as $k => ['relatedEntityClass' => $relClass, 'relatedEntityId' => $relId]) {
|
||||||
['relatedEntityClass' => $relatedEntityClass, 'relatedEntityId' => $relatedEntityId],
|
|
||||||
...$more,
|
|
||||||
] as $k => ['relatedEntityClass' => $relClass, 'relatedEntityId' => $relId]) {
|
|
||||||
$wheres[] = "(relatedEntityClass = :relatedEntityClass_{$k} AND relatedEntityId = :relatedEntityId_{$k})";
|
$wheres[] = "(relatedEntityClass = :relatedEntityClass_{$k} AND relatedEntityId = :relatedEntityId_{$k})";
|
||||||
$sqlParams["relatedEntityClass_{$k}"] = $relClass;
|
$sqlParams["relatedEntityClass_{$k}"] = $relClass;
|
||||||
$sqlParams["relatedEntityId_{$k}"] = $relId;
|
$sqlParams["relatedEntityId_{$k}"] = $relId;
|
||||||
@ -255,10 +254,12 @@ final class NotificationRepository implements ObjectRepository
|
|||||||
$qb = $this->repository->createQueryBuilder('n');
|
$qb = $this->repository->createQueryBuilder('n');
|
||||||
|
|
||||||
// add condition for related entity (in main arguments, and in more)
|
// add condition for related entity (in main arguments, and in more)
|
||||||
$or = $qb->expr()->orX($qb->expr()->andX(
|
$or = $qb->expr()->orX(
|
||||||
|
$qb->expr()->andX(
|
||||||
$qb->expr()->eq('n.relatedEntityClass', ':relatedEntityClass'),
|
$qb->expr()->eq('n.relatedEntityClass', ':relatedEntityClass'),
|
||||||
$qb->expr()->eq('n.relatedEntityId', ':relatedEntityId')
|
$qb->expr()->eq('n.relatedEntityId', ':relatedEntityId')
|
||||||
));
|
)
|
||||||
|
);
|
||||||
$qb
|
$qb
|
||||||
->setParameter('relatedEntityClass', $relatedEntityClass)
|
->setParameter('relatedEntityClass', $relatedEntityClass)
|
||||||
->setParameter('relatedEntityId', $relatedEntityId);
|
->setParameter('relatedEntityId', $relatedEntityId);
|
||||||
@ -310,4 +311,86 @@ final class NotificationRepository implements ObjectRepository
|
|||||||
|
|
||||||
return $qb;
|
return $qb;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return list<int> the ids of the notifications marked as unread
|
||||||
|
*/
|
||||||
|
public function markAllNotificationAsReadForUser(User $user): array
|
||||||
|
{
|
||||||
|
// Get the database connection from the entity manager
|
||||||
|
$connection = $this->em->getConnection();
|
||||||
|
|
||||||
|
/** @var Result $results */
|
||||||
|
$results = $connection->transactional(function (Connection $connection) use ($user) {
|
||||||
|
// Define the SQL query
|
||||||
|
$sql = <<<'SQL'
|
||||||
|
DELETE FROM chill_main_notification_addresses_unread
|
||||||
|
WHERE user_id = :user_id
|
||||||
|
RETURNING notification_id
|
||||||
|
SQL;
|
||||||
|
|
||||||
|
return $connection->executeQuery($sql, ['user_id' => $user->getId()]);
|
||||||
|
});
|
||||||
|
|
||||||
|
$notificationIdsTouched = [];
|
||||||
|
foreach ($results->iterateAssociative() as $row) {
|
||||||
|
$notificationIdsTouched[] = $row['notification_id'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return array_values($notificationIdsTouched);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param list<int> $notificationIds
|
||||||
|
*/
|
||||||
|
public function markAllNotificationAsUnreadForUser(User $user, array $notificationIds): array
|
||||||
|
{
|
||||||
|
// Get the database connection from the entity manager
|
||||||
|
$connection = $this->em->getConnection();
|
||||||
|
|
||||||
|
/** @var Result $results */
|
||||||
|
$results = $connection->transactional(function (Connection $connection) use ($user, $notificationIds) {
|
||||||
|
// This query double-check that the user is one of the addresses of the notification or the sender,
|
||||||
|
// if the notification is already marked as unread, this query does not fails.
|
||||||
|
// this query return the list of notification id which are affected
|
||||||
|
$sql = <<<'SQL'
|
||||||
|
INSERT INTO chill_main_notification_addresses_unread (user_id, notification_id)
|
||||||
|
SELECT ?, chill_main_notification_addresses_user.notification_id
|
||||||
|
FROM chill_main_notification_addresses_user JOIN chill_main_notification ON chill_main_notification_addresses_user.notification_id = chill_main_notification.id
|
||||||
|
WHERE (chill_main_notification_addresses_user.user_id = ? OR chill_main_notification.sender_id = ?)
|
||||||
|
AND chill_main_notification_addresses_user.notification_id IN ({ notification_ids })
|
||||||
|
ON CONFLICT (user_id, notification_id) DO NOTHING
|
||||||
|
RETURNING notification_id
|
||||||
|
SQL;
|
||||||
|
|
||||||
|
$params = [$user->getId(), $user->getId(), $user->getId(), ...array_values($notificationIds)];
|
||||||
|
$sql = strtr($sql, ['{ notification_ids }' => implode(', ', array_fill(0, count($notificationIds), '?'))]);
|
||||||
|
|
||||||
|
return $connection->executeQuery($sql, $params);
|
||||||
|
});
|
||||||
|
|
||||||
|
$notificationIdsTouched = [];
|
||||||
|
foreach ($results->iterateAssociative() as $row) {
|
||||||
|
$notificationIdsTouched[] = $row['notification_id'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return array_values($notificationIdsTouched);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function findAllUnreadByUser(User $user): array
|
||||||
|
{
|
||||||
|
$rsm = new Query\ResultSetMappingBuilder($this->em);
|
||||||
|
$rsm->addRootEntityFromClassMetadata(Notification::class, 'cmn');
|
||||||
|
|
||||||
|
$sql = 'SELECT '.$rsm->generateSelectClause(['cmn' => 'cmn']).' '.
|
||||||
|
'FROM chill_main_notification cmn '.
|
||||||
|
'WHERE '.
|
||||||
|
'EXISTS (SELECT 1 FROM chill_main_notification_addresses_unread cmnau WHERE cmnau.user_id = :userId AND cmnau.notification_id = cmn.id) '.
|
||||||
|
'ORDER BY cmn.date DESC';
|
||||||
|
|
||||||
|
$nq = $this->em->createNativeQuery($sql, $rsm)
|
||||||
|
->setParameter('userId', $user->getId());
|
||||||
|
|
||||||
|
return $nq->getResult();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
import {createApp} from "vue";
|
import { createApp } from "vue";
|
||||||
import NotificationReadToggle from "ChillMainAssets/vuejs/_components/Notification/NotificationReadToggle.vue";
|
import NotificationReadToggle from "ChillMainAssets/vuejs/_components/Notification/NotificationReadToggle.vue";
|
||||||
import { _createI18n } from "ChillMainAssets/vuejs/_js/i18n";
|
import { _createI18n } from "ChillMainAssets/vuejs/_js/i18n";
|
||||||
|
import NotificationReadAllToggle from "ChillMainAssets/vuejs/_components/Notification/NotificationReadAllToggle.vue";
|
||||||
|
|
||||||
const i18n = _createI18n({});
|
const i18n = _createI18n({});
|
||||||
|
|
||||||
window.addEventListener('DOMContentLoaded', function (e) {
|
window.addEventListener("DOMContentLoaded", function (e) {
|
||||||
document.querySelectorAll('.notification_toggle_read_status')
|
document
|
||||||
|
.querySelectorAll(".notification_toggle_read_status")
|
||||||
.forEach(function (el, i) {
|
.forEach(function (el, i) {
|
||||||
createApp({
|
createApp({
|
||||||
template: `<notification-read-toggle
|
template: `<notification-read-toggle
|
||||||
@ -22,35 +24,40 @@ window.addEventListener('DOMContentLoaded', function (e) {
|
|||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
notificationId: el.dataset.notificationId,
|
notificationId: parseInt(el.dataset.notificationId),
|
||||||
buttonClass: el.dataset.buttonClass,
|
buttonClass: el.dataset.buttonClass,
|
||||||
buttonNoText: 'false' === el.dataset.buttonText,
|
buttonNoText: "false" === el.dataset.buttonText,
|
||||||
showUrl: el.dataset.showButtonUrl,
|
showUrl: el.dataset.showButtonUrl,
|
||||||
isRead: 1 === Number.parseInt(el.dataset.notificationCurrentIsRead),
|
isRead: 1 === Number.parseInt(el.dataset.notificationCurrentIsRead),
|
||||||
container: el.dataset.container
|
container: el.dataset.container,
|
||||||
}
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
getContainer() {
|
getContainer() {
|
||||||
return document.querySelectorAll(`div.${this.container}`);
|
return document.querySelectorAll(`div.${this.container}`);
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
onMarkRead() {
|
onMarkRead() {
|
||||||
if (typeof this.getContainer[i] !== 'undefined') {
|
if (typeof this.getContainer[i] !== "undefined") {
|
||||||
this.getContainer[i].classList.replace('read', 'unread');
|
this.getContainer[i].classList.replace("read", "unread");
|
||||||
} else { throw 'data-container attribute is missing' }
|
} else {
|
||||||
|
throw "data-container attribute is missing";
|
||||||
|
}
|
||||||
this.isRead = false;
|
this.isRead = false;
|
||||||
},
|
},
|
||||||
onMarkUnread() {
|
onMarkUnread() {
|
||||||
if (typeof this.getContainer[i] !== 'undefined') {
|
if (typeof this.getContainer[i] !== "undefined") {
|
||||||
this.getContainer[i].classList.replace('unread', 'read');
|
this.getContainer[i].classList.replace("unread", "read");
|
||||||
} else { throw 'data-container attribute is missing' }
|
} else {
|
||||||
|
throw "data-container attribute is missing";
|
||||||
|
}
|
||||||
this.isRead = true;
|
this.isRead = true;
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
.use(i18n)
|
.use(i18n)
|
||||||
.mount(el);
|
.mount(el);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -0,0 +1,39 @@
|
|||||||
|
import { createApp } from "vue";
|
||||||
|
import { _createI18n } from "../../vuejs/_js/i18n";
|
||||||
|
import NotificationReadAllToggle from "../../vuejs/_components/Notification/NotificationReadAllToggle.vue";
|
||||||
|
|
||||||
|
const i18n = _createI18n({});
|
||||||
|
|
||||||
|
document.addEventListener("DOMContentLoaded", function () {
|
||||||
|
const elements = document.querySelectorAll(".notification_all_read");
|
||||||
|
|
||||||
|
elements.forEach((element) => {
|
||||||
|
console.log('launch');
|
||||||
|
createApp({
|
||||||
|
template: `<notification-read-all-toggle @markAsRead="markAsRead" @markAsUnRead="markAsUnread"></notification-read-all-toggle>`,
|
||||||
|
components: {
|
||||||
|
NotificationReadAllToggle,
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
markAsRead(id: number) {
|
||||||
|
const el = document.querySelector<HTMLDivElement>(`div.notification-status[data-notification-id="${id}"]`);
|
||||||
|
if (el === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
el.classList.add('read');
|
||||||
|
el.classList.remove('unread');
|
||||||
|
},
|
||||||
|
markAsUnread(id: number) {
|
||||||
|
const el = document.querySelector<HTMLDivElement>(`div.notification-status[data-notification-id="${id}"]`);
|
||||||
|
if (el === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
el.classList.remove('read');
|
||||||
|
el.classList.add('unread');
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.use(i18n)
|
||||||
|
.mount(element);
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,50 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<button v-if="idsMarkedAsRead.length === 0"
|
||||||
|
class="btn btn-primary"
|
||||||
|
type="button"
|
||||||
|
@click="markAllRead"
|
||||||
|
>
|
||||||
|
<i class="fa fa-sm fa-envelope-open-o"></i> Marquer tout comme lu
|
||||||
|
</button>
|
||||||
|
<button v-else
|
||||||
|
class="btn btn-primary"
|
||||||
|
type="button"
|
||||||
|
@click="undo"
|
||||||
|
>
|
||||||
|
<i class="fa fa-sm fa-envelope-open-o"></i> Annuler
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { makeFetch } from "../../../lib/api/apiMethods";
|
||||||
|
import { ref } from "vue";
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'markAsRead', id: number): void,
|
||||||
|
(e: 'markAsUnRead', id: number): void,
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const idsMarkedAsRead = ref([] as number[]);
|
||||||
|
|
||||||
|
async function markAllRead() {
|
||||||
|
const ids: number[] = await makeFetch("POST", `/api/1.0/main/notification/mark/allread`, null);
|
||||||
|
for (let i of ids) {
|
||||||
|
idsMarkedAsRead.value.push(i);
|
||||||
|
emit('markAsRead', i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function undo() {
|
||||||
|
const touched: number[] = await makeFetch("POST", `/api/1.0/main/notification/mark/undoallread`, idsMarkedAsRead.value);
|
||||||
|
while (idsMarkedAsRead.value.length > 0) {
|
||||||
|
idsMarkedAsRead.value.pop();
|
||||||
|
}
|
||||||
|
for (let t of touched) {
|
||||||
|
emit('markAsUnRead', t);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped></style>
|
@ -1,8 +1,11 @@
|
|||||||
<template>
|
<template>
|
||||||
<div :class="{'btn-group btn-group-sm float-end': isButtonGroup }"
|
<div
|
||||||
role="group" aria-label="Notification actions">
|
:class="{ 'btn-group btn-group-sm float-end': isButtonGroup }"
|
||||||
|
role="group"
|
||||||
<button v-if="isRead"
|
aria-label="Notification actions"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
v-if="isRead"
|
||||||
class="btn"
|
class="btn"
|
||||||
:class="overrideClass"
|
:class="overrideClass"
|
||||||
type="button"
|
type="button"
|
||||||
@ -11,11 +14,12 @@
|
|||||||
>
|
>
|
||||||
<i class="fa fa-sm fa-envelope-o"></i>
|
<i class="fa fa-sm fa-envelope-o"></i>
|
||||||
<span v-if="!buttonNoText" class="ps-2">
|
<span v-if="!buttonNoText" class="ps-2">
|
||||||
{{ $t('markAsUnread') }}
|
{{ $t("markAsUnread") }}
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button v-if="!isRead"
|
<button
|
||||||
|
v-if="!isRead"
|
||||||
class="btn"
|
class="btn"
|
||||||
:class="overrideClass"
|
:class="overrideClass"
|
||||||
type="button"
|
type="button"
|
||||||
@ -24,11 +28,12 @@
|
|||||||
>
|
>
|
||||||
<i class="fa fa-sm fa-envelope-open-o"></i>
|
<i class="fa fa-sm fa-envelope-open-o"></i>
|
||||||
<span v-if="!buttonNoText" class="ps-2">
|
<span v-if="!buttonNoText" class="ps-2">
|
||||||
{{ $t('markAsRead') }}
|
{{ $t("markAsRead") }}
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<a v-if="isButtonGroup"
|
<a
|
||||||
|
v-if="isButtonGroup"
|
||||||
type="button"
|
type="button"
|
||||||
class="btn btn-outline-primary"
|
class="btn btn-outline-primary"
|
||||||
:href="showUrl"
|
:href="showUrl"
|
||||||
@ -37,11 +42,25 @@
|
|||||||
<i class="fa fa-sm fa-comment-o"></i>
|
<i class="fa fa-sm fa-comment-o"></i>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
<!-- "Mark All Read" button -->
|
||||||
|
<button
|
||||||
|
v-if="showMarkAllButton"
|
||||||
|
class="btn"
|
||||||
|
:class="overrideClass"
|
||||||
|
type="button"
|
||||||
|
:title="$t('markAllRead')"
|
||||||
|
@click="markAllRead"
|
||||||
|
>
|
||||||
|
<i class="fa fa-sm fa-envelope-o"></i>
|
||||||
|
<span v-if="!buttonNoText" class="ps-2">
|
||||||
|
{{ $t("markAllRead") }}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { makeFetch } from 'ChillMainAssets/lib/api/apiMethods.ts';
|
import { makeFetch } from "ChillMainAssets/lib/api/apiMethods.ts";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "NotificationReadToggle",
|
name: "NotificationReadToggle",
|
||||||
@ -57,7 +76,7 @@ export default {
|
|||||||
// Optional
|
// Optional
|
||||||
buttonClass: {
|
buttonClass: {
|
||||||
required: false,
|
required: false,
|
||||||
type: String
|
type: String,
|
||||||
},
|
},
|
||||||
buttonNoText: {
|
buttonNoText: {
|
||||||
required: false,
|
required: false,
|
||||||
@ -65,14 +84,14 @@ export default {
|
|||||||
},
|
},
|
||||||
showUrl: {
|
showUrl: {
|
||||||
required: false,
|
required: false,
|
||||||
type: String
|
type: String,
|
||||||
}
|
|
||||||
},
|
},
|
||||||
emits: ['markRead', 'markUnread'],
|
},
|
||||||
|
emits: ["markRead", "markUnread"],
|
||||||
computed: {
|
computed: {
|
||||||
/// [Option] override default button appearance (btn-misc)
|
/// [Option] override default button appearance (btn-misc)
|
||||||
overrideClass() {
|
overrideClass() {
|
||||||
return this.buttonClass ? this.buttonClass : 'btn-misc'
|
return this.buttonClass ? this.buttonClass : "btn-misc";
|
||||||
},
|
},
|
||||||
/// [Option] don't display text on button
|
/// [Option] don't display text on button
|
||||||
buttonHideText() {
|
buttonHideText() {
|
||||||
@ -82,31 +101,48 @@ export default {
|
|||||||
// When passed, the component return a button-group with 2 buttons.
|
// When passed, the component return a button-group with 2 buttons.
|
||||||
isButtonGroup() {
|
isButtonGroup() {
|
||||||
return this.showUrl;
|
return this.showUrl;
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
markAsUnread() {
|
markAsUnread() {
|
||||||
makeFetch('POST', `/api/1.0/main/notification/${this.notificationId}/mark/unread`, []).then(response => {
|
makeFetch(
|
||||||
this.$emit('markRead', { notificationId: this.notificationId });
|
"POST",
|
||||||
})
|
`/api/1.0/main/notification/${this.notificationId}/mark/unread`,
|
||||||
|
[]
|
||||||
|
).then((response) => {
|
||||||
|
this.$emit("markRead", {notificationId: this.notificationId});
|
||||||
|
});
|
||||||
},
|
},
|
||||||
markAsRead() {
|
markAsRead() {
|
||||||
makeFetch('POST', `/api/1.0/main/notification/${this.notificationId}/mark/read`, []).then(response => {
|
makeFetch(
|
||||||
this.$emit('markUnread', { notificationId: this.notificationId });
|
"POST",
|
||||||
})
|
`/api/1.0/main/notification/${this.notificationId}/mark/read`,
|
||||||
|
[]
|
||||||
|
).then((response) => {
|
||||||
|
this.$emit("markUnread", {
|
||||||
|
notificationId: this.notificationId,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
markAllRead() {
|
||||||
|
makeFetch(
|
||||||
|
"POST",
|
||||||
|
`/api/1.0/main/notification/markallread`,
|
||||||
|
[]
|
||||||
|
).then((response) => {
|
||||||
|
this.$emit("markAllRead");
|
||||||
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
i18n: {
|
i18n: {
|
||||||
messages: {
|
messages: {
|
||||||
fr: {
|
fr: {
|
||||||
markAsUnread: 'Marquer comme non-lu',
|
markAsUnread: "Marquer comme non-lu",
|
||||||
markAsRead: 'Marquer comme lu'
|
markAsRead: "Marquer comme lu",
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss"></style>
|
||||||
|
|
||||||
</style>
|
|
||||||
|
@ -1,13 +1,16 @@
|
|||||||
{% macro title(c) %}
|
{% macro title(c) %}
|
||||||
<div class="item-row title">
|
<div class="item-row title">
|
||||||
<h2 class="notification-title">
|
<h2 class="notification-title">
|
||||||
<a href="{{ chill_path_add_return_path('chill_main_notification_show', {'id': c.notification.id}) }}">
|
<a
|
||||||
|
href="{{ chill_path_add_return_path('chill_main_notification_show', {
|
||||||
|
id: c.notification.id
|
||||||
|
}) }}"
|
||||||
|
>
|
||||||
{{ c.notification.title }}
|
{{ c.notification.title }}
|
||||||
</a>
|
</a>
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
{% endmacro %}
|
{% endmacro %}
|
||||||
|
|
||||||
{% macro header(c) %}
|
{% macro header(c) %}
|
||||||
<div class="item-row notification-header mt-2">
|
<div class="item-row notification-header mt-2">
|
||||||
<div class="item-col">
|
<div class="item-col">
|
||||||
@ -15,16 +18,16 @@
|
|||||||
{% if c.step is not defined or c.step == 'inbox' %}
|
{% if c.step is not defined or c.step == 'inbox' %}
|
||||||
<li class="notification-from">
|
<li class="notification-from">
|
||||||
<span class="item-key">
|
<span class="item-key">
|
||||||
<abbr title="{{ 'notification.received_from'|trans }}">
|
<abbr title="{{ 'notification.received_from' | trans }}">
|
||||||
{{ 'notification.from'|trans }} :
|
{{ "notification.from" | trans }} :
|
||||||
</abbr>
|
</abbr>
|
||||||
</span>
|
</span>
|
||||||
{% if not c.notification.isSystem %}
|
{% if not c.notification.isSystem %}
|
||||||
<span class="badge-user">
|
<span class="badge-user">
|
||||||
{{ c.notification.sender|chill_entity_render_string({'at_date': c.notification.date}) }}
|
{{ c.notification.sender | chill_entity_render_string({'at_date': c.notification.date}) }}
|
||||||
</span>
|
</span>
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="badge-user system">{{ 'notification.is_system'|trans }}</span>
|
<span class="badge-user system">{{ "notification.is_system" | trans }}</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -33,31 +36,34 @@
|
|||||||
{% if c.notification_cc is defined %}
|
{% if c.notification_cc is defined %}
|
||||||
{% if c.notification_cc %}
|
{% if c.notification_cc %}
|
||||||
<span class="item-key">
|
<span class="item-key">
|
||||||
<abbr title="{{ 'notification.sent_cc'|trans }}">
|
<abbr title="{{ 'notification.sent_cc' | trans }}">
|
||||||
{{ 'notification.cc'|trans }} :
|
{{ "notification.cc" | trans }} :
|
||||||
</abbr>
|
</abbr>
|
||||||
</span>
|
</span>
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="item-key">
|
<span class="item-key">
|
||||||
<abbr title="{{ 'notification.sent_to'|trans }}">
|
<abbr title="{{ 'notification.sent_to' | trans }}">
|
||||||
{{ 'notification.to'|trans }} :
|
{{ "notification.to" | trans }} :
|
||||||
</abbr>
|
</abbr>
|
||||||
</span>
|
</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="item-key">
|
<span class="item-key">
|
||||||
<abbr title="{{ 'notification.sent_to'|trans }}">
|
<abbr title="{{ 'notification.sent_to' | trans }}">
|
||||||
{{ 'notification.to'|trans }} :
|
{{ "notification.to" | trans }} :
|
||||||
</abbr>
|
</abbr>
|
||||||
</span>
|
</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% for a in c.notification.addressees %}
|
{% for a in c.notification.addressees %}
|
||||||
<span class="badge-user">
|
<span class="badge-user">
|
||||||
{{ a|chill_entity_render_string({'at_date': c.notification.date}) }}
|
{{ a | chill_entity_render_string({'at_date': c.notification.date}) }}
|
||||||
</span>
|
</span>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% for a in c.notification.addressesEmails %}
|
{% for a in c.notification.addressesEmails %}
|
||||||
<span class="badge-user" title="{{ 'notification.Email with access link'|trans|e('html_attr') }}">
|
<span
|
||||||
|
class="badge-user"
|
||||||
|
title="{{ 'notification.Email with access link'|trans|e('html_attr') }}"
|
||||||
|
>
|
||||||
{{ a }}
|
{{ a }}
|
||||||
</span>
|
</span>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
@ -70,7 +76,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endmacro %}
|
{% endmacro %}
|
||||||
|
|
||||||
{% macro content(c) %}
|
{% macro content(c) %}
|
||||||
<div class="item-row separator">
|
<div class="item-row separator">
|
||||||
{% if c.data is defined %}
|
{% if c.data is defined %}
|
||||||
@ -83,27 +88,31 @@
|
|||||||
<div class="notification-content">
|
<div class="notification-content">
|
||||||
{% if c.full_content is defined and c.full_content == true %}
|
{% if c.full_content is defined and c.full_content == true %}
|
||||||
{% if c.notification.message is not empty %}
|
{% if c.notification.message is not empty %}
|
||||||
{{ c.notification.message|chill_markdown_to_html }}
|
{{ c.notification.message | chill_markdown_to_html }}
|
||||||
{% else %}
|
{% else %}
|
||||||
<p class="chill-no-data-statement">{{ 'Any comment'|trans }}</p>
|
<p class="chill-no-data-statement">{{ "Any comment" | trans }}</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% else %}
|
{% else %}
|
||||||
{% if c.notification.message is not empty %}
|
{% if c.notification.message is not empty %}
|
||||||
{{ c.notification.message|u.truncate(250, '…', false)|chill_markdown_to_html }}
|
{{ c.notification.message|u.truncate(250, '…', false)|chill_markdown_to_html }}
|
||||||
<p class="read-more"><a href="{{ chill_path_add_return_path('chill_main_notification_show', {'id': c.notification.id}) }}">{{ 'Read more'|trans }}</a></p>
|
<p class="read-more">
|
||||||
|
<a
|
||||||
|
href="{{ chill_path_add_return_path('chill_main_notification_show', {
|
||||||
|
id: c.notification.id
|
||||||
|
}) }}"
|
||||||
|
>{{ "Read more" | trans }}</a>
|
||||||
|
</p>
|
||||||
{% else %}
|
{% else %}
|
||||||
<p class="chill-no-data-statement">{{ 'Any comment'|trans }}</p>
|
<p class="chill-no-data-statement">{{ "Any comment" | trans }}</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endmacro %}
|
{% endmacro %}
|
||||||
|
|
||||||
{% macro actions(c) %}
|
{% macro actions(c) %}
|
||||||
{% if c.action_button is not defined or c.action_button != false %}
|
{% if c.action_button is not defined or c.action_button != false %}
|
||||||
<div class="item-row separator">
|
<div class="item-row separator">
|
||||||
<div class="item-col item-meta">
|
<div class="item-col item-meta">
|
||||||
|
|
||||||
{% if c.notification.comments|length > 0 %}
|
{% if c.notification.comments|length > 0 %}
|
||||||
<div class="comment-counter">
|
<div class="comment-counter">
|
||||||
<span class="counter">
|
<span class="counter">
|
||||||
@ -111,13 +120,13 @@
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="item-col">
|
<div class="item-col">
|
||||||
<ul class="record_actions">
|
<ul class="record_actions">
|
||||||
<li>
|
<li>
|
||||||
{# Vue component #}
|
{# Vue component #}
|
||||||
<span class="notification_toggle_read_status"
|
<span
|
||||||
|
class="notification_toggle_read_status"
|
||||||
data-notification-id="{{ c.notification.id }}"
|
data-notification-id="{{ c.notification.id }}"
|
||||||
data-notification-current-is-read="{{ c.notification.isReadBy(app.user) }}"
|
data-notification-current-is-read="{{ c.notification.isReadBy(app.user) }}"
|
||||||
data-container="notification-status"
|
data-container="notification-status"
|
||||||
@ -125,18 +134,31 @@
|
|||||||
</li>
|
</li>
|
||||||
{% if is_granted('CHILL_MAIN_NOTIFICATION_UPDATE', c.notification) %}
|
{% if is_granted('CHILL_MAIN_NOTIFICATION_UPDATE', c.notification) %}
|
||||||
<li>
|
<li>
|
||||||
<a href="{{ chill_path_add_return_path('chill_main_notification_edit', {'id': c.notification.id}) }}"
|
<a
|
||||||
class="btn btn-edit" title="{{ 'Edit'|trans }}"></a>
|
href="{{ chill_path_add_return_path(
|
||||||
|
'chill_main_notification_edit',
|
||||||
|
{ id: c.notification.id }
|
||||||
|
) }}"
|
||||||
|
class="btn btn-edit"
|
||||||
|
title="{{ 'Edit' | trans }}"
|
||||||
|
></a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if is_granted('CHILL_MAIN_NOTIFICATION_SEE', c.notification) %}
|
{% if is_granted('CHILL_MAIN_NOTIFICATION_SEE',
|
||||||
|
c.notification) %}
|
||||||
<li>
|
<li>
|
||||||
<a href="{{ chill_path_add_return_path('chill_main_notification_show', {'id': c.notification.id}) }}"
|
<a
|
||||||
class="btn {% if not c.notification.isSystem %}btn-show change-icon{% else %}btn-misc{% endif %}" title="{{ 'notification.see_comments_thread'|trans }}">
|
href="{{ chill_path_add_return_path(
|
||||||
|
'chill_main_notification_show',
|
||||||
|
{ id: c.notification.id }
|
||||||
|
) }}"
|
||||||
|
class="btn {% if not c.notification.isSystem %}btn-show change-icon{% else %}btn-misc{% endif %}"
|
||||||
|
title="{{ 'notification.see_comments_thread' | trans }}"
|
||||||
|
>
|
||||||
{% if not c.notification.isSystem() %}
|
{% if not c.notification.isSystem() %}
|
||||||
<i class="fa fa-comment"></i>
|
<i class="fa fa-comment"></i>
|
||||||
{% else %}
|
{% else %}
|
||||||
{{ 'Read more'|trans }}
|
{{ "Read more" | trans }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
@ -147,24 +169,30 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
{% endmacro %}
|
{% endmacro %}
|
||||||
|
|
||||||
<div class="item-bloc notification-status {% if notification.isReadBy(app.user) %}read{% else %}unread{% endif %}">
|
<div
|
||||||
|
class="item-bloc notification-status {% if notification.isReadBy(app.user) %}read{% else %}unread{% endif %}"
|
||||||
|
data-notification-id="{{ notification.id|escape('html_attr') }}"
|
||||||
|
>
|
||||||
{% if fold_item is defined and fold_item != false %}
|
{% if fold_item is defined and fold_item != false %}
|
||||||
<div class="accordion-header" id="flush-heading-{{ notification.id }}">
|
<div class="accordion-header" id="flush-heading-{{ notification.id }}">
|
||||||
<button type="button" class="accordion-button collapsed"
|
<button
|
||||||
data-bs-toggle="collapse" data-bs-target="#flush-collapse-{{ notification.id }}"
|
type="button"
|
||||||
aria-expanded="false" aria-controls="flush-collapse-{{ notification.id }}">
|
class="accordion-button collapsed"
|
||||||
|
data-bs-toggle="collapse"
|
||||||
|
data-bs-target="#flush-collapse-{{ notification.id }}"
|
||||||
|
aria-expanded="false"
|
||||||
|
aria-controls="flush-collapse-{{ notification.id }}"
|
||||||
|
>
|
||||||
{{ _self.title(_context) }}
|
{{ _self.title(_context) }}
|
||||||
</button>
|
</button>
|
||||||
{{ _self.header(_context) }}
|
{{ _self.header(_context) }}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div id="flush-collapse-{{ notification.id }}"
|
<div
|
||||||
|
id="flush-collapse-{{ notification.id }}"
|
||||||
class="accordion-collapse collapse"
|
class="accordion-collapse collapse"
|
||||||
aria-labelledby="flush-heading-{{ notification.id }}"
|
aria-labelledby="flush-heading-{{ notification.id }}"
|
||||||
data-bs-parent="#notification-fold">
|
data-bs-parent="#notification-fold"
|
||||||
|
>
|
||||||
{{ _self.content(_context) }}
|
{{ _self.content(_context) }}
|
||||||
</div>
|
</div>
|
||||||
{{ _self.actions(_context) }}
|
{{ _self.actions(_context) }}
|
||||||
@ -174,5 +202,4 @@
|
|||||||
{{ _self.content(_context) }}
|
{{ _self.content(_context) }}
|
||||||
{{ _self.actions(_context) }}
|
{{ _self.actions(_context) }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
@ -4,59 +4,75 @@
|
|||||||
|
|
||||||
{% block js %}
|
{% block js %}
|
||||||
{{ parent() }}
|
{{ parent() }}
|
||||||
{{ encore_entry_script_tags('mod_notification_toggle_read_status') }}
|
{{ encore_entry_script_tags("mod_notification_toggle_read_status") }}
|
||||||
|
{{ encore_entry_script_tags("mod_notification_toggle_read_all_status") }}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block css %}
|
{% block css %}
|
||||||
{{ parent() }}
|
{{ parent() }}
|
||||||
{{ encore_entry_link_tags('mod_notification_toggle_read_status') }}
|
{{ encore_entry_link_tags("mod_notification_toggle_read_status") }}
|
||||||
|
{{ encore_entry_link_tags("mod_notification_toggle_read_all_status") }}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="col-10 notification notification-list">
|
<div class="col-10 notification notification-list">
|
||||||
<h1>{{ block('title') }}</h1>
|
<h1>{{ block("title") }}</h1>
|
||||||
|
|
||||||
<ul class="nav nav-pills justify-content-center">
|
<ul class="nav nav-pills justify-content-center">
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link {% if step == 'inbox' %}active{% endif %}" href="{{ path('chill_main_notification_my') }}">
|
<a
|
||||||
{{ 'notification.Notifications received'|trans }}
|
class="nav-link {% if step == 'inbox' %}active{% endif %}"
|
||||||
|
href="{{ path('chill_main_notification_my') }}"
|
||||||
|
>
|
||||||
|
{{ "notification.Notifications received" | trans }}
|
||||||
{% if unreads['inbox'] > 0 %}
|
{% if unreads['inbox'] > 0 %}
|
||||||
<span class="badge rounded-pill bg-danger">
|
<span class="badge rounded-pill bg-danger">
|
||||||
{{ unreads['inbox'] }}
|
{{ unreads["inbox"] }}
|
||||||
</span>
|
</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link {% if step == 'sent' %}active{% endif %}" href="{{ path('chill_main_notification_sent') }}">
|
<a
|
||||||
{{ 'notification.Notifications sent'|trans }}
|
class="nav-link {% if step == 'sent' %}active{% endif %}"
|
||||||
|
href="{{ path('chill_main_notification_sent') }}"
|
||||||
|
>
|
||||||
|
{{ "notification.Notifications sent" | trans }}
|
||||||
{% if unreads['sent'] > 0 %}
|
{% if unreads['sent'] > 0 %}
|
||||||
<span class="badge rounded-pill bg-danger">
|
<span class="badge rounded-pill bg-danger">
|
||||||
{{ unreads['sent'] }}
|
{{ unreads["sent"] }}
|
||||||
</span>
|
</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
{% if datas|length == 0 %}
|
{% if datas|length == 0 %} {% if step == 'inbox' %}
|
||||||
{% if step == 'inbox' %}
|
<p class="chill-no-data-statement">
|
||||||
<p class="chill-no-data-statement">{{ 'notification.Any notification received'|trans }}</p>
|
{{ "notification.Any notification received" | trans }}
|
||||||
|
</p>
|
||||||
{% else %}
|
{% else %}
|
||||||
<p class="chill-no-data-statement">{{ 'notification.Any notification sent'|trans }}</p>
|
<p class="chill-no-data-statement">
|
||||||
|
{{ "notification.Any notification sent" | trans }}
|
||||||
|
</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="flex-table accordion accordion-flush" id="notification-fold">
|
<div class="flex-table accordion accordion-flush" id="notification-fold">
|
||||||
{% for data in datas %}
|
{% for data in datas %}
|
||||||
{% set notification = data.notification %}
|
{% set notification = data.notification %}
|
||||||
{% include '@ChillMain/Notification/_list_item.html.twig' with {
|
{% include '@ChillMain/Notification/_list_item.html.twig' with {
|
||||||
'fold_item': true,
|
'fold_item': true, 'notification_cc': data.template_data.notificationCc
|
||||||
'notification_cc': data.template_data.notificationCc is defined ? data.template_data.notificationCc : false
|
is defined ? data.template_data.notificationCc : false } %}
|
||||||
} %}
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{ chill_pagination(paginator) }}
|
{{ chill_pagination(paginator) }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
|
||||||
|
<ul class="record_actions sticky-form-buttons justify-content-end">
|
||||||
|
<li class="ml-auto d-flex align-items-center gap-2">
|
||||||
|
<span class="notification_all_read"></span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
@ -9,7 +9,7 @@ declare(strict_types=1);
|
|||||||
* the LICENSE file that was distributed with this source code.
|
* the LICENSE file that was distributed with this source code.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
namespace Repository;
|
namespace Chill\MainBundle\Tests\Repository;
|
||||||
|
|
||||||
use Chill\MainBundle\Entity\NewsItem;
|
use Chill\MainBundle\Entity\NewsItem;
|
||||||
use Chill\MainBundle\Repository\NewsItemRepository;
|
use Chill\MainBundle\Repository\NewsItemRepository;
|
||||||
|
@ -0,0 +1,95 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Chill is a software for social workers
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view
|
||||||
|
* the LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Chill\MainBundle\Tests\Repository;
|
||||||
|
|
||||||
|
use Chill\MainBundle\Entity\Notification;
|
||||||
|
use Chill\MainBundle\Entity\User;
|
||||||
|
use Chill\MainBundle\Repository\NotificationRepository;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*
|
||||||
|
* @coversNothing
|
||||||
|
*/
|
||||||
|
class NotificationRepositoryTest extends KernelTestCase
|
||||||
|
{
|
||||||
|
private EntityManagerInterface $entityManager;
|
||||||
|
private NotificationRepository $repository;
|
||||||
|
|
||||||
|
protected function setUp(): void
|
||||||
|
{
|
||||||
|
self::bootKernel();
|
||||||
|
$this->entityManager = static::$kernel->getContainer()->get('doctrine.orm.entity_manager');
|
||||||
|
$this->repository = new NotificationRepository($this->entityManager);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testMarkAllNotificationAsReadForUser(): void
|
||||||
|
{
|
||||||
|
$user = $this->entityManager->createQuery('SELECT u FROM '.User::class.' u')
|
||||||
|
->setMaxResults(1)->getSingleResult();
|
||||||
|
|
||||||
|
$notification = (new Notification())
|
||||||
|
->setRelatedEntityClass('\Dummy')
|
||||||
|
->setRelatedEntityId(0)
|
||||||
|
;
|
||||||
|
$notification->addAddressee($user)->markAsUnreadBy($user);
|
||||||
|
|
||||||
|
$this->entityManager->persist($notification);
|
||||||
|
$this->entityManager->flush();
|
||||||
|
$notification->markAsUnreadBy($user);
|
||||||
|
$this->entityManager->flush();
|
||||||
|
|
||||||
|
$this->entityManager->refresh($notification);
|
||||||
|
|
||||||
|
if ($notification->isReadBy($user)) {
|
||||||
|
throw new \LogicException('Notification should not be marked as read');
|
||||||
|
}
|
||||||
|
|
||||||
|
$notificationsIds = $this->repository->markAllNotificationAsReadForUser($user);
|
||||||
|
self::assertContains($notification->getId(), $notificationsIds);
|
||||||
|
|
||||||
|
$this->entityManager->clear();
|
||||||
|
|
||||||
|
$notification = $this->entityManager->find(Notification::class, $notification->getId());
|
||||||
|
|
||||||
|
self::assertTrue($notification->isReadBy($user));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testMarkAllNotificationAsUnreadForUser(): void
|
||||||
|
{
|
||||||
|
$user = $this->entityManager->createQuery('SELECT u FROM '.User::class.' u')
|
||||||
|
->setMaxResults(1)->getSingleResult();
|
||||||
|
|
||||||
|
$notification = (new Notification())
|
||||||
|
->setRelatedEntityClass('\Dummy')
|
||||||
|
->setRelatedEntityId(0)
|
||||||
|
;
|
||||||
|
$notification->addAddressee($user); // we do not mark the notification as unread by the user
|
||||||
|
|
||||||
|
$this->entityManager->persist($notification);
|
||||||
|
$this->entityManager->flush();
|
||||||
|
$notification->markAsReadBy($user);
|
||||||
|
$this->entityManager->flush();
|
||||||
|
|
||||||
|
$this->entityManager->refresh($notification);
|
||||||
|
|
||||||
|
if (!$notification->isReadBy($user)) {
|
||||||
|
throw new \LogicException('Notification should be marked as read');
|
||||||
|
}
|
||||||
|
|
||||||
|
$notificationsIds = $this->repository->markAllNotificationAsUnreadForUser($user, [$notification->getId()]);
|
||||||
|
|
||||||
|
self::assertContains($notification->getId(), $notificationsIds);
|
||||||
|
}
|
||||||
|
}
|
@ -165,7 +165,6 @@ components:
|
|||||||
endDate:
|
endDate:
|
||||||
$ref: "#/components/schemas/Date"
|
$ref: "#/components/schemas/Date"
|
||||||
|
|
||||||
|
|
||||||
paths:
|
paths:
|
||||||
/1.0/search.json:
|
/1.0/search.json:
|
||||||
get:
|
get:
|
||||||
@ -237,7 +236,7 @@ paths:
|
|||||||
minItems: 2
|
minItems: 2
|
||||||
maxItems: 2
|
maxItems: 2
|
||||||
postcode:
|
postcode:
|
||||||
$ref: '#/components/schemas/PostalCode'
|
$ref: "#/components/schemas/PostalCode"
|
||||||
steps:
|
steps:
|
||||||
type: string
|
type: string
|
||||||
street:
|
street:
|
||||||
@ -275,7 +274,7 @@ paths:
|
|||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/components/schemas/Address'
|
$ref: "#/components/schemas/Address"
|
||||||
404:
|
404:
|
||||||
description: "not found"
|
description: "not found"
|
||||||
401:
|
401:
|
||||||
@ -321,7 +320,7 @@ paths:
|
|||||||
minItems: 2
|
minItems: 2
|
||||||
maxItems: 2
|
maxItems: 2
|
||||||
postcode:
|
postcode:
|
||||||
$ref: '#/components/schemas/PostalCode'
|
$ref: "#/components/schemas/PostalCode"
|
||||||
steps:
|
steps:
|
||||||
type: string
|
type: string
|
||||||
street:
|
street:
|
||||||
@ -344,7 +343,6 @@ paths:
|
|||||||
400:
|
400:
|
||||||
description: "transition cannot be applyed"
|
description: "transition cannot be applyed"
|
||||||
|
|
||||||
|
|
||||||
/1.0/main/address/{id}/duplicate.json:
|
/1.0/main/address/{id}/duplicate.json:
|
||||||
post:
|
post:
|
||||||
tags:
|
tags:
|
||||||
@ -365,7 +363,7 @@ paths:
|
|||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/components/schemas/Address'
|
$ref: "#/components/schemas/Address"
|
||||||
404:
|
404:
|
||||||
description: "not found"
|
description: "not found"
|
||||||
401:
|
401:
|
||||||
@ -406,7 +404,7 @@ paths:
|
|||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/components/schemas/AddressReference'
|
$ref: "#/components/schemas/AddressReference"
|
||||||
404:
|
404:
|
||||||
description: "not found"
|
description: "not found"
|
||||||
401:
|
401:
|
||||||
@ -439,7 +437,7 @@ paths:
|
|||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/components/schemas/AddressReference'
|
$ref: "#/components/schemas/AddressReference"
|
||||||
404:
|
404:
|
||||||
description: "not found"
|
description: "not found"
|
||||||
401:
|
401:
|
||||||
@ -477,7 +475,7 @@ paths:
|
|||||||
code:
|
code:
|
||||||
type: string
|
type: string
|
||||||
country:
|
country:
|
||||||
$ref: '#/components/schemas/Country'
|
$ref: "#/components/schemas/Country"
|
||||||
responses:
|
responses:
|
||||||
401:
|
401:
|
||||||
description: "Unauthorized"
|
description: "Unauthorized"
|
||||||
@ -510,7 +508,7 @@ paths:
|
|||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/components/schemas/PostalCode'
|
$ref: "#/components/schemas/PostalCode"
|
||||||
404:
|
404:
|
||||||
description: "not found"
|
description: "not found"
|
||||||
401:
|
401:
|
||||||
@ -541,7 +539,7 @@ paths:
|
|||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/components/schemas/PostalCode'
|
$ref: "#/components/schemas/PostalCode"
|
||||||
404:
|
404:
|
||||||
description: "not found"
|
description: "not found"
|
||||||
400:
|
400:
|
||||||
@ -575,13 +573,12 @@ paths:
|
|||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/components/schemas/Country'
|
$ref: "#/components/schemas/Country"
|
||||||
404:
|
404:
|
||||||
description: "not found"
|
description: "not found"
|
||||||
401:
|
401:
|
||||||
description: "Unauthorized"
|
description: "Unauthorized"
|
||||||
|
|
||||||
|
|
||||||
/1.0/main/user.json:
|
/1.0/main/user.json:
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
@ -626,7 +623,7 @@ paths:
|
|||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/components/schemas/User'
|
$ref: "#/components/schemas/User"
|
||||||
404:
|
404:
|
||||||
description: "not found"
|
description: "not found"
|
||||||
401:
|
401:
|
||||||
@ -784,7 +781,7 @@ paths:
|
|||||||
id: 1
|
id: 1
|
||||||
class: 'Chill\PersonBundle\Entity\AccompanyingPeriod'
|
class: 'Chill\PersonBundle\Entity\AccompanyingPeriod'
|
||||||
roles:
|
roles:
|
||||||
- 'CHILL_PERSON_ACCOMPANYING_PERIOD_SEE'
|
- "CHILL_PERSON_ACCOMPANYING_PERIOD_SEE"
|
||||||
/1.0/main/notification/{id}/mark/read:
|
/1.0/main/notification/{id}/mark/read:
|
||||||
post:
|
post:
|
||||||
tags:
|
tags:
|
||||||
@ -823,6 +820,38 @@ paths:
|
|||||||
description: "accepted"
|
description: "accepted"
|
||||||
403:
|
403:
|
||||||
description: "unauthorized"
|
description: "unauthorized"
|
||||||
|
/1.0/main/notification/mark/allread:
|
||||||
|
post:
|
||||||
|
tags:
|
||||||
|
- notification
|
||||||
|
summary: Mark all notifications as read
|
||||||
|
responses:
|
||||||
|
202:
|
||||||
|
description: "accepted"
|
||||||
|
403:
|
||||||
|
description: "unauthorized"
|
||||||
|
/1.0/main/notification/mark/undoallread:
|
||||||
|
post: # Use POST method for creating resources
|
||||||
|
tags:
|
||||||
|
- notification
|
||||||
|
summary: Mark notifications as unread
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
ids:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: integer
|
||||||
|
example: [1, 2, 3] # Example array of IDs
|
||||||
|
responses:
|
||||||
|
"202":
|
||||||
|
description: Notifications marked as unread successfully
|
||||||
|
"403":
|
||||||
|
description: Unauthorized
|
||||||
/1.0/main/civility.json:
|
/1.0/main/civility.json:
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
@ -844,7 +873,7 @@ paths:
|
|||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/components/schemas/UserJob'
|
$ref: "#/components/schemas/UserJob"
|
||||||
/1.0/main/workflow/my:
|
/1.0/main/workflow/my:
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
@ -858,7 +887,7 @@ paths:
|
|||||||
schema:
|
schema:
|
||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
$ref: '#/components/schemas/Workflow'
|
$ref: "#/components/schemas/Workflow"
|
||||||
403:
|
403:
|
||||||
description: "Unauthorized"
|
description: "Unauthorized"
|
||||||
/1.0/main/workflow/my-cc:
|
/1.0/main/workflow/my-cc:
|
||||||
@ -874,7 +903,7 @@ paths:
|
|||||||
schema:
|
schema:
|
||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
$ref: '#/components/schemas/Workflow'
|
$ref: "#/components/schemas/Workflow"
|
||||||
403:
|
403:
|
||||||
description: "Unauthorized"
|
description: "Unauthorized"
|
||||||
/1.0/main/dashboard-config-item.json:
|
/1.0/main/dashboard-config-item.json:
|
||||||
@ -888,7 +917,7 @@ paths:
|
|||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/components/schemas/DashboardConfigItem'
|
$ref: "#/components/schemas/DashboardConfigItem"
|
||||||
403:
|
403:
|
||||||
description: "Unauthorized"
|
description: "Unauthorized"
|
||||||
|
|
||||||
@ -905,6 +934,6 @@ paths:
|
|||||||
schema:
|
schema:
|
||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
$ref: '#/components/schemas/NewsItem'
|
$ref: "#/components/schemas/NewsItem"
|
||||||
403:
|
403:
|
||||||
description: "Unauthorized"
|
description: "Unauthorized"
|
||||||
|
@ -70,6 +70,7 @@ module.exports = function(encore, entries)
|
|||||||
encore.addEntry('mod_blur', __dirname + '/Resources/public/module/blur/index.js');
|
encore.addEntry('mod_blur', __dirname + '/Resources/public/module/blur/index.js');
|
||||||
encore.addEntry('mod_input_address', __dirname + '/Resources/public/vuejs/Address/mod_input_address_index.js');
|
encore.addEntry('mod_input_address', __dirname + '/Resources/public/vuejs/Address/mod_input_address_index.js');
|
||||||
encore.addEntry('mod_notification_toggle_read_status', __dirname + '/Resources/public/module/notification/toggle_read.js');
|
encore.addEntry('mod_notification_toggle_read_status', __dirname + '/Resources/public/module/notification/toggle_read.js');
|
||||||
|
encore.addEntry('mod_notification_toggle_read_all_status', __dirname + '/Resources/public/module/notification/toggle_read_all.ts');
|
||||||
encore.addEntry('mod_pickentity_type', __dirname + '/Resources/public/module/pick-entity/index.js');
|
encore.addEntry('mod_pickentity_type', __dirname + '/Resources/public/module/pick-entity/index.js');
|
||||||
encore.addEntry('mod_entity_workflow_subscribe', __dirname + '/Resources/public/module/entity-workflow-subscribe/index.js');
|
encore.addEntry('mod_entity_workflow_subscribe', __dirname + '/Resources/public/module/entity-workflow-subscribe/index.js');
|
||||||
encore.addEntry('mod_entity_workflow_pick', __dirname + '/Resources/public/module/entity-workflow-pick/index.js');
|
encore.addEntry('mod_entity_workflow_pick', __dirname + '/Resources/public/module/entity-workflow-pick/index.js');
|
||||||
|
Loading…
x
Reference in New Issue
Block a user