Newer
Older
i18n-key="alsijil.coursebook"
:gql-query="gqlQuery"
:gql-additional-query-args="gqlQueryArgs"
:enable-create="false"
:enable-edit="false"
@items="docsByDay = groupDocsByDay($event)"
<template #additionalActions="{ attrs, on }">
<coursebook-filters v-model="filters" />
<template #default>
v-for="day in listDocsByDay(docsByDay)"
:id="'documentation_' + day[0].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,
docsByDay: {},
// 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: dateRange[0].toISODate(),
dateEnd: dateRange[1].toISODate(),
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,
},
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 = {};
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
return [this.dateRange(dateRange[0])[0], this.dateRange(dateRange[lastIdx])[lastIdx]];
},
// => {dt: [dt doc ...] ...}
groupDocsByDay(docs) {
return 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;
}, {});
},
// => [[dt doc ...] ...]
listDocsByDay(docsByDay) {
return Object.keys(docsByDay)
.map((key) => docsByDay[key]);
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
debounce(fn, delay) {
let timer;
return () => {
console.log('debounce');
clearTimeout(timer);
timer = setTimeout(fn, delay);
}
},
// Adapted from
// https://github.com/vuejs/vuepress/blob/38e98634af117f83b6a32c8ff42488d91b66f663/packages/%40vuepress/plugin-active-header-links/clientRootMixin.js
setCurrentDay() {
const days = Array.from(document.querySelectorAll("[id^='documentation_']"));
const scrollTop = Math.max(
window.pageYOffset,
document.documentElement.scrollTop,
document.body.scrollTop
);
for (let i = 0; i < days.length; i++) {
const day = days[i];
const nextDay =days[i + 1];
if ((scrollTop >= day.offsetTop + 10 || i == 0) && (!nextDay || scrollTop < nextDay.offsetTop - 10)) {
const date = day.id.split("_")[1];
if (date !== this.$route.hash.substring(1)) {
this.gotoDate(date);
}
return
}
}
},
/**
* @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) {
// find missing & fetch missing range
// date +- 5 days ?
const dateRange = Interval
.fromDateTimes(date.minus({ days: 3 }), date.plus({ days: 4 }))
.splitBy({ days: 1 })
.map((ts) => ts.start);
console.log('assureDate', dateRange.map((ts) => ts.toISODate()));
// look up in docsByDay
console.log('missing', dateRange.map((ts) => this.docsByDay[ts] ));
console.log('missing', dateRange.filter((ts) => !this.docsByDay[ts] ));
// dateRange.forEach((ts) => { this.docsByDay[ts.toISODate()] = 42 });
console.log('docsByDay', this.docsByDay);
console.log('2024-03-29', this.docsByDay[DateTime.fromISO('2024-03-29')]);
console.log('2024-03-29', dateRange[3], this.docsByDay[dateRange[3]]);
// sort missing and ask for first to last
// integrate into docsByDay
}
console.log('hash', this.$route.hash);
// assure
this.assureDate(DateTime.fromISO(date));
mounted() {
window.addEventListener('scroll', this.debounce(this.setCurrentDay, 300));
},
<style>
.max-width {
max-width: 25rem;
}
</style>