diff --git a/dir_api.php b/dir_api.php index 3edaae7..831c1af 100644 --- a/dir_api.php +++ b/dir_api.php @@ -58,6 +58,23 @@ $conf_query = "SELECT semester,date1,date2,title FROM conf_conferences cc JOIN c $CONF = single_row_select($conf_query,0); $OPTIONS['conf'] = $CONF; +// Optional date-range helpers for API endpoints +function api_begin_end() { + global $AY; + $begin = isset($_REQUEST['begin']) ? $_REQUEST['begin'] : $AY['begin']; + $end = isset($_REQUEST['end']) ? $_REQUEST['end'] : $AY['end']; + // Normalize end to end-of-day if only a date is provided + if ($end && preg_match('/^\d{4}-\d{2}-\d{2}$/', $end)) { $end .= ' 23:59:59'; } + return array($begin, $end); +} + +function api_date_clause($column = 'c.starttime') { + if (isset($_REQUEST['all']) && $_REQUEST['all']) { return '1=1'; } + list($begin, $end) = api_begin_end(); + $b = ok($begin); $e = ok($end); + return "$column BETWEEN CAST('$b' AS DATE) AND CAST('$e' AS DATETIME)"; +} + function name_to_lc($fn,$ln) { $fn = str_replace( array( '-', ' '), '', strtolower($fn) ); @@ -603,7 +620,8 @@ if (isset($_REQUEST['a']) && $_REQUEST['a']=='add/letter') { update_welcome_lett // GET LIST OF ALL SESSIONS / WORKSHOPS / EVENTS // function get_sessions() { global $c, $AY; - return multi_row_select("SELECT c.id,c.title,c.desc,c.length,c.starttime,c.track,c.location,c.location_irl,c.mode,c.gets_survey,c.category,c.parent,c.recording,c.instructions,c.image_url,c.is_flex_approved,c.cal_uid,sst.type AS typeStr, sst.id AS type, GROUP_CONCAT(ctg.tag) AS tags FROM conf_sessions c LEFT JOIN conf_sessiontypes sst ON c.type=sst.id LEFT JOIN conf_tagmember ct ON c.id=ct.session LEFT JOIN conf_tags ctg ON ctg.id=ct.tag WHERE c.starttime BETWEEN CAST('{$AY['begin']}' AS DATE) AND CAST('{$AY['end']}' AS DATETIME) GROUP BY c.id ORDER BY c.track, c.starttime;",0); } + $date_clause = api_date_clause('c.starttime'); + return multi_row_select("SELECT c.id,c.title,c.desc,c.length,c.starttime,c.track,c.location,c.location_irl,c.mode,c.gets_survey,c.category,c.parent,c.recording,c.instructions,c.image_url,c.is_flex_approved,c.cal_uid,sst.type AS typeStr, sst.id AS type, GROUP_CONCAT(ctg.tag) AS tags FROM conf_sessions c LEFT JOIN conf_sessiontypes sst ON c.type=sst.id LEFT JOIN conf_tagmember ct ON c.id=ct.session LEFT JOIN conf_tags ctg ON ctg.id=ct.tag WHERE $date_clause GROUP BY c.id ORDER BY c.track, c.starttime;",0); } if (isset($_GET['a']) && $_GET['a'] == 'get/sessions') { echo json_encode(get_sessions()); exit(); } @@ -622,7 +640,8 @@ function multi_row_1d($qry) { global $c; // GET LIST OF ALL SESSIONS + HOSTS // function get_ses_hosts() { global $c, $AY; - 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.starttime BETWEEN CAST('{$AY['begin']}' AS DATE) AND CAST('{$AY['end']}' AS DATETIME) 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); } //return multi_row_1d("select DISTINCT(u.email) 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.starttime BETWEEN CAST('{$AY['begin']}' AS DATE) AND CAST('{$AY['end']}' AS DATETIME) ORDER BY u.name;"); } @@ -665,8 +684,9 @@ function get_rosters() { if (isset($_REQUEST['id'])) { $ID = ok($_REQUEST['id']); $where = "AND s.id={$ID}"; } + $date_clause = api_date_clause('s.starttime'); return multi_row_select( - "SELECT i.user, i.session, u.goo, u.email, u.name, s.id AS sesid FROM conf_signups as i LEFT JOIN conf_users as u ON i.user=u.id RIGHT JOIN conf_sessions as s ON i.session=s.id WHERE s.starttime BETWEEN CAST('{$AY['begin']}' AS DATE) AND CAST('{$AY['end']}' AS DATETIME) {$where} ORDER BY sesid;",0,$c); } + "SELECT i.user, i.session, u.goo, u.email, u.name, s.id AS sesid FROM conf_signups as i LEFT JOIN conf_users as u ON i.user=u.id RIGHT JOIN conf_sessions as s ON i.session=s.id WHERE $date_clause {$where} ORDER BY sesid;",0,$c); } if (isset($_GET['a']) && $_GET['a'] == 'get/rosters') { echo json_encode(get_rosters()); exit(); } @@ -680,8 +700,9 @@ function get_signups() { if (isset($_REQUEST['id'])) { $ID = ok($_REQUEST['id']); $where = "WHERE s.id={$ID}"; } + $date_clause = api_date_clause('s.starttime'); return multi_row_select( - "SELECT i.id, i.user, i.session, i.timestamp, i.certified_at, i.badged_at, i.not_flex FROM conf_signups AS i JOIN conf_sessions AS s ON i.session=s.id WHERE s.starttime BETWEEN CAST('{$AY['begin']}' AS DATE) AND CAST('{$AY['end']}' AS DATETIME) {$where} ORDER BY i.id DESC;",0,$c); } + "SELECT i.id, i.user, i.session, i.timestamp, i.certified_at, i.badged_at, i.not_flex FROM conf_signups AS i JOIN conf_sessions AS s ON i.session=s.id WHERE $date_clause {$where} ORDER BY i.id DESC;",0,$c); } if (isset($_GET['a']) && $_GET['a'] == 'get/signups') { echo json_encode(get_signups()); exit(); } @@ -744,7 +765,8 @@ if (isset($_GET['a'])&& preg_match('/get\/questions\/(\d+)$/', $_GET['a'], $matc // ALL Survey ANSWERS function get_answers_all() { global $c; - return multi_row_select( "SELECT ses.id AS ses_id, ses.title as s_title, ses.starttime, qq.id as q_id, qq.question as question, qq.type as type, ans.answer AS answer FROM conf_sessions AS ses JOIN conf_signups as sup on ses.id = sup.session JOIN conf_users as cus on cus.id = sup.user JOIN conf_q_set as qset on ses.gets_survey = qset.q_set RIGHT JOIN conf_questions as qq on qset.question = qq.id LEFT OUTER JOIN conf_answers as ans on ans.user = sup.user AND ans.question = qq.id AND ans.session = ses.id WHERE ans.answer is not null ORDER BY ses.starttime, ses.track asc, qset.order", 0); } + $date_clause = api_date_clause('ses.starttime'); + return multi_row_select( "SELECT ses.id AS ses_id, ses.title as s_title, ses.starttime, qq.id as q_id, qq.question as question, qq.type as type, ans.answer AS answer FROM conf_sessions AS ses JOIN conf_signups as sup on ses.id = sup.session JOIN conf_users as cus on cus.id = sup.user JOIN conf_q_set as qset on ses.gets_survey = qset.q_set RIGHT JOIN conf_questions as qq on qset.question = qq.id LEFT OUTER JOIN conf_answers as ans on ans.user = sup.user AND ans.question = qq.id AND ans.session = ses.id WHERE $date_clause AND ans.answer is not null ORDER BY ses.starttime, ses.track asc, qset.order", 0); } if (isset($_GET['a']) && $_GET['a'] == 'get/answers/all') { echo json_encode(get_answers_all()); exit(); } diff --git a/js/dir_app.js b/js/dir_app.js index 9892127..7ca187a 100644 --- a/js/dir_app.js +++ b/js/dir_app.js @@ -1,4 +1,4 @@ -var PROD = 0 +var PROD = 1 if (PROD && location.protocol !== 'https:') { @@ -2056,53 +2056,91 @@ const ActivityInfoReport2 = Vue.component('activityinforeport2', { watch: { }, template: `
-
-
Title:
-
{{ a.title }}
-
- - -
-
Date:
-
{{ $root.$dj(a.starttime).format('YYYY MMM DD dd h:mma') }}
-
- - -
-
Mode / Location:
-
- {{ mode_string(a) }} -
{{ a.location }}
-
{{ a.location_irl }}
-
-
- - -
-
Description:
-
-
- - -
-
Hosts:
-
{{ host }}
-
- - -
-
Attendees:
-
{{ user }}
-
- - -
-
Survey Results:
-
-
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Title:{{ a.title }}
Date:{{ $root.$dj(a.starttime).format('YYYY MMM DD dd h:mma') }}
Mode / Location: + {{ mode_string(a) }} +
{{ a.location }}
+
{{ 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:{{ a.title }}
Date:{{ $root.$dj(a.starttime).format('YYYY MMM DD dd h:mma') }}
Mode / Location: + {{ mode_string(a) }} +
{{ a.location }}
+
{{ a.location_irl }}
+
Description:
Hosts:{{ host }}
Attendees:{{ user }}
Survey Results:
+
` +}) + @@ -2121,63 +2159,83 @@ const ActivityInfoReport2 = Vue.component('activityinforeport2', { const ActivityReport = Vue.component('activityreport', { props: [ 'which' ], data: function () { - return { activities:[],hosts:[], hosts_by_sesid:[], everyone:[], questions:[], answers:{}, answers2:{}, rosters:[] } }, + return { activities:[],hosts:[], hosts_by_sesid:[], everyone:[], questions:[], answers:{}, answers2:{}, rosters:[], + semesters:[], selectedSemesterKey:'', q:'' } }, mounted: function() { var self = this - basic_get('dir_api.php?a=get/sessions', function(r2) { - self.activities = _.sortBy(r2,function(x) { return x.starttime } ); self.$forceUpdate() - if (self.which=='sp22') { - var start = dayjs('2022-01-26') - var end = dayjs('2022-01-29') - self.activities = self.activities.filter( function(item,index) { - this_time = dayjs(item.starttime) - return this_time.isBefore(end) && start.isBefore(this_time) } ) - } - if (self.which=='fa22') { /* UPDATE */ - var start = dayjs('2022-08-17') - var end = dayjs('2022-08-20') - self.activities = self.activities.filter( function(item,index) { - this_time = dayjs(item.starttime) - return this_time.isBefore(end) && start.isBefore(this_time) } ) - } - if (self.which && String(self.which).match( /^\d+$/ )) { - self.activities = self.activities.filter( function(item,index) { - return item.id == self.which } ) - } - - 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 } ) - } ) - basic_get('dir_api.php?a=get/rosters', function(r2) { - self.rosters = _.groupBy(r2,function(x) { return x.sesid } ) - } ) - basic_get('dir_api.php?a=get/allhosts', function(r2) { - self.hosts = r2 - setTimeout(function () { self.hosts = r2; self.$forceUpdate() }, 750); - self.active += 1; - } ) + self.buildSemesters() + 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 }) - basic_get('dir_api.php?a=get/answers/all', function(r2) { - var organized = _.groupBy(r2, function(x) { return x.ses_id; } ) - // TODO are answers and answers2 the same? - _.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() - } ) }, 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 (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&${q}`, function(r2) { + self.hosts_by_sesid = _.groupBy(r2,function(x) { return x.id } ) + }) + basic_get(`dir_api.php?a=get/rosters&${q}`, function(r2) { + self.rosters = _.groupBy(r2,function(x) { return x.sesid } ) + }) + basic_get(`dir_api.php?a=get/answers/all&${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 }, '') @@ -2205,11 +2263,51 @@ const ActivityReport = Vue.component('activityreport', { }, }, 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 (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 (!q) return true + const hay = (a.title + ' ' + (a.desc||'') + ' ' + (this.hoststr(a.id)||'')).toLowerCase() + return hay.indexOf(q) !== -1 + }) + } }, - watch: { }, - template: `
-
- + watch: { + selectedSemesterKey() { this.fetchRange() } + }, + template: `
+ +
+
+ + +
+
Showing {{ filteredActivities.length }} session(s)
+
+ +
+
` }) diff --git a/layout.php b/layout.php index daa31c5..81312f9 100644 --- a/layout.php +++ b/layout.php @@ -28,7 +28,10 @@ $MOD_DATE = file_exists(__FILE__) ? date("F d Y H:i.", filemtime(__FILE__)) : "( -
+ +
- - +