DOING THINGS
This commit is contained in:
parent
c5a5e5893b
commit
952fbf5dbc
45
buttons.json
Normal file
45
buttons.json
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
{
|
||||
"COLS": [
|
||||
"Normal Announcements",
|
||||
"Emergency Announcements"
|
||||
],
|
||||
"ROWS": [
|
||||
{
|
||||
"text": "Live Page",
|
||||
"btnClass": "btn-primary",
|
||||
"name": "LivePage",
|
||||
"doubleHeight": true,
|
||||
"context": {
|
||||
"context": "custom-emergency.9001.1",
|
||||
"timeout": 30000,
|
||||
"cid": "Live Page"
|
||||
}
|
||||
},
|
||||
{
|
||||
"text": "Emergency Announcement",
|
||||
"btnClass": "btn-danger",
|
||||
"name": "EmergencyAnnouncement",
|
||||
"doubleHeight": true,
|
||||
"faIcon": "fa-solid fa-octagon",
|
||||
"context": {
|
||||
"context": "custom-emergency.9002.1",
|
||||
"timeout": 30000,
|
||||
"cid": "Emergency Announcement"
|
||||
}
|
||||
},
|
||||
{},
|
||||
{
|
||||
"text": "Weather Alert",
|
||||
"btnClass": "btn-warning",
|
||||
"name": "WeatherAlert",
|
||||
"faIcon": "fa-regular fa-cloud-bolt",
|
||||
"doubleHeight": false,
|
||||
"context": {
|
||||
"context": "custom-emergency.9003.1",
|
||||
"timeout": 30000,
|
||||
"cid": "\"Weather Alert\" <9000>",
|
||||
"dial": "9000"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
64
index.ejs
Normal file
64
index.ejs
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
<%
|
||||
// buttons is provided to this template
|
||||
const numCols = (buttons && buttons.COLS && buttons.COLS.length) || 1;
|
||||
const colWidth = (12 / numCols) >= 1 ? Math.floor(12 / numCols) : null;
|
||||
|
||||
// chunk the flat ROWS array into logical rows of numCols items
|
||||
const chunks = [];
|
||||
for (let i = 0; i < (buttons.ROWS || []).length; i += numCols) {
|
||||
chunks.push((buttons.ROWS || []).slice(i, i + numCols));
|
||||
}
|
||||
%>
|
||||
|
||||
<style>
|
||||
.btn-grid { gap: 0.5rem; }
|
||||
.btn-grid .cell { display: flex; align-items: stretch; }
|
||||
.btn-grid .blank-cell { height: 3.75rem; } /* placeholder height */
|
||||
.btn-grid .double-height { height: 9.5rem; } /* adjust to taste */
|
||||
.btn-grid .cell > button { width: 100%; white-space: normal; }
|
||||
</style>
|
||||
|
||||
<div class="container">
|
||||
<!-- column headers -->
|
||||
<div class="row mb-2">
|
||||
<% for (let c = 0; c < numCols; c++) { %>
|
||||
<div class="<%= colWidth ? 'col-' + colWidth : 'col' %>">
|
||||
<h5><%= buttons.COLS[c] || '' %></h5>
|
||||
</div>
|
||||
<% } %>
|
||||
</div>
|
||||
|
||||
<!-- button grid -->
|
||||
<div class="btn-grid">
|
||||
<% chunks.forEach(function(row) { %>
|
||||
<div class="row mb-2">
|
||||
<% for (let c = 0; c < numCols; c++) {
|
||||
const cell = row[c];
|
||||
const isEmpty = !cell || Object.keys(cell).length === 0;
|
||||
const colClass = '<%= colWidth ? "col-' + colWidth + '" : "col" %>'; // placeholder for layout below
|
||||
%>
|
||||
<div class="<%= colWidth ? 'col-' + colWidth : 'col' %> cell">
|
||||
<% if (isEmpty) { %>
|
||||
<div class="blank-cell"></div>
|
||||
<% } else {
|
||||
// build data attributes from context if present
|
||||
const ctx = cell.context || {};
|
||||
const dataAttrs = [];
|
||||
if (ctx.context) dataAttrs.push('data-context="' + ctx.context + '"');
|
||||
if (ctx.timeout) dataAttrs.push('data-timeout="' + ctx.timeout + '"');
|
||||
if (ctx.cid) dataAttrs.push('data-cid="' + ctx.cid + '"');
|
||||
if (ctx.dial) dataAttrs.push('data-dial="' + ctx.dial + '"');
|
||||
%>
|
||||
<button
|
||||
type="button"
|
||||
class="btn <%= cell.btnClass || 'btn-secondary' %> <%= cell.doubleHeight ? 'double-height' : '' %>"
|
||||
name="<%= cell.name || '' %>"
|
||||
<%= dataAttrs.join(' ') %>
|
||||
><%= cell.text || '' %></button>
|
||||
<% } %>
|
||||
</div>
|
||||
<% } %>
|
||||
</div>
|
||||
<% }); %>
|
||||
</div>
|
||||
</div>
|
||||
40
index.js
40
index.js
|
|
@ -3,32 +3,34 @@ const { exec } = require('child_process');
|
|||
|
||||
require('dotenv').config();
|
||||
|
||||
const contexts = {
|
||||
"A1": {
|
||||
context: 'custom-emergency.9001.1',
|
||||
timeout: 30000,
|
||||
cid: 'Live Page'
|
||||
},
|
||||
"E1": {
|
||||
context: 'custom-emergency.9002.1',
|
||||
timeout: 30000,
|
||||
cid: 'Emergency Live Page'
|
||||
},
|
||||
"E2": { // Weather
|
||||
context: 'custom-emergency.9003.1',
|
||||
timeout: 30000,
|
||||
cid: '"Emergency 2" <2100>',
|
||||
number: process.env.PAGING_ADAPTER_EXT // Direct to page group, no phone needed
|
||||
const phonesCfg = require('./phones.json');
|
||||
const buttonsCfg = require('./buttons.json');
|
||||
|
||||
const contexts = {};
|
||||
|
||||
// Generate contexts from buttonsCfg
|
||||
if (buttonsCfg && buttonsCfg.ROWS) {
|
||||
buttonsCfg.ROWS.forEach(button => {
|
||||
if (button.name && button.context) {
|
||||
contexts[button.name] = {
|
||||
context: button.context.context,
|
||||
timeout: button.context.timeout,
|
||||
cid: button.context.cid,
|
||||
...(button.context.dial && { dial: button.context.dial })
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
console.log('Generated contexts:', contexts);
|
||||
|
||||
function trigCall(pageType, phone) {
|
||||
// If contexts[pageType] does not exist, return an error
|
||||
if (!contexts[pageType]) {
|
||||
throw new Error(`Invalid page type: ${pageType}`);
|
||||
}
|
||||
const { context, timeout, cid, number } = contexts[pageType];
|
||||
const targetNumber = number || phone;
|
||||
const { context, timeout, cid, dial } = contexts[pageType];
|
||||
const targetNumber = dial || phone;
|
||||
if (!targetNumber) {
|
||||
throw new Error(`Phone number is required for page type: ${pageType}`);
|
||||
}
|
||||
|
|
@ -90,7 +92,7 @@ function auth(req, res, next) {
|
|||
}
|
||||
|
||||
app.get('/', (req, res) => {
|
||||
res.render('index', { session: req.session });
|
||||
res.render('index', { session: req.session, phones: phonesCfg, buttons: require("./buttons.json") });
|
||||
});
|
||||
|
||||
app.post('/trig', async (req, res) => {
|
||||
|
|
|
|||
6
phones.json
Normal file
6
phones.json
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"Front Desk": "2120",
|
||||
"Cam": "2113",
|
||||
"Cam 2": "2112",
|
||||
"Ryan": "2100"
|
||||
}
|
||||
109
views/index.ejs
109
views/index.ejs
|
|
@ -1,57 +1,94 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" href="/assets/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/tuta-amb/fontawesome-pro@latest/web/css/all.min.css">
|
||||
<title>Funny goofy test page!!!!!1!</title>
|
||||
<style>
|
||||
.btn-tall { height: 152px; }
|
||||
.btn-short { height: 76px; }
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="position-absolute top-0 start-0 m-3">
|
||||
<select class="form-select" id="phoneSelect">
|
||||
<option value="2120">Front Desk</option>
|
||||
<option value="2113">Cam</option>
|
||||
<option value="2112">Cam 2</option>
|
||||
<option value="2100">Ryan</option>
|
||||
<% Object.entries(phones).forEach(([name, number])=> { %>
|
||||
<option value="<%= number %>">
|
||||
<%= name %>
|
||||
</option>
|
||||
<% }); %>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<%
|
||||
// determine columns and chunk rows
|
||||
const cols = (buttons && buttons.COLS) || [''];
|
||||
const numCols = Math.max(1, cols.length);
|
||||
const rowsFlat = (buttons && buttons.ROWS) || [];
|
||||
const chunks = [];
|
||||
for (let i = 0; i < rowsFlat.length; i += numCols) {
|
||||
const slice = rowsFlat.slice(i, i + numCols);
|
||||
// pad short slice with empty objects so layout stays consistent
|
||||
while (slice.length < numCols) slice.push({});
|
||||
chunks.push(slice);
|
||||
}
|
||||
%>
|
||||
|
||||
<div class="container-fluid d-flex justify-content-center align-items-center min-vh-100">
|
||||
<div>
|
||||
<table class="table table-borderless table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-left"><h3>Normal Announcements</h3></th>
|
||||
<th class="text-left"><h3>Emergency Announcements</h3></th>
|
||||
<% for (let c = 0; c < numCols; c++) { %>
|
||||
<th class="text-left">
|
||||
<h3><%= cols[c] || '' %></h3>
|
||||
</th>
|
||||
<% } %>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<% chunks.forEach((row) => { %>
|
||||
<tr>
|
||||
<% for (let c = 0; c < numCols; c++) {
|
||||
const cell = row[c] || {};
|
||||
const isBlank = Object.keys(cell).length === 0;
|
||||
%>
|
||||
<td class="text-left">
|
||||
<button class="btn btn-primary mb-1 w-100" name="A1" onclick="triggerAnnouncement(this)" style="height: 76px;">Live Page</button>
|
||||
</td>
|
||||
<td class="text-left">
|
||||
<button class="btn btn-danger mb-1 w-100" name="E1" onclick="triggerAnnouncement(this)" style="height: 76px;">Emergency Live Page</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="text-left">
|
||||
<button class="btn btn-success mb-1" name="A2" onclick="triggerAnnouncement(this)">Announcement 2</button>
|
||||
</td>
|
||||
<td class="text-left">
|
||||
<button class="btn btn-warning mb-1" name="E2" onclick="triggerAnnouncement(this)">⛈️ Weather</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="text-left">
|
||||
<button class="btn btn-info mb-1" name="A3" onclick="triggerAnnouncement(this)">Announcement 3</button>
|
||||
</td>
|
||||
<td class="text-left">
|
||||
<button class="btn btn-dark mb-1" name="E3" onclick="triggerAnnouncement(this)">Emergency 3</button>
|
||||
<% if (isBlank) { %>
|
||||
<!-- blank space -->
|
||||
<% } else {
|
||||
const ctx = cell.context || {};
|
||||
%>
|
||||
<button
|
||||
class="btn <%= cell.btnClass || 'btn-secondary' %> mb-1 w-100 <%= cell.doubleHeight ? 'btn-tall' : 'btn-short' %> fw-bold fs-4 <%= cell.faIcon ? 'd-flex justify-content-center align-items-center' : '' %>"
|
||||
name="<%= cell.name || '' %>"
|
||||
onclick="triggerAnnouncement(this)"
|
||||
<% if (ctx.context) { %> data-context="<%= ctx.context %>" <% } %>
|
||||
<% if (ctx.timeout) { %> data-timeout="<%= ctx.timeout %>" <% } %>
|
||||
<% if (ctx.cid) { %> data-cid="<%= ctx.cid %>" <% } %>
|
||||
<% if (ctx.dial) { %> data-dial="<%= ctx.dial %>" <% } %>
|
||||
>
|
||||
<% if (cell.faIcon) { %>
|
||||
<i class="<%= cell.faIcon %> me-2" <% if (cell.faColor) { %> style="color: <%= cell.faColor %>" <% } %>></i>
|
||||
<span><%= cell.text || '' %></span>
|
||||
<% } else { %>
|
||||
<%= cell.text || '' %>
|
||||
<% } %>
|
||||
</button>
|
||||
<% } %>
|
||||
</td>
|
||||
<% } %>
|
||||
</tr>
|
||||
<% }); %>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="/assets/js/bootstrap.min.js"></script>
|
||||
<script src="/assets/js/bootstrap.bundle.min.js"></script>
|
||||
<script src="/assets/js/jquery.min.js"></script>
|
||||
|
|
@ -60,15 +97,25 @@
|
|||
const ann = button.getAttribute('name');
|
||||
const phoneSelect = document.getElementById('phoneSelect');
|
||||
const phone = phoneSelect.value;
|
||||
console.log(name)
|
||||
console.log(ann);
|
||||
// send the button's context attrs if you want them server-side
|
||||
const payload = {
|
||||
pageType: ann,
|
||||
phone: phone,
|
||||
context: {
|
||||
context: button.dataset.context,
|
||||
timeout: button.dataset.timeout,
|
||||
cid: button.dataset.cid,
|
||||
dial: button.dataset.dial
|
||||
}
|
||||
};
|
||||
fetch('/trig', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ pageType: ann, phone: phone })
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(payload)
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
Loading…
Reference in a new issue