Got sorting working and polished up the table view

This commit is contained in:
PMKuipers 2024-02-23 23:19:46 +01:00
parent b3af1fa8c4
commit 6b616c0a6a
11 changed files with 443 additions and 196 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -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 <div class='q-fittext' ref='container' :style=rootStyle\">\n <span :style=\"'display:block; white-space:'+ ((singleline)?'nowrap':'normal')+';'\" class='q-fittext-text' ref='text'><slot></slot>\n </span\n ></div>\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 <div class='q-fittext' ref='container' :style=\"rootStyle\">\n <span :style=\"'display:block; white-space:'+ ((singleline)?'nowrap':'normal')+';'\" class='q-fittext-text' ref='text'><slot></slot>\n </span\n ></div>\n "})}};return _exports.default=_default,_exports.default}));
//# sourceMappingURL=fittext-vue.min.js.map

View File

@ -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 <div class='q-fittext' ref='container' :style=rootStyle\">\n <span :style=\"'display:block; white-space:'+ ((singleline)?'nowrap':'normal')+';'\" class='q-fittext-text' ref='text'><slot></slot>\n </span\n ></div>\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"}
{"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 <div class='q-fittext' ref='container' :style=\"rootStyle\">\n <span :style=\"'display:block; white-space:'+ ((singleline)?'nowrap':'normal')+';'\" class='q-fittext-text' ref='text'><slot></slot>\n </span\n ></div>\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"}

View File

@ -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: `
<table class='q-studyplanreport'
:style="'--resultColCount: '+resultColCount+';'">
<colgroup class="q-col-studentinfo">
<col class="q-name"></col>
<col class="q-email"></col>
</colgroup>
<colgroup class="q-col-resultinfo">
<col v-for="n in resultColCount"></col>
</colgroup>
<q-header
:sorting='sorting'
:structure='structure'
:expansion='expansioninfo'
@expansion='expansionChanged'
@togglesort="toggleSort"
></q-header>
<template v-for="group in sortedstudents">
<q-groupheading v-if="group.users" :label="group.label" :groupinfo="groupinfo[group.label]"></q-groupheading>
<template v-if='group.users && groupinfo[group.label].expand'>
<q-studentresults v-for="student in group.users"
:student='student'
:structure='structure'
:results='studentresults[student.id].results'
:loading='studentresults[student.id].loading'
:expansion='expansioninfo'
></q-studentresults>
<template v-if="!studentsloading">
<template v-for="group in sortedstudents">
<q-groupheading
v-if="group.users && group.users.length > 0"
:group="group"
:expanded="groupinfo[group.id].expanded"
@togglegroup="groupExpansionChanged"
:resultcolumns="resultColCount"
:studentinfocolumns="2"
></q-groupheading>
<template v-if='groupinfo[group.id].expanded'>
<q-studentresults v-for="(student,idx) in group.users"
:key="student.id"
:student='student'
:even="(idx%2==1)"
:structure='structure'
:results='studentresults[student.id].results'
:loading='studentresults[student.id].loading'
:expansion='expansioninfo'
></q-studentresults>
</template>
</template>
</template>
<q-inforow v-else
:resultcolumns="resultColCount"
:studentinfocolumns="2"><div class="spinner-border spinner-border-sm text-info" role="status"></div></q-inforow>
</table>
`,
});
@ -348,7 +416,7 @@ export default {
}
return sum;
} else {
return 1;
return 2;
}
},
colspanLine(period,line) {
@ -389,9 +457,11 @@ export default {
if ( val === undefined) {
val = !(this.expansion.items[item.id].expanded);
}
debug.info("Toggle item",item,val);
this.$emit('expansion','items',item.id,val);
},
toggleSort(heading) {
this.$emit('togglesort',heading);
}
},
mounted() {
@ -405,21 +475,22 @@ export default {
template: `
<thead class='q-header'>
<tr> <!-- period heading -->
<th rowspan='4' class='q-studentname'><span>{{text.students}}</span></th>
<th rowspan='3' colspan='2' class='q-studentinfo q-generic'><span>{{text.students}}</span></th>
<th v-for="p in structure.periods"
:class="'q-period-heading '+ ((expansion.periods[p.period.id].expanded)?'expanded':'collapsed')"
:colspan='colspanPeriod(p)'
:rowspan='(expansion.periods[p.period.id].expanded)?1:4'
><span class="q-wrap"><a href='#' @click.prevent="togglePeriod(p.period)"
:rowspan='(expansion.periods[p.period.id].expanded && p.lines.length > 0)?1:4'
><span class="q-wrap"><a v-if='(p.lines.length > 0)' href='#' @click.prevent="togglePeriod(p.period)"
><i v-if="expansion.periods[p.period.id].expanded"
class='q-chevron fa fa-chevron-down'></i
><i v-else class='q-chevron fa fa-chevron-right'></i
>&nbsp;{{ p.period.fullname}}</a></span
class='q-chevron fa fa-minus'></i
><i v-else class='q-chevron fa fa-plus'></i
>&nbsp;{{ p.period.fullname}}</a
><span v-else>{{ p.period.fullname}}</span></span
></th>
</tr>
<tr> <!-- line heading -->
<template v-for="p in structure.periods">
<template v-if="expansion.periods[p.period.id].expanded">
<template v-if="expansion.periods[p.period.id].expanded">
<th v-for="l in p.lines"
:class="'q-line-heading ' + ((expansion.lines[p.period.id][l.line.id].expanded)?'expanded':'collapsed')"
:colspan="colspanLine(p,l)"
@ -446,9 +517,9 @@ export default {
><span class="q-wrap"><a href='#'
@click.prevent="toggleItem(item)"
><i v-if="expansion.items[item.id].expanded"
class='q-chevron fa fa-chevron-down'></i
class='q-chevron fa fa-minus'></i
><i v-else
class='q-chevron fa fa-chevron-right'></i
class='q-chevron fa fa-plus'></i
></a
>&nbsp;<a style="display: inline-block;" href='#'
@click.prevent="toggleItem(item)"
@ -466,6 +537,23 @@ export default {
</template>
</tr>
<tr> <!-- condition heading -->
<th class="q-studentinfo q-name">
<fittext maxsize="12pt"
><a href="#" @click.prevent="toggleSort('firstname')">{{text.firstname}}</a
><i v-if="sorting.header=='firstname' && sorting.asc" class='fa fa-sort-asc fa-fw'></i
><i v-else-if="sorting.header=='firstname' && !sorting.asc" class='fa fa-sort-desc fa-fw'></i>
/ <a href="#" @click.prevent="toggleSort('lastname')">{{text.lastname}}</a
><i v-if="sorting.header=='lastname' && sorting.asc" class='fa fa-sort-asc fa-fw'></i
><i v-else-if="sorting.header=='lastname' && !sorting.asc" class='fa fa-sort-desc fa-fw'></i
></fittext>
</th>
<th class="q-studentinfo q-email">
<fittext maxsize="12pt"
><a href="#" @click.prevent="toggleSort('email')">{{text.email}}</a
><i v-if="sorting.header=='email' && sorting.asc" class='fa fa-sort-asc fa-fw'></i
><i v-else-if="sorting.header=='email' && !sorting.asc" class='fa fa-sort-desc fa-fw'></i
></fittext>
</th>
<template v-for="p in structure.periods">
<template v-if="expansion.periods[p.period.id].expanded">
<template v-for="l in p.lines">
@ -476,10 +564,11 @@ export default {
><span class='q-wrap'>{{ text.overall }}</span></th>
<th v-for="c in conditions(item)"
class='q-condition-heading'
><span class="q-wrap"><fittext vertical maxsize="18pt"><span class='q-label'
:title="c.name"
v-html="c.name"></span
></span></fittext
><span class="q-wrap"><fittext vertical maxsize="14pt"><a class='q-label q-condition-label'
:title="c.tooltip" href="#" @click.prevent
v-b-tooltip.focus
v-html="c.name"></a
></fittext></span
></th>
</template>
</template>
@ -494,9 +583,20 @@ export default {
Vue.component('q-groupheading', {
props: {
structure: {
group: {
type: Object,
},
resultcolumns: {
type: Number,
default: 1
},
studentinfocolumns: {
type: Number,
default: 1
},
expanded: {
type: Boolean,
}
},
data() {
return {
@ -506,14 +606,49 @@ export default {
computed: {
},
methods: {
toggleGroup(){
this.$emit('togglegroup',this.group);
}
},
template: `
<tr class='q-groupheading'>
<th :colspan="studentinfocolumns"><a href="#" @click.prevent="toggleGroup"
><i v-if="expanded" class="fa fa-minus"></i
><i v-else class="fa fa-plus"></i
>&nbsp;{{group.label}}</a></th>
<td :colspan="resultcolumns"></td>
</tr>
`,
});
Vue.component('q-inforow', {
props: {
resultcolumns: {
type: Number,
default: 1
},
studentinfocolumns: {
type: Number,
default: 1
},
},
data() {
return {
};
},
computed: {
},
methods: {
},
template: `
<tr class='q-inforow'>
<td :colspan="studentinfocolumns"><slot></slot></td>
<td :colspan="resultcolumns"></td>
</tr>
`,
});
Vue.component('q-studentresults', {
props: {
student: {
@ -532,6 +667,10 @@ export default {
expansion: {
type: Object,
},
even: {
type: Boolean,
default: false,
}
},
data() {
return {
@ -564,10 +703,11 @@ export default {
This should create a much better view than using divs overal.
*/
template: `
<tr class='q-student-results'>
<td class='q-studentname'><span>{{student.firstname}} {{student.lastname}}</span></td>
<tr :class="'q-student-results userrow ' + (even?'even':'odd')">
<td class='q-studentinfo q-name'><fittext maxsize="12pt">{{student.firstname}} {{student.lastname}}</fittext></td>
<td class='q-studentinfo q-email'><fittext maxsize="12pt">{{student.email}}</fittext></td>
<template v-for="p in structure.periods">
<template v-if="expansion.periods[p.period.id].expanded">
<template v-if="expansion.periods[p.period.id].expanded && p.lines.length > 0">
<template v-for="l in p.lines">
<template v-if="expansion.lines[p.period.id][l.line.id].expanded">
<template v-for="item in useritems(l)">
@ -594,7 +734,7 @@ export default {
<td v-else class='q-result collapsed'>&nbsp;</td>
</template>
</template>
<td v-else class='q-result collapsed'>&nbsp;</td>
<td v-else colspan="2" class='q-result collapsed'>&nbsp;</td>
</template>
</tr>
`,
@ -624,7 +764,7 @@ export default {
if (!course.enrolled) {
return false;
} else {
return (course.completion || course.competency || course.grades);
return (course.completion || course.competency || course.grades)?true:false;
}
},
completion_icon() {

View File

@ -52,20 +52,6 @@ export default {
vertical: self.vertical,
multiline: !self.singleline,
});
/*
} else {
// Since the method textFit uses does not do well with
// content that is altered after the initial change, but it does to better
// with vertically aligned text
textFit(self.$refs.text,{
multiLine: !self.singleline, // if true, textFit will not set white-space: no-wrap
detectMultiLine: false, // disable to turn off automatic multi-line sensing
minFontSize: calc(self.minsize),
maxFontSize: calc(self.maxsize),
reProcess: true,
widthOnly: !self.vertical,
});
} */
},
unmounted() {
if(this.mutationObserver) {
@ -76,7 +62,7 @@ export default {
}
},
template: `
<div class='q-fittext' ref='container' :style=rootStyle">
<div class='q-fittext' ref='container' :style="rootStyle">
<span :style="'display:block; white-space:'+ ((singleline)?'nowrap':'normal')+';'" class='q-fittext-text' ref='text'><slot></slot>
</span
></div>

View File

@ -563,6 +563,7 @@ class associationservice extends \external_api {
*/
public static function all_associated_grouped_returns() : \external_description {
return new \external_multiple_structure(new \external_single_structure([
'id' => new \external_value(PARAM_INT, 'id of group'),
'label' => new \external_value(PARAM_TEXT,'group label'),
'users' => new \external_multiple_structure(self::user_structure()),
]));
@ -581,6 +582,7 @@ class associationservice extends \external_api {
$userlist = [
[
'id' => 0,
'label' => get_string("individuals",'local_treestudyplan'),
'users' => self::associated_users($studyplanid),
]
@ -604,6 +606,7 @@ class associationservice extends \external_api {
$rs->close();
$userlist[] = [
'id' => $c->id,
'label' => $c->name,
'users' => $users,
];

View File

@ -170,6 +170,9 @@ class competency_aggregator extends \local_treestudyplan\aggregator {
$cci = new coursecompetencyinfo($course,$studyitem);
$competencies = $cci->course_competencies();
if (count($competencies) == 0) {
return completion::INCOMPLETE;
}
$count = 0;
$courseproficient = 0;
$proficient = 0;
@ -192,7 +195,7 @@ class competency_aggregator extends \local_treestudyplan\aggregator {
}
}
// Determine minimum for
$limit = $this->cfg()->thresh_completed * $count;
$coursefinished = ($course->enddate) ? ($course->enddate < time()) : false;

View File

@ -1577,27 +1577,28 @@ body.path-local-treestudyplan .editmode-switch-form > * {
.path-local-treestudyplan {
font: inherit;
--border-color: var(--primary);
--border-color: #ccc;
--conditions-bgcolor: #e7e7e7;
--courseresult-bgcolor: white;
--studentinfo-bgcolor: white;
}
.path-local-treestudyplan table.q-studyplanreport {
table-layout: fixed;
width: calc(12rem + var(--resultColCount) * 4rem);
width: calc(24rem + var(--resultColCount) * 4rem);
}
.path-local-treestudyplan .q-header,
.path-local-treestudyplan .q-student-results {
.path-local-treestudyplan table.q-studyplanreport colgroup.q-col-studentinfo col {
width: 12rem;
}
.path-local-treestudyplan table.q-studyplanreport colgroup.q-col-resultinfo col {
width: 4rem;
}
.path-local-treestudyplan .q-header {
background-color: var(--courseresult-bgcolor);
}
.path-local-treestudyplan .q-header .q-period-heading,
.path-local-treestudyplan .q-header .q-line-heading,
.path-local-treestudyplan .q-header .q-item-heading,
.path-local-treestudyplan .q-header .q-condition-heading,
.path-local-treestudyplan .q-student-results .q-period-heading,
.path-local-treestudyplan .q-student-results .q-line-heading,
.path-local-treestudyplan .q-student-results .q-item-heading,
.path-local-treestudyplan .q-student-results .q-condition-heading {
.path-local-treestudyplan .q-header .q-condition-heading {
border-right: 1px solid var(--border-color);
border-top: 1px solid var(--border-color);
padding: 0.5rem;
@ -1606,98 +1607,124 @@ body.path-local-treestudyplan .editmode-switch-form > * {
.path-local-treestudyplan .q-header .q-line-heading,
.path-local-treestudyplan .q-header .q-item-heading,
.path-local-treestudyplan .q-header .q-condition-heading.overall,
.path-local-treestudyplan .q-header .q-result.overall,
.path-local-treestudyplan .q-student-results .q-period-heading,
.path-local-treestudyplan .q-student-results .q-line-heading,
.path-local-treestudyplan .q-student-results .q-item-heading,
.path-local-treestudyplan .q-student-results .q-condition-heading.overall,
.path-local-treestudyplan .q-student-results .q-result.overall {
.path-local-treestudyplan .q-header .q-result.overall {
background-color: var(--courseresult-bgcolor);
}
.path-local-treestudyplan .q-header .q-period-heading.collapsed,
.path-local-treestudyplan .q-student-results .q-period-heading.collapsed {
width: 12rem;
.path-local-treestudyplan .q-header .q-period-heading.collapsed {
width: 6rem;
}
.path-local-treestudyplan .q-header .q-result,
.path-local-treestudyplan .q-student-results .q-result {
.path-local-treestudyplan .q-header .q-result {
height: 2rem;
padding-top: 0.25rem;
}
.path-local-treestudyplan .q-header .q-line-heading,
.path-local-treestudyplan .q-header .q-item-heading,
.path-local-treestudyplan .q-header .q-condition-heading,
.path-local-treestudyplan .q-student-results .q-line-heading,
.path-local-treestudyplan .q-student-results .q-item-heading,
.path-local-treestudyplan .q-student-results .q-condition-heading {
.path-local-treestudyplan .q-header .q-condition-heading {
width: 2rem;
writing-mode: vertical-rl;
text-orientation: sideways;
}
.path-local-treestudyplan .q-header .q-line-heading .q-chevron,
.path-local-treestudyplan .q-header .q-item-heading .q-chevron,
.path-local-treestudyplan .q-header .q-condition-heading .q-chevron,
.path-local-treestudyplan .q-student-results .q-line-heading .q-chevron,
.path-local-treestudyplan .q-student-results .q-item-heading .q-chevron,
.path-local-treestudyplan .q-student-results .q-condition-heading .q-chevron {
.path-local-treestudyplan .q-header .q-condition-heading .q-chevron {
text-orientation: initial;
writing-mode: horizontal-tb;
}
.path-local-treestudyplan .q-header .q-period-heading,
.path-local-treestudyplan .q-header .q-line-heading,
.path-local-treestudyplan .q-header .q-item-heading,
.path-local-treestudyplan .q-header .q-condition-heading,
.path-local-treestudyplan .q-student-results .q-period-heading,
.path-local-treestudyplan .q-student-results .q-line-heading,
.path-local-treestudyplan .q-student-results .q-item-heading,
.path-local-treestudyplan .q-student-results .q-condition-heading {
.path-local-treestudyplan .q-header .q-condition-heading {
vertical-align: top;
}
.path-local-treestudyplan .q-header .q-line-heading > span.q-wrap,
.path-local-treestudyplan .q-student-results .q-line-heading > span.q-wrap {
.path-local-treestudyplan .q-header .q-line-heading > span.q-wrap {
display: inline-block;
height: 7rem;
}
.path-local-treestudyplan .q-header .q-item-heading > span.q-wrap,
.path-local-treestudyplan .q-student-results .q-item-heading > span.q-wrap {
.path-local-treestudyplan .q-header .q-item-heading > span.q-wrap {
display: inline-block;
height: 7rem;
white-space: nowrap;
}
.path-local-treestudyplan .q-header .q-condition-heading,
.path-local-treestudyplan .q-student-results .q-condition-heading {
.path-local-treestudyplan .q-header .q-condition-heading {
text-align: left;
background: var(--conditions-bgcolor);
}
.path-local-treestudyplan .q-header .q-condition-heading > span.q-wrap,
.path-local-treestudyplan .q-student-results .q-condition-heading > span.q-wrap {
.path-local-treestudyplan .q-header .q-condition-heading > span.q-wrap {
display: inline-block;
height: 8rem;
}
.path-local-treestudyplan .q-result {
.path-local-treestudyplan .q-header a.q-condition-label {
text-decoration-line: underline;
text-decoration-style: dotted;
cursor: pointer;
color: inherit; /* Don't force a color on this link */
}
.path-local-treestudyplan .q-header .q-studentinfo {
padding: 0.5em;
border: 1px solid var(--border-color);
width: 12rem;
background-color: var(--studentinfo-bgcolor);
vertical-align: bottom;
font-weight: normal;
}
.path-local-treestudyplan .q-header .q-studentinfo.q-name {
white-space: nowrap;
}
.path-local-treestudyplan .q-groupheading td,
.path-local-treestudyplan .q-groupheading th {
background-color: var(--studentinfo-bgcolor);
padding: 0.5em;
border: 1px solid var(--border-color);
}
.path-local-treestudyplan .q-groupheading td:first-child,
.path-local-treestudyplan .q-groupheading th:first-child {
border-right: none;
}
.path-local-treestudyplan .q-groupheading td:last-child,
.path-local-treestudyplan .q-groupheading th:last-child {
border-left: none;
}
.path-local-treestudyplan .q-inforow td,
.path-local-treestudyplan .q-inforow th {
background-color: var(--studentinfo-bgcolor);
padding: 0.5em;
border: 1px solid var(--border-color);
}
.path-local-treestudyplan .q-student-results.odd td {
background-color: var(--light);
}
.path-local-treestudyplan .q-student-results.odd td.q-result {
background-color: var(--light);
}
.path-local-treestudyplan .q-student-results .q-result {
text-align: center;
vertical-align: middle;
border-right: 1px solid var(--border-color);
border: 1px solid var(--border-color);
width: 4rem;
padding-left: 0.25rem;
padding-right: 0.25rem;
background-color: var(--conditions-bgcolor);
}
.path-local-treestudyplan .q-result.overall,
.path-local-treestudyplan .q-result.collapsed {
.path-local-treestudyplan .q-student-results .q-result.overall,
.path-local-treestudyplan .q-student-results .q-result.collapsed {
background-color: var(--courseresult-bgcolor);
}
.path-local-treestudyplan .q-studentname {
.path-local-treestudyplan .q-student-results .q-result.collapsed {
border-top: none;
border-bottom: none;
}
.path-local-treestudyplan .q-student-results .q-studentinfo {
padding: 0.5em;
border-right: 2px solid var(--border-color);
border: 1px solid var(--border-color);
width: 12rem;
background-color: var(--studentinfo-bgcolor);
}
.path-local-treestudyplan .q-courseresult i.fa,
.path-local-treestudyplan .q-conditionresult i.fa {
.path-local-treestudyplan .q-student-results .q-courseresult i.fa,
.path-local-treestudyplan .q-student-results .q-conditionresult i.fa {
font-size: 21px;
vertical-align: middle;
}
.path-local-treestudyplan .q-conditionresult {
.path-local-treestudyplan .q-student-results .q-conditionresult {
font-size: 1.2rem;
display: inline-block;
width: 100%;

View File

@ -1,16 +1,27 @@
.path-local-treestudyplan {
font: inherit;
--border-color: var(--primary);
--border-color: #ccc;
--conditions-bgcolor: #e7e7e7;
--courseresult-bgcolor: white;
--studentinfo-bgcolor: white;
table.q-studyplanreport {
table-layout: fixed;
width: calc(12rem + (var(--resultColCount) * 4rem));
width: calc((2 * 12rem) + (var(--resultColCount) * 4rem));
colgroup.q-col-studentinfo {
col {
width: 12rem;
}
}
colgroup.q-col-resultinfo {
col {
width: 4rem;
}
}
}
.q-header, .q-student-results {
.q-header {
background-color: var(--courseresult-bgcolor);
.q-period-heading, .q-line-heading, .q-item-heading, .q-condition-heading
@ -26,7 +37,7 @@
}
.q-period-heading.collapsed {
width:12rem;
width:6rem;
}
.q-result {
@ -71,41 +82,91 @@
background: var(--conditions-bgcolor);
}
}
a.q-condition-label {
text-decoration-line: underline;
text-decoration-style: dotted;
cursor: pointer;
color: inherit; /* Don't force a color on this link */
}
.q-studentinfo {
padding: 0.5em;
border: 1px solid var(--border-color);
width: 12rem;
background-color: var(--studentinfo-bgcolor);
vertical-align: bottom;
font-weight: normal;
&.q-name {
white-space: nowrap;
}
}
.q-result {
text-align: center;
vertical-align: middle;
border-right: 1px solid var(--border-color);
width: 4rem;
padding-left: 0.25rem;
padding-right: 0.25rem;
background-color: var(--conditions-bgcolor);
&.overall, &.collapsed {
background-color: var(--courseresult-bgcolor);
}
.q-groupheading {
td,th {
&:first-child {
border-right: none;
}
&:last-child {
border-left: none;
}
background-color: var(--studentinfo-bgcolor);
padding: 0.5em;
border: 1px solid var(--border-color);
}
}
.q-inforow {
td,th {
background-color: var(--studentinfo-bgcolor);
padding: 0.5em;
border: 1px solid var(--border-color);
}
}
.q-studentname {
padding: 0.5em;
border-right: 2px solid var(--border-color);
width: 12rem;
background-color: var(--studentinfo-bgcolor);
}
.q-courseresult,
.q-conditionresult{
i.fa {
font-size: 21px;
.q-student-results {
&.odd td {
background-color: var(--light);
&.q-result {
background-color: var(--light);
}
}
.q-result {
text-align: center;
vertical-align: middle;
border: 1px solid var(--border-color);
width: 4rem;
padding-left: 0.25rem;
padding-right: 0.25rem;
background-color: var(--conditions-bgcolor);
&.overall, &.collapsed {
background-color: var(--courseresult-bgcolor);
}
&.collapsed {
border-top: none;
border-bottom: none;
}
}
.q-studentinfo {
padding: 0.5em;
border: 1px solid var(--border-color);
width: 12rem;
background-color: var(--studentinfo-bgcolor);
}
.q-courseresult,
.q-conditionresult{
i.fa {
font-size: 21px;
vertical-align: middle;
}
}
.q-conditionresult{
font-size: 1.2rem;
display: inline-block;
width: 100%;
}
}
.q-conditionresult{
font-size: 1.2rem;
display: inline-block;
width: 100%;
}
}

View File

@ -1577,27 +1577,28 @@ body.path-local-treestudyplan .editmode-switch-form > * {
.path-local-treestudyplan {
font: inherit;
--border-color: var(--primary);
--border-color: #ccc;
--conditions-bgcolor: #e7e7e7;
--courseresult-bgcolor: white;
--studentinfo-bgcolor: white;
}
.path-local-treestudyplan table.q-studyplanreport {
table-layout: fixed;
width: calc(12rem + var(--resultColCount) * 4rem);
width: calc(24rem + var(--resultColCount) * 4rem);
}
.path-local-treestudyplan .q-header,
.path-local-treestudyplan .q-student-results {
.path-local-treestudyplan table.q-studyplanreport colgroup.q-col-studentinfo col {
width: 12rem;
}
.path-local-treestudyplan table.q-studyplanreport colgroup.q-col-resultinfo col {
width: 4rem;
}
.path-local-treestudyplan .q-header {
background-color: var(--courseresult-bgcolor);
}
.path-local-treestudyplan .q-header .q-period-heading,
.path-local-treestudyplan .q-header .q-line-heading,
.path-local-treestudyplan .q-header .q-item-heading,
.path-local-treestudyplan .q-header .q-condition-heading,
.path-local-treestudyplan .q-student-results .q-period-heading,
.path-local-treestudyplan .q-student-results .q-line-heading,
.path-local-treestudyplan .q-student-results .q-item-heading,
.path-local-treestudyplan .q-student-results .q-condition-heading {
.path-local-treestudyplan .q-header .q-condition-heading {
border-right: 1px solid var(--border-color);
border-top: 1px solid var(--border-color);
padding: 0.5rem;
@ -1606,98 +1607,124 @@ body.path-local-treestudyplan .editmode-switch-form > * {
.path-local-treestudyplan .q-header .q-line-heading,
.path-local-treestudyplan .q-header .q-item-heading,
.path-local-treestudyplan .q-header .q-condition-heading.overall,
.path-local-treestudyplan .q-header .q-result.overall,
.path-local-treestudyplan .q-student-results .q-period-heading,
.path-local-treestudyplan .q-student-results .q-line-heading,
.path-local-treestudyplan .q-student-results .q-item-heading,
.path-local-treestudyplan .q-student-results .q-condition-heading.overall,
.path-local-treestudyplan .q-student-results .q-result.overall {
.path-local-treestudyplan .q-header .q-result.overall {
background-color: var(--courseresult-bgcolor);
}
.path-local-treestudyplan .q-header .q-period-heading.collapsed,
.path-local-treestudyplan .q-student-results .q-period-heading.collapsed {
width: 12rem;
.path-local-treestudyplan .q-header .q-period-heading.collapsed {
width: 6rem;
}
.path-local-treestudyplan .q-header .q-result,
.path-local-treestudyplan .q-student-results .q-result {
.path-local-treestudyplan .q-header .q-result {
height: 2rem;
padding-top: 0.25rem;
}
.path-local-treestudyplan .q-header .q-line-heading,
.path-local-treestudyplan .q-header .q-item-heading,
.path-local-treestudyplan .q-header .q-condition-heading,
.path-local-treestudyplan .q-student-results .q-line-heading,
.path-local-treestudyplan .q-student-results .q-item-heading,
.path-local-treestudyplan .q-student-results .q-condition-heading {
.path-local-treestudyplan .q-header .q-condition-heading {
width: 2rem;
writing-mode: vertical-rl;
text-orientation: sideways;
}
.path-local-treestudyplan .q-header .q-line-heading .q-chevron,
.path-local-treestudyplan .q-header .q-item-heading .q-chevron,
.path-local-treestudyplan .q-header .q-condition-heading .q-chevron,
.path-local-treestudyplan .q-student-results .q-line-heading .q-chevron,
.path-local-treestudyplan .q-student-results .q-item-heading .q-chevron,
.path-local-treestudyplan .q-student-results .q-condition-heading .q-chevron {
.path-local-treestudyplan .q-header .q-condition-heading .q-chevron {
text-orientation: initial;
writing-mode: horizontal-tb;
}
.path-local-treestudyplan .q-header .q-period-heading,
.path-local-treestudyplan .q-header .q-line-heading,
.path-local-treestudyplan .q-header .q-item-heading,
.path-local-treestudyplan .q-header .q-condition-heading,
.path-local-treestudyplan .q-student-results .q-period-heading,
.path-local-treestudyplan .q-student-results .q-line-heading,
.path-local-treestudyplan .q-student-results .q-item-heading,
.path-local-treestudyplan .q-student-results .q-condition-heading {
.path-local-treestudyplan .q-header .q-condition-heading {
vertical-align: top;
}
.path-local-treestudyplan .q-header .q-line-heading > span.q-wrap,
.path-local-treestudyplan .q-student-results .q-line-heading > span.q-wrap {
.path-local-treestudyplan .q-header .q-line-heading > span.q-wrap {
display: inline-block;
height: 7rem;
}
.path-local-treestudyplan .q-header .q-item-heading > span.q-wrap,
.path-local-treestudyplan .q-student-results .q-item-heading > span.q-wrap {
.path-local-treestudyplan .q-header .q-item-heading > span.q-wrap {
display: inline-block;
height: 7rem;
white-space: nowrap;
}
.path-local-treestudyplan .q-header .q-condition-heading,
.path-local-treestudyplan .q-student-results .q-condition-heading {
.path-local-treestudyplan .q-header .q-condition-heading {
text-align: left;
background: var(--conditions-bgcolor);
}
.path-local-treestudyplan .q-header .q-condition-heading > span.q-wrap,
.path-local-treestudyplan .q-student-results .q-condition-heading > span.q-wrap {
.path-local-treestudyplan .q-header .q-condition-heading > span.q-wrap {
display: inline-block;
height: 8rem;
}
.path-local-treestudyplan .q-result {
.path-local-treestudyplan .q-header a.q-condition-label {
text-decoration-line: underline;
text-decoration-style: dotted;
cursor: pointer;
color: inherit; /* Don't force a color on this link */
}
.path-local-treestudyplan .q-header .q-studentinfo {
padding: 0.5em;
border: 1px solid var(--border-color);
width: 12rem;
background-color: var(--studentinfo-bgcolor);
vertical-align: bottom;
font-weight: normal;
}
.path-local-treestudyplan .q-header .q-studentinfo.q-name {
white-space: nowrap;
}
.path-local-treestudyplan .q-groupheading td,
.path-local-treestudyplan .q-groupheading th {
background-color: var(--studentinfo-bgcolor);
padding: 0.5em;
border: 1px solid var(--border-color);
}
.path-local-treestudyplan .q-groupheading td:first-child,
.path-local-treestudyplan .q-groupheading th:first-child {
border-right: none;
}
.path-local-treestudyplan .q-groupheading td:last-child,
.path-local-treestudyplan .q-groupheading th:last-child {
border-left: none;
}
.path-local-treestudyplan .q-inforow td,
.path-local-treestudyplan .q-inforow th {
background-color: var(--studentinfo-bgcolor);
padding: 0.5em;
border: 1px solid var(--border-color);
}
.path-local-treestudyplan .q-student-results.odd td {
background-color: var(--light);
}
.path-local-treestudyplan .q-student-results.odd td.q-result {
background-color: var(--light);
}
.path-local-treestudyplan .q-student-results .q-result {
text-align: center;
vertical-align: middle;
border-right: 1px solid var(--border-color);
border: 1px solid var(--border-color);
width: 4rem;
padding-left: 0.25rem;
padding-right: 0.25rem;
background-color: var(--conditions-bgcolor);
}
.path-local-treestudyplan .q-result.overall,
.path-local-treestudyplan .q-result.collapsed {
.path-local-treestudyplan .q-student-results .q-result.overall,
.path-local-treestudyplan .q-student-results .q-result.collapsed {
background-color: var(--courseresult-bgcolor);
}
.path-local-treestudyplan .q-studentname {
.path-local-treestudyplan .q-student-results .q-result.collapsed {
border-top: none;
border-bottom: none;
}
.path-local-treestudyplan .q-student-results .q-studentinfo {
padding: 0.5em;
border-right: 2px solid var(--border-color);
border: 1px solid var(--border-color);
width: 12rem;
background-color: var(--studentinfo-bgcolor);
}
.path-local-treestudyplan .q-courseresult i.fa,
.path-local-treestudyplan .q-conditionresult i.fa {
.path-local-treestudyplan .q-student-results .q-courseresult i.fa,
.path-local-treestudyplan .q-student-results .q-conditionresult i.fa {
font-size: 21px;
vertical-align: middle;
}
.path-local-treestudyplan .q-conditionresult {
.path-local-treestudyplan .q-student-results .q-conditionresult {
font-size: 1.2rem;
display: inline-block;
width: 100%;