better report

This commit is contained in:
Peter Howell 2025-09-26 21:03:17 +00:00
parent fbba9edf63
commit 3986f90a83
7 changed files with 305 additions and 126 deletions

View File

@ -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(); }

View File

@ -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 1Jul 31) and Fall (Aug 1Dec 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>` })

View File

@ -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">

View File

@ -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
View File

@ -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
View File

@ -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);
}
?>

View File

@ -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';