Add admin management features and MongoDB setup scripts

- Created .vscode/settings.json for Python environment configuration.
- Added run.sh script for MongoDB installation and project setup.
- Implemented admin chat interface in admin_chats.html.
- Developed invoice management interface in admin_invoices.html.
- Created license management interface in admin_licenses.html.
- Added support ticket management interface in admin_tickets.html.
- Implemented user management interface in admin_users.html.
- Developed user chat interface in chat.html.
- Created user invoice overview in my_invoices.html.
- Developed user license overview in my_licenses.html.
- Added support ticket creation and tracking in tickets.html.
- Created test.sh script for MongoDB setup.
This commit is contained in:
Maximilian G.
2026-03-23 22:10:13 +00:00
committed by GitHub
parent 283a37638b
commit d00c4e2cb9
18 changed files with 1424 additions and 71 deletions
+58
View File
@@ -0,0 +1,58 @@
{% extends "base.html" %}
{% block title %}Admin Chat Center{% endblock %}
{% block content %}
<section class="panel">
<h1>Admin Chat Center</h1>
<p>Unterhaltungen mit Nutzern einsehen und beantworten.</p>
</section>
<section class="layout">
<aside class="users">
<h3>Unterhaltungen</h3>
{% for username in conversations %}
<a class="user-link {{ 'active' if username == selected_user else '' }}" href="{{ url_for('admin_chats', username=username) }}">{{ username }}</a>
{% else %}
<p>Keine aktiven Unterhaltungen.</p>
{% endfor %}
</aside>
<div class="chat-panel">
<div class="messages">
{% for msg in messages %}
<div class="message {{ msg.sender_role }}">
<strong>{{ msg.sender }}</strong>
<p>{{ msg.message }}</p>
<small>{{ msg.created_at }}</small>
</div>
{% else %}
<p>Keine Nachrichten vorhanden.</p>
{% endfor %}
</div>
{% if selected_user %}
<form method="post" class="composer">
<input type="hidden" name="username" value="{{ selected_user }}">
<textarea name="message" rows="3" required placeholder="Antwort an {{ selected_user }}..."></textarea>
<button type="submit">Antwort senden</button>
</form>
{% endif %}
</div>
</section>
<style>
.panel { margin-bottom: 1rem; }
.layout { display: grid; grid-template-columns: 260px 1fr; gap: 1rem; }
.users, .chat-panel { background: #fff; border: 1px solid #d8e1e8; border-radius: 14px; padding: 1rem; }
.user-link { display: block; padding: 0.5rem; border-radius: 10px; border: 1px solid transparent; margin-bottom: 0.45rem; }
.user-link:hover, .user-link.active { border-color: #bcd0de; background: #f4f9fd; }
.messages { display: grid; gap: 0.6rem; margin-bottom: 0.8rem; max-height: 460px; overflow: auto; }
.message { border: 1px solid #e4edf3; border-radius: 10px; padding: 0.65rem; }
.message.admin { background: #f2f8ff; }
.message.user { background: #fcfdff; }
.composer { display: grid; gap: 0.6rem; }
textarea { width: 100%; border: 1px solid #c9d8e3; border-radius: 12px; padding: 0.7rem; font: inherit; }
button { width: fit-content; border: 1px solid #0a4c74; background: linear-gradient(120deg, #0c5a86 0%, #08486c 100%); color: #fff; padding: 0.5rem 1rem; border-radius: 999px; font-weight: 700; }
@media (max-width: 960px) { .layout { grid-template-columns: 1fr; } }
</style>
{% endblock %}
+61
View File
@@ -0,0 +1,61 @@
{% extends "base.html" %}
{% block title %}Admin | Rechnungsverwaltung{% endblock %}
{% block content %}
<section class="panel">
<h1>Rechnungsverwaltung</h1>
<p>Rechnungen fuer Nutzer erstellen, aktualisieren und loeschen.</p>
</section>
<section class="layout">
<form method="post" class="form-card">
<h3>Neue Rechnung</h3>
<input type="hidden" name="action" value="create">
<input type="text" name="username" placeholder="Benutzername" required>
<input type="text" name="period" placeholder="Zeitraum (MM/YYYY)" required>
<input type="text" name="amount_eur" placeholder="Betrag in EUR" required>
<input type="text" name="due_date" placeholder="Faelligkeit (YYYY-MM-DD)">
<input type="text" name="status" placeholder="Status (Offen, Bezahlt)">
<button type="submit">Rechnung anlegen</button>
</form>
<div class="list">
{% for item in invoices %}
<article class="entry">
<h3>{{ item.invoice_number }}</h3>
<p><strong>Nutzer:</strong> {{ item.username }} | <strong>Zeitraum:</strong> {{ item.period }}</p>
<p><strong>Betrag:</strong> {{ '%.2f'|format(item.amount_eur) }} EUR | <strong>Status:</strong> {{ item.status }} | <strong>Faellig:</strong> {{ item.due_date }}</p>
<form method="post" class="inline-form">
<input type="hidden" name="action" value="update">
<input type="hidden" name="invoice_id" value="{{ item.id }}">
<input type="text" name="amount_eur" value="{{ item.amount_eur }}" placeholder="Betrag">
<input type="text" name="due_date" value="{{ item.due_date }}" placeholder="Faelligkeit">
<input type="text" name="status" value="{{ item.status }}" placeholder="Status">
<button type="submit">Aktualisieren</button>
</form>
<form method="post">
<input type="hidden" name="action" value="delete">
<input type="hidden" name="invoice_id" value="{{ item.id }}">
<button type="submit" class="danger">Loeschen</button>
</form>
</article>
{% else %}
<article class="entry"><p>Noch keine Rechnungen vorhanden.</p></article>
{% endfor %}
</div>
</section>
<style>
.panel { margin-bottom: 1rem; }
.layout { display: grid; grid-template-columns: 320px 1fr; gap: 1rem; }
.form-card, .entry { background: #fff; border: 1px solid #d8e1e8; border-radius: 14px; padding: 1rem; }
.form-card { display: grid; gap: 0.6rem; align-self: start; }
.list { display: grid; gap: 0.8rem; }
.inline-form { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 0.45rem; margin-top: 0.6rem; }
input { width: 100%; border: 1px solid #c9d8e3; border-radius: 10px; padding: 0.52rem; font: inherit; }
button { border: 1px solid #0a4c74; background: linear-gradient(120deg, #0c5a86 0%, #08486c 100%); color: #fff; padding: 0.45rem 0.8rem; border-radius: 999px; font-weight: 700; }
button.danger { border-color: #d04a49; background: #fff; color: #922e2e; margin-top: 0.55rem; }
@media (max-width: 980px) { .layout { grid-template-columns: 1fr; } .inline-form { grid-template-columns: 1fr; } }
</style>
{% endblock %}
+61
View File
@@ -0,0 +1,61 @@
{% extends "base.html" %}
{% block title %}Admin | Lizenzverwaltung{% endblock %}
{% block content %}
<section class="panel">
<h1>Lizenzverwaltung</h1>
<p>Lizenzen fuer Nutzer anlegen, bearbeiten und loeschen.</p>
</section>
<section class="layout">
<form method="post" class="form-card">
<h3>Neue Lizenz</h3>
<input type="hidden" name="action" value="create">
<input type="text" name="username" placeholder="Benutzername" required>
<input type="text" name="school_name" placeholder="Schulname" required>
<input type="text" name="plan" placeholder="Plan (z. B. Standard)">
<input type="text" name="status" placeholder="Status (Aktiv, Pausiert)">
<input type="text" name="valid_until" placeholder="Gueltig bis (YYYY-MM-DD)">
<button type="submit">Lizenz anlegen</button>
</form>
<div class="list">
{% for item in licenses %}
<article class="entry">
<h3>{{ item.username }} - {{ item.school_name }}</h3>
<p><strong>Key:</strong> {{ item.license_key }} | <strong>Plan:</strong> {{ item.plan }}</p>
<p><strong>Status:</strong> {{ item.status }} | <strong>Gueltig bis:</strong> {{ item.valid_until }}</p>
<form method="post" class="inline-form">
<input type="hidden" name="action" value="update">
<input type="hidden" name="license_id" value="{{ item.id }}">
<input type="text" name="plan" value="{{ item.plan }}" placeholder="Plan">
<input type="text" name="status" value="{{ item.status }}" placeholder="Status">
<input type="text" name="valid_until" value="{{ item.valid_until }}" placeholder="Gueltig bis">
<button type="submit">Aktualisieren</button>
</form>
<form method="post">
<input type="hidden" name="action" value="delete">
<input type="hidden" name="license_id" value="{{ item.id }}">
<button type="submit" class="danger">Loeschen</button>
</form>
</article>
{% else %}
<article class="entry"><p>Noch keine Lizenzen vorhanden.</p></article>
{% endfor %}
</div>
</section>
<style>
.panel { margin-bottom: 1rem; }
.layout { display: grid; grid-template-columns: 320px 1fr; gap: 1rem; }
.form-card, .entry { background: #fff; border: 1px solid #d8e1e8; border-radius: 14px; padding: 1rem; }
.form-card { display: grid; gap: 0.6rem; align-self: start; }
.list { display: grid; gap: 0.8rem; }
.inline-form { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 0.45rem; margin-top: 0.6rem; }
input { width: 100%; border: 1px solid #c9d8e3; border-radius: 10px; padding: 0.52rem; font: inherit; }
button { border: 1px solid #0a4c74; background: linear-gradient(120deg, #0c5a86 0%, #08486c 100%); color: #fff; padding: 0.45rem 0.8rem; border-radius: 999px; font-weight: 700; }
button.danger { border-color: #d04a49; background: #fff; color: #922e2e; margin-top: 0.55rem; }
@media (max-width: 980px) { .layout { grid-template-columns: 1fr; } .inline-form { grid-template-columns: 1fr; } }
</style>
{% endblock %}
+42
View File
@@ -0,0 +1,42 @@
{% extends "base.html" %}
{% block title %}Admin | Support Tickets{% endblock %}
{% block content %}
<section class="panel">
<h1>Support Tickets</h1>
<p>Ticketstatus verwalten und Antworten hinterlegen.</p>
</section>
<section class="tickets">
{% for ticket in tickets %}
<article class="ticket">
<h3>{{ ticket.title }}</h3>
<p><strong>Nutzer:</strong> {{ ticket.username }} | <strong>Prioritaet:</strong> {{ ticket.priority }}</p>
<p>{{ ticket.description }}</p>
<form method="post" class="update-form">
<input type="hidden" name="ticket_id" value="{{ ticket.id }}">
<select name="status">
<option value="Offen" {{ 'selected' if ticket.status == 'Offen' else '' }}>Offen</option>
<option value="In Bearbeitung" {{ 'selected' if ticket.status == 'In Bearbeitung' else '' }}>In Bearbeitung</option>
<option value="Geloest" {{ 'selected' if ticket.status == 'Geloest' else '' }}>Geloest</option>
</select>
<textarea name="admin_response" rows="3" placeholder="Antwort an Nutzer">{{ ticket.admin_response }}</textarea>
<button type="submit">Aktualisieren</button>
</form>
</article>
{% else %}
<article class="ticket"><p>Keine Tickets vorhanden.</p></article>
{% endfor %}
</section>
<style>
.panel { margin-bottom: 1rem; }
.tickets { display: grid; gap: 0.8rem; }
.ticket { background: #fff; border: 1px solid #d8e1e8; border-radius: 14px; padding: 1rem; }
.ticket p { margin-top: 0.3rem; }
.update-form { margin-top: 0.6rem; display: grid; gap: 0.55rem; }
select, textarea { width: 100%; border: 1px solid #c9d8e3; border-radius: 10px; padding: 0.55rem; font: inherit; }
button { width: fit-content; border: 1px solid #0a4c74; background: linear-gradient(120deg, #0c5a86 0%, #08486c 100%); color: #fff; padding: 0.5rem 1rem; border-radius: 999px; font-weight: 700; }
</style>
{% endblock %}
+65
View File
@@ -0,0 +1,65 @@
{% extends "base.html" %}
{% block title %}Admin | Userverwaltung{% endblock %}
{% block content %}
<section class="panel">
<h1>Userverwaltung</h1>
<p>Benutzer verwalten, Admin-Rechte vergeben und Konten bereinigen.</p>
</section>
<section class="table-wrap">
<table>
<thead>
<tr>
<th>Benutzername</th>
<th>Name</th>
<th>Rolle</th>
<th>Aktionen</th>
</tr>
</thead>
<tbody>
{% for user in users %}
<tr>
<td>{{ user.username }}</td>
<td>{{ user.display_name }}</td>
<td>{{ 'Admin' if user.is_admin else 'Nutzer' }}</td>
<td>
<div class="actions">
{% if not user.is_admin %}
<form method="post">
<input type="hidden" name="username" value="{{ user.username }}">
<input type="hidden" name="action" value="make_admin">
<button type="submit">Zu Admin</button>
</form>
{% else %}
<form method="post">
<input type="hidden" name="username" value="{{ user.username }}">
<input type="hidden" name="action" value="remove_admin">
<button type="submit" class="warn">Admin entziehen</button>
</form>
{% endif %}
<form method="post">
<input type="hidden" name="username" value="{{ user.username }}">
<input type="hidden" name="action" value="delete_user">
<button type="submit" class="danger">Loeschen</button>
</form>
</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</section>
<style>
.panel { margin-bottom: 1rem; }
.table-wrap { background: #fff; border: 1px solid #d8e1e8; border-radius: 14px; padding: 1rem; overflow-x: auto; }
table { width: 100%; border-collapse: collapse; }
th, td { text-align: left; padding: 0.7rem; border-bottom: 1px solid #e8eef2; }
.actions { display: flex; flex-wrap: wrap; gap: 0.5rem; }
button { border: 1px solid #bcd0de; background: #fff; padding: 0.4rem 0.65rem; border-radius: 999px; font-weight: 700; cursor: pointer; }
button.warn { border-color: #efcc95; }
button.danger { border-color: #f0b7b7; color: #8d2929; }
</style>
{% endblock %}
+114 -4
View File
@@ -25,6 +25,8 @@
--accent: #f28c28;
--radius-md: 14px;
--shadow-sm: 0 10px 30px rgba(17, 35, 50, 0.08);
--shell-gutter: clamp(12px, 2.2vw, 34px);
--shell-max-width: 100vw;
}
* { box-sizing: border-box; }
@@ -40,16 +42,39 @@
var(--bg-main);
}
h1, h2, h3, h4 {
h1 {
margin: 0;
font-family: "Space Grotesk", system-ui, sans-serif;
line-height: 1.08;
font-size: clamp(1.75rem, 4.5vw, 4.2rem);
}
h2 {
margin: 0;
font-family: "Space Grotesk", system-ui, sans-serif;
line-height: 1.08;
font-size: clamp(1.4rem, 3.2vw, 3rem);
}
h3 {
margin: 0;
font-family: "Space Grotesk", system-ui, sans-serif;
line-height: 1.08;
font-size: clamp(1.1rem, 2.5vw, 2rem);
}
h4 {
margin: 0;
font-family: "Space Grotesk", system-ui, sans-serif;
line-height: 1.08;
font-size: clamp(1rem, 1.8vw, 1.5rem);
}
p {
margin: 0;
line-height: 1.55;
color: var(--text-soft);
font-size: clamp(0.95rem, 1.2vw, 1.1rem);
}
a {
@@ -63,7 +88,7 @@
}
.site-shell {
width: min(1240px, calc(100% - 2rem));
width: min(var(--shell-max-width), calc(100% - (var(--shell-gutter) * 2)));
margin: 0 auto;
}
@@ -134,6 +159,56 @@
background: linear-gradient(120deg, #0b5b89 0%, #0b4262 100%);
}
.nav-dropdown {
position: relative;
}
.nav-dropdown > summary {
list-style: none;
cursor: pointer;
}
.nav-dropdown > summary::-webkit-details-marker {
display: none;
}
.nav-dropdown > summary::after {
content: "▾";
margin-left: 0.45rem;
font-size: 0.72rem;
color: #57798f;
}
.nav-dropdown[open] > summary::after {
content: "▴";
}
.dropdown-menu {
position: absolute;
right: 0;
top: calc(100% + 0.45rem);
min-width: 220px;
padding: 0.5rem;
border-radius: 12px;
border: 1px solid #ccdae5;
background: #ffffff;
box-shadow: 0 12px 24px rgba(16, 44, 62, 0.15);
display: grid;
gap: 0.35rem;
z-index: 40;
}
.dropdown-menu a {
padding: 0.5rem 0.65rem;
border-radius: 8px;
font-weight: 600;
color: #1a4865;
}
.dropdown-menu a:hover {
background: #f3f8fc;
}
.main-wrap {
padding: 2rem 0 3.4rem;
}
@@ -174,6 +249,12 @@
padding: 0.42rem 0.72rem;
font-size: 0.85rem;
}
.dropdown-menu {
right: auto;
left: 0;
min-width: 200px;
}
}
</style>
{% endblock %}
@@ -190,11 +271,40 @@
</div>
<div class="nav-actions">
{% if 'username' in session %}
<a class="nav-btn" href="{{ url_for('blog') }}">Blog</a>
<a class="nav-btn" href="{{ url_for('appointments') }}">Termin anfragen</a>
<details class="nav-dropdown">
<summary class="nav-btn">Nutzerbereich</summary>
<div class="dropdown-menu">
<a href="{{ url_for('my_licenses') }}">Meine Lizenzen</a>
<a href="{{ url_for('my_invoices') }}">Meine Rechnungen</a>
<a href="{{ url_for('user_chat') }}">Chat mit Admin</a>
<a href="{{ url_for('user_tickets') }}">Support Tickets</a>
</div>
</details>
<details class="nav-dropdown">
<summary class="nav-btn">Inhalte</summary>
<div class="dropdown-menu">
<a href="{{ url_for('blog') }}">Blog</a>
</div>
</details>
{% if session.get('is_admin') %}
<a class="nav-btn" href="{{ url_for('admin_dashboard') }}">Admin</a>
<details class="nav-dropdown">
<summary class="nav-btn">Admin</summary>
<div class="dropdown-menu">
<a href="{{ url_for('admin_dashboard') }}">Dashboard</a>
<a href="{{ url_for('admin_users') }}">Userverwaltung</a>
<a href="{{ url_for('admin_licenses') }}">Lizenzverwaltung</a>
<a href="{{ url_for('admin_invoices') }}">Rechnungsverwaltung</a>
<a href="{{ url_for('admin_chats') }}">Admin-Chat</a>
<a href="{{ url_for('admin_tickets') }}">Support-Center</a>
<a href="{{ url_for('admin_blog') }}">Blog verwalten</a>
</div>
</details>
{% endif %}
<a class="nav-btn" href="{{ url_for('logout') }}">Logout</a>
{% else %}
<a class="nav-btn" href="{{ url_for('blog') }}">Blog</a>
+42
View File
@@ -0,0 +1,42 @@
{% extends "base.html" %}
{% block title %}Chat mit Admin{% endblock %}
{% block content %}
<section class="panel">
<h1>Chat mit den Administratoren</h1>
<p>Stellen Sie Fragen direkt im Chat. Antworten erscheinen in derselben Unterhaltung.</p>
</section>
<section class="chat-box">
<div class="messages">
{% for msg in messages %}
<div class="message {{ 'admin' if msg.sender_role == 'admin' else 'user' }}">
<strong>{{ msg.sender }}</strong>
<p>{{ msg.message }}</p>
<small>{{ msg.created_at }}</small>
</div>
{% else %}
<p>Noch keine Nachrichten vorhanden.</p>
{% endfor %}
</div>
<form method="post" class="composer">
<textarea name="message" rows="3" placeholder="Ihre Nachricht..." required></textarea>
<button type="submit">Senden</button>
</form>
</section>
<style>
.panel { margin-bottom: 1rem; }
.chat-box { background: #fff; border: 1px solid #d8e1e8; border-radius: 14px; padding: 1rem; }
.messages { display: grid; gap: 0.6rem; margin-bottom: 0.8rem; max-height: 440px; overflow: auto; }
.message { border: 1px solid #e4edf3; border-radius: 10px; padding: 0.65rem; background: #f9fcff; }
.message.admin { background: #f2f8ff; border-color: #c8def1; }
.message.user { background: #fcfdff; }
.message p { margin-top: 0.3rem; }
.message small { color: #6f8798; }
.composer { display: grid; gap: 0.6rem; }
textarea { width: 100%; border: 1px solid #c9d8e3; border-radius: 12px; padding: 0.7rem; font: inherit; }
button { width: fit-content; border: 1px solid #0a4c74; background: linear-gradient(120deg, #0c5a86 0%, #08486c 100%); color: #fff; padding: 0.5rem 1rem; border-radius: 999px; font-weight: 700; }
</style>
{% endblock %}
+113 -42
View File
@@ -1,6 +1,6 @@
{% extends "base.html" %}
{% block title %}IT Loesungen | Modern{% endblock %}
{% block title %}Inventarlösungen{% endblock %}
{% block head %}
{{ super() }}
@@ -37,7 +37,7 @@
margin-bottom: 0.85rem;
padding: 0.3rem 0.65rem;
border-radius: 999px;
font-size: 0.75rem;
font-size: clamp(0.7rem, 0.9vw, 0.85rem);
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
@@ -46,7 +46,7 @@
}
.hero h1 {
font-size: clamp(2rem, 5.1vw, 3.4rem);
font-size: clamp(2rem, 5.5vw, 4rem);
color: #06293e;
text-wrap: balance;
}
@@ -54,7 +54,7 @@
.hero p {
max-width: 56ch;
margin-top: 1.1rem;
font-size: 1.06rem;
font-size: clamp(0.95rem, 1.4vw, 1.2rem);
}
.cta-row {
@@ -101,13 +101,13 @@
}
.hero-card h3 {
font-size: 1.15rem;
font-size: clamp(1rem, 2vw, 1.4rem);
margin-bottom: 0.6rem;
}
.hero-card p {
color: #b6d6ea;
font-size: 0.96rem;
font-size: clamp(0.9rem, 1.1vw, 1.05rem);
margin: 0;
}
@@ -148,7 +148,7 @@
}
.section h2 {
font-size: clamp(1.4rem, 2.5vw, 2rem);
font-size: clamp(1.5rem, 3.2vw, 2.8rem);
margin-bottom: 0.5rem;
color: #0f334b;
}
@@ -168,13 +168,13 @@
}
.card h3 {
font-size: 1.07rem;
font-size: clamp(1rem, 1.8vw, 1.3rem);
color: #113c58;
margin-bottom: 0.45rem;
}
.card p {
font-size: 0.96rem;
font-size: clamp(0.9rem, 1.1vw, 1rem);
}
.project-list {
@@ -198,10 +198,11 @@
display: block;
color: #0b3753;
margin-bottom: 0.2rem;
font-size: clamp(1rem, 1.5vw, 1.15rem);
}
.project span {
font-size: 0.88rem;
font-size: clamp(0.8rem, 0.95vw, 0.95rem);
color: #4a6579;
border: 1px solid #bfd0dd;
padding: 0.2rem 0.55rem;
@@ -227,12 +228,13 @@
.step strong {
display: block;
margin-bottom: 0.2rem;
font-size: 0.98rem;
font-size: clamp(0.9rem, 1.2vw, 1.1rem);
color: #0f3650;
}
.step small {
color: #5f7b8f;
font-size: clamp(0.8rem, 0.9vw, 0.95rem);
}
.team-note {
@@ -260,12 +262,13 @@
.contact h2 {
color: #ffffff;
font-size: clamp(1.25rem, 2.7vw, 2rem);
font-size: clamp(1.4rem, 3.2vw, 2.8rem);
margin-bottom: 0.4rem;
}
.contact p {
color: #b9d8ec;
font-size: clamp(0.95rem, 1.2vw, 1.1rem);
}
.contact .btn-secondary {
@@ -284,6 +287,64 @@
font-size: 0.95rem;
}
.partnership {
margin-top: 2.2rem;
padding: 2.5rem;
border-radius: 18px;
background: linear-gradient(135deg, #f0f7fc 0%, #e6effa 100%);
border: 2px solid var(--brand);
display: grid;
grid-template-columns: 1fr 1.2fr;
gap: 2rem;
align-items: center;
}
.partnership-icon {
font-size: clamp(4rem, 8vw, 6rem);
text-align: center;
line-height: 1;
}
.partnership-content h3 {
font-size: clamp(1.3rem, 2.5vw, 1.8rem);
color: var(--brand);
margin-bottom: 0.8rem;
}
.partnership-content p {
margin-bottom: 0.8rem;
color: var(--text-soft);
font-size: clamp(0.95rem, 1.1vw, 1.05rem);
}
.partnership-badges {
display: flex;
flex-wrap: wrap;
gap: 0.6rem;
margin-top: 1rem;
}
.badge {
display: inline-block;
padding: 0.4rem 0.85rem;
border-radius: 999px;
background: var(--brand);
color: #ffffff;
font-size: clamp(0.8rem, 0.95vw, 0.95rem);
font-weight: 600;
}
@media (max-width: 700px) {
.partnership {
grid-template-columns: 1fr;
padding: 1.8rem;
}
.partnership-icon {
font-size: clamp(3rem, 6vw, 4rem);
}
}
@keyframes rise {
from {
opacity: 0;
@@ -341,12 +402,10 @@
{% block content %}
<section class="hero" id="top">
<div>
<span class="eyebrow">IT Loesungen mit Haltung</span>
<span class="eyebrow">IT Lösungen mit Haltung</span>
<h1>Zuhören. Verstehen. Planen. Realisieren.</h1>
<p>
Wir unterstuetzen Unternehmen ganzheitlich bei Enterprise-Architektur,
Projektmanagement und IT-Betrieb. Die Anforderung bleibt im Mittelpunkt,
die Umsetzung wird klar, messbar und belastbar.
Wir sind spezialisiert auf Schulen aller Art und machen Schulinventur endlich einfach: Unser DSGVO-konformes Inventarsystem sorgt für klare Ordnung, volle Transparenz und schnelle Abläufe im Schulalltag. Jede Lösung wird individuell für Ihre Einrichtung entwickelt, mit passgenauen Features und einer besonders schnellen, unkomplizierten Inbetriebnahme.
</p>
<div class="cta-row">
{% if 'username' in session %}
@@ -359,7 +418,7 @@
</div>
</div>
<aside class="hero-card" aria-label="Kurzfakten">
<h3>IT Projekte mit Substanz</h3>
<h3>Inventur mit Verstand</h3>
<p>
Von Infrastruktur bis Governance: Wir verbinden konzeptionelle Tiefe mit
pragmatischer Umsetzung im Tagesgeschaeft.
@@ -372,56 +431,72 @@
</aside>
</section>
<section class="partnership" id="entwicklung">
<div class="partnership-icon">🤝</div>
<div class="partnership-content">
<h3>Entwickelt mit Schulen für Schulen</h3>
<p>
Unser Inventarsystem wurde nicht theoretisch konzipiert, sondern in enger Zusammenarbeit mit Einzelnen Schulen entwickelt. Wir verstehen die täglichen Herausforderungen im Schulbetrieb und entwickeln Lösungen, die wirklich einen Vorteil für den Schulaltag bringen.
</p>
<div class="partnership-badges">
<span class="badge">✓ Schulgetestet</span>
<span class="badge">✓ Praxisorientiert</span>
<span class="badge">✓ DSGVO-konform</span>
<span class="badge">✓ Einfach zu bedienen</span>
</div>
</div>
</section>
<section class="section" id="leistungen" style="animation-delay: 100ms;">
<h2>Dienstleistungen</h2>
<p>Modulare Leistungen, individuell kombiniert fuer Ihre IT-Roadmap.</p>
<h2>Leistungsbereiche</h2>
<p>Klare Gliederung in Konnektivitaet, Inventarverwaltung und laufenden Betrieb.</p>
<div class="section-grid">
<article class="card">
<h3>Support und Service</h3>
<p>Betriebskritische Systeme stabil halten, proaktiv ueberwachen und schnell reagieren.</p>
<h3>Konnektivitaet und Netzwerk</h3>
<p>Stabile Anbindung Ihrer Schulstandorte, saubere Netzstruktur und sichere Erreichbarkeit aller relevanten Systeme.</p>
</article>
<article class="card">
<h3>Optimierung und Tuning</h3>
<p>Strukturen, Kosten und Performance datenbasiert verbessern statt nur Symptome behandeln.</p>
<h3>Inventar und Lizenzmanagement</h3>
<p>Zentrale Verwaltung von Geraeten und Lizenzen mit schneller Uebersicht, nachvollziehbaren Prozessen und klaren Zustaendigkeiten.</p>
</article>
<article class="card">
<h3>Health Check und Verify</h3>
<p>Technische Due-Diligence, klare Handlungsplaene und belastbare Priorisierung.</p>
<h3>Support, Updates und Beratung</h3>
<p>Persoenliche Ansprechpartner, kurze Reaktionszeiten und planbare Updates fuer einen zuverlaessigen Betrieb im Schulalltag.</p>
</article>
</div>
</section>
<section class="section" id="projekte" style="animation-delay: 180ms;">
<h2>Projektfelder</h2>
<p>Ausgewählte Schwerpunkte aus laufenden und abgeschlossenen Kundenprojekten.</p>
<h2>Module im Ueberblick</h2>
<p>Alle Kernbereiche fuer Schulen in einer strukturierten Plattform: Konnektivitaet, Sicherheit und Betrieb.</p>
<div class="project-list">
<div class="project">
<div>
<b>Virtualisierung und Hybrid-Cloud</b>
Migration in wartbare Plattformen mit planbaren Betriebskosten.
<b>Konnektivitaet</b>
Sichere Standortanbindung, stabile Verbindungen und klare Netzwerkstruktur fuer einen reibungslosen Zugriff auf alle Inventardaten.
</div>
<span>Cloud Computing</span>
<span>Netzwerk</span>
</div>
<div class="project">
<div>
<b>Netzwerk und Sicherheit</b>
Segmentierung, Monitoring und Security-Standards auf Enterprise-Niveau.
<b>Sicherheit und DSGVO</b>
Datenschutz nach DSGVO, automatisierte Sicherheitspruefungen und klare Compliance-Standards fuer den Einsatz im oeffentlichen Bereich.
</div>
<span>Security</span>
</div>
<div class="project">
<div>
<b>Projektmanagement und EAM</b>
Governance, Architekturprinzipien und Umsetzung in einer Linie.
<b>Betrieb und Updates</b>
Regelmaessige Updates, schnelle Stoerungsbehebung und laufende Optimierung sorgen fuer einen dauerhaft stabilen Schulbetrieb.
</div>
<span>Transformation</span>
<span>Updates</span>
</div>
</div>
</section>
<section class="section" id="team" style="animation-delay: 260ms;">
<h2>Unsere Arbeitsweise</h2>
<p>Klassische Beratungsdisziplin trifft auf moderne Delivery-Kultur.</p>
<h2>Zusammenarbeit</h2>
<p>Strukturiert, nachvollziehbar und mit festen Ansprechpartnern von der Analyse bis zum Betrieb.</p>
<div class="process" aria-label="Prozessschritte">
<div class="step"><strong>Zuhoeren</strong><small>Anforderungen erfassen</small></div>
<div class="step"><strong>Verstehen</strong><small>Kontext analysieren</small></div>
@@ -430,16 +505,12 @@
<div class="step"><strong>Realisieren</strong><small>Loesung liefern</small></div>
<div class="step"><strong>Unterstuetzen</strong><small>Betrieb absichern</small></div>
</div>
<p class="team-note">
Aktuell verfuegbar: Ein IT-Infrastruktur-Architekt und ein IT-Governance-Experte
mit technischem Hintergrund fuer neue Aufgaben.
</p>
</section>
<section class="contact" id="kontakt">
<div>
<h2>Bereit fuer das naechste IT-Projekt?</h2>
<p>In einem kurzen Erstgespraech klaeren wir Zielbild, Risiken und realistische Umsetzungsschritte.</p>
<p>In einem kurzen Erstgespräch klären wir gemeinsam Zielbild, Risiken und realistische Umsetzungsschritte. Erstellen Sie dafür einfach ein Konto über den Button oben rechts und buchen Sie anschließend über die Kalenderfunktion einen Termin mit Ihrem Schulnamen und dem Betreff „Erstgespräch“. Alternativ können Sie uns jederzeit per E-Mail kontaktieren oder unseren Website-Chat nutzen.</p>
</div>
{% if 'username' in session %}
<a class="btn btn-secondary" href="{{ url_for('appointments') }}">Termin anfragen</a>
+46
View File
@@ -0,0 +1,46 @@
{% extends "base.html" %}
{% block title %}Meine Rechnungen{% endblock %}
{% block content %}
<section class="panel">
<h1>Rechnungseinsicht</h1>
<p>Alle Rechnungen im Ueberblick.</p>
</section>
<section class="table-wrap">
<table>
<thead>
<tr>
<th>Rechnungsnummer</th>
<th>Zeitraum</th>
<th>Betrag</th>
<th>Faelligkeit</th>
<th>Status</th>
</tr>
</thead>
<tbody>
{% for invoice in invoices %}
<tr>
<td>{{ invoice.invoice_number }}</td>
<td>{{ invoice.period }}</td>
<td>{{ '%.2f'|format(invoice.amount_eur) }} EUR</td>
<td>{{ invoice.due_date }}</td>
<td>{{ invoice.status }}</td>
</tr>
{% else %}
<tr>
<td colspan="5">Noch keine Rechnungen vorhanden.</td>
</tr>
{% endfor %}
</tbody>
</table>
</section>
<style>
.panel { margin-bottom: 1rem; }
.table-wrap { background: #fff; border: 1px solid #d8e1e8; border-radius: 14px; padding: 1rem; overflow-x: auto; }
table { width: 100%; border-collapse: collapse; }
th, td { text-align: left; padding: 0.7rem; border-bottom: 1px solid #e8eef2; }
</style>
{% endblock %}
+36
View File
@@ -0,0 +1,36 @@
{% extends "base.html" %}
{% block title %}Meine Lizenzen{% endblock %}
{% block content %}
<section class="panel">
<h1>Meine Lizenzen</h1>
<p>Hier sehen Sie Ihre aktiven und abgelaufenen Lizenzinformationen.</p>
</section>
<section class="grid">
{% if licenses %}
{% for item in licenses %}
<article class="card">
<h3>{{ item.plan }} Lizenz</h3>
<p><strong>Schule:</strong> {{ item.school_name }}</p>
<p><strong>Lizenzschluessel:</strong> {{ item.license_key }}</p>
<p><strong>Status:</strong> {{ item.status }}</p>
<p><strong>Gueltig bis:</strong> {{ item.valid_until }}</p>
</article>
{% endfor %}
{% else %}
<article class="card">
<h3>Keine Lizenzen vorhanden</h3>
<p>Aktuell sind noch keine Lizenzen hinterlegt.</p>
</article>
{% endif %}
</section>
<style>
.panel { margin-bottom: 1rem; }
.grid { display: grid; gap: 0.8rem; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); }
.card { background: #fff; border: 1px solid #d8e1e8; border-radius: 14px; padding: 1rem; }
.card p { margin-top: 0.35rem; }
</style>
{% endblock %}
+53
View File
@@ -0,0 +1,53 @@
{% extends "base.html" %}
{% block title %}Support Tickets{% endblock %}
{% block content %}
<section class="panel">
<h1>Support Tickets</h1>
<p>Erstellen und verfolgen Sie Ihre Supportanfragen.</p>
</section>
<section class="ticket-layout">
<form method="post" class="ticket-form">
<h3>Neues Ticket</h3>
<input type="text" name="title" placeholder="Titel" required>
<select name="priority">
<option>Normal</option>
<option>Hoch</option>
<option>Kritisch</option>
</select>
<textarea name="description" rows="5" placeholder="Beschreibung" required></textarea>
<button type="submit">Ticket erstellen</button>
</form>
<div class="tickets">
{% for item in tickets %}
<article class="ticket">
<h3>{{ item.title }}</h3>
<p><strong>Status:</strong> {{ item.status }} | <strong>Prioritaet:</strong> {{ item.priority }}</p>
<p>{{ item.description }}</p>
{% if item.admin_response %}
<p><strong>Admin Antwort:</strong> {{ item.admin_response }}</p>
{% endif %}
<small>{{ item.created_at }}</small>
</article>
{% else %}
<article class="ticket"><p>Keine Tickets vorhanden.</p></article>
{% endfor %}
</div>
</section>
<style>
.panel { margin-bottom: 1rem; }
.ticket-layout { display: grid; grid-template-columns: 320px 1fr; gap: 1rem; }
.ticket-form, .ticket { background: #fff; border: 1px solid #d8e1e8; border-radius: 14px; padding: 1rem; }
.ticket-form { display: grid; gap: 0.6rem; align-self: start; }
input, select, textarea { width: 100%; border: 1px solid #c9d8e3; border-radius: 10px; padding: 0.55rem; font: inherit; }
button { border: 1px solid #0a4c74; background: linear-gradient(120deg, #0c5a86 0%, #08486c 100%); color: #fff; padding: 0.5rem 1rem; border-radius: 999px; font-weight: 700; }
.tickets { display: grid; gap: 0.8rem; }
.ticket p { margin-top: 0.3rem; }
.ticket small { color: #6f8798; }
@media (max-width: 960px) { .ticket-layout { grid-template-columns: 1fr; } }
</style>
{% endblock %}