changes
This commit is contained in:
@@ -64,7 +64,7 @@ const tickets = { "habitat-1" : { owner: "teppy@egenesis.com" , offered: "", pa
|
|||||||
"habitat-6" : { owner: "teppy@egenesis.com" , offered: "", paid: 0.00 },
|
"habitat-6" : { owner: "teppy@egenesis.com" , offered: "", paid: 0.00 },
|
||||||
};
|
};
|
||||||
|
|
||||||
const camps = { "habitat": { issued: 6 } };
|
const camps = { "habitat": { leader: "teppy@egenesis.com", lastid:6 } };
|
||||||
|
|
||||||
const tokens = { "abc" : { username: "teppy@egenesis.com", expires: 0 }
|
const tokens = { "abc" : { username: "teppy@egenesis.com", expires: 0 }
|
||||||
};
|
};
|
||||||
@@ -157,6 +157,24 @@ function categorizeTickets(username) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
app.get('/camps',requireSuperUser, (req,res) => {
|
||||||
|
const camplist={};
|
||||||
|
for (const c in camps) {
|
||||||
|
camplist[c]={ leader:camps[c].leader, issued:0, claimed:0, used:0 };
|
||||||
|
}
|
||||||
|
for (const t in tickets) {
|
||||||
|
const parts=t.split("-");
|
||||||
|
const campname=parts[0];
|
||||||
|
const ticketnum=Number(parts[1]);
|
||||||
|
// camplist[campname]??={ leader:"", issued:0, claimed:0, used:0 };
|
||||||
|
camplist[campname].issued+=1;
|
||||||
|
if (tickets[t].owner!="") camplist[campname].claimed+=1;
|
||||||
|
if (tickets[t].used) camplist[campname].used+=1;
|
||||||
|
}
|
||||||
|
return res.render("camps",{ username:"Teppy", camps:camplist });
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
app.get('/issue', requireSuperUser, (req,res) => {
|
app.get('/issue', requireSuperUser, (req,res) => {
|
||||||
const camplist={};
|
const camplist={};
|
||||||
for (const t in tickets) {
|
for (const t in tickets) {
|
||||||
@@ -171,16 +189,17 @@ app.get('/issue', requireSuperUser, (req,res) => {
|
|||||||
return res.render("issue",{ username:"Teppy", camps:camplist });
|
return res.render("issue",{ username:"Teppy", camps:camplist });
|
||||||
})
|
})
|
||||||
|
|
||||||
app.post("/issue",(req,res) => {
|
app.post("/camps",(req,res) => {
|
||||||
const campname=req.body.campname;
|
const campname=req.body.campname;
|
||||||
const email=req.body.email;
|
const email=req.body.email;
|
||||||
const qty=Number(req.body.qty);
|
const qty=Number(req.body.qty);
|
||||||
camps[campname]??={ issued:0 };
|
console.log("New camp: ",campname);
|
||||||
|
camps[campname]??={ leader:leader, lastid:0 };
|
||||||
for (let i=0; i<qty; i++) {
|
for (let i=0; i<qty; i++) {
|
||||||
camps[campname].issued+=1;
|
camps[campname].lastid+=1;
|
||||||
tickets[campname+'-'+camps[campname].issued]={ owner: "", offered: email, used:false };
|
tickets[campname+'-'+camps[campname].lastid]={ owner: "", offered: email, used:false };
|
||||||
}
|
}
|
||||||
return res.redirect("/issue");
|
return res.redirect("/camps");
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
8
public/js/nav.js
Normal file
8
public/js/nav.js
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
const menuIcon = document.getElementById('menu-icon');
|
||||||
|
const navLinks = document.getElementById('nav-links');
|
||||||
|
|
||||||
|
menuIcon.addEventListener('click', function() {
|
||||||
|
navLinks.classList.toggle('active');
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -41,9 +41,11 @@
|
|||||||
position: relative;
|
position: relative;
|
||||||
background: #333;
|
background: #333;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
display: flex;
|
display: inline-block;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 0.5rem 1rem;
|
padding: 0.5rem 1rem;
|
||||||
|
top:0;
|
||||||
|
left:0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.menu-icon {
|
.menu-icon {
|
||||||
|
|||||||
43
views/camps.ejs
Normal file
43
views/camps.ejs
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Camps</title>
|
||||||
|
<link rel="stylesheet" href="styles.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<%- include('partials/nav') %>
|
||||||
|
<h1>Camp Editor (Administrator <%= username %>)</h1>
|
||||||
|
<form id="editor">
|
||||||
|
<table border="1">
|
||||||
|
<tr>
|
||||||
|
<th>Camp</th>
|
||||||
|
<th>Leader</th>
|
||||||
|
<th>#Issued</th>
|
||||||
|
<th>#Claimed</th>
|
||||||
|
<th>#Used</th>
|
||||||
|
</tr>
|
||||||
|
<% for (const c in camps) { %>
|
||||||
|
<tr>
|
||||||
|
<td><%=c%></td>
|
||||||
|
<td><%=camps[c].leader%> </td>
|
||||||
|
<td><%=camps[c].issued%> </td>
|
||||||
|
<td><%=camps[c].claimed%> </td>
|
||||||
|
<td><%=camps[c].used%> </td>
|
||||||
|
</tr>
|
||||||
|
<% } %>
|
||||||
|
<tr>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<br><b>Create New Group:</b><br>
|
||||||
|
Group Name: <input type="edit" name="campname"><br>
|
||||||
|
Lead Email: <input type="edit" name="email"><br>
|
||||||
|
Initial Qty: <input type="edit" name="qty"><br>
|
||||||
|
<input type="submit" value="Submit">
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<script src="/js/nav.js"></script>
|
||||||
|
<script>
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
134
views/groups.ejs
Normal file
134
views/groups.ejs
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Groups (Camps)</title>
|
||||||
|
<link rel="stylesheet" href="styles.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<%- include('partials/nav') %>
|
||||||
|
<h1>Welcome, <%= username %>!</h1>
|
||||||
|
<form id="editor">
|
||||||
|
<table border="1">
|
||||||
|
<tr>
|
||||||
|
<th>Camp</th>
|
||||||
|
<th>Leader</th>
|
||||||
|
<th>#Issued</th>
|
||||||
|
<th>#Claimed</th>
|
||||||
|
<th>#Used</th>
|
||||||
|
</tr>
|
||||||
|
<% for (const t in tickets) { %>
|
||||||
|
<tr>
|
||||||
|
<td><%=t%></td>
|
||||||
|
<td><input type="edit" value="<%=tickets[t].owner%>" id="<%=t%>-owner"> </td>
|
||||||
|
<td><input type="edit" value="<%=tickets[t].offered%>" id="<%=t%>-offered"></td>
|
||||||
|
<td><input type="checkbox" id="<%=t%>-used"<%=tickets[t].used ? ' checked' : ''%>></td>
|
||||||
|
<td><button id="<%=t%>-action" type="button"> QRCode </button></td>
|
||||||
|
</tr>
|
||||||
|
<% } %>
|
||||||
|
<tr>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</form>
|
||||||
|
<script>
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
const menuIcon = document.getElementById('menu-icon');
|
||||||
|
const navLinks = document.getElementById('nav-links');
|
||||||
|
|
||||||
|
menuIcon.addEventListener('click', function() {
|
||||||
|
navLinks.classList.toggle('active');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// JavaScript to change the form element
|
||||||
|
const blankimage=document.getElementById("QRCodeImage").src;
|
||||||
|
console.log("Blank is ",blankimage);
|
||||||
|
const ResponseDisplay = document.getElementById("server-response");
|
||||||
|
let ResponseStack = 0;
|
||||||
|
let ResponseError="";
|
||||||
|
let ooEdits={}; // Owner or Offer has been edited
|
||||||
|
|
||||||
|
function UpdateSR(delta) {
|
||||||
|
ResponseStack+=delta;
|
||||||
|
if (ResponseError!="") ResponseDisplay.textContent=ResponseError;
|
||||||
|
else if (ResponseStack>0) ResponseDisplay.textContent="Waiting for Server";
|
||||||
|
else ResponseDisplay.textContent="Server Ready";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function toggleUsed(el) {
|
||||||
|
const id=el.target.id.slice(0,-5);
|
||||||
|
const isChecked=this.checked;
|
||||||
|
const js=JSON.stringify( { ticket: id, checked: isChecked } );
|
||||||
|
const fetchtable={ method:'POST', headers: { 'Content-Type': 'application/json' }, body: js };
|
||||||
|
console.log(fetchtable);
|
||||||
|
UpdateSR(1);
|
||||||
|
fetch('/toggle',fetchtable)
|
||||||
|
.then( response => { if (!response.ok) throw new Error(`Server responded with status ${response.status}`); else return response.json(); } )
|
||||||
|
.then( data => UpdateSR(-1) )
|
||||||
|
.catch( error => { console.log("Here is the error!"+error); ResponseError=error; UpdateSR(-1); })
|
||||||
|
}
|
||||||
|
|
||||||
|
document.body.addEventListener("click", event => {
|
||||||
|
const id=event.target.id;
|
||||||
|
console.log("Click event on id ",id);
|
||||||
|
if (id.endsWith("-action")) {
|
||||||
|
const id0=id.slice(0,-7);
|
||||||
|
if (ooEdits[id0]) {
|
||||||
|
UpdateSR(1);
|
||||||
|
const offeredEdit=document.getElementById(id0+"-offered");
|
||||||
|
const js=JSON.stringify( { ticket: id0, offered: offeredEdit.value } );
|
||||||
|
const fetchtable={ method:'POST', headers: { 'Content-Type': 'application/json' }, body: js };
|
||||||
|
fetch('/updateoffered',fetchtable)
|
||||||
|
.then( response => { if (!response.ok) throw new Error(`Server responded with status ${response.status}`); else return response.json(); } )
|
||||||
|
.then( data => { UpdateSR(-1); event.target.textContent="QRCode"; ooEdits[id0]=false; } )
|
||||||
|
.catch( error => { console.log("Here is the error!"+error); ResponseError=error; UpdateSR(-1); })
|
||||||
|
} else {
|
||||||
|
const modal = document.getElementById('QRShow');
|
||||||
|
const qrbanner=document.getElementById('QRBanner');
|
||||||
|
const qrcodeimage=document.getElementById('QRCodeImage');
|
||||||
|
const closeModalSpan = document.querySelector('.close');
|
||||||
|
modal.style.display = 'flex';
|
||||||
|
const js=JSON.stringify( { ticket: id0 } );
|
||||||
|
const fetchtable={ method:'POST', headers: { 'Content-Type': 'application/json' }, body: js };
|
||||||
|
qrcodeimage.src=blankimage;
|
||||||
|
fetch('/qrcode',fetchtable)
|
||||||
|
.then( response => { if (!response.ok) throw new Error(`Server responded with status ${response.status}`); else return response.json(); } )
|
||||||
|
.then( data => {
|
||||||
|
console.log("Data is: ",data);
|
||||||
|
qrbanner.innerText="Ticket: "+id0+" Owner: "+data.owner;
|
||||||
|
qrcodeimage.src=data.qrcode;
|
||||||
|
} )
|
||||||
|
.catch( error => { console.log("Here is the error!"+error); ResponseError=error; UpdateSR(-1); })
|
||||||
|
|
||||||
|
console.log("QRCode ",id0);
|
||||||
|
}
|
||||||
|
} else if (["QRShow","QRCodeImage","QRBackground","QRBanner"].includes(id)) {
|
||||||
|
const modal = document.getElementById('QRShow');
|
||||||
|
modal.style.display="none";
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const checkboxes = document.querySelectorAll("[id$=-used]");
|
||||||
|
checkboxes.forEach(el => {
|
||||||
|
const UsedCheckbox = document.getElementById(el.id);
|
||||||
|
UsedCheckbox.addEventListener("change",toggleUsed);
|
||||||
|
});
|
||||||
|
|
||||||
|
const owners = document.querySelectorAll("[id$=-owner]");
|
||||||
|
owners.forEach(el => {
|
||||||
|
const id0=el.id.slice(0,-6);
|
||||||
|
const OwnerEdit = document.getElementById(id0+"-owner" );
|
||||||
|
const OfferedEdit = document.getElementById(id0+"-offered");
|
||||||
|
const ActionButton = document.getElementById(id0+"-action" );
|
||||||
|
OwnerEdit .addEventListener('input', () => { ActionButton.textContent = "Update"; ooEdits[id0]=true; console.log("Changed OfferedEdit:",OfferedEdit.value); });
|
||||||
|
OfferedEdit.addEventListener('input', () => { ActionButton.textContent = "Update"; ooEdits[id0]=true; console.log("Changed OfferedEdit:",OfferedEdit.value); });
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -8,8 +8,10 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ul class="nav-links" id="nav-links">
|
<ul class="nav-links" id="nav-links">
|
||||||
<li><a href="/">Home</a></li>
|
<li><a href="/claim">Claim Tickets</a></li>
|
||||||
<li><a href="/about">Aboot</a></li>
|
<li><a href="/view">View My Tickets</a></li>
|
||||||
<li><a href="/contact">Contact</a></li>
|
<li><a href="/camps">View Camps</a></li>
|
||||||
|
<li><a href="/issue">Issue Tickets (Admin)</a></li>
|
||||||
|
<li><a href="/edit">Edit Tickets (Admin)</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
|
|||||||
Reference in New Issue
Block a user