mivita/resources/views/dashboard/_statistics.blade.php
Kevin 27b4ad3eb5 Backoffice-Statistik vereinfacht & Dashboard-Monatsstatistik konsolidiert
- Statistik-Uebersicht: Tabelle auf Linie/Berater/Umsatz(Punkte)/Teamabos/
  Teamkundenabos reduziert; Neupartner als grünes "+N"-Badge in der
  Berater-Spalte; 1000-Punkte-Shop & Neupartner als klickbare KPI-Cards
  (Augen-Icon/Liste anzeigen)
- Teamkundenabos-Drilldown pro Teampartner gruppiert (Anzahl + Volumenpunkte
  gesamt, aufklappbar je Abo), CSV-Export angepasst
- Home: fehlerhafte Monatsstatistik-Card (inkonsistente aktiv-Definition,
  kein Monatsbezug, schwere Live-Logik im Blade) mit der Abo-KPI-Card
  zusammengefuehrt; Logik nach HomeController::buildMonthlyStats() ausgelagert,
  konsistent ueber TeamAboOverviewService/BackofficeDashboardService
- Team-Payline fuer laufenden Monat live berechnet und 6h gecacht (Snapshot)
- Kacheln in Cluster (Umsatz/Neupartner/Abos) gruppiert
- Services: teamKpis() um *_new ergaenzt, personalAboKpis() & newPartnersCount() neu
- i18n (de/en/es/fr), Tests ergaenzt/aktualisiert

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 13:00:38 +00:00

180 lines
9 KiB
PHP

@if (! empty($monthlyStats))
@php
$s = $monthlyStats;
$isVip = auth()->user()?->isVIP() ?? false;
$m = $s['month'];
$y = $s['year'];
$teamShow = route('user_abos_team_show');
$shop1000Link = $isVip
? route('user_backoffice_statistics_details', ['line' => 0, 'metric' => 'shop_1000', 'month' => $m, 'year' => $y])
: $teamShow;
$newPartnersLink = $isVip
? route('user_backoffice_statistics_details', ['line' => 0, 'metric' => 'new_partners', 'month' => $m, 'year' => $y])
: null;
$fmt = fn ($value) => number_format((float) $value, 0, ',', '.');
$clusters = [
[
'title' => __('home.stats_group_turnover'),
'col' => 'col-md-6 col-lg-4',
'tiles' => [
[
'label' => __('home.customer_turnover_points'), 'value' => $fmt($s['customer_points']),
'icon' => 'ion-md-cart', 'color' => 'primary', 'link' => null, 'new' => null, 'live' => false,
],
[
'label' => __('home.team_turnover_points') . ' (Payline)', 'value' => $fmt($s['team_payline_points']),
'icon' => 'ion-md-people', 'color' => 'success', 'link' => null, 'new' => null, 'live' => $s['payline_is_live'],
],
[
'label' => __('abo.shop_1000'), 'value' => $fmt($s['shop_1000']),
'icon' => 'ion-md-trophy', 'color' => 'success', 'link' => $shop1000Link, 'new' => null, 'live' => false,
],
],
],
[
'title' => __('home.stats_group_new_partners'),
'col' => 'col-md-6',
'tiles' => [
[
'label' => __('home.direct_new_partners'), 'value' => $fmt($s['direct_new_partners']),
'icon' => 'ion-md-person-add', 'color' => 'info', 'link' => null, 'new' => null, 'live' => false,
],
[
'label' => __('home.team_new_partners'), 'value' => $fmt($s['new_partners']),
'icon' => 'ion-md-contacts', 'color' => 'warning', 'link' => $newPartnersLink, 'new' => null, 'live' => false,
],
],
],
[
'title' => __('home.stats_group_subscriptions'),
'col' => 'col-sm-6 col-lg-3',
'tiles' => [
[
'label' => __('home.own_subscriptions'), 'value' => $fmt($s['own_abos']),
'icon' => 'ion-md-person', 'color' => 'secondary', 'link' => route('user_abos', 'me'), 'new' => $s['own_abos_new'], 'live' => false,
],
[
'label' => __('home.own_customer_subscriptions'), 'value' => $fmt($s['own_customer_abos']),
'icon' => 'ion-md-repeat', 'color' => 'secondary', 'link' => route('user_abos', 'ot'), 'new' => $s['own_customer_abos_new'], 'live' => false,
],
[
'label' => __('home.team_subscriptions'), 'value' => $fmt($s['team_partner_abos']),
'icon' => 'ion-md-sync', 'color' => 'dark', 'link' => $teamShow, 'new' => $s['team_partner_abos_new'], 'live' => false,
],
[
'label' => __('home.team_customer_subscriptions'), 'value' => $fmt($s['team_customer_abos']),
'icon' => 'ion-md-git-network', 'color' => 'dark', 'link' => $teamShow, 'new' => $s['team_customer_abos_new'], 'live' => false,
],
],
],
];
@endphp
<div class="d-flex col-xl-12 align-items-stretch">
<div class="card w-100 mb-4">
<h5 class="card-header with-elements d-flex justify-content-between align-items-center flex-wrap">
<div class="card-header-title">
<i class="ion ion-md-stats mr-2"></i>{{ __('home.monthly_statistics') }} -
{{ HTMLHelper::getMonth($m) }} {{ $y }}
</div>
<div class="d-flex align-items-center mt-2 mt-md-0">
<select id="stats-month-filter" class="form-control custom-select form-control-sm mr-2"
style="width: auto;">
@foreach (HTMLHelper::getTransMonths() as $monthNum => $monthName)
<option value="{{ $monthNum }}" {{ $m == $monthNum ? 'selected' : '' }}>
{{ $monthName }}</option>
@endforeach
</select>
<select id="stats-year-filter" class="form-control custom-select form-control-sm"
style="width: auto;">
@foreach (HTMLHelper::getYearRange() as $year)
<option value="{{ $year }}" {{ $y == $year ? 'selected' : '' }}>
{{ $year }}</option>
@endforeach
</select>
</div>
</h5>
<div class="card-body">
@foreach ($clusters as $cluster)
<h6 class="text-muted text-uppercase font-weight-bold small mb-3 {{ ! $loop->first ? 'mt-2 pt-3 border-top' : '' }}">
{{ $cluster['title'] }}
</h6>
<div class="row {{ ! $loop->last ? 'mb-2' : '' }}">
@foreach ($cluster['tiles'] as $tile)
<div class="{{ $cluster['col'] }} mb-4">
@if ($tile['link'])
<a href="{{ $tile['link'] }}"
class="d-flex align-items-center text-decoration-none text-body dashboard-stat-link">
@else
<div class="d-flex align-items-center">
@endif
<div class="bg-{{ $tile['color'] }} text-white rounded-circle d-flex align-items-center justify-content-center"
style="width: 50px; height: 50px; flex: 0 0 auto;">
<i class="ion {{ $tile['icon'] }}" style="font-size: 1.5rem;"></i>
</div>
<div class="ml-3">
<div class="text-muted small">
{{ $tile['label'] }}
@if ($tile['live'])
<span class="badge badge-pill badge-info ml-1"
title="{{ __('home.live_calculation_hint') }}">Live</span>
@endif
@if ($tile['link'])
<i class="ion ion-md-open text-primary ml-1" style="font-size: .8rem;"></i>
@endif
</div>
<div class="font-weight-bold text-large">
{{ $tile['value'] }}
@if (! empty($tile['new']) && $tile['new'] > 0)
<span class="badge badge-pill badge-success ml-1"
title="{{ __('abo.new_this_month') }}">+{{ $tile['new'] }}</span>
@endif
</div>
</div>
@if ($tile['link'])
</a>
@else
</div>
@endif
</div>
@endforeach
</div>
@endforeach
</div>
</div>
</div>
<style>
.dashboard-stat-link {
transition: transform .15s ease-in-out;
}
.dashboard-stat-link:hover {
transform: translateY(-2px);
}
.dashboard-stat-link:hover .text-large {
color: #4e73df;
}
</style>
<script>
$(document).ready(function() {
function updateStatsFilter() {
var month = $('#stats-month-filter').val();
var year = $('#stats-year-filter').val();
var url = new URL(window.location.href);
url.searchParams.set('stats_month', month);
url.searchParams.set('stats_year', year);
window.location.href = url.toString();
}
$('#stats-month-filter, #stats-year-filter').on('change', function() {
updateStatsFilter();
});
});
</script>
@endif