Newer
Older
i18n-key="alsijil.coursebook"
:gql-query="gqlQuery"
:gql-additional-query-args="gqlQueryArgs"
:enable-create="false"
:enable-edit="false"
<template #additionalActions="{ attrs, on }">
<coursebook-filters v-model="filters" />
v-for="{ date, docs, first, last } in groupDocsByDay(items)"
v-intersect="onIntersect"
:data-date="date.toISODate()"
:data-first="first"
:data-last="last"
:key="'day-' + date"
:id="'documentation_' + date.toISODate()"
<v-subheader class="text-h6">{{
$d(day[0], "dateWithWeekday")
}}</v-subheader>
<v-list max-width="100%" class="pt-0 mt-n1">
:key="'documentation-' + (doc.oldId || doc.id)"

Julian
committed
<documentation-modal
:documentation="doc"
:affected-query="lastQuery"
/>
</v-list-item>
</v-list>
</v-list-item-content>
</v-list-item>

Julian
committed
</template>
<template #loading>

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 DocumentationModal from "./documentation/DocumentationModal.vue";
import { DateTime, Interval } from "luxon";
import { documentationsForCoursebook } from "./coursebook.graphql";
import CoursebookFilters from "./CoursebookFilters.vue";
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,
currentDate: "",
visible: [],
dateStart: "",
dateEnd: "",
// Placeholder values while query isn't completed yet
groups: [],
courses: [],
// 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,
return {
objType: this.objType,
objId: this.objId,
filterType: this.filterType,
incomplete: this.incomplete,
};
},
set(selectedFilters) {
if (Object.hasOwn(selectedFilters, "incomplete")) {
this.incomplete = selectedFilters.incomplete;
} else if (
Object.hasOwn(selectedFilters, "filterType") ||
Object.hasOwn(selectedFilters, "objId") ||
Object.hasOwn(selectedFilters, "objType")
) {
params: {
filterType: selectedFilters.filterType
? selectedFilters.filterType
: this.filterType,
objType: selectedFilters.objType,
objId: selectedFilters.objId,
},
// computed should not have side effects
// but this was actually done before filters was refactored into
// its own component
this.resetDate();
resetDate() {
// Assure current date
console.log('Resetting date range', this.$route.hash);
if (!this.$route.hash) {
console.log('Set default date');
this.$router.replace({ hash: DateTime.now().toISODate() })
}
// Resetting known dates to dateRange around current date
this.knownDates = {};
this.currentDate = this.$route.hash.substring(1);
const dateRange = this.dateRange(DateTime.fromISO(this.$route.hash.substring(1)));
dateRange.forEach((ts) => this.knownDates[ts] = true);
const lastIdx = dateRange.length - 1;
// Returning a dateRange each around first & last date for the initial query
this.dateStart = this.dateRange(dateRange[0])[0].toISODate();
this.dateEnd = this.dateRange(dateRange[lastIdx])[lastIdx].toISODate();
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: [], first: false, last: false};
byDay[day]['docs'].push(doc);
.map((key, i, {length}) => {
const day = docsByDay[key];
if (i === 0) {
day['first'] = true;
} else if (i === length - 1) {
day['last'] = true;
return day;
});
// sorting is necessary since backend can send docs unordered
/**
* @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({
params: {
filterType: this.filterType,
objType: this.objType,
objId: this.objId,
},
});
// 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();
},
dateRange(date) {
return Interval
.fromDateTimes(date.minus({ days: 3 }), date.plus({ days: 4 }))
.splitBy({ days: 1 })
.map((ts) => ts.start);
},
// docsByDay: {dt: [dt doc ...] ...}
assureDate(date) {
console.log(this.lastQuery);
console.log('unknown date', date.toISODate());
// ask for first to last
this.lastQuery.fetchMore({
variables: {
dateStart: missing[0].toISODate(),
dateEnd: missing[missing.length - 1].toISODate(),
},
// Transform the previous result with new data
updateQuery: (previousResult, { fetchMoreResult }) => {
console.log('previousResult', previousResult);
console.log('fetchMoreResult', fetchMoreResult);
items: [...previousResult.items,
...fetchMoreResult.items.filter((doc) => {
return previousResult.items.find((prev) => prev.id === doc.id)
}),
],
};
}
console.log('hash', this.$route.hash);
// assure
this.assureDate(DateTime.fromISO(date));
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
onIntersect(entries, observer) {
const entry = entries[0];
if (entry.isIntersecting) {
// coming
console.log('intersect', this.visible);
// track visible
if (this.visible[0] > entry.target.dataset.date || this.visible.length === 0) {
// coming is new first (top) date
this.visible.unshift(entry.target.dataset.date);
console.log('current', this.visible[0]);
} else if (this.visible[this.visible.length -1] < entry.target.dataset.date) {
// coming is new last (bottom) date
this.visible.push(entry.target.dataset.date);
}
// load more
if (entry.target.dataset.first) {
console.log('load up');
} else if (entry.target.dataset.last) {
console.log('load down');
}
} else if (this.visible[0] === entry.target.dataset.date) {
// first (top) visible date is going
this.visible.shift()
console.log('current', this.visible[0]);
} else if (this.visible[this.visible.length - 1] === entry.target.dataset.date) {
// last (bottom) visible date is going
this.visible.pop()
}
},
created() {
this.resetDate();
},
<style>
.max-width {
max-width: 25rem;
}
</style>