better report
This commit is contained in:
parent
fbba9edf63
commit
3986f90a83
32
dir_api.php
32
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);
|
$CONF = single_row_select($conf_query,0);
|
||||||
$OPTIONS['conf'] = $CONF;
|
$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) {
|
function name_to_lc($fn,$ln) {
|
||||||
$fn = str_replace( array( '-', ' '), '', strtolower($fn) );
|
$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
|
// GET LIST OF ALL SESSIONS / WORKSHOPS / EVENTS
|
||||||
//
|
//
|
||||||
function get_sessions() { global $c, $AY;
|
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(); }
|
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
|
// GET LIST OF ALL SESSIONS + HOSTS
|
||||||
//
|
//
|
||||||
function get_ses_hosts() { global $c, $AY;
|
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;"); }
|
//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'])) {
|
if (isset($_REQUEST['id'])) {
|
||||||
$ID = ok($_REQUEST['id']);
|
$ID = ok($_REQUEST['id']);
|
||||||
$where = "AND s.id={$ID}"; }
|
$where = "AND s.id={$ID}"; }
|
||||||
|
$date_clause = api_date_clause('s.starttime');
|
||||||
return multi_row_select(
|
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(); }
|
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'])) {
|
if (isset($_REQUEST['id'])) {
|
||||||
$ID = ok($_REQUEST['id']);
|
$ID = ok($_REQUEST['id']);
|
||||||
$where = "WHERE s.id={$ID}"; }
|
$where = "WHERE s.id={$ID}"; }
|
||||||
|
$date_clause = api_date_clause('s.starttime');
|
||||||
return multi_row_select(
|
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(); }
|
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
|
// ALL Survey ANSWERS
|
||||||
function get_answers_all() { global $c;
|
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(); }
|
if (isset($_GET['a']) && $_GET['a'] == 'get/answers/all') { echo json_encode(get_answers_all()); exit(); }
|
||||||
|
|
||||||
|
|
|
||||||
280
js/dir_app.js
280
js/dir_app.js
|
|
@ -1,4 +1,4 @@
|
||||||
var PROD = 0
|
var PROD = 1
|
||||||
|
|
||||||
|
|
||||||
if (PROD && location.protocol !== 'https:') {
|
if (PROD && location.protocol !== 'https:') {
|
||||||
|
|
@ -2056,53 +2056,91 @@ const ActivityInfoReport2 = Vue.component('activityinforeport2', {
|
||||||
watch: { },
|
watch: { },
|
||||||
template: `<div class="bg-white rounded-md shadow-md p-6 text-sm text-gray-800 space-y-4">
|
template: `<div class="bg-white rounded-md shadow-md p-6 text-sm text-gray-800 space-y-4">
|
||||||
<!-- Title -->
|
<!-- Title -->
|
||||||
<div class="grid grid-cols-5 gap-4">
|
<table class="w-full border-collapse text-sm">
|
||||||
<div class="font-medium col-span-1">Title:</div>
|
<tbody>
|
||||||
<div class="col-span-4 font-semibold">{{ a.title }}</div>
|
<tr>
|
||||||
</div>
|
<th class="text-left align-top pr-4 py-1 w-32">Title:</th>
|
||||||
|
<td class="py-1 font-semibold">{{ a.title }}</td>
|
||||||
<!-- Date -->
|
</tr>
|
||||||
<div class="grid grid-cols-5 gap-4">
|
<tr>
|
||||||
<div class="font-medium col-span-1">Date:</div>
|
<th class="text-left align-top pr-4 py-1">Date:</th>
|
||||||
<div class="col-span-4">{{ $root.$dj(a.starttime).format('YYYY MMM DD dd h:mma') }}</div>
|
<td class="py-1">{{ $root.$dj(a.starttime).format('YYYY MMM DD dd h:mma') }}</td>
|
||||||
</div>
|
</tr>
|
||||||
|
<tr>
|
||||||
<!-- Mode / Location -->
|
<th class="text-left align-top pr-4 py-1">Mode / Location:</th>
|
||||||
<div class="grid grid-cols-5 gap-4">
|
<td class="py-1">
|
||||||
<div class="font-medium col-span-1">Mode / Location:</div>
|
|
||||||
<div class="col-span-4">
|
|
||||||
{{ mode_string(a) }}
|
{{ mode_string(a) }}
|
||||||
<div v-if="a.location" class="text-gray-600">{{ a.location }}</div>
|
<div v-if="a.location" class="text-gray-600">{{ a.location }}</div>
|
||||||
<div v-if="a.location_irl" class="text-gray-600">{{ a.location_irl }}</div>
|
<div v-if="a.location_irl" class="text-gray-600">{{ a.location_irl }}</div>
|
||||||
</div>
|
</td>
|
||||||
</div>
|
</tr>
|
||||||
|
<tr v-if="a.desc">
|
||||||
<!-- Description -->
|
<th class="text-left align-top pr-4 py-1">Description:</th>
|
||||||
<div class="grid grid-cols-5 gap-4">
|
<td class="py-1" v-html="a.desc"></td>
|
||||||
<div class="font-medium col-span-1">Description:</div>
|
</tr>
|
||||||
<div class="col-span-4" v-html="a.desc"></div>
|
<tr v-if="host">
|
||||||
</div>
|
<th class="text-left align-top pr-4 py-1">Hosts:</th>
|
||||||
|
<td class="py-1">{{ host }}</td>
|
||||||
<!-- Hosts -->
|
</tr>
|
||||||
<div class="grid grid-cols-5 gap-4">
|
<tr>
|
||||||
<div class="font-medium col-span-1">Hosts:</div>
|
<th class="text-left align-top pr-4 py-1">Attendees:</th>
|
||||||
<div class="col-span-4">{{ host }}</div>
|
<td class="py-1">{{ user }}</td>
|
||||||
</div>
|
</tr>
|
||||||
|
<tr v-if="survey">
|
||||||
<!-- Attendees -->
|
<th class="text-left align-top pr-4 py-1">Survey Results:</th>
|
||||||
<div class="grid grid-cols-5 gap-4">
|
<td class="py-1" v-html="survey"></td>
|
||||||
<div class="font-medium col-span-1">Attendees:</div>
|
</tr>
|
||||||
<div class="col-span-4">{{ user }}</div>
|
</tbody>
|
||||||
</div>
|
</table>
|
||||||
|
|
||||||
<!-- Survey Results -->
|
|
||||||
<div class="grid grid-cols-5 gap-4">
|
|
||||||
<div class="font-medium col-span-1">Survey Results:</div>
|
|
||||||
<div class="col-span-4" v-html="survey"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
` })
|
` })
|
||||||
|
|
||||||
|
// 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: `<div class="bg-white rounded-md shadow-md p-4 text-sm text-gray-800 space-y-2">
|
||||||
|
<table class="w-full border-collapse">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<th class="text-left align-top pr-4 py-1 w-28">Title:</th>
|
||||||
|
<td class="py-1 font-semibold">{{ a.title }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th class="text-left align-top pr-4 py-1">Date:</th>
|
||||||
|
<td class="py-1">{{ $root.$dj(a.starttime).format('YYYY MMM DD dd h:mma') }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th class="text-left align-top pr-4 py-1">Mode / Location:</th>
|
||||||
|
<td class="py-1">
|
||||||
|
{{ mode_string(a) }}
|
||||||
|
<div v-if="a.location">{{ a.location }}</div>
|
||||||
|
<div v-if="a.location_irl">{{ a.location_irl }}</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr v-if="a.desc">
|
||||||
|
<th class="text-left align-top pr-4 py-1">Description:</th>
|
||||||
|
<td class="py-1" v-html="a.desc"></td>
|
||||||
|
</tr>
|
||||||
|
<tr v-if="host">
|
||||||
|
<th class="text-left align-top pr-4 py-1">Hosts:</th>
|
||||||
|
<td class="py-1">{{ host }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th class="text-left align-top pr-4 py-1">Attendees:</th>
|
||||||
|
<td class="py-1">{{ user }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr v-if="survey">
|
||||||
|
<th class="text-left align-top pr-4 py-1">Survey Results:</th>
|
||||||
|
<td class="py-1" v-html="survey"></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>`
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -2121,63 +2159,83 @@ const ActivityInfoReport2 = Vue.component('activityinforeport2', {
|
||||||
const ActivityReport = Vue.component('activityreport', {
|
const ActivityReport = Vue.component('activityreport', {
|
||||||
props: [ 'which' ],
|
props: [ 'which' ],
|
||||||
data: function () {
|
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() {
|
mounted: function() {
|
||||||
var self = this
|
var self = this
|
||||||
basic_get('dir_api.php?a=get/sessions', function(r2) {
|
self.buildSemesters()
|
||||||
self.activities = _.sortBy(r2,function(x) { return x.starttime } ); self.$forceUpdate()
|
self.fetchRange()
|
||||||
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;
|
|
||||||
} )
|
|
||||||
basic_get('dir_api.php?a=get/names', function(r2) {
|
basic_get('dir_api.php?a=get/names', function(r2) {
|
||||||
self.everyone = []
|
self.everyone = []
|
||||||
_.each( r2.users, function(x) { self.everyone[x.id] = x.name } ) } )
|
_.each( r2.users, function(x) { self.everyone[x.id] = x.name } ) } )
|
||||||
basic_get('dir_api.php?a=get/questions', function(r2) {
|
basic_get('dir_api.php?a=get/questions', function(r2) {
|
||||||
self.questions = r2 })
|
self.questions = r2 })
|
||||||
basic_get('dir_api.php?a=get/answers/all', function(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 (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; } )
|
var organized = _.groupBy(r2, function(x) { return x.ses_id; } )
|
||||||
// TODO are answers and answers2 the same?
|
self.answers = {}
|
||||||
|
self.answers2 = {}
|
||||||
_.each( organized, function(val,key,lis) { self.answers[key] = _.groupBy( val, function(y) { return y.q_id; }); } )
|
_.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" ) } )
|
_.each( self.answers, function(val,key,lis) { self.answers2[key] = _.sortBy( val, "q_id" ) } )
|
||||||
self.$forceUpdate()
|
self.$forceUpdate()
|
||||||
} )
|
})
|
||||||
},
|
},
|
||||||
methods: {
|
|
||||||
hoststr: function(id) {
|
hoststr: function(id) {
|
||||||
var self = this
|
var self = this
|
||||||
return _.reduce( self.hosts_by_sesid[id], function(mem,val) { if (val.name) { return mem + val.name + ", " } return mem }, '')
|
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: {
|
computed: {
|
||||||
|
selectedSemester() {
|
||||||
|
return this.semesters.find(s => s.key === this.selectedSemesterKey) || null
|
||||||
},
|
},
|
||||||
watch: { },
|
filteredActivities() {
|
||||||
template: `<div class="activityreport">
|
const q = (this.q || '').toLowerCase().trim()
|
||||||
<div v-for="a in activities">
|
const sem = this.selectedSemester
|
||||||
<activityinforeport2 :a="a" :host="hoststr(a.id)" :num="usernum(a.id)" :user="userstr(a.id)" :emails="useremails(a.id)" :survey="surveystr(a.id)"></activityinforeport2>
|
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: {
|
||||||
|
selectedSemesterKey() { this.fetchRange() }
|
||||||
|
},
|
||||||
|
template: `<div class="activityreport space-y-4">
|
||||||
|
<!-- Controls -->
|
||||||
|
<div class="bg-white rounded-md shadow p-3 sticky top-16 md:static z-40">
|
||||||
|
<div class="flex flex-col gap-2 md:flex-row md:items-center">
|
||||||
|
<label class="text-sm text-gray-700">Semester
|
||||||
|
<select v-model="selectedSemesterKey" class="ml-2 border rounded px-2 py-1 text-sm">
|
||||||
|
<option :value="''">All</option>
|
||||||
|
<option v-for="s in semesters" :key="s.key" :value="s.key">{{ s.label }}</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
<input type="search" v-model="q" placeholder="Search host or title" class="border rounded px-3 py-1 text-sm w-full md:w-64"/>
|
||||||
|
</div>
|
||||||
|
<div class="text-xs text-gray-500 mt-1">Showing {{ filteredActivities.length }} session(s)</div>
|
||||||
|
</div>
|
||||||
|
<!-- Report Items (table layout for copy/paste) -->
|
||||||
|
<div v-for="a in filteredActivities" :key="a.id">
|
||||||
|
<activityinforeport_table :a="a" :host="hoststr(a.id)" :num="usernum(a.id)" :user="userstr(a.id)" :emails="useremails(a.id)" :survey="surveystr(a.id)"></activityinforeport_table>
|
||||||
</div>
|
</div>
|
||||||
</div>` })
|
</div>` })
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,10 @@ $MOD_DATE = file_exists(__FILE__) ? date("F d Y H:i.", filemtime(__FILE__)) : "(
|
||||||
<link rel="stylesheet" type="text/css" href="style.css" />
|
<link rel="stylesheet" type="text/css" href="style.css" />
|
||||||
</head>
|
</head>
|
||||||
<body class="bg-gray-100 min-h-screen">
|
<body class="bg-gray-100 min-h-screen">
|
||||||
<div class="max-w-7xl mx-auto p-4">
|
<?php // Mobile top nav (fixed)
|
||||||
|
include 'nav-mobile.php';
|
||||||
|
?>
|
||||||
|
<div class="max-w-7xl mx-auto p-4 pt-16 md:pt-4">
|
||||||
<!-- Breadcrumb -->
|
<!-- Breadcrumb -->
|
||||||
<nav class="text-sm text-gray-600 mb-4">
|
<nav class="text-sm text-gray-600 mb-4">
|
||||||
<?= $CRUMB_START . htmlspecialchars($MY_CRUMB) ?>
|
<?= $CRUMB_START . htmlspecialchars($MY_CRUMB) ?>
|
||||||
|
|
@ -59,8 +62,7 @@ $MOD_DATE = file_exists(__FILE__) ? date("F d Y H:i.", filemtime(__FILE__)) : "(
|
||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Mobile Navigation -->
|
<!-- Mobile Navigation moved to top (see include above) -->
|
||||||
<?php include 'nav-mobile.php'; ?>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="alert" class="fixed top-4 right-4 bg-yellow-100 border border-yellow-300 text-yellow-800 px-4 py-2 rounded shadow-md hidden">
|
<div id="alert" class="fixed top-4 right-4 bg-yellow-100 border border-yellow-300 text-yellow-800 px-4 py-2 rounded shadow-md hidden">
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,30 @@
|
||||||
<!-- Mobile sticky nav bar (visible only on small screens) -->
|
<?php
|
||||||
<footer class="fixed bottom-0 left-0 w-full bg-white/90 backdrop-blur border-t p-3 flex justify-around text-sm font-medium text-gray-800 shadow z-50 md:hidden">
|
$current_path = isset($MY_PATH) ? basename($MY_PATH) : basename($_SERVER['PHP_SELF']);
|
||||||
<a href="index.php" class="hover:text-blue-600">My Itinerary</a>
|
$is_index = ($current_path === 'index.php');
|
||||||
<a href="allsessions.php" class="hover:text-blue-600">All Sessions</a>
|
$is_all = ($current_path === 'allsessions.php');
|
||||||
<a href="history.php" class="hover:text-blue-600">History</a>
|
$is_hist = ($current_path === 'history.php');
|
||||||
</footer>
|
$is_report = ($current_path === 'report.php');
|
||||||
|
|
||||||
|
$link_base = 'px-3 py-2 rounded focus:outline-none focus:ring-2 focus:ring-white/70';
|
||||||
|
$link_active = 'bg-white text-blue-700';
|
||||||
|
$link_inactive = 'hover:bg-blue-700';
|
||||||
|
|
||||||
|
// Determine report access from conf_uinforecord id=6 (CSV of conf_users.id)
|
||||||
|
$allowed_ids_csv = '';
|
||||||
|
if (function_exists('single_row_select')) {
|
||||||
|
$row6 = single_row_select("SELECT value FROM conf_uinforecord WHERE id=6", 0);
|
||||||
|
if ($row6 && isset($row6['value'])) { $allowed_ids_csv = $row6['value']; }
|
||||||
|
}
|
||||||
|
$allowed_ids = array_filter(array_map('trim', explode(',', (string)$allowed_ids_csv)));
|
||||||
|
$current_uid = isset($USER['conf_id']) ? (string)$USER['conf_id'] : '';
|
||||||
|
$has_report_access = in_array($current_uid, $allowed_ids, true);
|
||||||
|
?>
|
||||||
|
<!-- Mobile sticky nav bar (now at the TOP; visible only on small screens) -->
|
||||||
|
<header class="fixed top-0 left-0 w-full bg-blue-600 text-white border-b p-3 flex justify-around text-base font-semibold shadow z-50 md:hidden">
|
||||||
|
<a href="index.php" class="<?= $link_base ?> <?= $is_index ? $link_active : $link_inactive ?>" <?= $is_index ? 'aria-current="page"' : '' ?>>My Itinerary</a>
|
||||||
|
<a href="allsessions.php" class="<?= $link_base ?> <?= $is_all ? $link_active : $link_inactive ?>" <?= $is_all ? 'aria-current="page"' : '' ?>>All Sessions</a>
|
||||||
|
<a href="history.php" class="<?= $link_base ?> <?= $is_hist ? $link_active : $link_inactive ?>" <?= $is_hist ? 'aria-current="page"' : '' ?>>History</a>
|
||||||
|
<?php if ($has_report_access) { ?>
|
||||||
|
<a href="report.php" class="<?= $link_base ?> <?= $is_report ? $link_active : $link_inactive ?>" <?= $is_report ? 'aria-current="page"' : '' ?>>Reports</a>
|
||||||
|
<?php } ?>
|
||||||
|
</header>
|
||||||
|
|
|
||||||
39
nav.php
39
nav.php
|
|
@ -1,15 +1,30 @@
|
||||||
|
<?php
|
||||||
|
$current_path = isset($MY_PATH) ? basename($MY_PATH) : basename($_SERVER['PHP_SELF']);
|
||||||
|
$is_index = ($current_path === 'index.php');
|
||||||
|
$is_all = ($current_path === 'allsessions.php');
|
||||||
|
$is_hist = ($current_path === 'history.php');
|
||||||
|
$is_report = ($current_path === 'report.php');
|
||||||
|
|
||||||
|
$base_link = 'px-4 py-2 rounded transition-colors';
|
||||||
|
$active = 'bg-blue-600 text-white shadow aria-current="page"';
|
||||||
|
$inactive = 'bg-gray-200 text-gray-900 hover:bg-gray-300';
|
||||||
|
|
||||||
|
// Determine report access from conf_uinforecord id=6 (CSV of conf_users.id)
|
||||||
|
$allowed_ids_csv = '';
|
||||||
|
if (function_exists('single_row_select')) {
|
||||||
|
$row6 = single_row_select("SELECT value FROM conf_uinforecord WHERE id=6", 0);
|
||||||
|
if ($row6 && isset($row6['value'])) { $allowed_ids_csv = $row6['value']; }
|
||||||
|
}
|
||||||
|
$allowed_ids = array_filter(array_map('trim', explode(',', (string)$allowed_ids_csv)));
|
||||||
|
$current_uid = isset($USER['conf_id']) ? (string)$USER['conf_id'] : '';
|
||||||
|
$has_report_access = in_array($current_uid, $allowed_ids, true);
|
||||||
|
?>
|
||||||
<!-- Sidebar Nav (hidden on mobile, visible on md+) -->
|
<!-- Sidebar Nav (hidden on mobile, visible on md+) -->
|
||||||
<nav class="hidden md:flex flex-col gap-2 text-sm font-medium">
|
<nav class="hidden md:flex flex-col gap-2 text-sm font-medium">
|
||||||
<a href="index.php" class="bg-gray-200 px-4 py-2 rounded hover:bg-gray-300">My Itinerary</a>
|
<a href="index.php" class="<?= $base_link ?> <?= $is_index ? $active : $inactive ?>">My Itinerary</a>
|
||||||
<a href="allsessions.php" class="bg-gray-200 px-4 py-2 rounded hover:bg-gray-300">All Sessions</a>
|
<a href="allsessions.php" class="<?= $base_link ?> <?= $is_all ? $active : $inactive ?>">All Sessions</a>
|
||||||
<a href="history.php" class="bg-gray-200 px-4 py-2 rounded hover:bg-gray-300">History</a>
|
<a href="history.php" class="<?= $base_link ?> <?= $is_hist ? $active : $inactive ?>">History</a>
|
||||||
|
<?php if ($has_report_access) { ?>
|
||||||
|
<a href="report.php" class="<?= $base_link ?> <?= $is_report ? $active : $inactive ?>">Reports</a>
|
||||||
|
<?php } ?>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
|
|
||||||
<!-- Mobile sticky nav bar (visible only on small screens) -->
|
|
||||||
<footer class="fixed bottom-0 left-0 w-full bg-white/90 backdrop-blur border-t p-3 flex justify-around text-sm font-medium text-gray-800 shadow z-50 md:hidden">
|
|
||||||
<a href="index.php" class="hover:text-blue-600">My Itinerary</a>
|
|
||||||
<a href="allsessions.php" class="hover:text-blue-600">All Sessions</a>
|
|
||||||
<a href="history.php" class="hover:text-blue-600">History</a>
|
|
||||||
</footer>
|
|
||||||
|
|
||||||
|
|
|
||||||
5
q.php
5
q.php
|
|
@ -138,6 +138,7 @@
|
||||||
$allowed_ip1 = '47.45.92.162';
|
$allowed_ip1 = '47.45.92.162';
|
||||||
$ip2 = '207.62.201.30';
|
$ip2 = '207.62.201.30';
|
||||||
$ip3 = '192.168.1.70';
|
$ip3 = '192.168.1.70';
|
||||||
|
$ip4 = "192.168.1.1";
|
||||||
|
|
||||||
function get_client_ip() {
|
function get_client_ip() {
|
||||||
if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
|
if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
|
||||||
|
|
@ -158,8 +159,8 @@
|
||||||
$client_ip = get_client_ip();
|
$client_ip = get_client_ip();
|
||||||
|
|
||||||
// Check if the incoming IP address matches the allowed IP
|
// Check if the incoming IP address matches the allowed IP
|
||||||
if ($client_ip !== $allowed_ip1 && $client_ip !== $ip2 && $client_ip !== $ip3) {
|
if ($client_ip !== $allowed_ip1 && $client_ip !== $ip2 && $client_ip !== $ip3 && $client_ip !== $ip4) {
|
||||||
die("Access denied. Unauthorized IP address.");
|
die("Access denied. Unauthorized IP address. " . $client_ip);
|
||||||
}
|
}
|
||||||
?>
|
?>
|
||||||
|
|
||||||
|
|
|
||||||
20
report.php
20
report.php
|
|
@ -1,9 +1,27 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
require_once 'single_sign_on.php';
|
||||||
|
|
||||||
if (isset($_GET['s'])) { $FOCUS = $_GET['s'] ; }
|
if (isset($_GET['s'])) { $FOCUS = $_GET['s'] ; }
|
||||||
else { $FOCUS = "all"; }
|
else { $FOCUS = "all"; }
|
||||||
|
|
||||||
|
// Access control: conf_uinforecord.id=6 contains comma-separated conf_user IDs
|
||||||
|
$allowed_ids_csv = '';
|
||||||
|
$row6 = single_row_select("SELECT value FROM conf_uinforecord WHERE id=6", 0);
|
||||||
|
if ($row6 && isset($row6['value'])) { $allowed_ids_csv = $row6['value']; }
|
||||||
|
$allowed_ids = array_filter(array_map('trim', explode(',', (string)$allowed_ids_csv)));
|
||||||
|
$current_uid = isset($USER['conf_id']) ? (string)$USER['conf_id'] : '';
|
||||||
|
$has_access = in_array($current_uid, $allowed_ids, true);
|
||||||
|
|
||||||
$MY_TITLE = "Session Report";
|
$MY_TITLE = "Session Report";
|
||||||
$MY_CRUMB = "Report";
|
$MY_CRUMB = "Report";
|
||||||
$CONTENT = "<activityreport :which='{$FOCUS}'></activityreport>";
|
|
||||||
|
if ($has_access) {
|
||||||
|
$CONTENT = "<activityreport :which='{$FOCUS}'></activityreport>";
|
||||||
|
} else {
|
||||||
|
$CONTENT = "<div class=\"bg-yellow-50 border border-yellow-200 text-yellow-900 p-4 rounded\">"
|
||||||
|
. "<strong>Access restricted.</strong> If you need access to this report, please contact the site administrator."
|
||||||
|
. "</div>";
|
||||||
|
}
|
||||||
|
|
||||||
include 'layout.php';
|
include 'layout.php';
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue