var PROD = 1 if (PROD && location.protocol !== 'https:') { location.replace(`https:${location.href.substring(location.protocol.length)}`); } // _ _ // | | | | // | |__ ___| |_ __ ___ _ __ ___ // | '_ \ / _ \ | '_ \ / _ \ '__/ __| // | | | | __/ | |_) | __/ | \__ \ // |_| |_|\___|_| .__/ \___|_| |___/ // | | // |_| function init_file_dropzone(parameter_name) { Dropzone.options.myGreatDropzone = { // camelized version of the `id` paramName: parameter_name, // The name that will be used to transfer the file maxFilesize: 6 }; // MB } // init_file_dropzone("staffpicupload") function parsesqltime(mysqldate) { // 2021-01-29 09:00:00 if (! mysqldate) { return 0 } var field = mysqldate.match(/^(\d\d\d\d)\-(\d+)\-(\d+)[T|\s](\d+)\:(\d+)\:(\d+)$/) var mydate = new Date(field[1], field[2] - 1 , field[3], field[4], field[5], field[6]) return mydate } function dj(mysqldate) { return dayjs(parsesqltime(mysqldate)) } Object.defineProperty(Vue.prototype, '$dj', { value: dj }); Object.defineProperty(Vue.prototype, '$parsesqltime', { value: parsesqltime }); // Insert zero-width spaces into long unbroken tokens so they wrap nicely. function wrapLongTokens(str, n=30) { if (str === null || str === undefined) return '' const s = String(str) return s.split(/(\s+)/).map(tok => { if (!tok || /\s/.test(tok) || tok.length < n) return tok return tok.replace(new RegExp('(.{'+n+'})', 'g'), '$1\u200B') }).join('') } Object.defineProperty(Vue.prototype, '$wrap', { value: wrapLongTokens }); // ________ __ // | ____\ \ / / // | |__ \ V / // | __| > < // | | / . \ // |_| /_/ \_\ // // // // VISUAL EFFECTS // function fade_message(theselector='#alert') { var a_dom = document.querySelector(theselector); TinyAnimate.animateCSS(a_dom, 'opacity', '', 1.0, 0.0, 750, 'easeInOutQuart', function() { }); } function alert_message(msg,color='yellow') { var a = $('#alert') a.text(msg) a.removeClass('hidden'); a.css('background-color',color) var a_dom = document.querySelector('#alert'); TinyAnimate.animateCSS(a_dom, 'opacity', '', 0.0, 1.0, 500, 'easeInOutQuart', function() { setTimeout( fade_message, 2500 ) }); } function fadein_message(theclass='success') { //var a = $('#'+theclass) //a.css('visibility','visible') var a_dom = document.querySelector('.'+theclass); TinyAnimate.animateCSS(a_dom, 'opacity', '', 0.0, 1.0, 500, 'easeInOutQuart', function() { setTimeout( function() { fade_message('.'+theclass) }, 2500 ) }); } // _ // | | // ___ ___ _ __ ___ _ __ ___ _ __ ___ _ __ | |_ ___ // / __/ _ \| '_ ` _ \| '_ \ / _ \| '_ \ / _ \ '_ \| __/ __| // | (_| (_) | | | | | | |_) | (_) | | | | __/ | | | |_\__ \ // \___\___/|_| |_| |_| .__/ \___/|_| |_|\___|_| |_|\__|___/ // | | // |_| // // FORM COMPONENTS // // TODO these should know if they're modifying the current user or someone else. // A single text style question const TQuestion = Vue.component('field', { props: [ 'table', 'qid', 'question', 'answer', 'placeholder', 'targetid', ], data: function () { return { "a":this.answer } }, watch: { "answer": function(val,Oldval) { this.a = val }, "a": function (val, oldVal) { this.$root.events.trigger('changed',[this.qid,this.table, val, this.targetid]) }, }, mounted() { }, template: `
`, }) // A single INLINE text style question const TIQuestion = Vue.component('ifield', { props: [ 'table', 'qid', 'question', 'answer', 'placeholder', 'myclass', 'targetid', ], data: function () { return { "a":this.answer } }, watch: { "answer": function(val,Oldval) { this.a = val }, "a": function (val, oldVal) { this.$root.events.trigger('changed',[this.qid,this.table, val, this.targetid]) }, }, template: ``, }) // A single checkbox const Checkbox = Vue.component('checkbox', { props: [ 'table', 'qid', 'question', 'answer', 'targetid', ], data: function () { return { "a":this.answer } }, watch: { "answer": function(val,Oldval) { this.a = val }, "a": function (val, oldVal) { var newVal = 0 if (val==true) { newVal = 1 } this.$root.events.trigger('changed',[this.qid,this.table, newVal, this.targetid]) }, }, template: `
` }) // A single long format text question const TAQuestion = Vue.component('tfield', { props: [ 'table', 'qid', 'question', 'answer', 'targetid', 'myclass' ], data: function () { return { "a": this.answer } }, watch: { "a": function (val, oldVal) { this.$root.events.trigger('changed',[this.qid,this.table, val, this.targetid]) }, "answer": function (val, oldVal) { this.a = val }, }, template: `
` }) // long format text WYSIWYG HTML const HTAQuestion = Vue.component('htfield', { props: [ 'table', 'qid', 'question', 'answer', 'targetid', 'myclass' ], data: function () { return { "a": this.answer, "p":0, } }, // the pell editor object mounted: function() { var self = this var element = document.getElementById(self.qid) this.p = pell.init( { element: element, onChange: function(h) { self.$root.events.trigger('changed',[self.qid, self.table, h, self.targetid]) } } ) self.p.content.innerHTML = self.answer }, watch: { "answer": function (val, oldVal) { this.p.content.innerHTML = val }, }, template: `
` }) // long format text WYSIWYG HTML FORM ALIGNED STYLE const HTAQuestionFA = Vue.component('htfield_fa', { props: [ 'table', 'qid', 'question', 'answer', 'targetid', 'myclass' ], data: function () { return { "a": this.answer, "p":0, } }, // the pell editor object mounted: function() { var self = this var element = document.getElementById(self.qid) this.p = pell.init( { element: element, onChange: function(h) { self.$root.events.trigger('changed',[self.qid, self.table, h, self.targetid]) } } ) self.p.content.innerHTML = self.answer }, watch: { "answer": function (val, oldVal) { this.p.content.innerHTML = val }, }, template: `
` }) // A single numeric question const NQuestioon = Vue.component('n-question', { props: [ 'qq' ], data: function () { return { "answer": this.qq.answer } }, watch: { "answer": function (val, oldVal) { this.$root.events.trigger('update_survey', [this.qq.user, this.qq.session, this.qq.qid, val]) }, }, template: `
{{ qq.question }}
                                                           
` }) // A single survey text question const STQuestion = Vue.component('st-question', { props: [ 'qq' ], data: function () { return { "answer": this.qq.answer } }, watch: { "answer": function (val, oldVal) { this.$emit('dirty') this.$root.events.trigger('update_survey', [this.qq.user, this.qq.session, this.qq.qid, val]); }, }, template: `
`, }) // The "I Certify" question const ICertify = Vue.component('icertify', { props: [ 'c' ], data: function () { return { "checked": this.c } }, watch: { checked() { this.$emit('dirty') this.$parent.docert(this.checked); }, }, template: `

` }); // Select menu /* table: name of database table qid: what column of the table to edit question: label displayed to user answer: data item used for v-model menu: key of root vue object with menu items labelfield: key of 'menu' object that should be displayed as choices targetid: when editing existing row: the id of the row */ const SelMenu = Vue.component('selectmenu', { props: [ 'table', 'qid', 'question', 'answer', 'menu', 'labelfield', 'targetid', ], data: function () { return { "a": this.answer } }, watch: { "a": function (val, oldVal) { this.$root.events.trigger('changed',[this.qid,this.table, val, this.targetid]) }, "answer": function (val, oldVal) { this.a = val }, }, template: `
` }) // Select menu FORM ALIGNED STYLE const SelMenuFA = Vue.component('selectmenu_fa', { props: [ 'table', 'qid', 'question', 'answer', 'menu', 'labelfield', 'targetid', ], data: function () { return { "a": this.answer } }, watch: { "a": function (val, oldVal) { this.$root.events.trigger('changed',[this.qid,this.table, val, this.targetid]) }, "answer": function (val, oldVal) { this.a = val }, }, template: `
` }) // A date time picker const DTPicker = Vue.component('dtpicker', { props: [ 'table', 'qid', 'question', 'answer', 'targetid', ], methods: { }, mounted: function() { }, data: function () { return { "a":this.answer } }, watch: { "answer": function(val,Oldval) { this.a = val }, "a": function (val, oldVal) { this.$root.events.trigger('changed',[this.qid,this.table, val, this.targetid]) }, }, template: `
`, }) // Accordion style box const ExpandyBox = Vue.component('expandybox', { props: [ 'header', 'body', ], methods: { toggle: function() { if (this.state=='close') { this.state='open'; this.symbol='-' } else { this.state='close'; this.symbol='+' } } }, mounted: function() { }, data: function () { return { "state":'close', "symbol":"+", } }, watch: { "answer": function(val,Oldval) { this.a = val }, "a": function (val, oldVal) { this.$root.events.trigger('changed',[this.qid,this.table, val, this.targetid]) }, }, template: `
`, }) // mode pill Vue.component('ModePill', { props: ['mode'], computed: { label() { switch (this.mode) { case 'inperson': return 'In Person'; case 'online': return 'Online'; case 'hybrid': return 'Hyflex'; default: return this.mode; } }, bgClass() { switch (this.mode) { case 'inperson': return 'bg-green-500'; case 'online': return 'bg-blue-500'; case 'hybrid': return 'bg-yellow-500 text-black'; default: return 'bg-gray-400'; } } }, template: ` {{ label }} ` }); // _ __ __ _____ _____ _____ // | | / _|/ _| | __ \_ _| __ \ // ___| |_ __ _| |_| |_ | | | || | | |__) | // / __| __/ _` | _| _| | | | || | | _ / // \__ \ || (_| | | | | | |__| || |_| | \ \ // |___/\__\__,_|_| |_| |_____/_____|_| \_\ // // // // ONE LINE OF THE STAFF DIR EDITOR listing // const StaffLine = Vue.component('staff_line', { props: [ 's', 'i', 'dup_class', ], //data: function() { return { } }, methods: { nope: function() { return 1; }, odd: function(i) { if (i % 2 ==0) { return "even" } return "odd" }, bio: function() { return "bio.php?p=" + this.s.id }, gtitle: function() { if (this.s.gtitle) {return _.findWhere(this.$root.titles_menu, {id:this.s.gtitle}).name}; return "" }, gdept1: function() { if (this.s.dept1) {return _.findWhere(this.$root.depts_menu, {id:this.s.dept1}).name}; return "" }, gdept2: function() { if (this.s.dept2) {return _.findWhere(this.$root.depts_menu, {id:this.s.dept2}).name}; return "-" }, grole: function() { if (this.s.role) {return _.findWhere(this.$root.roles_menu, {id:this.s.role}).descr}; return "" }, swapedit: function() { this.$emit('swapout', this.s.id) } }, template: `
status: {{ s.status }}
permissions: {{ grole() }}
pers id: {{ s.id }}
ext id: {{ s.ext_id }}
bio page: {{ s.web_on }}
Photo - use:  {{s.use_dir_photo}}  release: {{s.general_photo_release}}
        path: {{s.dir_photo_path}}
# Sections: {{s.num_taught}}
{{ s.sections }}      
staff: {{ s.staff_type }}
conf_id: {{ s.conf_id }}
G00{{s.conf_goo}}
espanol: {{s.espanol}}
  
{{s.first_name}} {{s.last_name}}
{{gtitle()}}
{{gdept1()}}  
{{gdept2()}}
(old dept: {{s.department}} )  
email: {{s.email}}
room:  {{s.room}}
phone: {{s.phone_number}}
Zoom: {{s.zoom}}
Preferred contact: {{s.preferred_contact}}  
edit
` }); // // // STAFF DIR LISTING // // ONE LINE - BUT EDITING! // const StaffLineEdit = Vue.component('staff_line_edit', { props: [ 's', 'i' ], //data: function() { return { } }, methods: { nope: function() { return 1; }, odd: function(i) { if (i % 2 ==0) { return "even" } return "odd" }, bio: function() { return "bio.php?p=" + this.s.id }, done: function() { this.$emit('done_edit') }, gtitle: function() { if (this.s.gtitle) {return _.findWhere(this.$root.titles_menu, {id:this.s.gtitle}).name}; return "" }, gdept1: function() { if (this.s.dept1) {return _.findWhere(this.$root.depts_menu, {id:this.s.dept1}).name}; return "" }, gdept2: function() { if (this.s.dept2) {return _.findWhere(this.$root.depts_menu, {id:this.s.dept2}).name}; return "-" }, grole: function() { if (this.s.role) {return _.findWhere(this.$root.roles_menu, {id:this.s.role}).descr}; return "" }, }, //v-lazy-container="{ selector: 'img' }" template: `
permissions:
pers id: {{ s.id }}
ext id: {{ s.ext_id }}
Dir photo
# Sections: {{s.num_taught}}
{{ s.sections }}
Staff Type
conf_id: {{ s.conf_id }}
GOO {{ s.conf_goo }}





(old dept: {{s.department}} )




done
` }); // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // STAFF DIR LISTING MAIN CONTAINER // const DirList = Vue.component('dirlist', { data: function () { return { "personnel":[], sortby:'last_name', search:'', reversed: false, id_list:[], id_dups:[], components: {n:StaffLine, e: StaffLineEdit }, editing: -1, } }, mounted: function() { var self = this; this.$root.fetch_menus(); fetch('dir_api.php?a=list/staffsemester', { method: 'GET' }).then(function (response) { if (response.ok) { response.json().then( function(r2) { self.personnel = r2; _.each( self.personnel, function(x) { if (x.sections==null) { x.num_taught=0 } if (x.dir_photo_path==null) { x.dir_photo_path='images_sm/nobody.jpg' } if (x.use_dir_photo == "0") { x.use_dir_photo = false } else { x.use_dir_photo = true } if (x.espanol == "0" || !x.espanol) { x.espanol = false } else { x.espanol = true } if (x.general_photo_release == "0") { x.general_photo_release = false } else { x.general_photo_release = true } if (self.id_list.includes(x.id)) { self.id_dups.push(x.id) } else { self.id_list.push(x.id) } x.searchable = '' if (x.first_name) { x.searchable += ' ' + x.first_name.toLowerCase() } if (x.last_name) { x.searchable += ' ' + x.last_name.toLowerCase() } if (x.dept1name) { x.searchable += ' ' + x.dept1name.toLowerCase() } if (x.titlename) { x.searchable += ' ' + x.titlename.toLowerCase() } if (x.status == "1" || x.status == null) { x.status = "1" } else { x.status = false; x.searchable += ' inactive' } } ) } ) } else { return Promise.reject(response) } }).then(function (data) { }).catch(function (err) { console.warn('Something went wrong.', err); }); }, methods: { setsort: function(ss) { if (this.sortby == ss) { this.reversed = ! this.reversed } else { this.reversed = false this.sortby = ss } }, swapme: function(x) { this.editing = x }, done_edit: function(id) { this.editing = -1 }, am_editing: function(id) { if (id == this.editing) { return StaffLineEdit } return StaffLine }, is_dup_id_class: function(id) { return this.id_dups.includes(id) ? " dup_line" : "" }, }, computed: { filtered: function() { var ff = this.personnel var self = this if (this.search) { var ss = self.search.toLowerCase() ff = ff.filter(function(x) { return x.searchable.includes(ss) }) } ff = _.sortBy(ff, function(x) { if (x[self.sortby]) { var s = x[self.sortby]; return s.trim().toLowerCase() } return 'zzzzzzzzzz' }) if (this.reversed) { ff.reverse() } return ff } }, watch: { }, template: `
Filter:

 
C
Name
Title
Dept
Dept1
Email
Phone
Room
` }) // https://www.daterangepicker.com/ // https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/datetime-local // __ _ _ _ _ _ _ // / _| | | | (_) (_) | (_) // | |_| | _____ __ __ _ ___| |_ ___ ___| |_ _ ___ ___ // | _| |/ _ \ \/ / / _` |/ __| __| \ \ / / | __| |/ _ \/ __| // | | | | __/> < | (_| | (__| |_| |\ V /| | |_| | __/\__ \ // |_| |_|\___/_/\_\ \__,_|\___|\__|_| \_/ |_|\__|_|\___||___/ // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // EDIT / CREATE activity or event // const ActivityEditor = Vue.component('activityedit', { props: [ 'which', ], data: function () { return { my_activity:0, creating:0, everyname:[], everyone:[], hosts_by_sesid:{}, this_hosts:[], host_search:[], host_search_str:'', activities:[], this_activity:{}, editing: -1, active:-1, } }, mounted: function() { this.fetch_mypeople() this.$root.fetch_menus() if (this.which==0) { this.creating = 1 this.$root.creating = 1 this.this_activity = {"title":"","desc":"","length":"1","starttime":"", "track":"","location":"","gets_survey":"1","category":"1", "parent":"","recording":"","instructions":"","image_url":"", "is_flex_approved":"1","typeId":"101"} } else { this.my_activity = this.which this.fetch_myevents() } }, methods: { set_id: function(new_id) { this.this_activity.id = new_id; this.creating=0; this.$root.creating=0 }, month_year: function(d) { var b = this.$root.$dj(d).format('MMM D YYYY'); return b }, setsort: function(ss) { if (this.sortby == ss) { this.reversed = ! this.reversed } else { this.reversed = false; this.sortby = ss } }, swapme: function(x) { this.editing = x }, done_edit: function(id) { this.editing = -1 }, am_editing: function(id) { return 0 }, remove: function(hostid) { var self = this basic_get('dir_api.php?a=remove/host/' + this.my_activity + '/' + hostid, function(r2) { self.this_hosts = _.reject(self.this_hosts, function(x) { return x.hostid == hostid} ) alert_message('Saved') } ) }, add: function(hostid) { var self = this basic_get('dir_api.php?a=add/host/' + this.my_activity + '/' + hostid, function(r2) { self.this_hosts.push( _.findWhere(self.everyone, {id:hostid} ) ) alert_message('Saved') } ) }, hostlookup: function() { var self = this if (this.host_search_str=='') { this.host_search=[] } else { this.host_search_str = this.host_search_str.toLowerCase() this.host_search = _.first(_.filter(self.everyone, function(x) { return x.name.toLowerCase().search( self.host_search_str) != -1 }),7) } }, save_new_event: function() { this.$root.events.trigger('create_new_session',this.this_activity) }, fetch_mypeople: function() { var self = this; basic_get('dir_api.php?a=get/names', function(r2) { self.everyone = r2.users _.each( self.everyone, function(x) { x.hostid = x.hostid } ) self.everyname = _.pluck(r2.users,'name') } ) }, fetch_myevents: function() { var self = this; basic_get('dir_api.php?a=get/sessions', function(r2) { self.activities = _.indexBy(r2,function(x) { return x.id } ); self.$forceUpdate() self.active += 1; _.each( self.activities, function(x) { x.searchable = x.title.toLowerCase() + ' ' + x.desc.toLowerCase() } ) self.this_activity = self.activities[ self.my_activity ] self.this_activity.starttime = self.this_activity.starttime.replace(' ','T')} ) basic_get('dir_api.php?a=get/hosts', function(r2) { var filtered = r2.filter(x => x.hostid != null) self.hosts_by_sesid = _.groupBy(filtered,function(x) { return x.id } ) self.this_hosts = self.hosts_by_sesid[ self.my_activity ] } ) }, }, template: `

type below to add a host
{{ h.name }}
{{ ho.name }}
` }) // // // // // // // ACTIVITIES LIST MAIN CONTAINER // ------------------------------ // // // const ActivityList = Vue.component('activitylist', { props: [ 'itineraryview','static' ], data: function () { return { activities:[], mysessions:[], search:'', sortby:'starttime', reversed:false, my_ses_ids:[], my_host_ids:[], show_filters: 'all', expanded: 1, editing: -1, active:-1, hosts:{}, hosts_by_sesid: {}, options:{}, conference:{}, ay:{}, conf:-1, survey_on:0, zoom_on:1, } }, mounted: function() { this.fetch_myevents() }, methods: { special_signup: function(activity_id) { if (activity_id==1462 || activity_id==1455) { return true; } return false; }, get_day_index: function(dateStr) { const date = new Date(dateStr); const yyyy = date.getFullYear(); const mm = String(date.getMonth() + 1).padStart(2, '0'); const dd = String(date.getDate()).padStart(2, '0'); return `${yyyy}${mm}${dd}`; }, toggleDay: function(id) { const group = document.getElementById(id); const btn = document.getElementById(id + '-toggle'); group.classList.toggle('hidden'); btn.textContent = group.classList.contains('hidden') ? 'Expand Day' : 'Collapse Day'; }, toggleDescription: function(id) { const para = document.getElementById(id); const btn = document.getElementById(id + '-btn'); para.classList.toggle('line-clamp-2'); btn.textContent = para.classList.contains('line-clamp-2') ? 'Show More' : 'Show Less'; }, addTime: function(time, x) { x = parseInt(x) let [y, m, d, h, i, s] = time.split(/[- :]/); // split time into parts let dt = new Date(y, m-1, d, h, i, s); // create Date object x > 8 ? dt.setMinutes(dt.getMinutes() + x) : dt.setHours(dt.getHours() + x); // add minutes or hours var rd = dayjs(dt); return rd.format('h:mma'); //return dt.toISOString().slice(0, 19).replace('T', ' '); // format output }, hoststr: function(id) { var self = this return _.reduce( self.hosts_by_sesid[id], function(mem,val) { if (val.name) { return mem + val.name + ", " } return mem }, '') }, mode_string: function(a) { if (this.$root.active) { return _.findWhere(this.$root.modes_menu, { 'id': a.mode })['string'] } return a.mode }, get_day_title: function(day) { var d = dayjs(day, 'MMM DD YYYY') convertedDateString = d.format('YYYY-MM-DD') return _.findWhere( this.conference, {date1:convertedDateString} ).title }, month_year: function(d) { var b = this.$root.$dj(d).format('MMM D YYYY'); return b }, setsort: function(ss) { if (this.sortby == ss) { this.reversed = ! this.reversed } else { this.reversed = false; this.sortby = ss } }, swapme: function(x) { this.editing = x }, done_edit: function(id) { this.editing = -1 }, am_editing: function(id) { return 0 }, fetch_myevents: function() { var self = this; basic_get('dir_api.php?a=get/hosts', function(r2) { self.hosts_by_sesid = _.groupBy(r2,function(x) { return x.id } ) } ) basic_get('api2.php?query=app', function(r2) { self.mysessions = r2.mysessions self.my_ses_ids = _.pluck(r2.mysessions, 'id') self.activities = r2.sessions if (r2.host != null) { self.my_host_ids = r2.host } else { self.my_host_ids = [] } self.options = r2.options self.conference = r2.conference self.ay = r2.ay self.hosts_by_sesid = _.groupBy(r2.hostbysession,function(x) { return x.id } ) self.survey_on = parseInt( _.findWhere(self.options, { label:'survey_on' }).value ) self.zoom_on = parseInt( _.findWhere(self.options, { label:'zoom_on' }).value ) self.active = 1 self.$forceUpdate(); } ) }, joinme: function(id) { var self = this basic_get('dir_api.php?a=signup/' + id, function(r2) { self.mysessions.push(_.findWhere(self.activities, {'id':id})) self.my_ses_ids.push(id) alert_message("Added activity") }) }, dumpme: function(id) { var self = this basic_get('dir_api.php?a=signdown/' + id, function(r2) { self.mysessions = _.without( self.mysessions, _.findWhere(self.activities, {'id':id})) self.my_ses_ids = _.without( self.my_ses_ids, id) self.$forceUpdate() alert_message("Removed activity") }) }, filtered: function(ff) { var self = this if (this.search) { var ss = self.search.toLowerCase() ff = ff.filter(function(x) { return ('searchable' in x ? x.searchable.includes(ss) : 0) }) } if (this.active>0) { var default_conf_id = _.findWhere(self.options, {label:'default_conference'}).value self.conf = _.findWhere(self.conference, {id:default_conf_id}) var start = dayjs( self.conf.date1) var end = dayjs(self.conf.date2) ff = ff.filter( function(item,index) { this_time = dayjs(item.starttime) return this_time.isBefore(end) && start.isBefore(this_time) } ) ff = _.sortBy(ff, function(x) { if (x[self.sortby]) { var s = x[self.sortby]; return s.trim().toLowerCase() } return '' }) if (this.reversed) { ff.reverse() } return ff } return [] }, }, computed: { current_time: function() { return dayjs().format('MMM D, h:mma') }, activities_filtered: function() { var a = this.filtered(this.activities); return a; }, mysessions_filtered: function() { return this.filtered(this.mysessions) }, activities_g_filtered: function() { if (this.active<1) { return {} } var self=this; return _.groupBy(self.activities_filtered, function(x) { return self.month_year(x.starttime) } ); }, mysessions_g_filtered: function() { if (this.active<1) { return {} } var self=this; return _.groupBy(self.mysessions_filtered, function(x) { return self.month_year(x.starttime) } ); }, }, watch: { }, template: `

Return here on the day of your sessions for Zoom links.

It looks like you haven't signed up for any sessions yet!

Go to the Sessions List to sign up.

{{ mmyy }} {{ get_day_title(mmyy) }}

{{ a.title }}

{{ $root.$dj(a.starttime).format('h:mma') }} - {{ addTime(a.starttime, a.length) }} · In person at {{ a.location_irl }} or online online · In person at {{ a.location_irl }} · Online · Online

{{ mmyy }} {{ get_day_title(mmyy) }}
Cancel Sign Up        Edit You are Host.

{{ a.title }}

{{ $root.$dj(a.starttime).format('h:mma') }} - {{ addTime(a.starttime, a.length) }} · {{ mode_string(a) }} · {{ a.location_irl }}

Presented by: {{ hoststr(a.id) }}

Location: {{ a.location_irl }}

Zoom Link: {{ a.location }}

` }) // A CHART LABEL // const ChartLabel = Vue.component('chartlabel', { props: [ 'title','start','end' ], data: function () { return { max_tracks: 6 } }, template: `
{{title}}
` }) // // // A CHART SESSION // const ChartSesh = Vue.component('chartsesh', { props: [ 'title','desc','track','start','end','start_h','end_h','mode','presenter','loc' ], data: function () { return { max_tracks: 6 } }, methods: { t_start: function() { if (this.track==0 || this.track==123) { return "track-1-start" } return "track-" + this.track + "-start" }, t_end: function() { if (this.track==0 || this.track==123) { return "track-" + this.max_tracks + "-end" } return "track-" + this.track + "-end" }, }, template: `

{{title}}

{{start_h}} - {{end_h}} {{desc}} {{presenter}} {{mode}} {{loc}}
` }) // // // // // // // ACTIVITIES LIST CHART STYLE VIEW // -------------------------------- // // // const ChartView = Vue.component('chartview', { props: [ 'day'], data: function () { return { activities:[], mysessions:[], search:'', sortby:'starttime', reversed:false, my_ses_ids:[], my_host_ids:[], show_filters: 'all', expanded: 1, editing: -1, active:-1, hosts:{}, filters: {'sp23':['2023-01-01','2023-02-02'],'fa22':['2022-08-17','2022-08-20'], 'sp22':['2022-01-26','2022-01-30'], 'all':['2022-01-01','2023-11-30'] }, //day_titles: {'Aug 18 2022':" - Optional PD Day", 'Aug 19 2022':' - Convocation Day'}, day_titles: {'Jan 26 2023':" - Optional Day", 'Jan 27 2023':' - Mandatory Day'}, time_labels: [['0900','0930','9am'], ['0930','1000','9:30am'], ['1000','1030','10am'], ['1030','1100','10:30am'], ['1100','1130','11am'], ['1130','1200','11:30am'], ['1200','1230','12pm'], ['1230','1300','12:30pm'], ['1300','1330','1pm'], ['1330','1400','1:30pm'], ['1400','1430','2pm'], ['1430','1500','2:30pm'], ['1500','1530','3pm'], ['1530','1600','3:30pm'], ['1600','1630','4pm'], ['1630','1700','4:30pm'], ['1700','1730','5pm'], ['1730','1800','5:30pm'], ], hosts_by_sesid: {}, } }, mounted: function() { this.$root.fetch_menus(); this.fetch_myevents() }, methods: { hoststr: function(id) { var self = this return _.reduce( self.hosts_by_sesid[id], function(mem,val) { if (val.name) { return mem + val.name + ", " } return mem }, '') }, mode_string: function(a) { return _.findWhere(this.$root.modes_menu, { 'id': a.mode })['string'] }, get_day_title: function(day) { if (day in this.day_titles) { return this.day_titles[day] } return '' }, month_year: function(d) { var b = this.$root.$dj(d).format('MMM D YYYY'); return b }, setsort: function(ss) { if (this.sortby == ss) { this.reversed = ! this.reversed } else { this.reversed = false; this.sortby = ss } }, swapme: function(x) { this.editing = x }, done_edit: function(id) { this.editing = -1 }, am_editing: function(id) { return 0 }, fetch_myevents: function() { var self = this; basic_get('api2.php?query=ses/'+self.day, function(r2) { r2 = r2.data self.activities = _.sortBy(r2,function(x) { return x.starttime } ); self.$forceUpdate() self.active += 1; _.each( self.activities, function(x) { var field = x.starttime.match(/^(\d\d\d\d)\-(\d+)\-(\d+)\s(\d+)\:(\d+)\:(\d+)$/) var mydate = new Date(field[1], field[2] - 1 , field[3], field[4], field[5], field[6]) x.dj = dayjs(mydate) x.searchable = x.title.toLowerCase() + ' ' + x.desc.toLowerCase() } ) self.$forceUpdate(); self.active += 1; } ) basic_get('dir_api.php?a=get/hosts', function(r2) { self.hosts_by_sesid = _.groupBy(r2,function(x) { return x.id } ) } ) }, joinme: function(id) { var self = this basic_get('dir_api.php?a=signup/' + id, function(r2) { self.mysessions.push(_.findWhere(self.activities, {'id':id})) self.my_ses_ids.push(id) alert_message("Added activity") }) }, dumpme: function(id) { var self = this basic_get('dir_api.php?a=signdown/' + id, function(r2) { self.mysessions = _.without( self.mysessions, _.findWhere(self.activities, {'id':id})) self.my_ses_ids = _.without( self.my_ses_ids, id) self.$forceUpdate() alert_message("Removed activity") }) }, filtered: function(ff) { if (this.active<1) { return [] } var self = this if (this.search) { var ss = self.search.toLowerCase() ff = ff.filter(function(x) { return ('searchable' in x ? x.searchable.includes(ss) : 0) }) } if (this.focus && 'options' in this.$root.$data.user ) { var start = dayjs(this.$root.$data.user.options.conf.date1) var end = dayjs(this.$root.$data.user.options.conf.date2) ff = ff.filter( function(item,index) { this_time = dayjs(item.starttime) return this_time.isBefore(end) && start.isBefore(this_time) } ) } ff = _.sortBy(ff, function(x) { if (x[self.sortby]) { var s = x[self.sortby]; return s.trim().toLowerCase() } return '' }) if (this.reversed) { ff.reverse() } return ff }, }, computed: { current_time: function() { return dayjs().format('MMM D, h:ma') }, activities_filtered: function() { var a = this.filtered(this.activities); return a; }, mysessions_filtered: function() { return this.filtered(this.mysessions) }, activities_g_filtered: function() { if (this.active<1) { return {} } var self=this; return _.groupBy(self.activities_filtered, function(x) { return self.month_year(x.starttime) } ); }, mysessions_g_filtered: function() { if (this.active<1) { return {} } var self=this; return _.groupBy(self.mysessions_filtered, function(x) { return self.month_year(x.starttime) } ); }, }, watch: { }, template: `
` }) const basic_getP = (url) => new Promise((resolve, reject) => basic_get(url, (r) => resolve(r), (e) => reject(e)) ); const ActivityEditorList = Vue.component('activityeditorlist', { data() { return { // data activities: [], mysessions: [], hosts: {}, // all hosts keyed by conf_id users: [], // everyone (names/users) hostsBySessionId: {}, // sessionId -> [host objects] byId: {}, // sessionId -> activity // ui search: "", sortby: "starttime", reversed: false, selectedIndex: 0, editingId: null, // filters show_filters: "all", filters: { fa23: ["2023-08-21", "2023-08-26"], sp23: ["2023-01-01", "2023-02-02"], fa22: ["2022-08-17", "2022-08-20"], sp22: ["2022-01-26", "2022-01-30"], all: ["2022-01-01", "2025-12-31"], }, day_titles: { "Jan 26 2023": " - Optional Day", "Jan 27 2023": " - Mandatory Day", "Aug 24 2023": " - Optional Day", "Aug 25 2023": " - Mandatory Day", }, active: 0, lastScrollY: 0, }; }, mounted() { this.$root.fetch_menus(); this.fetchAll(); window.addEventListener("keydown", this.onKey); }, beforeDestroy() { window.removeEventListener("keydown", this.onKey); }, methods: { async fetchAll() { this.active = 0; const [ mysessions, sessions, hostsRows, allhosts, namesPayload, ] = await Promise.all([ basic_getP("dir_api.php?a=get/mysessions"), basic_getP("dir_api.php?a=get/sessions"), basic_getP("dir_api.php?a=get/hosts"), basic_getP("dir_api.php?a=get/allhosts"), basic_getP("dir_api.php?a=get/names"), ]); // normalize hosts per session const hostsBySessionId = _.groupBy(hostsRows, (x) => x.id); Object.keys(hostsBySessionId).forEach((sid) => { hostsBySessionId[sid] = hostsBySessionId[sid].filter( (x) => x.hostid != null ); }); const byId = _.indexBy(sessions, (x) => x.id); this.activities = _.sortBy(sessions, (x) => x.starttime || ""); this.mysessions = _.sortBy(mysessions, (x) => x.starttime || ""); this.hosts = allhosts; // as provided (conf_id -> [host ids]?) if that's your shape this.users = namesPayload.users || []; this.hostsBySessionId = hostsBySessionId; this.byId = byId; this.active = 1; // decorate sessions sessions.forEach((x) => { // precompute dayjs + searchable text if (x.starttime) { const m = x.starttime.match( /^(\d{4})-(\d+)-(\d+)\s(\d+):(\d+):(\d+)$/ ); if (m) { const d = new Date(m[1], m[2] - 1, m[3], m[4], m[5], m[6]); x.dj = dayjs(d); } else { x.dj = dayjs(x.starttime); } } else { x.dj = dayjs.invalid(); } x.searchable = ((x.title || "") + " " + (x.desc || "")).toLowerCase(); x.missing = this.computeMissing(x); }); }, computeMissing(a) { const miss = []; required_fields = ["title", "starttime", "mode", "desc"]; if (a.mode=='inperson' || a.mode=='hybrid') { required_fields.push('location_irl')} if (a.mode=='online' || a.mode=='hybrid') { required_fields.push('location')} required_fields.forEach((k) => { const v = a[k]; if (v === undefined || v === null || String(v).trim() === "") miss.push(k); }); // special: no hosts const int_id = parseInt(a.id) if (!this.hostsBySessionId[a.id] || this.hostsBySessionId[int_id].length === 0) { miss.push("hosts"); } return miss; }, get_day_title(day) { return this.day_titles[day] || ""; }, month_year(d) { return this.$root.$dj(d).format("MMM D YYYY"); }, setsort(ss) { if (this.sortby === ss) this.reversed = !this.reversed; else { this.reversed = false; this.sortby = ss; } }, filtered(list) { if (!this.active) return []; let ff = list; if (this.search) { const q = this.search.toLowerCase(); ff = ff.filter((x) => x.searchable.includes(q)); } // AY filter example const ay = this.$root.settings["default_ay"]; if (ay && this.$root.ay_menu && this.$root.ay_menu[ay]) { const start = dayjs(this.$root.ay_menu[ay].begin); const end = dayjs(this.$root.ay_menu[ay].end); ff = ff.filter((item) => { const t = dayjs(item.starttime); return t.isValid() && t.isBefore(end) && start.isBefore(t); }); } ff = _.sortBy(ff, (x) => { const val = x[this.sortby]; return typeof val === "string" ? val.trim().toLowerCase() : val || ""; }); if (this.reversed) ff.reverse(); return ff; }, onKey(e) { // global keyboard navigation if (!this.activitiesFiltered.length) return; const max = this.activitiesFiltered.length - 1; if (e.key === "ArrowDown") { this.selectedIndex = Math.min(max, this.selectedIndex + 1); e.preventDefault(); } else if (e.key === "ArrowUp") { this.selectedIndex = Math.max(0, this.selectedIndex - 1); e.preventDefault(); } else if (e.key === "Enter" || e.key.toLowerCase() === "e") { const a = this.activitiesFiltered[this.selectedIndex]; this.startEdit(a.id); e.preventDefault(); } else if (e.key === "Escape") { this.closeEdit(); e.preventDefault(); } else if (e.key === "/") { // focus search const el = this.$refs.search; if (el && el.focus) el.focus(); e.preventDefault(); } }, // child emits updateActivity(payload) { // payload: { id, patch } — decorate + recompute missing const a = this.byId[payload.id]; Object.assign(a, payload.patch); a.missing = this.computeMissing(a); }, startEdit(id) { this.lastScrollY = window.scrollY; // remember current scroll this.editingId = id; }, closeEdit() { this.editingId = null; this.$nextTick(() => { window.scrollTo({ top: this.lastScrollY, behavior: "smooth" }); }); }, }, computed: { activitiesFiltered() { return this.filtered(this.activities); }, groupedByDay() { return _.groupBy(this.activitiesFiltered, (x) => this.month_year(x.starttime)); }, }, template: `
↑/↓ select • Enter/E edit • Esc close
Time Title Mode Location Hosts Missing
{{ $root.$dj(a.starttime).format('MMM D, h:mma') }} {{ a.title }} {{ a.mode }} {{ a.location_irl }}
{{ a.location }}
{{ h.name }}, {{ a.missing.length }} ( {{ a.missing.join(', ') }} )
` }) // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // DISPLAY / EDIT activity or event // const ActivityRow = Vue.component('activityrow', { props: { activity: { type: Object, required: true }, hosts: { type: Array, default: () => [] }, everyone: { type: Array, default: () => [] }, }, data() { return { editing: true, host_search_str: "", host_search: [], this_hosts: [...this.hosts], // local copy for UX }; }, watch: { host_search_str(val) { const q = (val || "").toLowerCase(); this.host_search = q ? _.first( this.everyone.filter((x) => x.name.toLowerCase().includes(q)), 7 ) : []; }, }, methods: { // host ops (call API ONCE per click, not per render) async removeHost(hostid) { await new Promise((resolve, reject) => basic_get(`dir_api.php?a=remove/host/${this.activity.id}/${hostid}`, () => resolve(), reject) ); this.this_hosts = this.this_hosts.filter((x) => x.hostid !== hostid); this.emitPatch({ hostsChanged: true }); alert_message("Saved"); }, async addHost(hostid) { await new Promise((resolve, reject) => basic_get(`dir_api.php?a=add/host/${this.activity.id}/${hostid}`, () => resolve(), reject) ); const found = _.findWhere(this.everyone, { id: hostid }); if (found) this.this_hosts.push({ ...found, hostid: hostid }); this.host_search_str = ""; this.emitPatch({ hostsChanged: true }); alert_message("Saved"); }, emitPatch(extra = {}) { // send minimal changes up; parent recomputes "missing" this.$emit("patch", { id: this.activity.id, patch: { ...extra } }); }, close() { this.editing = false; this.$emit("close"); }, }, mounted() { this.$nextTick(() => { const fieldComp = this.$refs.titleField; console.log(fieldComp) const inputEl = fieldComp?.$el?.querySelector("input, textarea"); console.log(inputEl) if (inputEl) { inputEl.scrollIntoView({ behavior: "smooth", block: "center" }); inputEl.focus(); } }); // Listen for Escape while in edit mode this.keyHandler = (e) => { if (e.key === "Escape") { this.$emit("close"); } }; window.addEventListener("keydown", this.keyHandler); }, beforeDestroy() { window.removeEventListener("keydown", this.keyHandler); }, template: `

Edit: {{ activity.title || '(untitled)' }}

{{ h.name }}
None yet
{{ ho.name }}
` }) // // // // // // Table of all signups and hosts in a conference // ------------------------------ // // // const Overview = Vue.component('overview', { name: 'Overview', data() { return { sessions: [ // Define your sessions data here // Each session should have an id and title property ], users: [ // Define your users data here // Each user should have an id and name property ], signups: [ // Define your signups data here // Each signup should have a userId and sessionId property ], hosts: [ // Define your hosts data here // Each host should have a userId and sessionId property ], fetched:0, }; }, computed: { sortedSessions() { // Sort sessions by startdate (modify the property name as per your data) return this.sessions.sort((a, b) => a.starttime.localeCompare(b.starttime)); }, sortedUsers() { // Sort users by name (modify the property name as per your data) return this.users.sort((a, b) => a.name.localeCompare(b.name)); }, filteredUsers() { // Filter users who have signup or host entries var sorted = this.users.sort((a, b) => a.name.localeCompare(b.name)) return sorted.filter((user) => this.signups.some((signup) => signup.user === user.id) || this.hosts.some((host) => host.user === user.id) ); }, }, methods: { start: function() { var self = this basic_get('dir_api.php?a=get/sessions', function(r2) { self.sessions = r2; self.fetched += 1; }) basic_get('dir_api.php?a=get/hosttable', function(r2) { self.hosts = r2; self.fetched += 1; }) basic_get('dir_api.php?a=get/signups', function(r2) { self.signups = r2; self.fetched += 1; }) basic_get('dir_api.php?a=get/users', function(r2) { self.users = r2; self.fetched += 1; }) }, getSignupHostStatus(userId, sessionId) { // Check if the user signed up for the session // Check if the user hosted the session const host = this.hosts.find((host) => host.host === userId && host.session === sessionId); if (host) { return 'H'; // User hosted } const signup = this.signups.find((signup) => signup.user === userId && signup.session === sessionId); if (signup) { return 'S'; // User signed up } return ''; // User didn't sign up or host } }, mounted: function() { this.start() }, template: `
{{ session.title }}
{{ user.name }} {{ getSignupHostStatus(user.id, session.id) }}
` }); /* - show upcoming, past, dropdown for category - show signup status, signup/cancel button - navigation: check permission, show edit / new button - page: create / edit activity. */ const AskSurvey = Vue.component('asksurvey', { name: 'AskSurvey', components: { 'essay-question': STQuestion, 'number-question': NQuestioon }, props: ['id', ], data () { return { questions: [] } }, methods: { start: function() { var self = this basic_get('dir_api.php?a=get/questions/'+self.id, function(r2) { self.questions = r2 }) }, }, mounted: function() { this.start() }, template: `

{{ questions[0].title }}

` }); const ShowSurveys = Vue.component('show-survey', { name: 'ShowSurveys', //components: { 'essay-question': EssayQuestion, 'number-question': NumberQuestion }, props: ['answer', ], methods: { logger: function() { console.log('logger'); console.log(this); console.log( this.answer ); return ''; }, ses_name: function() { if (this.answer) { return this.answer[0][0].s_title } return '' } }, template: `
{{ logger() }}
{{ q[0]['question'] }}
` }); const Survey = Vue.component('surveydisplay', { props: [ 'answers' ], data () { return { qa_all: [ ], sesh: {}, questions: [], one: {}, qdata: [], answer: "" } }, created: function() { var self = this self.questions = _.where(this.$root.$data.myquestions, {'ses_id':self.$route.params.take_ses_id}) self.$forceUpdate() /*this.$axios.get(this.$server + this.$api + '?a=get/questions', {withCredentials: true}).then( function(resp2) { self.qdata = resp2.data setTimeout(self.continueExecution, 1000) //wait 1 seconds before continuing } )*/ this.$axios.get(this.$server + this.$api + '?a=get/answers/all', {withCredentials: true}).then( function(resp2) { } ) }, methods: { continueExecution: function() {self.questions = _.where( this.qdata, {'ses_id': this.$route.params.take_ses_id } )}, one_session: function() { if (this.$route.params.take_ses_id) { var self = this var my_id = self.$route.params.take_ses_id var my_ses = _.find(this.$root.$data.activities, function(x) { return x.id==my_id } ) return my_ses } return {'title':''} }, answers: function() { var a = this.sesh[parseInt( this.$route.params.ses_id)] if (a) { return a } return 0 }, requested_session: function() { var self = this return _.find(this.$root.$data.activities, function(x) { return x.id == self.$route.params.ses_id } ) /* a = _.find(this.$root.$data.activities, function(x) { return x.id == self.$route.params.ses_id } ) return a */ } }, template: `
{{ q[0]['question'] }}

 
 
  ` }) // ACTIVITIY REPORT - SINGLE - SIGNUPS & SURVEYS // ----------------------------------- // // // const ActivityInfoReport = Vue.component('activityinforeport', { props: [ 'a', 'host','num','user','emails','survey' ], methods: { mode_string: function(a) { return _.findWhere(this.$root.modes_menu, { 'id': a.mode })['string'] }, }, computed: { }, watch: { }, template: `
{{ $root.$dj(a.starttime).format('YYYY MMM DD dd h:mma') }} - {{mode_string(a)}}
{{a.title}}
{{ a.location }}
{{a.typeStr}}
` }) // A single page version, suitable for emailing. const ActivityInfoReport2 = Vue.component('activityinforeport2', { props: [ 'a', 'host','num','user','emails','survey' ], methods: { mode_string: function(a) { return _.findWhere(this.$root.modes_menu, { 'id': a.mode })['string'] }, }, computed: { }, watch: { }, template: `
Title: {{ $wrap(a.title) }}
Date: {{ $root.$dj(a.starttime).format('YYYY MMM DD dd h:mma') }}
Mode / Location: {{ mode_string(a) }}
{{ $wrap(a.location) }}
{{ $wrap(a.location_irl) }}
Description:
Hosts: {{ host }}
Attendees: {{ user }}
Survey Results:
` }) // Copy-friendly table layout for Word/Docs const ActivityInfoReportTable = Vue.component('activityinforeport_table', { props: [ 'a', 'host','num','user','emails','survey' ], methods: { mode_string: function(a) { return _.findWhere(this.$root.modes_menu, { 'id': a.mode })['string'] }, }, template: `
Title: {{ $wrap(a.title) }}
Date: {{ $root.$dj(a.starttime).format('YYYY MMM DD dd h:mma') }}
Mode / Location: {{ mode_string(a) }}
{{ $wrap(a.location) }}
{{ $wrap(a.location_irl) }}
Description:
Hosts: {{ host }}
Attendees: {{ user }}
Survey Results:
` }) // Workshop history (signed up and hosted), grouped by semester const WorkshopHistory = Vue.component('workshophistory', { props: [ '' ], data: function () { return { ready: false, sessionsLoaded: false, sessions: [], // sessions user signed up for or hosted hostsBySes: {}, rostersBySes: {}, answersBySes: {}, // numeric-only for averages grouped: {}, // { 'Fall 2024': [sessions...] } collapsed: {}, // { 'Fall 2024': true|false } } }, mounted: function() { var self = this const init = function() { self.loadData() } if (this.$root && this.$root.active) { init() } else { this.$root.do_after_load(init) } }, methods: { semesterLabel(dt) { const d = dayjs(dt) const y = d.year() const m = d.month() + 1 if (m >= 8) return `Fall ${y}` return `Spring ${y}` }, loadData() { var self = this const uid = this.$root.user.conf_id if (!uid) { this.ready = true; return } // 1) sessions for this user (signed up or hosted) across all time basic_get(`dir_api.php?a=get/sessions/${uid}`, function(r2) { self.sessions = _.sortBy(r2, s => s.starttime) self.buildGroups() self.sessionsLoaded = true self.ready = true self.$forceUpdate() }) // 2) support data for hosts, rosters, survey answers basic_get('dir_api.php?a=get/hosts&all=1', function(r2) { self.hostsBySes = _.groupBy(r2, x => x.id) self.$forceUpdate() }) basic_get('dir_api.php?a=get/rosters&all=1', function(r2) { self.rostersBySes = _.groupBy(r2, x => x.sesid) self.$forceUpdate() }) basic_get('dir_api.php?a=get/answers/all&all=1', function(r2) { // keep only numeric answers per session const bySes = _.groupBy(r2, a => a.ses_id) _.each(bySes, function(list, sid) { const nums = [] _.each(list, function(a) { const v = parseFloat(a.answer) if (!isNaN(v)) nums.push(v) }) if (nums.length) self.answersBySes[sid] = nums }) self.$forceUpdate() }) // ready is set when sessions load; keep other data loading async }, buildGroups() { const self = this const out = {} _.each(this.sessions, function(s) { const label = self.semesterLabel(s.starttime) if (!out[label]) out[label] = [] out[label].push(s) }) // sort newest semester first, and sessions within by date desc this.grouped = {} const order = _.sortBy(Object.keys(out), lbl => { const m = lbl.match(/(Fall|Spring)\s(\d{4})/) if (!m) return 0 const season = m[1] === 'Fall' ? 2 : 1 const year = parseInt(m[2]) return -(year*10 + season) }) _.each(order, function(lbl) { self.grouped[lbl] = _.sortBy(out[lbl], s => -dayjs(s.starttime).valueOf()) if (self.collapsed[lbl] === undefined) self.$set(self.collapsed, lbl, false) }) }, isHost(sessionId) { const uid = this.$root.user.conf_id const hosts = this.hostsBySes[sessionId] || [] return _.some(hosts, h => String(h.hostid) === String(uid)) }, hostNames(sessionId) { const hosts = this.hostsBySes[sessionId] || [] // Filter out null/empty names and dedupe const names = _.chain(hosts) .pluck('name') .filter(n => n && String(n).trim().length) .uniq() .value() return names.join(', ') }, rosterCount(sessionId) { return (this.rostersBySes[sessionId] || []).length }, avgRating(sessionId) { const nums = this.answersBySes[sessionId] if (!nums || !nums.length) return null const sum = _.reduce(nums, (m,v)=>m+v, 0) return (sum/nums.length).toFixed(2) }, toggle(label) { this.$set(this.collapsed, label, !this.collapsed[label]) } }, template: `
Loading...
No workshop history yet.
{{ label }}
{{ s.title }}
{{ $root.$dj(s.starttime).format('YYYY MMM DD, ddd h:mma') }}
Hosts: {{ hostNames(s.id) }}
Host
Signups: {{ rosterCount(s.id) }}
Avg Rating: {{ avgRating(s.id) }}
` }) // // // // // // // ACTIVITIES LIST - SIGNUPS & SURVEYS // ----------------------------------- // // // const ActivityReport = Vue.component('activityreport', { props: [ 'which' ], data: function () { return { activities:[],hosts:[], hosts_by_sesid:[], everyone:[], questions:[], answers:{}, answers2:{}, rosters:[], semesters:[], selectedSemesterKey:'', q:'', single:false } }, mounted: function() { var self = this self.buildSemesters() self.single = (self.which && String(self.which).match(/^\d+$/)) ? true : false self.fetchRange() basic_get('dir_api.php?a=get/names', function(r2) { self.everyone = [] _.each( r2.users, function(x) { self.everyone[x.id] = x.name } ) } ) basic_get('dir_api.php?a=get/questions', function(r2) { self.questions = r2 }) }, methods: { buildSemesters: function() { // Generate semesters: Spring (Jan 1–Jul 31) and Fall (Aug 1–Dec 31), starting 8/2023 const out = [] const start = dayjs('2023-08-01') const endLimit = dayjs().add(2, 'year') let cursor = start while (cursor.isBefore(endLimit)) { const y = cursor.year() // Fall const fallStart = dayjs(`${y}-08-01`) const fallEnd = dayjs(`${y}-12-31`).endOf('day') if (fallStart.isAfter(start)) { out.push({ key: `fall-${y}`, label: `Fall ${y}`, start: fallStart, end: fallEnd }) } else { // include Fall 2023 explicitly out.push({ key: `fall-2023`, label: `Fall 2023`, start: dayjs('2023-08-01'), end: dayjs('2023-12-31').endOf('day') }) } // Spring of next year const sy = y + 1 const springStart = dayjs(`${sy}-01-01`) const springEnd = dayjs(`${sy}-07-31`).endOf('day') out.push({ key: `spring-${sy}`, label: `Spring ${sy}`, start: springStart, end: springEnd }) cursor = dayjs(`${sy}-08-01`) } // Sort newest first this.semesters = _.sortBy(out, s => -s.start.unix()) // Default to current semester const now = dayjs() const current = this.semesters.find(s => now.isAfter(s.start) && now.isBefore(s.end)) this.selectedSemesterKey = current ? current.key : (this.semesters[0] ? this.semesters[0].key : '') }, fetchRange: function() { const sem = this.selectedSemester let q = 'all=1' if (this.single) { q = `id=${this.which}` } else if (sem) { q = `begin=${sem.start.format('YYYY-MM-DD')}&end=${sem.end.format('YYYY-MM-DD')}` } var self = this basic_get(`dir_api.php?a=get/sessions&${q}`, function(r2) { self.activities = _.sortBy(r2,function(x) { return x.starttime } ) _.each( self.activities, function(x) { var field = x.starttime && x.starttime.match(/^(\d\d\d\d)\-(\d+)\-(\d+)\s(\d+)\:(\d+)\:(\d+)$/) if (field) { var mydate = new Date(field[1], field[2] - 1 , field[3], field[4], field[5], field[6]) x.dj = dayjs(mydate) } x.searchable = ((x.title||'') + ' ' + (x.desc||'')).toLowerCase() } ) self.$forceUpdate(); }) basic_get(`dir_api.php?a=get/hosts&${this.single ? ('id=' + this.which) : q}`, function(r2) { self.hosts_by_sesid = _.groupBy(r2,function(x) { return x.id } ) }) basic_get(`dir_api.php?a=get/rosters&${this.single ? ('id=' + this.which) : q}`, function(r2) { self.rosters = _.groupBy(r2,function(x) { return x.sesid } ) }) basic_get(`dir_api.php?a=get/answers/all&${this.single ? ('id=' + this.which) : q}`, function(r2) { var organized = _.groupBy(r2, function(x) { return x.ses_id; } ) self.answers = {} self.answers2 = {} _.each( organized, function(val,key,lis) { self.answers[key] = _.groupBy( val, function(y) { return y.q_id; }); } ) _.each( self.answers, function(val,key,lis) { self.answers2[key] = _.sortBy( val, "q_id" ) } ) self.$forceUpdate() }) }, hoststr: function(id) { var self = this return _.reduce( self.hosts_by_sesid[id], function(mem,val) { if (val.name) { return mem + val.name + ", " } return mem }, '') }, userstr: function(id) { return _.pluck(this.rosters[id], "name").join(', ') }, usernum: function(id) { if (id in this.rosters) { return this.rosters[id].length } return 0 }, useremails: function(id) { return _.pluck(this.rosters[id], "email").join('; ') }, surveystr: function(id) { var self = this var result = "" if (this.answers2[id]) { _.each( this.answers2[id], function (qlist) { // try to build numeric histogram 1–5 var nums = [] _.each( qlist, function(qanswer) { var v = parseInt(qanswer['answer']) if (!isNaN(v) && v>=1 && v<=5) { nums.push(v) } }) result += "
" result += "" + qlist[0]['question'] + "
" if (nums.length) { var counts = [0,0,0,0,0,0] // index 1..5 var sum = 0 _.each(nums, function(v){ counts[v]+=1; sum+=v }) var maxc = _.max(counts.slice(1)) || 1 var avg = (sum/nums.length).toFixed(2) // table-based bars with inline width; good for copy/paste result += "" for (var i=1;i<=5;i++) { var c = counts[i] var width = Math.round((c/maxc)*200) // px result += "" + "" + "" + "" } result += "
"+i+"" + "
" + "
" + "
" + ""+c+"" + "
" result += "
Responses: "+nums.length+", Average: "+avg+"
" } else { // fallback: list non-numeric answers result += "\n" } result += "
" }) return result } return "no survey results?" }, }, computed: { selectedSemester() { return this.semesters.find(s => s.key === this.selectedSemesterKey) || null }, filteredActivities() { const q = (this.q || '').toLowerCase().trim() const sem = this.selectedSemester return this.activities.filter(a => { // semester filter let ok = true if (!this.single && sem) { const t = dayjs(a.starttime) ok = t.isAfter(sem.start) && t.isBefore(sem.end) } if (!ok) return false // explicit id filter via prop 'which' if (this.which && String(this.which).match(/^\d+$/) && String(a.id) !== String(this.which)) { return false } // text filter against title/desc/hosts if (this.single) return true if (!q) return true const hay = (a.title + ' ' + (a.desc||'') + ' ' + (this.hoststr(a.id)||'')).toLowerCase() return hay.indexOf(q) !== -1 }) } }, watch: { selectedSemesterKey() { if (!this.single) this.fetchRange() } }, template: `
Showing {{ filteredActivities.length }} session(s)
` }) // // // // // // // TRAINING HISTORY - GOTT COURSES // ------------------------------- // // // const TrainingHistory = Vue.component('traininghistory', { props: [ '' ], data: function () { return { training: {}, mycourses: {}, msg:"Loading..." } }, mounted: function() { var self = this basic_get('gott_by_goo.json', function(r2) { self.training = r2; setTimeout(() => { self.msg = "" }, 1500); } ) }, computed: { user_courses() { if ('user' in this.$root && this.$root.active && this.training) { return this.training[this.$root.user.conf_goo] || null } return {} } }, methods: { formatDate(dateString) { const options = { year: 'numeric', month: 'long', day: 'numeric' }; return new Date(dateString).toLocaleDateString(undefined, options); } }, watch: { }, template: `

These are the GOTT courses you have taken.

{{ msg }}

Course: {{ course }}
Completed: {{ formatDate(date) }}

No training history found.

` }) // // // _ _ _ // | | | | (_) // ___ ___| |_| |_ _ _ __ __ _ ___ // / __|/ _ \ __| __| | '_ \ / _` / __| // \__ \ __/ |_| |_| | | | | (_| \__ \ // |___/\___|\__|\__|_|_| |_|\__, |___/ // __/ | // |___/ // // const Settings = Vue.component('settings', { props: [ ], data: function () { return { 'zoom_on':'', 'survey_on':'', 'ay':'', 'default_conference':'' } }, mounted: function() { var self = this this.zoom_on = this.$parent.settings.zoom_on this.survey_on = this.$parent.settings.survey_on this.ay = this.$parent.settings.default_ay this.default_conference = this.$parent.settings.default_conference }, methods: { }, computed: { }, watch: { }, template: `
` }) // _ _ // | | | | // ___ __ _| | ___ _ __ __| | __ _ _ __ // / __/ _` | |/ _ \ '_ \ / _` |/ _` | '__| // | (_| (_| | | __/ | | | (_| | (_| | | // \___\__,_|_|\___|_| |_|\__,_|\__,_|_| // // // VERY SIMPLE CALENDAR // const MyCal = Vue.component('mycal', { data: function () { return { today:new Date(), currentMonth:0, currentYear:0, selectYear:'', selectMonth:'', months:["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"], activities:[], by_date: {}, editing: -1, } }, mounted: function() { var self = this this.currentYear = this.today.getFullYear() this.currentMonth = this.today.getMonth() this.selectYear = this.currentYear this.selectMonth = this.currentMonth basic_get('dir_api.php?a=get/sessions', function(r2) { self.activities = _.map(r2, function(x) { var dd = new Date(x.starttime); var [m,d,y] = [dd.getMonth(), dd.getDate(), dd.getFullYear()] // months start at 0.... var d_string = d + "-" + m + "-" + y if (self.by_date[d_string]) { self.by_date[d_string].push(x) } else { self.by_date[d_string] = [x, ] } } ) self.$forceUpdate() } ) }, methods: { thisDay: function(i,j) { var dayOfMonth = ((7*(i-1))+j)-this.firstDay() return dayOfMonth }, eventsThisDay: function(i,j) { var dayOfMonth = ((7*(i-1))+j)-this.firstDay() var d_string = dayOfMonth + "-" + this.selectMonth + "-" + this.selectYear var day_text = '' if (this.by_date[d_string]) { evts = _.filter( this.by_date[d_string], function(x) { return x.typeId!="101" } ) return evts } return [] }, cleanTime: function(e) { var dd = new Date(e.starttime); var [h,m] = [dd.getHours(), dd.getMinutes()] var ampm = 'am' if (h > 12) { h -= 12; ampm = 'pm' } if (m == 0) { m = '' } else { m = ':' + m } return h + m + ampm }, daysInMonth: function() { return 32 - new Date(this.selectYear, this.selectMonth, 32).getDate() }, firstDay: function() { return (new Date(this.selectYear, this.selectMonth)).getDay() }, next: function() { this.selectYear = (this.selectYear === 11) ? this.selectYear + 1 : this.selectYear; this.selectMonth = (this.selectMonth + 1) % 12; }, previous: function() { this.selectYear = (this.selectYear === 0) ? this.selectYear - 1 : this.selectYear; this.selectMonth = (this.selectMonth === 0) ? 11 : this.selectMonth - 1; }, }, computed: { }, watch: { }, template:`

{{months[selectMonth] + " " + selectYear}}

SunMonTueWedThuFriSat
{{ thisDay(i,j) }}
{{cleanTime(ev)}} {{ ev.title }}
` }) // _ _ _ _ _ // | | | | | | | | | | // __ _____| | ___ ___ _ __ ___ ___ | | ___| |_| |_ ___ _ __ | | // \ \ /\ / / _ \ |/ __/ _ \| '_ ` _ \ / _ \ | |/ _ \ __| __/ _ \ '__| | | // \ V V / __/ | (_| (_) | | | | | | __/ | | __/ |_| || __/ | |_| // \_/\_/ \___|_|\___\___/|_| |_| |_|\___| |_|\___|\__|\__\___|_| (_) // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // WELCOME LETTER main component // const WelcomeLetter = Vue.component('welcomeletter', { props: [ 'mysem','mycrn', 'teacher_ext_id', ], data: function () { return { courses_by_semester:[], wl_sem:'', wl_crn:'', section_wl:{}, sortby:'code', reversed:false, active:-1, } }, watch: { "teacher_ext_id": function (val, oldVal) { this.fetch_sections() }, }, mounted: function() { var self = this if (this.mysem && this.mycrn) { this.fetch_wletters(this.mysem, this.mycrn) } else { this.$root.do_after_load( self.fetch_sections) } }, methods: { swap_section: function(section_obj) { this.fetch_wletters(section_obj.sem,section_obj.crn) }, show_list: function() { this.section_wl = {} }, fetch_wletters: function(ss,cc) { var self = this; fetch('dir_api.php?a=get/section/' + ss + '/' + cc, { method: 'GET' }).then(function (response) { // The API call was successful! if (response.ok) { response.json().then( function(r2) { self.section_wl = r2; } ) } else { return Promise.reject(response) } }).then(function (data) { }).catch(function (err) { console.warn('Something went wrong.', err); }); }, fetch_sections: function() { var self = this; basic_get('dir_api.php?a=get/sections/' + this.$root.user.ext_id, function(r2) { self.courses_by_semester = _.groupBy(r2,function(x) { return x.sem || 0} ); self.$forceUpdate(); self.active += 1; } ) }, }, template: `
back to list of sections
Course:
Description:
Delivery Format:
Schedule:
Length:
{{sm}}
      {{crs.code}} - {{crs.crn}} - {{crs.name}}
` }) // _ _ _ // | | | | | | // _____ _____ _ __ | |_ ___ | |__ ___| |_ __ ___ _ __ // / _ \ \ / / _ \ '_ \| __/ __| | '_ \ / _ \ | '_ \ / _ \ '__| // | __/\ V / __/ | | | |_\__ \ | | | | __/ | |_) | __/ | // \___| \_/ \___|_| |_|\__|___/ |_| |_|\___|_| .__/ \___|_| // | | // |_| // // AJAX POST UPDATES TO API // function post_update(table,cols,vals,id=0) { action = "nothing" //if (table=="update_survey") { action = "update" } if (table=="personnel") { action = "update" } if (table=="personnel_ext") { action = "update_xt" } if (table=="conf_users") { action = "update_cf" } if (table=="conf_sessions") { action = "update/activity" } if (table=="webpages") { action = "update_web" } if (table=="welcome_letters") { action = "update/letter" } // or insert? if (table=="uniforecord") { action = "update/settings" } var idstr = "" if (id) { idstr = "&id=" + id } fetch('dir_api.php', { method: 'POST', headers: new Headers({ 'Content-Type': 'application/x-www-form-urlencoded' }), body: "a="+action+"&cols="+cols+"&vals="+vals+idstr, }).then(function (response) { if (response.ok) { response.json().then( function(r2) { // display success alert alert_message('Saved.') } ) } else { return Promise.reject(response) } }).then(function (data) { }).catch(function (err) { alert_message("Couldn't save!",pink); console.warn('Something went wrong.', err); }); } function generic_fail(err,x="Something went wrong with an ajax fetch") { console.log(x); console.log(err) } function basic_get( url, after_fxn, fail_fxn=generic_fail ) { fetch(url, { method: 'GET' }).then(function (response) { if (response.ok) {response.json().then( function(r2) { after_fxn(r2) } ) } else { return Promise.reject(response) } }).then(function (data) { }).catch(function (err) { fail_fxn(err) } ) } var evt = { clear_tables: function() { var self = this _.each( this.tables, function(x) { self[x] = [] } ) this.data = {} this.target_ids = {} }, send_update: function() { var self = this _.each( this.tables, function(x) { if (self[x].length) { var cols = "" var vals = "" _.each(self[x], function(y) { if (cols.length) { cols += "," } if (vals.length) { vals += "," } cols += y if (typeof self.data[y] == "string") { re = /,/g vals += encodeURIComponent(self.data[y].replace(re,'[CMA]') ) } else { vals += self.data[y] } }) var edit_other = 0 if (self.target_ids[x]) { edit_other = self.target_ids[x] } post_update(x, cols, vals, edit_other) } } ) }, data: {}, target_ids: {}, tables: ['personnel','personnel_ext','webpages','welcome_letters','conf_sessions','conf_hosts', 'pers_departments','pers_committees','pers_titles'], } evt.clear_tables() MicroEvent.mixin(evt) // _ _____ _____ // (_) /\ | __ \| __ \ // _ __ ___ __ _ _ _ __ / \ | |__) | |__) | // | '_ ` _ \ / _` | | '_ \ / /\ \ | ___/| ___/ // | | | | | | (_| | | | | | / ____ \| | | | // |_| |_| |_|\__,_|_|_| |_| /_/ \_\_| |_| // // var app = new Vue({ el: '#dir_editor', data: { events: evt, msg: 'hello', active: false, creating:0, user: {'last_name':'', 'first_name':'', 'department':'', 'extension':'', 'phone_number':'', 'email':'', 'staff_type':'', 'room':'', 'status':'', 'user_id':'', 'password':'', 'time_created':'', 'time_updated':'', 'id':'', ext_id:false, 'web_on':'', use_dir_photo:0, general_photo_release:0, espanol:0, zoom:'', preferred_contact:'', officehours:'', title:'', picture:'', education:'', bio:'', courses:'', personal_page:'', changed:'' }, settings:{}, filter: [], roles_menu: [], depts_menu: [], titles_menu: [], sessiontypes_menu: [], parents_menu: [], ay_menu: [], modes_menu: [ {'id': 'Pending', 'string':'Pending'}, {'id':'online','string':'Online'}, {'id':'inperson','string':'In Person'}, {'id':'hybrid','string':'Hyflex'}, {'id':'','string':''}, ], waiting_fxns: [], data_loaded: 0, committees_menu: [], menus_fetched: false, }, watch: { 'data_loaded': function(newVal,oldVal) { if (newVal > 0) { _.each( this.waiting_fxns, function(fx) { fx() }) } }, }, methods: { do_after_load: function(do_fxn) { this.waiting_fxns.push(do_fxn) /*....*/ }, fetch_menus: function() { if (! this.menus_fetched) { var self = this; fetch('dir_api.php?a=menus', { method: 'GET' }).then(function (response) { // The API call was successful! if (response.ok) { response.json().then( function(r2) { self.depts_menu = r2.departments; self.roles_menu = r2.roles; self.titles_menu = r2.titles; self.committees_menu = r2.committees; self.sessiontypes_menu = r2.sessiontypes; self.parents_menu = r2.parents; self.menus_fetched = true; } ) } else { return Promise.reject(response) } }).then(function (data) { }).catch(function (err) { // FAILED TO LOAD. MOST LIKELY THE SSO/SESSION TIMED OUT // .... reload whole page to get redirect...? console.warn('Something went wrong.', err); }); } }, my_subscribe_calendar: function() { return "webcal://hhh.gavilan.edu/phowell/map/calendar" + this.user.conf_id + ".ics" }, clip_copy: function(x) { // see the .htaccess file for the mod_rewrite that makes the ics file work. // var data = [new ClipboardItem({ "text/plain": new Blob([this.my_subscribe_calendar()], { type: "text/plain" }) })]; navigator.clipboard.write(data).then(function() { fadein_message() }, function() { console.error("Unable to write to clipboard. :-("); }) }, }, computed: { }, mounted: function() { var self = this; fetch('api2.php?query=start', { method: 'GET' }).then(function (response) { // The API call was successful! if (response.ok) { response.json().then( function(r2) { var x = self.user.mysessions self.user = r2.user; self.depts_menu = r2.departments; self.roles_menu = r2.roles; self.titles_menu = r2.titles; self.committees_menu = r2.committees; self.sessiontypes_menu = r2.sessiontypes; self.parents_menu = r2.parents; self.ay_menu = r2.ay; self.settings = r2.settings; self.menus_fetched = true; self.data_loaded += 1 // pause half a second for the children to get populated before registering update events... setTimeout(function() { self.active = true; // fancier text editors... //pell.init( { element: document.getElementById('bio2'), onChange: function(h) { console.log(h) } } ) }, 1600); } ) } else { return Promise.reject(response) } }).then(function (data) { }).catch(function (err) { console.warn('Something went wrong.', err); }) } }) // _ // | | // _____ _____ _ __ | |_ ___ // / _ \ \ / / _ \ '_ \| __/ __| // | __/\ V / __/ | | | |_\__ \ // \___| \_/ \___|_| |_|\__|___/ // // // // SIMPLE EVENTS // var update_fxn = _.debounce( function() { alert_message('saving...','lightgreen') evt.send_update(); evt.clear_tables(); }, 1300 ) var update_survey_fxn = function() { } evt.bind('update_survey',_.debounce( function(dat) { if (app.active) { fetch('dir_api.php?a=update/answers', { method: 'POST', headers: new Headers({ 'Content-Type': 'application/x-www-form-urlencoded' }), body: "session=" +dat[1] + "&user=" +dat[0] + "&qid=" +dat[2] + "&answer="+dat[3], }).then(function (response) { if (response.ok) { response.json().then( function(r2) { // display success alert alert_message('Saved.') } ) } else { return Promise.reject(response) } }).then(function (data) { }).catch(function (err) { alert_message("Couldn't save!",pink); console.warn('Something went wrong.', err); }); } } , 1300 ) ); evt.bind('changed', function(dat) { if (app.active) { var column = dat[0] var table = dat[1] var value = dat[2] var target = dat[3] this.data[column] = value if (!this[table].includes(column) ) {this[table].push(column)} if (target) { this.target_ids[table] = target } } if (app.active && !app.creating) { update_fxn() } }); evt.bind('create_new_session', function(dat) { var default_activity = {"title":"","desc":"","length":"1","starttime":"","track":"","location":"","gets_survey":"1","category":"1", "parent":"","recording":"","instructions":"","image_url":"","is_flex_approved":"1","typeId":"101"} var new_activity = _.extend(default_activity, evt.data) if ('typeId' in new_activity) { new_activity.type = new_activity.typeId; delete new_activity.typeId; } let formData = new FormData(); _.each( Object.keys(new_activity), function(x) { formData.append(x, new_activity[x]) } ) fetch('dir_api.php?a=set/newsession', { method: 'POST', body: formData, }).then(function (response) { if (response.ok) { response.json().then( function(r2) { alert_message('Saved new activity.') app.$children[0].set_id(r2.new_id) } ) } else { return Promise.reject(response) } }).then(function (data) { }).catch(function (err) { alert_message("Couldn't create the activity!",pink); console.warn('Something went wrong.', err); }) }) // bold the current page function bold_nav() { var currentFileName = window.location.pathname.split('/').pop(); // Select the tag with the matching href value and apply the class $('#nav a[href="' + currentFileName + '"]').addClass('highlight'); } $(document).ready(function() { bold_nav() }) // // // MISC // // // // // v-lazy-container="{ selector: 'img' }" //