245 lines
14 KiB
JavaScript
245 lines
14 KiB
JavaScript
// 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 });
|