Calendar: Allow save/edit reminders for events

pull/5322/head
Angel Fernando Quiroz Campos 2 years ago
parent 248f4888b6
commit 40a9159d37
  1. 16
      assets/css/scss/_calendar.scss
  2. 2
      assets/vue/components/basecomponents/ChamiloIcons.js
  3. 3
      assets/vue/components/ccalendarevent/CCalendarEventForm.vue
  4. 3
      assets/vue/components/ccalendarevent/CCalendarEventInfo.vue
  5. 74
      assets/vue/components/ccalendarevent/CalendarRemindersEditor.vue
  6. 39
      assets/vue/components/ccalendarevent/CalendarRemindersInfo.vue
  7. 38
      assets/vue/composables/calendar/calendarReminders.js
  8. 10
      src/CoreBundle/ApiResource/CalendarEvent.php
  9. 2
      src/CoreBundle/Component/Utils/ActionIcon.php
  10. 2
      src/CoreBundle/Component/Utils/ObjectIcon.php
  11. 22
      src/CoreBundle/Controller/Api/UpdateCCalendarEventAction.php
  12. 2
      src/CoreBundle/Controller/PlatformConfigurationController.php
  13. 11
      src/CoreBundle/DataTransformer/CalendarEventTransformer.php
  14. 71
      src/CoreBundle/Entity/AgendaReminder.php
  15. 13
      src/CoreBundle/State/CCalendarEventProcessor.php
  16. 39
      src/CourseBundle/Entity/CCalendarEvent.php

@ -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;
}
}
}

@ -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",
};

@ -41,6 +41,8 @@
</div>
<CalendarInvitations v-model="item" />
<CalendarRemindersEditor v-model="item" />
<slot />
</form>
</template>
@ -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()

@ -26,6 +26,8 @@
:item="event"
:show-status="false"
/>
<CalendarRemindersInfo :event="event" />
</div>
</template>
@ -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()

@ -0,0 +1,74 @@
<script setup>
import { useI18n } from "vue-i18n"
import BaseButton from "../basecomponents/BaseButton.vue"
import Fieldset from "primevue/fieldset"
import InputNumber from "primevue/inputnumber"
import Dropdown from "primevue/dropdown"
import { useCalendarReminders } from "../../composables/calendar/calendarReminders"
const { t } = useI18n()
const { agendaRemindersEnabled, periodList } = useCalendarReminders()
const model = defineModel({
type: Object,
})
model.value.reminders = model.value.reminders || []
function addEmptyReminder() {
model.value.reminders.push({
count: 0,
period: "i",
})
}
</script>
<template>
<Fieldset
v-if="agendaRemindersEnabled"
:legend="t('Reminders')"
>
<div class="reminder-list space-y-4">
<BaseButton
:label="t('Add reminder')"
icon="add-event-reminder"
type="black"
@click="addEmptyReminder"
/>
<div
v-for="(reminderItem, i) in model.reminders"
:key="i"
class="flex flex-row gap-4"
>
<div class="p-inputgroup">
<InputNumber
v-model="reminderItem.count"
:min="0"
:step="1"
class="w-20"
/>
<Dropdown
v-model="reminderItem.period"
:options="periodList"
option-label="label"
option-value="value"
/>
<div
v-t="'Before'"
class="p-inputgroup-addon"
/>
</div>
<BaseButton
:label="t('Delete')"
icon="delete"
only-icon
type="danger"
@click="model.reminders.splice(i, 1)"
/>
</div>
</div>
</Fieldset>
</template>

@ -0,0 +1,39 @@
<script setup>
import { useCalendarReminders } from "../../composables/calendar/calendarReminders"
import BaseIcon from "../basecomponents/BaseIcon.vue"
const { decodeDateInterval } = useCalendarReminders()
defineProps({
event: {
type: Object,
required: true,
},
})
</script>
<template>
<div
v-if="event.reminders && event.reminders.length > 0"
class="reminders-info"
>
<h6
v-t="'Notification to remind the event'"
class="reminders-info__title"
/>
<ul class="reminders-info__list">
<li
v-for="(reminder, i) in event.reminders"
:key="i"
class="reminders-info__item"
>
<BaseIcon
icon="event-reminder"
size="small"
/>
{{ decodeDateInterval(reminder) }}
</li>
</ul>
</div>
</template>

@ -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,
}
}

@ -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<int, AgendaReminder>
*/
#[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

@ -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';
}

@ -154,4 +154,6 @@ enum ObjectIcon: string
case MAP_MARKER = 'map-marker';
// Sessions catalogue
case CATALOGUE = 'bookmark-multiple-outline';
case EVENT_REMINDER = 'alarm';
}

@ -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;
}
}

@ -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',

@ -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;
}

@ -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;
}
}

@ -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);
}

@ -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<int, AgendaReminder>
*/
#[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<int, AgendaReminder>
*/
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;
}
}

Loading…
Cancel
Save