diff --git a/notemyprogress/amd/build/setweeks.js b/notemyprogress/amd/build/setweeks.js new file mode 100644 index 0000000000000000000000000000000000000000..e146ee081510484791c4b287615930aa620b7271 --- /dev/null +++ b/notemyprogress/amd/build/setweeks.js @@ -0,0 +1,440 @@ +define([ + "local_notemyprogress/vue", + "local_notemyprogress/vuetify", + "local_notemyprogress/axios", + "local_notemyprogress/sortablejs", + "local_notemyprogress/draggable", + "local_notemyprogress/datepicker", + "local_notemyprogress/moment", + "local_notemyprogress/alertify", + "local_notemyprogress/pageheader", +], function ( + Vue, + Vuetify, + Axios, + Sortable, + Draggable, + Datepicker, + Moment, + Alertify, + Pageheader +) { + "use strict"; + + function init(content) { + content = add_collapsabled_property_to_weeks(content); + Vue.use(Vuetify); + Vue.component("draggable", Draggable); + Vue.component("datepicker", Datepicker); + Vue.component("pageheader", Pageheader); + const app = new Vue({ + delimiters: ["[[", "]]"], + el: "#setweeks", + vuetify: new Vuetify(), + data: { + display_settings: false, + settings: content.settings, + new_group: false, + scroll_mode: false, + weeks_started_at: new Date( + Moment(Number(content.weeks[0].weekstart) * 1000) + ), // will be cloned in the cachedValues cache + strings: content.strings, + sections: content.sections, // will be cloned in the cachedValues cache + courseid: content.courseid, + userid: content.userid, + raw_weeks: content.weeks, // will be cloned in the cachedValues cache + disabled_dates: { + days: [0, 2, 3, 4, 5, 6], + }, + saving_loader: false, + error_messages: [], + save_successful: false, + form_identical: true, + }, + mounted() { + document.querySelector("#setweeks-loader").style.display = "none"; + document.querySelector("#setweeks").style.display = "block"; + this.cache_weeks_started_at = new Date(this.weeks_started_at.getTime()); + this.cache_sections = JSON.parse(JSON.stringify(this.sections)); + this.cache_raw_weeks = JSON.parse(JSON.stringify(this.raw_weeks)); + }, + computed: { + weeks() { + for (let i = 0; i < this.raw_weeks.length; i++) { + let week = this.raw_weeks[i]; + if (i == 0) { + let start_weeks = this.weeks_started_at; + week.weekstart = start_weeks; + week.weekend = this.get_end_week(this.weeks_started_at); + } else { + week.weekstart = this.get_start_week( + this.raw_weeks[i - 1].weekend + ); + week.weekend = this.get_end_week(week.weekstart); + } + } + return this.raw_weeks; + }, + cache_weeks() { + for (let i = 0; i < this.cache_raw_weeks.length; i++) { + let week = this.cache_raw_weeks[i]; + if (i == 0) { + let start_weeks = this.cache_weeks_started_at; + week.weekstart = start_weeks; + week.weekend = this.get_end_week(this.cache_weeks_started_at); + } else { + week.weekstart = this.get_start_week( + this.cache_raw_weeks[i - 1].weekend + ); + week.weekend = this.get_end_week(week.weekstart); + } + } + return this.cache_raw_weeks; + }, + }, + + methods: { + + form_changed(){ + this.form_identical = false; + }, + cache_save() { + this.cache_weeks_started_at = new Date(this.weeks_started_at.getTime()); + this.cache_sections = JSON.parse(JSON.stringify(this.sections)); + this.cache_raw_weeks = JSON.parse(JSON.stringify(this.raw_weeks)); + this.form_identical = true; + //this.watcher_called = false; + }, + cache_cancel() { + this.weeks_started_at = new Date(this.cache_weeks_started_at.getTime()); + this.sections = JSON.parse(JSON.stringify(this.cache_sections)); + this.raw_weeks = JSON.parse(JSON.stringify(this.cache_raw_weeks)); + this.form_identical = true; + //this.watcher_called = false; + //console.log("cache_cancel end"); + }, + + section_name(section) { + let name = null; + if ( + typeof section.section_name != "undefined" && + section.section_name.length > 0 + ) { + name = section.section_name; + } else { + name = section.name; + } + return name; + }, + + section_exist(section) { + let exist = true; + if ( + typeof section != "undefined" && + typeof section.exists != "undefined" && + section.exists == false + ) { + exist = false; + } + return exist; + }, + + format_name(name, position) { + return name + " " + (position + 1); + }, + + customFormatter(date) { + let weeks_start_at = Moment(date).format("YYYY-MM-DD"); + return weeks_start_at; + }, + + add_week() { + this.raw_weeks.push({ + name: this.strings.week, + position: this.weeks.length + 1, + weekstart: null, + weekend: null, + collapsabled: false, + hours_dedications: 0, + removable: true, + sections: [], + }); + this.form_changed(); + }, + + has_items(array) { + return array.length > 0; + }, + + remove_week(week, index) { + if (index == 0) { + return null; + } + this.close_delete_confirm(); + for (let i = 0; i < week.sections.length; i++) { + this.sections.push(week.sections[i]); + } + let element_index = this.raw_weeks.indexOf(week); + this.raw_weeks.splice(element_index, 1); + this.form_changed(); + }, + + ask_delete_confirm() { + this.delete_confirm = true; + }, + + close_delete_confirm() { + this.delete_confirm = false; + }, + + get_start_week(pass_week) { + let start_date = Moment(Moment(pass_week).add(1, "days")).format( + "YYYY-MM-DD" + ); + return start_date; + }, + + get_end_week(start_week) { + let end_date = Moment(Moment(start_week).add(6, "days")).format( + "YYYY-MM-DD" + ); + return end_date; + }, + + get_date_next_day(requested_day, date, output_format = null) { + requested_day = requested_day.toLowerCase(); + let current_day = Moment(date).format("dddd").toLowerCase(); + while (current_day != requested_day) { + date = Moment(date).add(1, "days"); + current_day = Moment(date).format("dddd").toLowerCase(); + } + if (output_format) { + date = date.format(output_format); + } else { + if (typeof date != "number") { + date = parseInt(date.format("x")); + } + } + return date; + }, + + position(index) { + index++; + return `${index} - `; + }, + + save_changes() { + this.save_successful = false; + this.error_messages = []; + if (this.empty_weeks()) { + this.saving_loader = false; + Alertify.error(this.strings.error_empty_week); + this.error_messages.push(this.strings.error_empty_week); + return false; + } + if (this.weeks_deleted_from_course()) { + this.saving_loader = false; + this.error_messages.push(this.strings.error_section_removed); + return false; + } + + Alertify.confirm( + this.strings.save_warning_content, + () => { + this.saving_loader = true; + var weeks = this.weeks; + weeks[0].weekstart = Moment(weeks[0].weekstart).format( + "YYYY-MM-DD" + ); + var data = { + action: "saveconfigweek", + userid: this.userid, + courseid: this.courseid, + newinstance: this.new_group, + weeks: this.minify_query(weeks), // Stringify is a hack to clone object :D + }; + + Axios({ + method: "get", + url: M.cfg.wwwroot + "/local/notemyprogress/ajax.php", + params: data, + }) + .then((response) => { + if (response.status == 200 && response.data.ok) { + this.settings = response.data.data.settings; + Alertify.success(this.strings.save_successful); + this.save_successful = true; + this.cache_save(); + } else { + Alertify.error(this.strings.error_network); + this.error_messages.push(this.strings.error_network); + } + }) + .catch((e) => { + console.log("catch1"); + Alertify.error(this.strings.error_network); + console.log("catch2"); + this.error_messages.push(this.strings.error_network); + console.log("catch3"); + }) + .finally(() => { + this.saving_loader = false; + //this.addLogsIntoDB("saved", "configuration", "weeks", "Saved a new configuration for the weeks !"); + }); + }, + () => { + // ON CANCEL + this.saving_loader = false; + Alertify.warning(this.strings.cancel_action); + } + ) + .set({ title: this.strings.save_warning_title }) + .set({ + labels: { + cancel: this.strings.confirm_cancel, + ok: this.strings.confirm_ok, + }, + }); + }, + + minify_query(weeks) { + var minify = []; + weeks.forEach((week) => { + var wk = new Object(); + wk.h = week.hours_dedications; + wk.s = week.weekstart; + wk.e = week.weekend; + wk.sections = []; + week.sections.forEach((section) => { + var s = new Object(); + s.sid = section.sectionid; + wk.sections.push(s); + }); + minify.push(wk); + }); + return JSON.stringify(minify); + }, + + empty_weeks() { + if (this.weeks.length >= 2 && this.weeks[0].sections.length < 1) { + return true; + } + for (let i = 0; i < this.weeks.length; i++) { + if (i > 0 && this.weeks[i].sections.length <= 0) { + return true; + } + } + return false; + }, + + weeks_deleted_from_course() { + for ( + var week_position = 0; + week_position < this.weeks.length; + week_position++ + ) { + for ( + var section_position = 0; + section_position < this.weeks[week_position].sections.length; + section_position++ + ) { + if ( + !this.section_exist( + this.weeks[week_position].sections[section_position] + ) + ) { + return true; + } + } + } + return false; + }, + + exists_mistakes() { + let exists_mistakes = this.error_messages.length > 0; + return exists_mistakes; + }, + + change_collapsabled(index) { + this.raw_weeks[index].collapsabled = + !this.raw_weeks[index].collapsabled; + this.form_changed(); + }, + + course_finished() { + let finished = false; + let last = this.weeks.length - 1; + let end = Moment(this.weeks[last].weekend).format("X"); + let now = Moment().format("X"); + if (now > end) { + finished = true; + } else { + finished = false; + } + return finished; + }, + + get_settings_status() { + let visible = true; + Object.keys(this.settings).map((key) => { + if (!this.settings[key]) { + visible = false; + } + }); + let status = visible + ? this.strings.plugin_visible + : this.strings.plugin_hidden; + return status; + }, + + get_help_content() { + var help_contents = []; + var help = new Object(); + help.title = this.strings.title; + help.description = this.strings.description; + help_contents.push(help); + return help_contents; + }, + + addLogsIntoDB(action, objectName, objectType, objectDescription) { + let data = { + courseid: content.courseid, + userid: content.userid, + action: "addLogs", + sectionname: "CONFIGURATION_COURSE_WEEK", + actiontype: action, + objectType: objectType, + objectName: objectName, + currentUrl: document.location.href, + objectDescription: objectDescription, + }; + Axios({ + method: "get", + url: M.cfg.wwwroot + "/local/notemyprogress/ajax.php", + params: data, + }) + .then((response) => { + if (response.status == 200 && response.data.ok) { + } + }) + .catch((e) => {}); + }, + }, + }); + } + + function add_collapsabled_property_to_weeks(content) { + for (let i = 0; i < content.weeks.length; i++) { + let week = content.weeks[i]; + if (typeof week.collapsabled == "undefined") { + week.collapsabled = false; + } + } + return content; + } + + return { + init: init, + }; +}); diff --git a/notemyprogress/amd/build/setweeks.min.js b/notemyprogress/amd/build/setweeks.min.js deleted file mode 100644 index b23f6f716b3ea8261bf3928295aaaa758c36b238..0000000000000000000000000000000000000000 --- a/notemyprogress/amd/build/setweeks.min.js +++ /dev/null @@ -1 +0,0 @@ -define(["local_notemyprogress/vue","local_notemyprogress/vuetify","local_notemyprogress/axios","local_notemyprogress/sortablejs","local_notemyprogress/draggable","local_notemyprogress/datepicker","local_notemyprogress/moment","local_notemyprogress/alertify","local_notemyprogress/pageheader"],(function(Vue,Vuetify,Axios,Sortable,Draggable,Datepicker,Moment,Alertify,Pageheader){"use strict";function init(content){content=add_collapsabled_property_to_weeks(content),Vue.use(Vuetify),Vue.component("draggable",Draggable),Vue.component("datepicker",Datepicker),Vue.component("pageheader",Pageheader);const app=new Vue({delimiters:["[[","]]"],el:"#setweeks",vuetify:new Vuetify,data:{display_settings:!1,settings:content.settings,new_group:!1,scroll_mode:!1,weeks_started_at:new Date(Moment(1e3*Number(content.weeks[0].weekstart))),strings:content.strings,sections:content.sections,courseid:content.courseid,userid:content.userid,raw_weeks:content.weeks,disabled_dates:{days:[0,2,3,4,5,6]},saving_loader:!1,error_messages:[],save_successful:!1},mounted(){document.querySelector("#setweeks-loader").style.display="none",document.querySelector("#setweeks").style.display="block"},computed:{weeks(){for(let i=0;i<this.raw_weeks.length;i++){let week=this.raw_weeks[i];if(0==i){let start_weeks=this.weeks_started_at;week.weekstart=start_weeks,week.weekend=this.get_end_week(this.weeks_started_at)}else week.weekstart=this.get_start_week(this.raw_weeks[i-1].weekend),week.weekend=this.get_end_week(week.weekstart)}return this.raw_weeks}},methods:{section_name(section){let name=null;return name=void 0!==section.section_name&§ion.section_name.length>0?section.section_name:section.name,name},section_exist(section){let exist=!0;return void 0!==section&&void 0!==section.exists&&0==section.exists&&(exist=!1),exist},format_name:(name,position)=>name+" "+(position+1),customFormatter(date){let weeks_start_at;return Moment(date).format("YYYY-MM-DD")},add_week(){this.raw_weeks.push({name:this.strings.week,position:this.weeks.length+1,weekstart:null,weekend:null,collapsabled:!1,hours_dedications:0,removable:!0,sections:[]})},has_items:array=>array.length>0,remove_week(week,index){if(0==index)return null;this.close_delete_confirm();for(let i=0;i<week.sections.length;i++)this.sections.push(week.sections[i]);let element_index=this.raw_weeks.indexOf(week);this.raw_weeks.splice(element_index,1)},ask_delete_confirm(){this.delete_confirm=!0},close_delete_confirm(){this.delete_confirm=!1},get_start_week(pass_week){let start_date;return Moment(Moment(pass_week).add(1,"days")).format("YYYY-MM-DD")},get_end_week(start_week){let end_date;return Moment(Moment(start_week).add(6,"days")).format("YYYY-MM-DD")},get_date_next_day(requested_day,date,output_format=null){requested_day=requested_day.toLowerCase();let current_day=Moment(date).format("dddd").toLowerCase();for(;current_day!=requested_day;)date=Moment(date).add(1,"days"),current_day=Moment(date).format("dddd").toLowerCase();return output_format?date=date.format(output_format):"number"!=typeof date&&(date=parseInt(date.format("x"))),date},position:index=>`${++index} - `,save_changes(){return this.save_successful=!1,this.error_messages=[],this.empty_weeks()?(this.saving_loader=!1,Alertify.error(this.strings.error_empty_week),this.error_messages.push(this.strings.error_empty_week),!1):this.weeks_deleted_from_course()?(this.saving_loader=!1,this.error_messages.push(this.strings.error_section_removed),!1):void Alertify.confirm(this.strings.save_warning_content,()=>{this.saving_loader=!0;var weeks=this.weeks;weeks[0].weekstart=Moment(weeks[0].weekstart).format("YYYY-MM-DD");var data={action:"saveconfigweek",userid:this.userid,courseid:this.courseid,newinstance:this.new_group,weeks:this.minify_query(weeks)};Axios({method:"get",url:M.cfg.wwwroot+"/local/notemyprogress/ajax.php",params:data}).then(response=>{console.log("then1"),200==response.status&&response.data.ok?(console.log("then1.2"),this.settings=response.data.data.settings,console.log("then1.3"),Alertify.success(this.strings.save_successful),console.log("then1.4"),this.save_successful=!0,console.log("then1.5")):(console.log("then1.6"),Alertify.error(this.strings.error_network),console.log("then1.7"),this.error_messages.push(this.strings.error_network),console.log("then1.8"))}).catch(e=>{console.log("catch1"),Alertify.error(this.strings.error_network),console.log("catch2"),this.error_messages.push(this.strings.error_network),console.log("catch3")}).finally(()=>{console.log("finally1"),this.saving_loader=!1,console.log("finally2")})},()=>{this.saving_loader=!1,Alertify.warning(this.strings.cancel_action)}).set({title:this.strings.save_warning_title}).set({labels:{cancel:this.strings.confirm_cancel,ok:this.strings.confirm_ok}})},minify_query(weeks){var minify=[];return weeks.forEach(week=>{var wk=new Object;wk.h=week.hours_dedications,wk.s=week.weekstart,wk.e=week.weekend,wk.sections=[],week.sections.forEach(section=>{var s=new Object;s.sid=section.sectionid,wk.sections.push(s)}),minify.push(wk)}),JSON.stringify(minify)},empty_weeks(){if(this.weeks.length>=2&&this.weeks[0].sections.length<1)return!0;for(let i=0;i<this.weeks.length;i++)if(i>0&&this.weeks[i].sections.length<=0)return!0;return!1},weeks_deleted_from_course(){for(var week_position=0;week_position<this.weeks.length;week_position++)for(var section_position=0;section_position<this.weeks[week_position].sections.length;section_position++)if(!this.section_exist(this.weeks[week_position].sections[section_position]))return!0;return!1},exists_mistakes(){let exists_mistakes;return this.error_messages.length>0},change_collapsabled(index){this.raw_weeks[index].collapsabled=!this.raw_weeks[index].collapsabled},course_finished(){let finished=!1,last=this.weeks.length-1,end=Moment(this.weeks[last].weekend).format("X"),now;return finished=Moment().format("X")>end,finished},get_settings_status(){let visible=!0,status;return Object.keys(this.settings).map(key=>{this.settings[key]||(visible=!1)}),visible?this.strings.plugin_visible:this.strings.plugin_hidden},get_help_content(){var help_contents=[],help=new Object;return help.title=this.strings.title,help.description=this.strings.description,help_contents.push(help),help_contents},addLogsIntoDB(action,objectName,objectType,objectDescription){let data={courseid:content.courseid,userid:content.userid,action:"addLogs",sectionname:"CONFIGURATION_COURSE_WEEK",actiontype:action,objectType:objectType,objectName:objectName,currentUrl:document.location.href,objectDescription:objectDescription};Axios({method:"get",url:M.cfg.wwwroot+"/local/notemyprogress/ajax.php",params:data}).then(response=>{200==response.status&&response.data.ok}).catch(e=>{})}}})}function add_collapsabled_property_to_weeks(content){for(let i=0;i<content.weeks.length;i++){let week=content.weeks[i];void 0===week.collapsabled&&(week.collapsabled=!1)}return content}return{init:init}})); \ No newline at end of file diff --git a/notemyprogress/classes/student.php b/notemyprogress/classes/student.php index a478ead95a2578e145f4d1691dd6327701ce5d3a..b842328a8cffbd6ef1bd21a43557b7edeb9ed466 100644 --- a/notemyprogress/classes/student.php +++ b/notemyprogress/classes/student.php @@ -211,7 +211,7 @@ class student extends report if (!empty($weekcode)) { $week = self::find_week($weekcode); } - //Framboise + $work_sessions = self::get_work_sessions($week->weekstart, $week->weekend); $sessions = array_map(function ($user_sessions) { return $user_sessions->sessions; diff --git a/notemyprogress/lang/en/local_notemyprogress.php b/notemyprogress/lang/en/local_notemyprogress.php index 1a0280975be5340474e8be5a6e5b8f8c7f7f0e5e..2881fe65ae2783b3fe1372546e468f6920f375e1 100644 --- a/notemyprogress/lang/en/local_notemyprogress.php +++ b/notemyprogress/lang/en/local_notemyprogress.php @@ -1099,13 +1099,13 @@ $string['group_allstudent'] = "All students"; $string['nmp_use_navbar_menu'] = 'Activate new menu'; $string['nmp_use_navbar_menu_desc'] = 'Display the plugin menu in the top navigation bar, otherwise it will include the menu in the navigation block.'; /* Gamification */ -$string['tg_tab1']="Chart"; -$string['tg_tab2']="Ranking"; -$string['tg_tab3']="My profile"; -$string['EnableGame']="Disable/Enable :"; -$string['studentRanking1']="Do you want to appear in the Ranking Board ?"; -$string['studentRanking2']=" /!\ All the registered in this course having accepted you will see. Accepting is final:"; -$string['studentRanking3']="Accept"; +$string['tg_tab1'] = "Chart"; +$string['tg_tab2'] = "Ranking"; +$string['tg_tab3'] = "My profile"; +$string['EnableGame'] = "Disable/Enable :"; +$string['studentRanking1'] = "Do you want to appear in the Ranking Board ?"; +$string['studentRanking2'] = " /!\ All the registered in this course having accepted you will see. Accepting is final:"; +$string['studentRanking3'] = "Accept"; $string['gamification_denied'] = 'gamification has been desactivated by your teacher'; $string['tg_colon'] = '{$a->a}: {$a->b}'; $string['tg_section_title'] = 'Course gamification settings'; @@ -1147,8 +1147,8 @@ $string['tg_section_settings_rules'] = 'Rules setting'; $string['tg_section_settings_add_rule'] = 'Add new rule'; $string['tg_section_settings_earn'] = 'For this event win:'; $string['tg_section_settings_select_event'] = 'Select an event'; -$string['gm_Chart_Title']='Spreading chart'; -$string['gm_Chart_Y']='Students'; +$string['gm_Chart_Title'] = 'Spreading chart'; +$string['gm_Chart_Y'] = 'Students'; $string['tg_timenow'] = 'Just now'; $string['tg_timeseconds'] = '{$a}s ago'; @@ -1168,9 +1168,7 @@ $string['nmp_api_invalid_transaction'] = 'The request is incorrect'; $string['nmp_api_invalid_profile'] = 'You cannot do this action, your profile is incorrect'; $string['nmp_api_save_successful'] = 'The data has been successfully saved on the server'; $string['nmp_api_cancel_action'] = 'You have canceled the action'; -$string['overview']='Overview : '; -$string['game_point_error']='Points are required'; -$string['game_event_error']='Event is required'; -$string['game_name_error']='Name required'; - - +$string['overview'] = 'Overview : '; +$string['game_point_error'] = 'Points are required'; +$string['game_event_error'] = 'Event is required'; +$string['game_name_error'] = 'Name required'; diff --git a/notemyprogress/student.php b/notemyprogress/student.php index 74ec10486c4e3ae37420549f714b18ccd722ea08..34b5ad16035913fef843d4ba162b22aa1bf6b7b1 100644 --- a/notemyprogress/student.php +++ b/notemyprogress/student.php @@ -85,7 +85,6 @@ $content = [ "helplabel" => get_string("helplabel", "local_notemyprogress"), "exitbutton" => get_string("exitbutton", "local_notemyprogress"), "about" => get_string("nmp_about", "local_notemyprogress"), - "weeks" => array( get_string("nmp_week1", "local_notemyprogress"), get_string("nmp_week2", "local_notemyprogress"), @@ -94,7 +93,6 @@ $content = [ get_string("nmp_week5", "local_notemyprogress"), get_string("nmp_week6", "local_notemyprogress"), ), - "modules_strings" => array( "title" => get_string("nmp_modules_access_chart_title","local_notemyprogress"), "modules_no_viewed" => get_string("nmp_modules_no_viewed","local_notemyprogress"), diff --git a/notemyprogress/styles.css b/notemyprogress/styles.css index f0f7e48a1d6fa6e5d645568c407d1c0005c518f8..921d06b22ff1d69191b2f2b815c764b9c7ef5d4c 100644 --- a/notemyprogress/styles.css +++ b/notemyprogress/styles.css @@ -37,6 +37,9 @@ font-weight: bold; } +.btn-save { + margin: 10px; +} diff --git a/notemyprogress/templates/setweeks.mustache b/notemyprogress/templates/setweeks.mustache index c496251ca3002ffe1b72f09495afa8e6639ec637..ba32f2892403d2b7e5836b963a80b1fd6a3c7168 100644 --- a/notemyprogress/templates/setweeks.mustache +++ b/notemyprogress/templates/setweeks.mustache @@ -72,9 +72,9 @@ <v-layout mb-3 mt-3> - <v-flex row justify-space-around align-center> + <v-flex row justify-space-around align-center > <strong v-text="strings.start"></strong> - <datepicker + <datepicker @input = "form_changed()" v-if="!week.removable" v-model="weeks_started_at" :use-utc="false" @@ -93,13 +93,14 @@ <v-layout justify-space-between align-center class="hours-expected-selector"> <span v-text="strings.time_dedication" class="pr-5"></span> - <v-text-field type="number" min="0" max="99" outlined v-model="raw_weeks[index].hours_dedications"></v-text-field> + <v-text-field @input = "form_changed()" type="number" min="0" max="99" outlined v-model="raw_weeks[index].hours_dedications"></v-text-field> </v-layout> </template> </v-flex> </v-layout> <draggable + @change = "form_changed()" v-if="!week.collapsabled" class="set-weeks-list" tag="ul" @@ -147,8 +148,9 @@ <v-alert v-for="(message, index, key) in error_messages" :key="key" dense outlined type="error" v-text="message" class="mb-2"></v-alert> </v-row> - <v-row class="justify-center"> - <v-btn @click="save_changes()" v-text="strings.save" class="white--text" color="#118AB2"></v-btn> + <v-row class="justify-center btn-save" > + <v-btn :disabled="form_identical" @click="cache_cancel()" v-text="strings.confirm_cancel" class="white--text" color="#118AB2"></v-btn> + <v-btn :disabled="form_identical" @click="save_changes()" v-text="strings.save" class="white--text" color="#118AB2"></v-btn> <v-progress-linear v-if="saving_loader" indeterminate color="cyan" ></v-progress-linear> </v-row>