Allow to accept / reject participants
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
parent
ffa4ec9209
commit
abf3a58657
@ -82,6 +82,7 @@ export default class App extends Vue {
|
||||
@import "~bulma/sass/components/dropdown.sass";
|
||||
@import "~bulma/sass/components/breadcrumb.sass";
|
||||
@import "~bulma/sass/components/list.sass";
|
||||
@import "~bulma/sass/components/tabs";
|
||||
@import "~bulma/sass/elements/box.sass";
|
||||
@import "~bulma/sass/elements/button.sass";
|
||||
@import "~bulma/sass/elements/container.sass";
|
||||
@ -112,6 +113,7 @@ export default class App extends Vue {
|
||||
@import "~buefy/src/scss/components/radio";
|
||||
@import "~buefy/src/scss/components/switch";
|
||||
@import "~buefy/src/scss/components/table";
|
||||
@import "~buefy/src/scss/components/tabs";
|
||||
|
||||
.router-enter-active,
|
||||
.router-leave-active {
|
||||
|
@ -1,10 +1,10 @@
|
||||
<template>
|
||||
<span>
|
||||
<router-link v-if="actor.domain === null"
|
||||
<span v-if="actor.domain === null"
|
||||
:to="{name: 'Profile', params: { name: actor.preferredUsername } }"
|
||||
>
|
||||
<slot></slot>
|
||||
</router-link>
|
||||
</span>
|
||||
<a v-else :href="actor.url">
|
||||
<slot></slot>
|
||||
</a>
|
||||
|
48
js/src/components/Account/ParticipantCard.vue
Normal file
48
js/src/components/Account/ParticipantCard.vue
Normal file
@ -0,0 +1,48 @@
|
||||
<template>
|
||||
<article class="card">
|
||||
<div class="card-content">
|
||||
<div class="media">
|
||||
<div class="media-left" v-if="participant.actor.avatar">
|
||||
<figure class="image is-48x48">
|
||||
<img :src="participant.actor.avatar.url" />
|
||||
</figure>
|
||||
</div>
|
||||
<div class="media-content">
|
||||
<span class="title" ref="title">{{ actorDisplayName }}</span><br>
|
||||
<small class="has-text-grey">@{{ participant.actor.preferredUsername }}</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<footer class="card-footer">
|
||||
<b-button v-if="participant.role === ParticipantRole.NOT_APPROVED" @click="accept(participant)" type="is-success" class="card-footer-item">{{ $t('Approve') }}</b-button>
|
||||
<b-button v-if="participant.role === ParticipantRole.NOT_APPROVED" @click="reject(participant)" type="is-danger" class="card-footer-item">{{ $t('Reject')}} </b-button>
|
||||
<b-button v-if="participant.role === ParticipantRole.PARTICIPANT" @click="exclude(participant)" type="is-danger" class="card-footer-item">{{ $t('Exclude')}} </b-button>
|
||||
<span v-if="participant.role === ParticipantRole.CREATOR" class="card-footer-item">{{ $t('Creator')}} </span>
|
||||
</footer>
|
||||
</article>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Prop, Vue } from 'vue-property-decorator';
|
||||
import { IActor, IPerson, Person } from '@/types/actor';
|
||||
import { IParticipant, ParticipantRole } from '@/types/event.model';
|
||||
|
||||
@Component
|
||||
export default class ActorCard extends Vue {
|
||||
@Prop({ required: true }) participant!: IParticipant;
|
||||
@Prop({ type: Function }) accept;
|
||||
@Prop({ type: Function }) reject;
|
||||
@Prop({ type: Function }) exclude;
|
||||
|
||||
ParticipantRole = ParticipantRole;
|
||||
|
||||
get actorDisplayName(): string {
|
||||
const actor = new Person(this.participant.actor);
|
||||
return actor.displayName();
|
||||
}
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
</style>
|
@ -60,7 +60,6 @@ export interface IEventCardOptions {
|
||||
@Component({
|
||||
components: {
|
||||
DateCalendarIcon,
|
||||
EventCard,
|
||||
},
|
||||
mounted() {
|
||||
lineClamp(this.$refs.title, 3);
|
||||
|
@ -11,7 +11,8 @@
|
||||
<span v-if="participation.event.physicalAddress && participation.event.physicalAddress.locality">{{ participation.event.physicalAddress.locality }} - </span>
|
||||
<span v-if="participation.actor.id === participation.event.organizerActor.id">{{ $t("You're organizing this event") }}</span>
|
||||
<span v-else>
|
||||
<span>{{ $t('Organized by {name}', { name: participation.event.organizerActor.displayName() } ) }}</span> |
|
||||
<span v-if="participation.event.beginsOn < new Date()">{{ $t('Organized by {name}', { name: participation.event.organizerActor.displayName() } ) }}</span>
|
||||
|
|
||||
<span>{{ $t('Going as {name}', { name: participation.actor.displayName() }) }}</span>
|
||||
</span>
|
||||
</div>
|
||||
@ -50,9 +51,9 @@
|
||||
<a @click="openDeleteEventModalWrapper"><b-icon icon="delete" /> {{ $t('Delete') }}</a>
|
||||
</li>
|
||||
<li v-if="!([ParticipantRole.PARTICIPANT, ParticipantRole.NOT_APPROVED].includes(participation.role))">
|
||||
<a @click="">
|
||||
<router-link :to="{ name: EventRouteName.PARTICIPATIONS, params: { eventId: participation.event.uuid } }">
|
||||
<b-icon icon="account-multiple-plus" /> {{ $t('Manage participations') }}
|
||||
</a>
|
||||
</router-link>
|
||||
</li>
|
||||
<li>
|
||||
<router-link :to="{ name: EventRouteName.EVENT, params: { uuid: participation.event.uuid } }"><b-icon icon="view-compact" /> {{ $t('View event page') }}</router-link>
|
||||
|
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="modal-card">
|
||||
<header class="modal-card-head">
|
||||
<p class="modal-card-title">Join event {{ event.title }}</p>
|
||||
<p class="modal-card-title">{{ $t('Join event {title}', {title: event.title}) }}</p>
|
||||
</header>
|
||||
|
||||
<section class="modal-card-body is-flex">
|
||||
@ -14,14 +14,18 @@
|
||||
size="is-large"/>
|
||||
</div>
|
||||
<div class="media-content">
|
||||
<p>Do you want to participate in {{ event.title }}?</p>
|
||||
<p>{{ $t('Do you want to participate in {title}?', {title: event.title}) }}?</p>
|
||||
|
||||
<b-field :label="$t('Identity')">
|
||||
<identity-picker v-model="identity"></identity-picker>
|
||||
</b-field>
|
||||
|
||||
<p v-if="event.joinOptions === EventJoinOptions.RESTRICTED">
|
||||
{{ $t('The event organizer has chosen to approve manually the participations to this event. You will receive a notification when your participation has been approved')}}
|
||||
</p>
|
||||
|
||||
<p v-if="!event.local">
|
||||
The event came from another instance. Your participation will be confirmed after we confirm it with the other instance.
|
||||
{{ $t('The event came from another instance. Your participation will be confirmed after we confirm it with the other instance.') }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@ -32,13 +36,13 @@
|
||||
class="button"
|
||||
ref="cancelButton"
|
||||
@click="close">
|
||||
Cancel
|
||||
{{ $t('Cancel') }}
|
||||
</button>
|
||||
<button
|
||||
class="button is-primary"
|
||||
ref="confirmButton"
|
||||
@click="confirm">
|
||||
Confirm my particpation
|
||||
{{ $t('Confirm my particpation') }}
|
||||
</button>
|
||||
</footer>
|
||||
</div>
|
||||
@ -46,7 +50,7 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Prop, Vue } from 'vue-property-decorator';
|
||||
import { IEvent } from '@/types/event.model';
|
||||
import { IEvent, EventJoinOptions } from '@/types/event.model';
|
||||
import IdentityPicker from '@/views/Account/IdentityPicker.vue';
|
||||
import { IPerson } from '@/types/actor';
|
||||
|
||||
@ -66,6 +70,8 @@ export default class ReportModal extends Vue {
|
||||
isActive: boolean = false;
|
||||
identity: IPerson = this.defaultIdentity;
|
||||
|
||||
EventJoinOptions = EventJoinOptions;
|
||||
|
||||
confirm() {
|
||||
this.onConfirm(this.identity);
|
||||
}
|
||||
|
@ -16,24 +16,23 @@
|
||||
size="is-large"/>
|
||||
</div>
|
||||
<div class="media-content">
|
||||
<p>The report will be sent to the moderators of your instance.
|
||||
You can explain why you report this content below.</p>
|
||||
<p>{{ $t('The report will be sent to the moderators of your instance. You can explain why you report this content below.') }}</p>
|
||||
|
||||
<div class="control">
|
||||
<b-input
|
||||
v-model="content"
|
||||
type="textarea"
|
||||
@keyup.enter="confirm"
|
||||
placeholder="Additional comments"
|
||||
:placeholder="$t('Additional comments')"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<p v-if="outsideDomain">
|
||||
The content came from another server. Transfer an anonymous copy of the report ?
|
||||
{{ $t('The content came from another server. Transfer an anonymous copy of the report?') }}
|
||||
</p>
|
||||
|
||||
<div class="control" v-if="outsideDomain">
|
||||
<b-switch v-model="forward">Transfer to {{ outsideDomain }}</b-switch>
|
||||
<b-switch v-model="forward">{{ $t('Transfer to {outsideDomain}', { outsideDomain }) }}</b-switch>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -44,13 +43,13 @@
|
||||
class="button"
|
||||
ref="cancelButton"
|
||||
@click="close">
|
||||
{{ cancelText }}
|
||||
{{ translatedCancelText }}
|
||||
</button>
|
||||
<button
|
||||
class="button is-primary"
|
||||
ref="confirmButton"
|
||||
@click="confirm">
|
||||
{{ confirmText }}
|
||||
{{ translatedConfirmText }}
|
||||
</button>
|
||||
</footer>
|
||||
</div>
|
||||
@ -69,13 +68,21 @@ export default class ReportModal extends Vue {
|
||||
@Prop({ type: Function, default: () => {} }) onConfirm;
|
||||
@Prop({ type: String }) title;
|
||||
@Prop({ type: String, default: '' }) outsideDomain;
|
||||
@Prop({ type: String, default: 'Cancel' }) cancelText;
|
||||
@Prop({ type: String, default: 'Send the report' }) confirmText;
|
||||
@Prop({ type: String }) cancelText;
|
||||
@Prop({ type: String }) confirmText;
|
||||
|
||||
isActive: boolean = false;
|
||||
content: string = '';
|
||||
forward: boolean = false;
|
||||
|
||||
get translatedCancelText() {
|
||||
return this.cancelText || this.$t('Cancel');
|
||||
}
|
||||
|
||||
get translatedConfirmText() {
|
||||
return this.confirmText || this.$t('Send the report');
|
||||
}
|
||||
|
||||
confirm() {
|
||||
this.onConfirm(this.content, this.forward);
|
||||
this.close();
|
||||
|
@ -91,6 +91,7 @@ query LoggedUserParticipations($afterDateTime: DateTime, $beforeDateTime: DateTi
|
||||
remainingAttendeeCapacity
|
||||
}
|
||||
},
|
||||
id,
|
||||
role,
|
||||
actor {
|
||||
id,
|
||||
|
@ -2,6 +2,7 @@ import gql from 'graphql-tag';
|
||||
|
||||
const participantQuery = `
|
||||
role,
|
||||
id,
|
||||
actor {
|
||||
preferredUsername,
|
||||
avatar {
|
||||
@ -50,7 +51,7 @@ const optionsQuery = `
|
||||
`;
|
||||
|
||||
export const FETCH_EVENT = gql`
|
||||
query($uuid:UUID!) {
|
||||
query($uuid:UUID!, $roles: String) {
|
||||
event(uuid: $uuid) {
|
||||
id,
|
||||
uuid,
|
||||
@ -63,6 +64,7 @@ export const FETCH_EVENT = gql`
|
||||
endsOn,
|
||||
status,
|
||||
visibility,
|
||||
joinOptions,
|
||||
picture {
|
||||
id
|
||||
url
|
||||
@ -92,7 +94,7 @@ export const FETCH_EVENT = gql`
|
||||
# preferredUsername,
|
||||
# name,
|
||||
# },
|
||||
participants {
|
||||
participants (roles: $roles) {
|
||||
${participantQuery}
|
||||
},
|
||||
participantStats {
|
||||
@ -183,7 +185,8 @@ export const CREATE_EVENT = gql`
|
||||
$beginsOn: DateTime!,
|
||||
$endsOn: DateTime,
|
||||
$status: EventStatus,
|
||||
$visibility: EventVisibility
|
||||
$visibility: EventVisibility,
|
||||
$joinOptions: EventJoinOptions,
|
||||
$tags: [String],
|
||||
$picture: PictureInput,
|
||||
$onlineAddress: String,
|
||||
@ -200,6 +203,7 @@ export const CREATE_EVENT = gql`
|
||||
endsOn: $endsOn,
|
||||
status: $status,
|
||||
visibility: $visibility,
|
||||
joinOptions: $joinOptions,
|
||||
tags: $tags,
|
||||
picture: $picture,
|
||||
onlineAddress: $onlineAddress,
|
||||
@ -216,6 +220,7 @@ export const CREATE_EVENT = gql`
|
||||
endsOn,
|
||||
status,
|
||||
visibility,
|
||||
joinOptions,
|
||||
picture {
|
||||
id
|
||||
url
|
||||
@ -245,7 +250,8 @@ export const EDIT_EVENT = gql`
|
||||
$beginsOn: DateTime,
|
||||
$endsOn: DateTime,
|
||||
$status: EventStatus,
|
||||
$visibility: EventVisibility
|
||||
$visibility: EventVisibility,
|
||||
$joinOptions: EventJoinOptions,
|
||||
$tags: [String],
|
||||
$picture: PictureInput,
|
||||
$onlineAddress: String,
|
||||
@ -262,6 +268,7 @@ export const EDIT_EVENT = gql`
|
||||
endsOn: $endsOn,
|
||||
status: $status,
|
||||
visibility: $visibility,
|
||||
joinOptions: $joinOptions,
|
||||
tags: $tags,
|
||||
picture: $picture,
|
||||
onlineAddress: $onlineAddress,
|
||||
@ -278,6 +285,7 @@ export const EDIT_EVENT = gql`
|
||||
endsOn,
|
||||
status,
|
||||
visibility,
|
||||
joinOptions,
|
||||
picture {
|
||||
id
|
||||
url
|
||||
@ -323,6 +331,23 @@ export const LEAVE_EVENT = gql`
|
||||
}
|
||||
`;
|
||||
|
||||
export const ACCEPT_PARTICIPANT = gql`
|
||||
mutation AcceptParticipant($id: ID!, $moderatorActorId: ID!) {
|
||||
acceptParticipation(id: $id, moderatorActorId: $moderatorActorId) {
|
||||
role,
|
||||
id
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const REJECT_PARTICIPANT = gql`
|
||||
mutation RejectParticipant($id: ID!, $moderatorActorId: ID!) {
|
||||
rejectParticipation(id: $id, moderatorActorId: $moderatorActorId) {
|
||||
id
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const DELETE_EVENT = gql`
|
||||
mutation DeleteEvent($eventId: ID!, $actorId: ID!) {
|
||||
deleteEvent(
|
||||
@ -333,3 +358,17 @@ export const DELETE_EVENT = gql`
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const PARTICIPANTS = gql`
|
||||
query($uuid: UUID!, $page: Int, $limit: Int, $roles: String) {
|
||||
event(uuid: $uuid) {
|
||||
participants(page: $page, limit: $limit, roles: $roles) {
|
||||
${participantQuery}
|
||||
},
|
||||
participantStats {
|
||||
approved,
|
||||
unapproved
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
@ -211,5 +211,29 @@
|
||||
"Load more": "Load more",
|
||||
"Past events": "Passed events",
|
||||
"View everything": "View everything",
|
||||
"Last week": "Last week"
|
||||
"Last week": "Last week",
|
||||
"Approve": "Approve",
|
||||
"Reject": "Reject",
|
||||
"Exclude": "Exclude",
|
||||
"Creator": "Creator",
|
||||
"Join event {title}": "Join event {title}",
|
||||
"Cancel": "Cancel",
|
||||
"Confirm my particpation": "Confirm my particpation",
|
||||
"Manage participants": "Manage participants",
|
||||
"No participants yet.": "No participants yet.",
|
||||
"Participants": "Participants",
|
||||
"Do you want to participate in {title}?": "Do you want to participate in {title}?",
|
||||
"The event organizer has chosen to approve manually the participations to this event. You will receive a notification when your participation has been approved": "The event organizer has chosen to approve manually the participations to this event. You will receive a notification when your participation has been approved",
|
||||
"The event came from another instance. Your participation will be confirmed after we confirm it with the other instance.": "The event came from another instance. Your participation will be confirmed after we confirm it with the other instance.",
|
||||
"Waiting list": "Waiting list",
|
||||
"Leaving event \"{title}\"": "Leaving event \"{title}\"",
|
||||
"Are you sure you want to cancel your participation at event \"{title}\"?": "Are you sure you want to cancel your participation at event \"{title}\"?",
|
||||
"Leave event": "Leave event",
|
||||
"The report will be sent to the moderators of your instance. You can explain why you report this content below.": "The report will be sent to the moderators of your instance. You can explain why you report this content below.",
|
||||
"Additional comments": "Additional comments",
|
||||
"The content came from another server. Transfer an anonymous copy of the report?": "The content came from another server. Transfer an anonymous copy of the report ?",
|
||||
"Transfer to {outsideDomain}": "Transfer to {outsideDomain}",
|
||||
"Send the report": "Send the report",
|
||||
"Report this event": "Report this event"
|
||||
|
||||
}
|
@ -138,7 +138,7 @@
|
||||
"Register an account on Mobilizon!": "S'inscrire sur Mobilizon !",
|
||||
"Register": "S'inscrire",
|
||||
"Registration is currently closed.": "Les inscriptions sont actuellement fermées.",
|
||||
"Report": "Report",
|
||||
"Report": "Signaler",
|
||||
"Resend confirmation email": "Envoyer à nouveau l'email de confirmation",
|
||||
"Reset my password": "Réinitialiser mon mot de passe",
|
||||
"Save": "Enregistrer",
|
||||
@ -211,5 +211,28 @@
|
||||
"Load more": "Voir plus",
|
||||
"Past events": "Événements passés",
|
||||
"View everything": "Voir tout",
|
||||
"Last week": "La semaine dernière"
|
||||
"Last week": "La semaine dernière",
|
||||
"Approve": "Approuver",
|
||||
"Reject": "Rejetter",
|
||||
"Exclude": "Exclure",
|
||||
"Creator": "Créateur",
|
||||
"Join event {title}": "Rejoindre {title}",
|
||||
"Cancel": "Annuler",
|
||||
"Confirm my particpation": "Confirmer ma particpation",
|
||||
"Manage participants": "Gérer les participants",
|
||||
"No participants yet.": "Pas de participants pour le moment.",
|
||||
"Participants": "Participants",
|
||||
"Do you want to participate in {title}?": "Voulez-vous participer à {title} ?",
|
||||
"The event organizer has chosen to approve manually the participations to this event. You will receive a notification when your participation has been approved": "L'organisateur⋅ice de l'événement a choisi d'approuver manuellement les participations à cet événement. Vous recevrez une notification lorsque votre participation sera approuvée",
|
||||
"The event came from another instance. Your participation will be confirmed after we confirm it with the other instance.": "L'événement provient d'une autre instance. Votre participation sera confirmée après que nous ayons la confirmation de l'autre instance.",
|
||||
"Waiting list": "Liste d'attente",
|
||||
"Leaving event \"{title}\"": "Annuler ma participation à l'événement",
|
||||
"Are you sure you want to cancel your participation at event \"{title}\"?": "Êtes-vous certain⋅e de vouloir annuler votre participation à l'événement « {title} » ?",
|
||||
"Leave event": "Annuler ma participation à l'événement",
|
||||
"The report will be sent to the moderators of your instance. You can explain why you report this content below.": "Le signalement sera envoyé aux modérateur⋅ices de votre instance. Vous pouvez expliquer pourquoi vous signalez ce contenu ci-dessous.",
|
||||
"Additional comments": "Commentaires additionnels",
|
||||
"The content came from another server. Transfer an anonymous copy of the report?": "Le contenu provient d'une autre instance. Transférer une copie anonyme du signalement ?",
|
||||
"Transfer to {outsideDomain}": "Transférer à {outsideDomain}",
|
||||
"Send the report": "Envoyer le signalement",
|
||||
"Report this event": "Signaler cet événement"
|
||||
}
|
@ -3,9 +3,10 @@ import Location from '@/views/Location.vue';
|
||||
import { RouteConfig } from 'vue-router';
|
||||
|
||||
// tslint:disable:space-in-parens
|
||||
const editEvent = () => import(/* webpackChunkName: "create-event" */ '@/views/Event/Edit.vue');
|
||||
const participations = () => import(/* webpackChunkName: "participations" */ '@/views/Event/Participants.vue');
|
||||
const editEvent = () => import(/* webpackChunkName: "edit-event" */ '@/views/Event/Edit.vue');
|
||||
const event = () => import(/* webpackChunkName: "event" */ '@/views/Event/Event.vue');
|
||||
const myEvents = () => import(/* webpackChunkName: "event" */ '@/views/Event/MyEvents.vue');
|
||||
const myEvents = () => import(/* webpackChunkName: "my-events" */ '@/views/Event/MyEvents.vue');
|
||||
// tslint:enable
|
||||
|
||||
export enum EventRouteName {
|
||||
@ -13,6 +14,7 @@ export enum EventRouteName {
|
||||
CREATE_EVENT = 'CreateEvent',
|
||||
MY_EVENTS = 'MyEvents',
|
||||
EDIT_EVENT = 'EditEvent',
|
||||
PARTICIPATIONS = 'Participations',
|
||||
EVENT = 'Event',
|
||||
LOCATION = 'Location',
|
||||
}
|
||||
@ -43,6 +45,13 @@ export const eventRoutes: RouteConfig[] = [
|
||||
meta: { requiredAuth: true },
|
||||
props: { isUpdate: true },
|
||||
},
|
||||
{
|
||||
path: '/events/participations/:eventId',
|
||||
name: EventRouteName.PARTICIPATIONS,
|
||||
component: participations,
|
||||
meta: { requiredAuth: true },
|
||||
props: true,
|
||||
},
|
||||
{
|
||||
path: '/location/new',
|
||||
name: EventRouteName.LOCATION,
|
||||
|
@ -29,11 +29,11 @@ export enum EventVisibilityJoinOptions {
|
||||
}
|
||||
|
||||
export enum ParticipantRole {
|
||||
NOT_APPROVED = 'not_approved',
|
||||
PARTICIPANT = 'participant',
|
||||
MODERATOR = 'moderator',
|
||||
ADMINISTRATOR = 'administrator',
|
||||
CREATOR = 'creator',
|
||||
NOT_APPROVED = 'NOT_APPROVED',
|
||||
PARTICIPANT = 'PARTICIPANT',
|
||||
MODERATOR = 'MODERATOR',
|
||||
ADMINISTRATOR = 'ADMINISTRATOR',
|
||||
CREATOR = 'CREATOR',
|
||||
}
|
||||
|
||||
export enum Category {
|
||||
@ -45,12 +45,14 @@ export enum Category {
|
||||
}
|
||||
|
||||
export interface IParticipant {
|
||||
id?: string;
|
||||
role: ParticipantRole;
|
||||
actor: IActor;
|
||||
event: IEvent;
|
||||
}
|
||||
|
||||
export class Participant implements IParticipant {
|
||||
id?: string;
|
||||
event!: IEvent;
|
||||
actor!: IActor;
|
||||
role: ParticipantRole = ParticipantRole.NOT_APPROVED;
|
||||
@ -58,6 +60,7 @@ export class Participant implements IParticipant {
|
||||
constructor(hash?: IParticipant) {
|
||||
if (!hash) return;
|
||||
|
||||
this.id = hash.id;
|
||||
this.event = new EventModel(hash.event);
|
||||
this.actor = new Actor(hash.actor);
|
||||
this.role = hash.role;
|
||||
@ -83,7 +86,7 @@ export enum CommentModeration {
|
||||
}
|
||||
|
||||
export interface IEvent {
|
||||
id?: number;
|
||||
id?: string;
|
||||
uuid: string;
|
||||
url: string;
|
||||
local: boolean;
|
||||
@ -147,7 +150,7 @@ export class EventOptions implements IEventOptions {
|
||||
}
|
||||
|
||||
export class EventModel implements IEvent {
|
||||
id?: number;
|
||||
id?: string;
|
||||
|
||||
beginsOn = new Date();
|
||||
endsOn: Date | null = new Date();
|
||||
@ -232,6 +235,7 @@ export class EventModel implements IEvent {
|
||||
endsOn: this.endsOn ? this.endsOn.toISOString() : null,
|
||||
status: this.status,
|
||||
visibility: this.visibility,
|
||||
joinOptions: this.joinOptions,
|
||||
tags: this.tags.map(t => t.title),
|
||||
picture: this.picture,
|
||||
onlineAddress: this.onlineAddress,
|
||||
|
@ -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);
|
||||
|
@ -67,6 +67,7 @@ defmodule Mobilizon.Events.Event do
|
||||
@attrs @required_attrs ++ @optional_attrs
|
||||
|
||||
@update_required_attrs @required_attrs
|
||||
|
||||
@update_optional_attrs [
|
||||
:slug,
|
||||
:description,
|
||||
@ -74,6 +75,7 @@ defmodule Mobilizon.Events.Event do
|
||||
:category,
|
||||
:status,
|
||||
:visibility,
|
||||
:join_options,
|
||||
:publish_at,
|
||||
:online_address,
|
||||
:phone_address,
|
||||
|
@ -522,6 +522,26 @@ defmodule Mobilizon.Events do
|
||||
|
||||
@doc """
|
||||
Gets a single participant.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> get_participant(123)
|
||||
%Participant{}
|
||||
|
||||
iex> get_participant(456)
|
||||
nil
|
||||
|
||||
"""
|
||||
@spec get_participant(integer) :: Participant.t()
|
||||
def get_participant(participant_id) do
|
||||
Participant
|
||||
|> where([p], p.id == ^participant_id)
|
||||
|> preload([p], [:event, :actor])
|
||||
|> Repo.one()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets a single participation for an event and actor.
|
||||
"""
|
||||
@spec get_participant(integer | String.t(), integer | String.t()) ::
|
||||
{:ok, Participant.t()} | {:error, :participant_not_found}
|
||||
@ -536,8 +556,18 @@ defmodule Mobilizon.Events do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets a single participant.
|
||||
Raises `Ecto.NoResultsError` if the participant does not exist.
|
||||
Gets a single participation for an event and actor.
|
||||
|
||||
Raises `Ecto.NoResultsError` if the Participant does not exist.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> get_participant!(123, 19)
|
||||
%Participant{}
|
||||
|
||||
iex> get_participant!(456, 5)
|
||||
** (Ecto.NoResultsError)
|
||||
|
||||
"""
|
||||
@spec get_participant!(integer | String.t(), integer | String.t()) :: Participant.t()
|
||||
def get_participant!(event_id, actor_id) do
|
||||
@ -554,35 +584,20 @@ defmodule Mobilizon.Events do
|
||||
|> Repo.one()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets the default participant role depending on the event join options.
|
||||
"""
|
||||
@spec get_default_participant_role(Event.t()) :: :participant | :not_approved
|
||||
def get_default_participant_role(%Event{join_options: :free}), do: :participant
|
||||
def get_default_participant_role(%Event{join_options: _}), do: :not_approved
|
||||
@default_participant_roles [:participant, :moderator, :administrator, :creator]
|
||||
|
||||
@doc """
|
||||
Creates a participant.
|
||||
Returns the list of participants for an event.
|
||||
Default behaviour is to not return :not_approved participants
|
||||
"""
|
||||
@spec create_participant(map) :: {:ok, Participant.t()} | {:error, Ecto.Changeset.t()}
|
||||
def create_participant(attrs \\ %{}) do
|
||||
with {:ok, %Participant{} = participant} <-
|
||||
%Participant{}
|
||||
|> Participant.changeset(attrs)
|
||||
|> Repo.insert() do
|
||||
{:ok, Repo.preload(participant, [:event, :actor])}
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Updates a participant.
|
||||
"""
|
||||
@spec update_participant(Participant.t(), map) ::
|
||||
{:ok, Participant.t()} | {:error, Ecto.Changeset.t()}
|
||||
def update_participant(%Participant{} = participant, attrs) do
|
||||
participant
|
||||
|> Participant.changeset(attrs)
|
||||
|> Repo.update()
|
||||
@spec list_participants_for_event(String.t(), list(atom()), integer | nil, integer | nil) ::
|
||||
[Participant.t()]
|
||||
def list_participants_for_event(uuid, roles \\ @default_participant_roles, page, limit) do
|
||||
uuid
|
||||
|> list_participants_for_event_query()
|
||||
|> filter_role(roles)
|
||||
|> Page.paginate(page, limit)
|
||||
|> Repo.all()
|
||||
end
|
||||
|
||||
@doc """
|
||||
@ -596,84 +611,43 @@ defmodule Mobilizon.Events do
|
||||
[%Participant{}, ...]
|
||||
|
||||
"""
|
||||
def list_participations_for_user(
|
||||
user_id,
|
||||
after_datetime \\ nil,
|
||||
before_datetime \\ nil,
|
||||
page \\ nil,
|
||||
limit \\ nil
|
||||
)
|
||||
|
||||
def list_participations_for_user(user_id, %DateTime{} = after_datetime, nil, page, limit) do
|
||||
@spec list_participations_for_user(
|
||||
integer,
|
||||
DateTime.t() | nil,
|
||||
DateTime.t() | nil,
|
||||
integer | nil,
|
||||
integer | nil
|
||||
) :: list(Participant.t())
|
||||
def list_participations_for_user(user_id, after_datetime, before_datetime, page, limit) do
|
||||
user_id
|
||||
|> do_list_participations_for_user(page, limit)
|
||||
|> where([_p, e, _a], e.begins_on > ^after_datetime)
|
||||