149 lines
4.2 KiB
Vue
149 lines
4.2 KiB
Vue
<template>
|
|
<SMPage class="mx-auto workshop-list">
|
|
<h1>Workshops</h1>
|
|
<div class="toolbar">
|
|
<SMInput
|
|
v-model="filterKeywords"
|
|
placeholder="Keywords"
|
|
@change="handleFilter"></SMInput>
|
|
<SMInput
|
|
v-model="filterLocation"
|
|
placeholder="Location"
|
|
@change="handleFilter"></SMInput>
|
|
<SMDatePicker
|
|
v-model="filterDateRange"
|
|
:range="true"
|
|
placeholder="Date Range"
|
|
@update:model-value="handleFilter"></SMDatePicker>
|
|
</div>
|
|
<SMMessage
|
|
v-if="formMessage.message"
|
|
:icon="formMessage.icon"
|
|
:type="formMessage.type"
|
|
:message="formMessage.message"
|
|
class="mt-5" />
|
|
<SMPanelList
|
|
:loading="loading"
|
|
:not-found="events.value?.length == 0"
|
|
not-found-text="No workshops found">
|
|
<SMPanel
|
|
v-for="event in events.value"
|
|
:key="event.id"
|
|
:to="{ name: 'workshop-view', params: { id: event.id } }"
|
|
:title="event.title"
|
|
:image="event.hero"
|
|
:show-time="true"
|
|
:date="event.start_at"
|
|
:end-date="event.end_at"
|
|
:date-in-image="true"
|
|
:location="
|
|
event.location == 'online' ? 'Online Event' : event.address
|
|
"></SMPanel>
|
|
</SMPanelList>
|
|
</SMPage>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import SMDatePicker from "../components/SMDatePicker.vue";
|
|
import SMInput from "../components/SMInput.vue";
|
|
import SMMessage from "../components/SMMessage.vue";
|
|
import SMPanelList from "../components/SMPanelList.vue";
|
|
import SMPanel from "../components/SMPanel.vue";
|
|
import SMPage from "../components/SMPage.vue";
|
|
import { reactive, ref } from "vue";
|
|
import { api } from "../helpers/api";
|
|
import {
|
|
timestampLocalToUtc,
|
|
timestampNowUtc,
|
|
timestampUtcToLocal,
|
|
} from "../helpers/datetime";
|
|
|
|
const loading = ref(true);
|
|
const events = reactive([]);
|
|
|
|
const formMessage = reactive({
|
|
icon: "",
|
|
type: "",
|
|
message: "",
|
|
});
|
|
|
|
const filterKeywords = ref("");
|
|
const filterLocation = ref("");
|
|
const filterDateRange = ref("");
|
|
|
|
const handleLoad = async () => {
|
|
formMessage.type = "error";
|
|
formMessage.icon = "fa-solid fa-circle-exclamation";
|
|
formMessage.message = "";
|
|
|
|
events.value = [];
|
|
|
|
try {
|
|
let query = {};
|
|
query["limit"] = 10;
|
|
|
|
if (filterKeywords.value && filterKeywords.value.length > 0) {
|
|
query["q"] = filterKeywords.value;
|
|
}
|
|
if (filterLocation.value && filterLocation.value.length > 0) {
|
|
query["qlocation"] = filterLocation.value;
|
|
}
|
|
if (filterDateRange.value && Array.isArray(filterDateRange.value)) {
|
|
query["start_at"] =
|
|
timestampLocalToUtc(filterDateRange.value[0]) +
|
|
"<>" +
|
|
timestampLocalToUtc(filterDateRange.value[1]);
|
|
}
|
|
|
|
if (
|
|
Object.keys(query).length == 1 &&
|
|
Object.keys(query)[0] == "limit"
|
|
) {
|
|
query["end_at"] = ">" + timestampNowUtc();
|
|
}
|
|
|
|
let result = await api.get({
|
|
url: "/events",
|
|
params: query,
|
|
});
|
|
|
|
if (result.json.events) {
|
|
events.value = result.json.events;
|
|
|
|
events.value.forEach((item) => {
|
|
item.start_at = timestampUtcToLocal(item.start_at);
|
|
item.end_at = timestampUtcToLocal(item.end_at);
|
|
});
|
|
}
|
|
} catch (error) {
|
|
if (error.response.status != 404) {
|
|
formMessage.message =
|
|
error.response?.data?.message ||
|
|
"Could not load any events from the server.";
|
|
}
|
|
}
|
|
|
|
loading.value = false;
|
|
};
|
|
|
|
const handleFilter = async () => {
|
|
loading.value = true;
|
|
handleLoad();
|
|
};
|
|
|
|
handleLoad();
|
|
</script>
|
|
|
|
<style lang="scss">
|
|
.workshop-list .toolbar {
|
|
display: flex;
|
|
flex-direction: row;
|
|
}
|
|
|
|
@media screen and (max-width: 768px) {
|
|
.workshop-list .toolbar {
|
|
flex-direction: column;
|
|
}
|
|
}
|
|
</style>
|