Reworked to use tables. Untested.
This commit is contained in:
parent
4bb98b73eb
commit
ed353540f0
3 changed files with 306 additions and 173 deletions
2
amd/build/studyplan-report-components.min.js
vendored
2
amd/build/studyplan-report-components.min.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -6,18 +6,12 @@
|
||||||
/*eslint-env es6*/
|
/*eslint-env es6*/
|
||||||
// Put this file in path/to/plugin/amd/src
|
// Put this file in path/to/plugin/amd/src
|
||||||
|
|
||||||
import {SimpleLine} from './simpleline/simpleline';
|
|
||||||
import {get_strings} from 'core/str';
|
|
||||||
import {load_strings} from './util/string-helper';
|
import {load_strings} from './util/string-helper';
|
||||||
import {format_date,studyplanPageTiming,studyplanTiming} from './util/date-helper';
|
|
||||||
import {call} from 'core/ajax';
|
import {call} from 'core/ajax';
|
||||||
import notification from 'core/notification';
|
import notification from 'core/notification';
|
||||||
import {svgarcpath} from './util/svgarc';
|
|
||||||
import Debugger from './util/debugger';
|
import Debugger from './util/debugger';
|
||||||
import Config from 'core/config';
|
import Config from 'core/config';
|
||||||
import {ProcessStudyplan, ProcessStudyplanPage, objCopy} from './studyplan-processor';
|
|
||||||
import TSComponents from './treestudyplan-components';
|
import TSComponents from './treestudyplan-components';
|
||||||
import {eventTypes as editSwEventTypes} from 'core/edit_switch';
|
|
||||||
|
|
||||||
// Make π available as a constant
|
// Make π available as a constant
|
||||||
const π = Math.PI;
|
const π = Math.PI;
|
||||||
|
@ -281,51 +275,119 @@ export default {
|
||||||
|
|
||||||
return list;
|
return list;
|
||||||
},
|
},
|
||||||
|
colspanPeriod(period) {
|
||||||
|
if (this.expansion.periods[period.id].expanded) {
|
||||||
|
let sum = 0;
|
||||||
|
for (const l of period.lines) {
|
||||||
|
sum += this.colspanLine(period,l);
|
||||||
|
}
|
||||||
|
return sum;
|
||||||
|
} else {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
colspanLine(period,line) {
|
||||||
|
if (this.expansion.lines[period.id][line.id].expanded) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
|
|
||||||
},
|
},
|
||||||
updated() {
|
updated() {
|
||||||
},
|
},
|
||||||
|
/* 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: `
|
template: `
|
||||||
<div class='q-header'>
|
<thead class='q-header'>
|
||||||
<div class='q-studentname heading'><span>{{text.students}}</span></div>
|
<tr> <!-- period heading -->
|
||||||
<div v-for="p in structure.periods" class='q-period-heading' >
|
<th rowspan='3' class='q-studentname'><span>{{text.students}}</span></th>
|
||||||
<div class='q-header-title'>
|
<th v-for="p in structure.periods"
|
||||||
<a v-if="expansion.periods[p.period.id].expanded"
|
class='q-period-heading'
|
||||||
href='#' @click.prevent="$emit('expansion','periods',p.period.id,false);"><i class='fa fa-minus-square'></i></a>
|
:colspan='colspanPeriod(p)'
|
||||||
<a v-else
|
:rowspan='(expansion.periods[p.period.id].expanded)?1:3'
|
||||||
href='#' @click.prevent="$emit('expansion','periods',p.period.id,true);"><i class='fa fa-plus-square'></i></a>
|
><a v-if="expansion.periods[p.period.id].expanded"
|
||||||
{{ p.period.fullname}}
|
href='#' @click.prevent="$emit('expansion','periods',p.period.id,false);"
|
||||||
</div>
|
><i class='fa fa-minus-square'></i></a
|
||||||
<div v-if="expansion.periods[p.period.id].expanded" class='q-header-details'>
|
><a v-else
|
||||||
<div class='q-line-heading' v-for='l in p.lines'>
|
href='#' @click.prevent="$emit('expansion','periods',p.period.id,true);"
|
||||||
<div class='q-header-title'><span v-html="l.line.shortname"></span></div>
|
><i class='fa fa-plus-square'></i></a
|
||||||
<div v-if="l.items.length == 1 || expansion.lines[p.period.id][l.line.id].expanded" class='q-header-details'>
|
>{{ p.period.fullname}}<
|
||||||
<div class='q-item-heading' v-for='item in l.items'>
|
/th>
|
||||||
<div class='q-header-title'>
|
</tr>
|
||||||
<a v-if="expansion.items[item.id].expanded"
|
<tr> <!-- line heading -->
|
||||||
|
<template v-for="p in structure.periods">
|
||||||
|
<template v-if="expansion.periods[p.period.id].expanded">
|
||||||
|
<th v-for='l in p.lines"
|
||||||
|
class='q-line-heading'
|
||||||
|
:colspan="colspanLine(p,l)"
|
||||||
|
><span v-html="l.line.shortname"></span
|
||||||
|
><th>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</tr>
|
||||||
|
<tr> <!-- item heading -->
|
||||||
|
<template v-for="p in structure.periods">
|
||||||
|
<template v-if="expansion.periods[p.period.id].expanded">
|
||||||
|
<template v-for='l in p.lines">
|
||||||
|
<template v-if="expansion.lines[p.period.id][l.line.id].expanded">
|
||||||
|
<th v-for="item in l.items"
|
||||||
|
class='q-item-heading'
|
||||||
|
:colspan="colspanitem(item)"
|
||||||
|
:rowspan='(expansion.items[item.id].expanded)?1:2'
|
||||||
|
><a v-if="expansion.items[item.id].expanded"
|
||||||
href='#' @click.prevent="$emit('expansion','items',item.id,false);"
|
href='#' @click.prevent="$emit('expansion','items',item.id,false);"
|
||||||
><i class='fa fa-minus-square'></i></a>
|
><i class='fa fa-minus-square'></i></a
|
||||||
<a v-else
|
><a v-else
|
||||||
href='#' @click.prevent="$emit('expansion','items',item.id,true);"
|
href='#' @click.prevent="$emit('expansion','items',item.id,true);"
|
||||||
><i class='fa fa-plus-square'></i></a>
|
><i class='fa fa-plus-square'></i></a
|
||||||
{{ item.course.displayname}}
|
><span v-html="item.course.displayname"></span><
|
||||||
</div>
|
/th>
|
||||||
<div v-if="expansion.items[item.id].expanded" class='q-header-details'>
|
</template>
|
||||||
<div class='q-condition-heading'>
|
</template>
|
||||||
{{ text.overall }}
|
</template>
|
||||||
</div>
|
</template>
|
||||||
<div v-for="c in conditions(item)" class="q-condition-heading" >
|
</tr>
|
||||||
<span v-html="c.name"></span>
|
<tr> <!-- condition heading -->
|
||||||
</div>
|
<template v-for="p in structure.periods">
|
||||||
</div>
|
<template v-if="expansion.periods[p.period.id].expanded">
|
||||||
</div>
|
<template v-for='l in p.lines">
|
||||||
</div>
|
<template v-if="expansion.lines[p.period.id][l.line.id].expanded">
|
||||||
</div>
|
<template v-for="item in l.items">
|
||||||
</div>
|
<th class='q-condition-heading overall
|
||||||
</div>
|
>{{ text.overall }}<
|
||||||
</div>
|
/th>
|
||||||
|
<template v-if="expansion.items[item.id].expanded">
|
||||||
|
<th v-for="c in conditions(item)"
|
||||||
|
class='q-condition-heading'
|
||||||
|
><span v-html="c.name"></span
|
||||||
|
></th>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
`,
|
`,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -378,73 +440,6 @@ export default {
|
||||||
computed: {
|
computed: {
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
hasprogressinfo(item) {
|
|
||||||
if (!item.course.enrolled) {
|
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
return (item.course.completion || item.course.competency || item.course.grades);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
completion_icon(completion) {
|
|
||||||
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";
|
|
||||||
}
|
|
||||||
},
|
|
||||||
circle_icon(completion) {
|
|
||||||
switch(completion){
|
|
||||||
default: // case "incomplete"
|
|
||||||
return null;
|
|
||||||
case "failed":
|
|
||||||
return "times";
|
|
||||||
case "progress":
|
|
||||||
return "";
|
|
||||||
case "completed":
|
|
||||||
return "check";
|
|
||||||
case "good":
|
|
||||||
return "check";
|
|
||||||
case "excellent":
|
|
||||||
return "check";
|
|
||||||
}
|
|
||||||
},
|
|
||||||
courseprogress(item) {
|
|
||||||
if (!item.course.enrolled) {
|
|
||||||
return 0;
|
|
||||||
} else if(item.course.completion) {
|
|
||||||
return (item.course.completion.progress / item.course.completion.count);
|
|
||||||
} else if(item.course.competency) {
|
|
||||||
return (item.course.competency.progress / item.course.competency.count);
|
|
||||||
} else if(item.course.grades) {
|
|
||||||
return (this.gradeprogress(item.course.grades) / item.course.grades.length);
|
|
||||||
} else {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
gradeprogress(grades) {
|
|
||||||
let progress = 0;
|
|
||||||
for (const ix in grades) {
|
|
||||||
const g = grades[ix];
|
|
||||||
if (["completed","excellent","good"].includes(g.completion)) {
|
|
||||||
progress++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return progress;
|
|
||||||
},
|
|
||||||
conditions(item) {
|
|
||||||
|
|
||||||
},
|
|
||||||
useritems(line) {
|
useritems(line) {
|
||||||
const list = [];
|
const list = [];
|
||||||
for (const item of line.items) {
|
for (const item of line.items) {
|
||||||
|
@ -465,15 +460,93 @@ export default {
|
||||||
This should create a much better view than using divs overal.
|
This should create a much better view than using divs overal.
|
||||||
*/
|
*/
|
||||||
template: `
|
template: `
|
||||||
<div class='q-student-results'>
|
<tr class='q-student-results'>
|
||||||
<div class='q-studentname '><span>{{student.firstname}} {{student.lastname}}</span></div>
|
<th class='q-studentname'><span>{{student.firstname}} {{student.lastname}}</span></th>
|
||||||
<div v-for="p in structure.periods" class='q-period-results' >
|
<template v-for="p in structure.periods">
|
||||||
<div v-if="expansion.periods[p.period.id].expanded" class='q-result-details'>
|
<template v-if="expansion.periods[p.period.id].expanded">
|
||||||
<div class='q-line-results' v-for='l in p.lines'>
|
<template v-for='l in p.lines">
|
||||||
<div v-if="l.items.length == 1 || expansion.lines[p.period.id][l.line.id].expanded" class='q-result-details'>
|
<template v-if="expansion.lines[p.period.id][l.line.id].expanded">
|
||||||
<div class='q-item-results' v-for='item in useritems(l)'>
|
<template v-for="item in useritems(l)">
|
||||||
<div v-if="expansion.items[item.id].expanded" class='q-result-details'>
|
<td class='q-result overall
|
||||||
<div class='q-result q-overviewresult'>
|
><q-courseresult
|
||||||
|
:item="item"
|
||||||
|
:student="student"
|
||||||
|
:loading="loading"
|
||||||
|
></q-courseresult><
|
||||||
|
/td>
|
||||||
|
<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"
|
||||||
|
></q-conditionresult><
|
||||||
|
/td>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
<td v-else class='q-result collapsed'> </td>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
<td v-else class='q-result collapsed'> </td>
|
||||||
|
</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) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
return (course.completion || course.competency || course.grades);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
completion_icon() {
|
||||||
|
const completion = this.item.completion;
|
||||||
|
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";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
},
|
||||||
|
template: `
|
||||||
|
<div class='q-courseresult'>
|
||||||
<template v-if="loading">
|
<template v-if="loading">
|
||||||
<div class="spinner-border spinner-border-sm text-info" role="status"></div>
|
<div class="spinner-border spinner-border-sm text-info" role="status"></div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -482,55 +555,115 @@ export default {
|
||||||
class="r-course-result fa fa-exclamation-triangle t-not-enrolled-alert"
|
class="r-course-result fa fa-exclamation-triangle t-not-enrolled-alert"
|
||||||
:title="text.student_not_tracked"></i>
|
:title="text.student_not_tracked"></i>
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if='hasprogressinfo(item)'>
|
|
||||||
<i v-b-popover.top
|
|
||||||
:class="'r-course-result fa fa-'+completion_icon(item.completion)+
|
|
||||||
' r-completion-'+item.completion"
|
|
||||||
:title="text['completion_'+item.completion]"></i>
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<i v-b-popover.top
|
<i v-b-popover.top
|
||||||
:class="'r-course-result fa fa-'+completion_icon(item.completion)+
|
:class="'r-course-result fa fa-'+completion_icon+
|
||||||
' r-completion-'+item.completion"
|
' r-completion-'+item.completion"
|
||||||
:title="text['completion_'+item.completion]"></i>
|
:title="text['completion_'+item.completion]"></i>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
<div class='q-condition-heading' v-for='c in conditions(item)'>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-else class='q-result q-overviewresult'>
|
|
||||||
<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
|
|
||||||
class="r-course-result fa fa-exclamation-triangle t-not-enrolled-alert"
|
|
||||||
:title="text.student_not_tracked"></i>
|
|
||||||
</template>
|
|
||||||
<template v-else-if='hasprogressinfo(item)'>
|
|
||||||
<i v-b-popover.top
|
|
||||||
:class="'r-course-result fa fa-'+completion_icon(item.completion)+
|
|
||||||
' r-completion-'+item.completion"
|
|
||||||
:title="text['completion_'+item.completion]"></i>
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
<i v-b-popover.top
|
|
||||||
:class="'r-course-result fa fa-'+completion_icon(item.completion)+
|
|
||||||
' r-completion-'+item.completion"
|
|
||||||
:title="text['completion_'+item.completion]"></i>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`,
|
`,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
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() {
|
||||||
|
const course = this.item.course;
|
||||||
|
const list = [];
|
||||||
|
debug.info("Determining conditions", course);
|
||||||
|
if (course.completion) {
|
||||||
|
debug.info("Has Competencies");
|
||||||
|
return course.competencies;
|
||||||
|
} else if(course.completion) {
|
||||||
|
debug.info("Has Core completion");
|
||||||
|
const list = [];
|
||||||
|
for (const cnd of course.completion.conditions) {
|
||||||
|
for (const itm of cnd.items) {
|
||||||
|
list.push(itm);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
} else if(course.grades) {
|
||||||
|
return course.grades;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
completion_icon(completion) {
|
||||||
|
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";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// TODO: Show actual grades when relevant at all (don;t forget the grade point completion requirement)
|
||||||
|
template: `
|
||||||
|
<div class='q-conditionresult'>
|
||||||
|
<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
|
||||||
|
class="r-course-result fa fa-exclamation-triangle t-not-enrolled-alert"
|
||||||
|
:title="text.student_not_tracked"></i>
|
||||||
|
</template>
|
||||||
|
<i v-b-popover.top
|
||||||
|
:class="'r-course-result fa fa-'+completion_icon(item.completion)+
|
||||||
|
' r-completion-'+item.completion"
|
||||||
|
:title="text['completion_'+item.completion]"></i>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
},
|
},
|
||||||
};
|
};
|
Loading…
Reference in a new issue