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
+13 -6
View File
@@ -3,9 +3,14 @@
{% block title %}Admin | Rechnungsverwaltung{% endblock %} {% block title %}Admin | Rechnungsverwaltung{% endblock %}
{% block content %} {% block content %}
<section class="panel"> <section class="panel" style="display:flex; align-items:center; justify-content:space-between; gap:1rem;">
<h1>Rechnungsverwaltung</h1> <div>
<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> <h1>Rechnungsverwaltung</h1>
<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>
<section class="layout"> <section class="layout">
@@ -39,9 +44,11 @@
<p><strong>Unterlagen:</strong> {{ item.paperwork_status or 'Offen' }}{% if item.consent_accepted %} | Einwilligung bestätigt{% endif %}</p> <p><strong>Unterlagen:</strong> {{ item.paperwork_status or 'Offen' }}{% if item.consent_accepted %} | Einwilligung bestätigt{% endif %}</p>
<p><strong>Kontaktdaten:</strong> {{ item.booking_data.contact_person }} | {{ item.booking_data.contact_email }} | {{ item.booking_data.billing_city }}</p> <p><strong>Kontaktdaten:</strong> {{ item.booking_data.contact_person }} | {{ item.booking_data.contact_email }} | {{ item.booking_data.billing_city }}</p>
{% endif %} {% endif %}
{% if item.pdf_path %} {% 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>
{% endif %} <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"> <form method="post" class="inline-form" enctype="multipart/form-data">
<input type="hidden" name="action" value="update"> <input type="hidden" name="action" value="update">
<input type="hidden" name="invoice_id" value="{{ item.id }}"> <input type="hidden" name="invoice_id" value="{{ item.id }}">
+55
View File
@@ -682,6 +682,29 @@
} }
</style> </style>
</footer> </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"> <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> <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"> <div class="cookie-actions">
@@ -899,6 +922,38 @@
// Run once on load in case user already saved consent // Run once on load in case user already saved consent
applyConsent(); 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> </script>
</body> </body>
+8 -3
View File
@@ -4,9 +4,14 @@
{% block content %} {% block content %}
<div class="legal-page"> <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;">
<h1>Datenschutzerklärung</h1> <div>
<p>Gültig ab: 15. Mai 2026</p> <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>
<div class="legal-content"> <div class="legal-content">
+100 -39
View File
@@ -98,47 +98,108 @@
{% block content %} {% block content %}
<section class="page-hero"> <section class="page-hero">
<h1>Leistungen für Bildungseinrichtungen</h1> <h1>Module &amp; Leitfäden</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> <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>
<div class="bento" aria-label="Kernleistungen"> </section>
<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> <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> </div>
</section> </section>
<section class="service-grid"> <style>
<article class="service-card"> /* Module page styles */
<h2>Digitale Inventarverwaltung</h2> .module-shell { display: grid; grid-template-columns: 260px 1fr; gap: 1rem; margin-top: 1rem; }
<p>Alle Geräte, Räume und Zuständigkeiten an einem Ort. Das verkürzt Suchzeiten und reduziert Rückfragen.</p> .module-nav { border: 1px solid #d8dfd7; border-radius: 10px; background: #fff; padding: 0.6rem; }
</article> .module-nav ul { list-style: none; margin: 0; padding: 0; }
<article class="service-card"> .module-nav li { margin: 0.25rem 0; }
<h2>Instanz- und Fristenkontrolle</h2> .module-nav a { display: block; padding: 0.6rem 0.8rem; color: #173f46; text-decoration: none; border-radius: 8px; }
<p>Instanzzustände und wichtige Wartungsfristen werden transparent angezeigt, inklusive klarer Hinweise für den Betrieb.</p> .module-nav li.active a { background: linear-gradient(90deg,#eaf6f4,#f3fbf8); font-weight:700; }
</article> .module-content { border: 1px solid #d8dfd7; border-radius: 12px; padding: 1.1rem; background: #fff; }
<article class="service-card"> .module-panel { max-width: 80ch; }
<h2>Support mit klaren Prioritäten</h2> .module-panel.hidden { display: none; }
<p>Störungen werden strukturiert aufgenommen, priorisiert und mit nachvollziehbarem Status bearbeitet.</p> .module-panel h2 { margin-top: 0; color: #133e40; }
</article> .module-panel .lede { color: #455e5e; font-weight: 600; }
<article class="service-card"> .module-panel h3 { margin-top: 1rem; color: #22595b; }
<h2>DSGVO-konforme Dokumentation</h2> .module-panel ol { margin-left: 1.1rem; }
<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>
<section class="highlight"> @media (max-width: 900px) {
<h2>Besonders geeignet für Teams mit wenig Zeit</h2> .module-shell { grid-template-columns: 1fr; }
<p>Die Oberfläche ist bewusst reduziert aufgebaut: große, klare Bedienelemente, eindeutige Bezeichnungen und kurze Klickwege für tägliche Verwaltungsaufgaben.</p> .module-nav { order: 2; }
</section> .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 %} {% endblock %}
+8 -3
View File
@@ -4,9 +4,14 @@
{% block content %} {% block content %}
<div class="legal-page"> <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;">
<h1>Impressum</h1> <div>
<p>Angaben gemäß TMG (Telemediengesetz) und MDStV (Mediendienstestaatsvertrag)</p> <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>
<div class="legal-content"> <div class="legal-content">
+11 -5
View File
@@ -3,9 +3,14 @@
{% block title %}Meine Rechnungen{% endblock %} {% block title %}Meine Rechnungen{% endblock %}
{% block content %} {% block content %}
<section class="panel"> <section class="panel" style="display:flex; align-items:center; justify-content:space-between; gap:1rem;">
<h1>Rechnungseinsicht</h1> <div>
<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> <h1>Rechnungseinsicht</h1>
<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>
<section class="table-wrap"> <section class="table-wrap">
@@ -30,9 +35,10 @@
<td>{{ invoice.status }}</td> <td>{{ invoice.status }}</td>
<td> <td>
{% if invoice.pdf_path %} {% if invoice.pdf_path %}
<a href="{{ url_for('static', filename=invoice.pdf_path) }}" target="_blank" rel="noopener">PDF ansehen</a> <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 %} {% else %}
- -
{% endif %} {% endif %}
</td> </td>
</tr> </tr>
+8 -3
View File
@@ -3,9 +3,14 @@
{% block title %}Nutzungsbedingungen | Invario{% endblock %} {% block title %}Nutzungsbedingungen | Invario{% endblock %}
{% block content %} {% block content %}
<article class="panel"> <article class="panel" style="display:flex; align-items:flex-start; justify-content:space-between; gap:1rem;">
<h1>Allgemeine Nutzungs- und Lizenzbedingungen für die Invario UG Modul Suite</h1> <div>
<p><strong>Stand:</strong> Mai 2026</p> <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> <h2>Inhaltsverzeichnis</h2>
<ol> <ol>