Zwei neue Listen unter der Team-Übersicht: Ranking der Berater nach neuen Kundenabos im Monat (inkl. Gesamtbestand und Linie) sowie eigene und Kundenabos aller Führungskräfte ab Stufe Vertriebsleiter mit Gesamtsumme. Beides fließt mit in die Monats-Snapshots ein. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
415 lines
21 KiB
PHP
415 lines
21 KiB
PHP
@extends('layouts.layout-2')
|
|
|
|
@section('content')
|
|
<h4 class="font-weight-bold py-2 mb-2 d-flex justify-content-between align-items-center w-100">
|
|
<div>
|
|
{{ __('navigation.team_statistics') }}
|
|
<span class="badge badge-secondary ml-2">VIP</span>
|
|
</div>
|
|
</h4>
|
|
|
|
<div class="card mb-4">
|
|
<div class="card-body">
|
|
<div class="row align-items-center">
|
|
<div class="col-md-8">
|
|
<h5 class="mb-2">{{ __('navigation.statistics') }} / Backoffice MVP</h5>
|
|
<p class="text-muted mb-md-0">
|
|
Linienbasierte Übersicht für alle vorhandenen Team-Linien. Klickbare Zahlen führen direkt in die
|
|
passenden Namen- und Detaillisten.
|
|
</p>
|
|
@if (!empty($performance))
|
|
<div class="small text-muted mt-2">
|
|
Datenquelle:
|
|
<span class="badge badge-outline-default">{{ $performance['source_label'] }}</span>
|
|
<span class="ml-2">Berechnet in {{ number_format($performance['duration_ms'], 2, ',', '.') }}
|
|
ms</span>
|
|
@if (!empty($performance['calculated_at']))
|
|
<span class="ml-2">Snapshot vom {{ $performance['calculated_at'] }}</span>
|
|
@endif
|
|
</div>
|
|
@endif
|
|
</div>
|
|
<div class="col-md-4 mt-3 mt-md-0">
|
|
<form method="GET" action="{{ route('user_backoffice_statistics') }}"
|
|
class="form-inline justify-content-md-end">
|
|
<a href="{{ route('user_backoffice_statistics_overview_export', ['month' => $selectedMonth, 'year' => $selectedYear]) }}"
|
|
class="btn btn-sm btn-outline-secondary mr-2">
|
|
<span class="ion ion-md-download mr-1"></span> CSV
|
|
</a>
|
|
<select name="month" class="form-control custom-select form-control-sm mr-2">
|
|
@foreach ($filterMonths as $monthNumber => $monthName)
|
|
<option value="{{ $monthNumber }}"
|
|
{{ (int) $selectedMonth === (int) $monthNumber ? 'selected' : '' }}>
|
|
{{ $monthName }}
|
|
</option>
|
|
@endforeach
|
|
</select>
|
|
<select name="year" class="form-control custom-select form-control-sm mr-2">
|
|
@foreach ($filterYears as $year)
|
|
<option value="{{ $year }}"
|
|
{{ (int) $selectedYear === (int) $year ? 'selected' : '' }}>
|
|
{{ $year }}
|
|
</option>
|
|
@endforeach
|
|
</select>
|
|
<button type="submit" class="btn btn-sm btn-primary">Anzeigen</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
@php
|
|
$clickableMetrics = ['consultants', 'total_points', 'team_partner_abos', 'team_customer_abos'];
|
|
|
|
$metricHeadings = [
|
|
'consultants' => 'Berater',
|
|
'total_points' => 'Umsatz (Punkte)',
|
|
'team_partner_abos' => 'Teamabos',
|
|
'team_customer_abos' => 'Teamkundenabos',
|
|
];
|
|
|
|
$formatValue = function ($value, string $metric): string {
|
|
if ($metric === 'total_points') {
|
|
return \App\Services\Util::formatNumber($value);
|
|
}
|
|
|
|
return number_format((float) $value, 0, ',', '.');
|
|
};
|
|
|
|
$newAboMetric = function (array $row, string $metric): ?string {
|
|
return match ($metric) {
|
|
'consultants' => 'new_partners',
|
|
'team_partner_abos' => 'team_partner_abos_new',
|
|
'team_customer_abos' => 'team_customer_abos_new',
|
|
default => null,
|
|
};
|
|
};
|
|
|
|
$selectedMonthName = $filterMonths[(int) $selectedMonth] ?? $selectedMonth;
|
|
@endphp
|
|
|
|
<div class="row mb-4">
|
|
<div class="col-md-6 mb-3 mb-md-0">
|
|
<a href="{{ route('user_backoffice_statistics_details', [
|
|
'line' => 0,
|
|
'metric' => 'new_partners',
|
|
'month' => $selectedMonth,
|
|
'year' => $selectedYear,
|
|
]) }}"
|
|
class="card h-100 text-decoration-none backoffice-kpi-card">
|
|
<div class="card-body d-flex justify-content-between align-items-center">
|
|
<div>
|
|
<div class="text-muted small">Neupartner ({{ $selectedMonthName }} {{ $selectedYear }})</div>
|
|
<div class="display-4 font-weight-bold text-primary">
|
|
{{ number_format((float) ($statistics['totals']['new_partners'] ?? 0), 0, ',', '.') }}
|
|
</div>
|
|
<div class="text-primary small font-weight-semibold mt-1">
|
|
<span class="ion ion-md-eye mr-1"></span> Liste anzeigen
|
|
</div>
|
|
</div>
|
|
<span class="ion ion-md-person-add display-4 text-muted"></span>
|
|
</div>
|
|
</a>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<a href="{{ route('user_backoffice_statistics_details', [
|
|
'line' => 0,
|
|
'metric' => 'shop_1000',
|
|
'month' => $selectedMonth,
|
|
'year' => $selectedYear,
|
|
]) }}"
|
|
class="card h-100 text-decoration-none backoffice-kpi-card">
|
|
<div class="card-body d-flex justify-content-between align-items-center">
|
|
<div>
|
|
<div class="text-muted small">1000-Punkte-Shops ({{ $selectedMonthName }} {{ $selectedYear }})
|
|
</div>
|
|
<div class="display-4 font-weight-bold text-success">
|
|
{{ number_format((float) ($statistics['totals']['shop_1000'] ?? 0), 0, ',', '.') }}
|
|
</div>
|
|
<div class="text-primary small font-weight-semibold mt-1">
|
|
<span class="ion ion-md-eye mr-1"></span> Liste anzeigen
|
|
</div>
|
|
</div>
|
|
<span class="ion ion-md-trophy display-4 text-muted"></span>
|
|
</div>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card mb-4">
|
|
<div class="card-body p-0">
|
|
<div class="table-responsive">
|
|
<table class="table table-hover mb-0">
|
|
<thead>
|
|
<tr>
|
|
<th>Linie</th>
|
|
@foreach ($clickableMetrics as $metric)
|
|
<th class="text-right">{{ $metricHeadings[$metric] }}</th>
|
|
@endforeach
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
@foreach ($statistics['lines'] as $line)
|
|
<tr>
|
|
<td class="font-weight-semibold">{{ $line['label'] }}</td>
|
|
@foreach ($clickableMetrics as $metric)
|
|
<td class="text-right">
|
|
@if ($line[$metric] > 0)
|
|
<a href="{{ route('user_backoffice_statistics_details', [
|
|
'line' => $line['line'],
|
|
'metric' => $metric,
|
|
'month' => $selectedMonth,
|
|
'year' => $selectedYear,
|
|
]) }}"
|
|
class="badge badge-pill badge-outline-default backoffice-statistics-click-badge">
|
|
{{ $formatValue($line[$metric], $metric) }}
|
|
</a>
|
|
@php $newMetric = $newAboMetric($line, $metric); @endphp
|
|
@if ($newMetric && $line[$newMetric] > 0)
|
|
<span
|
|
class="badge badge-pill badge-success ml-1">+{{ number_format($line[$newMetric], 0, ',', '.') }}</span>
|
|
@endif
|
|
@else
|
|
<span
|
|
class="badge badge-pill badge-light backoffice-statistics-empty-badge">0</span>
|
|
@endif
|
|
</td>
|
|
@endforeach
|
|
</tr>
|
|
@endforeach
|
|
</tbody>
|
|
<tfoot>
|
|
<tr class="font-weight-bold bg-light">
|
|
<td>{{ $statistics['totals']['label'] }}</td>
|
|
@foreach ($clickableMetrics as $metric)
|
|
<td class="text-right">
|
|
@if ($statistics['totals'][$metric] > 0)
|
|
<a href="{{ route('user_backoffice_statistics_details', [
|
|
'line' => 0,
|
|
'metric' => $metric,
|
|
'month' => $selectedMonth,
|
|
'year' => $selectedYear,
|
|
]) }}"
|
|
class="badge badge-pill badge-secondary backoffice-statistics-click-badge">
|
|
{{ $formatValue($statistics['totals'][$metric], $metric) }}
|
|
</a>
|
|
@php $newMetric = $newAboMetric($statistics['totals'], $metric); @endphp
|
|
@if ($newMetric && $statistics['totals'][$newMetric] > 0)
|
|
<span
|
|
class="badge badge-pill badge-success ml-1">+{{ number_format($statistics['totals'][$newMetric], 0, ',', '.') }}</span>
|
|
@endif
|
|
@else
|
|
<span
|
|
class="badge badge-pill badge-light backoffice-statistics-empty-badge">0</span>
|
|
@endif
|
|
</td>
|
|
@endforeach
|
|
</tr>
|
|
</tfoot>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
@php
|
|
$newCustomerAbos = $statistics['new_customer_abos'] ?? null;
|
|
$leaderAbos = $statistics['leader_abos'] ?? null;
|
|
@endphp
|
|
|
|
<div class="row">
|
|
<div class="col-lg-6 mb-4">
|
|
<div class="card h-100">
|
|
<div class="card-header d-flex justify-content-between align-items-center">
|
|
<div>
|
|
<h5 class="mb-0">Neue Kundenabos</h5>
|
|
<div class="text-muted small">{{ $selectedMonthName }} {{ $selectedYear }} · nach Anzahl sortiert</div>
|
|
</div>
|
|
<span class="display-4 font-weight-bold text-primary mb-0">
|
|
{{ number_format((float) ($statistics['totals']['team_customer_abos_new'] ?? 0), 0, ',', '.') }}
|
|
</span>
|
|
</div>
|
|
@if ($newCustomerAbos === null)
|
|
<div class="card-body">
|
|
<p class="text-muted mb-0">
|
|
Für diesen Monat liegt noch ein älterer Snapshot ohne diese Liste vor.
|
|
</p>
|
|
</div>
|
|
@elseif (count($newCustomerAbos) === 0)
|
|
<div class="card-body">
|
|
<p class="text-muted mb-0">In diesem Monat wurden keine neuen Kundenabos abgeschlossen.</p>
|
|
</div>
|
|
@else
|
|
<div class="table-responsive">
|
|
<table class="table table-hover mb-0">
|
|
<thead>
|
|
<tr>
|
|
<th>Berater</th>
|
|
<th class="text-right">Gesamt</th>
|
|
<th class="text-right">Neu</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
@foreach ($newCustomerAbos as $row)
|
|
<tr>
|
|
<td>
|
|
<div class="font-weight-semibold">{{ $row['name'] }}</div>
|
|
<div class="text-muted small">Linie {{ $row['line'] }} · {{ $row['career_level'] }}</div>
|
|
</td>
|
|
<td class="text-right align-middle">
|
|
<span class="badge badge-pill badge-outline-default backoffice-statistics-click-badge">
|
|
{{ number_format((float) ($row['total_abo_count'] ?? 0), 0, ',', '.') }}
|
|
</span>
|
|
</td>
|
|
<td class="text-right align-middle">
|
|
<span class="badge badge-pill badge-success backoffice-statistics-click-badge">
|
|
+{{ number_format((float) $row['abo_count'], 0, ',', '.') }}
|
|
</span>
|
|
</td>
|
|
</tr>
|
|
@endforeach
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
@endif
|
|
</div>
|
|
</div>
|
|
<div class="col-lg-6 mb-4">
|
|
<div class="card h-100">
|
|
<div class="card-header d-flex justify-content-between align-items-center">
|
|
<div>
|
|
<h5 class="mb-0">Abos Führungskräfte</h5>
|
|
<div class="text-muted small">ab Stufe {{ $leaderAbos['level_name'] ?? 'Vertriebsleiter' }} · aktueller Stand</div>
|
|
</div>
|
|
<div class="text-right">
|
|
<span class="display-4 font-weight-bold text-primary mb-0">
|
|
{{ number_format((float) ($leaderAbos['total'] ?? 0), 0, ',', '.') }}
|
|
</span>
|
|
@if ($leaderAbos !== null)
|
|
<div class="text-muted small">
|
|
{{ number_format((float) ($leaderAbos['total_own'] ?? 0), 0, ',', '.') }} eigene ·
|
|
{{ number_format((float) ($leaderAbos['total_customer'] ?? 0), 0, ',', '.') }} Kunden
|
|
</div>
|
|
@endif
|
|
</div>
|
|
</div>
|
|
@if ($leaderAbos === null)
|
|
<div class="card-body">
|
|
<p class="text-muted mb-0">
|
|
Für diesen Monat liegt noch ein älterer Snapshot ohne diese Liste vor.
|
|
</p>
|
|
</div>
|
|
@elseif (count($leaderAbos['rows']) === 0)
|
|
<div class="card-body">
|
|
<p class="text-muted mb-0">
|
|
Im Team gibt es aktuell keine Führungskräfte ab Stufe {{ $leaderAbos['level_name'] }}.
|
|
</p>
|
|
</div>
|
|
@else
|
|
<div class="table-responsive">
|
|
<table class="table table-hover mb-0">
|
|
<thead>
|
|
<tr>
|
|
<th>Führungskraft</th>
|
|
<th class="text-right">Eigene Abos</th>
|
|
<th class="text-right">Kundenabos</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
@foreach ($leaderAbos['rows'] as $row)
|
|
<tr>
|
|
<td>
|
|
<div class="font-weight-semibold">{{ $row['name'] }}</div>
|
|
<div class="text-muted small">Linie {{ $row['line'] }} · {{ $row['career_level'] }}</div>
|
|
</td>
|
|
<td class="text-right align-middle">
|
|
@if (($row['own_abo_count'] ?? 0) > 0)
|
|
<span class="badge badge-pill badge-outline-default backoffice-statistics-click-badge">
|
|
{{ number_format((float) $row['own_abo_count'], 0, ',', '.') }}
|
|
</span>
|
|
@else
|
|
<span class="badge badge-pill badge-light backoffice-statistics-empty-badge">0</span>
|
|
@endif
|
|
</td>
|
|
<td class="text-right align-middle">
|
|
@if (($row['customer_abo_count'] ?? 0) > 0)
|
|
<span class="badge badge-pill badge-outline-default backoffice-statistics-click-badge">
|
|
{{ number_format((float) $row['customer_abo_count'], 0, ',', '.') }}
|
|
</span>
|
|
@else
|
|
<span class="badge badge-pill badge-light backoffice-statistics-empty-badge">0</span>
|
|
@endif
|
|
</td>
|
|
</tr>
|
|
@endforeach
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
@endif
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row">
|
|
<div class="col-md-6 mb-4">
|
|
<div class="card h-100">
|
|
<div class="card-body">
|
|
<h5 class="mb-2">Begriffe</h5>
|
|
<p class="text-muted mb-2">
|
|
Teamabos sind Berater-/Eigenabos im Team. Teamkundenabos sind Kundenabos, die einem Berater
|
|
aus der jeweiligen Linie zugeordnet sind.
|
|
</p>
|
|
<p class="text-muted mb-0">
|
|
Umsatz = Gesamtpunkte der Linie (Eigen- und Kundenpunkte zusammen). Ein Klick auf eine Zahl
|
|
öffnet die zugehörige Namensliste.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-6 mb-4">
|
|
<div class="card h-100">
|
|
<div class="card-body">
|
|
<h5 class="mb-2">Nächster Ausbau</h5>
|
|
<p class="text-muted mb-0">
|
|
Die Detailansichten liefern bereits Namen und Basisdaten. Im nächsten Schritt werden Abo-Punkte
|
|
und Umsatzarten noch feiner nach Abo, Einzelbestellung und Shop getrennt.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<style>
|
|
.backoffice-kpi-card {
|
|
cursor: pointer;
|
|
transition: box-shadow .15s ease-in-out, transform .15s ease-in-out, border-color .15s ease-in-out;
|
|
border: 1px solid rgba(0, 0, 0, .08);
|
|
}
|
|
|
|
.backoffice-kpi-card:hover,
|
|
.backoffice-kpi-card:focus {
|
|
border-color: #4e73df;
|
|
box-shadow: 0 .25rem .75rem rgba(78, 115, 223, .25);
|
|
transform: translateY(-2px);
|
|
}
|
|
|
|
.backoffice-statistics-click-badge,
|
|
.backoffice-statistics-empty-badge {
|
|
display: inline-block;
|
|
font-size: .85rem;
|
|
line-height: 1.35;
|
|
min-width: 2.35rem;
|
|
padding: .35rem .55rem;
|
|
text-align: center;
|
|
}
|
|
|
|
.backoffice-statistics-click-badge {
|
|
text-decoration: none;
|
|
}
|
|
|
|
.backoffice-statistics-click-badge:hover,
|
|
.backoffice-statistics-click-badge:focus {
|
|
text-decoration: none;
|
|
}
|
|
</style>
|
|
@endsection
|