2024-02-18 13:47:08 +01:00
|
|
|
/*eslint no-var: "error"*/
|
|
|
|
/*eslint no-console: "off"*/
|
|
|
|
/*eslint no-unused-vars: warn */
|
|
|
|
/*eslint max-len: ["error", { "code": 160 }] */
|
|
|
|
/*eslint-disable no-trailing-spaces */
|
|
|
|
/*eslint-env es6*/
|
|
|
|
// Put this file in path/to/plugin/amd/src
|
|
|
|
|
|
|
|
import {load_strings} from './util/string-helper';
|
|
|
|
import {call} from 'core/ajax';
|
|
|
|
import notification from 'core/notification';
|
|
|
|
import Debugger from './util/debugger';
|
|
|
|
import Config from 'core/config';
|
|
|
|
import TSComponents from './treestudyplan-components';
|
2024-02-23 09:20:10 +01:00
|
|
|
import FitTextVue from './util/fittext-vue';
|
2024-02-24 16:09:47 +01:00
|
|
|
import {format_datetime} from "./util/date-helper";
|
2024-02-18 13:47:08 +01:00
|
|
|
|
2024-02-20 20:40:18 +01:00
|
|
|
const debug = new Debugger("treestudyplan-viewer");
|
|
|
|
|
|
|
|
|
2024-02-18 13:47:08 +01:00
|
|
|
// Make π available as a constant
|
|
|
|
const π = Math.PI;
|
|
|
|
// Gravity value for arrow lines - determines how much a line is pulled in the direction of the start/end before changing direction
|
|
|
|
const LINE_GRAVITY = 1.3;
|
|
|
|
|
2024-02-23 23:19:46 +01:00
|
|
|
/**
|
|
|
|
* Strip tags from html
|
|
|
|
* @param {*} html
|
|
|
|
* @returns
|
|
|
|
*/
|
|
|
|
function striptags(html) {
|
|
|
|
const tmp = document.createElement("DIV");
|
|
|
|
tmp.innerHTML = html;
|
|
|
|
const text = tmp.textContent || tmp.innerText;
|
|
|
|
tmp.remove();
|
|
|
|
return text;
|
|
|
|
}
|
|
|
|
|
2024-02-20 20:40:18 +01:00
|
|
|
/**
|
|
|
|
* Retrieve condition headers
|
|
|
|
* @param {Object} item
|
|
|
|
*/
|
|
|
|
function conditionHeaders(item) {
|
|
|
|
const course = item.course;
|
|
|
|
const list = [];
|
2024-02-23 09:20:10 +01:00
|
|
|
if (course.competency) {
|
|
|
|
for (const cmp of course.competency.competencies) {
|
2024-02-20 20:40:18 +01:00
|
|
|
list.push({
|
2024-02-23 23:19:46 +01:00
|
|
|
name: (cmp.details?`${cmp.title} - ${cmp.details}`:cmp.title),
|
|
|
|
tooltip: cmp.description,
|
2024-02-20 20:40:18 +01:00
|
|
|
});
|
|
|
|
}
|
|
|
|
} else if(course.completion) {
|
|
|
|
for (const cnd of course.completion.conditions) {
|
|
|
|
for (const itm of cnd.items) {
|
|
|
|
list.push({
|
|
|
|
name: itm.title,
|
2024-02-23 23:19:46 +01:00
|
|
|
tooltip: `${itm.details.type}: ${itm.details.requirement}`,
|
2024-02-20 20:40:18 +01:00
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if(course.grades) {
|
|
|
|
for(const g of course.grades) {
|
|
|
|
if (g.selected) {
|
|
|
|
list.push({
|
|
|
|
name: g.name,
|
2024-02-23 23:19:46 +01:00
|
|
|
tooltip: `${g.typename}: ${striptags(g.name)}`,
|
2024-02-20 20:40:18 +01:00
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return list;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Retrieve conditions
|
|
|
|
* @param {Object} item
|
|
|
|
*/
|
|
|
|
function conditions(item) {
|
|
|
|
const course = item.course;
|
|
|
|
const list = [];
|
2024-02-23 09:20:10 +01:00
|
|
|
if (course.competency) {
|
|
|
|
for (const cmp of course.competency.competencies) {
|
2024-02-20 20:40:18 +01:00
|
|
|
list.push(cmp);
|
|
|
|
}
|
|
|
|
} else if(course.completion) {
|
|
|
|
for (const cnd of course.completion.conditions) {
|
|
|
|
for (const itm of cnd.items) {
|
|
|
|
list.push(itm);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if(course.grades) {
|
|
|
|
for(const g of course.grades) {
|
|
|
|
if (g.selected) {
|
|
|
|
list.push(g);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return list;
|
|
|
|
}
|
|
|
|
|
2024-02-18 13:47:08 +01:00
|
|
|
|
|
|
|
export default {
|
|
|
|
install(Vue/*,options*/){
|
|
|
|
Vue.use(TSComponents);
|
2024-02-23 09:20:10 +01:00
|
|
|
Vue.use(FitTextVue);
|
2024-02-18 13:47:08 +01:00
|
|
|
|
|
|
|
let strings = load_strings({
|
|
|
|
report: {
|
|
|
|
loading: "loadinghelp@core",
|
|
|
|
studyplan_past: "studyplan_past",
|
|
|
|
studyplan_present: "studyplan_present",
|
|
|
|
studyplan_future: "studyplan_future",
|
|
|
|
back: "back",
|
|
|
|
},
|
2024-02-18 23:27:57 +01:00
|
|
|
|
2024-02-18 13:47:08 +01:00
|
|
|
invalid: {
|
|
|
|
error: 'error',
|
|
|
|
},
|
2024-02-18 23:27:57 +01:00
|
|
|
header: {
|
|
|
|
overall: 'overall',
|
2024-02-23 23:19:46 +01:00
|
|
|
students: 'students@core',
|
|
|
|
firstname: 'firstname@core',
|
|
|
|
lastname: 'lastname@core',
|
2024-02-24 16:09:47 +01:00
|
|
|
email: 'email@core',
|
|
|
|
lastaccess: 'lastaccess@core',
|
2024-02-18 23:27:57 +01:00
|
|
|
},
|
|
|
|
studentresults: {
|
2024-02-18 13:47:08 +01:00
|
|
|
completion_incomplete: "completion_incomplete",
|
|
|
|
completion_failed: "completion_failed",
|
|
|
|
completion_pending: "completion_pending",
|
|
|
|
completion_progress: "completion_progress",
|
|
|
|
completion_completed: "completion_completed",
|
|
|
|
completion_good: "completion_good",
|
|
|
|
completion_excellent: "completion_excellent",
|
|
|
|
student_not_tracked: "student_not_tracked",
|
2024-02-24 16:09:47 +01:00
|
|
|
never: "never@core",
|
2024-02-18 13:47:08 +01:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
/************************************
|
|
|
|
* *
|
|
|
|
* Treestudyplan Viewer components *
|
|
|
|
* *
|
|
|
|
************************************/
|
|
|
|
|
|
|
|
Vue.component('q-studyplanreport', {
|
|
|
|
props: {
|
|
|
|
structure: {
|
|
|
|
type: Object,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
data() {
|
|
|
|
return {
|
|
|
|
students: [],
|
|
|
|
studentresults: {},
|
2024-02-23 23:19:46 +01:00
|
|
|
studentsloading: true,
|
2024-02-18 23:27:57 +01:00
|
|
|
expansioninfo: {
|
|
|
|
periods: {},
|
|
|
|
lines: {},
|
|
|
|
items: {},
|
|
|
|
},
|
|
|
|
groupinfo: {},
|
2024-02-18 13:47:08 +01:00
|
|
|
|
|
|
|
sorting: {
|
2024-02-23 23:19:46 +01:00
|
|
|
header: 'lastname',
|
|
|
|
asc: true,
|
2024-02-18 13:47:08 +01:00
|
|
|
}
|
|
|
|
};
|
|
|
|
},
|
|
|
|
created() {
|
|
|
|
},
|
2024-02-18 23:27:57 +01:00
|
|
|
watch:{
|
|
|
|
structure: {
|
|
|
|
immediate: true,
|
|
|
|
handler (structure) {
|
2024-02-24 16:09:47 +01:00
|
|
|
this.loadStudents(); // reload the student list
|
|
|
|
|
2024-02-18 23:27:57 +01:00
|
|
|
// (Re)build expansion info structure
|
2024-02-23 09:20:10 +01:00
|
|
|
let firstperiod = true;
|
2024-02-18 23:27:57 +01:00
|
|
|
for (const period of structure.periods) {
|
|
|
|
const pid = period.period.id;
|
|
|
|
if (!this.expansioninfo.periods[pid]) {
|
|
|
|
// Use this.$set to make sure the properties are reactive.
|
|
|
|
this.$set(
|
|
|
|
this.expansioninfo.periods,
|
|
|
|
pid,
|
|
|
|
{
|
2024-02-24 23:42:01 +01:00
|
|
|
expanded: ((firstperiod && period.lines.length > 0)?true:false),
|
2024-02-18 23:27:57 +01:00
|
|
|
}
|
|
|
|
);
|
|
|
|
this.$set(
|
|
|
|
this.expansioninfo.lines,
|
|
|
|
period.period.id,
|
|
|
|
{}
|
|
|
|
);
|
|
|
|
}
|
|
|
|
for (const line of period.lines) {
|
|
|
|
const lid = line.line.id;
|
|
|
|
if (!this.expansioninfo.lines[lid]) {
|
|
|
|
// Use this.$set to make sure the properties are reactive.
|
|
|
|
this.$set(
|
|
|
|
this.expansioninfo.lines[pid],
|
|
|
|
lid,
|
|
|
|
{
|
|
|
|
expanded: true,
|
|
|
|
}
|
|
|
|
);
|
|
|
|
}
|
|
|
|
for (const item of line.items) {
|
|
|
|
if (!this.expansioninfo.items[item.id]) {
|
|
|
|
// Use this.$set to make sure the properties are reactive.
|
|
|
|
this.$set(
|
|
|
|
this.expansioninfo.items,
|
|
|
|
item.id,
|
|
|
|
{
|
|
|
|
expanded: false,
|
|
|
|
}
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2024-02-23 09:20:10 +01:00
|
|
|
firstperiod = false;
|
2024-02-18 23:27:57 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2024-02-18 13:47:08 +01:00
|
|
|
},
|
2024-02-20 20:40:18 +01:00
|
|
|
computed: {
|
|
|
|
sortedstudents(){
|
2024-02-23 23:19:46 +01:00
|
|
|
const self=this;
|
2024-02-24 16:09:47 +01:00
|
|
|
// Probably could make a deep copy for purity's sake, but this works just as well and is probably more efficient.
|
2024-02-23 23:19:46 +01:00
|
|
|
const students = this.students;
|
|
|
|
for (const group of this.students) {
|
|
|
|
group.users.sort((a,b) => {
|
|
|
|
let d = a;
|
|
|
|
let e = b;
|
|
|
|
if (!this.sorting.asc) {
|
|
|
|
d = b;
|
|
|
|
e = a;
|
|
|
|
}
|
2024-02-24 16:09:47 +01:00
|
|
|
if (this.sorting.header == "lastaccess") {
|
|
|
|
const dvalue = (d[this.sorting.header]?d[this.sorting.header]:0);
|
|
|
|
const evalue = (e[this.sorting.header]?e[this.sorting.header]:0);
|
|
|
|
return dvalue - evalue;
|
|
|
|
} else {
|
|
|
|
return String(d[this.sorting.header]).localeCompare(String(e[this.sorting.header]));
|
|
|
|
}
|
2024-02-23 23:19:46 +01:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
return students;
|
2024-02-20 20:40:18 +01:00
|
|
|
},
|
|
|
|
resultColCount(){
|
|
|
|
let count = 0;
|
|
|
|
for (const period of this.structure.periods) {
|
|
|
|
const pid = period.period.id;
|
|
|
|
if (!this.expansioninfo.periods[pid].expanded) {
|
2024-02-23 09:20:10 +01:00
|
|
|
// This period is not expanded. Make it 3 units wide
|
2024-02-23 23:19:46 +01:00
|
|
|
count += 2;
|
2024-02-20 20:40:18 +01:00
|
|
|
} else {
|
|
|
|
for (const line of period.lines) {
|
|
|
|
const lid = line.line.id;
|
2024-02-23 09:20:10 +01:00
|
|
|
if (!this.expansioninfo.lines[pid][lid].expanded) {
|
2024-02-20 20:40:18 +01:00
|
|
|
count +=1;
|
|
|
|
} else {
|
|
|
|
for (const item of line.items) {
|
2024-02-23 09:20:10 +01:00
|
|
|
if (!this.expansioninfo.items[item.id].expanded) {
|
2024-02-20 20:40:18 +01:00
|
|
|
count += 1;
|
|
|
|
} else {
|
|
|
|
count += 1 + conditions(item).length;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return count;
|
|
|
|
}
|
|
|
|
},
|
2024-02-18 13:47:08 +01:00
|
|
|
methods: {
|
|
|
|
loadStudents() {
|
|
|
|
const self = this;
|
2024-02-23 23:19:46 +01:00
|
|
|
self.studentsloading=true;
|
2024-02-18 13:47:08 +01:00
|
|
|
call([{
|
|
|
|
methodname: 'local_treestudyplan_all_associated_grouped',
|
|
|
|
args: { studyplan_id: this.structure.studyplan.id}
|
|
|
|
}])[0].then(function(response){
|
|
|
|
self.students = response;
|
|
|
|
for(const group of self.students) {
|
2024-02-18 23:27:57 +01:00
|
|
|
self.$set(
|
|
|
|
self.groupinfo,
|
2024-02-23 23:19:46 +01:00
|
|
|
group.id,
|
2024-02-18 23:27:57 +01:00
|
|
|
{
|
2024-02-23 23:19:46 +01:00
|
|
|
expanded: true,
|
2024-02-18 23:27:57 +01:00
|
|
|
}
|
|
|
|
);
|
|
|
|
|
2024-02-18 13:47:08 +01:00
|
|
|
for(const student of group.users){
|
2024-02-19 11:34:40 +01:00
|
|
|
self.$set(
|
|
|
|
self.studentresults,
|
|
|
|
student.id,
|
|
|
|
{
|
|
|
|
loading: true,
|
|
|
|
results: [],
|
|
|
|
}
|
|
|
|
);
|
2024-02-18 13:47:08 +01:00
|
|
|
call([{
|
|
|
|
methodname: 'local_treestudyplan_get_report_data',
|
|
|
|
args: { pageid: self.structure.page.id,
|
|
|
|
userid: student.id,
|
|
|
|
firstperiod: self.structure.firstperiod,
|
|
|
|
lastperiod: self.structure.lastperiod,
|
|
|
|
}
|
|
|
|
}])[0].then(function(response){
|
|
|
|
self.studentresults[student.id].loading = false;
|
|
|
|
self.studentresults[student.id].results = response;
|
|
|
|
}).catch(notification.exception);
|
|
|
|
}
|
|
|
|
}
|
2024-02-23 23:19:46 +01:00
|
|
|
self.studentsloading=false;
|
2024-02-18 13:47:08 +01:00
|
|
|
}).catch(notification.exception);
|
2024-02-18 23:27:57 +01:00
|
|
|
},
|
|
|
|
expansionChanged(parm, id, val) {
|
|
|
|
if(parm[0] == 'p') {
|
|
|
|
parm = 'periods';
|
|
|
|
} else if(parm[0] == 'l') {
|
|
|
|
parm = 'lines';
|
|
|
|
} else {
|
|
|
|
parm = 'items';
|
|
|
|
}
|
|
|
|
|
|
|
|
if (parm == 'lines') {
|
|
|
|
this.expansioninfo[parm][id[0]][id[1]].expanded = val;
|
|
|
|
} else {
|
|
|
|
this.expansioninfo[parm][id].expanded = val;
|
|
|
|
}
|
2024-02-20 20:40:18 +01:00
|
|
|
},
|
2024-02-23 23:19:46 +01:00
|
|
|
groupExpansionChanged(group) {
|
|
|
|
this.groupinfo[group.id].expanded = !this.groupinfo[group.id].expanded;
|
|
|
|
},
|
|
|
|
toggleSort(header) {
|
|
|
|
if (this.sorting.header == header) {
|
|
|
|
this.sorting.asc = !this.sorting.asc;
|
|
|
|
} else {
|
|
|
|
this.sorting.header = header;
|
|
|
|
this.sorting.asc = true;
|
|
|
|
}
|
|
|
|
}
|
2024-02-18 13:47:08 +01:00
|
|
|
},
|
2024-02-18 23:27:57 +01:00
|
|
|
template: `
|
2024-02-23 09:20:10 +01:00
|
|
|
<table class='q-studyplanreport'
|
|
|
|
:style="'--resultColCount: '+resultColCount+';'">
|
2024-02-23 23:19:46 +01:00
|
|
|
<colgroup class="q-col-studentinfo">
|
|
|
|
<col class="q-name"></col>
|
2024-02-24 16:09:47 +01:00
|
|
|
<col class="q-lastaccess"></col>
|
2024-02-23 23:19:46 +01:00
|
|
|
</colgroup>
|
|
|
|
<colgroup class="q-col-resultinfo">
|
|
|
|
<col v-for="n in resultColCount"></col>
|
|
|
|
</colgroup>
|
2024-02-18 23:27:57 +01:00
|
|
|
<q-header
|
|
|
|
:sorting='sorting'
|
|
|
|
:structure='structure'
|
|
|
|
:expansion='expansioninfo'
|
|
|
|
@expansion='expansionChanged'
|
2024-02-23 23:19:46 +01:00
|
|
|
@togglesort="toggleSort"
|
2024-02-18 23:27:57 +01:00
|
|
|
></q-header>
|
2024-02-23 23:19:46 +01:00
|
|
|
<template v-if="!studentsloading">
|
|
|
|
<template v-for="group in sortedstudents">
|
|
|
|
<q-groupheading
|
|
|
|
v-if="group.users && group.users.length > 0"
|
|
|
|
:group="group"
|
|
|
|
:expanded="groupinfo[group.id].expanded"
|
|
|
|
@togglegroup="groupExpansionChanged"
|
|
|
|
:resultcolumns="resultColCount"
|
|
|
|
:studentinfocolumns="2"
|
|
|
|
></q-groupheading>
|
|
|
|
<template v-if='groupinfo[group.id].expanded'>
|
|
|
|
<q-studentresults v-for="(student,idx) in group.users"
|
|
|
|
:key="student.id"
|
|
|
|
:student='student'
|
|
|
|
:even="(idx%2==1)"
|
|
|
|
:structure='structure'
|
|
|
|
:results='studentresults[student.id].results'
|
|
|
|
:loading='studentresults[student.id].loading'
|
|
|
|
:expansion='expansioninfo'
|
|
|
|
></q-studentresults>
|
|
|
|
</template>
|
2024-02-18 23:27:57 +01:00
|
|
|
</template>
|
2024-02-20 20:40:18 +01:00
|
|
|
</template>
|
2024-02-23 23:19:46 +01:00
|
|
|
<q-inforow v-else
|
|
|
|
:resultcolumns="resultColCount"
|
|
|
|
:studentinfocolumns="2"><div class="spinner-border spinner-border-sm text-info" role="status"></div></q-inforow>
|
2024-02-20 20:40:18 +01:00
|
|
|
</table>
|
2024-02-18 23:27:57 +01:00
|
|
|
`,
|
|
|
|
});
|
|
|
|
|
|
|
|
Vue.component('q-header', {
|
|
|
|
props: {
|
|
|
|
structure: {
|
|
|
|
type: Object,
|
|
|
|
},
|
|
|
|
sorting: {
|
|
|
|
type: Object,
|
|
|
|
},
|
|
|
|
expansion: {
|
|
|
|
type: Object
|
|
|
|
},
|
|
|
|
},
|
|
|
|
data() {
|
|
|
|
return {
|
|
|
|
text: strings.header,
|
|
|
|
};
|
|
|
|
},
|
|
|
|
computed: {
|
|
|
|
},
|
|
|
|
methods: {
|
|
|
|
conditions(item) {
|
2024-02-20 20:40:18 +01:00
|
|
|
return conditionHeaders(item);
|
2024-02-18 23:27:57 +01:00
|
|
|
},
|
2024-02-19 23:50:47 +01:00
|
|
|
colspanPeriod(period) {
|
2024-02-20 20:40:18 +01:00
|
|
|
const pid = period.period.id;
|
|
|
|
if (this.expansion.periods[pid].expanded) {
|
2024-02-19 23:50:47 +01:00
|
|
|
let sum = 0;
|
|
|
|
for (const l of period.lines) {
|
|
|
|
sum += this.colspanLine(period,l);
|
|
|
|
}
|
|
|
|
return sum;
|
|
|
|
} else {
|
2024-02-23 23:19:46 +01:00
|
|
|
return 2;
|
2024-02-19 23:50:47 +01:00
|
|
|
}
|
|
|
|
},
|
|
|
|
colspanLine(period,line) {
|
2024-02-20 20:40:18 +01:00
|
|
|
const pid = period.period.id;
|
|
|
|
const lid = line.line.id;
|
|
|
|
|
|
|
|
if (this.expansion.lines[pid][lid].expanded) {
|
2024-02-19 23:50:47 +01:00
|
|
|
let sum = 0;
|
|
|
|
for (const i of line.items) {
|
|
|
|
sum += this.colspanItem(i);
|
|
|
|
}
|
|
|
|
return sum;
|
|
|
|
} else {
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
colspanItem(item) {
|
|
|
|
if (this.expansion.items[item.id].expanded) {
|
|
|
|
const cs = this.conditions(item);
|
|
|
|
return 1+cs.length;
|
|
|
|
} else {
|
|
|
|
return 1;
|
|
|
|
}
|
2024-02-23 09:20:10 +01:00
|
|
|
},
|
|
|
|
togglePeriod(period,val) {
|
|
|
|
if ( val === undefined) {
|
|
|
|
val = !(this.expansion.periods[period.id].expanded);
|
|
|
|
}
|
|
|
|
this.$emit('expansion','periods',period.id,val);
|
|
|
|
},
|
|
|
|
toggleLine(period,line,val) {
|
|
|
|
if ( val === undefined) {
|
|
|
|
val = !(this.expansion.lines[period.id][line.id].expanded);
|
|
|
|
}
|
|
|
|
this.$emit('expansion','lines',[period.id,line.id],val);
|
|
|
|
},
|
|
|
|
toggleItem(item,val) {
|
|
|
|
if ( val === undefined) {
|
|
|
|
val = !(this.expansion.items[item.id].expanded);
|
|
|
|
}
|
|
|
|
this.$emit('expansion','items',item.id,val);
|
|
|
|
},
|
2024-02-23 23:19:46 +01:00
|
|
|
toggleSort(heading) {
|
|
|
|
this.$emit('togglesort',heading);
|
|
|
|
}
|
2024-02-18 23:27:57 +01:00
|
|
|
},
|
2024-02-18 13:47:08 +01:00
|
|
|
mounted() {
|
2024-02-18 23:27:57 +01:00
|
|
|
|
2024-02-18 13:47:08 +01:00
|
|
|
},
|
|
|
|
updated() {
|
|
|
|
},
|
2024-03-06 23:49:50 +01:00
|
|
|
/* TODO: https://css-tricks.com/position-sticky-and-table-headers/ */
|
2024-02-18 13:47:08 +01:00
|
|
|
template: `
|
2024-02-19 23:50:47 +01:00
|
|
|
<thead class='q-header'>
|
|
|
|
<tr> <!-- period heading -->
|
2024-02-24 16:09:47 +01:00
|
|
|
<th rowspan='4' colspan='2' class='q-studentinfo q-generic'><span>{{text.students}}</span></th>
|
2024-02-19 23:50:47 +01:00
|
|
|
<th v-for="p in structure.periods"
|
2024-02-23 09:20:10 +01:00
|
|
|
:class="'q-period-heading '+ ((expansion.periods[p.period.id].expanded)?'expanded':'collapsed')"
|
2024-02-19 23:50:47 +01:00
|
|
|
:colspan='colspanPeriod(p)'
|
2024-02-24 16:09:47 +01:00
|
|
|
:rowspan='(expansion.periods[p.period.id].expanded && p.lines.length > 0)?1:5'
|
2024-02-23 23:19:46 +01:00
|
|
|
><span class="q-wrap"><a v-if='(p.lines.length > 0)' href='#' @click.prevent="togglePeriod(p.period)"
|
2024-02-23 09:20:10 +01:00
|
|
|
><i v-if="expansion.periods[p.period.id].expanded"
|
2024-02-23 23:19:46 +01:00
|
|
|
class='q-chevron fa fa-minus'></i
|
|
|
|
><i v-else class='q-chevron fa fa-plus'></i
|
|
|
|
> {{ p.period.fullname}}</a
|
|
|
|
><span v-else>{{ p.period.fullname}}</span></span
|
2024-02-23 09:20:10 +01:00
|
|
|
></th>
|
2024-02-19 23:50:47 +01:00
|
|
|
</tr>
|
|
|
|
<tr> <!-- line heading -->
|
|
|
|
<template v-for="p in structure.periods">
|
2024-02-23 23:19:46 +01:00
|
|
|
<template v-if="expansion.periods[p.period.id].expanded">
|
2024-02-20 20:40:18 +01:00
|
|
|
<th v-for="l in p.lines"
|
2024-02-23 09:20:10 +01:00
|
|
|
:class="'q-line-heading ' + ((expansion.lines[p.period.id][l.line.id].expanded)?'expanded':'collapsed')"
|
2024-02-19 23:50:47 +01:00
|
|
|
:colspan="colspanLine(p,l)"
|
2024-02-24 16:09:47 +01:00
|
|
|
:rowspan='(expansion.lines[p.period.id][l.line.id].expanded)?1:4'
|
2024-02-23 09:20:10 +01:00
|
|
|
><span class="q-wrap"><fittext vertical maxsize="18pt"
|
|
|
|
><span class='q-label'
|
|
|
|
:title="l.line.shortname"
|
|
|
|
v-html="l.line.shortname"
|
|
|
|
></span
|
|
|
|
></fittext></span
|
2024-02-20 20:40:18 +01:00
|
|
|
></th>
|
2024-02-19 23:50:47 +01:00
|
|
|
</template>
|
|
|
|
</template>
|
|
|
|
</tr>
|
|
|
|
<tr> <!-- item heading -->
|
|
|
|
<template v-for="p in structure.periods">
|
|
|
|
<template v-if="expansion.periods[p.period.id].expanded">
|
2024-02-20 20:40:18 +01:00
|
|
|
<template v-for="l in p.lines">
|
2024-02-19 23:50:47 +01:00
|
|
|
<template v-if="expansion.lines[p.period.id][l.line.id].expanded">
|
|
|
|
<th v-for="item in l.items"
|
2024-02-20 20:40:18 +01:00
|
|
|
:class="'q-item-heading ' + ((expansion.items[item.id].expanded)?'expanded':'collapsed')"
|
|
|
|
:colspan="colspanItem(item)"
|
2024-02-24 16:09:47 +01:00
|
|
|
:rowspan='(expansion.items[item.id].expanded)?1:3'
|
2024-02-23 09:20:10 +01:00
|
|
|
><span class="q-wrap"><a href='#'
|
|
|
|
@click.prevent="toggleItem(item)"
|
|
|
|
><i v-if="expansion.items[item.id].expanded"
|
2024-02-23 23:19:46 +01:00
|
|
|
class='q-chevron fa fa-minus'></i
|
2024-02-23 09:20:10 +01:00
|
|
|
><i v-else
|
2024-02-23 23:19:46 +01:00
|
|
|
class='q-chevron fa fa-plus'></i
|
2024-02-23 09:20:10 +01:00
|
|
|
></a
|
|
|
|
> <a style="display: inline-block;" href='#'
|
|
|
|
@click.prevent="toggleItem(item)"
|
|
|
|
><fittext vertical maxsize="18pt" singleline
|
|
|
|
><span class='q-label'
|
|
|
|
:title="item.course.displayname"
|
|
|
|
v-html="item.course.displayname"
|
|
|
|
></span
|
|
|
|
></fittext
|
|
|
|
></a></span
|
2024-02-20 20:40:18 +01:00
|
|
|
></th>
|
2024-02-19 23:50:47 +01:00
|
|
|
</template>
|
|
|
|
</template>
|
|
|
|
</template>
|
|
|
|
</template>
|
|
|
|
</tr>
|
|
|
|
<tr> <!-- condition heading -->
|
|
|
|
<template v-for="p in structure.periods">
|
|
|
|
<template v-if="expansion.periods[p.period.id].expanded">
|
2024-02-20 20:40:18 +01:00
|
|
|
<template v-for="l in p.lines">
|
2024-02-19 23:50:47 +01:00
|
|
|
<template v-if="expansion.lines[p.period.id][l.line.id].expanded">
|
|
|
|
<template v-for="item in l.items">
|
|
|
|
<template v-if="expansion.items[item.id].expanded">
|
2024-02-24 16:09:47 +01:00
|
|
|
<th class='q-condition-heading overall' rowspan="2"
|
2024-02-23 09:20:10 +01:00
|
|
|
><span class='q-wrap'>{{ text.overall }}</span></th>
|
2024-02-24 16:09:47 +01:00
|
|
|
<th v-for="c in conditions(item)"
|
|
|
|
rowspan="2"
|
2024-02-19 23:50:47 +01:00
|
|
|
class='q-condition-heading'
|
2024-02-23 23:19:46 +01:00
|
|
|
><span class="q-wrap"><fittext vertical maxsize="14pt"><a class='q-label q-condition-label'
|
|
|
|
:title="c.tooltip" href="#" @click.prevent
|
|
|
|
v-b-tooltip.focus
|
|
|
|
v-html="c.name"></a
|
|
|
|
></fittext></span
|
2024-02-19 23:50:47 +01:00
|
|
|
></th>
|
|
|
|
</template>
|
|
|
|
</template>
|
|
|
|
</template>
|
|
|
|
</template>
|
|
|
|
</template>
|
|
|
|
</template>
|
|
|
|
</tr>
|
2024-02-24 16:09:47 +01:00
|
|
|
<tr> <!-- student info heading -->
|
|
|
|
<th class="q-studentinfo q-name">
|
|
|
|
<fittext maxsize="12pt"
|
|
|
|
><a href="#" @click.prevent="toggleSort('firstname')">{{text.firstname}}</a
|
|
|
|
><i v-if="sorting.header=='firstname' && sorting.asc" class='fa fa-sort-asc fa-fw'></i
|
|
|
|
><i v-else-if="sorting.header=='firstname' && !sorting.asc" class='fa fa-sort-desc fa-fw'></i>
|
|
|
|
/ <a href="#" @click.prevent="toggleSort('lastname')">{{text.lastname}}</a
|
|
|
|
><i v-if="sorting.header=='lastname' && sorting.asc" class='fa fa-sort-asc fa-fw'></i
|
|
|
|
><i v-else-if="sorting.header=='lastname' && !sorting.asc" class='fa fa-sort-desc fa-fw'></i
|
|
|
|
></fittext>
|
|
|
|
</th>
|
|
|
|
<th class="q-studentinfo q-email">
|
|
|
|
<fittext maxsize="12pt"
|
|
|
|
><a href="#" @click.prevent="toggleSort('lastaccess')">{{text.lastaccess}}</a
|
|
|
|
><i v-if="sorting.header=='lastaccess' && sorting.asc" class='fa fa-sort-asc fa-fw'></i
|
|
|
|
><i v-else-if="sorting.header=='lastaccess' && !sorting.asc" class='fa fa-sort-desc fa-fw'></i
|
|
|
|
></fittext>
|
|
|
|
</th>
|
|
|
|
</tr>
|
2024-02-19 23:50:47 +01:00
|
|
|
</thead>
|
2024-02-18 13:47:08 +01:00
|
|
|
`,
|
|
|
|
});
|
|
|
|
|
2024-02-18 23:27:57 +01:00
|
|
|
Vue.component('q-groupheading', {
|
|
|
|
props: {
|
2024-02-23 23:19:46 +01:00
|
|
|
group: {
|
2024-02-18 23:27:57 +01:00
|
|
|
type: Object,
|
|
|
|
},
|
2024-02-23 23:19:46 +01:00
|
|
|
resultcolumns: {
|
|
|
|
type: Number,
|
|
|
|
default: 1
|
|
|
|
},
|
|
|
|
studentinfocolumns: {
|
|
|
|
type: Number,
|
|
|
|
default: 1
|
|
|
|
},
|
|
|
|
expanded: {
|
|
|
|
type: Boolean,
|
|
|
|
}
|
2024-02-18 23:27:57 +01:00
|
|
|
},
|
|
|
|
data() {
|
|
|
|
return {
|
|
|
|
|
|
|
|
};
|
|
|
|
},
|
|
|
|
computed: {
|
|
|
|
},
|
|
|
|
methods: {
|
2024-02-23 23:19:46 +01:00
|
|
|
toggleGroup(){
|
|
|
|
this.$emit('togglegroup',this.group);
|
|
|
|
}
|
2024-02-18 23:27:57 +01:00
|
|
|
},
|
|
|
|
template: `
|
2024-02-20 20:40:18 +01:00
|
|
|
<tr class='q-groupheading'>
|
2024-02-23 23:19:46 +01:00
|
|
|
<th :colspan="studentinfocolumns"><a href="#" @click.prevent="toggleGroup"
|
|
|
|
><i v-if="expanded" class="fa fa-minus"></i
|
|
|
|
><i v-else class="fa fa-plus"></i
|
|
|
|
> {{group.label}}</a></th>
|
|
|
|
<td :colspan="resultcolumns"></td>
|
|
|
|
</tr>
|
|
|
|
`,
|
|
|
|
});
|
2024-02-18 23:27:57 +01:00
|
|
|
|
2024-02-23 23:19:46 +01:00
|
|
|
Vue.component('q-inforow', {
|
|
|
|
props: {
|
|
|
|
resultcolumns: {
|
|
|
|
type: Number,
|
|
|
|
default: 1
|
|
|
|
},
|
|
|
|
studentinfocolumns: {
|
|
|
|
type: Number,
|
|
|
|
default: 1
|
|
|
|
},
|
|
|
|
},
|
|
|
|
data() {
|
|
|
|
return {
|
|
|
|
};
|
|
|
|
},
|
|
|
|
computed: {
|
|
|
|
},
|
|
|
|
methods: {
|
|
|
|
},
|
|
|
|
template: `
|
|
|
|
<tr class='q-inforow'>
|
|
|
|
<td :colspan="studentinfocolumns"><slot></slot></td>
|
|
|
|
<td :colspan="resultcolumns"></td>
|
2024-02-20 20:40:18 +01:00
|
|
|
</tr>
|
2024-02-18 23:27:57 +01:00
|
|
|
`,
|
|
|
|
});
|
|
|
|
|
2024-02-23 23:19:46 +01:00
|
|
|
|
2024-02-18 23:27:57 +01:00
|
|
|
Vue.component('q-studentresults', {
|
|
|
|
props: {
|
|
|
|
student: {
|
|
|
|
type: Object,
|
|
|
|
},
|
|
|
|
structure: {
|
|
|
|
type: Object,
|
|
|
|
},
|
|
|
|
results: {
|
2024-02-19 11:34:40 +01:00
|
|
|
type: Array,
|
|
|
|
},
|
|
|
|
loading: {
|
|
|
|
type: Boolean,
|
|
|
|
default: false
|
|
|
|
},
|
2024-02-18 23:27:57 +01:00
|
|
|
expansion: {
|
2024-02-19 11:34:40 +01:00
|
|
|
type: Object,
|
2024-02-18 23:27:57 +01:00
|
|
|
},
|
2024-02-23 23:19:46 +01:00
|
|
|
even: {
|
|
|
|
type: Boolean,
|
|
|
|
default: false,
|
|
|
|
}
|
2024-02-18 23:27:57 +01:00
|
|
|
},
|
|
|
|
data() {
|
|
|
|
return {
|
|
|
|
text: strings.studentresults,
|
|
|
|
};
|
|
|
|
},
|
|
|
|
computed: {
|
2024-02-24 16:09:47 +01:00
|
|
|
lastaccess() {
|
|
|
|
if (this.student.lastaccess) {
|
2024-03-04 22:39:29 +01:00
|
|
|
return format_datetime(this.student.lastaccess); // Takes date in milliseconds
|
2024-02-24 16:09:47 +01:00
|
|
|
} else {
|
|
|
|
return this.text.never;
|
|
|
|
}
|
|
|
|
}
|
2024-02-18 23:27:57 +01:00
|
|
|
},
|
|
|
|
methods: {
|
2024-02-19 23:50:47 +01:00
|
|
|
useritems(line) {
|
|
|
|
const list = [];
|
|
|
|
for (const item of line.items) {
|
|
|
|
let newitm = item;
|
|
|
|
for (const itm of this.results) {
|
|
|
|
if (item.id == itm.id) {
|
|
|
|
newitm = itm;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
list.push(newitm);
|
|
|
|
}
|
|
|
|
return list;
|
2024-02-20 20:40:18 +01:00
|
|
|
},
|
|
|
|
conditions(item) {
|
|
|
|
return conditions(item);
|
|
|
|
},
|
2024-02-19 23:50:47 +01:00
|
|
|
},
|
|
|
|
/* https://css-tricks.com/position-sticky-and-table-headers/ */
|
|
|
|
/* TODO: Rework below to make use of tables. Use <Thead> as main element. Then create multiple <tr> as needed for the headers.
|
|
|
|
This should create a much better view than using divs overal.
|
|
|
|
*/
|
|
|
|
template: `
|
2024-02-23 23:19:46 +01:00
|
|
|
<tr :class="'q-student-results userrow ' + (even?'even':'odd')">
|
|
|
|
<td class='q-studentinfo q-name'><fittext maxsize="12pt">{{student.firstname}} {{student.lastname}}</fittext></td>
|
2024-02-24 16:09:47 +01:00
|
|
|
<td class='q-studentinfo q-email'><fittext maxsize="12pt">{{lastaccess}}</fittext></td>
|
2024-02-19 23:50:47 +01:00
|
|
|
<template v-for="p in structure.periods">
|
2024-02-23 23:19:46 +01:00
|
|
|
<template v-if="expansion.periods[p.period.id].expanded && p.lines.length > 0">
|
2024-02-20 20:40:18 +01:00
|
|
|
<template v-for="l in p.lines">
|
2024-02-19 23:50:47 +01:00
|
|
|
<template v-if="expansion.lines[p.period.id][l.line.id].expanded">
|
|
|
|
<template v-for="item in useritems(l)">
|
2024-02-20 20:40:18 +01:00
|
|
|
<td class='q-result overall'
|
2024-02-19 23:50:47 +01:00
|
|
|
><q-courseresult
|
|
|
|
:item="item"
|
|
|
|
:student="student"
|
|
|
|
:loading="loading"
|
2024-02-20 20:40:18 +01:00
|
|
|
></q-courseresult
|
|
|
|
></td>
|
2024-02-19 23:50:47 +01:00
|
|
|
<template v-if="expansion.items[item.id].expanded">
|
|
|
|
<td v-for="(c,idx) in conditions(item)"
|
|
|
|
class='q-result'
|
|
|
|
><q-conditionresult
|
|
|
|
:item="item"
|
|
|
|
:conditionidx="idx"
|
|
|
|
:student="student"
|
|
|
|
:loading="loading"
|
2024-02-20 20:40:18 +01:00
|
|
|
></q-conditionresult
|
|
|
|
></td>
|
2024-02-19 23:50:47 +01:00
|
|
|
</template>
|
|
|
|
</template>
|
|
|
|
</template>
|
|
|
|
<td v-else class='q-result collapsed'> </td>
|
|
|
|
</template>
|
|
|
|
</template>
|
2024-02-23 23:19:46 +01:00
|
|
|
<td v-else colspan="2" class='q-result collapsed'> </td>
|
2024-02-19 23:50:47 +01:00
|
|
|
</template>
|
|
|
|
</tr>
|
|
|
|
`,
|
|
|
|
});
|
|
|
|
|
|
|
|
Vue.component('q-courseresult', {
|
|
|
|
props: {
|
|
|
|
student: {
|
|
|
|
type: Object,
|
|
|
|
},
|
|
|
|
item: {
|
|
|
|
type: Object,
|
|
|
|
},
|
|
|
|
loading: {
|
|
|
|
type: Boolean,
|
|
|
|
default: false
|
|
|
|
},
|
|
|
|
},
|
|
|
|
data() {
|
|
|
|
return {
|
|
|
|
text: strings.studentresults,
|
|
|
|
};
|
|
|
|
},
|
|
|
|
computed: {
|
|
|
|
hasprogressinfo() {
|
|
|
|
const course = this.item.course;
|
|
|
|
if (!course.enrolled) {
|
2024-02-18 23:27:57 +01:00
|
|
|
return false;
|
|
|
|
} else {
|
2024-02-23 23:19:46 +01:00
|
|
|
return (course.completion || course.competency || course.grades)?true:false;
|
2024-02-18 23:27:57 +01:00
|
|
|
}
|
|
|
|
},
|
2024-02-19 23:50:47 +01:00
|
|
|
completion_icon() {
|
|
|
|
const completion = this.item.completion;
|
2024-02-18 23:27:57 +01:00
|
|
|
switch(completion){
|
|
|
|
default: // case "incomplete"
|
|
|
|
return "circle-o";
|
|
|
|
case "pending":
|
|
|
|
return "question-circle";
|
|
|
|
case "failed":
|
|
|
|
return "times-circle";
|
|
|
|
case "progress":
|
|
|
|
return "exclamation-circle";
|
|
|
|
case "completed":
|
|
|
|
return "check-circle";
|
|
|
|
case "good":
|
|
|
|
return "check-circle";
|
|
|
|
case "excellent":
|
|
|
|
return "check-circle";
|
|
|
|
}
|
|
|
|
},
|
2024-02-19 23:50:47 +01:00
|
|
|
},
|
|
|
|
methods: {
|
|
|
|
},
|
|
|
|
template: `
|
2024-02-20 20:40:18 +01:00
|
|
|
<span class='q-courseresult'>
|
2024-02-19 23:50:47 +01:00
|
|
|
<template v-if="loading">
|
|
|
|
<div class="spinner-border spinner-border-sm text-info" role="status"></div>
|
|
|
|
</template>
|
|
|
|
<template v-else-if='!item.course.enrolled'>
|
|
|
|
<i v-b-popover.top
|
2024-02-20 20:40:18 +01:00
|
|
|
class="fa fa-exclamation-triangle t-not-enrolled-alert"
|
2024-02-19 23:50:47 +01:00
|
|
|
:title="text.student_not_tracked"></i>
|
|
|
|
</template>
|
2024-03-07 22:44:29 +01:00
|
|
|
<template v-else-if="item.lineenrolled" >
|
2024-02-19 23:50:47 +01:00
|
|
|
<i v-b-popover.top
|
2024-02-20 20:40:18 +01:00
|
|
|
:class="'fa fa-'+completion_icon+
|
2024-02-19 23:50:47 +01:00
|
|
|
' r-completion-'+item.completion"
|
|
|
|
:title="text['completion_'+item.completion]"></i>
|
|
|
|
</template>
|
2024-02-20 20:40:18 +01:00
|
|
|
</span>
|
2024-02-19 23:50:47 +01:00
|
|
|
`,
|
|
|
|
});
|
|
|
|
|
|
|
|
Vue.component('q-conditionresult', {
|
|
|
|
props: {
|
|
|
|
student: {
|
|
|
|
type: Object,
|
|
|
|
},
|
|
|
|
item: {
|
|
|
|
type: Object,
|
|
|
|
},
|
|
|
|
loading: {
|
|
|
|
type: Boolean,
|
|
|
|
default: false
|
|
|
|
},
|
|
|
|
conditionidx: {
|
|
|
|
type: Number,
|
|
|
|
}
|
|
|
|
},
|
|
|
|
data() {
|
|
|
|
return {
|
|
|
|
text: strings.studentresults,
|
|
|
|
};
|
|
|
|
},
|
|
|
|
computed: {
|
|
|
|
conditions() {
|
2024-02-20 20:40:18 +01:00
|
|
|
return conditions(this.item);
|
2024-02-19 23:50:47 +01:00
|
|
|
},
|
|
|
|
condition() {
|
|
|
|
if (this.conditionidx >= 0 && this.conditionidx < this.conditions.length) {
|
|
|
|
return this.conditions[this.conditionidx];
|
|
|
|
} else {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
hasprogressinfo() {
|
|
|
|
const course = this.item.course;
|
|
|
|
if (!course.enrolled) {
|
|
|
|
return false;
|
|
|
|
} else {
|
|
|
|
return (course.completion || course.competency || course.grades);
|
|
|
|
}
|
|
|
|
},
|
2024-02-23 09:20:10 +01:00
|
|
|
completion_icon() {
|
|
|
|
const completion = this.condition_completion();
|
2024-02-18 23:27:57 +01:00
|
|
|
switch(completion){
|
|
|
|
default: // case "incomplete"
|
2024-02-19 23:50:47 +01:00
|
|
|
return "circle-o";
|
|
|
|
case "pending":
|
|
|
|
return "question-circle";
|
2024-02-18 23:27:57 +01:00
|
|
|
case "failed":
|
2024-02-19 23:50:47 +01:00
|
|
|
return "times-circle";
|
2024-02-18 23:27:57 +01:00
|
|
|
case "progress":
|
2024-02-19 23:50:47 +01:00
|
|
|
return "exclamation-circle";
|
2024-02-18 23:27:57 +01:00
|
|
|
case "completed":
|
2024-02-19 23:50:47 +01:00
|
|
|
return "check-circle";
|
2024-02-18 23:27:57 +01:00
|
|
|
case "good":
|
2024-02-19 23:50:47 +01:00
|
|
|
return "check-circle";
|
2024-02-18 23:27:57 +01:00
|
|
|
case "excellent":
|
2024-02-19 23:50:47 +01:00
|
|
|
return "check-circle";
|
2024-02-18 23:27:57 +01:00
|
|
|
}
|
|
|
|
},
|
2024-02-23 09:20:10 +01:00
|
|
|
condition_value() {
|
|
|
|
const course = this.item.course;
|
|
|
|
if (course.competency) {
|
|
|
|
if (this.condition.grade) {
|
|
|
|
// Return grade if possible.
|
|
|
|
return this.condition.grade;
|
|
|
|
}
|
|
|
|
} else if(course.completion) {
|
|
|
|
if (this.condition.grade) {
|
|
|
|
// Return grade if possible.
|
|
|
|
return this.condition.grade;
|
|
|
|
}
|
|
|
|
} else if(course.grades) {
|
|
|
|
return this.condition.grade;
|
|
|
|
}
|
|
|
|
// Fallback to completion icon.
|
|
|
|
const icon = this.completion_icon();
|
|
|
|
return `<i class='fa fa-${icon}'></i>`;
|
|
|
|
},
|
|
|
|
condition_completion() {
|
|
|
|
// Unify completion information
|
|
|
|
const course = this.item.course;
|
|
|
|
if (course.competency) {
|
|
|
|
const competency = this.condition;
|
|
|
|
if (competency.proficient && competency.courseproficient) {
|
|
|
|
return "completed";
|
|
|
|
} else if (competency.proficient) {
|
|
|
|
return "completed";
|
|
|
|
} else if (competency.proficient === false) {
|
|
|
|
return "failed";
|
|
|
|
} else if (competency.progress) {
|
|
|
|
return "progress";
|
|
|
|
} else {
|
|
|
|
return "incomplete";
|
|
|
|
}
|
|
|
|
} else if(course.completion) {
|
|
|
|
return this.condition.status;
|
|
|
|
} else if(course.grades) {
|
|
|
|
return this.condition.completion;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
},
|
|
|
|
methods: {
|
2024-02-18 23:27:57 +01:00
|
|
|
},
|
2024-02-19 23:50:47 +01:00
|
|
|
// TODO: Show actual grades when relevant at all (don;t forget the grade point completion requirement)
|
2024-02-18 23:27:57 +01:00
|
|
|
template: `
|
2024-02-20 20:40:18 +01:00
|
|
|
<span class='q-conditionresult'>
|
2024-03-07 22:44:29 +01:00
|
|
|
<fittext v-if="item.lineenrolled" maxsize="10pt" singleline dynamic>
|
2024-02-23 09:20:10 +01:00
|
|
|
<template v-if="loading">
|
|
|
|
<div class="spinner-border spinner-border-sm text-info" role="status"></div>
|
|
|
|
</template>
|
|
|
|
<template v-else-if='!item.course.enrolled'>
|
|
|
|
<i class="fa fa-ellipsis-h"
|
|
|
|
:title="text.student_not_tracked"></i>
|
|
|
|
</template>
|
|
|
|
<template v-else>
|
|
|
|
<span
|
|
|
|
:class="'r-completion-'+condition_completion"
|
|
|
|
:title="text['completion_'+condition_completion]"
|
|
|
|
>{{condition_value}}</span
|
|
|
|
>
|
|
|
|
</template>
|
|
|
|
</fittext>
|
2024-02-20 20:40:18 +01:00
|
|
|
</span>
|
2024-02-18 23:27:57 +01:00
|
|
|
`,
|
|
|
|
});
|
2024-02-18 13:47:08 +01:00
|
|
|
|
2024-02-19 23:50:47 +01:00
|
|
|
|
|
|
|
|
2024-02-18 13:47:08 +01:00
|
|
|
},
|
|
|
|
};
|