Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • AlekSIS/official/AlekSIS-App-Alsijil
  • sunweaver/AlekSIS-App-Alsijil
  • 8tincsoVluke/AlekSIS-App-Alsijil
  • perfreicpo/AlekSIS-App-Alsijil
  • noifobarep/AlekSIS-App-Alsijil
  • 7ingannisdo/AlekSIS-App-Alsijil
  • unmruntartpa/AlekSIS-App-Alsijil
  • balrorebta/AlekSIS-App-Alsijil
  • comliFdifwa/AlekSIS-App-Alsijil
  • 3ranaadza/AlekSIS-App-Alsijil
10 results
Show changes
Commits on Source (17)
......@@ -13,38 +13,58 @@
disable-pagination
hide-default-footer
use-deep-search
:enable-search="!combinedSelectedParticipations.length"
>
<template #additionalActions="{ attrs, on }">
<coursebook-filters :page-type="pageType" v-model="filters" />
<v-expand-transition>
<v-card
outlined
<div v-show="!combinedSelectedParticipations.length">
<coursebook-filters :page-type="pageType" v-model="filters" />
</div>
<v-expand-transition :duration="{ leave: 0 }">
<div
class="full-width"
v-show="
pageType === 'absences' && combinedSelectedParticipations.length
"
>
<v-card-text>
<v-row align="center">
<v-col cols="6">
{{
$tc(
"alsijil.coursebook.absences.action_for_selected",
combinedSelectedParticipations.length,
)
}}
</v-col>
<v-col cols="6">
<absence-reason-buttons
allow-empty
empty-value="present"
:custom-absence-reasons="absenceReasons"
@input="handleMultipleAction"
/>
</v-col>
</v-row>
</v-card-text>
</v-card>
<v-row class="mb-2" no-gutters>
<v-col
v-for="(
participationsByDocumentation, index
) in selectedParticipationLists"
:key="index"
>
<v-card outlined class="mr-1">
<v-card-text class="pa-1">
{{
getDocumentationShortInformation(
participationsByDocumentation[0].documentation,
)
}}:&nbsp;<strong>{{
getParticipationsNameList(participationsByDocumentation)
}}</strong>
</v-card-text>
</v-card>
</v-col>
</v-row>
<v-row class="my-0" align="center">
<v-col cols="12" md="6" class="py-0">
<h4>{{ $t("alsijil.coursebook.participation_status") }}</h4>
<absence-reason-buttons
class="mb-1"
allow-empty
empty-value="present"
:custom-absence-reasons="absenceReasons"
@input="handleMultipleAction('absenceReason', $event)"
/>
</v-col>
<v-col cols="12" md="6" class="py-0">
<h4>{{ $t("alsijil.extra_marks.title_plural") }}</h4>
<extra-mark-buttons
@input="handleMultipleAction('extraMark', $event)"
/>
</v-col>
</v-row>
</div>
</v-expand-transition>
</template>
......@@ -56,8 +76,8 @@
:subjects="subjects"
:documentation="item"
:affected-query="lastQuery"
:value="(selectedParticipations[item.id] ??= [])"
@input="selectParticipation(item.id, $event)"
:value="getParticipationIDList(item)"
@input="selectParticipation(item, $event)"
/>
</template>
......@@ -76,6 +96,8 @@
</template>
<script>
import { DateTime } from "luxon";
import InfiniteScrollingDateSortedCRUDIterator from "aleksis.core/components/generic/InfiniteScrollingDateSortedCRUDIterator.vue";
import { documentationsForCoursebook } from "./coursebook.graphql";
import AbsenceReasonButtons from "aleksis.apps.kolego/components/AbsenceReasonButtons.vue";
......@@ -89,6 +111,7 @@ import DocumentationLoader from "./documentation/DocumentationLoader.vue";
import sendToServerMixin from "./absences/sendToServerMixin";
import { absenceReasons } from "./absences/absenceReasons.graphql";
import { subjects } from "aleksis.apps.cursus/components/subject.graphql";
import ExtraMarkButtons from "../extra_marks/ExtraMarkButtons.vue";
export default {
name: "Coursebook",
......@@ -101,6 +124,7 @@ export default {
DocumentationAbsencesModal,
InfiniteScrollingDateSortedCRUDIterator,
AbsenceCreationDialog,
ExtraMarkButtons,
},
mixins: [sendToServerMixin],
props: {
......@@ -249,31 +273,64 @@ export default {
return "DocumentationAbsencesModal";
}
},
selectedParticipationLists() {
return Object.values(this.selectedParticipations).filter(
(parts) => parts.length,
);
},
combinedSelectedParticipations() {
return Object.values(this.selectedParticipations).flat();
return this.selectedParticipationLists.flat();
},
expandedQuery() {
if (!this.lastQuery) {
return false;
}
return {
...this.lastQuery.options,
variables: JSON.parse(this.lastQuery.previousVariablesJson),
};
},
},
methods: {
selectParticipation(id, value) {
selectParticipation(documentation, participations) {
this.selectedParticipations = Object.assign(
{},
this.selectedParticipations,
{ [id]: value },
{
[documentation.id]: participations.map((p) => ({
...p,
documentation: documentation,
})),
},
);
},
handleMultipleAction(absenceReasonId) {
handleMultipleAction(field, id) {
this.loadSelectedParticiptions = true;
this.sendToServer(
this.combinedSelectedParticipations,
"absenceReason",
absenceReasonId,
);
this.sendToServer(this.combinedSelectedParticipations, field, id);
this.$once("save", this.resetMultipleAction);
},
resetMultipleAction() {
this.loadSelectedParticiptions = false;
this.selectedParticipations = {};
},
getParticipationIDList(documentation) {
const participations = (this.selectedParticipations[documentation.id] ??=
[]);
return participations.map((p) => p.id);
},
toDateTime(dateString) {
return DateTime.fromISO(dateString);
},
getDocumentationShortInformation(documentation) {
return `${this.$d(this.toDateTime(documentation.datetimeStart), "longNumeric")}${this.$d(this.toDateTime(documentation.datetimeEnd), "shortTime")} · ${
documentation.course?.name ||
documentation.amends.title ||
documentation.amends.amends.title
}`;
},
getParticipationsNameList(participations) {
return participations.map((p) => p.person.firstName).join(", ");
},
},
mounted() {
this.$setToolBarTitle(
......
......@@ -18,7 +18,12 @@
@click:clear="selectObject"
class="max-width"
/>
<div class="mx-6">
<div
class="mx-6 switch-grid"
:class="{
vertical: $vuetify.breakpoint.mobile,
}"
>
<v-switch
:loading="selectLoading"
:label="$t('alsijil.coursebook.filter.own')"
......@@ -29,12 +34,13 @@
hide-details
/>
<v-switch
v-if="pageType === 'absences'"
:loading="selectLoading"
:label="$t('alsijil.coursebook.filter.missing')"
:input-value="value.incomplete"
:label="$t('alsijil.coursebook.filter.absences_exist')"
:input-value="value.absencesExist"
@change="
$emit('input', {
incomplete: $event,
absencesExist: $event,
})
"
dense
......@@ -42,13 +48,13 @@
hide-details
/>
<v-switch
v-if="pageType === 'absences'"
class="span-2"
:loading="selectLoading"
:label="$t('alsijil.coursebook.filter.absences_exist')"
:input-value="value.absencesExist"
:label="$t('alsijil.coursebook.filter.missing')"
:input-value="value.incomplete"
@change="
$emit('input', {
absencesExist: $event,
incomplete: $event,
})
"
dense
......@@ -160,3 +166,17 @@ export default {
},
};
</script>
<style scoped>
.switch-grid {
display: grid;
grid-template-rows: min-content min-content;
column-gap: 1em;
}
.span-2 {
grid-column-end: span 2;
}
.vertical > * {
grid-column-end: span 3;
}
</style>
<template>
<v-card :class="{ 'my-1 full-width': true, 'd-flex flex-column': !compact }">
<v-card-title v-if="!compact">
<lesson-information v-bind="documentationPartProps" />
<lesson-information
v-bind="{ ...$attrs, ...documentationPartProps }"
:is-create="false"
:gql-patch-mutation="documentationsMutation"
/>
</v-card-title>
<v-card-text
......@@ -11,13 +15,19 @@
'pa-2': compact,
}"
>
<lesson-information v-if="compact" v-bind="documentationPartProps" />
<lesson-notes class="span-2" v-bind="documentationPartProps" />
<lesson-information
v-if="compact"
v-bind="documentationPartProps"
:is-create="false"
:gql-patch-mutation="documentationsMutation"
/>
<lesson-notes v-bind="documentationPartProps" hide-absence-reasons />
<participation-list
v-if="documentation.canEditParticipationStatus"
:include-present="false"
class="participation-list"
:class="{
'participation-list': compact && !$vuetify.breakpoint.mobile,
}"
v-bind="documentationPartProps"
:value="value"
@input="$emit('input', $event)"
......@@ -94,18 +104,15 @@ export default {
<style scoped>
.main-body {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-template-rows: min-content min-content;
column-gap: 1em;
grid-template-columns: 1fr 2fr;
gap: 1em;
align-items: center;
}
.participation-list {
grid-column-start: 1;
grid-column-end: span 3;
}
.span-2 {
grid-column-end: span 2;
}
.vertical > * {
grid-column-end: span 3;
.vertical {
grid-template-columns: 1fr;
}
</style>
......@@ -3,44 +3,48 @@ import AbsenceReasonGroupSelect from "aleksis.apps.kolego/components/AbsenceReas
</script>
<template>
<v-list v-if="filteredParticipations.length">
<v-list dense v-if="filteredParticipations.length">
<v-divider />
<v-list-item-group :value="value" multiple @change="changeSelect">
<v-list-item-group
:value="selectedParticipations"
multiple
@change="changeSelect"
>
<template v-for="(participation, index) in filteredParticipations">
<v-list-item
:key="`documentation-${documentation.id}-participation-${participation.id}`"
:value="participation.id"
:value="participation"
v-bind="$attrs"
two-line
>
<template #default="{ active }">
<v-list-item-action>
<v-checkbox :input-value="active" />
<v-list-item-action class="my-0">
<v-checkbox class="ma-0" :input-value="active" />
</v-list-item-action>
<v-list-item-content>
<v-list-item-title>
{{ participation.person.fullName }}
</v-list-item-title>
<absence-reason-group-select
v-if="participation.absenceReason && !compact"
class="full-width"
allow-empty
:load-selected-chip="loading"
:custom-absence-reasons="absenceReasons"
:value="participation.absenceReason?.id || 'present'"
@input="sendToServer([participation], 'absenceReason', $event)"
/>
<v-list-item-content class="pa-0">
<v-row align="center" justify="space-between" no-gutters>
<v-col>
<v-list-item-title
:class="{ 'mt-2': $vuetify.breakpoint.mobile }"
>
{{ participation.person.fullName }}
</v-list-item-title>
</v-col>
<v-col cols="12" md="auto">
<absence-reason-group-select
v-if="participation.absenceReason"
class="flex-grow-1 ml-auto"
allow-empty
:load-selected-chip="loading"
:custom-absence-reasons="absenceReasons"
:value="participation.absenceReason?.id || 'present'"
@input="
sendToServer([participation], 'absenceReason', $event)
"
/>
</v-col>
</v-row>
</v-list-item-content>
<v-list-item-action v-if="participation.absenceReason && compact">
<absence-reason-group-select
allow-empty
:load-selected-chip="loading"
:custom-absence-reasons="absenceReasons"
:value="participation.absenceReason?.id || 'present'"
@input="sendToServer([participation], 'absenceReason', $event)"
/>
</v-list-item-action>
</template>
</v-list-item>
<v-divider
......@@ -86,6 +90,11 @@ export default {
return this.documentation.participations;
}
},
selectedParticipations() {
return this.filteredParticipations.filter((p) =>
this.value.includes(p.id),
);
},
},
methods: {
changeSelect(value) {
......
......@@ -39,15 +39,24 @@ export default {
})),
},
(storedDocumentations, incomingStatuses) => {
// TODO: what should happen here in places where there is more than one documentation?
const documentation = storedDocumentations.find(
(doc) => doc.id === this.documentation.id,
);
let documentation = undefined;
if (this.documentation) {
documentation = storedDocumentations.find(
(doc) => doc.id === this.documentation.id,
);
}
incomingStatuses.forEach((newStatus) => {
const participationStatus = documentation.participations.find(
(part) => part.id === newStatus.id,
);
let participationStatus = undefined;
if (documentation) {
participationStatus = documentation.participations.find(
(part) => part.id === newStatus.id,
);
} else {
participationStatus = storedDocumentations
.find((doc) => doc.id === newStatus.relatedDocumentation.id)
.participations.find((part) => part.id === newStatus.id);
}
participationStatus.absenceReason = newStatus.absenceReason;
participationStatus.tardiness = newStatus.tardiness;
participationStatus.isOptimistic = newStatus.isOptimistic;
......@@ -67,34 +76,51 @@ export default {
this.afterSendToServer(participations, field, value);
},
addExtraMarks(participations, extraMarkId) {
// Get all participation statuses without this extra mark and get the respective person ids
const participants = participations
.filter(
(participation) =>
!participation.notesWithExtraMark.some(
(note) => note.extraMark.id === extraMarkId,
),
)
.map((participation) => participation.person.id);
// Get all participation statuses without this extra mark
const filteredParticipations = participations.filter(
(participation) =>
!participation.notesWithExtraMark.some(
(note) => note.extraMark.id === extraMarkId,
),
);
// CREATE new personal note
this.mutate(
createPersonalNotes,
{
input: participants.map((person) => ({
documentation: this.documentation.id,
person: person,
input: filteredParticipations.map((participation) => ({
documentation: this.documentation
? this.documentation.id
: participation.documentation.id,
person: participation.person.id,
extraMark: extraMarkId,
})),
},
(storedDocumentations, incomingPersonalNotes) => {
const documentation = storedDocumentations.find(
(doc) => doc.id === this.documentation.id,
);
incomingPersonalNotes.forEach((note, index) => {
const participationStatus = documentation.participations.find(
(part) => part.person.id === participants[index],
let documentation = undefined;
if (this.documentation) {
documentation = storedDocumentations.find(
(doc) => doc.id === this.documentation.id,
);
}
incomingPersonalNotes.forEach((note, index) => {
let participationStatus = undefined;
if (documentation) {
participationStatus = documentation.participations.find(
(part) =>
part.person.id === filteredParticipations[index].person.id,
);
} else {
participationStatus = storedDocumentations
.find(
(doc) =>
doc.id === filteredParticipations[index].documentation.id,
)
.participations.find(
(part) =>
part.person.id === filteredParticipations[index].person.id,
);
}
participationStatus.notesWithExtraMark.push(note);
});
......
......@@ -42,43 +42,55 @@ import TextNoteCard from "../personal_notes/TextNoteCard.vue";
{{ $t("alsijil.coursebook.participations.present") }}
</v-chip>
<template v-if="documentation.canViewParticipationStatus">
<absence-reason-chip
v-for="[reasonId, participations] in Object.entries(absences)"
:key="'reason-' + reasonId"
:absence-reason="participations[0].absenceReason"
dense
>
<template #append>
<span
>:
<span>
{{
participations
.slice(0, 5)
.map((participation) => participation.person.firstName)
.join(", ")
}}
</span>
<span v-if="participations.length > 5">
<!-- eslint-disable @intlify/vue-i18n/no-raw-text -->
+{{ participations.length - 5 }}
<!-- eslint-enable @intlify/vue-i18n/no-raw-text -->
<template v-if="!hideAbsenceReasons">
<template v-if="documentation.canViewParticipationStatus">
<absence-reason-chip
v-for="[reasonId, participations] in Object.entries(absences)"
:key="'reason-' + reasonId"
:absence-reason="participations[0].absenceReason"
dense
>
<template #append>
<span
>:
<template v-if="!$vuetify.breakpoint.mobile">
<span>
{{
participations
.slice(0, 5)
.map((participation) => participation.person.firstName)
.join(", ")
}}
</span>
<span v-if="participations.length > 5">
<!-- eslint-disable @intlify/vue-i18n/no-raw-text -->
+{{ participations.length - 5 }}
<!-- eslint-enable @intlify/vue-i18n/no-raw-text -->
</span>
</template>
<span v-else>
{{
$tc(
"alsijil.coursebook.notes.persons",
participations.length,
)
}}
</span>
</span>
</span>
</template>
</absence-reason-chip>
</template>
<template v-else>
<absence-reason-chip
v-for="[reasonId, participations] in Object.entries(absences)"
:key="'reason-' + reasonId"
:absence-reason="participations[0].absenceReason"
dense
@click="$emit('open')"
v-bind="dialogActivator.attrs"
v-on="dialogActivator.on"
/>
</template>
</absence-reason-chip>
</template>
<template v-else>
<absence-reason-chip
v-for="[reasonId, participations] in Object.entries(absences)"
:key="'reason-' + reasonId"
:absence-reason="participations[0].absenceReason"
dense
@click="$emit('open')"
v-bind="dialogActivator.attrs"
v-on="dialogActivator.on"
/>
</template>
</template>
<template v-if="documentation.canViewParticipationStatus">
......@@ -93,19 +105,26 @@ import TextNoteCard from "../personal_notes/TextNoteCard.vue";
<template #append>
<span
>:
<span>
<template v-if="!$vuetify.breakpoint.mobile">
<span>
{{
participations
.slice(0, 5)
.map((participation) => participation.person.firstName)
.join(", ")
}}
</span>
<span v-if="participations.length > 5">
<!-- eslint-disable @intlify/vue-i18n/no-raw-text -->
+{{ participations.length - 5 }}
<!-- eslint-enable @intlify/vue-i18n/no-raw-text -->
</span>
</template>
<span v-else>
{{
participations
.slice(0, 5)
.map((participation) => participation.person.firstName)
.join(", ")
$tc("alsijil.coursebook.notes.persons", participations.length)
}}
</span>
<span v-if="participations.length > 5">
<!-- eslint-disable @intlify/vue-i18n/no-raw-text -->
+{{ participations.length - 5 }}
<!-- eslint-enable @intlify/vue-i18n/no-raw-text -->
</span>
</span>
</template>
</extra-mark-chip>
......@@ -133,17 +152,28 @@ import TextNoteCard from "../personal_notes/TextNoteCard.vue";
<template #append>
<span
>:
{{
tardyParticipations
.slice(0, 5)
.map((participation) => participation.person.firstName)
.join(", ")
}}
<span v-if="tardyParticipations.length > 5">
<!-- eslint-disable @intlify/vue-i18n/no-raw-text -->
+{{ tardyParticipations.length - 5 }}
<!-- eslint-enable @intlify/vue-i18n/no-raw-text -->
<template v-if="!$vuetify.breakpoint.mobile">
<span>
{{
tardyParticipations
.slice(0, 5)
.map((participation) => participation.person.firstName)
.join(", ")
}}
</span>
<span v-if="tardyParticipations.length > 5">
<!-- eslint-disable @intlify/vue-i18n/no-raw-text -->
+{{ tardyParticipations.length - 5 }}
<!-- eslint-enable @intlify/vue-i18n/no-raw-text -->
</span>
</template>
<span v-else>
{{
$tc(
"alsijil.coursebook.notes.persons",
tardyParticipations.length,
)
}}
</span>
</span>
</template>
......@@ -244,6 +274,13 @@ export default {
name: "LessonNotes",
components: { ManageStudentsTrigger },
mixins: [documentationPartMixin],
props: {
hideAbsenceReasons: {
type: Boolean,
required: false,
default: false,
},
},
computed: {
total() {
return this.documentation.participations.length;
......
......@@ -12,7 +12,6 @@
},
"coursebook": {
"absences": {
"action_for_selected": "Ausgewählten Teilnehmer markieren als: | {count} ausgewählte Teilnehmer markieren als",
"button": "Abwesenheiten erfassen",
"lessons": "Keine Stunden | 1 Stunde | {count} Stunden",
"success": "Die Abwesenheiten wurden erfolgreich erfasst.",
......
......@@ -78,7 +78,8 @@
},
"notes": {
"show_list": "List of participants",
"future": "Lesson is in the future"
"future": "Lesson is in the future",
"persons": "One person | {count} persons"
},
"notices": {
"future": "Editing this lesson isn't allowed as this lesson is in the future.",
......@@ -108,7 +109,6 @@
"present": "Present"
},
"absences": {
"action_for_selected": "Mark selected participant as: | Mark {count} selected participants as",
"title": "Register absences",
"button": "Register absences",
"summary": "Summary",
......