140 lines
6.6 KiB
HTML
140 lines
6.6 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="de">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Sitzplan</title>
|
|
<style>
|
|
* { box-sizing: border-box; }
|
|
body { font-family: Arial, system-ui, sans-serif; background: #f8fafc; color: #0f172a;
|
|
margin: 0; padding: 2rem 1rem 4rem; }
|
|
h1 { text-align: center; margin: 0 0 0.2em; }
|
|
.hint { text-align: center; color: #64748b; margin: 0 0 1.8rem; font-size: 0.95rem; }
|
|
|
|
.grid { display: grid; grid-template-columns: repeat(5, 150px); gap: 14px;
|
|
justify-content: center; margin: 0 auto; }
|
|
.seat { border: 2px solid #cbd5e1; border-radius: 12px; padding: 10px; background: #fff;
|
|
min-height: 150px; display: flex; flex-direction: column; }
|
|
.seat.over { border-color: #2563eb; background: #eff6ff; }
|
|
.seat .nr { font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.08em; color: #64748b; }
|
|
.dropzone { flex: 1; display: flex; align-items: center; justify-content: center;
|
|
border: 2px dashed #e2e8f0; border-radius: 8px; margin: 6px 0; min-height: 44px;
|
|
color: #94a3b8; font-size: 0.8rem; text-align: center; }
|
|
.chip { background: #1e293b; color: #fff; border-radius: 8px; padding: 6px 12px;
|
|
font-weight: 600; cursor: grab; display: inline-flex; align-items: center; gap: 8px; }
|
|
.chip .x { cursor: pointer; opacity: 0.6; font-weight: 700; }
|
|
.chip .x:hover { opacity: 1; }
|
|
.msg { width: 100%; border: 1px solid #cbd5e1; border-radius: 8px; padding: 6px 8px;
|
|
font-size: 0.85rem; font-family: inherit; }
|
|
.msg:focus { outline: none; border-color: #2563eb; }
|
|
|
|
.pool-wrap { max-width: 800px; margin: 2rem auto 0; }
|
|
.pool-title { font-size: 0.8rem; text-transform: uppercase; letter-spacing: 0.08em; color: #64748b; margin-bottom: 0.4rem; }
|
|
.pool { border: 2px solid #e2e8f0; border-radius: 12px; background: #fff; padding: 14px;
|
|
min-height: 60px; display: flex; flex-wrap: wrap; gap: 10px; align-items: center; }
|
|
.pool.over { border-color: #2563eb; background: #eff6ff; }
|
|
.pool:empty::before { content: "alle Namen sind verteilt"; color: #94a3b8; font-size: 0.85rem; }
|
|
|
|
.bar { max-width: 800px; margin: 1.4rem auto 0; text-align: center; }
|
|
button { background: #2563eb; color: #fff; border: none; border-radius: 10px;
|
|
padding: 0.7em 1.6em; font-size: 1rem; font-family: inherit; cursor: pointer; }
|
|
button:hover { background: #1d4ed8; }
|
|
.status { margin-left: 1rem; color: #16a34a; font-weight: 600; opacity: 0; transition: opacity 0.2s; }
|
|
.status.show { opacity: 1; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
|
|
<h1>Sitzplan</h1>
|
|
<p class="hint">Namen aus der Box unten auf die Plätze ziehen · pro Platz eine kurze Nachricht eintippen · dann speichern.</p>
|
|
|
|
<div class="grid" id="grid"></div>
|
|
|
|
<div class="pool-wrap">
|
|
<div class="pool-title">Namen</div>
|
|
<div class="pool" id="pool"></div>
|
|
</div>
|
|
|
|
<div class="bar">
|
|
<button onclick="speichern()">Speichern</button>
|
|
<span class="status" id="status">✓ gespeichert</span>
|
|
</div>
|
|
|
|
<script>
|
|
const initial = {{ data|tojson }};
|
|
const namen = initial.namen.slice();
|
|
const belegung = {}; // nr -> name ("" = leer)
|
|
const nachricht = {}; // nr -> text
|
|
initial.plaetze.forEach(p => { belegung[p.nr] = p.name || ""; nachricht[p.nr] = p.nachricht || ""; });
|
|
const plaetze = initial.plaetze.map(p => p.nr);
|
|
|
|
function poolNamen() {
|
|
const vergeben = Object.values(belegung).filter(Boolean);
|
|
return namen.filter(n => !vergeben.includes(n));
|
|
}
|
|
|
|
function chip(name, from) {
|
|
const c = document.createElement("div");
|
|
c.className = "chip"; c.draggable = true; c.dataset.name = name;
|
|
c.innerHTML = name + (from !== "pool" ? ' <span class="x" title="entfernen">×</span>' : '');
|
|
c.addEventListener("dragstart", e => e.dataTransfer.setData("text/plain", JSON.stringify({ name, from })));
|
|
if (from !== "pool") {
|
|
c.querySelector(".x").addEventListener("click", () => { belegung[from] = ""; render(); });
|
|
}
|
|
return c;
|
|
}
|
|
|
|
function render() {
|
|
const grid = document.getElementById("grid");
|
|
grid.innerHTML = "";
|
|
plaetze.forEach(nr => {
|
|
const seat = document.createElement("div");
|
|
seat.className = "seat"; seat.dataset.nr = nr;
|
|
seat.innerHTML = '<div class="nr">Platz ' + nr + '</div>';
|
|
const zone = document.createElement("div");
|
|
zone.className = "dropzone";
|
|
if (belegung[nr]) { zone.appendChild(chip(belegung[nr], nr)); }
|
|
else { zone.textContent = "hierher ziehen"; }
|
|
seat.appendChild(zone);
|
|
const inp = document.createElement("input");
|
|
inp.className = "msg"; inp.placeholder = "Nachricht …"; inp.value = nachricht[nr] || "";
|
|
inp.addEventListener("input", e => nachricht[nr] = e.target.value);
|
|
seat.appendChild(inp);
|
|
seat.addEventListener("dragover", e => { e.preventDefault(); seat.classList.add("over"); });
|
|
seat.addEventListener("dragleave", () => seat.classList.remove("over"));
|
|
seat.addEventListener("drop", e => {
|
|
e.preventDefault(); seat.classList.remove("over");
|
|
const { name, from } = JSON.parse(e.dataTransfer.getData("text/plain"));
|
|
if (from !== "pool") belegung[from] = ""; // von altem Platz entfernen
|
|
belegung[nr] = name; // (evtl. alter Name hier wird frei)
|
|
render();
|
|
});
|
|
grid.appendChild(seat);
|
|
});
|
|
const pool = document.getElementById("pool");
|
|
pool.innerHTML = "";
|
|
poolNamen().forEach(n => pool.appendChild(chip(n, "pool")));
|
|
}
|
|
|
|
const pool = document.getElementById("pool");
|
|
pool.addEventListener("dragover", e => { e.preventDefault(); pool.classList.add("over"); });
|
|
pool.addEventListener("dragleave", () => pool.classList.remove("over"));
|
|
pool.addEventListener("drop", e => {
|
|
e.preventDefault(); pool.classList.remove("over");
|
|
const { name } = JSON.parse(e.dataTransfer.getData("text/plain"));
|
|
for (const nr of plaetze) if (belegung[nr] === name) belegung[nr] = "";
|
|
render();
|
|
});
|
|
|
|
function speichern() {
|
|
const daten = { namen, plaetze: plaetze.map(nr => ({ nr, name: belegung[nr], nachricht: nachricht[nr] || "" })) };
|
|
fetch("/speichern", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(daten) })
|
|
.then(r => r.json())
|
|
.then(() => { const s = document.getElementById("status"); s.classList.add("show"); setTimeout(() => s.classList.remove("show"), 1800); });
|
|
}
|
|
|
|
render();
|
|
</script>
|
|
|
|
</body>
|
|
</html>
|