Files
2026-04-29 04:38:26 +02:00

245 lines
14 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// rooms.jsx — richer Layout-Editor for rooms
const ROOM_LIST = [
{ id: "bc2-1103", name: "BC2 1.103", sub: "20 Sitze · 4 Tische", building: "Campus Belval", capacity: 20, used: "Funktionale Programmierung · SS 2026" },
{ id: "bc2-1207", name: "BC2 1.207", sub: "16 Sitze · 4 Tische", building: "Campus Belval", capacity: 16, used: "Datenbanken · SS 2026" },
{ id: "lib-004", name: "Lib 0.04", sub: "12 Sitze · 3 Tische", building: "Bibliothek", capacity: 12, used: "—" },
{ id: "kirch-12", name: "Kirchberg E12", sub: "24 Sitze · 6 Tische", building: "Kirchberg", capacity: 24, used: "Algorithmen · WS 2025" },
];
const RoomsScreenV2 = () => {
const [activeRoom, setActiveRoom] = React.useState("bc2-1103");
const [activeTool, setActiveTool] = React.useState("seat");
const [selectedEl, setSelectedEl] = React.useState({ kind: "seat", id: "T2-3", x: 540, y: 194, table: "T2" });
const tools = [
{ id: "select", label: "Auswählen", icon: "↖" },
{ id: "seat", label: "Sitz", icon: "○" },
{ id: "table", label: "Tisch", icon: "▭" },
{ id: "door", label: "Tür", icon: "⌐" },
{ id: "window", label: "Fenster", icon: "‖" },
{ id: "gap", label: "Lücke", icon: "·" },
];
const room = ROOM_LIST.find((r) => r.id === activeRoom);
return (
<div style={{ padding: "24px 28px", display: "flex", flexDirection: "column", gap: 16, height: "100%" }}>
<header style={{ display: "flex", alignItems: "flex-end", justifyContent: "space-between", gap: 16 }}>
<div>
<div className="eyebrow">Räume · Layout-Editor</div>
<div className="serif" style={{ fontSize: 30, fontWeight: 500, letterSpacing: "-0.015em", marginTop: 2 }}>
<span className="marker">{room.name}</span> <span style={{ color: "var(--ink-4)" }}>·</span> <span style={{ fontStyle: "italic", color: "var(--ink-3)" }}>{room.building}</span>
</div>
<div className="small" style={{ marginTop: 4 }}>Räume sind kursunabhängig einmal anlegen, mehrere Semester nutzen. Aktuell: {room.used}</div>
</div>
<div style={{ display: "flex", gap: 8 }}>
<button className="btn ghost sm">Vorschau</button>
<button className="btn ghost sm">Als Vorlage</button>
<button className="btn sm"><Icon.check /> Speichern</button>
</div>
</header>
<div style={{ display: "grid", gridTemplateColumns: "210px 1fr 240px", gap: 14, flex: 1, minHeight: 0 }}>
{/* LEFT: Rooms list */}
<div className="card" style={{ padding: 12, display: "flex", flexDirection: "column", gap: 8, overflow: "auto" }}>
<div style={{ display: "flex", alignItems: "center", justifyContent: "space-between" }}>
<div className="eyebrow">Räume</div>
<button className="btn ghost sm" style={{ padding: "3px 7px", fontSize: 11 }}><Icon.plus /></button>
</div>
<div style={{ display: "flex", flexDirection: "column", gap: 2 }}>
{ROOM_LIST.map((r) => (
<button key={r.id} onClick={() => setActiveRoom(r.id)}
style={{
border: "none", textAlign: "left", padding: "8px 9px", borderRadius: 4, cursor: "pointer",
background: r.id === activeRoom ? "rgba(31,27,22,0.06)" : "transparent",
borderLeft: `3px solid ${r.id === activeRoom ? "var(--accent)" : "transparent"}`,
}}>
<div style={{ fontSize: 13, fontWeight: 500 }}>{r.name}</div>
<div className="tiny">{r.sub}</div>
<div className="tiny" style={{ color: "var(--ink-4)", marginTop: 2 }}>{r.building}</div>
</button>
))}
</div>
</div>
{/* CENTER: Toolbar + canvas */}
<div className="card" style={{ display: "flex", flexDirection: "column", overflow: "hidden" }}>
{/* Toolbar */}
<div style={{ borderBottom: "1px solid var(--rule)", padding: "8px 12px", display: "flex", alignItems: "center", gap: 14 }}>
<div style={{ display: "flex", gap: 4 }}>
{tools.map((t) => (
<button key={t.id} onClick={() => setActiveTool(t.id)}
title={t.label}
style={{
padding: "5px 10px", borderRadius: 4, cursor: "pointer",
border: `1px solid ${activeTool === t.id ? "var(--ink)" : "var(--rule)"}`,
background: activeTool === t.id ? "var(--ink)" : "#fbf7ee",
color: activeTool === t.id ? "var(--paper)" : "var(--ink-2)",
fontFamily: "var(--sans)", fontSize: 12, fontWeight: 500,
display: "inline-flex", alignItems: "center", gap: 5,
}}>
<span style={{ fontFamily: "var(--mono)", fontSize: 13 }}>{t.icon}</span>
{t.label}
</button>
))}
</div>
<div style={{ flex: 1 }} />
<div style={{ display: "flex", alignItems: "center", gap: 8 }} className="tiny mono">
<button className="btn ghost sm" style={{ padding: "3px 8px" }}></button>
<span>78%</span>
<button className="btn ghost sm" style={{ padding: "3px 8px" }}>+</button>
<span style={{ color: "var(--rule)" }}>·</span>
<span>Raster: 24px</span>
</div>
</div>
{/* Canvas */}
<div style={{ flex: 1, position: "relative", background: "#efe6d2", overflow: "auto", padding: "28px 30px" }}>
{/* Rulers */}
<div style={{ position: "absolute", top: 8, left: 30, right: 30, height: 16, display: "flex", borderBottom: "1px solid var(--rule)" }} className="tiny mono">
{[0, 100, 200, 300, 400, 500, 600, 700].map((n) => (
<div key={n} style={{ width: 78, fontSize: 9, color: "var(--ink-4)", borderLeft: "1px solid var(--rule-soft)", paddingLeft: 3 }}>{n}</div>
))}
</div>
<div style={{ position: "absolute", top: 28, left: 8, bottom: 8, width: 18, display: "flex", flexDirection: "column", borderRight: "1px solid var(--rule)" }} className="tiny mono">
{[0, 100, 200, 300, 400].map((n) => (
<div key={n} style={{ height: 78, fontSize: 9, color: "var(--ink-4)", borderTop: "1px solid var(--rule-soft)", paddingTop: 1, paddingLeft: 2 }}>{n}</div>
))}
</div>
<div style={{ display: "flex", justifyContent: "center", alignItems: "center", paddingTop: 14, paddingLeft: 14 }}>
<div style={{ position: "relative" }}>
<SeatMap variant="tutor" assignments={{}} scale={0.78} />
{/* Selection ring on T2-3 */}
<div style={{
position: "absolute",
// T2 is at x=470, T2-3 is at t.x + t.w*0.28, t.y + t.h + 22 = (470+200*0.28, 150+70+22) = (526, 242). center.
left: (526 * 0.78) - 22, top: (242 * 0.78) - 22,
width: 44, height: 44, borderRadius: "50%",
border: "2px dashed var(--accent)",
pointerEvents: "none",
animation: "spin 12s linear infinite",
}} />
{/* drag handles */}
{[[0,-1],[1,0],[0,1],[-1,0]].map(([dx,dy], i) => (
<div key={i} style={{
position: "absolute",
left: (526 * 0.78) - 4 + dx * 22,
top: (242 * 0.78) - 4 + dy * 22,
width: 8, height: 8,
background: "#fbf7ee",
border: "1.5px solid var(--accent)",
pointerEvents: "none",
}} />
))}
{/* Marginalia */}
<div className="handwritten" style={{ position: "absolute", top: -10, right: -110, transform: "rotate(-6deg)", width: 130, lineHeight: 1.1 }}>
Sitz aus Palette ziehen <br/>oder Doppelklick
</div>
<div className="handwritten" style={{ position: "absolute", bottom: -8, left: -10, transform: "rotate(3deg)", width: 130, lineHeight: 1.1, color: "var(--ink-3)", fontSize: 16 }}>
Wand mit Snap-Raster
</div>
</div>
</div>
</div>
{/* Bottom status bar */}
<div style={{ borderTop: "1px solid var(--rule)", padding: "6px 12px", display: "flex", alignItems: "center", gap: 14 }} className="tiny mono">
<span>20 Elemente</span>
<span style={{ color: "var(--rule)" }}>·</span>
<span>20 Sitze · 4 Tische · 1 Tür · 1 Fenster · 1 Beamer</span>
<div style={{ flex: 1 }} />
<span style={{ color: "var(--ink-4)" }}>Auto-gespeichert · 11:42</span>
</div>
</div>
{/* RIGHT: Properties + layers */}
<div style={{ display: "flex", flexDirection: "column", gap: 14, overflow: "auto" }}>
<div className="card" style={{ padding: 14 }}>
<div className="eyebrow">Auswahl</div>
<div className="serif" style={{ fontSize: 16, fontWeight: 500, marginTop: 4 }}>Sitz <span className="mono" style={{ fontSize: 14 }}>{selectedEl.id}</span></div>
<div className="tiny" style={{ marginTop: 2 }}>gehört zu Tisch {selectedEl.table}</div>
<div style={{ marginTop: 12, display: "flex", flexDirection: "column", gap: 8 }}>
<FieldV2 label="Bezeichnung" value={selectedEl.id} />
<FieldV2 label="Tisch (Gruppe)" value={selectedEl.table} />
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 8 }}>
<FieldV2 label="X" value={selectedEl.x} mono suffix="px" />
<FieldV2 label="Y" value={selectedEl.y} mono suffix="px" />
</div>
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 8 }}>
<FieldV2 label="∅" value="36" mono suffix="px" />
<FieldV2 label="Rotation" value="0" mono suffix="°" />
</div>
</div>
<div className="div-h" style={{ margin: "12px 0" }} />
<div className="eyebrow">Aktionen</div>
<div style={{ marginTop: 6, display: "flex", flexDirection: "column", gap: 5 }}>
<button className="btn ghost sm" style={{ justifyContent: "flex-start" }}>Duplizieren <span className="tiny mono" style={{ marginLeft: "auto", color: "var(--ink-4)" }}>D</span></button>
<button className="btn ghost sm" style={{ justifyContent: "flex-start" }}>An Tisch ausrichten</button>
<button className="btn ghost sm" style={{ justifyContent: "flex-start", color: "var(--accent)" }}>Löschen <span className="tiny mono" style={{ marginLeft: "auto" }}></span></button>
</div>
</div>
<div className="card" style={{ padding: 14 }}>
<div style={{ display: "flex", alignItems: "center", justifyContent: "space-between" }}>
<div className="eyebrow">Ebenen</div>
<span className="tiny">20</span>
</div>
<div style={{ marginTop: 8, display: "flex", flexDirection: "column", gap: 1, maxHeight: 220, overflow: "auto" }}>
{[
{ kind: "wand", label: "Wände" },
{ kind: "tisch", label: "Tisch T1 + 5 Sitze", count: 6 },
{ kind: "tisch", label: "Tisch T2 + 5 Sitze", count: 6, sel: true },
{ kind: "tisch", label: "Tisch T3 + 5 Sitze", count: 6 },
{ kind: "tisch", label: "Tisch T4 + 5 Sitze", count: 6 },
{ kind: "elem", label: "Pult / Tutor:in" },
{ kind: "elem", label: "Beamer" },
{ kind: "elem", label: "Tür" },
{ kind: "elem", label: "Fenster Nord" },
].map((l, i) => (
<div key={i} style={{
display: "flex", alignItems: "center", gap: 7, padding: "5px 7px", borderRadius: 3,
background: l.sel ? "rgba(138,44,31,0.08)" : "transparent",
fontSize: 12,
color: l.sel ? "var(--accent)" : "var(--ink-2)",
fontWeight: l.sel ? 500 : 400,
}}>
<span style={{ fontFamily: "var(--mono)", fontSize: 11, color: "var(--ink-4)", width: 16 }}>{l.kind === "wand" ? "▢" : l.kind === "tisch" ? "▤" : "·"}</span>
<span style={{ flex: 1 }}>{l.label}</span>
{l.count && <span className="tiny mono" style={{ color: l.sel ? "var(--accent)" : "var(--ink-4)" }}>{l.count}</span>}
</div>
))}
</div>
</div>
</div>
</div>
</div>
);
};
const FieldV2 = ({ label, value, mono, suffix }) => (
<label style={{ display: "flex", flexDirection: "column", gap: 3 }}>
<span className="tiny">{label}</span>
<div style={{ position: "relative" }}>
<input className="input" defaultValue={value} style={{ fontFamily: mono ? "var(--mono)" : "var(--sans)", fontSize: 12, width: "100%", paddingRight: suffix ? 32 : 11 }} />
{suffix && <span className="tiny mono" style={{ position: "absolute", right: 8, top: "50%", transform: "translateY(-50%)", color: "var(--ink-4)" }}>{suffix}</span>}
</div>
</label>
);
// keyframe for selection spin
if (typeof document !== "undefined" && !document.getElementById("rooms-anim")) {
const s = document.createElement("style");
s.id = "rooms-anim";
s.textContent = "@keyframes spin { to { transform: rotate(360deg); } }";
document.head.appendChild(s);
}
Object.assign(window, { RoomsScreenV2 });