diff --git a/amd/build/studyplan-report-components.min.js b/amd/build/studyplan-report-components.min.js
index 1bfa403..371e7b6 100644
--- a/amd/build/studyplan-report-components.min.js
+++ b/amd/build/studyplan-report-components.min.js
@@ -1,3 +1,3 @@
-define("local_treestudyplan/studyplan-report-components",["exports","./util/string-helper","core/ajax","core/notification","./util/debugger","core/config","./treestudyplan-components","./util/fittext-vue","./util/date-helper"],(function(_exports,_stringHelper,_ajax,_notification,_debugger,_config,_treestudyplanComponents,_fittextVue,_dateHelper){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_notification=_interopRequireDefault(_notification),_debugger=_interopRequireDefault(_debugger),_config=_interopRequireDefault(_config),_treestudyplanComponents=_interopRequireDefault(_treestudyplanComponents),_fittextVue=_interopRequireDefault(_fittextVue);new _debugger.default("treestudyplan-viewer"),Math.PI;function striptags(html){const tmp=document.createElement("DIV");tmp.innerHTML=html;const text=tmp.textContent||tmp.innerText;return tmp.remove(),text}function conditions(item){const course=item.course,list=[];if(course.competency)for(const cmp of course.competency.competencies)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)g.selected&&list.push(g);return list}var _default={install(Vue){Vue.use(_treestudyplanComponents.default),Vue.use(_fittextVue.default);let strings=(0,_stringHelper.load_strings)({report:{loading:"loadinghelp@core",studyplan_past:"studyplan_past",studyplan_present:"studyplan_present",studyplan_future:"studyplan_future",back:"back"},invalid:{error:"error"},header:{overall:"overall",students:"students@core",firstname:"firstname@core",lastname:"lastname@core",email:"email@core",lastaccess:"lastaccess@core"},studentresults:{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",never:"never@core"}});Vue.component("q-studyplanreport",{props:{structure:{type:Object}},data:()=>({students:[],studentresults:{},studentsloading:!0,expansioninfo:{periods:{},lines:{},items:{}},groupinfo:{},sorting:{header:"lastname",asc:!0}}),created(){},watch:{structure:{immediate:!0,handler(structure){this.loadStudents();let firstperiod=!0;for(const period of structure.periods){const pid=period.period.id;this.expansioninfo.periods[pid]||(this.$set(this.expansioninfo.periods,pid,{expanded:!!(firstperiod&&period.lines.length>0)}),this.$set(this.expansioninfo.lines,period.period.id,{}));for(const line of period.lines){const lid=line.line.id;this.expansioninfo.lines[lid]||this.$set(this.expansioninfo.lines[pid],lid,{expanded:!0});for(const item of line.items)this.expansioninfo.items[item.id]||this.$set(this.expansioninfo.items,item.id,{expanded:!1})}firstperiod=!1}}}},computed:{sortedstudents(){const students=this.students;for(const group of this.students)group.users.sort(((a,b)=>{let d=a,e=b;if(this.sorting.asc||(d=b,e=a),"lastaccess"==this.sorting.header){return(d[this.sorting.header]?d[this.sorting.header]:0)-(e[this.sorting.header]?e[this.sorting.header]:0)}return String(d[this.sorting.header]).localeCompare(String(e[this.sorting.header]))}));return students},resultColCount(){let count=0;for(const period of this.structure.periods){const pid=period.period.id;if(this.expansioninfo.periods[pid].expanded)for(const line of period.lines){const lid=line.line.id;if(this.expansioninfo.lines[pid][lid].expanded)for(const item of line.items)this.expansioninfo.items[item.id].expanded?count+=1+conditions(item).length:count+=1;else count+=1}else count+=2}return count}},methods:{loadStudents(){const self=this;self.studentsloading=!0,(0,_ajax.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){self.$set(self.groupinfo,group.id,{expanded:!0});for(const student of group.users)self.$set(self.studentresults,student.id,{loading:!0,results:[]}),(0,_ajax.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=!1,self.studentresults[student.id].results=response})).catch(_notification.default.exception)}self.studentsloading=!1})).catch(_notification.default.exception)},expansionChanged(parm,id,val){"lines"==(parm="p"==parm[0]?"periods":"l"==parm[0]?"lines":"items")?this.expansioninfo[parm][id[0]][id[1]].expanded=val:this.expansioninfo[parm][id].expanded=val},groupExpansionChanged(group){this.groupinfo[group.id].expanded=!this.groupinfo[group.id].expanded},toggleSort(header){this.sorting.header==header?this.sorting.asc=!this.sorting.asc:(this.sorting.header=header,this.sorting.asc=!0)}},template:'\n
\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
\n '}),Vue.component("q-header",{props:{structure:{type:Object},sorting:{type:Object},expansion:{type:Object}},data:()=>({text:strings.header}),computed:{},methods:{conditions:item=>function(item){const course=item.course,list=[];if(course.competency)for(const cmp of course.competency.competencies)list.push({name:cmp.details?`${cmp.title} - ${cmp.details}`:cmp.title,tooltip:cmp.description});else if(course.completion)for(const cnd of course.completion.conditions)for(const itm of cnd.items)list.push({name:itm.title,tooltip:`${itm.details.type}: ${itm.details.requirement}`});else if(course.grades)for(const g of course.grades)g.selected&&list.push({name:g.name,tooltip:`${g.typename}: ${striptags(g.name)}`});return list}(item),colspanPeriod(period){const pid=period.period.id;if(this.expansion.periods[pid].expanded){let sum=0;for(const l of period.lines)sum+=this.colspanLine(period,l);return sum}return 2},colspanLine(period,line){const pid=period.period.id,lid=line.line.id;if(this.expansion.lines[pid][lid].expanded){let sum=0;for(const i of line.items)sum+=this.colspanItem(i);return sum}return 1},colspanItem(item){if(this.expansion.items[item.id].expanded){return 1+this.conditions(item).length}return 1},togglePeriod(period,val){void 0===val&&(val=!this.expansion.periods[period.id].expanded),this.$emit("expansion","periods",period.id,val)},toggleLine(period,line,val){void 0===val&&(val=!this.expansion.lines[period.id][line.id].expanded),this.$emit("expansion","lines",[period.id,line.id],val)},toggleItem(item,val){void 0===val&&(val=!this.expansion.items[item.id].expanded),this.$emit("expansion","items",item.id,val)},toggleSort(heading){this.$emit("togglesort",heading)}},mounted(){},updated(){},template:'\n \n '}),Vue.component("q-groupheading",{props:{group:{type:Object},resultcolumns:{type:Number,default:1},studentinfocolumns:{type:Number,default:1},expanded:{type:Boolean}},data:()=>({}),computed:{},methods:{toggleGroup(){this.$emit("togglegroup",this.group)}},template:'\n \n {{group.label}} | \n | \n
\n '}),Vue.component("q-inforow",{props:{resultcolumns:{type:Number,default:1},studentinfocolumns:{type:Number,default:1}},data:()=>({}),computed:{},methods:{},template:'\n \n | \n | \n
\n '}),Vue.component("q-studentresults",{props:{student:{type:Object},structure:{type:Object},results:{type:Array},loading:{type:Boolean,default:!1},expansion:{type:Object},even:{type:Boolean,default:!1}},data:()=>({text:strings.studentresults}),computed:{lastaccess(){return this.student.lastaccess?(0,_dateHelper.format_datetime)(this.student.lastaccess):this.text.never}},methods:{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},conditions:item=>conditions(item)},template:'\n \n {{student.firstname}} {{student.lastname}} | \n {{lastaccess}} | \n \n \n \n \n \n | \n \n | \n \n \n \n | \n \n \n | \n \n
\n '}),Vue.component("q-courseresult",{props:{student:{type:Object},item:{type:Object},loading:{type:Boolean,default:!1}},data:()=>({text:strings.studentresults}),computed:{hasprogressinfo(){const course=this.item.course;return!!course.enrolled&&!!(course.completion||course.competency||course.grades)},completion_icon(){switch(this.item.completion){default:return"circle-o";case"pending":return"question-circle";case"failed":return"times-circle";case"progress":return"exclamation-circle";case"completed":case"good":case"excellent":return"check-circle"}}},methods:{},template:'\n \n \n \n \n \n \n \n \n \n \n \n '}),Vue.component("q-conditionresult",{props:{student:{type:Object},item:{type:Object},loading:{type:Boolean,default:!1},conditionidx:{type:Number}},data:()=>({text:strings.studentresults}),computed:{conditions(){return conditions(this.item)},condition(){return this.conditionidx>=0&&this.conditionidx`},condition_completion(){const course=this.item.course;if(course.competency){const competency=this.condition;return competency.proficient&&competency.courseproficient||competency.proficient?"completed":!1===competency.proficient?"failed":competency.progress?"progress":"incomplete"}return course.completion?this.condition.status:course.grades?this.condition.completion:void 0}},methods:{},template:'\n \n \n \n \n \n \n \n \n \n {{condition_value}}\n \n \n \n '})}};return _exports.default=_default,_exports.default}));
+define("local_treestudyplan/studyplan-report-components",["exports","./util/string-helper","core/ajax","core/notification","./util/debugger","core/config","./treestudyplan-components","./util/fittext-vue","./util/date-helper"],(function(_exports,_stringHelper,_ajax,_notification,_debugger,_config,_treestudyplanComponents,_fittextVue,_dateHelper){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_notification=_interopRequireDefault(_notification),_debugger=_interopRequireDefault(_debugger),_config=_interopRequireDefault(_config),_treestudyplanComponents=_interopRequireDefault(_treestudyplanComponents),_fittextVue=_interopRequireDefault(_fittextVue);new _debugger.default("treestudyplan-viewer"),Math.PI;function striptags(html){const tmp=document.createElement("DIV");tmp.innerHTML=html;const text=tmp.textContent||tmp.innerText;return tmp.remove(),text}function conditions(item){const course=item.course,list=[];if(course.competency)for(const cmp of course.competency.competencies)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)g.selected&&list.push(g);return list}var _default={install(Vue){Vue.use(_treestudyplanComponents.default),Vue.use(_fittextVue.default);let strings=(0,_stringHelper.load_strings)({report:{loading:"loadinghelp@core",studyplan_past:"studyplan_past",studyplan_present:"studyplan_present",studyplan_future:"studyplan_future",back:"back"},invalid:{error:"error"},header:{overall:"overall",students:"students@core",firstname:"firstname@core",lastname:"lastname@core",email:"email@core",lastaccess:"lastaccess@core"},studentresults:{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",never:"never@core"}});Vue.component("q-studyplanreport",{props:{structure:{type:Object}},data:()=>({students:[],studentresults:{},studentsloading:!0,expansioninfo:{periods:{},lines:{},items:{}},groupinfo:{},sorting:{header:"lastname",asc:!0}}),created(){},watch:{structure:{immediate:!0,handler(structure){this.loadStudents();let firstperiod=!0;for(const period of structure.periods){const pid=period.period.id;this.expansioninfo.periods[pid]||(this.$set(this.expansioninfo.periods,pid,{expanded:!!(firstperiod&&period.lines.length>0)}),this.$set(this.expansioninfo.lines,period.period.id,{}));for(const line of period.lines){const lid=line.line.id;this.expansioninfo.lines[lid]||this.$set(this.expansioninfo.lines[pid],lid,{expanded:!0});for(const item of line.items)this.expansioninfo.items[item.id]||this.$set(this.expansioninfo.items,item.id,{expanded:!1})}firstperiod=!1}}}},computed:{sortedstudents(){const students=this.students;for(const group of this.students)group.users.sort(((a,b)=>{let d=a,e=b;if(this.sorting.asc||(d=b,e=a),"lastaccess"==this.sorting.header){return(d[this.sorting.header]?d[this.sorting.header]:0)-(e[this.sorting.header]?e[this.sorting.header]:0)}return String(d[this.sorting.header]).localeCompare(String(e[this.sorting.header]))}));return students},resultColCount(){let count=0;for(const period of this.structure.periods){const pid=period.period.id;if(this.expansioninfo.periods[pid].expanded)for(const line of period.lines){const lid=line.line.id;if(this.expansioninfo.lines[pid][lid].expanded)for(const item of line.items)this.expansioninfo.items[item.id].expanded?count+=1+conditions(item).length:count+=1;else count+=1}else count+=2}return count}},methods:{loadStudents(){const self=this;self.studentsloading=!0,(0,_ajax.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){self.$set(self.groupinfo,group.id,{expanded:!0});for(const student of group.users)self.$set(self.studentresults,student.id,{loading:!0,results:[]}),(0,_ajax.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=!1,self.studentresults[student.id].results=response})).catch(_notification.default.exception)}self.studentsloading=!1})).catch(_notification.default.exception)},expansionChanged(parm,id,val){"lines"==(parm="p"==parm[0]?"periods":"l"==parm[0]?"lines":"items")?this.expansioninfo[parm][id[0]][id[1]].expanded=val:this.expansioninfo[parm][id].expanded=val},groupExpansionChanged(group){this.groupinfo[group.id].expanded=!this.groupinfo[group.id].expanded},toggleSort(header){this.sorting.header==header?this.sorting.asc=!this.sorting.asc:(this.sorting.header=header,this.sorting.asc=!0)}},template:'\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
\n '}),Vue.component("q-header",{props:{structure:{type:Object},sorting:{type:Object},expansion:{type:Object}},data:()=>({text:strings.header}),computed:{},methods:{conditions:item=>function(item){const course=item.course,list=[];if(course.competency)for(const cmp of course.competency.competencies)list.push({name:cmp.details?`${cmp.title} - ${cmp.details}`:cmp.title,tooltip:cmp.description});else if(course.completion)for(const cnd of course.completion.conditions)for(const itm of cnd.items)list.push({name:itm.title,tooltip:`${itm.details.type}: ${itm.details.requirement}`});else if(course.grades)for(const g of course.grades)g.selected&&list.push({name:g.name,tooltip:`${g.typename}: ${striptags(g.name)}`});return list}(item),colspanPeriod(period){const pid=period.period.id;if(this.expansion.periods[pid].expanded){let sum=0;for(const l of period.lines)sum+=this.colspanLine(period,l);return sum}return 2},colspanLine(period,line){const pid=period.period.id,lid=line.line.id;if(this.expansion.lines[pid][lid].expanded){let sum=0;for(const i of line.items)sum+=this.colspanItem(i);return sum}return 1},colspanItem(item){if(this.expansion.items[item.id].expanded){return 1+this.conditions(item).length}return 1},togglePeriod(period,val){void 0===val&&(val=!this.expansion.periods[period.id].expanded),this.$emit("expansion","periods",period.id,val)},toggleLine(period,line,val){void 0===val&&(val=!this.expansion.lines[period.id][line.id].expanded),this.$emit("expansion","lines",[period.id,line.id],val)},toggleItem(item,val){void 0===val&&(val=!this.expansion.items[item.id].expanded),this.$emit("expansion","items",item.id,val)},toggleSort(heading){this.$emit("togglesort",heading)}},mounted(){},updated(){},template:'\n \n '}),Vue.component("q-groupheading",{props:{group:{type:Object},resultcolumns:{type:Number,default:1},studentinfocolumns:{type:Number,default:1},expanded:{type:Boolean}},data:()=>({}),computed:{},methods:{toggleGroup(){this.$emit("togglegroup",this.group)}},template:'\n \n {{group.label}} | \n | \n
\n '}),Vue.component("q-inforow",{props:{resultcolumns:{type:Number,default:1},studentinfocolumns:{type:Number,default:1}},data:()=>({}),computed:{},methods:{},template:'\n \n | \n | \n
\n '}),Vue.component("q-studentresults",{props:{student:{type:Object},structure:{type:Object},results:{type:Array},loading:{type:Boolean,default:!1},expansion:{type:Object},even:{type:Boolean,default:!1}},data:()=>({text:strings.studentresults}),computed:{lastaccess(){return this.student.lastaccess?(0,_dateHelper.format_datetime)(this.student.lastaccess):this.text.never}},methods:{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},conditions:item=>conditions(item)},template:'\n \n {{student.firstname}} {{student.lastname}} | \n {{lastaccess}} | \n \n \n \n \n \n | \n \n | \n \n \n \n | \n \n \n | \n \n
\n '}),Vue.component("q-courseresult",{props:{student:{type:Object},item:{type:Object},loading:{type:Boolean,default:!1}},data:()=>({text:strings.studentresults}),computed:{hasprogressinfo(){const course=this.item.course;return!!course.enrolled&&!!(course.completion||course.competency||course.grades)},completion_icon(){switch(this.item.completion){default:return"circle-o";case"pending":return"question-circle";case"failed":return"times-circle";case"progress":return"exclamation-circle";case"completed":case"good":case"excellent":return"check-circle"}}},methods:{},template:'\n \n \n \n \n \n \n \n \n \n \n \n '}),Vue.component("q-conditionresult",{props:{student:{type:Object},item:{type:Object},loading:{type:Boolean,default:!1},conditionidx:{type:Number}},data:()=>({text:strings.studentresults}),computed:{conditions(){return conditions(this.item)},condition(){return this.conditionidx>=0&&this.conditionidx`},condition_completion(){const course=this.item.course;if(course.competency){const competency=this.condition;return competency.proficient&&competency.courseproficient||competency.proficient?"completed":!1===competency.proficient?"failed":competency.progress?"progress":"incomplete"}return course.completion?this.condition.status:course.grades?this.condition.completion:void 0}},methods:{},template:'\n \n \n \n \n \n \n \n \n \n {{condition_value}}\n \n \n \n '})}};return _exports.default=_default,_exports.default}));
//# sourceMappingURL=studyplan-report-components.min.js.map
\ No newline at end of file
diff --git a/amd/build/studyplan-report-components.min.js.map b/amd/build/studyplan-report-components.min.js.map
index e7b3d89..dad842d 100644
--- a/amd/build/studyplan-report-components.min.js.map
+++ b/amd/build/studyplan-report-components.min.js.map
@@ -1 +1 @@
-{"version":3,"file":"studyplan-report-components.min.js","sources":["../src/studyplan-report-components.js"],"sourcesContent":["/*eslint no-var: \"error\"*/\n/*eslint no-console: \"off\"*/\n/*eslint no-unused-vars: warn */\n/*eslint max-len: [\"error\", { \"code\": 160 }] */\n/*eslint-disable no-trailing-spaces */\n/*eslint-env es6*/\n// Put this file in path/to/plugin/amd/src\n\nimport {load_strings} from './util/string-helper';\nimport {call} from 'core/ajax';\nimport notification from 'core/notification';\nimport Debugger from './util/debugger';\nimport Config from 'core/config';\nimport TSComponents from './treestudyplan-components';\nimport FitTextVue from './util/fittext-vue';\nimport {format_datetime} from \"./util/date-helper\";\n\nconst debug = new Debugger(\"treestudyplan-viewer\");\n\n\n// Make π available as a constant\nconst π = Math.PI;\n// Gravity value for arrow lines - determines how much a line is pulled in the direction of the start/end before changing direction\nconst LINE_GRAVITY = 1.3;\n\n/**\n * Strip tags from html\n * @param {*} html \n * @returns \n */\nfunction striptags(html) {\n const tmp = document.createElement(\"DIV\");\n tmp.innerHTML = html;\n const text = tmp.textContent || tmp.innerText;\n tmp.remove();\n return text;\n}\n\n/**\n * Retrieve condition headers\n * @param {Object} item\n */\nfunction conditionHeaders(item) {\n const course = item.course;\n const list = [];\n if (course.competency) {\n for (const cmp of course.competency.competencies) {\n list.push({\n name: (cmp.details?`${cmp.title} - ${cmp.details}`:cmp.title),\n tooltip: cmp.description,\n });\n }\n } else if(course.completion) {\n for (const cnd of course.completion.conditions) {\n for (const itm of cnd.items) {\n list.push({\n name: itm.title,\n tooltip: `${itm.details.type}: ${itm.details.requirement}`,\n });\n }\n }\n } else if(course.grades) {\n for(const g of course.grades) {\n if (g.selected) {\n list.push({\n name: g.name,\n tooltip: `${g.typename}: ${striptags(g.name)}`,\n });\n }\n }\n }\n return list;\n}\n\n/**\n * Retrieve conditions\n * @param {Object} item\n */\nfunction conditions(item) {\n const course = item.course;\n const list = [];\n if (course.competency) {\n for (const cmp of course.competency.competencies) {\n list.push(cmp);\n }\n } else if(course.completion) {\n for (const cnd of course.completion.conditions) {\n for (const itm of cnd.items) {\n list.push(itm);\n }\n }\n } else if(course.grades) {\n for(const g of course.grades) {\n if (g.selected) {\n list.push(g);\n }\n }\n }\n return list;\n}\n\n\nexport default {\n install(Vue/*,options*/){\n Vue.use(TSComponents);\n Vue.use(FitTextVue);\n\n let strings = load_strings({\n report: {\n loading: \"loadinghelp@core\",\n studyplan_past: \"studyplan_past\",\n studyplan_present: \"studyplan_present\",\n studyplan_future: \"studyplan_future\",\n back: \"back\",\n },\n \n invalid: {\n error: 'error',\n },\n header: {\n overall: 'overall',\n students: 'students@core',\n firstname: 'firstname@core',\n lastname: 'lastname@core',\n email: 'email@core',\n lastaccess: 'lastaccess@core',\n },\n studentresults: {\n completion_incomplete: \"completion_incomplete\",\n completion_failed: \"completion_failed\",\n completion_pending: \"completion_pending\",\n completion_progress: \"completion_progress\",\n completion_completed: \"completion_completed\",\n completion_good: \"completion_good\",\n completion_excellent: \"completion_excellent\",\n student_not_tracked: \"student_not_tracked\",\n never: \"never@core\",\n }\n });\n\n /************************************\n * *\n * Treestudyplan Viewer components *\n * *\n ************************************/\n\n Vue.component('q-studyplanreport', {\n props: {\n structure: {\n type: Object,\n },\n },\n data() {\n return {\n students: [],\n studentresults: {},\n studentsloading: true,\n expansioninfo: {\n periods: {},\n lines: {},\n items: {},\n },\n groupinfo: {},\n\n sorting: {\n header: 'lastname',\n asc: true,\n }\n };\n },\n created() {\n },\n watch:{\n structure: {\n immediate: true,\n handler (structure) {\n this.loadStudents(); // reload the student list\n \n // (Re)build expansion info structure\n let firstperiod = true;\n for (const period of structure.periods) {\n const pid = period.period.id;\n if (!this.expansioninfo.periods[pid]) {\n // Use this.$set to make sure the properties are reactive.\n this.$set(\n this.expansioninfo.periods, \n pid, \n {\n expanded: ((firstperiod && period.lines.length > 0)?true:false),\n }\n );\n this.$set(\n this.expansioninfo.lines, \n period.period.id, \n {}\n );\n }\n for (const line of period.lines) {\n const lid = line.line.id;\n if (!this.expansioninfo.lines[lid]) {\n // Use this.$set to make sure the properties are reactive.\n this.$set(\n this.expansioninfo.lines[pid],\n lid, \n {\n expanded: true,\n }\n );\n }\n for (const item of line.items) {\n if (!this.expansioninfo.items[item.id]) {\n // Use this.$set to make sure the properties are reactive.\n this.$set(\n this.expansioninfo.items,\n item.id,\n {\n expanded: false,\n }\n );\n }\n }\n }\n firstperiod = false;\n }\n }\n }\n },\n computed: {\n sortedstudents(){\n const self=this;\n // Probably could make a deep copy for purity's sake, but this works just as well and is probably more efficient.\n const students = this.students;\n for (const group of this.students) {\n group.users.sort((a,b) => {\n let d = a;\n let e = b;\n if (!this.sorting.asc) {\n d = b;\n e = a;\n } \n if (this.sorting.header == \"lastaccess\") {\n const dvalue = (d[this.sorting.header]?d[this.sorting.header]:0);\n const evalue = (e[this.sorting.header]?e[this.sorting.header]:0);\n return dvalue - evalue;\n } else {\n return String(d[this.sorting.header]).localeCompare(String(e[this.sorting.header]));\n }\n });\n }\n \n return students;\n },\n resultColCount(){\n let count = 0;\n for (const period of this.structure.periods) {\n const pid = period.period.id;\n if (!this.expansioninfo.periods[pid].expanded) {\n // This period is not expanded. Make it 3 units wide \n count += 2;\n } else {\n for (const line of period.lines) {\n const lid = line.line.id;\n if (!this.expansioninfo.lines[pid][lid].expanded) {\n count +=1;\n } else { \n for (const item of line.items) {\n if (!this.expansioninfo.items[item.id].expanded) {\n count += 1;\n } else {\n count += 1 + conditions(item).length;\n }\n }\n }\n }\n }\n }\n return count; \n }\n },\n methods: {\n loadStudents() {\n const self = this;\n self.studentsloading=true;\n call([{\n methodname: 'local_treestudyplan_all_associated_grouped',\n args: { studyplan_id: this.structure.studyplan.id}\n }])[0].then(function(response){\n self.students = response;\n for(const group of self.students) {\n self.$set(\n self.groupinfo,\n group.id,\n {\n expanded: true,\n }\n );\n\n for(const student of group.users){\n self.$set(\n self.studentresults,\n student.id, \n {\n loading: true,\n results: [],\n }\n );\n call([{\n methodname: 'local_treestudyplan_get_report_data',\n args: { pageid: self.structure.page.id, \n userid: student.id,\n firstperiod: self.structure.firstperiod,\n lastperiod: self.structure.lastperiod,\n }\n }])[0].then(function(response){\n self.studentresults[student.id].loading = false;\n self.studentresults[student.id].results = response;\n }).catch(notification.exception);\n }\n }\n self.studentsloading=false;\n }).catch(notification.exception);\n },\n expansionChanged(parm, id, val) {\n if(parm[0] == 'p') {\n parm = 'periods';\n } else if(parm[0] == 'l') {\n parm = 'lines';\n } else {\n parm = 'items';\n }\n\n if (parm == 'lines') {\n this.expansioninfo[parm][id[0]][id[1]].expanded = val;\n } else {\n this.expansioninfo[parm][id].expanded = val;\n }\n },\n groupExpansionChanged(group) {\n this.groupinfo[group.id].expanded = !this.groupinfo[group.id].expanded;\n },\n toggleSort(header) {\n if (this.sorting.header == header) {\n this.sorting.asc = !this.sorting.asc;\n } else {\n this.sorting.header = header;\n this.sorting.asc = true;\n }\n }\n },\n template: `\n \n \n \n \n \n \n \n \n \n \n \n 0\" \n :group=\"group\"\n :expanded=\"groupinfo[group.id].expanded\"\n @togglegroup=\"groupExpansionChanged\"\n :resultcolumns=\"resultColCount\"\n :studentinfocolumns=\"2\"\n >\n \n \n \n \n \n \n
\n `,\n });\n\n Vue.component('q-header', {\n props: {\n structure: {\n type: Object,\n },\n sorting: {\n type: Object,\n },\n expansion: {\n type: Object\n },\n },\n data() {\n return {\n text: strings.header,\n };\n },\n computed: {\n },\n methods: {\n conditions(item) {\n return conditionHeaders(item);\n },\n colspanPeriod(period) {\n const pid = period.period.id;\n if (this.expansion.periods[pid].expanded) {\n let sum = 0;\n for (const l of period.lines) {\n sum += this.colspanLine(period,l);\n }\n return sum;\n } else {\n return 2;\n }\n },\n colspanLine(period,line) {\n const pid = period.period.id;\n const lid = line.line.id;\n\n if (this.expansion.lines[pid][lid].expanded) {\n let sum = 0;\n for (const i of line.items) {\n sum += this.colspanItem(i);\n }\n return sum;\n } else {\n return 1;\n }\n },\n colspanItem(item) {\n if (this.expansion.items[item.id].expanded) {\n const cs = this.conditions(item);\n return 1+cs.length;\n } else {\n return 1;\n }\n },\n togglePeriod(period,val) {\n if ( val === undefined) {\n val = !(this.expansion.periods[period.id].expanded);\n }\n this.$emit('expansion','periods',period.id,val);\n },\n toggleLine(period,line,val) {\n if ( val === undefined) {\n val = !(this.expansion.lines[period.id][line.id].expanded);\n }\n this.$emit('expansion','lines',[period.id,line.id],val);\n },\n toggleItem(item,val) {\n if ( val === undefined) {\n val = !(this.expansion.items[item.id].expanded);\n }\n this.$emit('expansion','items',item.id,val);\n },\n toggleSort(heading) {\n this.$emit('togglesort',heading);\n }\n },\n mounted() {\n \n },\n updated() {\n },\n /* TODO: https://css-tricks.com/position-sticky-and-table-headers/ */\n template: `\n \n `,\n });\n\n Vue.component('q-groupheading', {\n props: {\n group: {\n type: Object,\n },\n resultcolumns: {\n type: Number,\n default: 1\n },\n studentinfocolumns: {\n type: Number,\n default: 1\n },\n expanded: {\n type: Boolean,\n }\n },\n data() {\n return {\n\n };\n },\n computed: {\n },\n methods: {\n toggleGroup(){\n this.$emit('togglegroup',this.group);\n }\n },\n template: `\n \n {{group.label}} | \n | \n
\n `,\n });\n\n Vue.component('q-inforow', {\n props: {\n resultcolumns: {\n type: Number,\n default: 1\n },\n studentinfocolumns: {\n type: Number,\n default: 1\n },\n },\n data() {\n return {\n };\n },\n computed: {\n },\n methods: {\n },\n template: `\n \n | \n | \n
\n `,\n });\n\n\n Vue.component('q-studentresults', {\n props: {\n student: {\n type: Object,\n },\n structure: {\n type: Object,\n },\n results: {\n type: Array,\n },\n loading: {\n type: Boolean,\n default: false\n },\n expansion: {\n type: Object,\n },\n even: {\n type: Boolean,\n default: false,\n }\n },\n data() {\n return {\n text: strings.studentresults,\n };\n },\n computed: {\n lastaccess() {\n if (this.student.lastaccess) {\n return format_datetime(this.student.lastaccess); // Takes date in milliseconds\n } else {\n return this.text.never;\n }\n }\n },\n methods: {\n useritems(line) {\n const list = [];\n for (const item of line.items) {\n let newitm = item;\n for (const itm of this.results) {\n if (item.id == itm.id) {\n newitm = itm;\n break;\n }\n }\n list.push(newitm);\n }\n return list;\n },\n conditions(item) {\n return conditions(item);\n },\n },\n /* https://css-tricks.com/position-sticky-and-table-headers/ */\n /* TODO: Rework below to make use of tables. Use as main element. Then create multiple as needed for the headers.\n This should create a much better view than using divs overal.\n */\n template: `\n
\n {{student.firstname}} {{student.lastname}} | \n {{lastaccess}} | \n \n 0\">\n \n \n \n | \n \n | \n \n \n \n | \n \n \n | \n \n
\n `,\n });\n\n Vue.component('q-courseresult', {\n props: {\n student: {\n type: Object,\n },\n item: {\n type: Object,\n },\n loading: {\n type: Boolean,\n default: false\n },\n },\n data() {\n return {\n text: strings.studentresults,\n };\n },\n computed: {\n hasprogressinfo() {\n const course = this.item.course;\n if (!course.enrolled) {\n return false;\n } else {\n return (course.completion || course.competency || course.grades)?true:false;\n }\n },\n completion_icon() {\n const completion = this.item.completion;\n switch(completion){\n default: // case \"incomplete\"\n return \"circle-o\";\n case \"pending\":\n return \"question-circle\";\n case \"failed\":\n return \"times-circle\";\n case \"progress\":\n return \"exclamation-circle\";\n case \"completed\":\n return \"check-circle\";\n case \"good\":\n return \"check-circle\";\n case \"excellent\":\n return \"check-circle\";\n }\n },\n },\n methods: {\n },\n template: `\n \n \n \n \n \n \n \n \n \n \n \n `,\n });\n\n Vue.component('q-conditionresult', {\n props: {\n student: {\n type: Object,\n },\n item: {\n type: Object,\n },\n loading: {\n type: Boolean,\n default: false\n },\n conditionidx: {\n type: Number,\n }\n },\n data() {\n return {\n text: strings.studentresults,\n };\n },\n computed: {\n conditions() {\n return conditions(this.item);\n },\n condition() {\n if (this.conditionidx >= 0 && this.conditionidx < this.conditions.length) {\n return this.conditions[this.conditionidx];\n } else {\n return null;\n }\n },\n hasprogressinfo() {\n const course = this.item.course;\n if (!course.enrolled) {\n return false;\n } else {\n return (course.completion || course.competency || course.grades);\n }\n },\n completion_icon() {\n const completion = this.condition_completion();\n switch(completion){\n default: // case \"incomplete\"\n return \"circle-o\";\n case \"pending\":\n return \"question-circle\";\n case \"failed\":\n return \"times-circle\";\n case \"progress\":\n return \"exclamation-circle\";\n case \"completed\":\n return \"check-circle\";\n case \"good\":\n return \"check-circle\";\n case \"excellent\":\n return \"check-circle\";\n }\n },\n condition_value() {\n const course = this.item.course;\n if (course.competency) {\n if (this.condition.grade) {\n // Return grade if possible.\n return this.condition.grade;\n } \n } else if(course.completion) {\n if (this.condition.grade) {\n // Return grade if possible.\n return this.condition.grade;\n } \n } else if(course.grades) {\n return this.condition.grade;\n }\n // Fallback to completion icon.\n const icon = this.completion_icon();\n return ``;\n },\n condition_completion() {\n // Unify completion information\n const course = this.item.course;\n if (course.competency) {\n const competency = this.condition;\n if (competency.proficient && competency.courseproficient) {\n return \"completed\";\n } else if (competency.proficient) {\n return \"completed\";\n } else if (competency.proficient === false) {\n return \"failed\";\n } else if (competency.progress) {\n return \"progress\";\n } else {\n return \"incomplete\";\n }\n } else if(course.completion) {\n return this.condition.status;\n } else if(course.grades) {\n return this.condition.completion;\n }\n }\n\n },\n methods: {\n },\n // TODO: Show actual grades when relevant at all (don;t forget the grade point completion requirement)\n template: `\n \n \n \n \n \n \n \n \n \n {{condition_value}}\n \n \n \n `,\n });\n\n\n\n },\n};"],"names":["Debugger","Math","PI","striptags","html","tmp","document","createElement","innerHTML","text","textContent","innerText","remove","conditions","item","course","list","competency","cmp","competencies","push","completion","cnd","itm","items","grades","g","selected","install","Vue","use","TSComponents","FitTextVue","strings","report","loading","studyplan_past","studyplan_present","studyplan_future","back","invalid","error","header","overall","students","firstname","lastname","email","lastaccess","studentresults","completion_incomplete","completion_failed","completion_pending","completion_progress","completion_completed","completion_good","completion_excellent","student_not_tracked","never","component","props","structure","type","Object","data","studentsloading","expansioninfo","periods","lines","groupinfo","sorting","asc","created","watch","immediate","handler","loadStudents","firstperiod","period","pid","id","this","$set","expanded","length","line","lid","computed","sortedstudents","group","users","sort","a","b","d","e","String","localeCompare","resultColCount","count","methods","self","methodname","args","studyplan_id","studyplan","then","response","student","results","pageid","page","userid","lastperiod","catch","notification","exception","expansionChanged","parm","val","groupExpansionChanged","toggleSort","template","expansion","name","details","title","tooltip","description","requirement","typename","conditionHeaders","colspanPeriod","sum","l","colspanLine","i","colspanItem","togglePeriod","undefined","$emit","toggleLine","toggleItem","heading","mounted","updated","resultcolumns","Number","default","studentinfocolumns","Boolean","toggleGroup","Array","even","useritems","newitm","hasprogressinfo","enrolled","completion_icon","conditionidx","condition","condition_completion","condition_value","grade","proficient","courseproficient","progress","status"],"mappings":"gwBAiBc,IAAIA,kBAAS,wBAIjBC,KAAKC,YASNC,UAAUC,YACTC,IAAMC,SAASC,cAAc,OACnCF,IAAIG,UAAYJ,WACVK,KAAOJ,IAAIK,aAAeL,IAAIM,iBACpCN,IAAIO,SACGH,cA2CFI,WAAWC,YACVC,OAASD,KAAKC,OACdC,KAAO,MACTD,OAAOE,eACF,MAAMC,OAAOH,OAAOE,WAAWE,aAChCH,KAAKI,KAAKF,UAEX,GAAGH,OAAOM,eACR,MAAMC,OAAOP,OAAOM,WAAWR,eAC3B,MAAMU,OAAOD,IAAIE,MAClBR,KAAKI,KAAKG,UAGf,GAAGR,OAAOU,WACT,MAAMC,KAAKX,OAAOU,OACdC,EAAEC,UACFX,KAAKI,KAAKM,UAIfV,kBAII,CACXY,QAAQC,KACJA,IAAIC,IAAIC,kCACRF,IAAIC,IAAIE,yBAEJC,SAAU,8BAAa,CACvBC,OAAQ,CACJC,QAAS,mBACTC,eAAgB,iBAChBC,kBAAmB,oBACnBC,iBAAkB,mBAClBC,KAAM,QAGVC,QAAS,CACLC,MAAO,SAEXC,OAAQ,CACJC,QAAS,UACTC,SAAU,gBACVC,UAAW,iBACXC,SAAU,gBACVC,MAAO,aACPC,WAAY,mBAEhBC,eAAgB,CACZC,sBAAuB,wBACvBC,kBAAmB,oBACnBC,mBAAoB,qBACpBC,oBAAqB,sBACrBC,qBAAsB,uBACtBC,gBAAiB,kBACjBC,qBAAsB,uBACtBC,oBAAqB,sBACrBC,MAAO,gBAUf7B,IAAI8B,UAAU,oBAAqB,CAC/BC,MAAO,CACHC,UAAW,CACPC,KAAMC,SAGdC,KAAI,KACO,CACHpB,SAAU,GACVK,eAAgB,GAChBgB,iBAAiB,EACjBC,cAAe,CACXC,QAAS,GACTC,MAAO,GACP5C,MAAO,IAEX6C,UAAW,GAEXC,QAAS,CACL5B,OAAQ,WACR6B,KAAK,KAIjBC,YAEAC,MAAM,CACFZ,UAAW,CACPa,WAAW,EACXC,QAASd,gBACAe,mBAGDC,aAAc,MACb,MAAMC,UAAUjB,UAAUM,QAAS,OAC9BY,IAAMD,OAAOA,OAAOE,GACrBC,KAAKf,cAAcC,QAAQY,YAEvBG,KACDD,KAAKf,cAAcC,QACnBY,IACA,CACII,YAAYN,aAAeC,OAAOV,MAAMgB,OAAS,UAGpDF,KACDD,KAAKf,cAAcE,MACnBU,OAAOA,OAAOE,GACd,SAGH,MAAMK,QAAQP,OAAOV,MAAO,OACvBkB,IAAMD,KAAKA,KAAKL,GACjBC,KAAKf,cAAcE,MAAMkB,WAErBJ,KACDD,KAAKf,cAAcE,MAAMW,KACzBO,IACA,CACIH,UAAU,QAIjB,MAAMrE,QAAQuE,KAAK7D,MACfyD,KAAKf,cAAc1C,MAAMV,KAAKkE,UAE1BE,KACDD,KAAKf,cAAc1C,MACnBV,KAAKkE,GACL,CACAG,UAAU,IAM1BN,aAAc,MAK9BU,SAAU,CACNC,uBAGU5C,SAAWqC,KAAKrC,aACjB,MAAM6C,SAASR,KAAKrC,SACrB6C,MAAMC,MAAMC,MAAK,CAACC,EAAEC,SACZC,EAAIF,EACJG,EAAIF,KACHZ,KAAKX,QAAQC,MACduB,EAAID,EACJE,EAAIH,GAEmB,cAAvBX,KAAKX,QAAQ5B,OAAwB,QACrBoD,EAAEb,KAAKX,QAAQ5B,QAAQoD,EAAEb,KAAKX,QAAQ5B,QAAQ,IAC9CqD,EAAEd,KAAKX,QAAQ5B,QAAQqD,EAAEd,KAAKX,QAAQ5B,QAAQ,UAGvDsD,OAAOF,EAAEb,KAAKX,QAAQ5B,SAASuD,cAAcD,OAAOD,EAAEd,KAAKX,QAAQ5B,oBAK/EE,UAEXsD,qBACQC,MAAQ,MACP,MAAMrB,UAAUG,KAAKpB,UAAUM,QAAS,OACnCY,IAAMD,OAAOA,OAAOE,MACrBC,KAAKf,cAAcC,QAAQY,KAAKI,aAI5B,MAAME,QAAQP,OAAOV,MAAO,OACvBkB,IAAMD,KAAKA,KAAKL,MACjBC,KAAKf,cAAcE,MAAMW,KAAKO,KAAKH,aAG/B,MAAMrE,QAAQuE,KAAK7D,MACfyD,KAAKf,cAAc1C,MAAMV,KAAKkE,IAAIG,SAGnCgB,OAAS,EAAItF,WAAWC,MAAMsE,OAF9Be,OAAS,OAJjBA,OAAQ,OALhBA,OAAS,SAkBVA,QAGfC,QAAS,CACLxB,qBACUyB,KAAOpB,KACboB,KAAKpC,iBAAgB,iBAChB,CAAC,CACFqC,WAAY,6CACZC,KAAM,CAAEC,aAAcvB,KAAKpB,UAAU4C,UAAUzB,OAC/C,GAAG0B,MAAK,SAASC,UACjBN,KAAKzD,SAAW+D,aACZ,MAAMlB,SAASY,KAAKzD,SAAU,CAC9ByD,KAAKnB,KACDmB,KAAKhC,UACLoB,MAAMT,GACN,CACEG,UAAU,QAIZ,MAAMyB,WAAWnB,MAAMC,MACvBW,KAAKnB,KACDmB,KAAKpD,eACL2D,QAAQ5B,GACR,CACI7C,SAAS,EACT0E,QAAS,oBAGZ,CAAC,CACFP,WAAY,sCACZC,KAAM,CAAEO,OAAQT,KAAKxC,UAAUkD,KAAK/B,GAC5BgC,OAAQJ,QAAQ5B,GAChBH,YAAawB,KAAKxC,UAAUgB,YAC5BoC,WAAYZ,KAAKxC,UAAUoD,eAEnC,GAAGP,MAAK,SAASC,UACjBN,KAAKpD,eAAe2D,QAAQ5B,IAAI7C,SAAU,EAC1CkE,KAAKpD,eAAe2D,QAAQ5B,IAAI6B,QAAUF,YAC3CO,MAAMC,sBAAaC,WAG9Bf,KAAKpC,iBAAgB,KACtBiD,MAAMC,sBAAaC,YAE1BC,iBAAiBC,KAAMtC,GAAIuC,KASX,UAPRD,KADU,KAAXA,KAAK,GACG,UACU,KAAXA,KAAK,GACJ,QAEA,cAIFpD,cAAcoD,MAAMtC,GAAG,IAAIA,GAAG,IAAIG,SAAWoC,SAE7CrD,cAAcoD,MAAMtC,IAAIG,SAAWoC,KAGhDC,sBAAsB/B,YACbpB,UAAUoB,MAAMT,IAAIG,UAAYF,KAAKZ,UAAUoB,MAAMT,IAAIG,UAElEsC,WAAW/E,QACHuC,KAAKX,QAAQ5B,QAAUA,YAClB4B,QAAQC,KAAOU,KAAKX,QAAQC,UAE5BD,QAAQ5B,OAASA,YACjB4B,QAAQC,KAAM,KAI/BmD,SAAW,wxEA+Cf7F,IAAI8B,UAAU,WAAY,CACtBC,MAAO,CACHC,UAAW,CACPC,KAAMC,QAEVO,QAAS,CACLR,KAAMC,QAEV4D,UAAW,CACP7D,KAAMC,SAGdC,KAAI,KACO,CACHvD,KAAMwB,QAAQS,SAGtB6C,SAAU,GAEVa,QAAS,CACLvF,WAAWC,eAtXDA,YAChBC,OAASD,KAAKC,OACdC,KAAO,MACTD,OAAOE,eACF,MAAMC,OAAOH,OAAOE,WAAWE,aAChCH,KAAKI,KAAK,CACNwG,KAAO1G,IAAI2G,QAAS,GAAE3G,IAAI4G,WAAW5G,IAAI2G,UAAU3G,IAAI4G,MACvDC,QAAS7G,IAAI8G,mBAGlB,GAAGjH,OAAOM,eACR,MAAMC,OAAOP,OAAOM,WAAWR,eAC3B,MAAMU,OAAOD,IAAIE,MAClBR,KAAKI,KAAK,CACNwG,KAAMrG,IAAIuG,MACVC,QAAU,GAAExG,IAAIsG,QAAQ/D,SAASvC,IAAIsG,QAAQI,qBAItD,GAAGlH,OAAOU,WACT,MAAMC,KAAKX,OAAOU,OACdC,EAAEC,UACFX,KAAKI,KAAK,CACNwG,KAAMlG,EAAEkG,KACRG,QAAU,GAAErG,EAAEwG,aAAa/H,UAAUuB,EAAEkG,iBAKhD5G,KA0VgBmH,CAAiBrH,MAE5BsH,cAActD,cACJC,IAAMD,OAAOA,OAAOE,MACtBC,KAAK0C,UAAUxD,QAAQY,KAAKI,SAAU,KAClCkD,IAAM,MACL,MAAMC,KAAKxD,OAAOV,MACnBiE,KAAOpD,KAAKsD,YAAYzD,OAAOwD,UAE5BD,WAEA,GAGfE,YAAYzD,OAAOO,YACTN,IAAMD,OAAOA,OAAOE,GACpBM,IAAMD,KAAKA,KAAKL,MAElBC,KAAK0C,UAAUvD,MAAMW,KAAKO,KAAKH,SAAU,KACrCkD,IAAM,MACL,MAAMG,KAAKnD,KAAK7D,MACjB6G,KAAOpD,KAAKwD,YAAYD,UAErBH,WAEA,GAGfI,YAAY3H,SACJmE,KAAK0C,UAAUnG,MAAMV,KAAKkE,IAAIG,SAAU,QAEjC,EADIF,KAAKpE,WAAWC,MACfsE,cAEL,GAGfsD,aAAa5D,OAAOyC,UACHoB,IAARpB,MACDA,KAAQtC,KAAK0C,UAAUxD,QAAQW,OAAOE,IAAIG,eAEzCyD,MAAM,YAAY,UAAU9D,OAAOE,GAAGuC,MAE/CsB,WAAW/D,OAAOO,KAAKkC,UACNoB,IAARpB,MACDA,KAAQtC,KAAK0C,UAAUvD,MAAMU,OAAOE,IAAIK,KAAKL,IAAIG,eAEhDyD,MAAM,YAAY,QAAQ,CAAC9D,OAAOE,GAAGK,KAAKL,IAAIuC,MAEvDuB,WAAWhI,KAAKyG,UACCoB,IAARpB,MACDA,KAAQtC,KAAK0C,UAAUnG,MAAMV,KAAKkE,IAAIG,eAErCyD,MAAM,YAAY,QAAQ9H,KAAKkE,GAAGuC,MAE3CE,WAAWsB,cACFH,MAAM,aAAaG,WAGhCC,YAGAC,YAGAvB,SAAW,oxPAoHf7F,IAAI8B,UAAU,iBAAkB,CAC5BC,MAAO,CACH6B,MAAO,CACH3B,KAAMC,QAEVmF,cAAe,CACXpF,KAAMqF,OACNC,QAAS,GAEbC,mBAAoB,CAChBvF,KAAMqF,OACNC,QAAS,GAEbjE,SAAU,CACNrB,KAAMwF,UAGdtF,KAAI,KACO,IAIXuB,SAAU,GAEVa,QAAS,CACLmD,mBACSX,MAAM,cAAc3D,KAAKQ,SAGtCiC,SAAW,8YAWf7F,IAAI8B,UAAU,YAAa,CACvBC,MAAO,CACHsF,cAAe,CACXpF,KAAMqF,OACNC,QAAS,GAEbC,mBAAoB,CAChBvF,KAAMqF,OACNC,QAAS,IAGjBpF,KAAI,KACO,IAGXuB,SAAU,GAEVa,QAAS,GAETsB,SAAW,sMASf7F,IAAI8B,UAAU,mBAAoB,CAC9BC,MAAO,CACHgD,QAAS,CACL9C,KAAMC,QAEVF,UAAW,CACPC,KAAMC,QAEV8C,QAAS,CACL/C,KAAM0F,OAEVrH,QAAS,CACL2B,KAAMwF,QACNF,SAAS,GAEbzB,UAAW,CACP7D,KAAMC,QAEV0F,KAAM,CACF3F,KAAMwF,QACNF,SAAS,IAGjBpF,KAAI,KACO,CACHvD,KAAMwB,QAAQgB,iBAGtBsC,SAAU,CACNvC,oBACQiC,KAAK2B,QAAQ5D,YACN,+BAAgBiC,KAAK2B,QAAQ5D,YAE7BiC,KAAKxE,KAAKiD,QAI7B0C,QAAS,CACLsD,UAAUrE,YACArE,KAAO,OACR,MAAMF,QAAQuE,KAAK7D,MAAO,KACvBmI,OAAS7I,SACR,MAAMS,OAAO0D,KAAK4B,WACf/F,KAAKkE,IAAMzD,IAAIyD,GAAI,CACnB2E,OAASpI,UAIjBP,KAAKI,KAAKuI,eAEP3I,MAEXH,WAAWC,MACAD,WAAWC,OAO1B4G,SAAW,osEAsCf7F,IAAI8B,UAAU,iBAAkB,CAC5BC,MAAO,CACHgD,QAAS,CACL9C,KAAMC,QAEVjD,KAAM,CACFgD,KAAMC,QAEV5B,QAAS,CACL2B,KAAMwF,QACNF,SAAS,IAGjBpF,KAAI,KACO,CACHvD,KAAMwB,QAAQgB,iBAGtBsC,SAAU,CACNqE,wBACU7I,OAASkE,KAAKnE,KAAKC,eACpBA,OAAO8I,aAGA9I,OAAOM,YAAcN,OAAOE,YAAcF,OAAOU,SAGjEqI,yBACuB7E,KAAKnE,KAAKO,0BAGd,eACN,gBACM,sBACN,eACM,mBACN,iBACM,yBACN,gBAEA,WAEA,kBACM,kBAIvB+E,QAAS,GAETsB,SAAW,o2BAoBf7F,IAAI8B,UAAU,oBAAqB,CAC/BC,MAAO,CACHgD,QAAS,CACL9C,KAAMC,QAEVjD,KAAM,CACFgD,KAAMC,QAEV5B,QAAS,CACL2B,KAAMwF,QACNF,SAAS,GAEbW,aAAc,CACVjG,KAAMqF,SAGdnF,KAAI,KACO,CACHvD,KAAMwB,QAAQgB,iBAGtBsC,SAAU,CACN1E,oBACWA,WAAWoE,KAAKnE,OAE3BkJ,mBACQ/E,KAAK8E,cAAgB,GAAK9E,KAAK8E,aAAe9E,KAAKpE,WAAWuE,OACvDH,KAAKpE,WAAWoE,KAAK8E,cAErB,MAGfH,wBACU7I,OAASkE,KAAKnE,KAAKC,eACpBA,OAAO8I,WAGA9I,OAAOM,YAAcN,OAAOE,YAAcF,OAAOU,SAGjEqI,yBACuB7E,KAAKgF,sCAGT,eACN,gBACM,sBACN,eACM,mBACN,iBACM,yBACN,gBAEA,WAEA,kBACM,iBAGnBC,wBACUnJ,OAASkE,KAAKnE,KAAKC,UACrBA,OAAOE,eACHgE,KAAK+E,UAAUG,aAERlF,KAAK+E,UAAUG,WAEvB,GAAGpJ,OAAOM,eACT4D,KAAK+E,UAAUG,aAERlF,KAAK+E,UAAUG,WAEvB,GAAGpJ,OAAOU,cACNwD,KAAK+E,UAAUG,YAIlB,mBADKlF,KAAK6E,2BAGtBG,6BAEUlJ,OAASkE,KAAKnE,KAAKC,UACrBA,OAAOE,WAAY,OACbA,WAAagE,KAAK+E,iBACpB/I,WAAWmJ,YAAcnJ,WAAWoJ,kBAE7BpJ,WAAWmJ,WADX,aAG0B,IAA1BnJ,WAAWmJ,WACX,SACAnJ,WAAWqJ,SACX,WAEA,aAER,OAAGvJ,OAAOM,WACN4D,KAAK+E,UAAUO,OAChBxJ,OAAOU,OACNwD,KAAK+E,UAAU3I,gBADnB,IAMf+E,QAAS,GAGTsB,SAAW"}
\ No newline at end of file
+{"version":3,"file":"studyplan-report-components.min.js","sources":["../src/studyplan-report-components.js"],"sourcesContent":["/*eslint no-var: \"error\"*/\n/*eslint no-console: \"off\"*/\n/*eslint no-unused-vars: warn */\n/*eslint max-len: [\"error\", { \"code\": 160 }] */\n/*eslint-disable no-trailing-spaces */\n/*eslint-env es6*/\n// Put this file in path/to/plugin/amd/src\n\nimport {load_strings} from './util/string-helper';\nimport {call} from 'core/ajax';\nimport notification from 'core/notification';\nimport Debugger from './util/debugger';\nimport Config from 'core/config';\nimport TSComponents from './treestudyplan-components';\nimport FitTextVue from './util/fittext-vue';\nimport {format_datetime} from \"./util/date-helper\";\n\nconst debug = new Debugger(\"treestudyplan-viewer\");\n\n\n// Make π available as a constant\nconst π = Math.PI;\n// Gravity value for arrow lines - determines how much a line is pulled in the direction of the start/end before changing direction\nconst LINE_GRAVITY = 1.3;\n\n/**\n * Strip tags from html\n * @param {*} html \n * @returns \n */\nfunction striptags(html) {\n const tmp = document.createElement(\"DIV\");\n tmp.innerHTML = html;\n const text = tmp.textContent || tmp.innerText;\n tmp.remove();\n return text;\n}\n\n/**\n * Retrieve condition headers\n * @param {Object} item\n */\nfunction conditionHeaders(item) {\n const course = item.course;\n const list = [];\n if (course.competency) {\n for (const cmp of course.competency.competencies) {\n list.push({\n name: (cmp.details?`${cmp.title} - ${cmp.details}`:cmp.title),\n tooltip: cmp.description,\n });\n }\n } else if(course.completion) {\n for (const cnd of course.completion.conditions) {\n for (const itm of cnd.items) {\n list.push({\n name: itm.title,\n tooltip: `${itm.details.type}: ${itm.details.requirement}`,\n });\n }\n }\n } else if(course.grades) {\n for(const g of course.grades) {\n if (g.selected) {\n list.push({\n name: g.name,\n tooltip: `${g.typename}: ${striptags(g.name)}`,\n });\n }\n }\n }\n return list;\n}\n\n/**\n * Retrieve conditions\n * @param {Object} item\n */\nfunction conditions(item) {\n const course = item.course;\n const list = [];\n if (course.competency) {\n for (const cmp of course.competency.competencies) {\n list.push(cmp);\n }\n } else if(course.completion) {\n for (const cnd of course.completion.conditions) {\n for (const itm of cnd.items) {\n list.push(itm);\n }\n }\n } else if(course.grades) {\n for(const g of course.grades) {\n if (g.selected) {\n list.push(g);\n }\n }\n }\n return list;\n}\n\n\nexport default {\n install(Vue/*,options*/){\n Vue.use(TSComponents);\n Vue.use(FitTextVue);\n\n let strings = load_strings({\n report: {\n loading: \"loadinghelp@core\",\n studyplan_past: \"studyplan_past\",\n studyplan_present: \"studyplan_present\",\n studyplan_future: \"studyplan_future\",\n back: \"back\",\n },\n \n invalid: {\n error: 'error',\n },\n header: {\n overall: 'overall',\n students: 'students@core',\n firstname: 'firstname@core',\n lastname: 'lastname@core',\n email: 'email@core',\n lastaccess: 'lastaccess@core',\n },\n studentresults: {\n completion_incomplete: \"completion_incomplete\",\n completion_failed: \"completion_failed\",\n completion_pending: \"completion_pending\",\n completion_progress: \"completion_progress\",\n completion_completed: \"completion_completed\",\n completion_good: \"completion_good\",\n completion_excellent: \"completion_excellent\",\n student_not_tracked: \"student_not_tracked\",\n never: \"never@core\",\n }\n });\n\n /************************************\n * *\n * Treestudyplan Viewer components *\n * *\n ************************************/\n\n Vue.component('q-studyplanreport', {\n props: {\n structure: {\n type: Object,\n },\n },\n data() {\n return {\n students: [],\n studentresults: {},\n studentsloading: true,\n expansioninfo: {\n periods: {},\n lines: {},\n items: {},\n },\n groupinfo: {},\n\n sorting: {\n header: 'lastname',\n asc: true,\n }\n };\n },\n created() {\n },\n watch:{\n structure: {\n immediate: true,\n handler (structure) {\n this.loadStudents(); // reload the student list\n \n // (Re)build expansion info structure\n let firstperiod = true;\n for (const period of structure.periods) {\n const pid = period.period.id;\n if (!this.expansioninfo.periods[pid]) {\n // Use this.$set to make sure the properties are reactive.\n this.$set(\n this.expansioninfo.periods, \n pid, \n {\n expanded: ((firstperiod && period.lines.length > 0)?true:false),\n }\n );\n this.$set(\n this.expansioninfo.lines, \n period.period.id, \n {}\n );\n }\n for (const line of period.lines) {\n const lid = line.line.id;\n if (!this.expansioninfo.lines[lid]) {\n // Use this.$set to make sure the properties are reactive.\n this.$set(\n this.expansioninfo.lines[pid],\n lid, \n {\n expanded: true,\n }\n );\n }\n for (const item of line.items) {\n if (!this.expansioninfo.items[item.id]) {\n // Use this.$set to make sure the properties are reactive.\n this.$set(\n this.expansioninfo.items,\n item.id,\n {\n expanded: false,\n }\n );\n }\n }\n }\n firstperiod = false;\n }\n }\n }\n },\n computed: {\n sortedstudents(){\n const self=this;\n // Probably could make a deep copy for purity's sake, but this works just as well and is probably more efficient.\n const students = this.students;\n for (const group of this.students) {\n group.users.sort((a,b) => {\n let d = a;\n let e = b;\n if (!this.sorting.asc) {\n d = b;\n e = a;\n } \n if (this.sorting.header == \"lastaccess\") {\n const dvalue = (d[this.sorting.header]?d[this.sorting.header]:0);\n const evalue = (e[this.sorting.header]?e[this.sorting.header]:0);\n return dvalue - evalue;\n } else {\n return String(d[this.sorting.header]).localeCompare(String(e[this.sorting.header]));\n }\n });\n }\n \n return students;\n },\n resultColCount(){\n let count = 0;\n for (const period of this.structure.periods) {\n const pid = period.period.id;\n if (!this.expansioninfo.periods[pid].expanded) {\n // This period is not expanded. Make it 3 units wide \n count += 2;\n } else {\n for (const line of period.lines) {\n const lid = line.line.id;\n if (!this.expansioninfo.lines[pid][lid].expanded) {\n count +=1;\n } else { \n for (const item of line.items) {\n if (!this.expansioninfo.items[item.id].expanded) {\n count += 1;\n } else {\n count += 1 + conditions(item).length;\n }\n }\n }\n }\n }\n }\n return count; \n }\n },\n methods: {\n loadStudents() {\n const self = this;\n self.studentsloading=true;\n call([{\n methodname: 'local_treestudyplan_all_associated_grouped',\n args: { studyplan_id: this.structure.studyplan.id}\n }])[0].then(function(response){\n self.students = response;\n for(const group of self.students) {\n self.$set(\n self.groupinfo,\n group.id,\n {\n expanded: true,\n }\n );\n\n for(const student of group.users){\n self.$set(\n self.studentresults,\n student.id, \n {\n loading: true,\n results: [],\n }\n );\n call([{\n methodname: 'local_treestudyplan_get_report_data',\n args: { pageid: self.structure.page.id, \n userid: student.id,\n firstperiod: self.structure.firstperiod,\n lastperiod: self.structure.lastperiod,\n }\n }])[0].then(function(response){\n self.studentresults[student.id].loading = false;\n self.studentresults[student.id].results = response;\n }).catch(notification.exception);\n }\n }\n self.studentsloading=false;\n }).catch(notification.exception);\n },\n expansionChanged(parm, id, val) {\n if(parm[0] == 'p') {\n parm = 'periods';\n } else if(parm[0] == 'l') {\n parm = 'lines';\n } else {\n parm = 'items';\n }\n\n if (parm == 'lines') {\n this.expansioninfo[parm][id[0]][id[1]].expanded = val;\n } else {\n this.expansioninfo[parm][id].expanded = val;\n }\n },\n groupExpansionChanged(group) {\n this.groupinfo[group.id].expanded = !this.groupinfo[group.id].expanded;\n },\n toggleSort(header) {\n if (this.sorting.header == header) {\n this.sorting.asc = !this.sorting.asc;\n } else {\n this.sorting.header = header;\n this.sorting.asc = true;\n }\n }\n },\n template: `\n \n \n \n \n \n \n \n \n \n \n \n 0\" \n :group=\"group\"\n :expanded=\"groupinfo[group.id].expanded\"\n @togglegroup=\"groupExpansionChanged\"\n :resultcolumns=\"resultColCount\"\n :studentinfocolumns=\"2\"\n >\n \n \n \n \n \n \n
\n `,\n });\n\n Vue.component('q-header', {\n props: {\n structure: {\n type: Object,\n },\n sorting: {\n type: Object,\n },\n expansion: {\n type: Object\n },\n },\n data() {\n return {\n text: strings.header,\n };\n },\n computed: {\n },\n methods: {\n conditions(item) {\n return conditionHeaders(item);\n },\n colspanPeriod(period) {\n const pid = period.period.id;\n if (this.expansion.periods[pid].expanded) {\n let sum = 0;\n for (const l of period.lines) {\n sum += this.colspanLine(period,l);\n }\n return sum;\n } else {\n return 2;\n }\n },\n colspanLine(period,line) {\n const pid = period.period.id;\n const lid = line.line.id;\n\n if (this.expansion.lines[pid][lid].expanded) {\n let sum = 0;\n for (const i of line.items) {\n sum += this.colspanItem(i);\n }\n return sum;\n } else {\n return 1;\n }\n },\n colspanItem(item) {\n if (this.expansion.items[item.id].expanded) {\n const cs = this.conditions(item);\n return 1+cs.length;\n } else {\n return 1;\n }\n },\n togglePeriod(period,val) {\n if ( val === undefined) {\n val = !(this.expansion.periods[period.id].expanded);\n }\n this.$emit('expansion','periods',period.id,val);\n },\n toggleLine(period,line,val) {\n if ( val === undefined) {\n val = !(this.expansion.lines[period.id][line.id].expanded);\n }\n this.$emit('expansion','lines',[period.id,line.id],val);\n },\n toggleItem(item,val) {\n if ( val === undefined) {\n val = !(this.expansion.items[item.id].expanded);\n }\n this.$emit('expansion','items',item.id,val);\n },\n toggleSort(heading) {\n this.$emit('togglesort',heading);\n }\n },\n mounted() {\n \n },\n updated() {\n },\n /* TODO: https://css-tricks.com/position-sticky-and-table-headers/ */\n template: `\n \n `,\n });\n\n Vue.component('q-groupheading', {\n props: {\n group: {\n type: Object,\n },\n resultcolumns: {\n type: Number,\n default: 1\n },\n studentinfocolumns: {\n type: Number,\n default: 1\n },\n expanded: {\n type: Boolean,\n }\n },\n data() {\n return {\n\n };\n },\n computed: {\n },\n methods: {\n toggleGroup(){\n this.$emit('togglegroup',this.group);\n }\n },\n template: `\n \n {{group.label}} | \n | \n
\n `,\n });\n\n Vue.component('q-inforow', {\n props: {\n resultcolumns: {\n type: Number,\n default: 1\n },\n studentinfocolumns: {\n type: Number,\n default: 1\n },\n },\n data() {\n return {\n };\n },\n computed: {\n },\n methods: {\n },\n template: `\n \n | \n | \n
\n `,\n });\n\n\n Vue.component('q-studentresults', {\n props: {\n student: {\n type: Object,\n },\n structure: {\n type: Object,\n },\n results: {\n type: Array,\n },\n loading: {\n type: Boolean,\n default: false\n },\n expansion: {\n type: Object,\n },\n even: {\n type: Boolean,\n default: false,\n }\n },\n data() {\n return {\n text: strings.studentresults,\n };\n },\n computed: {\n lastaccess() {\n if (this.student.lastaccess) {\n return format_datetime(this.student.lastaccess); // Takes date in milliseconds\n } else {\n return this.text.never;\n }\n }\n },\n methods: {\n useritems(line) {\n const list = [];\n for (const item of line.items) {\n let newitm = item;\n for (const itm of this.results) {\n if (item.id == itm.id) {\n newitm = itm;\n break;\n }\n }\n list.push(newitm);\n }\n return list;\n },\n conditions(item) {\n return conditions(item);\n },\n },\n /* https://css-tricks.com/position-sticky-and-table-headers/ */\n /* TODO: Rework below to make use of tables. Use as main element. Then create multiple as needed for the headers.\n This should create a much better view than using divs overal.\n */\n template: `\n
\n {{student.firstname}} {{student.lastname}} | \n {{lastaccess}} | \n \n 0\">\n \n \n \n | \n \n | \n \n \n \n | \n \n \n | \n \n
\n `,\n });\n\n Vue.component('q-courseresult', {\n props: {\n student: {\n type: Object,\n },\n item: {\n type: Object,\n },\n loading: {\n type: Boolean,\n default: false\n },\n },\n data() {\n return {\n text: strings.studentresults,\n };\n },\n computed: {\n hasprogressinfo() {\n const course = this.item.course;\n if (!course.enrolled) {\n return false;\n } else {\n return (course.completion || course.competency || course.grades)?true:false;\n }\n },\n completion_icon() {\n const completion = this.item.completion;\n switch(completion){\n default: // case \"incomplete\"\n return \"circle-o\";\n case \"pending\":\n return \"question-circle\";\n case \"failed\":\n return \"times-circle\";\n case \"progress\":\n return \"exclamation-circle\";\n case \"completed\":\n return \"check-circle\";\n case \"good\":\n return \"check-circle\";\n case \"excellent\":\n return \"check-circle\";\n }\n },\n },\n methods: {\n },\n template: `\n \n \n \n \n \n \n \n \n \n \n \n `,\n });\n\n Vue.component('q-conditionresult', {\n props: {\n student: {\n type: Object,\n },\n item: {\n type: Object,\n },\n loading: {\n type: Boolean,\n default: false\n },\n conditionidx: {\n type: Number,\n }\n },\n data() {\n return {\n text: strings.studentresults,\n };\n },\n computed: {\n conditions() {\n return conditions(this.item);\n },\n condition() {\n if (this.conditionidx >= 0 && this.conditionidx < this.conditions.length) {\n return this.conditions[this.conditionidx];\n } else {\n return null;\n }\n },\n hasprogressinfo() {\n const course = this.item.course;\n if (!course.enrolled) {\n return false;\n } else {\n return (course.completion || course.competency || course.grades);\n }\n },\n completion_icon() {\n const completion = this.condition_completion();\n switch(completion){\n default: // case \"incomplete\"\n return \"circle-o\";\n case \"pending\":\n return \"question-circle\";\n case \"failed\":\n return \"times-circle\";\n case \"progress\":\n return \"exclamation-circle\";\n case \"completed\":\n return \"check-circle\";\n case \"good\":\n return \"check-circle\";\n case \"excellent\":\n return \"check-circle\";\n }\n },\n condition_value() {\n const course = this.item.course;\n if (course.competency) {\n if (this.condition.grade) {\n // Return grade if possible.\n return this.condition.grade;\n } \n } else if(course.completion) {\n if (this.condition.grade) {\n // Return grade if possible.\n return this.condition.grade;\n } \n } else if(course.grades) {\n return this.condition.grade;\n }\n // Fallback to completion icon.\n const icon = this.completion_icon();\n return ``;\n },\n condition_completion() {\n // Unify completion information\n const course = this.item.course;\n if (course.competency) {\n const competency = this.condition;\n if (competency.proficient && competency.courseproficient) {\n return \"completed\";\n } else if (competency.proficient) {\n return \"completed\";\n } else if (competency.proficient === false) {\n return \"failed\";\n } else if (competency.progress) {\n return \"progress\";\n } else {\n return \"incomplete\";\n }\n } else if(course.completion) {\n return this.condition.status;\n } else if(course.grades) {\n return this.condition.completion;\n }\n }\n\n },\n methods: {\n },\n // TODO: Show actual grades when relevant at all (don;t forget the grade point completion requirement)\n template: `\n \n \n \n \n \n \n \n \n \n {{condition_value}}\n \n \n \n `,\n });\n\n\n\n },\n};"],"names":["Debugger","Math","PI","striptags","html","tmp","document","createElement","innerHTML","text","textContent","innerText","remove","conditions","item","course","list","competency","cmp","competencies","push","completion","cnd","itm","items","grades","g","selected","install","Vue","use","TSComponents","FitTextVue","strings","report","loading","studyplan_past","studyplan_present","studyplan_future","back","invalid","error","header","overall","students","firstname","lastname","email","lastaccess","studentresults","completion_incomplete","completion_failed","completion_pending","completion_progress","completion_completed","completion_good","completion_excellent","student_not_tracked","never","component","props","structure","type","Object","data","studentsloading","expansioninfo","periods","lines","groupinfo","sorting","asc","created","watch","immediate","handler","loadStudents","firstperiod","period","pid","id","this","$set","expanded","length","line","lid","computed","sortedstudents","group","users","sort","a","b","d","e","String","localeCompare","resultColCount","count","methods","self","methodname","args","studyplan_id","studyplan","then","response","student","results","pageid","page","userid","lastperiod","catch","notification","exception","expansionChanged","parm","val","groupExpansionChanged","toggleSort","template","expansion","name","details","title","tooltip","description","requirement","typename","conditionHeaders","colspanPeriod","sum","l","colspanLine","i","colspanItem","togglePeriod","undefined","$emit","toggleLine","toggleItem","heading","mounted","updated","resultcolumns","Number","default","studentinfocolumns","Boolean","toggleGroup","Array","even","useritems","newitm","hasprogressinfo","enrolled","completion_icon","conditionidx","condition","condition_completion","condition_value","grade","proficient","courseproficient","progress","status"],"mappings":"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"}
\ No newline at end of file
diff --git a/amd/src/studyplan-report-components.js b/amd/src/studyplan-report-components.js
index f5f48c7..94206cd 100644
--- a/amd/src/studyplan-report-components.js
+++ b/amd/src/studyplan-report-components.js
@@ -521,23 +521,18 @@ export default {
:class="'q-item-heading ' + ((expansion.items[item.id].expanded)?'expanded':'collapsed')"
:colspan="colspanItem(item)"
:rowspan='(expansion.items[item.id].expanded)?1:3'
- >