14-04-2026

This commit is contained in:
Kevin Adametz 2026-04-14 18:07:45 +02:00
parent f58c709945
commit 0f82fea88a
72 changed files with 7414 additions and 148 deletions

View file

@ -0,0 +1,264 @@
@extends('layouts.dashboard')
@section('page-title', 'Entwickler-Dashboard')
@section('content')
{{-- ── Stats ── --}}
<div class="grid-4 mb-6">
<div class="stat-card {{ $stats['open_incidents'] > 0 ? 'danger' : 'ok' }}">
<div class="stat-label">Offen / Wartend</div>
<div class="stat-value">{{ $stats['open_incidents'] }}</div>
<div class="stat-sub">Incidents ohne Lösung</div>
</div>
<div class="stat-card {{ $stats['in_progress'] > 0 ? 'warning' : 'ok' }}">
<div class="stat-label">In Bearbeitung</div>
<div class="stat-value">{{ $stats['in_progress'] }}</div>
<div class="stat-sub">Aktiv bearbeitet</div>
</div>
<div class="stat-card ok">
<div class="stat-label">Gelöst diesen Monat</div>
<div class="stat-value">{{ $stats['resolved_this_month'] }}</div>
<div class="stat-sub">{{ now()->format('F Y') }}</div>
</div>
<div class="stat-card {{ $stats['payone_incidents_30d'] >= 3 ? 'danger' : ($stats['payone_incidents_30d'] >= 1 ? 'warning' : 'ok') }}">
<div class="stat-label">PAYONE (30 Tage)</div>
<div class="stat-value">{{ $stats['payone_incidents_30d'] }}</div>
<div class="stat-sub">Incidents bei PAYONE</div>
</div>
</div>
{{-- ── Anbieter-Status ── --}}
<div class="card mb-6">
<div class="flex items-center justify-between mb-4">
<div class="card-title" style="margin:0">Anbieter-Status</div>
</div>
<div class="provider-grid">
@foreach($providerStats as $key => $provider)
<div class="provider-card {{ $provider['open_incidents'] > 0 ? 'has-issues' : 'ok' }}">
<div class="provider-name">{{ $provider['label'] }}</div>
<div class="provider-incidents">{{ $provider['open_incidents'] }}</div>
<div class="provider-sub">offene Störungen</div>
<div style="margin-top:8px; font-size:11px; color: var(--text-muted);">{{ $provider['total_30d'] }}× in 30 Tagen</div>
@if($provider['last_incident'])
<div style="margin-top:4px; font-size:10px; color: var(--text-muted);">
Zuletzt: {{ $provider['last_incident']->detected_at->format('d.m.Y H:i') }}
</div>
@endif
</div>
@endforeach
</div>
</div>
<div class="grid-2">
{{-- ── Offene Incidents ── --}}
<div>
<div class="flex items-center justify-between mb-4">
<div class="section-title" style="margin:0; border:none; padding:0;">Offene Incidents</div>
<button class="btn btn-primary" onclick="document.getElementById('modal-new').classList.add('open')">
+ Neuer Incident
</button>
</div>
@forelse($openIncidents as $incident)
<div class="card mb-4" style="border-left: 3px solid {{ $incident->severity_color }};">
<div class="flex items-center justify-between mb-4">
<div>
<div style="font-weight:700; margin-bottom:4px;">{{ $incident->title }}</div>
<div class="flex gap-2">
<span class="badge badge-{{ $incident->severity }}">{{ ucfirst($incident->severity) }}</span>
<span class="badge badge-{{ $incident->status }}">{{ $incident->status_label }}</span>
<span style="font-size:11px; color:var(--text-muted); padding: 3px 0;">{{ $incident->provider_label }}</span>
</div>
</div>
<a href="{{ route('payment-dashboard.show', $incident) }}" class="btn btn-ghost" style="font-size:12px;">Detail </a>
</div>
<div style="font-size:12px; color:var(--text-muted); margin-bottom:12px;">
Erkannt: {{ $incident->detected_at->format('d.m.Y H:i') }} · Dauer: {{ $incident->duration }}
@if($incident->ticket_number)
· Ticket: <span class="text-accent">{{ $incident->ticket_number }}</span>
@endif
@if($incident->affected_orders > 0)
· {{ $incident->affected_orders }} Bestellungen betroffen
@endif
</div>
{{-- Letzte Aktivität --}}
@if($incident->activities->count() > 0)
@php $last = $incident->activities->sortByDesc('created_at')->first(); @endphp
<div style="background: var(--surface-2); border: 1px solid var(--border); border-radius:7px; padding:10px 12px; font-size:12px;">
<span style="color:var(--text-muted);">{{ $last->type_icon }} Letzte Aktivität:</span>
<strong>{{ $last->title }}</strong>
<span class="text-muted"> {{ $last->created_at->diffForHumans() }}</span>
</div>
@endif
{{-- Schnell-Status-Update --}}
<form action="{{ route('payment-dashboard.status.update', $incident) }}" method="POST" style="margin-top:12px; display:flex; gap:8px; flex-wrap:wrap;">
@csrf @method('PATCH')
<select name="status" style="width:auto; flex:1;">
<option value="open" {{ $incident->status === 'open' ? 'selected' : '' }}>Offen</option>
<option value="in_progress" {{ $incident->status === 'in_progress' ? 'selected' : '' }}>In Bearbeitung</option>
<option value="waiting_provider" {{ $incident->status === 'waiting_provider' ? 'selected' : '' }}>Wartet auf Anbieter</option>
<option value="resolved" {{ $incident->status === 'resolved' ? 'selected' : '' }}>Gelöst</option>
<option value="closed" {{ $incident->status === 'closed' ? 'selected' : '' }}>Geschlossen</option>
</select>
<button type="submit" class="btn btn-ghost" style="font-size:12px;">Aktualisieren</button>
</form>
</div>
@empty
<div class="card" style="text-align:center; padding:32px; border-color: var(--green);">
<div style="font-size:28px; margin-bottom:8px;"></div>
<div style="font-weight:700; color:var(--green);">Keine offenen Incidents</div>
</div>
@endforelse
</div>
{{-- ── Letzte Aktivitäten ── --}}
<div>
<div class="section-title">Kommunikations-Verlauf</div>
<div class="card">
<div class="timeline">
@forelse($recentActivity as $activity)
<div class="timeline-item">
<div class="timeline-dot"></div>
<div class="timeline-title">
{{ $activity->type_icon }} {{ $activity->title }}
</div>
<div class="timeline-meta">
{{ $activity->type_label }} · {{ $activity->author }} · {{ $activity->created_at->format('d.m.Y H:i') }}
@if($activity->incident)
· <a href="{{ route('payment-dashboard.show', $activity->incident) }}">{{ $activity->incident->title }}</a>
@endif
</div>
@if($activity->content)
<div class="timeline-content">{{ $activity->content }}</div>
@endif
</div>
@empty
<div class="text-muted">Noch keine Aktivitäten erfasst.</div>
@endforelse
</div>
</div>
</div>
</div>
{{-- ── Alle Incidents Tabelle ── --}}
<div class="card" style="margin-top:8px;">
<div class="flex items-center justify-between mb-4">
<div class="card-title" style="margin:0;">Alle Incidents</div>
<span class="text-muted" style="font-size:12px;">{{ $allIncidents->total() }} gesamt</span>
</div>
<div class="table-wrap">
<table>
<thead>
<tr>
<th>#</th>
<th>Titel</th>
<th>Anbieter</th>
<th>Typ</th>
<th>Schwere</th>
<th>Status</th>
<th>Erkannt</th>
<th>Dauer</th>
<th>Ticket</th>
<th></th>
</tr>
</thead>
<tbody>
@forelse($allIncidents as $incident)
<tr>
<td class="text-muted" style="font-size:11px;">#{{ $incident->id }}</td>
<td><strong>{{ $incident->title }}</strong></td>
<td>{{ $incident->provider_label }}</td>
<td style="font-size:12px; color:var(--text-muted);">{{ $incident->type }}</td>
<td><span class="badge badge-{{ $incident->severity }}">{{ ucfirst($incident->severity) }}</span></td>
<td><span class="badge badge-{{ $incident->status }}">{{ $incident->status_label }}</span></td>
<td style="font-size:12px;">{{ $incident->detected_at->format('d.m.Y H:i') }}</td>
<td style="font-size:12px;">{{ $incident->duration }}</td>
<td style="font-size:12px; color:var(--accent);">{{ $incident->ticket_number ?? '' }}</td>
<td><a href="{{ route('payment-dashboard.show', $incident) }}" class="btn btn-ghost" style="font-size:11px; padding:4px 10px;">Detail</a></td>
</tr>
@empty
<tr><td colspan="10" class="text-muted">Noch keine Incidents.</td></tr>
@endforelse
</tbody>
</table>
</div>
<div style="margin-top:16px;">{{ $allIncidents->links() }}</div>
</div>
{{-- ── Modal: Neuer Incident ── --}}
<div class="modal-overlay" id="modal-new" onclick="if(event.target===this)this.classList.remove('open')">
<div class="modal">
<div class="modal-title">Neuen Incident erfassen</div>
<form action="{{ route('payment-dashboard.store') }}" method="POST">
@csrf
<div class="form-group">
<label>Titel *</label>
<input type="text" name="title" placeholder="z.B. IPN-Fehler PayPal via PAYONE" required>
</div>
<div class="form-row">
<div class="form-group">
<label>Anbieter *</label>
<select name="provider">
<option value="payone">PAYONE</option>
<option value="stripe">Stripe</option>
<option value="paypal">PayPal</option>
<option value="mollie">Mollie</option>
<option value="other">Sonstige</option>
</select>
</div>
<div class="form-group">
<label>Typ *</label>
<select name="type">
<option value="ipn_error">IPN-Fehler</option>
<option value="outage">Komplettausfall</option>
<option value="payment_failure">Zahlungsfehler</option>
<option value="slow_response">Langsame Reaktion</option>
<option value="other">Sonstiges</option>
</select>
</div>
</div>
<div class="form-row">
<div class="form-group">
<label>Schwere *</label>
<select name="severity">
<option value="critical">Critical</option>
<option value="high">High</option>
<option value="medium" selected>Medium</option>
<option value="low">Low</option>
</select>
</div>
<div class="form-group">
<label>Ticket-Nummer</label>
<input type="text" name="ticket_number" placeholder="z.B. PAY-20240112">
</div>
</div>
<div class="form-row">
<div class="form-group">
<label>Betroffene Bestellungen</label>
<input type="number" name="affected_orders" value="0" min="0">
</div>
<div class="form-group">
<label>Betroffener Umsatz ()</label>
<input type="number" name="affected_revenue" value="0" min="0" step="0.01">
</div>
</div>
<div class="form-group">
<label>Erkannt am *</label>
<input type="datetime-local" name="detected_at" value="{{ now()->format('Y-m-d\TH:i') }}" required>
</div>
<div class="form-group">
<label>Beschreibung</label>
<textarea name="description" rows="3" placeholder="Was ist passiert? Fehlermeldung, Kontext..."></textarea>
</div>
<div class="flex gap-2" style="justify-content:flex-end;">
<button type="button" class="btn btn-ghost" onclick="document.getElementById('modal-new').classList.remove('open')">Abbrechen</button>
<button type="submit" class="btn btn-primary">Incident anlegen</button>
</div>
</form>
</div>
</div>
@endsection

View file

@ -0,0 +1,120 @@
@extends('layouts.dashboard')
@section('page-title', 'Zahlungssystem Übersicht')
@section('content')
{{-- ── Stat-Karten ── --}}
<div class="grid-4 mb-6">
<div class="stat-card {{ $stats['open_incidents'] > 0 ? 'danger' : 'ok' }}">
<div class="stat-label">Offene Störungen</div>
<div class="stat-value">{{ $stats['open_incidents'] }}</div>
<div class="stat-sub">Warten auf Lösung</div>
</div>
<div class="stat-card {{ $stats['in_progress'] > 0 ? 'warning' : 'ok' }}">
<div class="stat-label">In Bearbeitung</div>
<div class="stat-value">{{ $stats['in_progress'] }}</div>
<div class="stat-sub">Aktiv bearbeitet</div>
</div>
<div class="stat-card {{ $stats['total_affected_revenue'] > 0 ? 'danger' : 'ok' }}">
<div class="stat-label">Betroffener Umsatz</div>
<div class="stat-value">{{ number_format($stats['total_affected_revenue'], 0, ',', '.') }} </div>
<div class="stat-sub">Offene Incidents</div>
</div>
<div class="stat-card {{ $stats['payone_incidents_30d'] >= 3 ? 'danger' : ($stats['payone_incidents_30d'] >= 1 ? 'warning' : 'ok') }}">
<div class="stat-label">PAYONE Probleme</div>
<div class="stat-value">{{ $stats['payone_incidents_30d'] }}</div>
<div class="stat-sub">Letzte 30 Tage</div>
</div>
</div>
{{-- ── Anbieter-Status ── --}}
<div class="card mb-6">
<div class="card-title">Anbieter-Übersicht</div>
<div class="provider-grid">
@foreach($providerStats as $key => $provider)
<div class="provider-card {{ $provider['open_incidents'] > 0 ? 'has-issues' : 'ok' }}">
<div class="provider-name">{{ $provider['label'] }}</div>
<div class="provider-incidents">{{ $provider['open_incidents'] }}</div>
<div class="provider-sub">offene Störungen</div>
<div class="provider-sub" style="margin-top:6px;">{{ $provider['total_30d'] }}× in 30 Tagen</div>
@if($provider['last_incident'])
<div class="provider-sub" style="margin-top:4px; font-size:10px;">
Zuletzt: {{ $provider['last_incident']->detected_at->format('d.m.Y') }}
</div>
@endif
</div>
@endforeach
</div>
</div>
{{-- ── Offene Störungen ── --}}
@if($openIncidents->count() > 0)
<div class="card mb-6" style="border-color: var(--red);">
<div class="card-title" style="color: var(--red);"> Aktive Störungen</div>
<div class="table-wrap">
<table>
<thead>
<tr>
<th>Störung</th>
<th>Anbieter</th>
<th>Schwere</th>
<th>Status</th>
<th>Seit</th>
<th>Betroffene Bestellungen</th>
</tr>
</thead>
<tbody>
@foreach($openIncidents as $incident)
<tr>
<td><strong>{{ $incident->title }}</strong></td>
<td>{{ $incident->provider_label }}</td>
<td><span class="badge badge-{{ $incident->severity }}">{{ ucfirst($incident->severity) }}</span></td>
<td><span class="badge badge-{{ $incident->status }}">{{ $incident->status_label }}</span></td>
<td>{{ $incident->detected_at->format('d.m.Y H:i') }}<br><span class="text-muted">{{ $incident->duration }}</span></td>
<td>{{ $incident->affected_orders > 0 ? $incident->affected_orders . ' Bestellungen' : '' }}</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
@else
<div class="card mb-6" style="border-color: var(--green); text-align:center; padding: 32px;">
<div style="font-size: 32px; margin-bottom: 8px;"></div>
<div style="font-weight: 700; color: var(--green); margin-bottom: 4px;">Keine aktiven Störungen</div>
<div class="text-muted">Alle Zahlungssysteme laufen normal.</div>
</div>
@endif
{{-- ── Letzte Incidents ── --}}
<div class="card">
<div class="card-title">Letzte Vorfälle</div>
<div class="table-wrap">
<table>
<thead>
<tr>
<th>Titel</th>
<th>Anbieter</th>
<th>Datum</th>
<th>Status</th>
<th>Dauer</th>
</tr>
</thead>
<tbody>
@forelse($recentIncidents as $incident)
<tr>
<td>{{ $incident->title }}</td>
<td>{{ $incident->provider_label }}</td>
<td>{{ $incident->detected_at->format('d.m.Y') }}</td>
<td><span class="badge badge-{{ $incident->status }}">{{ $incident->status_label }}</span></td>
<td>{{ $incident->duration }}</td>
</tr>
@empty
<tr><td colspan="5" class="text-muted">Noch keine Vorfälle erfasst.</td></tr>
@endforelse
</tbody>
</table>
</div>
</div>
@endsection

View file

@ -0,0 +1,150 @@
@extends('layouts.dashboard')
@section('page-title', 'Incident #' . $incident->id)
@section('content')
<div class="flex items-center gap-3 mb-6" style="flex-wrap:wrap;">
<a href="{{ route('payment-dashboard.developer') }}" class="btn btn-ghost" style="font-size:12px;"> Zurück</a>
<span class="badge badge-{{ $incident->severity }}">{{ ucfirst($incident->severity) }}</span>
<span class="badge badge-{{ $incident->status }}">{{ $incident->status_label }}</span>
<span style="font-size:13px; color:var(--text-muted);">{{ $incident->provider_label }} · {{ $incident->detected_at->format('d.m.Y H:i') }} · {{ $incident->duration }}</span>
</div>
<div class="grid-2">
{{-- ── Incident-Details ── --}}
<div>
<div class="card mb-4">
<div class="card-title">Incident-Details</div>
<div style="font-size:20px; font-weight:800; margin-bottom:12px;">{{ $incident->title }}</div>
@if($incident->description)
<div style="background:var(--surface-2); border:1px solid var(--border); border-radius:7px; padding:14px; font-size:13px; color:var(--text-muted); margin-bottom:16px;">
{{ $incident->description }}
</div>
@endif
<table style="width:100%; font-size:13px;">
<tr>
<td style="padding:6px 0; color:var(--text-muted); width:140px;">Anbieter</td>
<td style="padding:6px 0; font-weight:600;">{{ $incident->provider_label }}</td>
</tr>
<tr>
<td style="padding:6px 0; color:var(--text-muted);">Typ</td>
<td style="padding:6px 0;">{{ $incident->type }}</td>
</tr>
<tr>
<td style="padding:6px 0; color:var(--text-muted);">Erkannt</td>
<td style="padding:6px 0;">{{ $incident->detected_at->format('d.m.Y H:i') }} Uhr</td>
</tr>
@if($incident->resolved_at)
<tr>
<td style="padding:6px 0; color:var(--text-muted);">Gelöst</td>
<td style="padding:6px 0; color:var(--green);">{{ $incident->resolved_at->format('d.m.Y H:i') }} Uhr</td>
</tr>
@endif
<tr>
<td style="padding:6px 0; color:var(--text-muted);">Dauer</td>
<td style="padding:6px 0;">{{ $incident->duration }}</td>
</tr>
@if($incident->ticket_number)
<tr>
<td style="padding:6px 0; color:var(--text-muted);">Ticket-Nr.</td>
<td style="padding:6px 0; color:var(--accent); font-weight:600;">{{ $incident->ticket_number }}</td>
</tr>
@endif
@if($incident->affected_orders > 0)
<tr>
<td style="padding:6px 0; color:var(--text-muted);">Bestellungen</td>
<td style="padding:6px 0; color:var(--red);">{{ $incident->affected_orders }} betroffen</td>
</tr>
@endif
@if($incident->affected_revenue > 0)
<tr>
<td style="padding:6px 0; color:var(--text-muted);">Umsatz</td>
<td style="padding:6px 0; color:var(--red); font-weight:700;">{{ number_format($incident->affected_revenue, 2, ',', '.') }} </td>
</tr>
@endif
</table>
</div>
{{-- Status ändern --}}
<div class="card mb-4">
<div class="card-title">Status aktualisieren</div>
<form action="{{ route('payment-dashboard.status.update', $incident) }}" method="POST" class="flex gap-2">
@csrf @method('PATCH')
<select name="status" style="flex:1;">
<option value="open" {{ $incident->status === 'open' ? 'selected' : '' }}>Offen</option>
<option value="in_progress" {{ $incident->status === 'in_progress' ? 'selected' : '' }}>In Bearbeitung</option>
<option value="waiting_provider" {{ $incident->status === 'waiting_provider' ? 'selected' : '' }}>Wartet auf Anbieter</option>
<option value="resolved" {{ $incident->status === 'resolved' ? 'selected' : '' }}>Gelöst</option>
<option value="closed" {{ $incident->status === 'closed' ? 'selected' : '' }}>Geschlossen</option>
</select>
<button type="submit" class="btn btn-primary">Speichern</button>
</form>
</div>
{{-- Aktivität hinzufügen --}}
<div class="card">
<div class="card-title">Aktivität hinzufügen</div>
<form action="{{ route('payment-dashboard.activity.store', $incident) }}" method="POST">
@csrf
<div class="form-group">
<label>Typ</label>
<select name="type">
<option value="note">📝 Notiz</option>
<option value="email">✉️ E-Mail</option>
<option value="call">📞 Telefonat</option>
<option value="ticket">🎫 Support-Ticket</option>
<option value="provider_response">💬 Anbieter-Antwort</option>
</select>
</div>
<div class="form-group">
<label>Titel *</label>
<input type="text" name="title" placeholder="z.B. Ticket bei PAYONE eingereicht" required>
</div>
<div class="form-group">
<label>Details</label>
<textarea name="content" rows="3" placeholder="Inhalt der Mail, Antwort des Anbieters, Notizen..."></textarea>
</div>
<button type="submit" class="btn btn-primary" style="width:100%;">Aktivität speichern</button>
</form>
</div>
</div>
{{-- ── Aktivitäts-Timeline ── --}}
<div>
<div class="section-title">Kommunikations-Verlauf ({{ $incident->activities->count() }} Einträge)</div>
<div class="card">
@if($incident->activities->count() > 0)
<div class="timeline">
@foreach($incident->activities->sortByDesc('created_at') as $activity)
<div class="timeline-item">
<div class="timeline-dot" style="background: {{ match($activity->type) {
'provider_response' => 'var(--green)',
'email' => 'var(--accent)',
'call' => 'var(--yellow)',
'ticket' => 'var(--orange)',
'status_change' => 'var(--text-muted)',
default => 'var(--accent)'
} }};"></div>
<div class="timeline-title">
{{ $activity->type_icon }} {{ $activity->title }}
</div>
<div class="timeline-meta">
{{ $activity->type_label }} · {{ $activity->author }} · {{ $activity->created_at->format('d.m.Y H:i') }} Uhr
· <span style="color: var(--text-muted)">{{ $activity->created_at->diffForHumans() }}</span>
</div>
@if($activity->content)
<div class="timeline-content">{{ $activity->content }}</div>
@endif
</div>
@endforeach
</div>
@else
<div class="text-muted" style="text-align:center; padding:24px;">Noch keine Aktivitäten erfasst.</div>
@endif
</div>
</div>
</div>
@endsection

View file

@ -0,0 +1,402 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="csrf-token" content="{{ csrf_token() }}">
<title>@yield('title', 'Payment Dashboard') mivita</title>
<style>
/* ── Reset & Base ── */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
:root {
--bg: #0d0f14;
--surface: #161920;
--surface-2: #1e2230;
--border: #2a2f3f;
--text: #e8eaf0;
--text-muted: #7c839a;
--accent: #4f8ef7;
--accent-soft: #1a2a4a;
--red: #ef4444;
--red-soft: #3a1515;
--orange: #f97316;
--orange-soft: #3a2010;
--yellow: #eab308;
--yellow-soft: #332d00;
--green: #22c55e;
--green-soft: #0f2d1a;
--radius: 10px;
--shadow: 0 4px 24px rgba(0,0,0,0.4);
}
body {
font-family: 'DM Sans', -apple-system, BlinkMacSystemFont, sans-serif;
background: var(--bg);
color: var(--text);
min-height: 100vh;
font-size: 14px;
line-height: 1.6;
}
/* ── Layout ── */
.layout { display: flex; min-height: 100vh; }
.sidebar {
width: 220px;
background: var(--surface);
border-right: 1px solid var(--border);
padding: 24px 0;
flex-shrink: 0;
display: flex;
flex-direction: column;
}
.sidebar-logo {
padding: 0 20px 24px;
border-bottom: 1px solid var(--border);
margin-bottom: 16px;
}
.sidebar-logo span {
font-size: 11px;
font-weight: 600;
letter-spacing: 0.12em;
text-transform: uppercase;
color: var(--text-muted);
}
.sidebar-logo strong {
display: block;
font-size: 16px;
font-weight: 700;
color: var(--text);
margin-top: 2px;
}
.sidebar-nav { padding: 0 12px; flex: 1; }
.nav-section {
font-size: 10px;
font-weight: 600;
letter-spacing: 0.1em;
text-transform: uppercase;
color: var(--text-muted);
padding: 16px 8px 6px;
}
.nav-link {
display: flex;
align-items: center;
gap: 10px;
padding: 9px 10px;
border-radius: 7px;
color: var(--text-muted);
text-decoration: none;
font-size: 13px;
font-weight: 500;
transition: all 0.15s;
margin-bottom: 2px;
}
.nav-link:hover, .nav-link.active {
background: var(--surface-2);
color: var(--text);
}
.nav-link.active { color: var(--accent); }
.nav-icon { font-size: 15px; width: 20px; text-align: center; }
/* ── Main ── */
.main { flex: 1; display: flex; flex-direction: column; overflow: hidden; }
.topbar {
background: var(--surface);
border-bottom: 1px solid var(--border);
padding: 16px 32px;
display: flex;
align-items: center;
justify-content: space-between;
}
.topbar-title { font-size: 18px; font-weight: 700; }
.topbar-meta { font-size: 12px; color: var(--text-muted); }
.content { padding: 32px; overflow-y: auto; flex: 1; }
/* ── Cards & Grid ── */
.grid-4 { display: grid; grid-template-columns: repeat(4, 1fr); gap: 16px; margin-bottom: 24px; }
.grid-2 { display: grid; grid-template-columns: repeat(2, 1fr); gap: 20px; margin-bottom: 24px; }
.grid-3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; margin-bottom: 24px; }
.card {
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 20px;
}
.card-title {
font-size: 11px;
font-weight: 600;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--text-muted);
margin-bottom: 16px;
}
/* ── Stat Cards ── */
.stat-card {
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 20px;
}
.stat-label {
font-size: 11px;
font-weight: 600;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--text-muted);
margin-bottom: 8px;
}
.stat-value {
font-size: 32px;
font-weight: 800;
line-height: 1;
margin-bottom: 4px;
}
.stat-sub { font-size: 12px; color: var(--text-muted); }
.stat-card.danger { border-color: var(--red); background: var(--red-soft); }
.stat-card.warning { border-color: var(--orange); background: var(--orange-soft); }
.stat-card.ok { border-color: var(--green); background: var(--green-soft); }
/* ── Badges ── */
.badge {
display: inline-flex;
align-items: center;
gap: 5px;
padding: 3px 10px;
border-radius: 20px;
font-size: 11px;
font-weight: 600;
letter-spacing: 0.03em;
}
.badge-critical { background: var(--red-soft); color: var(--red); border: 1px solid var(--red); }
.badge-high { background: var(--orange-soft); color: var(--orange); border: 1px solid var(--orange); }
.badge-medium { background: var(--yellow-soft); color: var(--yellow); border: 1px solid var(--yellow); }
.badge-low { background: var(--green-soft); color: var(--green); border: 1px solid var(--green); }
.badge-open { background: #1a1a2e; color: #818cf8; border: 1px solid #818cf8; }
.badge-in_progress { background: #1a2a3a; color: var(--accent); border: 1px solid var(--accent); }
.badge-waiting_provider { background: var(--orange-soft); color: var(--orange); border: 1px solid var(--orange); }
.badge-resolved { background: var(--green-soft); color: var(--green); border: 1px solid var(--green); }
.badge-closed { background: var(--surface-2); color: var(--text-muted); border: 1px solid var(--border); }
/* ── Table ── */
.table-wrap { overflow-x: auto; }
table { width: 100%; border-collapse: collapse; }
th {
text-align: left;
font-size: 11px;
font-weight: 600;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--text-muted);
padding: 10px 14px;
border-bottom: 1px solid var(--border);
}
td {
padding: 12px 14px;
border-bottom: 1px solid var(--border);
font-size: 13px;
vertical-align: middle;
}
tr:last-child td { border-bottom: none; }
tr:hover td { background: var(--surface-2); }
/* ── Timeline ── */
.timeline { position: relative; padding-left: 28px; }
.timeline::before {
content: '';
position: absolute;
left: 8px; top: 8px; bottom: 8px;
width: 2px;
background: var(--border);
}
.timeline-item {
position: relative;
margin-bottom: 20px;
}
.timeline-dot {
position: absolute;
left: -24px;
top: 4px;
width: 12px; height: 12px;
border-radius: 50%;
background: var(--accent);
border: 2px solid var(--bg);
}
.timeline-title {
font-size: 13px;
font-weight: 600;
margin-bottom: 2px;
}
.timeline-meta { font-size: 11px; color: var(--text-muted); margin-bottom: 4px; }
.timeline-content {
font-size: 13px;
color: var(--text-muted);
background: var(--surface-2);
padding: 10px 14px;
border-radius: 7px;
margin-top: 6px;
border: 1px solid var(--border);
}
/* ── Provider Status ── */
.provider-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; }
.provider-card {
background: var(--surface-2);
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 16px;
text-align: center;
}
.provider-name { font-size: 13px; font-weight: 700; margin-bottom: 6px; }
.provider-incidents { font-size: 28px; font-weight: 800; margin-bottom: 2px; }
.provider-sub { font-size: 11px; color: var(--text-muted); }
.provider-card.has-issues { border-color: var(--red); }
.provider-card.has-issues .provider-incidents { color: var(--red); }
.provider-card.ok .provider-incidents { color: var(--green); }
/* ── Forms ── */
.btn {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 8px 16px;
border-radius: 7px;
font-size: 13px;
font-weight: 600;
cursor: pointer;
border: none;
transition: all 0.15s;
text-decoration: none;
}
.btn-primary { background: var(--accent); color: #fff; }
.btn-primary:hover { background: #3a7ef0; }
.btn-ghost { background: var(--surface-2); color: var(--text); border: 1px solid var(--border); }
.btn-ghost:hover { border-color: var(--accent); color: var(--accent); }
.btn-danger { background: var(--red-soft); color: var(--red); border: 1px solid var(--red); }
select, input, textarea {
background: var(--surface-2);
border: 1px solid var(--border);
color: var(--text);
padding: 8px 12px;
border-radius: 7px;
font-size: 13px;
font-family: inherit;
width: 100%;
outline: none;
transition: border-color 0.15s;
}
select:focus, input:focus, textarea:focus { border-color: var(--accent); }
label { display: block; font-size: 12px; font-weight: 600; color: var(--text-muted); margin-bottom: 5px; }
.form-group { margin-bottom: 16px; }
.form-row { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
/* ── Alert ── */
.alert {
padding: 12px 16px;
border-radius: 7px;
margin-bottom: 20px;
font-size: 13px;
font-weight: 500;
}
.alert-success { background: var(--green-soft); color: var(--green); border: 1px solid var(--green); }
.alert-danger { background: var(--red-soft); color: var(--red); border: 1px solid var(--red); }
/* ── Modal ── */
.modal-overlay {
display: none;
position: fixed; inset: 0;
background: rgba(0,0,0,0.7);
z-index: 100;
align-items: center;
justify-content: center;
}
.modal-overlay.open { display: flex; }
.modal {
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 28px;
width: 560px;
max-width: 95vw;
max-height: 90vh;
overflow-y: auto;
box-shadow: var(--shadow);
}
.modal-title { font-size: 16px; font-weight: 700; margin-bottom: 20px; }
/* ── Misc ── */
.text-muted { color: var(--text-muted); }
.text-red { color: var(--red); }
.text-green { color: var(--green); }
.text-accent { color: var(--accent); }
.mb-4 { margin-bottom: 16px; }
.mb-6 { margin-bottom: 24px; }
.flex { display: flex; }
.items-center { align-items: center; }
.justify-between { justify-content: space-between; }
.gap-2 { gap: 8px; }
.gap-3 { gap: 12px; }
.section-title {
font-size: 14px;
font-weight: 700;
margin-bottom: 16px;
padding-bottom: 10px;
border-bottom: 1px solid var(--border);
}
a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }
@import url('https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;600;700;800&display=swap');
</style>
@stack('styles')
</head>
<body>
<div class="layout">
<aside class="sidebar">
<div class="sidebar-logo">
<span>mivita</span>
<strong>Payment Monitor</strong>
</div>
<nav class="sidebar-nav">
<div class="nav-section">Ansichten</div>
<a href="{{ route('payment-dashboard.developer') }}" class="nav-link {{ request()->routeIs('payment-dashboard.developer') ? 'active' : '' }}">
<span class="nav-icon">⚙️</span> Entwickler
</a>
<a href="{{ route('payment-dashboard.management') }}" class="nav-link {{ request()->routeIs('payment-dashboard.management') ? 'active' : '' }}">
<span class="nav-icon">📊</span> Management
</a>
</nav>
</aside>
<div class="main">
<div class="topbar">
<div>
<div class="topbar-title">@yield('page-title', 'Dashboard')</div>
</div>
<div class="topbar-meta">
Stand: {{ now()->format('d.m.Y H:i') }} Uhr
</div>
</div>
<div class="content">
@if(session('success'))
<div class="alert alert-success"> {{ session('success') }}</div>
@endif
@if(session('error'))
<div class="alert alert-danger"> {{ session('error') }}</div>
@endif
@yield('content')
</div>
</div>
</div>
@stack('scripts')
</body>
</html>