365 lines
10 KiB
Vue
365 lines
10 KiB
Vue
<template>
|
|
<SMPage permission="admin/media">
|
|
<SMMastHead
|
|
title="Events"
|
|
:back-link="{ name: 'dashboard' }"
|
|
back-title="Return to Dashboard" />
|
|
<SMContainer class="flex-grow-1">
|
|
<SMToolbar>
|
|
<SMButton
|
|
:to="{ name: 'dashboard-event-create' }"
|
|
type="primary"
|
|
label="Create Event" />
|
|
<SMInput
|
|
v-model="itemSearch"
|
|
label="Search"
|
|
class="toolbar-search"
|
|
@keyup.enter="handleSearch">
|
|
<template #append>
|
|
<SMButton
|
|
type="primary"
|
|
label="Search"
|
|
icon="search-outline"
|
|
@click="handleSearch" />
|
|
</template>
|
|
</SMInput>
|
|
</SMToolbar>
|
|
<SMLoading large v-if="itemsLoading" />
|
|
<template v-else>
|
|
<SMPagination
|
|
v-if="items.length < itemsTotal"
|
|
v-model="itemsPage"
|
|
:total="itemsTotal"
|
|
:per-page="itemsPerPage" />
|
|
<SMNoItems v-if="items.length == 0" text="No Media Found" />
|
|
<SMTable
|
|
v-else
|
|
:headers="headers"
|
|
:items="items"
|
|
@row-click="handleEdit">
|
|
<template #item-location="item"
|
|
>{{ parseEventLocation(item) }}
|
|
</template>
|
|
<template #item-status="item"
|
|
>{{ toTitleCase(item.status) }}
|
|
</template>
|
|
<template #item-actions="item">
|
|
<SMButton
|
|
label="Edit"
|
|
:dropdown="{
|
|
view: 'View',
|
|
duplicate: 'Duplicate',
|
|
delete: 'Delete',
|
|
}"
|
|
size="medium"
|
|
@click="
|
|
handleActionButton(item, $event)
|
|
"></SMButton>
|
|
</template>
|
|
</SMTable>
|
|
</template>
|
|
</SMContainer>
|
|
</SMPage>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { ref, watch } from "vue";
|
|
import { useRoute, useRouter } from "vue-router";
|
|
import { openDialog } from "../../components/SMDialog";
|
|
import { api } from "../../helpers/api";
|
|
import { EventCollection, Event } from "../../helpers/api.types";
|
|
import { SMDate } from "../../helpers/datetime";
|
|
import { updateRouterParams } from "../../helpers/url";
|
|
import { useToastStore } from "../../store/ToastStore";
|
|
import { toTitleCase } from "../../helpers/string";
|
|
import SMButton from "../../components/SMButton.vue";
|
|
import SMDialogConfirm from "../../components/dialogs/SMDialogConfirm.vue";
|
|
import SMInput from "../../components/SMInput.vue";
|
|
import SMLoading from "../../components/SMLoading.vue";
|
|
import SMMastHead from "../../components/SMMastHead.vue";
|
|
import SMNoItems from "../../components/SMNoItems.vue";
|
|
import SMPagination from "../../components/SMPagination.vue";
|
|
import SMTable from "../../components/SMTable.vue";
|
|
import SMToolbar from "../../components/SMToolbar.vue";
|
|
|
|
const route = useRoute();
|
|
const router = useRouter();
|
|
const toastStore = useToastStore();
|
|
|
|
const items = ref([]);
|
|
const itemsLoading = ref(true);
|
|
const itemSearch = ref((route.query.search as string) || "");
|
|
const itemsTotal = ref(0);
|
|
const itemsPerPage = 25;
|
|
const itemsPage = ref(parseInt((route.query.page as string) || "1"));
|
|
|
|
const headers = [
|
|
{ text: "Title", value: "title", sortable: true },
|
|
{ text: "Starts", value: "start_at", sortable: true },
|
|
{ text: "Status", value: "status", sortable: true },
|
|
{ text: "Location", value: "location", sortable: true },
|
|
{ text: "Actions", value: "actions" },
|
|
];
|
|
|
|
/**
|
|
* Watch if page number changes.
|
|
*/
|
|
watch(itemsPage, () => {
|
|
handleLoad();
|
|
});
|
|
|
|
/**
|
|
* Handle searching for item.
|
|
*/
|
|
const handleSearch = () => {
|
|
itemsPage.value = 1;
|
|
handleLoad();
|
|
};
|
|
|
|
/**
|
|
* Handle user selecting option in action button.
|
|
*
|
|
* @param {Event} item The event item.
|
|
* @param option
|
|
*/
|
|
const handleActionButton = (item: Event, option: string): void => {
|
|
if (option.length == 0) {
|
|
handleEdit(item);
|
|
} else if (option.toLowerCase() == "view") {
|
|
handleView(item);
|
|
} else if (option.toLowerCase() == "duplicate") {
|
|
handleDuplicate(item);
|
|
} else if (option.toLowerCase() == "delete") {
|
|
handleDelete(item);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Handle loading the page and list
|
|
*/
|
|
const handleLoad = async () => {
|
|
itemsLoading.value = true;
|
|
items.value = [];
|
|
itemsTotal.value = 0;
|
|
|
|
updateRouterParams(router, {
|
|
search: itemSearch.value,
|
|
page: itemsPage.value == 1 ? "" : itemsPage.value.toString(),
|
|
});
|
|
|
|
try {
|
|
let params = {
|
|
page: itemsPage.value,
|
|
limit: itemsPerPage,
|
|
};
|
|
|
|
if (itemSearch.value.length > 0) {
|
|
params[
|
|
"filter"
|
|
] = `title:${itemSearch.value},OR,content:${itemSearch.value}`;
|
|
}
|
|
|
|
let result = await api.get({
|
|
url: "/events",
|
|
params: params,
|
|
});
|
|
|
|
const data = result.data as EventCollection;
|
|
data.events.forEach(async (row) => {
|
|
if (row.start_at !== "undefined") {
|
|
row.start_at = new SMDate(row.start_at, {
|
|
format: "ymd",
|
|
utc: true,
|
|
}).relative();
|
|
}
|
|
if (row.end_at !== "undefined") {
|
|
row.end_at = new SMDate(row.end_at, {
|
|
format: "ymd",
|
|
utc: true,
|
|
}).relative();
|
|
}
|
|
if (row.publish_at !== "undefined") {
|
|
row.publish_at = new SMDate(row.publish_at, {
|
|
format: "ymd",
|
|
utc: true,
|
|
}).relative();
|
|
}
|
|
if (row.created_at !== "undefined") {
|
|
row.created_at = new SMDate(row.created_at, {
|
|
format: "ymd",
|
|
utc: true,
|
|
}).relative();
|
|
}
|
|
if (row.updated_at !== "undefined") {
|
|
row.updated_at = new SMDate(row.updated_at, {
|
|
format: "ymd",
|
|
utc: true,
|
|
}).relative();
|
|
}
|
|
|
|
items.value.push(row);
|
|
});
|
|
|
|
itemsTotal.value = data.total;
|
|
} catch (error) {
|
|
if (error.status != 404) {
|
|
toastStore.addToast({
|
|
title: "Server Error",
|
|
content:
|
|
"An error occurred retrieving the list from the server.",
|
|
type: "danger",
|
|
});
|
|
}
|
|
} finally {
|
|
itemsLoading.value = false;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Handle viewing an event.
|
|
*
|
|
* @param item
|
|
*/
|
|
const handleView = (item: Event): void => {
|
|
// router.push({ name: "event", params: { id: item.id } });
|
|
window.open(
|
|
router.resolve({ name: "event", params: { id: item.id } }).href,
|
|
"_blank"
|
|
);
|
|
};
|
|
|
|
/**
|
|
* Handle duplicating an event.
|
|
*
|
|
* @param item
|
|
*/
|
|
const handleDuplicate = async (item: Event): Promise<void> => {
|
|
// try {
|
|
// let result = await api.post({
|
|
// url: "/events",
|
|
// body: item,
|
|
// });
|
|
// toastStore.addToast({
|
|
// title: "Event Duplicated",
|
|
// content:
|
|
// "The event has been duplicated.",
|
|
// type: "success",
|
|
// });
|
|
// router.push({ name: "dashboard-event-edit", params: { id: result.id } });
|
|
// } catch {
|
|
// toastStore.addToast({
|
|
// title: "Server Error",
|
|
// content:
|
|
// "An error occurred duplicating the event.",
|
|
// type: "danger",
|
|
// });
|
|
// }
|
|
};
|
|
|
|
/**
|
|
* User requests to edit the item
|
|
*
|
|
* @param {Event} item The event item.
|
|
*/
|
|
const handleEdit = (item: Event) => {
|
|
router.push({
|
|
name: "dashboard-event-edit",
|
|
params: { id: item.id },
|
|
query: {
|
|
return: encodeURIComponent(
|
|
window.location.pathname + window.location.search
|
|
),
|
|
},
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Request to delete an event item from the server.
|
|
*
|
|
* @param {Event} item The event object to delete.
|
|
*/
|
|
const handleDelete = async (item: Event) => {
|
|
let result = await openDialog(SMDialogConfirm, {
|
|
title: "Delete File?",
|
|
text: `Are you sure you want to delete the event <strong>${item.title}</strong>?`,
|
|
cancel: {
|
|
type: "secondary",
|
|
label: "Cancel",
|
|
},
|
|
confirm: {
|
|
type: "danger",
|
|
label: "Delete File",
|
|
},
|
|
});
|
|
|
|
if (result == true) {
|
|
try {
|
|
await api.delete({
|
|
url: "/events/{id}",
|
|
params: {
|
|
id: item.id,
|
|
},
|
|
});
|
|
|
|
toastStore.addToast({
|
|
title: "Event Deleted",
|
|
content: `The event ${item.title} has been deleted.`,
|
|
type: "success",
|
|
});
|
|
handleLoad();
|
|
} catch (error) {
|
|
toastStore.addToast({
|
|
title: "Error Deleting Event",
|
|
content:
|
|
error.data?.message ||
|
|
"An unexpected server error occurred",
|
|
type: "danger",
|
|
});
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Parse Event location for humans.
|
|
*
|
|
* @param {Event} item The event object to delete.
|
|
* @returns {string} human readable location.
|
|
*/
|
|
const parseEventLocation = (item: Event) => {
|
|
if (item.location == "online") {
|
|
return "Online";
|
|
}
|
|
|
|
return item.address;
|
|
};
|
|
|
|
handleLoad();
|
|
</script>
|
|
|
|
<style lang="scss">
|
|
.page-dashboard-event-list {
|
|
.toolbar-search {
|
|
max-width: 350px;
|
|
}
|
|
|
|
.table tr {
|
|
td:first-of-type,
|
|
td:nth-of-type(2) {
|
|
word-break: break-all;
|
|
}
|
|
|
|
td:not(:first-of-type) {
|
|
white-space: nowrap;
|
|
}
|
|
}
|
|
}
|
|
|
|
@media only screen and (max-width: 768px) {
|
|
.page-dashboard-event-list {
|
|
.toolbar-search {
|
|
max-width: none;
|
|
}
|
|
}
|
|
}
|
|
</style>
|