// seatmap.jsx — top-down room layout with tables, chairs, and tutor notes panel // SeatMap — pure visual; takes assignments + selectedStudent + variant + onSeatClick function SeatMap({ assignments = SEAT_ASSIGN, selectedStudent, onSeatClick, hoveredSeat, setHoveredSeat, variant = "tutor", // "tutor" | "student" | "student-self" ownSeat, // for student view: which seat is "mine" scale = 1, }) { const W = ROOM.width, H = ROOM.height; const studentBy = (id) => STUDENTS.find((s) => s.id === id); return (
{/* Inner ruled grid — like graph paper */}
{/* Walls */}
{/* Window */}
Fenster
{/* Door — gap with arc */}
Tür
{/* Beamer */}
Beamer
{/* Podium */}
Pult · Tutor:in
{/* Tables */} {ROOM.tables.map((t) => (
{t.label}
))} {/* Seats */} {SEATS.map((seat) => { const sid = assignments[seat.id]; const student = sid ? studentBy(sid) : null; const isSelected = selectedStudent && sid === selectedStudent; const isHover = hoveredSeat === seat.id; const isOwn = ownSeat === seat.id; let bg, border, label, labelColor; if (variant === "tutor") { if (student) { bg = isSelected ? "var(--ink)" : (isHover ? "#e0d4b6" : "#fbf7ee"); border = isSelected ? "var(--ink)" : "var(--ink-2)"; label = student.initials; labelColor = isSelected ? "#f7eedc" : "var(--ink)"; } else { bg = "#f7f1e3"; border = "var(--ink-4)"; label = ""; labelColor = "var(--ink-4)"; } } else if (variant === "student") { // student picking a seat if (student) { bg = "#d6cdb5"; // grey occupied border = "var(--ink-4)"; label = ""; } else if (isOwn) { bg = "var(--accent)"; border = "var(--accent)"; label = ""; } else { bg = "#fbf7ee"; border = "var(--ink-2)"; label = ""; } } else if (variant === "student-self") { // read-only own seat if (isOwn) { bg = "var(--accent)"; border = "var(--accent)"; } else if (student) { bg = "#d6cdb5"; border = "var(--ink-4)"; } else { bg = "#fbf7ee"; border = "var(--ink-3)"; } label = ""; } return ( ); })} {/* Compass */}
N
); } // ───────────────────────────────────────────────────────────── // Tutor live view — seat map + roster + notes panel // ───────────────────────────────────────────────────────────── function TutorLiveView({ variant = "split" }) { // variant: "split" (map + side panel) | "stacked" (notes below) | "compact" const [selected, setSelected] = React.useState(3); const [hovered, setHovered] = React.useState(null); const [notes, setNotes] = React.useState(NOTES); const presentIds = Object.values(SEAT_ASSIGN); const presentSet = new Set(presentIds); const present = STUDENTS.filter((s) => presentSet.has(s.id)); const absent = STUDENTS.filter((s) => !presentSet.has(s.id)); const sel = STUDENTS.find((s) => s.id === selected); const seatOf = (sid) => Object.entries(SEAT_ASSIGN).find(([, v]) => v === sid)?.[0]; return (
{/* Header bar */}
Tutor:innen-Ansicht · Live
Woche 04 · Donnerstag, 30. April 2026
{COURSE.name}, {COURSE.semester} · {ROOM.name} · 14:00 – 15:00 Uhr
Check-in Code
K7QJ-MX2P
tutor.puchstein.dev/s/K7QJMX2P
{/* Body grid */}
{/* Seat map column */}
Sitzplan
{ const sid = SEAT_ASSIGN[seat.id]; if (sid) setSelected(sid); }} variant="tutor" scale={0.85} />
{/* Tally */}
{/* Notes panel column */}
{/* Roster */}
Studierende
{present.length} anwesend · {absent.length} fehlen
{present.map((s) => { const isSel = s.id === selected; const hasNote = notes[s.id]; return ( ); })} {absent.map((s) => ( ))}
{/* Note editor */}
{sel?.initials}
{sel?.name}
Sitzplatz {seatOf(selected) || "—"} · seit {CHECKED_IN_AT[selected] || "—"}
Präsent
Notiz · Woche 04