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);
|
||||
$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(); }
|
||||
|
||||
|
|
|
|||
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:') {
|
||||
|
|
@ -2056,53 +2056,91 @@ const ActivityInfoReport2 = Vue.component('activityinforeport2', {
|
|||
watch: { },
|
||||
template: `<div class="bg-white rounded-md shadow-md p-6 text-sm text-gray-800 space-y-4">
|
||||
<!-- Title -->
|
||||
<div class="grid grid-cols-5 gap-4">
|
||||
<div class="font-medium col-span-1">Title:</div>
|
||||
<div class="col-span-4 font-semibold">{{ a.title }}</div>
|
||||
</div>
|
||||
|
||||
<!-- Date -->
|
||||
<div class="grid grid-cols-5 gap-4">
|
||||
<div class="font-medium col-span-1">Date:</div>
|
||||
<div class="col-span-4">{{ $root.$dj(a.starttime).format('YYYY MMM DD dd h:mma') }}</div>
|
||||
</div>
|
||||
|
||||
<!-- Mode / Location -->
|
||||
<div class="grid grid-cols-5 gap-4">
|
||||
<div class="font-medium col-span-1">Mode / Location:</div>
|
||||
<div class="col-span-4">
|
||||
<table class="w-full border-collapse text-sm">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th class="text-left align-top pr-4 py-1 w-32">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" class="text-gray-600">{{ a.location }}</div>
|
||||
<div v-if="a.location_irl" class="text-gray-600">{{ a.location_irl }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Description -->
|
||||
<div class="grid grid-cols-5 gap-4">
|
||||
<div class="font-medium col-span-1">Description:</div>
|
||||
<div class="col-span-4" v-html="a.desc"></div>
|
||||
</div>
|
||||
|
||||
<!-- Hosts -->
|
||||
<div class="grid grid-cols-5 gap-4">
|
||||
<div class="font-medium col-span-1">Hosts:</div>
|
||||
<div class="col-span-4">{{ host }}</div>
|
||||
</div>
|
||||
|
||||
<!-- Attendees -->
|
||||
<div class="grid grid-cols-5 gap-4">
|
||||
<div class="font-medium col-span-1">Attendees:</div>
|
||||
<div class="col-span-4">{{ user }}</div>
|
||||
</div>
|
||||
|
||||
<!-- 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>
|
||||
</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>
|
||||
` })
|
||||
|
||||
// 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', {
|
||||
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) {
|
||||
},
|
||||
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; } )
|
||||
// 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( self.answers, function(val,key,lis) { self.answers2[key] = _.sortBy( val, "q_id" ) } )
|
||||
self.$forceUpdate()
|
||||
} )
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
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
|
||||
},
|
||||
watch: { },
|
||||
template: `<div class="activityreport">
|
||||
<div v-for="a in activities">
|
||||
<activityinforeport2 :a="a" :host="hoststr(a.id)" :num="usernum(a.id)" :user="userstr(a.id)" :emails="useremails(a.id)" :survey="surveystr(a.id)"></activityinforeport2>
|
||||
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: {
|
||||
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>` })
|
||||
|
||||
|
|
|
|||
|
|
@ -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" />
|
||||
</head>
|
||||
<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 -->
|
||||
<nav class="text-sm text-gray-600 mb-4">
|
||||
<?= $CRUMB_START . htmlspecialchars($MY_CRUMB) ?>
|
||||
|
|
@ -59,8 +62,7 @@ $MOD_DATE = file_exists(__FILE__) ? date("F d Y H:i.", filemtime(__FILE__)) : "(
|
|||
</footer>
|
||||
</div>
|
||||
|
||||
<!-- Mobile Navigation -->
|
||||
<?php include 'nav-mobile.php'; ?>
|
||||
<!-- Mobile Navigation moved to top (see include above) -->
|
||||
</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">
|
||||
|
|
|
|||
|
|
@ -1,7 +1,30 @@
|
|||
<!-- 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>
|
||||
<?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');
|
||||
|
||||
$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+) -->
|
||||
<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="allsessions.php" class="bg-gray-200 px-4 py-2 rounded hover:bg-gray-300">All Sessions</a>
|
||||
<a href="history.php" class="bg-gray-200 px-4 py-2 rounded hover:bg-gray-300">History</a>
|
||||
<a href="index.php" class="<?= $base_link ?> <?= $is_index ? $active : $inactive ?>">My Itinerary</a>
|
||||
<a href="allsessions.php" class="<?= $base_link ?> <?= $is_all ? $active : $inactive ?>">All Sessions</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>
|
||||
|
||||
|
||||
<!-- 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';
|
||||
$ip2 = '207.62.201.30';
|
||||
$ip3 = '192.168.1.70';
|
||||
$ip4 = "192.168.1.1";
|
||||
|
||||
function get_client_ip() {
|
||||
if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
|
||||
|
|
@ -158,8 +159,8 @@
|
|||
$client_ip = get_client_ip();
|
||||
|
||||
// Check if the incoming IP address matches the allowed IP
|
||||
if ($client_ip !== $allowed_ip1 && $client_ip !== $ip2 && $client_ip !== $ip3) {
|
||||
die("Access denied. Unauthorized IP address.");
|
||||
if ($client_ip !== $allowed_ip1 && $client_ip !== $ip2 && $client_ip !== $ip3 && $client_ip !== $ip4) {
|
||||
die("Access denied. Unauthorized IP address. " . $client_ip);
|
||||
}
|
||||
?>
|
||||
|
||||
|
|
|
|||
20
report.php
20
report.php
|
|
@ -1,9 +1,27 @@
|
|||
<?php
|
||||
|
||||
require_once 'single_sign_on.php';
|
||||
|
||||
if (isset($_GET['s'])) { $FOCUS = $_GET['s'] ; }
|
||||
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_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';
|
||||
|
|
|
|||
Loading…
Reference in New Issue