Files
3d.lehrstun.de/templates/admin_dashboard.html
Hans Puettmann abf96ca8f2 Etappe 2: Cockpit progressiv + Check-In + Paarung + Lehrer-Dashboard
Cockpit (V1, progressives Layout):
- /cockpit zeigt nur Karten, die zum Status passen.
  Onboarding-Karte nur fuer Neue; Paarungs-Karte immer wenn aktiv;
  Drucker-Buchung erst nach L1 fertig.
- Banner-System: ungelesene Lehrer-Notizen, offene Rueckfragen,
  eingegangene Paarungs-Anfragen.

Check-In (V3 + E-021):
- Modal beim ersten Cockpit-Aufruf eines Besuchstages.
- "Ich arbeite weiter" als Default-Ein-Klick-Option.
- Freitext optional.
- POST /cockpit/check-in mit UNIQUE-Constraint pro Tag.

Onboarding-Status (E-019):
- /cockpit/lektion/<nr>/status (selbst-zertifiziert).
- profil_lektion_status-Tabelle, ON CONFLICT-Upsert.

Paarung (E-002):
- profil_anfrage-Tabelle on-demand.
- Anfrage stellen / annehmen / ablehnen / aufloesen.
- Konflikt-Check (keine Doppel-Paarungen).
- Banner im Cockpit des Adressaten.

Lehrer-Dashboard (V5, docs/lehrer-dashboard.md):
- /admin/dashboard mit 4 Sektionen (Wer ist heute hier / Wartet auf
  Review / Drucker-Status / Wer haengt fest) + Schnell-Zahlen + offene
  Rueckfragen.
- "Notiz hinterlassen"-Quick-Action via lehrer_notiz-Tabelle.
- Live-Drucker-Status kommt mit Etappe 5 (aktuell nur DB-Stand).

Daten-Helper getrennt:
- services/cockpit_status.py: Bundle fuer progressives Cockpit
- services/dashboard_status.py: 4-Sektionen-Queries

Vorgezogen aus Etappe 3:
- /lektionen + /lektion/<nr> Minimal-Renderer mit Markdown
- 3 Onboarding-Lektionen als Seeds (Platzhalter-Text fuer Markus)

Smoke-Test bestanden: Login als neuer SuS -> Onboarding ->
Check-In -> L1 fertig -> Paarung mit zweitem SuS -> Admin-Dashboard
-> Lehrer-Notiz -> Banner im Cockpit.
2026-05-24 19:13:19 +02:00

183 lines
8.9 KiB
HTML
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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.
{% extends "base.html" %}
{% block title %}Dashboard — Admin{% endblock %}
{% block content %}
<div class="section-header">
<h1>Dashboard</h1>
<div>
<a href="{{ url_for('admin.profile_liste') }}" class="btn btn-secondary">Profile</a>
<a href="{{ url_for('admin.admin_logout') }}" class="btn btn-secondary">Admin-Logout</a>
</div>
</div>
{# ====== Schnell-Zahlen ====== #}
<div class="grid" style="grid-template-columns: repeat(3, 1fr); gap: 1rem;">
<div class="stat-card">
<div class="number">{{ zahlen.heute_n }}</div>
<div class="label">heute eingecheckt</div>
</div>
<div class="stat-card" style="{% if zahlen.review_n > 0 %}border: 2px solid var(--warning);{% endif %}">
<div class="number" style="{% if zahlen.review_n > 0 %}color: var(--warning);{% endif %}">{{ zahlen.review_n }}</div>
<div class="label">wartet auf Review</div>
</div>
<div class="stat-card" style="{% if zahlen.drucker_problem_n > 0 %}border: 2px solid var(--danger);{% endif %}">
<div class="number" style="{% if zahlen.drucker_problem_n > 0 %}color: var(--danger);{% endif %}">{{ zahlen.drucker_problem_n }}</div>
<div class="label">Drucker-Problem</div>
</div>
</div>
{# ====== Sektion 1: Wer ist heute hier ====== #}
<div class="card" style="margin-top: 1.5rem;">
<h2>👋 Wer ist heute hier ({{ heute|length }})</h2>
{% if heute %}
<table>
<thead>
<tr><th></th><th>Vorname</th><th>Modus</th><th>Notiz</th><th>seit</th><th></th></tr>
</thead>
<tbody>
{% for h in heute %}
<tr>
<td style="font-size: 1.6rem;">{{ tier_emoji(h.tier) }}</td>
<td><strong>{{ h.vorname }}</strong></td>
<td>
{% if h.modus == 'weiter_wie_bisher' %}Arbeitet weiter
{% elif h.modus == 'neues_projekt' %}🆕 Neues Projekt
{% elif h.modus == 'onboarding' %}📚 Onboarding
{% elif h.modus == 'drucken' %}🖨️ Drucken
{% elif h.modus == 'helfen' %}🤝 Hilft anderen
{% else %}{{ h.modus }}{% endif %}
</td>
<td style="font-style: italic; color: var(--text-light); font-size: 0.9rem;">{{ h.freitext or "" }}</td>
<td style="font-size: 0.85rem; color: var(--text-light);">{{ h.erstellt_am[11:16] if h.erstellt_am else '' }}</td>
<td>
<details>
<summary class="btn btn-secondary" style="padding: 0.2rem 0.5rem; font-size: 0.8rem; list-style: none;">Notiz schreiben</summary>
<form method="post" action="{{ url_for('admin.profil_notiz', profil_id=h.profil_id) }}" style="margin-top: 0.4rem; display: flex; gap: 0.3rem;">
<input type="text" name="text" placeholder="Kurze Nachricht …" required
style="padding: 0.3rem; border: 1px solid var(--border); border-radius: 4px; width: 220px;">
<button class="btn btn-primary" style="padding: 0.3rem 0.6rem; font-size: 0.85rem;">senden</button>
</form>
</details>
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<p style="color: var(--text-light);">Noch keiner heute eingecheckt.</p>
{% endif %}
</div>
{# ====== Sektion 2: Wartet auf Review ====== #}
<div class="card">
<h2>⏳ Wartet auf Review ({{ review|length }})</h2>
{% if review %}
<table>
<thead><tr><th></th><th>Projekt</th><th>SuS</th><th>Selbsteinschaetzung</th><th>abgeschlossen</th><th></th></tr></thead>
<tbody>
{% for r in review %}
<tr>
<td style="font-size: 1.4rem;">{{ tier_emoji(r.tier) }}</td>
<td><strong>{{ r.titel }}</strong></td>
<td>{{ r.vorname }}</td>
<td>
{% if r.selbsteinschaetzung == 'zufrieden' %}🟢 zufrieden
{% elif r.selbsteinschaetzung == 'mittel' %}🟡 mittel
{% elif r.selbsteinschaetzung == 'gelernt' %}🔴 gelernt
{% endif %}
</td>
<td style="font-size: 0.85rem; color: var(--text-light);">{{ r.abgeschlossen_am[:10] if r.abgeschlossen_am else '' }}</td>
<td>
<a href="#" class="btn btn-primary" style="padding: 0.2rem 0.6rem; font-size: 0.85rem; opacity: 0.5;" title="Etappe 6">Reviewen</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<p style="color: var(--text-light);">Keine offenen Reviews. <em>(Selbstlern-Loop kommt mit Etappe 6.)</em></p>
{% endif %}
</div>
{# ====== Sektion 3: Drucker-Status ====== #}
<div class="card">
<h2>🖨️ Drucker-Status</h2>
<div class="grid" style="grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 0.8rem;">
{% for d in drucker %}
<div class="drucker-kachel drucker-{{ d.status or 'idle' }}">
<h3 style="margin-bottom: 0.2rem;">{{ d.name }}</h3>
<p style="font-size: 0.85rem; color: var(--text-light); margin: 0;">
{{ d.modell }}{% if not d.geschlossenes_gehaeuse %} · <span style="color: var(--danger);">offen</span>{% endif %}
</p>
<p style="margin-top: 0.5rem; font-weight: 600;">
{% if d.status == 'printing' %}🟢 drucken
{% elif d.status == 'idle' %}⚪ idle
{% elif d.status == 'paused' %}🟡 pausiert
{% elif d.status == 'finished' %}✅ fertig
{% elif d.status == 'failed' %}🔴 Fehler
{% elif d.status == 'offline' %}⚫ offline
{% else %}⚪ kein Status (API noch nicht in Etappe 5){% endif %}
</p>
{% if d.job_name %}<p style="font-size: 0.85rem;">{{ d.job_name }}</p>{% endif %}
{% if d.progress_pct %}<p style="font-size: 0.85rem;">{{ d.progress_pct|round|int }} %{% if d.restzeit_min %} · noch {{ d.restzeit_min }} min{% endif %}</p>{% endif %}
{% if d.fehlermeldung %}<p style="font-size: 0.85rem; color: var(--danger);">{{ d.fehlermeldung }}</p>{% endif %}
{% if d.notiz %}<p style="font-size: 0.8rem; color: var(--text-light); margin-top: 0.5rem;"> {{ d.notiz }}</p>{% endif %}
</div>
{% endfor %}
</div>
</div>
{# ====== Sektion 4: Wer haengt fest ====== #}
<div class="card">
<h2>💤 Wer hängt fest (>2 Wochen kein Check-In)</h2>
{% if haengen_fest %}
<table>
<thead><tr><th></th><th>Vorname</th><th>letzter Check-In</th><th></th></tr></thead>
<tbody>
{% for h in haengen_fest %}
<tr>
<td style="font-size: 1.4rem;">{{ tier_emoji(h.tier) }}</td>
<td><strong>{{ h.vorname }}</strong></td>
<td style="font-size: 0.85rem; color: var(--text-light);">
{% if h.letzter_check_in %}{{ h.letzter_check_in }}{% else %}<em>noch nie</em>{% endif %}
</td>
<td>
<details>
<summary class="btn btn-secondary" style="padding: 0.2rem 0.5rem; font-size: 0.8rem; list-style: none;">Erinnerung</summary>
<form method="post" action="{{ url_for('admin.profil_notiz', profil_id=h.id) }}" style="margin-top: 0.4rem; display: flex; gap: 0.3rem;">
<input type="hidden" name="typ" value="erinnerung">
<input type="text" name="text" placeholder="Hey, lange nicht gesehen — alles okay?" required
style="padding: 0.3rem; border: 1px solid var(--border); border-radius: 4px; width: 240px;">
<button class="btn btn-primary" style="padding: 0.3rem 0.6rem; font-size: 0.85rem;">senden</button>
</form>
</details>
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<p style="color: var(--text-light);">Alle dabei. 👍</p>
{% endif %}
</div>
{# ====== Sektion: Offene Rueckfragen ====== #}
{% if rueckfragen %}
<div class="card" style="border-left: 4px solid var(--warning);">
<h2>❓ Offene Rückfragen ({{ rueckfragen|length }})</h2>
<p style="font-size: 0.85rem; color: var(--text-light);">SuS hat noch nicht nachgebessert.</p>
<ul style="margin-top: 0.5rem; padding-left: 1.2rem;">
{% for r in rueckfragen %}
<li style="margin: 0.3rem 0;">
{{ tier_emoji(r.tier) }} <strong>{{ r.vorname }}</strong> ·
„{{ r.projekt_titel }}" ·
seit {{ r.erstellt_am[:10] }} ·
<em>„{{ r.text[:80] }}{% if r.text|length > 80 %}…{% endif %}"</em>
</li>
{% endfor %}
</ul>
</div>
{% endif %}
{% endblock %}