changes
This commit is contained in:
144
foftickets.js
144
foftickets.js
@@ -23,8 +23,10 @@ const QRSalt="!SaltyMagic5392370662";
|
|||||||
// + Issue Tickets
|
// + Issue Tickets
|
||||||
// + Transfer Tickets Complicated page. (One ticket: static with transfer and QR code. Multi: buttons to transfer or show QR code)
|
// + Transfer Tickets Complicated page. (One ticket: static with transfer and QR code. Multi: buttons to transfer or show QR code)
|
||||||
// Split Transfer into admin (change owner, unused/used/cancelled pulldown) and public (no Used column) versions
|
// Split Transfer into admin (change owner, unused/used/cancelled pulldown) and public (no Used column) versions
|
||||||
// Claim Tickets
|
|
||||||
// + Use Ticket
|
// + Use Ticket
|
||||||
|
// Claim One Claims, displays QRCode, and allows transfer with a single button.
|
||||||
|
// Claim Many Claims, but then displays tickets line by line with update and QRCode functionality
|
||||||
|
// Edit Regex based ticket list. Allows changing Owner, Offered, Used (Dropdown).
|
||||||
//
|
//
|
||||||
// ToDo, Later
|
// ToDo, Later
|
||||||
// + Use a templating engine
|
// + Use a templating engine
|
||||||
@@ -56,12 +58,13 @@ const users = { "teppy@egenesis.com" : { password: hashPW("x6321872X.1"),
|
|||||||
"fallsonfire@gmail.com" : { password: hashPW("indiantreeonfire"), superuser:false }
|
"fallsonfire@gmail.com" : { password: hashPW("indiantreeonfire"), superuser:false }
|
||||||
};
|
};
|
||||||
|
|
||||||
const tickets = { "habitat-1" : { owner: "teppy@egenesis.com" , offered: "", paid: 0.00, used: false },
|
// Status can be "i"=issued, "u"=used, "r"=revoked"
|
||||||
"habitat-2" : { owner: "teppy@egenesis.com" , offered: "", paid: 0.00 },
|
const tickets = { "habitat-1" : { owner: "teppyx@egenesis.com" , offered: "", paid: 0.00, status:"i" },
|
||||||
"habitat-3" : { owner: "ginachen94@gmail.com", offered: "teppy@egenesis.com", paid: 0.00 },
|
"habitat-2" : { owner: "teppyx@egenesis.com" , offered: "", paid: 0.00, status:"u" },
|
||||||
"habitat-4" : { owner: "teppy@egenesis.com" , offered: "noahstern00@gmail.com", paid: 0.00 },
|
"habitat-3" : { owner: "ginachen94@gmail.com", offered: "teppyx@egenesis.com", paid: 0.00, status:"i" },
|
||||||
"habitat-5" : { owner: "teppy@egenesis.com" , offered: "", paid: 0.00, used: true },
|
"habitat-4" : { owner: "teppyx@egenesis.com" , offered: "noahstern00@gmail.com", paid: 0.00, status:"i" },
|
||||||
"habitat-6" : { owner: "teppy@egenesis.com" , offered: "", paid: 0.00 },
|
"habitat-5" : { owner: "teppyx@egenesis.com" , offered: "", paid: 0.00, status:"r" },
|
||||||
|
"habitat-6" : { owner: "teppyx@egenesis.com" , offered: "", paid: 0.00, status:"i" },
|
||||||
};
|
};
|
||||||
|
|
||||||
const camps = { "habitat": { leader: "teppy@egenesis.com", lastid:6 } };
|
const camps = { "habitat": { leader: "teppy@egenesis.com", lastid:6 } };
|
||||||
@@ -82,6 +85,7 @@ app.use(session({
|
|||||||
// Middleware to protect routes
|
// Middleware to protect routes
|
||||||
function requireLogin(req, res, next) {
|
function requireLogin(req, res, next) {
|
||||||
if (req.session.username) return next();
|
if (req.session.username) return next();
|
||||||
|
req.session.returnTo=req.originalUrl;
|
||||||
return res.redirect('/login');
|
return res.redirect('/login');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -139,7 +143,7 @@ function categorizeTickets(username) {
|
|||||||
let offering=0;
|
let offering=0;
|
||||||
let simpleoffered=0;
|
let simpleoffered=0;
|
||||||
let thet="";
|
let thet="";
|
||||||
for (const t in tickets) if (!tickets[t].used) {
|
for (const t in tickets) if (tickets[t].status=="i") {
|
||||||
if (tickets[t].owner==username && tickets[t].offered=="") simpleowner+=1;
|
if (tickets[t].owner==username && tickets[t].offered=="") simpleowner+=1;
|
||||||
else if (tickets[t].owner==username && tickets[t].offered!=username) offering+=1;
|
else if (tickets[t].owner==username && tickets[t].offered!=username) offering+=1;
|
||||||
else if (tickets[t].owner!=username && tickets[t].offered==username) simpleoffered+=1;
|
else if (tickets[t].owner!=username && tickets[t].offered==username) simpleoffered+=1;
|
||||||
@@ -164,16 +168,30 @@ app.get('/camps',requireSuperUser, (req,res) => {
|
|||||||
}
|
}
|
||||||
for (const t in tickets) {
|
for (const t in tickets) {
|
||||||
const parts=t.split("-");
|
const parts=t.split("-");
|
||||||
const campname=parts[0];
|
const campname=parts[0];
|
||||||
const ticketnum=Number(parts[1]);
|
const ticketnum=Number(parts[1]);
|
||||||
// camplist[campname]??={ leader:"", issued:0, claimed:0, used:0 };
|
if (tickets[t].status!="r") {
|
||||||
camplist[campname].issued+=1;
|
camplist[campname].issued+=1;
|
||||||
if (tickets[t].owner!="") camplist[campname].claimed+=1;
|
if (tickets[t].owner!="") camplist[campname].claimed+=1;
|
||||||
if (tickets[t].used) camplist[campname].used+=1;
|
if (tickets[t].status=="u") camplist[campname].used+=1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return res.render("camps",{ username:"Teppy", camps:camplist });
|
return res.render("camps",{ username:req.session.username, camps:camplist });
|
||||||
})
|
})
|
||||||
|
|
||||||
|
app.post("/camps",requireSuperUser,(req,res) => {
|
||||||
|
console.log("New camp: ");
|
||||||
|
const campname=req.body.campname;
|
||||||
|
const leader=req.body.leader;
|
||||||
|
const qty=Number(req.body.qty);
|
||||||
|
camps[campname]??={ leader:leader, lastid:0 };
|
||||||
|
for (let i=0; i<qty; i++) {
|
||||||
|
camps[campname].lastid+=1;
|
||||||
|
tickets[campname+'-'+camps[campname].lastid]={ owner: "", offered: leader, status:"i" };
|
||||||
|
}
|
||||||
|
return res.redirect("/camps");
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
app.get('/issue', requireSuperUser, (req,res) => {
|
app.get('/issue', requireSuperUser, (req,res) => {
|
||||||
const camplist={};
|
const camplist={};
|
||||||
@@ -182,26 +200,68 @@ app.get('/issue', requireSuperUser, (req,res) => {
|
|||||||
const campname=parts[0];
|
const campname=parts[0];
|
||||||
const ticketnum=Number(parts[1]);
|
const ticketnum=Number(parts[1]);
|
||||||
camplist[campname]??={ issued:0, claimed:0, used:0 };
|
camplist[campname]??={ issued:0, claimed:0, used:0 };
|
||||||
camplist[campname].issued+=1;
|
if (tickets[t].status!="r") {
|
||||||
if (tickets[t].owner!="") camplist[campname].claimed+=1;
|
camplist[campname].issued+=1;
|
||||||
if (tickets[t].used) camplist[campname].used+=1;
|
if (tickets[t].owner!="") camplist[campname].claimed+=1;
|
||||||
|
if (tickets[t].status=="u") camplist[campname].used+=1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return res.render("issue",{ username:"Teppy", camps:camplist });
|
return res.render("issue",{ camps:camplist });
|
||||||
})
|
})
|
||||||
|
|
||||||
app.post("/camps",(req,res) => {
|
|
||||||
const campname=req.body.campname;
|
app.get('/camplead', requireLogin, (req,res) => {
|
||||||
const email=req.body.email;
|
let username=req.session.username;
|
||||||
const qty=Number(req.body.qty);
|
const edit={ tickets: {} };
|
||||||
console.log("New camp: ",campname);
|
for (const t in tickets) if (tickets[t].owner==username) {
|
||||||
camps[campname]??={ leader:leader, lastid:0 };
|
edit.tickets[t]={};
|
||||||
for (let i=0; i<qty; i++) {
|
edit.tickets[t].owner=tickets[t].owner;
|
||||||
camps[campname].lastid+=1;
|
edit.tickets[t].offered=tickets[t].offered;
|
||||||
tickets[campname+'-'+camps[campname].lastid]={ owner: "", offered: email, used:false };
|
|
||||||
}
|
}
|
||||||
return res.redirect("/camps");
|
return res.render("camplead",edit);
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
app.get('/editcamp', requireSuperUser, (req,res) => {
|
||||||
|
let campname=req.query.campname;
|
||||||
|
const edit={ username:req.session.username, tickets: {} };
|
||||||
|
for (const t in tickets) {
|
||||||
|
const parts=t.split("-");
|
||||||
|
const cname=parts[0];
|
||||||
|
const tnum=Number(parts[1]);
|
||||||
|
console.log("Cname: ",cname," campname ",campname," tnum ",tnum);
|
||||||
|
if (cname==campname) {
|
||||||
|
edit.tickets[t]={};
|
||||||
|
edit.tickets[t].owner=tickets[t].owner;
|
||||||
|
edit.tickets[t].offered=tickets[t].offered;
|
||||||
|
edit.tickets[t].status=tickets[t].status;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log("Edit is ",edit);
|
||||||
|
return res.render("editcamp",edit);
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
app.get('/mytickets',requireLogin, (req,res)=> {
|
||||||
|
let username=req.session.username;
|
||||||
|
let claimed=0;
|
||||||
|
let owned=0;
|
||||||
|
let theticket="";
|
||||||
|
const edit={ username:req.session.username, tickets: {} };
|
||||||
|
for (const t in tickets) {
|
||||||
|
if (tickets[t].status=="i" && tickets[t].offered==username) { claimed++; tickets[t].owner=username; tickets[t].offered=""; }
|
||||||
|
if (tickets[t].status=="i" && tickets[t].owner==username) {
|
||||||
|
owned++;
|
||||||
|
if (owned==1) theticket=t; else theticket="";
|
||||||
|
edit.tickets[t]={};
|
||||||
|
edit.tickets[t].owner=tickets[t].owner;
|
||||||
|
edit.tickets[t].offered=tickets[t].offered;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (owned==0) return res.render("message",{ username:username, message:"You have no unused tickets" });
|
||||||
|
else if (owned==1) return res.render("oneticket",{ username:username, ticket:theticket, offered:tickets[theticket].offered });
|
||||||
|
else return res.render("manytickets",edit);
|
||||||
|
});
|
||||||
|
|
||||||
// Big Kahuna
|
// Big Kahuna
|
||||||
// If you have zero tickets, show something saying that
|
// If you have zero tickets, show something saying that
|
||||||
@@ -247,11 +307,18 @@ app.post("/toggle", requireSuperUser, (req,res) => {
|
|||||||
const ticket=req.body.ticket;
|
const ticket=req.body.ticket;
|
||||||
const isChecked = req.body.checked;
|
const isChecked = req.body.checked;
|
||||||
tickets[ticket].used=isChecked;
|
tickets[ticket].used=isChecked;
|
||||||
// Perform any server-side logic here
|
|
||||||
res.json({ message: 'Checkbox state received', checked: isChecked });
|
res.json({ message: 'Checkbox state received', checked: isChecked });
|
||||||
})
|
})
|
||||||
|
|
||||||
|
app.post("/changestatus", requireSuperUser, (req,res) => {
|
||||||
|
const ticket=req.body.ticket;
|
||||||
|
console.log("Changestatus ",ticket);
|
||||||
|
tickets[ticket].status=req.body.status;
|
||||||
|
res.json({ message: 'Status received', status: req.body.status });
|
||||||
|
})
|
||||||
|
|
||||||
app.post("/updateoffered", requireLogin, (req,res) => {
|
app.post("/updateoffered", requireLogin, (req,res) => {
|
||||||
|
console.log("UpdateOffered being rubn");
|
||||||
const ticket=req.body.ticket;
|
const ticket=req.body.ticket;
|
||||||
const offered=req.body.offered;
|
const offered=req.body.offered;
|
||||||
if (tickets[ticket].owner!=req.session.username) res.status(500).send("Ticket "+ticket+" owned by someone else");
|
if (tickets[ticket].owner!=req.session.username) res.status(500).send("Ticket "+ticket+" owned by someone else");
|
||||||
@@ -263,6 +330,16 @@ app.post("/updateoffered", requireLogin, (req,res) => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
app.post("/updateticketsu", requireSuperUser, (req,res) => {
|
||||||
|
const ticket=req.body.ticket;
|
||||||
|
const owner=req.body.owner;
|
||||||
|
const offered=req.body.offered;
|
||||||
|
tickets[ticket].owner=req.body.owner;
|
||||||
|
tickets[ticket].offered=req.body.offered;
|
||||||
|
res.json({ message: 'Updated '+ticket });
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
app.get("/useticket",(req,res) => {
|
app.get("/useticket",(req,res) => {
|
||||||
let ticket=req.t;
|
let ticket=req.t;
|
||||||
let hash=req.h;
|
let hash=req.h;
|
||||||
@@ -445,6 +522,13 @@ app.post('/qrcode',requireLogin,async (req,res) => {
|
|||||||
return res.send({ owner:tickets[ticket].owner, qrcode: URL });
|
return res.send({ owner:tickets[ticket].owner, qrcode: URL });
|
||||||
})
|
})
|
||||||
|
|
||||||
|
app.post('/qrcodesu',requireSuperUser,async (req,res) => {
|
||||||
|
const username=req.session.username;
|
||||||
|
const ticket=req.body.ticket;
|
||||||
|
const URL=await QRCode.toDataURL('localhost:3000/useticket?t='+ticket+'&h='+hashQR(ticket,username));
|
||||||
|
return res.send({ owner:tickets[ticket].owner, qrcode: URL });
|
||||||
|
})
|
||||||
|
|
||||||
// Protected routes
|
// Protected routes
|
||||||
app.get('/products', requireLogin, (req, res) => {
|
app.get('/products', requireLogin, (req, res) => {
|
||||||
res.send(`
|
res.send(`
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
document.addEventListener('DOMContentLoaded', function() {
|
|
||||||
const menuIcon = document.getElementById('menu-icon');
|
|
||||||
const navLinks = document.getElementById('nav-links');
|
|
||||||
|
|
||||||
menuIcon.addEventListener('click', function() {
|
|
||||||
navLinks.classList.toggle('active');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|||||||
@@ -37,15 +37,48 @@
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.navbar {
|
|
||||||
position: relative;
|
/* General Reset */
|
||||||
background: #333;
|
body {
|
||||||
color: #fff;
|
margin: 0;
|
||||||
display: inline-block;
|
font-family: Arial, sans-serif;
|
||||||
align-items: center;
|
display: flex; /* Ensure the body uses a flex container */
|
||||||
padding: 0.5rem 1rem;
|
height: 100vh; /* Full viewport height */
|
||||||
top:0;
|
}
|
||||||
left:0;
|
|
||||||
|
/* Sidebar (Nav Links) */
|
||||||
|
.nav-links {
|
||||||
|
width: 250px; /* Set a fixed width for the sidebar */
|
||||||
|
background-color: #f4f4f4; /* Light gray background */
|
||||||
|
padding: 20px;
|
||||||
|
box-shadow: 2px 0 5px rgba(0, 0, 0, 0.1); /* Add a slight shadow for separation */
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column; /* Ensure items stack vertically */
|
||||||
|
justify-content: flex-start; /* Optional: adjust spacing of items */
|
||||||
|
min-height: 100vh; /* Stretch to full viewport height */
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-links nav ul {
|
||||||
|
list-style: none; /* Remove bullet points */
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-links nav ul li {
|
||||||
|
margin: 10px 0; /* Add spacing between links */
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-links nav ul li a {
|
||||||
|
text-decoration: none; /* Remove underline */
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-links nav ul li a:hover {
|
||||||
|
color: #007BFF; /* Change link color on hover */
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
flex-grow: 1; /* Allow content to fill the remaining space */
|
||||||
|
padding: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.menu-icon {
|
.menu-icon {
|
||||||
@@ -62,38 +95,3 @@
|
|||||||
transition: 0.4s;
|
transition: 0.4s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-links {
|
|
||||||
list-style: none;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
display: none; /* Hidden by default; shown when .active */
|
|
||||||
position: absolute;
|
|
||||||
top: 100%; /* Appear below the navbar */
|
|
||||||
left: 0; /* Aligned left under the hamburger */
|
|
||||||
width: 200px; /* Fixed width for the dropdown panel */
|
|
||||||
background: #333;
|
|
||||||
box-shadow: 0 2px 8px rgba(0,0,0,0.4); /* Optional shadow for a "dropdown" effect */
|
|
||||||
border-radius: 4px; /* Slight rounding for a nicer look */
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-links li {
|
|
||||||
border-bottom: 1px solid #444;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-links li:last-child {
|
|
||||||
border-bottom: none; /* No border on the last item */
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-links li a {
|
|
||||||
color: #fff;
|
|
||||||
text-decoration: none;
|
|
||||||
display: block;
|
|
||||||
padding: 0.75rem 1rem;
|
|
||||||
white-space: nowrap; /* Prevent awkward text wrapping */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Show the nav-links when .active is toggled by JavaScript */
|
|
||||||
.nav-links.active {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|||||||
118
views/camplead.ejs
Normal file
118
views/camplead.ejs
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Your Tickets (Camp Lead)</title>
|
||||||
|
<link rel="stylesheet" href="styles.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<%- include('partials/nav') %>
|
||||||
|
<div id="QRShow" class="modal">
|
||||||
|
<div id="QRBackground" class="modal-content">
|
||||||
|
<p id="QRBanner">Generating QR Code...</p>
|
||||||
|
<img class="qrcode-image" id="QRCodeImage" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/wcAAgMBAAeWBjwAAAAASUVORK5CYII=" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<h1>Welcome, <%= username %>!</h1>
|
||||||
|
<form id="editor">
|
||||||
|
<div id="server-response">Server Ready</div>
|
||||||
|
<table border="1">
|
||||||
|
<tr>
|
||||||
|
<th>Ticket#</th>
|
||||||
|
<th>Owner</th>
|
||||||
|
<th>Offered To</th>
|
||||||
|
<th>Action</th>
|
||||||
|
</tr>
|
||||||
|
<% for (const t in tickets) { %>
|
||||||
|
<tr>
|
||||||
|
<td><%=t%></td>
|
||||||
|
<td><%=tickets[t].owner%></td>
|
||||||
|
<td><input type="edit" value="<%=tickets[t].offered%>" id="<%=t%>-offered"></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";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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 owners = document.querySelectorAll("[id$=-owner]");
|
||||||
|
owners.forEach(el => {
|
||||||
|
const id0=el.id.slice(0,-6);
|
||||||
|
const OfferedEdit = document.getElementById(id0+"-offered");
|
||||||
|
const ActionButton = document.getElementById(id0+"-action" );
|
||||||
|
OfferedEdit.addEventListener('input', () => { ActionButton.textContent = "Update"; ooEdits[id0]=true; console.log("Changed OfferedEdit:",OfferedEdit.value); });
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -6,8 +6,7 @@
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<%- include('partials/nav') %>
|
<%- include('partials/nav') %>
|
||||||
<h1>Camp Editor (Administrator <%= username %>)</h1>
|
<form id="editor" method="post">
|
||||||
<form id="editor">
|
|
||||||
<table border="1">
|
<table border="1">
|
||||||
<tr>
|
<tr>
|
||||||
<th>Camp</th>
|
<th>Camp</th>
|
||||||
@@ -18,7 +17,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
<% for (const c in camps) { %>
|
<% for (const c in camps) { %>
|
||||||
<tr>
|
<tr>
|
||||||
<td><%=c%></td>
|
<td><a href="/editcamp?campname=<%= c %>"><%=c%></td>
|
||||||
<td><%=camps[c].leader%> </td>
|
<td><%=camps[c].leader%> </td>
|
||||||
<td><%=camps[c].issued%> </td>
|
<td><%=camps[c].issued%> </td>
|
||||||
<td><%=camps[c].claimed%> </td>
|
<td><%=camps[c].claimed%> </td>
|
||||||
@@ -28,16 +27,11 @@
|
|||||||
<tr>
|
<tr>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
<br><b>Create New Group:</b><br>
|
<br><b>Create New Camp:</b><br>
|
||||||
Group Name: <input type="edit" name="campname"><br>
|
Group Name: <input type="edit" name="campname"><br>
|
||||||
Lead Email: <input type="edit" name="email"><br>
|
Lead Email: <input type="edit" name="leader"><br>
|
||||||
Initial Qty: <input type="edit" name="qty"><br>
|
Initial Qty (Optional): <input type="edit" name="qty"><br>
|
||||||
<input type="submit" value="Submit">
|
<input type="submit" value="Submit">
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<script src="/js/nav.js"></script>
|
|
||||||
<script>
|
|
||||||
</script>
|
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
137
views/editcamp.ejs
Normal file
137
views/editcamp.ejs
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Edit Camp</title>
|
||||||
|
<link rel="stylesheet" href="styles.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<%- include('partials/nav') %>
|
||||||
|
<div id="QRShow" class="modal">
|
||||||
|
<div id="QRBackground" class="modal-content">
|
||||||
|
<p id="QRBanner">Generating QR Code...</p>
|
||||||
|
<img class="qrcode-image" id="QRCodeImage" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/wcAAgMBAAeWBjwAAAAASUVORK5CYII=" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<form id="editor">
|
||||||
|
<div id="server-response">Server Ready</div>
|
||||||
|
<table border="1">
|
||||||
|
<tr>
|
||||||
|
<th>Ticket#</th>
|
||||||
|
<th>Owner</th>
|
||||||
|
<th>Offered To</th>
|
||||||
|
<th>Status</th>
|
||||||
|
<th>Action</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>
|
||||||
|
<select id="<%=t%>-status" name="status">
|
||||||
|
<option value="i"<%=tickets[t].status=="i" ? " selected" : ""%>>Issued</option>
|
||||||
|
<option value="u"<%=tickets[t].status=="u" ? " selected" : ""%>>Used</option>
|
||||||
|
<option value="r"<%=tickets[t].status=="r" ? " selected" : ""%>>Revoked</option>
|
||||||
|
</select>
|
||||||
|
</td>
|
||||||
|
<td><button id="<%=t%>-action" type="button"> QRCode </button></td>
|
||||||
|
</tr>
|
||||||
|
<% } %>
|
||||||
|
<tr>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</form>
|
||||||
|
<script>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// JavaScript to change the form element
|
||||||
|
const blankimage=document.getElementById("QRCodeImage").src;
|
||||||
|
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 changeStatus(el) {
|
||||||
|
console.log("Change Status: ",el.target.value);
|
||||||
|
const id=el.target.id.slice(0,-7);
|
||||||
|
const js=JSON.stringify( { ticket: id, status: el.target.value } );
|
||||||
|
const fetchtable={ method:'POST', headers: { 'Content-Type': 'application/json' }, body: js };
|
||||||
|
UpdateSR(1);
|
||||||
|
fetch('/changestatus',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 ownerEdit=document.getElementById(id0+"-owner");
|
||||||
|
const offeredEdit=document.getElementById(id0+"-offered");
|
||||||
|
const js=JSON.stringify( { ticket: id0, owner:ownerEdit.value, offered: offeredEdit.value } );
|
||||||
|
const fetchtable={ method:'POST', headers: { 'Content-Type': 'application/json' }, body: js };
|
||||||
|
fetch('/updateticketsu',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('/qrcodesu',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 pulldowns = document.querySelectorAll("[id$=-status]");
|
||||||
|
pulldowns.forEach(el => {
|
||||||
|
const pd = document.getElementById(el.id);
|
||||||
|
pd.addEventListener("change",changeStatus);
|
||||||
|
});
|
||||||
|
|
||||||
|
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>
|
||||||
@@ -5,28 +5,34 @@
|
|||||||
<link rel="stylesheet" href="styles.css">
|
<link rel="stylesheet" href="styles.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<h1>Welcome, <%= username %>!</h1>
|
<%- include('partials/nav') %>
|
||||||
<form id="editor" action="/issue" method="post">
|
<main class="content">
|
||||||
<table border="1">
|
<form id="editor" action="/issue" method="post">
|
||||||
<tr>
|
<table border="1">
|
||||||
<th>Group</th>
|
<tr>
|
||||||
<th>Quantity</th>
|
<th>Group</th>
|
||||||
<th>Claimed</th>
|
<th>Quantity</th>
|
||||||
<th>Used</th>
|
<th>Claimed</th>
|
||||||
</tr>
|
<th>Used</th>
|
||||||
<% for (const [c,v] of Object.entries(camps)) { %>
|
</tr>
|
||||||
|
<% for (const [c,v] of Object.entries(camps)) { %>
|
||||||
<tr>
|
<tr>
|
||||||
<td><%=c%></td>
|
<td><%=c%></td>
|
||||||
<td><%=v.issued%></td>
|
<td><%=v.issued%></td>
|
||||||
<td><%=v.claimed%></td>
|
<td><%=v.claimed%></td>
|
||||||
<td><%=v.used%></td>
|
<td><%=v.used%></td>
|
||||||
</tr>
|
</tr>
|
||||||
<% } %>
|
<% } %>
|
||||||
</table>
|
</table>
|
||||||
Issue new tickets:<br>
|
Issue new tickets:<br>
|
||||||
Group Name: <input type="edit" name="campname"><br>
|
Group Name: <input type="edit" name="campname"><br>
|
||||||
Lead Email: <input type="edit" name="email"><br>
|
Lead Email: <input type="edit" name="email"><br>
|
||||||
Qty: <input type="edit" name="qty"><br>
|
Qty: <input type="edit" name="qty"><br>
|
||||||
<input type="submit" value="Submit">
|
<input type="submit" value="Submit">
|
||||||
</form>
|
</form>
|
||||||
<script>
|
</main>
|
||||||
|
</body>
|
||||||
|
<script>
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</html>
|
||||||
|
|||||||
12
views/message.ejs
Normal file
12
views/message.ejs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Message</title>
|
||||||
|
<link rel="stylesheet" href="styles.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<%- include('partials/nav') %>
|
||||||
|
<%=message%>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
17
views/oneticket.ejs
Normal file
17
views/oneticket.ejs
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Your Ticket</title>
|
||||||
|
<link rel="stylesheet" href="styles.css">
|
||||||
|
</head>
|
||||||
|
<%- include('partials/nav') %>
|
||||||
|
<body>
|
||||||
|
<form id="editor">
|
||||||
|
Transfer Ticket:
|
||||||
|
<input type="edit" placeholder="yourfriend@xyz.com" value="<%=offered%>" id="offered">
|
||||||
|
<button id="Submit" type="button">Transfer</button>
|
||||||
|
<br>
|
||||||
|
</form>
|
||||||
|
<img width=300 height=300 src="https://via.placeholder.com/150" alt="Placeholder Image">
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -1,17 +1,10 @@
|
|||||||
<!-- views/partials/nav.ejs -->
|
<!-- views/partials/nav.ejs -->
|
||||||
<nav class="navbar">
|
<aside>
|
||||||
<div class="menu-icon" id="menu-icon">
|
<nav class="nav-links">
|
||||||
<!-- The "three lines" hamburger icon -->
|
Welcome, <%= username %>!
|
||||||
<span></span>
|
<ul>
|
||||||
<span></span>
|
<li><a href="/mytickets">View My Tickets</a></li>
|
||||||
<span></span>
|
<li><a href="/camps">View Camps (Admin)</a></li>
|
||||||
</div>
|
</ul>
|
||||||
|
</nav>
|
||||||
<ul class="nav-links" id="nav-links">
|
</aside>
|
||||||
<li><a href="/claim">Claim Tickets</a></li>
|
|
||||||
<li><a href="/view">View My Tickets</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>
|
|
||||||
</nav>
|
|
||||||
|
|||||||
@@ -38,14 +38,6 @@
|
|||||||
</form>
|
</form>
|
||||||
<script>
|
<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
|
// JavaScript to change the form element
|
||||||
|
|||||||
Reference in New Issue
Block a user