Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Ability to render groups as attendees #5396

Merged
merged 1 commit into from
Nov 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
211 changes: 114 additions & 97 deletions src/components/Editor/AvatarParticipationStatus.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
<!--
- @copyright Copyright (c) 2019 Georg Ehrke <oc.list@georgehrke.com>
- @copyright Copyright (c) 2023 Jonas Heinrich <heinrich@synyx.net>
-
- @author Georg Ehrke <oc.list@georgehrke.com>
- @author Jonas Heinrich <heinrich@synyx.net>

-
- @license AGPL-3.0-or-later
-
Expand All @@ -22,110 +25,119 @@

<template>
<div class="avatar-participation-status">
<Avatar :disable-tooltip="true"
<Avatar v-if="isGroup">
<template #icon>
<AccountMultiple :size="28" />
</template>
</Avatar>
<Avatar v-else
:disable-tooltip="true"
:user="commonName"
:display-name="commonName"
:is-no-user="true" />
<template v-if="participationStatus === 'ACCEPTED' && isViewedByOrganizer">
<IconCheck class="avatar-participation-status__indicator"
fill-color="#32CD32"
:size="20" />
<div class="avatar-participation-status__text">
{{ t('calendar', 'Invitation accepted') }}
</div>
</template>
<template v-else-if="isResource && participationStatus === 'ACCEPTED'">
<IconCheck class="avatar-participation-status__indicator"
fill-color="#32CD32"
:size="20" />
<div class="avatar-participation-status__text">
{{ t('calendar', 'Available') }}
</div>
</template>
<template v-else-if="isSuggestion">
<IconCheck class="avatar-participation-status__indicator"
fill-color="#32CD32"
:size="20" />
<div class="avatar-participation-status__text">
{{ t('calendar', 'Suggested') }}
</div>
</template>
<template v-else-if="participationStatus === 'TENTATIVE'">
<IconCheck class="avatar-participation-status__indicator"
fill-color="#32CD32"
:size="20" />
<div class="avatar-participation-status__text">
{{ t('calendar', 'Participation marked as tentative') }}
</div>
</template>
<template v-else-if="participationStatus === 'ACCEPTED' && !isViewedByOrganizer">
<IconCheck class="avatar-participation-status__indicator"
fill-color="#32CD32"
:size="20" />
<div class="avatar-participation-status__text">
{{ t('calendar', 'Accepted {organizerName}\'s invitation', {
organizerName: organizerDisplayName,
}) }}
</div>
</template>
<template v-else-if="isResource && participationStatus === 'DECLINED'">
<IconClose class="avatar-participation-status__indicator"
:size="20" />
<div class="avatar-participation-status__text">
{{ t('calendar', 'Not available') }}
</div>
</template>
<template v-else-if="participationStatus === 'DECLINED' && isViewedByOrganizer">
<IconClose class="avatar-participation-status__indicator"
:size="20" />
<div class="avatar-participation-status__text">
{{ t('calendar', 'Invitation declined') }}
</div>
</template>
<template v-else-if="participationStatus === 'DECLINED' && !isViewedByOrganizer">
<IconClose class="avatar-participation-status__indicator"
:size="20" />
<div class="avatar-participation-status__text">
{{ t('calendar', 'Declined {organizerName}\'s invitation', {
organizerName: organizerDisplayName,
}) }}
</div>
</template>
<template v-else-if="participationStatus === 'DELEGATED'">
<IconDelegated class="avatar-participation-status__indicator"
:size="20" />
<div class="avatar-participation-status__text">
{{ t('calendar', 'Invitation is delegated') }}
</div>
</template>
<template v-else-if="isResource">
<IconNoResponse class="avatar-participation-status__indicator"
:size="20" />
<div class="avatar-participation-status__text">
{{ t('calendar', 'Checking availability') }}
</div>
</template>
<template v-else-if="isViewedByOrganizer">
<IconNoResponse class="avatar-participation-status__indicator"
:size="20" />
<div class="avatar-participation-status__text">
{{ t('calendar', 'Invitation sent') }}
</div>
</template>
<template v-else>
<IconNoResponse class="avatar-participation-status__indicator"
:size="20" />
<div class="avatar-participation-status__text">
{{ t('calendar', 'Has not responded to {organizerName}\'s invitation yet', {
organizerName: organizerDisplayName,
}) }}
</div>
<template v-if="!isGroup">
<template v-if="participationStatus === 'ACCEPTED' && isViewedByOrganizer">
<IconCheck class="avatar-participation-status__indicator"
fill-color="#32CD32"
:size="20" />
<div class="avatar-participation-status__text">
{{ t('calendar', 'Invitation accepted') }}
</div>
</template>
<template v-else-if="isResource && participationStatus === 'ACCEPTED'">
<IconCheck class="avatar-participation-status__indicator"
fill-color="#32CD32"
:size="20" />
<div class="avatar-participation-status__text">
{{ t('calendar', 'Available') }}
</div>
</template>
<template v-else-if="isSuggestion">
<IconCheck class="avatar-participation-status__indicator"
fill-color="#32CD32"
:size="20" />
<div class="avatar-participation-status__text">
{{ t('calendar', 'Suggested') }}

Check warning on line 60 in src/components/Editor/AvatarParticipationStatus.vue

View check run for this annotation

Codecov / codecov/patch

src/components/Editor/AvatarParticipationStatus.vue#L56-L60

Added lines #L56 - L60 were not covered by tests
</div>
</template>
<template v-else-if="participationStatus === 'TENTATIVE'">
<IconCheck class="avatar-participation-status__indicator"
fill-color="#32CD32"

Check warning on line 65 in src/components/Editor/AvatarParticipationStatus.vue

View check run for this annotation

Codecov / codecov/patch

src/components/Editor/AvatarParticipationStatus.vue#L65

Added line #L65 was not covered by tests
:size="20" />
<div class="avatar-participation-status__text">
{{ t('calendar', 'Participation marked as tentative') }}
</div>
</template>
<template v-else-if="participationStatus === 'ACCEPTED' && !isViewedByOrganizer">

Check warning on line 71 in src/components/Editor/AvatarParticipationStatus.vue

View check run for this annotation

Codecov / codecov/patch

src/components/Editor/AvatarParticipationStatus.vue#L70-L71

Added lines #L70 - L71 were not covered by tests
<IconCheck class="avatar-participation-status__indicator"
fill-color="#32CD32"
:size="20" />
<div class="avatar-participation-status__text">
{{ t('calendar', 'Accepted {organizerName}\'s invitation', {
organizerName: organizerDisplayName,
}) }}
</div>
</template>
<template v-else-if="isResource && participationStatus === 'DECLINED'">

Check warning on line 81 in src/components/Editor/AvatarParticipationStatus.vue

View check run for this annotation

Codecov / codecov/patch

src/components/Editor/AvatarParticipationStatus.vue#L81

Added line #L81 was not covered by tests
<IconClose class="avatar-participation-status__indicator"
:size="20" />
<div class="avatar-participation-status__text">
{{ t('calendar', 'Not available') }}
</div>
</template>
<template v-else-if="participationStatus === 'DECLINED' && isViewedByOrganizer">
<IconClose class="avatar-participation-status__indicator"
:size="20" />
<div class="avatar-participation-status__text">
{{ t('calendar', 'Invitation declined') }}
</div>

Check warning on line 93 in src/components/Editor/AvatarParticipationStatus.vue

View check run for this annotation

Codecov / codecov/patch

src/components/Editor/AvatarParticipationStatus.vue#L91-L93

Added lines #L91 - L93 were not covered by tests
</template>
<template v-else-if="participationStatus === 'DECLINED' && !isViewedByOrganizer">
<IconClose class="avatar-participation-status__indicator"
:size="20" />
<div class="avatar-participation-status__text">
{{ t('calendar', 'Declined {organizerName}\'s invitation', {
organizerName: organizerDisplayName,
}) }}
</div>
</template>
<template v-else-if="participationStatus === 'DELEGATED'">
<IconDelegated class="avatar-participation-status__indicator"
:size="20" />
<div class="avatar-participation-status__text">
{{ t('calendar', 'Invitation is delegated') }}
</div>
</template>
<template v-else-if="isResource">
<IconNoResponse class="avatar-participation-status__indicator"

Check warning on line 112 in src/components/Editor/AvatarParticipationStatus.vue

View check run for this annotation

Codecov / codecov/patch

src/components/Editor/AvatarParticipationStatus.vue#L111-L112

Added lines #L111 - L112 were not covered by tests
:size="20" />
<div class="avatar-participation-status__text">
{{ t('calendar', 'Checking availability') }}
</div>
</template>
<template v-else-if="isViewedByOrganizer">
<IconNoResponse class="avatar-participation-status__indicator"
:size="20" />
<div class="avatar-participation-status__text">
{{ t('calendar', 'Invitation sent') }}
</div>
</template>
<template v-else>
<IconNoResponse class="avatar-participation-status__indicator"
:size="20" />
<div class="avatar-participation-status__text">
{{ t('calendar', 'Has not responded to {organizerName}\'s invitation yet', {
organizerName: organizerDisplayName,
}) }}

Check warning on line 131 in src/components/Editor/AvatarParticipationStatus.vue

View check run for this annotation

Codecov / codecov/patch

src/components/Editor/AvatarParticipationStatus.vue#L130-L131

Added lines #L130 - L131 were not covered by tests
</div>
</template>
</template>
</div>
</template>

<script>
import { NcAvatar as Avatar } from '@nextcloud/vue'
import AccountMultiple from 'vue-material-design-icons/AccountMultiple.vue'
import IconCheck from 'vue-material-design-icons/CheckCircle.vue'
import IconNoResponse from 'vue-material-design-icons/HelpCircle.vue'
import IconClose from 'vue-material-design-icons/CloseCircle.vue'
Expand All @@ -134,8 +146,9 @@
export default {
name: 'AvatarParticipationStatus',
components: {
Avatar,
IconCheck,
Avatar,
AccountMultiple,

Check warning on line 150 in src/components/Editor/AvatarParticipationStatus.vue

View check run for this annotation

Codecov / codecov/patch

src/components/Editor/AvatarParticipationStatus.vue#L149-L150

Added lines #L149 - L150 were not covered by tests
IconCheck,
IconNoResponse,
IconClose,
IconDelegated,
Expand All @@ -162,6 +175,10 @@
type: Boolean,
required: true,
},
isGroup: {
type: Boolean,
required: false,
},
isSuggestion: {
type: Boolean,
default: false,
Expand Down
56 changes: 55 additions & 1 deletion src/components/Editor/Invitees/InviteesList.vue
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
<!--
- @copyright Copyright (c) 2019 Georg Ehrke <oc.list@georgehrke.com>
- @copyright Copyright (c) 2023 Jonas Heinrich <heinrich@synyx.net>
-
- @author Georg Ehrke <oc.list@georgehrke.com>
- @author Richard Steinmetz <richard@steinmetz.cloud>
- @author Jonas Heinrich <heinrich@synyx.net>
-
- @license AGPL-3.0-or-later
-
Expand Down Expand Up @@ -34,6 +36,7 @@
:attendee="invitee"
:is-read-only="isReadOnly || isSharedWithMe"
:organizer-display-name="organizerDisplayName"
:members="invitee.members"
@remove-attendee="removeAttendee" />
<NoAttendeesView v-if="isReadOnly && isListEmpty"
:message="noInviteesMessage" />
Expand Down Expand Up @@ -129,13 +132,51 @@
return !['RESOURCE', 'ROOM'].includes(attendee.attendeeProperty.userType)
})
},
groups() {
return this.calendarObjectInstance.attendees.filter(attendee => {

Check warning on line 136 in src/components/Editor/Invitees/InviteesList.vue

View check run for this annotation

Codecov / codecov/patch

src/components/Editor/Invitees/InviteesList.vue#L135-L136

Added lines #L135 - L136 were not covered by tests
return attendee.attendeeProperty.userType === 'GROUP'
})

Check warning on line 138 in src/components/Editor/Invitees/InviteesList.vue

View check run for this annotation

Codecov / codecov/patch

src/components/Editor/Invitees/InviteesList.vue#L138

Added line #L138 was not covered by tests
},
inviteesWithoutOrganizer() {

if (!this.calendarObjectInstance.organizer) {
return this.invitees
}

return this.invitees
.filter(attendee => attendee.uri !== this.calendarObjectInstance.organizer.uri)
.filter(attendee => {
// Filter attendees which are part of an invited group
let isMemberOfGroup = false
if (attendee.attendeeProperty.member) {
isMemberOfGroup = this.groups.some(function(group) {
return attendee.attendeeProperty.member.includes(group.uri)
&& attendee.attendeeProperty.userType === 'INDIVIDUAL'

Check warning on line 153 in src/components/Editor/Invitees/InviteesList.vue

View check run for this annotation

Codecov / codecov/patch

src/components/Editor/Invitees/InviteesList.vue#L153

Added line #L153 was not covered by tests
})
}

// Add attendee to group member list
if (isMemberOfGroup) {
this.groups.forEach(group => {
if (attendee.member.includes(group.uri)) {
if (!group.members) {
group.members = []
}
group.members.push(attendee)
}
})

Check warning on line 166 in src/components/Editor/Invitees/InviteesList.vue

View check run for this annotation

Codecov / codecov/patch

src/components/Editor/Invitees/InviteesList.vue#L166

Added line #L166 was not covered by tests
}

// Check if attendee is an empty group
let isEmptyGroup = attendee.attendeeProperty.userType === 'GROUP'
this.invitees.forEach(invitee => {
if (invitee.member && invitee.member.includes(attendee.uri)) {
isEmptyGroup = false

Check warning on line 173 in src/components/Editor/Invitees/InviteesList.vue

View check run for this annotation

Codecov / codecov/patch

src/components/Editor/Invitees/InviteesList.vue#L168-L173

Added lines #L168 - L173 were not covered by tests
}
})

Check warning on line 175 in src/components/Editor/Invitees/InviteesList.vue

View check run for this annotation

Codecov / codecov/patch

src/components/Editor/Invitees/InviteesList.vue#L175

Added line #L175 was not covered by tests

return attendee.uri !== this.calendarObjectInstance.organizer.uri
&& !isMemberOfGroup && !isEmptyGroup
})
},
isOrganizer() {
return this.calendarObjectInstance.organizer !== null
Expand Down Expand Up @@ -204,6 +245,19 @@
})
},
removeAttendee(attendee) {
// Remove attendee from participating group
if (attendee.member) {
this.groups.forEach(group => {

Check warning on line 250 in src/components/Editor/Invitees/InviteesList.vue

View check run for this annotation

Codecov / codecov/patch

src/components/Editor/Invitees/InviteesList.vue#L249-L250

Added lines #L249 - L250 were not covered by tests
if (attendee.member.includes(group.uri)) {
group.members = group.members.filter(member => {
if (!attendee.member.includes(group.uri)) {
return true

Check warning on line 254 in src/components/Editor/Invitees/InviteesList.vue

View check run for this annotation

Codecov / codecov/patch

src/components/Editor/Invitees/InviteesList.vue#L254

Added line #L254 was not covered by tests
}
return false
})
}
})
}
this.$store.commit('removeAttendee', {
calendarObjectInstance: this.calendarObjectInstance,
attendee,
Expand Down
Loading
Loading