559 lines
25 KiB
HTML

{% extends "base.html" %}
{% block title %}Dashboard - Mercedes-Benz MYP Platform{% endblock %}
{% block extra_css %}
<style>
/* Professional Mercedes-Benz Dashboard Styles - Responsive Enhanced */
.mb-dashboard-card {
background: white;
border: 1px solid #e5e7eb;
border-radius: clamp(0.75rem, 2vw, 1rem);
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06);
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
position: relative;
overflow: hidden;
}
.dark .mb-dashboard-card {
background: var(--mb-black, #000000);
border-color: var(--border-primary, #334155);
}
.mb-dashboard-card:hover {
transform: translateY(-2px);
box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.04);
}
@media (min-width: 768px) {
.mb-dashboard-card:hover {
transform: translateY(-4px);
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
}
}
.dark .mb-dashboard-card:hover {
box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.3), 0 4px 6px -2px rgba(0, 0, 0, 0.2);
}
@media (min-width: 768px) {
.dark .mb-dashboard-card:hover {
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.3), 0 10px 10px -5px rgba(0, 0, 0, 0.2);
}
}
.mb-stat-card {
background: linear-gradient(135deg, #f0f9ff 0%, #e6f2ff 100%);
color: #0f172a;
position: relative;
overflow: hidden;
border: none;
}
.dark .mb-stat-card {
background: linear-gradient(135deg, #000000 0%, #111827 100%);
color: var(--text-primary, #f8fafc);
}
.mb-stat-card::before {
content: '';
position: absolute;
top: -50%;
right: -50%;
width: 200%;
height: 200%;
background: radial-gradient(circle, rgba(0, 114, 206, 0.1) 0%, transparent 70%);
animation: pulse-glow 4s ease-in-out infinite;
}
.dark .mb-stat-card::before {
background: radial-gradient(circle, rgba(255, 255, 255, 0.05) 0%, transparent 70%);
}
@keyframes pulse-glow {
0%, 100% { opacity: 0.5; transform: scale(1); }
50% { opacity: 0.8; transform: scale(1.05); }
}
.mb-stat-icon {
background: rgba(0, 0, 0, 0.1);
backdrop-filter: blur(10px);
border-radius: clamp(0.75rem, 2vw, 1rem);
padding: clamp(0.75rem, 2vw, 1rem);
display: inline-flex;
align-items: center;
justify-content: center;
position: relative;
z-index: 10;
transition: all 0.3s ease;
}
.dark .mb-stat-icon {
background: rgba(255, 255, 255, 0.1);
}
.mb-stat-card:hover .mb-stat-icon {
transform: scale(1.1);
background: rgba(0, 0, 0, 0.2);
}
.dark .mb-stat-card:hover .mb-stat-icon {
background: rgba(255, 255, 255, 0.15);
}
.mb-status-indicator {
width: clamp(0.75rem, 2vw, 0.875rem);
height: clamp(0.75rem, 2vw, 0.875rem);
border-radius: 50%;
position: relative;
display: inline-block;
}
.mb-status-indicator::after {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
border-radius: 50%;
animation: status-pulse 2s infinite;
}
.mb-status-online {
background: #10b981;
}
.mb-status-online::after {
background: #10b981;
}
.mb-status-busy {
background: #f59e0b;
}
.mb-status-busy::after {
background: #f59e0b;
}
.mb-status-offline {
background: #ef4444;
}
.mb-status-offline::after {
background: #ef4444;
}
.mb-status-idle {
background: #6b7280;
}
.mb-status-idle::after {
background: #6b7280;
}
@keyframes status-pulse {
0% { transform: scale(1); opacity: 1; }
50% { transform: scale(1.5); opacity: 0.5; }
100% { transform: scale(2); opacity: 0; }
}
.mb-progress-container {
background: #f3f4f6;
height: clamp(0.5rem, 1.5vw, 0.625rem);
border-radius: clamp(0.25rem, 1vw, 0.375rem);
overflow: hidden;
position: relative;
}
.dark .mb-progress-container {
background: #374151;
}
.mb-progress-bar {
background: linear-gradient(90deg, #000000 0%, #333333 100%);
height: 100%;
border-radius: clamp(0.25rem, 1vw, 0.375rem);
transition: width 0.8s cubic-bezier(0.4, 0, 0.2, 1);
position: relative;
overflow: hidden;
}
.dark .mb-progress-bar {
background: linear-gradient(90deg, #f3f4f6 0%, #e5e7eb 100%);
}
.mb-progress-bar::after {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.3), transparent);
animation: progress-shine 2s infinite;
}
@keyframes progress-shine {
0% { transform: translateX(-100%); }
100% { transform: translateX(100%); }
}
.mb-activity-item {
border-left: clamp(2px, 0.5vw, 4px) solid #000000;
background: linear-gradient(90deg, rgba(0, 0, 0, 0.05) 0%, transparent 100%);
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
border-radius: 0 clamp(0.5rem, 1.5vw, 0.75rem) clamp(0.5rem, 1.5vw, 0.75rem) 0;
}
.dark .mb-activity-item {
border-left-color: #f3f4f6;
background: linear-gradient(90deg, rgba(255, 255, 255, 0.02) 0%, transparent 100%);
}
/* Responsive Text Sizes */
.section-title {
color: #1e293b;
font-size: clamp(1rem, 3vw, 1.25rem);
font-weight: 600;
margin-bottom: clamp(0.75rem, 2vw, 1rem);
}
.dark .section-title {
color: #ffffff;
}
.stat-value {
color: #0f172a;
font-weight: 700;
font-size: clamp(1.25rem, 4vw, 1.5rem);
}
.dark .stat-value {
color: #ffffff;
}
.stat-label {
color: #64748b;
font-size: clamp(0.75rem, 2.5vw, 0.875rem);
}
.dark .stat-label {
color: #94a3b8;
}
/* Mobile Optimizations */
@media (max-width: 767px) {
.mb-dashboard-card {
margin: 0.5rem;
padding: 1rem;
}
.mb-stat-icon {
padding: 0.75rem;
}
.mb-progress-container {
height: 0.5rem;
}
.mb-activity-item {
border-left-width: 2px;
border-radius: 0 0.5rem 0.5rem 0;
}
}
/* Tablet Optimizations */
@media (min-width: 768px) and (max-width: 1023px) {
.mb-dashboard-card {
margin: 0.75rem;
padding: 1.25rem;
}
.mb-stat-icon {
padding: 0.875rem;
}
.mb-progress-container {
height: 0.625rem;
}
}
/* Desktop Optimizations */
@media (min-width: 1024px) {
.mb-dashboard-card {
margin: 1rem;
padding: 1.5rem;
}
.mb-stat-icon {
padding: 1rem;
}
.mb-progress-container {
height: 0.75rem;
}
}
</style>
{% endblock %}
{% block content %}
<div class="responsive-container space-y-6 sm:space-y-8">
<!-- Dashboard Header Card - Responsive -->
<div class="dashboard-card">
<div class="flex flex-col gap-4 sm:gap-6 md:flex-row md:items-center md:justify-between">
<div class="flex items-center gap-4 sm:gap-6">
<div class="w-12 h-12 sm:w-14 sm:h-14 md:w-16 md:h-16 flex-shrink-0">
<svg class="w-full h-full text-slate-900 dark:text-white" fill="currentColor" viewBox="0 0 80 80" aria-hidden="true">
<path d="M58.6,4.5C53,1.6,46.7,0,40,0c-6.7,0-13,1.6-18.6,4.5v0C8.7,11.2,0,24.6,0,40c0,15.4,8.7,28.8,21.5,35.5
C27,78.3,33.3,80,40,80c6.7,0,12.9-1.7,18.5-4.6C71.3,68.8,80,55.4,80,40C80,24.6,71.3,11.2,58.6,4.5z M4,40
c0-13.1,7-24.5,17.5-30.9v0C26.6,6,32.5,4.2,39,4l-4.5,32.7L21.5,46.8v0L8.3,57.1C5.6,52,4,46.2,4,40z M58.6,70.8
C53.1,74.1,46.8,76,40,76c-6.8,0-13.2-1.9-18.6-5.2c-4.9-2.9-8.9-6.9-11.9-11.7l11.9-4.9v0L40,46.6l18.6,7.5v0l12,4.9
C67.6,63.9,63.4,67.9,58.6,70.8z M58.6,46.8L58.6,46.8l-12.9-10L41.1,4c6.3,0.2,12.3,2,17.4,5.1v0C69,15.4,76,26.9,76,40
c0,6.2-1.5,12-4.3,17.1L58.6,46.8z"/>
</svg>
</div>
<div>
<h1 class="text-responsive-xl font-bold text-slate-900 dark:text-white tracking-tight">Dashboard</h1>
<p class="text-slate-500 dark:text-slate-400 mt-1 text-responsive">Übersicht über Ihre 3D-Druck Aktivitäten</p>
</div>
</div>
<div class="flex flex-wrap gap-2 sm:gap-3">
<button id="refreshDashboard"
class="btn-secondary flex items-center gap-2 touch-target">
<svg class="w-4 h-4 sm:w-5 sm:h-5" 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>
<span class="hidden sm:inline">Aktualisieren</span>
</button>
<a href="{{ url_for('jobs_page') }}"
class="btn-primary flex items-center gap-2 touch-target">
<svg class="w-4 h-4 sm:w-5 sm:h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6" />
</svg>
<span class="hidden sm:inline">Neuer Auftrag</span>
<span class="sm:hidden">Neu</span>
</a>
<a href="{{ url_for('guest.guest_requests_overview') }}"
class="btn-secondary flex items-center gap-2 touch-target">
<svg class="w-4 h-4 sm:w-5 sm:h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<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>
<span class="hidden sm:inline">Anträge Übersicht</span>
<span class="sm:hidden">Anträge</span>
</a>
</div>
</div>
</div>
<!-- Stats Overview Cards - Responsive Grid -->
<div class="responsive-grid-4">
<!-- Active Jobs Card -->
<div class="dashboard-card">
<div class="flex justify-between items-start">
<div>
<h3 class="stat-label">Aktive Aufträge</h3>
<div class="stat-value">{{ active_jobs_count }}</div>
</div>
<div class="mb-stat-icon text-slate-900 dark:text-white">
<svg class="w-5 h-5 sm:w-6 sm:h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-3 7h3m-3 4h3m-6-4h.01M9 16h.01" />
</svg>
</div>
</div>
</div>
<!-- Available Printers Card -->
<div class="dashboard-card">
<div class="flex justify-between items-start">
<div>
<h3 class="stat-label">Verfügbare Drucker</h3>
<div class="stat-value">{{ available_printers_count }}</div>
</div>
<div class="mb-stat-icon text-green-600 dark:text-green-400">
<svg class="w-5 h-5 sm:w-6 sm:h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 12h14M5 12a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v4a2 2 0 01-2 2M5 12a2 2 0 00-2 2v4a2 2 0 002 2h14a2 2 0 002-2v-4a2 2 0 00-2-2m-2-4h.01M17 16h.01" />
</svg>
</div>
</div>
</div>
<!-- Total Jobs Card -->
<div class="dashboard-card">
<div class="flex justify-between items-start">
<div>
<h3 class="stat-label">Aufträge (gesamt)</h3>
<div class="stat-value">{{ total_jobs_count }}</div>
</div>
<div class="mb-stat-icon text-purple-600 dark:text-purple-400">
<svg class="w-5 h-5 sm:w-6 sm:h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 17v-2m3 2v-4m3 4v-6m2 10H7a2 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>
</div>
</div>
</div>
<!-- Success Rate Card -->
<div class="dashboard-card">
<div class="flex justify-between items-start">
<div>
<h3 class="stat-label">Erfolgsrate</h3>
<div class="stat-value">{{ success_rate }}%</div>
</div>
<div class="mb-stat-icon text-amber-600 dark:text-amber-400">
<svg class="w-5 h-5 sm:w-6 sm:h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11.049 2.927c.3-.921 1.603-.921 1.902 0l1.519 4.674a1 1 0 00.95.69h4.915c.969 0 1.371 1.24.588 1.81l-3.976 2.888a1 1 0 00-.363 1.118l1.518 4.674c.3.922-.755 1.688-1.538 1.118l-3.976-2.888a1 1 0 00-1.176 0l-3.976 2.888c-.783.57-1.838-.197-1.538-1.118l1.518-4.674a1 1 0 00-.363-1.118l-3.976-2.888c-.784-.57-.38-1.81.588-1.81h4.914a1 1 0 00.951-.69l1.519-4.674z" />
</svg>
</div>
</div>
</div>
</div>
<!-- Active Jobs Section - Responsive -->
<div class="dashboard-card">
<h2 class="section-title">Aktuelle Druckaufträge</h2>
<div class="table-container">
<table class="table-styled">
<thead>
<tr class="text-left text-xs font-medium text-slate-500 dark:text-slate-400 uppercase tracking-wider">
<th class="px-3 sm:px-6 py-2 sm:py-3">Status</th>
<th class="px-3 sm:px-6 py-2 sm:py-3">Auftrag</th>
<th class="px-3 sm:px-6 py-2 sm:py-3 hidden sm:table-cell">Drucker</th>
<th class="px-3 sm:px-6 py-2 sm:py-3 hidden md:table-cell">Startzeit</th>
<th class="px-3 sm:px-6 py-2 sm:py-3">Fortschritt</th>
<th class="px-3 sm:px-6 py-2 sm:py-3">Aktionen</th>
</tr>
</thead>
<tbody class="bg-white dark:bg-slate-800 divide-y divide-gray-200 dark:divide-slate-700">
{% if active_jobs and active_jobs|length > 0 %}
{% for job in active_jobs %}
<tr class="hover:bg-gray-50 dark:hover:bg-slate-700/50">
<td class="px-3 sm:px-6 py-2 sm:py-4 whitespace-nowrap">
<div class="flex items-center">
<div class="mb-status-indicator {{ job.status_class }}"></div>
<span class="ml-2 text-xs sm:text-sm font-medium text-slate-700 dark:text-slate-300">
<span class="hidden sm:inline">{{ job.status_text }}</span>
<span class="sm:hidden">{{ job.status_text[:3] }}</span>
</span>
</div>
</td>
<td class="px-3 sm:px-6 py-2 sm:py-4">
<div class="text-xs sm:text-sm font-medium text-slate-900 dark:text-white truncate max-w-32 sm:max-w-none">{{ job.name }}</div>
<div class="text-xs text-slate-500 dark:text-slate-400 truncate max-w-32 sm:max-w-none hidden sm:block">{{ job.file_name }}</div>
</td>
<td class="px-3 sm:px-6 py-2 sm:py-4 whitespace-nowrap hidden sm:table-cell">
<div class="text-xs sm:text-sm text-slate-700 dark:text-slate-300">{{ job.printer }}</div>
</td>
<td class="px-3 sm:px-6 py-2 sm:py-4 whitespace-nowrap hidden md:table-cell">
<div class="text-xs sm:text-sm text-slate-700 dark:text-slate-300">{{ job.start_time }}</div>
</td>
<td class="px-3 sm:px-6 py-2 sm:py-4 whitespace-nowrap">
<div class="mb-progress-container">
<div class="mb-progress-bar" style="width: {{ job.progress }}%"></div>
</div>
<div class="text-xs text-right mt-1 text-slate-500 dark:text-slate-400">{{ job.progress }}%</div>
</td>
<td class="px-3 sm:px-6 py-2 sm:py-4 whitespace-nowrap text-right">
<a href="{{ url_for('job_detail', job_id=job.id) }}" class="text-slate-900 dark:text-white hover:text-slate-700 dark:hover:text-slate-300 font-medium text-xs sm:text-sm">
<span class="hidden sm:inline">Details</span>
<span class="sm:hidden"></span>
</a>
</td>
</tr>
{% endfor %}
{% else %}
<tr>
<td colspan="6" class="px-3 sm:px-6 py-6 sm:py-8 text-center">
<div class="text-slate-500 dark:text-slate-400">
<svg class="w-8 h-8 sm:w-12 sm:h-12 mx-auto mb-3 text-slate-400 dark:text-slate-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-3 7h3m-3 4h3m-6-4h.01M9 16h.01" />
</svg>
<p class="text-slate-700 dark:text-slate-300 font-medium mb-1 text-sm sm:text-base">Keine aktiven Druckaufträge</p>
<p class="text-slate-500 dark:text-slate-400 text-xs sm:text-sm">Starten Sie einen neuen Druckauftrag, um ihn hier zu sehen.</p>
</div>
</td>
</tr>
{% endif %}
</tbody>
</table>
</div>
<div class="mt-4 text-right">
<a href="{{ url_for('jobs_page') }}" class="text-slate-900 dark:text-white hover:text-slate-700 dark:hover:text-slate-300 text-xs sm:text-sm font-medium">
<span class="hidden sm:inline">Alle Druckaufträge anzeigen →</span>
<span class="sm:hidden">Alle anzeigen →</span>
</a>
</div>
</div>
<!-- Last Row: Printer Status and Recent Activity - Responsive Grid -->
<div class="responsive-grid-2">
<!-- Available Printers -->
<div class="dashboard-card">
<h2 class="section-title">Druckerstatus</h2>
<div class="space-y-3 sm:space-y-4">
{% if printers and printers|length > 0 %}
{% for printer in printers %}
<div class="flex items-center justify-between p-3 sm:p-4 rounded-lg sm:rounded-xl bg-gray-50 dark:bg-slate-700/30">
<div class="flex items-center min-w-0 flex-1">
<div class="mb-status-indicator {{ printer.status_class }} mr-2 sm:mr-3 flex-shrink-0"></div>
<div class="min-w-0 flex-1">
<div class="text-xs sm:text-sm font-medium text-slate-900 dark:text-white truncate">{{ printer.name }}</div>
<div class="text-xs text-slate-500 dark:text-slate-400 truncate hidden sm:block">{{ printer.model }}</div>
</div>
</div>
<div class="text-right flex-shrink-0 ml-2">
<div class="text-xs sm:text-sm font-medium text-slate-700 dark:text-slate-300">
<span class="hidden sm:inline">{{ printer.status_text }}</span>
<span class="sm:hidden">{{ printer.status_text[:3] }}</span>
</div>
<div class="text-xs text-slate-500 dark:text-slate-400 hidden sm:block">{{ printer.location }}</div>
</div>
</div>
{% endfor %}
{% else %}
<div class="text-center py-6 sm:py-8">
<svg class="w-8 h-8 sm:w-12 sm:h-12 mx-auto mb-3 text-slate-400 dark:text-slate-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 12h14M5 12a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v4a2 2 0 01-2 2M5 12a2 2 0 00-2 2v4a2 2 0 002 2h14a2 2 0 002-2v-4a2 2 0 00-2-2m-2-4h.01M17 16h.01" />
</svg>
<p class="text-slate-700 dark:text-slate-300 font-medium mb-1 text-sm sm:text-base">Keine Drucker gefunden</p>
<p class="text-slate-500 dark:text-slate-400 text-xs sm:text-sm">Prüfen Sie die Verbindung oder fügen Sie Drucker hinzu.</p>
</div>
{% endif %}
</div>
<div class="mt-4 text-right">
<a href="{{ url_for('printers_page') }}" class="text-slate-900 dark:text-white hover:text-slate-700 dark:hover:text-slate-300 text-xs sm:text-sm font-medium">
<span class="hidden sm:inline">Alle Drucker anzeigen →</span>
<span class="sm:hidden">Alle anzeigen →</span>
</a>
</div>
</div>
<!-- Recent Activity -->
<div class="dashboard-card">
<h2 class="section-title">Letzte Aktivitäten</h2>
<div class="space-y-2 sm:space-y-3">
{% if activities and activities|length > 0 %}
{% for activity in activities %}
<div class="mb-activity-item pl-3 sm:pl-4 py-2 sm:py-3">
<div class="flex items-center justify-between">
<div class="flex-1 min-w-0">
<p class="text-xs sm:text-sm text-slate-700 dark:text-slate-300 truncate">{{ activity.description }}</p>
<p class="text-xs text-slate-500 dark:text-slate-400">{{ activity.time }}</p>
</div>
</div>
</div>
{% endfor %}
{% else %}
<div class="text-center py-6 sm:py-8">
<svg class="w-8 h-8 sm:w-12 sm:h-12 mx-auto mb-3 text-slate-400 dark:text-slate-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
<p class="text-slate-700 dark:text-slate-300 font-medium mb-1 text-sm sm:text-base">Keine Aktivitäten</p>
<p class="text-slate-500 dark:text-slate-400 text-xs sm:text-sm">Ihre Aktivitäten werden hier angezeigt.</p>
</div>
{% endif %}
</div>
</div>
</div>
</div>
{% endblock %}