start info screen

This commit is contained in:
dreamer committed 2026-10-13 17:46:23 +02:00
1 parent 1e1000268a
commit b44800b5f1
6 files changed
+128 -5

No files matched your search

+16
View File
@@ -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")
+1 -1
View File
@@ -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()]
+30
View File
@@ -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>
+2
View File
@@ -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)
+63
View File
@@ -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
+16 -4
View File
@@ -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;