Skip to content
Snippets Groups Projects
ManageStudentsDialog.vue 8.19 KiB
<script>
import AbsenceReasonButtons from "aleksis.apps.kolego/components/AbsenceReasonButtons.vue";
import AbsenceReasonChip from "aleksis.apps.kolego/components/AbsenceReasonChip.vue";
import AbsenceReasonGroupSelect from "aleksis.apps.kolego/components/AbsenceReasonGroupSelect.vue";
import DialogCloseButton from "aleksis.core/components/generic/buttons/DialogCloseButton.vue";
import MobileFullscreenDialog from "aleksis.core/components/generic/dialogs/MobileFullscreenDialog.vue";
import mutateMixin from "aleksis.core/mixins/mutateMixin.js";
import deepSearchMixin from "aleksis.core/mixins/deepSearchMixin.js";
import documentationPartMixin from "../documentation/documentationPartMixin";
import LessonInformation from "../documentation/LessonInformation.vue";
import { updateParticipationStatuses } from "./participationStatus.graphql";
import SlideIterator from "aleksis.core/components/generic/SlideIterator.vue";
import PersonalNotes from "../personal_notes/PersonalNotes.vue";
import ExtraMarkChip from "../../extra_marks/ExtraMarkChip.vue";
import TardinessChip from "./TardinessChip.vue";
import TardinessField from "./TardinessField.vue";

export default {
  name: "ManageStudentsDialog",
  extends: MobileFullscreenDialog,
  components: {
    TardinessChip,
    ExtraMarkChip,
    AbsenceReasonChip,
    AbsenceReasonGroupSelect,
    AbsenceReasonButtons,
    PersonalNotes,
    LessonInformation,
    MobileFullscreenDialog,
    SlideIterator,
    TardinessField,
    DialogCloseButton,
  },
  mixins: [documentationPartMixin, mutateMixin, deepSearchMixin],
  data() {
    return {
      dialog: false,
      search: "",
      loadSelected: false,
      selected: [],
      isExpanded: false,
    };
  },
  props: {
    loadingIndicator: {
      type: Boolean,
      default: false,
      required: false,
    },
    useDeepSearch: {
      type: Boolean,
      default: true,
      required: false,
    },
  },
  computed: {
    items() {
      return this.documentation.participations;
    },
  },
  methods: {
    sendToServer(participations, field, value) {
      let fieldValue;

      if (field === "absenceReason") {
        fieldValue = {
          absenceReason: value === "present" ? null : value,
        };
      } else if (field === "tardiness") {
        fieldValue = {
          tardiness: value,
        };
      } else {
        console.error(`Wrong field '${field}' for sendToServer`);
        return;
      }

      this.mutate(
        updateParticipationStatuses,
        {
          input: participations.map((participation) => ({
            id: participation.id,
            ...fieldValue,
          })),
        },
        (storedDocumentations, incomingStatuses) => {
          const documentation = storedDocumentations.find(
            (doc) => doc.id === this.documentation.id,
          );

          incomingStatuses.forEach((newStatus) => {
            const participationStatus = documentation.participations.find(
              (part) => part.id === newStatus.id,
            );
            participationStatus.absenceReason = newStatus.absenceReason;
            participationStatus.tardiness = newStatus.tardiness;
            participationStatus.isOptimistic = newStatus.isOptimistic;
          });

          return storedDocumentations;
        },
      );
    },
    handleMultipleAction(absenceReasonId) {
      this.loadSelected = true;
      this.sendToServer(this.selected, "absenceReason", absenceReasonId);
      this.$once("save", this.resetMultipleAction);
    },
    resetMultipleAction() {
      this.loadSelected = false;
      this.$set(this.selected, []);
      this.$refs.iterator.selected = [];
    },
  },
};
</script>

<template>
  <mobile-fullscreen-dialog
    scrollable
    v-bind="$attrs"
    v-on="$listeners"
    v-model="dialog"
    :close-button="false"
  >
    <template #activator="activator">
      <slot name="activator" v-bind="activator" />
    </template>

    <template #title>
      <div class="d-flex full-width">
        <lesson-information v-bind="documentationPartProps" :compact="false" />
        <dialog-close-button @click="dialog = false" class="ml-4" />
      </div>
      <v-scroll-x-transition leave-absolute>
        <v-text-field
          v-show="!isExpanded"
          type="search"
          v-model="search"
          clearable
          rounded
          hide-details
          single-line
          prepend-inner-icon="$search"
          dense
          outlined
          :placeholder="$t('actions.search')"
          class="pt-4 full-width"
        />
      </v-scroll-x-transition>
    </template>
    <template #content>
      <slide-iterator
        ref="iterator"
        v-model="selected"
        :items="items"
        :search="search"
        :item-key-getter="
          (item) => 'documentation-' + documentation.id + '-student-' + item.id
        "
        :is-expanded.sync="isExpanded"
        :loading="loadingIndicator || loadSelected"
        :load-only-selected="loadSelected"
        :disabled="loading"
        :custom-filter="deepSearch"
      >
        <template #listItemContent="{ item }">
          <v-list-item-title>
            {{ item.person.fullName }}
          </v-list-item-title>
          <v-list-item-subtitle
            v-if="
              item.absenceReason ||
              item.notesWithNote?.length > 0 ||
              item.notesWithExtraMark?.length > 0 ||
              item.tardiness
            "
            class="d-flex flex-wrap gap"
          >
            <absence-reason-chip
              v-if="item.absenceReason"
              small
              :absence-reason="item.absenceReason"
            />
            <v-chip
              v-for="note in item.notesWithNote"
              :key="'text-note-note-overview-' + note.id"
              small
            >
              <v-avatar left>
                <v-icon small>mdi-note-outline</v-icon>
              </v-avatar>
              <span class="text-truncate" style="max-width: 30ch">
                {{ note.note }}
              </span>
            </v-chip>
            <extra-mark-chip
              v-for="note in item.notesWithExtraMark"
              :key="'extra-mark-note-overview-' + note.id"
              :extra-mark="extraMarks.find((e) => e.id === note.extraMark.id)"
              small
            />
            <tardiness-chip
              v-if="item.tardiness"
              :tardiness="item.tardiness"
              small
            />
          </v-list-item-subtitle>
        </template>

        <template #expandedItem="{ item, close }">
          <v-card-title>
            <v-tooltip bottom>
              <template #activator="{ on, attrs }">
                <v-btn v-bind="attrs" v-on="on" icon @click="close">
                  <v-icon>$prev</v-icon>
                </v-btn>
              </template>
              <span v-t="'actions.back_to_overview'" />
            </v-tooltip>
            {{ item.person.fullName }}
          </v-card-title>
          <v-card-text>
            <absence-reason-group-select
              allow-empty
              empty-value="present"
              :loadSelectedChip="loading"
              :value="item.absenceReason?.id || 'present'"
              @input="sendToServer([item], 'absenceReason', $event)"
            />
            <tardiness-field
              v-bind="documentationPartProps"
              :loading="loading"
              :disabled="loading"
              :participation="item"
              :value="item.tardiness"
              @input="sendToServer([item], 'tardiness', $event)"
            />
          </v-card-text>
          <v-divider />
          <v-card-text>
            <personal-notes
              v-bind="documentationPartProps"
              :participation="
                documentation.participations.find((p) => p.id === item.id)
              "
            />
          </v-card-text>
        </template>
      </slide-iterator>
    </template>

    <template #actions>
      <v-scroll-y-reverse-transition>
        <div v-show="selected.length > 0" class="full-width">
          <absence-reason-buttons
            allow-empty
            empty-value="present"
            @input="handleMultipleAction"
          />
        </div>
      </v-scroll-y-reverse-transition>
    </template>
  </mobile-fullscreen-dialog>
</template>

<style scoped></style>