Fix participation section, show how many places are available

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
Thomas Citharel 2019-10-11 15:06:58 +02:00
parent 69ea0f9721
commit a16832a446
7 changed files with 118 additions and 50 deletions

View File

@ -101,7 +101,8 @@ export const FETCH_EVENT = gql`
# }, # },
participantStats { participantStats {
approved, approved,
unapproved unapproved,
participants
}, },
tags { tags {
${tagsQuery} ${tagsQuery}
@ -257,7 +258,8 @@ export const CREATE_EVENT = gql`
}, },
participantStats { participantStats {
approved, approved,
unapproved unapproved,
participants
}, },
tags { tags {
${tagsQuery} ${tagsQuery}
@ -341,7 +343,8 @@ export const EDIT_EVENT = gql`
}, },
participantStats { participantStats {
approved, approved,
unapproved unapproved,
participants
}, },
tags { tags {
${tagsQuery} ${tagsQuery}
@ -407,7 +410,8 @@ export const PARTICIPANTS = gql`
participantStats { participantStats {
approved, approved,
unapproved, unapproved,
rejected rejected,
participants
} }
} }
} }

View File

@ -273,5 +273,6 @@
"{count} participants": "{count} participants", "{count} participants": "{count} participants",
"{count} requests waiting": "{count} requests waiting", "{count} requests waiting": "{count} requests waiting",
"{license} guarantees {respect} of the people who will use it. Since {source}, anyone can audit it, which guarantees its transparency.": "{license} guarantees {respect} of the people who will use it. Since {source}, anyone can audit it, which guarantees its transparency.", "{license} guarantees {respect} of the people who will use it. Since {source}, anyone can audit it, which guarantees its transparency.": "{license} guarantees {respect} of the people who will use it. Since {source}, anyone can audit it, which guarantees its transparency.",
"© The Mobilizon Contributors {date} - Made with Elixir, Phoenix, VueJS & with some love and some weeks": "© The Mobilizon Contributors {date} - Made with Elixir, Phoenix, VueJS & with some love and some weeks" "© The Mobilizon Contributors {date} - Made with Elixir, Phoenix, VueJS & with some love and some weeks": "© The Mobilizon Contributors {date} - Made with Elixir, Phoenix, VueJS & with some love and some weeks",
"All the places have already been taken": "All the places have been taken|One place is still available|{places} places are still available"
} }

View File

@ -312,5 +312,6 @@
"{count} participants": "Un⋅e participant⋅e|{count} participant⋅e⋅s", "{count} participants": "Un⋅e participant⋅e|{count} participant⋅e⋅s",
"{count} requests waiting": "Un⋅e demande en attente|{count} demandes en attente", "{count} requests waiting": "Un⋅e demande en attente|{count} demandes en attente",
"{license} guarantees {respect} of the people who will use it. Since {source}, anyone can audit it, which guarantees its transparency.": "{license} garantit {respect} des personnes qui l'utiliseront. Puisque {source}, il est publiquement auditable, ce qui garantit sa transparence.", "{license} guarantees {respect} of the people who will use it. Since {source}, anyone can audit it, which guarantees its transparency.": "{license} garantit {respect} des personnes qui l'utiliseront. Puisque {source}, il est publiquement auditable, ce qui garantit sa transparence.",
"© The Mobilizon Contributors {date} - Made with Elixir, Phoenix, VueJS & with some love and some weeks": "© Les contributeurs de Mobilizon {date} - Fait avec Elixir, Phoenix, VueJS & et de l'amour et des semaines" "© The Mobilizon Contributors {date} - Made with Elixir, Phoenix, VueJS & with some love and some weeks": "© Les contributeurs de Mobilizon {date} - Fait avec Elixir, Phoenix, VueJS & et de l'amour et des semaines",
"All the places have already been taken": "Toutes les places ont été prises|Une place est encore disponible|{places} places sont encore disponibles"
} }

View File

@ -112,6 +112,7 @@ export interface IEvent {
approved: number; approved: number;
unapproved: number; unapproved: number;
rejected: number; rejected: number;
participants: number;
}; };
participants: IParticipant[]; participants: IParticipant[];
@ -178,7 +179,7 @@ export class EventModel implements IEvent {
publishAt = new Date(); publishAt = new Date();
participantStats = { approved: 0, unapproved: 0, rejected: 0 }; participantStats = { approved: 0, unapproved: 0, rejected: 0, participants: 0 };
participants: IParticipant[] = []; participants: IParticipant[] = [];
relatedEvents: IEvent[] = []; relatedEvents: IEvent[] = [];

View File

@ -17,17 +17,24 @@
<div class="date-component"> <div class="date-component">
<date-calendar-icon :date="event.beginsOn"></date-calendar-icon> <date-calendar-icon :date="event.beginsOn"></date-calendar-icon>
</div> </div>
<h1 class="title">{{ event.title }}</h1> <div class="title-and-informations">
<h1 class="title">{{ event.title }}</h1>
<span>
<small v-if="event.participantStats.approved > 0 && !actorIsParticipant">
{{ $tc('One person is going', event.participantStats.approved, {approved: event.participantStats.approved}) }}
</small>
<small v-else-if="event.participantStats.approved > 0 && actorIsParticipant">
{{ $tc('You and one other person are going to this event', event.participantStats.participants, { approved: event.participantStats.participants }) }}
</small>
<small v-if="event.options.maximumAttendeeCapacity">
{{ $tc('All the places have already been taken', numberOfPlacesStillAvailable, { places: numberOfPlacesStillAvailable}) }}
</small>
</span>
</div>
</div> </div>
<div class="has-text-right" v-if="new Date(endDate) > new Date()"> <div class="event-participation has-text-right" v-if="new Date(endDate) > new Date()">
<small v-if="event.participantStats.approved > 0 && !actorIsParticipant">
{{ $tc('One person is going', event.participantStats.approved, {approved: event.participantStats.approved}) }}
</small>
<small v-else-if="event.participantStats.approved > 0 && actorIsParticipant">
{{ $tc('You and one other person are going to this event', event.participantStats.approved - 1, {approved: event.participantStats.approved - 1}) }}
</small>
<participation-button <participation-button
v-if="currentActor.id && !actorIsOrganizer && !event.draft" v-if="currentActor.id && !actorIsOrganizer && !event.draft && (eventCapacityOK || actorIsParticipant)"
:participation="participations[0]" :participation="participations[0]"
:current-actor="currentActor" :current-actor="currentActor"
@joinEvent="joinEvent" @joinEvent="joinEvent"
@ -46,14 +53,14 @@
</div> </div>
<div class="metadata columns"> <div class="metadata columns">
<div class="column is-three-quarters-desktop"> <div class="column is-three-quarters-desktop">
<p class="tags" v-if="event.category || event.tags.length > 0"> <p class="tags" v-if="event.tags.length > 0">
<b-tag type="is-warning" size="is-medium" v-if="event.draft">{{ $t('Draft') }}</b-tag> <b-tag type="is-warning" size="is-medium" v-if="event.draft">{{ $t('Draft') }}</b-tag>
<b-tag type="is-success" v-if="event.tags" v-for="tag in event.tags" :key="tag.title">{{ tag.title }}</b-tag> <span class="visibility" v-if="!event.draft">
<span v-if="event.tags > 0"></span>
<span class="visibility" v-if="!event.draft">
<b-tag type="is-info" v-if="event.visibility === EventVisibility.PUBLIC">{{ $t('Public event') }}</b-tag> <b-tag type="is-info" v-if="event.visibility === EventVisibility.PUBLIC">{{ $t('Public event') }}</b-tag>
<b-tag type="is-info" v-if="event.visibility === EventVisibility.UNLISTED">{{ $t('Private event') }}</b-tag> <b-tag type="is-info" v-if="event.visibility === EventVisibility.UNLISTED">{{ $t('Private event') }}</b-tag>
</span> </span>
<b-tag type="is-success" v-if="event.tags" v-for="tag in event.tags" :key="tag.title">{{ tag.title }}</b-tag>
<span v-if="event.tags > 0"></span>
</p> </p>
<div class="date-and-add-to-calendar"> <div class="date-and-add-to-calendar">
<div class="date-and-privacy" v-if="event.beginsOn"> <div class="date-and-privacy" v-if="event.beginsOn">
@ -147,6 +154,9 @@
<div class="columns"> <div class="columns">
<div class="column is-half has-text-centered"> <div class="column is-half has-text-centered">
<h3 class="title">{{ $t('Share this event') }}</h3> <h3 class="title">{{ $t('Share this event') }}</h3>
<small class="maximumNumberOfPlacesWarning" v-if="!eventCapacityOK">
{{ $t('All the places have already been taken') }}
</small>
<div> <div>
<b-icon icon="mastodon" size="is-large" type="is-primary" /> <b-icon icon="mastodon" size="is-large" type="is-primary" />
<a :href="facebookShareUrl" target="_blank" rel="nofollow noopener"><b-icon icon="facebook" size="is-large" type="is-primary" /></a> <a :href="facebookShareUrl" target="_blank" rel="nofollow noopener"><b-icon icon="facebook" size="is-large" type="is-primary" /></a>
@ -346,7 +356,7 @@ export default class Event extends EventMixin {
const participationCachedData = store.readQuery<{ person: IPerson }>({ const participationCachedData = store.readQuery<{ person: IPerson }>({
query: EVENT_PERSON_PARTICIPATION, query: EVENT_PERSON_PARTICIPATION,
variables: { eventId: this.event.id, name: identity.preferredUsername }, variables: { eventId: this.event.id, actorId: identity.id },
}); });
if (participationCachedData == null) return; if (participationCachedData == null) return;
const { person } = participationCachedData; const { person } = participationCachedData;
@ -357,7 +367,7 @@ export default class Event extends EventMixin {
person.participations.push(data.joinEvent); person.participations.push(data.joinEvent);
store.writeQuery({ store.writeQuery({
query: EVENT_PERSON_PARTICIPATION, query: EVENT_PERSON_PARTICIPATION,
variables: { eventId: this.event.id, name: identity.preferredUsername }, variables: { eventId: this.event.id, actorId: identity.id },
data: { person }, data: { person },
}); });
@ -408,7 +418,7 @@ export default class Event extends EventMixin {
const participationCachedData = store.readQuery<{ person: IPerson }>({ const participationCachedData = store.readQuery<{ person: IPerson }>({
query: EVENT_PERSON_PARTICIPATION, query: EVENT_PERSON_PARTICIPATION,
variables: { eventId: this.event.id, name: this.currentActor.preferredUsername }, variables: { eventId: this.event.id, actorId: this.currentActor.id },
}); });
if (participationCachedData == null) return; if (participationCachedData == null) return;
const { person } = participationCachedData; const { person } = participationCachedData;
@ -420,7 +430,7 @@ export default class Event extends EventMixin {
person.participations = []; person.participations = [];
store.writeQuery({ store.writeQuery({
query: EVENT_PERSON_PARTICIPATION, query: EVENT_PERSON_PARTICIPATION,
variables: { eventId: this.event.id, name: this.currentActor.preferredUsername }, variables: { eventId: this.event.id, actorId: this.currentActor.id },
data: { person }, data: { person },
}); });
@ -497,6 +507,15 @@ export default class Event extends EventMixin {
return meta.getAttribute('content') || ''; return meta.getAttribute('content') || '';
} }
get eventCapacityOK(): boolean {
if (!this.event.options.maximumAttendeeCapacity) return true;
return this.event.options.maximumAttendeeCapacity > this.event.participantStats.participants;
}
get numberOfPlacesStillAvailable(): number {
return this.event.options.maximumAttendeeCapacity - this.event.participantStats.participants;
}
} }
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@ -600,22 +619,40 @@ export default class Event extends EventMixin {
/*align-self: center;*/ /*align-self: center;*/
align-items: stretch; align-items: stretch;
/*align-content: space-around;*/ /*align-content: space-around;*/
padding: 15px 10px 0; padding: 7.5px 10px 0;
margin-bottom: 1rem;
div.title-wrapper { div.title-wrapper {
display: flex; display: flex;
flex: 1 1 auto; flex: 1 1 auto;
div.title-and-informations {
h1.title {
font-weight: normal;
word-break: break-word;
font-size: 1.7em;
margin-bottom: 0;
}
span small {
&:not(:last-child):after {
content: '⋅'
}
&:not(:first-child) {
padding-left: 3px;
}
}
}
div.date-component { div.date-component {
margin-right: 16px; margin-right: 16px;
} }
}
h1.title { .event-participation small {
font-weight: normal; display: block;
word-break: break-word;
font-size: 1.7em;
}
} }
.participate-button { .participate-button {
@ -660,12 +697,16 @@ export default class Event extends EventMixin {
p.tags { p.tags {
span { span {
&.tag.is-success { &.tag {
&::before { margin: 0 2px 4px;
content: '#';
&.is-success {
&::before {
content: '#';
}
text-transform: uppercase;
color: #111111;
} }
text-transform: uppercase;
color: #111111;
} }
margin: auto 5px; margin: auto 5px;
@ -733,6 +774,36 @@ export default class Event extends EventMixin {
padding: 10rem 0; padding: 10rem 0;
} }
h3 {
display: block;
color: $primary;
font-size: 3rem;
text-decoration: underline;
text-decoration-color: $secondary;
cursor: pointer;
max-width: 20rem;
}
.column:first-child {
h3 {
margin: 0 auto 1rem;
font-weight: normal;
}
small.maximumNumberOfPlacesWarning {
margin: 0 auto 1rem;
display: block;
}
}
.column:last-child {
h3 {
margin-right: 0;
margin-left: auto;
}
}
.add-to-calendar { .add-to-calendar {
background-repeat: no-repeat; background-repeat: no-repeat;
background-size: 400px; background-size: 400px;
@ -749,19 +820,6 @@ export default class Event extends EventMixin {
height: 40%; height: 40%;
width: 1px; width: 1px;
} }
h3 {
display: block;
color: $primary;
font-size: 3rem;
text-decoration: underline;
text-decoration-color: $secondary;
cursor: pointer;
max-width: 20rem;
margin-right: 0;
margin-left: auto;
}
} }
} }
} }

View File

@ -102,7 +102,7 @@ defmodule MobilizonWeb.Resolvers.Event do
approved: Mobilizon.Events.count_approved_participants(id), approved: Mobilizon.Events.count_approved_participants(id),
unapproved: Mobilizon.Events.count_unapproved_participants(id), unapproved: Mobilizon.Events.count_unapproved_participants(id),
rejected: Mobilizon.Events.count_rejected_participants(id), rejected: Mobilizon.Events.count_rejected_participants(id),
participants: Mobilizon.Events.count_participant_participants(id), participants: Mobilizon.Events.count_participant_participants(id)
}} }}
end end

View File

@ -115,7 +115,10 @@ defmodule MobilizonWeb.Schema.EventType do
field(:approved, :integer, description: "The number of approved participants") field(:approved, :integer, description: "The number of approved participants")
field(:unapproved, :integer, description: "The number of unapproved participants") field(:unapproved, :integer, description: "The number of unapproved participants")
field(:rejected, :integer, description: "The number of rejected participants") field(:rejected, :integer, description: "The number of rejected participants")
field(:participants, :integer, description: "The number of simple participants (excluding creators)")
field(:participants, :integer,
description: "The number of simple participants (excluding creators)"
)
end end
object :event_offer do object :event_offer do