Newer
Older
i18n-key="alsijil.coursebook"
:gql-query="gqlQuery"
:gql-additional-query-args="gqlQueryArgs"
:enable-create="false"
:enable-edit="false"
<template #additionalActions="{ attrs, on }">
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
<div class="d-flex flex-grow-1 justify-end">
<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"
class="max-width"
/>
<div class="ml-6">
<v-switch
:loading="selectLoading"
:label="$t('alsijil.coursebook.filter.own')"
:input-value="filterType === 'my'"
@change="
changeSelection({
filterType: $event ? 'my' : 'all',
type: objType,
id: objId,
})
"
dense
inset
hide-details
/>
<v-switch
:loading="selectLoading"
:label="$t('alsijil.coursebook.filter.missing')"
v-model="incomplete"
dense
inset
hide-details
/>
</div>
</div>
v-for="{ date, docs, first, last } in groupDocsByDay(items)"
handler: intersectHandler(date, first, last),
:focus-on-mount="initDate && (initDate.toMillis() === date.toMillis())"
<date-select-footer
:value="$route.hash.substring(1)"
@input="gotoDate"
@prev="gotoPrev"
@next="gotoNext"
/>

Julian
committed
</template>
<template #loading>
:number-of-days="10"
:number-of-docs="5"
/>

Julian
committed
</template>
<template #no-data>
<CoursebookEmptyMessage icon="mdi-book-off-outline">
{{ $t("alsijil.coursebook.no_data") }}
</CoursebookEmptyMessage>

Julian
committed
</template>
<template #no-results>
<CoursebookEmptyMessage icon="mdi-book-alert-outline">
{{
$t("alsijil.coursebook.no_results", { search: $refs.iterator.search })
}}
</template>
</c-r-u-d-iterator>
</template>
<script>
import CRUDIterator from "aleksis.core/components/generic/CRUDIterator.vue";
import DateSelectFooter from "aleksis.core/components/generic/DateSelectFooter.vue";
import { DateTime, Interval } from "luxon";
import CoursebookLoader from "./CoursebookLoader.vue";
import CoursebookEmptyMessage from "./CoursebookEmptyMessage.vue";
export default {
name: "Coursebook",
components: {
filterType: {
type: String,
required: true,
},
objId: {
objType: {
type: String,
required: false,
},
gqlQuery: documentationsForCoursebook,
dateStart: "",
dateEnd: "",
// Placeholder values while query isn't completed yet
groups: [],
courses: [],
query: groupsByPerson,
query: coursesOfPerson,
// Assertion: Should only fire on page load or selection change.
// Resets date range.
own: this.filterType === "all" ? false : true,
objId: this.objId ? Number(this.objId) : undefined,
objType: this.objType?.toUpperCase(),
dateStart: this.dateStart,
dateEnd: this.dateEnd,
{ 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 })),
];
},
return this.selectable.find(
(o) => o.type === this.objType && o.id === this.objId,
);
return (
this.$apollo.queries.groups.loading ||
this.$apollo.queries.courses.loading
);
},
resetDate() {
// Assure current date
console.log('Resetting date range', this.$route.hash);
if (!this.$route.hash) {
console.log('Set default date');
this.setDate(DateTime.now().toISODate());
const date = DateTime.fromISO(this.$route.hash.substring(1));
this.dateStart = date.minus({ days: 3 }).toISODate();
this.dateEnd = date.plus({ days: 4 }).toISODate();
changeSelection(selection) {
this.$router.push({
filterType: selection.filterType
? selection.filterType
: this.filterType,
objType: selection.type,
objId: selection.id,
},
groupDocsByDay(docs) {
const docsByDay = docs.reduce((byDay, doc) => {
// This works with dummy. Does actual doc have dateStart instead?
const day = DateTime.fromISO(doc.datetimeStart).startOf("day");
byDay[day] ??= {date: day, docs: []};
byDay[day].docs.push(doc);
// => [{date: dt, docs: doc ..., idx: idx, lastIdx: last-idx} ...]
// sorting is necessary since backend can send docs unordered
const day = docsByDay[key];
day.first = idx === 0;
const lastIdx = length - 1;
day.last = idx === lastIdx;
// docsByDay: {dt: [dt doc ...] ...}
fetchMore(from, to, then) {
console.log('fetching', from, to);
this.lastQuery.fetchMore({
variables: {
dateStart: from,
dateEnd: to,
},
// Transform the previous result with new data
updateQuery: (previousResult, { fetchMoreResult }) => {
console.log('previousResult', previousResult);
console.log('fetchMoreResult', fetchMoreResult);
return { items: previousResult.items.concat(fetchMoreResult.items) };
}
if (!(this.$route.hash.substring(1) === date)) {
this.$router.replace({ hash: date })
}
fixScrollPos(height, top) {
console.log('fix @', top, document.documentElement.scrollTop, height, document.documentElement.scrollHeight);
this.$nextTick(() => {
console.log('fix @', top, document.documentElement.scrollTop, height, document.documentElement.scrollHeight);
if (height < document.documentElement.scrollHeight) {
console.log('fixingTop');
document.documentElement.scrollTop = document.documentElement.scrollHeight - height + top;
this.ready = true;
} else {
// Update top, could have changed in the meantime.
this.fixScrollPos(height, document.documentElement.scrollTop);
}
});
},
intersectHandler(date, first, last) {
let once = true;
return (entries) => {
const entry = entries[0];
if (entry.isIntersecting) {
if ((entry.boundingClientRect.top <= 165)
|| first
|| last) {
console.log('@', date.toISODate());
this.setDate(date.toISODate());
}
if (once && this.ready && first) {
console.log('load up', date.toISODate());
this.ready = false;
this.fetchMore(date.minus({ days: 5 }).toISODate(),
date.minus({ days: 1 }).toISODate(),
() => {
this.fixScrollPos(document.documentElement.scrollHeight,
document.documentElement.scrollTop);
});
once = false;
} else if (once && this.ready && last) {
console.log('load down', date.toISODate());
this.ready = false;
this.fetchMore(date.plus({ days: 1 }).toISODate(),
date.plus({ days: 5 }).toISODate(),
() => { this.ready = true });
once = false;
}
}
};
},
// TODO: only load the else if out of range / not just not present
gotoDate(date) {
const present = this.$refs.days
.find((day) => day.date.toISODate() === date);
if (present) {
// React immediatly -> smoother navigation
// Also intersect handler does not always react to scrollIntoView
this.setDate(date);
present.focus("smooth");
} else {
this.setDate(date);
this.resetDate();
}
},
// TODO: Disable navigation while loading!
gotoPrev() {
const current = this.$route.hash.substring(1);
const pref = this.$refs.days
.map((day) => day.date.toISODate())
.sort()
.reverse()
.find((date) => date < current);
this.gotoDate(pref);
},
gotoNext() {
const current = this.$route.hash.substring(1);
const next = this.$refs.days
.map((day) => day.date.toISODate())
.sort()
.find((date) => date > current);
this.gotoDate(next);
},
created() {
this.resetDate();
},
<style>
.max-width {
max-width: 25rem;
}
</style>