Skip to content
Snippets Groups Projects
SubstitutionCard.vue 13.1 KiB
Newer Older
<script setup>
Hangzhi Yu's avatar
Hangzhi Yu committed
import SubstitutionInformation from "./SubstitutionInformation.vue";
Hangzhi Yu's avatar
Hangzhi Yu committed
import TeacherField from "aleksis.apps.cursus/components/TeacherField.vue";
import SubjectChipSelectField from "aleksis.apps.cursus/components/SubjectChipSelectField.vue";

import createOrPatchMixin from "aleksis.core/mixins/createOrPatchMixin.js";
import deleteMixin from "aleksis.core/mixins/deleteMixin.js";
</script>

<template>
  <v-card class="my-1 full-width" :loading="loading">
Hangzhi Yu's avatar
Hangzhi Yu committed
    <v-card-text
      class="full-width main-body pa-2"
      :class="{
Hangzhi Yu's avatar
Hangzhi Yu committed
        vertical: $vuetify.breakpoint.mobile,
Hangzhi Yu's avatar
Hangzhi Yu committed
      }"
Hangzhi Yu's avatar
Hangzhi Yu committed
      <substitution-information :substitution="substitution" />
      <subject-chip-select-field
Hangzhi Yu's avatar
Hangzhi Yu committed
        :value="subject"
        :disabled="loading"
        :items="amendableSubjects"
Hangzhi Yu's avatar
Hangzhi Yu committed
        @input="subjectInput"
      />

      <v-autocomplete
        :value="substitutionRoomIDs"
        multiple
        chips
        deletable-chips
        dense
        hide-details
        outlined
        :items="amendableRooms"
        item-text="name"
        item-value="id"
        :disabled="loading"
Hangzhi Yu's avatar
Hangzhi Yu committed
        :label="$t('chronos.substitutions.overview.rooms.label')"
        @input="roomsInput"
      >
        <template #prepend-inner>
Hangzhi Yu's avatar
Hangzhi Yu committed
          <template
Hangzhi Yu's avatar
Hangzhi Yu committed
            v-if="roomsWithStatus.filter((t) => t.status === 'regular').length"
Hangzhi Yu's avatar
Hangzhi Yu committed
          >
Hangzhi Yu's avatar
Hangzhi Yu committed
            <v-chip
              v-for="room in roomsWithStatus.filter(
                (t) => t.status === 'regular',
              )"
              :key="room.id"
              class="mb-1"
              small
            >
              {{ room.shortName }}
            </v-chip>
          </template>
          <template
Hangzhi Yu's avatar
Hangzhi Yu committed
            v-if="roomsWithStatus.filter((t) => t.status === 'removed').length"
Hangzhi Yu's avatar
Hangzhi Yu committed
            <v-chip
              v-for="room in roomsWithStatus.filter(
                (t) => t.status === 'removed',
              )"
              :key="room.id"
              outlined
              color="error"
              class="mb-1"
              small
            >
              <v-icon left small>mdi-cancel</v-icon>
              <div class="text-decoration-line-through">
                {{ room.shortName ? room.shortName : room.name }}
              </div>
            </v-chip>
          </template>
        </template>
        <template #selection="data">
          <v-chip
            v-bind="data.attrs"
            :input-value="data.selected"
Hangzhi Yu's avatar
Hangzhi Yu committed
            v-if="getRoomStatus(data.item) === 'new'"
Hangzhi Yu's avatar
Hangzhi Yu committed
            class="mb-1 mt-1"
Hangzhi Yu's avatar
Hangzhi Yu committed
            outlined
            color="success"
            @click:close="removeRoom(data.item)"
          >
            <v-icon left small> mdi-plus </v-icon>
Hangzhi Yu's avatar
Hangzhi Yu committed
            {{ data.item.shortName ? data.item.shortName : data.item.name }}
          </v-chip>
        </template>
      </v-autocomplete>
Hangzhi Yu's avatar
Hangzhi Yu committed

Hangzhi Yu's avatar
Hangzhi Yu committed
      <teacher-field
        v-if="amendableTeachers.length > 0"
        :priority-subject="subject"
        :show-subjects="true"
        :value="substitutionTeacherIDs"
        chips
        deletable-chips
        dense
        hide-details
        outlined
Hangzhi Yu's avatar
Hangzhi Yu committed
        :label="$t('chronos.substitutions.overview.teacher.label')"
        :custom-teachers="amendableTeachers"
        @input="teachersInput"
      >
        <template #prepend-inner>
          <v-tooltip bottom>
            <template #activator="{ on, attrs }">
              <v-chip
                v-for="teacher in teachersWithStatus.filter(
                  (t) => t.status === 'removed',
                )"
Hangzhi Yu's avatar
Hangzhi Yu committed
                :key="teacher.id"
                outlined
                color="error"
                class="mb-1"
                small
                v-on="on"
                v-bind="attrs"
              >
                <v-icon left small>mdi-account-off-outline</v-icon>
                <div class="text-decoration-line-through">
                  {{ teacher.fullName }}
                </div>
              </v-chip>
            </template>
Hangzhi Yu's avatar
Hangzhi Yu committed
            <span>{{
              $t("chronos.substitutions.overview.teacher.status.absent")
            }}</span>
          </v-tooltip>
        </template>
        <template #selection="data">
          <v-tooltip bottom>
            <template #activator="{ on, attrs }">
              <v-chip
                v-bind="{ ...data.attrs, ...attrs }"
                :input-value="data.selected"
                close
                class="mb-1 mt-1"
                small
                :outlined="getTeacherStatus(data.item) === 'new'"
                :color="getTeacherStatus(data.item) === 'new' ? 'success' : ''"
                @click:close="removeTeacher(data.item)"
                v-on="on"
              >
                <v-icon left small v-if="getTeacherStatus(data.item) === 'new'">
                  mdi-account-plus-outline
                </v-icon>
                {{ data.item.fullName }}
              </v-chip>
            </template>
Hangzhi Yu's avatar
Hangzhi Yu committed
            <span>{{
              getTeacherStatus(data.item) === "new"
                ? $t("chronos.substitutions.overview.teacher.status.new")
                : $t("chronos.substitutions.overview.teacher.status.regular")
            }}</span>
          </v-tooltip>
Hangzhi Yu's avatar
Hangzhi Yu committed
      </teacher-field>
Hangzhi Yu's avatar
Hangzhi Yu committed
      <v-text-field
        dense
        outlined
        hide-details
        :label="$t('chronos.substitutions.overview.comment')"
        :value="substitution.comment"
        @input="comment = $event"
        @focusout="save(false)"
        @keydown.enter="save(false)"
      <v-btn-toggle
        dense
        :color="substitution.cancelled ? 'error' : 'success'"
        :disabled="loading"
        class="justify-self-end"
        :value="substitution.cancelled"
        @change="save(false)"
Hangzhi Yu's avatar
Hangzhi Yu committed
        <v-btn outlined :value="false" @click="cancelled = false">
          {{ $t("chronos.substitutions.overview.cancel.not_cancelled") }}
Hangzhi Yu's avatar
Hangzhi Yu committed
        <v-btn outlined :value="true" @click="cancelled = true">
          {{ $t("chronos.substitutions.overview.cancel.cancelled") }}
        </v-btn>
      </v-btn-toggle>
Hangzhi Yu's avatar
Hangzhi Yu committed
    </v-card-text>
  </v-card>
</template>

<script>
export default {
  name: "SubstitutionCard",
  emits: ["open", "close", "delete"],
  mixins: [createOrPatchMixin, deleteMixin],
  data() {
    return {
      loading: false,
      teachers: [],
      rooms: [],
Hangzhi Yu's avatar
Hangzhi Yu committed
      substitutionSubject: null,
      comment: null,
      cancelled: null,
      type: Object,
      required: true,
    },
    amendableRooms: {
      type: Array,
      required: false,
      default: () => [],
    },
    amendableSubjects: {
      type: Array,
      required: false,
      default: () => [],
    },
    amendableTeachers: {
      type: Array,
      required: false,
      default: () => [],
    },
    handleUpdateAfterCreateOrPatch(itemId) {
      return (cached, incoming) => {
        for (const object of incoming) {
          console.log("summary: handleUpdateAfterCreateOrPatch", object);
          // Replace the current substitution
          const index = cached.findIndex(
            (o) => o[itemId] === this.substitution.id,
          );
          // merged with the incoming partial substitution
          // if creation of proper substitution from dummy one, set ID of substitution currently being edited as oldID so that key in overview doesn't change
          cached[index] = {
            ...this.substitution,
            ...object,
            oldId:
              this.substitution.id !== object.id
                ? this.substitution.id
                : this.substitution.oldId,
          };
        }
        return cached;
      };
    },
    handleUpdateAfterDelete(ids, itemId) {
      return (cached, incoming) => {
        for (const id of ids) {
          // Remove item from cached data or reset it to old ID, if present
          const index = cached.findIndex((o) => o[itemId] === id);
          if (cached[index].oldId) {
            cached[index].id = cached[index].oldId;
            cached[index].oldId = null;

            // Clear dummy substitution
            cached[index].teachers = [];
            cached[index].rooms = [];
            cached[index].subject = null;
            cached[index].comment = "";
            cached[index].cancelled = false;
            this.$emit("delete", cached[index].datetimeStart);
    getTeacherStatus(teacher) {
      return this.teachersWithStatus.find((t) => t.id === teacher.id)?.status;
    },
    getRoomStatus(room) {
      return this.roomsWithStatus.find((r) => r.id === room.id)?.status;
    removeTeacher(teacher) {
Hangzhi Yu's avatar
Hangzhi Yu committed
      this.teachers = this.substitutionTeacherIDs.filter(
        (t) => t !== teacher.id,
      );
      this.save(true);
    },
    removeRoom(room) {
      this.rooms = this.substitutionRoomIDs.filter((r) => r !== room.id);
      this.save(true);
Hangzhi Yu's avatar
Hangzhi Yu committed
    subjectInput(subject) {
      this.substitutionSubject = subject.id;
      this.save();
    },
    teachersInput(teachers) {
      this.teachers = teachers;
      this.save();
Hangzhi Yu's avatar
Hangzhi Yu committed
    roomsInput(rooms) {
      this.rooms = rooms;
      this.save();
Hangzhi Yu's avatar
Hangzhi Yu committed
    },
Hangzhi Yu's avatar
Hangzhi Yu committed
    cancelledInput(cancelled) {
      this.cancelled = cancelled;
      this.save();
    },
    save(allowEmpty = false) {
      if (
        this.teachers.length ||
        this.rooms.length ||
Hangzhi Yu's avatar
Hangzhi Yu committed
        this.substitutionSubject !== null ||
Hangzhi Yu's avatar
Hangzhi Yu committed
        (this.comment !== null && this.comment !== "") ||
        this.cancelled !== null
      ) {
        this.createOrPatch([
          {
            id: this.substitution.id,
Hangzhi Yu's avatar
Hangzhi Yu committed
            ...((allowEmpty || this.teachers.length) && {
              teachers: this.teachers,
            }),
            ...((allowEmpty || this.rooms.length) && { rooms: this.rooms }),
Hangzhi Yu's avatar
Hangzhi Yu committed
            ...(this.substitutionSubject !== null && {
              subject: this.substitutionSubject,
            }),
            ...(this.comment !== null &&
              this.comment !== "" && { comment: this.comment }),
            ...(this.cancelled !== null && { cancelled: this.cancelled }),
Jonathan Weth's avatar
Jonathan Weth committed
            ...((this.teachers.length || this.rooms.length) && {
              cancelled: false,
            }),
        this.teachers = [];
        this.rooms = [];
Hangzhi Yu's avatar
Hangzhi Yu committed
        this.substitutionSubject = null;
        this.comment = null;
        this.cancelled = null;
      } else if (!this.substitution.id.startsWith("DUMMY")) {
        this.delete([this.substitution]);
  computed: {
    substitutionTeacherIDs() {
      return this.substitution.teachers.map((teacher) => teacher.id);
    },
    substitutionRoomIDs() {
      return this.substitution.rooms.map((room) => room.id);
    },
Hangzhi Yu's avatar
Hangzhi Yu committed
    // Group teachers by their substitution status (regular, new, removed)
    teachersWithStatus() {
Hangzhi Yu's avatar
Hangzhi Yu committed
      // IDs of teachers of amended lesson
Hangzhi Yu's avatar
Hangzhi Yu committed
      const oldIds = this.substitution.amends.teachers.map(
        (teacher) => teacher.id,
      );
Hangzhi Yu's avatar
Hangzhi Yu committed
      // IDs of teachers of new substitution lesson
      const newIds = this.substitution.teachers.map((teacher) => teacher.id);
Hangzhi Yu's avatar
Hangzhi Yu committed
      const allTeachers = new Set(
        this.substitution.amends.teachers.concat(this.substitution.teachers),
      );
      let teachersWithStatus = Array.from(allTeachers).map((teacher) => {
        let status = "regular";
        if (newIds.includes(teacher.id) && !oldIds.includes(teacher.id)) {
Hangzhi Yu's avatar
Hangzhi Yu committed
          // Mark teacher as being new if they are only linked to the substitution lesson
Hangzhi Yu's avatar
Hangzhi Yu committed
          status = "new";
        } else if (
          !newIds.includes(teacher.id) &&
          oldIds.includes(teacher.id)
        ) {
Hangzhi Yu's avatar
Hangzhi Yu committed
          // Mark teacher as being rremoved if they are only linked to the amended lesson
Hangzhi Yu's avatar
Hangzhi Yu committed
          status = "removed";
        }
        return { ...teacher, status: status };
      });
      return teachersWithStatus;
    },
    roomsWithStatus() {
Hangzhi Yu's avatar
Hangzhi Yu committed
      const oldIds = this.substitution.amends.rooms.map((room) => room.id);
      const newIds = this.substitution.rooms.map((room) => room.id);
      const allRooms = new Set(
Jonathan Weth's avatar
Jonathan Weth committed
        this.substitution.amends.rooms.concat(
          this.substitution.rooms.filter((r) => !oldIds.includes(r.id)),
        ),
      );
      let roomsWithStatus = Array.from(allRooms).map((room) => {
        let status = "regular";
        if (newIds.includes(room.id) && !oldIds.includes(room.id)) {
          status = "new";
        } else if (
Hangzhi Yu's avatar
Hangzhi Yu committed
          newIds.length &&
          !newIds.includes(room.id) &&
          oldIds.includes(room.id)
        ) {
          status = "removed";
        }
        return { ...room, status: status };
      });
      return roomsWithStatus;
    },
Hangzhi Yu's avatar
Hangzhi Yu committed
    subject() {
Hangzhi Yu's avatar
Hangzhi Yu committed
      if (this.substitution.subject) {
        return this.substitution.subject;
Hangzhi Yu's avatar
Hangzhi Yu committed
      } else if (this.substitution.course?.subject) {
Hangzhi Yu's avatar
Hangzhi Yu committed
        return this.substitution.course.subject;
Hangzhi Yu's avatar
Hangzhi Yu committed
      } else if (this.substitution.amends?.subject) {
Hangzhi Yu's avatar
Hangzhi Yu committed
        return this.substitution.amends.subject;
      }
      return undefined;
Hangzhi Yu's avatar
Hangzhi Yu committed
    },
Hangzhi Yu's avatar
Hangzhi Yu committed

<style scoped>
.main-body {
  display: grid;
  align-items: center;
  grid-template-columns: 2fr 1fr 2fr 3fr 1fr 2fr;
Hangzhi Yu's avatar
Hangzhi Yu committed
  gap: 1em;
}
.vertical {
  grid-template-columns: 1fr;
}
.justify-self-end {
  justify-self: end;
}
Hangzhi Yu's avatar
Hangzhi Yu committed
</style>