Add Live Queue slide to kiosk rotation

- Added a third kiosk slide showing the active customer queue.
- Customer names are derived from the local-part of their email address.
- Statuses are mapped to friendly labels:
    pending -> Received
    prompt_ready / songs_uploaded -> Trollgorithm Recording in Studio
    revisions_requested -> Waiting for Customer Response
    awaiting_payment -> Payment Due
- Completed/delivered statuses are not shown.
- If the queue overflows, a note points customers to the status page.
- Added queue-only mode (kiosk_cycle_seconds = 1) and updated the
  cycle logic to rotate QR -> pricing -> queue.
- Updated /admin/settings help text for the new mode.

Bumps version to 0.5.3.
This commit is contained in:
Troll (Hermes Agent) 2026-08-05 20:27:33 +00:00
parent 96b6470c25
commit 52558010e8
5 changed files with 129 additions and 19 deletions

View file

@ -272,12 +272,12 @@
<!-- Kiosk cycle -->
<div class="section">
<h2>Kiosk Display Mode</h2>
<p class="copy-hint">Set how the public <a href="{{ url_for('kiosk') }}" target="_blank" rel="noopener noreferrer">/kiosk</a> page cycles. Use -1 for QR only, 0 for pricing only, or 5+ seconds to cycle between them.</p>
<p class="copy-hint">Set how the public <a href="{{ url_for('kiosk') }}" target="_blank" rel="noopener noreferrer">/kiosk</a> page cycles. Use -1 for QR only, 0 for pricing only, 1 for queue only, or 5+ seconds to cycle between QR, pricing, and queue.</p>
<form method="POST">
<input type="hidden" name="action" value="save_kiosk_cycle">
<label for="kiosk_cycle_seconds">Kiosk cycle seconds</label>
<input type="number" id="kiosk_cycle_seconds" name="kiosk_cycle_seconds" value="{{ current_kiosk_cycle_seconds }}" min="-1">
<p class="copy-hint">Examples: -1 = QR only, 0 = pricing only, 10 = swap every 10 seconds.</p>
<p class="copy-hint">Examples: -1 = QR only, 0 = pricing only, 1 = queue only, 10 = rotate QR → pricing → queue every 10 seconds.</p>
<button type="submit">Save Kiosk Mode</button>
</form>
</div>

View file

@ -118,6 +118,53 @@
font-size:1.3rem;
color:#9ca3af;
}
.queue-card{
background:rgba(31,41,55,.85);
border:1px solid rgba(96,165,250,.25);
border-radius:1.2rem;
padding:3vh 4vw;
box-shadow:0 15px 50px rgba(0,0,0,.35);
width:100%;
max-height:62vh;
display:flex;
flex-direction:column;
}
.queue-card h2{
margin:0 0 2vh 0;
font-size:clamp(1.6rem,4vw,2.6rem);
color:#fbbf24;
}
.queue-list{
list-style:none;
margin:0;
padding:0;
text-align:left;
overflow-y:auto;
flex:1;
}
.queue-list li{
display:flex;
justify-content:space-between;
align-items:center;
border-bottom:1px solid rgba(147,197,253,.2);
padding:1.4vh 0;
font-size:clamp(1.2rem,2.8vw,1.7rem);
}
.queue-list li:last-child{border-bottom:none;}
.queue-list .customer{color:#e5e7eb;font-weight:600;}
.queue-list .status{font-weight:700;color:#60a5fa;text-align:right;}
.queue-empty{
font-size:1.4rem;
color:#9ca3af;
text-align:center;
margin:auto 0;
}
.queue-note{
font-size:1rem;
color:#9ca3af;
margin-top:1.5vh;
text-align:center;
}
.footer{
font-size:1rem;
color:#9ca3af;
@ -175,6 +222,25 @@
{% endif %}
</div>
</div>
{% elif mode == 'queue' %}
<div class="slide">
<div class="queue-card">
<h2>Live Queue</h2>
{% if queue %}
<ul class="queue-list">
{% for item in queue %}
<li>
<span class="customer">{{ item.name }}</span>
<span class="status">{{ item.status }}</span>
</li>
{% endfor %}
</ul>
<p class="queue-note">If your request isn't shown, visit the request page and use the status link to check your place.</p>
{% else %}
<p class="queue-empty">No active requests right now. Be the first!</p>
{% endif %}
</div>
</div>
{% else %}
<div class="slide" id="slide-qr">
<h1>Scan to Order</h1>
@ -198,6 +264,24 @@
{% endif %}
</div>
</div>
<div class="slide hidden" id="slide-queue">
<div class="queue-card">
<h2>Live Queue</h2>
{% if queue %}
<ul class="queue-list">
{% for item in queue %}
<li>
<span class="customer">{{ item.name }}</span>
<span class="status">{{ item.status }}</span>
</li>
{% endfor %}
</ul>
<p class="queue-note">If your request isn't shown, visit the request page and use the status link to check your place.</p>
{% else %}
<p class="queue-empty">No active requests right now. Be the first!</p>
{% endif %}
</div>
</div>
{% endif %}
</div>
@ -206,30 +290,28 @@
<div class="dots">
<div class="dot active" id="dot-qr"></div>
<div class="dot" id="dot-prices"></div>
<div class="dot" id="dot-queue"></div>
</div>
</div>
<script>
(function(){
const qr = document.getElementById('slide-qr');
const prices = document.getElementById('slide-prices');
const queue = document.getElementById('slide-queue');
const dotQr = document.getElementById('dot-qr');
const dotPrices = document.getElementById('dot-prices');
const dotQueue = document.getElementById('dot-queue');
const seconds = {{ cycle_seconds }};
if (seconds > 0 && qr && prices) {
let showingQr = true;
const slides = [qr, prices, queue];
const dots = [dotQr, dotPrices, dotQueue];
if (seconds > 0) {
let idx = 0;
setInterval(function(){
showingQr = !showingQr;
if (showingQr) {
qr.classList.remove('hidden');
prices.classList.add('hidden');
dotQr.classList.add('active');
dotPrices.classList.remove('active');
} else {
qr.classList.add('hidden');
prices.classList.remove('hidden');
dotQr.classList.remove('active');
dotPrices.classList.add('active');
}
slides[idx].classList.add('hidden');
dots[idx].classList.remove('active');
idx = (idx + 1) % slides.length;
slides[idx].classList.remove('hidden');
dots[idx].classList.add('active');
}, seconds * 1000);
}
})();