Started work on coaching page

This commit is contained in:
PMKuipers 2024-03-09 00:11:42 +01:00
parent c480c20098
commit 25a5ff8398
15 changed files with 336 additions and 149 deletions

3
amd/build/page-coach.min.js vendored Normal file
View File

@ -0,0 +1,3 @@
define("local_treestudyplan/page-coach",["exports","core/ajax","core/notification","./vue/vue","./util/debugger","./util/string-helper","./studyplan-processor","./util/date-helper","./report-viewer-components","./treestudyplan-components","./modedit-modal","./portal-vue/portal-vue.esm","./bootstrap-vue/bootstrap-vue"],(function(_exports,_ajax,_notification,_vue,_debugger,_stringHelper,_studyplanProcessor,_dateHelper,_reportViewerComponents,_treestudyplanComponents,_modeditModal,_portalVue,_bootstrapVue){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=function(){let app=new _vue.default({el:"#root",data:{displayedstudyplan:null,activestudyplan:null,associatedstudents:[],selectedstudent:null,studentstudyplan:null,loadingstudyplan:!1,studyplans:[],text:strings.studyplan,toolbox:{right:!0},usedcontexts:[]},async mounted(){(0,_ajax.call)([{methodname:"list_coaching_studyplans",args:{}}])[0].then((function(response){const timingval={present:0,past:1,future:2};response.sort(((a,b)=>{const timinga=(0,_dateHelper.studyplanTiming)(a),timingb=(0,_dateHelper.studyplanTiming)(b),t=timingval[timinga]-timingval[timingb];return 0==t?a.name.localeCompare(b.name):t})),app.studyplans=response;const parts=window.location.hash.replace("#","").split("-");if(parts&&parts.length>0)for(let idx in app.studyplans)if(app.studyplans[idx].id==parts[0]){app.selectStudyplan(app.studyplans[idx],parts[1]);break}})).catch(_notification.default.exception)},computed:{},methods:{closeStudyplan(){app.activestudyplan=null,app.associatedstudents=[],app.studentstudyplan=[],app.displayedstudyplan=null,window.location.hash=""},selectStudyplan(studyplan,studentid){app.loadingstudyplan=!0,app.activestudyplan=null,app.associatedstudents=[],app.selectedstudent=null,app.studentstudyplan=null,(0,_ajax.call)([{methodname:"local_treestudyplan_get_studyplan_map",args:{id:studyplan.id}}])[0].then((function(response){app.activestudyplan=(0,_studyplanProcessor.ProcessStudyplan)(response,!0),app.displayedstudyplan=app.activestudyplan,app.loadingstudyplan=!1,window.location.hash=app.activestudyplan.id,(0,_ajax.call)([{methodname:"local_treestudyplan_all_associated_grouped",args:{studyplan_id:studyplan.id}}])[0].then((function(response){if(app.associatedstudents=response,studentid)for(const group of app.associatedstudents)for(const student of group.users)if(student.id==studentid){app.showStudentView(student);break}})).catch(_notification.default.exception)})).catch((function(error){_notification.default.exception(error),app.loadingstudyplan=!1}))},showStudentView(student){app.selectedstudent=student,app.studentstudyplan=null,student&&(app.loadingstudyplan=!0,(0,_ajax.call)([{methodname:"local_treestudyplan_get_user_studyplan",args:{userid:student.id,studyplanid:app.activestudyplan.id}}])[0].then((function(response){app.studentstudyplan=(0,_studyplanProcessor.ProcessStudyplan)(response,!1),app.displayedstudyplan=app.studentstudyplan,app.loadingstudyplan=!1,window.location.hash=app.activestudyplan.id+"-"+student.id})).catch((function(error){_notification.default.exception(error),app.loadingstudyplan=!1})))},showOverview(){app.selectedstudent=null,app.studentstudyplan=null,app.displayedstudyplan=app.activestudyplan,window.location.hash=app.activestudyplan.id}}})},_notification=_interopRequireDefault(_notification),_vue=_interopRequireDefault(_vue),_debugger=_interopRequireDefault(_debugger),_reportViewerComponents=_interopRequireDefault(_reportViewerComponents),_treestudyplanComponents=_interopRequireDefault(_treestudyplanComponents),_modeditModal=_interopRequireDefault(_modeditModal),_portalVue=_interopRequireDefault(_portalVue),_bootstrapVue=_interopRequireDefault(_bootstrapVue),_vue.default.use(_reportViewerComponents.default),_vue.default.use(_modeditModal.default),_vue.default.use(_portalVue.default),_vue.default.use(_bootstrapVue.default);new _debugger.default("treestudyplanviewer");let strings=(0,_stringHelper.load_strings)({studyplan:{studyplan_select_placeholder:"studyplan_select_placeholder"}})}));
//# sourceMappingURL=page-coach.min.js.map

File diff suppressed because one or more lines are too long

3
amd/build/page-view-plan copy.min.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

169
amd/src/page-coach.js Normal file
View File

@ -0,0 +1,169 @@
/*eslint no-var: "error" */
/*eslint no-unused-vars: "off" */
/*eslint linebreak-style: "off" */
/*eslint no-trailing-spaces: "off" */
/*eslint-env es6*/
// Put this file in path/to/plugin/amd/src
// You can call it anything you like
import {call} from 'core/ajax';
import notification from 'core/notification';
import Vue from './vue/vue';
import Debugger from './util/debugger';
import {load_strings} from './util/string-helper';
import {ProcessStudyplan} from './studyplan-processor';
import {studyplanTiming} from './util/date-helper';
import RVComponents from './report-viewer-components';
Vue.use(RVComponents);
import TSComponents from './treestudyplan-components';
import ModalComponents from './modedit-modal';
Vue.use(ModalComponents);
import PortalVue from './portal-vue/portal-vue.esm';
Vue.use(PortalVue);
import BootstrapVue from './bootstrap-vue/bootstrap-vue';
Vue.use(BootstrapVue);
let debug = new Debugger("treestudyplanviewer");
let strings = load_strings({
studyplan: {
studyplan_select_placeholder: 'studyplan_select_placeholder',
},
});
/**
* Initialize the Page
*/
export function init() {
let app = new Vue({
el: '#root',
data: {
displayedstudyplan: null,
activestudyplan: null,
associatedstudents: [],
selectedstudent: null,
studentstudyplan: null,
loadingstudyplan: false,
studyplans: [],
text: strings.studyplan,
toolbox: {
right: true,
},
usedcontexts: [],
},
async mounted() {
call([{
methodname: 'list_coaching_studyplans',
args: {}
}])[0].then(function(response){
const timingval = { present: 0, past: 1, future: 2};
response.sort((a,b) => {
const timinga = studyplanTiming(a);
const timingb = studyplanTiming(b);
const t = timingval[timinga] - timingval[timingb];
if(t == 0){
// sort by name if timing is equal
return a.name.localeCompare(b.name);
}
else {
return t;
}
});
app.studyplans = response;
// load studyplan from hash if applicable
const hash = window.location.hash.replace('#','');
const parts = hash.split("-");
if(!!parts && parts.length > 0){
for(let idx in app.studyplans){
if(app.studyplans[idx].id == parts[0]){
app.selectStudyplan(app.studyplans[idx],parts[1]);
break;
}
}
}
}).catch(notification.exception);
},
computed: {
},
methods: {
closeStudyplan() {
app.activestudyplan = null;
app.associatedstudents = [];
app.studentstudyplan = [];
app.displayedstudyplan = null;
window.location.hash = '';
},
selectStudyplan(studyplan,studentid){
// fetch studyplan
app.loadingstudyplan = true;
app.activestudyplan = null;
app.associatedstudents = [];
app.selectedstudent = null;
app.studentstudyplan = null;
call([{
methodname: 'local_treestudyplan_get_studyplan_map',
args: { id: studyplan.id}
}])[0].then(function(response){
app.activestudyplan = ProcessStudyplan(response,true);
app.displayedstudyplan = app.activestudyplan;
app.loadingstudyplan = false;
window.location.hash = app.activestudyplan.id;
call([{
methodname: 'local_treestudyplan_all_associated_grouped',
args: { studyplan_id: studyplan.id}
}])[0].then(function(response){
app.associatedstudents = response;
if(studentid){
for(const group of app.associatedstudents) {
for(const student of group.users){
if(student.id == studentid){
app.showStudentView(student);
break;
}
}
}
}
}).catch(notification.exception);
}).catch(function(error){
notification.exception(error);
app.loadingstudyplan = false;
});
},
showStudentView(student){
app.selectedstudent = student;
app.studentstudyplan = null;
if (student) {
app.loadingstudyplan = true;
call([{
methodname: 'local_treestudyplan_get_user_studyplan',
args: { userid: student.id, studyplanid: app.activestudyplan.id}
}])[0].then(function(response){
app.studentstudyplan = ProcessStudyplan(response,false);
app.displayedstudyplan = app.studentstudyplan;
app.loadingstudyplan = false;
window.location.hash = app.activestudyplan.id + "-" + student.id;
}).catch(function(error){
notification.exception(error);
app.loadingstudyplan = false;
});
}
},
showOverview(){
app.selectedstudent = null;
app.studentstudyplan = null;
app.displayedstudyplan = app.activestudyplan;
window.location.hash = app.activestudyplan.id;
}
},
});
}

View File

@ -1334,7 +1334,15 @@ export default {
* T-STUDYPLAN
*/
Vue.component('t-studyplan', {
props: [ 'value', 'index'],
props: {
'value': {
type: Object,
},
'coaching': {
type: Boolean,
default: false,
},
},
data() {
return {
config: {
@ -1720,10 +1728,12 @@ export default {
<div>
<div class='controlbox t-studyplan-controlbox'>
<div class="controlbox-group">
<b-form-checkbox v-model="edit.studyline.editmode" class="sw-studyplan-toolbar" switch
<b-form-checkbox v-if="!coaching"
v-model="edit.studyline.editmode" class="sw-studyplan-toolbar" switch
@change="toolbox_switched(edit.toolbox_shown && !edit.studyline.editmode); "
>{{ text.studyline_editmode }}</b-form-checkbox>
<b-form-checkbox v-if="!edit.studyline.editmode" v-model="edit.toolbox_shown" class="sw-studyplan-toolbar" switch
<b-form-checkbox
v-if="!edit.studyline.editmode" v-model="edit.toolbox_shown" class="sw-studyplan-toolbar" switch
@change="toolbox_switched"
>{{ text.toolbox_toggle}}</b-form-checkbox>
<drop
@ -1734,7 +1744,7 @@ export default {
><i class='fa fa-trash'></i>
</drop>
</div>
<div class="controlbox-group">
<div class="controlbox-group" v-if="!coaching">
<span class='control editable'>
<t-studyplan-advanced v-model="value" :selectedpage="selectedpage"></t-studyplan-advanced>
</span>
@ -1760,6 +1770,7 @@ export default {
<!-- New Tab Button (Using tabs-end slot) -->
<template #tabs-end>
<t-studyplan-page-edit
v-if="!coaching"
:studyplan="value"
v-model="create.page"
type="link"
@ -1774,7 +1785,7 @@ export default {
<template #title>
{{page.shortname}}
<t-studyplan-page-edit
v-if="pageindex == selectedpageindex"
v-if="!coaching && (pageindex == selectedpageindex)"
v-model="value.pages[pageindex]"
:studyplan="value"
type="link"
@ -1802,6 +1813,7 @@ export default {
<i class="fa fa-arrows text-primary"></i>
</template>
<t-studyline-edit
v-if="!coaching"
v-model="item"
@edit='editLineStart(item)'
@delete='deleteLine(page,item)'
@ -1844,6 +1856,7 @@ export default {
v-if="index > 0"
v-model="page.perioddesc[index-1]"
><t-period-edit
v-if="!coaching"
:ref="'periodeditor-'+index"
@edited="periodEdited"
v-model="page.perioddesc[index-1]"

View File

@ -38,7 +38,11 @@ class studentstudyplanservice extends \external_api {
* @var string
*/
const CAP_VIEWOTHER = "local/treestudyplan:viewuserreports";
/**
* Capability required to be linked as coach to a studyplan
* @var string
*/
const CAP_COACH = "local/treestudyplan:coach";
/************************
* *
* list_user_studyplans *
@ -510,6 +514,51 @@ class studentstudyplanservice extends \external_api {
return $list;
}
/***************************
* *
* list_coaching_studyplans *
* *
***************************/
/**
* Parameter description for webservice function list_teaching_studyplans
*/
public static function list_coaching_studyplans_parameters() : \external_function_parameters {
return new \external_function_parameters( [] );
}
/**
* Return value description for webservice function list_teaching_studyplans
*/
public static function list_coaching_studyplans_returns() : \external_description {
return new \external_multiple_structure(
studyplan::simple_structure()
);
}
/**
* Get all or one studyplan the current user is teaching in
* @param int $id Optional specific id of studyplan
* @return array
*/
public static function list_coaching_studyplans() {
global $USER, $DB;
$userid = $USER->id;
\external_api::validate_context(\context_system::instance());
$studyplanids = $DB->get_records("local_treestudyplan_coach",["user_id" => $userid]);
$list = [];
foreach ($studyplanids as $id) {
$studyplan = studyplan::find_by_id($id);
if (has_capability(self::CAP_COACH,$studyplan->context(),$USER)) {
$list[] = $studyplan->simple_model($userid);
}
}
return $list;
}
/***************************
* *
* get_teaching_studyplan *

146
coach.php
View File

@ -32,71 +32,20 @@ $systemcontext = context_system::instance();
$PAGE->set_url("/local/treestudyplan/view-plan.php", array());
require_login();
// Figure out the context (category or system, based on either category or context parameter).
$categoryid = optional_param('categoryid', 0, PARAM_INT); // Category id.
$contextid = optional_param('contextid', 0, PARAM_INT); // Context id.
if ($categoryid > 0) {
$studyplancontext = context_coursecat::instance($categoryid);
} else if ($contextid > 0) {
$studyplancontext = context::instance_by_id($contextid);
if (in_array($studyplancontext->contextlevel, [CONTEXT_SYSTEM, CONTEXT_COURSECAT])) {
$categoryid = $studyplancontext->instanceid;
} else {
$studyplancontext = $systemcontext;
}
} else {
// If no context is selected, find the first available one.
$availablecontexts = courseservice::list_available_categories("view");
$contextid = 1; // Fallback to system context.
foreach ($availablecontexts as $ctx) {
if ($ctx["studyplancount"] > 0) {
$contextid = $ctx["context_id"];
break;
}
}
// Reload page with selected category.
$url = new \moodle_url('/local/treestudyplan/view-plan.php', ["contextid" => $contextid]);
header('Location: '.$url->out(false), true, 302);
exit;
}
$ci = new contextinfo($studyplancontext);
$contextname = $ci->pathstr();
$PAGE->set_pagelayout('base');
//$PAGE->set_context($studyplancontext);
$PAGE->set_title(get_string('view_plan', 'local_treestudyplan')." - ".$contextname);
$PAGE->set_heading(get_string('view_plan', 'local_treestudyplan')." - ".$contextname);
$PAGE->set_title(get_string('coaching_plans', 'local_treestudyplan')." - ".$contextname);
$PAGE->set_heading(get_string('coaching_plans', 'local_treestudyplan')." - ".$contextname);
if ($studyplancontext->id > 1) {
navigation_node::override_active_url(new moodle_url('/course/index.php', ['categoryid' => $categoryid ]));
$PAGE->navbar->add(get_string('view_plan', 'local_treestudyplan'));
// Coursecat context
$cat = \core_course_category::get($studyplancontext->instanceid,IGNORE_MISSING,true); // We checck visibility later
} else {
// System context
$cat = \core_course_category::top();
}
if (!$cat->is_uservisible()) {
throw new \moodle_exception("error:cannotviewcategory","local_treestudyplan","/local/treestudyplan/view_plan.php",$contextname);
}
if(!has_capability('local/treestudyplan:viewuserreports', $studyplancontext)) {
throw new \moodle_exception("error:nostudyplanviewaccess","local_treestudyplan","/local/treestudyplan/view_plan.php",$contextname);
}
// Load javascripts and specific css.
$PAGE->requires->css(new moodle_url($CFG->wwwroot.'/local/treestudyplan/css/bootstrap-vue/bootstrap-vue.css'));
if ($CFG->debugdeveloper) {
$PAGE->requires->css(new moodle_url($CFG->wwwroot.'/local/treestudyplan/css/devstyles.css'));
}
$PAGE->requires->js_call_amd('local_treestudyplan/page-view-plan', 'init', [$studyplancontext->id, $categoryid]);
$PAGE->requires->js_call_amd('local_treestudyplan/page-coach', 'init', []);
/**
* Shortcut function to provide translations
@ -119,65 +68,52 @@ print $OUTPUT->header();
</div>
</div>
<div v-cloak>
<div v-if='!activestudyplan && usedcontexts' class='ml-3 mb-3 s-context-selector'>
<b-form-select text='<?php print($contextname);?>' :value="contextid" @change='switchContext'
:class="(!(usedcontexts.length))?'text-primary':''">
<b-form-select-option v-if='!(usedcontexts.length)' :value="contextid"
:class="'text-primary'">
<span><?php t("loading",null,"core"); ?>...</span></b-form-select-option>
<b-form-select-option v-for='ctx in usedcontexts' :key='ctx.id' :value="ctx.context_id"
:class="(ctx.studyplancount > 0) ? 'font-weight-bold' : ''"
><span v-for="(p, i) in ctx.category.path"><span v-if="i>0"> / </span>{{ p }}</span>
<span>({{ ctx.studyplancount }})</span></b-form-select-option>
</b-form-select>
<div v-if="!(usedcontexts.length)" style="position: relative; top: 0.3rem; width: 1.2rem; height: 1.2rem; font-size: 0.7rem;"
class="spinner-border text-primary" role="status"></div>
</div>
<h3 v-else><?php print $contextname; ?></h3>
<div class="m-buttonbar" style="margin-bottom: 1em;">
<a href='#' v-if='displayedstudyplan' @click.prevent='closeStudyplan'
><i style='font-size: 150%;' class='fa fa-chevron-left'></i> <?php t('back');?></a>
<span v-if='displayedstudyplan'><?php t("studyplan_select"); ?></span>&nbsp;
<b-form-select v-if='displayedstudyplan' lazy :text='dropdown_title' :value='displayedstudyplan.id'>
<b-form-select-option
v-for='(studyplan, planindex) in studyplans'
:key='studyplan.id'
@click='selectStudyplan(studyplan)'
:value='studyplan.id'
>{{ studyplan.name }}</b-form-select-option>
</b-form-select>&nbsp;
<s-studyplan-details
v-model="displayedstudyplan"
v-if="displayedstudyplan && displayedstudyplan.description"
></s-studyplan-details>
<div class="flex-grow-1"><!-- Spacer to align student selector right --></div>
<div v-if="displayedstudyplan && displayedstudyplan.description">
<span><?php t('selectstudent_btn') ?></span>
<s-prevnext-selector
:options="associatedstudents"
title="firstname"
v-model="selectedstudent"
defaultselectable
grouped
optionsfield='users'
arrows
@change="showStudentView"
class="ml-2"
variant="primary"
>
<template v-slot="{value}">{{value.firstname}} {{value.lastname}}</template>
<template #defaultlabel><span class='text-primary'><?php t("showoverview"); ?></span></template>
</s-prevnext-selector>
</div>
<template v-if="displayedstudyplan">
<a href='#' @click.prevent='closeStudyplan'
><i style='font-size: 150%;' class='fa fa-chevron-left'></i> <?php t('back');?></a>
<span><?php t("studyplan_select"); ?></span>&nbsp;
<s-studyplan-details
v-model="displayedstudyplan"
v-if="displayedstudyplan.description"
></s-studyplan-details>
<div class="flex-grow-1"><!-- Spacer to align student selector right --></div>
<div v-if="displayedstudyplan.description">
<span><?php t('selectstudent_btn') ?></span>
<s-prevnext-selector
:options="associatedstudents"
title="firstname"
v-model="selectedstudent"
defaultselectable
grouped
optionsfield='users'
arrows
@change="showStudentView"
class="ml-2"
variant="primary"
>
<template v-slot="{value}">{{value.firstname}} {{value.lastname}}</template>
<template #defaultlabel><span class='text-primary'><?php t("showoverview"); ?></span></template>
</s-prevnext-selector>
</div>
</template>
</div>
<div class='t-studyplan-container'>
<h2 v-if='displayedstudyplan&& selectedstudent'
>{{selectedstudent.firstname}} {{selectedstudent.lastname}} - {{displayedstudyplan.name}}</h2>
<h2 v-else-if='displayedstudyplan'><?php t("showoverview"); ?> - {{displayedstudyplan.name}}</h2>
<r-studyplan v-if='!loadingstudyplan && displayedstudyplan'
<template v-if="!loadingstudyplan && displayedstudyplan">
<r-studyplan v-if="!editmode"
v-model='displayedstudyplan' :teachermode='!selectedstudent'
></r-studyplan>
<t-studyplan
v-else
v-model='displayedstudyplan'
@toggletoolbox="toggletoolbox"
@pagechanged="onPageChange"
coachmode
></t-studyplan>
</template>
<div v-else-if='loadingstudyplan' class="spinner-border text-primary" role="status">
<span class="sr-only">Loading...</span>
</div>

View File

@ -715,6 +715,15 @@ $functions = [
'capabilities' => '',
'loginrequired' => true,
],
'local_treestudyplan_list_coaching_studyplans' => [ // Web service function name.
'classname' => '\local_treestudyplan\studentstudyplanservice', // Class containing the external function.
'methodname' => 'list_coaching_studyplans', // External function name.
'description' => 'List studyplans for current user as coach',
'type' => 'read', // Database rights of the web service function (read, write).
'ajax' => true,
'capabilities' => '',
'loginrequired' => true,
],
/***************************
* Studyplan report functions
***************************/

View File

@ -78,6 +78,7 @@ $string["invite_mail_text"] = '
$string["view_plan"] = 'View study plans';
$string["edit_plan"] = 'Edit study plan';
$string["coaching_plans"] = "Coaching study plans";
$string["settingspage"] = 'Study plan settings';
$string["setting_navigation_heading"] = 'Navigation';

View File

@ -79,6 +79,7 @@ $string["invite_mail_text"] = '
$string["view_plan"] = 'Studieplannen bekijken';
$string["edit_plan"] = 'Studieplan bewerken';
$string["coaching_plans"] = "Studieplannen die ik coach";
$string["settingspage"] = 'Studieplan instellingen';
$string["setting_navigation_heading"] = 'Navigatie';

View File

@ -22,7 +22,7 @@
defined('MOODLE_INTERNAL') || die();
$plugin->component = 'local_treestudyplan'; // Recommended since 2.0.2 (MDL-26035). Required since 3.0 (MDL-48494).
$plugin->version = 2024030804; // YYYYMMDDHH (year, month, day, iteration).
$plugin->version = 2024030805; // YYYYMMDDHH (year, month, day, iteration).
$plugin->requires = 2021051700; // YYYYMMDDHH (This is the release version for Moodle 3.11).
$plugin->release = "1.1.6";

View File

@ -135,41 +135,42 @@ print $OUTPUT->header();
</div>
<h3 v-else><?php print $contextname; ?></h3>
<div class="m-buttonbar" style="margin-bottom: 1em;">
<a href='#' v-if='displayedstudyplan' @click.prevent='closeStudyplan'
><i style='font-size: 150%;' class='fa fa-chevron-left'></i> <?php t('back');?></a>
<span v-if='displayedstudyplan'><?php t("studyplan_select"); ?></span>&nbsp;
<b-form-select v-if='displayedstudyplan' lazy :text='dropdown_title' :value='displayedstudyplan.id'>
<b-form-select-option
v-for='(studyplan, planindex) in studyplans'
:key='studyplan.id'
@click='selectStudyplan(studyplan)'
:value='studyplan.id'
>{{ studyplan.name }}</b-form-select-option>
</b-form-select>&nbsp;
<s-studyplan-details
v-model="displayedstudyplan"
v-if="displayedstudyplan && displayedstudyplan.description"
></s-studyplan-details>
<div class="flex-grow-1"><!-- Spacer to align student selector right --></div>
<div v-if="displayedstudyplan && displayedstudyplan.description">
<span><?php t('selectstudent_btn') ?></span>
<s-prevnext-selector
:options="associatedstudents"
title="firstname"
v-model="selectedstudent"
defaultselectable
grouped
optionsfield='users'
arrows
@change="showStudentView"
class="ml-2"
variant="primary"
>
<template v-slot="{value}">{{value.firstname}} {{value.lastname}}</template>
<template #defaultlabel><span class='text-primary'><?php t("showoverview"); ?></span></template>
</s-prevnext-selector>
</div>
<template v-if="displayedstudyplan">
<a href='#' @click.prevent='closeStudyplan'
><i style='font-size: 150%;' class='fa fa-chevron-left'></i> <?php t('back');?></a>
<span><?php t("studyplan_select"); ?></span>&nbsp;
<b-form-select lazy :text='dropdown_title' :value='displayedstudyplan.id'>
<b-form-select-option
v-for='(studyplan, planindex) in studyplans'
:key='studyplan.id'
@click='selectStudyplan(studyplan)'
:value='studyplan.id'
>{{ studyplan.name }}</b-form-select-option>
</b-form-select>&nbsp;
<s-studyplan-details
v-model="displayedstudyplan"
v-if="displayedstudyplan.description"
></s-studyplan-details>
<div class="flex-grow-1"><!-- Spacer to align student selector right --></div>
<div>
<span><?php t('selectstudent_btn') ?></span>
<s-prevnext-selector
:options="associatedstudents"
title="firstname"
v-model="selectedstudent"
defaultselectable
grouped
optionsfield='users'
arrows
@change="showStudentView"
class="ml-2"
variant="primary"
>
<template v-slot="{value}">{{value.firstname}} {{value.lastname}}</template>
<template #defaultlabel><span class='text-primary'><?php t("showoverview"); ?></span></template>
</s-prevnext-selector>
</div>
</template>
</div>
<div class='t-studyplan-container'>
<h2 v-if='displayedstudyplan&& selectedstudent'