Reworked to use tables. Untested.

This commit is contained in:
PMKuipers 2024-02-19 23:50:47 +01:00
parent 4bb98b73eb
commit ed353540f0
3 changed files with 306 additions and 173 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -6,18 +6,12 @@
/*eslint-env es6*/
// 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 {format_date,studyplanPageTiming,studyplanTiming} from './util/date-helper';
import {call} from 'core/ajax';
import notification from 'core/notification';
import {svgarcpath} from './util/svgarc';
import Debugger from './util/debugger';
import Config from 'core/config';
import {ProcessStudyplan, ProcessStudyplanPage, objCopy} from './studyplan-processor';
import TSComponents from './treestudyplan-components';
import {eventTypes as editSwEventTypes} from 'core/edit_switch';
// Make π available as a constant
const π = Math.PI;
@ -281,51 +275,119 @@ export default {
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() {
},
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: `
<div class='q-header'>
<div class='q-studentname heading'><span>{{text.students}}</span></div>
<div v-for="p in structure.periods" class='q-period-heading' >
<div class='q-header-title'>
<a v-if="expansion.periods[p.period.id].expanded"
href='#' @click.prevent="$emit('expansion','periods',p.period.id,false);"><i class='fa fa-minus-square'></i></a>
<a v-else
href='#' @click.prevent="$emit('expansion','periods',p.period.id,true);"><i class='fa fa-plus-square'></i></a>
{{ p.period.fullname}}
</div>
<div v-if="expansion.periods[p.period.id].expanded" class='q-header-details'>
<div class='q-line-heading' v-for='l in p.lines'>
<div class='q-header-title'><span v-html="l.line.shortname"></span></div>
<div v-if="l.items.length == 1 || expansion.lines[p.period.id][l.line.id].expanded" class='q-header-details'>
<div class='q-item-heading' v-for='item in l.items'>
<div class='q-header-title'>
<a v-if="expansion.items[item.id].expanded"
<thead class='q-header'>
<tr> <!-- period heading -->
<th rowspan='3' class='q-studentname'><span>{{text.students}}</span></th>
<th v-for="p in structure.periods"
class='q-period-heading'
:colspan='colspanPeriod(p)'
:rowspan='(expansion.periods[p.period.id].expanded)?1:3'
><a v-if="expansion.periods[p.period.id].expanded"
href='#' @click.prevent="$emit('expansion','periods',p.period.id,false);"
><i class='fa fa-minus-square'></i></a
><a v-else
href='#' @click.prevent="$emit('expansion','periods',p.period.id,true);"
><i class='fa fa-plus-square'></i></a
>{{ p.period.fullname}}<
/th>
</tr>
<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);"
><i class='fa fa-minus-square'></i></a>
<a v-else
><i class='fa fa-minus-square'></i></a
><a v-else
href='#' @click.prevent="$emit('expansion','items',item.id,true);"
><i class='fa fa-plus-square'></i></a>
{{ item.course.displayname}}
</div>
<div v-if="expansion.items[item.id].expanded" class='q-header-details'>
<div class='q-condition-heading'>
{{ text.overall }}
</div>
<div v-for="c in conditions(item)" class="q-condition-heading" >
<span v-html="c.name"></span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
><i class='fa fa-plus-square'></i></a
><span v-html="item.course.displayname"></span><
/th>
</template>
</template>
</template>
</template>
</tr>
<tr> <!-- condition 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">
<template v-for="item in l.items">
<th class='q-condition-heading overall
>{{ text.overall }}<
/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: {
},
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) {
const list = [];
for (const item of line.items) {
@ -465,72 +460,210 @@ export default {
This should create a much better view than using divs overal.
*/
template: `
<div class='q-student-results'>
<div class='q-studentname '><span>{{student.firstname}} {{student.lastname}}</span></div>
<div v-for="p in structure.periods" class='q-period-results' >
<div v-if="expansion.periods[p.period.id].expanded" class='q-result-details'>
<div class='q-line-results' 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'>
<div class='q-item-results' v-for='item in useritems(l)'>
<div v-if="expansion.items[item.id].expanded" class='q-result-details'>
<div 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 class='q-condition-heading' v-for='c in conditions(item)'>
<tr class='q-student-results'>
<th class='q-studentname'><span>{{student.firstname}} {{student.lastname}}</span></th>
<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">
<template v-for="item in useritems(l)">
<td class='q-result overall
><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'>&nbsp;</td>
</template>
</template>
<td v-else class='q-result collapsed'>&nbsp;</td>
</template>
</tr>
`,
});
</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>
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">
<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>
<i v-b-popover.top
:class="'r-course-result fa fa-'+completion_icon+
' r-completion-'+item.completion"
:title="text['completion_'+item.completion]"></i>
</template>
</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>
`,
});
},
};