Coursebook.vue 7.60 KiB
<template>
<c-r-u-d-iterator
i18n-key="alsijil.coursebook"
:gql-query="gqlQuery"
:gql-additional-query-args="gqlQueryArgs"
:enable-create="false"
:enable-edit="false"
@lastQuery="lastQuery = $event"
ref="iterator"
>
<template #additionalActions="{ attrs, on }">
<v-autocomplete
:items="selectable"
item-text="name"
clearable
return-object
filled
dense
hide-details
:placeholder="$t('alsijil.coursebook.filter.filter_for_obj')"
:loading="selectLoading"
:value="currentObj"
@input="changeSelection"
@click:clear="changeSelection"
/>
<v-switch
:loading="selectLoading"
:label="$t('alsijil.coursebook.filter.own')"
:input-value="filterType === 'my'"
@change="
changeSelection({
filterType: $event ? 'my' : 'all',
type: objType,
id: objId,
})
"
/>
</template>
<template #default="{ items }">
<v-list-item
v-for="day in groupDocsByDay(items)"
two-line
:key="'day-' + day[0]"
>
<v-list-item-content :id="'documentation_' + day[0].toISODate()">
<v-list-item-title>{{ $d(day[0], "short") }}</v-list-item-title>
<v-list max-width="100%">
<v-list-item
v-for="doc in day.slice(1)"
:key="'documentation-' + doc.id"
>
<documentation-modal
:documentation="doc"
:affected-query="lastQuery"
/>
</v-list-item>
</v-list>
</v-list-item-content>
</v-list-item>
<coursebook-date-select :value="date" @click="handleDateMove" />
</template>
<template #loading>
<CoursebookLoader />
</template>
<template #no-data>
<CoursebookEmptyMessage icon="mdi-book-off-outline">
{{ $t("alsijil.coursebook.no_data") }}
</CoursebookEmptyMessage>
</template>
<template #no-results>
<CoursebookEmptyMessage icon="mdi-book-alert-outline">
{{
$t("alsijil.coursebook.no_results", { search: $refs.iterator.search })
}}
</CoursebookEmptyMessage>
</template>
</c-r-u-d-iterator>
</template>
<script>
import CRUDIterator from "aleksis.core/components/generic/CRUDIterator.vue";
import DocumentationModal from "./documentation/DocumentationModal.vue";
import { DateTime } from "luxon";
import {
coursesOfPerson,
documentationsForCoursebook,
groupsByPerson,
} from "./coursebook.graphql";
import CoursebookLoader from "./CoursebookLoader.vue";
import CoursebookEmptyMessage from "./CoursebookEmptyMessage.vue";
import CoursebookDateSelect from "./CoursebookDateSelect.vue";
export default {
name: "Coursebook",
components: {
CoursebookDateSelect,
CoursebookEmptyMessage,
CoursebookLoader,
CRUDIterator,
DocumentationModal,
},
props: {
// Either as props OR route params
// TODO: Remove default?
filterType: {
type: String,
required: true,
},
objId: {
type: [Number, String],
required: false,
default: null,
},
objType: {
type: String,
required: false,
default: null,
},
// ISODate
date: {
type: String,
required: false,
default: "",
},
},
data() {
return {
gqlQuery: documentationsForCoursebook,
lastQuery: null,
// Placeholder values while query isn't completed yet
groups: [],
courses: [],
dateStart: null,
dateEnd: null,
};
},
apollo: {
groups: {
query: groupsByPerson,
},
courses: {
query: coursesOfPerson,
},
},
computed: {
gqlQueryArgs() {
return {
// Assure courseId is a number
own: this.filterType === "all" ? false : true,
objId: this.objId ? Number(this.objId) : null,
objType: this.objType?.toUpperCase(),
dateStart: this.dateStart ?? this.date,
dateEnd:
this.dateEnd ??
DateTime.fromISO(this.date).plus({ weeks: 1 }).toISODate(),
};
},
selectable() {
return [
{ header: this.$t("alsijil.coursebook.filter.groups") },
...this.groups.map((group) => ({ type: "group", ...group })),
{ header: this.$t("alsijil.coursebook.filter.courses") },
...this.courses.map((course) => ({ type: "course", ...course })),
];
},
currentObj() {
return this.selectable.find(
(o) => o.type === this.objType && o.id === this.objId,
);
},
selectLoading() {
return (
this.$apollo.queries.groups.loading ||
this.$apollo.queries.courses.loading
);
},
},
methods: {
changeSelection(selection) {
this.$router.push({
name: "alsijil.coursebook_by_type_and_date",
params: {
filterType: selection.filterType
? selection.filterType
: this.filterType,
objType: selection.type,
objId: selection.id,
date: this.date,
},
});
},
// => [[dt doc ...] ...]
groupDocsByDay(docs) {
const byDay = docs.reduce((byDay, doc) => {
// This works with dummy. Does actual doc have dateStart instead?
const day = DateTime.fromISO(doc.datetimeStart).startOf("day");
byDay[day] ??= [day];
byDay[day].push(doc);
return byDay;
}, {});
return Object.keys(byDay)
.sort()
.map((key) => byDay[key]);
},
/**
* @param {"prev"|"next"} direction
*/
handleDateMove(direction) {
const dateStartParsed = DateTime.fromISO(this.dateStart);
const dateEndParsed = DateTime.fromISO(this.dateEnd);
const dateParsed = DateTime.fromISO(this.date);
const newDate =
direction === "prev"
? dateParsed.minus({ days: 1 })
: dateParsed.plus({ days: 1 });
/*
TODO:
Everything below this line is also needed for when a date is selected via the calendar.
→ probably move this into a different function and create a second event listener for the input event.
*/
// Load 3 days into the future/past
if (dateStartParsed >= newDate) {
this.dateStart = newDate.minus({ days: 3 }).toISODate();
}
if (dateEndParsed <= newDate) {
this.dateEnd = newDate.plus({ days: 3 }).toISODate();
}
this.$router.push({
name: "alsijil.coursebook_by_type_and_date",
params: {
filterType: this.filterType,
objType: this.objType,
objId: this.objId,
date: newDate.toISODate(),
},
});
// Define the function to find the nearest ID
const ids = Array.from(
document.querySelectorAll("[id^='documentation_']"),
).map((el) => el.id);
// TODO: This should only be done after loading the new data
const nearestId = this.findNearestId(newDate, direction, ids);
this.$vuetify.goTo("#" + nearestId);
},
findNearestId(targetDate, direction, ids) {
const sortedIds = ids
.map((id) => DateTime.fromISO(id.split("_")[1]))
.sort((a, b) => a - b);
if (direction === "prev") {
sortedIds.reverse();
}
const nearestId =
sortedIds.find((id) =>
direction === "next" ? id >= targetDate : id <= targetDate,
) || sortedIds[sortedIds.length - 1];
return "documentation_" + nearestId.toISODate();
},
},
mounted() {
this.dateStart = this.date;
this.dateEnd = DateTime.fromISO(this.dateStart)
.plus({ weeks: 1 })
.toISODate();
},
};
</script>