Files
Website/resources/js/views/dashboard/EventList.vue
2023-04-23 13:56:27 +10:00

326 lines
9.1 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: 'workshops' }"
type="primary"
label="Create Event"
@click="handleCreate" />
<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-actions="item">
<SMButton
label="Edit"
:dropdown="{
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 { bytesReadable } from "../../helpers/types";
import { updateRouterParams } from "../../helpers/url";
import { useToastStore } from "../../store/ToastStore";
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: "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() == "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,name:${itemSearch.value},OR,description:${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 creating new event.
*/
const handleCreate = (): void => {
router.push({ name: "dashboard-event-create" });
};
/**
* Handle duplicating an event.
*
* @param item
*/
const handleDuplicate = (item: Event): void => {
alert("not implemented");
};
/**
* 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 } });
};
/**
* 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>