Files
Rotatory/public/index.html
T
ferdi2goandClaude Fable 5.1 8a0f903a40 Oberfläche auf das tradeo Corporate Design umstellen
Nur tradeo Blue und Weiß, Neutraltöne als Deckkraft-Abstufungen. Poppins für
Titel und Kennzeichen, Inter für Bedienelemente, beide selbst gehostet.
Kopfzeile als Markenfläche, Listen mit Trennlinien statt Kästen, Linien-SVGs
statt Emojis, Rot nur noch funktional. Design-System unter docs/DESIGN.md
abgelegt und in CLAUDE.md verankert.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-16 09:33:42 +00:00

246 lines
13 KiB
HTML
Raw 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.
<!doctype html>
<html lang="de">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<meta name="color-scheme" content="light">
<title>Rotatory – Morning-Meeting-Moderation</title>
<link rel="stylesheet" href="fonts/fonts.css">
<link rel="stylesheet" href="style.css">
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Crect width='16' height='16' rx='3' fill='%23122E45'/%3E%3C/svg%3E">
</head>
<body>
<header class="topbar">
<h1>Rotatory</h1>
<nav aria-label="Bereiche">
<a href="#kalender" data-view="kalender">Kalender</a>
<a href="#personen" data-view="personen">Personen</a>
<a href="#abwesenheiten" data-view="abwesenheiten">Abwesenheiten</a>
<a href="#einstellungen" data-view="einstellungen">Einstellungen</a>
</nav>
</header>
<main id="main">
<!-- Kalender -->
<section id="view-kalender" class="view" aria-labelledby="cal-title">
<div class="toolbar">
<div class="month-nav">
<button type="button" id="prev-month" class="icon-button" aria-label="Vorheriger Monat"><svg class="icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M15 6l-6 6 6 6"/></svg></button>
<h2 id="cal-title">Monat</h2>
<button type="button" id="next-month" class="icon-button" aria-label="Nächster Monat"><svg class="icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M9 6l6 6-6 6"/></svg></button>
<button type="button" id="today-month" class="ghost">Heute</button>
</div>
<div class="actions">
<button type="button" id="open-plan" class="primary">Auslosen …</button>
<button type="button" id="open-nextcloud">Nextcloud …</button>
<a id="download-ics" class="button ghost" href="#" download>ICS-Datei</a>
</div>
</div>
<p id="cal-hint" class="hint" hidden></p>
<div class="calendar" aria-labelledby="cal-title">
<div class="weekdays" aria-hidden="true">
<div>Mo</div><div>Di</div><div>Mi</div><div>Do</div><div>Fr</div>
</div>
<div id="cal-grid" class="days"></div>
</div>
<div class="legend">
<span><span class="badge main">H</span> Hauptmoderation</span>
<span><span class="badge co">N</span> Nebenmoderation</span>
<span><span class="lock" aria-hidden="true"><svg class="icon" viewBox="0 0 24 24" aria-hidden="true"><rect x="5" y="11" width="14" height="10" rx="1.5"/><path d="M8 11V7a4 4 0 0 1 8 0v4"/></svg></span> fixiert</span>
<span><span class="avatar empty small" aria-hidden="true">?</span> unbesetzt</span>
</div>
<section class="stats" aria-labelledby="stats-title">
<h3 id="stats-title">Einsätze in diesem Monat</h3>
<div id="stats"></div>
</section>
</section>
<!-- Personen -->
<section id="view-personen" class="view" hidden aria-labelledby="persons-title">
<h2 id="persons-title">Personen</h2>
<form id="person-form" class="card form-grid" autocomplete="off">
<input type="hidden" name="id">
<label>Vorname <input name="firstName" required maxlength="60"></label>
<label>Nachname <input name="lastName" required maxlength="60"></label>
<label>E-Mail (optional, für Kalendereinladungen) <input name="email" type="email" maxlength="120"></label>
<label>Notiz / Grund (optional) <input name="note" maxlength="300" placeholder="z. B. Azubi, nur Nebenmoderation"></label>
<fieldset class="checks">
<legend>Rolle in der Rotation</legend>
<label><input type="checkbox" name="canMain" checked> Hauptmoderation möglich</label>
<label><input type="checkbox" name="canCo" checked> Nebenmoderation möglich</label>
<label><input type="checkbox" name="active" checked> Aktiv (in der Belegschaft)</label>
</fieldset>
<fieldset class="checks">
<legend>Feste freie Wochentage (Teilzeit)</legend>
<label><input type="checkbox" name="freeDays" value="1"> Mo</label>
<label><input type="checkbox" name="freeDays" value="2"> Di</label>
<label><input type="checkbox" name="freeDays" value="3"> Mi</label>
<label><input type="checkbox" name="freeDays" value="4"> Do</label>
<label><input type="checkbox" name="freeDays" value="5"> Fr</label>
</fieldset>
<p class="hint">Beide Rollen abwählen = Nicht-Moderator (bleibt angelegt, wird nie ausgelost).</p>
<div class="form-actions">
<button type="submit" class="primary" id="person-submit">Person anlegen</button>
<button type="button" class="ghost" id="person-cancel" hidden>Abbrechen</button>
<span class="spacer"></span>
<button type="button" id="open-contacts">Aus Nextcloud importieren …</button>
</div>
</form>
<ul id="person-list" class="person-list"></ul>
<input type="file" id="photo-input" accept="image/*" hidden>
</section>
<!-- Abwesenheiten -->
<section id="view-abwesenheiten" class="view" hidden aria-labelledby="absences-title">
<h2 id="absences-title">Abwesenheiten</h2>
<form id="absence-form" class="card form-grid" autocomplete="off">
<label>Person <select name="personId" required></select></label>
<label>Art
<select name="type">
<option value="urlaub">Urlaub</option>
<option value="krankheit">Krankheit (geplant)</option>
<option value="frei">Frei (Gleittag, Teilzeit)</option>
<option value="sonstiges">Sonstiges</option>
</select>
</label>
<label>Von <input type="date" name="from" required></label>
<label>Bis <input type="date" name="to" required></label>
<label class="wide">Notiz (optional) <input name="note" maxlength="200"></label>
<div class="form-actions">
<button type="submit" class="primary">Abwesenheit eintragen</button>
</div>
</form>
<form id="absence-import" class="card form-grid" autocomplete="off">
<h3 class="wide">Aus Nextcloud-Kalender übernehmen</h3>
<label class="wide">Quellkalender (Termine wie „Vorname Nachname (Urlaub)", „… (krank)", „… (frei)")
<select name="calendarHref" required><option value="">– Kalender laden …</option></select>
</label>
<label>Von Monat <input type="month" name="from" required></label>
<label>Bis Monat <input type="month" name="to" required></label>
<p class="hint">Bereits importierte Einträge im Zeitraum werden ersetzt, manuelle bleiben. Namen, die keiner Person zugeordnet werden können, werden aufgelistet.</p>
<p id="absence-import-result" class="result wide" hidden></p>
<div class="form-actions">
<button type="submit" class="primary" id="absence-import-go">Importieren</button>
</div>
</form>
<p class="hint">Abwesenheiten wirken auf die nächste Auslosung. Bereits ausgeloste Tage werden markiert und lassen sich im Kalender neu auslosen.</p>
<label class="inline" id="absence-toggle"><input type="checkbox" id="absence-show-past"> Vergangene anzeigen</label>
<ul id="absence-list" class="list"></ul>
</section>
<!-- Einstellungen -->
<section id="view-einstellungen" class="view" hidden aria-labelledby="settings-title">
<h2 id="settings-title">Einstellungen</h2>
<form id="settings-form" class="card form-grid" autocomplete="off">
<label>Uhrzeit des Meetings (leer = ganztägig) <input type="time" name="meetingTime"></label>
<label>Dauer in Minuten <input type="number" name="durationMinutes" min="5" max="480"></label>
<label>Bundesland (für Feiertage) <select name="bundesland"></select></label>
<label>Fairness-Fenster in Tagen <input type="number" name="historyWindowDays" min="0" max="365"></label>
<label>Kalendername in Nextcloud <input name="calendarDisplayName" maxlength="60"></label>
<label class="inline wide"><input type="checkbox" name="inviteAttendees"> Moderatoren als Teilnehmer eintragen (E-Mail nötig; Nextcloud verschickt dann Einladungen)</label>
<div class="form-actions"><button type="submit" class="primary">Speichern</button></div>
</form>
<h3>Feiertage und betriebsfreie Tage</h3>
<div class="card">
<form id="holiday-generate" class="row">
<label>Gesetzliche Feiertage erzeugen für <input type="number" name="year" min="2000" max="2100" required></label>
<button type="submit">Erzeugen</button>
</form>
<form id="holiday-add" class="row">
<label>Tag <input type="date" name="date" required></label>
<label>Bezeichnung <input name="name" maxlength="80" placeholder="z. B. Betriebsausflug"></label>
<button type="submit">Hinzufügen</button>
</form>
<ul id="holiday-list" class="list compact"></ul>
</div>
<h3>Nextcloud</h3>
<div class="card" id="nextcloud-status"></div>
</section>
</main>
<!-- Dialog: Tag bearbeiten -->
<dialog id="day-dialog">
<form method="dialog" class="dialog-form" id="day-form">
<h2 id="day-dialog-title">Tag</h2>
<p id="day-dialog-info" class="hint"></p>
<label>Hauptmoderation <select name="main"></select></label>
<label>Nebenmoderation <select name="co"></select></label>
<label class="inline"><input type="checkbox" name="locked"> Fixieren (bleibt bei jeder Auslosung erhalten)</label>
<label class="inline"><input type="checkbox" name="off"> Kein Meeting an diesem Tag</label>
<div class="form-actions">
<button type="button" id="day-reroll">Neu auslosen</button>
<button type="button" id="day-clear" class="ghost danger">Entfernen</button>
<span class="spacer"></span>
<button type="button" id="day-close" class="ghost">Abbrechen</button>
<button type="submit" class="primary">Speichern</button>
</div>
</form>
</dialog>
<!-- Dialog: Auslosen -->
<dialog id="plan-dialog">
<form method="dialog" class="dialog-form" id="plan-form">
<h2>Moderation auslosen</h2>
<label>Von Monat <input type="month" name="from" required></label>
<label>Bis Monat <input type="month" name="to" required></label>
<label class="inline"><input type="checkbox" name="overwrite"> Bereits ausgeloste, nicht fixierte Tage neu auslosen</label>
<p class="hint">Werktage ohne Feiertag. Abwesende werden übersprungen, niemand zwei Werktage in Folge, Einsätze möglichst gleich verteilt.</p>
<p id="plan-result" class="result" hidden></p>
<div class="form-actions">
<button type="button" id="plan-clear" class="ghost danger">Zeitraum leeren</button>
<span class="spacer"></span>
<button type="button" class="ghost" id="plan-close">Schließen</button>
<button type="submit" class="primary">Auslosen</button>
</div>
</form>
</dialog>
<!-- Dialog: Nextcloud -->
<dialog id="nextcloud-dialog">
<form method="dialog" class="dialog-form" id="nextcloud-form">
<h2>In Nextcloud schreiben</h2>
<p id="nextcloud-info" class="hint"></p>
<label>Zielkalender
<select name="target"><option value="">– Kalender laden …</option></select>
</label>
<label>Von Monat <input type="month" name="from" required></label>
<label>Bis Monat <input type="month" name="to" required></label>
<label class="inline"><input type="checkbox" name="removeForeign"> Andere Termine im Zeitraum aus dem Zielkalender entfernen (z. B. alte Morning-Meeting-Termine)</label>
<p class="hint">Je Werktag ein Termin. Wiederholtes Schreiben aktualisiert bestehende Termine; entfallene Tage werden im Kalender gelöscht.</p>
<p id="nextcloud-result" class="result" hidden></p>
<div class="form-actions">
<button type="button" id="nextcloud-test">Verbindung testen</button>
<span class="spacer"></span>
<button type="button" class="ghost" id="nextcloud-close">Schließen</button>
<button type="submit" class="primary" id="nextcloud-go">In Nextcloud schreiben</button>
</div>
</form>
</dialog>
<!-- Dialog: Kontakte importieren -->
<dialog id="contacts-dialog">
<form method="dialog" class="dialog-form" id="contacts-form">
<h2>Personen aus Nextcloud</h2>
<label>Adressbuch <select name="addressbook"></select></label>
<p id="contacts-info" class="hint">Lade Kontakte …</p>
<div class="contacts-tools">
<button type="button" class="ghost" id="contacts-all">Alle neuen wählen</button>
<button type="button" class="ghost" id="contacts-none">Keine</button>
</div>
<ul id="contacts-list" class="contacts-list"></ul>
<p id="contacts-result" class="result" hidden></p>
<div class="form-actions">
<span class="spacer"></span>
<button type="button" class="ghost" id="contacts-close">Schließen</button>
<button type="submit" class="primary" id="contacts-go">Ausgewählte anlegen</button>
</div>
</form>
</dialog>
<div id="toast" class="toast" role="status" aria-live="polite" hidden></div>
<script type="module" src="app.js"></script>
</body>
</html>