302 lines
17 KiB
PHP
302 lines
17 KiB
PHP
@extends('layouts.layout-2')
|
||
|
||
@section('content')
|
||
|
||
<div class="d-flex justify-content-between align-items-center mb-3 flex-wrap">
|
||
<div class="mb-2">
|
||
<a href="{{ route('admin.payment-dashboard.index') }}" class="btn btn-sm btn-outline-secondary mr-2">
|
||
<i class="ion ion-md-arrow-back"></i> Zurück
|
||
</a>
|
||
<strong>Checkout-Funnel Tracking</strong>
|
||
<small class="text-muted ml-2">Internes Tracking aller Checkout-Schritte</small>
|
||
</div>
|
||
<form method="GET" class="form-inline">
|
||
<label class="mr-2 text-muted small">Zeitraum:</label>
|
||
<select name="days" class="form-control form-control-sm" onchange="this.form.submit()">
|
||
@foreach ([1 => 'Heute', 7 => '7 Tage', 14 => '14 Tage', 30 => '30 Tage', 60 => '60 Tage', 90 => '90 Tage', 0 => 'Alle'] as $value => $label)
|
||
<option value="{{ $value }}" {{ $days == $value ? 'selected' : '' }}>{{ $label }}</option>
|
||
@endforeach
|
||
</select>
|
||
</form>
|
||
</div>
|
||
|
||
@php $topCount = $funnelSteps[0]['count'] > 0 ? $funnelSteps[0]['count'] : 1; @endphp
|
||
|
||
<div class="row">
|
||
{{-- ── Funnel ──────────────────────────────────────────────────────── --}}
|
||
<div class="col-lg-8">
|
||
<div class="card mb-4">
|
||
<div class="card-header"><strong>Checkout-Funnel</strong></div>
|
||
<div class="card-body">
|
||
@foreach ($funnelSteps as $i => $step)
|
||
@php $barWidth = $topCount > 0 ? round($step['count'] / $topCount * 100) : 0; @endphp
|
||
<div class="mb-3">
|
||
<div class="d-flex justify-content-between mb-1">
|
||
<div>
|
||
<span class="badge badge-secondary mr-1">{{ $i + 1 }}</span>
|
||
<strong>{{ $step['label'] }}</strong>
|
||
</div>
|
||
<div class="text-right">
|
||
<span class="font-weight-bold">{{ number_format($step['count'], 0, ',', '.') }}</span>
|
||
@if ($step['conversion'] !== null)
|
||
<span
|
||
class="ml-2 small {{ $step['conversion'] >= 70 ? 'text-success' : ($step['conversion'] >= 40 ? 'text-warning' : 'text-danger') }}">
|
||
↓ {{ $step['conversion'] }}%
|
||
</span>
|
||
@endif
|
||
</div>
|
||
</div>
|
||
<div class="progress" style="height:20px;">
|
||
<div class="progress-bar {{ $i === 0 ? 'bg-primary' : ($i === count($funnelSteps) - 1 ? 'bg-success' : 'bg-info') }}"
|
||
style="width:{{ max($barWidth, $step['count'] > 0 ? 2 : 0) }}%" role="progressbar">
|
||
@if ($barWidth > 8)
|
||
{{ $barWidth }}%
|
||
@endif
|
||
</div>
|
||
</div>
|
||
</div>
|
||
@endforeach
|
||
|
||
@php
|
||
$totalConversion =
|
||
$funnelSteps[0]['count'] > 0
|
||
? round(($funnelSteps[4]['count'] / $funnelSteps[0]['count']) * 100, 1)
|
||
: 0;
|
||
@endphp
|
||
<div
|
||
class="alert {{ $totalConversion >= 50 ? 'alert-success' : ($totalConversion >= 25 ? 'alert-warning' : 'alert-danger') }} mt-2 mb-0 py-2">
|
||
<strong>Gesamt-Konversionsrate: {{ $totalConversion }}%</strong>
|
||
<small class="text-muted ml-2">(Checkout aufgerufen → Zahlung bestätigt)</small>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{{-- ── Rechte Spalte ────────────────────────────────────────────────── --}}
|
||
<div class="col-lg-4">
|
||
|
||
{{-- Rückkehr-Status --}}
|
||
<div class="card mb-4">
|
||
<div class="card-header"><strong>Rückkehr von PAYONE</strong></div>
|
||
<div class="card-body p-0">
|
||
<table class="table table-sm mb-0">
|
||
<tbody>
|
||
@forelse($returnStats as $status => $count)
|
||
<tr>
|
||
<td>
|
||
@if ($status === 'success')
|
||
<span class="badge badge-success">success</span>
|
||
@elseif($status === 'cancel')
|
||
<span class="badge badge-warning">cancel</span>
|
||
@elseif($status === 'error')
|
||
<span class="badge badge-danger">error</span>
|
||
@else
|
||
<span class="badge badge-secondary">{{ $status ?? '?' }}</span>
|
||
@endif
|
||
</td>
|
||
<td class="text-right font-weight-bold">{{ number_format($count, 0, ',', '.') }}</td>
|
||
</tr>
|
||
@empty
|
||
<tr>
|
||
<td colspan="2" class="text-muted text-center py-3 small">Noch keine Daten</td>
|
||
</tr>
|
||
@endforelse
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
|
||
{{-- Quell-Kanal --}}
|
||
<div class="card mb-4">
|
||
<div class="card-header"><strong>Quelle (Checkout-Aufrufe)</strong></div>
|
||
<div class="card-body p-0">
|
||
<table class="table table-sm mb-0">
|
||
<tbody>
|
||
@forelse($sourceBreakdown as $key => $source)
|
||
<tr>
|
||
<td style="font-size:0.82rem">
|
||
@if ($key === 'kundenshop')
|
||
<i class="ion ion-md-cart text-success mr-1"></i>
|
||
@elseif($key === 'salescenter')
|
||
<i class="ion ion-md-briefcase text-primary mr-1"></i>
|
||
@elseif($key === 'beraterzugang')
|
||
<i class="ion ion-md-person text-info mr-1"></i>
|
||
@elseif($key === 'testserver')
|
||
<i class="ion ion-md-flask text-warning mr-1"></i>
|
||
@else
|
||
<i class="ion ion-md-help text-muted mr-1"></i>
|
||
@endif
|
||
{{ $source['label'] }}
|
||
</td>
|
||
<td class="text-right font-weight-bold">
|
||
{{ number_format($source['count'], 0, ',', '.') }}</td>
|
||
</tr>
|
||
@empty
|
||
<tr>
|
||
<td colspan="2" class="text-muted text-center py-3 small">Noch keine Daten</td>
|
||
</tr>
|
||
@endforelse
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
|
||
{{-- ── Ereignisse (gefiltert + paginiert) ─────────────────────────────────── --}}
|
||
<div class="card mb-4">
|
||
<div class="card-header d-flex justify-content-between align-items-center flex-wrap">
|
||
<strong>Ereignisse</strong>
|
||
{{-- Filter-Leiste --}}
|
||
<form method="GET" class="form-inline mt-1 mt-md-0">
|
||
<input type="hidden" name="days" value="{{ $days }}">
|
||
<select name="event" class="form-control form-control-sm mr-1" onchange="this.form.submit()">
|
||
<option value="">Alle Ereignisse</option>
|
||
@foreach (\App\Models\CheckoutFunnelEvent::eventLabels() as $val => $label)
|
||
<option value="{{ $val }}" {{ $filterEvent == $val ? 'selected' : '' }}>
|
||
{{ $label }}</option>
|
||
@endforeach
|
||
</select>
|
||
<select name="return_status" class="form-control form-control-sm mr-1" onchange="this.form.submit()">
|
||
<option value="">Alle Status</option>
|
||
<option value="success" {{ $filterStatus == 'success' ? 'selected' : '' }}>success</option>
|
||
<option value="cancel" {{ $filterStatus == 'cancel' ? 'selected' : '' }}>cancel</option>
|
||
<option value="error" {{ $filterStatus == 'error' ? 'selected' : '' }}>error</option>
|
||
</select>
|
||
<select name="source" class="form-control form-control-sm mr-1" onchange="this.form.submit()">
|
||
<option value="">Alle Quellen</option>
|
||
@foreach (\App\Models\CheckoutFunnelEvent::sourceLabels() as $val => $label)
|
||
<option value="{{ $val }}" {{ $filterSource == $val ? 'selected' : '' }}>
|
||
{{ $label }}</option>
|
||
@endforeach
|
||
</select>
|
||
@if ($filterEvent || $filterStatus || $filterSource)
|
||
<a href="{{ route('admin.payment-dashboard.funnel', ['days' => $days]) }}"
|
||
class="btn btn-sm btn-outline-secondary">✕ Reset</a>
|
||
@endif
|
||
</form>
|
||
</div>
|
||
<div class="table-responsive">
|
||
<table class="table table-sm table-hover mb-0">
|
||
<thead class="thead-light">
|
||
<tr>
|
||
<th>Zeitpunkt</th>
|
||
<th>Ereignis</th>
|
||
<th>Quelle</th>
|
||
<th>Domain</th>
|
||
<th>Berater</th>
|
||
<th>Order-ID</th>
|
||
<th>Zahlungsart</th>
|
||
<th>Betrag</th>
|
||
<th>Status</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
@forelse($recentEvents as $event)
|
||
<tr>
|
||
<td class="text-muted small text-nowrap">
|
||
{{ $event->created_at->format('d.m. H:i:s') }}<br>
|
||
<small>{{ $event->created_at->diffForHumans() }}</small>
|
||
</td>
|
||
<td>
|
||
@php
|
||
$badgeClass = match ($event->event) {
|
||
'checkout_visited' => 'badge-secondary',
|
||
'form_submitted' => 'badge-info',
|
||
'payment_initiated' => 'badge-primary',
|
||
'payment_returned' => match ($event->return_status) {
|
||
'success' => 'badge-success',
|
||
'cancel' => 'badge-warning',
|
||
default => 'badge-danger',
|
||
},
|
||
'payment_confirmed' => 'badge-success',
|
||
default => 'badge-light',
|
||
};
|
||
@endphp
|
||
<span class="badge {{ $badgeClass }}">{{ $event->event_label }}</span>
|
||
@if ($event->metadata && isset($event->metadata['txaction']))
|
||
<small class="text-muted ml-1">{{ $event->metadata['txaction'] }}</small>
|
||
@endif
|
||
</td>
|
||
<td class="small">
|
||
@php $src = $event->source_type; @endphp
|
||
@if ($src === 'kundenshop')
|
||
<span class="badge badge-success" title="Kundenshop">Shop</span>
|
||
@elseif($src === 'salescenter')
|
||
<span class="badge badge-primary" title="Salescenter">SC</span>
|
||
@elseif($src === 'beraterzugang')
|
||
<span class="badge badge-info" title="Beraterzugang">BZ</span>
|
||
@elseif($src === 'testserver')
|
||
<span class="badge badge-warning" title="Testserver">TEST</span>
|
||
@else
|
||
<span class="badge badge-secondary">?</span>
|
||
@endif
|
||
</td>
|
||
<td class="text-muted small">{{ $event->domain ?? '–' }}</td>
|
||
<td class="small">
|
||
@if ($event->consultant)
|
||
{{ $event->consultant->firstname }} {{ $event->consultant->lastname }}
|
||
@else
|
||
<span class="text-muted">–</span>
|
||
@endif
|
||
</td>
|
||
<td class="small">
|
||
@if ($event->shopping_order_id)
|
||
<a href="{{ route('admin_sales_users_detail', $event->shopping_order_id) }}"
|
||
target="_blank">
|
||
#{{ $event->shopping_order_id }}
|
||
</a>
|
||
@else
|
||
<span class="text-muted">–</span>
|
||
@endif
|
||
</td>
|
||
<td class="small">{{ $event->payment_method ?? '–' }}</td>
|
||
<td class="small">
|
||
{{ $event->amount_cents ? number_format($event->amount_cents / 100, 2, ',', '.') . ' €' : '–' }}
|
||
</td>
|
||
<td class="small">
|
||
@if ($event->return_status)
|
||
@if ($event->return_status === 'success')
|
||
<span class="badge badge-success">{{ $event->return_status }}</span>
|
||
@elseif($event->return_status === 'cancel')
|
||
<span class="badge badge-warning">{{ $event->return_status }}</span>
|
||
@else
|
||
<span class="badge badge-danger">{{ $event->return_status }}</span>
|
||
@endif
|
||
@else
|
||
<span class="text-muted">–</span>
|
||
@endif
|
||
</td>
|
||
</tr>
|
||
@empty
|
||
<tr>
|
||
<td colspan="9" class="text-center text-muted py-4">
|
||
Keine Ereignisse gefunden.
|
||
@if ($filterEvent || $filterStatus || $filterSource)
|
||
<a href="{{ route('admin.payment-dashboard.funnel', ['days' => $days]) }}">Filter
|
||
zurücksetzen</a>
|
||
@endif
|
||
</td>
|
||
</tr>
|
||
@endforelse
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
@if ($recentEvents->hasPages())
|
||
<div class="card-footer">
|
||
{{ $recentEvents->links() }}
|
||
</div>
|
||
@endif
|
||
</div>
|
||
|
||
{{-- Tracking-Hinweis --}}
|
||
<div class="alert alert-info small py-2">
|
||
<i class="ion ion-md-information-circle mr-1"></i>
|
||
<strong>Hinweis:</strong> Das Tracking ist ab dem Aktivierungszeitpunkt aktiv. Ältere Checkouts sind nicht
|
||
enthalten.
|
||
Schritt 5 „PAYONE Callback" wird sowohl bei synchroner Bestätigung (<code>transactionApproved</code>)
|
||
als auch bei asynchronem IPN-Callback (<code>txaction=paid</code> und <code>appointed</code>) erfasst.
|
||
</div>
|
||
|
||
@endsection
|