264 lines
13 KiB
PHP
264 lines
13 KiB
PHP
@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
|