jan26 prepped
This commit is contained in:
parent
4ceb28e7ef
commit
411a2a96d0
|
|
@ -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); }
|
||||
|
||||
|
|
|
|||
1
edit.php
1
edit.php
|
|
@ -3,5 +3,4 @@
|
|||
$MY_TITLE = "Edit Sessions";
|
||||
$MY_CRUMB = "Edit";
|
||||
$CONTENT = '<activityeditorlist></activityeditorlist>';
|
||||
$XTRAJS = 'js/editor.js';
|
||||
include 'layout.php';
|
||||
|
|
|
|||
125
js/dir_app.js
125
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
|
||||
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, '<br>');
|
||||
},
|
||||
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', {
|
|||
<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="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.mode">· {{ sel.mode }}</span>
|
||||
</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>
|
||||
|
||||
|
|
@ -1247,7 +1281,10 @@ const ActivityList = Vue.component('activitylist', {
|
|||
<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 bg-emerald-600 rounded hover:bg-emerald-700">TAKE Survey</a>
|
||||
: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)"
|
||||
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>
|
||||
|
|
@ -1336,6 +1373,10 @@ const ActivityList = Vue.component('activitylist', {
|
|||
<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>
|
||||
<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-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>
|
||||
|
|
@ -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', {
|
|||
/>
|
||||
<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>
|
||||
<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>
|
||||
</div>
|
||||
|
||||
|
|
@ -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: `<div>
|
||||
<h3 v-if="questions.length">{{ questions[0].title }}</h3>
|
||||
template: `<div class="survey-page bg-white p-4 rounded shadow">
|
||||
<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">
|
||||
<number-question v-if="q['type']=='2'" :qq="q"></number-question>
|
||||
<essay-question v-if="q['type']=='1'" :qq="q"></essay-question>
|
||||
</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>`
|
||||
});
|
||||
|
||||
|
|
|
|||
20
style.css
20
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;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue