Skip to content

Commit 88981f8

Browse files
authored
Merge pull request #408 from AOEpeople/feature/#261413-event-participations
added modal that shows the participants for an event on that day
2 parents 19712cb + 3c4411a commit 88981f8

File tree

9 files changed

+193
-2
lines changed

9 files changed

+193
-2
lines changed

src/Mealz/MealBundle/Controller/EventController.php

+19
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
namespace App\Mealz\MealBundle\Controller;
66

77
use App\Mealz\MealBundle\Entity\Event;
8+
use App\Mealz\MealBundle\Entity\Participant;
89
use App\Mealz\MealBundle\Event\EventParticipationUpdateEvent;
910
use App\Mealz\MealBundle\Repository\DayRepositoryInterface;
1011
use App\Mealz\MealBundle\Repository\EventRepositoryInterface;
@@ -149,4 +150,22 @@ public function leave(DateTime $date): JsonResponse
149150

150151
return new JsonResponse($this->eventPartSrv->getEventParticipationData($day, $profile), 200);
151152
}
153+
154+
public function getEventParticipants(DateTime $date): JsonResponse
155+
{
156+
$day = $this->dayRepo->getDayByDate($date);
157+
158+
if (null === $day) {
159+
return new JsonResponse(['message' => 'Could not find day'], 404);
160+
}
161+
162+
$participants = $day->getEvent()->getParticipants();
163+
164+
$participantsNames = array_map(
165+
fn (Participant $participant) => $participant->getProfile()->getFullName(),
166+
$participants->toArray()
167+
);
168+
169+
return new JsonResponse($participantsNames, 200);
170+
}
152171
}

src/Mealz/MealBundle/Resources/config/routing.yml

+5
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,11 @@ MealzMealBundle_api_participations:
306306
defaults: { _controller: App\Mealz\MealBundle\Controller\ParticipantController::getParticipationsForWeek }
307307
methods: [ GET ]
308308

309+
MealzMealBundle_api_event_participations:
310+
path: /api/participations/event/{date}
311+
defaults: { _controller: App\Mealz\MealBundle\Controller\EventController::getEventParticipants }
312+
methods: [ GET ]
313+
309314
MealzMealBundle_api_non_participating:
310315
path: /api/participations/{week}/abstaining
311316
defaults: { _controller: App\Mealz\MealBundle\Controller\ParticipantController::getProfilesWithoutParticipation }
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import useApi from "./api";
2+
import { IMessage } from "@/interfaces/IMessage";
3+
4+
export default async function getEventParticipants(date: string) {
5+
const { error, response, request } = useApi<string[] | IMessage>(
6+
'GET',
7+
`api/participations/event/${date}`
8+
);
9+
10+
await request();
11+
12+
return { error, response };
13+
}

src/Resources/src/components/dashboard/EventData.vue

+7-1
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,13 @@
2323
<span class="mr-[5px] inline-block grow self-center break-words text-[12px] font-bold leading-[20px] tracking-[0.5px] text-primary-1 min-[380px]:text-note">
2424
{{ getEventById(day.event.eventId)?.title }}
2525
</span>
26-
<ParticipationCounter
26+
<EventPopup
2727
class="justify-self-end"
28+
:event-title="getEventById(day.event.eventId)?.title"
29+
:date="day.date.date"
30+
/>
31+
<ParticipationCounter
32+
class="mx-[5px] justify-self-end"
2833
:limit="0"
2934
:mealCSS="!day.isLocked ? 'bg-primary-4' : 'bg-[#80909F]'"
3035
>
@@ -49,6 +54,7 @@ import CheckBox from '../misc/CheckBox.vue';
4954
import EventIcon from '../misc/EventIcon.vue';
5055
import BannerSpacer from '../misc/BannerSpacer.vue';
5156
import { useI18n } from 'vue-i18n';
57+
import EventPopup from '@/components/eventParticipation/EventPopup.vue';
5258
5359
defineProps<{
5460
day: Day
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
<template>
2+
<InformationCircleIcon
3+
class="h-[24px] w-[24px] text-primary hover:cursor-pointer"
4+
@click="showParticipations = true"
5+
/>
6+
<PopupModal
7+
:isOpen="showParticipations"
8+
>
9+
<div
10+
class="grid max-h-[70vh] min-w-[300px] max-w-[310px] grid-cols-1 grid-rows-[auto_minmax(0,1fr)_auto] md:max-w-lg"
11+
>
12+
<div class="flex h-[48px] flex-row gap-2 rounded-t-lg bg-[#1c5298] p-2">
13+
<span class="grow self-center justify-self-center truncate font-bold uppercase leading-4 tracking-[3px] text-white">
14+
{{ t('dashboard.participations').replace('%EventTitle%', eventTitle) }}
15+
</span>
16+
<XCircleIcon
17+
class="h-8 w-8 cursor-pointer self-end text-white transition-transform hover:scale-[120%] hover:text-[#FAFAFA]"
18+
@click="showParticipations = false"
19+
/>
20+
</div>
21+
<RefreshIcon
22+
v-if="isLoading"
23+
class="aspect-[1/1] h-[75px] animate-spin-loading place-self-center p-4 text-primary drop-shadow-[0_0_0.35rem_rgba(0,0,0,0.75)]"
24+
/>
25+
<ul
26+
v-else-if="participations.length > 0"
27+
class="overflow-y-auto p-4"
28+
>
29+
<li
30+
v-for="(participation, index) in participations"
31+
:key="`${participation}_${index}`"
32+
class="border-b-2 p-2 last:border-b-0"
33+
>
34+
{{ participation }}
35+
</li>
36+
</ul>
37+
<span
38+
v-else
39+
class="p-4"
40+
>
41+
{{ t('dashboard.noParticipants') }}
42+
</span>
43+
<span
44+
class="w-full border-t-2 p-2 text-center font-bold"
45+
>
46+
{{ t('dashboard.participationCount').replace('%count%', participations.length.toString()) }}
47+
</span>
48+
</div>
49+
</PopupModal>
50+
</template>
51+
52+
<script setup lang="ts">
53+
import { ref, watch } from 'vue';
54+
import PopupModal from '../misc/PopupModal.vue';
55+
import { InformationCircleIcon, RefreshIcon } from '@heroicons/vue/outline';
56+
import { XCircleIcon } from '@heroicons/vue/solid';
57+
import { useI18n } from 'vue-i18n';
58+
import { useEvents } from '@/stores/eventsStore';
59+
60+
const { t } = useI18n();
61+
const { getParticipantsForEvent } = useEvents();
62+
63+
const props = defineProps<{
64+
eventTitle: string,
65+
date: string
66+
}>();
67+
68+
const showParticipations = ref(false);
69+
const participations = ref([]);
70+
const isLoading = ref(false);
71+
72+
watch(
73+
showParticipations,
74+
async () => {
75+
if (showParticipations.value === true) {
76+
isLoading.value = true;
77+
participations.value = await getParticipantsForEvent(props.date);
78+
isLoading.value = false;
79+
}
80+
}
81+
)
82+
</script>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<template>
2+
<TransitionRoot
3+
as="template"
4+
:show="isOpen"
5+
>
6+
<Dialog
7+
class="relative z-50"
8+
>
9+
<div
10+
class="fixed inset-0 flex items-center justify-center bg-[rgba(0,0,0,0.1)]"
11+
>
12+
<TransitionChild
13+
as="template"
14+
enter="duration-300 ease-out"
15+
enter-from="opacity-0"
16+
enter-to="opacity-100"
17+
leave="duration-200 ease-in"
18+
leave-from="opacity-100"
19+
leave-to="opacity-0"
20+
>
21+
<DialogPanel
22+
class="relative overflow-hidden rounded-lg bg-white text-left shadow-xl"
23+
>
24+
<slot />
25+
</DialogPanel>
26+
</TransitionChild>
27+
</div>
28+
</Dialog>
29+
</TransitionRoot>
30+
</template>
31+
32+
<script setup lang="ts">
33+
import { Dialog, DialogPanel, TransitionChild, TransitionRoot } from '@headlessui/vue';
34+
35+
defineProps<{
36+
isOpen: boolean
37+
}>();
38+
</script>

src/Resources/src/locales/de.json

+3
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,9 @@
7979
"new": "neu",
8080
"print": "Heutige Teilnehmer",
8181
"show": "Zeige Teilnehmer",
82+
"noParticipants": "Noch keine Teilnehmer für dieses Event",
83+
"participations": "Teilnahmen \"%EventTitle%\"",
84+
"participationCount": "Es gibt %count% Teilnehmer",
8285
"popover": "Jemand anderes kann jetzt dein Gericht nehmen.",
8386
"slot": {
8487
"timeslot": "Zeitfenster",

src/Resources/src/locales/en.json

+3
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,9 @@
7979
"new": "new",
8080
"print": "Today's Participations",
8181
"show": "Show Participants",
82+
"noParticipants": "No participants for event yet",
83+
"participations": "Participations \"%EventTitle%\"",
84+
"participationCount": "Es gibt %count% Teilnehmer",
8285
"popover": "Someone else can take your meal now.",
8386
"slot": {
8487
"timeslot": "Timeslot",

src/Resources/src/stores/eventsStore.ts

+23-1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import deleteEvent from "@/api/deleteEvent";
1010
import postJoinEvent from "@/api/postJoinEvent";
1111
import useEventsBus from '@/tools/eventBus';
1212
import { deleteLeaveEvent } from "@/api/deleteLeaveEvent";
13+
import getEventParticipants from "@/api/getEventParticipants";
1314

1415
export interface Event {
1516
id: number,
@@ -85,6 +86,7 @@ export function useEvents() {
8586
setTimeout(fetchEvents, TIMEOUT_PERIOD);
8687
}
8788

89+
EventsState.error = '';
8890
EventsState.isLoading = false;
8991
}
9092

@@ -101,6 +103,8 @@ export function useEvents() {
101103
return;
102104
}
103105

106+
EventsState.error = '';
107+
104108
sendFlashMessage({
105109
type: FlashMessageType.INFO,
106110
message: 'events.created'
@@ -126,6 +130,7 @@ export function useEvents() {
126130
event.slug = (response.value as Event).slug;
127131
event.id = (response.value as Event).id;
128132

133+
EventsState.error = '';
129134
sendFlashMessage({
130135
type: FlashMessageType.INFO,
131136
message: 'events.edited'
@@ -146,6 +151,7 @@ export function useEvents() {
146151
EventsState.error = response.value?.message;
147152
} else {
148153
EventsState.events.splice(EventsState.events.findIndex((event) => event.slug === slug), 1);
154+
EventsState.error = '';
149155
sendFlashMessage({
150156
type: FlashMessageType.INFO,
151157
message: 'events.deleted'
@@ -165,6 +171,7 @@ export function useEvents() {
165171
} else if (error.value === true) {
166172
EventsState.error = 'Unknown error occured on joining the event';
167173
} else {
174+
EventsState.error = '';
168175
emit(EVENT_PARTICIPATION_UPDATE, response.value);
169176
}
170177
}
@@ -181,10 +188,24 @@ export function useEvents() {
181188
} else if (error.value === true) {
182189
EventsState.error = 'Unknown error occured on leaving the event';
183190
} else {
191+
EventsState.error = '';
184192
emit(EVENT_PARTICIPATION_UPDATE, response.value);
185193
}
186194
}
187195

196+
async function getParticipantsForEvent(date: string) {
197+
const { error, response } = await getEventParticipants(date);
198+
199+
if (error.value === true && isMessage(response.value) === true) {
200+
EventsState.error = (response.value as IMessage)?.message;
201+
} else if (error.value === true) {
202+
EventsState.error = 'Unknown error occured on getting participants for the event';
203+
} else {
204+
EventsState.error = '';
205+
return (response.value as string[]);
206+
}
207+
}
208+
188209
/**
189210
* Sets the filter string
190211
* @param newFilter The new filter string
@@ -226,6 +247,7 @@ export function useEvents() {
226247
getEventById,
227248
setFilter,
228249
joinEvent,
229-
leaveEvent
250+
leaveEvent,
251+
getParticipantsForEvent
230252
}
231253
}

0 commit comments

Comments
 (0)