Calendar: allow to subscribe/unsubscribe to personal events + improve info about invitations/subscriptions

pull/5266/head
Angel Fernando Quiroz Campos 2 years ago
parent b1afcb0251
commit 007514dd66
  1. 19
      assets/css/scss/_calendar.scss
  2. 2
      assets/css/scss/index.scss
  3. 25
      assets/vue/components/ccalendarevent/CCalendarEventInfo.vue
  4. 32
      assets/vue/components/ccalendarevent/CalendarEventInvitationsInfo.vue
  5. 69
      assets/vue/components/ccalendarevent/CalendarEventSubscriptionsInfo.vue
  6. 38
      assets/vue/composables/calendar/calendarEvent.js
  7. 6
      assets/vue/services/baseService.js
  8. 5
      assets/vue/services/resourceLinkService.js
  9. 41
      assets/vue/views/ccalendarevent/CCalendarEventList.vue
  10. 2
      src/CoreBundle/ApiResource/CalendarEvent.php
  11. 9
      src/CoreBundle/DataTransformer/CalendarEventTransformer.php
  12. 1
      src/CoreBundle/Entity/ResourceLink.php
  13. 6
      src/CoreBundle/Entity/ResourceNode.php

@ -0,0 +1,19 @@
.calendar-event-info {
@apply flex flex-col space-y-4;
.invitations-info {
@apply space-y-2;
&__title {
@apply text-gray-50 mb-3;
}
&__item {
@apply flex text-body-2 flex-row justify-between;
p {
@apply first:font-semibold;
}
}
}
}

@ -59,7 +59,7 @@
@import 'layout/main_container';
@import "admin_index";
@import "calendar";
@import "course_home";
@import "documents";

@ -1,5 +1,5 @@
<template>
<div class="flex flex-col space-y-4">
<div class="calendar-event-info">
<h5 v-text="event.title" />
<p v-text="abbreviatedDatetime(event.startDate)" />
@ -13,14 +13,19 @@
<div v-html="event.content" />
<div v-if="allowCollectiveInvitations && type.invitation === event.invitationType">
<h6 v-t="'Invitees'" />
<ShowLinks
:item="event"
:show-status="false"
/>
</div>
<CalendarEventSubscriptionsInfo
v-if="type.subscription === event.invitationType"
:event="event"
/>
<CalendarEventInvitationsInfo
v-else-if="type.invitation === event.invitationType"
:event="event"
/>
<ShowLinks
v-else
:item="event"
:show-status="false"
/>
</div>
</template>
@ -29,6 +34,8 @@ import { useFormatDate } from "../../composables/formatDate"
import ShowLinks from "../resource_links/ShowLinks"
import { useCalendarInvitations } from "../../composables/calendar/calendarInvitations"
import { type } from "../../constants/entity/ccalendarevent"
import CalendarEventSubscriptionsInfo from "./CalendarEventSubscriptionsInfo.vue"
import CalendarEventInvitationsInfo from "./CalendarEventInvitationsInfo.vue"
const { abbreviatedDatetime } = useFormatDate()
const { allowCollectiveInvitations } = useCalendarInvitations()

@ -0,0 +1,32 @@
<script setup>
import ShowLinks from "../resource_links/ShowLinks.vue"
defineProps({
event: {
type: Object,
required: true,
},
})
</script>
<template>
<div class="invitations-info">
<h6
v-t="'Invitations'"
class="invitations-info__title"
/>
<div
v-if="event.resourceLinkListFromEntity.length"
class="invitations-info__item"
>
<p v-t="'Invitees'" />
<div>
<ShowLinks
:item="event"
:show-status="false"
/>
</div>
</div>
</div>
</template>

@ -0,0 +1,69 @@
<script setup>
import ShowLinks from "../resource_links/ShowLinks.vue"
import { subscriptionVisibility } from "../../constants/entity/ccalendarevent"
defineProps({
event: {
type: Object,
required: true,
},
})
</script>
<template>
<div class="invitations-info">
<h6
v-t="'Subscriptions'"
class="invitations-info__title"
/>
<div class="invitations-info__item">
<p v-t="'Allow subscriptions'" />
<p
v-if="subscriptionVisibility.no === event.subscriptionVisibility"
v-text="'No'"
/>
<p
v-else-if="subscriptionVisibility.all === event.subscriptionVisibility"
v-text="'All system users'"
/>
<p
v-else-if="subscriptionVisibility.class === event.subscriptionVisibility"
v-text="'Users inside the class'"
/>
<p
v-if="subscriptionVisibility.class === event.subscriptionVisibility"
v-text="event.subscriptionItemTitle"
/>
</div>
<div
v-if="event.maxAttendees"
class="invitations-info__item"
>
<p v-t="'Maximum number of subscriptions'" />
<p v-text="event.maxAttendees" />
</div>
<div
v-if="event.maxAttendees"
class="invitations-info__item"
>
<p v-t="'Subscriptions count'" />
<p v-text="event.resourceLinkListFromEntity.length" />
</div>
<div
v-if="event.resourceLinkListFromEntity.length"
class="invitations-info__item"
>
<p v-t="'Subscribers'" />
<div>
<ShowLinks
:item="event"
:show-status="false"
/>
</div>
</div>
</div>
</template>

@ -1,9 +1,13 @@
import { type } from "../../constants/entity/ccalendarevent"
import { type, subscriptionVisibility } from "../../constants/entity/ccalendarevent"
export function useCalendarEvent() {
return {
findUserLink,
isEditableByUser,
isSubscribeable,
canSubscribeToEvent,
allowSubscribeToEvent,
allowUnsubscribeToEvent,
}
}
@ -36,3 +40,35 @@ function isEditableByUser(event, userId) {
return false
}
function isSubscribeable(event) {
if (type.subscription !== event.invitationType) {
return false
}
return subscriptionVisibility.no !== event.subscriptionVisibility
}
/**
* @param {Object} event
* @returns {boolean}
*/
function canSubscribeToEvent(event) {
return event.resourceLinkListFromEntity.length < event.maxAttendees || 0 === event.maxAttendees
}
function allowSubscribeToEvent(event) {
if (!isSubscribeable(event)) {
return false
}
return canSubscribeToEvent(event)
}
function allowUnsubscribeToEvent(event, userId) {
if (!isSubscribeable(event)) {
return false
}
return !!findUserLink(event, userId)
}

@ -8,6 +8,8 @@ async function find(iri) {
return await api.get(iri)
}
export {
find
async function post(params) {
return await api.post("/api/resource_links", params)
}
export { find, post }

@ -0,0 +1,5 @@
import { post } from "./baseService"
export default {
post,
}

@ -44,6 +44,7 @@
v-model:visible="dialogShow"
:header="t('Event')"
:modal="true"
:style="{ width: '30rem' }"
>
<CCalendarEventInfo :event="item" />
@ -54,6 +55,22 @@
type="black"
@click="dialogShow = false"
/>
<BaseButton
v-if="allowToUnsubscribe"
:label="t('Unsubscribe')"
type="black"
icon="join-group"
@click="unsubscribeToEvent"
/>
<BaseButton
v-else-if="allowToSubscribe"
:label="t('Subscribe')"
type="black"
icon="join-group"
@click="subscribeToEvent"
/>
<BaseButton
:label="t('Delete')"
icon="delete"
@ -130,6 +147,7 @@ import { storeToRefs } from "pinia"
import CalendarSectionHeader from "../../components/ccalendarevent/CalendarSectionHeader.vue"
import { useCalendarActionButtons } from "../../composables/calendar/calendarActionButtons"
import { useCalendarEvent } from "../../composables/calendar/calendarEvent"
import resourceLinkService from "../../services/resourceLinkService"
const store = useStore()
const confirm = useConfirm()
@ -141,12 +159,14 @@ const { abbreviatedDatetime } = useFormatDate()
const { showAddButton } = useCalendarActionButtons()
const { isEditableByUser } = useCalendarEvent()
const { isEditableByUser, allowSubscribeToEvent, allowUnsubscribeToEvent } = useCalendarEvent()
const item = ref({})
const dialog = ref(false)
const dialogShow = ref(false)
const allowToEdit = ref(false)
const allowToSubscribe = ref(false)
const allowToUnsubscribe = ref(false)
const currentUser = computed(() => store.getters["security/getUser"])
const { t } = useI18n()
@ -248,6 +268,8 @@ const calendarOptions = ref({
item.value["parentResourceNodeId"] = event.extendedProps.resourceNode.creator.id
allowToEdit.value = isEditableByUser(item.value, currentUser.value.id)
allowToSubscribe.value = !allowToEdit.value && allowSubscribeToEvent(item.value)
allowToUnsubscribe.value = !allowToEdit.value && allowUnsubscribeToEvent(item.value, currentUser.value.id)
dialogShow.value = true
},
@ -309,6 +331,23 @@ function confirmDelete() {
})
}
async function subscribeToEvent() {
try {
await resourceLinkService.post({
resourceNode: item.value.resourceNode["@id"],
user: currentUser.value["@id"],
visibility: RESOURCE_LINK_PUBLISHED,
})
allowToSubscribe.value = false
allowToUnsubscribe.value = true
} catch (e) {
console.error(e)
}
}
async function unsubscribeToEvent() {}
const isLoading = computed(() => store.getters["ccalendarevent/isLoading"])
const createForm = ref(null)

@ -37,6 +37,8 @@ class CalendarEvent extends AbstractResource
#[Groups(['calendar_event:read'])]
public ?int $subscriptionItemId = null,
#[Groups(['calendar_event:read'])]
public ?string $subscriptionItemTitle = null,
#[Groups(['calendar_event:read'])]
public int $maxAttendees = 0,
#[Groups(['calendar_event:read'])]
public ?ResourceNode $resourceNode = null,

@ -10,6 +10,7 @@ use ApiPlatform\Core\DataTransformer\DataTransformerInterface;
use Chamilo\CoreBundle\ApiResource\CalendarEvent;
use Chamilo\CoreBundle\Entity\Session;
use Chamilo\CoreBundle\Entity\SessionRelCourse;
use Chamilo\CoreBundle\Repository\Node\UsergroupRepository;
use Chamilo\CourseBundle\Entity\CCalendarEvent;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Routing\RouterInterface;
@ -18,6 +19,7 @@ class CalendarEventTransformer implements DataTransformerInterface
{
public function __construct(
private readonly RouterInterface $router,
private readonly UsergroupRepository $usergroupRepository,
) {}
public function transform($object, string $to, array $context = []): object
@ -40,6 +42,12 @@ class CalendarEventTransformer implements DataTransformerInterface
$object->setResourceLinkListFromEntity();
$subscriptionItemTitle = null;
if (CCalendarEvent::SUBSCRIPTION_VISIBILITY_CLASS == $object->getSubscriptionVisibility()) {
$subscriptionItemTitle = $this->usergroupRepository->find($object->getSubscriptionItemId())?->getTitle();
}
return new CalendarEvent(
'calendar_event_'.$object->getIid(),
$object->getTitle(),
@ -52,6 +60,7 @@ class CalendarEventTransformer implements DataTransformerInterface
$object->isCollective(),
$object->getSubscriptionVisibility(),
$object->getSubscriptionItemId(),
$subscriptionItemTitle,
$object->getMaxAttendees(),
$object->getResourceNode(),
$object->getResourceLinkListFromEntity(),

@ -259,6 +259,7 @@ class ResourceLink implements Stringable
public function setResourceNode(ResourceNode $resourceNode): self
{
$this->resourceNode = $resourceNode;
$this->resourceTypeGroup = $resourceNode->getResourceType()->getId();
return $this;
}

@ -469,10 +469,8 @@ class ResourceNode implements Stringable
public function addResourceLink(ResourceLink $link): self
{
$link
->setResourceNode($this)
->setResourceTypeGroup($this->resourceType->getId())
;
$link->setResourceNode($this);
$this->resourceLinks->add($link);
return $this;

Loading…
Cancel
Save