start info screen
This commit is contained in:
1 parent
1e1000268a
commit
b44800b5f1
6 files changed
+128
-5
No files matched your search
@@ -0,0 +1,16 @@
|
||||
from pathlib import Path
|
||||
|
||||
from fastapi import APIRouter
|
||||
from fastapi.responses import FileResponse
|
||||
|
||||
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||
|
||||
router = APIRouter(
|
||||
prefix="/info",
|
||||
tags=["Info"]
|
||||
)
|
||||
|
||||
|
||||
@router.get("", response_class=FileResponse)
|
||||
async def get_info():
|
||||
return FileResponse(BASE_DIR / "html" / "info.html")
|
||||
@@ -27,7 +27,7 @@ async def get_infodesk_screenings(
|
||||
select(Screening)
|
||||
.where(Screening.start > now)
|
||||
.order_by(col(Screening.start))
|
||||
.limit(10)
|
||||
.limit(6)
|
||||
)
|
||||
screenings = await session.exec(query)
|
||||
return [ScreeningRead.model_validate(s) for s in screenings.all()]
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="nl">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>VANDAAG — Filmhuis Den Haag</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="/static/style.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="screen">
|
||||
<section class="programme">
|
||||
<h1>Vandaag</h1>
|
||||
<div id="films"></div>
|
||||
</section>
|
||||
<aside class="sidebar">
|
||||
<div class="top">
|
||||
<div class="date" id="date"></div>
|
||||
<div class="logo"><img src="/static/Logo.svg" alt="Filmhuis Den Haag" style="width: 100%; height: auto; filter: invert(1);"></div>
|
||||
</div>
|
||||
<h2>Prijzen</h2>
|
||||
<div id="prijzen"></div>
|
||||
<h3>Kortingsprijzen</h3>
|
||||
<div id="kortingsprijzen"></div>
|
||||
</aside>
|
||||
</div>
|
||||
<script src="/static/info.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -5,6 +5,7 @@ from fastapi import FastAPI
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
|
||||
from fdhview.api.admin import router as admin_router
|
||||
from fdhview.api.info import router as info_router
|
||||
from fdhview.api.screenings import router as screenings_router
|
||||
from fdhview.core.database import init_db
|
||||
|
||||
@@ -24,3 +25,4 @@ app.mount("/static", StaticFiles(directory=BASE_DIR / "static"), name="static")
|
||||
|
||||
app.include_router(admin_router)
|
||||
app.include_router(screenings_router, prefix="/api")
|
||||
app.include_router(info_router)
|
||||
@@ -0,0 +1,63 @@
|
||||
const POLL_MS = 5 * 60 * 1000;
|
||||
|
||||
// "2026-10-13T17:30:00" -> "17:30"
|
||||
function hhmm(iso) {
|
||||
return iso.slice(11, 16);
|
||||
}
|
||||
|
||||
// map kijkwijzer codes to /static/<name>.png
|
||||
function kijkwijzerIcons(codes) {
|
||||
return (codes || []).map(c =>
|
||||
`<img class="kijkwijzer" src="/static/${c}.png" alt="${c}">`
|
||||
).join("");
|
||||
}
|
||||
|
||||
function renderFilms(films) {
|
||||
document.getElementById("films").innerHTML = films.map(f => {
|
||||
const isBoutique = f.screen === "6";
|
||||
return `
|
||||
<div class="film">
|
||||
<div class="times"><div class="start">${hhmm(f.start)}</div><div class="end">${hhmm(f.end)}</div></div>
|
||||
<div class="icons">${kijkwijzerIcons(f.kijkwijzer)}</div>
|
||||
<div class="info">
|
||||
<div class="title">${f.title}</div>
|
||||
<div class="meta">
|
||||
${isBoutique
|
||||
? `<span class="pill">Zaal 6 - Boutique Cinema</span>`
|
||||
: `<span class="zaal">Zaal ${f.screen}</span>`}
|
||||
${f.en_subs ? "<span>EN subs</span>" : ""}
|
||||
</div>
|
||||
</div>
|
||||
${f.seats_sold / f.seats_total > 0.85 ? '<div class="badge-last">Laatste kaarten</div>' : "<div></div>"}
|
||||
</div>`;
|
||||
}).join("");
|
||||
}
|
||||
function renderPrices(prices) {
|
||||
const row = p => `<div class="price-row"><span>${p.label}</span><span class="price">${p.price}</span></div>`;
|
||||
document.getElementById("prijzen").innerHTML = prices.prijzen.map(row).join("");
|
||||
document.getElementById("kortingsprijzen").innerHTML = prices.kortingsprijzen.map(row).join("");
|
||||
}
|
||||
|
||||
function renderClock() {
|
||||
const d = new Date();
|
||||
document.getElementById("date").textContent =
|
||||
d.toLocaleTimeString("nl-NL", { hour: "2-digit", minute: "2-digit" });
|
||||
}
|
||||
|
||||
async function refresh() {
|
||||
try {
|
||||
const [filmsRes, pricesRes] = await Promise.all([
|
||||
fetch("/api/screenings"),
|
||||
fetch("/api/screenings/prices"),
|
||||
]);
|
||||
renderFilms(await filmsRes.json());
|
||||
renderPrices(await pricesRes.json());
|
||||
} catch (err) {
|
||||
console.error("refresh failed, keeping current display:", err);
|
||||
}
|
||||
}
|
||||
|
||||
renderClock();
|
||||
refresh(); // initial paint
|
||||
setInterval(refresh, POLL_MS); // poll every 5 minutes
|
||||
setInterval(renderClock, 30_000); // keep the clock accurate
|
||||
@@ -24,15 +24,26 @@ body {
|
||||
}
|
||||
.film {
|
||||
display: grid;
|
||||
grid-template-columns: 90px 60px 1fr 170px;
|
||||
gap: 12px; align-items: start;
|
||||
grid-template-columns: 90px 160px 1fr 170px;
|
||||
gap: 14px; align-items: start;
|
||||
padding: 16px 0 18px;
|
||||
border-top: 1px solid var(--rule);
|
||||
}
|
||||
.times { line-height: 1.25; }
|
||||
.times .start { font-size: 20px; font-weight: 600; }
|
||||
.times .end { font-size: 20px; color: var(--muted); }
|
||||
.icons { display: flex; gap: 6px; padding-top: 3px; }
|
||||
.icons {
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
gap: 6px;
|
||||
align-items: center;
|
||||
padding-top: 3px;
|
||||
}
|
||||
.icons .kijkwijzer {
|
||||
width: 26px; height: 26px;
|
||||
object-fit: contain;
|
||||
display: block;
|
||||
}
|
||||
.icons .dot {
|
||||
width: 26px; height: 26px; border-radius: 50%;
|
||||
background: var(--badge); color: #fff;
|
||||
@@ -49,7 +60,8 @@ body {
|
||||
.pill {
|
||||
background: #b9bfc9; border-radius: 14px;
|
||||
padding: 4px 14px; font-size: 12px; font-weight: 600;
|
||||
letter-spacing: 1px; text-transform: uppercase;
|
||||
letter-spacing: 1px;
|
||||
text-transform: none; /* "Boutique Cinema" reads like the photo */
|
||||
}
|
||||
.badge-last {
|
||||
justify-self: end; align-self: start; margin-top: 3px;
|
||||
|
||||
Reference in new issue
Block a user