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>
246 lines
13 KiB
HTML
246 lines
13 KiB
HTML
<!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>
|