feat: Add print functionality and improve layout for various templates

This commit is contained in:
2026-05-27 13:42:33 +02:00
parent f68906f3cb
commit e8a2e78c71
7 changed files with 203 additions and 59 deletions
+10 -3
View File
@@ -3,9 +3,14 @@
{% block title %}Admin | Rechnungsverwaltung{% endblock %}
{% block content %}
<section class="panel">
<section class="panel" style="display:flex; align-items:center; justify-content:space-between; gap:1rem;">
<div>
<h1>Rechnungsverwaltung</h1>
<p>Rechnungen für Nutzer erstellen, aktualisieren und löschen. Neue Rechnungen starten mit dem Status „Zu prüfen“, damit der Admin sie zuerst kontrolliert. Direktbuchungen werden hier automatisch mit zugewiesenem Admin und Buchungsunterlagen angezeigt.</p>
<p style="margin:0">Rechnungen für Nutzer erstellen, aktualisieren und löschen. Neue Rechnungen starten mit dem Status „Zu prüfen“, damit der Admin sie zuerst kontrolliert. Direktbuchungen werden hier automatisch mit zugewiesenem Admin und Buchungsunterlagen angezeigt.</p>
</div>
<div class="no-print">
<button class="print-btn" type="button" onclick="printDocument()">Seite drucken</button>
</div>
</section>
<section class="layout">
@@ -40,7 +45,9 @@
<p><strong>Kontaktdaten:</strong> {{ item.booking_data.contact_person }} | {{ item.booking_data.contact_email }} | {{ item.booking_data.billing_city }}</p>
{% endif %}
{% if item.pdf_path %}
<p><strong>PDF:</strong> <a href="{{ url_for('static', filename=item.pdf_path) }}" target="_blank" rel="noopener">Ansehen</a></p>
<p><strong>PDF:</strong> <a href="{{ url_for('static', filename=item.pdf_path) }}" target="_blank" rel="noopener">Ansehen</a>
<button class="print-btn" type="button" onclick="printPdf('{{ url_for('static', filename=item.pdf_path) }}')">PDF drucken</button>
</p>
{% endif %}
<form method="post" class="inline-form" enctype="multipart/form-data">
<input type="hidden" name="action" value="update">
+55
View File
@@ -682,6 +682,29 @@
}
</style>
</footer>
<style>
/* Print button shared style */
.print-btn {
display: inline-flex;
align-items: center;
gap: 0.5rem;
padding: 0.45rem 0.75rem;
background: #ffffff;
border: 1px solid #ccd5cc;
border-radius: 8px;
color: #184f57;
font-weight: 700;
cursor: pointer;
}
.print-btn.primary {
background: linear-gradient(120deg, #286f78 0%, #194f56 100%);
color: white;
border-color: #184f57;
}
@media print {
.no-print { display: none !important; }
}
</style>
<div class="cookie-banner hidden" id="cookieBanner" role="dialog" aria-live="polite" aria-label="Cookie Hinweis">
<p>Wir verwenden Cookies, um die Website sicher und benutzerfreundlich zu betreiben. Du kannst auswählen, welche Cookies du erlaubst. Details in <a href="{{ url_for('datenschutz') }}">Datenschutz</a>.</p>
<div class="cookie-actions">
@@ -899,6 +922,38 @@
// Run once on load in case user already saved consent
applyConsent();
// Global print helpers
window.printDocument = function () {
try { window.print(); } catch (e) { console.error('print error', e); }
};
window.printPdf = function (url) {
try {
var w = window.open(url, '_blank');
if (!w) return;
// Attempt to print after load; poll for readyState
var attempts = 0;
var t = setInterval(function () {
attempts++;
try {
if (w.document && (w.document.readyState === 'complete' || w.document.readyState === 'interactive')) {
w.focus();
w.print();
clearInterval(t);
}
} catch (err) {
// cross-domain or plugin viewer may block access; fallback: focus window and leave to user
if (attempts > 8) {
clearInterval(t);
try { w.focus(); } catch (e) {}
}
}
if (attempts > 30) clearInterval(t);
}, 300);
} catch (e) {
console.error('printPdf error', e);
}
};
})();
</script>
</body>
+6 -1
View File
@@ -4,10 +4,15 @@
{% block content %}
<div class="legal-page">
<div class="legal-header">
<div class="legal-header" style="display:flex; align-items:flex-start; justify-content:space-between; gap:1rem;">
<div>
<h1>Datenschutzerklärung</h1>
<p>Gültig ab: 15. Mai 2026</p>
</div>
<div class="no-print">
<button class="print-btn" type="button" onclick="printDocument()">Dokument drucken</button>
</div>
</div>
<div class="legal-content">
<section>
+100 -39
View File
@@ -98,47 +98,108 @@
{% block content %}
<section class="page-hero">
<h1>Leistungen für Bildungseinrichtungen</h1>
<p>Invario bündelt die Leistungen so, dass Schulen sofort verstehen, was sie bekommen: Inventarverwaltung für 250 € pro Jahr, Bibliothekssystem für 100 € pro Jahr für Schulen unter 400 Schülern und 150 € pro Jahr für größere Schulen, dazu Ticket-Support, Detailierte Video Schulung und dokumentierte Sicherheit.</p>
<div class="bento" aria-label="Kernleistungen">
<div class="bento-item"><strong>Schulen</strong>5-Minuten-Start, Zettelwirtschaft vermeiden, intuitive Web-App ohne Installation.</div>
<div class="bento-item"><strong>Schulträger</strong>Rechtssicherheit, revisionssichere PDF-Berichte und Serverstandort Deutschland.</div>
<h1>Module &amp; Leitfäden</h1>
<p>Diese Seite stellt vier austauschbare Module dar. Jedes Modul ist als kompakter, wiki‑ähnlicher Leitfaden gehalten — übersichtlich, schnell zu scannen und leicht editierbar.</p>
</section>
<section class="module-shell" aria-label="Module Übersicht">
<div class="module-nav" role="navigation" aria-label="Module Navigation">
<ul>
<li class="active" data-module="mod1"><a href="#mod1">Modul 1</a></li>
<li data-module="mod2"><a href="#mod2">Modul 2</a></li>
<li data-module="mod3"><a href="#mod3">Modul 3</a></li>
<li data-module="mod4"><a href="#mod4">Modul 4</a></li>
</ul>
</div>
<div class="module-content" id="moduleContent">
<article id="mod1" class="module-panel">
<h2>Modul 1 — Titel</h2>
<p class="lede">Kurze Zusammenfassung des Moduls. (Platzhalter — du ersetzt den Inhalt später.)</p>
<h3>Was ist das?</h3>
<p>Beschreibe hier den Zweck, Zielgruppe und schnelle Anwendungsfälle.</p>
<h3>Schnellstart</h3>
<ol>
<li>Schritt 1 — Kurzer Tipp.</li>
<li>Schritt 2 — Konfigurationsempfehlung.</li>
<li>Schritt 3 — Prüfpunkte.</li>
</ol>
<h3>FAQs</h3>
<dl>
<dt>Frage: Wie mache ich X?</dt>
<dd>Antwort: Kurze Anleitung.</dd>
</dl>
</article>
<article id="mod2" class="module-panel hidden">
<h2>Modul 2 — Titel</h2>
<p class="lede">Platzhaltertext für Modul 2.</p>
</article>
<article id="mod3" class="module-panel hidden">
<h2>Modul 3 — Titel</h2>
<p class="lede">Platzhaltertext für Modul 3.</p>
</article>
<article id="mod4" class="module-panel hidden">
<h2>Modul 4 — Titel</h2>
<p class="lede">Platzhaltertext für Modul 4.</p>
</article>
</div>
</section>
<section class="service-grid">
<article class="service-card">
<h2>Digitale Inventarverwaltung</h2>
<p>Alle Geräte, Räume und Zuständigkeiten an einem Ort. Das verkürzt Suchzeiten und reduziert Rückfragen.</p>
</article>
<article class="service-card">
<h2>Instanz- und Fristenkontrolle</h2>
<p>Instanzzustände und wichtige Wartungsfristen werden transparent angezeigt, inklusive klarer Hinweise für den Betrieb.</p>
</article>
<article class="service-card">
<h2>Support mit klaren Prioritäten</h2>
<p>Störungen werden strukturiert aufgenommen, priorisiert und mit nachvollziehbarem Status bearbeitet.</p>
</article>
<article class="service-card">
<h2>DSGVO-konforme Dokumentation</h2>
<p>Vorgänge und Änderungen bleiben nachvollziehbar dokumentiert, damit Prüfungen und Abstimmungen einfacher werden.</p>
</article>
<article class="service-card">
<h2>Einrichtung und Schulung</h2>
<p>Die Einführung erfolgt in klaren, alltagsnahen Schritten, damit der laufende Betrieb stabil bleibt.</p>
</article>
<article class="service-card">
<h2>Begleitung im laufenden Betrieb</h2>
<p>Feste Ansprechpartner unterstützen bei Fragen, Erweiterungen und organisatorischen Anpassungen.</p>
</article>
<article class="service-card">
<h2>Technische Sicherheitsbasis</h2>
<p>Rollenrechte, sichere Sitzungen, Schutz-Header und protokollierte Änderungen bilden die Grundlage für einen verlässlichen Betrieb.</p>
</article>
</section>
<style>
/* Module page styles */
.module-shell { display: grid; grid-template-columns: 260px 1fr; gap: 1rem; margin-top: 1rem; }
.module-nav { border: 1px solid #d8dfd7; border-radius: 10px; background: #fff; padding: 0.6rem; }
.module-nav ul { list-style: none; margin: 0; padding: 0; }
.module-nav li { margin: 0.25rem 0; }
.module-nav a { display: block; padding: 0.6rem 0.8rem; color: #173f46; text-decoration: none; border-radius: 8px; }
.module-nav li.active a { background: linear-gradient(90deg,#eaf6f4,#f3fbf8); font-weight:700; }
.module-content { border: 1px solid #d8dfd7; border-radius: 12px; padding: 1.1rem; background: #fff; }
.module-panel { max-width: 80ch; }
.module-panel.hidden { display: none; }
.module-panel h2 { margin-top: 0; color: #133e40; }
.module-panel .lede { color: #455e5e; font-weight: 600; }
.module-panel h3 { margin-top: 1rem; color: #22595b; }
.module-panel ol { margin-left: 1.1rem; }
<section class="highlight">
<h2>Besonders geeignet für Teams mit wenig Zeit</h2>
<p>Die Oberfläche ist bewusst reduziert aufgebaut: große, klare Bedienelemente, eindeutige Bezeichnungen und kurze Klickwege für tägliche Verwaltungsaufgaben.</p>
</section>
@media (max-width: 900px) {
.module-shell { grid-template-columns: 1fr; }
.module-nav { order: 2; }
.module-content { order: 1; }
}
</style>
<script>
// Simple client-side module switching for an immediate wiki-like experience
document.addEventListener('DOMContentLoaded', function () {
var navItems = document.querySelectorAll('.module-nav li');
navItems.forEach(function (li) {
li.addEventListener('click', function (ev) {
ev.preventDefault();
var target = li.getAttribute('data-module');
if (!target) return;
// activate nav
navItems.forEach(function (n) { n.classList.remove('active'); });
li.classList.add('active');
// show panel
document.querySelectorAll('.module-panel').forEach(function (p) { p.classList.add('hidden'); });
var panel = document.getElementById(target);
if (panel) panel.classList.remove('hidden');
// update hash
history.replaceState(null, '', '#'+target);
// scroll into view on small screens
if (window.innerWidth < 900) panel.scrollIntoView({ behavior: 'smooth' });
});
});
// If hash present on load, open that module
var hash = window.location.hash.replace('#','');
if (hash) {
var initial = document.querySelector('.module-nav li[data-module="'+hash+'"]');
if (initial) initial.click();
}
});
</script>
{% endblock %}
+6 -1
View File
@@ -4,10 +4,15 @@
{% block content %}
<div class="legal-page">
<div class="legal-header">
<div class="legal-header" style="display:flex; align-items:flex-start; justify-content:space-between; gap:1rem;">
<div>
<h1>Impressum</h1>
<p>Angaben gemäß TMG (Telemediengesetz) und MDStV (Mediendienstestaatsvertrag)</p>
</div>
<div class="no-print">
<button class="print-btn" type="button" onclick="printDocument()">Dokument drucken</button>
</div>
</div>
<div class="legal-content">
<section>
+8 -2
View File
@@ -3,9 +3,14 @@
{% block title %}Meine Rechnungen{% endblock %}
{% block content %}
<section class="panel">
<section class="panel" style="display:flex; align-items:center; justify-content:space-between; gap:1rem;">
<div>
<h1>Rechnungseinsicht</h1>
<p>Alle Rechnungen im Überblick. Der Admin prüft neue Rechnungen zuerst; der Status zeigt dir, ob die Rechnung noch geprüft wird oder bereits bearbeitet wurde.</p>
<p style="margin:0">Alle Rechnungen im Überblick. Der Admin prüft neue Rechnungen zuerst; der Status zeigt dir, ob die Rechnung noch geprüft wird oder bereits bearbeitet wurde.</p>
</div>
<div class="no-print">
<button class="print-btn" type="button" onclick="printDocument()">Seite drucken</button>
</div>
</section>
<section class="table-wrap">
@@ -31,6 +36,7 @@
<td>
{% if invoice.pdf_path %}
<a href="{{ url_for('static', filename=invoice.pdf_path) }}" target="_blank" rel="noopener">PDF ansehen</a>
<button class="print-btn" type="button" onclick="printPdf('{{ url_for('static', filename=invoice.pdf_path) }}')">PDF drucken</button>
{% else %}
-
{% endif %}
+6 -1
View File
@@ -3,9 +3,14 @@
{% block title %}Nutzungsbedingungen | Invario{% endblock %}
{% block content %}
<article class="panel">
<article class="panel" style="display:flex; align-items:flex-start; justify-content:space-between; gap:1rem;">
<div>
<h1>Allgemeine Nutzungs- und Lizenzbedingungen für die Invario UG Modul Suite</h1>
<p><strong>Stand:</strong> Mai 2026</p>
</div>
<div class="no-print">
<button class="print-btn" type="button" onclick="printDocument()">Dokument drucken</button>
</div>
<h2>Inhaltsverzeichnis</h2>
<ol>