From 6b616c0a6a310961bcc8d25b5d0e283e47d2e5c5 Mon Sep 17 00:00:00 2001 From: PMKuipers Date: Fri, 23 Feb 2024 23:19:46 +0100 Subject: [PATCH] Got sorting working and polished up the table view --- amd/build/studyplan-report-components.min.js | 2 +- .../studyplan-report-components.min.js.map | 2 +- amd/build/util/fittext-vue.min.js | 2 +- amd/build/util/fittext-vue.min.js.map | 2 +- amd/src/studyplan-report-components.js | 224 ++++++++++++++---- amd/src/util/fittext-vue.js | 16 +- classes/associationservice.php | 3 + .../aggregators/competency_aggregator.php | 5 +- css/devstyles.css | 127 ++++++---- scss/studyplan-report.scss | 129 +++++++--- styles.css | 127 ++++++---- 11 files changed, 443 insertions(+), 196 deletions(-) diff --git a/amd/build/studyplan-report-components.min.js b/amd/build/studyplan-report-components.min.js index 8de84fd..b675212 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"],(function(_exports,_stringHelper,_ajax,_notification,_debugger,_config,_treestudyplanComponents,_fittextVue){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);const debug=new _debugger.default("treestudyplan-viewer");Math.PI;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"},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"}});Vue.component("q-studyplanreport",{props:{structure:{type:Object}},data:()=>({students:[],studentresults:{},expansioninfo:{periods:{},lines:{},items:{}},groupinfo:{},sorting:{name:"asc"}}),created(){this.loadStudents()},watch:{structure:{immediate:!0,handler(structure){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}),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(){return this.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+=3}return count}},methods:{loadStudents(){const self=this;(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.label,{expand:!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)}})).catch(_notification.default.exception)},expansionChanged(parm,id,val){parm="p"==parm[0]?"periods":"l"==parm[0]?"lines":"items",debug.info("Expansion Changed",parm,id,val),"lines"==parm?this.expansioninfo[parm][id[0]][id[1]].expanded=val:this.expansioninfo[parm][id].expanded=val}},template:"\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.title});else if(course.completion)for(const cnd of course.completion.conditions)for(const itm of cnd.items)list.push({name:itm.title});else if(course.grades)for(const g of course.grades)g.selected&&list.push({name: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 1},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),debug.info("Toggle item",item,val),this.$emit("expansion","items",item.id,val)}},mounted(){},updated(){},template:'\n \n \x3c!-- period heading --\x3e\n {{text.students}}\n  {{ p.period.fullname}}\n \n \x3c!-- line heading --\x3e\n \n \n \x3c!-- item heading --\x3e\n \n \n \x3c!-- condition heading --\x3e\n \n \n \n '}),Vue.component("q-groupheading",{props:{structure:{type:Object}},data:()=>({}),computed:{},methods:{},template:"\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}},data:()=>({text:strings.studentresults}),computed:{},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 \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 '}),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 '})}};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"],(function(_exports,_stringHelper,_ajax,_notification,_debugger,_config,_treestudyplanComponents,_fittextVue){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"},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"}});Vue.component("q-studyplanreport",{props:{structure:{type:Object}},data:()=>({students:[],studentresults:{},studentsloading:!0,expansioninfo:{periods:{},lines:{},items:{}},groupinfo:{},sorting:{header:"lastname",asc:!0}}),created(){this.loadStudents()},watch:{structure:{immediate:!0,handler(structure){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}),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;return this.sorting.asc||(d=b,e=a),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 '}),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 \x3c!-- period heading --\x3e\n {{text.students}}\n 0)?1:4\'\n > 0)\' href=\'#\' @click.prevent="togglePeriod(p.period)"\n > {{ p.period.fullname}}{{ p.period.fullname}}\n \n \x3c!-- line heading --\x3e\n \n \n \x3c!-- item heading --\x3e\n \n \n \x3c!-- condition heading --\x3e\n \n {{text.firstname}}\n / {{text.lastname}}\n \n \n {{text.email}}\n \n \n \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:{},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 {{student.email}}\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 '}),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 '})}};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 ff4483c..ec15ef5 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';\n\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 * 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.title,\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 });\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 });\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 },\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 }\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 expansioninfo: {\n periods: {},\n lines: {},\n items: {},\n },\n groupinfo: {},\n\n sorting: {\n name: \"asc\",\n }\n };\n },\n created() {\n this.loadStudents();\n },\n watch:{\n structure: {\n immediate: true,\n handler (structure) {\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?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 return this.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 += 3;\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 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.label,\n {\n expand: 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 }).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 debug.info('Expansion Changed',parm,id,val);\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\n },\n template: `\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 1;\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 debug.info(\"Toggle item\",item,val);\n this.$emit('expansion','items',item.id,val);\n },\n },\n mounted() {\n \n },\n updated() {\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 \n {{text.students}}\n  {{ p.period.fullname}}\n \n \n \n \n \n \n \n \n \n \n \n `,\n });\n\n Vue.component('q-groupheading', {\n props: {\n structure: {\n type: Object,\n },\n },\n data() {\n return {\n\n };\n },\n computed: {\n },\n methods: {\n },\n template: `\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 },\n data() {\n return {\n text: strings.studentresults,\n };\n },\n computed: {\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 \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);\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 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\n\n\n },\n};"],"names":["debug","Debugger","Math","PI","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","studentresults","completion_incomplete","completion_failed","completion_pending","completion_progress","completion_completed","completion_good","completion_excellent","student_not_tracked","component","props","structure","type","Object","data","expansioninfo","periods","lines","groupinfo","sorting","name","created","loadStudents","watch","immediate","handler","firstperiod","period","pid","id","this","$set","expanded","line","lid","computed","sortedstudents","resultColCount","count","length","methods","self","methodname","args","studyplan_id","studyplan","then","response","group","label","expand","student","users","results","pageid","page","userid","lastperiod","catch","notification","exception","expansionChanged","parm","val","info","template","expansion","text","title","conditionHeaders","colspanPeriod","sum","l","colspanLine","i","colspanItem","togglePeriod","undefined","$emit","toggleLine","toggleItem","mounted","updated","Array","Boolean","default","useritems","newitm","hasprogressinfo","enrolled","completion_icon","conditionidx","Number","condition","condition_completion","condition_value","grade","proficient","courseproficient","progress","status"],"mappings":"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"} \ 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';\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 },\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 }\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 this.loadStudents();\n },\n watch:{\n structure: {\n immediate: true,\n handler (structure) {\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?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 should make a deep copy for purity's sake, but this works just as well.\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 return String(d[this.sorting.header]).localeCompare(String(e[this.sorting.header]));\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
\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 /* 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 \n {{text.students}}\n  {{ p.period.fullname}}{{ p.period.fullname}}\n \n \n \n \n \n \n \n \n \n {{text.firstname}}\n / {{text.lastname}}\n \n \n {{text.email}}\n \n \n \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 },\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 {{student.email}}\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 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\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","studentresults","completion_incomplete","completion_failed","completion_pending","completion_progress","completion_completed","completion_good","completion_excellent","student_not_tracked","component","props","structure","type","Object","data","studentsloading","expansioninfo","periods","lines","groupinfo","sorting","asc","created","loadStudents","watch","immediate","handler","firstperiod","period","pid","id","this","$set","expanded","line","lid","computed","sortedstudents","group","users","sort","a","b","d","e","String","localeCompare","resultColCount","count","length","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/build/util/fittext-vue.min.js b/amd/build/util/fittext-vue.min.js index e5b0ca0..e0e9ce5 100644 --- a/amd/build/util/fittext-vue.min.js +++ b/amd/build/util/fittext-vue.min.js @@ -1,3 +1,3 @@ -define("local_treestudyplan/util/fittext-vue",["exports","./css-calc","./fitty","./textfit"],(function(_exports,_cssCalc,_fitty,_textfit){var obj;Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_fitty=(obj=_fitty)&&obj.__esModule?obj:{default:obj};var _default={install(Vue){Vue.component("fittext",{props:{maxsize:{type:String,default:"512px"},minsize:{type:String,default:"10px"},vertical:Boolean,singleline:Boolean,dynamic:Boolean},data:()=>({resizeObserver:null,mutationObserver:null}),computed:{rootStyle(){return this.vertical?"height: 100%;":"width: 100%;"}},methods:{},mounted(){(0,_fitty.default)(this.$refs.text,{minSize:(0,_cssCalc.calc)(this.minsize),maxSize:(0,_cssCalc.calc)(this.maxsize),vertical:this.vertical,multiline:!this.singleline})},unmounted(){this.mutationObserver&&this.mutationObserver.disconnect(),this.resizeObserver&&this.resizeObserver.disconnect()},template:"\n
\n \n
\n "})}};return _exports.default=_default,_exports.default})); +define("local_treestudyplan/util/fittext-vue",["exports","./css-calc","./fitty","./textfit"],(function(_exports,_cssCalc,_fitty,_textfit){var obj;Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_fitty=(obj=_fitty)&&obj.__esModule?obj:{default:obj};var _default={install(Vue){Vue.component("fittext",{props:{maxsize:{type:String,default:"512px"},minsize:{type:String,default:"10px"},vertical:Boolean,singleline:Boolean,dynamic:Boolean},data:()=>({resizeObserver:null,mutationObserver:null}),computed:{rootStyle(){return this.vertical?"height: 100%;":"width: 100%;"}},methods:{},mounted(){(0,_fitty.default)(this.$refs.text,{minSize:(0,_cssCalc.calc)(this.minsize),maxSize:(0,_cssCalc.calc)(this.maxsize),vertical:this.vertical,multiline:!this.singleline})},unmounted(){this.mutationObserver&&this.mutationObserver.disconnect(),this.resizeObserver&&this.resizeObserver.disconnect()},template:"\n
\n \n
\n "})}};return _exports.default=_default,_exports.default})); //# sourceMappingURL=fittext-vue.min.js.map \ No newline at end of file diff --git a/amd/build/util/fittext-vue.min.js.map b/amd/build/util/fittext-vue.min.js.map index fec1903..239142d 100644 --- a/amd/build/util/fittext-vue.min.js.map +++ b/amd/build/util/fittext-vue.min.js.map @@ -1 +1 @@ -{"version":3,"file":"fittext-vue.min.js","sources":["../../src/util/fittext-vue.js"],"sourcesContent":["/*eslint no-unused-vars: warn */\n/*eslint max-len: [\"error\", { \"code\": 160 }] */\n/*eslint-disable no-trailing-spaces */\n/*eslint-env es6*/\n\nimport {calc} from \"./css-calc\";\nimport fitty from \"./fitty\";\nimport {textFit} from \"./textfit\";\n\nexport default {\n install(Vue/*,options*/){\n Vue.component('fittext',{\n props: {\n maxsize: {\n type: String,\n default: \"512px\",\n },\n minsize: {\n type: String,\n default: \"10px\",\n },\n vertical: Boolean,\n singleline: Boolean,\n dynamic: Boolean,\n },\n data() {\n return {\n resizeObserver: null,\n mutationObserver: null,\n };\n },\n computed: {\n rootStyle() {\n if (this.vertical) {\n return `height: 100%;`;\n } else {\n return `width: 100%;`;\n }\n }\n },\n methods: {\n },\n mounted() {\n const self = this;\n // If the content could change after initial presentation,\n // Use the fitty method. It is slightly worse on multiline horizontal text,\n // but better supports content that can change later on.\n fitty(self.$refs.text,\n {\n minSize: calc(self.minsize),\n maxSize: calc(self.maxsize),\n vertical: self.vertical,\n multiline: !self.singleline,\n });\n /*\n } else {\n // Since the method textFit uses does not do well with\n // content that is altered after the initial change, but it does to better\n // with vertically aligned text\n textFit(self.$refs.text,{\n multiLine: !self.singleline, // if true, textFit will not set white-space: no-wrap\n detectMultiLine: false, // disable to turn off automatic multi-line sensing\n minFontSize: calc(self.minsize),\n maxFontSize: calc(self.maxsize),\n reProcess: true, \n widthOnly: !self.vertical,\n });\n } */\n },\n unmounted() {\n if(this.mutationObserver) {\n this.mutationObserver.disconnect();\n }\n if(this.resizeObserver) {\n this.resizeObserver.disconnect();\n }\n },\n template: `\n
\n \n
\n `,\n });\n },\n};"],"names":["install","Vue","component","props","maxsize","type","String","default","minsize","vertical","Boolean","singleline","dynamic","data","resizeObserver","mutationObserver","computed","rootStyle","this","methods","mounted","$refs","text","minSize","maxSize","multiline","unmounted","disconnect","template"],"mappings":"qSASe,CACXA,QAAQC,KACJA,IAAIC,UAAU,UAAU,CACxBC,MAAO,CACHC,QAAS,CACLC,KAAMC,OACNC,QAAS,SAEbC,QAAS,CACLH,KAAMC,OACNC,QAAS,QAEbE,SAAUC,QACVC,WAAYD,QACZE,QAASF,SAEbG,KAAI,KACO,CACHC,eAAgB,KAChBC,iBAAkB,OAG1BC,SAAU,CACNC,mBACQC,KAAKT,SACG,gBAEA,iBAIpBU,QAAS,GAETC,6BACiBF,KAIFG,MAAMC,KACb,CACAC,SAAS,iBANAL,KAMUV,SACnBgB,SAAS,iBAPAN,KAOUd,SACnBK,SARSS,KAQMT,SACfgB,WATSP,KASQP,cAiBzBe,YACOR,KAAKH,uBACCA,iBAAiBY,aAEvBT,KAAKJ,qBACCA,eAAea,cAG5BC,SAAW"} \ No newline at end of file +{"version":3,"file":"fittext-vue.min.js","sources":["../../src/util/fittext-vue.js"],"sourcesContent":["/*eslint no-unused-vars: warn */\n/*eslint max-len: [\"error\", { \"code\": 160 }] */\n/*eslint-disable no-trailing-spaces */\n/*eslint-env es6*/\n\nimport {calc} from \"./css-calc\";\nimport fitty from \"./fitty\";\nimport {textFit} from \"./textfit\";\n\nexport default {\n install(Vue/*,options*/){\n Vue.component('fittext',{\n props: {\n maxsize: {\n type: String,\n default: \"512px\",\n },\n minsize: {\n type: String,\n default: \"10px\",\n },\n vertical: Boolean,\n singleline: Boolean,\n dynamic: Boolean,\n },\n data() {\n return {\n resizeObserver: null,\n mutationObserver: null,\n };\n },\n computed: {\n rootStyle() {\n if (this.vertical) {\n return `height: 100%;`;\n } else {\n return `width: 100%;`;\n }\n }\n },\n methods: {\n },\n mounted() {\n const self = this;\n // If the content could change after initial presentation,\n // Use the fitty method. It is slightly worse on multiline horizontal text,\n // but better supports content that can change later on.\n fitty(self.$refs.text,\n {\n minSize: calc(self.minsize),\n maxSize: calc(self.maxsize),\n vertical: self.vertical,\n multiline: !self.singleline,\n });\n },\n unmounted() {\n if(this.mutationObserver) {\n this.mutationObserver.disconnect();\n }\n if(this.resizeObserver) {\n this.resizeObserver.disconnect();\n }\n },\n template: `\n
\n \n
\n `,\n });\n },\n};"],"names":["install","Vue","component","props","maxsize","type","String","default","minsize","vertical","Boolean","singleline","dynamic","data","resizeObserver","mutationObserver","computed","rootStyle","this","methods","mounted","$refs","text","minSize","maxSize","multiline","unmounted","disconnect","template"],"mappings":"qSASe,CACXA,QAAQC,KACJA,IAAIC,UAAU,UAAU,CACxBC,MAAO,CACHC,QAAS,CACLC,KAAMC,OACNC,QAAS,SAEbC,QAAS,CACLH,KAAMC,OACNC,QAAS,QAEbE,SAAUC,QACVC,WAAYD,QACZE,QAASF,SAEbG,KAAI,KACO,CACHC,eAAgB,KAChBC,iBAAkB,OAG1BC,SAAU,CACNC,mBACQC,KAAKT,SACG,gBAEA,iBAIpBU,QAAS,GAETC,6BACiBF,KAIFG,MAAMC,KACb,CACAC,SAAS,iBANAL,KAMUV,SACnBgB,SAAS,iBAPAN,KAOUd,SACnBK,SARSS,KAQMT,SACfgB,WATSP,KASQP,cAGzBe,YACOR,KAAKH,uBACCA,iBAAiBY,aAEvBT,KAAKJ,qBACCA,eAAea,cAG5BC,SAAW"} \ No newline at end of file diff --git a/amd/src/studyplan-report-components.js b/amd/src/studyplan-report-components.js index 4a411b9..0728e44 100644 --- a/amd/src/studyplan-report-components.js +++ b/amd/src/studyplan-report-components.js @@ -14,7 +14,6 @@ import Config from 'core/config'; import TSComponents from './treestudyplan-components'; import FitTextVue from './util/fittext-vue'; - const debug = new Debugger("treestudyplan-viewer"); @@ -23,6 +22,19 @@ const π = Math.PI; // Gravity value for arrow lines - determines how much a line is pulled in the direction of the start/end before changing direction const LINE_GRAVITY = 1.3; +/** + * Strip tags from html + * @param {*} html + * @returns + */ +function striptags(html) { + const tmp = document.createElement("DIV"); + tmp.innerHTML = html; + const text = tmp.textContent || tmp.innerText; + tmp.remove(); + return text; +} + /** * Retrieve condition headers * @param {Object} item @@ -33,7 +45,8 @@ function conditionHeaders(item) { if (course.competency) { for (const cmp of course.competency.competencies) { list.push({ - name: cmp.title, + name: (cmp.details?`${cmp.title} - ${cmp.details}`:cmp.title), + tooltip: cmp.description, }); } } else if(course.completion) { @@ -41,6 +54,7 @@ function conditionHeaders(item) { for (const itm of cnd.items) { list.push({ name: itm.title, + tooltip: `${itm.details.type}: ${itm.details.requirement}`, }); } } @@ -49,6 +63,7 @@ function conditionHeaders(item) { if (g.selected) { list.push({ name: g.name, + tooltip: `${g.typename}: ${striptags(g.name)}`, }); } } @@ -103,7 +118,10 @@ export default { }, header: { overall: 'overall', - students: 'students@core' + students: 'students@core', + firstname: 'firstname@core', + lastname: 'lastname@core', + email: 'email@core' }, studentresults: { completion_incomplete: "completion_incomplete", @@ -133,6 +151,7 @@ export default { return { students: [], studentresults: {}, + studentsloading: true, expansioninfo: { periods: {}, lines: {}, @@ -141,7 +160,8 @@ export default { groupinfo: {}, sorting: { - name: "asc", + header: 'lastname', + asc: true, } }; }, @@ -203,7 +223,22 @@ export default { }, computed: { sortedstudents(){ - return this.students; + const self=this; + // Probably should make a deep copy for purity's sake, but this works just as well. + const students = this.students; + for (const group of this.students) { + group.users.sort((a,b) => { + let d = a; + let e = b; + if (!this.sorting.asc) { + d = b; + e = a; + } + return String(d[this.sorting.header]).localeCompare(String(e[this.sorting.header])); + }); + } + + return students; }, resultColCount(){ let count = 0; @@ -211,7 +246,7 @@ export default { const pid = period.period.id; if (!this.expansioninfo.periods[pid].expanded) { // This period is not expanded. Make it 3 units wide - count += 3; + count += 2; } else { for (const line of period.lines) { const lid = line.line.id; @@ -235,6 +270,7 @@ export default { methods: { loadStudents() { const self = this; + self.studentsloading=true; call([{ methodname: 'local_treestudyplan_all_associated_grouped', args: { studyplan_id: this.structure.studyplan.id} @@ -243,9 +279,9 @@ export default { for(const group of self.students) { self.$set( self.groupinfo, - group.label, + group.id, { - expand: true, + expanded: true, } ); @@ -271,6 +307,7 @@ export default { }).catch(notification.exception); } } + self.studentsloading=false; }).catch(notification.exception); }, expansionChanged(parm, id, val) { @@ -281,7 +318,6 @@ export default { } else { parm = 'items'; } - debug.info('Expansion Changed',parm,id,val); if (parm == 'lines') { this.expansioninfo[parm][id[0]][id[1]].expanded = val; @@ -289,29 +325,61 @@ export default { this.expansioninfo[parm][id].expanded = val; } }, - + groupExpansionChanged(group) { + this.groupinfo[group.id].expanded = !this.groupinfo[group.id].expanded; + }, + toggleSort(header) { + if (this.sorting.header == header) { + this.sorting.asc = !this.sorting.asc; + } else { + this.sorting.header = header; + this.sorting.asc = true; + } + } }, template: ` + + + + + + + -