|
|
|
@ -28,10 +28,18 @@ function init_file_dropzone(parameter_name) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function parsesqltime(mysqldate) { // 2021-01-29 09:00:00
|
|
|
|
function parsesqltime(mysqldate) { // 2021-01-29 09:00:00
|
|
|
|
if (! mysqldate) { return 0 }
|
|
|
|
if (!mysqldate) { return 0 }
|
|
|
|
var field = mysqldate.match(/^(\d\d\d\d)\-(\d+)\-(\d+)[T|\s](\d+)\:(\d+)\:(\d+)$/)
|
|
|
|
if (mysqldate instanceof Date) { return mysqldate }
|
|
|
|
var mydate = new Date(field[1], field[2] - 1 , field[3], field[4], field[5], field[6])
|
|
|
|
var s = String(mysqldate).trim()
|
|
|
|
return mydate }
|
|
|
|
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)) }
|
|
|
|
function dj(mysqldate) { return dayjs(parsesqltime(mysqldate)) }
|
|
|
|
|
|
|
|
|
|
|
|
@ -872,11 +880,17 @@ const ActivityList = Vue.component('activitylist', {
|
|
|
|
data: function () {
|
|
|
|
data: function () {
|
|
|
|
return { activities:[], mysessions:[], search:'', sortby:'starttime', reversed:false, my_ses_ids:[], my_host_ids:[],
|
|
|
|
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,
|
|
|
|
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, } },
|
|
|
|
hosts_by_sesid: {}, options:{}, conference:{}, ay:{}, conf:-1, survey_on:0, zoom_on:1,
|
|
|
|
|
|
|
|
cancelingIds:[], timeslotConfig:null, expandedDesc:{}, collapsedDays:{},
|
|
|
|
|
|
|
|
surveyAnswersBySession: {}, } },
|
|
|
|
mounted: function() {
|
|
|
|
mounted: function() {
|
|
|
|
this.fetch_myevents()
|
|
|
|
this.fetch_myevents()
|
|
|
|
},
|
|
|
|
},
|
|
|
|
methods: {
|
|
|
|
methods: {
|
|
|
|
|
|
|
|
normalizeId: function(id) {
|
|
|
|
|
|
|
|
var parsed = parseInt(id, 10);
|
|
|
|
|
|
|
|
return Number.isNaN(parsed) ? id : parsed;
|
|
|
|
|
|
|
|
},
|
|
|
|
special_signup: function(activity_id) {
|
|
|
|
special_signup: function(activity_id) {
|
|
|
|
if (activity_id==1462 || activity_id==1455) {
|
|
|
|
if (activity_id==1462 || activity_id==1455) {
|
|
|
|
return true;
|
|
|
|
return true;
|
|
|
|
@ -896,12 +910,45 @@ const ActivityList = Vue.component('activitylist', {
|
|
|
|
group.classList.toggle('hidden');
|
|
|
|
group.classList.toggle('hidden');
|
|
|
|
btn.textContent = group.classList.contains('hidden') ? 'Expand Day' : 'Collapse Day';
|
|
|
|
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) {
|
|
|
|
toggleDescription: function(id) {
|
|
|
|
const para = document.getElementById(id);
|
|
|
|
const para = document.getElementById(id);
|
|
|
|
const btn = document.getElementById(id + '-btn');
|
|
|
|
const btn = document.getElementById(id + '-btn');
|
|
|
|
para.classList.toggle('line-clamp-2');
|
|
|
|
para.classList.toggle('line-clamp-2');
|
|
|
|
btn.textContent = para.classList.contains('line-clamp-2') ? 'Show More' : 'Show Less';
|
|
|
|
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) {
|
|
|
|
slotKeyFromStart: function(starttime) {
|
|
|
|
if (!starttime) { return null; }
|
|
|
|
if (!starttime) { return null; }
|
|
|
|
return this.$root.$dj(starttime).format('YYYY-MM-DDTHH:mm');
|
|
|
|
return this.$root.$dj(starttime).format('YYYY-MM-DDTHH:mm');
|
|
|
|
@ -945,21 +992,39 @@ const ActivityList = Vue.component('activitylist', {
|
|
|
|
am_editing: function(id) { return 0 },
|
|
|
|
am_editing: function(id) { return 0 },
|
|
|
|
fetch_myevents: function() {
|
|
|
|
fetch_myevents: function() {
|
|
|
|
var self = this;
|
|
|
|
var self = this;
|
|
|
|
basic_get('dir_api.php?a=get/hosts', function(r2) {
|
|
|
|
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 } )
|
|
|
|
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',
|
|
|
|
basic_get('api2.php?query=app',
|
|
|
|
function(r2) {
|
|
|
|
function(r2) {
|
|
|
|
self.mysessions = r2.mysessions
|
|
|
|
self.mysessions = r2.mysessions
|
|
|
|
self.my_ses_ids = _.pluck(r2.mysessions, 'id')
|
|
|
|
self.my_ses_ids = _.map(_.pluck(r2.mysessions, 'id'), function(val) {
|
|
|
|
|
|
|
|
return self.normalizeId(val);
|
|
|
|
|
|
|
|
})
|
|
|
|
self.activities = r2.sessions
|
|
|
|
self.activities = r2.sessions
|
|
|
|
if (r2.host != null) { self.my_host_ids = r2.host }
|
|
|
|
if (r2.host != null) { self.my_host_ids = r2.host }
|
|
|
|
else { self.my_host_ids = [] }
|
|
|
|
else { self.my_host_ids = [] }
|
|
|
|
self.options = r2.options
|
|
|
|
self.options = r2.options
|
|
|
|
self.conference = r2.conference
|
|
|
|
self.conference = r2.conference
|
|
|
|
self.ay = r2.ay
|
|
|
|
self.ay = r2.ay
|
|
|
|
|
|
|
|
if (r2.hostbysession) {
|
|
|
|
self.hosts_by_sesid = _.groupBy(r2.hostbysession,function(x) { return x.id } )
|
|
|
|
self.hosts_by_sesid = _.groupBy(r2.hostbysession,function(x) { return x.id } )
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
self.survey_on = parseInt( _.findWhere(self.options, { label:'survey_on' }).value )
|
|
|
|
self.survey_on = parseInt( _.findWhere(self.options, { label:'survey_on' }).value )
|
|
|
|
self.zoom_on = parseInt( _.findWhere(self.options, { label:'zoom_on' }).value )
|
|
|
|
self.zoom_on = parseInt( _.findWhere(self.options, { label:'zoom_on' }).value )
|
|
|
|
@ -971,20 +1036,35 @@ const ActivityList = Vue.component('activitylist', {
|
|
|
|
},
|
|
|
|
},
|
|
|
|
joinme: function(id) {
|
|
|
|
joinme: function(id) {
|
|
|
|
var self = this
|
|
|
|
var self = this
|
|
|
|
|
|
|
|
id = self.normalizeId(id)
|
|
|
|
basic_get('dir_api.php?a=signup/' + id,
|
|
|
|
basic_get('dir_api.php?a=signup/' + id,
|
|
|
|
function(r2) {
|
|
|
|
function(r2) {
|
|
|
|
self.mysessions.push(_.findWhere(self.activities, {'id':id}))
|
|
|
|
if (!self.my_ses_ids.includes(id)) { self.my_ses_ids.push(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") })
|
|
|
|
alert_message("Added activity") })
|
|
|
|
},
|
|
|
|
},
|
|
|
|
dumpme: function(id) {
|
|
|
|
dumpme: function(id) {
|
|
|
|
var self = this
|
|
|
|
var self = this
|
|
|
|
|
|
|
|
id = self.normalizeId(id)
|
|
|
|
if (!self.cancelingIds.includes(id)) { self.cancelingIds.push(id) }
|
|
|
|
if (!self.cancelingIds.includes(id)) { self.cancelingIds.push(id) }
|
|
|
|
basic_get('dir_api.php?a=signdown/' + id,
|
|
|
|
basic_get('dir_api.php?a=signdown/' + id,
|
|
|
|
function(r2) {
|
|
|
|
function(r2) {
|
|
|
|
setTimeout(function() {
|
|
|
|
setTimeout(function() {
|
|
|
|
self.mysessions = _.without( self.mysessions, _.findWhere(self.activities, {'id':id}))
|
|
|
|
self.mysessions = _.filter(self.mysessions, function(s) {
|
|
|
|
self.my_ses_ids = _.without( self.my_ses_ids, id)
|
|
|
|
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.cancelingIds = _.without(self.cancelingIds, id)
|
|
|
|
self.$forceUpdate()
|
|
|
|
self.$forceUpdate()
|
|
|
|
alert_message("Removed activity")
|
|
|
|
alert_message("Removed activity")
|
|
|
|
@ -1008,6 +1088,27 @@ const ActivityList = Vue.component('activitylist', {
|
|
|
|
},
|
|
|
|
},
|
|
|
|
slotPresets: function(slot) { return slot.presetSessions || []; },
|
|
|
|
slotPresets: function(slot) { return slot.presetSessions || []; },
|
|
|
|
slotHasPreset: function(slot) { return (slot.presetSessions && slot.presetSessions.length>0); },
|
|
|
|
slotHasPreset: function(slot) { return (slot.presetSessions && slot.presetSessions.length>0); },
|
|
|
|
|
|
|
|
nl2br: function(text) {
|
|
|
|
|
|
|
|
if (!text) { return ''; }
|
|
|
|
|
|
|
|
return String(text).replace(/\r?\n/g, '<br>');
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
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() {
|
|
|
|
loadTimeslots: function() {
|
|
|
|
var self=this;
|
|
|
|
var self=this;
|
|
|
|
if (window.TIMESLOT_CONFIG) {
|
|
|
|
if (window.TIMESLOT_CONFIG) {
|
|
|
|
@ -1134,8 +1235,14 @@ const ActivityList = Vue.component('activitylist', {
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div v-for="(slots, mmyy) in timeSlotsByDay" :key="mmyy" class="mb-6">
|
|
|
|
<div v-for="(slots, mmyy) in timeSlotsByDay" :key="mmyy" class="mb-6">
|
|
|
|
<h4 class="text-lg font-semibold text-gray-700 mb-2">{{ mmyy }} {{ get_day_title(mmyy) }}</h4>
|
|
|
|
<div class="flex items-center gap-2 mb-2">
|
|
|
|
<div class="space-y-3">
|
|
|
|
<button class="text-lg font-semibold text-gray-500 hover:text-gray-700 w-6 text-center"
|
|
|
|
|
|
|
|
@click.prevent="toggleDaySlots(mmyy)">
|
|
|
|
|
|
|
|
{{ isDayCollapsed(mmyy) ? '+' : '-' }}
|
|
|
|
|
|
|
|
</button>
|
|
|
|
|
|
|
|
<h4 class="text-lg font-semibold text-gray-700">{{ mmyy }} {{ get_day_title(mmyy) }}</h4>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div v-if="!isDayCollapsed(mmyy)" class="space-y-3">
|
|
|
|
<div v-for="slot in slots" :key="slot.key" :class="['border rounded-lg p-4 bg-white shadow-sm flex items-start gap-4', selectedSlotKey === slot.key ? 'ring-2 ring-blue-400' : '']">
|
|
|
|
<div v-for="slot in slots" :key="slot.key" :class="['border rounded-lg p-4 bg-white shadow-sm flex items-start gap-4', selectedSlotKey === slot.key ? 'ring-2 ring-blue-400' : '']">
|
|
|
|
<div class="w-32 text-right">
|
|
|
|
<div class="w-32 text-right">
|
|
|
|
<div class="text-2xl font-extrabold text-blue-800 leading-tight">{{ slot.startLabel }}</div>
|
|
|
|
<div class="text-2xl font-extrabold text-blue-800 leading-tight">{{ slot.startLabel }}</div>
|
|
|
|
@ -1148,11 +1255,11 @@ const ActivityList = Vue.component('activitylist', {
|
|
|
|
<div v-for="sel in slotPresets(slot)" :key="sel.title" class="border-b last:border-none pb-2 last:pb-0">
|
|
|
|
<div v-for="sel in slotPresets(slot)" :key="sel.title" class="border-b last:border-none pb-2 last:pb-0">
|
|
|
|
<div class="font-semibold text-gray-900">{{ sel.title }}</div>
|
|
|
|
<div class="font-semibold text-gray-900">{{ sel.title }}</div>
|
|
|
|
<div class="text-sm text-gray-600">
|
|
|
|
<div class="text-sm text-gray-600">
|
|
|
|
<span v-if="sel.audience" class="mr-2 capitalize">{{ sel.audience }} meeting</span>
|
|
|
|
<span v-if="sel.audience" class="capitalize">{{ sel.audience }} meeting</span>
|
|
|
|
<span v-if="sel.location">at {{ sel.location }}</span>
|
|
|
|
<span v-if="sel.location">at {{ sel.location }}</span>
|
|
|
|
<span v-if="sel.mode">· {{ sel.mode }}</span>
|
|
|
|
<span v-if="sel.mode">· {{ sel.mode }}</span>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div v-if="sel.notes" class="text-xs text-gray-500 mt-1">{{ sel.notes }}</div>
|
|
|
|
<div v-if="sel.notes" class="text-xs text-gray-500 mt-1" v-html="nl2br(sel.notes)"></div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
@ -1160,13 +1267,31 @@ const ActivityList = Vue.component('activitylist', {
|
|
|
|
<div v-for="sel in slotSelection(slot)" :key="sel.id" :class="['border-b last:border-none pb-2 last:pb-0 transition-opacity duration-300', isCanceling(sel.id) ? 'opacity-40' : '']">
|
|
|
|
<div v-for="sel in slotSelection(slot)" :key="sel.id" :class="['border-b last:border-none pb-2 last:pb-0 transition-opacity duration-300', isCanceling(sel.id) ? 'opacity-40' : '']">
|
|
|
|
<div class="font-semibold text-gray-900">{{ sel.title }}</div>
|
|
|
|
<div class="font-semibold text-gray-900">{{ sel.title }}</div>
|
|
|
|
<div class="text-sm text-gray-600">
|
|
|
|
<div class="text-sm text-gray-600">
|
|
|
|
{{ $root.$dj(sel.starttime).format('h:mma') }} · {{ mode_string(sel) }}
|
|
|
|
<div class="flex flex-wrap items-center gap-2">
|
|
|
|
|
|
|
|
<span>{{ $root.$dj(sel.starttime).format('h:mma') }} - {{ addTime(sel.starttime, sel.length) }} · {{ mode_string(sel) }}</span>
|
|
|
|
|
|
|
|
<span v-if="sel.mode === 'hybrid'">·
|
|
|
|
|
|
|
|
<span v-if="sel.location_irl">{{ sel.location_irl }}</span>
|
|
|
|
|
|
|
|
<span v-else>(location TBD)</span>
|
|
|
|
|
|
|
|
</span>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div class="mt-1 flex flex-wrap items-center gap-2">
|
|
|
|
|
|
|
|
<a v-if="canJoin(sel) && hasJoinLink(sel)"
|
|
|
|
|
|
|
|
:href="sel.location"
|
|
|
|
|
|
|
|
class="inline-flex items-center px-2 py-1 text-xs font-semibold uppercase tracking-wide text-white bg-blue-600 rounded hover:bg-blue-700">JOIN Online</a>
|
|
|
|
|
|
|
|
<span v-else-if="canJoin(sel)" class="text-xs text-red-600">[missing zoom link]</span>
|
|
|
|
|
|
|
|
<a v-if="canSurvey(sel)"
|
|
|
|
|
|
|
|
:href="'survey.php?s=' + sel.id"
|
|
|
|
|
|
|
|
:class="['inline-flex items-center px-2 py-1 text-xs font-semibold uppercase tracking-wide text-white rounded',
|
|
|
|
|
|
|
|
hasSurveyAnswer(sel) ? 'bg-red-600 hover:bg-red-700' : 'bg-emerald-600 hover:bg-emerald-700']">
|
|
|
|
|
|
|
|
{{ hasSurveyAnswer(sel) ? 'EDIT Survey' : 'TAKE Survey' }}
|
|
|
|
|
|
|
|
</a>
|
|
|
|
<button v-if="!my_host_ids.includes(sel.id)"
|
|
|
|
<button v-if="!my_host_ids.includes(sel.id)"
|
|
|
|
class="mt-2 text-sm text-red-600 hover:underline"
|
|
|
|
class="inline-flex items-center px-2 py-1 text-xs font-semibold uppercase tracking-wide text-white bg-red-600 rounded hover:bg-red-700"
|
|
|
|
@click.prevent="dumpme(sel.id)">Cancel</button>
|
|
|
|
@click.prevent="dumpme(sel.id)">Cancel</button>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<button class="mt-1 text-sm text-blue-600 hover:underline" @click="selectSlot(slot)">Change selection</button>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<button class="mt-1 text-sm text-blue-600 hover:underline" @click="selectSlot(slot)">Change session</button>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div v-else class="mt-1">
|
|
|
|
<div v-else class="mt-1">
|
|
|
|
<emptyslotcard label="Signup" @select="selectSlot(slot)"></emptyslotcard>
|
|
|
|
<emptyslotcard label="Signup" @select="selectSlot(slot)"></emptyslotcard>
|
|
|
|
@ -1187,15 +1312,28 @@ const ActivityList = Vue.component('activitylist', {
|
|
|
|
<div class="flex items-start justify-between gap-3">
|
|
|
|
<div class="flex items-start justify-between gap-3">
|
|
|
|
<div class="flex-1">
|
|
|
|
<div class="flex-1">
|
|
|
|
<div class="font-semibold text-gray-900">{{ a.title }}</div>
|
|
|
|
<div class="font-semibold text-gray-900">{{ a.title }}</div>
|
|
|
|
<div class="text-xs uppercase text-gray-500">{{ mode_string(a) }}</div>
|
|
|
|
<!--<div class="text-xs uppercase text-gray-500">{{ mode_string(a) }}</div>-->
|
|
|
|
<div class="text-sm text-gray-600 mt-1">
|
|
|
|
<div class="text-sm text-gray-600 mt-1">
|
|
|
|
<span v-if="a.mode === 'hybrid'">In person at {{ a.location_irl }} or online</span>
|
|
|
|
<span v-if="a.mode === 'hybrid'">In person<span v-if="a.location_irl"> at {{ a.location_irl }}</span> or online</span>
|
|
|
|
<span v-if="a.mode === 'inperson'">In person at {{ a.location_irl }}</span>
|
|
|
|
<span v-if="a.mode === 'inperson'">In person<span v-if="a.location_irl"> at {{ a.location_irl }}</span><span v-else> location TBD</span></span>
|
|
|
|
<span v-if="a.mode === 'online'">Online</span>
|
|
|
|
<span v-if="a.mode === 'online'">Online</span>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div v-if="a.desc" class="text-sm text-gray-700 mt-2">
|
|
|
|
|
|
|
|
<span v-if="!isDescExpanded(a.id)">
|
|
|
|
|
|
|
|
{{ descPreview(a.desc) }}
|
|
|
|
|
|
|
|
<button v-if="descIsTruncated(a.desc)"
|
|
|
|
|
|
|
|
class="ml-1 text-blue-600 hover:underline"
|
|
|
|
|
|
|
|
@click.prevent="toggleDesc(a.id)">[+ read more]</button>
|
|
|
|
|
|
|
|
</span>
|
|
|
|
|
|
|
|
<span v-else>
|
|
|
|
|
|
|
|
<span v-html="a.desc"></span>
|
|
|
|
|
|
|
|
<button class="ml-1 text-blue-600 hover:underline"
|
|
|
|
|
|
|
|
@click.prevent="toggleDesc(a.id)">[- read less]</button>
|
|
|
|
|
|
|
|
</span>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div class="flex items-center gap-2">
|
|
|
|
<div class="flex items-center gap-2">
|
|
|
|
<button v-if="!my_ses_ids.includes(a.id)"
|
|
|
|
<button v-if="!my_ses_ids.includes(normalizeId(a.id))"
|
|
|
|
class="px-3 py-1 text-sm font-medium text-white bg-blue-600 rounded hover:bg-blue-700"
|
|
|
|
class="px-3 py-1 text-sm font-medium text-white bg-blue-600 rounded hover:bg-blue-700"
|
|
|
|
@click.prevent="chooseSession(a)">
|
|
|
|
@click.prevent="chooseSession(a)">
|
|
|
|
Sign Up
|
|
|
|
Sign Up
|
|
|
|
@ -1216,10 +1354,13 @@ const ActivityList = Vue.component('activitylist', {
|
|
|
|
|
|
|
|
|
|
|
|
<!-- No sessions signed up -->
|
|
|
|
<!-- No sessions signed up -->
|
|
|
|
<div v-if="itineraryview && mysessions_filtered.length === 0" class="mb-6">
|
|
|
|
<div v-if="itineraryview && mysessions_filtered.length === 0" class="mb-6">
|
|
|
|
|
|
|
|
<p v-if="active < 1" class="font-semibold text-gray-800">Loading your itinerary...</p>
|
|
|
|
|
|
|
|
<template v-else>
|
|
|
|
<p class="font-semibold text-gray-800">It looks like you haven't signed up for any sessions yet!</p>
|
|
|
|
<p class="font-semibold text-gray-800">It looks like you haven't signed up for any sessions yet!</p>
|
|
|
|
<p class="mt-2 text-sm text-blue-600">
|
|
|
|
<p class="mt-2 text-sm text-blue-600">
|
|
|
|
Go to the <a class="underline hover:text-blue-800" href="allsessions.php">Sessions List</a> to sign up.
|
|
|
|
Go to the <a class="underline hover:text-blue-800" href="allsessions.php">Sessions List</a> to sign up.
|
|
|
|
</p>
|
|
|
|
</p>
|
|
|
|
|
|
|
|
</template>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- Simple full list for All Sessions -->
|
|
|
|
<!-- Simple full list for All Sessions -->
|
|
|
|
@ -1232,6 +1373,10 @@ const ActivityList = Vue.component('activitylist', {
|
|
|
|
<div>
|
|
|
|
<div>
|
|
|
|
<div class="text-sm text-gray-500">{{ $root.$dj(a.starttime).format('h:mma') }} - {{ addTime(a.starttime, a.length) }}</div>
|
|
|
|
<div class="text-sm text-gray-500">{{ $root.$dj(a.starttime).format('h:mma') }} - {{ addTime(a.starttime, a.length) }}</div>
|
|
|
|
<h4 class="text-lg font-semibold text-gray-900">{{ a.title }}</h4>
|
|
|
|
<h4 class="text-lg font-semibold text-gray-900">{{ a.title }}</h4>
|
|
|
|
|
|
|
|
<div class="text-sm text-gray-600">
|
|
|
|
|
|
|
|
<span v-if="hoststr(a.id)">{{ hoststr(a.id) }}</span>
|
|
|
|
|
|
|
|
<i v-else>no host registered</i>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
<p class="text-sm text-gray-700 mt-1" v-html="a.desc"></p>
|
|
|
|
<p class="text-sm text-gray-700 mt-1" v-html="a.desc"></p>
|
|
|
|
<p class="text-xs uppercase text-gray-500 mt-2">{{ mode_string(a) }}</p>
|
|
|
|
<p class="text-xs uppercase text-gray-500 mt-2">{{ mode_string(a) }}</p>
|
|
|
|
<p class="text-sm text-gray-600" v-if="a.location_irl">Location: {{ a.location_irl }}</p>
|
|
|
|
<p class="text-sm text-gray-600" v-if="a.location_irl">Location: {{ a.location_irl }}</p>
|
|
|
|
@ -1450,6 +1595,7 @@ const ActivityEditorList = Vue.component('activityeditorlist', {
|
|
|
|
reversed: false,
|
|
|
|
reversed: false,
|
|
|
|
selectedIndex: 0,
|
|
|
|
selectedIndex: 0,
|
|
|
|
editingId: null,
|
|
|
|
editingId: null,
|
|
|
|
|
|
|
|
dayFilter: "all",
|
|
|
|
// filters
|
|
|
|
// filters
|
|
|
|
show_filters: "all",
|
|
|
|
show_filters: "all",
|
|
|
|
filters: {
|
|
|
|
filters: {
|
|
|
|
@ -1473,9 +1619,27 @@ const ActivityEditorList = Vue.component('activityeditorlist', {
|
|
|
|
this.$root.fetch_menus();
|
|
|
|
this.$root.fetch_menus();
|
|
|
|
this.fetchAll();
|
|
|
|
this.fetchAll();
|
|
|
|
window.addEventListener("keydown", this.onKey);
|
|
|
|
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() {
|
|
|
|
beforeDestroy() {
|
|
|
|
window.removeEventListener("keydown", this.onKey);
|
|
|
|
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: {
|
|
|
|
methods: {
|
|
|
|
async fetchAll() {
|
|
|
|
async fetchAll() {
|
|
|
|
@ -1574,6 +1738,13 @@ const ActivityEditorList = Vue.component('activityeditorlist', {
|
|
|
|
ff = ff.filter((x) => x.searchable.includes(q));
|
|
|
|
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
|
|
|
|
// AY filter example
|
|
|
|
const ay = this.$root.settings["default_ay"];
|
|
|
|
const ay = this.$root.settings["default_ay"];
|
|
|
|
if (ay && this.$root.ay_menu && this.$root.ay_menu[ay]) {
|
|
|
|
if (ay && this.$root.ay_menu && this.$root.ay_menu[ay]) {
|
|
|
|
@ -1604,7 +1775,7 @@ const ActivityEditorList = Vue.component('activityeditorlist', {
|
|
|
|
} else if (e.key === "ArrowUp") {
|
|
|
|
} else if (e.key === "ArrowUp") {
|
|
|
|
this.selectedIndex = Math.max(0, this.selectedIndex - 1);
|
|
|
|
this.selectedIndex = Math.max(0, this.selectedIndex - 1);
|
|
|
|
e.preventDefault();
|
|
|
|
e.preventDefault();
|
|
|
|
} else if (e.key === "Enter" || e.key.toLowerCase() === "e") {
|
|
|
|
} else if (e.key === "Enter") {
|
|
|
|
const a = this.activitiesFiltered[this.selectedIndex];
|
|
|
|
const a = this.activitiesFiltered[this.selectedIndex];
|
|
|
|
this.startEdit(a.id);
|
|
|
|
this.startEdit(a.id);
|
|
|
|
e.preventDefault();
|
|
|
|
e.preventDefault();
|
|
|
|
@ -1641,6 +1812,20 @@ const ActivityEditorList = Vue.component('activityeditorlist', {
|
|
|
|
activitiesFiltered() {
|
|
|
|
activitiesFiltered() {
|
|
|
|
return this.filtered(this.activities);
|
|
|
|
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() {
|
|
|
|
groupedByDay() {
|
|
|
|
return _.groupBy(this.activitiesFiltered, (x) => this.month_year(x.starttime));
|
|
|
|
return _.groupBy(this.activitiesFiltered, (x) => this.month_year(x.starttime));
|
|
|
|
},
|
|
|
|
},
|
|
|
|
@ -1656,6 +1841,10 @@ const ActivityEditorList = Vue.component('activityeditorlist', {
|
|
|
|
/>
|
|
|
|
/>
|
|
|
|
<button @click="setsort('starttime')" class="border px-2 py-1 rounded">Sort: Start</button>
|
|
|
|
<button @click="setsort('starttime')" class="border px-2 py-1 rounded">Sort: Start</button>
|
|
|
|
<button @click="setsort('title')" class="border px-2 py-1 rounded">Sort: Title</button>
|
|
|
|
<button @click="setsort('title')" class="border px-2 py-1 rounded">Sort: Title</button>
|
|
|
|
|
|
|
|
<select v-model="dayFilter" class="border px-2 py-1 rounded">
|
|
|
|
|
|
|
|
<option value="all">All days</option>
|
|
|
|
|
|
|
|
<option v-for="d in availableDays" :key="d.key" :value="d.key">{{ d.label }}</option>
|
|
|
|
|
|
|
|
</select>
|
|
|
|
<span class="text-sm text-gray-600">↑/↓ select • Enter/E edit • Esc close</span>
|
|
|
|
<span class="text-sm text-gray-600">↑/↓ select • Enter/E edit • Esc close</span>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
@ -2025,14 +2214,30 @@ const AskSurvey = Vue.component('asksurvey', {
|
|
|
|
function(r2) {
|
|
|
|
function(r2) {
|
|
|
|
self.questions = 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() },
|
|
|
|
mounted: function() { this.start() },
|
|
|
|
template: `<div>
|
|
|
|
template: `<div class="survey-page bg-white p-4 rounded shadow">
|
|
|
|
<h3 v-if="questions.length">{{ questions[0].title }}</h3>
|
|
|
|
<div v-if="questions.length" class="mb-4">
|
|
|
|
|
|
|
|
<span class="text-gray-600 font-medium">Session name:</span>
|
|
|
|
|
|
|
|
<span class="text-gray-900 font-semibold">[{{ questions[0].title }}]</span>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
<div class="session-survey" v-for="q in questions">
|
|
|
|
<div class="session-survey" v-for="q in questions">
|
|
|
|
<number-question v-if="q['type']=='2'" :qq="q"></number-question>
|
|
|
|
<number-question v-if="q['type']=='2'" :qq="q"></number-question>
|
|
|
|
<essay-question v-if="q['type']=='1'" :qq="q"></essay-question>
|
|
|
|
<essay-question v-if="q['type']=='1'" :qq="q"></essay-question>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div class="mt-4">
|
|
|
|
|
|
|
|
<button class="inline-flex items-center px-3 py-2 text-sm font-semibold uppercase tracking-wide text-white bg-blue-600 rounded hover:bg-blue-700"
|
|
|
|
|
|
|
|
@click.prevent="saveAll">Save</button>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
</div>`
|
|
|
|
</div>`
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|