From 40a9159d375aff1cf68ce9a9c5626a63786240b8 Mon Sep 17 00:00:00 2001 From: Angel Fernando Quiroz Campos Date: Fri, 22 Mar 2024 02:42:34 -0500 Subject: [PATCH] Calendar: Allow save/edit reminders for events --- assets/css/scss/_calendar.scss | 16 ++++ .../components/basecomponents/ChamiloIcons.js | 2 + .../ccalendarevent/CCalendarEventForm.vue | 3 + .../ccalendarevent/CCalendarEventInfo.vue | 3 + .../CalendarRemindersEditor.vue | 74 +++++++++++++++++++ .../ccalendarevent/CalendarRemindersInfo.vue | 39 ++++++++++ .../composables/calendar/calendarReminders.js | 38 ++++++++++ src/CoreBundle/ApiResource/CalendarEvent.php | 10 ++- src/CoreBundle/Component/Utils/ActionIcon.php | 2 + src/CoreBundle/Component/Utils/ObjectIcon.php | 2 + .../Api/UpdateCCalendarEventAction.php | 22 +++++- .../PlatformConfigurationController.php | 2 +- .../CalendarEventTransformer.php | 11 ++- src/CoreBundle/Entity/AgendaReminder.php | 71 ++++++++++++++---- .../State/CCalendarEventProcessor.php | 13 ++++ src/CourseBundle/Entity/CCalendarEvent.php | 39 ++++++++++ 16 files changed, 327 insertions(+), 20 deletions(-) create mode 100644 assets/vue/components/ccalendarevent/CalendarRemindersEditor.vue create mode 100644 assets/vue/components/ccalendarevent/CalendarRemindersInfo.vue create mode 100644 assets/vue/composables/calendar/calendarReminders.js diff --git a/assets/css/scss/_calendar.scss b/assets/css/scss/_calendar.scss index 827430ff43..c9ab2cfd65 100644 --- a/assets/css/scss/_calendar.scss +++ b/assets/css/scss/_calendar.scss @@ -16,4 +16,20 @@ } } } + + .reminders-info { + @apply space-y-2; + + &__title { + @apply text-gray-50 mb-3; + } + + &__list { + @apply space-y-2; + } + + &__item { + @apply flex text-body-2 flex-row gap-2; + } + } } diff --git a/assets/vue/components/basecomponents/ChamiloIcons.js b/assets/vue/components/basecomponents/ChamiloIcons.js index bf46b0eb1a..72e437c3e7 100644 --- a/assets/vue/components/basecomponents/ChamiloIcons.js +++ b/assets/vue/components/basecomponents/ChamiloIcons.js @@ -118,4 +118,6 @@ export const chamiloIconToClass = { "map-search": "mdi mdi-map-search-outline", "join-group": "mdi mdi-account-multiple-plus", "add-topic": "mdi mdi-forum-outline", + "event-reminder": "mdi mdi-alarm", + "add-event-reminder": "mdi mdi-alarm-plus", }; diff --git a/assets/vue/components/ccalendarevent/CCalendarEventForm.vue b/assets/vue/components/ccalendarevent/CCalendarEventForm.vue index 4dd36e3677..49e270edc4 100644 --- a/assets/vue/components/ccalendarevent/CCalendarEventForm.vue +++ b/assets/vue/components/ccalendarevent/CCalendarEventForm.vue @@ -41,6 +41,8 @@ + + @@ -53,6 +55,7 @@ import BaseInputText from "../basecomponents/BaseInputText.vue" import { useI18n } from "vue-i18n" import BaseCalendar from "../basecomponents/BaseCalendar.vue" import CalendarInvitations from "./CalendarInvitations.vue" +import CalendarRemindersEditor from "./CalendarRemindersEditor.vue" const { t } = useI18n() diff --git a/assets/vue/components/ccalendarevent/CCalendarEventInfo.vue b/assets/vue/components/ccalendarevent/CCalendarEventInfo.vue index ff3a7dfdd4..a1d24dee14 100644 --- a/assets/vue/components/ccalendarevent/CCalendarEventInfo.vue +++ b/assets/vue/components/ccalendarevent/CCalendarEventInfo.vue @@ -26,6 +26,8 @@ :item="event" :show-status="false" /> + + @@ -36,6 +38,7 @@ import { useCalendarInvitations } from "../../composables/calendar/calendarInvit import { type } from "../../constants/entity/ccalendarevent" import CalendarEventSubscriptionsInfo from "./CalendarEventSubscriptionsInfo.vue" import CalendarEventInvitationsInfo from "./CalendarEventInvitationsInfo.vue" +import CalendarRemindersInfo from "./CalendarRemindersInfo.vue" const { abbreviatedDatetime } = useFormatDate() const { allowCollectiveInvitations } = useCalendarInvitations() diff --git a/assets/vue/components/ccalendarevent/CalendarRemindersEditor.vue b/assets/vue/components/ccalendarevent/CalendarRemindersEditor.vue new file mode 100644 index 0000000000..78b7f347a0 --- /dev/null +++ b/assets/vue/components/ccalendarevent/CalendarRemindersEditor.vue @@ -0,0 +1,74 @@ + + + diff --git a/assets/vue/components/ccalendarevent/CalendarRemindersInfo.vue b/assets/vue/components/ccalendarevent/CalendarRemindersInfo.vue new file mode 100644 index 0000000000..c1c481d8fa --- /dev/null +++ b/assets/vue/components/ccalendarevent/CalendarRemindersInfo.vue @@ -0,0 +1,39 @@ + + + diff --git a/assets/vue/composables/calendar/calendarReminders.js b/assets/vue/composables/calendar/calendarReminders.js new file mode 100644 index 0000000000..10cfc38c7d --- /dev/null +++ b/assets/vue/composables/calendar/calendarReminders.js @@ -0,0 +1,38 @@ +import { usePlatformConfig } from "../../store/platformConfig" +import { useI18n } from "vue-i18n" + +export function useCalendarReminders() { + const platformConfigStore = usePlatformConfig() + + const { t } = useI18n() + + const agendaRemindersEnabled = "true" === platformConfigStore.getSetting("agenda.agenda_reminders") + + const periodList = [ + { label: t("Minutes"), value: "i" }, + { label: t("Hours"), value: "h" }, + { label: t("Days"), value: "d" }, + ] + + /** + * @param {Object} reminder + * @returns {string} + */ + function decodeDateInterval(reminder) { + if (reminder.period === "i") { + return t("%d minutes before", [reminder.count]) + } + + if (reminder.period === "h") { + return t("%d hours before", [reminder.count]) + } + + return t("%d days before", [reminder.count]) + } + + return { + agendaRemindersEnabled, + periodList, + decodeDateInterval, + } +} diff --git a/src/CoreBundle/ApiResource/CalendarEvent.php b/src/CoreBundle/ApiResource/CalendarEvent.php index 74a189cb72..217c091b88 100644 --- a/src/CoreBundle/ApiResource/CalendarEvent.php +++ b/src/CoreBundle/ApiResource/CalendarEvent.php @@ -6,9 +6,11 @@ declare(strict_types=1); namespace Chamilo\CoreBundle\ApiResource; +use Chamilo\CoreBundle\Entity\AgendaReminder; use Chamilo\CoreBundle\Entity\ResourceNode; use Chamilo\CourseBundle\Entity\CCalendarEvent; use DateTime; +use Doctrine\Common\Collections\Collection; use Symfony\Component\Serializer\Annotation\Groups; class CalendarEvent extends AbstractResource @@ -40,15 +42,19 @@ class CalendarEvent extends AbstractResource public ?string $subscriptionItemTitle = null, #[Groups(['calendar_event:read'])] public int $maxAttendees = 0, + /** + * @var Collection + */ + #[Groups(['calendar_event:read'])] + public ?Collection $reminders = null, #[Groups(['calendar_event:read'])] public ?ResourceNode $resourceNode = null, - ?array $resourceLinkListFromEntity = null, + public ?array $resourceLinkListFromEntity = null, #[Groups(['calendar_event:read'])] public ?string $color = null, #[Groups(['calendar_event:read'])] public ?string $type = null, ) { - $this->resourceLinkListFromEntity = $resourceLinkListFromEntity; } public function getType(): ?string diff --git a/src/CoreBundle/Component/Utils/ActionIcon.php b/src/CoreBundle/Component/Utils/ActionIcon.php index 672ebcd174..a40e572022 100644 --- a/src/CoreBundle/Component/Utils/ActionIcon.php +++ b/src/CoreBundle/Component/Utils/ActionIcon.php @@ -158,4 +158,6 @@ enum ActionIcon: string case EXIT = 'exit-run'; // Edit badges/skills case EDIT_BADGE = 'shield-edit-outline'; + + case ADD_EVENT_REMINDER = 'alarm-plus'; } diff --git a/src/CoreBundle/Component/Utils/ObjectIcon.php b/src/CoreBundle/Component/Utils/ObjectIcon.php index aa25dcdb95..5c39a01c88 100644 --- a/src/CoreBundle/Component/Utils/ObjectIcon.php +++ b/src/CoreBundle/Component/Utils/ObjectIcon.php @@ -154,4 +154,6 @@ enum ObjectIcon: string case MAP_MARKER = 'map-marker'; // Sessions catalogue case CATALOGUE = 'bookmark-multiple-outline'; + + case EVENT_REMINDER = 'alarm'; } diff --git a/src/CoreBundle/Controller/Api/UpdateCCalendarEventAction.php b/src/CoreBundle/Controller/Api/UpdateCCalendarEventAction.php index 7fa9cdd0b8..0e00bb96d2 100644 --- a/src/CoreBundle/Controller/Api/UpdateCCalendarEventAction.php +++ b/src/CoreBundle/Controller/Api/UpdateCCalendarEventAction.php @@ -6,6 +6,8 @@ declare(strict_types=1); namespace Chamilo\CoreBundle\Controller\Api; +use Chamilo\CoreBundle\Entity\AgendaReminder; +use Chamilo\CoreBundle\Settings\SettingsManager; use Chamilo\CourseBundle\Entity\CCalendarEvent; use Chamilo\CourseBundle\Repository\CCalendarEventRepository; use DateTime; @@ -18,7 +20,8 @@ class UpdateCCalendarEventAction extends BaseResourceFileAction CCalendarEvent $calendarEvent, Request $request, CCalendarEventRepository $repo, - EntityManager $em + EntityManager $em, + SettingsManager $settingsManager, ): CCalendarEvent { $this->handleUpdateRequest($calendarEvent, $repo, $request, $em); @@ -34,6 +37,23 @@ class UpdateCCalendarEventAction extends BaseResourceFileAction ->setCollective($result['collective'] ?? false) ; + if ('true' === $settingsManager->getSetting('agenda.agenda_reminders')) { + $calendarEvent->getReminders()->clear(); + + foreach ($result['reminders'] as $reminderInfo) { + $reminder = new AgendaReminder(); + $reminder->count = $reminderInfo['count']; + $reminder->period = $reminderInfo['period']; + + $reminder + ->setType('') + ->decodeDateInterval() + ; + + $calendarEvent->addReminder($reminder); + } + } + return $calendarEvent; } } diff --git a/src/CoreBundle/Controller/PlatformConfigurationController.php b/src/CoreBundle/Controller/PlatformConfigurationController.php index f15a55f459..cf9ef6a3e6 100644 --- a/src/CoreBundle/Controller/PlatformConfigurationController.php +++ b/src/CoreBundle/Controller/PlatformConfigurationController.php @@ -58,7 +58,7 @@ class PlatformConfigurationController extends AbstractController 'agenda.allow_personal_agenda', 'agenda.personal_calendar_show_sessions_occupation', - // 'agenda.agenda_reminders', + 'agenda.agenda_reminders', 'agenda.agenda_collective_invitations', 'agenda.agenda_event_subscriptions', diff --git a/src/CoreBundle/DataTransformer/CalendarEventTransformer.php b/src/CoreBundle/DataTransformer/CalendarEventTransformer.php index 4b2a25e017..355c5ac506 100644 --- a/src/CoreBundle/DataTransformer/CalendarEventTransformer.php +++ b/src/CoreBundle/DataTransformer/CalendarEventTransformer.php @@ -8,6 +8,7 @@ namespace Chamilo\CoreBundle\DataTransformer; use ApiPlatform\Core\DataTransformer\DataTransformerInterface; use Chamilo\CoreBundle\ApiResource\CalendarEvent; +use Chamilo\CoreBundle\Entity\AgendaReminder; use Chamilo\CoreBundle\Entity\Session; use Chamilo\CoreBundle\Entity\SessionRelCourse; use Chamilo\CoreBundle\Repository\Node\UsergroupRepository; @@ -23,7 +24,7 @@ class CalendarEventTransformer implements DataTransformerInterface private readonly RouterInterface $router, private readonly UsergroupRepository $usergroupRepository, private readonly CCalendarEventRepository $calendarEventRepository, - private readonly SettingsManager $settingsManager + private readonly SettingsManager $settingsManager, ) {} public function transform($object, string $to, array $context = []): object @@ -69,12 +70,20 @@ class CalendarEventTransformer implements DataTransformerInterface $object->getSubscriptionItemId(), $subscriptionItemTitle, $object->getMaxAttendees(), + null, $object->getResourceNode(), $object->getResourceLinkListFromEntity(), $color ); + $calendarEvent->setType($eventType); + if ('true' === $this->settingsManager->getSetting('agenda.agenda_reminders')) { + $object->getReminders()->forAll(fn(int $i, AgendaReminder $reminder) => $reminder->encodeDateInterval()); + + $calendarEvent->reminders = $object->getReminders(); + } + return $calendarEvent; } diff --git a/src/CoreBundle/Entity/AgendaReminder.php b/src/CoreBundle/Entity/AgendaReminder.php index 94a4041ed1..ab78c12e18 100644 --- a/src/CoreBundle/Entity/AgendaReminder.php +++ b/src/CoreBundle/Entity/AgendaReminder.php @@ -7,8 +7,10 @@ declare(strict_types=1); namespace Chamilo\CoreBundle\Entity; use Chamilo\CoreBundle\Traits\TimestampableTypedEntity; +use Chamilo\CourseBundle\Entity\CCalendarEvent; use DateInterval; use Doctrine\ORM\Mapping as ORM; +use Symfony\Component\Serializer\Annotation\Groups; #[ORM\Entity] #[ORM\Table(name: 'agenda_reminder')] @@ -19,20 +21,28 @@ class AgendaReminder #[ORM\Id] #[ORM\Column(type: 'integer')] #[ORM\GeneratedValue(strategy: 'AUTO')] + #[Groups(['calendar_event:read'])] protected ?int $id = null; #[ORM\Column(name: 'type', type: 'string')] protected string $type; - #[ORM\Column(name: 'event_id', type: 'integer')] - protected int $eventId; - #[ORM\Column(name: 'date_interval', type: 'dateinterval')] protected DateInterval $dateInterval; #[ORM\Column(name: 'sent', type: 'boolean')] protected bool $sent; + #[Groups(['calendar_event:write', 'calendar_event:read'])] + public int $count; + + #[Groups(['calendar_event:write', 'calendar_event:read'])] + public string $period; + + #[ORM\ManyToOne(inversedBy: 'reminders')] + #[ORM\JoinColumn(referencedColumnName: 'iid', nullable: false)] + private ?CCalendarEvent $event = null; + public function __construct() { $this->sent = false; @@ -55,18 +65,6 @@ class AgendaReminder return $this; } - public function getEventId(): int - { - return $this->eventId; - } - - public function setEventId(int $eventId): self - { - $this->eventId = $eventId; - - return $this; - } - public function getDateInterval(): DateInterval { return $this->dateInterval; @@ -90,4 +88,47 @@ class AgendaReminder return $this; } + + public function getEvent(): ?CCalendarEvent + { + return $this->event; + } + + public function setEvent(?CCalendarEvent $event): static + { + $this->event = $event; + + return $this; + } + + public function decodeDateInterval(): static + { + $this->dateInterval = match ($this->period) { + 'i' => DateInterval::createFromDateString("{$this->count} minutes"), + 'h' => DateInterval::createFromDateString("{$this->count} hours"), + 'd' => DateInterval::createFromDateString("{$this->count} days"), + default => null, + }; + + return $this; + } + + public function encodeDateInterval(): static + { + if ($this->dateInterval->i) { + $this->count = $this->dateInterval->i; + $this->period = 'i'; + } elseif ($this->dateInterval->h) { + $this->count = $this->dateInterval->h; + $this->period = 'h'; + } elseif ($this->dateInterval->d) { + $this->count = $this->dateInterval->d; + $this->period = 'd'; + } else { + $this->count = (int) $this->dateInterval->format('%a'); + $this->period = 'd'; + } + + return $this; + } } diff --git a/src/CoreBundle/State/CCalendarEventProcessor.php b/src/CoreBundle/State/CCalendarEventProcessor.php index 7f7254cce3..59b99113ab 100644 --- a/src/CoreBundle/State/CCalendarEventProcessor.php +++ b/src/CoreBundle/State/CCalendarEventProcessor.php @@ -8,7 +8,9 @@ namespace Chamilo\CoreBundle\State; use ApiPlatform\Metadata\Operation; use ApiPlatform\State\ProcessorInterface; +use Chamilo\CoreBundle\Entity\AgendaReminder; use Chamilo\CoreBundle\Entity\User; +use Chamilo\CoreBundle\Settings\SettingsManager; use Chamilo\CourseBundle\Entity\CCalendarEvent; use Exception; use Symfony\Component\Security\Core\Security; @@ -21,6 +23,7 @@ class CCalendarEventProcessor implements ProcessorInterface public function __construct( private readonly ProcessorInterface $persistProcessor, private readonly Security $security, + private readonly SettingsManager $settingsManager, ) {} /** @@ -43,6 +46,16 @@ class CCalendarEventProcessor implements ProcessorInterface } } + if ('true' === $this->settingsManager->getSetting('agenda.agenda_reminders')) { + $data->getReminders()->forAll(function (int $i, AgendaReminder $reminder) { + $reminder->setType(''); + + return $reminder->decodeDateInterval(); + }); + } else { + $data->getReminders()->clear(); + } + /** @var CCalendarEvent $result */ return $this->persistProcessor->process($data, $operation, $uriVariables, $context); } diff --git a/src/CourseBundle/Entity/CCalendarEvent.php b/src/CourseBundle/Entity/CCalendarEvent.php index 1de8928128..4c5fd56aa6 100644 --- a/src/CourseBundle/Entity/CCalendarEvent.php +++ b/src/CourseBundle/Entity/CCalendarEvent.php @@ -18,6 +18,7 @@ use ApiPlatform\Metadata\Put; use Chamilo\CoreBundle\ApiResource\CalendarEvent; use Chamilo\CoreBundle\Controller\Api\UpdateCCalendarEventAction; use Chamilo\CoreBundle\Entity\AbstractResource; +use Chamilo\CoreBundle\Entity\AgendaReminder; use Chamilo\CoreBundle\Entity\ResourceInterface; use Chamilo\CoreBundle\Entity\Room; use Chamilo\CoreBundle\Filter\CidFilter; @@ -167,6 +168,13 @@ class CCalendarEvent extends AbstractResource implements ResourceInterface, Stri #[ORM\Column(name: 'max_attendees', type: 'integer')] protected int $maxAttendees = 0; + /** + * @var Collection + */ + #[Groups(['calendar_event:write'])] + #[ORM\OneToMany(mappedBy: 'event', targetEntity: AgendaReminder::class, cascade: ['persist'], orphanRemoval: true)] + private Collection $reminders; + public function __construct() { $this->children = new ArrayCollection(); @@ -174,6 +182,7 @@ class CCalendarEvent extends AbstractResource implements ResourceInterface, Stri $this->repeatEvents = new ArrayCollection(); $this->allDay = false; $this->collective = false; + $this->reminders = new ArrayCollection(); } public function __toString(): string @@ -435,4 +444,34 @@ class CCalendarEvent extends AbstractResource implements ResourceInterface, Stri return $this; } + + /** + * @return Collection + */ + public function getReminders(): Collection + { + return $this->reminders; + } + + public function addReminder(AgendaReminder $reminder): static + { + if (!$this->reminders->contains($reminder)) { + $this->reminders->add($reminder); + $reminder->setEvent($this); + } + + return $this; + } + + public function removeReminder(AgendaReminder $reminder): static + { + if ($this->reminders->removeElement($reminder)) { + // set the owning side to null (unless already changed) + if ($reminder->getEvent() === $this) { + $reminder->setEvent(null); + } + } + + return $this; + } }