change edit screen to grid style
This commit is contained in:
parent
d23d529f34
commit
833a4d689d
1
edit.php
1
edit.php
|
|
@ -3,4 +3,5 @@
|
||||||
$MY_TITLE = "Edit Sessions";
|
$MY_TITLE = "Edit Sessions";
|
||||||
$MY_CRUMB = "Edit";
|
$MY_CRUMB = "Edit";
|
||||||
$CONTENT = '<activityeditorlist></activityeditorlist>';
|
$CONTENT = '<activityeditorlist></activityeditorlist>';
|
||||||
|
$XTRAJS = 'js/editor.js';
|
||||||
include 'layout.php';
|
include 'layout.php';
|
||||||
|
|
|
||||||
841
js/dir_app.js
841
js/dir_app.js
|
|
@ -101,6 +101,8 @@ const TQuestion = Vue.component('field', {
|
||||||
"answer": function(val,Oldval) { this.a = val },
|
"answer": function(val,Oldval) { this.a = val },
|
||||||
"a": function (val, oldVal) {
|
"a": function (val, oldVal) {
|
||||||
this.$root.events.trigger('changed',[this.qid,this.table, val, this.targetid]) }, },
|
this.$root.events.trigger('changed',[this.qid,this.table, val, this.targetid]) }, },
|
||||||
|
mounted() {
|
||||||
|
},
|
||||||
template: `<div class="mb-4">
|
template: `<div class="mb-4">
|
||||||
<label v-if="question" :for="qid" class="block text-sm font-medium text-gray-700 mb-1">
|
<label v-if="question" :for="qid" class="block text-sm font-medium text-gray-700 mb-1">
|
||||||
{{ question }}
|
{{ question }}
|
||||||
|
|
@ -829,289 +831,6 @@ const ActivityEditor = Vue.component('activityedit', {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // //
|
|
||||||
// // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // //
|
|
||||||
//
|
|
||||||
// DISPLAY / EDIT activity or event
|
|
||||||
//
|
|
||||||
const ActivityInlineEditor = Vue.component('activityinlineedit', {
|
|
||||||
props: [ 'which', ],
|
|
||||||
data: function () {
|
|
||||||
return { my_activity:0, creating:0,
|
|
||||||
everyname:[], everyone:[], hosts_by_sesid:{}, this_hosts:[], host_search:[], host_search_str:'',
|
|
||||||
activities:[], this_activity:{}, editing: 0, active:-1, } },
|
|
||||||
mounted: function() {
|
|
||||||
this.fetch_mypeople()
|
|
||||||
this.$root.fetch_menus()
|
|
||||||
if (this.which==0) {
|
|
||||||
this.creating = 1
|
|
||||||
this.$root.creating = 1
|
|
||||||
this.this_activity = {"title":"","desc":"","length":"1","starttime":"",
|
|
||||||
"track":"","location":"","gets_survey":"1","category":"1",
|
|
||||||
"parent":"","recording":"","instructions":"","image_url":"",
|
|
||||||
"is_flex_approved":"1","typeId":"101"}
|
|
||||||
} else { this.my_activity = this.which
|
|
||||||
this.fetch_myevents() } },
|
|
||||||
methods: {
|
|
||||||
set_id: function(new_id) { this.this_activity.id = new_id; this.creating=0; this.$root.creating=0 },
|
|
||||||
month_year: function(d) { var b = this.$root.$dj(d).format('MMM D YYYY'); return b },
|
|
||||||
setsort: function(ss) {
|
|
||||||
if (this.sortby == ss) { this.reversed = ! this.reversed }
|
|
||||||
else { this.reversed = false; this.sortby = ss } },
|
|
||||||
swapme: function(x) { this.editing = x },
|
|
||||||
done_edit: function(id) { this.editing = -1 },
|
|
||||||
am_editing: function(id) { return 0 },
|
|
||||||
remove: function(hostid) {
|
|
||||||
var self = this
|
|
||||||
basic_get('dir_api.php?a=remove/host/' + this.my_activity + '/' + hostid,
|
|
||||||
function(r2) {
|
|
||||||
self.this_hosts = _.reject(self.this_hosts, function(x) { return x.hostid == hostid} )
|
|
||||||
alert_message('Saved') } )
|
|
||||||
},
|
|
||||||
add: function(hostid) {
|
|
||||||
var self = this
|
|
||||||
basic_get('dir_api.php?a=add/host/' + this.my_activity + '/' + hostid,
|
|
||||||
function(r2) {
|
|
||||||
self.this_hosts.push( _.findWhere(self.everyone, {id:hostid} ) )
|
|
||||||
alert_message('Saved') } )
|
|
||||||
},
|
|
||||||
hostlookup: function() {
|
|
||||||
var self = this
|
|
||||||
if (this.host_search_str=='') { this.host_search=[] }
|
|
||||||
else { this.host_search_str = this.host_search_str.toLowerCase()
|
|
||||||
this.host_search = _.first(_.filter(self.everyone, function(x) { return x.name.toLowerCase().search( self.host_search_str) != -1 }),7) } },
|
|
||||||
save_new_event: function() {
|
|
||||||
this.$root.events.trigger('create_new_session',this.this_activity)
|
|
||||||
},
|
|
||||||
fetch_mypeople: function() {
|
|
||||||
var self = this;
|
|
||||||
basic_get('dir_api.php?a=get/names',
|
|
||||||
function(r2) {
|
|
||||||
self.everyone = r2.users
|
|
||||||
_.each( self.everyone, function(x) {
|
|
||||||
x.hostid = x.id } )
|
|
||||||
self.everyname = _.pluck(r2.users,'name') } ) },
|
|
||||||
fetch_myevents: function() {
|
|
||||||
var self = this;
|
|
||||||
basic_get('dir_api.php?a=get/sessions',
|
|
||||||
function(r2) {
|
|
||||||
self.activities = _.indexBy(r2,function(x) { return x.id } ); self.$forceUpdate()
|
|
||||||
self.active += 1;
|
|
||||||
_.each( self.activities, function(x) {
|
|
||||||
x.searchable = x.title + ' ' + x.desc
|
|
||||||
x.searchable = x.searchable.toLowerCase()
|
|
||||||
} )
|
|
||||||
self.this_activity = self.activities[ self.my_activity ]
|
|
||||||
self.this_activity.starttime = self.this_activity.starttime.replace(' ','T')} )
|
|
||||||
basic_get('dir_api.php?a=get/hosts',
|
|
||||||
function(r2) {
|
|
||||||
self.hosts_by_sesid = _.groupBy(r2,function(x) { return x.id } )
|
|
||||||
var unfiltered = self.hosts_by_sesid[ self.my_activity ]
|
|
||||||
self.this_hosts = unfiltered.filter(x => x.hostid != null)
|
|
||||||
} )
|
|
||||||
},
|
|
||||||
},
|
|
||||||
template: `<div class="">
|
|
||||||
<div v-if="editing" class="bg-white rounded-lg shadow p-4 mb-6">
|
|
||||||
<!-- Top action bar -->
|
|
||||||
<div class="flex justify-end mb-4">
|
|
||||||
<button
|
|
||||||
@click.prevent="editing = 0"
|
|
||||||
class="text-sm text-blue-600 hover:underline font-medium"
|
|
||||||
>
|
|
||||||
Done
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Form fields -->
|
|
||||||
<div class="space-y-4">
|
|
||||||
<field
|
|
||||||
myclass="double"
|
|
||||||
table="conf_sessions"
|
|
||||||
qid="title"
|
|
||||||
:answer="this_activity.title"
|
|
||||||
:targetid="this_activity.id"
|
|
||||||
question="Title"
|
|
||||||
placeholder="Title"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<dtpicker
|
|
||||||
table="conf_sessions"
|
|
||||||
qid="starttime"
|
|
||||||
:answer="this_activity.starttime"
|
|
||||||
:targetid="this_activity.id"
|
|
||||||
question="Starts at"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<field
|
|
||||||
myclass="double"
|
|
||||||
table="conf_sessions"
|
|
||||||
qid="length"
|
|
||||||
:answer="this_activity.length"
|
|
||||||
:targetid="this_activity.id"
|
|
||||||
question="Length (in hours)"
|
|
||||||
placeholder="1"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<selectmenu_fa
|
|
||||||
table="conf_sessions"
|
|
||||||
qid="mode"
|
|
||||||
:answer="this_activity.mode"
|
|
||||||
menu="modes_menu"
|
|
||||||
:targetid="this_activity.id"
|
|
||||||
question="What mode?"
|
|
||||||
labelfield="string"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<field
|
|
||||||
myclass="double"
|
|
||||||
table="conf_sessions"
|
|
||||||
qid="location"
|
|
||||||
:answer="this_activity.location"
|
|
||||||
:targetid="this_activity.id"
|
|
||||||
question="Location / Zoom"
|
|
||||||
placeholder="Location"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<field
|
|
||||||
myclass="double"
|
|
||||||
table="conf_sessions"
|
|
||||||
qid="location_irl"
|
|
||||||
:answer="this_activity.location_irl"
|
|
||||||
:targetid="this_activity.id"
|
|
||||||
question="Location / In Person"
|
|
||||||
placeholder="Location"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<!-- Host List -->
|
|
||||||
<div v-if="!creating">
|
|
||||||
<label class="block text-sm font-medium text-gray-700 mb-1">Hosts</label>
|
|
||||||
<div class="border rounded-md p-3 bg-gray-50 space-y-1">
|
|
||||||
<div
|
|
||||||
v-for="h in this_hosts"
|
|
||||||
:key="h.hostid"
|
|
||||||
class="flex items-center justify-between text-sm"
|
|
||||||
>
|
|
||||||
<span>{{ h.name }}</span>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
@click="remove(h.hostid)"
|
|
||||||
class="text-red-500 hover:text-red-700 font-semibold"
|
|
||||||
>
|
|
||||||
×
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div v-if="this_hosts.length === 0" class="text-sm text-gray-500 italic">
|
|
||||||
Add a host by typing below
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Host Add Input + Suggestions -->
|
|
||||||
<div v-if="!creating">
|
|
||||||
<label class="block text-sm font-medium text-gray-700 mb-1">Add a host</label>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
id="addhost"
|
|
||||||
name="addhost"
|
|
||||||
v-model="host_search_str"
|
|
||||||
@keyup="hostlookup"
|
|
||||||
autocomplete="off"
|
|
||||||
class="w-full rounded-md border border-gray-500 bg-white text-gray-900 shadow focus:border-blue-600 focus:ring focus:ring-blue-300 focus:ring-opacity-50 px-4 py-2 text-base mb-2"
|
|
||||||
/>
|
|
||||||
<div class="border rounded-md p-3 bg-gray-50 space-y-1">
|
|
||||||
<div
|
|
||||||
v-for="ho in host_search"
|
|
||||||
:key="ho.id"
|
|
||||||
class="flex items-center justify-between text-sm"
|
|
||||||
>
|
|
||||||
<span @click="add(ho.id)" class="cursor-pointer hover:underline">
|
|
||||||
{{ ho.name }}
|
|
||||||
</span>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
@click="add(ho.id)"
|
|
||||||
class="text-blue-500 hover:text-blue-700 font-semibold"
|
|
||||||
>
|
|
||||||
+
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<htfield_fa
|
|
||||||
table="conf_sessions"
|
|
||||||
qid="desc"
|
|
||||||
question="Description"
|
|
||||||
myclass="pell"
|
|
||||||
:answer="this_activity.desc"
|
|
||||||
:targetid="this_activity.id"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<selectmenu_fa
|
|
||||||
table="conf_sessions"
|
|
||||||
qid="type"
|
|
||||||
:answer="this_activity.type"
|
|
||||||
menu="sessiontypes_menu"
|
|
||||||
:targetid="this_activity.id"
|
|
||||||
question="Type"
|
|
||||||
labelfield="type"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<selectmenu_fa
|
|
||||||
v-if="this_activity.type === '101'"
|
|
||||||
table="conf_sessions"
|
|
||||||
qid="parent"
|
|
||||||
:answer="this_activity.parent"
|
|
||||||
menu="parents_menu"
|
|
||||||
:targetid="this_activity.id"
|
|
||||||
question="What flex day?"
|
|
||||||
labelfield="title"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<field
|
|
||||||
myclass="double"
|
|
||||||
table="conf_sessions"
|
|
||||||
qid="recording"
|
|
||||||
:answer="this_activity.recording"
|
|
||||||
:targetid="this_activity.id"
|
|
||||||
question="Recording Link"
|
|
||||||
placeholder=""
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-else class="flex flex-col md:flex-row gap-4 bg-white rounded-md shadow p-4 mb-4">
|
|
||||||
<!-- Left: Edit Button -->
|
|
||||||
<div class="md:w-40 shrink-0">
|
|
||||||
<a href="#" @click.prevent="editing = 1" class="text-sm text-blue-600 hover:underline font-medium">
|
|
||||||
Edit
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Right: Session Info -->
|
|
||||||
<div class="flex-grow text-sm text-gray-800">
|
|
||||||
<p class="text-base font-semibold text-gray-900 mb-1">{{ this_activity.title }}</p>
|
|
||||||
|
|
||||||
<p class="text-gray-600 mb-1">
|
|
||||||
{{ $root.$dj(this_activity.starttime).format('h:mma') }} /
|
|
||||||
<span class="capitalize">{{ this_activity.mode }}</span>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p class="text-gray-600 mb-1">
|
|
||||||
Presented by:
|
|
||||||
<span v-for="(h, i) in this_hosts" :key="h.hostid">
|
|
||||||
{{ h.name }}<span v-if="i < this_hosts.length - 1">, </span>
|
|
||||||
</span>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<div class="mt-2 text-gray-700" v-html="this_activity.desc"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>` })
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//
|
//
|
||||||
|
|
@ -1369,7 +1088,7 @@ const ActivityList = Vue.component('activitylist', {
|
||||||
target="_blank"
|
target="_blank"
|
||||||
href="https://forms.office.com/r/GGz56DdSEG"
|
href="https://forms.office.com/r/GGz56DdSEG"
|
||||||
class="px-3 py-1 text-sm font-medium text-white bg-amber-500 rounded hover:bg-amber-700">Sign Up</a>
|
class="px-3 py-1 text-sm font-medium text-white bg-amber-500 rounded hover:bg-amber-700">Sign Up</a>
|
||||||
-->
|
-->
|
||||||
<span v-if="special_signup(a.id)"
|
<span v-if="special_signup(a.id)"
|
||||||
class="px-3 py-1 text-sm font-medium text-white">
|
class="px-3 py-1 text-sm font-medium text-white">
|
||||||
</span>
|
</span>
|
||||||
|
|
@ -1584,120 +1303,468 @@ const ChartView = Vue.component('chartview', {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const basic_getP = (url) =>
|
||||||
|
new Promise((resolve, reject) =>
|
||||||
|
basic_get(url, (r) => resolve(r), (e) => reject(e))
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
//
|
|
||||||
//
|
|
||||||
//
|
|
||||||
//
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// Activity EDITOR List
|
|
||||||
// ------------------------------
|
|
||||||
//
|
|
||||||
//
|
|
||||||
//
|
|
||||||
const ActivityEditorList = Vue.component('activityeditorlist', {
|
const ActivityEditorList = Vue.component('activityeditorlist', {
|
||||||
props: [ ],
|
data() {
|
||||||
data: function () {
|
return {
|
||||||
return { activities:[], mysessions:[], search:'', sortby:'starttime', reversed:false, my_ses_ids:[], my_host_ids:[],
|
// data
|
||||||
show_filters: 'all', expanded: 1, editing: -1, active:-1, hosts:{},
|
activities: [],
|
||||||
filters: {'fa23':['2023-08-21','2023-08-26'], 'sp23':['2023-01-01','2023-02-02'],'fa22':['2022-08-17','2022-08-20'], 'sp22':['2022-01-26','2022-01-30'], 'all':['2022-01-01','2023-11-30'] },
|
mysessions: [],
|
||||||
//day_titles: {'Aug 18 2022':" - Optional PD Day", 'Aug 19 2022':' - Convocation Day'},
|
hosts: {}, // all hosts keyed by conf_id
|
||||||
day_titles: {'Jan 26 2023':" - Optional Day", 'Jan 27 2023':' - Mandatory Day',
|
users: [], // everyone (names/users)
|
||||||
'Aug 24 2023':" - Optional Day", 'Aug 25 2023':' - Mandatory Day'},
|
hostsBySessionId: {}, // sessionId -> [host objects]
|
||||||
hosts_by_sesid: {}, } },
|
byId: {}, // sessionId -> activity
|
||||||
mounted: function() {
|
// ui
|
||||||
this.$root.fetch_menus();
|
search: "",
|
||||||
this.fetch_myevents()
|
sortby: "starttime",
|
||||||
|
reversed: false,
|
||||||
|
selectedIndex: 0,
|
||||||
|
editingId: null,
|
||||||
|
// filters
|
||||||
|
show_filters: "all",
|
||||||
|
filters: {
|
||||||
|
fa23: ["2023-08-21", "2023-08-26"],
|
||||||
|
sp23: ["2023-01-01", "2023-02-02"],
|
||||||
|
fa22: ["2022-08-17", "2022-08-20"],
|
||||||
|
sp22: ["2022-01-26", "2022-01-30"],
|
||||||
|
all: ["2022-01-01", "2025-12-31"],
|
||||||
|
},
|
||||||
|
day_titles: {
|
||||||
|
"Jan 26 2023": " - Optional Day",
|
||||||
|
"Jan 27 2023": " - Mandatory Day",
|
||||||
|
"Aug 24 2023": " - Optional Day",
|
||||||
|
"Aug 25 2023": " - Mandatory Day",
|
||||||
|
},
|
||||||
|
active: 0,
|
||||||
|
lastScrollY: 0,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.$root.fetch_menus();
|
||||||
|
this.fetchAll();
|
||||||
|
window.addEventListener("keydown", this.onKey);
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
window.removeEventListener("keydown", this.onKey);
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
hoststr: function(id) { var self = this
|
async fetchAll() {
|
||||||
return _.reduce( self.hosts_by_sesid[id], function(mem,val) { if (val.name) { return mem + val.name + ", " } return mem }, '') },
|
this.active = 0;
|
||||||
mode_string: function(a) { return _.findWhere(this.$root.modes_menu, { 'id': a.mode })['string'] },
|
const [
|
||||||
get_day_title: function(day) { if (day in this.day_titles) { return this.day_titles[day] } return '' },
|
mysessions,
|
||||||
month_year: function(d) { var b = this.$root.$dj(d).format('MMM D YYYY'); return b },
|
sessions,
|
||||||
setsort: function(ss) {
|
hostsRows,
|
||||||
if (this.sortby == ss) { this.reversed = ! this.reversed }
|
allhosts,
|
||||||
else { this.reversed = false; this.sortby = ss } },
|
namesPayload,
|
||||||
fetch_myevents: function() {
|
] = await Promise.all([
|
||||||
var self = this;
|
basic_getP("dir_api.php?a=get/mysessions"),
|
||||||
basic_get('dir_api.php?a=get/mysessions',
|
basic_getP("dir_api.php?a=get/sessions"),
|
||||||
function(r2) {
|
basic_getP("dir_api.php?a=get/hosts"),
|
||||||
self.mysessions = _.sortBy(r2,function(x) { return x.starttime || 0} );
|
basic_getP("dir_api.php?a=get/allhosts"),
|
||||||
self.my_ses_ids = _.pluck(r2, 'id')
|
basic_getP("dir_api.php?a=get/names"),
|
||||||
self.$forceUpdate();
|
]);
|
||||||
self.active += 1;
|
|
||||||
} )
|
// normalize hosts per session
|
||||||
basic_get('dir_api.php?a=get/sessions',
|
const hostsBySessionId = _.groupBy(hostsRows, (x) => x.id);
|
||||||
function(r2) {
|
Object.keys(hostsBySessionId).forEach((sid) => {
|
||||||
self.activities = _.sortBy(r2,function(x) { return x.starttime } ); self.$forceUpdate()
|
hostsBySessionId[sid] = hostsBySessionId[sid].filter(
|
||||||
self.active += 1;
|
(x) => x.hostid != null
|
||||||
_.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)
|
const byId = _.indexBy(sessions, (x) => x.id);
|
||||||
x.searchable = x.title + ' ' + x.desc
|
|
||||||
x.searchable = x.searchable.toLowerCase()
|
this.activities = _.sortBy(sessions, (x) => x.starttime || "");
|
||||||
} )
|
this.mysessions = _.sortBy(mysessions, (x) => x.starttime || "");
|
||||||
self.$forceUpdate();
|
this.hosts = allhosts; // as provided (conf_id -> [host ids]?) if that's your shape
|
||||||
self.active += 1; } )
|
this.users = namesPayload.users || [];
|
||||||
basic_get('dir_api.php?a=get/hosts', function(r2) {
|
this.hostsBySessionId = hostsBySessionId;
|
||||||
self.hosts_by_sesid = _.groupBy(r2,function(x) { return x.id } )
|
this.byId = byId;
|
||||||
} )
|
this.active = 1;
|
||||||
basic_get('dir_api.php?a=get/allhosts',
|
|
||||||
function(r2) {
|
// decorate sessions
|
||||||
self.hosts = r2
|
sessions.forEach((x) => {
|
||||||
setTimeout(function () {
|
// precompute dayjs + searchable text
|
||||||
self.my_host_ids = r2[ self.$root.user.conf_id ]
|
if (x.starttime) {
|
||||||
if (! self.my_host_ids) { self.my_host_ids = [] }
|
const m = x.starttime.match(
|
||||||
}, 750);
|
/^(\d{4})-(\d+)-(\d+)\s(\d+):(\d+):(\d+)$/
|
||||||
self.active += 1;
|
);
|
||||||
} )
|
if (m) {
|
||||||
},
|
const d = new Date(m[1], m[2] - 1, m[3], m[4], m[5], m[6]);
|
||||||
filtered: function(ff) { if (this.active<1) { return [] }
|
x.dj = dayjs(d);
|
||||||
var self = this
|
} else {
|
||||||
var ay = this.$root.settings['default_ay']
|
x.dj = dayjs(x.starttime);
|
||||||
if (this.search) {
|
}
|
||||||
var ss = self.search.toLowerCase()
|
} else {
|
||||||
ff = ff.filter(function(x) { return ('searchable' in x ? x.searchable.includes(ss) : 0) }) }
|
x.dj = dayjs.invalid();
|
||||||
if (this.focus) {
|
|
||||||
var start = dayjs(this.$root.ay_menu[ay].begin ) /* UPDATE this.filters.sp23[0]*/
|
|
||||||
var end = dayjs(this.$root.ay_menu[ay].end)
|
|
||||||
ff = ff.filter( function(item,index) {
|
|
||||||
this_time = dayjs(item.starttime)
|
|
||||||
return this_time.isBefore(end) && start.isBefore(this_time) } )
|
|
||||||
}
|
}
|
||||||
ff = _.sortBy(ff, function(x) {
|
x.searchable = ((x.title || "") + " " + (x.desc || "")).toLowerCase();
|
||||||
if (x[self.sortby]) { var s = x[self.sortby]; return s.trim().toLowerCase() }
|
x.missing = this.computeMissing(x);
|
||||||
return '' })
|
});
|
||||||
if (this.reversed) { ff.reverse() }
|
|
||||||
return ff }, },
|
},
|
||||||
|
|
||||||
|
computeMissing(a) {
|
||||||
|
const miss = [];
|
||||||
|
required_fields = ["title", "starttime", "mode", "desc"];
|
||||||
|
if (a.mode=='inperson' || a.mode=='hybrid') { required_fields.push('location_irl')}
|
||||||
|
if (a.mode=='online' || a.mode=='hybrid') { required_fields.push('location')}
|
||||||
|
required_fields.forEach((k) => {
|
||||||
|
const v = a[k];
|
||||||
|
if (v === undefined || v === null || String(v).trim() === "") miss.push(k);
|
||||||
|
});
|
||||||
|
// special: no hosts
|
||||||
|
const int_id = parseInt(a.id)
|
||||||
|
if (!this.hostsBySessionId[a.id] || this.hostsBySessionId[int_id].length === 0) {
|
||||||
|
miss.push("hosts");
|
||||||
|
}
|
||||||
|
return miss;
|
||||||
|
},
|
||||||
|
|
||||||
|
get_day_title(day) {
|
||||||
|
return this.day_titles[day] || "";
|
||||||
|
},
|
||||||
|
month_year(d) {
|
||||||
|
return this.$root.$dj(d).format("MMM D YYYY");
|
||||||
|
},
|
||||||
|
setsort(ss) {
|
||||||
|
if (this.sortby === ss) this.reversed = !this.reversed;
|
||||||
|
else {
|
||||||
|
this.reversed = false;
|
||||||
|
this.sortby = ss;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
filtered(list) {
|
||||||
|
if (!this.active) return [];
|
||||||
|
let ff = list;
|
||||||
|
|
||||||
|
if (this.search) {
|
||||||
|
const q = this.search.toLowerCase();
|
||||||
|
ff = ff.filter((x) => x.searchable.includes(q));
|
||||||
|
}
|
||||||
|
|
||||||
|
// AY filter example
|
||||||
|
const ay = this.$root.settings["default_ay"];
|
||||||
|
if (ay && this.$root.ay_menu && this.$root.ay_menu[ay]) {
|
||||||
|
const start = dayjs(this.$root.ay_menu[ay].begin);
|
||||||
|
const end = dayjs(this.$root.ay_menu[ay].end);
|
||||||
|
ff = ff.filter((item) => {
|
||||||
|
const t = dayjs(item.starttime);
|
||||||
|
return t.isValid() && t.isBefore(end) && start.isBefore(t);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ff = _.sortBy(ff, (x) => {
|
||||||
|
const val = x[this.sortby];
|
||||||
|
return typeof val === "string" ? val.trim().toLowerCase() : val || "";
|
||||||
|
});
|
||||||
|
if (this.reversed) ff.reverse();
|
||||||
|
return ff;
|
||||||
|
},
|
||||||
|
|
||||||
|
onKey(e) {
|
||||||
|
// global keyboard navigation
|
||||||
|
if (!this.activitiesFiltered.length) return;
|
||||||
|
|
||||||
|
const max = this.activitiesFiltered.length - 1;
|
||||||
|
if (e.key === "ArrowDown") {
|
||||||
|
this.selectedIndex = Math.min(max, this.selectedIndex + 1);
|
||||||
|
e.preventDefault();
|
||||||
|
} else if (e.key === "ArrowUp") {
|
||||||
|
this.selectedIndex = Math.max(0, this.selectedIndex - 1);
|
||||||
|
e.preventDefault();
|
||||||
|
} else if (e.key === "Enter" || e.key.toLowerCase() === "e") {
|
||||||
|
const a = this.activitiesFiltered[this.selectedIndex];
|
||||||
|
this.startEdit(a.id);
|
||||||
|
e.preventDefault();
|
||||||
|
} else if (e.key === "Escape") {
|
||||||
|
this.closeEdit();
|
||||||
|
e.preventDefault();
|
||||||
|
} else if (e.key === "/") {
|
||||||
|
// focus search
|
||||||
|
const el = this.$refs.search;
|
||||||
|
if (el && el.focus) el.focus();
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// child emits
|
||||||
|
updateActivity(payload) {
|
||||||
|
// payload: { id, patch } — decorate + recompute missing
|
||||||
|
const a = this.byId[payload.id];
|
||||||
|
Object.assign(a, payload.patch);
|
||||||
|
a.missing = this.computeMissing(a);
|
||||||
|
},
|
||||||
|
startEdit(id) {
|
||||||
|
this.lastScrollY = window.scrollY; // remember current scroll
|
||||||
|
this.editingId = id;
|
||||||
|
},
|
||||||
|
closeEdit() {
|
||||||
|
this.editingId = null;
|
||||||
|
this.$nextTick(() => {
|
||||||
|
window.scrollTo({ top: this.lastScrollY, behavior: "smooth" });
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
computed: {
|
computed: {
|
||||||
current_time: function() { return dayjs().format('MMM D, h:ma') },
|
activitiesFiltered() {
|
||||||
activities_filtered: function() { var a = this.filtered(this.activities); return a; },
|
return this.filtered(this.activities);
|
||||||
mysessions_filtered: function() { return this.filtered(this.mysessions) },
|
},
|
||||||
|
groupedByDay() {
|
||||||
activities_g_filtered: function() { if (this.active<1) { return {} }
|
return _.groupBy(this.activitiesFiltered, (x) => this.month_year(x.starttime));
|
||||||
var self=this;
|
},
|
||||||
return _.groupBy(self.activities_filtered, function(x) { return self.month_year(x.starttime) } ); },
|
},
|
||||||
mysessions_g_filtered: function() { if (this.active<1) { return {} }
|
|
||||||
var self=this; return _.groupBy(self.mysessions_filtered, function(x) { return self.month_year(x.starttime) } ); }, },
|
|
||||||
watch: { },
|
|
||||||
template: `<div class="activitylist">
|
template: `<div class="activitylist">
|
||||||
<div v-for="items,mmyy in activities_g_filtered">
|
<!-- Top bar -->
|
||||||
<div class="pure-g">
|
<div class="flex items-center gap-3 mb-3">
|
||||||
<div class="day_title pure-u-1-1">{{mmyy}} {{get_day_title(mmyy)}}</div>
|
<input
|
||||||
</div>
|
ref="search"
|
||||||
<div v-for="a in items" class="activitylist">
|
v-model.trim="search"
|
||||||
<activityinlineedit :which="a.id"></activityinlineedit>
|
placeholder="Search… (press / to focus)"
|
||||||
</div>
|
class="border px-2 py-1 rounded w-64"
|
||||||
</div>
|
/>
|
||||||
</div>` })
|
<button @click="setsort('starttime')" class="border px-2 py-1 rounded">Sort: Start</button>
|
||||||
|
<button @click="setsort('title')" class="border px-2 py-1 rounded">Sort: Title</button>
|
||||||
|
<span class="text-sm text-gray-600">↑/↓ select • Enter/E edit • Esc close</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Grid (spreadsheet-like) -->
|
||||||
|
<table class="sessiongrid w-full text-sm border-collapse">
|
||||||
|
<thead>
|
||||||
|
<tr class="text-left border-b">
|
||||||
|
<th class="py-2 pr-2">Time</th>
|
||||||
|
<th class="py-2 pr-2">Title</th>
|
||||||
|
<th class="py-2 pr-2">Mode</th>
|
||||||
|
<th class="py-2 pr-2">Location</th>
|
||||||
|
<th class="py-2 pr-2">Hosts</th>
|
||||||
|
<th class="py-2 pr-2">Missing</th>
|
||||||
|
<th class="py-2 pr-2"></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr v-for="(a, idx) in activitiesFiltered"
|
||||||
|
:key="a.id"
|
||||||
|
:class="['border-b', idx===selectedIndex ? 'bg-yellow-50' : '']">
|
||||||
|
<td class="py-2 pr-2 whitespace-nowrap">{{ $root.$dj(a.starttime).format('MMM D, h:mma') }}</td>
|
||||||
|
<td class="py-2 pr-2">{{ a.title }}</td>
|
||||||
|
<td class="py-2 pr-2 capitalize">{{ a.mode }}</td>
|
||||||
|
<td class="py-2 pr-2">{{ a.location_irl }}<br />{{ a.location }}</td>
|
||||||
|
<td class="py-2 pr-2">
|
||||||
|
<span v-for="(h,i) in (hostsBySessionId[a.id]||[])"
|
||||||
|
:key="h.hostid">
|
||||||
|
{{ h.name }}<span v-if="i < (hostsBySessionId[a.id]||[]).length-1">, </span>
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td class="py-2 pr-2">
|
||||||
|
<span v-if="a.missing.length" class="inline-flex items-center gap-1">
|
||||||
|
<strong>{{ a.missing.length }}</strong>
|
||||||
|
<span class="text-gray-500">(
|
||||||
|
{{ a.missing.join(', ') }}
|
||||||
|
)</span>
|
||||||
|
</span>
|
||||||
|
<span v-else class="text-green-600">✓</span>
|
||||||
|
</td>
|
||||||
|
<td class="py-2 pr-2">
|
||||||
|
<button class="text-blue-600 underline"
|
||||||
|
@click.prevent="editingId = a.id">
|
||||||
|
Edit
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<!-- Inline editor (single expanded row) -->
|
||||||
|
<div v-if="editingId" class="mt-4">
|
||||||
|
<activityrow
|
||||||
|
:activity="byId[editingId]"
|
||||||
|
:hosts="hostsBySessionId[editingId] || []"
|
||||||
|
:everyone="users"
|
||||||
|
@patch="updateActivity"
|
||||||
|
@close="closeEdit"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>`
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // //
|
||||||
|
// // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // //
|
||||||
|
//
|
||||||
|
// DISPLAY / EDIT activity or event
|
||||||
|
//
|
||||||
|
const ActivityRow = Vue.component('activityrow', { props: {
|
||||||
|
activity: { type: Object, required: true },
|
||||||
|
hosts: { type: Array, default: () => [] },
|
||||||
|
everyone: { type: Array, default: () => [] },
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
editing: true,
|
||||||
|
host_search_str: "",
|
||||||
|
host_search: [],
|
||||||
|
this_hosts: [...this.hosts], // local copy for UX
|
||||||
|
};
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
host_search_str(val) {
|
||||||
|
const q = (val || "").toLowerCase();
|
||||||
|
this.host_search = q
|
||||||
|
? _.first(
|
||||||
|
this.everyone.filter((x) => x.name.toLowerCase().includes(q)),
|
||||||
|
7
|
||||||
|
)
|
||||||
|
: [];
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
// host ops (call API ONCE per click, not per render)
|
||||||
|
async removeHost(hostid) {
|
||||||
|
await new Promise((resolve, reject) =>
|
||||||
|
basic_get(`dir_api.php?a=remove/host/${this.activity.id}/${hostid}`, () => resolve(), reject)
|
||||||
|
);
|
||||||
|
this.this_hosts = this.this_hosts.filter((x) => x.hostid !== hostid);
|
||||||
|
this.emitPatch({ hostsChanged: true });
|
||||||
|
alert_message("Saved");
|
||||||
|
},
|
||||||
|
async addHost(hostid) {
|
||||||
|
await new Promise((resolve, reject) =>
|
||||||
|
basic_get(`dir_api.php?a=add/host/${this.activity.id}/${hostid}`, () => resolve(), reject)
|
||||||
|
);
|
||||||
|
const found = _.findWhere(this.everyone, { id: hostid });
|
||||||
|
if (found) this.this_hosts.push({ ...found, hostid: hostid });
|
||||||
|
this.host_search_str = "";
|
||||||
|
this.emitPatch({ hostsChanged: true });
|
||||||
|
alert_message("Saved");
|
||||||
|
},
|
||||||
|
|
||||||
|
emitPatch(extra = {}) {
|
||||||
|
// send minimal changes up; parent recomputes "missing"
|
||||||
|
this.$emit("patch", { id: this.activity.id, patch: { ...extra } });
|
||||||
|
},
|
||||||
|
|
||||||
|
close() {
|
||||||
|
this.editing = false;
|
||||||
|
this.$emit("close");
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
const fieldComp = this.$refs.titleField;
|
||||||
|
console.log(fieldComp)
|
||||||
|
const inputEl = fieldComp?.$el?.querySelector("input, textarea");
|
||||||
|
console.log(inputEl)
|
||||||
|
if (inputEl) {
|
||||||
|
inputEl.scrollIntoView({ behavior: "smooth", block: "center" });
|
||||||
|
inputEl.focus();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// Listen for Escape while in edit mode
|
||||||
|
this.keyHandler = (e) => {
|
||||||
|
if (e.key === "Escape") {
|
||||||
|
this.$emit("close");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
window.addEventListener("keydown", this.keyHandler);
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
window.removeEventListener("keydown", this.keyHandler);
|
||||||
|
},
|
||||||
|
template: `<div class="bg-white rounded-lg shadow p-4">
|
||||||
|
<div class="flex justify-between items-center mb-3">
|
||||||
|
<h3 class="font-semibold text-base">Edit: {{ activity.title || '(untitled)' }}</h3>
|
||||||
|
<button @click.prevent="close" class="text-blue-600 underline">Done</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Use your existing field components so persistence stays identical -->
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-3">
|
||||||
|
<field myclass="double" table="conf_sessions" qid="title"
|
||||||
|
:answer="activity.title" :targetid="activity.id" question="Title"
|
||||||
|
ref="titleField"
|
||||||
|
@saved="emitPatch()" />
|
||||||
|
|
||||||
|
<dtpicker table="conf_sessions" qid="starttime"
|
||||||
|
:answer="activity.starttime"
|
||||||
|
:targetid="activity.id" question="Starts at"
|
||||||
|
@saved="emitPatch()" />
|
||||||
|
|
||||||
|
<field myclass="double" table="conf_sessions" qid="length"
|
||||||
|
:answer="activity.length" :targetid="activity.id" question="Length (hrs)"
|
||||||
|
@saved="emitPatch()" />
|
||||||
|
|
||||||
|
<selectmenu_fa table="conf_sessions" qid="mode"
|
||||||
|
:answer="activity.mode" menu="modes_menu"
|
||||||
|
:targetid="activity.id" question="Mode" labelfield="string"
|
||||||
|
@saved="emitPatch()" />
|
||||||
|
|
||||||
|
<field myclass="double" table="conf_sessions" qid="location"
|
||||||
|
:answer="activity.location" :targetid="activity.id" question="Location / Zoom"
|
||||||
|
@saved="emitPatch()" />
|
||||||
|
|
||||||
|
<field myclass="double" table="conf_sessions" qid="location_irl"
|
||||||
|
:answer="activity.location_irl" :targetid="activity.id" question="Location / In Person"
|
||||||
|
@saved="emitPatch()" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Hosts -->
|
||||||
|
<div class="mt-4">
|
||||||
|
<label class="block text-sm font-medium mb-1">Hosts</label>
|
||||||
|
<div class="border rounded p-2 bg-gray-50 space-y-1">
|
||||||
|
<div v-for="h in this_hosts" :key="h.hostid" class="flex items-center justify-between">
|
||||||
|
<span>{{ h.name }}</span>
|
||||||
|
<button class="text-red-600 font-semibold" @click="removeHost(h.hostid)">×</button>
|
||||||
|
</div>
|
||||||
|
<div v-if="!this_hosts.length" class="text-gray-500 italic">None yet</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-2">
|
||||||
|
<input class="border rounded px-2 py-1 w-full"
|
||||||
|
v-model="host_search_str" placeholder="Type to add host…" />
|
||||||
|
<div class="border rounded p-2 bg-white mt-1" v-if="host_search.length">
|
||||||
|
<div v-for="ho in host_search" :key="ho.id"
|
||||||
|
class="flex items-center justify-between text-sm">
|
||||||
|
<span @click="addHost(ho.id)" class="cursor-pointer underline">{{ ho.name }}</span>
|
||||||
|
<button class="text-blue-600 font-semibold" @click="addHost(ho.id)">+</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Description / Type / Parent / Recording -->
|
||||||
|
<div class="mt-4 grid grid-cols-1 md:grid-cols-2 gap-3">
|
||||||
|
<htfield_fa table="conf_sessions" qid="desc" question="Description"
|
||||||
|
myclass="pell" :answer="activity.desc" :targetid="activity.id"
|
||||||
|
@saved="emitPatch()" />
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<selectmenu_fa table="conf_sessions" qid="type"
|
||||||
|
:answer="activity.type" menu="sessiontypes_menu"
|
||||||
|
:targetid="activity.id" question="Type" labelfield="type"
|
||||||
|
@saved="emitPatch()" />
|
||||||
|
<selectmenu_fa v-if="String(activity.type) === '101'"
|
||||||
|
table="conf_sessions" qid="parent"
|
||||||
|
:answer="activity.parent" menu="parents_menu"
|
||||||
|
:targetid="activity.id" question="What flex day?" labelfield="title"
|
||||||
|
@saved="emitPatch()" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<field myclass="double" table="conf_sessions" qid="recording"
|
||||||
|
:answer="activity.recording" :targetid="activity.id" question="Recording Link"
|
||||||
|
@saved="emitPatch()" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>`
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
//
|
|
||||||
//
|
//
|
||||||
//
|
//
|
||||||
//
|
//
|
||||||
|
|
|
||||||
|
|
@ -72,5 +72,6 @@ $MOD_DATE = file_exists(__FILE__) ? date("F d Y H:i.", filemtime(__FILE__)) : "(
|
||||||
<br />
|
<br />
|
||||||
<script src="js/intranet_libs_bottom.js"/></script>
|
<script src="js/intranet_libs_bottom.js"/></script>
|
||||||
<script src="js/dir_app.js"></script>
|
<script src="js/dir_app.js"></script>
|
||||||
|
<script src="<?= $XTRAJS ?>"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ class peter_db {
|
||||||
|
|
||||||
// Constructor to initialize database connection variables
|
// Constructor to initialize database connection variables
|
||||||
public function __construct() {
|
public function __construct() {
|
||||||
$this->DBServer = '192.168.1.6'; // Your DB server (example: localhost)
|
$this->DBServer = '192.168.1.198'; // Your DB server (example: localhost)
|
||||||
$this->DBUser = 'phowell'; // Your DB username
|
$this->DBUser = 'phowell'; // Your DB username
|
||||||
$this->DBPass = 'rolley34'; // Your DB password
|
$this->DBPass = 'rolley34'; // Your DB password
|
||||||
$this->DBName = 'db'; // Your DB name
|
$this->DBName = 'db'; // Your DB name
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue