diff --git a/dir_api.php b/dir_api.php index 53075a2..3073afa 100644 --- a/dir_api.php +++ b/dir_api.php @@ -649,6 +649,9 @@ function get_ses_hosts() { global $c, $AY; $ID = ok($_REQUEST['id']); return multi_row_select("select s.id, s.title, s.starttime, u.name, u.email, u.id AS hostid from conf_sessions as s LEFT OUTER JOIN conf_hosts as h ON h.session=s.id LEFT OUTER JOIN conf_users AS u ON h.host=u.id WHERE s.id='{$ID}' ORDER BY u.name;",1); } + if (isset($_REQUEST['all']) && $_REQUEST['all'] == '1') { + return multi_row_select("select s.id, s.title, s.starttime, u.name, u.email, u.id AS hostid from conf_sessions as s LEFT OUTER JOIN conf_hosts as h ON h.session=s.id LEFT OUTER JOIN conf_users AS u ON h.host=u.id ORDER BY u.name;",1); + } $date_clause = api_date_clause('s.starttime'); return multi_row_select("select s.id, s.title, s.starttime, u.name, u.email, u.id AS hostid from conf_sessions as s LEFT OUTER JOIN conf_hosts as h ON h.session=s.id LEFT OUTER JOIN conf_users AS u ON h.host=u.id WHERE $date_clause ORDER BY u.name;",1); } diff --git a/edit.php b/edit.php index 47231de..5235bcd 100644 --- a/edit.php +++ b/edit.php @@ -3,5 +3,4 @@ $MY_TITLE = "Edit Sessions"; $MY_CRUMB = "Edit"; $CONTENT = ''; -$XTRAJS = 'js/editor.js'; include 'layout.php'; diff --git a/js/dir_app.js b/js/dir_app.js index c675f25..0db360f 100644 --- a/js/dir_app.js +++ b/js/dir_app.js @@ -28,10 +28,18 @@ function init_file_dropzone(parameter_name) { function parsesqltime(mysqldate) { // 2021-01-29 09:00:00 - if (! mysqldate) { return 0 } - var field = mysqldate.match(/^(\d\d\d\d)\-(\d+)\-(\d+)[T|\s](\d+)\:(\d+)\:(\d+)$/) - var mydate = new Date(field[1], field[2] - 1 , field[3], field[4], field[5], field[6]) - return mydate } + 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)) } @@ -873,7 +881,8 @@ const ActivityList = Vue.component('activitylist', { 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:{}, } }, + cancelingIds:[], timeslotConfig:null, expandedDesc:{}, collapsedDays:{}, + surveyAnswersBySession: {}, } }, mounted: function() { this.fetch_myevents() }, @@ -983,10 +992,24 @@ const ActivityList = Vue.component('activitylist', { am_editing: function(id) { return 0 }, fetch_myevents: function() { 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 } ) } ) + 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 @@ -999,7 +1022,9 @@ const ActivityList = Vue.component('activitylist', { self.options = r2.options self.conference = r2.conference self.ay = r2.ay - self.hosts_by_sesid = _.groupBy(r2.hostbysession,function(x) { return x.id } ) + 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 ) @@ -1063,6 +1088,10 @@ const ActivityList = Vue.component('activitylist', { }, 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'); @@ -1071,6 +1100,11 @@ const ActivityList = Vue.component('activitylist', { 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; @@ -1221,11 +1255,11 @@ const ActivityList = Vue.component('activitylist', {
{{ sel.title }}
- {{ sel.audience }} meeting + {{ sel.audience }} meeting at {{ sel.location }} · {{ sel.mode }}
-
{{ sel.notes }}
+
@@ -1247,7 +1281,10 @@ const ActivityList = Vue.component('activitylist', { [missing zoom link] TAKE Survey + :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' }} + @@ -1336,6 +1373,10 @@ const ActivityList = Vue.component('activitylist', {
{{ $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 }}

@@ -1554,6 +1595,7 @@ const ActivityEditorList = Vue.component('activityeditorlist', { reversed: false, selectedIndex: 0, editingId: null, + dayFilter: "all", // filters show_filters: "all", filters: { @@ -1577,9 +1619,27 @@ const ActivityEditorList = Vue.component('activityeditorlist', { 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() { @@ -1678,6 +1738,13 @@ const ActivityEditorList = Vue.component('activityeditorlist', { 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]) { @@ -1708,7 +1775,7 @@ const ActivityEditorList = Vue.component('activityeditorlist', { } else if (e.key === "ArrowUp") { this.selectedIndex = Math.max(0, this.selectedIndex - 1); e.preventDefault(); - } else if (e.key === "Enter" || e.key.toLowerCase() === "e") { + } else if (e.key === "Enter") { const a = this.activitiesFiltered[this.selectedIndex]; this.startEdit(a.id); e.preventDefault(); @@ -1745,6 +1812,20 @@ const ActivityEditorList = Vue.component('activityeditorlist', { 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)); }, @@ -1760,6 +1841,10 @@ const ActivityEditorList = Vue.component('activityeditorlist', { /> + ↑/↓ select • Enter/E edit • Esc close
@@ -2129,14 +2214,30 @@ const AskSurvey = Vue.component('asksurvey', { 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: `
-

{{ questions[0].title }}

+ template: `
+
+ Session name: + [{{ questions[0].title }}] +
+
+ +
` }); diff --git a/style.css b/style.css index 8ed2a36..e7b36df 100644 --- a/style.css +++ b/style.css @@ -64,3 +64,23 @@ ol { .slotpanel-leave { max-height: 600px; } + +/* Survey page label tone */ +.survey-page .question, +.survey-page label, +.survey-page .form-check-label { + color: #4b5563; +} + +/* Survey textarea should span full width */ +.survey-page textarea { + width: 100%; + border: 1px solid #d1d5db; + border-radius: 6px; + padding: 8px; + box-sizing: border-box; +} + +.survey-page .session-survey { + margin-bottom: 12px; +}