🌟 🎉 Major Update:

This commit is contained in:
2025-06-01 23:41:02 +02:00
parent 62efe03887
commit 4042f07c00
228 changed files with 8598 additions and 1893 deletions

Binary file not shown.

View File

@ -0,0 +1,31 @@
/* CRITICAL INLINE CSS - Sollte im HTML <head> stehen */
/* Nur die absolut notwendigen Styles für First Paint */
:root{--primary:#0073ce;--bg:#fafbfc;--surface:#fff;--text:#111827;--border:#e5e7eb;--shadow:0 2px 4px rgba(0,0,0,.05)}
*{box-sizing:border-box;margin:0;padding:0;contain:layout style}
body{font-family:system-ui,-apple-system,sans-serif;background:var(--bg);color:var(--text);line-height:1.5;text-rendering:optimizeSpeed;-webkit-font-smoothing:antialiased}
.header{background:var(--surface);border-bottom:1px solid var(--border);padding:1rem;position:sticky;top:0;z-index:1000}
.nav{display:flex;gap:1rem}
.nav-item{padding:.5rem 1rem;border-radius:6px;text-decoration:none;color:#6b7280;transition:background .1s}
.nav-item:hover{background:var(--bg);color:var(--text)}
.nav-item.active{background:var(--primary);color:#fff}
.container{max-width:1200px;margin:0 auto;padding:0 1rem}
.btn{background:var(--primary);color:#fff;border:none;border-radius:6px;padding:.75rem 1.5rem;font-weight:600;cursor:pointer;transition:background .1s}
.btn:hover{background:#005a9f}
.card{background:var(--surface);border:1px solid var(--border);border-radius:8px;padding:1rem;box-shadow:var(--shadow)}
.flex{display:flex}
.grid{display:grid;gap:1rem}
.hidden{display:none}
.w-full{width:100%}
.text-center{text-align:center}
.p-4{padding:1rem}
.mb-4{margin-bottom:1rem}
.status{display:inline-block;padding:.25rem .75rem;border-radius:999px;font-size:.75rem;font-weight:600;text-transform:uppercase}
.status-online{background:#d1fae5;color:#065f46}
.status-offline{background:#fee2e2;color:#991b1b}
.status-printing{background:#dbeafe;color:#1e40af}
.input{background:var(--surface);border:1px solid var(--border);border-radius:6px;padding:.75rem;width:100%}
.input:focus{outline:none;border-color:var(--primary)}
@media (prefers-color-scheme:dark){:root{--bg:#1e293b;--surface:#334155;--text:#f8fafc;--border:#475569;--shadow:0 2px 4px rgba(0,0,0,.3)}}
@media (max-width:768px){.nav{flex-direction:column;gap:.5rem}}
@media (prefers-reduced-motion:reduce){*{transition:none!important}}

View File

@ -0,0 +1,498 @@
/* MYP Platform - Kiosk Optimierte CSS Bundle */
/* Generiert am: 2025-06-01 23:40:46 */
/**
* MYP Platform - Kiosk-Optimierte CSS
* Maximale Performance für Offline-Kiosk-Umgebung
* Keine Touch-Events, minimale Animationen, optimierte Rendering-Performance
*/
/* ===== CRITICAL INLINE STYLES (sollten im HTML-Head stehen) ===== */
:root {
/* Reduzierte Farbvariablen für bessere Performance */
--primary: #0073ce;
--primary-dark: #005a9f;
--bg: #fafbfc;
--surface: #ffffff;
--text: #111827;
--text-muted: #6b7280;
--border: #e5e7eb;
--shadow: 0 2px 4px rgba(0,0,0,0.05);
}
/* CSS Containment für bessere Performance */
* {
contain: layout style;
}
/* Basis-Reset ohne aufwendige Normalisierung */
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: system-ui, -apple-system, sans-serif;
background: var(--bg);
color: var(--text);
line-height: 1.5;
contain: layout style paint;
/* Optimiert für Kiosk-Rendering */
text-rendering: optimizeSpeed;
-webkit-font-smoothing: antialiased;
}
/* ===== LAYOUT OPTIMIERUNGEN ===== */
.header {
background: var(--surface);
border-bottom: 1px solid var(--border);
padding: 1rem;
contain: layout style;
}
.nav {
display: flex;
gap: 1rem;
contain: layout;
}
.nav-item {
padding: 0.5rem 1rem;
border-radius: 6px;
text-decoration: none;
color: var(--text-muted);
transition: background-color 0.1s ease; /* Minimale Transition */
}
.nav-item:hover {
background: var(--bg);
color: var(--text);
}
.nav-item.active {
background: var(--primary);
color: white;
}
/* ===== OPTIMIERTE KARTEN ===== */
.card {
background: var(--surface);
border: 1px solid var(--border);
border-radius: 8px;
padding: 1rem;
box-shadow: var(--shadow);
contain: layout style paint;
}
.card:hover {
transform: translateY(-1px); /* Minimaler Hover-Effekt */
}
/* ===== BUTTONS OHNE KOMPLEXE ANIMATIONEN ===== */
.btn {
background: var(--primary);
color: white;
border: none;
border-radius: 6px;
padding: 0.75rem 1.5rem;
font-weight: 600;
cursor: pointer;
transition: background-color 0.1s ease;
contain: layout style;
}
.btn:hover {
background: var(--primary-dark);
}
.btn-secondary {
background: var(--surface);
color: var(--text);
border: 1px solid var(--border);
}
.btn-secondary:hover {
background: var(--bg);
}
/* ===== INPUTS OPTIMIERT ===== */
.input {
background: var(--surface);
border: 1px solid var(--border);
border-radius: 6px;
padding: 0.75rem;
width: 100%;
transition: border-color 0.1s ease;
contain: layout style;
}
.input:focus {
outline: none;
border-color: var(--primary);
}
/* ===== TABELLEN OHNE KOMPLEXE EFFEKTE ===== */
.table {
width: 100%;
border-collapse: collapse;
background: var(--surface);
border-radius: 8px;
overflow: hidden;
contain: layout;
}
.table th {
background: var(--bg);
padding: 1rem;
text-align: left;
font-weight: 600;
border-bottom: 1px solid var(--border);
}
.table td {
padding: 1rem;
border-bottom: 1px solid var(--border);
}
.table tr:hover {
background: var(--bg);
}
/* ===== STATUS BADGES VEREINFACHT ===== */
.status {
display: inline-block;
padding: 0.25rem 0.75rem;
border-radius: 999px;
font-size: 0.75rem;
font-weight: 600;
text-transform: uppercase;
}
.status-online { background: #d1fae5; color: #065f46; }
.status-offline { background: #fee2e2; color: #991b1b; }
.status-printing { background: #dbeafe; color: #1e40af; }
/* ===== GRID LAYOUTS OPTIMIERT ===== */
.grid {
display: grid;
gap: 1rem;
contain: layout;
}
.grid-2 { grid-template-columns: repeat(2, 1fr); }
.grid-3 { grid-template-columns: repeat(3, 1fr); }
.grid-4 { grid-template-columns: repeat(4, 1fr); }
/* ===== UTILITIES MINIMAL ===== */
.flex { display: flex; contain: layout; }
.flex-col { flex-direction: column; }
.items-center { align-items: center; }
.justify-between { justify-content: space-between; }
.gap-1 { gap: 0.25rem; }
.gap-2 { gap: 0.5rem; }
.gap-4 { gap: 1rem; }
.p-1 { padding: 0.25rem; }
.p-2 { padding: 0.5rem; }
.p-4 { padding: 1rem; }
.p-6 { padding: 1.5rem; }
.m-1 { margin: 0.25rem; }
.m-2 { margin: 0.5rem; }
.m-4 { margin: 1rem; }
.mb-2 { margin-bottom: 0.5rem; }
.mb-4 { margin-bottom: 1rem; }
.mb-6 { margin-bottom: 1.5rem; }
.text-sm { font-size: 0.875rem; }
.text-lg { font-size: 1.125rem; }
.text-xl { font-size: 1.25rem; }
.text-2xl { font-size: 1.5rem; }
.font-semibold { font-weight: 600; }
.font-bold { font-weight: 700; }
.text-center { text-align: center; }
.text-right { text-align: right; }
.w-full { width: 100%; }
.h-full { height: 100%; }
.rounded { border-radius: 6px; }
.rounded-lg { border-radius: 8px; }
.border { border: 1px solid var(--border); }
.border-t { border-top: 1px solid var(--border); }
.border-b { border-bottom: 1px solid var(--border); }
.bg-white { background: var(--surface); }
.bg-gray-50 { background: var(--bg); }
.text-gray-600 { color: var(--text-muted); }
.text-blue-600 { color: var(--primary); }
/* ===== DARK MODE MINIMAL ===== */
@media (prefers-color-scheme: dark) {
:root {
--bg: #1e293b;
--surface: #334155;
--text: #f8fafc;
--text-muted: #94a3b8;
--border: #475569;
--shadow: 0 2px 4px rgba(0,0,0,0.3);
}
}
/* ===== RESPONSIVE FÜR KIOSK-DISPLAYS ===== */
@media (max-width: 1024px) {
.grid-4 { grid-template-columns: repeat(2, 1fr); }
.grid-3 { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 768px) {
.grid-4,
.grid-3,
.grid-2 { grid-template-columns: 1fr; }
.nav {
flex-direction: column;
gap: 0.5rem;
}
}
/* ===== PERFORMANCE OPTIMIERUNGEN ===== */
/* Deaktivierung nicht benötigter Features für Kiosk */
* {
/* Keine Touch-Events */
touch-action: none;
-webkit-touch-callout: none;
-webkit-user-select: none;
user-select: none;
}
/* Nur notwendige Elemente selektierbar */
input,
textarea {
-webkit-user-select: text;
user-select: text;
touch-action: manipulation;
}
/* Optimierte Scrolling-Performance */
* {
-webkit-overflow-scrolling: touch;
scroll-behavior: smooth;
}
/* GPU-Acceleration nur wo nötig */
.card:hover,
.btn:hover {
will-change: transform;
}
/* Minimale Animationen für bessere Performance */
@media (prefers-reduced-motion: reduce) {
* {
transition: none !important;
animation: none !important;
}
}
/* Print-Optimierung (falls Kiosk drucken kann) */
@media print {
.nav,
.btn {
display: none;
}
.card {
box-shadow: none;
border: 1px solid #000;
}
}
/* ===== LAYOUT SHIFT PREVENTION ===== */
img {
height: auto;
max-width: 100%;
vertical-align: middle;
}
/* Container für stabile Layouts */
.container {
max-width: 1200px;
margin: 0 auto;
padding: 0 1rem;
contain: layout;
}
/* ===== KIOSK-SPEZIFISCHE OPTIMIERUNGEN ===== */
/* Vollbildmodus-Unterstützung */
html,
body {
height: 100%;
overflow-x: hidden;
}
/* Fokus-Management für Keyboard-Navigation */
:focus {
outline: 2px solid var(--primary);
outline-offset: 2px;
}
/* Kein Selection-Highlighting */
::selection {
background: transparent;
}
/* Optimierte Font-Loading */
@font-face {
font-family: 'system-ui';
font-display: swap;
}
/**
* Minimales Icon-Set für Kiosk-Modus
* SVG-basierte Icons als CSS-Pseudo-Elemente für beste Performance
* Ersetzt FontAwesome für deutlich kleinere Bundle-Größe
*/
/* Icon-Basis-Klasse */
.icon {
display: inline-block;
width: 1rem;
height: 1rem;
background-size: contain;
background-repeat: no-repeat;
background-position: center;
vertical-align: middle;
}
.icon-lg { width: 1.5rem; height: 1.5rem; }
.icon-xl { width: 2rem; height: 2rem; }
/* ===== DRUCKER ICONS ===== */
.icon-printer {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23374151'%3E%3Cpath d='M19 8H5c-1.66 0-3 1.34-3 3v6h4v4h12v-4h4v-6c0-1.66-1.34-3-3-3zm-3 11H8v-5h8v5zm3-7c-.55 0-1-.45-1-1s.45-1 1-1 1 .45 1 1-.45 1-1 1zm-1-9H6v4h12V3z'/%3E%3C/svg%3E");
}
.icon-print {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23374151'%3E%3Cpath d='M19 8H5c-1.66 0-3 1.34-3 3v6h4v4h12v-4h4v-6c0-1.66-1.34-3-3-3zm-3 11H8v-5h8v5zm3-7c-.55 0-1-.45-1-1s.45-1 1-1 1 .45 1 1-.45 1-1 1zm-1-9H6v4h12V3z'/%3E%3C/svg%3E");
}
/* ===== STATUS ICONS ===== */
.icon-check {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%2310b981'%3E%3Cpath d='M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z'/%3E%3C/svg%3E");
}
.icon-warning {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23f59e0b'%3E%3Cpath d='M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z'/%3E%3C/svg%3E");
}
.icon-error {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23ef4444'%3E%3Cpath d='M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z'/%3E%3C/svg%3E");
}
.icon-offline {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%236b7280'%3E%3Cpath d='M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z'/%3E%3C/svg%3E");
}
/* ===== NAVIGATION ICONS ===== */
.icon-home {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23374151'%3E%3Cpath d='M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z'/%3E%3C/svg%3E");
}
.icon-settings {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23374151'%3E%3Cpath d='M19.14,12.94c0.04-0.3,0.06-0.61,0.06-0.94c0-0.32-0.02-0.64-0.07-0.94l2.03-1.58c0.18-0.14,0.23-0.41,0.12-0.61 l-1.92-3.32c-0.12-0.22-0.37-0.29-0.59-0.22l-2.39,0.96c-0.5-0.38-1.03-0.7-1.62-0.94L14.4,2.81c-0.04-0.24-0.24-0.41-0.48-0.41 h-3.84c-0.24,0-0.43,0.17-0.47,0.41L9.25,5.35C8.66,5.59,8.12,5.92,7.63,6.29L5.24,5.33c-0.22-0.08-0.47,0-0.59,0.22L2.74,8.87 C2.62,9.08,2.66,9.34,2.86,9.48l2.03,1.58C4.84,11.36,4.8,11.69,4.8,12s0.02,0.64,0.07,0.94l-2.03,1.58 c-0.18,0.14-0.23,0.41-0.12,0.61l1.92,3.32c0.12,0.22,0.37,0.29,0.59,0.22l2.39-0.96c0.5,0.38,1.03,0.7,1.62,0.94l0.36,2.54 c0.05,0.24,0.24,0.41,0.48,0.41h3.84c0.24,0,0.44-0.17,0.47-0.41l0.36-2.54c0.59-0.24,1.13-0.56,1.62-0.94l2.39,0.96 c0.22,0.08,0.47,0,0.59-0.22l1.92-3.32c0.12-0.22,0.07-0.47-0.12-0.61L19.14,12.94z M12,15.6c-1.98,0-3.6-1.62-3.6-3.6 s1.62-3.6,3.6-3.6s3.6,1.62,3.6,3.6S13.98,15.6,12,15.6z'/%3E%3C/svg%3E");
}
.icon-users {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23374151'%3E%3Cpath d='M16 4c0-1.11.89-2 2-2s2 .89 2 2-.89 2-2 2-2-.89-2-2zm4 18v-6h2.5l-2.54-7.63A3.012 3.012 0 0 0 16.43 6c-.68 0-1.3.27-1.77.72L12 8.5l-2.66-1.78C8.87 6.27 8.25 6 7.57 6c-1.31 0-2.42.83-2.83 2L2.5 16H5v6h2v-6h2v6h2zm-6.5-10.5c.83 0 1.5-.67 1.5-1.5s-.67-1.5-1.5-1.5S12 9.17 12 10s.67 1.5 1.5 1.5z'/%3E%3C/svg%3E");
}
.icon-dashboard {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23374151'%3E%3Cpath d='M3 13h8V3H3v10zm0 8h8v-6H3v6zm10 0h8V11h-8v10zm0-18v6h8V3h-8z'/%3E%3C/svg%3E");
}
/* ===== ACTION ICONS ===== */
.icon-plus {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23374151'%3E%3Cpath d='M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z'/%3E%3C/svg%3E");
}
.icon-edit {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23374151'%3E%3Cpath d='M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34c-.39-.39-1.02-.39-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z'/%3E%3C/svg%3E");
}
.icon-delete {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23ef4444'%3E%3Cpath d='M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z'/%3E%3C/svg%3E");
}
.icon-refresh {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23374151'%3E%3Cpath d='M17.65 6.35C16.2 4.9 14.21 4 12 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08c-.82 2.33-3.04 4-5.65 4-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z'/%3E%3C/svg%3E");
}
/* ===== POWER/CONNECTION ICONS ===== */
.icon-power {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%2310b981'%3E%3Cpath d='M13 3h-2v10h2V3zm4.83 2.17l-1.42 1.42C17.99 7.86 19 9.81 19 12c0 3.87-3.13 7-7 7s-7-3.13-7-7c0-2.19 1.01-4.14 2.58-5.42L6.17 5.17C4.23 6.82 3 9.26 3 12c0 4.97 4.03 9 9 9s9-4.03 9-9c0-2.74-1.23-5.18-3.17-6.83z'/%3E%3C/svg%3E");
}
.icon-wifi {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%2310b981'%3E%3Cpath d='M1 9l2 2c4.97-4.97 13.03-4.97 18 0l2-2C16.93 2.93 7.07 2.93 1 9zm8 8l3 3 3-3c-1.65-1.66-4.34-1.66-6 0zm-4-4l2 2c2.76-2.76 7.24-2.76 10 0l2-2C15.14 9.14 8.87 9.14 5 13z'/%3E%3C/svg%3E");
}
/* ===== DOKUMENT ICONS ===== */
.icon-file {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23374151'%3E%3Cpath d='M14,2H6A2,2 0 0,0 4,4V20A2,2 0 0,0 6,22H18A2,2 0 0,0 20,20V8L14,2M18,20H6V4H13V9H18V20Z'/%3E%3C/svg%3E");
}
.icon-queue {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23374151'%3E%3Cpath d='M4 6H2v14c0 1.1.9 2 2 2h14v-2H4V6zm16-4H8c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm-1 9H9V9h10v2zm-4 4H9v-2h6v2zm4-8H9V5h10v2z'/%3E%3C/svg%3E");
}
/* ===== INFO ICONS ===== */
.icon-info {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%233b82f6'%3E%3Cpath d='M12,2C6.48,2 2,6.48 2,12C2,17.52 6.48,22 12,22C17.52,22 22,17.52 22,12C22,6.48 17.52,2 12,2M13,17H11V11H13M13,9H11V7H13'/%3E%3C/svg%3E");
}
.icon-time {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23374151'%3E%3Cpath d='M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M16.2,16.2L11,13V7H12.5V12.2L17,14.7L16.2,16.2Z'/%3E%3C/svg%3E");
}
/* ===== ARROW ICONS ===== */
.icon-arrow-right {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23374151'%3E%3Cpath d='M8.59 16.59L13.17 12 8.59 7.41 10 6l6 6-6 6-1.41-1.41z'/%3E%3C/svg%3E");
}
.icon-arrow-down {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23374151'%3E%3Cpath d='M7.41 8.59L12 13.17l4.59-4.58L18 10l-6 6-6-6 1.41-1.41z'/%3E%3C/svg%3E");
}
/* ===== HOVER-STATES ===== */
.btn:hover .icon,
.nav-item:hover .icon {
opacity: 0.8;
}
/* ===== DARK MODE ===== */
@media (prefers-color-scheme: dark) {
.icon-printer,
.icon-print,
.icon-home,
.icon-settings,
.icon-users,
.icon-dashboard,
.icon-plus,
.icon-edit,
.icon-refresh,
.icon-file,
.icon-queue,
.icon-time,
.icon-arrow-right,
.icon-arrow-down {
filter: brightness(2);
}
}
/* ===== RESPONSIVE ICON-SIZES ===== */
@media (max-width: 768px) {
.icon { width: 1.25rem; height: 1.25rem; }
.icon-lg { width: 1.75rem; height: 1.75rem; }
.icon-xl { width: 2.25rem; height: 2.25rem; }
}

View File

@ -0,0 +1,497 @@
/* MYP Platform - Kiosk Optimierte CSS Bundle */
/* Generiert am: 2025-06-01 23:40:46 */
/**
* MYP Platform - Kiosk-Optimierte CSS
* Maximale Performance für Offline-Kiosk-Umgebung
* Keine Touch-Events, minimale Animationen, optimierte Rendering-Performance
*/
/* ===== CRITICAL INLINE STYLES (sollten im HTML-Head stehen) ===== */
:root {
/* Reduzierte Farbvariablen für bessere Performance */
--primary: #0073ce;
--primary-dark: #005a9f;
--bg: #fafbfc;
--surface: #ffffff;
--text: #111827;
--text-muted: #6b7280;
--border: #e5e7eb;
--shadow: 0 2px 4px rgba(0,0,0,0.05);
}
/* CSS Containment für bessere Performance */
* {
contain: layout style;
}
/* Basis-Reset ohne aufwendige Normalisierung */
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: system-ui, -apple-system, sans-serif;
background: var(--bg);
color: var(--text);
line-height: 1.5;
contain: layout style paint;
/* Optimiert für Kiosk-Rendering */
text-rendering: optimizeSpeed;
-webkit-font-smoothing: antialiased;
}
/* ===== LAYOUT OPTIMIERUNGEN ===== */
.header {
background: var(--surface);
border-bottom: 1px solid var(--border);
padding: 1rem;
contain: layout style;
}
.nav {
display: flex;
gap: 1rem;
contain: layout;
}
.nav-item {
padding: 0.5rem 1rem;
border-radius: 6px;
text-decoration: none;
color: var(--text-muted);
transition: background-color 0.1s ease; /* Minimale Transition */
}
.nav-item:hover {
background: var(--bg);
color: var(--text);
}
.nav-item.active {
background: var(--primary);
color: white;
}
/* ===== OPTIMIERTE KARTEN ===== */
.card {
background: var(--surface);
border: 1px solid var(--border);
border-radius: 8px;
padding: 1rem;
box-shadow: var(--shadow);
contain: layout style paint;
}
.card:hover {
transform: translateY(-1px); /* Minimaler Hover-Effekt */
}
/* ===== BUTTONS OHNE KOMPLEXE ANIMATIONEN ===== */
.btn {
background: var(--primary);
color: white;
border: none;
border-radius: 6px;
padding: 0.75rem 1.5rem;
font-weight: 600;
cursor: pointer;
transition: background-color 0.1s ease;
contain: layout style;
}
.btn:hover {
background: var(--primary-dark);
}
.btn-secondary {
background: var(--surface);
color: var(--text);
border: 1px solid var(--border);
}
.btn-secondary:hover {
background: var(--bg);
}
/* ===== INPUTS OPTIMIERT ===== */
.input {
background: var(--surface);
border: 1px solid var(--border);
border-radius: 6px;
padding: 0.75rem;
width: 100%;
transition: border-color 0.1s ease;
contain: layout style;
}
.input:focus {
outline: none;
border-color: var(--primary);
}
/* ===== TABELLEN OHNE KOMPLEXE EFFEKTE ===== */
.table {
width: 100%;
border-collapse: collapse;
background: var(--surface);
border-radius: 8px;
overflow: hidden;
contain: layout;
}
.table th {
background: var(--bg);
padding: 1rem;
text-align: left;
font-weight: 600;
border-bottom: 1px solid var(--border);
}
.table td {
padding: 1rem;
border-bottom: 1px solid var(--border);
}
.table tr:hover {
background: var(--bg);
}
/* ===== STATUS BADGES VEREINFACHT ===== */
.status {
display: inline-block;
padding: 0.25rem 0.75rem;
border-radius: 999px;
font-size: 0.75rem;
font-weight: 600;
text-transform: uppercase;
}
.status-online { background: #d1fae5; color: #065f46; }
.status-offline { background: #fee2e2; color: #991b1b; }
.status-printing { background: #dbeafe; color: #1e40af; }
/* ===== GRID LAYOUTS OPTIMIERT ===== */
.grid {
display: grid;
gap: 1rem;
contain: layout;
}
.grid-2 { grid-template-columns: repeat(2, 1fr); }
.grid-3 { grid-template-columns: repeat(3, 1fr); }
.grid-4 { grid-template-columns: repeat(4, 1fr); }
/* ===== UTILITIES MINIMAL ===== */
.flex { display: flex; contain: layout; }
.flex-col { flex-direction: column; }
.items-center { align-items: center; }
.justify-between { justify-content: space-between; }
.gap-1 { gap: 0.25rem; }
.gap-2 { gap: 0.5rem; }
.gap-4 { gap: 1rem; }
.p-1 { padding: 0.25rem; }
.p-2 { padding: 0.5rem; }
.p-4 { padding: 1rem; }
.p-6 { padding: 1.5rem; }
.m-1 { margin: 0.25rem; }
.m-2 { margin: 0.5rem; }
.m-4 { margin: 1rem; }
.mb-2 { margin-bottom: 0.5rem; }
.mb-4 { margin-bottom: 1rem; }
.mb-6 { margin-bottom: 1.5rem; }
.text-sm { font-size: 0.875rem; }
.text-lg { font-size: 1.125rem; }
.text-xl { font-size: 1.25rem; }
.text-2xl { font-size: 1.5rem; }
.font-semibold { font-weight: 600; }
.font-bold { font-weight: 700; }
.text-center { text-align: center; }
.text-right { text-align: right; }
.w-full { width: 100%; }
.h-full { height: 100%; }
.rounded { border-radius: 6px; }
.rounded-lg { border-radius: 8px; }
.border { border: 1px solid var(--border); }
.border-t { border-top: 1px solid var(--border); }
.border-b { border-bottom: 1px solid var(--border); }
.bg-white { background: var(--surface); }
.bg-gray-50 { background: var(--bg); }
.text-gray-600 { color: var(--text-muted); }
.text-blue-600 { color: var(--primary); }
/* ===== DARK MODE MINIMAL ===== */
@media (prefers-color-scheme: dark) {
:root {
--bg: #1e293b;
--surface: #334155;
--text: #f8fafc;
--text-muted: #94a3b8;
--border: #475569;
--shadow: 0 2px 4px rgba(0,0,0,0.3);
}
}
/* ===== RESPONSIVE FÜR KIOSK-DISPLAYS ===== */
@media (max-width: 1024px) {
.grid-4 { grid-template-columns: repeat(2, 1fr); }
.grid-3 { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 768px) {
.grid-4,
.grid-3,
.grid-2 { grid-template-columns: 1fr; }
.nav {
flex-direction: column;
gap: 0.5rem;
}
}
/* ===== PERFORMANCE OPTIMIERUNGEN ===== */
/* Deaktivierung nicht benötigter Features für Kiosk */
* {
/* Keine Touch-Events */
touch-action: none;
-webkit-touch-callout: none;
-webkit-user-select: none;
user-select: none;
}
/* Nur notwendige Elemente selektierbar */
input,
textarea {
-webkit-user-select: text;
user-select: text;
touch-action: manipulation;
}
/* Optimierte Scrolling-Performance */
* {
-webkit-overflow-scrolling: touch;
scroll-behavior: smooth;
}
/* GPU-Acceleration nur wo nötig */
.card:hover,
.btn:hover {
will-change: transform;
}
/* Minimale Animationen für bessere Performance */
@media (prefers-reduced-motion: reduce) {
* {
transition: none !important;
animation: none !important;
}
}
/* Print-Optimierung (falls Kiosk drucken kann) */
@media print {
.nav,
.btn {
display: none;
}
.card {
box-shadow: none;
border: 1px solid #000;
}
}
/* ===== LAYOUT SHIFT PREVENTION ===== */
img {
height: auto;
max-width: 100%;
vertical-align: middle;
}
/* Container für stabile Layouts */
.container {
max-width: 1200px;
margin: 0 auto;
padding: 0 1rem;
contain: layout;
}
/* ===== KIOSK-SPEZIFISCHE OPTIMIERUNGEN ===== */
/* Vollbildmodus-Unterstützung */
html,
body {
height: 100%;
overflow-x: hidden;
}
/* Fokus-Management für Keyboard-Navigation */
:focus {
outline: 2px solid var(--primary);
outline-offset: 2px;
}
/* Kein Selection-Highlighting */
::selection {
background: transparent;
}
/* Optimierte Font-Loading */
@font-face {
font-family: 'system-ui';
font-display: swap;
}
/**
* Minimales Icon-Set für Kiosk-Modus
* SVG-basierte Icons als CSS-Pseudo-Elemente für beste Performance
*/
/* Icon-Basis-Klasse */
.icon {
display: inline-block;
width: 1rem;
height: 1rem;
background-size: contain;
background-repeat: no-repeat;
background-position: center;
vertical-align: middle;
}
.icon-lg { width: 1.5rem; height: 1.5rem; }
.icon-xl { width: 2rem; height: 2rem; }
/* ===== DRUCKER ICONS ===== */
.icon-printer {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23374151'%3E%3Cpath d='M19 8H5c-1.66 0-3 1.34-3 3v6h4v4h12v-4h4v-6c0-1.66-1.34-3-3-3zm-3 11H8v-5h8v5zm3-7c-.55 0-1-.45-1-1s.45-1 1-1 1 .45 1 1-.45 1-1 1zm-1-9H6v4h12V3z'/%3E%3C/svg%3E");
}
.icon-print {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23374151'%3E%3Cpath d='M19 8H5c-1.66 0-3 1.34-3 3v6h4v4h12v-4h4v-6c0-1.66-1.34-3-3-3zm-3 11H8v-5h8v5zm3-7c-.55 0-1-.45-1-1s.45-1 1-1 1 .45 1 1-.45 1-1 1zm-1-9H6v4h12V3z'/%3E%3C/svg%3E");
}
/* ===== STATUS ICONS ===== */
.icon-check {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%2310b981'%3E%3Cpath d='M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z'/%3E%3C/svg%3E");
}
.icon-warning {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23f59e0b'%3E%3Cpath d='M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z'/%3E%3C/svg%3E");
}
.icon-error {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23ef4444'%3E%3Cpath d='M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z'/%3E%3C/svg%3E");
}
.icon-offline {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%236b7280'%3E%3Cpath d='M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z'/%3E%3C/svg%3E");
}
/* ===== NAVIGATION ICONS ===== */
.icon-home {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23374151'%3E%3Cpath d='M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z'/%3E%3C/svg%3E");
}
.icon-settings {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23374151'%3E%3Cpath d='M19.14,12.94c0.04-0.3,0.06-0.61,0.06-0.94c0-0.32-0.02-0.64-0.07-0.94l2.03-1.58c0.18-0.14,0.23-0.41,0.12-0.61 l-1.92-3.32c-0.12-0.22-0.37-0.29-0.59-0.22l-2.39,0.96c-0.5-0.38-1.03-0.7-1.62-0.94L14.4,2.81c-0.04-0.24-0.24-0.41-0.48-0.41 h-3.84c-0.24,0-0.43,0.17-0.47,0.41L9.25,5.35C8.66,5.59,8.12,5.92,7.63,6.29L5.24,5.33c-0.22-0.08-0.47,0-0.59,0.22L2.74,8.87 C2.62,9.08,2.66,9.34,2.86,9.48l2.03,1.58C4.84,11.36,4.8,11.69,4.8,12s0.02,0.64,0.07,0.94l-2.03,1.58 c-0.18,0.14-0.23,0.41-0.12,0.61l1.92,3.32c0.12,0.22,0.37,0.29,0.59,0.22l2.39-0.96c0.5,0.38,1.03,0.7,1.62,0.94l0.36,2.54 c0.05,0.24,0.24,0.41,0.48,0.41h3.84c0.24,0,0.44-0.17,0.47-0.41l0.36-2.54c0.59-0.24,1.13-0.56,1.62-0.94l2.39,0.96 c0.22,0.08,0.47,0,0.59-0.22l1.92-3.32c0.12-0.22,0.07-0.47-0.12-0.61L19.14,12.94z M12,15.6c-1.98,0-3.6-1.62-3.6-3.6 s1.62-3.6,3.6-3.6s3.6,1.62,3.6,3.6S13.98,15.6,12,15.6z'/%3E%3C/svg%3E");
}
.icon-users {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23374151'%3E%3Cpath d='M16 4c0-1.11.89-2 2-2s2 .89 2 2-.89 2-2 2-2-.89-2-2zm4 18v-6h2.5l-2.54-7.63A3.012 3.012 0 0 0 16.43 6c-.68 0-1.3.27-1.77.72L12 8.5l-2.66-1.78C8.87 6.27 8.25 6 7.57 6c-1.31 0-2.42.83-2.83 2L2.5 16H5v6h2v-6h2v6h2zm-6.5-10.5c.83 0 1.5-.67 1.5-1.5s-.67-1.5-1.5-1.5S12 9.17 12 10s.67 1.5 1.5 1.5z'/%3E%3C/svg%3E");
}
.icon-dashboard {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23374151'%3E%3Cpath d='M3 13h8V3H3v10zm0 8h8v-6H3v6zm10 0h8V11h-8v10zm0-18v6h8V3h-8z'/%3E%3C/svg%3E");
}
/* ===== ACTION ICONS ===== */
.icon-plus {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23374151'%3E%3Cpath d='M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z'/%3E%3C/svg%3E");
}
.icon-edit {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23374151'%3E%3Cpath d='M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34c-.39-.39-1.02-.39-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z'/%3E%3C/svg%3E");
}
.icon-delete {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23ef4444'%3E%3Cpath d='M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z'/%3E%3C/svg%3E");
}
.icon-refresh {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23374151'%3E%3Cpath d='M17.65 6.35C16.2 4.9 14.21 4 12 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08c-.82 2.33-3.04 4-5.65 4-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z'/%3E%3C/svg%3E");
}
/* ===== POWER/CONNECTION ICONS ===== */
.icon-power {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%2310b981'%3E%3Cpath d='M13 3h-2v10h2V3zm4.83 2.17l-1.42 1.42C17.99 7.86 19 9.81 19 12c0 3.87-3.13 7-7 7s-7-3.13-7-7c0-2.19 1.01-4.14 2.58-5.42L6.17 5.17C4.23 6.82 3 9.26 3 12c0 4.97 4.03 9 9 9s9-4.03 9-9c0-2.74-1.23-5.18-3.17-6.83z'/%3E%3C/svg%3E");
}
.icon-wifi {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%2310b981'%3E%3Cpath d='M1 9l2 2c4.97-4.97 13.03-4.97 18 0l2-2C16.93 2.93 7.07 2.93 1 9zm8 8l3 3 3-3c-1.65-1.66-4.34-1.66-6 0zm-4-4l2 2c2.76-2.76 7.24-2.76 10 0l2-2C15.14 9.14 8.87 9.14 5 13z'/%3E%3C/svg%3E");
}
/* ===== DOKUMENT ICONS ===== */
.icon-file {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23374151'%3E%3Cpath d='M14,2H6A2,2 0 0,0 4,4V20A2,2 0 0,0 6,22H18A2,2 0 0,0 20,20V8L14,2M18,20H6V4H13V9H18V20Z'/%3E%3C/svg%3E");
}
.icon-queue {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23374151'%3E%3Cpath d='M4 6H2v14c0 1.1.9 2 2 2h14v-2H4V6zm16-4H8c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm-1 9H9V9h10v2zm-4 4H9v-2h6v2zm4-8H9V5h10v2z'/%3E%3C/svg%3E");
}
/* ===== INFO ICONS ===== */
.icon-info {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%233b82f6'%3E%3Cpath d='M12,2C6.48,2 2,6.48 2,12C2,17.52 6.48,22 12,22C17.52,22 22,17.52 22,12C22,6.48 17.52,2 12,2M13,17H11V11H13M13,9H11V7H13'/%3E%3C/svg%3E");
}
.icon-time {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23374151'%3E%3Cpath d='M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M16.2,16.2L11,13V7H12.5V12.2L17,14.7L16.2,16.2Z'/%3E%3C/svg%3E");
}
/* ===== ARROW ICONS ===== */
.icon-arrow-right {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23374151'%3E%3Cpath d='M8.59 16.59L13.17 12 8.59 7.41 10 6l6 6-6 6-1.41-1.41z'/%3E%3C/svg%3E");
}
.icon-arrow-down {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23374151'%3E%3Cpath d='M7.41 8.59L12 13.17l4.59-4.58L18 10l-6 6-6-6 1.41-1.41z'/%3E%3C/svg%3E");
}
/* ===== HOVER-STATES ===== */
.btn:hover .icon,
.nav-item:hover .icon {
opacity: 0.8;
}
/* ===== DARK MODE ===== */
@media (prefers-color-scheme: dark) {
.icon-printer,
.icon-print,
.icon-home,
.icon-settings,
.icon-users,
.icon-dashboard,
.icon-plus,
.icon-edit,
.icon-refresh,
.icon-file,
.icon-queue,
.icon-time,
.icon-arrow-right,
.icon-arrow-down {
filter: brightness(2);
}
}
/* ===== RESPONSIVE ICON-SIZES ===== */
@media (max-width: 768px) {
.icon { width: 1.25rem; height: 1.25rem; }
.icon-lg { width: 1.75rem; height: 1.75rem; }
.icon-xl { width: 2.25rem; height: 2.25rem; }
}

View File

@ -0,0 +1,498 @@
/* MYP Platform - Kiosk Optimierte CSS Bundle */
/* Generiert am: 2025-06-01 23:40:46 */
/**
* MYP Platform - Kiosk-Optimierte CSS
* Maximale Performance für Offline-Kiosk-Umgebung
* Keine Touch-Events, minimale Animationen, optimierte Rendering-Performance
*/
/* ===== CRITICAL INLINE STYLES (sollten im HTML-Head stehen) ===== */
:root {
/* Reduzierte Farbvariablen für bessere Performance */
--primary: #0073ce;
--primary-dark: #005a9f;
--bg: #fafbfc;
--surface: #ffffff;
--text: #111827;
--text-muted: #6b7280;
--border: #e5e7eb;
--shadow: 0 2px 4px rgba(0,0,0,0.05);
}
/* CSS Containment für bessere Performance */
* {
contain: layout style;
}
/* Basis-Reset ohne aufwendige Normalisierung */
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: system-ui, -apple-system, sans-serif;
background: var(--bg);
color: var(--text);
line-height: 1.5;
contain: layout style paint;
/* Optimiert für Kiosk-Rendering */
text-rendering: optimizeSpeed;
-webkit-font-smoothing: antialiased;
}
/* ===== LAYOUT OPTIMIERUNGEN ===== */
.header {
background: var(--surface);
border-bottom: 1px solid var(--border);
padding: 1rem;
contain: layout style;
}
.nav {
display: flex;
gap: 1rem;
contain: layout;
}
.nav-item {
padding: 0.5rem 1rem;
border-radius: 6px;
text-decoration: none;
color: var(--text-muted);
transition: background-color 0.1s ease; /* Minimale Transition */
}
.nav-item:hover {
background: var(--bg);
color: var(--text);
}
.nav-item.active {
background: var(--primary);
color: white;
}
/* ===== OPTIMIERTE KARTEN ===== */
.card {
background: var(--surface);
border: 1px solid var(--border);
border-radius: 8px;
padding: 1rem;
box-shadow: var(--shadow);
contain: layout style paint;
}
.card:hover {
transform: translateY(-1px); /* Minimaler Hover-Effekt */
}
/* ===== BUTTONS OHNE KOMPLEXE ANIMATIONEN ===== */
.btn {
background: var(--primary);
color: white;
border: none;
border-radius: 6px;
padding: 0.75rem 1.5rem;
font-weight: 600;
cursor: pointer;
transition: background-color 0.1s ease;
contain: layout style;
}
.btn:hover {
background: var(--primary-dark);
}
.btn-secondary {
background: var(--surface);
color: var(--text);
border: 1px solid var(--border);
}
.btn-secondary:hover {
background: var(--bg);
}
/* ===== INPUTS OPTIMIERT ===== */
.input {
background: var(--surface);
border: 1px solid var(--border);
border-radius: 6px;
padding: 0.75rem;
width: 100%;
transition: border-color 0.1s ease;
contain: layout style;
}
.input:focus {
outline: none;
border-color: var(--primary);
}
/* ===== TABELLEN OHNE KOMPLEXE EFFEKTE ===== */
.table {
width: 100%;
border-collapse: collapse;
background: var(--surface);
border-radius: 8px;
overflow: hidden;
contain: layout;
}
.table th {
background: var(--bg);
padding: 1rem;
text-align: left;
font-weight: 600;
border-bottom: 1px solid var(--border);
}
.table td {
padding: 1rem;
border-bottom: 1px solid var(--border);
}
.table tr:hover {
background: var(--bg);
}
/* ===== STATUS BADGES VEREINFACHT ===== */
.status {
display: inline-block;
padding: 0.25rem 0.75rem;
border-radius: 999px;
font-size: 0.75rem;
font-weight: 600;
text-transform: uppercase;
}
.status-online { background: #d1fae5; color: #065f46; }
.status-offline { background: #fee2e2; color: #991b1b; }
.status-printing { background: #dbeafe; color: #1e40af; }
/* ===== GRID LAYOUTS OPTIMIERT ===== */
.grid {
display: grid;
gap: 1rem;
contain: layout;
}
.grid-2 { grid-template-columns: repeat(2, 1fr); }
.grid-3 { grid-template-columns: repeat(3, 1fr); }
.grid-4 { grid-template-columns: repeat(4, 1fr); }
/* ===== UTILITIES MINIMAL ===== */
.flex { display: flex; contain: layout; }
.flex-col { flex-direction: column; }
.items-center { align-items: center; }
.justify-between { justify-content: space-between; }
.gap-1 { gap: 0.25rem; }
.gap-2 { gap: 0.5rem; }
.gap-4 { gap: 1rem; }
.p-1 { padding: 0.25rem; }
.p-2 { padding: 0.5rem; }
.p-4 { padding: 1rem; }
.p-6 { padding: 1.5rem; }
.m-1 { margin: 0.25rem; }
.m-2 { margin: 0.5rem; }
.m-4 { margin: 1rem; }
.mb-2 { margin-bottom: 0.5rem; }
.mb-4 { margin-bottom: 1rem; }
.mb-6 { margin-bottom: 1.5rem; }
.text-sm { font-size: 0.875rem; }
.text-lg { font-size: 1.125rem; }
.text-xl { font-size: 1.25rem; }
.text-2xl { font-size: 1.5rem; }
.font-semibold { font-weight: 600; }
.font-bold { font-weight: 700; }
.text-center { text-align: center; }
.text-right { text-align: right; }
.w-full { width: 100%; }
.h-full { height: 100%; }
.rounded { border-radius: 6px; }
.rounded-lg { border-radius: 8px; }
.border { border: 1px solid var(--border); }
.border-t { border-top: 1px solid var(--border); }
.border-b { border-bottom: 1px solid var(--border); }
.bg-white { background: var(--surface); }
.bg-gray-50 { background: var(--bg); }
.text-gray-600 { color: var(--text-muted); }
.text-blue-600 { color: var(--primary); }
/* ===== DARK MODE MINIMAL ===== */
@media (prefers-color-scheme: dark) {
:root {
--bg: #1e293b;
--surface: #334155;
--text: #f8fafc;
--text-muted: #94a3b8;
--border: #475569;
--shadow: 0 2px 4px rgba(0,0,0,0.3);
}
}
/* ===== RESPONSIVE FÜR KIOSK-DISPLAYS ===== */
@media (max-width: 1024px) {
.grid-4 { grid-template-columns: repeat(2, 1fr); }
.grid-3 { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 768px) {
.grid-4,
.grid-3,
.grid-2 { grid-template-columns: 1fr; }
.nav {
flex-direction: column;
gap: 0.5rem;
}
}
/* ===== PERFORMANCE OPTIMIERUNGEN ===== */
/* Deaktivierung nicht benötigter Features für Kiosk */
* {
/* Keine Touch-Events */
touch-action: none;
-webkit-touch-callout: none;
-webkit-user-select: none;
user-select: none;
}
/* Nur notwendige Elemente selektierbar */
input,
textarea {
-webkit-user-select: text;
user-select: text;
touch-action: manipulation;
}
/* Optimierte Scrolling-Performance */
* {
-webkit-overflow-scrolling: touch;
scroll-behavior: smooth;
}
/* GPU-Acceleration nur wo nötig */
.card:hover,
.btn:hover {
will-change: transform;
}
/* Minimale Animationen für bessere Performance */
@media (prefers-reduced-motion: reduce) {
* {
transition: none !important;
animation: none !important;
}
}
/* Print-Optimierung (falls Kiosk drucken kann) */
@media print {
.nav,
.btn {
display: none;
}
.card {
box-shadow: none;
border: 1px solid #000;
}
}
/* ===== LAYOUT SHIFT PREVENTION ===== */
img {
height: auto;
max-width: 100%;
vertical-align: middle;
}
/* Container für stabile Layouts */
.container {
max-width: 1200px;
margin: 0 auto;
padding: 0 1rem;
contain: layout;
}
/* ===== KIOSK-SPEZIFISCHE OPTIMIERUNGEN ===== */
/* Vollbildmodus-Unterstützung */
html,
body {
height: 100%;
overflow-x: hidden;
}
/* Fokus-Management für Keyboard-Navigation */
:focus {
outline: 2px solid var(--primary);
outline-offset: 2px;
}
/* Kein Selection-Highlighting */
::selection {
background: transparent;
}
/* Optimierte Font-Loading */
@font-face {
font-family: 'system-ui';
font-display: swap;
}
/**
* Minimales Icon-Set für Kiosk-Modus
* SVG-basierte Icons als CSS-Pseudo-Elemente für beste Performance
* Ersetzt FontAwesome für deutlich kleinere Bundle-Größe
*/
/* Icon-Basis-Klasse */
.icon {
display: inline-block;
width: 1rem;
height: 1rem;
background-size: contain;
background-repeat: no-repeat;
background-position: center;
vertical-align: middle;
}
.icon-lg { width: 1.5rem; height: 1.5rem; }
.icon-xl { width: 2rem; height: 2rem; }
/* ===== DRUCKER ICONS ===== */
.icon-printer {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23374151'%3E%3Cpath d='M19 8H5c-1.66 0-3 1.34-3 3v6h4v4h12v-4h4v-6c0-1.66-1.34-3-3-3zm-3 11H8v-5h8v5zm3-7c-.55 0-1-.45-1-1s.45-1 1-1 1 .45 1 1-.45 1-1 1zm-1-9H6v4h12V3z'/%3E%3C/svg%3E");
}
.icon-print {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23374151'%3E%3Cpath d='M19 8H5c-1.66 0-3 1.34-3 3v6h4v4h12v-4h4v-6c0-1.66-1.34-3-3-3zm-3 11H8v-5h8v5zm3-7c-.55 0-1-.45-1-1s.45-1 1-1 1 .45 1 1-.45 1-1 1zm-1-9H6v4h12V3z'/%3E%3C/svg%3E");
}
/* ===== STATUS ICONS ===== */
.icon-check {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%2310b981'%3E%3Cpath d='M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z'/%3E%3C/svg%3E");
}
.icon-warning {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23f59e0b'%3E%3Cpath d='M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z'/%3E%3C/svg%3E");
}
.icon-error {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23ef4444'%3E%3Cpath d='M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z'/%3E%3C/svg%3E");
}
.icon-offline {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%236b7280'%3E%3Cpath d='M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z'/%3E%3C/svg%3E");
}
/* ===== NAVIGATION ICONS ===== */
.icon-home {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23374151'%3E%3Cpath d='M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z'/%3E%3C/svg%3E");
}
.icon-settings {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23374151'%3E%3Cpath d='M19.14,12.94c0.04-0.3,0.06-0.61,0.06-0.94c0-0.32-0.02-0.64-0.07-0.94l2.03-1.58c0.18-0.14,0.23-0.41,0.12-0.61 l-1.92-3.32c-0.12-0.22-0.37-0.29-0.59-0.22l-2.39,0.96c-0.5-0.38-1.03-0.7-1.62-0.94L14.4,2.81c-0.04-0.24-0.24-0.41-0.48-0.41 h-3.84c-0.24,0-0.43,0.17-0.47,0.41L9.25,5.35C8.66,5.59,8.12,5.92,7.63,6.29L5.24,5.33c-0.22-0.08-0.47,0-0.59,0.22L2.74,8.87 C2.62,9.08,2.66,9.34,2.86,9.48l2.03,1.58C4.84,11.36,4.8,11.69,4.8,12s0.02,0.64,0.07,0.94l-2.03,1.58 c-0.18,0.14-0.23,0.41-0.12,0.61l1.92,3.32c0.12,0.22,0.37,0.29,0.59,0.22l2.39-0.96c0.5,0.38,1.03,0.7,1.62,0.94l0.36,2.54 c0.05,0.24,0.24,0.41,0.48,0.41h3.84c0.24,0,0.44-0.17,0.47-0.41l0.36-2.54c0.59-0.24,1.13-0.56,1.62-0.94l2.39,0.96 c0.22,0.08,0.47,0,0.59-0.22l1.92-3.32c0.12-0.22,0.07-0.47-0.12-0.61L19.14,12.94z M12,15.6c-1.98,0-3.6-1.62-3.6-3.6 s1.62-3.6,3.6-3.6s3.6,1.62,3.6,3.6S13.98,15.6,12,15.6z'/%3E%3C/svg%3E");
}
.icon-users {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23374151'%3E%3Cpath d='M16 4c0-1.11.89-2 2-2s2 .89 2 2-.89 2-2 2-2-.89-2-2zm4 18v-6h2.5l-2.54-7.63A3.012 3.012 0 0 0 16.43 6c-.68 0-1.3.27-1.77.72L12 8.5l-2.66-1.78C8.87 6.27 8.25 6 7.57 6c-1.31 0-2.42.83-2.83 2L2.5 16H5v6h2v-6h2v6h2zm-6.5-10.5c.83 0 1.5-.67 1.5-1.5s-.67-1.5-1.5-1.5S12 9.17 12 10s.67 1.5 1.5 1.5z'/%3E%3C/svg%3E");
}
.icon-dashboard {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23374151'%3E%3Cpath d='M3 13h8V3H3v10zm0 8h8v-6H3v6zm10 0h8V11h-8v10zm0-18v6h8V3h-8z'/%3E%3C/svg%3E");
}
/* ===== ACTION ICONS ===== */
.icon-plus {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23374151'%3E%3Cpath d='M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z'/%3E%3C/svg%3E");
}
.icon-edit {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23374151'%3E%3Cpath d='M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34c-.39-.39-1.02-.39-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z'/%3E%3C/svg%3E");
}
.icon-delete {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23ef4444'%3E%3Cpath d='M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z'/%3E%3C/svg%3E");
}
.icon-refresh {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23374151'%3E%3Cpath d='M17.65 6.35C16.2 4.9 14.21 4 12 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08c-.82 2.33-3.04 4-5.65 4-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z'/%3E%3C/svg%3E");
}
/* ===== POWER/CONNECTION ICONS ===== */
.icon-power {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%2310b981'%3E%3Cpath d='M13 3h-2v10h2V3zm4.83 2.17l-1.42 1.42C17.99 7.86 19 9.81 19 12c0 3.87-3.13 7-7 7s-7-3.13-7-7c0-2.19 1.01-4.14 2.58-5.42L6.17 5.17C4.23 6.82 3 9.26 3 12c0 4.97 4.03 9 9 9s9-4.03 9-9c0-2.74-1.23-5.18-3.17-6.83z'/%3E%3C/svg%3E");
}
.icon-wifi {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%2310b981'%3E%3Cpath d='M1 9l2 2c4.97-4.97 13.03-4.97 18 0l2-2C16.93 2.93 7.07 2.93 1 9zm8 8l3 3 3-3c-1.65-1.66-4.34-1.66-6 0zm-4-4l2 2c2.76-2.76 7.24-2.76 10 0l2-2C15.14 9.14 8.87 9.14 5 13z'/%3E%3C/svg%3E");
}
/* ===== DOKUMENT ICONS ===== */
.icon-file {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23374151'%3E%3Cpath d='M14,2H6A2,2 0 0,0 4,4V20A2,2 0 0,0 6,22H18A2,2 0 0,0 20,20V8L14,2M18,20H6V4H13V9H18V20Z'/%3E%3C/svg%3E");
}
.icon-queue {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23374151'%3E%3Cpath d='M4 6H2v14c0 1.1.9 2 2 2h14v-2H4V6zm16-4H8c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm-1 9H9V9h10v2zm-4 4H9v-2h6v2zm4-8H9V5h10v2z'/%3E%3C/svg%3E");
}
/* ===== INFO ICONS ===== */
.icon-info {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%233b82f6'%3E%3Cpath d='M12,2C6.48,2 2,6.48 2,12C2,17.52 6.48,22 12,22C17.52,22 22,17.52 22,12C22,6.48 17.52,2 12,2M13,17H11V11H13M13,9H11V7H13'/%3E%3C/svg%3E");
}
.icon-time {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23374151'%3E%3Cpath d='M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M16.2,16.2L11,13V7H12.5V12.2L17,14.7L16.2,16.2Z'/%3E%3C/svg%3E");
}
/* ===== ARROW ICONS ===== */
.icon-arrow-right {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23374151'%3E%3Cpath d='M8.59 16.59L13.17 12 8.59 7.41 10 6l6 6-6 6-1.41-1.41z'/%3E%3C/svg%3E");
}
.icon-arrow-down {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23374151'%3E%3Cpath d='M7.41 8.59L12 13.17l4.59-4.58L18 10l-6 6-6-6 1.41-1.41z'/%3E%3C/svg%3E");
}
/* ===== HOVER-STATES ===== */
.btn:hover .icon,
.nav-item:hover .icon {
opacity: 0.8;
}
/* ===== DARK MODE ===== */
@media (prefers-color-scheme: dark) {
.icon-printer,
.icon-print,
.icon-home,
.icon-settings,
.icon-users,
.icon-dashboard,
.icon-plus,
.icon-edit,
.icon-refresh,
.icon-file,
.icon-queue,
.icon-time,
.icon-arrow-right,
.icon-arrow-down {
filter: brightness(2);
}
}
/* ===== RESPONSIVE ICON-SIZES ===== */
@media (max-width: 768px) {
.icon { width: 1.25rem; height: 1.25rem; }
.icon-lg { width: 1.75rem; height: 1.75rem; }
.icon-xl { width: 2.25rem; height: 2.25rem; }
}

View File

@ -0,0 +1,288 @@
/**
* MYP Platform - CSS Caching-Optimierungen
* Performance-optimierte Styles für besseres Caching und schnellere Ladezeiten
*/
/* ===== KRITISCHE ABOVE-THE-FOLD STYLES ===== */
/* Diese Styles sollten inline im HTML-Head geladen werden */
.critical-header {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(8px);
position: sticky;
top: 0;
z-index: 1000;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
}
.critical-nav {
display: flex;
align-items: center;
padding: 1rem;
max-width: 1200px;
margin: 0 auto;
}
.critical-logo {
font-weight: 700;
font-size: 1.25rem;
color: #0073ce;
}
.critical-main {
min-height: 100vh;
background: linear-gradient(135deg, #fafbfc 0%, #f5f7f9 100%);
}
/* ===== LAZY LOADING PLACEHOLDER ===== */
.lazy-placeholder {
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
background-size: 200% 100%;
animation: skeleton-loading 1.5s infinite ease-in-out;
border-radius: 4px;
}
@keyframes skeleton-loading {
0% {
background-position: 200% 0;
}
100% {
background-position: -200% 0;
}
}
.dark .lazy-placeholder {
background: linear-gradient(90deg, #374151 25%, #4b5563 50%, #374151 75%);
background-size: 200% 100%;
}
/* ===== PRELOAD HINTS ===== */
.preload-image {
content-visibility: auto;
contain-intrinsic-size: 300px 200px;
}
.preload-component {
content-visibility: auto;
contain-intrinsic-size: 100px;
}
/* ===== CACHE-OPTIMIERTE KOMPONENTEN ===== */
.cache-card {
background: rgba(255, 255, 255, 0.9);
border: 1px solid rgba(229, 231, 235, 0.5);
border-radius: 8px;
padding: 1rem;
margin-bottom: 1rem;
transition: transform 0.2s ease;
contain: layout style paint;
}
.cache-card:hover {
transform: translateY(-1px);
}
.cache-button {
background: #0073ce;
color: white;
border: none;
border-radius: 6px;
padding: 0.5rem 1rem;
font-weight: 600;
transition: background-color 0.2s ease;
contain: layout style;
}
.cache-button:hover {
background: #005a9f;
}
.cache-input {
background: rgba(255, 255, 255, 0.9);
border: 1px solid rgba(229, 231, 235, 0.8);
border-radius: 4px;
padding: 0.5rem;
transition: border-color 0.2s ease;
contain: layout style;
}
.cache-input:focus {
outline: none;
border-color: #0073ce;
box-shadow: 0 0 0 2px rgba(0, 115, 206, 0.1);
}
/* ===== LAYOUT SHIFT PREVENTION ===== */
.prevent-cls {
min-height: 200px; /* Prevent layout shift */
contain: layout;
}
.image-container {
aspect-ratio: 16/9;
overflow: hidden;
border-radius: 8px;
background: #f3f4f6;
}
.image-container img {
width: 100%;
height: 100%;
object-fit: cover;
transition: opacity 0.3s ease;
}
.image-container img[loading="lazy"] {
opacity: 0;
}
.image-container img[loading="lazy"].loaded {
opacity: 1;
}
/* ===== OPTIMIERTE TYPOGRAPHY ===== */
.text-heading {
font-weight: 700;
line-height: 1.2;
color: #111827;
contain: layout style;
}
.text-body {
line-height: 1.6;
color: #374151;
contain: layout style;
}
.text-muted {
color: #6b7280;
contain: layout style;
}
/* ===== DARK MODE CACHE OPTIMIZATIONS ===== */
.dark .cache-card {
background: rgba(30, 41, 59, 0.9);
border-color: rgba(100, 116, 139, 0.3);
}
.dark .cache-input {
background: rgba(30, 41, 59, 0.9);
border-color: rgba(100, 116, 139, 0.5);
color: #e2e8f0;
}
.dark .text-heading {
color: #f8fafc;
}
.dark .text-body {
color: #e2e8f0;
}
.dark .text-muted {
color: #94a3b8;
}
.dark .image-container {
background: #374151;
}
/* ===== RESPONSIVE OPTIMIZATIONS ===== */
@media (max-width: 768px) {
.critical-nav {
padding: 0.75rem;
}
.cache-card {
padding: 0.75rem;
margin-bottom: 0.75rem;
}
.cache-button {
padding: 0.75rem 1.25rem;
font-size: 0.875rem;
}
}
/* ===== PRINT OPTIMIZATIONS ===== */
@media print {
.critical-header,
.cache-button {
display: none;
}
.cache-card {
box-shadow: none;
border: 1px solid #000;
break-inside: avoid;
}
}
/* ===== GPU ACCELERATION HINTS ===== */
.gpu-accelerated {
transform: translateZ(0);
will-change: transform;
}
/* ===== EFFICIENT ANIMATIONS ===== */
.fade-in-optimized {
opacity: 0;
transform: translateY(10px);
transition: opacity 0.3s ease, transform 0.3s ease;
}
.fade-in-optimized.visible {
opacity: 1;
transform: translateY(0);
}
/* ===== CONTAINER QUERIES SUPPORT ===== */
@container (min-width: 300px) {
.cache-card {
display: grid;
grid-template-columns: 1fr auto;
gap: 1rem;
align-items: center;
}
}
/* ===== CONTENT VISIBILITY OPTIMIZATIONS ===== */
.auto-height {
content-visibility: auto;
contain-intrinsic-size: 0 200px;
}
.hidden-content {
content-visibility: hidden;
}
/* ===== REDUCED MOTION PREFERENCES ===== */
@media (prefers-reduced-motion: reduce) {
.fade-in-optimized,
.cache-card,
.cache-button,
.cache-input {
transition: none !important;
}
.lazy-placeholder {
animation: none !important;
background: #f0f0f0 !important;
}
}
/* ===== HIGH CONTRAST MODE ===== */
@media (prefers-contrast: high) {
.cache-card {
border-width: 2px;
border-color: #000;
}
.cache-button {
border: 2px solid #000;
}
.cache-input {
border-width: 2px;
border-color: #000;
}
}

Binary file not shown.

View File

@ -0,0 +1 @@
.critical-header{background:rgba(255,255,255,0.95);backdrop-filter:blur(8px);position:sticky;top:0;z-index:1000;box-shadow:0 2px 8px rgba(0,0,0,0.06)}.critical-nav{display:flex;align-items:center;padding:1rem;max-width:1200px;margin:0 auto}.critical-logo{font-weight:700;font-size:1.25rem;color:#0073ce}.critical-main{min-height:100vh;background:linear-gradient(135deg,#fafbfc 0,#f5f7f9 100%)}.lazy-placeholder{background:linear-gradient(90deg,#f0f0f0 25%,#e0e0e0 50%,#f0f0f0 75%);background-size:200% 100%;animation:skeleton-loading 1.5s infinite ease-in-out;border-radius:4px}@keyframes skeleton-loading{0%{background-position:200% 0}100%{background-position:-200% 0}}.dark .lazy-placeholder{background:linear-gradient(90deg,#374151 25%,#4b5563 50%,#374151 75%);background-size:200% 100%}.preload-image{content-visibility:auto;contain-intrinsic-size:300px 200px}.preload-component{content-visibility:auto;contain-intrinsic-size:100px}.cache-card{background:rgba(255,255,255,0.9);border:1px solid rgba(229,231,235,0.5);border-radius:8px;padding:1rem;margin-bottom:1rem;transition:transform .2s ease;contain:layout style paint}.cache-card:hover{transform:translateY(-1px)}.cache-button{background:#0073ce;color:white;border:0;border-radius:6px;padding:.5rem 1rem;font-weight:600;transition:background-color .2s ease;contain:layout style}.cache-button:hover{background:#005a9f}.cache-input{background:rgba(255,255,255,0.9);border:1px solid rgba(229,231,235,0.8);border-radius:4px;padding:.5rem;transition:border-color .2s ease;contain:layout style}.cache-input:focus{outline:0;border-color:#0073ce;box-shadow:0 0 0 2px rgba(0,115,206,0.1)}.prevent-cls{min-height:200px;contain:layout}.image-container{aspect-ratio:16/9;overflow:hidden;border-radius:8px;background:#f3f4f6}.image-container img{width:100%;height:100%;object-fit:cover;transition:opacity .3s ease}.image-container img[loading="lazy"]{opacity:0}.image-container img[loading="lazy"].loaded{opacity:1}.text-heading{font-weight:700;line-height:1.2;color:#111827;contain:layout style}.text-body{line-height:1.6;color:#374151;contain:layout style}.text-muted{color:#6b7280;contain:layout style}.dark .cache-card{background:rgba(30,41,59,0.9);border-color:rgba(100,116,139,0.3)}.dark .cache-input{background:rgba(30,41,59,0.9);border-color:rgba(100,116,139,0.5);color:#e2e8f0}.dark .text-heading{color:#f8fafc}.dark .text-body{color:#e2e8f0}.dark .text-muted{color:#94a3b8}.dark .image-container{background:#374151}@media(max-width:768px){.critical-nav{padding:.75rem}.cache-card{padding:.75rem;margin-bottom:.75rem}.cache-button{padding:.75rem 1.25rem;font-size:.875rem}}@media print{.critical-header,.cache-button{display:none}.cache-card{box-shadow:none;border:1px solid #000;break-inside:avoid}}.gpu-accelerated{transform:translateZ(0);will-change:transform}.fade-in-optimized{opacity:0;transform:translateY(10px);transition:opacity .3s ease,transform .3s ease}.fade-in-optimized.visible{opacity:1;transform:translateY(0)}@container(min-width:300px){.cache-card{display:grid;grid-template-columns:1fr auto;gap:1rem;align-items:center}}.auto-height{content-visibility:auto;contain-intrinsic-size:0 200px}.hidden-content{content-visibility:hidden}@media(prefers-reduced-motion:reduce){.fade-in-optimized,.cache-card,.cache-button,.cache-input{transition:none !important}.lazy-placeholder{animation:none !important;background:#f0f0f0 !important}}@media(prefers-contrast:high){.cache-card{border-width:2px;border-color:#000}.cache-button{border:2px solid #000}.cache-input{border-width:2px;border-color:#000}}

Binary file not shown.

Binary file not shown.

1
backend/static/css/components.min.css vendored Normal file

File diff suppressed because one or more lines are too long

Binary file not shown.

View File

@ -0,0 +1,31 @@
/* CRITICAL INLINE CSS - Sollte im HTML <head> stehen */
/* Nur die absolut notwendigen Styles für First Paint */
:root{--primary:#0073ce;--bg:#fafbfc;--surface:#fff;--text:#111827;--border:#e5e7eb;--shadow:0 2px 4px rgba(0,0,0,.05)}
*{box-sizing:border-box;margin:0;padding:0;contain:layout style}
body{font-family:system-ui,-apple-system,sans-serif;background:var(--bg);color:var(--text);line-height:1.5;text-rendering:optimizeSpeed;-webkit-font-smoothing:antialiased}
.header{background:var(--surface);border-bottom:1px solid var(--border);padding:1rem;position:sticky;top:0;z-index:1000}
.nav{display:flex;gap:1rem}
.nav-item{padding:.5rem 1rem;border-radius:6px;text-decoration:none;color:#6b7280;transition:background .1s}
.nav-item:hover{background:var(--bg);color:var(--text)}
.nav-item.active{background:var(--primary);color:#fff}
.container{max-width:1200px;margin:0 auto;padding:0 1rem}
.btn{background:var(--primary);color:#fff;border:none;border-radius:6px;padding:.75rem 1.5rem;font-weight:600;cursor:pointer;transition:background .1s}
.btn:hover{background:#005a9f}
.card{background:var(--surface);border:1px solid var(--border);border-radius:8px;padding:1rem;box-shadow:var(--shadow)}
.flex{display:flex}
.grid{display:grid;gap:1rem}
.hidden{display:none}
.w-full{width:100%}
.text-center{text-align:center}
.p-4{padding:1rem}
.mb-4{margin-bottom:1rem}
.status{display:inline-block;padding:.25rem .75rem;border-radius:999px;font-size:.75rem;font-weight:600;text-transform:uppercase}
.status-online{background:#d1fae5;color:#065f46}
.status-offline{background:#fee2e2;color:#991b1b}
.status-printing{background:#dbeafe;color:#1e40af}
.input{background:var(--surface);border:1px solid var(--border);border-radius:6px;padding:.75rem;width:100%}
.input:focus{outline:none;border-color:var(--primary)}
@media (prefers-color-scheme:dark){:root{--bg:#1e293b;--surface:#334155;--text:#f8fafc;--border:#475569;--shadow:0 2px 4px rgba(0,0,0,.3)}}
@media (max-width:768px){.nav{flex-direction:column;gap:.5rem}}
@media (prefers-reduced-motion:reduce){*{transition:none!important}}

Binary file not shown.

View File

@ -0,0 +1 @@
*,*::before,*::after{box-sizing:border-box}body{margin:0;font-family:system-ui,-apple-system,sans-serif;line-height:1.6;color:#111827;background:#fafbfc}.critical-header{position:sticky;top:0;z-index:1000;background:rgba(255,255,255,0.95);backdrop-filter:blur(8px);border-bottom:1px solid #e5e7eb}.critical-nav{display:flex;align-items:center;justify-content:space-between;padding:1rem;max-width:1200px;margin:0 auto}.critical-logo{font-weight:700;font-size:1.25rem;color:#0073ce;text-decoration:none}.critical-main{min-height:100vh;padding:2rem 1rem}.critical-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(300px,1fr));gap:1.5rem;max-width:1200px;margin:0 auto}.critical-card{background:#fff;border:1px solid #e5e7eb;border-radius:8px;padding:1.5rem;box-shadow:0 1px 3px rgba(0,0,0,0.1)}.btn-primary{background:#0073ce;color:#fff;border:0;border-radius:6px;padding:.75rem 1.5rem;font-weight:600;cursor:pointer;text-decoration:none;display:inline-block}.loading{opacity:.6;pointer-events:none}.hidden{display:none}@media(max-width:768px){.critical-nav{padding:.75rem}.critical-main{padding:1rem}.critical-grid{grid-template-columns:1fr}.critical-card{padding:1rem}}@media(prefers-color-scheme:dark){body{background:#0f172a;color:#f8fafc}.critical-header{background:rgba(15,23,42,0.95);border-bottom-color:#334155}.critical-card{background:#1e293b;border-color:#334155;color:#f8fafc}}

Binary file not shown.

View File

@ -1,485 +1,255 @@
/* Enhanced Glassmorphism Effects for MYP Application */
/* Vereinfachte Glassmorphism-Effekte für MYP Application */
/* Base Glass Effects */
/* ===== BASIS GLASS EFFEKTE ===== */
.glass-base {
backdrop-filter: blur(24px) saturate(200%) brightness(115%);
-webkit-backdrop-filter: blur(24px) saturate(200%) brightness(115%);
border: 1px solid rgba(255, 255, 255, 0.25);
box-shadow:
0 25px 50px rgba(0, 0, 0, 0.12),
0 8px 16px rgba(0, 0, 0, 0.08),
inset 0 1px 0 rgba(255, 255, 255, 0.3);
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
background: rgba(255, 255, 255, 0.85);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.3);
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.1);
transition: all 0.2s ease;
}
.glass-strong {
backdrop-filter: blur(28px) saturate(220%) brightness(125%);
-webkit-backdrop-filter: blur(28px) saturate(220%) brightness(125%);
box-shadow:
0 35px 70px rgba(0, 0, 0, 0.15),
0 12px 24px rgba(0, 0, 0, 0.1),
inset 0 1px 0 rgba(255, 255, 255, 0.4);
background: rgba(255, 255, 255, 0.9);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
border: 1px solid rgba(255, 255, 255, 0.4);
box-shadow: 0 12px 24px rgba(0, 0, 0, 0.12);
}
.glass-subtle {
backdrop-filter: blur(20px) saturate(180%) brightness(110%);
-webkit-backdrop-filter: blur(20px) saturate(180%) brightness(110%);
box-shadow:
0 15px 35px rgba(0, 0, 0, 0.08),
0 4px 8px rgba(0, 0, 0, 0.05),
inset 0 1px 0 rgba(255, 255, 255, 0.2);
background: rgba(255, 255, 255, 0.8);
backdrop-filter: blur(8px);
-webkit-backdrop-filter: blur(8px);
border: 1px solid rgba(255, 255, 255, 0.2);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
}
/* Light Mode Glass */
.glass-light {
background: rgba(255, 255, 255, 0.85);
border: 1px solid rgba(255, 255, 255, 0.4);
box-shadow:
0 20px 40px rgba(0, 0, 0, 0.1),
0 8px 16px rgba(0, 115, 206, 0.05),
inset 0 1px 0 rgba(255, 255, 255, 0.6);
}
.glass-light-strong {
background: rgba(255, 255, 255, 0.75);
border: 1px solid rgba(255, 255, 255, 0.5);
box-shadow:
0 25px 50px rgba(0, 0, 0, 0.12),
0 10px 20px rgba(0, 115, 206, 0.08),
inset 0 1px 0 rgba(255, 255, 255, 0.7);
}
/* Light Mode Glass Premium */
.glass-light-premium {
background: linear-gradient(135deg,
rgba(255, 255, 255, 0.9) 0%,
rgba(248, 250, 252, 0.8) 50%,
rgba(255, 255, 255, 0.85) 100%);
border: 1px solid rgba(226, 232, 240, 0.6);
box-shadow:
0 25px 50px rgba(0, 0, 0, 0.08),
0 10px 20px rgba(0, 115, 206, 0.06),
inset 0 2px 0 rgba(255, 255, 255, 0.8),
inset 0 0 20px rgba(255, 255, 255, 0.3);
}
.glass-light-card {
background: linear-gradient(135deg,
rgba(255, 255, 255, 0.95) 0%,
rgba(250, 251, 252, 0.9) 100%);
border: 1px solid rgba(226, 232, 240, 0.4);
box-shadow:
0 20px 40px rgba(0, 0, 0, 0.06),
0 8px 16px rgba(0, 115, 206, 0.04),
inset 0 1px 0 rgba(255, 255, 255, 0.9);
}
/* Dark Mode Glass */
.glass-dark {
/* ===== DARK MODE GLASS ===== */
.dark .glass-base {
background: rgba(15, 23, 42, 0.8);
border: 1px solid rgba(255, 255, 255, 0.1);
box-shadow:
0 25px 50px rgba(0, 0, 0, 0.4),
0 8px 16px rgba(0, 0, 0, 0.2),
inset 0 1px 0 rgba(255, 255, 255, 0.05);
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.3);
}
.glass-dark-strong {
.dark .glass-strong {
background: rgba(30, 41, 59, 0.85);
border: 1px solid rgba(255, 255, 255, 0.15);
box-shadow:
0 35px 70px rgba(0, 0, 0, 0.5),
0 12px 24px rgba(0, 0, 0, 0.3),
inset 0 1px 0 rgba(255, 255, 255, 0.08);
box-shadow: 0 12px 24px rgba(0, 0, 0, 0.4);
}
/* Interactive Glass Elements */
.glass-interactive {
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
cursor: pointer;
position: relative;
overflow: hidden;
.dark .glass-subtle {
background: rgba(15, 23, 42, 0.7);
border: 1px solid rgba(255, 255, 255, 0.08);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
}
.glass-interactive::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg,
transparent,
rgba(255, 255, 255, 0.2),
transparent);
transition: left 0.6s ease;
z-index: 1;
}
.glass-interactive:hover {
transform: translateY(-3px) scale(1.01);
backdrop-filter: blur(32px) saturate(240%) brightness(130%);
-webkit-backdrop-filter: blur(32px) saturate(240%) brightness(130%);
box-shadow:
0 40px 80px rgba(0, 0, 0, 0.15),
0 16px 32px rgba(0, 115, 206, 0.1),
inset 0 1px 0 rgba(255, 255, 255, 0.4);
}
.glass-interactive:hover::before {
left: 100%;
}
.glass-interactive:active {
transform: translateY(-1px) scale(0.99);
transition: transform 0.1s ease;
}
/* Glass Navigation */
.glass-nav {
background: linear-gradient(135deg,
rgba(255, 255, 255, 0.9) 0%,
rgba(248, 250, 252, 0.85) 50%,
rgba(255, 255, 255, 0.9) 100%);
backdrop-filter: blur(24px) saturate(200%) brightness(115%);
-webkit-backdrop-filter: blur(24px) saturate(200%) brightness(115%);
border: 1px solid rgba(226, 232, 240, 0.5);
border-bottom: 1px solid rgba(203, 213, 225, 0.6);
box-shadow:
0 8px 32px rgba(0, 0, 0, 0.08),
0 4px 12px rgba(0, 115, 206, 0.05),
inset 0 1px 0 rgba(255, 255, 255, 0.8);
}
.dark .glass-nav {
background: rgba(15, 23, 42, 0.85);
border-color: rgba(51, 65, 85, 0.6);
border-bottom-color: rgba(71, 85, 105, 0.7);
box-shadow:
0 8px 32px rgba(0, 0, 0, 0.3),
inset 0 1px 0 rgba(255, 255, 255, 0.1);
}
/* Glass Cards */
/* ===== GLASS KARTEN ===== */
.glass-card {
background: linear-gradient(135deg,
rgba(255, 255, 255, 0.95) 0%,
rgba(250, 251, 252, 0.9) 100%);
backdrop-filter: blur(20px) saturate(180%) brightness(110%);
-webkit-backdrop-filter: blur(20px) saturate(180%) brightness(110%);
border: 1px solid rgba(229, 231, 235, 0.6);
border-radius: 16px;
box-shadow:
0 20px 40px rgba(0, 0, 0, 0.08),
0 8px 16px rgba(0, 115, 206, 0.04),
inset 0 1px 0 rgba(255, 255, 255, 0.9);
background: rgba(255, 255, 255, 0.9);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
border: 1px solid rgba(229, 231, 235, 0.5);
border-radius: 12px;
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.08);
padding: 1.5rem;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
position: relative;
overflow: hidden;
}
.glass-card::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 1px;
background: linear-gradient(90deg,
transparent 0%,
rgba(226, 232, 240, 0.8) 50%,
transparent 100%);
transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.glass-card:hover {
transform: translateY(-2px);
box-shadow:
0 25px 50px rgba(0, 0, 0, 0.12),
0 12px 24px rgba(0, 115, 206, 0.08),
inset 0 1px 0 rgba(255, 255, 255, 0.95);
transform: translateY(-1px);
box-shadow: 0 12px 20px rgba(0, 0, 0, 0.12);
}
.dark .glass-card {
background: rgba(30, 41, 59, 0.85);
border-color: rgba(100, 116, 139, 0.4);
box-shadow:
0 20px 40px rgba(0, 0, 0, 0.3),
inset 0 1px 0 rgba(255, 255, 255, 0.1);
border: 1px solid rgba(100, 116, 139, 0.3);
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.25);
}
.dark .glass-card:hover {
box-shadow:
0 25px 50px rgba(0, 0, 0, 0.4),
inset 0 1px 0 rgba(255, 255, 255, 0.15);
box-shadow: 0 12px 20px rgba(0, 0, 0, 0.35);
}
/* Glass Buttons */
/* ===== GLASS NAVIGATION ===== */
.glass-nav {
background: rgba(255, 255, 255, 0.9);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
border: 1px solid rgba(226, 232, 240, 0.4);
border-bottom: 1px solid rgba(203, 213, 225, 0.5);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.06);
}
.dark .glass-nav {
background: rgba(15, 23, 42, 0.85);
border: 1px solid rgba(51, 65, 85, 0.5);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
}
/* ===== GLASS BUTTONS ===== */
.glass-btn {
background: linear-gradient(135deg,
rgba(255, 255, 255, 0.9) 0%,
rgba(248, 250, 252, 0.8) 100%);
backdrop-filter: blur(16px) saturate(180%);
-webkit-backdrop-filter: blur(16px) saturate(180%);
border: 1px solid rgba(226, 232, 240, 0.6);
border-radius: 12px;
background: rgba(255, 255, 255, 0.8);
backdrop-filter: blur(8px);
-webkit-backdrop-filter: blur(8px);
border: 1px solid rgba(226, 232, 240, 0.5);
border-radius: 8px;
padding: 0.75rem 1.5rem;
color: #0f172a;
font-weight: 600;
text-shadow: 0 1px 2px rgba(255, 255, 255, 0.8);
box-shadow:
0 4px 12px rgba(0, 0, 0, 0.08),
0 2px 4px rgba(0, 115, 206, 0.05),
inset 0 1px 0 rgba(255, 255, 255, 0.8);
transition: all 0.2s ease;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
transition: transform 0.2s ease, background 0.2s ease;
}
.glass-btn:hover {
transform: translateY(-1px);
background: linear-gradient(135deg,
rgba(255, 255, 255, 0.95) 0%,
rgba(248, 250, 252, 0.85) 100%);
box-shadow:
0 8px 20px rgba(0, 0, 0, 0.12),
0 4px 8px rgba(0, 115, 206, 0.08),
inset 0 1px 0 rgba(255, 255, 255, 0.9);
}
.glass-btn:active {
transform: translateY(0);
box-shadow:
0 2px 8px rgba(0, 0, 0, 0.1),
inset 0 1px 0 rgba(255, 255, 255, 0.7);
background: rgba(255, 255, 255, 0.9);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
.glass-btn-primary {
background: linear-gradient(135deg,
rgba(0, 115, 206, 0.9) 0%,
rgba(0, 90, 159, 0.85) 100%);
background: rgba(0, 115, 206, 0.9);
color: white;
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);
box-shadow:
0 4px 12px rgba(0, 115, 206, 0.3),
0 2px 4px rgba(0, 115, 206, 0.2),
inset 0 1px 0 rgba(255, 255, 255, 0.2);
box-shadow: 0 2px 8px rgba(0, 115, 206, 0.2);
}
.glass-btn-primary:hover {
background: linear-gradient(135deg,
rgba(0, 115, 206, 0.95) 0%,
rgba(0, 90, 159, 0.9) 100%);
box-shadow:
0 8px 20px rgba(0, 115, 206, 0.4),
0 4px 8px rgba(0, 115, 206, 0.3),
inset 0 1px 0 rgba(255, 255, 255, 0.3);
background: rgba(0, 115, 206, 0.95);
box-shadow: 0 4px 12px rgba(0, 115, 206, 0.3);
}
.dark .glass-btn {
background: rgba(30, 41, 59, 0.8);
color: #e2e8f0;
border-color: rgba(100, 116, 139, 0.6);
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.5);
box-shadow:
0 4px 12px rgba(0, 0, 0, 0.2),
inset 0 1px 0 rgba(255, 255, 255, 0.1);
border: 1px solid rgba(100, 116, 139, 0.5);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
}
/* Glass Modals */
.dark .glass-btn:hover {
background: rgba(30, 41, 59, 0.9);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.25);
}
/* ===== GLASS MODALS ===== */
.glass-modal {
background: linear-gradient(135deg,
rgba(255, 255, 255, 0.98) 0%,
rgba(248, 250, 252, 0.95) 50%,
rgba(255, 255, 255, 0.98) 100%);
backdrop-filter: blur(32px) saturate(220%) brightness(120%);
-webkit-backdrop-filter: blur(32px) saturate(220%) brightness(120%);
border: 1px solid rgba(226, 232, 240, 0.7);
border-radius: 20px;
box-shadow:
0 50px 100px rgba(0, 0, 0, 0.15),
0 20px 40px rgba(0, 115, 206, 0.08),
inset 0 2px 0 rgba(255, 255, 255, 0.9),
inset 0 0 40px rgba(255, 255, 255, 0.2);
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(16px);
-webkit-backdrop-filter: blur(16px);
border: 1px solid rgba(226, 232, 240, 0.6);
border-radius: 16px;
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.12);
}
.dark .glass-modal {
background: rgba(15, 23, 42, 0.95);
border-color: rgba(51, 65, 85, 0.7);
box-shadow:
0 50px 100px rgba(0, 0, 0, 0.5),
inset 0 2px 0 rgba(255, 255, 255, 0.1);
border: 1px solid rgba(51, 65, 85, 0.6);
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.4);
}
/* Glass Form Elements */
/* ===== GLASS FORM ELEMENTE ===== */
.glass-input {
background: rgba(255, 255, 255, 0.8);
backdrop-filter: blur(16px);
-webkit-backdrop-filter: blur(16px);
border: 1px solid rgba(226, 232, 240, 0.6);
border-radius: 8px;
backdrop-filter: blur(8px);
-webkit-backdrop-filter: blur(8px);
border: 1px solid rgba(226, 232, 240, 0.5);
border-radius: 6px;
padding: 0.75rem 1rem;
color: #0f172a;
box-shadow:
0 2px 8px rgba(0, 0, 0, 0.06),
inset 0 1px 0 rgba(255, 255, 255, 0.8);
transition: all 0.2s ease;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.04);
transition: border-color 0.2s ease, box-shadow 0.2s ease;
}
.glass-input:focus {
outline: none;
border-color: rgba(0, 115, 206, 0.6);
box-shadow:
0 4px 12px rgba(0, 115, 206, 0.15),
0 0 0 3px rgba(0, 115, 206, 0.1),
inset 0 1px 0 rgba(255, 255, 255, 0.9);
border-color: rgba(0, 115, 206, 0.5);
box-shadow: 0 2px 8px rgba(0, 115, 206, 0.1);
}
.dark .glass-input {
background: rgba(30, 41, 59, 0.8);
border-color: rgba(100, 116, 139, 0.6);
border: 1px solid rgba(100, 116, 139, 0.5);
color: #e2e8f0;
box-shadow:
0 2px 8px rgba(0, 0, 0, 0.2),
inset 0 1px 0 rgba(255, 255, 255, 0.1);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.15);
}
/* Glass Dropdown */
.dark .glass-input:focus {
border-color: rgba(59, 130, 246, 0.5);
box-shadow: 0 2px 8px rgba(59, 130, 246, 0.15);
}
/* ===== GLASS DROPDOWN ===== */
.glass-dropdown {
background: rgba(255, 255, 255, 0.8);
backdrop-filter: blur(24px) saturate(200%) brightness(120%);
-webkit-backdrop-filter: blur(24px) saturate(200%) brightness(120%);
background: rgba(255, 255, 255, 0.9);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
border: 1px solid rgba(255, 255, 255, 0.3);
border-radius: 12px;
box-shadow: 0 25px 50px rgba(0, 0, 0, 0.25), 0 0 0 1px rgba(255, 255, 255, 0.1);
border-radius: 8px;
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.15);
}
.dark .glass-dropdown {
background: rgba(0, 0, 0, 0.8);
border: 1px solid rgba(255, 255, 255, 0.15);
box-shadow: 0 25px 50px rgba(0, 0, 0, 0.4), 0 0 0 1px rgba(255, 255, 255, 0.08);
background: rgba(15, 23, 42, 0.9);
border: 1px solid rgba(255, 255, 255, 0.1);
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.3);
}
/* Animation for glass elements */
@keyframes glassFloat {
0%, 100% {
transform: translateY(0px);
}
50% {
transform: translateY(-2px);
}
}
.glass-float {
animation: glassFloat 3s ease-in-out infinite;
}
/* Glass overlay for backgrounds */
/* ===== UTILITY CLASSES ===== */
.glass-overlay {
background: linear-gradient(135deg, rgba(255, 255, 255, 0.1) 0%, rgba(255, 255, 255, 0.05) 100%);
backdrop-filter: blur(40px) saturate(200%);
-webkit-backdrop-filter: blur(40px) saturate(200%);
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
}
.dark .glass-overlay {
background: linear-gradient(135deg, rgba(0, 0, 0, 0.3) 0%, rgba(0, 0, 0, 0.1) 100%);
background: rgba(0, 0, 0, 0.2);
}
/* Responsive glass effects */
/* ===== RESPONSIVE ANPASSUNGEN ===== */
@media (max-width: 768px) {
.glass-card {
padding: 1rem;
border-radius: 12px;
border-radius: 8px;
}
.glass-modal {
border-radius: 16px;
border-radius: 12px;
}
.glass-base,
.glass-strong,
.glass-subtle {
backdrop-filter: blur(6px);
-webkit-backdrop-filter: blur(6px);
}
}
/* High contrast mode adjustments */
/* ===== REDUZIERTE BEWEGUNG ===== */
@media (prefers-reduced-motion: reduce) {
.glass-card,
.glass-btn,
.glass-input {
transition: none !important;
}
}
/* ===== HOHER KONTRAST MODUS ===== */
@media (prefers-contrast: high) {
.glass-base,
.glass-strong,
.glass-card {
border-width: 2px;
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
backdrop-filter: blur(4px);
-webkit-backdrop-filter: blur(4px);
}
}
/* Reduced motion support */
@media (prefers-reduced-motion: reduce) {
.glass-interactive,
.glass-btn,
.glass-card {
transition: none !important;
}
.glass-interactive::before {
display: none;
}
}
/* Glass Loading States */
.glass-loading {
position: relative;
overflow: hidden;
}
.glass-loading::after {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg,
transparent,
rgba(255, 255, 255, 0.3),
transparent);
animation: glass-shimmer 2s infinite;
}
.dark .glass-loading::after {
background: linear-gradient(90deg,
transparent,
rgba(255, 255, 255, 0.1),
transparent);
}
@keyframes glass-shimmer {
0% { left: -100%; }
100% { left: 100%; }
}
/* Premium Glow Effects for Light Mode */
.glass-glow {
position: relative;
}
.glass-glow::after {
content: '';
position: absolute;
top: -2px;
left: -2px;
right: -2px;
bottom: -2px;
background: linear-gradient(45deg,
rgba(0, 115, 206, 0.1),
rgba(0, 90, 159, 0.05),
rgba(0, 115, 206, 0.1));
border-radius: inherit;
z-index: -1;
opacity: 0;
transition: opacity 0.3s ease;
}
.glass-glow:hover::after {
opacity: 1;
}
.dark .glass-glow::after {
background: linear-gradient(45deg,
rgba(59, 130, 246, 0.2),
rgba(29, 78, 216, 0.1),
rgba(59, 130, 246, 0.2));
/* ===== PERFORMANCE OPTIMIERUNGEN ===== */
.glass-base,
.glass-strong,
.glass-subtle,
.glass-card,
.glass-btn,
.glass-modal {
will-change: transform;
}

Binary file not shown.

View File

@ -0,0 +1 @@
.glass-base{background:rgba(255,255,255,0.85);backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);border:1px solid rgba(255,255,255,0.3);box-shadow:0 8px 20px rgba(0,0,0,0.1);transition:all .2s ease}.glass-strong{background:rgba(255,255,255,0.9);backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);border:1px solid rgba(255,255,255,0.4);box-shadow:0 12px 24px rgba(0,0,0,0.12)}.glass-subtle{background:rgba(255,255,255,0.8);backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);border:1px solid rgba(255,255,255,0.2);box-shadow:0 4px 12px rgba(0,0,0,0.08)}.dark .glass-base{background:rgba(15,23,42,0.8);border:1px solid rgba(255,255,255,0.1);box-shadow:0 8px 20px rgba(0,0,0,0.3)}.dark .glass-strong{background:rgba(30,41,59,0.85);border:1px solid rgba(255,255,255,0.15);box-shadow:0 12px 24px rgba(0,0,0,0.4)}.dark .glass-subtle{background:rgba(15,23,42,0.7);border:1px solid rgba(255,255,255,0.08);box-shadow:0 4px 12px rgba(0,0,0,0.2)}.glass-card{background:rgba(255,255,255,0.9);backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);border:1px solid rgba(229,231,235,0.5);border-radius:12px;box-shadow:0 8px 16px rgba(0,0,0,0.08);padding:1.5rem;transition:transform .2s ease,box-shadow .2s ease}.glass-card:hover{transform:translateY(-1px);box-shadow:0 12px 20px rgba(0,0,0,0.12)}.dark .glass-card{background:rgba(30,41,59,0.85);border:1px solid rgba(100,116,139,0.3);box-shadow:0 8px 16px rgba(0,0,0,0.25)}.dark .glass-card:hover{box-shadow:0 12px 20px rgba(0,0,0,0.35)}.glass-nav{background:rgba(255,255,255,0.9);backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);border:1px solid rgba(226,232,240,0.4);border-bottom:1px solid rgba(203,213,225,0.5);box-shadow:0 4px 12px rgba(0,0,0,0.06)}.dark .glass-nav{background:rgba(15,23,42,0.85);border:1px solid rgba(51,65,85,0.5);box-shadow:0 4px 12px rgba(0,0,0,0.2)}.glass-btn{background:rgba(255,255,255,0.8);backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);border:1px solid rgba(226,232,240,0.5);border-radius:8px;padding:.75rem 1.5rem;color:#0f172a;font-weight:600;box-shadow:0 2px 8px rgba(0,0,0,0.06);transition:transform .2s ease,background .2s ease}.glass-btn:hover{transform:translateY(-1px);background:rgba(255,255,255,0.9);box-shadow:0 4px 12px rgba(0,0,0,0.1)}.glass-btn-primary{background:rgba(0,115,206,0.9);color:white;box-shadow:0 2px 8px rgba(0,115,206,0.2)}.glass-btn-primary:hover{background:rgba(0,115,206,0.95);box-shadow:0 4px 12px rgba(0,115,206,0.3)}.dark .glass-btn{background:rgba(30,41,59,0.8);color:#e2e8f0;border:1px solid rgba(100,116,139,0.5);box-shadow:0 2px 8px rgba(0,0,0,0.15)}.dark .glass-btn:hover{background:rgba(30,41,59,0.9);box-shadow:0 4px 12px rgba(0,0,0,0.25)}.glass-modal{background:rgba(255,255,255,0.95);backdrop-filter:blur(16px);-webkit-backdrop-filter:blur(16px);border:1px solid rgba(226,232,240,0.6);border-radius:16px;box-shadow:0 20px 40px rgba(0,0,0,0.12)}.dark .glass-modal{background:rgba(15,23,42,0.95);border:1px solid rgba(51,65,85,0.6);box-shadow:0 20px 40px rgba(0,0,0,0.4)}.glass-input{background:rgba(255,255,255,0.8);backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);border:1px solid rgba(226,232,240,0.5);border-radius:6px;padding:.75rem 1rem;color:#0f172a;box-shadow:0 2px 4px rgba(0,0,0,0.04);transition:border-color .2s ease,box-shadow .2s ease}.glass-input:focus{outline:0;border-color:rgba(0,115,206,0.5);box-shadow:0 2px 8px rgba(0,115,206,0.1)}.dark .glass-input{background:rgba(30,41,59,0.8);border:1px solid rgba(100,116,139,0.5);color:#e2e8f0;box-shadow:0 2px 4px rgba(0,0,0,0.15)}.dark .glass-input:focus{border-color:rgba(59,130,246,0.5);box-shadow:0 2px 8px rgba(59,130,246,0.15)}.glass-dropdown{background:rgba(255,255,255,0.9);backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);border:1px solid rgba(255,255,255,0.3);border-radius:8px;box-shadow:0 8px 20px rgba(0,0,0,0.15)}.dark .glass-dropdown{background:rgba(15,23,42,0.9);border:1px solid rgba(255,255,255,0.1);box-shadow:0 8px 20px rgba(0,0,0,0.3)}.glass-overlay{background:rgba(255,255,255,0.1);backdrop-filter:blur(20px);-webkit-backdrop-filter:blur(20px)}.dark .glass-overlay{background:rgba(0,0,0,0.2)}@media(max-width:768px){.glass-card{padding:1rem;border-radius:8px}.glass-modal{border-radius:12px}.glass-base,.glass-strong,.glass-subtle{backdrop-filter:blur(6px);-webkit-backdrop-filter:blur(6px)}}@media(prefers-reduced-motion:reduce){.glass-card,.glass-btn,.glass-input{transition:none !important}}@media(prefers-contrast:high){.glass-base,.glass-strong,.glass-card{border-width:2px;backdrop-filter:blur(4px);-webkit-backdrop-filter:blur(4px)}}.glass-base,.glass-strong,.glass-subtle,.glass-card,.glass-btn,.glass-modal{will-change:transform}

Binary file not shown.

View File

@ -0,0 +1,148 @@
/**
* Minimales Icon-Set für Kiosk-Modus
* SVG-basierte Icons als CSS-Pseudo-Elemente für beste Performance
* Ersetzt FontAwesome für deutlich kleinere Bundle-Größe
*/
/* Icon-Basis-Klasse */
.icon {
display: inline-block;
width: 1rem;
height: 1rem;
background-size: contain;
background-repeat: no-repeat;
background-position: center;
vertical-align: middle;
}
.icon-lg { width: 1.5rem; height: 1.5rem; }
.icon-xl { width: 2rem; height: 2rem; }
/* ===== DRUCKER ICONS ===== */
.icon-printer {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23374151'%3E%3Cpath d='M19 8H5c-1.66 0-3 1.34-3 3v6h4v4h12v-4h4v-6c0-1.66-1.34-3-3-3zm-3 11H8v-5h8v5zm3-7c-.55 0-1-.45-1-1s.45-1 1-1 1 .45 1 1-.45 1-1 1zm-1-9H6v4h12V3z'/%3E%3C/svg%3E");
}
.icon-print {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23374151'%3E%3Cpath d='M19 8H5c-1.66 0-3 1.34-3 3v6h4v4h12v-4h4v-6c0-1.66-1.34-3-3-3zm-3 11H8v-5h8v5zm3-7c-.55 0-1-.45-1-1s.45-1 1-1 1 .45 1 1-.45 1-1 1zm-1-9H6v4h12V3z'/%3E%3C/svg%3E");
}
/* ===== STATUS ICONS ===== */
.icon-check {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%2310b981'%3E%3Cpath d='M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z'/%3E%3C/svg%3E");
}
.icon-warning {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23f59e0b'%3E%3Cpath d='M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z'/%3E%3C/svg%3E");
}
.icon-error {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23ef4444'%3E%3Cpath d='M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z'/%3E%3C/svg%3E");
}
.icon-offline {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%236b7280'%3E%3Cpath d='M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z'/%3E%3C/svg%3E");
}
/* ===== NAVIGATION ICONS ===== */
.icon-home {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23374151'%3E%3Cpath d='M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z'/%3E%3C/svg%3E");
}
.icon-settings {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23374151'%3E%3Cpath d='M19.14,12.94c0.04-0.3,0.06-0.61,0.06-0.94c0-0.32-0.02-0.64-0.07-0.94l2.03-1.58c0.18-0.14,0.23-0.41,0.12-0.61 l-1.92-3.32c-0.12-0.22-0.37-0.29-0.59-0.22l-2.39,0.96c-0.5-0.38-1.03-0.7-1.62-0.94L14.4,2.81c-0.04-0.24-0.24-0.41-0.48-0.41 h-3.84c-0.24,0-0.43,0.17-0.47,0.41L9.25,5.35C8.66,5.59,8.12,5.92,7.63,6.29L5.24,5.33c-0.22-0.08-0.47,0-0.59,0.22L2.74,8.87 C2.62,9.08,2.66,9.34,2.86,9.48l2.03,1.58C4.84,11.36,4.8,11.69,4.8,12s0.02,0.64,0.07,0.94l-2.03,1.58 c-0.18,0.14-0.23,0.41-0.12,0.61l1.92,3.32c0.12,0.22,0.37,0.29,0.59,0.22l2.39-0.96c0.5,0.38,1.03,0.7,1.62,0.94l0.36,2.54 c0.05,0.24,0.24,0.41,0.48,0.41h3.84c0.24,0,0.44-0.17,0.47-0.41l0.36-2.54c0.59-0.24,1.13-0.56,1.62-0.94l2.39,0.96 c0.22,0.08,0.47,0,0.59-0.22l1.92-3.32c0.12-0.22,0.07-0.47-0.12-0.61L19.14,12.94z M12,15.6c-1.98,0-3.6-1.62-3.6-3.6 s1.62-3.6,3.6-3.6s3.6,1.62,3.6,3.6S13.98,15.6,12,15.6z'/%3E%3C/svg%3E");
}
.icon-users {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23374151'%3E%3Cpath d='M16 4c0-1.11.89-2 2-2s2 .89 2 2-.89 2-2 2-2-.89-2-2zm4 18v-6h2.5l-2.54-7.63A3.012 3.012 0 0 0 16.43 6c-.68 0-1.3.27-1.77.72L12 8.5l-2.66-1.78C8.87 6.27 8.25 6 7.57 6c-1.31 0-2.42.83-2.83 2L2.5 16H5v6h2v-6h2v6h2zm-6.5-10.5c.83 0 1.5-.67 1.5-1.5s-.67-1.5-1.5-1.5S12 9.17 12 10s.67 1.5 1.5 1.5z'/%3E%3C/svg%3E");
}
.icon-dashboard {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23374151'%3E%3Cpath d='M3 13h8V3H3v10zm0 8h8v-6H3v6zm10 0h8V11h-8v10zm0-18v6h8V3h-8z'/%3E%3C/svg%3E");
}
/* ===== ACTION ICONS ===== */
.icon-plus {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23374151'%3E%3Cpath d='M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z'/%3E%3C/svg%3E");
}
.icon-edit {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23374151'%3E%3Cpath d='M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34c-.39-.39-1.02-.39-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z'/%3E%3C/svg%3E");
}
.icon-delete {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23ef4444'%3E%3Cpath d='M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z'/%3E%3C/svg%3E");
}
.icon-refresh {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23374151'%3E%3Cpath d='M17.65 6.35C16.2 4.9 14.21 4 12 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08c-.82 2.33-3.04 4-5.65 4-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z'/%3E%3C/svg%3E");
}
/* ===== POWER/CONNECTION ICONS ===== */
.icon-power {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%2310b981'%3E%3Cpath d='M13 3h-2v10h2V3zm4.83 2.17l-1.42 1.42C17.99 7.86 19 9.81 19 12c0 3.87-3.13 7-7 7s-7-3.13-7-7c0-2.19 1.01-4.14 2.58-5.42L6.17 5.17C4.23 6.82 3 9.26 3 12c0 4.97 4.03 9 9 9s9-4.03 9-9c0-2.74-1.23-5.18-3.17-6.83z'/%3E%3C/svg%3E");
}
.icon-wifi {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%2310b981'%3E%3Cpath d='M1 9l2 2c4.97-4.97 13.03-4.97 18 0l2-2C16.93 2.93 7.07 2.93 1 9zm8 8l3 3 3-3c-1.65-1.66-4.34-1.66-6 0zm-4-4l2 2c2.76-2.76 7.24-2.76 10 0l2-2C15.14 9.14 8.87 9.14 5 13z'/%3E%3C/svg%3E");
}
/* ===== DOKUMENT ICONS ===== */
.icon-file {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23374151'%3E%3Cpath d='M14,2H6A2,2 0 0,0 4,4V20A2,2 0 0,0 6,22H18A2,2 0 0,0 20,20V8L14,2M18,20H6V4H13V9H18V20Z'/%3E%3C/svg%3E");
}
.icon-queue {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23374151'%3E%3Cpath d='M4 6H2v14c0 1.1.9 2 2 2h14v-2H4V6zm16-4H8c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm-1 9H9V9h10v2zm-4 4H9v-2h6v2zm4-8H9V5h10v2z'/%3E%3C/svg%3E");
}
/* ===== INFO ICONS ===== */
.icon-info {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%233b82f6'%3E%3Cpath d='M12,2C6.48,2 2,6.48 2,12C2,17.52 6.48,22 12,22C17.52,22 22,17.52 22,12C22,6.48 17.52,2 12,2M13,17H11V11H13M13,9H11V7H13'/%3E%3C/svg%3E");
}
.icon-time {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23374151'%3E%3Cpath d='M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M16.2,16.2L11,13V7H12.5V12.2L17,14.7L16.2,16.2Z'/%3E%3C/svg%3E");
}
/* ===== ARROW ICONS ===== */
.icon-arrow-right {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23374151'%3E%3Cpath d='M8.59 16.59L13.17 12 8.59 7.41 10 6l6 6-6 6-1.41-1.41z'/%3E%3C/svg%3E");
}
.icon-arrow-down {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23374151'%3E%3Cpath d='M7.41 8.59L12 13.17l4.59-4.58L18 10l-6 6-6-6 1.41-1.41z'/%3E%3C/svg%3E");
}
/* ===== HOVER-STATES ===== */
.btn:hover .icon,
.nav-item:hover .icon {
opacity: 0.8;
}
/* ===== DARK MODE ===== */
@media (prefers-color-scheme: dark) {
.icon-printer,
.icon-print,
.icon-home,
.icon-settings,
.icon-users,
.icon-dashboard,
.icon-plus,
.icon-edit,
.icon-refresh,
.icon-file,
.icon-queue,
.icon-time,
.icon-arrow-right,
.icon-arrow-down {
filter: brightness(2);
}
}
/* ===== RESPONSIVE ICON-SIZES ===== */
@media (max-width: 768px) {
.icon { width: 1.25rem; height: 1.25rem; }
.icon-lg { width: 1.75rem; height: 1.75rem; }
.icon-xl { width: 2.25rem; height: 2.25rem; }
}

Binary file not shown.

1
backend/static/css/input.min.css vendored Normal file

File diff suppressed because one or more lines are too long

Binary file not shown.

View File

@ -0,0 +1,346 @@
/**
* MYP Platform - Kiosk-Optimierte CSS
* Maximale Performance für Offline-Kiosk-Umgebung
* Keine Touch-Events, minimale Animationen, optimierte Rendering-Performance
*/
/* ===== CRITICAL INLINE STYLES (sollten im HTML-Head stehen) ===== */
:root {
/* Reduzierte Farbvariablen für bessere Performance */
--primary: #0073ce;
--primary-dark: #005a9f;
--bg: #fafbfc;
--surface: #ffffff;
--text: #111827;
--text-muted: #6b7280;
--border: #e5e7eb;
--shadow: 0 2px 4px rgba(0,0,0,0.05);
}
/* CSS Containment für bessere Performance */
* {
contain: layout style;
}
/* Basis-Reset ohne aufwendige Normalisierung */
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: system-ui, -apple-system, sans-serif;
background: var(--bg);
color: var(--text);
line-height: 1.5;
contain: layout style paint;
/* Optimiert für Kiosk-Rendering */
text-rendering: optimizeSpeed;
-webkit-font-smoothing: antialiased;
}
/* ===== LAYOUT OPTIMIERUNGEN ===== */
.header {
background: var(--surface);
border-bottom: 1px solid var(--border);
padding: 1rem;
contain: layout style;
}
.nav {
display: flex;
gap: 1rem;
contain: layout;
}
.nav-item {
padding: 0.5rem 1rem;
border-radius: 6px;
text-decoration: none;
color: var(--text-muted);
transition: background-color 0.1s ease; /* Minimale Transition */
}
.nav-item:hover {
background: var(--bg);
color: var(--text);
}
.nav-item.active {
background: var(--primary);
color: white;
}
/* ===== OPTIMIERTE KARTEN ===== */
.card {
background: var(--surface);
border: 1px solid var(--border);
border-radius: 8px;
padding: 1rem;
box-shadow: var(--shadow);
contain: layout style paint;
}
.card:hover {
transform: translateY(-1px); /* Minimaler Hover-Effekt */
}
/* ===== BUTTONS OHNE KOMPLEXE ANIMATIONEN ===== */
.btn {
background: var(--primary);
color: white;
border: none;
border-radius: 6px;
padding: 0.75rem 1.5rem;
font-weight: 600;
cursor: pointer;
transition: background-color 0.1s ease;
contain: layout style;
}
.btn:hover {
background: var(--primary-dark);
}
.btn-secondary {
background: var(--surface);
color: var(--text);
border: 1px solid var(--border);
}
.btn-secondary:hover {
background: var(--bg);
}
/* ===== INPUTS OPTIMIERT ===== */
.input {
background: var(--surface);
border: 1px solid var(--border);
border-radius: 6px;
padding: 0.75rem;
width: 100%;
transition: border-color 0.1s ease;
contain: layout style;
}
.input:focus {
outline: none;
border-color: var(--primary);
}
/* ===== TABELLEN OHNE KOMPLEXE EFFEKTE ===== */
.table {
width: 100%;
border-collapse: collapse;
background: var(--surface);
border-radius: 8px;
overflow: hidden;
contain: layout;
}
.table th {
background: var(--bg);
padding: 1rem;
text-align: left;
font-weight: 600;
border-bottom: 1px solid var(--border);
}
.table td {
padding: 1rem;
border-bottom: 1px solid var(--border);
}
.table tr:hover {
background: var(--bg);
}
/* ===== STATUS BADGES VEREINFACHT ===== */
.status {
display: inline-block;
padding: 0.25rem 0.75rem;
border-radius: 999px;
font-size: 0.75rem;
font-weight: 600;
text-transform: uppercase;
}
.status-online { background: #d1fae5; color: #065f46; }
.status-offline { background: #fee2e2; color: #991b1b; }
.status-printing { background: #dbeafe; color: #1e40af; }
/* ===== GRID LAYOUTS OPTIMIERT ===== */
.grid {
display: grid;
gap: 1rem;
contain: layout;
}
.grid-2 { grid-template-columns: repeat(2, 1fr); }
.grid-3 { grid-template-columns: repeat(3, 1fr); }
.grid-4 { grid-template-columns: repeat(4, 1fr); }
/* ===== UTILITIES MINIMAL ===== */
.flex { display: flex; contain: layout; }
.flex-col { flex-direction: column; }
.items-center { align-items: center; }
.justify-between { justify-content: space-between; }
.gap-1 { gap: 0.25rem; }
.gap-2 { gap: 0.5rem; }
.gap-4 { gap: 1rem; }
.p-1 { padding: 0.25rem; }
.p-2 { padding: 0.5rem; }
.p-4 { padding: 1rem; }
.p-6 { padding: 1.5rem; }
.m-1 { margin: 0.25rem; }
.m-2 { margin: 0.5rem; }
.m-4 { margin: 1rem; }
.mb-2 { margin-bottom: 0.5rem; }
.mb-4 { margin-bottom: 1rem; }
.mb-6 { margin-bottom: 1.5rem; }
.text-sm { font-size: 0.875rem; }
.text-lg { font-size: 1.125rem; }
.text-xl { font-size: 1.25rem; }
.text-2xl { font-size: 1.5rem; }
.font-semibold { font-weight: 600; }
.font-bold { font-weight: 700; }
.text-center { text-align: center; }
.text-right { text-align: right; }
.w-full { width: 100%; }
.h-full { height: 100%; }
.rounded { border-radius: 6px; }
.rounded-lg { border-radius: 8px; }
.border { border: 1px solid var(--border); }
.border-t { border-top: 1px solid var(--border); }
.border-b { border-bottom: 1px solid var(--border); }
.bg-white { background: var(--surface); }
.bg-gray-50 { background: var(--bg); }
.text-gray-600 { color: var(--text-muted); }
.text-blue-600 { color: var(--primary); }
/* ===== DARK MODE MINIMAL ===== */
@media (prefers-color-scheme: dark) {
:root {
--bg: #1e293b;
--surface: #334155;
--text: #f8fafc;
--text-muted: #94a3b8;
--border: #475569;
--shadow: 0 2px 4px rgba(0,0,0,0.3);
}
}
/* ===== RESPONSIVE FÜR KIOSK-DISPLAYS ===== */
@media (max-width: 1024px) {
.grid-4 { grid-template-columns: repeat(2, 1fr); }
.grid-3 { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 768px) {
.grid-4,
.grid-3,
.grid-2 { grid-template-columns: 1fr; }
.nav {
flex-direction: column;
gap: 0.5rem;
}
}
/* ===== PERFORMANCE OPTIMIERUNGEN ===== */
/* Deaktivierung nicht benötigter Features für Kiosk */
* {
/* Keine Touch-Events */
touch-action: none;
-webkit-touch-callout: none;
-webkit-user-select: none;
user-select: none;
}
/* Nur notwendige Elemente selektierbar */
input,
textarea {
-webkit-user-select: text;
user-select: text;
touch-action: manipulation;
}
/* Optimierte Scrolling-Performance */
* {
-webkit-overflow-scrolling: touch;
scroll-behavior: smooth;
}
/* GPU-Acceleration nur wo nötig */
.card:hover,
.btn:hover {
will-change: transform;
}
/* Minimale Animationen für bessere Performance */
@media (prefers-reduced-motion: reduce) {
* {
transition: none !important;
animation: none !important;
}
}
/* Print-Optimierung (falls Kiosk drucken kann) */
@media print {
.nav,
.btn {
display: none;
}
.card {
box-shadow: none;
border: 1px solid #000;
}
}
/* ===== LAYOUT SHIFT PREVENTION ===== */
img {
height: auto;
max-width: 100%;
vertical-align: middle;
}
/* Container für stabile Layouts */
.container {
max-width: 1200px;
margin: 0 auto;
padding: 0 1rem;
contain: layout;
}
/* ===== KIOSK-SPEZIFISCHE OPTIMIERUNGEN ===== */
/* Vollbildmodus-Unterstützung */
html,
body {
height: 100%;
overflow-x: hidden;
}
/* Fokus-Management für Keyboard-Navigation */
:focus {
outline: 2px solid var(--primary);
outline-offset: 2px;
}
/* Kein Selection-Highlighting */
::selection {
background: transparent;
}
/* Optimierte Font-Loading */
@font-face {
font-family: 'system-ui';
font-display: swap;
}

View File

@ -1,100 +1,27 @@
/**
* MYP Platform - Optimierungs-Animationen
* Belohnende und motivierende Animationen für Auto-Optimierung
* MYP Platform - Optimierte einfache Animationen
* Reduzierte, performante Animationen für bessere User Experience
*/
/* ===== MODAL FADE-IN ANIMATION ===== */
/* ===== EINFACHE FADE-IN ANIMATION ===== */
@keyframes fade-in {
from {
opacity: 0;
backdrop-filter: blur(0px);
}
to {
opacity: 1;
backdrop-filter: blur(8px);
}
}
.animate-fade-in {
animation: fade-in 0.3s ease-out;
animation: fade-in 0.2s ease-out;
}
/* ===== BOUNCE-IN ANIMATION ===== */
@keyframes bounce-in {
0% {
opacity: 0;
transform: scale(0.3) translateY(-50px);
}
50% {
opacity: 1;
transform: scale(1.05) translateY(0);
}
70% {
transform: scale(0.95);
}
100% {
opacity: 1;
transform: scale(1);
}
}
.animate-bounce-in {
animation: bounce-in 0.6s cubic-bezier(0.175, 0.885, 0.32, 1.275);
}
/* ===== PULSE-SCALE ANIMATION ===== */
@keyframes pulse-scale {
0%, 100% {
transform: scale(1);
}
50% {
transform: scale(1.1);
}
}
.animate-pulse-scale {
animation: pulse-scale 3s infinite ease-in-out;
}
/* ===== FLOATING ANIMATIONS ===== */
@keyframes float {
0%, 100% {
transform: translateY(0px) rotate(0deg);
}
33% {
transform: translateY(-15px) rotate(5deg);
}
66% {
transform: translateY(-5px) rotate(-3deg);
}
}
@keyframes float-delay {
0%, 100% {
transform: translateY(0px) rotate(0deg);
}
33% {
transform: translateY(-10px) rotate(-5deg);
}
66% {
transform: translateY(-8px) rotate(3deg);
}
}
.animate-float {
animation: float 4s infinite ease-in-out;
}
.animate-float-delay {
animation: float-delay 4s infinite ease-in-out;
animation-delay: 1.5s;
}
/* ===== SLIDE-UP ANIMATIONS ===== */
/* ===== EINFACHE SLIDE-UP ANIMATION ===== */
@keyframes slide-up {
from {
opacity: 0;
transform: translateY(30px);
transform: translateY(15px);
}
to {
opacity: 1;
@ -103,239 +30,105 @@
}
.animate-slide-up {
animation: slide-up 0.6s ease-out;
animation: slide-up 0.3s ease-out;
}
.animate-slide-up-delay {
animation: slide-up 0.6s ease-out;
animation-delay: 0.2s;
animation: slide-up 0.3s ease-out;
animation-delay: 0.1s;
animation-fill-mode: both;
}
.animate-slide-up-delay-2 {
animation: slide-up 0.6s ease-out;
animation-delay: 0.4s;
animation: slide-up 0.3s ease-out;
animation-delay: 0.2s;
animation-fill-mode: both;
}
.animate-slide-up-delay-3 {
animation: slide-up 0.6s ease-out;
animation-delay: 0.6s;
animation-fill-mode: both;
/* ===== EINFACHER HOVER-EFFEKT ===== */
.animate-hover-lift {
transition: transform 0.2s ease;
}
/* ===== COUNT-UP ANIMATION ===== */
@keyframes count-up {
from {
opacity: 0;
transform: scale(0.5) rotate(-10deg);
}
50% {
opacity: 1;
transform: scale(1.2) rotate(5deg);
}
to {
opacity: 1;
transform: scale(1) rotate(0deg);
}
.animate-hover-lift:hover {
transform: translateY(-2px);
}
.animate-count-up {
animation: count-up 0.8s cubic-bezier(0.175, 0.885, 0.32, 1.275);
animation-delay: 0.3s;
animation-fill-mode: both;
}
/* ===== GLOW ANIMATION ===== */
@keyframes glow {
0%, 100% {
box-shadow: 0 0 5px rgba(59, 130, 246, 0.5),
0 0 10px rgba(59, 130, 246, 0.3),
0 0 15px rgba(59, 130, 246, 0.1);
}
50% {
box-shadow: 0 0 20px rgba(59, 130, 246, 0.8),
0 0 30px rgba(59, 130, 246, 0.6),
0 0 40px rgba(59, 130, 246, 0.4);
}
}
.animate-glow {
animation: glow 3s infinite ease-in-out;
}
/* ===== KONFETTI ANIMATION ===== */
.confetti-container {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
overflow: hidden;
pointer-events: none;
}
.confetti-piece {
position: absolute;
width: 8px;
height: 8px;
top: -10px;
border-radius: 2px;
animation: confetti-fall linear infinite;
}
@keyframes confetti-fall {
/* ===== EINFACHE SUCCESS ANIMATION ===== */
@keyframes simple-success {
0% {
transform: translateY(-100vh) rotate(0deg);
opacity: 1;
opacity: 0;
transform: scale(0.9);
}
100% {
transform: translateY(120vh) rotate(720deg);
opacity: 0;
}
}
/* ===== ERFOLGS-BADGE SPEZIAL-ANIMATIONEN ===== */
@keyframes star-twinkle {
0%, 100% {
opacity: 0.6;
opacity: 1;
transform: scale(1);
}
50% {
}
.animate-success {
animation: simple-success 0.3s ease-out;
}
/* ===== EINFACHER PULSE FÜR LOADING ===== */
@keyframes simple-pulse {
0%, 100% {
opacity: 1;
transform: scale(1.2);
}
}
.badge-star {
animation: star-twinkle 1.5s infinite ease-in-out;
}
/* ===== LOADING SPINNER IMPROVEMENTS ===== */
@keyframes spinner-glow {
0% {
filter: drop-shadow(0 0 5px rgba(59, 130, 246, 0.5));
}
50% {
filter: drop-shadow(0 0 15px rgba(59, 130, 246, 0.8));
}
100% {
filter: drop-shadow(0 0 5px rgba(59, 130, 246, 0.5));
opacity: 0.6;
}
}
#optimization-loader .animate-spin {
animation: spin 1s linear infinite, spinner-glow 2s ease-in-out infinite;
.animate-pulse-simple {
animation: simple-pulse 2s infinite ease-in-out;
}
/* ===== DARK MODE OPTIMIERUNGEN ===== */
@media (prefers-color-scheme: dark) {
.animate-glow {
animation: glow-dark 2s infinite ease-in-out;
}
@keyframes glow-dark {
0%, 100% {
box-shadow: 0 0 5px rgba(96, 165, 250, 0.5),
0 0 10px rgba(96, 165, 250, 0.3),
0 0 15px rgba(96, 165, 250, 0.1);
}
50% {
box-shadow: 0 0 20px rgba(96, 165, 250, 0.8),
0 0 30px rgba(96, 165, 250, 0.6),
0 0 40px rgba(96, 165, 250, 0.4);
}
}
/* ===== EINFACHER PROGRESS BAR ===== */
.progress-fill {
transition: width 1s ease-out;
}
/* ===== PERFORMANCE OPTIMIERUNGEN ===== */
.animate-bounce-in,
.animate-fade-in,
.animate-slide-up,
.animate-slide-up-delay,
.animate-slide-up-delay-2,
.animate-slide-up-delay-3 {
.animate-slide-up-delay-2 {
will-change: transform, opacity;
}
.animate-pulse-scale,
.animate-float,
.animate-float-delay {
.animate-hover-lift {
will-change: transform;
}
.animate-glow {
will-change: box-shadow;
/* ===== UTILITY CLASSES ===== */
.animate-smooth {
transition: all 0.2s ease;
}
.animate-smooth-fast {
transition: all 0.1s ease;
}
/* ===== RESPONSIVE ANPASSUNGEN ===== */
@media (max-width: 768px) {
.confetti-piece {
width: 6px;
height: 6px;
.animate-fade-in,
.animate-slide-up,
.animate-slide-up-delay,
.animate-slide-up-delay-2 {
animation-duration: 0.2s;
}
}
/* ===== REDUZIERTE BEWEGUNG UNTERSTÜTZUNG ===== */
@media (prefers-reduced-motion: reduce) {
* {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
#optimization-reward-modal .text-8xl {
font-size: 4rem;
}
#optimization-reward-modal .max-w-md {
max-width: 90vw;
}
}
/* ===== BUTTON HOVER VERBESSERUNGEN ===== */
button:hover .badge-star {
animation-duration: 0.8s;
}
/* ===== ZUSÄTZLICHE UTILITY CLASSES ===== */
.animate-shake {
animation: shake 0.5s ease-in-out;
}
@keyframes shake {
0%, 100% { transform: translateX(0); }
10%, 30%, 50%, 70%, 90% { transform: translateX(-5px); }
20%, 40%, 60%, 80% { transform: translateX(5px); }
}
.animate-heartbeat {
animation: heartbeat 1.5s infinite;
}
@keyframes heartbeat {
0%, 100% { transform: scale(1); }
14% { transform: scale(1.1); }
28% { transform: scale(1); }
42% { transform: scale(1.1); }
70% { transform: scale(1); }
}
/* ===== PROGRESS BAR ANIMATION ===== */
.progress-fill {
animation: progress-grow 2s ease-out;
}
@keyframes progress-grow {
from {
width: 0%;
}
to {
width: var(--progress-width, 100%);
}
}
/* ===== SUCCESS CHECKMARK ANIMATION ===== */
.success-checkmark {
animation: checkmark-draw 0.8s ease-out;
}
@keyframes checkmark-draw {
0% {
stroke-dasharray: 0 100;
}
100% {
stroke-dasharray: 100 0;
.animate-hover-lift:hover {
transform: none;
}
}

Binary file not shown.

View File

@ -0,0 +1 @@
@keyframes fade-in{from{opacity:0}to{opacity:1}}.animate-fade-in{animation:fade-in .2s ease-out}@keyframes slide-up{from{opacity:0;transform:translateY(15px)}to{opacity:1;transform:translateY(0)}}.animate-slide-up{animation:slide-up .3s ease-out}.animate-slide-up-delay{animation:slide-up .3s ease-out;animation-delay:.1s;animation-fill-mode:both}.animate-slide-up-delay-2{animation:slide-up .3s ease-out;animation-delay:.2s;animation-fill-mode:both}.animate-hover-lift{transition:transform .2s ease}.animate-hover-lift:hover{transform:translateY(-2px)}@keyframes simple-success{0%{opacity:0;transform:scale(0.9)}100%{opacity:1;transform:scale(1)}}.animate-success{animation:simple-success .3s ease-out}@keyframes simple-pulse{0%,100%{opacity:1}50%{opacity:.6}}.animate-pulse-simple{animation:simple-pulse 2s infinite ease-in-out}.progress-fill{transition:width 1s ease-out}.animate-fade-in,.animate-slide-up,.animate-slide-up-delay,.animate-slide-up-delay-2{will-change:transform,opacity}.animate-hover-lift{will-change:transform}.animate-smooth{transition:all .2s ease}.animate-smooth-fast{transition:all .1s ease}@media(max-width:768px){.animate-fade-in,.animate-slide-up,.animate-slide-up-delay,.animate-slide-up-delay-2{animation-duration:.2s}}@media(prefers-reduced-motion:reduce){*{animation-duration:.01ms !important;animation-iteration-count:1 !important;transition-duration:.01ms !important}.animate-hover-lift:hover{transform:none}}

Binary file not shown.

Binary file not shown.

1
backend/static/css/output.min.css vendored Normal file

File diff suppressed because one or more lines are too long

Binary file not shown.

Binary file not shown.

1
backend/static/css/printers.min.css vendored Normal file
View File

@ -0,0 +1 @@
.filter-btn{transition:all .2s ease-in-out}.filter-btn.active{background-color:white;box-shadow:0 1px 3px 0 rgba(0,0,0,0.1),0 1px 2px 0 rgba(0,0,0,0.06);color:#374151}.dark .filter-btn.active{background-color:#475569;color:#f1f5f9}.printer-card-online{background:linear-gradient(135deg,#f0fdf4 0,#fff 100%);border-color:#bbf7d0;box-shadow:0 1px 3px 0 rgba(34,197,94,0.1),0 1px 2px 0 rgba(34,197,94,0.06)}.dark .printer-card-online{background:linear-gradient(135deg,rgba(34,197,94,0.1) 0,#1e293b 100%);border-color:#166534;box-shadow:0 1px 3px 0 rgba(34,197,94,0.2),0 1px 2px 0 rgba(34,197,94,0.1)}.printer-card-online:hover{box-shadow:0 4px 6px -1px rgba(34,197,94,0.2),0 2px 4px -1px rgba(34,197,94,0.1)}.dark .printer-card-online:hover{box-shadow:0 4px 6px -1px rgba(34,197,94,0.3),0 2px 4px -1px rgba(34,197,94,0.2)}.online-indicator{animation:pulse-green 2s cubic-bezier(0.4,0,0.6,1) infinite}@keyframes pulse-green{0%,100%{opacity:1;box-shadow:0 0 0 0 rgba(34,197,94,0.7)}50%{opacity:.8;box-shadow:0 0 0 4px rgba(34,197,94,0)}}.status-count-change{animation:count-change .5s ease-in-out}@keyframes count-change{0%{transform:scale(1)}50%{transform:scale(1.1)}100%{transform:scale(1)}}.auto-refresh-active{background:linear-gradient(45deg,#10b981,#059669);animation:gradient-shift 3s ease-in-out infinite}@keyframes gradient-shift{0%,100%{background-position:0 50%}50%{background-position:100% 50%}}.printer-card{transition:all .3s cubic-bezier(0.4,0,0.2,1)}.printer-card:hover{transform:translateY(-2px)}.live-update-spinner{animation:spin 1s linear infinite}@keyframes spin{from{transform:rotate(0)}to{transform:rotate(360deg)}}@media(max-width:640px){.filter-btn{padding:.375rem .75rem;font-size:.75rem}.status-overview{flex-direction:column;gap:.5rem}.printer-card{padding:1rem}}.dark .printer-card{background-color:#1e293b;border-color:#334155}.dark .printer-card:hover{background-color:#334155}.filter-btn:focus{outline:2px solid #3b82f6;outline-offset:2px}.printer-card:focus-within{outline:2px solid #3b82f6;outline-offset:2px}@media print{.filter-btn,.auto-refresh-btn,.printer-detail-btn,.delete-printer-btn{display:none}.printer-card{break-inside:avoid;box-shadow:none;border:1px solid #000}}@media(prefers-contrast:high){.printer-card-online{border:2px solid #059669}.online-indicator{border:1px solid #000}}@media(prefers-reduced-motion:reduce){.online-indicator,.auto-refresh-active,.live-update-spinner{animation:none}.printer-card{transition:none}.printer-card:hover{transform:none}}

Binary file not shown.

View File

@ -1,12 +1,11 @@
/**
* Mercedes-Benz MYP Platform - Erweiterte Professional Theme
* Verbesserte Light/Dark Mode Implementierung mit optimierten Kontrasten
* OPTIMIERT: Light Mode deutlich verbessert für bessere Lesbarkeit
* Mercedes-Benz MYP Platform - Optimierte Professional Theme
* Vereinfachte Light/Dark Mode Implementierung für bessere Performance
*/
/* Globale CSS-Variablen für konsistente Theming */
:root {
/* Mercedes-Benz Markenfarben - Erweitert */
/* Mercedes-Benz Markenfarben */
--mb-primary: #0073ce;
--mb-primary-dark: #005a9f;
--mb-secondary: #64748b;
@ -14,32 +13,22 @@
--mb-black: #000000;
--mb-silver: #c0c0c0;
/* Light Mode Farbpalette - DEUTLICH VERBESSERT für optimale Lesbarkeit */
/* Light Mode Farbpalette */
--light-bg-primary: #ffffff;
--light-bg-secondary: #fafbfc;
--light-bg-tertiary: #f3f5f7;
--light-bg-accent: #fbfcfd;
--light-surface: #ffffff;
--light-surface-hover: #fafbfc;
--light-surface-active: #f3f5f7;
--light-text-primary: #111827; /* Deutlich verstärkter Kontrast */
--light-text-secondary: #374151; /* Erhöhter Kontrast für bessere Lesbarkeit */
--light-text-muted: #6b7280; /* Optimierter Muted-Text, immer noch lesbar */
--light-text-primary: #111827;
--light-text-secondary: #374151;
--light-text-muted: #6b7280;
--light-text-accent: #0073ce;
--light-border: #e5e7eb; /* Sichtbarere aber elegante Borders */
--light-border: #e5e7eb;
--light-border-strong: #d1d5db;
--light-border-accent: #0073ce15; /* Subtilerer Accent-Border */
--light-shadow: rgba(0, 0, 0, 0.04); /* Sanftere, natürlichere Schatten */
--light-shadow: rgba(0, 0, 0, 0.04);
--light-shadow-strong: rgba(0, 0, 0, 0.08);
--light-shadow-accent: rgba(0, 115, 206, 0.08);
/* Neue Light Mode Gradients - VERBESSERT für sanftere, harmonischere Optik */
--light-gradient-primary: linear-gradient(135deg, #ffffff 0%, #fafbfc 25%, #f8fafc 75%, #f3f5f7 100%);
--light-gradient-card: linear-gradient(135deg, #ffffff 0%, #fdfdfe 50%, #fafbfc 100%);
--light-gradient-hero: linear-gradient(135deg, #fafbfc 0%, #f5f7f9 30%, #f0f3f6 70%, #f8fafc 100%);
--light-gradient-accent: linear-gradient(135deg, #0073ce 0%, #005a9f 100%);
/* Dark Mode Farbpalette - UNVERÄNDERT (bereits perfekt) */
/* Dark Mode Farbpalette */
--dark-bg-primary: #0f172a;
--dark-bg-secondary: #1e293b;
--dark-bg-tertiary: #334155;
@ -54,118 +43,81 @@
--dark-shadow-strong: rgba(0, 0, 0, 0.5);
}
/* Professionelle Hero-Header Stile - VERBESSERT für Light Mode */
/* Vereinfachte Hero-Header */
.professional-hero {
position: relative;
overflow: hidden;
border-radius: 1.5rem;
border-radius: 1rem;
margin: 1.5rem;
margin-bottom: 2rem;
background: var(--light-gradient-hero);
background: var(--light-bg-secondary);
border: 1px solid var(--light-border);
box-shadow:
0 8px 25px var(--light-shadow),
0 4px 12px rgba(0, 115, 206, 0.03),
inset 0 1px 0 rgba(255, 255, 255, 0.9);
transition: all 0.3s ease;
box-shadow: 0 4px 12px var(--light-shadow);
transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.professional-hero:hover {
transform: translateY(-1px);
box-shadow:
0 12px 35px var(--light-shadow-strong),
0 6px 16px var(--light-shadow-accent),
inset 0 1px 0 rgba(255, 255, 255, 0.95);
box-shadow: 0 8px 20px var(--light-shadow-strong);
}
.dark .professional-hero {
background: linear-gradient(135deg, var(--dark-bg-primary) 0%, var(--dark-bg-secondary) 100%);
background: var(--dark-bg-secondary);
border: 1px solid var(--dark-border);
box-shadow: 0 20px 40px var(--dark-shadow-strong);
box-shadow: 0 8px 20px var(--dark-shadow);
}
.professional-hero::before {
content: '';
position: absolute;
inset: 0;
background: linear-gradient(45deg, transparent 30%, rgba(255, 255, 255, 0.06) 50%, transparent 70%);
opacity: 0.6;
}
.dark .professional-hero::before {
background: linear-gradient(45deg, transparent 30%, rgba(255, 255, 255, 0.05) 50%, transparent 70%);
opacity: 0.3;
}
/* Hero Pattern Overlay - OPTIMIERT */
.hero-pattern {
background-image:
radial-gradient(circle at 20% 20%, var(--light-border) 0.8px, transparent 0.8px),
radial-gradient(circle at 80% 80%, var(--light-border) 0.8px, transparent 0.8px);
background-size: 48px 48px;
background-position: 0 0, 24px 24px;
}
.dark .hero-pattern {
background-image:
radial-gradient(circle at 20% 20%, var(--dark-border) 1px, transparent 1px),
radial-gradient(circle at 80% 80%, var(--dark-border) 1px, transparent 1px);
}
/* Professionelle Container - VERBESSERT */
/* Vereinfachte Container */
.professional-container {
background: var(--light-surface);
border: 1px solid var(--light-border);
border-radius: 1rem;
box-shadow: 0 4px 20px var(--light-shadow);
backdrop-filter: blur(16px);
transition: all 0.3s ease;
border-radius: 0.75rem;
box-shadow: 0 2px 8px var(--light-shadow);
transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.dark .professional-container {
background: var(--dark-surface);
border: 1px solid var(--dark-border);
box-shadow: 0 10px 30px var(--dark-shadow);
box-shadow: 0 4px 12px var(--dark-shadow);
}
.professional-container:hover {
transform: translateY(-2px);
box-shadow: 0 8px 30px var(--light-shadow-strong);
transform: translateY(-1px);
box-shadow: 0 4px 16px var(--light-shadow-strong);
}
.dark .professional-container:hover {
box-shadow: 0 20px 40px var(--dark-shadow-strong);
box-shadow: 0 8px 20px var(--dark-shadow-strong);
}
/* Mercedes-Benz Glassmorphism Effekt - OPTIMIERT für Light Mode */
/* Vereinfachte Glassmorphism-Effekte */
.mb-glass {
background: rgba(255, 255, 255, 0.94);
backdrop-filter: blur(16px) saturate(180%);
background: rgba(255, 255, 255, 0.9);
backdrop-filter: blur(8px);
border: 1px solid rgba(229, 231, 235, 0.4);
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.04);
transition: all 0.3s ease;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
transition: transform 0.2s ease, background 0.2s ease;
}
.dark .mb-glass {
background: rgba(15, 23, 42, 0.9);
background: rgba(15, 23, 42, 0.85);
border: 1px solid rgba(255, 255, 255, 0.1);
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.25);
}
.mb-glass:hover {
background: rgba(255, 255, 255, 0.97);
background: rgba(255, 255, 255, 0.95);
transform: translateY(-1px);
box-shadow: 0 8px 28px rgba(0, 0, 0, 0.06);
}
.dark .mb-glass:hover {
background: rgba(15, 23, 42, 0.95);
box-shadow: 0 12px 40px rgba(0, 0, 0, 0.4);
background: rgba(15, 23, 42, 0.9);
}
/* Professional Buttons - VERBESSERT */
/* Vereinfachte Buttons */
.btn-professional {
background: var(--light-gradient-accent);
background: linear-gradient(135deg, var(--mb-primary) 0%, var(--mb-primary-dark) 100%);
color: white;
border: none;
border-radius: 0.5rem;
@ -174,73 +126,50 @@
font-size: 0.875rem;
text-transform: uppercase;
letter-spacing: 0.025em;
transition: all 0.2s ease;
box-shadow:
0 2px 8px rgba(0, 115, 206, 0.2),
0 1px 3px rgba(0, 115, 206, 0.08);
position: relative;
overflow: hidden;
}
.btn-professional::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
transition: left 0.5s ease;
transition: transform 0.2s ease, box-shadow 0.2s ease;
box-shadow: 0 2px 8px rgba(0, 115, 206, 0.2);
}
.btn-professional:hover {
transform: translateY(-1px);
box-shadow:
0 4px 15px rgba(0, 115, 206, 0.25),
0 2px 8px rgba(0, 115, 206, 0.15);
}
.btn-professional:hover::before {
left: 100%;
box-shadow: 0 4px 12px rgba(0, 115, 206, 0.3);
}
.btn-professional:active {
transform: translateY(0);
}
/* Secondary Button Style - VERBESSERT */
/* Secondary Button */
.btn-secondary-professional {
background: var(--light-surface);
color: var(--light-text-primary);
border: 1px solid var(--light-border-strong);
border-radius: 0.75rem;
padding: 0.75rem 1.75rem;
border-radius: 0.5rem;
padding: 0.75rem 1.5rem;
font-weight: 600;
font-size: 0.875rem;
transition: all 0.3s ease;
box-shadow: 0 2px 8px var(--light-shadow);
transition: all 0.2s ease;
box-shadow: 0 1px 4px var(--light-shadow);
}
.dark .btn-secondary-professional {
background: var(--dark-surface);
color: var(--dark-text-primary);
border-color: var(--dark-border-strong);
box-shadow: 0 4px 15px var(--dark-shadow);
box-shadow: 0 2px 8px var(--dark-shadow);
}
.btn-secondary-professional:hover {
background: var(--light-surface-hover);
border-color: var(--mb-primary);
transform: translateY(-1px);
box-shadow: 0 4px 15px var(--light-shadow-strong);
}
.dark .btn-secondary-professional:hover {
background: var(--dark-surface-hover);
box-shadow: 0 8px 25px var(--dark-shadow);
}
/* Professional Input Fields - VERBESSERT */
/* Vereinfachte Input Fields */
.input-professional {
background: var(--light-surface);
border: 1px solid var(--light-border);
@ -248,20 +177,20 @@
padding: 0.75rem 1rem;
color: var(--light-text-primary);
font-size: 0.875rem;
transition: all 0.3s ease;
box-shadow: 0 1px 6px var(--light-shadow);
transition: border-color 0.2s ease, box-shadow 0.2s ease;
box-shadow: 0 1px 4px var(--light-shadow);
}
.dark .input-professional {
background: var(--dark-surface);
border-color: var(--dark-border);
color: var(--dark-text-primary);
box-shadow: 0 2px 8px var(--dark-shadow);
box-shadow: 0 2px 4px var(--dark-shadow);
}
.input-professional:focus {
border-color: var(--mb-primary);
box-shadow: 0 0 0 3px rgba(0, 115, 206, 0.06);
box-shadow: 0 0 0 3px rgba(0, 115, 206, 0.1);
transform: translateY(-1px);
}
@ -273,77 +202,51 @@
color: var(--dark-text-muted);
}
/* Professional Cards - VERBESSERT */
/* Vereinfachte Cards */
.card-professional {
background: var(--light-gradient-card);
background: var(--light-surface);
border: 1px solid var(--light-border);
border-radius: 0.75rem;
padding: 1.5rem;
box-shadow:
0 2px 12px var(--light-shadow),
0 1px 4px rgba(0, 115, 206, 0.02),
inset 0 1px 0 rgba(255, 255, 255, 0.8);
transition: all 0.3s ease;
position: relative;
overflow: hidden;
}
.card-professional::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 1px;
background: var(--light-gradient-accent);
opacity: 0;
transition: opacity 0.3s ease;
box-shadow: 0 2px 8px var(--light-shadow);
transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.card-professional:hover {
transform: translateY(-2px);
box-shadow:
0 8px 25px var(--light-shadow-strong),
0 4px 12px var(--light-shadow-accent),
inset 0 1px 0 rgba(255, 255, 255, 0.9);
}
.card-professional:hover::before {
opacity: 1;
box-shadow: 0 4px 16px var(--light-shadow-strong);
}
.dark .card-professional {
background: var(--dark-surface);
border-color: var(--dark-border);
box-shadow: 0 4px 15px var(--dark-shadow);
box-shadow: 0 4px 12px var(--dark-shadow);
}
/* Professional Statistics Cards - VERBESSERT */
/* Vereinfachte Statistics Cards */
.stat-card {
background: var(--light-surface);
border: 1px solid var(--light-border);
border-radius: 0.75rem;
padding: 1.5rem;
text-align: center;
transition: all 0.3s ease;
box-shadow: 0 2px 12px var(--light-shadow);
position: relative;
overflow: hidden;
transition: transform 0.2s ease, box-shadow 0.2s ease;
box-shadow: 0 2px 8px var(--light-shadow);
}
.dark .stat-card {
background: var(--dark-surface);
border-color: var(--dark-border);
box-shadow: 0 4px 15px var(--dark-shadow);
box-shadow: 0 4px 12px var(--dark-shadow);
}
.stat-card:hover {
transform: translateY(-1px) scale(1.01);
box-shadow: 0 6px 20px var(--light-shadow-strong);
transform: translateY(-1px);
box-shadow: 0 4px 16px var(--light-shadow-strong);
}
.dark .stat-card:hover {
box-shadow: 0 8px 30px var(--dark-shadow-strong);
box-shadow: 0 8px 20px var(--dark-shadow-strong);
}
.stat-number {
@ -370,7 +273,7 @@
color: var(--dark-text-muted);
}
/* Professional Status Badges */
/* Vereinfachte Status Badges */
.status-professional {
display: inline-flex;
align-items: center;
@ -381,121 +284,61 @@
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.05em;
transition: all 0.2s ease;
transition: transform 0.2s ease;
border: 1px solid transparent;
}
.status-professional:hover {
transform: scale(1.05);
transform: scale(1.02);
}
/* Verbesserte Status Indicators */
/* Status Indicators */
.status-online {
background: linear-gradient(135deg, #ecfdf5 0%, #d1fae5 100%);
background: #ecfdf5;
color: #065f46;
border: 1px solid rgba(16, 185, 129, 0.2);
box-shadow: 0 2px 8px rgba(16, 185, 129, 0.1);
}
.dark .status-online {
background: linear-gradient(135deg, rgba(16, 185, 129, 0.15) 0%, rgba(16, 185, 129, 0.05) 100%);
background: rgba(16, 185, 129, 0.15);
color: #10b981;
border-color: rgba(16, 185, 129, 0.3);
}
.status-offline {
background: linear-gradient(135deg, #fef2f2 0%, #fecaca 100%);
background: #fef2f2;
color: #991b1b;
border: 1px solid rgba(239, 68, 68, 0.2);
box-shadow: 0 2px 8px rgba(239, 68, 68, 0.1);
}
.dark .status-offline {
background: linear-gradient(135deg, rgba(239, 68, 68, 0.15) 0%, rgba(239, 68, 68, 0.05) 100%);
background: rgba(239, 68, 68, 0.15);
color: #ef4444;
border-color: rgba(239, 68, 68, 0.3);
}
.status-printing {
background: linear-gradient(135deg, #eff6ff 0%, #dbeafe 100%);
background: #eff6ff;
color: #1d4ed8;
border: 1px solid rgba(59, 130, 246, 0.2);
box-shadow: 0 2px 8px rgba(59, 130, 246, 0.1);
}
.dark .status-printing {
background: linear-gradient(135deg, rgba(59, 130, 246, 0.15) 0%, rgba(59, 130, 246, 0.05) 100%);
background: rgba(59, 130, 246, 0.15);
color: #3b82f6;
border-color: rgba(59, 130, 246, 0.3);
}
.status-maintenance {
background: linear-gradient(135deg, #faf5ff 0%, #ede9fe 100%);
color: #6b21a8;
border: 1px solid rgba(139, 92, 246, 0.2);
box-shadow: 0 2px 8px rgba(139, 92, 246, 0.1);
}
.dark .status-maintenance {
background: linear-gradient(135deg, rgba(139, 92, 246, 0.15) 0%, rgba(139, 92, 246, 0.05) 100%);
color: #8b5cf6;
border-color: rgba(139, 92, 246, 0.3);
}
.status-pending {
background: linear-gradient(135deg, #fffbeb 0%, #fef3c7 100%);
color: #92400e;
border: 1px solid rgba(251, 191, 36, 0.2);
box-shadow: 0 2px 8px rgba(251, 191, 36, 0.1);
}
.dark .status-pending {
background: linear-gradient(135deg, rgba(251, 191, 36, 0.15) 0%, rgba(251, 191, 36, 0.05) 100%);
color: #fbbf24;
border-color: rgba(251, 191, 36, 0.3);
}
.status-approved {
background: linear-gradient(135deg, #ecfdf5 0%, #a7f3d0 100%);
color: #065f46;
border: 1px solid rgba(16, 185, 129, 0.3);
box-shadow: 0 2px 8px rgba(16, 185, 129, 0.15);
}
.dark .status-approved {
background: linear-gradient(135deg, rgba(16, 185, 129, 0.2) 0%, rgba(16, 185, 129, 0.1) 100%);
color: #10b981;
}
.status-denied {
background: linear-gradient(135deg, #fef2f2 0%, #fca5a5 100%);
color: #991b1b;
border: 1px solid rgba(239, 68, 68, 0.3);
box-shadow: 0 2px 8px rgba(239, 68, 68, 0.15);
}
.dark .status-denied {
background: linear-gradient(135deg, rgba(239, 68, 68, 0.2) 0%, rgba(239, 68, 68, 0.1) 100%);
color: #ef4444;
}
/* Professional Typography */
/* Vereinfachte Typography */
.title-professional {
background: linear-gradient(135deg, var(--light-text-primary) 0%, var(--light-text-accent) 50%, var(--light-text-secondary) 100%);
background-clip: text;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
color: var(--light-text-primary);
font-weight: 700;
letter-spacing: -0.025em;
line-height: 1.1;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.dark .title-professional {
background: linear-gradient(135deg, var(--dark-text-primary) 0%, var(--dark-text-secondary) 100%);
background-clip: text;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
color: var(--dark-text-primary);
}
.subtitle-professional {
@ -512,21 +355,17 @@
/* Professional Navigation */
.nav-professional {
background: var(--light-gradient-card);
background: var(--light-bg-secondary);
border: 1px solid var(--light-border);
border-radius: 1rem;
border-radius: 0.75rem;
padding: 0.5rem;
box-shadow:
0 4px 15px var(--light-shadow),
0 2px 8px rgba(0, 115, 206, 0.05),
inset 0 1px 0 rgba(255, 255, 255, 0.6);
backdrop-filter: blur(20px);
box-shadow: 0 2px 8px var(--light-shadow);
}
.dark .nav-professional {
background: var(--dark-surface);
background: var(--dark-bg-secondary);
border-color: var(--dark-border);
box-shadow: 0 4px 15px var(--dark-shadow);
box-shadow: 0 4px 12px var(--dark-shadow);
}
.nav-item-professional {
@ -563,8 +402,8 @@
background: linear-gradient(135deg, rgba(0, 115, 206, 0.1) 0%, rgba(0, 115, 206, 0.05) 100%);
color: var(--mb-primary);
font-weight: 600;
border: 1px solid var(--light-border-accent);
box-shadow: 0 2px 8px var(--light-shadow-accent);
border: 1px solid var(--light-border-strong);
box-shadow: 0 2px 8px var(--light-shadow);
}
.dark .nav-item-professional.active {
@ -575,23 +414,20 @@
.table-professional {
width: 100%;
border-collapse: collapse;
background: var(--light-gradient-card);
border-radius: 1rem;
background: var(--light-bg-secondary);
border-radius: 0.75rem;
overflow: hidden;
box-shadow:
0 4px 20px var(--light-shadow),
0 2px 8px rgba(0, 115, 206, 0.05),
inset 0 1px 0 rgba(255, 255, 255, 0.6);
box-shadow: 0 2px 8px var(--light-shadow);
border: 1px solid var(--light-border);
}
.dark .table-professional {
background: var(--dark-surface);
box-shadow: 0 4px 20px var(--dark-shadow);
background: var(--dark-bg-secondary);
box-shadow: 0 4px 12px var(--dark-shadow);
}
.table-professional th {
background: linear-gradient(135deg, var(--light-bg-secondary) 0%, var(--light-bg-tertiary) 100%);
background: linear-gradient(135deg, var(--light-bg-tertiary) 0%, var(--light-bg-secondary) 100%);
color: var(--light-text-primary);
font-weight: 600;
text-align: left;
@ -611,7 +447,7 @@
}
.dark .table-professional th {
background: var(--dark-bg-secondary);
background: var(--dark-bg-tertiary);
color: var(--dark-text-primary);
border-bottom-color: var(--dark-border);
}
@ -723,7 +559,7 @@
/* Background Gradients für verschiedene Seiten */
.bg-professional {
background: var(--light-gradient-primary);
background: var(--light-bg-secondary);
min-height: 100vh;
position: relative;
}

Binary file not shown.

File diff suppressed because one or more lines are too long

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 304 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 592 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 908 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

View File

@ -0,0 +1,223 @@
let currentRequests=[];let filteredRequests=[];let currentPage=0;let totalPages=0;let totalRequests=0;let refreshInterval=null;let csrfToken='';function detectApiBaseUrl(){return'';}
const API_BASE_URL=detectApiBaseUrl();document.addEventListener('DOMContentLoaded',function(){csrfToken=document.querySelector('meta[name="csrf-token"]')?.getAttribute('content')||'';initEventListeners();loadGuestRequests();startAutoRefresh();console.log('🎯 Admin Guest Requests Management geladen');});function initEventListeners(){const searchInput=document.getElementById('search-requests');if(searchInput){searchInput.addEventListener('input',debounce(handleSearch,300));}
const statusFilter=document.getElementById('status-filter');if(statusFilter){statusFilter.addEventListener('change',handleFilterChange);}
const sortOrder=document.getElementById('sort-order');if(sortOrder){sortOrder.addEventListener('change',handleSortChange);}
const refreshBtn=document.getElementById('refresh-btn');if(refreshBtn){refreshBtn.addEventListener('click',()=>{loadGuestRequests();showNotification('🔄 Gastaufträge aktualisiert','info');});}
const exportBtn=document.getElementById('export-btn');if(exportBtn){exportBtn.addEventListener('click',handleExport);}
const bulkActionsBtn=document.getElementById('bulk-actions-btn');if(bulkActionsBtn){bulkActionsBtn.addEventListener('click',showBulkActionsModal);}
const selectAllCheckbox=document.getElementById('select-all');if(selectAllCheckbox){selectAllCheckbox.addEventListener('change',handleSelectAll);}}
async function loadGuestRequests(){try{showLoading(true);const url=`${API_BASE_URL}/api/admin/guest-requests`;const response=await fetch(url,{method:'GET',headers:{'Content-Type':'application/json','X-CSRFToken':csrfToken}});if(!response.ok){throw new Error(`HTTP ${response.status}:${response.statusText}`);}
const data=await response.json();if(data.success){currentRequests=data.requests||[];totalRequests=data.total||0;updateStats(data.stats||{});applyFiltersAndSort();console.log(`${currentRequests.length}Gastaufträge geladen`);}else{throw new Error(data.message||'Fehler beim Laden der Gastaufträge');}}catch(error){console.error('Fehler beim Laden der Gastaufträge:',error);showNotification('❌ Fehler beim Laden der Gastaufträge: '+error.message,'error');showEmptyState();}finally{showLoading(false);}}
function updateStats(stats){const elements={'pending-count':stats.pending||0,'approved-count':stats.approved||0,'rejected-count':stats.rejected||0,'total-count':stats.total||0};Object.entries(elements).forEach(([id,value])=>{const element=document.getElementById(id);if(element){animateCounter(element,value);}});}
function animateCounter(element,targetValue){const currentValue=parseInt(element.textContent)||0;const difference=targetValue-currentValue;const steps=20;const stepValue=difference/steps;let step=0;const interval=setInterval(()=>{step++;const value=Math.round(currentValue+(stepValue*step));element.textContent=value;if(step>=steps){clearInterval(interval);element.textContent=targetValue;}},50);}
function applyFiltersAndSort(){let requests=[...currentRequests];const statusFilter=document.getElementById('status-filter')?.value;if(statusFilter&&statusFilter!=='all'){requests=requests.filter(req=>req.status===statusFilter);}
const searchTerm=document.getElementById('search-requests')?.value.toLowerCase();if(searchTerm){requests=requests.filter(req=>req.name?.toLowerCase().includes(searchTerm)||req.email?.toLowerCase().includes(searchTerm)||req.file_name?.toLowerCase().includes(searchTerm)||req.reason?.toLowerCase().includes(searchTerm));}
const sortOrder=document.getElementById('sort-order')?.value;requests.sort((a,b)=>{switch(sortOrder){case'oldest':return new Date(a.created_at)-new Date(b.created_at);case'priority':return getPriorityValue(b)-getPriorityValue(a);case'newest':default:return new Date(b.created_at)-new Date(a.created_at);}});filteredRequests=requests;renderRequestsTable();}
function getPriorityValue(request){const now=new Date();const created=new Date(request.created_at);const hoursOld=(now-created)/(1000*60*60);let priority=0;if(request.status==='pending')priority+=10;else if(request.status==='approved')priority+=5;if(hoursOld>24)priority+=5;else if(hoursOld>8)priority+=3;else if(hoursOld>2)priority+=1;return priority;}
function renderRequestsTable(){const tableBody=document.getElementById('requests-table-body');const emptyState=document.getElementById('empty-state');if(!tableBody)return;if(filteredRequests.length===0){tableBody.innerHTML='';showEmptyState();return;}
hideEmptyState();const requestsHtml=filteredRequests.map(request=>createRequestRow(request)).join('');tableBody.innerHTML=requestsHtml;addRowEventListeners();}
function createRequestRow(request){const statusColor=getStatusColor(request.status);const priorityLevel=getPriorityLevel(request);const timeAgo=getTimeAgo(request.created_at);return`<tr class="hover:bg-slate-50 dark:hover:bg-slate-700/50 transition-colors duration-200"data-request-id="${request.id}"><td class="px-6 py-4"><input type="checkbox"class="request-checkbox rounded border-slate-300 text-blue-600 focus:ring-blue-500"
value="${request.id}"></td><td class="px-6 py-4"><div class="flex items-center"><div class="flex-shrink-0 h-10 w-10"><div class="h-10 w-10 rounded-full bg-gradient-to-br from-blue-500 to-purple-600 flex items-center justify-center text-white font-semibold">${request.name?request.name[0].toUpperCase():'G'}</div></div><div class="ml-4"><div class="text-sm font-medium text-slate-900 dark:text-white">${escapeHtml(request.name||'Unbekannt')}</div><div class="text-sm text-slate-500 dark:text-slate-400">${escapeHtml(request.email||'Keine E-Mail')}</div></div></div></td><td class="px-6 py-4"><div class="text-sm text-slate-900 dark:text-white font-medium">${escapeHtml(request.file_name||'Keine Datei')}</div><div class="text-sm text-slate-500 dark:text-slate-400">${request.duration_minutes?`${request.duration_minutes}Min.`:'Unbekannte Dauer'}
${request.copies?`${request.copies}Kopien`:''}</div>${request.reason?`<div class="text-xs text-slate-400 dark:text-slate-500 mt-1 truncate max-w-xs">${escapeHtml(request.reason)}</div>`:''}</td><td class="px-6 py-4"><span class="inline-flex items-center px-2 py-1 text-xs font-semibold rounded-full ${statusColor}"><span class="w-2 h-2 mr-1 rounded-full ${getStatusDot(request.status)}"></span>${getStatusText(request.status)}</span></td><td class="px-6 py-4"><div class="text-sm text-slate-900 dark:text-white">${timeAgo}</div><div class="text-xs text-slate-500 dark:text-slate-400">${formatDateTime(request.created_at)}</div></td><td class="px-6 py-4"><div class="flex items-center">${getPriorityBadge(priorityLevel)}
${request.is_urgent?'<span class="ml-2 text-red-500 text-xs">🔥 Dringend</span>':''}</div></td><td class="px-6 py-4"><div class="flex items-center space-x-2"><button onclick="showRequestDetail(${request.id})"
class="text-blue-600 hover:text-blue-900 dark:text-blue-400 dark:hover:text-blue-300 transition-colors"
title="Details anzeigen"><svg class="w-5 h-5"fill="none"stroke="currentColor"viewBox="0 0 24 24"><path stroke-linecap="round"stroke-linejoin="round"stroke-width="2"d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/><path stroke-linecap="round"stroke-linejoin="round"stroke-width="2"d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/></svg></button>${request.status==='pending'?`<button onclick="approveRequest(${request.id})"
class="text-green-600 hover:text-green-900 dark:text-green-400 dark:hover:text-green-300 transition-colors"
title="Genehmigen"><svg class="w-5 h-5"fill="none"stroke="currentColor"viewBox="0 0 24 24"><path stroke-linecap="round"stroke-linejoin="round"stroke-width="2"d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/></svg></button><button onclick="rejectRequest(${request.id})"
class="text-red-600 hover:text-red-900 dark:text-red-400 dark:hover:text-red-300 transition-colors"
title="Ablehnen"><svg class="w-5 h-5"fill="none"stroke="currentColor"viewBox="0 0 24 24"><path stroke-linecap="round"stroke-linejoin="round"stroke-width="2"d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z"/></svg></button>`:''}<button onclick="deleteRequest(${request.id})"
class="text-gray-600 hover:text-gray-900 dark:text-gray-400 dark:hover:text-gray-300 transition-colors"
title="Löschen"><svg class="w-5 h-5"fill="none"stroke="currentColor"viewBox="0 0 24 24"><path stroke-linecap="round"stroke-linejoin="round"stroke-width="2"d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"/></svg></button></div></td></tr>`;}
function getStatusColor(status){const colors={'pending':'bg-yellow-100 text-yellow-800 dark:bg-yellow-900/30 dark:text-yellow-300','approved':'bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-300','rejected':'bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-300','expired':'bg-gray-100 text-gray-800 dark:bg-gray-900/30 dark:text-gray-300'};return colors[status]||'bg-gray-100 text-gray-800 dark:bg-gray-900/30 dark:text-gray-300';}
function getStatusDot(status){const dots={'pending':'bg-yellow-400 dark:bg-yellow-300','approved':'bg-green-400 dark:bg-green-300','rejected':'bg-red-400 dark:bg-red-300','expired':'bg-gray-400 dark:bg-gray-300'};return dots[status]||'bg-gray-400 dark:bg-gray-300';}
function getStatusText(status){const texts={'pending':'Wartend','approved':'Genehmigt','rejected':'Abgelehnt','expired':'Abgelaufen'};return texts[status]||status;}
function getPriorityLevel(request){const priority=getPriorityValue(request);if(priority>=15)return'high';if(priority>=8)return'medium';return'low';}
function getPriorityBadge(level){const badges={'high':'<span class="inline-flex items-center px-2 py-1 text-xs font-medium rounded-full bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-300">🔴 Hoch</span>','medium':'<span class="inline-flex items-center px-2 py-1 text-xs font-medium rounded-full bg-orange-100 text-orange-800 dark:bg-orange-900/30 dark:text-orange-300">🟡 Mittel</span>','low':'<span class="inline-flex items-center px-2 py-1 text-xs font-medium rounded-full bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-300">🟢 Niedrig</span>'};return badges[level]||badges['low'];}
async function approveRequest(requestId){if(!confirm('Möchten Sie diesen Gastauftrag wirklich genehmigen?'))return;try{showLoading(true);const url=`${API_BASE_URL}/api/guest-requests/${requestId}/approve`;const response=await fetch(url,{method:'POST',headers:{'Content-Type':'application/json','X-CSRFToken':csrfToken},body:JSON.stringify({})});const data=await response.json();if(data.success){showNotification('✅ Gastauftrag erfolgreich genehmigt','success');loadGuestRequests();}else{throw new Error(data.message||'Fehler beim Genehmigen');}}catch(error){console.error('Fehler beim Genehmigen:',error);showNotification('❌ Fehler beim Genehmigen: '+error.message,'error');}finally{showLoading(false);}}
async function rejectRequest(requestId){const reason=prompt('Grund für die Ablehnung:');if(!reason)return;try{showLoading(true);const url=`${API_BASE_URL}/api/guest-requests/${requestId}/reject`;const response=await fetch(url,{method:'POST',headers:{'Content-Type':'application/json','X-CSRFToken':csrfToken},body:JSON.stringify({reason})});const data=await response.json();if(data.success){showNotification('✅ Gastauftrag erfolgreich abgelehnt','success');loadGuestRequests();}else{throw new Error(data.message||'Fehler beim Ablehnen');}}catch(error){console.error('Fehler beim Ablehnen:',error);showNotification('❌ Fehler beim Ablehnen: '+error.message,'error');}finally{showLoading(false);}}
async function deleteRequest(requestId){if(!confirm('Möchten Sie diesen Gastauftrag wirklich löschen? Diese Aktion kann nicht rückgängig gemacht werden.'))return;try{showLoading(true);const url=`${API_BASE_URL}/api/guest-requests/${requestId}`;const response=await fetch(url,{method:'DELETE',headers:{'Content-Type':'application/json','X-CSRFToken':csrfToken}});const data=await response.json();if(data.success){showNotification('✅ Gastauftrag erfolgreich gelöscht','success');loadGuestRequests();}else{throw new Error(data.message||'Fehler beim Löschen');}}catch(error){console.error('Fehler beim Löschen:',error);showNotification('❌ Fehler beim Löschen: '+error.message,'error');}finally{showLoading(false);}}
function showRequestDetail(requestId){const request=currentRequests.find(req=>req.id===requestId);if(!request)return;const modal=document.getElementById('detail-modal');const content=document.getElementById('modal-content');content.innerHTML=`<div class="p-6 border-b border-gray-200 dark:border-gray-700"><div class="flex justify-between items-center"><h3 class="text-xl font-bold text-gray-900 dark:text-white">Gastauftrag Details</h3><button onclick="closeDetailModal()"class="text-gray-400 hover:text-gray-600 dark:hover:text-gray-300"><svg class="w-6 h-6"fill="none"stroke="currentColor"viewBox="0 0 24 24"><path stroke-linecap="round"stroke-linejoin="round"stroke-width="2"d="M6 18L18 6M6 6l12 12"/></svg></button></div></div><div class="p-6"><div class="grid grid-cols-1 md:grid-cols-2 gap-6"><div class="space-y-4"><h4 class="text-lg font-semibold text-gray-900 dark:text-white">Antragsteller</h4><div class="bg-slate-50 dark:bg-slate-700 rounded-lg p-4"><p><strong>Name:</strong>${escapeHtml(request.name||'Unbekannt')}</p><p><strong>E-Mail:</strong>${escapeHtml(request.email||'Keine E-Mail')}</p><p><strong>Erstellt am:</strong>${formatDateTime(request.created_at)}</p></div></div><div class="space-y-4"><h4 class="text-lg font-semibold text-gray-900 dark:text-white">Auftrag Details</h4><div class="bg-slate-50 dark:bg-slate-700 rounded-lg p-4"><p><strong>Datei:</strong>${escapeHtml(request.file_name||'Keine Datei')}</p><p><strong>Dauer:</strong>${request.duration_minutes||'Unbekannt'}Minuten</p><p><strong>Kopien:</strong>${request.copies||1}</p><p><strong>Status:</strong>${getStatusText(request.status)}</p></div></div></div>${request.reason?`<div class="mt-6"><h4 class="text-lg font-semibold text-gray-900 dark:text-white mb-2">Begründung</h4><div class="bg-slate-50 dark:bg-slate-700 rounded-lg p-4"><p class="text-gray-700 dark:text-gray-300">${escapeHtml(request.reason)}</p></div></div>`:''}<div class="mt-8 flex justify-end space-x-3">${request.status==='pending'?`<button onclick="approveRequest(${request.id}); closeDetailModal();"
class="px-4 py-2 bg-green-500 text-white rounded-lg hover:bg-green-600 transition-colors">Genehmigen</button><button onclick="rejectRequest(${request.id}); closeDetailModal();"
class="px-4 py-2 bg-red-500 text-white rounded-lg hover:bg-red-600 transition-colors">Ablehnen</button>`:''}<button onclick="closeDetailModal()"
class="px-4 py-2 bg-gray-300 dark:bg-gray-600 text-gray-700 dark:text-gray-300 rounded-lg hover:bg-gray-400 dark:hover:bg-gray-500 transition-colors">Schließen</button></div></div>`;modal.classList.remove('hidden');}
function closeDetailModal(){const modal=document.getElementById('detail-modal');modal.classList.add('hidden');}
function showBulkActionsModal(){const selectedIds=getSelectedRequestIds();if(selectedIds.length===0){showNotification('⚠️ Bitte wählen Sie mindestens einen Gastauftrag aus','warning');return;}
const modal=document.getElementById('bulk-modal');modal.classList.remove('hidden');}
function closeBulkModal(){const modal=document.getElementById('bulk-modal');modal.classList.add('hidden');}
async function performBulkAction(action){const selectedIds=getSelectedRequestIds();if(selectedIds.length===0)return;const confirmMessages={'approve':`Möchten Sie ${selectedIds.length}Gastaufträge genehmigen?`,'reject':`Möchten Sie ${selectedIds.length}Gastaufträge ablehnen?`,'delete':`Möchten Sie ${selectedIds.length}Gastaufträge löschen?`};if(!confirm(confirmMessages[action]))return;try{showLoading(true);closeBulkModal();const promises=selectedIds.map(async(id)=>{const url=`${API_BASE_URL}/api/guest-requests/${id}/${action}`;const method=action==='delete'?'DELETE':'POST';return fetch(url,{method,headers:{'Content-Type':'application/json','X-CSRFToken':csrfToken}});});const results=await Promise.allSettled(promises);const successCount=results.filter(r=>r.status==='fulfilled'&&r.value.ok).length;showNotification(`${successCount}von ${selectedIds.length}Aktionen erfolgreich`,'success');loadGuestRequests();document.getElementById('select-all').checked=false;document.querySelectorAll('.request-checkbox').forEach(cb=>cb.checked=false);}catch(error){console.error('Fehler bei Bulk-Aktion:',error);showNotification('❌ Fehler bei der Bulk-Aktion: '+error.message,'error');}finally{showLoading(false);}}
function getSelectedRequestIds(){const checkboxes=document.querySelectorAll('.request-checkbox:checked');return Array.from(checkboxes).map(cb=>parseInt(cb.value));}
function handleSearch(){applyFiltersAndSort();}
function handleFilterChange(){applyFiltersAndSort();}
function handleSortChange(){applyFiltersAndSort();}
function handleSelectAll(event){const checkboxes=document.querySelectorAll('.request-checkbox');checkboxes.forEach(checkbox=>{checkbox.checked=event.target.checked;});}
function handleExport(){const selectedIds=getSelectedRequestIds();const exportData=selectedIds.length>0?filteredRequests.filter(req=>selectedIds.includes(req.id)):filteredRequests;if(exportData.length===0){showNotification('⚠️ Keine Daten zum Exportieren verfügbar','warning');return;}
exportToCSV(exportData);}
function exportToCSV(data){const headers=['ID','Name','E-Mail','Datei','Status','Erstellt','Dauer (Min)','Kopien','Begründung'];const rows=data.map(req=>[req.id,req.name||'',req.email||'',req.file_name||'',getStatusText(req.status),formatDateTime(req.created_at),req.duration_minutes||'',req.copies||'',req.reason||'']);const csvContent=[headers,...rows].map(row=>row.map(field=>`"${String(field).replace(/"/g,'""')}"`).join(','))
.join('\n');
const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
const link = document.createElement('a');
if (link.download !== undefined) {
const url = URL.createObjectURL(blob);
link.setAttribute('href', url);
link.setAttribute('download', `gastauftraege_${new Date().toISOString().split('T')[0]}.csv`);
link.style.visibility = 'hidden';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
showNotification('📄 CSV-Export erfolgreich erstellt', 'success');
}
/**
* Auto-Refresh
*/
function startAutoRefresh() {
// Refresh alle 30 Sekunden
refreshInterval = setInterval(() => {
loadGuestRequests();
}, 30000);
}
function stopAutoRefresh() {
if (refreshInterval) {
clearInterval(refreshInterval);
refreshInterval = null;
}
}
/**
* Utility-Funktionen
*/
function addRowEventListeners() {
// Falls notwendig, können hier zusätzliche Event Listener hinzugefügt werden
}
function showLoading(show) {
const loadingElement = document.getElementById('table-loading');
const tableBody = document.getElementById('requests-table-body');
if (loadingElement) {
loadingElement.classList.toggle('hidden', !show);
}
if (show && tableBody) {
tableBody.innerHTML = '';
}
}
function showEmptyState() {
const emptyState = document.getElementById('empty-state');
if (emptyState) {
emptyState.classList.remove('hidden');
}
}
function hideEmptyState() {
const emptyState = document.getElementById('empty-state');
if (emptyState) {
emptyState.classList.add('hidden');
}
}
function showNotification(message, type = 'info') {
const notification = document.createElement('div');
notification.className = `fixed top-4 right-4 px-6 py-4 rounded-xl shadow-2xl z-50 transform transition-all duration-500 translate-x-full ${
type === 'success' ? 'bg-green-500 text-white' :
type === 'error' ? 'bg-red-500 text-white' :
type === 'warning' ? 'bg-yellow-500 text-black' :
'bg-blue-500 text-white'
}`;
notification.innerHTML = `
<div class="flex items-center space-x-3">
<span class="text-lg">
${type === 'success' ? '✅' :
type === 'error' ? '❌' :
type === 'warning' ? '⚠️' : ''}
</span>
<span class="font-medium">${message}</span>
</div>
`;
document.body.appendChild(notification);
// Animation einblenden
setTimeout(() => {
notification.classList.remove('translate-x-full');
}, 100);
// Nach 5 Sekunden entfernen
setTimeout(() => {
notification.classList.add('translate-x-full');
setTimeout(() => notification.remove(), 5000);
}, 5000);
}
function debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
function escapeHtml(text) {
const map = {
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#039;'
};
return text ? String(text).replace(/[&<>"']/g, m => map[m]) : '';
}
function formatDateTime(dateString) {
if (!dateString) return 'Unbekannt';
const date = new Date(dateString);
return date.toLocaleString('de-DE', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit'
});
}
function getTimeAgo(dateString) {
if (!dateString) return 'Unbekannt';
const now = new Date();
const date = new Date(dateString);
const diffMs = now - date;
const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
const diffDays = Math.floor(diffHours / 24);
if (diffDays > 0) {
return `vor ${diffDays} Tag${diffDays === 1 ? '' : 'en'}`;
} else if (diffHours > 0) {
return `vor ${diffHours} Stunde${diffHours === 1 ? '' : 'n'}`;
} else {
const diffMinutes = Math.floor(diffMs / (1000 * 60));
return `vor ${Math.max(1, diffMinutes)} Minute${diffMinutes === 1 ? '' : 'n'}`;
}
}
// Globale Funktionen für onclick-Handler
window.showRequestDetail = showRequestDetail;
window.approveRequest = approveRequest;
window.rejectRequest = rejectRequest;
window.deleteRequest = deleteRequest;
window.closeDetailModal = closeDetailModal;
window.closeBulkModal = closeBulkModal;
window.performBulkAction = performBulkAction;
console.log('📋 Admin Guest Requests JavaScript vollständig geladen'

Binary file not shown.

Binary file not shown.

77
backend/static/js/admin-panel.min.js vendored Normal file
View File

@ -0,0 +1,77 @@
document.addEventListener('DOMContentLoaded',function(){initializeAdminPanel();loadAdminStats();initializeActiveTab();initializeEventHandlers();setInterval(function(){if(document.visibilityState==='visible'){refreshActiveTabData();}},30000);});function initializeActiveTab(){const hash=window.location.hash.substring(1);if(hash){activateTab(hash);}else{const defaultTab=document.querySelector('.nav-tab');if(defaultTab){const tabName=defaultTab.getAttribute('data-tab');activateTab(tabName);}}}
function activateTab(tabName){const tabs=document.querySelectorAll('.nav-tab');tabs.forEach(tab=>{if(tab.getAttribute('data-tab')===tabName){tab.classList.add('active');}else{tab.classList.remove('active');}});const tabPanes=document.querySelectorAll('.tab-pane');tabPanes.forEach(pane=>{if(pane.id===`${tabName}-tab`){pane.classList.add('active');pane.classList.remove('hidden');}else{pane.classList.remove('active');pane.classList.add('hidden');}});switch(tabName){case'users':loadUsers();break;case'printers':loadPrinters();break;case'scheduler':loadSchedulerStatus();break;case'system':loadSystemStats();break;case'logs':loadLogs();break;}
window.location.hash=tabName;}
function refreshActiveTabData(){const activeTab=document.querySelector('.nav-tab.active');if(!activeTab)return;const tabName=activeTab.getAttribute('data-tab');switch(tabName){case'users':loadUsers();break;case'printers':loadPrinters();break;case'scheduler':loadSchedulerStatus();break;case'system':loadSystemStats();break;case'logs':loadLogs();break;}
loadAdminStats();}
function initializeEventHandlers(){const addUserBtn=document.getElementById('add-user-btn');if(addUserBtn){addUserBtn.addEventListener('click',showAddUserModal);}
const addPrinterBtn=document.getElementById('add-printer-btn');if(addPrinterBtn){addPrinterBtn.addEventListener('click',showAddPrinterModal);}
const refreshLogsBtn=document.getElementById('refresh-logs-btn');if(refreshLogsBtn){refreshLogsBtn.addEventListener('click',refreshLogs);}
const closeDeleteModalBtn=document.getElementById('close-delete-modal');if(closeDeleteModalBtn){closeDeleteModalBtn.addEventListener('click',closeDeleteModal);}
const cancelDeleteBtn=document.getElementById('cancel-delete');if(cancelDeleteBtn){cancelDeleteBtn.addEventListener('click',closeDeleteModal);}
const closeAddUserBtn=document.getElementById('close-add-user-modal');if(closeAddUserBtn){closeAddUserBtn.addEventListener('click',closeAddUserModal);}
const cancelAddUserBtn=document.getElementById('cancel-add-user');if(cancelAddUserBtn){cancelAddUserBtn.addEventListener('click',closeAddUserModal);}
const confirmAddUserBtn=document.getElementById('confirm-add-user');if(confirmAddUserBtn){confirmAddUserBtn.addEventListener('click',addUser);}
const closeAddPrinterBtn=document.getElementById('close-add-printer-modal');if(closeAddPrinterBtn){closeAddPrinterBtn.addEventListener('click',closeAddPrinterModal);}
const cancelAddPrinterBtn=document.getElementById('cancel-add-printer');if(cancelAddPrinterBtn){cancelAddPrinterBtn.addEventListener('click',closeAddPrinterModal);}
const confirmAddPrinterBtn=document.getElementById('confirm-add-printer');if(confirmAddPrinterBtn){confirmAddPrinterBtn.addEventListener('click',addPrinter);}
const startSchedulerBtn=document.getElementById('start-scheduler');if(startSchedulerBtn){startSchedulerBtn.addEventListener('click',startScheduler);}
const stopSchedulerBtn=document.getElementById('stop-scheduler');if(stopSchedulerBtn){stopSchedulerBtn.addEventListener('click',stopScheduler);}
const refreshButton=document.createElement('button');refreshButton.id='refresh-admin-btn';refreshButton.className='fixed bottom-8 right-8 p-3 bg-primary text-white rounded-full shadow-lg z-40';refreshButton.innerHTML=`<svg class="w-6 h-6"fill="none"viewBox="0 0 24 24"stroke="currentColor"><path stroke-linecap="round"stroke-linejoin="round"stroke-width="2"d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/></svg>`;refreshButton.title='Alle Daten aktualisieren';refreshButton.addEventListener('click',refreshAllData);if(!document.getElementById('refresh-admin-btn')){document.body.appendChild(refreshButton);}}
function showAddUserModal(){const modal=document.getElementById('add-user-modal');if(modal){const form=document.getElementById('add-user-form');if(form){form.reset();}
modal.classList.remove('hidden');modal.classList.add('show');modal.style.display='flex';}}
function closeAddUserModal(){const modal=document.getElementById('add-user-modal');if(modal){modal.classList.remove('show');modal.classList.add('hidden');modal.style.display='none';}}
async function addUser(){const form=document.getElementById('add-user-form');if(!form)return;const userData={email:document.getElementById('user-email').value,name:document.getElementById('user-name').value,password:document.getElementById('user-password').value,role:document.getElementById('user-role').value,status:document.getElementById('user-status').value};try{const response=await fetch('/api/users',{method:'POST',headers:{'Content-Type':'application/json','X-CSRF-Token':getCSRFToken()},body:JSON.stringify(userData)});if(!response.ok){throw new Error('Fehler beim Hinzufügen des Benutzers');}
showNotification('Benutzer erfolgreich hinzugefügt','success');closeAddUserModal();loadUsers();}catch(error){console.error('Error adding user:',error);showNotification(error.message,'error');}}
function showAddPrinterModal(){const modal=document.getElementById('add-printer-modal');if(modal){const form=document.getElementById('add-printer-form');if(form){form.reset();}
modal.classList.remove('hidden');modal.classList.add('show');modal.style.display='flex';}}
function closeAddPrinterModal(){const modal=document.getElementById('add-printer-modal');if(modal){modal.classList.remove('show');modal.classList.add('hidden');modal.style.display='none';}}
async function addPrinter(){const form=document.getElementById('add-printer-form');if(!form)return;const printerData={name:document.getElementById('printer-name').value,model:document.getElementById('printer-model').value,ip_address:document.getElementById('printer-ip').value,location:document.getElementById('printer-location').value,status:document.getElementById('printer-status').value};try{const response=await fetch('/api/printers',{method:'POST',headers:{'Content-Type':'application/json','X-CSRF-Token':getCSRFToken()},body:JSON.stringify(printerData)});if(!response.ok){throw new Error('Fehler beim Hinzufügen des Druckers');}
showNotification('Drucker erfolgreich hinzugefügt','success');closeAddPrinterModal();loadPrinters();}catch(error){console.error('Error adding printer:',error);showNotification(error.message,'error');}}
function refreshLogs(){loadLogs();showNotification('Logs aktualisiert','info');}
function closeDeleteModal(){const modal=document.getElementById('delete-confirm-modal');if(modal){modal.classList.remove('show');modal.style.display='none';}}
function showDeleteConfirmation(id,type,name,callback){const modal=document.getElementById('delete-confirm-modal');const messageEl=document.getElementById('delete-message');const idField=document.getElementById('delete-id');const typeField=document.getElementById('delete-type');const confirmBtn=document.getElementById('confirm-delete');if(modal&&messageEl&&idField&&typeField&&confirmBtn){messageEl.textContent=`Möchten Sie ${type==='user'?'den Benutzer':'den Drucker'}"${name}"wirklich löschen?`;idField.value=id;typeField.value=type;confirmBtn.onclick=function(){if(typeof callback==='function'){callback(id,name);}
closeDeleteModal();};modal.classList.add('show');modal.style.display='flex';}}
function initializeAdminPanel(){const tabs=document.querySelectorAll('.nav-tab');tabs.forEach(tab=>{tab.addEventListener('click',function(){const tabName=this.getAttribute('data-tab');if(!tabName)return;activateTab(tabName);});});const logLevelFilter=document.getElementById('log-level-filter');if(logLevelFilter){logLevelFilter.addEventListener('change',function(){if(window.logsData){const level=this.value;if(level==='all'){window.filteredLogs=[...window.logsData];}else{window.filteredLogs=window.logsData.filter(log=>log.level.toLowerCase()===level);}
renderLogs();}});}
const confirmDeleteBtn=document.getElementById('confirm-delete');if(confirmDeleteBtn){confirmDeleteBtn.addEventListener('click',function(){const id=document.getElementById('delete-id').value;const type=document.getElementById('delete-type').value;if(type==='user'){deleteUser(id);}else if(type==='printer'){deletePrinter(id);}
closeDeleteModal();});}}
async function loadAdminStats(){try{const response=await fetch('/api/admin/stats');if(!response.ok){throw new Error('Fehler beim Laden der Admin-Statistiken');}
const data=await response.json();const statsContainer=document.getElementById('admin-stats');if(!statsContainer){console.warn('Stats-Container nicht gefunden');return;}
statsContainer.innerHTML=`<div class="stat-card"><div class="stat-icon"><svg class="w-10 h-10"fill="none"stroke="currentColor"viewBox="0 0 24 24"><path stroke-linecap="round"stroke-linejoin="round"stroke-width="2"d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z"/></svg></div><div class="stat-title">Benutzer</div><div class="stat-value"id="admin-total-users-count">${data.total_users||0}</div><div class="stat-desc">Registrierte Benutzer</div></div><div class="stat-card"><div class="stat-icon"><svg class="w-10 h-10"fill="none"stroke="currentColor"viewBox="0 0 24 24"><path stroke-linecap="round"stroke-linejoin="round"stroke-width="2"d="M17 17h2a2 2 0 002-2v-4a2 2 0 00-2-2H5a2 2 0 00-2 2v4a2 2 0 002 2h2m2 4h6a2 2 0 002-2v-4a2 2 0 00-2-2H9a2 2 0 00-2 2v4a2 2 0 002 2zm8-12V5a2 2 0 00-2-2H9a2 2 0 00-2 2v4h10z"/></svg></div><div class="stat-title">Drucker</div><div class="stat-value"id="admin-total-printers-count">${data.total_printers||0}</div><div class="stat-desc">Verbundene Drucker</div></div><div class="stat-card"><div class="stat-icon"><svg class="w-10 h-10"fill="none"stroke="currentColor"viewBox="0 0 24 24"><path stroke-linecap="round"stroke-linejoin="round"stroke-width="2"d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10"/></svg></div><div class="stat-title">Aktive Jobs</div><div class="stat-value"id="admin-active-jobs-count">${data.active_jobs||0}</div><div class="stat-desc">Laufende Druckaufträge</div></div><div class="stat-card"><div class="stat-icon"><svg class="w-10 h-10"fill="none"stroke="currentColor"viewBox="0 0 24 24"><path stroke-linecap="round"stroke-linejoin="round"stroke-width="2"d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/></svg></div><div class="stat-title">Erfolgsrate</div><div class="stat-value"id="admin-success-rate">${data.success_rate||'0%'}</div><div class="stat-desc">Erfolgreiche Druckaufträge</div></div>`;console.log('✅ Admin-Statistiken erfolgreich aktualisiert:',data);}catch(error){console.error('Error loading admin stats:',error);showNotification('Fehler beim Laden der Admin-Statistiken','error');}}
async function loadUsers(){try{const response=await fetch('/api/users');if(!response.ok){throw new Error('Fehler beim Laden der Benutzer');}
const data=await response.json();const userTableBody=document.querySelector('#users-tab table tbody');if(!userTableBody)return;let html='';if(data.users&&data.users.length>0){data.users.forEach(user=>{html+=`<tr class="border-b border-gray-200 dark:border-gray-700"><td class="px-4 py-3">${user.id}</td><td class="px-4 py-3">${user.name||'-'}</td><td class="px-4 py-3">${user.email}</td><td class="px-4 py-3"><span class="px-2 py-1 text-xs rounded-full ${user.active ? 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200' : 'bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200'}">${user.active?'Aktiv':'Inaktiv'}</span></td><td class="px-4 py-3">${user.role||'Benutzer'}</td><td class="px-4 py-3"><div class="flex justify-end space-x-2"><button onclick="editUser(${user.id})"class="p-1 text-blue-600 hover:text-blue-900 dark:text-blue-400 dark:hover:text-blue-200"><svg class="w-5 h-5"fill="none"viewBox="0 0 24 24"stroke="currentColor"><path stroke-linecap="round"stroke-linejoin="round"stroke-width="2"d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"/></svg></button><button onclick="showDeleteConfirmation(${user.id}, 'user', '${user.name || user.email}', deleteUser)"class="p-1 text-red-600 hover:text-red-900 dark:text-red-400 dark:hover:text-red-200"><svg class="w-5 h-5"fill="none"viewBox="0 0 24 24"stroke="currentColor"><path stroke-linecap="round"stroke-linejoin="round"stroke-width="2"d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"/></svg></button></div></td></tr>`;});}else{html=`<tr><td colspan="6"class="px-4 py-6 text-center text-gray-500 dark:text-gray-400">Keine Benutzer gefunden</td></tr>`;}
userTableBody.innerHTML=html;}catch(error){console.error('Error loading users:',error);showNotification('Fehler beim Laden der Benutzer','error');}}
async function loadPrinters(){try{const response=await fetch('/api/printers');if(!response.ok){throw new Error('Fehler beim Laden der Drucker');}
const data=await response.json();const printerTableBody=document.querySelector('#printers-tab table tbody');if(!printerTableBody)return;let html='';if(data.printers&&data.printers.length>0){data.printers.forEach(printer=>{html+=`<tr class="border-b border-gray-200 dark:border-gray-700"><td class="px-4 py-3">${printer.id}</td><td class="px-4 py-3">${printer.name}</td><td class="px-4 py-3">${printer.model||'-'}</td><td class="px-4 py-3">${printer.ip_address||'-'}</td><td class="px-4 py-3">${printer.location||'-'}</td><td class="px-4 py-3"><span class="px-2 py-1 text-xs rounded-full ${printer.status === 'online' ? 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200' : 'bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200'}">${printer.status==='online'?'Online':'Offline'}</span></td><td class="px-4 py-3"><div class="flex justify-end space-x-2"><button onclick="editPrinter(${printer.id})"class="p-1 text-blue-600 hover:text-blue-900 dark:text-blue-400 dark:hover:text-blue-200"><svg class="w-5 h-5"fill="none"viewBox="0 0 24 24"stroke="currentColor"><path stroke-linecap="round"stroke-linejoin="round"stroke-width="2"d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"/></svg></button><button onclick="showDeleteConfirmation(${printer.id}, 'printer', '${printer.name}', deletePrinter)"class="p-1 text-red-600 hover:text-red-900 dark:text-red-400 dark:hover:text-red-200"><svg class="w-5 h-5"fill="none"viewBox="0 0 24 24"stroke="currentColor"><path stroke-linecap="round"stroke-linejoin="round"stroke-width="2"d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"/></svg></button></div></td></tr>`;});}else{html=`<tr><td colspan="7"class="px-4 py-6 text-center text-gray-500 dark:text-gray-400">Keine Drucker gefunden</td></tr>`;}
printerTableBody.innerHTML=html;}catch(error){console.error('Error loading printers:',error);showNotification('Fehler beim Laden der Drucker','error');}}
async function loadSchedulerStatus(){try{const response=await fetch('/api/scheduler/status');if(!response.ok){throw new Error('Fehler beim Laden des Scheduler-Status');}
const data=await response.json();const statusIndicator=document.getElementById('scheduler-status-indicator');const statusText=document.getElementById('scheduler-status-text');const startSchedulerBtn=document.getElementById('start-scheduler');const stopSchedulerBtn=document.getElementById('stop-scheduler');if(statusIndicator&&statusText){if(data.active){statusIndicator.classList.remove('bg-red-500');statusIndicator.classList.add('bg-green-500');statusText.textContent='Aktiv';if(startSchedulerBtn&&stopSchedulerBtn){startSchedulerBtn.disabled=true;stopSchedulerBtn.disabled=false;}}else{statusIndicator.classList.remove('bg-green-500');statusIndicator.classList.add('bg-red-500');statusText.textContent='Inaktiv';if(startSchedulerBtn&&stopSchedulerBtn){startSchedulerBtn.disabled=false;stopSchedulerBtn.disabled=true;}}}
const schedulerDetails=document.getElementById('scheduler-details');if(schedulerDetails){schedulerDetails.innerHTML=`<div class="mb-4"><h4 class="text-sm font-semibold text-gray-600 dark:text-gray-300 mb-2">Letzte Ausführung</h4><p class="text-gray-800 dark:text-gray-100">${data.last_run?new Date(data.last_run).toLocaleString('de-DE'):'Nie'}</p></div><div class="mb-4"><h4 class="text-sm font-semibold text-gray-600 dark:text-gray-300 mb-2">Nächste Ausführung</h4><p class="text-gray-800 dark:text-gray-100">${data.next_run?new Date(data.next_run).toLocaleString('de-DE'):'Nicht geplant'}</p></div><div class="mb-4"><h4 class="text-sm font-semibold text-gray-600 dark:text-gray-300 mb-2">Ausführungsintervall</h4><p class="text-gray-800 dark:text-gray-100">${data.interval||'60'}Sekunden</p></div><div><h4 class="text-sm font-semibold text-gray-600 dark:text-gray-300 mb-2">Verarbeitete Jobs</h4><p class="text-gray-800 dark:text-gray-100">${data.processed_jobs||0}Jobs seit dem letzten Neustart</p></div>`;}
const jobQueueContainer=document.getElementById('job-queue');if(jobQueueContainer&&data.pending_jobs){let queueHtml='';if(data.pending_jobs.length>0){queueHtml=`<div class="bg-white dark:bg-gray-800 shadow-md rounded-lg overflow-hidden"><table class="min-w-full"><thead class="bg-gray-50 dark:bg-gray-700"><tr><th class="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">Job ID</th><th class="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">Name</th><th class="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">Geplant für</th><th class="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">Status</th></tr></thead><tbody class="divide-y divide-gray-200 dark:divide-gray-700">`;data.pending_jobs.forEach(job=>{queueHtml+=`<tr><td class="px-4 py-3 text-sm text-gray-900 dark:text-gray-100">${job.id}</td><td class="px-4 py-3 text-sm text-gray-900 dark:text-gray-100">${job.name}</td><td class="px-4 py-3 text-sm text-gray-900 dark:text-gray-100">${new Date(job.start_time).toLocaleString('de-DE')}</td><td class="px-4 py-3 text-sm"><span class="px-2 py-1 text-xs rounded-full bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200">Warten</span></td></tr>`;});queueHtml+=`</tbody></table></div>`;}else{queueHtml=`<div class="bg-white dark:bg-gray-800 shadow-md rounded-lg p-6 text-center"><p class="text-gray-500 dark:text-gray-400">Keine ausstehenden Jobs in der Warteschlange</p></div>`;}
jobQueueContainer.innerHTML=queueHtml;}}catch(error){console.error('Error loading scheduler status:',error);showNotification('Fehler beim Laden des Scheduler-Status','error');}}
async function loadSystemStats(){try{const response=await fetch('/api/system/stats');if(!response.ok){throw new Error('Fehler beim Laden der Systemstatistiken');}
const data=await response.json();const cpuUsageElement=document.getElementById('cpu-usage');if(cpuUsageElement){cpuUsageElement.style.width=`${data.cpu_usage||0}%`;cpuUsageElement.textContent=`${data.cpu_usage||0}%`;}
const ramUsageElement=document.getElementById('ram-usage');if(ramUsageElement){ramUsageElement.style.width=`${data.ram_usage_percent||0}%`;ramUsageElement.textContent=`${data.ram_usage_percent||0}%`;}
const diskUsageElement=document.getElementById('disk-usage');if(diskUsageElement){diskUsageElement.style.width=`${data.disk_usage_percent||0}%`;diskUsageElement.textContent=`${data.disk_usage_percent||0}%`;}
const systemDetailsElement=document.getElementById('system-details');if(systemDetailsElement){systemDetailsElement.innerHTML=`<div class="mb-4"><h4 class="text-sm font-semibold text-gray-600 dark:text-gray-300 mb-2">System</h4><p class="text-gray-800 dark:text-gray-100">${data.os_name||'Unbekannt'}${data.os_version||''}</p></div><div class="mb-4"><h4 class="text-sm font-semibold text-gray-600 dark:text-gray-300 mb-2">Laufzeit</h4><p class="text-gray-800 dark:text-gray-100">${data.uptime||'Unbekannt'}</p></div><div class="mb-4"><h4 class="text-sm font-semibold text-gray-600 dark:text-gray-300 mb-2">Python-Version</h4><p class="text-gray-800 dark:text-gray-100">${data.python_version||'Unbekannt'}</p></div><div><h4 class="text-sm font-semibold text-gray-600 dark:text-gray-300 mb-2">Server-Zeit</h4><p class="text-gray-800 dark:text-gray-100">${data.server_time?new Date(data.server_time).toLocaleString('de-DE'):'Unbekannt'}</p></div>`;}
const systemEventsElement=document.getElementById('system-events');if(systemEventsElement&&data.recent_events){let eventsHtml='';if(data.recent_events.length>0){eventsHtml='<ul class="divide-y divide-gray-200 dark:divide-gray-700">';data.recent_events.forEach(event=>{let eventTypeClass='text-blue-600 dark:text-blue-400';switch(event.type.toLowerCase()){case'error':eventTypeClass='text-red-600 dark:text-red-400';break;case'warning':eventTypeClass='text-yellow-600 dark:text-yellow-400';break;case'success':eventTypeClass='text-green-600 dark:text-green-400';break;}
eventsHtml+=`<li class="py-3"><div class="flex items-start"><span class="${eventTypeClass} mr-2"><svg class="h-5 w-5"fill="none"viewBox="0 0 24 24"stroke="currentColor"><path stroke-linecap="round"stroke-linejoin="round"stroke-width="2"d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/></svg></span><div><p class="text-sm text-gray-800 dark:text-gray-200">${event.message}</p><p class="text-xs text-gray-500 dark:text-gray-400">${new Date(event.timestamp).toLocaleString('de-DE')}</p></div></div></li>`;});eventsHtml+='</ul>';}else{eventsHtml='<p class="text-center text-gray-500 dark:text-gray-400 py-4">Keine Ereignisse vorhanden</p>';}
systemEventsElement.innerHTML=eventsHtml;}}catch(error){console.error('Error loading system stats:',error);showNotification('Fehler beim Laden der Systemstatistiken','error');}}
async function loadLogs(){try{const response=await fetch('/api/logs');if(!response.ok){throw new Error('Fehler beim Laden der Logs');}
const data=await response.json();window.logsData=data.logs||[];window.filteredLogs=[...window.logsData];renderLogs();}catch(error){console.error('Error loading logs:',error);showNotification('Fehler beim Laden der Logs','error');}}
function renderLogs(){const logsContainer=document.getElementById('logs-container');if(!logsContainer)return;if(!window.filteredLogs||window.filteredLogs.length===0){logsContainer.innerHTML='<div class="p-8 text-center text-gray-500 dark:text-gray-400">Keine Logs gefunden</div>';return;}
let html='';window.filteredLogs.forEach(log=>{let levelClass='';switch(log.level.toLowerCase()){case'error':levelClass='bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200';break;case'warning':levelClass='bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200';break;case'info':levelClass='bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200';break;case'debug':levelClass='bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200';break;default:levelClass='bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-gray-200';}
html+=`<div class="log-entry border-l-4 border-gray-300 dark:border-gray-600 pl-4 py-3 mb-3"><div class="flex items-center justify-between mb-1"><div><span class="px-2 py-1 text-xs rounded-full ${levelClass} mr-2">${log.level}</span><span class="text-sm text-gray-500 dark:text-gray-400">${log.timestamp?new Date(log.timestamp).toLocaleString('de-DE'):''}</span></div><div class="text-sm text-gray-500 dark:text-gray-400">${log.source||'System'}</div></div><div class="text-sm text-gray-800 dark:text-gray-200">${log.message}</div>${log.details?`<div class="mt-1 text-xs text-gray-500 dark:text-gray-400 bg-gray-50 dark:bg-gray-800 p-2 rounded overflow-auto"><pre>${log.details}</pre></div>`:''}</div>`;});logsContainer.innerHTML=html;}
async function deleteUser(userId){try{const response=await fetch(`/api/users/${userId}`,{method:'DELETE',headers:{'X-CSRF-Token':getCSRFToken()}});if(!response.ok){throw new Error('Fehler beim Löschen des Benutzers');}
showNotification('Benutzer erfolgreich gelöscht','success');loadUsers();}catch(error){console.error('Error deleting user:',error);showNotification(error.message,'error');}}
async function deletePrinter(printerId){try{const response=await fetch(`/api/printers/${printerId}`,{method:'DELETE',headers:{'X-CSRF-Token':getCSRFToken()}});if(!response.ok){throw new Error('Fehler beim Löschen des Druckers');}
showNotification('Drucker erfolgreich gelöscht','success');loadPrinters();}catch(error){console.error('Error deleting printer:',error);showNotification(error.message,'error');}}
async function startScheduler(){try{const response=await fetch('/api/scheduler/start',{method:'POST',headers:{'X-CSRF-Token':getCSRFToken()}});if(!response.ok){throw new Error('Fehler beim Starten des Schedulers');}
showNotification('Scheduler erfolgreich gestartet','success');loadSchedulerStatus();}catch(error){console.error('Error starting scheduler:',error);showNotification(error.message,'error');}}
async function stopScheduler(){try{const response=await fetch('/api/scheduler/stop',{method:'POST',headers:{'X-CSRF-Token':getCSRFToken()}});if(!response.ok){throw new Error('Fehler beim Stoppen des Schedulers');}
showNotification('Scheduler erfolgreich gestoppt','success');loadSchedulerStatus();}catch(error){console.error('Error stopping scheduler:',error);showNotification(error.message,'error');}}
function getCSRFToken(){return document.querySelector('meta[name="csrf-token"]')?.getAttribute('content')||'';}
function refreshAllData(){loadAdminStats();const activeTab=document.querySelector('.nav-tab.active');if(activeTab){const tabName=activeTab.getAttribute('data-tab');activateTab(tabName);}
showNotification('Daten wurden aktualisiert','success');}
function showNotification(message,type='info'){const notification=document.getElementById('notification');const messageEl=document.getElementById('notification-message');const iconEl=document.getElementById('notification-icon');if(!notification||!messageEl||!iconEl)return;messageEl.textContent=message;notification.classList.remove('notification-success','notification-error','notification-warning','notification-info');notification.classList.add(`notification-${type}`);let icon='';switch(type){case'success':icon='<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" />';break;case'error':icon='<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />';break;case'warning':icon='<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />';break;case'info':default:icon='<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />';break;}
iconEl.innerHTML=icon;notification.classList.remove('hidden');notification.classList.add('show');setTimeout(()=>{notification.classList.remove('show');setTimeout(()=>{notification.classList.add('hidden');},300);},5000);}

Binary file not shown.

Binary file not shown.

101
backend/static/js/admin-unified.min.js vendored Normal file
View File

@ -0,0 +1,101 @@
class AdminDashboard{constructor(){this.csrfToken=null;this.updateInterval=null;this.eventListenersAttached=false;this.apiBaseUrl=this.detectApiBaseUrl();this.retryCount=0;this.maxRetries=3;this.isInitialized=false;this.init();}
detectApiBaseUrl(){const currentHost=window.location.hostname;const currentPort=window.location.port;if(currentPort==='5000'){return'';}
return`http:}
init(){if(this.isInitialized){console.log('🔄 Admin Dashboard bereits initialisiert, überspringe...');return;}
console.log('🚀 Initialisiere Mercedes-Benz MYP Admin Dashboard');this.csrfToken=this.extractCSRFToken();console.log('🔒 CSRF Token:',this.csrfToken?'verfügbar':'FEHLT!');this.attachEventListeners();this.startLiveUpdates();this.loadInitialData();this.isInitialized=true;console.log('✅ Admin Dashboard erfolgreich initialisiert');}
extractCSRFToken(){const metaToken=document.querySelector('meta[name="csrf-token"]')?.getAttribute('content');if(metaToken){console.log('🔒 CSRF Token aus meta Tag geladen');return metaToken;}
const hiddenInput=document.querySelector('input[name="csrf_token"]')?.value;if(hiddenInput){console.log('🔒 CSRF Token aus hidden input geladen');return hiddenInput;}
const cookieToken=document.cookie.split('; ').find(row=>row.startsWith('csrf_token='))?.split('=')[1];if(cookieToken){console.log('🔒 CSRF Token aus Cookie geladen');return cookieToken;}
const flaskToken=document.querySelector('meta[name="csrf-token"]')?.content;if(flaskToken){console.log('🔒 CSRF Token aus Flask-WTF Meta geladen');return flaskToken;}
console.error('❌ CSRF Token konnte nicht gefunden werden!');return null;}
attachEventListeners(){if(this.eventListenersAttached){console.log('⚠️ Event-Listener bereits registriert, überspringe...');return;}
this.attachSystemButtons();this.attachUserManagement();this.attachPrinterManagement();this.attachJobManagement();this.attachModalEvents();this.eventListenersAttached=true;console.log('📌 Event-Listener erfolgreich registriert');}
attachSystemButtons(){this.addEventListenerSafe('#system-status-btn','click',(e)=>{e.preventDefault();e.stopPropagation();this.showSystemStatus();});this.addEventListenerSafe('#analytics-btn','click',(e)=>{e.preventDefault();e.stopPropagation();this.showAnalytics();});this.addEventListenerSafe('#clear-cache-btn','click',(e)=>{e.preventDefault();e.stopPropagation();this.clearSystemCache();});this.addEventListenerSafe('#optimize-db-btn','click',(e)=>{e.preventDefault();e.stopPropagation();this.optimizeDatabase();});this.addEventListenerSafe('#create-backup-btn','click',(e)=>{e.preventDefault();e.stopPropagation();this.createSystemBackup();});this.addEventListenerSafe('#force-init-printers-btn','click',(e)=>{e.preventDefault();e.stopPropagation();this.forceInitializePrinters();});}
attachUserManagement(){this.addEventListenerSafe('#add-user-btn','click',(e)=>{e.preventDefault();e.stopPropagation();this.showUserModal();});document.addEventListener('click',(e)=>{if(e.target.closest('.edit-user-btn')){e.preventDefault();e.stopPropagation();const userId=e.target.closest('button').dataset.userId;this.editUser(userId);}
if(e.target.closest('.delete-user-btn')){e.preventDefault();e.stopPropagation();const userId=e.target.closest('button').dataset.userId;const userName=e.target.closest('button').dataset.userName;this.deleteUser(userId,userName);}});}
attachPrinterManagement(){this.addEventListenerSafe('#add-printer-btn','click',(e)=>{e.preventDefault();e.stopPropagation();this.showPrinterModal();});document.addEventListener('click',(e)=>{if(e.target.closest('.manage-printer-btn')){e.preventDefault();e.stopPropagation();const printerId=e.target.closest('button').dataset.printerId;this.managePrinter(printerId);}
if(e.target.closest('.settings-printer-btn')){e.preventDefault();e.stopPropagation();const printerId=e.target.closest('button').dataset.printerId;this.showPrinterSettings(printerId);}
if(e.target.closest('.toggle-printer-power-btn')){e.preventDefault();e.stopPropagation();const button=e.target.closest('button');const printerId=button.dataset.printerId;const printerName=button.dataset.printerName;this.togglePrinterPower(printerId,printerName,button);}});}
attachJobManagement(){document.addEventListener('click',(e)=>{if(e.target.closest('.job-action-btn')){e.preventDefault();e.stopPropagation();const action=e.target.closest('button').dataset.action;const jobId=e.target.closest('button').dataset.jobId;this.handleJobAction(action,jobId);}});}
attachModalEvents(){this.addEventListenerSafe('#fix-errors-btn','click',(e)=>{e.preventDefault();e.stopPropagation();this.fixErrors();});this.addEventListenerSafe('#dismiss-errors-btn','click',(e)=>{e.preventDefault();e.stopPropagation();this.dismissErrors();});this.addEventListenerSafe('#view-error-details-btn','click',(e)=>{e.preventDefault();e.stopPropagation();window.location.href='/admin-dashboard?tab=logs';});this.addEventListenerSafe('#refresh-logs-btn','click',(e)=>{e.preventDefault();e.stopPropagation();this.loadLogs();});this.addEventListenerSafe('#export-logs-btn','click',(e)=>{e.preventDefault();e.stopPropagation();this.exportLogs();});this.addEventListenerSafe('#log-level-filter','change',(e)=>{this.loadLogs();});}
addEventListenerSafe(selector,event,handler){const element=document.querySelector(selector);if(element&&!element.dataset.listenerAttached){element.addEventListener(event,handler);element.dataset.listenerAttached='true';}}
startLiveUpdates(){if(this.updateInterval){clearInterval(this.updateInterval);}
this.updateInterval=setInterval(()=>{this.loadLiveStats();},30000);setInterval(()=>{this.updateLiveTime();},1000);setInterval(()=>{this.checkSystemHealth();},30000);console.log('🔄 Live-Updates gestartet');}
async loadInitialData(){await this.loadLiveStats();await this.checkSystemHealth();setTimeout(()=>{this.testButtons();},1000);if(window.location.search.includes('tab=logs')||document.querySelector('.tabs [href*="logs"]')?.classList.contains('active')){await this.loadLogs();}
const urlParams=new URLSearchParams(window.location.search);const activeTab=urlParams.get('tab');if(activeTab==='logs'){await this.loadLogs();}
const logsContainer=document.getElementById('logs-container');if(logsContainer&&logsContainer.offsetParent!==null){await this.loadLogs();}}
async loadLiveStats(){try{const url=`${this.apiBaseUrl}/api/stats`;const response=await fetch(url);if(!response.ok){throw new Error(`HTTP ${response.status}:${response.statusText}`);}
const data=await response.json();this.updateStatsDisplay(data);this.retryCount=0;}catch(error){console.error('Fehler beim Laden der Live-Statistiken:',error);this.retryCount++;if(this.retryCount<=this.maxRetries){setTimeout(()=>this.loadLiveStats(),5000);}}}
updateStatsDisplay(data){this.updateElement('live-users-count',data.total_users||0);this.updateElement('live-printers-count',data.total_printers||0);this.updateElement('live-printers-online',`${data.online_printers||0}online`);this.updateElement('live-jobs-active',data.active_jobs||0);this.updateElement('live-jobs-queued',`${data.queued_jobs||0}in Warteschlange`);this.updateElement('live-success-rate',`${data.success_rate||0}%`);this.updateProgressBar('users-progress',data.total_users,20);this.updateProgressBar('printers-progress',data.online_printers,data.total_printers);this.updateProgressBar('jobs-progress',data.active_jobs,10);this.updateProgressBar('success-progress',data.success_rate,100);console.log('📊 Live-Statistiken aktualisiert');}
updateElement(elementId,value){const element=document.getElementById(elementId);if(element){element.textContent=value;}}
updateProgressBar(progressId,currentValue,maxValue){const progressEl=document.getElementById(progressId);if(progressEl&&currentValue!==undefined&&maxValue>0){const percentage=Math.min(100,Math.max(0,(currentValue/maxValue)*100));progressEl.style.width=`${percentage}%`;}}
updateLiveTime(){const timeElement=document.getElementById('live-time');if(timeElement){const now=new Date();timeElement.textContent=now.toLocaleTimeString('de-DE');}}
async showSystemStatus(){console.log('🔧 System Status wird angezeigt');this.showNotification('System Status wird geladen...','info');}
async showAnalytics(){console.log('📈 Analytics wird angezeigt');this.showNotification('Analytics werden geladen...','info');}
async showMaintenance(){console.log('🛠️ Wartung wird angezeigt');const systemTab=document.querySelector('a[href*="tab=system"]');if(systemTab){systemTab.click();}}
async clearSystemCache(){if(!confirm('🗑️ Möchten Sie wirklich den System-Cache leeren?'))return;try{const response=await fetch(`${this.apiBaseUrl}/api/admin/cache/clear`,{method:'POST',headers:{'Content-Type':'application/json','X-CSRFToken':this.csrfToken}});const data=await response.json();if(data.success){this.showNotification('✅ Cache erfolgreich geleert!','success');setTimeout(()=>window.location.reload(),2000);}else{this.showNotification('❌ Fehler beim Leeren des Cache','error');}}catch(error){this.showNotification('❌ Fehler beim Leeren des Cache','error');}}
async optimizeDatabase(){if(!confirm('🔧 Möchten Sie die Datenbank optimieren?'))return;this.showNotification('🔄 Datenbank wird optimiert...','info');try{const response=await fetch(`${this.apiBaseUrl}/api/admin/database/optimize`,{method:'POST',headers:{'X-CSRFToken':this.csrfToken}});const data=await response.json();if(data.success){this.showNotification('✅ Datenbank erfolgreich optimiert!','success');}else{this.showNotification('❌ Fehler bei der Datenbank-Optimierung','error');}}catch(error){this.showNotification('❌ Fehler bei der Datenbank-Optimierung','error');}}
async createSystemBackup(){if(!confirm('💾 Möchten Sie ein System-Backup erstellen?'))return;this.showNotification('🔄 Backup wird erstellt...','info');try{const response=await fetch(`${this.apiBaseUrl}/api/admin/backup/create`,{method:'POST',headers:{'X-CSRFToken':this.csrfToken}});const data=await response.json();if(data.success){this.showNotification('✅ Backup erfolgreich erstellt!','success');}else{this.showNotification('❌ Fehler beim Erstellen des Backups','error');}}catch(error){this.showNotification('❌ Fehler beim Erstellen des Backups','error');}}
async forceInitializePrinters(){if(!confirm('🔄 Möchten Sie die Drucker-Initialisierung erzwingen?'))return;this.showNotification('🔄 Drucker werden initialisiert...','info');try{const response=await fetch(`${this.apiBaseUrl}/api/admin/printers/force-init`,{method:'POST',headers:{'X-CSRFToken':this.csrfToken}});const data=await response.json();if(data.success){this.showNotification('✅ Drucker erfolgreich initialisiert!','success');setTimeout(()=>window.location.reload(),2000);}else{this.showNotification('❌ Fehler bei der Drucker-Initialisierung','error');}}catch(error){this.showNotification('❌ Fehler bei der Drucker-Initialisierung','error');}}
showUserModal(userId=null){const isEdit=userId!==null;const title=isEdit?'Benutzer bearbeiten':'Neuer Benutzer';const modalHtml=`<div id="user-modal"class="fixed inset-0 bg-black/60 backdrop-blur-sm z-50 flex items-center justify-center p-4"><div class="bg-white dark:bg-slate-800 rounded-2xl p-8 max-w-md w-full shadow-2xl transform scale-100 transition-all duration-300"><div class="flex justify-between items-center mb-6"><h3 class="text-2xl font-bold text-slate-900 dark:text-white">${title}</h3><button onclick="this.closest('#user-modal').remove()"class="p-2 hover:bg-gray-100 dark:hover:bg-slate-700 rounded-lg transition-colors"><svg class="w-6 h-6 text-slate-500"fill="none"stroke="currentColor"viewBox="0 0 24 24"><path stroke-linecap="round"stroke-linejoin="round"stroke-width="2"d="M6 18L18 6M6 6l12 12"/></svg></button></div><form id="user-form"class="space-y-4"><div><label class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">E-Mail-Adresse*</label><input type="email"name="email"id="user-email"required
class="w-full px-4 py-3 border border-slate-300 dark:border-slate-600 rounded-xl
focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all
dark:bg-slate-700 dark:text-white bg-slate-50"></div><div><label class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">Benutzername</label><input type="text"name="username"id="user-username"
class="w-full px-4 py-3 border border-slate-300 dark:border-slate-600 rounded-xl
focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all
dark:bg-slate-700 dark:text-white bg-slate-50"></div><div><label class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">Name</label><input type="text"name="name"id="user-name"
class="w-full px-4 py-3 border border-slate-300 dark:border-slate-600 rounded-xl
focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all
dark:bg-slate-700 dark:text-white bg-slate-50"></div><div><label class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">Passwort ${isEdit?'(leer lassen für keine Änderung)':'*'}</label><input type="password"name="password"id="user-password"${!isEdit?'required':''}
class="w-full px-4 py-3 border border-slate-300 dark:border-slate-600 rounded-xl
focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all
dark:bg-slate-700 dark:text-white bg-slate-50"></div><div><label class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">Rolle</label><select name="role"id="user-role"
class="w-full px-4 py-3 border border-slate-300 dark:border-slate-600 rounded-xl
focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all
dark:bg-slate-700 dark:text-white bg-slate-50"><option value="user">Benutzer</option><option value="admin">Administrator</option></select></div>${isEdit?`<div class="flex items-center space-x-2"><input type="checkbox"name="is_active"id="user-active"
class="w-4 h-4 text-blue-600 bg-slate-100 border-slate-300 rounded focus:ring-blue-500"><label for="user-active"class="text-sm font-medium text-slate-700 dark:text-slate-300">Aktiv</label></div>`:''}<div class="flex justify-end space-x-3 mt-8 pt-6 border-t border-slate-200 dark:border-slate-600"><button type="button"onclick="this.closest('#user-modal').remove()"
class="px-6 py-3 bg-slate-200 dark:bg-slate-600 text-slate-700 dark:text-slate-300
rounded-xl hover:bg-slate-300 dark:hover:bg-slate-500 transition-colors font-medium">Abbrechen</button><button type="submit"id="user-submit-btn"
class="px-6 py-3 bg-gradient-to-r from-blue-500 to-blue-600 text-white
rounded-xl hover:from-blue-600 hover:to-blue-700 transition-all duration-300
shadow-lg hover:shadow-xl font-medium">${isEdit?'Aktualisieren':'Erstellen'}</button></div></form></div></div>`;document.body.insertAdjacentHTML('beforeend',modalHtml);const form=document.getElementById('user-form');form.addEventListener('submit',(e)=>{e.preventDefault();e.stopPropagation();if(isEdit){this.updateUser(userId,new FormData(form));}else{this.createUser(new FormData(form));}});if(isEdit){this.loadUserData(userId);}
setTimeout(()=>{document.getElementById('user-email').focus();},100);}
async loadUserData(userId){try{const response=await fetch(`${this.apiBaseUrl}/api/admin/users/${userId}`);if(!response.ok){throw new Error(`HTTP ${response.status}:${response.statusText}`);}
const data=await response.json();if(data.success){const user=data.user;document.getElementById('user-email').value=user.email||'';document.getElementById('user-username').value=user.username||'';document.getElementById('user-name').value=user.name||'';document.getElementById('user-role').value=user.is_admin?'admin':'user';const activeCheckbox=document.getElementById('user-active');if(activeCheckbox){activeCheckbox.checked=user.is_active!==false;}}else{this.showNotification('❌ Fehler beim Laden der Benutzerdaten','error');}}catch(error){console.error('Fehler beim Laden der Benutzerdaten:',error);this.showNotification('❌ Fehler beim Laden der Benutzerdaten','error');}}
async createUser(formData){const submitBtn=document.getElementById('user-submit-btn');const originalText=submitBtn.innerHTML;try{submitBtn.innerHTML='<div class="animate-spin rounded-full h-5 w-5 border-b-2 border-white mx-auto"></div>';submitBtn.disabled=true;const userData={email:formData.get('email'),username:formData.get('username')||formData.get('email').split('@')[0],name:formData.get('name'),password:formData.get('password'),is_admin:formData.get('role')==='admin'};const response=await fetch(`${this.apiBaseUrl}/api/admin/users`,{method:'POST',headers:{'Content-Type':'application/json','X-CSRFToken':this.csrfToken},body:JSON.stringify(userData)});const data=await response.json();if(data.success){this.showNotification('✅ Benutzer erfolgreich erstellt!','success');document.getElementById('user-modal').remove();setTimeout(()=>{window.location.reload();},1000);}else{this.showNotification(`❌ Fehler:${data.error}`,'error');}}catch(error){console.error('Fehler beim Erstellen des Benutzers:',error);this.showNotification('❌ Fehler beim Erstellen des Benutzers','error');}finally{submitBtn.innerHTML=originalText;submitBtn.disabled=false;}}
async updateUser(userId,formData){const submitBtn=document.getElementById('user-submit-btn');const originalText=submitBtn.innerHTML;try{submitBtn.innerHTML='<div class="animate-spin rounded-full h-5 w-5 border-b-2 border-white mx-auto"></div>';submitBtn.disabled=true;const userData={email:formData.get('email'),username:formData.get('username'),name:formData.get('name'),is_admin:formData.get('role')==='admin',is_active:formData.get('is_active')==='on'};const password=formData.get('password');if(password&&password.trim()){userData.password=password;}
const response=await fetch(`${this.apiBaseUrl}/api/admin/users/${userId}`,{method:'PUT',headers:{'Content-Type':'application/json','X-CSRFToken':this.csrfToken},body:JSON.stringify(userData)});const data=await response.json();if(data.success){this.showNotification('✅ Benutzer erfolgreich aktualisiert!','success');document.getElementById('user-modal').remove();setTimeout(()=>{window.location.reload();},1000);}else{this.showNotification(`❌ Fehler:${data.error}`,'error');}}catch(error){console.error('Fehler beim Aktualisieren des Benutzers:',error);this.showNotification('❌ Fehler beim Aktualisieren des Benutzers','error');}finally{submitBtn.innerHTML=originalText;submitBtn.disabled=false;}}
editUser(userId){console.log(`✏️ Benutzer ${userId}wird bearbeitet`);this.showUserModal(userId);}
async deleteUser(userId,userName){if(!confirm(`🗑️ Möchten Sie den Benutzer"${userName}"wirklich löschen?\n\nDiese Aktion kann nicht rückgängig gemacht werden!`)){return;}
try{this.showNotification(`🔄 Benutzer"${userName}"wird gelöscht...`,'info');const response=await fetch(`${this.apiBaseUrl}/api/admin/users/${userId}`,{method:'DELETE',headers:{'Content-Type':'application/json','X-CSRFToken':this.csrfToken}});const data=await response.json();if(data.success){this.showNotification(`✅ Benutzer"${userName}"erfolgreich gelöscht!`,'success');setTimeout(()=>{window.location.reload();},1000);}else{this.showNotification(`❌ Fehler beim Löschen:${data.error}`,'error');}}catch(error){console.error('Fehler beim Löschen des Benutzers:',error);this.showNotification('❌ Fehler beim Löschen des Benutzers','error');}}
showPrinterModal(){console.log('🖨️ Drucker-Modal wird angezeigt');this.showNotification('Drucker-Funktionen werden geladen...','info');}
managePrinter(printerId){console.log(`🔧 Drucker ${printerId}wird verwaltet`);this.showNotification(`Drucker ${printerId}wird verwaltet...`,'info');}
showPrinterSettings(printerId){console.log(`⚙️ Drucker-Einstellungen ${printerId}werden angezeigt`);this.showNotification(`Drucker-Einstellungen werden geladen...`,'info');}
async togglePrinterPower(printerId,printerName,button){console.log(`🔌 Smart-Plug Toggle für Drucker ${printerId}(${printerName})`);const confirmMessage=`Möchten Sie die Steckdose für"${printerName}"umschalten?\n\nDies schaltet den Drucker ein/aus.`;if(!confirm(confirmMessage))return;const originalContent=button.innerHTML;button.disabled=true;button.innerHTML=`<div class="animate-spin rounded-full h-4 w-4 border-2 border-white border-t-transparent"></div><span class="hidden lg:inline">Schaltet...</span>`;try{const response=await fetch(`${this.apiBaseUrl}/api/admin/printers/${printerId}/toggle`,{method:'POST',headers:{'Content-Type':'application/json','X-CSRFToken':this.csrfToken},body:JSON.stringify({reason:'Admin-Panel Smart-Plug Toggle'})});const data=await response.json();if(response.ok&&data.success){const action=data.action||'umgeschaltet';this.showNotification(`✅ Steckdose für"${printerName}"erfolgreich ${action}`,'success');button.classList.remove('from-orange-500','to-red-500');button.classList.add('from-green-500','to-green-600');setTimeout(()=>{button.classList.remove('from-green-500','to-green-600');button.classList.add('from-orange-500','to-red-500');},2000);setTimeout(()=>{this.loadLiveStats();},1000);}else{const errorMsg=data.error||'Unbekannter Fehler beim Schalten der Steckdose';this.showNotification(`❌ Fehler:${errorMsg}`,'error');console.error('Smart-Plug Toggle Fehler:',data);}}catch(error){console.error('Netzwerkfehler beim Smart-Plug Toggle:',error);this.showNotification(`❌ Netzwerkfehler beim Schalten der Steckdose für"${printerName}"`,'error');}finally{button.disabled=false;button.innerHTML=originalContent;}}
handleJobAction(action,jobId){console.log(`📋 Job-Aktion"${action}"für Job ${jobId}`);this.showNotification(`Job-Aktion"${action}"wird ausgeführt...`,'info');}
async checkSystemHealth(){try{const response=await fetch('/api/admin/system-health');const data=await response.json();if(data.success){this.updateHealthDisplay(data);this.updateErrorAlerts(data);}}catch(error){console.error('Fehler bei System-Health-Check:',error);}}
updateHealthDisplay(data){const statusIndicator=document.getElementById('db-status-indicator');const statusText=document.getElementById('db-status-text');if(statusIndicator&&statusText){if(data.health_status==='critical'){statusIndicator.className='w-3 h-3 bg-red-500 rounded-full animate-pulse';statusText.textContent='Kritisch';statusText.className='text-sm font-medium text-red-600 dark:text-red-400';}else if(data.health_status==='warning'){statusIndicator.className='w-3 h-3 bg-yellow-500 rounded-full animate-pulse';statusText.textContent='Warnung';statusText.className='text-sm font-medium text-yellow-600 dark:text-yellow-400';}else{statusIndicator.className='w-3 h-3 bg-green-400 rounded-full animate-pulse';statusText.textContent='Gesund';statusText.className='text-sm font-medium text-green-600 dark:text-green-400';}}
this.updateElement('last-migration',data.last_migration||'Unbekannt');this.updateElement('schema-integrity',data.schema_integrity||'Prüfung');this.updateElement('recent-errors-count',data.recent_errors_count||0);}
updateErrorAlerts(data){const alertContainer=document.getElementById('critical-errors-alert');if(!alertContainer)return;const allErrors=[...(data.critical_errors||[]),...(data.warnings||[])];if(allErrors.length>0){alertContainer.classList.remove('hidden');}else{alertContainer.classList.add('hidden');}}
async fixErrors(){if(!confirm('🔧 Möchten Sie die automatische Fehlerkorrektur durchführen?'))return;this.showNotification('🔄 Fehler werden automatisch behoben...','info');if(!this.csrfToken){console.error('❌ CSRF Token fehlt! Versuche Token neu zu laden...');this.csrfToken=this.extractCSRFToken();if(!this.csrfToken){this.showNotification('❌ Sicherheitsfehler: CSRF Token nicht verfügbar','error');return;}}
console.log('🔧 Starte automatische Fehlerkorrektur...');console.log('🔒 CSRF Token für Request:',this.csrfToken.substring(0,10)+'...');try{const requestOptions={method:'POST',headers:{'Content-Type':'application/json','X-CSRFToken':this.csrfToken}};console.log('📡 Sende Request an:','/api/admin/fix-errors');console.log('📝 Request Headers:',requestOptions.headers);const response=await fetch('/api/admin/fix-errors',requestOptions);console.log('📡 Response Status:',response.status);console.log('📡 Response Headers:',Object.fromEntries(response.headers.entries()));if(!response.ok){const errorText=await response.text();console.error('❌ Response Error:',errorText);throw new Error(`HTTP ${response.status}:${response.statusText}-${errorText}`);}
const data=await response.json();console.log('✅ Response Data:',data);if(data.success){this.showNotification('✅ Automatische Reparatur erfolgreich!','success');setTimeout(()=>this.checkSystemHealth(),2000);}else{this.showNotification(`❌ Automatische Reparatur fehlgeschlagen:${data.message||'Unbekannter Fehler'}`,'error');}}catch(error){console.error('❌ Fehler bei automatischer Reparatur:',error);this.showNotification(`❌ Fehler bei der automatischen Reparatur:${error.message}`,'error');}}
dismissErrors(){const alertContainer=document.getElementById('critical-errors-alert');if(alertContainer){alertContainer.classList.add('hidden');}}
showNotification(message,type='info'){let notification=document.getElementById('admin-notification');if(!notification){notification=document.createElement('div');notification.id='admin-notification';notification.className='fixed top-4 right-4 z-50 p-4 rounded-lg shadow-lg max-w-sm transition-all duration-300';document.body.appendChild(notification);}
const colors={success:'bg-green-500 text-white',error:'bg-red-500 text-white',info:'bg-blue-500 text-white',warning:'bg-yellow-500 text-white'};notification.className=`fixed top-4 right-4 z-50 p-4 rounded-lg shadow-lg max-w-sm transition-all duration-300 ${colors[type]}`;notification.textContent=message;notification.style.transform='translateX(0)';setTimeout(()=>{if(notification){notification.style.transform='translateX(100%)';setTimeout(()=>{if(notification&&notification.parentNode){notification.parentNode.removeChild(notification);}},300);}},3000);}
async loadLogs(level=null){const logsContainer=document.getElementById('logs-container');if(!logsContainer)return;logsContainer.innerHTML=`<div class="flex justify-center items-center py-12"><div class="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600 dark:border-blue-400"></div><span class="ml-3 text-slate-600 dark:text-slate-400">Logs werden geladen...</span></div>`;try{const filter=level||document.getElementById('log-level-filter')?.value||'all';const url=`${this.apiBaseUrl}/api/admin/logs?level=${filter}&limit=100`;const response=await fetch(url,{headers:{'X-CSRFToken':this.csrfToken}});if(!response.ok){throw new Error(`HTTP ${response.status}:${response.statusText}`);}
const data=await response.json();this.displayLogs(data.logs||[]);console.log('📋 Logs erfolgreich geladen');}catch(error){console.error('Fehler beim Laden der Logs:',error);logsContainer.innerHTML=`<div class="bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-xl p-6 text-center"><svg class="w-12 h-12 text-red-500 mx-auto mb-4"fill="none"stroke="currentColor"viewBox="0 0 24 24"><path stroke-linecap="round"stroke-linejoin="round"stroke-width="2"d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.732-.833-2.464 0L4.35 16.5c-.77.833.192 2.5 1.732 2.5z"/></svg><h3 class="text-lg font-semibold text-red-800 dark:text-red-200 mb-2">Fehler beim Laden der Logs</h3><p class="text-red-600 dark:text-red-400 mb-4">${error.message}</p><button onclick="adminDashboard.loadLogs()"class="px-4 py-2 bg-red-600 text-white rounded-lg hover:bg-red-700 transition-colors">🔄 Erneut versuchen</button></div>`;}}
displayLogs(logs){const logsContainer=document.getElementById('logs-container');if(!logsContainer)return;if(!logs||logs.length===0){logsContainer.innerHTML=`<div class="text-center py-12"><svg class="w-16 h-16 mx-auto text-slate-400 dark:text-slate-500 mb-4"fill="none"stroke="currentColor"viewBox="0 0 24 24"><path stroke-linecap="round"stroke-linejoin="round"stroke-width="2"d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/></svg><h3 class="text-lg font-medium text-slate-900 dark:text-white mb-2">Keine Logs gefunden</h3><p class="text-slate-500 dark:text-slate-400">Es sind keine Logs für die ausgewählten Kriterien vorhanden.</p></div>`;return;}
const logsHtml=logs.map(log=>{const levelColors={'error':'bg-red-50 dark:bg-red-900/20 border-red-200 dark:border-red-800 text-red-800 dark:text-red-200','warning':'bg-yellow-50 dark:bg-yellow-900/20 border-yellow-200 dark:border-yellow-800 text-yellow-800 dark:text-yellow-200','info':'bg-blue-50 dark:bg-blue-900/20 border-blue-200 dark:border-blue-800 text-blue-800 dark:text-blue-200','debug':'bg-gray-50 dark:bg-gray-900/20 border-gray-200 dark:border-gray-800 text-gray-800 dark:text-gray-200','critical':'bg-red-100 dark:bg-red-900/40 border-red-300 dark:border-red-700 text-red-900 dark:text-red-100'};const levelIcons={'error':'❌','warning':'⚠️','info':'','debug':'🔍','critical':'🚨'};const levelClass=levelColors[log.level]||levelColors['info'];const levelIcon=levelIcons[log.level]||'';return`<div class="border rounded-xl p-4 transition-all duration-200 hover:shadow-md ${levelClass}"><div class="flex items-start justify-between mb-2"><div class="flex items-center space-x-2"><span class="text-lg">${levelIcon}</span><span class="font-semibold text-sm uppercase tracking-wide">${log.level}</span><span class="text-xs opacity-75">${log.component||'System'}</span></div><div class="text-xs opacity-75">${this.formatLogTimestamp(log.timestamp)}</div></div><div class="mb-2"><p class="font-medium">${this.escapeHtml(log.message)}</p>${log.details?`<p class="text-sm opacity-75 mt-1">${this.escapeHtml(log.details)}</p>`:''}</div>${log.user?`<div class="text-xs opacity-75"><span class="font-medium">Benutzer:</span>${this.escapeHtml(log.user)}</div>`:''}
${log.ip_address?`<div class="text-xs opacity-75"><span class="font-medium">IP:</span>${this.escapeHtml(log.ip_address)}</div>`:''}
${log.request_id?`<div class="text-xs opacity-75 mt-2 font-mono"><span class="font-medium">Request-ID:</span>${this.escapeHtml(log.request_id)}</div>`:''}</div>`;}).join('');logsContainer.innerHTML=logsHtml;}
formatLogTimestamp(timestamp){if(!timestamp)return'Unbekannt';try{const date=new Date(timestamp);return date.toLocaleString('de-DE',{year:'numeric',month:'2-digit',day:'2-digit',hour:'2-digit',minute:'2-digit',second:'2-digit'});}catch(error){return timestamp;}}
escapeHtml(text){if(!text)return'';const div=document.createElement('div');div.textContent=text;return div.innerHTML;}
async exportLogs(){try{this.showNotification('📥 Logs werden exportiert...','info');const filter=document.getElementById('log-level-filter')?.value||'all';const url=`${this.apiBaseUrl}/api/admin/logs/export?level=${filter}`;const response=await fetch(url,{headers:{'X-CSRFToken':this.csrfToken}});if(!response.ok){throw new Error(`HTTP ${response.status}:${response.statusText}`);}
const blob=await response.blob();const downloadUrl=window.URL.createObjectURL(blob);const a=document.createElement('a');a.href=downloadUrl;a.download=`system-logs-${new Date().toISOString().split('T')[0]}.csv`;document.body.appendChild(a);a.click();document.body.removeChild(a);window.URL.revokeObjectURL(downloadUrl);this.showNotification('✅ Logs erfolgreich exportiert!','success');}catch(error){console.error('Fehler beim Exportieren der Logs:',error);this.showNotification('❌ Fehler beim Exportieren der Logs: '+error.message,'error');}}
testButtons(){console.log('🧪 Teste Button-Funktionalität...');const fixBtn=document.querySelector('#fix-errors-btn');if(fixBtn){console.log('✅ Fix-Errors Button gefunden:',fixBtn);console.log('🔗 Event-Listener-Status:',fixBtn.dataset.listenerAttached);fixBtn.addEventListener('click',(e)=>{console.log('🖱️ Fix-Errors Button wurde geklickt (manueller Listener)');e.preventDefault();e.stopPropagation();this.testFixErrors();});}else{console.error('❌ Fix-Errors Button NICHT gefunden!');}
const viewBtn=document.querySelector('#view-error-details-btn');if(viewBtn){console.log('✅ View-Details Button gefunden:',viewBtn);console.log('🔗 Event-Listener-Status:',viewBtn.dataset.listenerAttached);viewBtn.addEventListener('click',(e)=>{console.log('🖱️ View-Details Button wurde geklickt (manueller Listener)');e.preventDefault();e.stopPropagation();console.log('🔄 Weiterleitung zu Logs-Tab...');window.location.href='/admin-dashboard?tab=logs';});}else{console.error('❌ View-Details Button NICHT gefunden!');}}
async testFixErrors(){console.log('🧪 TEST: Fix-Errors wird ausgeführt...');console.log('🔒 Aktueller CSRF Token:',this.csrfToken);if(!this.csrfToken){console.error('❌ CSRF Token fehlt - versuche neu zu laden...');this.csrfToken=this.extractCSRFToken();console.log('🔒 Neu geladener Token:',this.csrfToken);}
this.showNotification('🧪 TEST: Starte automatische Fehlerkorrektur...','info');try{const requestOptions={method:'POST',headers:{'Content-Type':'application/json','X-CSRFToken':this.csrfToken,'X-Requested-With':'XMLHttpRequest'}};console.log('📡 TEST Request an:','/api/admin/fix-errors');console.log('📝 TEST Headers:',requestOptions.headers);const response=await fetch('/api/admin/fix-errors',requestOptions);console.log('📡 TEST Response Status:',response.status);console.log('📡 TEST Response Headers:',Object.fromEntries(response.headers.entries()));if(!response.ok){const errorText=await response.text();console.error('❌ TEST Response Error:',errorText);this.showNotification(`❌ TEST Fehler:${response.status}-${errorText}`,'error');return;}
const data=await response.json();console.log('✅ TEST Response Data:',data);if(data.success){this.showNotification('✅ TEST: Automatische Reparatur erfolgreich!','success');}else{this.showNotification(`❌ TEST:Reparatur fehlgeschlagen-${data.message}`,'error');}}catch(error){console.error('❌ TEST Fehler:',error);this.showNotification(`❌ TEST Netzwerk-Fehler:${error.message}`,'error');}}}
let adminDashboardInstance=null;document.addEventListener('DOMContentLoaded',function(){if(!adminDashboardInstance){adminDashboardInstance=new AdminDashboard();window.AdminDashboard=adminDashboardInstance;console.log('🎯 Admin Dashboard erfolgreich initialisiert (unified)');}});window.AdminDashboard=AdminDashboard;

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,79 @@
(function(){'use strict';window.MYP=window.MYP||{};window.MYP.Advanced=window.MYP.Advanced||{};class ProgressBar{constructor(container,options={}){this.container=typeof container==='string'?document.querySelector(container):container;this.options={value:0,max:100,showLabel:true,showPercentage:true,animated:true,color:'blue',size:'md',striped:false,...options};this.currentValue=this.options.value;this.init();}
init(){if(!this.container){console.error('ProgressBar: Container nicht gefunden');return;}
this.render();}
render(){const percentage=Math.round((this.currentValue/this.options.max)*100);const sizeClass=this.getSizeClass();const colorClass=this.getColorClass();this.container.innerHTML=`<div class="progress-bar-container ${sizeClass}">${this.options.showLabel?`<div class="flex justify-between items-center mb-2"><span class="text-sm font-medium text-slate-700 dark:text-slate-300">${this.options.label||'Fortschritt'}</span>${this.options.showPercentage?`<span class="text-sm font-medium text-slate-700 dark:text-slate-300">${percentage}%</span>`:''}</div>`:''}<div class="progress-bar-track ${sizeClass}"><div class="progress-bar-fill ${colorClass} ${this.options.animated ? 'animated' : ''} ${this.options.striped ? 'striped' : ''}"
style="width: ${percentage}%"
role="progressbar"
aria-valuenow="${this.currentValue}"
aria-valuemin="0"
aria-valuemax="${this.options.max}"></div></div></div>`;}
getSizeClass(){const sizes={'sm':'h-2','md':'h-3','lg':'h-4','xl':'h-6'};return sizes[this.options.size]||sizes.md;}
getColorClass(){const colors={'blue':'bg-blue-500','green':'bg-green-500','red':'bg-red-500','yellow':'bg-yellow-500','purple':'bg-purple-500','indigo':'bg-indigo-500'};return colors[this.options.color]||colors.blue;}
setValue(value,animate=true){const oldValue=this.currentValue;this.currentValue=Math.max(0,Math.min(this.options.max,value));if(animate){this.animateToValue(oldValue,this.currentValue);}else{this.render();}}
animateToValue(from,to){const duration=500;const steps=60;const stepValue=(to-from)/steps;let currentStep=0;const animate=()=>{if(currentStep<steps){this.currentValue=from+(stepValue*currentStep);this.render();currentStep++;requestAnimationFrame(animate);}else{this.currentValue=to;this.render();}};animate();}
increment(amount=1){this.setValue(this.currentValue+amount);}
decrement(amount=1){this.setValue(this.currentValue-amount);}
reset(){this.setValue(0);}
complete(){this.setValue(this.options.max);}}
class FileUpload{constructor(container,options={}){this.container=typeof container==='string'?document.querySelector(container):container;this.options={multiple:false,accept:'*/*',maxSize:50*1024*1024,maxFiles:10,dragDrop:true,showProgress:true,showPreview:true,uploadUrl:'/api/upload',chunkSize:1024*1024,...options};this.files=[];this.uploads=new Map();this.init();}
init(){if(!this.container){console.error('FileUpload: Container nicht gefunden');return;}
this.render();this.setupEventListeners();}
render(){this.container.innerHTML=`<div class="file-upload-area"id="fileUploadArea"><div class="file-upload-dropzone ${this.options.dragDrop ? 'drag-enabled' : ''}"id="dropzone"><div class="text-center py-12"><svg class="mx-auto h-12 w-12 text-slate-400"stroke="currentColor"fill="none"viewBox="0 0 48 48"><path d="M28 8H12a4 4 0 00-4 4v20m32-12v8m0 0v8a4 4 0 01-4 4H12a4 4 0 01-4-4v-4m32-4l-3.172-3.172a4 4 0 00-5.656 0L28 28M8 32l9.172-9.172a4 4 0 015.656 0L28 28m0 0l4 4m4-24h8m-4-4v8m-12 4h.02"stroke-width="2"stroke-linecap="round"stroke-linejoin="round"/></svg><div class="mt-4"><label for="fileInput"class="cursor-pointer"><span class="mt-2 block text-sm font-medium text-slate-900 dark:text-white">${this.options.dragDrop?'Dateien hierher ziehen oder':''}<span class="text-blue-600 dark:text-blue-400 hover:text-blue-500">durchsuchen</span></span></label><input type="file"
id="fileInput"
name="files"
${this.options.multiple?'multiple':''}
accept="${this.options.accept}"
class="sr-only"></div><p class="mt-1 text-xs text-slate-500 dark:text-slate-400">${this.getFileTypeText()}• Max.${this.formatFileSize(this.options.maxSize)}</p></div></div><div class="file-list mt-4"id="fileList"></div></div>`;}
setupEventListeners(){const fileInput=this.container.querySelector('#fileInput');const dropzone=this.container.querySelector('#dropzone');fileInput.addEventListener('change',(e)=>{this.handleFiles(Array.from(e.target.files));});if(this.options.dragDrop){dropzone.addEventListener('dragover',(e)=>{e.preventDefault();dropzone.classList.add('drag-over');});dropzone.addEventListener('dragleave',(e)=>{e.preventDefault();dropzone.classList.remove('drag-over');});dropzone.addEventListener('drop',(e)=>{e.preventDefault();dropzone.classList.remove('drag-over');this.handleFiles(Array.from(e.dataTransfer.files));});}}
handleFiles(fileList){for(const file of fileList){if(this.validateFile(file)){this.addFile(file);}}
this.renderFileList();}
validateFile(file){if(file.size>this.options.maxSize){this.showError(`Datei"${file.name}"ist zu groß.Maximum:${this.formatFileSize(this.options.maxSize)}`);return false;}
if(!this.options.multiple&&this.files.length>0){this.files=[];}else if(this.files.length>=this.options.maxFiles){this.showError(`Maximal ${this.options.maxFiles}Dateien erlaubt`);return false;}
return true;}
addFile(file){const fileData={id:this.generateId(),file:file,name:file.name,size:file.size,type:file.type,status:'pending',progress:0,error:null};this.files.push(fileData);if(this.options.showPreview&&file.type.startsWith('image/')){this.generatePreview(fileData);}}
generatePreview(fileData){const reader=new FileReader();reader.onload=(e)=>{fileData.preview=e.target.result;this.renderFileList();};reader.readAsDataURL(fileData.file);}
renderFileList(){const fileListContainer=this.container.querySelector('#fileList');if(this.files.length===0){fileListContainer.innerHTML='';return;}
fileListContainer.innerHTML=this.files.map(fileData=>`<div class="file-item"data-file-id="${fileData.id}"><div class="flex items-center space-x-4 p-4 bg-slate-50 dark:bg-slate-800 rounded-lg">${fileData.preview?`<img src="${fileData.preview}"class="w-12 h-12 object-cover rounded"alt="Preview">`:`<div class="w-12 h-12 bg-slate-200 dark:bg-slate-700 rounded flex items-center justify-center"><svg class="w-6 h-6 text-slate-400"fill="currentColor"viewBox="0 0 20 20"><path fill-rule="evenodd"d="M4 4a2 2 0 012-2h4.586A2 2 0 0112 2.586L15.414 6A2 2 0 0116 7.414V16a2 2 0 01-2 2H6a2 2 0 01-2-2V4z"clip-rule="evenodd"/></svg></div>`}<div class="flex-1 min-w-0"><div class="flex items-center justify-between"><p class="text-sm font-medium text-slate-900 dark:text-white truncate">${fileData.name}</p><button class="remove-file text-slate-400 hover:text-red-500"data-file-id="${fileData.id}"><svg class="w-4 h-4"fill="currentColor"viewBox="0 0 20 20"><path fill-rule="evenodd"d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z"clip-rule="evenodd"/></svg></button></div><p class="text-xs text-slate-500 dark:text-slate-400">${this.formatFileSize(fileData.size)}${this.getStatusText(fileData.status)}</p>${this.options.showProgress&&fileData.status==='uploading'?`<div class="mt-2"><div class="w-full bg-slate-200 dark:bg-slate-600 rounded-full h-2"><div class="bg-blue-500 h-2 rounded-full transition-all duration-300"
style="width: ${fileData.progress}%"></div></div></div>`:''}
${fileData.error?`<p class="text-xs text-red-500 mt-1">${fileData.error}</p>`:''}</div></div></div>`).join('');fileListContainer.querySelectorAll('.remove-file').forEach(button=>{button.addEventListener('click',(e)=>{const fileId=e.target.closest('.remove-file').dataset.fileId;this.removeFile(fileId);});});}
removeFile(fileId){this.files=this.files.filter(f=>f.id!==fileId);this.renderFileList();}
async uploadFiles(){const pendingFiles=this.files.filter(f=>f.status==='pending');for(const fileData of pendingFiles){await this.uploadFile(fileData);}}
async uploadFile(fileData){fileData.status='uploading';fileData.progress=0;this.renderFileList();try{const formData=new FormData();formData.append('file',fileData.file);const response=await fetch(this.options.uploadUrl,{method:'POST',body:formData,headers:{'X-CSRFToken':document.querySelector('meta[name="csrf-token"]')?.getAttribute('content')||''}});if(response.ok){fileData.status='completed';fileData.progress=100;const result=await response.json();fileData.url=result.url;}else{throw new Error(`Upload fehlgeschlagen:${response.status}`);}}catch(error){fileData.status='error';fileData.error=error.message;}
this.renderFileList();}
getFileTypeText(){if(this.options.accept==='*/*')return'Alle Dateitypen';if(this.options.accept.includes('image/'))return'Bilder';if(this.options.accept.includes('.pdf'))return'PDF-Dateien';return'Spezifische Dateitypen';}
getStatusText(status){const statusTexts={'pending':'Wartend','uploading':'Wird hochgeladen...','completed':'Abgeschlossen','error':'Fehler'};return statusTexts[status]||status;}
formatFileSize(bytes){if(bytes===0)return'0 Bytes';const k=1024;const sizes=['Bytes','KB','MB','GB'];const i=Math.floor(Math.log(bytes)/Math.log(k));return parseFloat((bytes/Math.pow(k,i)).toFixed(2))+' '+sizes[i];}
generateId(){return'file_'+Math.random().toString(36).substr(2,9);}
showError(message){if(window.showToast){window.showToast(message,'error');}else{alert(message);}}
getFiles(){return this.files;}
getCompletedFiles(){return this.files.filter(f=>f.status==='completed');}
clear(){this.files=[];this.renderFileList();}}
class DatePicker{constructor(input,options={}){this.input=typeof input==='string'?document.querySelector(input):input;this.options={format:'dd.mm.yyyy',minDate:null,maxDate:null,disabledDates:[],language:'de',closeOnSelect:true,showWeekNumbers:false,...options};this.isOpen=false;this.currentDate=new Date();this.selectedDate=null;this.init();}
init(){if(!this.input){console.error('DatePicker: Input-Element nicht gefunden');return;}
this.setupInput();this.createCalendar();this.setupEventListeners();}
setupInput(){this.input.setAttribute('readonly','true');this.input.classList.add('datepicker-input');this.container=document.createElement('div');this.container.className='datepicker-container relative';this.input.parentNode.insertBefore(this.container,this.input);this.container.appendChild(this.input);}
createCalendar(){this.calendar=document.createElement('div');this.calendar.className='datepicker-calendar absolute top-full left-0 mt-1 bg-white dark:bg-slate-800 border border-slate-200 dark:border-slate-600 rounded-lg shadow-lg z-50 hidden';this.calendar.innerHTML=this.renderCalendar();this.container.appendChild(this.calendar);}
renderCalendar(){const year=this.currentDate.getFullYear();const month=this.currentDate.getMonth();const monthName=this.getMonthName(month);return`<div class="datepicker-header p-4 border-b border-slate-200 dark:border-slate-600"><div class="flex items-center justify-between"><button type="button"class="prev-month p-1 hover:bg-slate-100 dark:hover:bg-slate-700 rounded"><svg class="w-4 h-4"fill="currentColor"viewBox="0 0 20 20"><path fill-rule="evenodd"d="M12.707 5.293a1 1 0 010 1.414L9.414 10l3.293 3.293a1 1 0 01-1.414 1.414l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 0z"clip-rule="evenodd"/></svg></button><div class="text-sm font-medium text-slate-900 dark:text-white">${monthName}${year}</div><button type="button"class="next-month p-1 hover:bg-slate-100 dark:hover:bg-slate-700 rounded"><svg class="w-4 h-4"fill="currentColor"viewBox="0 0 20 20"><path fill-rule="evenodd"d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z"clip-rule="evenodd"/></svg></button></div></div><div class="datepicker-body p-4"><div class="grid grid-cols-7 gap-1 mb-2">${this.getWeekdayHeaders()}</div><div class="grid grid-cols-7 gap-1">${this.getDaysOfMonth(year,month)}</div></div>`;}
getWeekdayHeaders(){const weekdays=['Mo','Di','Mi','Do','Fr','Sa','So'];return weekdays.map(day=>`<div class="text-xs font-medium text-slate-500 dark:text-slate-400 text-center p-1">${day}</div>`).join('');}
getDaysOfMonth(year,month){const firstDay=new Date(year,month,1);const lastDay=new Date(year,month+1,0);const startDate=new Date(firstDay);startDate.setDate(startDate.getDate()-((firstDay.getDay()+6)%7));const days=[];const current=new Date(startDate);while(current<=lastDay||current.getMonth()===month){const isCurrentMonth=current.getMonth()===month;const isToday=this.isToday(current);const isSelected=this.isSelectedDate(current);const isDisabled=this.isDisabledDate(current);const classes=['w-8 h-8 text-sm rounded cursor-pointer flex items-center justify-center transition-colors',isCurrentMonth?'text-slate-900 dark:text-white':'text-slate-400 dark:text-slate-600',isToday?'bg-blue-100 dark:bg-blue-900 text-blue-900 dark:text-blue-100':'',isSelected?'bg-blue-500 text-white':'',!isDisabled&&isCurrentMonth?'hover:bg-slate-100 dark:hover:bg-slate-700':'',isDisabled?'cursor-not-allowed opacity-50':''].filter(Boolean);days.push(`<div class="${classes.join(' ')}"
data-date="${this.formatDateForData(current)}"
${isDisabled?'':'data-selectable="true"'}>${current.getDate()}</div>`);current.setDate(current.getDate()+1);if(days.length>=42)break;}
return days.join('');}
setupEventListeners(){this.input.addEventListener('click',()=>{this.toggle();});this.calendar.addEventListener('click',(e)=>{if(e.target.classList.contains('prev-month')){this.previousMonth();}else if(e.target.classList.contains('next-month')){this.nextMonth();}else if(e.target.dataset.selectable){this.selectDate(new Date(e.target.dataset.date));}});document.addEventListener('click',(e)=>{if(!this.container.contains(e.target)){this.close();}});}
toggle(){if(this.isOpen){this.close();}else{this.open();}}
open(){this.calendar.classList.remove('hidden');this.isOpen=true;this.updateCalendar();}
close(){this.calendar.classList.add('hidden');this.isOpen=false;}
selectDate(date){this.selectedDate=new Date(date);this.input.value=this.formatDate(date);this.input.dispatchEvent(new CustomEvent('dateselected',{detail:{date:new Date(date)}}));if(this.options.closeOnSelect){this.close();}}
previousMonth(){this.currentDate.setMonth(this.currentDate.getMonth()-1);this.updateCalendar();}
nextMonth(){this.currentDate.setMonth(this.currentDate.getMonth()+1);this.updateCalendar();}
updateCalendar(){this.calendar.innerHTML=this.renderCalendar();}
isToday(date){const today=new Date();return date.toDateString()===today.toDateString();}
isSelectedDate(date){return this.selectedDate&&date.toDateString()===this.selectedDate.toDateString();}
isDisabledDate(date){if(this.options.minDate&&date<this.options.minDate)return true;if(this.options.maxDate&&date>this.options.maxDate)return true;return this.options.disabledDates.some(disabled=>date.toDateString()===disabled.toDateString());}
formatDate(date){const day=date.getDate().toString().padStart(2,'0');const month=(date.getMonth()+1).toString().padStart(2,'0');const year=date.getFullYear();return this.options.format.replace('dd',day).replace('mm',month).replace('yyyy',year);}
formatDateForData(date){return date.toISOString().split('T')[0];}
getMonthName(monthIndex){const months=['Januar','Februar','März','April','Mai','Juni','Juli','August','September','Oktober','November','Dezember'];return months[monthIndex];}
setValue(date){if(date){this.selectDate(new Date(date));}}
getValue(){return this.selectedDate;}
clear(){this.selectedDate=null;this.input.value='';}}
window.MYP.Advanced={ProgressBar,FileUpload,DatePicker,createProgressBar:(container,options)=>new ProgressBar(container,options),createFileUpload:(container,options)=>new FileUpload(container,options),createDatePicker:(input,options)=>new DatePicker(input,options)};document.addEventListener('DOMContentLoaded',function(){document.querySelectorAll('[data-datepicker]').forEach(input=>{const options=JSON.parse(input.dataset.datepicker||'{}');new DatePicker(input,options);});document.querySelectorAll('[data-file-upload]').forEach(container=>{const options=JSON.parse(container.dataset.fileUpload||'{}');new FileUpload(container,options);});console.log('🚀 MYP Advanced Components geladen');});})();

Binary file not shown.

Binary file not shown.

14
backend/static/js/auto-logout.min.js vendored Normal file
View File

@ -0,0 +1,14 @@
class AutoLogoutManager{constructor(){this.timer=null;this.warningTimer=null;this.timeout=60;this.warningTime=5;this.isWarningShown=false;this.init();}
async init(){await this.loadSettings();this.setupActivityListeners();this.startTimer();}
async loadSettings(){try{const response=await fetch('/api/user/settings');if(response.ok){const data=await response.json();if(data.success&&data.settings.privacy?.auto_logout){const timeout=parseInt(data.settings.privacy.auto_logout);if(timeout>0&&timeout!=='never'){this.timeout=timeout;}else{this.timeout=0;}}}}catch(error){console.warn('Auto-Logout-Einstellungen konnten nicht geladen werden:',error);}}
setupActivityListeners(){const events=['mousedown','mousemove','keypress','scroll','touchstart','click'];events.forEach(event=>{document.addEventListener(event,()=>this.resetTimer(),{passive:true});});}
startTimer(){if(this.timeout<=0)return;this.clearTimers();const timeoutMs=this.timeout*60*1000;const warningMs=this.warningTime*60*1000;this.warningTimer=setTimeout(()=>this.showWarning(),timeoutMs-warningMs);this.timer=setTimeout(()=>this.performLogout(),timeoutMs);}
resetTimer(){if(this.isWarningShown){this.closeWarning();}
this.startTimer();}
clearTimers(){if(this.timer)clearTimeout(this.timer);if(this.warningTimer)clearTimeout(this.warningTimer);}
showWarning(){if(this.isWarningShown)return;this.isWarningShown=true;const modal=document.createElement('div');modal.id='auto-logout-warning';modal.className='fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50';modal.innerHTML=`<div class="bg-white dark:bg-slate-800 rounded-lg p-6 max-w-md mx-4 shadow-xl"><h3 class="text-lg font-medium text-slate-900 dark:text-white mb-4">Automatische Abmeldung</h3><p class="text-sm text-slate-600 dark:text-slate-300 mb-4">Sie werden in ${this.warningTime}Minuten aufgrund von Inaktivität abgemeldet.</p><div class="flex space-x-3"><button id="stay-logged-in"class="bg-blue-600 text-white px-4 py-2 rounded-lg">Angemeldet bleiben</button><button id="logout-now"class="bg-gray-300 text-slate-700 px-4 py-2 rounded-lg">Jetzt abmelden</button></div></div>`;document.body.appendChild(modal);document.getElementById('stay-logged-in').onclick=()=>{this.closeWarning();this.sendKeepAlive();this.resetTimer();};document.getElementById('logout-now').onclick=()=>{this.performLogout();};}
closeWarning(){const modal=document.getElementById('auto-logout-warning');if(modal)modal.remove();this.isWarningShown=false;}
async sendKeepAlive(){try{await fetch('/api/auth/keep-alive',{method:'POST',headers:{'Content-Type':'application/json','X-CSRFToken':this.getCSRFToken()}});}catch(error){console.warn('Keep-Alive fehlgeschlagen:',error);}}
getCSRFToken(){const metaTag=document.querySelector('meta[name="csrf-token"]');return metaTag?metaTag.getAttribute('content'):'';}
async performLogout(){this.closeWarning();this.clearTimers();window.location.href='/auth/logout';}}
document.addEventListener('DOMContentLoaded',function(){if(!window.location.pathname.includes('/login')){window.autoLogoutManager=new AutoLogoutManager();}});

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More