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*/ /*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,72 +460,210 @@ 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
<template v-if="loading"> :item="item"
<div class="spinner-border spinner-border-sm text-info" role="status"></div> :student="student"
</template> :loading="loading"
<template v-else-if='!item.course.enrolled'> ></q-courseresult><
<i v-b-popover.top /td>
class="r-course-result fa fa-exclamation-triangle t-not-enrolled-alert" <template v-if="expansion.items[item.id].expanded">
:title="text.student_not_tracked"></i> <td v-for="(c,idx) in conditions(item)"
</template> class='q-result'
<template v-else-if='hasprogressinfo(item)'> ><q-conditionresult
<i v-b-popover.top :item="item"
:class="'r-course-result fa fa-'+completion_icon(item.completion)+ :conditionidx="idx"
' r-completion-'+item.completion" :student="student"
:title="text['completion_'+item.completion]"></i> :loading="loading"
</template> ></q-conditionresult><
<template v-else> /td>
<i v-b-popover.top </template>
:class="'r-course-result fa fa-'+completion_icon(item.completion)+ </template>
' r-completion-'+item.completion" </template>
:title="text['completion_'+item.completion]"></i> <td v-else class='q-result collapsed'>&nbsp;</td>
</template> </template>
</div> </template>
<div class='q-condition-heading' v-for='c in conditions(item)'> <td v-else class='q-result collapsed'>&nbsp;</td>
</template>
</tr>
`,
});
Vue.component('q-courseresult', {
</div> props: {
</div> student: {
<div v-else class='q-result q-overviewresult'> type: Object,
<template v-if="loading"> },
<div class="spinner-border spinner-border-sm text-info" role="status"></div> item: {
</template> type: Object,
<template v-else-if='!item.course.enrolled'> },
<i v-b-popover.top loading: {
class="r-course-result fa fa-exclamation-triangle t-not-enrolled-alert" type: Boolean,
:title="text.student_not_tracked"></i> default: false
</template> },
<template v-else-if='hasprogressinfo(item)'> },
<i v-b-popover.top data() {
:class="'r-course-result fa fa-'+completion_icon(item.completion)+ return {
' r-completion-'+item.completion" text: strings.studentresults,
:title="text['completion_'+item.completion]"></i> };
</template> },
<template v-else> computed: {
<i v-b-popover.top hasprogressinfo() {
:class="'r-course-result fa fa-'+completion_icon(item.completion)+ const course = this.item.course;
' r-completion-'+item.completion" if (!course.enrolled) {
:title="text['completion_'+item.completion]"></i> return false;
</template> } else {
</div> return (course.completion || course.competency || course.grades);
</div> }
</div> },
</div> completion_icon() {
</div> const completion = this.item.completion;
</div> 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> </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>
`,
});
}, },
}; };