Replaced student email by last course access time
This commit is contained in:
parent
6b616c0a6a
commit
8f2673c4a4
2
amd/build/studyplan-report-components.min.js
vendored
2
amd/build/studyplan-report-components.min.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
2
amd/build/util/date-helper.min.js
vendored
2
amd/build/util/date-helper.min.js
vendored
|
@ -1,3 +1,3 @@
|
|||
define("local_treestudyplan/util/date-helper",["exports"],(function(_exports){function format_date(d,short){d instanceof Date||(d=new Date(d));let monthformat="short";return!0===short?monthformat="numeric":!1===short&&(monthformat="long"),d.toLocaleDateString(document.documentElement.lang,{year:"numeric",month:monthformat,day:"numeric"})}function studyplanDates(plan){let earliestStart=null,latestEnd=null,openEnded=!1;for(const ix in plan.pages){const page=plan.pages[ix],s=new Date(page.startdate);if(page.enddate||(openEnded=!0),(!earliestStart||s<earliestStart)&&(earliestStart=s),page.enddate){const e=new Date(page.enddate);(!latestEnd||e>latestEnd)&&(latestEnd=e)}}return{start:earliestStart,end:openEnded?null:latestEnd}}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.add_days=function(datestr,days){const date=new Date(datestr);return function(date){const d=new Date(date);let month=""+(d.getMonth()+1),day=""+d.getDate();const year=d.getFullYear();month.length<2&&(month="0"+month);day.length<2&&(day="0"+day);return[year,month,day].join("-")}(new Date(date.getTime()+864e5*days))},_exports.datespaninfo=function(first,last){first instanceof Date||(first=new Date(first));last instanceof Date||(last=new Date(last));first.setHours(0),first.setMinutes(0),first.setSeconds(0),first.setMilliseconds(0),last.setHours(23),last.setMinutes(59),last.setSeconds(59),last.setMilliseconds(999);const dayspan=Math.round((last-first+1)/864e5),years=Math.floor(dayspan/365),ydaysleft=dayspan%365,weeks=Math.floor(ydaysleft/7);return{first:first,last:last,totaldays:dayspan,years:years,weeks:weeks,days:ydaysleft%7,formatted:{first:format_date(first),last:format_date(last)}}},_exports.format_date=format_date,_exports.studyplanDates=studyplanDates,_exports.studyplanPageTiming=function(page){const now=(new Date).getTime(),start=new Date(page.startdate),end=page.enddate?new Date(page.enddate):null;return start<now?end&&now>end?"past":"present":"future"},_exports.studyplanTiming=function(plan){const now=(new Date).getTime(),dates=studyplanDates(plan);return dates.start<now?dates.end&&now>dates.end?"past":"present":"future"}}));
|
||||
define("local_treestudyplan/util/date-helper",["exports"],(function(_exports){function format_date(d,short){d instanceof Date||(d=new Date(d));let monthformat="short";return!0===short?monthformat="numeric":!1===short&&(monthformat="long"),d.toLocaleDateString(document.documentElement.lang,{year:"numeric",month:monthformat,day:"numeric"})}function studyplanDates(plan){let earliestStart=null,latestEnd=null,openEnded=!1;for(const ix in plan.pages){const page=plan.pages[ix],s=new Date(page.startdate);if(page.enddate||(openEnded=!0),(!earliestStart||s<earliestStart)&&(earliestStart=s),page.enddate){const e=new Date(page.enddate);(!latestEnd||e>latestEnd)&&(latestEnd=e)}}return{start:earliestStart,end:openEnded?null:latestEnd}}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.add_days=function(datestr,days){const date=new Date(datestr);return function(date){const d=new Date(date);let month=""+(d.getMonth()+1),day=""+d.getDate();const year=d.getFullYear();month.length<2&&(month="0"+month);day.length<2&&(day="0"+day);return[year,month,day].join("-")}(new Date(date.getTime()+864e5*days))},_exports.datespaninfo=function(first,last){first instanceof Date||(first=new Date(first));last instanceof Date||(last=new Date(last));first.setHours(0),first.setMinutes(0),first.setSeconds(0),first.setMilliseconds(0),last.setHours(23),last.setMinutes(59),last.setSeconds(59),last.setMilliseconds(999);const dayspan=Math.round((last-first+1)/864e5),years=Math.floor(dayspan/365),ydaysleft=dayspan%365,weeks=Math.floor(ydaysleft/7);return{first:first,last:last,totaldays:dayspan,years:years,weeks:weeks,days:ydaysleft%7,formatted:{first:format_date(first),last:format_date(last)}}},_exports.format_date=format_date,_exports.format_datetime=function(d,short){d instanceof Date||(d=new Date(d));let monthformat="short";!0===short?monthformat="numeric":!1===short&&(monthformat="long");return d.toLocaleDateString(document.documentElement.lang,{year:"numeric",month:monthformat,day:"numeric"})+" "+d.toLocaleTimeString(document.documentElement.lang,{timeStyle:"short"})},_exports.studyplanDates=studyplanDates,_exports.studyplanPageTiming=function(page){const now=(new Date).getTime(),start=new Date(page.startdate),end=page.enddate?new Date(page.enddate):null;return start<now?end&&now>end?"past":"present":"future"},_exports.studyplanTiming=function(plan){const now=(new Date).getTime(),dates=studyplanDates(plan);return dates.start<now?dates.end&&now>dates.end?"past":"present":"future"}}));
|
||||
|
||||
//# sourceMappingURL=date-helper.min.js.map
|
File diff suppressed because one or more lines are too long
|
@ -13,6 +13,7 @@ import Debugger from './util/debugger';
|
|||
import Config from 'core/config';
|
||||
import TSComponents from './treestudyplan-components';
|
||||
import FitTextVue from './util/fittext-vue';
|
||||
import {format_datetime} from "./util/date-helper";
|
||||
|
||||
const debug = new Debugger("treestudyplan-viewer");
|
||||
|
||||
|
@ -121,7 +122,8 @@ export default {
|
|||
students: 'students@core',
|
||||
firstname: 'firstname@core',
|
||||
lastname: 'lastname@core',
|
||||
email: 'email@core'
|
||||
email: 'email@core',
|
||||
lastaccess: 'lastaccess@core',
|
||||
},
|
||||
studentresults: {
|
||||
completion_incomplete: "completion_incomplete",
|
||||
|
@ -132,6 +134,7 @@ export default {
|
|||
completion_good: "completion_good",
|
||||
completion_excellent: "completion_excellent",
|
||||
student_not_tracked: "student_not_tracked",
|
||||
never: "never@core",
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -166,12 +169,13 @@ export default {
|
|||
};
|
||||
},
|
||||
created() {
|
||||
this.loadStudents();
|
||||
},
|
||||
watch:{
|
||||
structure: {
|
||||
immediate: true,
|
||||
handler (structure) {
|
||||
this.loadStudents(); // reload the student list
|
||||
|
||||
// (Re)build expansion info structure
|
||||
let firstperiod = true;
|
||||
for (const period of structure.periods) {
|
||||
|
@ -224,7 +228,7 @@ export default {
|
|||
computed: {
|
||||
sortedstudents(){
|
||||
const self=this;
|
||||
// Probably should make a deep copy for purity's sake, but this works just as well.
|
||||
// Probably could make a deep copy for purity's sake, but this works just as well and is probably more efficient.
|
||||
const students = this.students;
|
||||
for (const group of this.students) {
|
||||
group.users.sort((a,b) => {
|
||||
|
@ -234,7 +238,13 @@ export default {
|
|||
d = b;
|
||||
e = a;
|
||||
}
|
||||
return String(d[this.sorting.header]).localeCompare(String(e[this.sorting.header]));
|
||||
if (this.sorting.header == "lastaccess") {
|
||||
const dvalue = (d[this.sorting.header]?d[this.sorting.header]:0);
|
||||
const evalue = (e[this.sorting.header]?e[this.sorting.header]:0);
|
||||
return dvalue - evalue;
|
||||
} else {
|
||||
return String(d[this.sorting.header]).localeCompare(String(e[this.sorting.header]));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -342,7 +352,7 @@ export default {
|
|||
:style="'--resultColCount: '+resultColCount+';'">
|
||||
<colgroup class="q-col-studentinfo">
|
||||
<col class="q-name"></col>
|
||||
<col class="q-email"></col>
|
||||
<col class="q-lastaccess"></col>
|
||||
</colgroup>
|
||||
<colgroup class="q-col-resultinfo">
|
||||
<col v-for="n in resultColCount"></col>
|
||||
|
@ -475,11 +485,11 @@ export default {
|
|||
template: `
|
||||
<thead class='q-header'>
|
||||
<tr> <!-- period heading -->
|
||||
<th rowspan='3' colspan='2' class='q-studentinfo q-generic'><span>{{text.students}}</span></th>
|
||||
<th rowspan='4' 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 && p.lines.length > 0)?1:4'
|
||||
:rowspan='(expansion.periods[p.period.id].expanded && p.lines.length > 0)?1:5'
|
||||
><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-minus'></i
|
||||
|
@ -494,7 +504,7 @@ export default {
|
|||
<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)"
|
||||
:rowspan='(expansion.lines[p.period.id][l.line.id].expanded)?1:3'
|
||||
:rowspan='(expansion.lines[p.period.id][l.line.id].expanded)?1:4'
|
||||
><span class="q-wrap"><fittext vertical maxsize="18pt"
|
||||
><span class='q-label'
|
||||
:title="l.line.shortname"
|
||||
|
@ -513,7 +523,7 @@ export default {
|
|||
<th v-for="item in l.items"
|
||||
:class="'q-item-heading ' + ((expansion.items[item.id].expanded)?'expanded':'collapsed')"
|
||||
:colspan="colspanItem(item)"
|
||||
:rowspan='(expansion.items[item.id].expanded)?1:2'
|
||||
:rowspan='(expansion.items[item.id].expanded)?1:3'
|
||||
><span class="q-wrap"><a href='#'
|
||||
@click.prevent="toggleItem(item)"
|
||||
><i v-if="expansion.items[item.id].expanded"
|
||||
|
@ -537,32 +547,16 @@ 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">
|
||||
<template v-if="expansion.lines[p.period.id][l.line.id].expanded">
|
||||
<template v-for="item in l.items">
|
||||
<template v-if="expansion.items[item.id].expanded">
|
||||
<th class='q-condition-heading overall'
|
||||
<th class='q-condition-heading overall' rowspan="2"
|
||||
><span class='q-wrap'>{{ text.overall }}</span></th>
|
||||
<th v-for="c in conditions(item)"
|
||||
<th v-for="c in conditions(item)"
|
||||
rowspan="2"
|
||||
class='q-condition-heading'
|
||||
><span class="q-wrap"><fittext vertical maxsize="14pt"><a class='q-label q-condition-label'
|
||||
:title="c.tooltip" href="#" @click.prevent
|
||||
|
@ -577,6 +571,25 @@ export default {
|
|||
</template>
|
||||
</template>
|
||||
</tr>
|
||||
<tr> <!-- student info 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('lastaccess')">{{text.lastaccess}}</a
|
||||
><i v-if="sorting.header=='lastaccess' && sorting.asc" class='fa fa-sort-asc fa-fw'></i
|
||||
><i v-else-if="sorting.header=='lastaccess' && !sorting.asc" class='fa fa-sort-desc fa-fw'></i
|
||||
></fittext>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
`,
|
||||
});
|
||||
|
@ -678,6 +691,13 @@ export default {
|
|||
};
|
||||
},
|
||||
computed: {
|
||||
lastaccess() {
|
||||
if (this.student.lastaccess) {
|
||||
return format_datetime(this.student.lastaccess*1000); // Takes date in milliseconds
|
||||
} else {
|
||||
return this.text.never;
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
useritems(line) {
|
||||
|
@ -705,7 +725,7 @@ export default {
|
|||
template: `
|
||||
<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>
|
||||
<td class='q-studentinfo q-email'><fittext maxsize="12pt">{{lastaccess}}</fittext></td>
|
||||
<template v-for="p in structure.periods">
|
||||
<template v-if="expansion.periods[p.period.id].expanded && p.lines.length > 0">
|
||||
<template v-for="l in p.lines">
|
||||
|
|
|
@ -20,6 +20,30 @@ export function format_date(d,short){
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a date according to localized custom
|
||||
* @param {Date|string} d The date to convert
|
||||
* @param {boolean} short Short format (default false)
|
||||
* @returns {string}
|
||||
*/
|
||||
export function format_datetime(d,short){
|
||||
if(!(d instanceof Date)){
|
||||
d = new Date(d);
|
||||
}
|
||||
|
||||
let monthformat = "short";
|
||||
if(short === true){
|
||||
monthformat = "numeric";
|
||||
} else if (short === false) {
|
||||
monthformat = "long";
|
||||
}
|
||||
return d.toLocaleDateString(document.documentElement.lang,{
|
||||
year: 'numeric', month: monthformat, day: 'numeric'
|
||||
})+" "+d.toLocaleTimeString(document.documentElement.lang,{
|
||||
timeStyle: "short",
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides standardized information about the period between two dates
|
||||
* As
|
||||
|
|
|
@ -53,6 +53,7 @@ class associationservice extends \external_api {
|
|||
"lastname" => new \external_value(PARAM_TEXT, 'last name'),
|
||||
"idnumber" => new \external_value(PARAM_TEXT, 'id number'),
|
||||
"email" => new \external_value(PARAM_TEXT, 'email address'),
|
||||
"lastaccess" => new \external_value(PARAM_INT, 'id of last access this user had to any course in the studyplan', VALUE_OPTIONAL),
|
||||
]);
|
||||
}
|
||||
|
||||
|
@ -125,6 +126,28 @@ class associationservice extends \external_api {
|
|||
|
||||
}
|
||||
|
||||
public static function user_lastaccess($userid,$studyplanid=null) {
|
||||
global $DB;
|
||||
if (!empty($studyplanid)) {
|
||||
$lasql = "SELECT MAX(a.timeaccess) FROM {user_lastaccess} a
|
||||
INNER JOIN {local_treestudyplan_item} i ON i.course_id = a.courseid
|
||||
INNER JOIN {local_treestudyplan_line} l ON l.id = i.line_id
|
||||
INNER JOIN {local_treestudyplan_page} p ON l.page_id = p.id
|
||||
WHERE a.userid = :userid AND p.studyplan_id = :studyplanid";
|
||||
$lastaccess = $DB->get_field_sql($lasql,["userid" => $userid, "studyplanid" => $studyplanid]);
|
||||
debug::write("Got lastaccess '{$lastaccess}' for user {$userid} in plan {$studyplanid}");
|
||||
} else {
|
||||
$lasql = "SELECT MAX(a.timeaccess) FROM {user_lastaccess} a
|
||||
WHERE a.userid = :userid";
|
||||
$lastaccess = $DB->get_field_sql($lasql,["userid" => $userid]);
|
||||
debug::write("Got lastaccess '{$lastaccess}' for user {$userid} in any course");
|
||||
}
|
||||
|
||||
|
||||
return $lastaccess;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Parameter description for webservice function list_cohort
|
||||
*/
|
||||
|
@ -454,9 +477,17 @@ class associationservice extends \external_api {
|
|||
ORDER BY u.lastname, u.firstname";
|
||||
$rs = $DB->get_recordset_sql($sql, ['studyplan_id' => $studyplanid]);
|
||||
|
||||
/*
|
||||
ID: 30
|
||||
page: 33
|
||||
plan: 28
|
||||
*/
|
||||
$users = [];
|
||||
foreach ($rs as $u) {
|
||||
$users[] = self::make_user_model($u);
|
||||
$user = self::make_user_model($u);
|
||||
$user["lastaccess"] = self::user_lastaccess($u->id,$studyplanid);
|
||||
$users[] = $user;
|
||||
|
||||
}
|
||||
$rs->close();
|
||||
self::sortusermodels($users);
|
||||
|
@ -601,7 +632,9 @@ class associationservice extends \external_api {
|
|||
$rs = $DB->get_recordset_sql($sql, ["cohortid" => $c->id]);
|
||||
|
||||
foreach ($rs as $u) {
|
||||
$users[] = self::make_user_model($u);
|
||||
$user = self::make_user_model($u);
|
||||
$user["lastaccess"] = self::user_lastaccess($u->id,$studyplanid);
|
||||
$users[] = $user;
|
||||
}
|
||||
$rs->close();
|
||||
|
||||
|
|
|
@ -1586,8 +1586,11 @@ body.path-local-treestudyplan .editmode-switch-form > * {
|
|||
table-layout: fixed;
|
||||
width: calc(24rem + var(--resultColCount) * 4rem);
|
||||
}
|
||||
.path-local-treestudyplan table.q-studyplanreport colgroup.q-col-studentinfo col {
|
||||
width: 12rem;
|
||||
.path-local-treestudyplan table.q-studyplanreport colgroup.q-col-studentinfo col.q-name {
|
||||
width: 14rem;
|
||||
}
|
||||
.path-local-treestudyplan table.q-studyplanreport colgroup.q-col-studentinfo col.q-lastaccess {
|
||||
width: 10rem;
|
||||
}
|
||||
.path-local-treestudyplan table.q-studyplanreport colgroup.q-col-resultinfo col {
|
||||
width: 4rem;
|
||||
|
|
|
@ -9,8 +9,11 @@
|
|||
table-layout: fixed;
|
||||
width: calc((2 * 12rem) + (var(--resultColCount) * 4rem));
|
||||
colgroup.q-col-studentinfo {
|
||||
col {
|
||||
width: 12rem;
|
||||
col.q-name {
|
||||
width: 14rem;
|
||||
}
|
||||
col.q-lastaccess {
|
||||
width: 10rem;
|
||||
}
|
||||
}
|
||||
colgroup.q-col-resultinfo {
|
||||
|
|
|
@ -1586,8 +1586,11 @@ body.path-local-treestudyplan .editmode-switch-form > * {
|
|||
table-layout: fixed;
|
||||
width: calc(24rem + var(--resultColCount) * 4rem);
|
||||
}
|
||||
.path-local-treestudyplan table.q-studyplanreport colgroup.q-col-studentinfo col {
|
||||
width: 12rem;
|
||||
.path-local-treestudyplan table.q-studyplanreport colgroup.q-col-studentinfo col.q-name {
|
||||
width: 14rem;
|
||||
}
|
||||
.path-local-treestudyplan table.q-studyplanreport colgroup.q-col-studentinfo col.q-lastaccess {
|
||||
width: 10rem;
|
||||
}
|
||||
.path-local-treestudyplan table.q-studyplanreport colgroup.q-col-resultinfo col {
|
||||
width: 4rem;
|
||||
|
|
Loading…
Reference in New Issue
Block a user