Allow to accept / reject participants
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import {EventJoinOptions} from "@/types/event.model";
|
||||
<template>
|
||||
<section class="container">
|
||||
<h1 class="title" v-if="isUpdate === false">
|
||||
@@ -187,11 +188,17 @@
|
||||
</style>
|
||||
|
||||
<script lang="ts">
|
||||
import { CREATE_EVENT, EDIT_EVENT, FETCH_EVENT, FETCH_EVENTS } from '@/graphql/event';
|
||||
import { CREATE_EVENT, EDIT_EVENT, FETCH_EVENT } from '@/graphql/event';
|
||||
import { Component, Prop, Vue, Watch } from 'vue-property-decorator';
|
||||
import { EventModel, EventStatus, EventVisibility, EventVisibilityJoinOptions, CommentModeration, IEvent } from '@/types/event.model';
|
||||
import {
|
||||
CommentModeration, EventJoinOptions,
|
||||
EventModel,
|
||||
EventStatus,
|
||||
EventVisibility,
|
||||
EventVisibilityJoinOptions,
|
||||
} from '@/types/event.model';
|
||||
import { CURRENT_ACTOR_CLIENT } from '@/graphql/actor';
|
||||
import { IPerson, Person } from '@/types/actor';
|
||||
import { Person } from '@/types/actor';
|
||||
import PictureUpload from '@/components/PictureUpload.vue';
|
||||
import Editor from '@/components/Editor.vue';
|
||||
import DateTimePicker from '@/components/Event/DateTimePicker.vue';
|
||||
@@ -352,6 +359,15 @@ export default class EditEvent extends Vue {
|
||||
}
|
||||
}
|
||||
|
||||
@Watch('needsApproval')
|
||||
updateEventJoinOptions(needsApproval) {
|
||||
if (needsApproval === true) {
|
||||
this.event.joinOptions = EventJoinOptions.RESTRICTED;
|
||||
} else {
|
||||
this.event.joinOptions = EventJoinOptions.FREE;
|
||||
}
|
||||
}
|
||||
|
||||
// getAddressData(addressData) {
|
||||
// if (addressData !== null) {
|
||||
// this.event.address = {
|
||||
|
||||
@@ -103,7 +103,7 @@
|
||||
</b-modal>
|
||||
</div>
|
||||
<div class="organizer">
|
||||
<actor-link :actor="event.organizerActor">
|
||||
<span>
|
||||
<span v-if="event.organizerActor">
|
||||
{{ $t('By {name}', {name: event.organizerActor.name ? event.organizerActor.name : event.organizerActor.preferredUsername}) }}
|
||||
</span>
|
||||
@@ -113,31 +113,11 @@
|
||||
:src="event.organizerActor.avatar.url"
|
||||
:alt="event.organizerActor.avatar.alt" />
|
||||
</figure>
|
||||
</actor-link>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- <p v-if="actorIsOrganizer()">-->
|
||||
<!-- <translate>You are an organizer.</translate>-->
|
||||
<!-- </p>-->
|
||||
<!-- <div v-else>-->
|
||||
<!-- <p v-if="actorIsParticipant()">-->
|
||||
<!-- <translate>You announced that you're going to this event.</translate>-->
|
||||
<!-- </p>-->
|
||||
<!-- <p v-else>-->
|
||||
<!-- <translate>Are you going to this event?</translate><br />-->
|
||||
<!-- <span>-->
|
||||
<!-- <translate-->
|
||||
<!-- :translate-n="event.participants.length"-->
|
||||
<!-- translate-plural="{event.participants.length} persons are going"-->
|
||||
<!-- >-->
|
||||
<!-- One person is going.-->
|
||||
<!-- </translate>-->
|
||||
<!-- </span>-->
|
||||
<!-- </p>-->
|
||||
<!-- </div>-->
|
||||
<div class="description">
|
||||
<div class="description-container container">
|
||||
<h3 class="title">
|
||||
@@ -147,63 +127,31 @@
|
||||
{{ $t("The event organizer didn't add any description.") }}
|
||||
</p>
|
||||
<div class="columns" v-else>
|
||||
<div class="column is-half">
|
||||
<p>
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|
||||
Suspendisse vehicula ex dapibus augue volutpat, ultrices cursus mi rutrum.
|
||||
Nunc ante nunc, facilisis a tellus quis, tempor mollis diam. Aenean consectetur quis est a ultrices.
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|
||||
</p>
|
||||
<p><a href="https://framasoft.org">https://framasoft.org</a>
|
||||
<p>
|
||||
Nam sit amet est eget velit tristique commodo. Etiam sollicitudin dignissim diam, ut ultricies tortor.
|
||||
Sed quis blandit diam, a tincidunt nunc. Donec tincidunt tristique neque at rhoncus. Ut eget vulputate felis.
|
||||
Pellentesque nibh purus, viverra ac augue sed, iaculis feugiat velit. Nulla ut hendrerit elit.
|
||||
Etiam at justo eu nunc tempus sagittis. Sed ac tincidunt tellus, sit amet luctus velit.
|
||||
Nam ullamcorper eros eleifend, eleifend diam vitae, lobortis risus.
|
||||
</p>
|
||||
<p>
|
||||
<em>
|
||||
Curabitur rhoncus sapien tortor, vitae imperdiet massa scelerisque non.
|
||||
Aliquam eu augue mi. Donec hendrerit lorem orci.
|
||||
</em>
|
||||
</p>
|
||||
<p>
|
||||
Donec volutpat, enim eu laoreet dictum, urna quam varius enim, eu convallis urna est vitae massa.
|
||||
Morbi porttitor lacus a sem efficitur blandit. Mauris in est in quam tincidunt iaculis non vitae ipsum.
|
||||
Phasellus eget velit tellus. Curabitur ac neque pharetra velit viverra mollis.
|
||||
</p>
|
||||
<img src="https://framasoft.org/img/biglogo-notxt.png" alt="logo Framasoft"/>
|
||||
<p>Aenean gravida, ante vitae aliquet aliquet, elit quam tristique orci, sit amet dictum lorem ipsum nec tortor.
|
||||
Vestibulum est eros, faucibus et semper vel, dapibus ac est. Suspendisse potenti. Suspendisse potenti.
|
||||
Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.
|
||||
Nulla molestie nisi ac risus hendrerit, dapibus mattis sapien scelerisque.
|
||||
</p>
|
||||
<p>Maecenas id pretium justo, nec dignissim sapien. Mauris in venenatis odio, in congue augue. </p>
|
||||
<div class="column is-half" v-html="event.description">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- <section class="container">-->
|
||||
<!-- <h2 class="title">Participants</h2>-->
|
||||
<!-- <span v-if="event.participants.length === 0">No participants yet.</span>-->
|
||||
<!-- <div class="columns">-->
|
||||
<!-- <router-link-->
|
||||
<!-- class="column"-->
|
||||
<!-- v-for="participant in event.participants"-->
|
||||
<!-- :key="participant.preferredUsername"-->
|
||||
<!-- :to="{name: 'Profile', params: { name: participant.actor.preferredUsername }}"-->
|
||||
<!-- >-->
|
||||
<!-- <div>-->
|
||||
<!-- <figure>-->
|
||||
<!-- <img v-if="!participant.actor.avatar.url" src="https://picsum.photos/125/125/">-->
|
||||
<!-- <img v-else :src="participant.actor.avatar.url">-->
|
||||
<!-- </figure>-->
|
||||
<!-- <span>{{ participant.actor.preferredUsername }}</span>-->
|
||||
<!-- </div>-->
|
||||
<!-- </router-link>-->
|
||||
<!-- </div>-->
|
||||
<!-- </section>-->
|
||||
<section class="container">
|
||||
<h3 class="title">{{ $t('Participants') }}</h3>
|
||||
<router-link v-if="currentActor.id === event.organizerActor.id" :to="{ name: EventRouteName.PARTICIPATIONS, params: { eventId: event.uuid } }">
|
||||
{{ $t('Manage participants') }}
|
||||
</router-link>
|
||||
<span v-if="event.participants.length === 0">{{ $t('No participants yet.') }}</span>
|
||||
<div class="columns">
|
||||
<div
|
||||
class="column"
|
||||
v-for="participant in event.participants"
|
||||
:key="participant.id"
|
||||
>
|
||||
<figure class="image is-48x48">
|
||||
<img v-if="!participant.actor.avatar.url" src="https://picsum.photos/48/48/" class="is-rounded">
|
||||
<img v-else :src="participant.actor.avatar.url" class="is-rounded">
|
||||
</figure>
|
||||
<span>{{ participant.actor.preferredUsername }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section class="share">
|
||||
<div class="container">
|
||||
<div class="columns">
|
||||
@@ -236,7 +184,7 @@
|
||||
</div>
|
||||
</section>
|
||||
<b-modal :active.sync="isReportModalActive" has-modal-card ref="reportModal">
|
||||
<report-modal :on-confirm="reportEvent" title="Report this event" :outside-domain="event.organizerActor.domain" @close="$refs.reportModal.close()" />
|
||||
<report-modal :on-confirm="reportEvent" :title="$t('Report this event')" :outside-domain="event.organizerActor.domain" @close="$refs.reportModal.close()" />
|
||||
</b-modal>
|
||||
<b-modal :active.sync="isJoinModalActive" has-modal-card ref="participationModal">
|
||||
<participation-modal :on-confirm="joinEvent" :event="event" :defaultIdentity="currentActor" @close="$refs.participationModal.close()" />
|
||||
@@ -249,7 +197,7 @@
|
||||
import { DELETE_EVENT, FETCH_EVENT, JOIN_EVENT, LEAVE_EVENT } from '@/graphql/event';
|
||||
import { Component, Prop, Vue } from 'vue-property-decorator';
|
||||
import { CURRENT_ACTOR_CLIENT } from '@/graphql/actor';
|
||||
import { EventVisibility, IEvent, IParticipant } from '@/types/event.model';
|
||||
import { EventVisibility, IEvent, IParticipant, ParticipantRole } from '@/types/event.model';
|
||||
import { IPerson } from '@/types/actor';
|
||||
import { RouteName } from '@/router';
|
||||
import { GRAPHQL_API_ENDPOINT } from '@/api/_entrypoint';
|
||||
@@ -263,6 +211,7 @@ import ParticipationModal from '@/components/Event/ParticipationModal.vue';
|
||||
import { IReport } from '@/types/report.model';
|
||||
import { CREATE_REPORT } from '@/graphql/report';
|
||||
import EventMixin from '@/mixins/event';
|
||||
import { EventRouteName } from '@/router/event';
|
||||
|
||||
@Component({
|
||||
components: {
|
||||
@@ -283,6 +232,7 @@ import EventMixin from '@/mixins/event';
|
||||
variables() {
|
||||
return {
|
||||
uuid: this.uuid,
|
||||
roles: [ParticipantRole.CREATOR, ParticipantRole.MODERATOR, ParticipantRole.MODERATOR, ParticipantRole.PARTICIPANT].join(),
|
||||
};
|
||||
},
|
||||
},
|
||||
@@ -302,6 +252,7 @@ export default class Event extends EventMixin {
|
||||
isJoinModalActive: boolean = false;
|
||||
|
||||
EventVisibility = EventVisibility;
|
||||
EventRouteName = EventRouteName;
|
||||
|
||||
/**
|
||||
* Delete the event, then redirect to home.
|
||||
@@ -367,9 +318,10 @@ export default class Event extends EventMixin {
|
||||
|
||||
confirmLeave() {
|
||||
this.$buefy.dialog.confirm({
|
||||
title: `Leaving event « ${this.event.title} »`,
|
||||
message: `Are you sure you want to leave event « ${this.event.title} »`,
|
||||
confirmText: 'Leave event',
|
||||
title: this.$t('Leaving event "{title}"', { title: this.event.title }) as string,
|
||||
message: this.$t('Are you sure you want to cancel your participation at event "{title}"?', { title: this.event.title }) as string,
|
||||
confirmText: this.$t('Leave event') as string,
|
||||
cancelText: this.$t('Cancel') as string,
|
||||
type: 'is-danger',
|
||||
hasIcon: true,
|
||||
onConfirm: () => this.leaveEvent(),
|
||||
|
||||
197
js/src/views/Event/Participants.vue
Normal file
197
js/src/views/Event/Participants.vue
Normal file
@@ -0,0 +1,197 @@
|
||||
<template>
|
||||
<main class="container">
|
||||
<b-tabs type="is-boxed" v-if="event">
|
||||
<b-tab-item>
|
||||
<template slot="header">
|
||||
<b-icon icon="information-outline"></b-icon>
|
||||
<span> Participants <b-tag rounded> {{ participantStats.approved }} </b-tag> </span>
|
||||
</template>
|
||||
<section v-if="participantsAndCreators.length > 0">
|
||||
<h2 class="title">{{ $t('Participants') }}</h2>
|
||||
<div class="columns">
|
||||
<div class="column is-one-quarter-desktop" v-for="participant in participantsAndCreators" :key="participant.actor.id">
|
||||
<participant-card
|
||||
:participant="participant"
|
||||
:accept="acceptParticipant"
|
||||
:reject="refuseParticipant"
|
||||
:exclude="refuseParticipant"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</b-tab-item>
|
||||
<b-tab-item>
|
||||
<template slot="header">
|
||||
<b-icon icon="source-pull"></b-icon>
|
||||
<span> Demandes <b-tag rounded> {{ participantStats.unapproved }} </b-tag> </span>
|
||||
</template>
|
||||
<section v-if="queue.length > 0">
|
||||
<h2 class="title">{{ $t('Waiting list') }}</h2>
|
||||
<div class="columns">
|
||||
<div class="column is-one-quarter-desktop" v-for="participant in queue" :key="participant.actor.id">
|
||||
<participant-card
|
||||
:participant="participant"
|
||||
:accept="acceptParticipant"
|
||||
:reject="refuseParticipant"
|
||||
:exclude="refuseParticipant"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</b-tab-item>
|
||||
</b-tabs>
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Prop, Vue } from 'vue-property-decorator';
|
||||
import { IEvent, IParticipant, Participant, ParticipantRole } from '@/types/event.model';
|
||||
import { ACCEPT_PARTICIPANT, PARTICIPANTS, REJECT_PARTICIPANT } from '@/graphql/event';
|
||||
import ParticipantCard from '@/components/Account/ParticipantCard.vue';
|
||||
import { CURRENT_ACTOR_CLIENT } from '@/graphql/actor';
|
||||
import { IPerson } from '@/types/actor';
|
||||
|
||||
|
||||
@Component({
|
||||
components: {
|
||||
ParticipantCard,
|
||||
},
|
||||
apollo: {
|
||||
currentActor: {
|
||||
query: CURRENT_ACTOR_CLIENT,
|
||||
},
|
||||
event: {
|
||||
query: PARTICIPANTS,
|
||||
variables() {
|
||||
return {
|
||||
uuid: this.eventId,
|
||||
page: 1,
|
||||
limit: 10,
|
||||
roles: [ParticipantRole.PARTICIPANT].join(),
|
||||
};
|
||||
},
|
||||
},
|
||||
organizers: {
|
||||
query: PARTICIPANTS,
|
||||
variables() {
|
||||
return {
|
||||
uuid: this.eventId,
|
||||
page: 1,
|
||||
limit: 20,
|
||||
roles: [ParticipantRole.CREATOR].join(),
|
||||
};
|
||||
},
|
||||
update: data => data.event.participants.map(participation => new Participant(participation)),
|
||||
},
|
||||
queue: {
|
||||
query: PARTICIPANTS,
|
||||
variables() {
|
||||
return {
|
||||
uuid: this.eventId,
|
||||
page: 1,
|
||||
limit: 20,
|
||||
roles: [ParticipantRole.NOT_APPROVED].join(),
|
||||
};
|
||||
},
|
||||
update: data => data.event.participants.map(participation => new Participant(participation)),
|
||||
},
|
||||
},
|
||||
})
|
||||
export default class Participants extends Vue {
|
||||
@Prop({ required: true }) eventId!: string;
|
||||
page: number = 1;
|
||||
limit: number = 10;
|
||||
|
||||
// participants: IParticipant[] = [];
|
||||
organizers: IParticipant[] = [];
|
||||
queue: IParticipant[] = [];
|
||||
event!: IEvent;
|
||||
|
||||
ParticipantRole = ParticipantRole;
|
||||
currentActor!: IPerson;
|
||||
|
||||
hasMoreParticipants: boolean = false;
|
||||
|
||||
get participants(): IParticipant[] {
|
||||
return this.event.participants.map(participant => new Participant(participant));
|
||||
}
|
||||
|
||||
get participantStats(): Object {
|
||||
return this.event.participantStats;
|
||||
}
|
||||
|
||||
get participantsAndCreators(): IParticipant[] {
|
||||
if (this.event) {
|
||||
return [...this.organizers, ...this.participants];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
loadMoreParticipants() {
|
||||
this.page += 1;
|
||||
this.$apollo.queries.participants.fetchMore({
|
||||
// New variables
|
||||
variables: {
|
||||
page: this.page,
|
||||
limit: this.limit,
|
||||
},
|
||||
// Transform the previous result with new data
|
||||
updateQuery: (previousResult, { fetchMoreResult }) => {
|
||||
const newParticipations = fetchMoreResult.event.participants;
|
||||
this.hasMoreParticipants = newParticipations.length === this.limit;
|
||||
|
||||
return {
|
||||
loggedUser: {
|
||||
__typename: previousResult.event.__typename,
|
||||
participations: [...previousResult.event.participants, ...newParticipations],
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async acceptParticipant(participant: IParticipant) {
|
||||
try {
|
||||
const { data } = await this.$apollo.mutate({
|
||||
mutation: ACCEPT_PARTICIPANT,
|
||||
variables: {
|
||||
id: participant.id,
|
||||
moderatorActorId: this.currentActor.id,
|
||||
},
|
||||
});
|
||||
if (data) {
|
||||
console.log('accept', data);
|
||||
this.queue.filter(participant => participant !== data.acceptParticipation.id);
|
||||
this.participants.push(participant);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
async refuseParticipant(participant: IParticipant) {
|
||||
try {
|
||||
const { data } = await this.$apollo.mutate({
|
||||
mutation: REJECT_PARTICIPANT,
|
||||
variables: {
|
||||
id: participant.id,
|
||||
moderatorActorId: this.currentActor.id,
|
||||
},
|
||||
});
|
||||
if (data) {
|
||||
this.participants.filter(participant => participant !== data.rejectParticipation.id);
|
||||
this.queue.filter(participant => participant !== data.rejectParticipation.id);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
||||
<style lang="scss" scoped>
|
||||
section {
|
||||
padding: 3rem 0;
|
||||
}
|
||||
</style>
|
||||
@@ -107,7 +107,7 @@ export default class Group extends Vue {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss">
|
||||
<style lang="scss" scoped>
|
||||
section.container {
|
||||
min-height: 30em;
|
||||
}
|
||||
|
||||
@@ -32,12 +32,13 @@
|
||||
<router-link :to="{ name: RouteName.CREATE_GROUP }">{{ $t('Group') }}</router-link>
|
||||
</b-dropdown-item>
|
||||
</b-dropdown>
|
||||
<section v-if="currentActor" class="container">
|
||||
<section v-if="currentActor && goingToEvents.size > 0" class="container">
|
||||
<h3 class="title">
|
||||
{{ $t("Upcoming") }}
|
||||
</h3>
|
||||
<pre>{{ Array.from(goingToEvents.entries()) }}</pre>
|
||||
<b-loading :active.sync="$apollo.loading"></b-loading>
|
||||
<div v-if="goingToEvents.size > 0" v-for="row in goingToEvents" class="upcoming-events">
|
||||
<div v-for="row in goingToEvents" class="upcoming-events">
|
||||
<span class="date-component-container" v-if="isInLessThanSevenDays(row[0])">
|
||||
<date-component :date="row[0]"></date-component>
|
||||
<h3 class="subtitle"
|
||||
@@ -63,9 +64,6 @@
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<b-message v-else type="is-danger">
|
||||
{{ $t("You're not going to any event yet") }}
|
||||
</b-message>
|
||||
<span class="view-all">
|
||||
<router-link :to=" { name: EventRouteName.MY_EVENTS }">{{ $t('View everything')}} >></router-link>
|
||||
</span>
|
||||
@@ -78,9 +76,10 @@
|
||||
<div class="level">
|
||||
<EventListCard
|
||||
v-for="participation in lastWeekEvents"
|
||||
:key="participation.event.uuid"
|
||||
:key="participation.id"
|
||||
:participation="participation"
|
||||
class="level-item"
|
||||
:options="{ hideDate: false }"
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
@@ -190,6 +189,10 @@ export default class Home extends Vue {
|
||||
return this.calculateDiffDays(date) < nbDays;
|
||||
}
|
||||
|
||||
isAfter(date: string, nbDays: number) :boolean {
|
||||
return this.calculateDiffDays(date) >= nbDays;
|
||||
}
|
||||
|
||||
isInLessThanSevenDays(date: string): boolean {
|
||||
return this.isBefore(date, 7);
|
||||
}
|
||||
@@ -200,7 +203,7 @@ export default class Home extends Vue {
|
||||
|
||||
get goingToEvents(): Map<string, Map<string, IParticipant>> {
|
||||
const res = this.currentUserParticipations.filter(({ event }) => {
|
||||
return event.beginsOn != null && !this.isBefore(event.beginsOn.toDateString(), 0);
|
||||
return event.beginsOn != null && this.isAfter(event.beginsOn.toDateString(), 0) && this.isBefore(event.beginsOn.toDateString(), 7);
|
||||
});
|
||||
res.sort(
|
||||
(a: IParticipant, b: IParticipant) => a.event.beginsOn.getTime() - b.event.beginsOn.getTime(),
|
||||
@@ -208,7 +211,7 @@ export default class Home extends Vue {
|
||||
return res.reduce((acc: Map<string, Map<string, IParticipant>>, participation: IParticipant) => {
|
||||
const day = (new Date(participation.event.beginsOn)).toDateString();
|
||||
const participations: Map<string, IParticipant> = acc.get(day) || new Map();
|
||||
participations.set(participation.event.uuid, participation);
|
||||
participations.set(`${participation.event.uuid}${participation.actor.id}`, participation);
|
||||
acc.set(day, participations);
|
||||
return acc;
|
||||
}, new Map());
|
||||
@@ -273,7 +276,7 @@ export default class Home extends Vue {
|
||||
</script>
|
||||
|
||||
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
||||
<style lang="scss">
|
||||
<style lang="scss" scoped>
|
||||
.search-autocomplete {
|
||||
border: 1px solid #dbdbdb;
|
||||
color: rgba(0, 0, 0, 0.87);
|
||||
|
||||
Reference in New Issue
Block a user