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 }
if (mysqldate instanceof Date) { return mysqldate }
var s = String(mysqldate).trim()
var field = s.match(/^(\d{4})-(\d{1,2})-(\d{1,2})[T\s](\d{1,2}):(\d{2})(?::(\d{2}))?$/)
if (field) {
var sec = field[6] ? field[6] : 0
return new Date(field[1], field[2] - 1, field[3], field[4], field[5], sec)
}
var parsed = new Date(s)
if (!isNaN(parsed.getTime())) { return parsed }
return 0
}
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: `
{{ question }}
`,
})
// 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: `
{{ question }}
`
})
// 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: `
{{ question }}
` })
// 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: ``
})
// 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: `{{ qq.question }}
`,
})
// 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: `
I certify that I have attended this event.
` });
// 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: `
{{ question }}
{{ o[labelfield] }}
` })
// 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: `
{{ question }}
{{ o[labelfield] }}
` })
// 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: `
{{ question }}
`,
})
// 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: `
`
});
// // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // //
// // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // //
//
// 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: `` })
// 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: `` })
//
//
//
//
//
//
// ACTIVITIES LIST MAIN CONTAINER
// ------------------------------
//
//
//
// Empty time slot card
const EmptySlotCard = Vue.component('emptyslotcard', {
props: ['label'],
template: `
+
{{ label || 'Signup' }}
`
})
const ActivityList = Vue.component('activitylist', {
props: [ 'itineraryview','static','show_all_sessions' ],
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:{}, selectedSlotKey: null,
hosts_by_sesid: {}, options:{}, conference:{}, ay:{}, conf:-1, survey_on:0, zoom_on:1,
cancelingIds:[], timeslotConfig:null, expandedDesc:{}, collapsedDays:{},
surveyAnswersBySession: {}, } },
mounted: function() {
this.fetch_myevents()
},
methods: {
normalizeId: function(id) {
var parsed = parseInt(id, 10);
return Number.isNaN(parsed) ? id : parsed;
},
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';
},
toggleDaySlots: function(dayKey) {
var isCollapsed = !!this.collapsedDays[dayKey];
this.$set(this.collapsedDays, dayKey, !isCollapsed);
},
isDayCollapsed: function(dayKey) {
return !!this.collapsedDays[dayKey];
},
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';
},
descPlain: function(desc) {
if (!desc) { return ''; }
return String(desc)
.replace(/<[^>]*>/g, ' ')
.replace(/\s+/g, ' ')
.trim();
},
descPreview: function(desc) {
var text = this.descPlain(desc);
var limit = 200;
if (text.length <= limit) { return text; }
var slice = text.slice(0, limit + 1);
var cutoff = slice.lastIndexOf(' ');
if (cutoff < 0) { cutoff = limit; }
return text.slice(0, cutoff).trim() + '...';
},
descIsTruncated: function(desc) {
return this.descPlain(desc).length > 200;
},
isDescExpanded: function(id) {
return this.expandedDesc && this.expandedDesc[id];
},
toggleDesc: function(id) {
if (!this.expandedDesc) { this.expandedDesc = {}; }
this.$set(this.expandedDesc, id, !this.expandedDesc[id]);
},
slotKeyFromStart: function(starttime) {
if (!starttime) { return null; }
return this.$root.$dj(starttime).format('YYYY-MM-DDTHH:mm');
},
slotSelection: function(slot) {
var self = this;
var list = this.filtered(this.mysessions, { applySearch: false, applySlot: false });
return _.filter(list, function(s) { return self.slotKeyFromStart(s.starttime) === slot.key; });
},
selectSlot: function(slot) {
if (this.selectedSlotKey === slot.key) { this.selectedSlotKey = null; return; }
this.selectedSlotKey = slot.key;
this.search = '';
},
clearSlot: function() { this.selectedSlotKey = null; },
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')
var convertedDateString = d.format('YYYY-MM-DD')
var found = _.findWhere( this.conference, {date1:convertedDateString} )
if (found && found.title) { return found.title }
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;
var hostUrl = 'dir_api.php?a=get/hosts';
if (self.show_all_sessions) { hostUrl += '&all=1'; }
basic_get(hostUrl, function(r2) {
self.hosts_by_sesid = _.groupBy(r2,function(x) { return x.id } )
} )
basic_get('dir_api.php?a=get/questions', function(r2) {
var answered = {};
_.each(r2, function(row) {
var sesId = self.normalizeId(row.ses_id || row.session || row.ses);
if (!sesId) { return; }
if (row.answer !== null && String(row.answer).trim() !== '') {
answered[sesId] = true;
}
});
self.surveyAnswersBySession = answered;
})
basic_get('api2.php?query=app',
function(r2) {
self.mysessions = r2.mysessions
self.my_ses_ids = _.map(_.pluck(r2.mysessions, 'id'), function(val) {
return self.normalizeId(val);
})
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
if (r2.hostbysession) {
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.loadTimeslots()
self.$forceUpdate();
} )
},
joinme: function(id) {
var self = this
id = self.normalizeId(id)
basic_get('dir_api.php?a=signup/' + id,
function(r2) {
if (!self.my_ses_ids.includes(id)) { self.my_ses_ids.push(id) }
var existing = _.find(self.mysessions, function(s) {
return self.normalizeId(s.id) === id;
});
if (!existing) {
var activity = _.find(self.activities, function(a) {
return self.normalizeId(a.id) === id;
});
if (activity) { self.mysessions.push(activity) }
}
self.$forceUpdate()
alert_message("Added activity") })
},
dumpme: function(id) {
var self = this
id = self.normalizeId(id)
if (!self.cancelingIds.includes(id)) { self.cancelingIds.push(id) }
basic_get('dir_api.php?a=signdown/' + id,
function(r2) {
setTimeout(function() {
self.mysessions = _.filter(self.mysessions, function(s) {
return self.normalizeId(s.id) !== id;
});
self.my_ses_ids = _.filter(self.my_ses_ids, function(sid) {
return self.normalizeId(sid) !== id;
});
self.cancelingIds = _.without(self.cancelingIds, id)
self.$forceUpdate()
alert_message("Removed activity")
}, 250)
})
},
sessionsForSlot: function(slot) {
var self=this;
if (slot.presetSessions && slot.presetSessions.length) { return []; }
return _.chain(this.activities_for_slots)
.filter(function(item) { return self.slotKeyFromStart(item.starttime) === slot.key; })
.sortBy('starttime')
.value();
},
chooseSession: function(activity) {
var self=this;
if (!self.my_ses_ids.includes(activity.id)) {
self.joinme(activity.id);
}
self.clearSlot();
},
slotPresets: function(slot) { return slot.presetSessions || []; },
slotHasPreset: function(slot) { return (slot.presetSessions && slot.presetSessions.length>0); },
nl2br: function(text) {
if (!text) { return ''; }
return String(text).replace(/\r?\n/g, ' ');
},
canJoin: function(activity) {
if (this.zoom_on !== 1) { return false; }
return activity && (activity.mode === 'online' || activity.mode === 'hybrid');
},
canSurvey: function(activity) {
if (this.survey_on !== 1) { return false; }
return !!activity;
},
hasSurveyAnswer: function(activity) {
if (!activity) { return false; }
var sid = this.normalizeId(activity.id);
return !!this.surveyAnswersBySession[sid];
},
hasJoinLink: function(activity) {
if (!activity || !activity.location) { return false; }
return String(activity.location).trim().length > 0;
},
loadTimeslots: function() {
var self=this;
if (window.TIMESLOT_CONFIG) {
self.timeslotConfig = window.TIMESLOT_CONFIG;
return;
}
fetch('schedule_timeslots.json').then(function(resp) {
if (!resp.ok) { throw new Error('timeslot fetch'); }
return resp.json();
}).then(function(json) {
self.timeslotConfig = json;
}).catch(function(e) {
console.warn('No schedule_timeslots.json found or unreadable', e);
});
},
isCanceling: function(id) { return this.cancelingIds.includes(id) },
filtered: function(ff, opts = { applySearch: true, applySlot: false }) {
var self = this
var applySearch = (opts.applySearch !== false)
var applySlot = (opts.applySlot !== false)
if (applySearch && 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) } )
if (this.selectedSlotKey && applySlot) {
ff = ff.filter(function(item) { return self.slotKeyFromStart(item.starttime) === self.selectedSlotKey })
}
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_for_slots: function() { return this.filtered(this.activities, { applySearch: false, applySlot: false }) },
timeSlotsByDay: function() {
if (this.active < 1) { return {}; }
var self=this;
if (this.timeslotConfig && this.timeslotConfig.versions && this.timeslotConfig.days) {
var res = {};
_.each(this.timeslotConfig.days, function(versionKey, dayStr) {
var version = self.timeslotConfig.versions[versionKey];
if (!version || !version.slots) { return; }
var dayLabel = dayjs(dayStr).format('MMM D YYYY');
res[dayLabel] = _.map(version.slots, function(slot) {
var start = dayjs(dayStr + ' ' + slot.start);
var end = dayjs(dayStr + ' ' + slot.end);
return {
key: dayStr + 'T' + slot.start,
startLabel: start.isValid() ? start.format('h:mma') : slot.start,
endLabel: end.isValid() ? end.format('h:mma') : slot.end,
dayLabel: dayLabel,
presetSessions: slot.sessions || [],
version: versionKey,
versionLabel: version.label || versionKey
};
});
});
return res;
}
var grouped = _.groupBy(this.activities_for_slots, function(x) { return self.month_year(x.starttime) });
return _.mapObject(grouped, function(list) {
return _.chain(list)
.sortBy('starttime')
.map(function(item) {
var start = self.$root.$dj(item.starttime);
return {
key: self.slotKeyFromStart(item.starttime),
startLabel: start.format('h:mma'),
endLabel: self.addTime(item.starttime, item.length),
dayLabel: self.month_year(item.starttime),
presetSessions: [],
version: null,
versionLabel: null,
};
})
.uniq(false, function(slot) { return slot.key; })
.value();
});
},
selectedSlotLabel: function() {
if (!this.selectedSlotKey) { return '' }
var m = dayjs(this.selectedSlotKey);
if (!m.isValid()) { return '' }
return m.format('MMM D, h:mma');
},
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.
No session times are available yet.
{{ isDayCollapsed(mmyy) ? '+' : '-' }}
{{ mmyy }} {{ get_day_title(mmyy) }}
to {{ slot.endLabel }}
Selected
{{ sel.title }}
{{ sel.audience }} meeting
at {{ sel.location }}
· {{ sel.mode }}
{{ sel.title }}
{{ $root.$dj(sel.starttime).format('h:mma') }} - {{ addTime(sel.starttime, sel.length) }} · {{ mode_string(sel) }}
·
{{ sel.location_irl }}
(location TBD)
Change session
Pick for {{ slot.startLabel }} - {{ slot.endLabel }}
Available sessions
X
{{ a.title }}
In person at {{ a.location_irl }} or online
In person at {{ a.location_irl }} location TBD
Online
{{ descPreview(a.desc) }}
[+ read more]
[- read less]
Sign Up
In itinerary
No sessions are available in this time slot.
Loading your itinerary...
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) }}
{{ $root.$dj(a.starttime).format('h:mma') }} - {{ addTime(a.starttime, a.length) }}
{{ a.title }}
{{ hoststr(a.id) }}
no host registered
{{ mode_string(a) }}
Location: {{ a.location_irl }}
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,
dayFilter: "all",
// 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);
this.changedHandler = (dat) => {
const column = dat[0];
const table = dat[1];
const value = dat[2];
const target = dat[3];
if (table !== "conf_sessions" || !target) return;
const a = this.byId[target];
if (!a) return;
a[column] = value;
if (column === "title" || column === "desc") {
a.searchable = ((a.title || "") + " " + (a.desc || "")).toLowerCase();
}
a.missing = this.computeMissing(a);
};
this.$root.events.bind("changed", this.changedHandler);
},
beforeDestroy() {
window.removeEventListener("keydown", this.onKey);
if (this.changedHandler && this.$root && this.$root.events && this.$root.events.unbind) {
this.$root.events.unbind("changed", this.changedHandler);
}
},
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));
}
if (this.dayFilter && this.dayFilter !== "all") {
ff = ff.filter((item) => {
const t = dayjs(item.starttime);
return t.isValid() && t.format("YYYY-MM-DD") === this.dayFilter;
});
}
// 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") {
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);
},
availableDays() {
const seen = {};
this.activities.forEach((a) => {
const t = dayjs(a.starttime);
if (!t.isValid()) return;
const key = t.format("YYYY-MM-DD");
if (!seen[key]) {
seen[key] = t.format("MMM D YYYY");
}
});
return Object.keys(seen)
.sort()
.map((key) => ({ key, label: seen[key] }));
},
groupedByDay() {
return _.groupBy(this.activitiesFiltered, (x) => this.month_year(x.starttime));
},
},
template: `
`
})
// // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // //
// // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // //
//
// 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)' }}
Done
`
})
//
//
//
//
//
// 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 })
},
saveAll: function() {
var self = this
_.each(self.questions, function(q) {
self.$root.events.trigger('update_survey', [q.user, q.session, q.qid, q.answer])
})
if (typeof alert_message === 'function') {
alert_message('Saved.', 'lightgreen')
}
},
},
mounted: function() { this.start() },
template: `
Session name:
[{{ questions[0].title }}]
Save
`
});
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: `
` })
// 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 }}
{{ collapsed[label] ? 'Expand' : 'Collapse' }}
{{ 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 += ""
+ ""+i+" "
+ ""
+ ""
+ ""+c+" "
+ " "
+ " "
}
result += "
"
result += "
Responses: "+nums.length+", Average: "+avg+"
"
} else {
// fallback: list non-numeric answers
result += "
"
_.each( qlist, function(qanswer) {
var txt = (qanswer['answer']||'')
// escape < and > to avoid breaking markup, then wrap long tokens
txt = String(txt).replace(//g,'>')
txt = wrapLongTokens(txt)
result += "" + txt + " \n"
})
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: `` })
//
//
//
//
//
//
// 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}}
Sun Mon Tue Wed Thu Fri Sat
{{ 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: `` })
// _ _ _
// | | | | | |
// _____ _____ _ __ | |_ ___ | |__ ___| |_ __ ___ _ __
// / _ \ \ / / _ \ '_ \| __/ __| | '_ \ / _ \ | '_ \ / _ \ '__|
// | __/\ 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' }"
//