2022-07-12 10:55:28 +02:00
|
|
|
<template>
|
2022-08-22 12:12:09 +02:00
|
|
|
<div
|
|
|
|
class="container mx-auto md:py-3 md:px-4 flex flex-col lg:flex-row gap-x-5 gap-y-1"
|
|
|
|
>
|
|
|
|
<aside
|
|
|
|
class="flex-none lg:block lg:sticky top-8 rounded-md px-2 pt-2 w-full lg:w-80 flex-col justify-between mt-2 lg:pb-10 lg:px-8 overflow-y-auto dark:text-slate-100 bg-white dark:bg-mbz-purple"
|
|
|
|
>
|
|
|
|
<o-button
|
|
|
|
@click="toggleFilters"
|
|
|
|
icon-left="filter"
|
|
|
|
class="w-full inline-flex lg:!hidden text-white px-4 py-2 justify-center"
|
|
|
|
>
|
|
|
|
<span v-if="!filtersPanelOpened">{{ t("Hide filters") }}</span>
|
|
|
|
<span v-else>{{ t("Show filters") }}</span>
|
|
|
|
</o-button>
|
|
|
|
<form
|
|
|
|
@submit.prevent="doNewSearch"
|
|
|
|
:class="{ hidden: filtersPanelOpened }"
|
|
|
|
class="lg:block mt-2"
|
|
|
|
>
|
|
|
|
<p class="sr-only">{{ t("Type") }}</p>
|
|
|
|
<ul
|
|
|
|
class="font-medium text-gray-900 dark:text-slate-100 space-y-4 pb-4 border-b border-gray-200 dark:border-gray-500"
|
|
|
|
>
|
|
|
|
<li
|
|
|
|
v-for="content in contentTypeMapping"
|
|
|
|
:key="content.contentType"
|
|
|
|
class="flex gap-1"
|
2022-07-12 10:55:28 +02:00
|
|
|
>
|
2022-08-22 12:12:09 +02:00
|
|
|
<Magnify
|
|
|
|
v-if="content.contentType === ContentType.ALL"
|
|
|
|
:size="24"
|
2022-07-12 10:55:28 +02:00
|
|
|
/>
|
2022-08-22 12:12:09 +02:00
|
|
|
|
|
|
|
<Calendar
|
|
|
|
v-if="content.contentType === ContentType.EVENTS"
|
|
|
|
:size="24"
|
|
|
|
/>
|
|
|
|
|
|
|
|
<AccountMultiple
|
|
|
|
v-if="content.contentType === ContentType.GROUPS"
|
|
|
|
:size="24"
|
|
|
|
/>
|
|
|
|
|
|
|
|
<router-link
|
|
|
|
:to="{
|
|
|
|
...$route,
|
|
|
|
query: { ...$route.query, contentType: content.contentType },
|
|
|
|
}"
|
|
|
|
>
|
|
|
|
{{ content.label }}
|
|
|
|
</router-link>
|
|
|
|
</li>
|
|
|
|
</ul>
|
|
|
|
|
|
|
|
<div
|
|
|
|
class="py-4 border-b border-gray-200 dark:border-gray-500"
|
|
|
|
v-show="contentType !== 'GROUPS'"
|
|
|
|
>
|
|
|
|
<o-switch v-model="isOnline">{{ t("Online events") }}</o-switch>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<filter-section
|
|
|
|
v-show="contentType !== 'GROUPS'"
|
|
|
|
v-model:opened="searchFilterSectionsOpenStatus.eventDate"
|
|
|
|
:title="t('Event date')"
|
|
|
|
>
|
|
|
|
<template #options>
|
|
|
|
<fieldset class="flex flex-col">
|
|
|
|
<legend class="sr-only">{{ t("Event date") }}</legend>
|
|
|
|
<div
|
|
|
|
v-for="(eventStartDateRangeOption, key) in dateOptions"
|
|
|
|
:key="key"
|
2022-07-12 10:55:28 +02:00
|
|
|
>
|
2022-08-22 12:12:09 +02:00
|
|
|
<input
|
|
|
|
:id="key"
|
|
|
|
v-model="when"
|
|
|
|
type="radio"
|
|
|
|
name="eventStartDateRange"
|
|
|
|
:value="key"
|
|
|
|
class="w-4 h-4 border-gray-300 focus:ring-2 focus:ring-blue-300 dark:focus:ring-blue-600 dark:focus:bg-blue-600 dark:bg-gray-700 dark:border-gray-600"
|
|
|
|
/>
|
|
|
|
<label
|
|
|
|
:for="key"
|
|
|
|
class="ml-3 text-sm font-medium text-gray-900 dark:text-gray-300"
|
|
|
|
>{{ eventStartDateRangeOption.label }}</label
|
|
|
|
>
|
|
|
|
</div>
|
|
|
|
</fieldset>
|
|
|
|
</template>
|
|
|
|
<template #preview>
|
|
|
|
<span
|
|
|
|
class="bg-blue-100 text-blue-800 text-sm font-semibold p-0.5 rounded dark:bg-blue-200 dark:text-blue-800 grow-0"
|
2022-07-12 10:55:28 +02:00
|
|
|
>
|
2022-08-22 12:12:09 +02:00
|
|
|
{{
|
|
|
|
Object.entries(dateOptions).find(([key]) => key === when)?.[1]
|
|
|
|
?.label
|
|
|
|
}}
|
|
|
|
</span>
|
|
|
|
</template>
|
|
|
|
</filter-section>
|
|
|
|
|
|
|
|
<filter-section
|
|
|
|
v-show="!isOnline"
|
|
|
|
v-model:opened="searchFilterSectionsOpenStatus.eventDistance"
|
|
|
|
:title="t('Distance')"
|
|
|
|
>
|
|
|
|
<template #options>
|
|
|
|
<fieldset class="flex flex-col">
|
|
|
|
<legend class="sr-only">{{ t("Distance") }}</legend>
|
|
|
|
<div
|
|
|
|
v-for="distanceOption in eventDistance"
|
|
|
|
:key="distanceOption.id"
|
2022-07-12 10:55:28 +02:00
|
|
|
>
|
2022-08-22 12:12:09 +02:00
|
|
|
<input
|
|
|
|
:id="distanceOption.id"
|
|
|
|
v-model="distance"
|
|
|
|
type="radio"
|
|
|
|
name="eventDistance"
|
|
|
|
:value="distanceOption.id"
|
|
|
|
class="w-4 h-4 border-gray-300 focus:ring-2 focus:ring-blue-300 dark:focus:ring-blue-600 dark:focus:bg-blue-600 dark:bg-gray-700 dark:border-gray-600"
|
|
|
|
/>
|
|
|
|
<label
|
|
|
|
:for="distanceOption.id"
|
|
|
|
class="ml-3 text-sm font-medium text-gray-900 dark:text-gray-300"
|
|
|
|
>{{ distanceOption.label }}</label
|
|
|
|
>
|
|
|
|
</div>
|
|
|
|
</fieldset>
|
|
|
|
</template>
|
|
|
|
<template #preview>
|
|
|
|
<span
|
|
|
|
class="bg-blue-100 text-blue-800 text-sm font-semibold p-0.5 rounded dark:bg-blue-200 dark:text-blue-800 grow-0"
|
2022-07-12 10:55:28 +02:00
|
|
|
>
|
2022-08-22 12:12:09 +02:00
|
|
|
{{ eventDistance.find(({ id }) => id === distance)?.label }}
|
|
|
|
</span>
|
|
|
|
</template>
|
|
|
|
</filter-section>
|
|
|
|
|
|
|
|
<filter-section
|
|
|
|
v-show="contentType !== 'GROUPS'"
|
|
|
|
v-model:opened="searchFilterSectionsOpenStatus.eventCategory"
|
|
|
|
:title="t('Categories')"
|
|
|
|
>
|
|
|
|
<template #options>
|
|
|
|
<fieldset class="flex flex-col">
|
|
|
|
<legend class="sr-only">{{ t("Categories") }}</legend>
|
|
|
|
<div v-for="category in eventCategories" :key="category.id">
|
|
|
|
<input
|
|
|
|
:id="category.id"
|
|
|
|
v-model="categoryOneOf"
|
|
|
|
type="checkbox"
|
|
|
|
name="category"
|
|
|
|
:value="category.id"
|
|
|
|
class="w-4 h-4 border-gray-300 focus:ring-2 focus:ring-blue-300 dark:focus:ring-blue-600 dark:focus:bg-blue-600 dark:bg-gray-700 dark:border-gray-600"
|
|
|
|
/>
|
|
|
|
<label
|
|
|
|
:for="category.id"
|
|
|
|
class="ml-3 text-sm font-medium text-gray-900 dark:text-gray-300"
|
|
|
|
>{{ category.label }}</label
|
|
|
|
>
|
|
|
|
</div>
|
|
|
|
</fieldset>
|
|
|
|
</template>
|
|
|
|
<template #preview>
|
|
|
|
<span
|
|
|
|
class="bg-blue-100 text-blue-800 text-sm font-semibold p-0.5 rounded dark:bg-blue-200 dark:text-blue-800 grow-0"
|
|
|
|
v-if="categoryOneOf.length > 2"
|
|
|
|
>
|
|
|
|
{{
|
|
|
|
t("{numberOfCategories} selected", {
|
|
|
|
numberOfCategories: categoryOneOf.length,
|
|
|
|
})
|
|
|
|
}}
|
|
|
|
</span>
|
|
|
|
<span
|
|
|
|
class="bg-blue-100 text-blue-800 text-sm font-semibold p-0.5 rounded dark:bg-blue-200 dark:text-blue-800 grow-0"
|
|
|
|
v-else-if="categoryOneOf.length > 0"
|
2022-07-12 10:55:28 +02:00
|
|
|
>
|
2022-08-22 12:12:09 +02:00
|
|
|
{{
|
|
|
|
listShortDisjunctionFormatter(
|
|
|
|
categoryOneOf.map(
|
|
|
|
(category) =>
|
|
|
|
(eventCategories ?? []).find(({ id }) => id === category)
|
|
|
|
?.label ?? ""
|
|
|
|
)
|
|
|
|
)
|
|
|
|
}}
|
|
|
|
</span>
|
|
|
|
<span
|
|
|
|
class="bg-blue-100 text-blue-800 text-sm font-semibold p-0.5 rounded dark:bg-blue-200 dark:text-blue-800 grow-0"
|
|
|
|
v-else-if="categoryOneOf.length === 0"
|
|
|
|
>
|
|
|
|
{{ t("Categories", "All") }}
|
|
|
|
</span>
|
|
|
|
</template>
|
|
|
|
</filter-section>
|
|
|
|
<filter-section
|
|
|
|
v-show="contentType !== 'GROUPS'"
|
|
|
|
v-model:opened="searchFilterSectionsOpenStatus.eventStatus"
|
|
|
|
:title="t('Event status')"
|
|
|
|
>
|
|
|
|
<template #options>
|
|
|
|
<fieldset class="flex flex-col">
|
|
|
|
<legend class="sr-only">{{ t("Event status") }}</legend>
|
|
|
|
<div
|
|
|
|
v-for="eventStatusOption in eventStatuses"
|
|
|
|
:key="eventStatusOption.id"
|
2022-07-12 10:55:28 +02:00
|
|
|
>
|
2022-08-22 12:12:09 +02:00
|
|
|
<input
|
|
|
|
:id="eventStatusOption.id"
|
|
|
|
v-model="statusOneOf"
|
|
|
|
type="checkbox"
|
|
|
|
name="eventStatus"
|
|
|
|
:value="eventStatusOption.id"
|
|
|
|
class="w-4 h-4 border-gray-300 focus:ring-2 focus:ring-blue-300 dark:focus:ring-blue-600 dark:focus:bg-blue-600 dark:bg-gray-700 dark:border-gray-600"
|
|
|
|
/>
|
|
|
|
<label
|
|
|
|
:for="eventStatusOption.id"
|
|
|
|
class="ml-3 text-sm font-medium text-gray-900 dark:text-gray-300"
|
|
|
|
>{{ eventStatusOption.label }}</label
|
|
|
|
>
|
|
|
|
</div>
|
|
|
|
</fieldset>
|
|
|
|
</template>
|
|
|
|
<template #preview>
|
|
|
|
<span
|
|
|
|
class="bg-blue-100 text-blue-800 text-sm font-semibold p-0.5 rounded dark:bg-blue-200 dark:text-blue-800 grow-0"
|
|
|
|
v-if="statusOneOf.length === Object.values(EventStatus).length"
|
|
|
|
>
|
|
|
|
{{ t("Statuses", "All") }}
|
|
|
|
</span>
|
|
|
|
<span
|
|
|
|
class="bg-blue-100 text-blue-800 text-sm font-semibold p-0.5 rounded dark:bg-blue-200 dark:text-blue-800 grow-0"
|
|
|
|
v-else-if="statusOneOf.length > 0"
|
|
|
|
>
|
|
|
|
{{
|
|
|
|
listShortDisjunctionFormatter(
|
|
|
|
statusOneOf.map(
|
|
|
|
(status) =>
|
|
|
|
eventStatuses.find(({ id }) => id === status)?.label ?? ""
|
|
|
|
)
|
|
|
|
)
|
|
|
|
}}
|
|
|
|
</span>
|
|
|
|
</template>
|
|
|
|
</filter-section>
|
|
|
|
|
|
|
|
<filter-section
|
|
|
|
v-model:opened="searchFilterSectionsOpenStatus.eventLanguage"
|
|
|
|
:title="t('Languages')"
|
2022-07-12 10:55:28 +02:00
|
|
|
>
|
2022-08-22 12:12:09 +02:00
|
|
|
<template #options>
|
|
|
|
<fieldset class="flex flex-col">
|
|
|
|
<legend class="sr-only">{{ t("Languages") }}</legend>
|
|
|
|
<div v-for="(language, key) in langs" :key="key">
|
|
|
|
<input
|
|
|
|
:id="key"
|
|
|
|
type="checkbox"
|
|
|
|
name="eventStartDateRange"
|
|
|
|
:value="key"
|
|
|
|
v-model="languageOneOf"
|
|
|
|
class="w-4 h-4 border-gray-300 focus:ring-2 focus:ring-blue-300 dark:focus:ring-blue-600 dark:focus:bg-blue-600 dark:bg-gray-700 dark:border-gray-600"
|
|
|
|
/>
|
|
|
|
<label
|
|
|
|
:for="key"
|
|
|
|
class="ml-3 text-sm font-medium text-gray-900 dark:text-gray-300"
|
|
|
|
>{{ language }}</label
|
|
|
|
>
|
|
|
|
</div>
|
|
|
|
</fieldset>
|
|
|
|
</template>
|
|
|
|
<template #preview>
|
|
|
|
<span
|
|
|
|
class="bg-blue-100 text-blue-800 text-sm font-semibold p-0.5 rounded dark:bg-blue-200 dark:text-blue-800 grow-0"
|
|
|
|
v-if="languageOneOf.length > 2"
|
|
|
|
>
|
|
|
|
{{
|
|
|
|
t("{numberOfLanguages} selected", {
|
|
|
|
numberOfLanguages: languageOneOf.length,
|
|
|
|
})
|
|
|
|
}}
|
|
|
|
</span>
|
|
|
|
<span
|
|
|
|
class="bg-blue-100 text-blue-800 text-sm font-semibold p-0.5 rounded dark:bg-blue-200 dark:text-blue-800 grow-0"
|
|
|
|
v-else-if="languageOneOf.length > 0"
|
|
|
|
>
|
|
|
|
{{
|
|
|
|
listShortDisjunctionFormatter(
|
|
|
|
languageOneOf.map((lang) => langs[lang])
|
|
|
|
)
|
|
|
|
}}
|
|
|
|
</span>
|
|
|
|
<span
|
|
|
|
class="bg-blue-100 text-blue-800 text-sm font-semibold p-0.5 rounded dark:bg-blue-200 dark:text-blue-800 grow-0"
|
|
|
|
v-else-if="languageOneOf.length === 0"
|
|
|
|
>
|
|
|
|
{{ t("Languages", "All") }}
|
|
|
|
</span>
|
|
|
|
</template>
|
|
|
|
</filter-section>
|
|
|
|
|
|
|
|
<!--
|
|
|
|
|
|
|
|
<div class="">
|
|
|
|
<label v-translate class="font-bold" for="host">Mobilizon instance</label>
|
|
|
|
|
|
|
|
<input
|
|
|
|
id="host"
|
|
|
|
v-model="formHost"
|
|
|
|
type="text"
|
|
|
|
name="host"
|
|
|
|
placeholder="mobilizon.fr"
|
|
|
|
class="dark:text-black md:max-w-fit w-full"
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div class="">
|
|
|
|
<label v-translate class="inline font-bold" for="tagsAllOf">All of these tags</label>
|
|
|
|
<button
|
|
|
|
v-if="formTagsAllOf.length !== 0"
|
|
|
|
v-translate
|
|
|
|
class="text-sm ml-2"
|
|
|
|
@click="resetField('tagsAllOf')"
|
|
|
|
>
|
|
|
|
Reset
|
|
|
|
</button>
|
|
|
|
|
|
|
|
<vue-tags-input
|
|
|
|
v-model="formTagAllOf"
|
|
|
|
:placeholder="tagsPlaceholder"
|
|
|
|
:tags="formTagsAllOf"
|
|
|
|
@tags-changed="(newTags) => (formTagsAllOf = newTags)"
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div>
|
|
|
|
<div>
|
|
|
|
<label v-translate class="inline font-bold" for="tagsOneOf">One of these tags</label>
|
|
|
|
<button
|
|
|
|
v-if="formTagsOneOf.length !== 0"
|
|
|
|
v-translate
|
|
|
|
class="text-sm ml-2"
|
|
|
|
@click="resetField('tagsOneOf')"
|
|
|
|
>
|
|
|
|
Reset
|
|
|
|
</button>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<vue-tags-input
|
|
|
|
v-model="formTagOneOf"
|
|
|
|
:placeholder="tagsPlaceholder"
|
|
|
|
:tags="formTagsOneOf"
|
|
|
|
@tags-changed="(newTags) => (formTagsOneOf = newTags)"
|
|
|
|
/>
|
|
|
|
</div>-->
|
|
|
|
|
|
|
|
<div class="sr-only">
|
|
|
|
<button
|
|
|
|
class="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center mr-2 mb-2 dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800"
|
|
|
|
type="submit"
|
2022-07-12 10:55:28 +02:00
|
|
|
>
|
2022-08-22 12:12:09 +02:00
|
|
|
{{ t("Apply filters") }}
|
|
|
|
</button>
|
2022-07-12 10:55:28 +02:00
|
|
|
</div>
|
2022-08-22 12:12:09 +02:00
|
|
|
|
|
|
|
<o-button
|
|
|
|
@click="toggleFilters"
|
|
|
|
icon-left="filter"
|
|
|
|
class="w-full inline-flex lg:!hidden text-white px-4 py-2 justify-center"
|
|
|
|
>
|
|
|
|
{{ t("Hide filters") }}
|
|
|
|
</o-button>
|
|
|
|
</form>
|
|
|
|
</aside>
|
|
|
|
<div class="flex-1 px-2">
|
|
|
|
<o-tabs type="boxed" v-if="contentType == ContentType.ALL">
|
|
|
|
<o-tab-item>
|
|
|
|
<template #header>
|
|
|
|
<Calendar />
|
|
|
|
|
|
|
|
<span>
|
|
|
|
{{ t("Events") }}
|
|
|
|
<b-tag rounded>{{ searchEvents?.total }}</b-tag>
|
|
|
|
</span>
|
|
|
|
</template>
|
|
|
|
<div v-if="searchEvents && searchEvents.total > 0">
|
|
|
|
<multi-card class="my-4" :events="searchEvents?.elements" />
|
|
|
|
<div
|
|
|
|
class="pagination"
|
|
|
|
v-if="searchEvents && searchEvents?.total > EVENT_PAGE_LIMIT"
|
|
|
|
>
|
|
|
|
<o-pagination
|
|
|
|
:total="searchEvents.total"
|
|
|
|
v-model:current="eventPage"
|
|
|
|
:per-page="EVENT_PAGE_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>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<o-notification v-else-if="searchLoading === false" variant="info">
|
|
|
|
<p>{{ t("No events found") }}</p>
|
|
|
|
<p v-if="searchIsUrl && !currentUser?.id">
|
|
|
|
{{
|
|
|
|
t(
|
|
|
|
"Only registered users may fetch remote events from their URL."
|
|
|
|
)
|
|
|
|
}}
|
|
|
|
</p>
|
|
|
|
</o-notification>
|
|
|
|
</o-tab-item>
|
|
|
|
|
|
|
|
<o-tab-item>
|
|
|
|
<template #header>
|
|
|
|
<AccountMultiple />
|
|
|
|
<span>
|
|
|
|
{{ t("Groups") }} <b-tag rounded>{{ searchGroups?.total }}</b-tag>
|
|
|
|
</span>
|
|
|
|
</template>
|
|
|
|
<o-notification v-if="features && !features.groups" variant="danger">
|
|
|
|
{{ t("Groups are not enabled on this instance.") }}
|
|
|
|
</o-notification>
|
|
|
|
<div v-else-if="searchGroups && searchGroups?.total > 0">
|
|
|
|
<multi-group-card class="my-4" :groups="searchGroups?.elements" />
|
|
|
|
<div class="pagination">
|
|
|
|
<o-pagination
|
|
|
|
:total="searchGroups?.total"
|
|
|
|
v-model:current="groupPage"
|
|
|
|
:per-page="GROUP_PAGE_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>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<o-notification v-else-if="searchLoading === false" variant="danger">
|
|
|
|
{{ t("No groups found") }}
|
|
|
|
</o-notification>
|
|
|
|
</o-tab-item>
|
|
|
|
</o-tabs>
|
|
|
|
<div v-else-if="contentType === ContentType.EVENTS">
|
2022-07-12 10:55:28 +02:00
|
|
|
<div v-if="searchEvents && searchEvents.total > 0">
|
|
|
|
<multi-card class="my-4" :events="searchEvents?.elements" />
|
2022-08-22 12:12:09 +02:00
|
|
|
<o-pagination
|
|
|
|
v-show="searchEvents && searchEvents?.total > EVENT_PAGE_LIMIT"
|
|
|
|
:total="searchEvents.total"
|
|
|
|
v-model:current="eventPage"
|
|
|
|
:per-page="EVENT_PAGE_LIMIT"
|
|
|
|
:aria-next-label="t('Next page')"
|
|
|
|
:aria-previous-label="t('Previous page')"
|
|
|
|
:aria-page-label="t('Page')"
|
|
|
|
:aria-current-label="t('Current page')"
|
2022-07-12 10:55:28 +02:00
|
|
|
>
|
2022-08-22 12:12:09 +02:00
|
|
|
</o-pagination>
|
2022-07-12 10:55:28 +02:00
|
|
|
</div>
|
2022-08-22 12:12:09 +02:00
|
|
|
<o-notification v-else-if="searchLoading === false" variant="info">
|
|
|
|
<p>{{ t("No events found") }}</p>
|
2022-07-12 10:55:28 +02:00
|
|
|
<p v-if="searchIsUrl && !currentUser?.id">
|
|
|
|
{{
|
2022-08-22 12:12:09 +02:00
|
|
|
t("Only registered users may fetch remote events from their URL.")
|
2022-07-12 10:55:28 +02:00
|
|
|
}}
|
|
|
|
</p>
|
|
|
|
</o-notification>
|
2022-08-22 12:12:09 +02:00
|
|
|
</div>
|
|
|
|
<div v-else-if="contentType === ContentType.GROUPS">
|
|
|
|
<o-notification v-if="features && !features.groups" variant="danger">
|
|
|
|
{{ t("Groups are not enabled on this instance.") }}
|
2022-07-12 10:55:28 +02:00
|
|
|
</o-notification>
|
|
|
|
<div v-else-if="searchGroups && searchGroups?.total > 0">
|
|
|
|
<multi-group-card class="my-4" :groups="searchGroups?.elements" />
|
2022-08-22 12:12:09 +02:00
|
|
|
<o-pagination
|
|
|
|
v-show="searchGroups && searchGroups?.total > GROUP_PAGE_LIMIT"
|
|
|
|
:total="searchGroups?.total"
|
|
|
|
v-model:current="groupPage"
|
|
|
|
:per-page="GROUP_PAGE_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>
|
2022-07-12 10:55:28 +02:00
|
|
|
</div>
|
|
|
|
<o-notification v-else-if="searchLoading === false" variant="danger">
|
2022-08-22 12:12:09 +02:00
|
|
|
{{ t("No groups found") }}
|
2022-07-12 10:55:28 +02:00
|
|
|
</o-notification>
|
2022-08-22 12:12:09 +02:00
|
|
|
</div>
|
|
|
|
</div>
|
2022-07-12 10:55:28 +02:00
|
|
|
</div>
|
|
|
|
</template>
|
|
|
|
|
|
|
|
<script lang="ts" setup>
|
|
|
|
import {
|
|
|
|
endOfToday,
|
|
|
|
addDays,
|
|
|
|
startOfDay,
|
|
|
|
endOfDay,
|
|
|
|
endOfWeek,
|
|
|
|
addWeeks,
|
|
|
|
startOfWeek,
|
|
|
|
endOfMonth,
|
|
|
|
addMonths,
|
|
|
|
startOfMonth,
|
|
|
|
eachWeekendOfInterval,
|
|
|
|
} from "date-fns";
|
2022-08-22 12:12:09 +02:00
|
|
|
import { ContentType, EventStatus } from "@/types/enums";
|
2022-07-12 10:55:28 +02:00
|
|
|
import MultiCard from "../components/Event/MultiCard.vue";
|
|
|
|
import { IEvent } from "../types/event.model";
|
|
|
|
import FullAddressAutoComplete from "../components/Event/FullAddressAutoComplete.vue";
|
|
|
|
import { SEARCH_EVENTS_AND_GROUPS } from "../graphql/search";
|
|
|
|
import { Paginate } from "../types/paginate";
|
|
|
|
import { IGroup } from "../types/actor";
|
|
|
|
import MultiGroupCard from "../components/Group/MultiGroupCard.vue";
|
|
|
|
import { CURRENT_USER_CLIENT } from "@/graphql/user";
|
|
|
|
import { ICurrentUser } from "@/types/current-user.model";
|
|
|
|
import { useQuery } from "@vue/apollo-composable";
|
2022-08-22 12:12:09 +02:00
|
|
|
import { computed, inject, ref } from "vue";
|
2022-07-12 10:55:28 +02:00
|
|
|
import { useI18n } from "vue-i18n";
|
|
|
|
import {
|
|
|
|
floatTransformer,
|
|
|
|
integerTransformer,
|
|
|
|
useRouteQuery,
|
2022-08-22 12:12:09 +02:00
|
|
|
enumTransformer,
|
|
|
|
booleanTransformer,
|
|
|
|
RouteQueryTransformer,
|
2022-07-12 10:55:28 +02:00
|
|
|
} from "vue-use-route-query";
|
|
|
|
import Calendar from "vue-material-design-icons/Calendar.vue";
|
|
|
|
import AccountMultiple from "vue-material-design-icons/AccountMultiple.vue";
|
2022-08-22 12:12:09 +02:00
|
|
|
import Magnify from "vue-material-design-icons/Magnify.vue";
|
|
|
|
|
2022-07-12 10:55:28 +02:00
|
|
|
import { useHead } from "@vueuse/head";
|
|
|
|
import type { Locale } from "date-fns";
|
2022-08-22 12:12:09 +02:00
|
|
|
import FilterSection from "@/components/Search/filters/FilterSection.vue";
|
|
|
|
import { listShortDisjunctionFormatter } from "@/utils/listFormat";
|
|
|
|
import langs from "@/i18n/langs.json";
|
|
|
|
import { useEventCategories, useFeatures } from "@/composition/apollo/config";
|
2022-08-22 13:47:10 +02:00
|
|
|
import geohash from "ngeohash";
|
|
|
|
import { coordsToGeoHash } from "@/utils/location";
|
2022-07-12 10:55:28 +02:00
|
|
|
|
|
|
|
const search = useRouteQuery("search", "");
|
|
|
|
|
|
|
|
interface ISearchTimeOption {
|
|
|
|
label: string;
|
2022-08-22 12:12:09 +02:00
|
|
|
start?: string | null;
|
|
|
|
end?: string | null;
|
2022-07-12 10:55:28 +02:00
|
|
|
}
|
|
|
|
|
2022-08-22 12:12:09 +02:00
|
|
|
const arrayTransformer: RouteQueryTransformer<string[]> = {
|
|
|
|
fromQuery(query: string) {
|
|
|
|
return query.split(",");
|
|
|
|
},
|
|
|
|
toQuery(value: string[]) {
|
|
|
|
return value.join(",");
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
2022-07-12 10:55:28 +02:00
|
|
|
const eventPage = useRouteQuery("eventPage", 1, integerTransformer);
|
|
|
|
const groupPage = useRouteQuery("groupPage", 1, integerTransformer);
|
2022-08-22 12:12:09 +02:00
|
|
|
|
2022-08-22 13:47:10 +02:00
|
|
|
const latitude = useRouteQuery("lat", undefined, floatTransformer);
|
|
|
|
const longitude = useRouteQuery("lon", undefined, floatTransformer);
|
|
|
|
|
2022-08-22 12:12:09 +02:00
|
|
|
const distance = useRouteQuery("distance", "10_km");
|
2022-07-12 10:55:28 +02:00
|
|
|
const when = useRouteQuery("when", "any");
|
2022-08-22 12:12:09 +02:00
|
|
|
const contentType = useRouteQuery(
|
|
|
|
"contentType",
|
|
|
|
ContentType.ALL,
|
|
|
|
enumTransformer(ContentType)
|
|
|
|
);
|
|
|
|
const isOnline = useRouteQuery("isOnline", false, booleanTransformer);
|
|
|
|
const categoryOneOf = useRouteQuery("categoryOneOf", [], arrayTransformer);
|
|
|
|
const statusOneOf = useRouteQuery(
|
|
|
|
"statusOneOf",
|
|
|
|
[EventStatus.CONFIRMED],
|
|
|
|
arrayTransformer
|
|
|
|
);
|
|
|
|
const languageOneOf = useRouteQuery("languageOneOf", [], arrayTransformer);
|
2022-07-12 10:55:28 +02:00
|
|
|
|
2022-08-22 12:12:09 +02:00
|
|
|
const EVENT_PAGE_LIMIT = 16;
|
2022-07-12 10:55:28 +02:00
|
|
|
|
2022-08-22 12:12:09 +02:00
|
|
|
const GROUP_PAGE_LIMIT = 16;
|
2022-07-12 10:55:28 +02:00
|
|
|
|
|
|
|
const props = defineProps<{
|
|
|
|
tag?: string;
|
|
|
|
}>();
|
|
|
|
|
2022-08-22 12:12:09 +02:00
|
|
|
const { features } = useFeatures();
|
|
|
|
const { eventCategories } = useEventCategories();
|
2022-07-12 10:55:28 +02:00
|
|
|
|
|
|
|
const searchEvents = computed(() => searchElementsResult.value?.searchEvents);
|
|
|
|
const searchGroups = computed(() => searchElementsResult.value?.searchGroups);
|
|
|
|
|
|
|
|
const { result: currentUserResult } = useQuery<{ currentUser: ICurrentUser }>(
|
|
|
|
CURRENT_USER_CLIENT
|
|
|
|
);
|
|
|
|
|
|
|
|
const currentUser = computed(() => currentUserResult.value?.currentUser);
|
|
|
|
|
2022-08-22 12:12:09 +02:00
|
|
|
const { t } = useI18n({ useScope: "global" });
|
2022-07-12 10:55:28 +02:00
|
|
|
|
|
|
|
useHead({
|
|
|
|
title: computed(() => t("Explore events")),
|
|
|
|
});
|
|
|
|
|
|
|
|
const dateFnsLocale = inject<Locale>("dateFnsLocale");
|
|
|
|
|
|
|
|
const weekend = computed((): { start: Date; end: Date } => {
|
|
|
|
const now = new Date();
|
|
|
|
const endOfWeekDate = endOfWeek(now, { locale: dateFnsLocale });
|
|
|
|
const startOfWeekDate = startOfWeek(now, { locale: dateFnsLocale });
|
|
|
|
const [start, end] = eachWeekendOfInterval({
|
|
|
|
start: startOfWeekDate,
|
|
|
|
end: endOfWeekDate,
|
|
|
|
});
|
|
|
|
return { start: startOfDay(start), end: endOfDay(end) };
|
|
|
|
});
|
|
|
|
|
|
|
|
const dateOptions: Record<string, ISearchTimeOption> = {
|
|
|
|
past: {
|
|
|
|
label: t("In the past") as string,
|
|
|
|
start: null,
|
2022-08-22 12:12:09 +02:00
|
|
|
end: new Date().toISOString(),
|
2022-07-12 10:55:28 +02:00
|
|
|
},
|
|
|
|
today: {
|
|
|
|
label: t("Today") as string,
|
2022-08-22 12:12:09 +02:00
|
|
|
start: new Date().toISOString(),
|
|
|
|
end: endOfToday().toISOString(),
|
2022-07-12 10:55:28 +02:00
|
|
|
},
|
|
|
|
tomorrow: {
|
|
|
|
label: t("Tomorrow") as string,
|
2022-08-22 12:12:09 +02:00
|
|
|
start: startOfDay(addDays(new Date(), 1)).toISOString(),
|
|
|
|
end: endOfDay(addDays(new Date(), 1)).toISOString(),
|
2022-07-12 10:55:28 +02:00
|
|
|
},
|
|
|
|
weekend: {
|
|
|
|
label: t("This weekend") as string,
|
2022-08-22 12:12:09 +02:00
|
|
|
start: weekend.value.start.toISOString(),
|
|
|
|
end: weekend.value.end.toISOString(),
|
2022-07-12 10:55:28 +02:00
|
|
|
},
|
|
|
|
week: {
|
|
|
|
label: t("This week") as string,
|
2022-08-22 12:12:09 +02:00
|
|
|
start: new Date().toISOString(),
|
|
|
|
end: endOfWeek(new Date(), { locale: dateFnsLocale }).toISOString(),
|
2022-07-12 10:55:28 +02:00
|
|
|
},
|
|
|
|
next_week: {
|
|
|
|
label: t("Next week") as string,
|
|
|
|
start: startOfWeek(addWeeks(new Date(), 1), {
|
|
|
|
locale: dateFnsLocale,
|
2022-08-22 12:12:09 +02:00
|
|
|
}).toISOString(),
|
|
|
|
end: endOfWeek(addWeeks(new Date(), 1), {
|
|
|
|
locale: dateFnsLocale,
|
|
|
|
}).toISOString(),
|
2022-07-12 10:55:28 +02:00
|
|
|
},
|
|
|
|
month: {
|
|
|
|
label: t("This month") as string,
|
2022-08-22 12:12:09 +02:00
|
|
|
start: new Date().toISOString(),
|
|
|
|
end: endOfMonth(new Date()).toISOString(),
|
2022-07-12 10:55:28 +02:00
|
|
|
},
|
|
|
|
next_month: {
|
|
|
|
label: t("Next month") as string,
|
2022-08-22 12:12:09 +02:00
|
|
|
start: startOfMonth(addMonths(new Date(), 1)).toISOString(),
|
|
|
|
end: endOfMonth(addMonths(new Date(), 1)).toISOString(),
|
2022-07-12 10:55:28 +02:00
|
|
|
},
|
|
|
|
any: {
|
|
|
|
label: t("Any day") as string,
|
|
|
|
start: undefined,
|
|
|
|
end: undefined,
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
2022-08-22 12:12:09 +02:00
|
|
|
const start = computed((): string | undefined | null => {
|
2022-07-12 10:55:28 +02:00
|
|
|
if (dateOptions[when.value]) {
|
|
|
|
return dateOptions[when.value].start;
|
|
|
|
}
|
|
|
|
return undefined;
|
|
|
|
});
|
|
|
|
|
2022-08-22 12:12:09 +02:00
|
|
|
const end = computed((): string | undefined | null => {
|
2022-07-12 10:55:28 +02:00
|
|
|
if (dateOptions[when.value]) {
|
|
|
|
return dateOptions[when.value].end;
|
|
|
|
}
|
|
|
|
return undefined;
|
|
|
|
});
|
|
|
|
|
|
|
|
const searchIsUrl = computed((): boolean => {
|
|
|
|
let url;
|
|
|
|
if (!search.value) return false;
|
|
|
|
try {
|
|
|
|
url = new URL(search.value);
|
|
|
|
} catch (_) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return url.protocol === "http:" || url.protocol === "https:";
|
|
|
|
});
|
|
|
|
|
2022-08-22 12:12:09 +02:00
|
|
|
const contentTypeMapping = computed(() => {
|
|
|
|
return [
|
|
|
|
{
|
|
|
|
contentType: "ALL",
|
|
|
|
label: t("Everything"),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
contentType: "EVENTS",
|
|
|
|
label: t("Events"),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
contentType: "GROUPS",
|
|
|
|
label: t("Groups"),
|
|
|
|
},
|
|
|
|
];
|
|
|
|
});
|
2022-07-12 10:55:28 +02:00
|
|
|
|
2022-08-22 12:12:09 +02:00
|
|
|
const eventDistance = computed(() => {
|
|
|
|
return [
|
|
|
|
{
|
|
|
|
id: "anywhere",
|
|
|
|
label: t("Any distance"),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
id: "5_km",
|
|
|
|
label: t(
|
|
|
|
"{number} kilometers",
|
|
|
|
{
|
|
|
|
number: 5,
|
|
|
|
},
|
|
|
|
5
|
|
|
|
),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
id: "10_km",
|
|
|
|
label: t(
|
|
|
|
"{number} kilometers",
|
|
|
|
{
|
|
|
|
number: 10,
|
|
|
|
},
|
|
|
|
10
|
|
|
|
),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
id: "25_km",
|
|
|
|
label: t(
|
|
|
|
"{number} kilometers",
|
|
|
|
{
|
|
|
|
number: 25,
|
|
|
|
},
|
|
|
|
25
|
|
|
|
),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
id: "50_km",
|
|
|
|
label: t(
|
|
|
|
"{number} kilometers",
|
|
|
|
{
|
|
|
|
number: 50,
|
|
|
|
},
|
|
|
|
50
|
|
|
|
),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
id: "100_km",
|
|
|
|
label: t(
|
|
|
|
"{number} kilometers",
|
|
|
|
{
|
|
|
|
number: 100,
|
|
|
|
},
|
|
|
|
100
|
|
|
|
),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
id: "150_km",
|
|
|
|
label: t(
|
|
|
|
"{number} kilometers",
|
|
|
|
{
|
|
|
|
number: 150,
|
|
|
|
},
|
|
|
|
150
|
|
|
|
),
|
|
|
|
},
|
|
|
|
];
|
|
|
|
});
|
2022-07-12 10:55:28 +02:00
|
|
|
|
2022-08-22 12:12:09 +02:00
|
|
|
const eventStatuses = computed(() => {
|
|
|
|
return [
|
|
|
|
{
|
|
|
|
id: EventStatus.CONFIRMED,
|
|
|
|
label: t("Confirmed"),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
id: EventStatus.TENTATIVE,
|
|
|
|
label: t("Tentative"),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
id: EventStatus.CANCELLED,
|
|
|
|
label: t("Cancelled"),
|
|
|
|
},
|
|
|
|
];
|
2022-07-12 10:55:28 +02:00
|
|
|
});
|
|
|
|
|
2022-08-22 12:12:09 +02:00
|
|
|
const searchFilterSectionsOpenStatus = ref({
|
|
|
|
eventDate: true,
|
|
|
|
eventLanguage: false,
|
|
|
|
eventCategory: false,
|
|
|
|
eventStatus: false,
|
|
|
|
eventDistance: false,
|
|
|
|
});
|
|
|
|
|
|
|
|
const filtersPanelOpened = ref(true);
|
|
|
|
|
|
|
|
const toggleFilters = () =>
|
|
|
|
(filtersPanelOpened.value = !filtersPanelOpened.value);
|
|
|
|
|
2022-08-22 13:47:10 +02:00
|
|
|
const geoHashLocation = computed(() =>
|
|
|
|
coordsToGeoHash(latitude.value, longitude.value)
|
|
|
|
);
|
|
|
|
|
|
|
|
const radius = computed(() => Number.parseInt(distance.value.slice(0, -3)));
|
|
|
|
|
2022-08-22 12:12:09 +02:00
|
|
|
const { result: searchElementsResult, loading: searchLoading } = useQuery<{
|
2022-07-12 10:55:28 +02:00
|
|
|
searchEvents: Paginate<IEvent>;
|
|
|
|
searchGroups: Paginate<IGroup>;
|
2022-08-22 12:12:09 +02:00
|
|
|
}>(SEARCH_EVENTS_AND_GROUPS, () => ({
|
|
|
|
term: search.value,
|
|
|
|
tags: props.tag,
|
2022-08-22 13:47:10 +02:00
|
|
|
location: geoHashLocation.value,
|
2022-08-22 12:12:09 +02:00
|
|
|
beginsOn: start.value,
|
|
|
|
endsOn: end.value,
|
|
|
|
radius: radius.value,
|
|
|
|
eventPage: eventPage.value,
|
|
|
|
groupPage: groupPage.value,
|
|
|
|
limit: EVENT_PAGE_LIMIT,
|
|
|
|
type: isOnline.value ? "ONLINE" : "IN_PERSON",
|
|
|
|
categoryOneOf: categoryOneOf.value,
|
|
|
|
statusOneOf: statusOneOf.value,
|
|
|
|
}));
|
2022-07-12 10:55:28 +02:00
|
|
|
</script>
|