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 (128)
Showing
with 1618 additions and 3 deletions
......@@ -38,6 +38,7 @@ Licence
Copyright © 2020, 2021 Julian Leucker <leuckeju@katharineum.de>
Copyright © 2020, 2022 Hangzhi Yu <yuha@katharineum.de>
Copyright © 2021 Lloyd Meins <meinsll@katharineum.de>
Copyright © 2022 magicfelix <felix@felix-zauberer.de>
Licenced under the EUPL, version 1.2 or later, by Teckids e.V. (Bonn, Germany).
......
......@@ -17,4 +17,5 @@ class AlsijilConfig(AppConfig):
([2020, 2021], "Julian Leucker", "leuckeju@katharineum.de"),
([2020, 2022], "Hangzhi Yu", "yuha@katharineum.de"),
([2021], "Lloyd Meins", "meinsll@katharineum.de"),
([2022], "magicfelix", "felix@felix-zauberer.de"),
)
......@@ -47,7 +47,7 @@ class LessonDocumentationForm(forms.ModelForm):
self.fields["homework"].label = _("Homework for the next lesson")
if (
self.instance.lesson_period
and get_site_preferences()["alsijil__allow_carry_over_same_week"]
and get_site_preferences()["alsijil__save_lesson_documentations_by_week"]
):
self.fields["carry_over_week"] = forms.BooleanField(
label=_("Carry over data to all other lessons with the same subject in this week"),
......@@ -58,7 +58,7 @@ class LessonDocumentationForm(forms.ModelForm):
def save(self, **kwargs):
lesson_documentation = super(LessonDocumentationForm, self).save(commit=True)
if (
get_site_preferences()["alsijil__allow_carry_over_same_week"]
get_site_preferences()["alsijil__save_lesson_documentations_by_week"]
and self.cleaned_data["carry_over_week"]
and (
lesson_documentation.topic
......@@ -68,7 +68,7 @@ class LessonDocumentationForm(forms.ModelForm):
and lesson_documentation.lesson_period
):
lesson_documentation.carry_over_data(
LessonPeriod.objects.filter(lesson=lesson_documentation.lesson_period.lesson)
LessonPeriod.objects.filter(lesson=lesson_documentation.lesson_period.lesson), True
)
......
export const
ERROR = "ERROR", // Something went wrong
SAVED = "SAVED", // Everything alright
UPDATING = "UPDATING", // We are sending something to the server
CHANGES = "CHANGES" // the user changed something, but it has not been saved yet
<template>
<div>
<div class="text-h2">
{{ title(courseBookLesson) }}
</div>
<div v-if="courseBookLesson && alsijilSitePreferences">
<div class="d-flex justify-space-between">
<v-btn text color="primary" :to="{ name: 'alsijil.courseBookList' }">
<v-icon left>mdi-chevron-left</v-icon>
{{ $t("alsijil.back") }}
</v-btn>
<update-indicator @manual-update="updateManually()" ref="indicator" :status="status"></update-indicator>
</div>
<v-row>
<v-col cols="12">
<lesson-documentations
:lesson-documentations="courseBookLessonDocumentations"
:planned-lesson-periods-date-times="courseBookLesson.plannedLessonperiodsDatetimes"
:groups="courseBookLesson.groups"
:excuse-types="excuseTypes"
:extra-marks="extraMarks"
:save-lesson-documentations-per-week="alsijilSitePreferences.saveLessonDocumentationsByWeek"
/>
</v-col>
</v-row>
</div>
</div>
</template>
<script>
import {CHANGES, SAVED, UPDATING} from "../../UpdateStatuses.js";
import UpdateIndicator from "./UpdateIndicator.vue";
import LessonDocumentations from "./LessonDocumentations.vue";
import gqlCourseBookLesson from "./courseBookLesson.graphql";
import gqlCourseBookLessonDocumentations from "./courseBookLesson.graphql";
import gqlExcuseTypes from "./excuseTypes.graphql";
import gqlExtraMarks from "./extraMarks.graphql";
import gqlAlsijilSitePreferences from "./alsijilSitePreferences.graphql";
export default {
components: {
UpdateIndicator,
LessonDocumentations,
},
props: {
lessonId: String,
},
apollo: {
courseBookLesson: {
query: gqlCourseBookLesson,
variables() {
return {
lessonId: this.lessonId,
};
},
update: (data) => data.lesson,
},
courseBookLessonDocumentations: {
query: gqlCourseBookLessonDocumentations,
variables() {
return {
lessonId: this.lessonId,
};
},
update: (data) => data.lessonDocumentationsByLessonId,
},
excuseTypes: gqlExcuseTypes,
extraMarks: gqlExtraMarks,
alsijilSitePreferences: gqlAlsijilSitePreferences,
},
methods: {
processDataChange(event) {
this.status = CHANGES;
// alert("Probably save the data");
console.log(event);
setTimeout(() => {
this.status = UPDATING;
}, 500)
setTimeout(() => {
this.status = SAVED;
}, 1000)
},
updateManually(event) {
alert("Data sync triggered manually");
this.status = UPDATING;
setTimeout(() => {
this.status = SAVED;
}, 500)
},
title(lesson) {
return this.$t('alsijil.coursebook.menu_title') + (lesson && lesson.groups && lesson.subject ? (" " + lesson.groups.map(g => g.shortName).join() + " · " + lesson.subject.name) : "");
},
},
watch: {
courseBookLesson: {
handler(newLesson) {
this.$root.$setPageTitle(this.title(newLesson));
},
deep: true,
}
},
name: "course-book",
data: () => {
return{
ping: "ping",
status: SAVED,
alsijilSitePreferences: null,
courseBookLesson: null,
courseBookLessonDocumentations: null,
excuseTypes: null,
extraMarks: null,
}
}
}
</script>
<template>
<v-row>
<v-col cols="12" xl="12" class="d-flex">
<div class="text-h2">
{{ $t("alsijil.coursebook.title") }}
</div>
</v-col>
<v-col
v-if="$apollo.queries.coursebookLessons.loading"
cols="12"
xs="12"
sm="6"
md="6"
lg="4"
xl="3"
class="d-flex"
>
<v-card
class="flex-grow-1"
>
<v-skeleton-loader
type="article, actions"
></v-skeleton-loader>
</v-card>
</v-col>
<v-col v-else-if="coursebookLessons" v-for="lesson in coursebookLessons" xs="12" sm="6" md="6" lg="4" xl="3" class="d-flex">
<v-card class="flex-grow-1">
<v-card-title>
{{ lesson.groups.map(g => g.shortName).join() + " · " + lesson.subject.name }}
</v-card-title>
<v-card-subtitle>
{{ lesson.validity.dateStart + " - " + lesson.validity.dateEnd }}
</v-card-subtitle>
<v-card-text>
{{ lesson.teachers.map(t => t.fullName).join() }}
</v-card-text>
<v-card-actions>
<v-btn :to="{ name: 'alsijil.courseBookByID', params: { lessonId: lesson.id } }" text color="secondary">
<v-icon left>mdi-book-search-outline</v-icon>
{{ $t("alsijil.coursebook.open_coursebook") }}
</v-btn>
</v-card-actions>
</v-card>
</v-col>
<v-container
class="text-center fill-height"
style="height: calc(100vh - 58px);"
v-else-if="!coursebookLessons"
>
<v-row align="center">
<v-col>
<h1 class="text-h3 primary--text">
<v-icon color="error" x-large>mdi-book-off-outline</v-icon>
{{ $t("alsijil.coursebook.no_coursebook") }}
</h1>
<p> {{ $t("alsijil.coursebook.no_courses_as_teacher") }} </p>
</v-col>
</v-row>
</v-container>
</v-row>
</template>
<script>
import gqlCourseBookLessons from "./courseBookLessons.graphql";
export default {
name: "CourseBookList",
data() {
return {
coursebookLessons: null,
}
},
apollo: {
coursebookLessons: gqlCourseBookLessons,
},
}
</script>
<style scoped>
</style>
\ No newline at end of file
<template>
<ApolloMutation
:mutation="require('./lessonDocumentation.graphql')"
:variables=lessonDocumentationEdit
@done="onDone"
>
<template v-slot="{ mutate, loading, error }">
<v-card elevation="2" :loading="loading">
<v-form v-model="valid">
<v-card-title v-if="saveLessonDocumentationsPerWeek === 'True'">
<span
v-text="getWeekText(lessonDocumentationEdit)"
class="ma-1 text-h5">
</span>
</v-card-title>
<v-card-title v-else>
<v-hover v-slot="{ hover }">
<div>
<v-menu
v-model="showPicker"
:close-on-content-click="false"
transition="scale-transition"
offset-y
min-width="auto"
>
<template v-slot:activator="{ on, attrs }">
<span>
<span v-text="$d(new Date(lessonDocumentationEdit.date), 'short')"
class="ma-1 text-h5"></span>
<v-btn right v-bind="attrs" v-on="on" icon v-if="hover && dateAndPeriodEditable">
<v-icon>mdi-pencil-outline</v-icon>
</v-btn>
</span>
</template>
<v-date-picker
scrollable
no-title
@input="showPicker = false; $emit('change-date', $event)"
v-model="lessonDocumentationEdit.date"
></v-date-picker>
</v-menu>
</div>
</v-hover>
<v-hover v-slot="{ hover }" v-if="!(saveLessonDocumentationsPerWeek === 'True')">
<div>
<v-menu offset-y>
<template v-slot:activator="{ on, attrs }">
<span>
<span
v-text="$t('alsijil.period_number', {number: lessonDocumentationEdit.period})"
class="ma-1 text-h5"></span>
<v-btn
right
v-bind="attrs"
v-on="on"
icon
v-if="hover && dateAndPeriodEditable"
>
<v-icon>mdi-pencil-outline</v-icon>
</v-btn>
</span>
</template>
<v-list>
<!-- Fixme: load valid lessons -->
<v-list-item
v-for="(item, index) in [1, 2, 3, 4, 5, 6, 7, 8, 9]"
:key="index"
>
<v-list-item-title>{{ item }}</v-list-item-title>
</v-list-item>
</v-list>
</v-menu>
</div>
</v-hover>
</v-card-title>
<v-card-text>
<v-row>
<v-col cols="12" md="12" lg="12">
<message-box type="error" v-if="error">{{ $t("alsijil.error_updating") }}</message-box>
<v-textarea
name="input-7-1"
:label="$t('alsijil.lesson_documentation.topic')"
rows="1"
auto-grow
required
v-model="lessonDocumentationEdit.topic"
></v-textarea>
<v-textarea
name="input-7-1"
:label="$t('alsijil.lesson_documentation.homework')"
rows="1"
auto-grow
v-model="lessonDocumentationEdit.homework"
></v-textarea>
<v-textarea
name="input-7-1"
:label="$t('alsijil.lesson_documentation.group_note')"
rows="1"
auto-grow
v-model="lessonDocumentationEdit.groupNote"
></v-textarea>
</v-col>
<v-col v-if="!(saveLessonDocumentationsPerWeek === 'True')" cols="12" md="4" lg="4">
Personal notes
<personal-notes
:lesson-documentation-id="lessonDocumentationEdit.id"
:groups="groups"
:excuse-types="excuseTypes"
:extra-marks="extraMarks"
v-model="lessonDocumentationEdit.personalNotes"
@change="$emit('change-personal-notes', $event)"
></personal-notes>
</v-col>
</v-row>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn
color="error"
outlined
@click="$emit('cancel-lesson-documentation-dialog', $event)"
>
{{ $t('alsijil.cancel') }}
</v-btn>
<v-btn
color="success"
@click="mutate()"
>
{{ $t('alsijil.save') }}
</v-btn>
</v-card-actions>
</v-form>
</v-card>
</template>
</ApolloMutation>
</template>
<script>
import PersonalNotes from "./PersonalNotes.vue";
export default {
components: {PersonalNotes},
props: ["lessonDocumentationEdit", "groups", "excuseTypes", "extraMarks", "saveLessonDocumentationsPerWeek", "getWeekText"],
name: "lesson-documentation",
data() {
return {
dateAndPeriodEditable: false,
showPicker: false,
//lessonDocumentationEdit: {},
}
},
//created() {
//this.lessonDocumentationEdit = this.lessonDocumentation
//}
}
</script>
<template><div>
<v-dialog
v-model="dialog"
max-width="800"
>
<template v-slot:activator="{ on, attrs }">
<v-row>
<v-col cols="12" md="6" class="pb-0 pb-md-3">
<v-select
v-if="saveLessonDocumentationsPerWeek === 'True'"
:items="emptyLessonPeriods"
:label="$t('alsijil.coursebook.choose_week')"
:item-text="getWeekText"
v-model="selectedLessonPeriodDatetime"
return-object
></v-select>
<v-select
v-else
:items="emptyLessonPeriods"
:label="$t('alsijil.coursebook.choose_lesson_date')"
:item-text="getLessonText"
v-model="selectedLessonPeriodDatetime"
return-object
></v-select>
</v-col>
<v-col cols="12" md="6" class="pt-0 pt-md-3">
<v-btn
color="primary"
dark
v-bind="attrs"
v-on="on"
@click="createLessonDocumentation()"
>
{{ $t("alsijil.coursebook.create_documentation") }}
</v-btn>
</v-col>
</v-row>
</template>
<lesson-documentation
:lesson-documentation-edit="lessonDocumentationEdit"
:groups="groups"
:excuse-types="excuseTypes"
:extra-marks="extraMarks"
:save-lesson-documentations-per-week="saveLessonDocumentationsPerWeek"
:get-week-text="getWeekText"
@cancel-lesson-documentation-dialog="cancelDialog"
/>
</v-dialog>
<v-data-table
:headers="headers"
:items="computedLessonDocumentations"
@click:row="editLessonDocumentation"
class="elevation-1 my-3"
:expanded.sync="expanded"
show-expand
multi-sort
:sort-by="['year','week']"
:sort-desc="[true, true]"
>
<template v-slot:item.period="{ item }">
<span class="text-no-wrap">{{ (saveLessonDocumentationsPerWeek === "True") ? getWeekText(item) : getLessonText(item) }}</span>
</template>
<template v-slot:expanded-item="{ headers, item }">
<td :colspan="headers.length">
<template v-if="saveLessonDocumentationsPerWeek === 'True'" v-for="lessonDocumentation in item.documentations">
<v-list-item>
<v-list-item-content>
<v-list-item-title>{{ getLessonText(lessonDocumentation) }}</v-list-item-title>
<v-list-item-action>
<personal-notes
:lesson-documentation-id="lessonDocumentation.id"
:groups="groups"
:excuse-types="excuseTypes"
:extra-marks="extraMarks"
v-model="lessonDocumentation.personalNotes"
@change="$emit('change-personal-notes', $event)"
></personal-notes>
</v-list-item-action>
</v-list-item-content>
</v-list-item>
<v-divider></v-divider>
</template>
<template v-else v-for="personalNote in item.personalNotes">
<!-- FIXME: Add edit and delete functionality to personal note chips-->
<v-chip class="ma-1" v-if="personalNoteString(personalNote)">
{{ personalNote.person.fullName }}: {{ personalNoteString(personalNote) }}
</v-chip>
</template>
</td>
</template>
</v-data-table>
</div></template>
<script>
import LessonDocumentation from "./LessonDocumentation.vue";
import PersonalNotes from "./PersonalNotes.vue";
export default {
components: {LessonDocumentation, PersonalNotes},
props: [ "lessonDocumentations", "plannedLessonPeriodsDateTimes", "groups", "excuseTypes", "extraMarks", "saveLessonDocumentationsPerWeek" ],
name: "lesson-documentations",
data () {
return {
dialog: false,
expanded: [],
headers: [
{ text: this.$t("alsijil.period"), value: "period" },
{ text: this.$t("alsijil.lesson_documentation.topic"), value: "topic" },
{ text: this.$t("alsijil.lesson_documentation.homework"), value: "homework" },
{ text: this.$t("alsijil.lesson_documentation.group_note"), value: "groupNote" },
{ text: this.$t("alsijil.personal_note.title_plural"), value: "data-table-expand" }
],
lessonDocumentationEdit: {},
selectedLessonPeriodDatetime: {},
recordedWeeks: [],
}
},
computed: {
emptyLessonPeriods() {
if (this.saveLessonDocumentationsPerWeek === "True") {
let currentDatetime = new Date()
let weeks = {}
let lpdts = this.plannedLessonPeriodsDateTimes.filter(lp => new Date(lp.datetimeStart) > currentDatetime)
for (let ldIndex in lpdts) {
let ld = lpdts[ldIndex]
if (ld.week in weeks) {
weeks[ld.week]["planned"].push(ld)
} else {
weeks[ld.week] = {
"year": ld.year,
"week": ld.week,
"startDate": this.calculateStartDateOfCW(ld.year, ld.week),
"datetimeStart": ld.datetimeStart,
"lessonPeriod": ld.lessonPeriod,
"planned": [ld]
}
}
}
return Object.values(weeks) // FIXME sort by date
} else {
let currentDatetime = new Date()
return this.plannedLessonPeriodsDateTimes.filter(lp => new Date(lp.datetimeStart) > currentDatetime)
}
},
computedLessonDocumentations() {
if (this.saveLessonDocumentationsPerWeek === "True") {
let weeks = {}
for (let ldIndex in this.lessonDocumentations) {
let ld = this.lessonDocumentations[ldIndex]
if (ld.week in weeks) {
weeks[ld.week]["documentations"].push(ld)
} else {
weeks[ld.week] = {
"id": ld.id,
"startDate": this.calculateStartDateOfCW(ld.year, ld.week),
"year": ld.year,
"week": ld.week,
"topic": ld.topic,
"homework": ld.homework,
"groupNote": ld.groupNote,
"documentations": [ld]
}
}
}
return Object.values(weeks)
} else {
return this.lessonDocumentations
}
}
},
methods: {
cancelDialog() {
this.dialog = false;
this.lessonDocumentationEdit = {};
},
recordDocumentation(item) {
if (this.recordedWeeks.includes(item.week)) {
return false
}
this.recordedWeeks.push(item.week)
return true
},
async loadLessonDocumentation(item) {
const result = await this.$apollo.mutate({
mutation: require("./LessonDocumentation.graphql"),
variables: {
year: item.year,
week: item.week,
lessonPeriodId: item.lessonPeriod ? item.lessonPeriod.id : null,
eventId: item.event ? item.event.id : null,
extraLessonId: item.extraLesson ? item.extraLesson.id : null,
},
})
let lessonDocumentation = result.data.updateOrCreateLessonDocumentation.lessonDocumentation
this.lessonDocumentationEdit = {
id: lessonDocumentation.id,
year: item.year,
week: item.week,
date: lessonDocumentation.date,
period: item.period,
lessonPeriodId: item.lessonPeriod ? item.lessonPeriod.id : null,
eventId: item.event ? item.event.id : null,
extraLessonId: item.extraLesson ? item.extraLesson.id : null,
topic: lessonDocumentation.topic,
homework: lessonDocumentation.homework,
groupNote: lessonDocumentation.groupNote,
personalNotes: lessonDocumentation.personalNotes,
}
},
editLessonDocumentation(item) {
if (this.saveLessonDocumentationsPerWeek === "True") {
this.loadLessonDocumentation(item.documentations[0])
} else {
this.loadLessonDocumentation(item)
}
this.dialog = true
},
createLessonDocumentation() { // FIXME: Update cache to show newly created LessonDocumentation in table
let lessonDocumentation = this.selectedLessonPeriodDatetime
lessonDocumentation["event"] = null
lessonDocumentation["extraLesson"] = null
this.loadLessonDocumentation(lessonDocumentation)
this.dialog = true
},
calculateStartDateOfCW(year, week){
let ld_date = new Date(Date.UTC(year, 0, 1 + (week - 1) * 7));
let dow = ld_date.getDay();
let start_date = ld_date;
if (dow <= 4)
return start_date.setDate(ld_date.getDate() - ld_date.getDay() + 1)
else
return start_date.setDate(ld_date.getDate() + 8 - ld_date.getDay())
},
getLessonText(item) {
let date_obj = new Date(item.hasOwnProperty("datetimeStart") ? item.datetimeStart : item.date)
let period = item.lessonPeriod ? ", " + this.$t('alsijil.period_number', {number: item.lessonPeriod.period.period}) : "" // FIXME: Cases without lessonPeriod
return this.$d(date_obj, "short") + period
},
getWeekText(item) {
if (item.hasOwnProperty("startDate")) {
var start_date = new Date(item.startDate)
} else {
let lesson_date = new Date(item.date)
var start_date = new Date(((lesson_date.getDay() || 7) !== 1) ? lesson_date.setHours(-24 * (lesson_date.getDay() - 1)) : lesson_date)
}
let end_date = new Date(start_date)
end_date.setDate(end_date.getDate() + 6)
return start_date.toLocaleDateString(this.$root.languageCode) + " - " + end_date.toLocaleDateString(this.$root.languageCode) + ", " + this.$root.django.gettext('CW') + " " + item.week
},
personalNoteString(personalNote) {
let personalNoteString = "";
if (personalNote.tardiness > 0) {
personalNoteString += personalNote.tardiness + " min. ";
}
if (personalNote.absent) {
personalNoteString += this.$t("absent") + ", ";
}
if (personalNote.excused) {
personalNoteString += this.$t("excused") + ", ";
}
if (personalNote.excuseType) {
personalNoteString += personalNote.excuseType.name;
}
if (personalNote.extraMarks.length > 0) {
personalNoteString += " (";
personalNote.extraMarks.forEach(item => {
personalNoteString += item.name + ", ";
});
personalNoteString = personalNoteString.substring(0, personalNoteString.length - 2);
personalNoteString += ") ";
}
if (personalNote.remarks) {
personalNoteString += "\"" + personalNote.remarks + "\" ";
}
return personalNoteString;
},
}
}
</script>
<template>
<v-dialog
v-model="dialog"
max-width="600px"
@click:outside="cancelDialog"
>
<template v-slot:activator="{ on, attrs }">
<div>
<template v-for="personalNote in personalNotes">
<v-chip class="ma-1" close @click="editPersonalNote(personalNote.person.id)"
@click:close="removePersonalNote(personalNote.person.id)" v-if="personalNoteString(personalNote)">
{{ personalNote.person.fullName }}: {{ personalNoteString(personalNote) }}
</v-chip>
</template>
</div>
<v-tooltip bottom>
<template v-slot:activator="{ on, attrs }">
<v-btn
class="ma-1"
color="primary"
icon
outlined
v-bind="attrs"
v-on="on"
@click="createPersonalNote"
>
<v-icon>
mdi-plus
</v-icon>
</v-btn>
</template>
<span v-text="$t('alsijil.coursebook.add_personal_note')"></span>
</v-tooltip>
</template>
<v-card>
<v-card-title>
<span class="text-h5">Personal Note</span>
</v-card-title>
<v-card-text>
<v-container>
<v-select
item-text="fullName"
item-value="id"
:items="persons"
:label="$t('alsijil.personal_note.person')"
v-model="editedPersonID"
@input="updatePersonalNote"
></v-select>
<v-text-field
:label="$t('alsijil.personal_note.tardiness')"
suffix="min" type="number"
min="0"
:disabled="editedPersonID === ID_NO_PERSON"
v-model="editedTardiness"
></v-text-field>
<v-checkbox
:label="$t('alsijil.personal_note.absent')"
v-model="editedAbsent"
:disabled="editedPersonID === ID_NO_PERSON"
@change="editedExcused = false; editedExcuseType = null"
></v-checkbox>
<v-checkbox
:label="$t('alsijil.personal_note.excused')"
v-model="editedExcused"
:disabled="editedPersonID === ID_NO_PERSON || !editedAbsent"
@change="editedExcuseType = null"
></v-checkbox>
<v-select
:label="$t('alsijil.personal_note.excuse_type')"
v-model="editedExcuseType"
:items="excuseTypes"
item-text="name"
return-object
:disabled="editedPersonID === ID_NO_PERSON || !editedAbsent || !editedExcused"
></v-select>
<v-select
:label="$t('alsijil.personal_note.extra_marks')"
v-model="editedExtraMarks"
:items="extraMarks"
item-text="name"
return-object
:disabled="editedPersonID === ID_NO_PERSON"
multiple
chips
></v-select>
<v-text-field
:label="$t('alsijil.personal_note.remarks')"
v-model="editedRemarks"
:disabled="editedPersonID === ID_NO_PERSON"
></v-text-field>
</v-container>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn
color="error"
outlined
@click="cancelDialog"
>
{{ $t("alsijil.cancel") }}
</v-btn>
<v-btn
color="success"
@click="saveDialog"
:disabled="editedPersonID === ID_NO_PERSON"
>
{{ $t("alsijil.save") }}
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</template>
<script>
import gql from 'graphql-tag';
const ID_NO_PERSON = null;
export default {
model: {
prop: "personalNotes",
event: "change",
},
created() {
this.ID_NO_PERSON = ID_NO_PERSON;
},
methods: {
removePersonalNote(personID) {
if (personID === ID_NO_PERSON) {
return
}
console.log("removing personal note of person", personID);
this.editedPersonID = personID;
this.editedTardiness = 0;
this.editedAbsent = false;
this.editedExcused = false;
this.editedExcuseType = null;
this.editedExtraMarks = [];
this.editedRemarks = "";
this.savePersonalNote();
},
editPersonalNote(personID) {
console.log("editing personal note of person", personID);
this.editedPersonID = personID;
this.updatePersonalNote();
this.dialog = true;
},
updatePersonalNote() {
let personalNote = this.personalNoteByStudentID(this.editedPersonID);
this.editedTardiness = personalNote.tardiness || 0;
this.editedAbsent = personalNote.absent || false;
this.editedExcused = personalNote.excused || false;
this.editedExcuseType = personalNote.excuseType || null;
this.editedExtraMarks = personalNote.extraMarks || [];
this.editedRemarks = personalNote.remarks || "";
this.newPersonalNote = !!(personalNote && Object.keys(personalNote).length === 0 && Object.getPrototypeOf(personalNote) === Object.prototype);
},
createPersonalNote() {
this.editedPersonID = ID_NO_PERSON;
this.editedTardiness = 0;
this.editedAbsent = false;
this.editedExcused = false;
this.editedExcuseType = null;
this.editedExtraMarks = [];
this.editedRemarks = "";
this.newPersonalNote = true;
this.dialog = true;
},
personalNoteByStudentID(studentID) {
if (this.editedPersonID === ID_NO_PERSON) {
return {};
}
return this.personalNotes.filter(item => item.person.id === studentID)[0] || {};
},
savePersonalNote() {
if (this.editedPersonID === ID_NO_PERSON) {
return
}
let editedExcuseTypeID = (this.editedExcuseType) ? this.editedExcuseType.id : null;
let editedExtraMarksIDs = [];
this.editedExtraMarks.forEach(item => {editedExtraMarksIDs.push(item.id);});
// We save the user input in case of an error
const variables = {
"personId": this.editedPersonID,
"tardiness": this.editedTardiness,
"absent": this.editedAbsent,
"excused": this.editedExcused,
"excuseType": editedExcuseTypeID,
"extraMarks": editedExtraMarksIDs,
"remarks": this.editedRemarks,
"lessonDocumentation": this.lessonDocumentationId,
}
console.log(variables)
// Call to the graphql mutation
this.$apollo.mutate({
// Query
mutation: gql`mutation updateOrCreatePersonalNote(
$personId: ID!,
$lessonDocumentation: ID!,
$tardiness: Int,
$absent: Boolean,
$excused: Boolean,
$excuseType: ID,
$extraMarks: [ID],
$remarks: String
) {
updateOrCreatePersonalNote(personId: $personId,
lessonDocumentation: $lessonDocumentation,
tardiness: $tardiness,
absent: $absent,
excused: $excused,
excuseType: $excuseType,
extraMarks: $extraMarks,
remarks: $remarks
) {
personalNote {
id
person {
id
fullName
}
tardiness
remarks
absent
excused
excuseType {
id
}
extraMarks {
id
}
}
}
}
`,
// Parameters
variables: variables,
}).then((data) => {
// Result
console.log(data)
// FIXME: check if data changed (?), display success message
}).catch((error) => {
// Error
console.error(error)
// FIXME: Notify the user about the error, maybe retry
})
if (this.newPersonalNote) {
this.personalNotes.push({
person: {
id: this.editedPersonID,
fullName: this.studentNameByID(this.editedPersonID)
},
tardiness: this.editedTardiness,
absent: this.editedAbsent,
excused: this.editedExcused,
excuseType: this.editedExcuseType,
extraMarks: this.editedExtraMarks,
remarks: this.editedRemarks,
});
} else {
// Loop through all personal notes and update the ones that match the editedPersonID
this.personalNotes.forEach(item => {
if (item.person.id === this.editedPersonID) {
item.tardiness = this.editedTardiness;
item.absent = this.editedAbsent;
item.excused = this.editedExcused;
item.excuseType = this.editedExcuseType;
item.extraMarks = this.editedExtraMarks;
item.remarks = this.editedRemarks;
}
});
}
this.$emit('change', this.personalNotes)
},
cancelDialog() {
this.dialog = false;
this.editedPersonID = ID_NO_PERSON;
},
saveDialog() {
this.savePersonalNote();
this.dialog = false;
this.editedPersonID = ID_NO_PERSON;
},
personalNoteString(personalNote) {
let personalNoteString = "";
if (personalNote.tardiness > 0) {
personalNoteString += personalNote.tardiness + " min. ";
}
if (personalNote.absent) {
personalNoteString += $t("alsijil.absent") + " ";
}
if (personalNote.excused) {
personalNoteString += $t("alsijil.excused") + " ";
}
if (personalNote.excuseType) {
personalNoteString += personalNote.excuseType.name;
}
if (personalNote.extraMarks.length > 0) {
personalNoteString += " (";
personalNote.extraMarks.forEach(item => {
personalNoteString += item.name + ", ";
});
personalNoteString = personalNoteString.substring(0, personalNoteString.length - 2);
personalNoteString += ") ";
}
if (personalNote.remarks) {
personalNoteString += "\"" + personalNote.remarks + "\" ";
}
return personalNoteString;
},
studentNameByID(studentID) {
try {
return this.persons.filter(item => item.id === studentID)[0].fullName;
} catch (TypeError) {
return "";
}
}
},
props: ["lessonDocumentationId", "personalNotes", "groups", "excuseTypes", "extraMarks"],
name: "personal-notes",
data: () => {
return {
dialog: false,
// Absent versp. exc. type hw note
editPersonalNoteId: null,
editedPersonID: ID_NO_PERSON,
editedTardiness: 0,
editedAbsent: false,
editedExcused: false,
editedExcuseType: null,
editedExtraMarks: [],
editedRemarks: "",
newPersonalNote: false,
}
},
computed: {
persons() {
// go through each group and get the students
// use the group names as headers for the v-select
return this.groups.map(
group => {
return [
{header: group.name, id: group.shortName},
group.members
]
}
).flat(2);
}
}
}
</script>
<template>
<v-tooltip bottom>
<template v-slot:activator="{ on, attrs }">
<v-btn
right
icon
v-bind="attrs"
v-on="on"
@click="() => {isAbleToClick ? $emit('manual-update') : null}"
:loading="status === UPDATING"
>
<v-icon
v-if="status !== UPDATING"
:color="color"
>
{{ icon }}
</v-icon>
</v-btn>
</template>
<span>{{ text }}</span>
</v-tooltip>
</template>
<script>
import {CHANGES, ERROR, SAVED, UPDATING} from "../../UpdateStatuses.js";
export default {
created() {
this.ERROR = ERROR;
this.SAVED = SAVED;
this.UPDATING = UPDATING;
this.CHANGES = CHANGES;
},
name: "update-indicator",
emits: ["manual-update"],
props: ["status"],
computed: {
text() {
switch (this.status) {
case SAVED:
return this.$t("alsijil.coursebook.sync.saved");
case UPDATING:
return this.$t("alsijil.coursebook.sync.updating");
case CHANGES:
return this.$t("alsijil.coursebook.sync.changes");
default:
return this.$t("alsijil.coursebook.sync.error");
}
},
color() {
switch (this.status) {
case SAVED:
return "success";
case CHANGES:
return "secondary";
case UPDATING:
return "secondary";
default:
return "error";
}
},
icon() {
// FIXME use app sdhasdhahsdhsadhsadh
switch (this.status) {
case SAVED:
return "mdi-check-circle-outline";
case CHANGES:
return "mdi-dots-horizontal";
default:
return "mdi-alert-outline";
}
},
isAbleToClick() {
return this.status === CHANGES || this.status === ERROR;
}
},
}
</script>
{
alsijilSitePreferences {
saveLessonDocumentationsByWeek
}
}
\ No newline at end of file
query courseBookLesson($lessonId: ID!) {
lesson: lessonById(id: $lessonId) {
groups {
name
shortName
members {
id
fullName
}
}
subject {
name
}
plannedLessonperiodsDatetimes {
year
week
datetimeStart
lessonPeriod{
id
period{
period
}
}
}
}
}
\ No newline at end of file
query courseBookLessonDocumentations ($lessonId: ID!) {
lessonDocumentationsByLessonId(id: $lessonId) {
id
topic
homework
groupNote
year
week
lessonPeriod {
id
period {
id
period
}
}
event {
id
}
extraLesson {
id
}
period
date
personalNotes {
id
person {
id
fullName
}
tardiness
absent
excused
excuseType {
id
name
shortName
}
remarks
extraMarks {
id
name
shortName
}
}
}
}
}
\ No newline at end of file
{
coursebookLessons {
groups {
shortName
}
subject {
name
}
validity {
dateStart
dateEnd
}
teachers {
fullName
}
id
}
}
{
excuseTypes {
id
name
shortName
}
}
\ No newline at end of file
{
extraMarks {
id
name
shortName
}
}
\ No newline at end of file
mutation UpdateOrCreateLessonDocumentation($year:Int!, $week:Int!, $lessonPeriodId:ID, $topic:String, $homework:String, $groupNote:String) {
updateOrCreateLessonDocumentation(year:$year, week:$week, lessonPeriodId:$lessonPeriodId, topic:$topic, homework:$homework, groupNote:$groupNote) {
lessonDocumentation{
id
topic
homework
groupNote
date
personalNotes {
id
person {
id
fullName
}
tardiness
absent
excused
excuseType {
id
name
shortName
}
remarks
extraMarks {
id
name
shortName
}
}
}
}
}
import { notLoggedInValidator, hasPersonValidator } from "aleksis.core/routeValidators";
export default
{
meta: {
inMenu: true,
titleKey: "alsijil.menu_title",
icon: "mdi-account-group-outline",
validators: [
hasPersonValidator
]
},
children: [
{
path: "coursebook/:lessonId(\\d+)/",
component: () => import("./components/coursebook/CourseBook.vue"),
props: true,
name: "alsijil.courseBookByID",
},
{
path: "coursebook/",
component: () => import("./components/coursebook/CourseBookList.vue"),
name: "alsijil.courseBookList",
meta: {
inMenu: true,
titleKey: "alsijil.coursebook.menu_title",
icon: "mdi-book-education-outline",
permission: "alsijil.view_coursebook_rule",
},
},
{
path: "lesson",
component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"),
name: "alsijil.lessonPeriod",
meta: {
inMenu: true,
titleKey: "alsijil.lesson.menu_title",
icon: "mdi-alarm",
permission: "alsijil.view_lesson_menu_rule",
},
},
{
path: "lesson/:year(\\d+)/:week(\\d+)/:id_(\\d+)",
component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"),
name: "alsijil.lessonPeriodByCWAndID",
},
{
path: "extra_lesson/:id_(\\d+)/",
component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"),
name: "alsijil.extraLessonByID",
},
{
path: "event/:id_(\\d+)/",
component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"),
name: "alsijil.eventByID",
},
{
path: "week/",
component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"),
name: "alsijil.weekView",
meta: {
inMenu: true,
titleKey: "alsijil.week.menu_title",
icon: "mdi-view-week-outline",
permission: "alsijil.view_week_menu_rule",
},
},
{
path: "week/:year(\\d+)/:week(\\d+)/",
component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"),
name: "alsijil.weekViewByWeek",
},
{
path: "week/year/cw/",
component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"),
name: "alsijil.weekViewPlaceholders",
},
{
path: "week/:type_/:id_(\\d+)/",
component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"),
name: "alsijil.weekViewByTypeAndID",
},
{
path: "week/year/cw/:type_/:id_(\\d+)/",
component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"),
name: "alsijil.weekViewPlaceholdersByTypeAndID",
},
{
path: "week/:year(\\d+)/:week(\\d+)/:type_/:id_(\\d+)/",
component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"),
name: "alsijil.weekViewByWeekTypeAndID",
},
{
path: "print/group/:id_(\\d+)",
component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"),
name: "alsijil.fullRegisterGroup",
},
{
path: "groups/",
component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"),
name: "alsijil.myGroups",
meta: {
inMenu: true,
titleKey: "alsijil.groups.menu_title",
icon: "mdi-account-multiple-outline",
permission: "alsijil.view_my_groups_rule",
},
},
{
path: "groups/:pk(\\d+)/",
component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"),
name: "alsijil.studentsList",
},
{
path: "persons/",
component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"),
name: "alsijil.myStudents",
meta: {
inMenu: true,
titleKey: "alsijil.persons.menu_title",
icon: "mdi-account-school-outline",
permission: "alsijil.view_my_students_rule",
},
},
{
path: "persons/:id_(\\d+)/",
component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"),
name: "alsijil.overviewPerson",
},
{
path: "me/",
component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"),
name: "alsijil.overviewMe",
meta: {
inMenu: true,
titleKey: "alsijil.my_overview.menu_title",
icon: "mdi-chart-box-outline",
permission: "alsijil.view_person_overview_menu_rule",
},
},
{
path: "notes/:pk(\\d+)/delete/",
component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"),
name: "alsijil.deletePersonalNote",
},
{
path: "absence/new/:id_(\\d+)/",
component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"),
name: "alsijil.registerAbsenceWithID",
},
{
path: "absence/new/",
component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"),
name: "alsijil.registerAbsence",
meta: {
inMenu: true,
titleKey: "alsijil.absence.menu_title",
icon: "mdi-message-draw",
permission: "alsijil.view_register_absence_rule",
},
},
{
path: "extra_marks/",
component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"),
name: "alsijil.extraMarks",
meta: {
inMenu: true,
titleKey: "alsijil.extra_marks.menu_title",
icon: "mdi-label-variant-outline",
permission: "alsijil.view_extramarks_rule",
},
},
{
path: "extra_marks/create/",
component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"),
name: "alsijil.createExtraMark",
},
{
path: "extra_marks/:pk(\\d+)/edit/",
component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"),
name: "alsijil.editExtraMark",
},
{
path: "extra_marks/:pk(\\d+)/delete/",
component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"),
name: "alsijil.deleteExtraMark",
},
{
path: "excuse_types/",
component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"),
name: "alsijil.excuseTypes",
meta: {
inMenu: true,
titleKey: "alsijil.excuse_types.menu_title",
icon: "mdi-label-outline",
permission: "alsijil.view_excusetypes_rule",
},
},
{
path: "excuse_types/create/",
component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"),
name: "alsijil.createExcuseType",
},
{
path: "excuse_types/:pk(\\d+)/edit/",
component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"),
name: "alsijil.editExcuseType",
},
{
path: "excuse_types/:pk(\\d+)/delete/",
component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"),
name: "alsijil.deleteExcuseType",
},
{
path: "group_roles/",
component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"),
name: "alsijil.groupRoles",
meta: {
inMenu: true,
titleKey: "alsijil.group_roles.menu_title_manage",
icon: "mdi-clipboard-plus-outline",
permission: "alsijil.view_grouproles_rule",
},
},
{
path: "group_roles/create/",
component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"),
name: "alsijil.createGroupRole",
},
{
path: "group_roles/:pk(\\d+)/edit/",
component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"),
name: "alsijil.editGroupRole",
},
{
path: "group_roles/:pk(\\d+)/delete/",
component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"),
name: "alsijil.deleteGroupRole",
},
{
path: "groups/:pk(\\d+)/group_roles/",
component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"),
name: "alsijil.assignedGroupRoles",
},
{
path: "groups/:pk(\\d+)/group_roles/assign/",
component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"),
name: "alsijil.assignGroupRole",
},
{
path: "groups/:pk(\\d+)/group_roles/:role_pk(\\d+)/assign/",
component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"),
name: "alsijil.assignGroupRoleByRolePK",
},
{
path: "group_roles/assignments/:pk(\\d+)/edit/",
component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"),
name: "alsijil.editGroupRoleAssignment",
},
{
path: "group_roles/assignments/:pk(\\d+)/stop/",
component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"),
name: "alsijil.stopGroupRoleAssignment",
},
{
path: "group_roles/assignments/:pk(\\d+)/delete/",
component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"),
name: "alsijil.deleteGroupRoleAssignment",
},
{
path: "group_roles/assignments/assign/",
component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"),
name: "alsijil.assignGroupRoleMultiple",
meta: {
inMenu: true,
titleKey: "alsijil.group_roles.menu_title_assign",
icon: "mdi-clipboard-account-outline",
permission: "alsijil.assign_grouprole_for_multiple_rule",
},
},
{
path: "all/",
component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"),
name: "alsijil.allRegisterObjects",
meta: {
inMenu: true,
titleKey: "alsijil.all_lessons.menu_title",
icon: "mdi-format-list-text",
permission: "alsijil.view_register_objects_list_rule",
},
},
],
};
{
"alsijil": {
"coursebook": {
"menu_title": "Kursbuch",
"title": "Kursbücher"
},
"menu_title": "Klassenbuch"
}
}
{
"alsijil": {
"coursebook": {
"menu_title": "Coursebook",
"title": "Coursebooks",
"create_documentation": "Create documentation",
"choose_week": "Choose week",
"choose_lesson_date": "Choose lesson date",
"sync": {
"saved": "All changes are saved.",
"updating": "Changes are being synced.",
"changes": "You have unsaved changes. Click to save them immediately.",
"error": "There has been an error while saving the latest changes."
},
"no_coursebook": "No coursebook",
"no_courses_as_teacher": "There are no courses where you are a teacher.",
"open_coursebook": "Open in coursebook"
},
"lesson": {
"menu_title": "Current lesson"
},
"week": {
"menu_title": "Current week"
},
"groups": {
"menu_title": "My groups"
},
"persons": {
"menu_title": "My students"
},
"absence": {
"menu_title": "Register absence"
},
"my_overview": {
"menu_title": "My overview"
},
"extra_marks": {
"menu_title": "Extra marks"
},
"excuse_types": {
"menu_title": "Excuse types"
},
"group_roles": {
"menu_title_manage": "Manage group roles",
"menu_title_assign": "Assign group roles"
},
"all_lessons": {
"menu_title": "All lessons"
},
"period": "Period",
"period_number": "{number}. period",
"lesson_documentation": {
"topic": "Topic",
"homework": "Homework",
"group_note": "Group note"
},
"calendar_week": "Calendar week",
"calendar_week_short": "Week",
"personal_note": {
"title": "Personal Note",
"title_plural": "Personal Notes",
"absent_title": "Absent",
"excused_title": "Excused",
"absent": "absent",
"excused": "excused",
"person": "Person",
"tardiness": "Tardiness",
"excuse_type": "Excuse type",
"extra_marks": "Extra marks",
"remarks": "Remarks",
"actions": {
"add": "Add personal note"
}
},
"error_occurred": "An error occurred",
"error_updating": "Error updating data",
"cancel": "Cancel",
"save": "Save",
"back": "Back",
"menu_title": "Class register"
}
}