Files
3d.lehrstun.de/templates/drucker.html
Hans Puettmann 8cbd803328 Software-Sektion + Drucker-Verwaltung + Slot-System + Selbstlern-Loop
Vier zusammenhaengende Iterationen, die das Funktions-Geruest der AG aufbauen.
Alle 6 Migrationen sind idempotent und legen Schema-Erweiterungen + initiale
Seeds an (Mock-Projekte nur, falls Profil 100 existiert — auf prod nicht).

Software-Sektion (Etappe 3):
  - software_seite-Tabelle: schlanker Karten-Katalog (slug, titel, urls,
    plattform, kategorie, sortierung, Cover-BLOB)
  - 7 Initial-Karten (Bambu Studio/Handy, Printables, MakerWorld,
    TinkerCAD, Onshape, Gridfinity-Generator)
  - Admin-Verwaltung: Liste, Anlegen-Form, Bearbeiten, Aktiv-Toggle,
    Hart-Loeschen, Bild-Upload via Crop-Tool (Strg+V/Drag&Drop, 400x400)

Drucker-Verwaltung (Etappe 5-vorgezogen):
  - drucker.freigegeben + gesperrt_grund (Lehrer-Sperre fuer Reparatur)
  - drucker.manual_url + troubleshooting_url (Override fuer Modell-Defaults)
  - SuS-View /drucker (eingeloggt): Kacheln + Sperre-Banner + Belegung
  - Admin /admin/drucker: Status-Override (idle/printing/failed/...),
    Sperren/Freigeben mit Grund, Inline-Edit fuer URLs

Slot-System / Reservierung (Etappe 7-vorgezogen):
  - profil.reservieren_freigeschaltet (Lehrer schaltet manuell frei,
    ersetzt L1-Gate-Automatismus)
  - services/slots.py: Konflikt-Check, Belegungs-Helper
  - SuS-Routen /cockpit/slot/neu (mit Konflikt-Check), /cockpit/slots,
    Stornieren
  - Admin /admin/slots Uebersicht + /admin/slot/neu (Lehrer reserviert
    fuer SuS) + Hart-Loeschen + Profil-Freigabe-Toggle

Selbstlern-Loop (Etappe 6 — Kern):
  - projekt-Spalten: 3-Achsen-Eval (Gesamt + Planung + Ausfuehrung),
    was_klappte_nicht, cover_blob, modellier_software_slug,
    drucker_id_used, makerworld_url
  - Regel: max. 1 aktives Projekt pro SuS, neues nur nach Freigabe
    (auto bei zufrieden/mittel, Lehrer-Review bei gelernt)
  - SuS-Werkstatt-Sektion: Titel + Beschreibung + Software-Dropdown +
    Drucker-Dropdown + MakerWorld-URL + Files-Upload (drag&drop,
    base64, max 30 MB)
  - Cover-Bild via Crop-Tool (Re-use admin_software_bild-Pattern)
  - Abschluss-Block: Pflicht Cover + mindestens 1 Datei
  - Eval-Form: visuelle 3x3 Radio-Karten + 2 Freitexte
  - Lehrer-Review: /admin/projekte mit Filter (wartet/in_arbeit/
    abgeschlossen/rueckfrage), Aktionen freigeben/rueckfrage/loeschen
  - Oeffentlicher Katalog /projekte: Sortier-Tabelle (Titel/Person/
    Status/Erfolg/Datum), aufklappbare Detail mit Cover/Beschreibung/
    Datei-Downloads/MakerWorld-Link
  - Wiederhol-Logik: "Sowas mache ich auch" via Verknuepfung
    (vorlage_projekt_id), bidirektionale Lineage-Banner

Cockpit-Refactoring:
  - Projekt-Karte ganz oben (vor Onboarding) mit "Hier weitermachen"
  - Check-In-Quick-Actions: nach Auswahl direkt zu /cockpit/projekt/neu,
    /drucker, /lektionen, /cockpit/projekt/<aktiv>
  - "Modus aendern"-Button im Eingecheckt-Banner
  - Admin-Dashboard-Hub mit Karten: SuS-Profile, Software, Lektionen,
    Drucker, Reservierungen, Projekte (Badge bei wartenden), Alph

Bug-Fixes:
  - Admin-Login redirected jetzt auf /admin/dashboard statt /admin/profile
  - app.py: neue Jinja-Filter iso_format + dauer_min
  - .gitignore: .claude/ ausschliessen (lokale launch.json)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 19:04:18 +02:00

164 lines
6.7 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 %}Drucker{% endblock %}
{% block content %}
<style>
.druckerkachel-gross {
display: flex;
flex-direction: column;
gap: 0.5rem;
padding: 1.2rem;
}
.druckerkachel-gross h2 { margin: 0; color: var(--primary-dark); }
.druckerkachel-gross .modell { font-size: 0.85rem; color: var(--text-light); }
.druckerkachel-gross .status-zeile { font-size: 1rem; font-weight: 600; margin-top: 0.3rem; }
.druckerkachel-gross .progress {
font-size: 0.85rem; color: var(--text-light); margin-top: 0.2rem;
}
.druckerkachel-gross .sperre-banner {
background: #FADBD8; color: #943126; border: 1px solid #F5B7B1;
border-radius: 6px; padding: 0.6rem 0.8rem; font-size: 0.9rem;
}
.druckerkachel-gross .api-hinweis {
font-size: 0.8rem; color: var(--text-light); font-style: italic;
}
.druckerkachel-gross .links {
display: flex; gap: 0.5rem; flex-wrap: wrap; margin-top: auto; padding-top: 0.5rem;
}
.druckerkachel-gross .links .btn { font-size: 0.8rem; padding: 0.35rem 0.7rem; }
.druckerkachel-gross.gesperrt { border-left: 4px solid var(--danger); }
</style>
<div class="section-header" style="display:flex; justify-content:space-between; align-items:center;">
<h1>Drucker</h1>
<a href="{{ url_for('profil.slots') }}" class="btn btn-secondary">Meine Reservierungen</a>
</div>
<p style="color: var(--text-light);">
Aktueller Stand unserer 4 Bambu-Lab-Drucker. <strong>Kein Drucken ohne Reservierung</strong> — es
muss immer klar sein, wer gerade an welchem Drucker druckt.
</p>
{% if not darf_reservieren %}
<div class="card" style="background:#FEF9E7; border-left: 4px solid var(--warning);">
<strong>Reservierung noch nicht freigeschaltet.</strong>
Du kannst die Drucker bisher nur ansehen. Lass dich beim Lehrer freischalten,
dann kannst du selbst Slots reservieren.
</div>
{% endif %}
{% set gesperrte = drucker_liste | selectattr('freigegeben', 'equalto', 0) | list %}
{% if gesperrte %}
<div class="card" style="background:#FADBD8; border-left: 4px solid var(--danger);">
<strong>⚠ {{ gesperrte|length }} Drucker derzeit gesperrt:</strong>
{{ gesperrte | map(attribute='name') | join(', ') }}
— Details auf den jeweiligen Kacheln.
</div>
{% endif %}
<div class="grid" style="grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));">
{% for d in drucker_liste %}
<div class="card druckerkachel-gross drucker-{{ d.status or 'idle' }} {% if not d.freigegeben %}gesperrt{% endif %}">
<div>
<h2>{{ d.name }}</h2>
<div class="modell">
{{ d.modell }}{% if not d.geschlossenes_gehaeuse %} · <span style="color: var(--danger);">offen</span>{% endif %}
</div>
</div>
{# Sperre-Banner hat Vorrang vor allem anderen #}
{% if not d.freigegeben %}
<div class="sperre-banner">
<strong>Gesperrt</strong>{% if d.gesperrt_grund %} — {{ d.gesperrt_grund }}{% endif %}
<div style="font-size: 0.8rem; margin-top: 0.3rem;">Reservierung derzeit nicht moeglich.</div>
</div>
{% endif %}
{# Status-Zeile #}
<div class="status-zeile">
{% if d.status == 'printing' %}🟢 druckt
{% elif d.status == 'idle' %}⚪ frei
{% elif d.status == 'paused' %}🟡 pausiert
{% elif d.status == 'finished' %}✅ fertig — Druck abholen
{% elif d.status == 'failed' %}🔴 Fehler
{% elif d.status == 'offline' %}⚫ offline
{% else %}⚪ kein Status
{% endif %}
</div>
{# Job-Details wenn aktiv #}
{% if d.job_name %}<div class="progress">{{ d.job_name }}</div>{% endif %}
{% if d.progress_pct %}
<div class="progress">
{{ d.progress_pct|round|int }} %{% if d.restzeit_min %} · noch {{ d.restzeit_min }} min{% endif %}
</div>
{% endif %}
{% if d.fehlermeldung %}
<div style="color: var(--danger); font-size: 0.9rem;">{{ d.fehlermeldung }}</div>
{% endif %}
{# Notiz vom Lehrer (auch wenn nicht gesperrt) #}
{% if d.notiz and d.freigegeben %}
<div style="font-size: 0.85rem; color: var(--text-light);"> {{ d.notiz }}</div>
{% endif %}
{# Hinweis wenn noch keine API-Daten #}
{% if not d.status %}
<div class="api-hinweis">
(Live-Status via Bambu-API kommt mit Etappe 5. Bis dahin pflegt der Lehrer den Status manuell.)
</div>
{% elif d.manuell_gesetzt %}
<div class="api-hinweis">vom Lehrer gesetzt</div>
{% endif %}
{# Belegung: aktuell + naechste 3 Slots #}
<div style="background:#F4F6F7; border-radius: 6px; padding: 0.6rem 0.8rem; font-size: 0.85rem;">
{% if d.belegung %}
<div><strong>🔖 Jetzt belegt:</strong>
{{ tier_emoji(d.belegung.tier or '') }} {{ d.belegung.vorname or '—' }}
bis {{ d.belegung.ende_zeit|iso_format('%H:%M') }} Uhr
</div>
{% else %}
<div><strong>🔖 Aktuell frei.</strong></div>
{% endif %}
{% if d.kommend %}
<div style="margin-top: 0.3rem; color: var(--text-light);">
Naechste:
{% for s in d.kommend %}
{{ s.start_zeit|iso_format('%d.%m. %H:%M') }}{{ s.ende_zeit|iso_format('%H:%M') }}
({{ s.vorname or '—' }}){% if not loop.last %}, {% endif %}
{% endfor %}
</div>
{% endif %}
</div>
{# Reservieren-Button #}
{% if d.freigegeben and darf_reservieren %}
<a href="{{ url_for('profil.slot_neu', drucker_id=d.id) }}" class="btn btn-primary"
style="text-align: center;">+ Diesen Drucker reservieren</a>
{% elif d.freigegeben and not darf_reservieren %}
<div style="font-size: 0.8rem; color: var(--text-light); font-style: italic;">
Reservierung ist fuer dich noch nicht freigeschaltet.
</div>
{% endif %}
{# Manual + Troubleshooting #}
<div class="links">
{% if d.manual %}
<a href="{{ d.manual }}" target="_blank" rel="noopener noreferrer" class="btn btn-secondary">📖 Manual ↗</a>
{% endif %}
{% if d.troubleshooting %}
<a href="{{ d.troubleshooting }}" target="_blank" rel="noopener noreferrer" class="btn btn-secondary">🔧 Hilfe ↗</a>
{% endif %}
</div>
</div>
{% endfor %}
</div>
{% if not drucker_liste %}
<div class="card">
<p style="color: var(--text-light);">Noch keine Drucker eingerichtet.</p>
</div>
{% endif %}
{% endblock %}