Skip to content
Snippets Groups Projects
Coursebook.vue 7.78 KiB
Newer Older
permcu's avatar
permcu committed
<template>
  <c-r-u-d-iterator
Julian's avatar
Julian committed
    i18n-key="alsijil.coursebook"
    :gql-query="gqlQuery"
    :gql-additional-query-args="gqlQueryArgs"
    :enable-create="false"
    :enable-edit="false"
    :elevated="false"
Julian's avatar
Julian committed
    @lastQuery="lastQuery = $event"
    ref="iterator"
permcu's avatar
permcu committed
    disable-pagination
    hide-default-footer
Julian's avatar
Julian committed
    use-deep-search
Hangzhi Yu's avatar
Hangzhi Yu committed
  >
    <template #additionalActions="{ attrs, on }">
      <coursebook-filters v-model="filters" />
    </template>
permcu's avatar
permcu committed
    <template #default="{ items }">
Julian's avatar
Julian committed
      <v-list-item
permcu's avatar
permcu committed
        v-for="{ date, docs, idx, lastIdx } in groupDocsByDay(items)"
        v-intersect="{
permcu's avatar
permcu committed
            handler: intersectHandler(date, idx, lastIdx),
            options: {
permcu's avatar
permcu committed
              threshold: [0, 1],
            },
Julian's avatar
Julian committed
        two-line
        :key="'day-' + date"
        :id="'documentation_' + date.toISODate()"
Julian's avatar
Julian committed
      >
        <v-list-item-content>
Hangzhi Yu's avatar
Hangzhi Yu committed
          <v-subheader class="text-h6">{{
            $d(day[0], "dateWithWeekday")
          }}</v-subheader>
          <v-list max-width="100%" class="pt-0 mt-n1">
Julian's avatar
Julian committed
            <v-list-item
              v-for="doc in docs"
              :key="'documentation-' + (doc.oldId || doc.id)"
Julian's avatar
Julian committed
            >
              <documentation-modal
                :documentation="doc"
                :affected-query="lastQuery"
              />
            </v-list-item>
          </v-list>
        </v-list-item-content>
      </v-list-item>
permcu's avatar
permcu committed
      <date-select-footer :value="$route.hash.substring(1)" />
      <CoursebookEmptyMessage icon="mdi-book-off-outline">
        {{ $t("alsijil.coursebook.no_data") }}
      </CoursebookEmptyMessage>
      <CoursebookEmptyMessage icon="mdi-book-alert-outline">
Hangzhi Yu's avatar
Hangzhi Yu committed
        {{
          $t("alsijil.coursebook.no_results", { search: $refs.iterator.search })
        }}
      </CoursebookEmptyMessage>
permcu's avatar
permcu committed
    </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";
permcu's avatar
permcu committed

export default {
  name: "Coursebook",
  components: {
    CoursebookEmptyMessage,
    CoursebookFilters,
permcu's avatar
permcu committed
    CRUDIterator,
    DateSelectFooter,
    DocumentationModal,
permcu's avatar
permcu committed
  },
  props: {
    filterType: {
      type: String,
      required: true,
    },
permcu's avatar
permcu committed
      type: [Number, String],
      required: false,
permcu's avatar
permcu committed
  },
  data() {
    return {
      gqlQuery: documentationsForCoursebook,
permcu's avatar
permcu committed
      lastQuery: null,
permcu's avatar
permcu committed
      visible: [],
      dateStart: "",
      dateEnd: "",
      // Placeholder values while query isn't completed yet
      groups: [],
      courses: [],
      incomplete: false,
permcu's avatar
permcu committed
    };
  },
  computed: {
    // Assertion: Should only fire on page load or selection change.
    //            Resets date range.
permcu's avatar
permcu committed
    gqlQueryArgs() {
permcu's avatar
permcu committed
      console.log('computing gqlQueryArgs');
permcu's avatar
permcu committed
      return {
        own: this.filterType === "all" ? false : true,
permcu's avatar
permcu committed
        objId: this.objId ? Number(this.objId) : undefined,
        objType: this.objType?.toUpperCase(),
        dateStart: this.dateStart,
        dateEnd: this.dateEnd,
        incomplete: !!this.incomplete,
    filters: {
      get() {
Hangzhi Yu's avatar
Hangzhi Yu committed
        return {
          objType: this.objType,
          objId: this.objId,
          filterType: this.filterType,
          incomplete: this.incomplete,
        };
      },
      set(selectedFilters) {
        if (Object.hasOwn(selectedFilters, "incomplete")) {
          this.incomplete = selectedFilters.incomplete;
Hangzhi Yu's avatar
Hangzhi Yu committed
        } else if (
          Object.hasOwn(selectedFilters, "filterType") ||
          Object.hasOwn(selectedFilters, "objId") ||
          Object.hasOwn(selectedFilters, "objType")
        ) {
          this.$router.push({
permcu's avatar
permcu committed
            name: "alsijil.coursebook",
            params: {
              filterType: selectedFilters.filterType
                ? selectedFilters.filterType
                : this.filterType,
              objType: selectedFilters.objType,
              objId: selectedFilters.objId,
            },
permcu's avatar
permcu committed
            hash: this.$route.hash,
          // computed should not have side effects
          // but this was actually done before filters was refactored into
          // its own component
          this.resetDate();
permcu's avatar
permcu committed
          // might skip query until both set = atomic
Hangzhi Yu's avatar
Hangzhi Yu committed
      },
    },
permcu's avatar
permcu committed
    // TODO: Scroll to actual first date!
    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() })
      } 

      // if (date) {
      //   this.$router.replace({ hash: date })
      // }
permcu's avatar
permcu committed
      const date = DateTime.fromISO(this.$route.hash.substring(1));
      // Reset visible
      this.visible = [];
      this.dateStart = date.minus({ days: 3 }).toISODate();
      this.dateEnd = date.plus({ days: 4 }).toISODate();
permcu's avatar
permcu committed
      // => {dt: {date: dt, docs: doc ...} ...}
      const docsByDay = docs.reduce((byDay, doc) => {
Hangzhi Yu's avatar
Hangzhi Yu committed
        // This works with dummy. Does actual doc have dateStart instead?
        const day = DateTime.fromISO(doc.datetimeStart).startOf("day");
permcu's avatar
permcu committed
        byDay[day] ??= {date: day, docs: []};
        byDay[day].docs.push(doc);
Hangzhi Yu's avatar
Hangzhi Yu committed
        return byDay;
      }, {});
permcu's avatar
permcu committed
      // => [{date: dt, docs: doc ..., idx: idx, lastIdx: last-idx} ...]
      // sorting is necessary since backend can send docs unordered
      return Object.keys(docsByDay)
Hangzhi Yu's avatar
Hangzhi Yu committed
        .sort()
permcu's avatar
permcu committed
        .map((key, idx, {length}) => {
          const day = docsByDay[key];
permcu's avatar
permcu committed
          day.idx = idx;
          day.lastIdx = length - 1;
    // docsByDay: {dt: [dt doc ...] ...}
permcu's avatar
permcu committed
    fetchMore(from, to) {
      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) };
        }
    setDate(date) {
      this.$router.replace({ hash: date })
permcu's avatar
permcu committed
   intersectHandler(date, idx, lastIdx) {
     let waiting = true;
     return (entries) => {
       const entry = entries[0];
       if (entry.isIntersecting) {
permcu's avatar
permcu committed
         if (entry.boundingClientRect.top <= 0) {
           console.log('@', date.toISODate());
           this.setDate(date.toISODate());
         }
permcu's avatar
permcu committed
         if (waiting && idx === 0) {
           console.log('load up', date.toISODate());
           this.fetchMore(date.minus({ days: 4 }).toISODate(),
                          date.minus({ days: 1 }).toISODate());
           waiting = false;
         } else if (waiting && idx === lastIdx) {
           console.log('load down', date.toISODate());
           this.fetchMore(date.plus({ days: 1 }).toISODate(),
                          date.plus({ days: 5 }).toISODate());
           waiting = false;
         }
       }
     };
   },
  created() {
    this.resetDate();
permcu's avatar
permcu committed
};
</script>

<style>
.max-width {
  max-width: 25rem;
}
</style>