490 lines
25 KiB
JavaScript
490 lines
25 KiB
JavaScript
// admin.jsx — Tutor admin shell + supporting screens (dashboard, attendance, rooms, students, login)
|
||
|
||
const TutorShell = ({ active, onNav, children }) => {
|
||
const navItems = [
|
||
{ id: "dashboard", label: "Dashboard" },
|
||
{ id: "live", label: "Live · Sitzplan" },
|
||
{ id: "attendance", label: "Anwesenheit" },
|
||
{ id: "rooms", label: "Räume" },
|
||
{ id: "students", label: "Studierende" },
|
||
];
|
||
return (
|
||
<div className="paper-bg" style={{ width: "100%", height: "100%", display: "grid", gridTemplateColumns: "220px 1fr", overflow: "hidden" }}>
|
||
{/* Sidebar */}
|
||
<aside style={{ borderRight: "1px solid var(--rule)", padding: "20px 18px", background: "rgba(0,0,0,0.015)", display: "flex", flexDirection: "column", gap: 18 }}>
|
||
<div>
|
||
<div className="serif" style={{ fontSize: 22, fontWeight: 500, letterSpacing: "-0.01em" }}>
|
||
Tutor<span style={{ color: "var(--accent)" }}>·</span>manager
|
||
</div>
|
||
<div className="tiny" style={{ marginTop: 2 }}>v0.1 · Puchstein</div>
|
||
</div>
|
||
|
||
<div>
|
||
<div className="eyebrow" style={{ marginBottom: 8 }}>Kurs</div>
|
||
<div style={{ padding: "10px 12px", background: "#fbf7ee", border: "1px solid var(--rule)", borderRadius: 4 }}>
|
||
<div className="serif" style={{ fontSize: 14, fontWeight: 500 }}>{COURSE.name}</div>
|
||
<div className="tiny" style={{ marginTop: 2 }}>{COURSE.semester} · {COURSE.weekday}s</div>
|
||
</div>
|
||
</div>
|
||
|
||
<nav style={{ display: "flex", flexDirection: "column", gap: 1 }}>
|
||
<div className="eyebrow" style={{ marginBottom: 6 }}>Navigation</div>
|
||
{navItems.map((item) => (
|
||
<button key={item.id}
|
||
onClick={() => onNav && onNav(item.id)}
|
||
style={{
|
||
textAlign: "left", border: "none", background: active === item.id ? "rgba(31,27,22,0.08)" : "transparent",
|
||
padding: "8px 10px", borderRadius: 4, cursor: "pointer",
|
||
fontFamily: "var(--sans)", fontSize: 13,
|
||
color: active === item.id ? "var(--ink)" : "var(--ink-2)",
|
||
fontWeight: active === item.id ? 500 : 400,
|
||
display: "flex", alignItems: "center", gap: 8,
|
||
}}>
|
||
<span style={{ width: 4, height: 4, borderRadius: "50%", background: active === item.id ? "var(--accent)" : "var(--ink-4)" }} />
|
||
{item.label}
|
||
</button>
|
||
))}
|
||
</nav>
|
||
|
||
<div style={{ flex: 1 }} />
|
||
|
||
<div style={{ borderTop: "1px solid var(--rule)", paddingTop: 14, display: "flex", alignItems: "center", gap: 8 }}>
|
||
<span style={{ width: 28, height: 28, borderRadius: "50%", background: "var(--ink)", color: "var(--paper)", display: "flex", alignItems: "center", justifyContent: "center", fontSize: 10, fontWeight: 600 }}>LP</span>
|
||
<div style={{ flex: 1, minWidth: 0 }}>
|
||
<div style={{ fontSize: 12, fontWeight: 500 }}>{COURSE.tutorin}</div>
|
||
<div className="tiny" style={{ overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>lina@puchstein.lu</div>
|
||
</div>
|
||
</div>
|
||
</aside>
|
||
|
||
<main style={{ overflow: "auto" }}>{children}</main>
|
||
</div>
|
||
);
|
||
};
|
||
|
||
// Dashboard ─────────────────────────────────────────────────────────────
|
||
const Dashboard = () => {
|
||
const slots = [
|
||
{ id: 1, week: 4, day: "Do, 30. April", time: "14:00 – 15:00", room: "BC2 1.103", status: "open", code: "K7QJMX2P", checkedIn: 14, total: 19 },
|
||
{ id: 2, week: 4, day: "Do, 30. April", time: "17:00 – 18:00", room: "BC2 1.207", status: "closed", code: null, checkedIn: 0, total: 19 },
|
||
{ id: 3, week: 3, day: "Do, 23. April", time: "14:00 – 15:00", room: "BC2 1.103", status: "locked", code: "RX48ZF2K", checkedIn: 17, total: 19 },
|
||
{ id: 4, week: 3, day: "Do, 23. April", time: "17:00 – 18:00", room: "BC2 1.207", status: "locked", code: "QM9WJ3VC", checkedIn: 12, total: 19 },
|
||
{ id: 5, week: 2, day: "Do, 16. April", time: "14:00 – 15:00", room: "BC2 1.103", status: "locked", code: "B2HFNX9P", checkedIn: 18, total: 19 },
|
||
{ id: 6, week: 2, day: "Do, 16. April", time: "17:00 – 18:00", room: "BC2 1.207", status: "locked", code: "VJ5KQM7R", checkedIn: 15, total: 19 },
|
||
{ id: 7, week: 1, day: "Do, 09. April", time: "14:00 – 15:00", room: "— (Papier)", status: "locked", code: null, checkedIn: 16, total: 19 },
|
||
];
|
||
|
||
return (
|
||
<div style={{ padding: "28px 36px", display: "flex", flexDirection: "column", gap: 22 }}>
|
||
<header style={{ display: "flex", alignItems: "flex-end", justifyContent: "space-between", gap: 16 }}>
|
||
<div>
|
||
<div className="eyebrow">Dashboard</div>
|
||
<div className="serif" style={{ fontSize: 36, fontWeight: 500, letterSpacing: "-0.015em", marginTop: 2 }}>
|
||
Diese Woche, <span className="marker">Woche 04</span>
|
||
</div>
|
||
<div className="small" style={{ marginTop: 6 }}>2 Slots geplant · 1 läuft gerade · 14 von 19 sind eingecheckt.</div>
|
||
</div>
|
||
<div style={{ display: "flex", gap: 8 }}>
|
||
<button className="btn ghost"><Icon.download /> Export</button>
|
||
<button className="btn"><Icon.plus /> Neuer Slot</button>
|
||
</div>
|
||
</header>
|
||
|
||
{/* Stat row */}
|
||
<div style={{ display: "grid", gridTemplateColumns: "repeat(4, 1fr)", gap: 14 }}>
|
||
<StatCard label="Anwesend gerade" value="14" suffix="/ 19" accent="var(--green)" hint="Slot K7QJ-MX2P" />
|
||
<StatCard label="Ø Anwesenheit" value="84%" hint="Über 4 Wochen" />
|
||
<StatCard label="Bonus vergeben" value="186" suffix="Punkte" hint="Saison gesamt" />
|
||
<StatCard label="Offene Notizen" value="7" hint="ohne Eintrag diese Woche" accent="var(--accent)" />
|
||
</div>
|
||
|
||
{/* Slots table */}
|
||
<section className="card" style={{ overflow: "hidden" }}>
|
||
<div style={{ padding: "14px 18px", display: "flex", alignItems: "center", justifyContent: "space-between", borderBottom: "1px solid var(--rule)" }}>
|
||
<div>
|
||
<div className="serif" style={{ fontSize: 18, fontWeight: 500 }}>Slots</div>
|
||
<UnderlineStroke width={50} />
|
||
</div>
|
||
<div className="small">Sortiert: neueste zuerst</div>
|
||
</div>
|
||
<table style={{ width: "100%", borderCollapse: "collapse", fontSize: 13 }}>
|
||
<thead>
|
||
<tr style={{ background: "rgba(0,0,0,0.02)", color: "var(--ink-3)", textAlign: "left" }}>
|
||
<Th>Woche</Th>
|
||
<Th>Datum</Th>
|
||
<Th>Zeit</Th>
|
||
<Th>Raum</Th>
|
||
<Th>Status</Th>
|
||
<Th>Code</Th>
|
||
<Th>Eingecheckt</Th>
|
||
<Th style={{ textAlign: "right" }}>Aktionen</Th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{slots.map((s, i) => (
|
||
<tr key={s.id} className="row-hover" style={{ borderTop: i === 0 ? "none" : "1px solid var(--rule)" }}>
|
||
<Td><span className="mono" style={{ fontSize: 12 }}>W{String(s.week).padStart(2, "0")}</span></Td>
|
||
<Td>{s.day}</Td>
|
||
<Td className="mono" style={{ fontSize: 12 }}>{s.time}</Td>
|
||
<Td>{s.room}</Td>
|
||
<Td><StatusPill status={s.status} /></Td>
|
||
<Td>{s.code ? <span className="mono" style={{ fontSize: 12, letterSpacing: "0.08em" }}>{s.code}</span> : <span className="tiny">—</span>}</Td>
|
||
<Td>
|
||
<div style={{ display: "flex", alignItems: "center", gap: 8 }}>
|
||
<span style={{ fontVariantNumeric: "tabular-nums" }}>{s.checkedIn} / {s.total}</span>
|
||
<div style={{ width: 48, height: 4, background: "var(--paper-2)", borderRadius: 2, overflow: "hidden" }}>
|
||
<div style={{ width: `${(s.checkedIn / s.total) * 100}%`, height: "100%", background: s.status === "locked" ? "var(--ink-3)" : "var(--accent)" }} />
|
||
</div>
|
||
</div>
|
||
</Td>
|
||
<Td style={{ textAlign: "right" }}>
|
||
{s.status === "open" && <button className="btn sm"><Icon.lock /> Sperren</button>}
|
||
{s.status === "closed" && <button className="btn sm"><Icon.open /> Öffnen</button>}
|
||
{s.status === "locked" && <button className="btn ghost sm">Anzeigen</button>}
|
||
</Td>
|
||
</tr>
|
||
))}
|
||
</tbody>
|
||
</table>
|
||
</section>
|
||
</div>
|
||
);
|
||
};
|
||
|
||
const Th = ({ children, style }) => <th style={{ padding: "10px 14px", fontFamily: "var(--mono)", fontSize: 10.5, letterSpacing: "0.1em", textTransform: "uppercase", fontWeight: 500, ...style }}>{children}</th>;
|
||
const Td = ({ children, style, className }) => <td className={className} style={{ padding: "12px 14px", ...style }}>{children}</td>;
|
||
|
||
const StatCard = ({ label, value, suffix, hint, accent }) => (
|
||
<div className="card" style={{ padding: "14px 16px" }}>
|
||
<div className="eyebrow">{label}</div>
|
||
<div style={{ display: "flex", alignItems: "baseline", gap: 6, marginTop: 4 }}>
|
||
<span className="serif" style={{ fontSize: 32, fontWeight: 500, color: accent || "var(--ink)", letterSpacing: "-0.01em" }}>{value}</span>
|
||
{suffix && <span className="small">{suffix}</span>}
|
||
</div>
|
||
{hint && <div className="tiny" style={{ marginTop: 4 }}>{hint}</div>}
|
||
</div>
|
||
);
|
||
|
||
// Attendance matrix ─────────────────────────────────────────────────────────────
|
||
const AttendanceMatrix = () => {
|
||
const weeks = [1, 2, 3, 4];
|
||
// deterministic random-ish presence
|
||
const presence = STUDENTS.map((s) => ({
|
||
student: s,
|
||
weeks: weeks.map((w) => {
|
||
const hash = (s.id * 31 + w * 7) % 11;
|
||
return hash > 2; // ~80%
|
||
}),
|
||
}));
|
||
|
||
return (
|
||
<div style={{ padding: "28px 36px", display: "flex", flexDirection: "column", gap: 18 }}>
|
||
<header style={{ display: "flex", alignItems: "flex-end", justifyContent: "space-between" }}>
|
||
<div>
|
||
<div className="eyebrow">Anwesenheit</div>
|
||
<div className="serif" style={{ fontSize: 32, fontWeight: 500, letterSpacing: "-0.015em", marginTop: 2 }}>
|
||
Kursmatrix · <span className="marker">SS 2026</span>
|
||
</div>
|
||
<div className="small" style={{ marginTop: 6 }}>Bonus = Anwesenheiten × 3 Punkte</div>
|
||
</div>
|
||
<div style={{ display: "flex", gap: 8 }}>
|
||
<button className="btn ghost sm"><Icon.download /> CSV</button>
|
||
<button className="btn ghost sm"><Icon.download /> Markdown</button>
|
||
<button className="btn ghost sm"><Icon.download /> SQLite Backup</button>
|
||
</div>
|
||
</header>
|
||
|
||
<div className="card" style={{ overflow: "hidden" }}>
|
||
<div className="tabs" style={{ padding: "0 18px" }}>
|
||
<div className="tab active">Pro Studierende:r</div>
|
||
<div className="tab">Pro Woche</div>
|
||
<div className="tab">Notizen</div>
|
||
</div>
|
||
|
||
<table style={{ width: "100%", borderCollapse: "collapse", fontSize: 13 }}>
|
||
<thead>
|
||
<tr style={{ background: "rgba(0,0,0,0.02)", color: "var(--ink-3)" }}>
|
||
<Th style={{ width: 40 }}>#</Th>
|
||
<Th>Studierende:r</Th>
|
||
{weeks.map((w) => (
|
||
<Th key={w} style={{ textAlign: "center" }}>W{String(w).padStart(2, "0")}</Th>
|
||
))}
|
||
<Th style={{ textAlign: "right" }}>Anwesend</Th>
|
||
<Th style={{ textAlign: "center", width: 60 }}>Bonus</Th>
|
||
<Th style={{ width: 32 }}></Th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{presence.map((row, i) => {
|
||
const count = row.weeks.filter(Boolean).length;
|
||
return (
|
||
<tr key={row.student.id} className="row-hover" style={{ borderTop: "1px solid var(--rule-soft)" }}>
|
||
<Td className="mono tiny">{String(i + 1).padStart(2, "0")}</Td>
|
||
<Td>
|
||
<div style={{ display: "flex", alignItems: "center", gap: 9 }}>
|
||
<span style={{ width: 22, height: 22, borderRadius: "50%", background: "var(--paper-2)", color: "var(--ink-2)", display: "flex", alignItems: "center", justifyContent: "center", fontSize: 9, fontWeight: 600 }}>{row.student.initials}</span>
|
||
<span>{row.student.name}</span>
|
||
</div>
|
||
</Td>
|
||
{row.weeks.map((p, wi) => (
|
||
<Td key={wi} style={{ textAlign: "center" }}>
|
||
{p ? (
|
||
<span style={{ display: "inline-flex", width: 22, height: 22, borderRadius: 3, background: "rgba(74,107,58,0.14)", color: "var(--green)", alignItems: "center", justifyContent: "center" }}>
|
||
<Icon.check />
|
||
</span>
|
||
) : (
|
||
<span style={{ display: "inline-flex", width: 22, height: 22, borderRadius: 3, color: "var(--ink-4)", alignItems: "center", justifyContent: "center", fontSize: 13 }}>—</span>
|
||
)}
|
||
</Td>
|
||
))}
|
||
<Td style={{ textAlign: "right", fontVariantNumeric: "tabular-nums" }}>{count} / {weeks.length}</Td>
|
||
<Td style={{ textAlign: "center" }}>
|
||
{count > 0 ? (
|
||
<span style={{ display: "inline-flex", width: 24, height: 24, borderRadius: "50%", background: "rgba(138,44,31,0.08)", color: "var(--accent)", alignItems: "center", justifyContent: "center" }}>
|
||
<Icon.check />
|
||
</span>
|
||
) : (
|
||
<span className="tiny">—</span>
|
||
)}
|
||
</Td>
|
||
<Td><Icon.arrow style={{ color: "var(--ink-4)" }} /></Td>
|
||
</tr>
|
||
);
|
||
})}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
);
|
||
};
|
||
|
||
// Rooms / layout editor ─────────────────────────────────────────────────────────────
|
||
const RoomsScreen = () => {
|
||
return (
|
||
<div style={{ padding: "28px 36px", display: "flex", flexDirection: "column", gap: 18 }}>
|
||
<header style={{ display: "flex", alignItems: "flex-end", justifyContent: "space-between" }}>
|
||
<div>
|
||
<div className="eyebrow">Räume</div>
|
||
<div className="serif" style={{ fontSize: 32, fontWeight: 500, letterSpacing: "-0.015em", marginTop: 2 }}>
|
||
Layout-Editor · <span className="serif" style={{ fontStyle: "italic" }}>{ROOM.name}</span>
|
||
</div>
|
||
<div className="small" style={{ marginTop: 6 }}>Räume sind kursunabhängig — einmal anlegen, mehrere Semester nutzen.</div>
|
||
</div>
|
||
<div style={{ display: "flex", gap: 8 }}>
|
||
<button className="btn ghost sm">Als Vorlage speichern</button>
|
||
<button className="btn"><Icon.check /> Speichern</button>
|
||
</div>
|
||
</header>
|
||
|
||
<div style={{ display: "grid", gridTemplateColumns: "240px 1fr", gap: 18 }}>
|
||
{/* Tool palette */}
|
||
<div className="card" style={{ padding: 16, display: "flex", flexDirection: "column", gap: 12, height: "fit-content" }}>
|
||
<div>
|
||
<div className="eyebrow">Werkzeuge</div>
|
||
<div style={{ marginTop: 8, display: "grid", gridTemplateColumns: "1fr 1fr", gap: 8 }}>
|
||
<ToolBtn label="Sitz" active />
|
||
<ToolBtn label="Tisch" />
|
||
<ToolBtn label="Tür" />
|
||
<ToolBtn label="Fenster" />
|
||
<ToolBtn label="Lücke" />
|
||
<ToolBtn label="Beamer" />
|
||
</div>
|
||
</div>
|
||
|
||
<div className="div-h" />
|
||
|
||
<div>
|
||
<div className="eyebrow">Eigenschaften</div>
|
||
<div style={{ marginTop: 8, display: "flex", flexDirection: "column", gap: 8 }}>
|
||
<Field label="Bezeichnung" value="T2-3" />
|
||
<Field label="Tisch" value="T2" />
|
||
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 8 }}>
|
||
<Field label="X" value="540" mono />
|
||
<Field label="Y" value="194" mono />
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="div-h" />
|
||
|
||
<div>
|
||
<div className="eyebrow">Räume</div>
|
||
<div style={{ marginTop: 8, display: "flex", flexDirection: "column", gap: 4 }}>
|
||
<RoomItem label="BC2 1.103" sub="20 Sitze · 4 Tische" active />
|
||
<RoomItem label="BC2 1.207" sub="16 Sitze · 4 Tische" />
|
||
<RoomItem label="Lib 0.04" sub="12 Sitze · 3 Tische" />
|
||
<button className="btn ghost sm" style={{ marginTop: 6, justifyContent: "center" }}><Icon.plus /> Neuer Raum</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Editor canvas with seat being dragged */}
|
||
<div className="card" style={{ padding: 18, position: "relative" }}>
|
||
<div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", marginBottom: 12 }}>
|
||
<div className="serif" style={{ fontSize: 16, fontWeight: 500 }}>Bearbeiten</div>
|
||
<div className="tiny mono">760 × 460 · 1× Zoom</div>
|
||
</div>
|
||
<div style={{ display: "flex", justifyContent: "center" }}>
|
||
<SeatMap variant="tutor" assignments={{}} scale={0.78} />
|
||
</div>
|
||
<div className="handwritten" style={{ position: "absolute", top: 90, right: 90, transform: "rotate(-6deg)", maxWidth: 130, lineHeight: 1.1 }}>
|
||
ziehen um Sitz zu verschieben →
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
};
|
||
|
||
const ToolBtn = ({ label, active }) => (
|
||
<button style={{
|
||
padding: "10px 8px", borderRadius: 4, cursor: "pointer",
|
||
border: `1px solid ${active ? "var(--ink)" : "var(--rule)"}`,
|
||
background: active ? "var(--ink)" : "#fbf7ee",
|
||
color: active ? "var(--paper)" : "var(--ink-2)",
|
||
fontFamily: "var(--sans)", fontSize: 12, fontWeight: 500,
|
||
}}>{label}</button>
|
||
);
|
||
|
||
const Field = ({ label, value, mono }) => (
|
||
<label style={{ display: "flex", flexDirection: "column", gap: 3 }}>
|
||
<span className="tiny">{label}</span>
|
||
<input className="input" defaultValue={value} style={{ fontFamily: mono ? "var(--mono)" : "var(--sans)", fontSize: 12 }} />
|
||
</label>
|
||
);
|
||
|
||
const RoomItem = ({ label, sub, active }) => (
|
||
<button style={{
|
||
border: "none", background: active ? "rgba(31,27,22,0.06)" : "transparent",
|
||
textAlign: "left", padding: "7px 9px", borderRadius: 4, cursor: "pointer",
|
||
borderLeft: `3px solid ${active ? "var(--accent)" : "transparent"}`,
|
||
}}>
|
||
<div style={{ fontSize: 13, fontWeight: 500 }}>{label}</div>
|
||
<div className="tiny">{sub}</div>
|
||
</button>
|
||
);
|
||
|
||
// Students CRUD ─────────────────────────────────────────────────────────────
|
||
const StudentsScreen = () => {
|
||
return (
|
||
<div style={{ padding: "28px 36px", display: "flex", flexDirection: "column", gap: 18 }}>
|
||
<header style={{ display: "flex", alignItems: "flex-end", justifyContent: "space-between" }}>
|
||
<div>
|
||
<div className="eyebrow">Studierende</div>
|
||
<div className="serif" style={{ fontSize: 32, fontWeight: 500, letterSpacing: "-0.015em", marginTop: 2 }}>
|
||
{STUDENTS.length} Studierende · {COURSE.name}
|
||
</div>
|
||
</div>
|
||
<div style={{ display: "flex", gap: 8 }}>
|
||
<div style={{ display: "flex", alignItems: "center", gap: 6, padding: "6px 10px", border: "1px solid var(--rule)", borderRadius: 4, background: "#fbf7ee" }}>
|
||
<Icon.search style={{ color: "var(--ink-3)" }} />
|
||
<input placeholder="Suchen…" style={{ border: "none", background: "transparent", outline: "none", fontSize: 13, width: 160 }} />
|
||
</div>
|
||
<button className="btn ghost sm">CSV importieren</button>
|
||
<button className="btn"><Icon.plus /> Hinzufügen</button>
|
||
</div>
|
||
</header>
|
||
|
||
<div className="card" style={{ overflow: "hidden" }}>
|
||
<table style={{ width: "100%", borderCollapse: "collapse", fontSize: 13 }}>
|
||
<thead>
|
||
<tr style={{ background: "rgba(0,0,0,0.02)", color: "var(--ink-3)" }}>
|
||
<Th style={{ width: 40 }}>#</Th>
|
||
<Th>Name</Th>
|
||
<Th>Anwesend</Th>
|
||
<Th>Bonus</Th>
|
||
<Th>Notizen</Th>
|
||
<Th>Letzte Sitzung</Th>
|
||
<Th></Th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{STUDENTS.map((s, i) => {
|
||
const count = (s.id * 7) % 5;
|
||
const noteCount = (s.id * 3) % 4;
|
||
return (
|
||
<tr key={s.id} className="row-hover" style={{ borderTop: "1px solid var(--rule-soft)" }}>
|
||
<Td className="mono tiny">{String(i + 1).padStart(2, "0")}</Td>
|
||
<Td>
|
||
<div style={{ display: "flex", alignItems: "center", gap: 9 }}>
|
||
<span style={{ width: 24, height: 24, borderRadius: "50%", background: "var(--paper-2)", color: "var(--ink-2)", display: "flex", alignItems: "center", justifyContent: "center", fontSize: 10, fontWeight: 600 }}>{s.initials}</span>
|
||
<span>{s.name}</span>
|
||
</div>
|
||
</Td>
|
||
<Td><span style={{ fontVariantNumeric: "tabular-nums" }}>{count} / 4</span></Td>
|
||
<Td>
|
||
{count > 0 ? (
|
||
<span style={{ display: "inline-flex", width: 22, height: 22, borderRadius: "50%", background: "rgba(138,44,31,0.08)", color: "var(--accent)", alignItems: "center", justifyContent: "center" }}>
|
||
<Icon.check />
|
||
</span>
|
||
) : (
|
||
<span className="tiny">—</span>
|
||
)}
|
||
</Td>
|
||
<Td>
|
||
{noteCount > 0 ? (
|
||
<span style={{ display: "inline-flex", alignItems: "center", gap: 5 }}>
|
||
<span style={{ width: 6, height: 6, borderRadius: "50%", background: "var(--accent)" }} />
|
||
<span>{noteCount} Notizen</span>
|
||
</span>
|
||
) : (
|
||
<span className="tiny">—</span>
|
||
)}
|
||
</Td>
|
||
<Td className="tiny">Do, 23. April · T{(s.id % 4) + 1}-{(s.id % 5) + 1}</Td>
|
||
<Td><Icon.arrow style={{ color: "var(--ink-4)" }} /></Td>
|
||
</tr>
|
||
);
|
||
})}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
);
|
||
};
|
||
|
||
// Login ─────────────────────────────────────────────────────────────
|
||
const LoginScreen = () => {
|
||
return (
|
||
<div className="paper-bg" style={{ width: "100%", height: "100%", display: "flex", alignItems: "center", justifyContent: "center", padding: 40 }}>
|
||
<div style={{ width: 420 }}>
|
||
<div className="serif" style={{ fontSize: 38, fontWeight: 500, letterSpacing: "-0.015em" }}>
|
||
Tutor<span style={{ color: "var(--accent)" }}>·</span>manager
|
||
</div>
|
||
<div className="body" style={{ marginTop: 6, color: "var(--ink-3)" }}>
|
||
Anwesenheit & Notizen für Tutorien.
|
||
</div>
|
||
|
||
<div className="card" style={{ marginTop: 28, padding: 26 }}>
|
||
<div className="eyebrow">Anmeldung</div>
|
||
<div className="serif" style={{ fontSize: 22, fontWeight: 500, marginTop: 4 }}>Willkommen zurück</div>
|
||
<UnderlineStroke width={120} />
|
||
|
||
<label style={{ display: "flex", flexDirection: "column", gap: 5, marginTop: 22 }}>
|
||
<span className="tiny">E-Mail</span>
|
||
<input className="input" defaultValue="lina@puchstein.lu" />
|
||
</label>
|
||
<label style={{ display: "flex", flexDirection: "column", gap: 5, marginTop: 14 }}>
|
||
<span className="tiny">Passwort</span>
|
||
<input type="password" className="input" defaultValue="••••••••••" />
|
||
</label>
|
||
|
||
<button className="btn" style={{ width: "100%", justifyContent: "center", marginTop: 22, padding: "11px 14px" }}>
|
||
Anmelden
|
||
</button>
|
||
|
||
<div className="tiny" style={{ marginTop: 16, textAlign: "center", color: "var(--ink-3)" }}>
|
||
Nur für Tutor:innen. Studierende nutzen den vom Beamer projizierten Code.
|
||
</div>
|
||
</div>
|
||
|
||
<div className="handwritten" style={{ marginTop: 18, textAlign: "center" }}>
|
||
~ Donnerstags ab 14 Uhr ~
|
||
</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
};
|
||
|
||
Object.assign(window, { TutorShell, Dashboard, AttendanceMatrix, RoomsScreen, StudentsScreen, LoginScreen });
|