mobilizon.chapril.org-mobil.../js/src/views/Event/MyEventsView.vue

441 lines
14 KiB
Vue

<template>
<div class="container mx-auto px-1">
<h1>
{{ t("My events") }}
</h1>
<p>
{{
t(
"You will find here all the events you have created or of which you are a participant, as well as events organized by groups you follow or are a member of."
)
}}
</p>
<div class="my-2" v-if="!hideCreateEventButton">
<o-button
tag="router-link"
variant="primary"
:to="{ name: RouteName.CREATE_EVENT }"
>{{ t("Create event") }}</o-button
>
</div>
<!-- <o-loading v-model:active="$apollo.loading"></o-loading> -->
<div class="flex flex-wrap gap-4 items-start">
<div
class="rounded p-3 flex-auto md:flex-none bg-zinc-300 dark:bg-zinc-700"
>
<o-field>
<o-switch v-model="showUpcoming">{{
showUpcoming ? t("Upcoming events") : t("Past events")
}}</o-switch>
</o-field>
<o-field v-if="showUpcoming">
<o-checkbox v-model="showDrafts">{{ t("Drafts") }}</o-checkbox>
</o-field>
<o-field v-if="showUpcoming">
<o-checkbox v-model="showAttending">{{ t("Attending") }}</o-checkbox>
</o-field>
<o-field v-if="showUpcoming">
<o-checkbox v-model="showMyGroups">{{
t("From my groups")
}}</o-checkbox>
</o-field>
<p v-if="!showUpcoming">
{{
t(
"You have attended {count} events in the past.",
{
count: pastParticipations.total,
},
pastParticipations.total
)
}}
</p>
<o-field
class="date-filter"
expanded
:label="
showUpcoming
? t('Showing events starting on')
: t('Showing events before')
"
labelFor="events-start-datepicker"
>
<o-datepicker
v-model="dateFilter"
:first-day-of-week="firstDayOfWeek"
id="events-start-datepicker"
/>
<o-button
@click="dateFilter = new Date()"
class="reset-area"
icon-left="close"
:title="t('Clear date filter field')"
/>
</o-field>
</div>
<div class="flex-1 min-w-[300px]">
<section
class="py-4 first:pt-0"
v-if="showUpcoming && showDrafts && drafts && drafts.total > 0"
>
<h2 class="text-2xl mb-2">{{ t("Drafts") }}</h2>
<multi-event-minimalist-card
:events="drafts.elements"
:showOrganizer="true"
/>
<o-pagination
class="mt-4"
v-show="drafts.total > LOGGED_USER_DRAFTS_LIMIT"
:total="drafts.total"
v-model:current="draftsPage"
:per-page="LOGGED_USER_DRAFTS_LIMIT"
:aria-next-label="t('Next page')"
:aria-previous-label="t('Previous page')"
:aria-page-label="t('Page')"
:aria-current-label="t('Current page')"
>
</o-pagination>
</section>
<section
class="py-4 first:pt-0"
v-if="
showUpcoming && monthlyFutureEvents && monthlyFutureEvents.size > 0
"
>
<transition-group name="list" tag="p">
<div
class="mb-5"
v-for="month of monthlyFutureEvents"
:key="month[0]"
>
<h2 class="text-2xl">{{ month[0] }}</h2>
<div v-for="element in month[1]" :key="element.id">
<event-participation-card
v-if="'role' in element"
:participation="element"
@event-deleted="eventDeleted"
class="participation"
/>
<event-minimalist-card
v-else-if="
element.id &&
!monthParticipationsIds(month[1]).includes(element?.id)
"
:event="element"
class="participation"
/>
</div>
</div>
</transition-group>
<div class="columns is-centered">
<o-button
class="column is-narrow"
v-if="
hasMoreFutureParticipations &&
futureParticipations &&
futureParticipations.length === limit
"
@click="loadMoreFutureParticipations"
size="large"
variant="primary"
>{{ t("Load more") }}</o-button
>
</div>
</section>
<section
class="text-center not-found"
v-if="
showUpcoming &&
monthlyFutureEvents &&
monthlyFutureEvents.size === 0 &&
true // !$apollo.loading
"
>
<div class="img-container h-64" />
<div class="text-center prose dark:prose-invert">
<p>
{{
t(
"You don't have any upcoming events. Maybe try another filter?"
)
}}
</p>
<i18n-t
keypath="Do you wish to {create_event} or {explore_events}?"
tag="p"
>
<template #create_event>
<router-link :to="{ name: RouteName.CREATE_EVENT }">{{
t("create an event")
}}</router-link>
</template>
<template #explore_events>
<router-link :to="{ name: RouteName.SEARCH }">{{
t("explore the events")
}}</router-link>
</template>
</i18n-t>
</div>
</section>
<section v-if="!showUpcoming && pastParticipations.elements.length > 0">
<transition-group name="list" tag="p">
<div v-for="month in monthlyPastParticipations" :key="month[0]">
<span class="past-month">{{ month[0] }}</span>
<event-participation-card
v-for="participation in month[1]"
:key="participation.id"
:participation="(participation as IParticipant)"
:options="{ hideDate: false }"
@event-deleted="eventDeleted"
class="participation"
/>
</div>
</transition-group>
<div class="columns is-centered">
<o-button
class="column is-narrow"
v-if="
hasMorePastParticipations &&
pastParticipations.elements.length === limit
"
@click="loadMorePastParticipations"
size="large"
variant="primary"
>{{ t("Load more") }}</o-button
>
</div>
</section>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { ParticipantRole } from "@/types/enums";
import RouteName from "@/router/name";
import type { IParticipant } from "../../types/participant.model";
import { LOGGED_USER_DRAFTS } from "../../graphql/actor";
import type { IEvent } from "../../types/event.model";
import MultiEventMinimalistCard from "../../components/Event/MultiEventMinimalistCard.vue";
import EventMinimalistCard from "../../components/Event/EventMinimalistCard.vue";
import {
LOGGED_USER_PARTICIPATIONS,
LOGGED_USER_UPCOMING_EVENTS,
} from "@/graphql/participant";
import { useQuery } from "@vue/apollo-composable";
import { computed, inject, ref, defineAsyncComponent } from "vue";
import { IUser } from "@/types/current-user.model";
import {
booleanTransformer,
integerTransformer,
useRouteQuery,
} from "vue-use-route-query";
import { Locale } from "date-fns";
import { useI18n } from "vue-i18n";
import { useRestrictions } from "@/composition/apollo/config";
import { useHead } from "@vueuse/head";
const EventParticipationCard = defineAsyncComponent(
() => import("@/components/Event/EventParticipationCard.vue")
);
type Eventable = IParticipant | IEvent;
const { t } = useI18n({ useScope: "global" });
const futurePage = ref(1);
const pastPage = ref(1);
const limit = ref(10);
const showUpcoming = useRouteQuery("showUpcoming", true, booleanTransformer);
const showDrafts = useRouteQuery("showDrafts", true, booleanTransformer);
const showAttending = useRouteQuery("showAttending", true, booleanTransformer);
const showMyGroups = useRouteQuery("showMyGroups", false, booleanTransformer);
const dateFilter = useRouteQuery("dateFilter", new Date(), {
fromQuery(query) {
if (query && /(\d{4}-\d{2}-\d{2})/.test(query)) {
return new Date(`${query}T00:00:00Z`);
}
return new Date();
},
toQuery(value: Date) {
const pad = (number: number) => {
if (number < 10) {
return "0" + number;
}
return number;
};
return `${value.getFullYear()}-${pad(value.getMonth() + 1)}-${pad(
value.getDate()
)}`;
},
});
const hasMoreFutureParticipations = ref(true);
const hasMorePastParticipations = ref(true);
const {
result: loggedUserUpcomingEventsResult,
fetchMore: fetchMoreUpcomingEvents,
} = useQuery<{
loggedUser: IUser;
}>(LOGGED_USER_UPCOMING_EVENTS, () => ({
page: 1,
limit: 10,
afterDateTime: dateFilter.value,
}));
const futureParticipations = computed(
() =>
loggedUserUpcomingEventsResult.value?.loggedUser.participations.elements ??
[]
);
const groupEvents = computed(
() =>
loggedUserUpcomingEventsResult.value?.loggedUser.followedGroupEvents
.elements ?? []
);
const LOGGED_USER_DRAFTS_LIMIT = 10;
const draftsPage = useRouteQuery("draftsPage", 1, integerTransformer);
const { result: draftsResult } = useQuery<{
loggedUser: Pick<IUser, "drafts">;
}>(
LOGGED_USER_DRAFTS,
() => ({ page: draftsPage.value, limit: LOGGED_USER_DRAFTS_LIMIT }),
() => ({ fetchPolicy: "cache-and-network" })
);
const drafts = computed(() => draftsResult.value?.loggedUser.drafts);
const { result: participationsResult, fetchMore: fetchMoreParticipations } =
useQuery<{
loggedUser: Pick<IUser, "participations">;
}>(LOGGED_USER_PARTICIPATIONS, () => ({ page: 1, limit: 10 }));
const pastParticipations = computed(
() =>
participationsResult.value?.loggedUser.participations ?? {
elements: [],
total: 0,
}
);
const monthlyEvents = (
elements: Eventable[],
revertSort = false
): Map<string, Eventable[]> => {
const res = elements.filter((element: Eventable) => {
if ("role" in element) {
return (
element.event.beginsOn != null &&
element.role !== ParticipantRole.REJECTED
);
}
return element.beginsOn != null;
});
if (revertSort) {
res.sort((a: Eventable, b: Eventable) => {
const aTime = "role" in a ? a.event.beginsOn : a.beginsOn;
const bTime = "role" in b ? b.event.beginsOn : b.beginsOn;
return new Date(bTime).getTime() - new Date(aTime).getTime();
});
} else {
res.sort((a: Eventable, b: Eventable) => {
const aTime = "role" in a ? a.event.beginsOn : a.beginsOn;
const bTime = "role" in b ? b.event.beginsOn : b.beginsOn;
return new Date(aTime).getTime() - new Date(bTime).getTime();
});
}
return res.reduce((acc: Map<string, Eventable[]>, element: Eventable) => {
const month = new Date(
"role" in element ? element.event.beginsOn : element.beginsOn
).toLocaleDateString(undefined, {
year: "numeric",
month: "long",
});
const filteredElements: Eventable[] = acc.get(month) || [];
filteredElements.push(element);
acc.set(month, filteredElements);
return acc;
}, new Map());
};
const monthlyFutureEvents = computed((): Map<string, Eventable[]> => {
let eventable = [] as Eventable[];
if (showAttending.value) {
eventable = [...eventable, ...futureParticipations.value];
}
if (showMyGroups.value) {
eventable = [...eventable, ...groupEvents.value.map(({ event }) => event)];
}
return monthlyEvents(eventable);
});
const monthlyPastParticipations = computed((): Map<string, Eventable[]> => {
return monthlyEvents(pastParticipations.value.elements, true);
});
const monthParticipationsIds = (elements: Eventable[]): string[] => {
const res = elements.filter((element: Eventable) => {
return "role" in element;
}) as IParticipant[];
return res.map(({ event }: { event: IEvent }) => {
return event.id as string;
});
};
const loadMoreFutureParticipations = (): void => {
futurePage.value += 1;
if (fetchMoreUpcomingEvents) {
fetchMoreUpcomingEvents({
// New variables
variables: {
page: futurePage.value,
limit: limit.value,
},
});
}
};
const loadMorePastParticipations = (): void => {
pastPage.value += 1;
if (fetchMoreParticipations) {
fetchMoreParticipations({
// New variables
variables: {
page: pastPage.value,
limit: limit.value,
},
});
}
};
const eventDeleted = (eventid: string): void => {
futureParticipations.value = futureParticipations.value.filter(
(participation) => participation.event.id !== eventid
);
pastParticipations.value = {
elements: pastParticipations.value.elements.filter(
(participation) => participation.event.id !== eventid
),
total: pastParticipations.value.total - 1,
};
};
const { restrictions } = useRestrictions();
const hideCreateEventButton = computed((): boolean => {
return restrictions.value?.onlyGroupsCanCreateEvents === true;
});
const dateFnsLocale = inject<Locale>("dateFnsLocale");
const firstDayOfWeek = computed((): number => {
return dateFnsLocale?.options?.weekStartsOn ?? 0;
});
useHead({
title: computed(() => t("My events")),
});
</script>