{"version":3,"file":"studyplan-report-components.min.js","sources":["../src/studyplan-report-components.js"],"sourcesContent":["/* eslint no-var: \"error\"*/\n/* eslint no-unused-vars: warn */\n/* eslint max-depth: [\"error\", 6] */\n/* eslint promise/no-nesting: \"off\" */\n/* eslint camelcase: \"off\" */\n/* eslint-env es6*/\n// Put this file in path/to/plugin/amd/src\n\nimport {loadStrings} from './util/string-helper';\nimport {call} from 'core/ajax';\nimport notification from 'core/notification';\nimport TSComponents from './treestudyplan-components';\nimport FitTextVue from './util/fittext-vue';\nimport {formatDatetime} from \"./util/date-helper\";\n\n\n/**\n * Strip tags from html\n * @param {*} html\n * @returns {string}\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 * @returns {Array}\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 * @returns {Array}\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 = loadStrings({\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 watch: {\n structure: {\n immediate: true,\n handler(structure) {\n this.loadStudents(); // Reload the student list\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 // Probably could 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 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 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((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: {\n pageid: self.structure.page.id,\n userid: student.id,\n firstperiod: self.structure.firstperiod,\n lastperiod: self.structure.lastperiod,\n }\n }])[0].then((response) => {\n self.studentresults[student.id].loading = false;\n self.studentresults[student.id].results = response;\n return;\n }).catch(notification.exception);\n }\n }\n self.studentsloading = false;\n return;\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