Implemented group completion stats for all situations
This commit is contained in:
parent
73f4646121
commit
e698377dca
17 changed files with 1014 additions and 270 deletions
|
@ -11,6 +11,7 @@ import {call} from 'core/ajax';
|
||||||
import notification from 'core/notification';
|
import notification from 'core/notification';
|
||||||
import {svgarcpath} from './svgarc';
|
import {svgarcpath} from './svgarc';
|
||||||
//import {fixLineWrappers} from './studyplan-processor';
|
//import {fixLineWrappers} from './studyplan-processor';
|
||||||
|
import Debugger from './debugger';
|
||||||
|
|
||||||
// Make π available as a constant
|
// Make π available as a constant
|
||||||
const π = Math.PI;
|
const π = Math.PI;
|
||||||
|
@ -18,7 +19,8 @@ const π = Math.PI;
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
install(Vue/*,options*/){
|
install(Vue/*,options*/){
|
||||||
|
let debug = new Debugger("treestudyplan-viewer");
|
||||||
|
debug.enable();
|
||||||
let strings = load_strings({
|
let strings = load_strings({
|
||||||
invalid: {
|
invalid: {
|
||||||
error: 'error',
|
error: 'error',
|
||||||
|
@ -32,15 +34,39 @@ export default {
|
||||||
unknown: "unknown",
|
unknown: "unknown",
|
||||||
},
|
},
|
||||||
completion: {
|
completion: {
|
||||||
completion_completed: "completion_completed",
|
completed: "completion_completed",
|
||||||
completion_incomplete: "completion_incomplete",
|
incomplete: "completion_incomplete",
|
||||||
|
completed_pass: "completion_passed",
|
||||||
|
completed_fail: "completion_failed",
|
||||||
|
ungraded: "ungraded",
|
||||||
|
aggregation_all: "aggregation_all",
|
||||||
|
aggregation_any: "aggregation_any",
|
||||||
|
aggregation_overall_all: "aggregation_overall_all",
|
||||||
|
aggregation_overall_any: "aggregation_overall_any",
|
||||||
|
completion_not_configured: "completion_not_configured",
|
||||||
|
configure_completion: "configure_completion",
|
||||||
},
|
},
|
||||||
badge: {
|
badge: {
|
||||||
share_badge: "share_badge",
|
share_badge: "share_badge",
|
||||||
dateissued: "dateissued",
|
dateissued: "dateissued",
|
||||||
dateexpire: "dateexpire",
|
dateexpire: "dateexpire",
|
||||||
badgeinfo: "badgeinfo",
|
badgeinfo: "badgeinfo",
|
||||||
}
|
badgeissuedstats: "badgeissuedstats",
|
||||||
|
},
|
||||||
|
course: {
|
||||||
|
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",
|
||||||
|
view_feedback: "view_feedback",
|
||||||
|
coursetiming_past: "coursetiming_past",
|
||||||
|
coursetiming_present: "coursetiming_present",
|
||||||
|
coursetiming_future: "coursetiming_future",
|
||||||
|
required_goal: "required_goal",
|
||||||
|
},
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -573,20 +599,7 @@ export default {
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
text: {
|
text: strings.course,
|
||||||
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",
|
|
||||||
view_feedback: "view_feedback",
|
|
||||||
coursetiming_past: "coursetiming_past",
|
|
||||||
coursetiming_present: "coursetiming_present",
|
|
||||||
coursetiming_future: "coursetiming_future",
|
|
||||||
required_goal: "required_goal",
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
@ -673,9 +686,21 @@ export default {
|
||||||
</div>
|
</div>
|
||||||
<div class="r-course-detail-header-right">
|
<div class="r-course-detail-header-right">
|
||||||
<div class="r-completion-detail-header">
|
<div class="r-completion-detail-header">
|
||||||
|
<template v-if='value.course.completion'>
|
||||||
|
{{text['completion_'+value.completion]}}
|
||||||
|
<r-progress-circle
|
||||||
|
:value='value.course.completion.progress'
|
||||||
|
:max='value.course.completion.count'
|
||||||
|
:min='0'
|
||||||
|
:title="text['completion_'+value.completion]"
|
||||||
|
:class="'r-progress-circle-popup r-completion-'+value.completion"
|
||||||
|
></r-progress-circle>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
{{text['completion_'+value.completion]}}
|
{{text['completion_'+value.completion]}}
|
||||||
<i :class="'fa fa-'+completion_icon(value.completion)+' r-completion-'+value.completion"
|
<i :class="'fa fa-'+completion_icon(value.completion)+' r-completion-'+value.completion"
|
||||||
:title="text['completion_'+value.completion]"></i>
|
:title="text['completion_'+value.completion]"></i>
|
||||||
|
</template>
|
||||||
</div>
|
</div>
|
||||||
<div :class="'r-timing-'+value.course.timing">
|
<div :class="'r-timing-'+value.course.timing">
|
||||||
{{text['coursetiming_'+value.course.timing]}}<br>
|
{{text['coursetiming_'+value.course.timing]}}<br>
|
||||||
|
@ -711,6 +736,7 @@ export default {
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
text: strings.course,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
@ -879,10 +905,11 @@ export default {
|
||||||
<tr v-for='ci in cgroup.items'>
|
<tr v-for='ci in cgroup.items'>
|
||||||
<td><span v-if='guestmode'>{{ci.title}}</span>
|
<td><span v-if='guestmode'>{{ci.title}}</span>
|
||||||
<span v-else v-html='ci.details.criteria'></span>
|
<span v-else v-html='ci.details.criteria'></span>
|
||||||
<abbr v-if="ci.details.requirement" :title="ci.details.requirement"
|
<a href="#" v-b-tooltip.click="{ customClass: 'r-tooltip ' + ci.status}"
|
||||||
:class="'s-required ' + ci.status"
|
:title="ci.details.requirement"
|
||||||
><i class='fa fa-questionmark' ></i
|
:class="'s-required ' + ci.status"><i v-if="ci.details.requirement"
|
||||||
></abbr>
|
class='fa fa-question-circle'
|
||||||
|
></i></a>
|
||||||
<td><span :class="' r-completion-'+ci.status">{{ci.grade}}</span></td>
|
<td><span :class="' r-completion-'+ci.status">{{ci.grade}}</span></td>
|
||||||
<td><i :class="'fa fa-'+completion_icon(ci.status)+' r-completion-'+ci.status"
|
<td><i :class="'fa fa-'+completion_icon(ci.status)+' r-completion-'+ci.status"
|
||||||
:title="text['completion_'+ci.status]"></i>
|
:title="text['completion_'+ci.status]"></i>
|
||||||
|
@ -913,7 +940,7 @@ export default {
|
||||||
`,
|
`,
|
||||||
});
|
});
|
||||||
|
|
||||||
//TODO: Implement corecompletion
|
//TAG: Teacher course
|
||||||
Vue.component('r-item-teachercourse', {
|
Vue.component('r-item-teachercourse', {
|
||||||
props: {
|
props: {
|
||||||
value :{
|
value :{
|
||||||
|
@ -968,6 +995,56 @@ export default {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
isCompletable() {
|
||||||
|
let completable = false;
|
||||||
|
if(this.value.course.completion){
|
||||||
|
if(this.value.course.completion.conditions.length > 0){
|
||||||
|
completable = true;
|
||||||
|
}
|
||||||
|
} else if (this.value.course.grades){
|
||||||
|
if(this.value.course.grades.length > 0){
|
||||||
|
completable = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return completable;
|
||||||
|
},
|
||||||
|
progress_circle() { //INFO:
|
||||||
|
const status = {
|
||||||
|
students: 0,
|
||||||
|
completed: 0,
|
||||||
|
completed_pass: 0,
|
||||||
|
completed_fail: 0,
|
||||||
|
ungraded: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
if(this.value.course.completion){
|
||||||
|
for(const cond of this.value.course.completion.conditions){
|
||||||
|
for(const itm of cond.items){
|
||||||
|
if(itm.progress){
|
||||||
|
status.students += itm.progress.students;
|
||||||
|
status.completed += itm.progress.completed;
|
||||||
|
status.completed_pass += itm.progress.completed_pass;
|
||||||
|
status.completed_fail += itm.progress.completed_fail;
|
||||||
|
status.ungraded += itm.progress.completed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (this.value.course.grades){
|
||||||
|
for( const g of this.value.course.grades){
|
||||||
|
if(g.grading){
|
||||||
|
status.students += g.grading.students;
|
||||||
|
status.completed += g.grading.completed;
|
||||||
|
status.completed_pass += g.grading.completed_pass;
|
||||||
|
status.completed_fail += g.grading.completed_fail;
|
||||||
|
status.ungraded += g.grading.ungraded;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return status;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
created(){
|
created(){
|
||||||
const self = this;
|
const self = this;
|
||||||
|
@ -1054,13 +1131,12 @@ export default {
|
||||||
</b-col>
|
</b-col>
|
||||||
<b-col md="11">
|
<b-col md="11">
|
||||||
<b-card-body class="align-items-center">
|
<b-card-body class="align-items-center">
|
||||||
<i v-b-popover.hover
|
|
||||||
:class="'r-course-graded fa fa-'+course_grading_icon+' r-graded-'+course_grading_needed"
|
|
||||||
:title="txt.grading[course_grading_needed]"></i>
|
|
||||||
<a v-b-modal="'r-item-course-details-'+value.id"
|
<a v-b-modal="'r-item-course-details-'+value.id"
|
||||||
:href="(!guestmode)?('/course/view.php?id='+value.course.id):'#'"
|
:href="(!guestmode)?('/course/view.php?id='+value.course.id):'#'"
|
||||||
@click.prevent.stop=''
|
@click.prevent.stop=''
|
||||||
>{{ value.course.displayname }}</i></a>
|
>{{ value.course.displayname }}</i></a>
|
||||||
|
<r-completion-circle class="r-course-graded" :disabled="!isCompletable"
|
||||||
|
v-model="progress_circle"></r-completion-circle>
|
||||||
</b-card-body>
|
</b-card-body>
|
||||||
</b-col>
|
</b-col>
|
||||||
</b-row>
|
</b-row>
|
||||||
|
@ -1082,14 +1158,16 @@ export default {
|
||||||
:useRequiredGrades="useRequiredGrades"
|
:useRequiredGrades="useRequiredGrades"
|
||||||
:plan="plan"
|
:plan="plan"
|
||||||
></r-item-teacher-gradepicker>
|
></r-item-teacher-gradepicker>
|
||||||
|
<a v-if='!!value.course.completion && value.course.amteacher'
|
||||||
|
:href="'/course/completion.php?id='+value.course.id" target="_blank"
|
||||||
|
:title="text.configure_completion"><i class="fa fa-gear"></i></a>
|
||||||
</h1>
|
</h1>
|
||||||
{{ value.course.context.path.join(" / ")}}
|
{{ value.course.context.path.join(" / ")}}
|
||||||
</div>
|
</div>
|
||||||
<div class="r-course-detail-header-right">
|
<div class="r-course-detail-header-right">
|
||||||
<div class="r-completion-detail-header">
|
<div class="r-completion-detail-header">
|
||||||
{{ txt.grading[course_grading_needed] }}
|
<r-completion-circle class="r-progress-circle-popup" :disabled="!isCompletable"
|
||||||
<i v-b-popover.hover :class="'fa fa-'+course_grading_icon+' r-graded-'+course_grading_needed"
|
v-model="progress_circle"></r-completion-circle>
|
||||||
:title="txt.grading[course_grading_needed]"></i>
|
|
||||||
</div>
|
</div>
|
||||||
<div :class="'r-timing-'+value.course.timing">
|
<div :class="'r-timing-'+value.course.timing">
|
||||||
{{ text['coursetiming_'+value.course.timing] }}<br>
|
{{ text['coursetiming_'+value.course.timing] }}<br>
|
||||||
|
@ -1114,7 +1192,7 @@ export default {
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
//TODO: Selecte activities to use in grade overview
|
//TAG: Select activities to use in grade overview
|
||||||
Vue.component('r-item-teacher-gradepicker', {
|
Vue.component('r-item-teacher-gradepicker', {
|
||||||
props: {
|
props: {
|
||||||
value : {
|
value : {
|
||||||
|
@ -1191,7 +1269,7 @@ export default {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//TODO: Selected activities dispaly
|
//TAG: Selected activities dispaly
|
||||||
Vue.component('r-item-teachergrades',{
|
Vue.component('r-item-teachergrades',{
|
||||||
props: {
|
props: {
|
||||||
value : {
|
value : {
|
||||||
|
@ -1247,12 +1325,17 @@ export default {
|
||||||
return this.determine_grading_icon(this.is_grading_needed(grade));
|
return this.determine_grading_icon(this.is_grading_needed(grade));
|
||||||
},
|
},
|
||||||
is_grading_needed(grade){
|
is_grading_needed(grade){
|
||||||
|
debug.info("Grade: ", grade.name);
|
||||||
|
debug.info(grade.grading);
|
||||||
if(grade.grading){
|
if(grade.grading){
|
||||||
|
debug.info("Ping");
|
||||||
if(grade.grading.ungraded){
|
if(grade.grading.ungraded){
|
||||||
return 'ungraded';
|
return 'ungraded';
|
||||||
}
|
}
|
||||||
else if(grade.grading.graded){
|
else if(grade.grading.completed_pass || grade.grading.completed || grade.grading.completed_fail) {
|
||||||
if(Number(grade.grading.graded) == Number(grade.grading.students)){
|
if(Number(grade.grading.completed) + Number(grade.grading.completed_pass)
|
||||||
|
+ Number(grade.grading.completed_fail)
|
||||||
|
== Number(grade.grading.students)){
|
||||||
return 'allgraded';
|
return 'allgraded';
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
@ -1312,7 +1395,7 @@ export default {
|
||||||
:title="txt.grading[is_grading_needed(g)]"></i>
|
:title="txt.grading[is_grading_needed(g)]"></i>
|
||||||
</td>
|
</td>
|
||||||
<td v-if='g.grading'>
|
<td v-if='g.grading'>
|
||||||
<r-grading-bar v-model="g.grading" :width="150" :height="15"></r-grading-bar>
|
<r-completion-bar v-model="g.grading" :width="150" :height="15"></r-completion-bar>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
@ -1338,20 +1421,7 @@ export default {
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
text: {
|
text: strings.completion,
|
||||||
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",
|
|
||||||
view_feedback: "view_feedback",
|
|
||||||
coursetiming_past: "coursetiming_past",
|
|
||||||
coursetiming_present: "coursetiming_present",
|
|
||||||
coursetiming_future: "coursetiming_future",
|
|
||||||
required_goal: "required_goal",
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
created(){
|
created(){
|
||||||
|
@ -1372,67 +1442,47 @@ export default {
|
||||||
computed: {
|
computed: {
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
completion_icon(completion) {
|
hasCompletions() {
|
||||||
switch(completion){
|
if(this.value.conditions) {
|
||||||
case "progress":
|
for(const cgroup of this.value.conditions){
|
||||||
return "exclamation-circle";
|
if(cgroup.items && cgroup.items.length > 0){
|
||||||
case "complete":
|
return true;
|
||||||
return "check-circle";
|
|
||||||
case "complete-pass":
|
|
||||||
return "check-circle";
|
|
||||||
case "complete-fail":
|
|
||||||
return "times-circle";
|
|
||||||
default: // case "incomplete"
|
|
||||||
return "circle-o";
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
},
|
},
|
||||||
|
|
||||||
completion_tag(cgroup){
|
|
||||||
return cgroup.completion?'completed':'incomplete';
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
template: `
|
template: `
|
||||||
<table class="r-item-course-grade-details">
|
<table class="r-item-course-grade-details">
|
||||||
|
<tr v-if="hasCompletions">
|
||||||
|
<td colspan='2'><span v-if="value.aggregation == 'all'">{{ text.aggregation_overall_all}}</span
|
||||||
|
><span v-else>{{ text.aggregation_overall_any}}</span></td>
|
||||||
|
</tr>
|
||||||
|
<tr v-else>
|
||||||
|
<td colspan='2'>{{text.completion_not_configured}}!
|
||||||
|
<span v-if="course.amteacher">
|
||||||
|
<br><a :href="'/course/completion.php?id='+course.id" target='_blank'>{{text.configure_completion}}</a>
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
<template v-for='cgroup in value.conditions'>
|
<template v-for='cgroup in value.conditions'>
|
||||||
<tr>
|
<tr>
|
||||||
<th colspan='2'>{{cgroup.title}}</th>
|
<th colspan='2'><span v-if="cgroup.items.length > 1"
|
||||||
<th><r-progress-circle
|
><span v-if="cgroup.aggregation == 'all'">{{ text.aggregation_all}}</span
|
||||||
:value='cgroup.progress'
|
><span v-else>{{ text.aggregation_any}}</span></span>
|
||||||
:max='cgroup.count'
|
{{cgroup.title}}</th>
|
||||||
:class="'r-completion-'+cgroup.status"
|
|
||||||
:title="text['completion_'+cgroup.status]"
|
|
||||||
></r-progress-circle></th>
|
|
||||||
</tr>
|
</tr>
|
||||||
<tr v-for='ci in cgroup.items'>
|
<tr v-for='ci in cgroup.items'>
|
||||||
<td><span v-if='guestmode'>{{ci.title}}</span>
|
<td><span v-html='ci.details.criteria'></span>
|
||||||
<span v-else v-html='ci.details.criteria'></span>
|
<a href="#" v-b-tooltip.click
|
||||||
<abbr v-if="ci.details.requirement" :title="ci.details.requirement"
|
:title="ci.details.requirement"
|
||||||
:class="'s-required ' + ci.status"
|
class='text-info'><i v-if="ci.details.requirement"
|
||||||
><i class='fa fa-questionmark' ></i
|
class='fa fa-question-circle'
|
||||||
></abbr>
|
></i></a>
|
||||||
<td><span :class="' r-completion-'+ci.status">{{ci.grade}}</span></td>
|
|
||||||
<td><i :class="'fa fa-'+completion_icon(ci.status)+' r-completion-'+ci.status"
|
|
||||||
:title="text['completion_'+ci.status]"></i>
|
|
||||||
<i v-if='ci.pending' :title="text['completion_pending']"
|
|
||||||
class="r-pendingsubmission fa fa-clock-o"></i>
|
|
||||||
</td>
|
</td>
|
||||||
<td v-if="ci.feedback">
|
<td>
|
||||||
<a v-b-modal="'r-grade-feedback-'+ci.id"
|
<r-completion-bar v-model="ci.progress" :width="150" :height="15"></r-completion-bar>
|
||||||
href="#"
|
|
||||||
>{{ text["view_feedback"]}}</a>
|
|
||||||
<b-modal
|
|
||||||
:id="'r-grade-feedback-'+ci.id"
|
|
||||||
size="sm"
|
|
||||||
ok-only
|
|
||||||
centered
|
|
||||||
scrollable
|
|
||||||
>
|
|
||||||
<template #modal-header>
|
|
||||||
<h2><i class="fa fa-graduation-cap"></i>{{ course.fullname }}</h2><br>
|
|
||||||
<span class="r-activity-icon" :title="ci.typename" v-html="ci.icon"></span>{{ci.name}}
|
|
||||||
</template>
|
|
||||||
<span v-html="ci.feedback"></span>
|
|
||||||
</b-modal>
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</template>
|
</template>
|
||||||
|
@ -1516,6 +1566,245 @@ export default {
|
||||||
`,
|
`,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Vue.component('r-completion-bar',{
|
||||||
|
props: {
|
||||||
|
value : {
|
||||||
|
type: Object,
|
||||||
|
default: function(){ return {
|
||||||
|
students: 0,
|
||||||
|
completed: 0,
|
||||||
|
completed_pass: 0,
|
||||||
|
completed_fail: 0,
|
||||||
|
ungraded: 0,
|
||||||
|
};},
|
||||||
|
},
|
||||||
|
width: {
|
||||||
|
type: Number,
|
||||||
|
default: 150,
|
||||||
|
},
|
||||||
|
height: {
|
||||||
|
type: Number,
|
||||||
|
default: 15,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
text: strings.completion,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
width_incomplete() {
|
||||||
|
return this.width * this.fraction_incomplete();
|
||||||
|
},
|
||||||
|
width_completed() {
|
||||||
|
return this.width * this.fraction_completed();
|
||||||
|
},
|
||||||
|
width_completed_pass() {
|
||||||
|
return this.width * this.fraction_completed_pass();
|
||||||
|
},
|
||||||
|
width_completed_fail() {
|
||||||
|
return this.width * this.fraction_completed_fail();
|
||||||
|
},
|
||||||
|
width_ungraded() {
|
||||||
|
return this.width * this.fraction_ungraded();
|
||||||
|
},
|
||||||
|
count_incomplete(){
|
||||||
|
return (this.value.students - this.value.completed - this.value.completed_pass
|
||||||
|
- this.value.completed_fail - this.value.ungraded);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
fraction_incomplete() {
|
||||||
|
if(this.value.students > 0){
|
||||||
|
return 1 - (
|
||||||
|
(this.value.completed + this.value.completed_pass +
|
||||||
|
this.value.completed_fail + this.value.ungraded) / this.value.students);
|
||||||
|
} else {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fraction_completed() {
|
||||||
|
if(this.value.students > 0){
|
||||||
|
return this.value.completed / this.value.students;
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fraction_completed_pass() {
|
||||||
|
if(this.value.students > 0){
|
||||||
|
return this.value.completed_pass / this.value.students;
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fraction_completed_fail() {
|
||||||
|
if(this.value.students > 0){
|
||||||
|
return this.value.completed_fail / this.value.students;
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fraction_ungraded() {
|
||||||
|
if(this.value.students > 0){
|
||||||
|
return this.value.ungraded / this.value.students;
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
template: `
|
||||||
|
<span class="r-grading-bar" :style="{height: height+'px'}"
|
||||||
|
><span :style="{height: height+'px', width: width_ungraded+'px'}"
|
||||||
|
class='r-grading-bar-segment r-completion-bar-ungraded'
|
||||||
|
:title="text.ungraded + ' (' + this.value.ungraded + ')'" v-b-popover.hover.top
|
||||||
|
></span
|
||||||
|
><span :style="{height: height+'px', width: width_completed+'px'}"
|
||||||
|
class='r-grading-bar-segment r-completion-bar-completed'
|
||||||
|
:title="text.completed + ' (' + this.value.completed + ')'" v-b-popover.hover.top
|
||||||
|
></span
|
||||||
|
><span :style="{height: height+'px', width: width_completed_pass+'px'}"
|
||||||
|
class='r-grading-bar-segment r-completion-bar-completed-pass'
|
||||||
|
:title="text.completed_pass + ' (' + this.value.completed_pass + ')'" v-b-popover.hover.top
|
||||||
|
></span
|
||||||
|
><span :style="{height: height+'px', width: width_completed_fail+'px'}"
|
||||||
|
class='r-grading-bar-segment r-completion-bar-completed-fail'
|
||||||
|
:title="text.completed_fail + ' (' + this.value.completed_fail + ')'" v-b-popover.hover.top
|
||||||
|
></span
|
||||||
|
><span :style="{height: height+'px', width: width_incomplete+'px'}"
|
||||||
|
class='r-grading-bar-segment r-completion-bar-incomplete'
|
||||||
|
:title="text.incomplete + ' (' + count_incomplete + ')'" v-b-popover.hover.top
|
||||||
|
></span
|
||||||
|
></span>
|
||||||
|
`,
|
||||||
|
});
|
||||||
|
|
||||||
|
Vue.component('r-completion-circle',{
|
||||||
|
props: {
|
||||||
|
value : {
|
||||||
|
type: Object,
|
||||||
|
default: function(){ return {
|
||||||
|
students: 10,
|
||||||
|
completed: 2,
|
||||||
|
completed_pass: 2,
|
||||||
|
completed_fail: 2,
|
||||||
|
ungraded: 2,
|
||||||
|
};},
|
||||||
|
},
|
||||||
|
stroke: {
|
||||||
|
type: Number,
|
||||||
|
default: 0.2,
|
||||||
|
},
|
||||||
|
disabled: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
type: String,
|
||||||
|
default: "",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
radius() {
|
||||||
|
return 50 - (50*this.stroke);
|
||||||
|
},
|
||||||
|
|
||||||
|
arcpath_ungraded() {
|
||||||
|
const begin = 0;
|
||||||
|
return this.arcpath(begin,this.fraction_ungraded());
|
||||||
|
},
|
||||||
|
arcpath_completed() {
|
||||||
|
const begin = this.fraction_ungraded();
|
||||||
|
return this.arcpath(begin,this.fraction_completed());
|
||||||
|
},
|
||||||
|
arcpath_completed_pass() {
|
||||||
|
const begin = this.fraction_ungraded()
|
||||||
|
+ this.fraction_completed();
|
||||||
|
return this.arcpath(begin,this.fraction_completed_pass());
|
||||||
|
},
|
||||||
|
arcpath_completed_fail() {
|
||||||
|
const begin = this.fraction_ungraded()
|
||||||
|
+ this.fraction_completed()
|
||||||
|
+ this.fraction_completed_pass();
|
||||||
|
return this.arcpath(begin,this.fraction_completed_fail());
|
||||||
|
},
|
||||||
|
arcpath_incomplete() {
|
||||||
|
const begin = this.fraction_ungraded()
|
||||||
|
+ this.fraction_completed()
|
||||||
|
+ this.fraction_completed_pass()
|
||||||
|
+ this.fraction_completed_fail();
|
||||||
|
return this.arcpath(begin,this.fraction_incomplete());
|
||||||
|
},
|
||||||
|
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
arcpath(start, end) {
|
||||||
|
const r = 50 - (50*this.stroke);
|
||||||
|
|
||||||
|
const t1 = start * 2*π;
|
||||||
|
const Δ = end * 2*π;
|
||||||
|
return svgarcpath([50,50],[r,r],[t1,Δ], 1.5*π);
|
||||||
|
},
|
||||||
|
fraction_incomplete() {
|
||||||
|
if(this.value.students > 0){
|
||||||
|
return 1 - (
|
||||||
|
(this.value.completed + this.value.completed_pass +
|
||||||
|
this.value.completed_fail + this.value.ungraded) / this.value.students);
|
||||||
|
} else {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fraction_completed() {
|
||||||
|
if(this.value.students > 0){
|
||||||
|
return this.value.completed / this.value.students;
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fraction_completed_pass() {
|
||||||
|
if(this.value.students > 0){
|
||||||
|
return this.value.completed_pass / this.value.students;
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fraction_completed_fail() {
|
||||||
|
if(this.value.students > 0){
|
||||||
|
return this.value.completed_fail / this.value.students;
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fraction_ungraded() {
|
||||||
|
if(this.value.students > 0){
|
||||||
|
return this.value.ungraded / this.value.students;
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
template: `
|
||||||
|
<svg width="1em" height="1em" viewBox="0 0 100 100">
|
||||||
|
<title>{{title}}</title>
|
||||||
|
<circle cx="50" cy="50" :r="radius"
|
||||||
|
:style="'stroke-width: ' + (stroke*100)+'; stroke: #ccc; fill: none;'"/>
|
||||||
|
<path :d="arcpath_ungraded"
|
||||||
|
:style="'stroke-width: ' + (stroke*100) +'; stroke: var(--warning); fill: none;'"/>
|
||||||
|
<path :d="arcpath_completed"
|
||||||
|
:style="'stroke-width: ' + (stroke*100) +'; stroke: var(--info); fill: none;'"/>
|
||||||
|
<path :d="arcpath_completed_pass"
|
||||||
|
:style="'stroke-width: ' + (stroke*100) +'; stroke: var(--success); fill: none;'"/>
|
||||||
|
<path :d="arcpath_completed_fail"
|
||||||
|
:style="'stroke-width: ' + (stroke*100) +'; stroke: var(--danger); fill: none;'"/>
|
||||||
|
|
||||||
|
<circle v-if="disabled" cx="50" cy="50" :r="radius/2"
|
||||||
|
:style="'fill: var(--dark);'"/>
|
||||||
|
<circle v-else-if="value.ungraded > 0" cx="50" cy="50" :r="radius/2"
|
||||||
|
:style="'fill: var(--warning);'"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
`,
|
||||||
|
});
|
||||||
|
|
||||||
Vue.component('r-item-junction',{
|
Vue.component('r-item-junction',{
|
||||||
props: {
|
props: {
|
||||||
value : {
|
value : {
|
||||||
|
@ -1666,16 +1955,46 @@ export default {
|
||||||
return "check";
|
return "check";
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
issuestats(){
|
||||||
|
// so the r-completion-bar can be used to show issuing stats
|
||||||
|
return {
|
||||||
|
students: (this.value.badge.studentcount)?this.value.badge.studentcount:0,
|
||||||
|
completed: (this.value.badge.issuedcount)?this.value.badge.issuedcount:0,
|
||||||
|
completed_pass: 0,
|
||||||
|
completed_fail: 0,
|
||||||
|
ungraded: 0,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
arcpath_issued(){
|
||||||
|
if(this.value.badge.studentcount){
|
||||||
|
const fraction = this.value.badge.issuedcount/this.value.badge.studentcount;
|
||||||
|
return this.arcpath(0,fraction);
|
||||||
|
} else {
|
||||||
|
return ""; // no path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
arcpath(start, end) {
|
||||||
|
const r = 44;
|
||||||
|
|
||||||
|
const t1 = start * 2*π;
|
||||||
|
const Δ = end * 2*π;
|
||||||
|
return svgarcpath([50,50],[r,r],[t1,Δ], 1.5*π);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
template: `
|
template: `
|
||||||
<div :class="'r-item-badge r-item-filter r-completion-'+completion" v-b-tooltip.hover :title="value.badge.name">
|
<div :class="'r-item-badge r-item-filter r-completion-'+completion" v-b-tooltip.hover :title="value.badge.name">
|
||||||
<a v-b-modal="'r-item-badge-details-'+value.id"
|
<a v-b-modal="'r-item-badge-details-'+value.id"
|
||||||
><svg class="r-badge-backdrop " width='50px' height='50px' viewBox="0 0 100 100">
|
><svg class="r-badge-backdrop " width='50px' height='50px' viewBox="0 0 100 100">
|
||||||
<title>{{value.badge.name}}</title>
|
<title>{{value.badge.name}}</title>
|
||||||
<circle v-if="teachermode" cx="50" cy="50" r="46"
|
<template v-if="teachermode">
|
||||||
style="stroke: #999; stroke-width: 6; fill: #ddd; fill-opacity: 0.8;"/>
|
<circle cx="50" cy="50" r="44"
|
||||||
|
style="stroke: #ccc; stroke-width: 8; fill: #ddd; fill-opacity: 0.8;"/>
|
||||||
|
<path :d="arcpath_issued"
|
||||||
|
:style="'stroke-width: 8; stroke: var(--info); fill: none;'"/>
|
||||||
|
</template>
|
||||||
<circle v-else-if="value.badge.issued" cx="50" cy="50" r="46"
|
<circle v-else-if="value.badge.issued" cx="50" cy="50" r="46"
|
||||||
style="stroke: currentcolor; stroke-width: 4; fill: currentcolor; fill-opacity: 0.5;"/>
|
style="stroke: currentcolor; stroke-width: 4; fill: currentcolor; fill-opacity: 0.5;"/>
|
||||||
<circle v-else cx="50" cy="50" r="46"
|
<circle v-else cx="50" cy="50" r="46"
|
||||||
|
@ -1701,7 +2020,7 @@ export default {
|
||||||
>{{ value.badge.name }}</a
|
>{{ value.badge.name }}</a
|
||||||
></h1>
|
></h1>
|
||||||
</div>
|
</div>
|
||||||
<div class="r-course-detail-header-right">
|
<div class="r-course-detail-header-right" v-if="!teachermode">
|
||||||
<div class="r-completion-detail-header">
|
<div class="r-completion-detail-header">
|
||||||
{{ txt.completion['completion_'+completion] }}
|
{{ txt.completion['completion_'+completion] }}
|
||||||
<i v-b-popover.hover :class="'fa fa-'+issued_icon+' r-completion-'+completion"
|
<i v-b-popover.hover :class="'fa fa-'+issued_icon+' r-completion-'+completion"
|
||||||
|
@ -1727,7 +2046,12 @@ export default {
|
||||||
v-if="value.badge.criteria"><li v-for="crit in value.badge.criteria"
|
v-if="value.badge.criteria"><li v-for="crit in value.badge.criteria"
|
||||||
><span v-html='crit'></span></li></ul>
|
><span v-html='crit'></span></li></ul>
|
||||||
<p v-if="(!guestmode)"><strong><i class="fa fa-link"></i>
|
<p v-if="(!guestmode)"><strong><i class="fa fa-link"></i>
|
||||||
<a :href="value.badge.infolink">{{ txt.badge.badgeinfo }}</a></strong></p>
|
<a :href="value.badge.infolink" target="_blank"
|
||||||
|
>{{ txt.badge.badgeinfo }}</a></strong></p>
|
||||||
|
<p v-if="teachermode && !guestmode"
|
||||||
|
>{{txt.badge.badgeissuedstats}}:<br>
|
||||||
|
<r-completion-bar v-model="issuestats" :width="150" :height="15"></r-completion-bar>
|
||||||
|
</p>
|
||||||
</b-col></b-row>
|
</b-col></b-row>
|
||||||
</b-container>
|
</b-container>
|
||||||
</b-modal>
|
</b-modal>
|
||||||
|
|
|
@ -172,13 +172,20 @@ export default {
|
||||||
completion: {
|
completion: {
|
||||||
completion_completed: "completion_completed",
|
completion_completed: "completion_completed",
|
||||||
completion_incomplete: "completion_incomplete",
|
completion_incomplete: "completion_incomplete",
|
||||||
|
aggregation_all: "aggregation_all",
|
||||||
|
aggregation_any: "aggregation_any",
|
||||||
|
aggregation_overall_all: "aggregation_overall_all",
|
||||||
|
aggregation_overall_any: "aggregation_overall_any",
|
||||||
|
completion_not_configured: "completion_not_configured",
|
||||||
|
configure_completion: "configure_completion",
|
||||||
},
|
},
|
||||||
badge: {
|
badge: {
|
||||||
share_badge: "share_badge",
|
share_badge: "share_badge",
|
||||||
dateissued: "dateissued",
|
dateissued: "dateissued",
|
||||||
dateexpire: "dateexpire",
|
dateexpire: "dateexpire",
|
||||||
badgeinfo: "badgeinfo",
|
badgeinfo: "badgeinfo",
|
||||||
}
|
},
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
@ -2152,14 +2159,6 @@ export default {
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
useRequiredGrades() {
|
|
||||||
if(this.plan && this.plan.aggregation_info && this.plan.aggregation_info.useRequiredGrades !== undefined){
|
|
||||||
return this.plan.aggregation_info.useRequiredGrades;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
useItemConditions() {
|
useItemConditions() {
|
||||||
if(this.plan && this.plan.aggregation_info && this.plan.aggregation_info.useItemConditions !== undefined){
|
if(this.plan && this.plan.aggregation_info && this.plan.aggregation_info.useItemConditions !== undefined){
|
||||||
return this.plan.aggregation_info.useItemConditions;
|
return this.plan.aggregation_info.useItemConditions;
|
||||||
|
@ -2168,18 +2167,45 @@ export default {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
selectedgrades(){
|
|
||||||
let list = [];
|
configurationState(){
|
||||||
for(let ix in this.value.course.grades){
|
if(this.hasGrades() || this.hasCompletions()) {
|
||||||
let g = this.value.course.grades[ix];
|
return "t-configured-ok";
|
||||||
if(g.selected){
|
} else {
|
||||||
list.push(g);
|
return "t-configured-alert";
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return list;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
|
configurationIcon(){
|
||||||
|
if(this.hasGrades() || this.hasCompletions()) {
|
||||||
|
return "check";
|
||||||
|
} else {
|
||||||
|
return "exclamation-circle";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
hasGrades() {
|
||||||
|
if(this.value.course.grades && this.value.course.grades > 0){
|
||||||
|
for(const g of this.value.course.grades){
|
||||||
|
if(g.selected){
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
hasCompletions() {
|
||||||
|
if(this.value.course.completion && this.value.course.completion.conditions) {
|
||||||
|
for(const cgroup of this.value.course.completion.conditions){
|
||||||
|
if(cgroup.items && cgroup.items.length > 0){
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
},
|
||||||
includeChanged(newValue,g){
|
includeChanged(newValue,g){
|
||||||
call([{
|
call([{
|
||||||
methodname: 'local_treestudyplan_include_grade',
|
methodname: 'local_treestudyplan_include_grade',
|
||||||
|
@ -2225,7 +2251,8 @@ export default {
|
||||||
<b-card-body class="align-items-center">
|
<b-card-body class="align-items-center">
|
||||||
<a class="t-item-course-config"
|
<a class="t-item-course-config"
|
||||||
v-b-modal="'t-item-course-config-'+value.id"
|
v-b-modal="'t-item-course-config-'+value.id"
|
||||||
href="#" @click.prevent=""><i class="fa fa-gear"></i></a>
|
href="#" @click.prevent=""
|
||||||
|
><i :class="'fa fa-'+configurationIcon+' ' + configurationState"></i></a>
|
||||||
<a v-b-modal="'t-item-course-config-'+value.id"
|
<a v-b-modal="'t-item-course-config-'+value.id"
|
||||||
:id="'t-item-course-details-'+value.id"
|
:id="'t-item-course-details-'+value.id"
|
||||||
:href="'/course/view.php?id='+value.course.id"
|
:href="'/course/view.php?id='+value.course.id"
|
||||||
|
@ -2237,12 +2264,17 @@ export default {
|
||||||
:id="'t-item-course-config-'+value.id"
|
:id="'t-item-course-config-'+value.id"
|
||||||
:title="value.course.displayname + ' - ' + value.course.fullname"
|
:title="value.course.displayname + ' - ' + value.course.fullname"
|
||||||
ok-only
|
ok-only
|
||||||
|
size="lg"
|
||||||
scrollable
|
scrollable
|
||||||
>
|
>
|
||||||
<template #modal-header>
|
<template #modal-header>
|
||||||
<div>
|
<div>
|
||||||
<h1><a :href="'/course/view.php?id='+value.course.id" target="_blank"
|
<h1><a :href="'/course/view.php?id='+value.course.id" target="_blank"
|
||||||
><i class="fa fa-graduation-cap"></i> {{ value.course.fullname }}</a></h1>
|
><i class="fa fa-graduation-cap"></i> {{ value.course.fullname }}</a>
|
||||||
|
<a v-if='!!value.course.completion'
|
||||||
|
:href="'/course/completion.php?id='+value.course.id" target="_blank"
|
||||||
|
:title="text.configure_completion"><i class="fa fa-gear"></i></a>
|
||||||
|
</h1>
|
||||||
{{ value.course.context.path.join(" / ")}} / {{value.course.shortname}}
|
{{ value.course.context.path.join(" / ")}} / {{value.course.shortname}}
|
||||||
</div>
|
</div>
|
||||||
<div class="r-course-detail-header-right">
|
<div class="r-course-detail-header-right">
|
||||||
|
@ -2253,6 +2285,85 @@ export default {
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<t-item-course-grades
|
||||||
|
v-if='!!value.course.grades && value.course.grades.length > 0'
|
||||||
|
v-model='value' :plan="plan"
|
||||||
|
></t-item-course-grades>
|
||||||
|
<t-item-course-completion
|
||||||
|
v-if='!!value.course.completion'
|
||||||
|
v-model='value.course.completion'
|
||||||
|
:course='value.course'
|
||||||
|
></t-item-course-completion>
|
||||||
|
</b-modal>
|
||||||
|
</b-card>
|
||||||
|
`,
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
Vue.component('t-item-course-grades', {
|
||||||
|
props: {
|
||||||
|
'value' :{
|
||||||
|
type: Object,
|
||||||
|
default(){ return null;},
|
||||||
|
},
|
||||||
|
'plan' :{
|
||||||
|
type: Object,
|
||||||
|
default(){ return null;},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
condition_options: string_keys.conditions,
|
||||||
|
text: strings.item_course_text,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
useRequiredGrades() {
|
||||||
|
if(this.plan && this.plan.aggregation_info && this.plan.aggregation_info.useRequiredGrades !== undefined){
|
||||||
|
return this.plan.aggregation_info.useRequiredGrades;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
selectedgrades(){
|
||||||
|
let list = [];
|
||||||
|
for(let ix in this.value.course.grades){
|
||||||
|
let g = this.value.course.grades[ix];
|
||||||
|
if(g.selected){
|
||||||
|
list.push(g);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
includeChanged(newValue,g){
|
||||||
|
call([{
|
||||||
|
methodname: 'local_treestudyplan_include_grade',
|
||||||
|
args: { 'grade_id': g.id,
|
||||||
|
'item_id': this.value.id,
|
||||||
|
'include': newValue,
|
||||||
|
'required': g.required,
|
||||||
|
}
|
||||||
|
}])[0].fail(notification.exception);
|
||||||
|
},
|
||||||
|
requiredChanged(newValue,g){
|
||||||
|
call([{
|
||||||
|
methodname: 'local_treestudyplan_include_grade',
|
||||||
|
args: { 'grade_id': g.id,
|
||||||
|
'item_id': this.value.id,
|
||||||
|
'include': g.selected,
|
||||||
|
'required': newValue,
|
||||||
|
}
|
||||||
|
}])[0].fail(notification.exception);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
|
||||||
|
},
|
||||||
|
template: `
|
||||||
|
<div>
|
||||||
<b-form-group v-if="useItemConditions"
|
<b-form-group v-if="useItemConditions"
|
||||||
:label="text.select_conditions"
|
:label="text.select_conditions"
|
||||||
><b-form-select size="sm"
|
><b-form-select size="sm"
|
||||||
|
@ -2286,11 +2397,108 @@ export default {
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</b-form-group>
|
</b-form-group>
|
||||||
</b-modal>
|
</div>
|
||||||
</b-card>
|
|
||||||
`,
|
`,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Vue.component('t-item-course-completion',{
|
||||||
|
props: {
|
||||||
|
value : {
|
||||||
|
type: Object,
|
||||||
|
default: function(){ return {};},
|
||||||
|
},
|
||||||
|
guestmode: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
course: {
|
||||||
|
type: Object,
|
||||||
|
default: function(){ return {};},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
text: strings.completion,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
created(){
|
||||||
|
const self = this;
|
||||||
|
// Get text strings for condition settings
|
||||||
|
let stringkeys = [];
|
||||||
|
for(const key in this.text){
|
||||||
|
stringkeys.push({ key: key, component: 'local_treestudyplan'});
|
||||||
|
}
|
||||||
|
get_strings(stringkeys).then(function(strings){
|
||||||
|
let i = 0;
|
||||||
|
for(const key in self.text){
|
||||||
|
self.text[key] = strings[i];
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
hasCompletions() {
|
||||||
|
if(this.value.conditions) {
|
||||||
|
for(const cgroup of this.value.conditions){
|
||||||
|
if(cgroup.items && cgroup.items.length > 0){
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
completion_icon(completion) {
|
||||||
|
switch(completion){
|
||||||
|
case "progress":
|
||||||
|
return "exclamation-circle";
|
||||||
|
case "complete":
|
||||||
|
return "check-circle";
|
||||||
|
case "complete-pass":
|
||||||
|
return "check-circle";
|
||||||
|
case "complete-fail":
|
||||||
|
return "times-circle";
|
||||||
|
default: // case "incomplete"
|
||||||
|
return "circle-o";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
completion_tag(cgroup){
|
||||||
|
return cgroup.completion?'completed':'incomplete';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
template: `
|
||||||
|
<table class="r-item-course-grade-details">
|
||||||
|
<tr v-if="hasCompletions">
|
||||||
|
<td colspan='2'><span v-if="value.aggregation == 'all'">{{ text.aggregation_overall_all}}</span
|
||||||
|
><span v-else>{{ text.aggregation_overall_any}}</span></td>
|
||||||
|
</tr>
|
||||||
|
<tr v-else>
|
||||||
|
<td colspan='2'>{{text.completion_not_configured}}!
|
||||||
|
<br/><a :href="'/course/completion.php?id='+course.id" target='_blank'>{{text.configure_completion}}</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<template v-for='cgroup in value.conditions'>
|
||||||
|
<tr>
|
||||||
|
<th colspan='2'><span v-if="cgroup.items.length > 1"
|
||||||
|
><span v-if="cgroup.aggregation == 'all'">{{ text.aggregation_all}}</span
|
||||||
|
><span v-else>{{ text.aggregation_any}}</span></span>
|
||||||
|
{{cgroup.title}}</th>
|
||||||
|
</tr>
|
||||||
|
<tr v-for='ci in cgroup.items'>
|
||||||
|
<td><span v-html='ci.details.criteria'></span>
|
||||||
|
</td>
|
||||||
|
<td v-if="ci.details.requirement" class="font-italic">
|
||||||
|
{{ci.details.requirement}}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</template>
|
||||||
|
</table>
|
||||||
|
`,
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
/************************************
|
/************************************
|
||||||
* *
|
* *
|
||||||
* Competency map Vue components *
|
* Competency map Vue components *
|
||||||
|
|
|
@ -50,10 +50,12 @@ class badgeinfo {
|
||||||
"criteria" => new \external_multiple_structure(new \external_value(PARAM_RAW, 'criteria text'),'badge criteria',VALUE_OPTIONAL),
|
"criteria" => new \external_multiple_structure(new \external_value(PARAM_RAW, 'criteria text'),'badge criteria',VALUE_OPTIONAL),
|
||||||
"description"=> new \external_value(PARAM_TEXT, 'badge description'),
|
"description"=> new \external_value(PARAM_TEXT, 'badge description'),
|
||||||
"imageurl" => new \external_value(PARAM_TEXT, 'url of badge image'),
|
"imageurl" => new \external_value(PARAM_TEXT, 'url of badge image'),
|
||||||
|
"studentcount" => new \external_value(PARAM_INT, 'number of studyplan students that can get this badge',VALUE_OPTIONAL),
|
||||||
|
"issuedcount" => new \external_value(PARAM_INT, 'number of studyplan students that have got this badge',VALUE_OPTIONAL),
|
||||||
],"Badge info",$value);
|
],"Badge info",$value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function editor_model()
|
public function editor_model(array $studentlist=null)
|
||||||
{
|
{
|
||||||
$context = ($this->badge->type == BADGE_TYPE_SITE) ? \context_system::instance() : \context_course::instance($this->badge->courseid);
|
$context = ($this->badge->type == BADGE_TYPE_SITE) ? \context_system::instance() : \context_course::instance($this->badge->courseid);
|
||||||
// If the user is viewing another user's badge and doesn't have the right capability return only part of the data.
|
// If the user is viewing another user's badge and doesn't have the right capability return only part of the data.
|
||||||
|
@ -72,6 +74,13 @@ class badgeinfo {
|
||||||
'description' => $this->badge->description,
|
'description' => $this->badge->description,
|
||||||
'imageurl' => \moodle_url::make_pluginfile_url($context->id, 'badges', 'badgeimage', $this->badge->id, '/','f1')->out(false),
|
'imageurl' => \moodle_url::make_pluginfile_url($context->id, 'badges', 'badgeimage', $this->badge->id, '/','f1')->out(false),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// Add badge issue stats if a studentlist is attached to the request
|
||||||
|
if(!empty($studentlist) && is_array($studentlist)){
|
||||||
|
$model['studentcount'] = count($studentlist);
|
||||||
|
$model['issuedcount'] = $this->count_issued($studentlist);
|
||||||
|
}
|
||||||
|
|
||||||
return $model;
|
return $model;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -130,7 +139,7 @@ class badgeinfo {
|
||||||
function count_issued(array $student_ids){
|
function count_issued(array $student_ids){
|
||||||
$issuecount = 0;
|
$issuecount = 0;
|
||||||
|
|
||||||
foreach($student_ids as $uid){
|
foreach($student_ids as $userid){
|
||||||
if($this->badge->is_issued($userid)){
|
if($this->badge->is_issued($userid)){
|
||||||
$issuecount++;
|
$issuecount++;
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,9 +5,6 @@ require_once($CFG->libdir.'/externallib.php');
|
||||||
|
|
||||||
use \grade_item;
|
use \grade_item;
|
||||||
|
|
||||||
// $gi->courseid,
|
|
||||||
// $gi->itemmodule,
|
|
||||||
// $gi->iteminstance
|
|
||||||
class completionscanner
|
class completionscanner
|
||||||
{
|
{
|
||||||
private static $mod_supported = [];
|
private static $mod_supported = [];
|
||||||
|
@ -39,17 +36,19 @@ class completionscanner
|
||||||
return self::$course_students[$courseid];
|
return self::$course_students[$courseid];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function __construct(\completion_criteria $crit){
|
public function __construct(\completion_criteria $crit,$course){
|
||||||
$this->course = $crit->course;
|
$this->courseid = $course->id;
|
||||||
|
$this->course = $course;
|
||||||
|
$this->modinfo = get_fast_modinfo($course);
|
||||||
$this->crit = $crit;
|
$this->crit = $crit;
|
||||||
|
|
||||||
|
$this->completioninfo = new \completion_info($course);
|
||||||
|
|
||||||
// Find a related scanner if the type is an activity type
|
// Find a related scanner if the type is an activity type
|
||||||
if($crit->criteriatype == COMPLETION_CRITERIA_TYPE_ACTIVITY){
|
if($crit->criteriatype == COMPLETION_CRITERIA_TYPE_ACTIVITY){
|
||||||
// First find the course module
|
// First find the course module
|
||||||
$modinfo = get_fast_modinfo($course);
|
$this->cm = $this->modinfo->get_cm($crit->moduleinstance);
|
||||||
$this->cm = $modinfo->get_cm($crit->moduleinstance);
|
$gi = grade_item::fetch(['itemtype' => 'mod', 'itemmodule' => $this->cm->modname, 'iteminstance' => $this->cm->instance, 'courseid' => $this->courseid]);
|
||||||
|
|
||||||
$gi = grade_item::fetch(['itemtype' => 'mod', 'itemmodule' => $cm->mod, 'iteminstance' => $cm->id, 'courseid' => $course->id]);
|
|
||||||
if($gi !== false)
|
if($gi !== false)
|
||||||
{
|
{
|
||||||
// Grade none items should not be relevant
|
// Grade none items should not be relevant
|
||||||
|
@ -57,38 +56,17 @@ class completionscanner
|
||||||
if(($gi->gradetype == GRADE_TYPE_VALUE || $gi->gradetype == GRADE_TYPE_SCALE))
|
if(($gi->gradetype == GRADE_TYPE_VALUE || $gi->gradetype == GRADE_TYPE_SCALE))
|
||||||
{
|
{
|
||||||
// If it's a relevant grade type, initialize a scanner if possible
|
// If it's a relevant grade type, initialize a scanner if possible
|
||||||
$this->gi = gi;
|
$this->gi = $gi;
|
||||||
if(self::supported($gi->itemmodule)) {
|
if(self::supported($gi->itemmodule)) {
|
||||||
$scannerclass = "\local_treestudyplan\\local\ungradedscanners\\{$gi->itemmodule}_scanner";
|
$scannerclass = "\local_treestudyplan\\local\ungradedscanners\\{$gi->itemmodule}_scanner";
|
||||||
$this->scanner = new $scannerclass($gi);
|
$this->scanner = new $scannerclass($gi);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public function count_students(){
|
|
||||||
return count(self::get_course_students($this->gi->courseid));
|
|
||||||
}
|
|
||||||
|
|
||||||
// count students with ungraded (including completed-fail) worl
|
|
||||||
public function count_ungraded(){
|
|
||||||
if($this->scanner === null) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
return $this->scanner->count_ungraded(self::get_course_students($this->gi->courseid));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function count_completed(){
|
|
||||||
if($this->scanner === null) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
return $this->scanner->count_graded(self::get_course_students($this->gi->courseid));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function pending($userid){
|
public function pending($userid){
|
||||||
if(!array_key_exists($userid, $this->pending_cache)){
|
if(!array_key_exists($userid, $this->pending_cache)){
|
||||||
if($this->scanner === null) {
|
if($this->scanner === null) {
|
||||||
|
@ -112,12 +90,48 @@ class completionscanner
|
||||||
}
|
}
|
||||||
|
|
||||||
public function model(){
|
public function model(){
|
||||||
|
|
||||||
|
// get completion info
|
||||||
|
$students = self::get_course_students($this->courseid);
|
||||||
|
$completed = 0;
|
||||||
|
$ungraded = 0;
|
||||||
|
$completed_pass = 0;
|
||||||
|
$completed_fail = 0;
|
||||||
|
foreach($students as $userid){
|
||||||
|
if($this->pending($userid)){
|
||||||
|
// First check if the completion needs grading
|
||||||
|
$ungraded++;
|
||||||
|
} else {
|
||||||
|
$completion = $this->completioninfo->get_user_completion($userid,$this->crit);
|
||||||
|
|
||||||
|
if($this->crit->criteriatype == COMPLETION_CRITERIA_TYPE_ACTIVITY){
|
||||||
|
// If it's an activity completion, add all the relevant activities as sub-items
|
||||||
|
$completion_status = $this->completioninfo->get_grade_completion($this->cm,$userid);
|
||||||
|
|
||||||
|
if($completion_status == COMPLETION_COMPLETE_PASS){
|
||||||
|
$completed_pass++;
|
||||||
|
} else if ($completion_status == COMPLETION_COMPLETE_FAIL){
|
||||||
|
$completed_fail++;
|
||||||
|
} else if ($completion_status == COMPLETION_COMPLETE){
|
||||||
|
$completed++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
if($completion->is_complete()){
|
||||||
|
$completed++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'ungraded' => $this->count_ungraded(),
|
'ungraded' => $ungraded,
|
||||||
'completed' => $this->count_completed(),
|
'completed' => $completed,
|
||||||
'completed_pass' => 0,
|
'completed_pass' => $completed_pass,
|
||||||
'completed_fail' => 0,
|
'completed_fail' => $completed_fail,
|
||||||
'students' => $this->count_students(),
|
'students' => count($students),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -62,7 +62,7 @@ class corecompletioninfo {
|
||||||
"requirement" => new \external_value(PARAM_RAW, 'requirement',VALUE_OPTIONAL),
|
"requirement" => new \external_value(PARAM_RAW, 'requirement',VALUE_OPTIONAL),
|
||||||
"status" => new \external_value(PARAM_RAW, 'status',VALUE_OPTIONAL),
|
"status" => new \external_value(PARAM_RAW, 'status',VALUE_OPTIONAL),
|
||||||
]),
|
]),
|
||||||
//"studentcompletion" => completionscanner::structure(VALUE_OPTIONAL),
|
"progress" => completionscanner::structure(),
|
||||||
], 'completion type',$value);
|
], 'completion type',$value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -240,7 +240,7 @@ class corecompletioninfo {
|
||||||
"type" => get_string('coursegrade', 'completion'),
|
"type" => get_string('coursegrade', 'completion'),
|
||||||
"criteria" => get_string('graderequired', 'completion'),
|
"criteria" => get_string('graderequired', 'completion'),
|
||||||
// TODO: convert to selected representation (letter, percentage, etc)
|
// TODO: convert to selected representation (letter, percentage, etc)
|
||||||
"requirement" => format_float($criteria->gradepass, $decimalpoints),
|
"requirement" => get_string('graderequired', 'completion').": ".format_float($criteria->gradepass, $decimalpoints),
|
||||||
"status" => "",
|
"status" => "",
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@ -274,13 +274,14 @@ class corecompletioninfo {
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$scanner = new completionscanner($criteria,$this->course);
|
||||||
|
|
||||||
// only add the items list if we actually have items...
|
// only add the items list if we actually have items...
|
||||||
$cinfo["items"][] = [
|
$cinfo["items"][] = [
|
||||||
"id" => $criteria->id,
|
"id" => $criteria->id,
|
||||||
"title" => $criteria->get_title_detailed(),
|
"title" => $criteria->get_title_detailed(),
|
||||||
"details" => $details,
|
"details" => $details,
|
||||||
//"studentcompletion" =>
|
"progress" => $scanner->model(),
|
||||||
];
|
];
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -208,7 +208,7 @@ class courseinfo {
|
||||||
$gradables = gradeinfo::list_course_gradables($this->course,$studyitem);
|
$gradables = gradeinfo::list_course_gradables($this->course,$studyitem);
|
||||||
|
|
||||||
foreach($gradables as $gradable) {
|
foreach($gradables as $gradable) {
|
||||||
$info['grades'][] = $gradable->editor_model();
|
$info['grades'][] = $gradable->editor_model($studyitem);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|
|
@ -328,6 +328,7 @@ class courseservice extends \external_api
|
||||||
return new \external_function_parameters( [
|
return new \external_function_parameters( [
|
||||||
"criteriaid" => new \external_value(PARAM_INT, 'CriteriaID to scan progress for',VALUE_DEFAULT),
|
"criteriaid" => new \external_value(PARAM_INT, 'CriteriaID to scan progress for',VALUE_DEFAULT),
|
||||||
"studyplanid" => new \external_value(PARAM_INT, 'Study plan id to check progress in',VALUE_DEFAULT),
|
"studyplanid" => new \external_value(PARAM_INT, 'Study plan id to check progress in',VALUE_DEFAULT),
|
||||||
|
"courseid" => new \external_value(PARAM_INT, 'Course id of criteria',VALUE_DEFAULT),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -336,7 +337,7 @@ class courseservice extends \external_api
|
||||||
return completionscanner::structure(VALUE_REQUIRED);
|
return completionscanner::structure(VALUE_REQUIRED);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function scan_completion_progress($criteriaid, $studyplanid)
|
public static function scan_completion_progress($criteriaid, $studyplanid,$courseid)
|
||||||
{
|
{
|
||||||
global $DB;
|
global $DB;
|
||||||
// Verify access to the study plan
|
// Verify access to the study plan
|
||||||
|
@ -344,11 +345,10 @@ class courseservice extends \external_api
|
||||||
webservicehelper::require_capabilities(self::CAP_VIEW,$o->context());
|
webservicehelper::require_capabilities(self::CAP_VIEW,$o->context());
|
||||||
|
|
||||||
$crit = \completion_criteria::fetch(["id" => $criteriaid]);
|
$crit = \completion_criteria::fetch(["id" => $criteriaid]);
|
||||||
$courseid = $crit->course;
|
|
||||||
if(!$o->course_linked($courseid)){
|
if(!$o->course_linked($courseid)){
|
||||||
throw new \webservice_access_exception("Course {$courseid} linked to criteria {$criteriaid} is not linked to studyplan {$o->id()}");
|
throw new \webservice_access_exception("Course {$courseid} linked to criteria {$criteriaid} is not linked to studyplan {$o->id()}");
|
||||||
}
|
}
|
||||||
$scanner = new completionscanner($crit);
|
$scanner = new completionscanner($crit,\get_course($courseid));
|
||||||
return $scanner->model();
|
return $scanner->model();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -181,7 +181,7 @@ class gradeinfo {
|
||||||
], 'referenced course information',$value);
|
], 'referenced course information',$value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function editor_model(studyitem $studyitem=null) {
|
public function editor_model(studyitem $studyitem = null) {
|
||||||
$model = [
|
$model = [
|
||||||
"id" => $this->id,
|
"id" => $this->id,
|
||||||
"cmid" => $this->cmid,
|
"cmid" => $this->cmid,
|
||||||
|
@ -194,7 +194,8 @@ class gradeinfo {
|
||||||
"gradinglink" => $this->gradinglink,
|
"gradinglink" => $this->gradinglink,
|
||||||
"required" => $this->is_required(),
|
"required" => $this->is_required(),
|
||||||
];
|
];
|
||||||
if($this->is_selected() && has_capability('local/treestudyplan:viewuserreports',\context_system::instance())
|
// Unfortunately, lazy loading of the completion data is off, since we need the data to show study item completion...
|
||||||
|
if($studyitem !== null && $this->is_selected() && has_capability('local/treestudyplan:viewuserreports',$studyitem->getStudyline()->getStudyplan()->context())
|
||||||
&& $this->gradingscanner->is_available()){
|
&& $this->gradingscanner->is_available()){
|
||||||
$model['grading'] = $this->gradingscanner->model();
|
$model['grading'] = $this->gradingscanner->model();
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,6 +38,7 @@ class gradingscanner
|
||||||
}
|
}
|
||||||
|
|
||||||
public function __construct(grade_item $gi){
|
public function __construct(grade_item $gi){
|
||||||
|
$this->courseid = $gi->courseid;
|
||||||
$this->gi = $gi;
|
$this->gi = $gi;
|
||||||
if(self::supported($gi->itemmodule)) {
|
if(self::supported($gi->itemmodule)) {
|
||||||
$scannerclass = "\local_treestudyplan\\local\ungradedscanners\\{$gi->itemmodule}_scanner";
|
$scannerclass = "\local_treestudyplan\\local\ungradedscanners\\{$gi->itemmodule}_scanner";
|
||||||
|
@ -49,24 +50,6 @@ class gradingscanner
|
||||||
return $this->scanner !== null;
|
return $this->scanner !== null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function count_students(){
|
|
||||||
return count(self::get_course_students($this->gi->courseid));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function count_ungraded(){
|
|
||||||
if($this->scanner === null) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
return $this->scanner->count_ungraded(self::get_course_students($this->gi->courseid));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function count_graded(){
|
|
||||||
if($this->scanner === null) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
return $this->scanner->count_graded(self::get_course_students($this->gi->courseid));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function pending($userid){
|
public function pending($userid){
|
||||||
if(!array_key_exists($userid, $this->pending_cache)){
|
if(!array_key_exists($userid, $this->pending_cache)){
|
||||||
if($this->scanner === null) {
|
if($this->scanner === null) {
|
||||||
|
@ -79,22 +62,113 @@ class gradingscanner
|
||||||
return $this->pending_cache[$userid];
|
return $this->pending_cache[$userid];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public static function structure($value=VALUE_OPTIONAL){
|
public static function structure($value=VALUE_OPTIONAL){
|
||||||
return new \external_single_structure([
|
return new \external_single_structure([
|
||||||
"ungraded" => new \external_value(PARAM_INT, 'number of ungraded submissions'),
|
"ungraded" => new \external_value(PARAM_INT, 'number of ungraded submissions'),
|
||||||
"graded" => new \external_value(PARAM_INT, 'number of graded students'),
|
"completed" => new \external_value(PARAM_INT, 'number of completed students'),
|
||||||
|
"completed_pass" => new \external_value(PARAM_INT, 'number of completed-pass students'),
|
||||||
|
"completed_fail" => new \external_value(PARAM_INT, 'number of completed-fail students'),
|
||||||
"students" => new \external_value(PARAM_INT, 'number of students that should submit'),
|
"students" => new \external_value(PARAM_INT, 'number of students that should submit'),
|
||||||
],"details about gradable submissions",$value);
|
],"details about gradable submissions",$value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function model(){
|
public function model(){
|
||||||
return [
|
// Upda
|
||||||
'ungraded' => $this->count_ungraded(),
|
$students = self::get_course_students($this->courseid);
|
||||||
'graded' => $this->count_graded(),
|
$completed = 0;
|
||||||
'students' => $this->count_students(),
|
$ungraded = 0;
|
||||||
];
|
$completed_pass = 0;
|
||||||
|
$completed_fail = 0;
|
||||||
|
foreach($students as $userid){
|
||||||
|
if($this->pending($userid)){
|
||||||
|
// First check if the completion needs grading
|
||||||
|
$ungraded++;
|
||||||
|
} else {
|
||||||
|
$grade = $this->gi->get_final($userid);
|
||||||
|
if(!is_numeric($grade->finalgrade) && empty($grade->finalgrade)){
|
||||||
|
//skip
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
//compare grade to minimum grade
|
||||||
|
if($this->grade_passed($grade)){
|
||||||
|
$completed_pass++;
|
||||||
|
} else {
|
||||||
|
$completed_fail++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'ungraded' => $ungraded,
|
||||||
|
'completed' => $completed,
|
||||||
|
'completed_pass' => $completed_pass,
|
||||||
|
'completed_fail' => $completed_fail,
|
||||||
|
'students' => count($students),
|
||||||
|
];
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function copied from bistate aggregator to avoid reference mazes
|
||||||
|
private function grade_passed($grade){
|
||||||
|
global $DB;
|
||||||
|
$table = "local_treestudyplan_gradecfg";
|
||||||
|
// first determine if we have a grade_config for this scale or this maximum grade
|
||||||
|
$finalgrade = $grade->finalgrade;
|
||||||
|
$scale = $this->gi->load_scale();
|
||||||
|
if( isset($scale)){
|
||||||
|
$gradecfg = $DB->get_record($table,["scale_id"=>$scale->id]);
|
||||||
|
}
|
||||||
|
else if($this->gi->grademin == 0)
|
||||||
|
{
|
||||||
|
$gradecfg = $DB->get_record($table,["grade_points"=>$this->gi->grademax]);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$gradecfg = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// for point grades, a provided grade pass overrides the defaults in the gradeconfig
|
||||||
|
// for scales, the configuration in the gradeconfig is leading
|
||||||
|
|
||||||
|
if($gradecfg && (isset($scale) || $this->gi->gradepass == 0))
|
||||||
|
{
|
||||||
|
// if so, we need to know if the grade is
|
||||||
|
if($finalgrade >= $gradecfg->min_completed){
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if($this->gi->gradepass > 0)
|
||||||
|
{
|
||||||
|
$range = floatval($this->gi->grademax - $this->gi->grademin);
|
||||||
|
// if no gradeconfig and gradepass is set, use that one to determine config.
|
||||||
|
if($finalgrade >= $this->gi->gradepass){
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Blind assumptions if nothing is provided
|
||||||
|
// over 55% of range is completed
|
||||||
|
// if range >= 3 and failed is enabled, assume that this means failed
|
||||||
|
$g = floatval($finalgrade - $this->gi->grademin);
|
||||||
|
$range = floatval($this->gi->grademax - $this->gi->grademin);
|
||||||
|
$score = $g / $range;
|
||||||
|
|
||||||
|
if($score > 0.55){
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
|
@ -218,6 +218,7 @@ class bistate_aggregator extends \local_treestudyplan\aggregator {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
$grade = $gradeitem->get_final($userid);
|
||||||
// first determine if we have a grade_config for this scale or this maximum grade
|
// first determine if we have a grade_config for this scale or this maximum grade
|
||||||
$finalgrade = $grade->finalgrade;
|
$finalgrade = $grade->finalgrade;
|
||||||
$scale = $gradeinfo->getScale();
|
$scale = $gradeinfo->getScale();
|
||||||
|
@ -291,4 +292,5 @@ class bistate_aggregator extends \local_treestudyplan\aggregator {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
|
@ -145,6 +145,10 @@ class core_aggregator extends \local_treestudyplan\aggregator {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// CORE COMPLETION DOESN'T REALLY USE THE FUNCTIONS BELOW
|
||||||
|
// AGGREGATORS ARE GOING TO BE DEPRECATED ANYWAY... but used in legacy parts of this plugin.
|
||||||
|
|
||||||
public function grade_completion(gradeinfo $gradeinfo, $userid) {
|
public function grade_completion(gradeinfo $gradeinfo, $userid) {
|
||||||
global $DB;
|
global $DB;
|
||||||
$table = "local_treestudyplan_gradecfg";
|
$table = "local_treestudyplan_gradecfg";
|
||||||
|
@ -169,6 +173,7 @@ class core_aggregator extends \local_treestudyplan\aggregator {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
$grade = $gradeitem->get_final($userid);
|
||||||
// first determine if we have a grade_config for this scale or this maximum grade
|
// first determine if we have a grade_config for this scale or this maximum grade
|
||||||
$finalgrade = $grade->finalgrade;
|
$finalgrade = $grade->finalgrade;
|
||||||
$scale = $gradeinfo->getScale();
|
$scale = $gradeinfo->getScale();
|
||||||
|
|
|
@ -92,7 +92,6 @@ class tristate_aggregator extends \local_treestudyplan\aggregator {
|
||||||
|
|
||||||
public function grade_completion(gradeinfo $gradeinfo, $userid) {
|
public function grade_completion(gradeinfo $gradeinfo, $userid) {
|
||||||
global $DB;
|
global $DB;
|
||||||
$table = "local_treestudyplan_gradecfg";
|
|
||||||
$gradeitem = $gradeinfo->getGradeitem();
|
$gradeitem = $gradeinfo->getGradeitem();
|
||||||
$grade = $gradeitem->get_final($userid);
|
$grade = $gradeitem->get_final($userid);
|
||||||
|
|
||||||
|
@ -153,4 +152,6 @@ class tristate_aggregator extends \local_treestudyplan\aggregator {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
|
@ -145,7 +145,10 @@ class studyitem {
|
||||||
if($mode == "export"){
|
if($mode == "export"){
|
||||||
$model['badge'] = $badgeinfo->name();
|
$model['badge'] = $badgeinfo->name();
|
||||||
} else {
|
} else {
|
||||||
$model['badge'] = $badgeinfo->editor_model();
|
// Also supply a list of linked users, so the badgeinfo can give stats on
|
||||||
|
// the amount issued, related to this studyplan
|
||||||
|
$studentids = $this->getStudyline()->getStudyplan()->find_linked_userids();
|
||||||
|
$model['badge'] = $badgeinfo->editor_model($studentids);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,7 @@ class studyplan {
|
||||||
private $id;
|
private $id;
|
||||||
private $aggregator;
|
private $aggregator;
|
||||||
private $context = null; // Hold context object once retrieved
|
private $context = null; // Hold context object once retrieved
|
||||||
|
private $linked_userids = null; // cache lookup of linked users (saves queries)
|
||||||
|
|
||||||
public function getAggregator(){
|
public function getAggregator(){
|
||||||
return $this->aggregator;
|
return $this->aggregator;
|
||||||
|
@ -392,9 +393,10 @@ class studyplan {
|
||||||
* Retrieve the user id's of the users linked to this studyplan.
|
* Retrieve the user id's of the users linked to this studyplan.
|
||||||
* @return array of int (User Id)
|
* @return array of int (User Id)
|
||||||
*/
|
*/
|
||||||
private function find_linked_userids(): array {
|
public function find_linked_userids(): array {
|
||||||
global $DB;
|
global $DB;
|
||||||
|
|
||||||
|
if($this->linked_userids === null){
|
||||||
$uids = [];
|
$uids = [];
|
||||||
// First get directly linked userids
|
// First get directly linked userids
|
||||||
$sql = "SELECT j.user_id FROM {local_treestudyplan_user} j
|
$sql = "SELECT j.user_id FROM {local_treestudyplan_user} j
|
||||||
|
@ -414,7 +416,9 @@ class studyplan {
|
||||||
|
|
||||||
$uids = array_merge($uids,$ulist);
|
$uids = array_merge($uids,$ulist);
|
||||||
|
|
||||||
return array_unique($uids);
|
$this->linked_userids = array_unique($uids);
|
||||||
|
}
|
||||||
|
return $this->linked_userids;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Check if this studyplan is linked to a particular user
|
/** Check if this studyplan is linked to a particular user
|
||||||
|
@ -738,6 +742,23 @@ class studyplan {
|
||||||
return ($count > 0)?true:false;
|
return ($count > 0)?true:false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List the course id is linked in this studyplan
|
||||||
|
* Used for cohort enrolment cascading
|
||||||
|
*/
|
||||||
|
public function get_linked_course_ids(){
|
||||||
|
global $DB;
|
||||||
|
|
||||||
|
$sql = "SELECT i.course_id
|
||||||
|
FROM {local_treestudyplan}
|
||||||
|
INNER JOIN {local_treestudyplan_line} l ON p.id = l.studyplan_id
|
||||||
|
INNER JOIN {local_treestudyplan_item} i ON l.id = i.line_id
|
||||||
|
WHERE p.id = :planid ";
|
||||||
|
$fields = $DB->get_fieldset_sql($sql,["planid" => $this->id]);
|
||||||
|
|
||||||
|
return $fields;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* See if the specified course id is linked in this studyplan
|
* See if the specified course id is linked in this studyplan
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -724,6 +724,12 @@ tr.r-completion-category-header {
|
||||||
margin-top: 2px;
|
margin-top: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.r-progress-circle-popup{
|
||||||
|
position: relative;
|
||||||
|
top: -0.15em;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
.r-completion-detail-header {
|
.r-completion-detail-header {
|
||||||
font-size: 20pt;
|
font-size: 20pt;
|
||||||
}
|
}
|
||||||
|
@ -760,7 +766,7 @@ tr.r-completion-category-header {
|
||||||
}
|
}
|
||||||
|
|
||||||
table.r-item-course-grade-details td {
|
table.r-item-course-grade-details td {
|
||||||
padding-right: 10px;
|
padding-right: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.r-course-detail-header-right {
|
.r-course-detail-header-right {
|
||||||
|
@ -833,12 +839,20 @@ table.r-item-course-grade-details td {
|
||||||
color: #35f;
|
color: #35f;
|
||||||
}
|
}
|
||||||
.r-graded-graded {
|
.r-graded-graded {
|
||||||
color: #3a3;
|
color: var(--success);
|
||||||
}
|
}
|
||||||
.r-graded-nogrades {
|
.r-graded-nogrades {
|
||||||
color: #ddd;
|
color: #ddd;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.t-configured-ok {
|
||||||
|
color: var(--success);
|
||||||
|
}
|
||||||
|
.t-configured-alert {
|
||||||
|
color: var(--warning);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
.r-grading-bar {
|
.r-grading-bar {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
@ -865,17 +879,40 @@ table.r-item-course-grade-details td {
|
||||||
}
|
}
|
||||||
|
|
||||||
.r-grading-bar-unsubmitted {
|
.r-grading-bar-unsubmitted {
|
||||||
background-color: #ddd;
|
background-color: var(--light);
|
||||||
}
|
}
|
||||||
|
|
||||||
.r-grading-bar-graded {
|
.r-grading-bar-graded {
|
||||||
background-color: #3a3;
|
background-color: var(--success);
|
||||||
}
|
}
|
||||||
|
|
||||||
.r-grading-bar-ungraded {
|
.r-grading-bar-ungraded {
|
||||||
background-color: #a33;
|
background-color:var(--danger);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.r-completion-bar-incomplete {
|
||||||
|
background-color: var(--light);
|
||||||
|
}
|
||||||
|
|
||||||
|
.r-completion-bar-completed {
|
||||||
|
background-color: var(--info);
|
||||||
|
}
|
||||||
|
|
||||||
|
.r-completion-bar-completed-pass {
|
||||||
|
background-color: var(--success);
|
||||||
|
}
|
||||||
|
|
||||||
|
.r-completion-bar-completed-fail {
|
||||||
|
background-color: var(--danger);
|
||||||
|
}
|
||||||
|
|
||||||
|
.r-completion-bar-ungraded {
|
||||||
|
background-color: var(--warning);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
.card.s-studyplan-card {
|
.card.s-studyplan-card {
|
||||||
min-width: 300px;
|
min-width: 300px;
|
||||||
max-width: 500px;
|
max-width: 500px;
|
||||||
|
@ -918,20 +955,59 @@ table.r-item-course-grade-details td {
|
||||||
}
|
}
|
||||||
|
|
||||||
.s-required {
|
.s-required {
|
||||||
color: #a33;
|
color: var(--danger);
|
||||||
}
|
}
|
||||||
|
|
||||||
.s-required.completed,
|
.s-required.complete {
|
||||||
|
color: var(--info);
|
||||||
|
}
|
||||||
|
|
||||||
|
.s-required.complete-pass,
|
||||||
.s-required.good,
|
.s-required.good,
|
||||||
.s-required.excellent,
|
.s-required.excellent,
|
||||||
.s-required.allgraded {
|
.s-required.allgraded {
|
||||||
color: rgb(0, 126, 0);
|
color: var(--success);
|
||||||
}
|
}
|
||||||
|
|
||||||
.s-required.neutral {
|
.s-required.neutral {
|
||||||
color: #aaa;
|
color: #aaa;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.r-tooltip.incomplete .tooltip-inner,
|
||||||
|
.r-tooltip.complete-fail .tooltip-inner,
|
||||||
|
.r-tooltip.completed-fail .tooltip-inner {
|
||||||
|
background-color: var(--danger);
|
||||||
|
}
|
||||||
|
.r-tooltip.incomplete .arrow::before,
|
||||||
|
.r-tooltip.complete-fail .arrow::before,
|
||||||
|
.r-tooltip.completed-fail .arrow::before {
|
||||||
|
border-top-color: var(--danger);
|
||||||
|
}
|
||||||
|
|
||||||
|
.r-tooltip.complete .tooltip-inner,
|
||||||
|
.r-tooltip.completed .tooltip-inner {
|
||||||
|
background-color: var(--info);
|
||||||
|
}
|
||||||
|
.r-tooltip.complete .arrow::before,
|
||||||
|
.r-tooltip.completed .arrow::before {
|
||||||
|
border-top-color: var(--info);
|
||||||
|
}
|
||||||
|
|
||||||
|
.r-tooltip.complete-pass .tooltip-inner,
|
||||||
|
.r-tooltip.completed-pass .tooltip-inner {
|
||||||
|
background-color: var(--success);
|
||||||
|
}
|
||||||
|
.r-tooltip.complete-pass .arrow::before,
|
||||||
|
.r-tooltip.completed-pass .arrow::before {
|
||||||
|
border-top-color: var(--success);
|
||||||
|
}
|
||||||
|
|
||||||
|
.r-tooltip.incomplete .tooltip-inner {
|
||||||
|
background-color: var(--danger);
|
||||||
|
}
|
||||||
|
.r-tooltip.incomplete .arrow::before {
|
||||||
|
border-top-color: var(--danger);
|
||||||
|
}
|
||||||
|
|
||||||
.m-buttonbar {
|
.m-buttonbar {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -939,7 +1015,6 @@ table.r-item-course-grade-details td {
|
||||||
justify-content: left;
|
justify-content: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.m-buttonbar a,
|
.m-buttonbar a,
|
||||||
.m-buttonbar span,
|
.m-buttonbar span,
|
||||||
.m-buttonbar i {
|
.m-buttonbar i {
|
||||||
|
|
|
@ -109,6 +109,8 @@ $string['condition_any'] = 'One or more entries need to be completed';
|
||||||
$string['courses'] = 'Courses';
|
$string['courses'] = 'Courses';
|
||||||
$string['select_grades'] = 'Grades included in report';
|
$string['select_grades'] = 'Grades included in report';
|
||||||
|
|
||||||
|
$string['configure_completion'] = "Configure course completion";
|
||||||
|
$string['completion_not_configured'] = "Course completion has not yet been configured.";
|
||||||
$string['completion_failed'] = "Failed";
|
$string['completion_failed'] = "Failed";
|
||||||
$string['completion_incomplete'] = "Not started";
|
$string['completion_incomplete'] = "Not started";
|
||||||
$string['completion_pending'] = "Pending review";
|
$string['completion_pending'] = "Pending review";
|
||||||
|
@ -116,6 +118,7 @@ $string['completion_progress'] = "In progress";
|
||||||
$string['completion_completed'] = "Completed";
|
$string['completion_completed'] = "Completed";
|
||||||
$string['completion_good'] = "Good";
|
$string['completion_good'] = "Good";
|
||||||
$string['completion_excellent'] = "Excellent";
|
$string['completion_excellent'] = "Excellent";
|
||||||
|
$string['completion_passed'] = "Passed";
|
||||||
|
|
||||||
$string['cfg_grades'] = 'Configure grade & scale interpretation';
|
$string['cfg_grades'] = 'Configure grade & scale interpretation';
|
||||||
$string['cfg_plans'] = 'Manage study plans';
|
$string['cfg_plans'] = 'Manage study plans';
|
||||||
|
@ -151,7 +154,6 @@ $string['selected'] = 'Select';
|
||||||
$string['name'] = 'Name';
|
$string['name'] = 'Name';
|
||||||
$string['context'] = 'Category';
|
$string['context'] = 'Category';
|
||||||
|
|
||||||
|
|
||||||
$string['error'] = "Error";
|
$string['error'] = "Error";
|
||||||
$string['ungraded'] = 'Needs grading';
|
$string['ungraded'] = 'Needs grading';
|
||||||
$string['graded'] = 'Graded';
|
$string['graded'] = 'Graded';
|
||||||
|
@ -249,3 +251,4 @@ $string["share_badge"] = "Share badge";
|
||||||
$string["dateissued"] = "Issued on";
|
$string["dateissued"] = "Issued on";
|
||||||
$string["dateexpire"] = "Expires on";
|
$string["dateexpire"] = "Expires on";
|
||||||
$string["badgeinfo"] = "Badge details";
|
$string["badgeinfo"] = "Badge details";
|
||||||
|
$string["badgeissuedstats"] = "Issuing progress";
|
|
@ -111,7 +111,8 @@ $string['condition_any'] = 'Minimaal één onderdeel moet afgerond zijn';
|
||||||
$string['courses'] = 'Cursussen';
|
$string['courses'] = 'Cursussen';
|
||||||
$string['select_grades'] = 'Resultaten die meetellen';
|
$string['select_grades'] = 'Resultaten die meetellen';
|
||||||
|
|
||||||
|
$string['configure_completion'] = "Voltooiing instellen";
|
||||||
|
$string['completion_not_configured'] = "De cursusvoltooing is nog niet ingesteld.";
|
||||||
$string['completion_failed'] = "Onvoldoende";
|
$string['completion_failed'] = "Onvoldoende";
|
||||||
$string['completion_incomplete'] = "Niet gestart";
|
$string['completion_incomplete'] = "Niet gestart";
|
||||||
$string['completion_pending'] = "Wacht op beoordelen";
|
$string['completion_pending'] = "Wacht op beoordelen";
|
||||||
|
@ -119,6 +120,7 @@ $string['completion_progress'] = "In ontwikkeling";
|
||||||
$string['completion_completed'] = "Voltooid";
|
$string['completion_completed'] = "Voltooid";
|
||||||
$string['completion_good'] = "Goed";
|
$string['completion_good'] = "Goed";
|
||||||
$string['completion_excellent'] = "Uitstekend";
|
$string['completion_excellent'] = "Uitstekend";
|
||||||
|
$string['completion_passed'] = "Behaald";
|
||||||
|
|
||||||
$string['cfg_grades'] = 'Configureer betekenis van beoordelingen en schalen';
|
$string['cfg_grades'] = 'Configureer betekenis van beoordelingen en schalen';
|
||||||
$string['cfg_plans'] = 'Studieplannen beheren';
|
$string['cfg_plans'] = 'Studieplannen beheren';
|
||||||
|
@ -252,3 +254,4 @@ $string["share_badge"] = "Bewijs delen";
|
||||||
$string["dateissued"] = "Afgegeven op";
|
$string["dateissued"] = "Afgegeven op";
|
||||||
$string["dateexpire"] = "Veloopt op";
|
$string["dateexpire"] = "Veloopt op";
|
||||||
$string["badgeinfo"] = "Meer details";
|
$string["badgeinfo"] = "Meer details";
|
||||||
|
$string["badgeissuedstats"] = "Voortgang van uitgifte";
|
Loading…
Reference in a new issue