Die Pakete stehen im Plan als umgesetzt und sind live, der Controller- und
View-Teil lag aber nur im Arbeitsbaum (Sprachkeys und Model-Methoden sind
seit 5a38f4a/685f7a9 versioniert):
- K5: Abo-Bestellungen in Punkteuebersicht, Dashboard-Punkten und eigener
Statistik tuerkis mit Zusatz "(Abo)", Legende darueber.
- K4: Summe der Payline-Abos ("149 + 391 = 540 Abos gesamt").
- Home: payline_is_career_fallback, solange die Monatsquali aussteht
(Kundenentscheid 01.09.); die View wertet das Kennzeichen schon aus.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
325 lines
18 KiB
PHP
325 lines
18 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>
|
|
{{ __('abo.team_subscriptions') }} / {{ __('navigation.overview') }}
|
|
</div>
|
|
</h4>
|
|
|
|
{{-- Gesamtübersicht (aktueller Bestand) --}}
|
|
@php($totals = $overview['totals'])
|
|
<div class="row mb-2">
|
|
<div class="col-md-4 col-12 mb-3">
|
|
<div class="card h-100">
|
|
<div class="card-body text-center">
|
|
<div class="text-muted small">{{ __('abo.team_total_abos') }}</div>
|
|
<div class="display-5 font-weight-bold text-primary">{{ $totals['total_abos'] }}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-4 col-6 mb-3">
|
|
<div class="card h-100">
|
|
<div class="card-body text-center">
|
|
<div class="text-muted small">{{ __('abo.team_own_abos') }}</div>
|
|
<div class="display-5 font-weight-bold">{{ $totals['own_abos'] }}</div>
|
|
<div class="small text-success">+{{ $totals['own_abos_new'] }} {{ __('abo.new_this_month') }}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-4 col-6 mb-3">
|
|
<div class="card h-100">
|
|
<div class="card-body text-center">
|
|
<div class="text-muted small">{{ __('abo.team_customer_abos') }}</div>
|
|
<div class="display-5 font-weight-bold">{{ $totals['customer_abos'] }}</div>
|
|
<div class="small text-success">+{{ $totals['customer_abos_new'] }} {{ __('abo.new_this_month') }}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{{-- Abo-Umsatz in der Payline (Soll/Ist, Live) --}}
|
|
@if (isset($paylineAboTurnover))
|
|
<div class="row mb-2">
|
|
<div class="col-lg-6 col-12 mb-3">
|
|
<div class="card h-100">
|
|
<div class="card-body">
|
|
<div class="text-muted small">
|
|
{{ __('abo.payline_abo_turnover') }}
|
|
<span class="badge badge-pill badge-info ml-1"
|
|
title="{{ __('abo.payline_abo_turnover_hint') }}">Live</span>
|
|
</div>
|
|
@if (($paylineAboTurnover['paylines'] ?? 0) > 0)
|
|
<div class="row mt-2">
|
|
<div class="col-6 text-center">
|
|
<div class="text-muted small">{{ __('abo.payline_target_label') }}</div>
|
|
<div class="display-5 font-weight-bold text-primary">
|
|
{{ \App\Services\Util::formatNumber($paylineAboTurnover['target']) }}
|
|
</div>
|
|
</div>
|
|
<div class="col-6 text-center">
|
|
<div class="text-muted small">{{ __('abo.payline_actual_label') }}</div>
|
|
<div class="display-5 font-weight-bold">
|
|
{{ \App\Services\Util::formatNumber($paylineAboTurnover['actual']) }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="text-muted small mt-2 text-center">
|
|
{{ __('abo.payline_hint', ['lines' => $paylineAboTurnover['paylines'], 'members' => $paylineAboTurnover['members']]) }}
|
|
<br>
|
|
@php($paylineTeamAbos = (int) ($paylineAboTurnover['team_abo_count'] ?? 0))
|
|
@php($paylineCustomerAbos = (int) ($paylineAboTurnover['team_customer_abo_count'] ?? 0))
|
|
{{ __('abo.payline_abo_counts', [
|
|
'team_abos' => $paylineTeamAbos,
|
|
'customer_abos' => $paylineCustomerAbos,
|
|
'total' => $paylineTeamAbos + $paylineCustomerAbos,
|
|
]) }}
|
|
</div>
|
|
@else
|
|
<p class="text-muted mb-0 mt-2">{{ __('abo.payline_none') }}</p>
|
|
@endif
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
@endif
|
|
|
|
{{-- Linien-/Ebenen-Tabelle --}}
|
|
<div class="card mb-4">
|
|
<div class="card-header d-flex justify-content-between align-items-center">
|
|
<span class="font-weight-bold">{{ __('abo.team_by_level') }}</span>
|
|
</div>
|
|
<div class="table-responsive">
|
|
<table class="table table-hover mb-0">
|
|
<thead>
|
|
<tr class="text-muted small">
|
|
<th>{{ __('abo.level') }}</th>
|
|
<th class="text-center">{{ __('abo.consultants') }}</th>
|
|
<th class="text-center">{{ __('abo.team_own_abos') }}</th>
|
|
<th class="text-center">{{ __('abo.team_customer_abos') }}</th>
|
|
<th class="text-center">{{ __('abo.new_this_month') }} ({{ $currentMonthName }})</th>
|
|
<th class="text-center">{{ __('abo.team_total') }}</th>
|
|
<th class="text-right">{{ __('abo.abo_points_per_cycle') }}</th>
|
|
<th class="text-right">{{ __('abo.abo_points_billed', ['month' => $currentMonthName]) }}</th>
|
|
<th class="text-right" title="{{ __('abo.abo_points_expected_hint') }}">{{ __('abo.abo_points_expected') }}</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
@forelse($overview['lines'] as $line)
|
|
@php($collapseId = 'team-line-'.$line['line'])
|
|
<tr class="{{ $line['line'] === 1 ? 'table-active' : '' }}" style="cursor: pointer;"
|
|
data-toggle="collapse" data-target="#{{ $collapseId }}">
|
|
<td class="font-weight-bold">
|
|
<span class="ion ion-md-arrow-dropright"></span>
|
|
{{ $line['label'] }}
|
|
</td>
|
|
<td class="text-center">{{ $line['consultants'] }}</td>
|
|
<td class="text-center">
|
|
{{ $line['own_abos'] }}
|
|
@if($line['own_abos_new'] > 0)<span class="badge badge-success">+{{ $line['own_abos_new'] }}</span>@endif
|
|
</td>
|
|
<td class="text-center">
|
|
{{ $line['customer_abos'] }}
|
|
@if($line['customer_abos_new'] > 0)<span class="badge badge-success">+{{ $line['customer_abos_new'] }}</span>@endif
|
|
</td>
|
|
<td class="text-center text-success font-weight-bold">+{{ $line['own_abos_new'] + $line['customer_abos_new'] }}</td>
|
|
<td class="text-center font-weight-bold text-primary">{{ $line['total_abos'] }}</td>
|
|
<td class="text-right font-weight-bold">{{ \App\Services\Util::formatNumber($line['own_abo_points'] + $line['customer_abo_points']) }}</td>
|
|
<td class="text-right">{{ \App\Services\Util::formatNumber($line['abo_points_billed'] ?? 0) }}</td>
|
|
<td class="text-right text-muted">{{ \App\Services\Util::formatNumber($line['abo_points_expected'] ?? 0) }}</td>
|
|
</tr>
|
|
<tr class="p-0">
|
|
<td colspan="9" class="p-0 border-0">
|
|
<div class="collapse {{ $line['line'] === 1 ? 'show' : '' }}" id="{{ $collapseId }}">
|
|
<div class="p-3 bg-lightest">
|
|
<table class="table table-sm table-borderless mb-0">
|
|
<thead>
|
|
<tr class="text-muted small">
|
|
<th>{{ __('tables.adviser') }}</th>
|
|
<th>{{ __('abo.career_level') }}</th>
|
|
<th class="text-center">{{ __('abo.own_abo') }}</th>
|
|
<th class="text-center">{{ __('abo.customer_abos_count') }}</th>
|
|
<th class="text-right">{{ __('navigation.points') }}</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
@foreach($line['members'] as $member)
|
|
<tr>
|
|
<td>
|
|
<a href="#" class="text-black" data-toggle="modal"
|
|
data-target="#modals-load-content"
|
|
data-id="{{ $member['user_id'] }}"
|
|
data-action="team-abo-member-show"
|
|
data-back=""
|
|
data-modal="modal-md"
|
|
data-init_from="member"
|
|
data-route="{{ route('modal_load') }}">
|
|
<span class="mr-1 ion ion-ios-contact"></span>{{ $member['name'] }}
|
|
</a>
|
|
</td>
|
|
<td class="small text-muted">{{ $member['career_level'] }}</td>
|
|
<td class="text-center">
|
|
@if($member['has_own_abo'])
|
|
<span class="badge badge-success">{{ __('abo.yes') }}</span>
|
|
@else
|
|
<span class="badge badge-outline-secondary">{{ __('abo.no') }}</span>
|
|
@endif
|
|
</td>
|
|
<td class="text-center">{{ $member['customer_abo_count'] }}</td>
|
|
<td class="text-right">
|
|
<span class="text-primary font-weight-bold">{{ \App\Services\Util::formatNumber($member['own_abo_points'] + $member['customer_abo_points']) }}</span> Pkt.
|
|
</td>
|
|
</tr>
|
|
@endforeach
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
@empty
|
|
<tr>
|
|
<td colspan="9" class="text-muted text-center py-4">{{ __('tables.no_data_available') }}</td>
|
|
</tr>
|
|
@endforelse
|
|
</tbody>
|
|
@if(!empty($overview['lines']))
|
|
<tfoot>
|
|
<tr class="font-weight-bold">
|
|
<td>{{ $totals['label'] }}</td>
|
|
<td class="text-center">{{ $totals['consultants'] }}</td>
|
|
<td class="text-center">{{ $totals['own_abos'] }}</td>
|
|
<td class="text-center">{{ $totals['customer_abos'] }}</td>
|
|
<td class="text-center text-success">+{{ $totals['own_abos_new'] + $totals['customer_abos_new'] }}</td>
|
|
<td class="text-center text-primary">{{ $totals['total_abos'] }}</td>
|
|
<td class="text-right">{{ \App\Services\Util::formatNumber($totals['own_abo_points'] + $totals['customer_abo_points']) }}</td>
|
|
<td class="text-right">{{ \App\Services\Util::formatNumber($totals['abo_points_billed'] ?? 0) }}</td>
|
|
<td class="text-right text-muted">{{ \App\Services\Util::formatNumber($totals['abo_points_expected'] ?? 0) }}</td>
|
|
</tr>
|
|
</tfoot>
|
|
@endif
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
{{-- Wachstum: neue Abos je Monat im gewählten Jahr --}}
|
|
<div class="card mb-4">
|
|
<div class="card-header d-flex justify-content-between align-items-center">
|
|
<span class="font-weight-bold">{{ __('abo.team_growth') }}</span>
|
|
<form method="GET" action="{{ url()->current() }}" class="form-inline">
|
|
<select name="year" class="form-control form-control-sm" onchange="this.form.submit()">
|
|
@foreach($filterYears as $y)
|
|
<option value="{{ $y }}" {{ (int) $growthYear === (int) $y ? 'selected' : '' }}>{{ $y }}</option>
|
|
@endforeach
|
|
</select>
|
|
<noscript><button type="submit" class="btn btn-sm btn-secondary ml-2">{{ __('abo.filter') }}</button></noscript>
|
|
</form>
|
|
</div>
|
|
<div class="table-responsive">
|
|
<table class="table table-hover mb-0">
|
|
<thead>
|
|
<tr class="text-muted small">
|
|
<th>{{ __('abo.month') }}</th>
|
|
<th class="text-center">{{ __('abo.new_own_abos') }}</th>
|
|
<th class="text-center">{{ __('abo.new_customer_abos') }}</th>
|
|
<th class="text-center">{{ __('abo.new_total') }}</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
@foreach($growth['months'] as $row)
|
|
<tr class="{{ $row['total_new'] === 0 ? 'text-muted' : '' }}">
|
|
<td>{{ $row['label'] }}</td>
|
|
<td class="text-center">{{ $row['own_new'] }}</td>
|
|
<td class="text-center">{{ $row['customer_new'] }}</td>
|
|
<td class="text-center font-weight-bold {{ $row['total_new'] > 0 ? 'text-success' : '' }}">{{ $row['total_new'] }}</td>
|
|
</tr>
|
|
@endforeach
|
|
</tbody>
|
|
<tfoot>
|
|
<tr class="font-weight-bold">
|
|
<td>{{ __('abo.team_total') }} {{ $growth['year'] }}</td>
|
|
<td class="text-center">{{ $growth['totals']['own_new'] }}</td>
|
|
<td class="text-center">{{ $growth['totals']['customer_new'] }}</td>
|
|
<td class="text-center text-primary">{{ $growth['totals']['total_new'] }}</td>
|
|
</tr>
|
|
</tfoot>
|
|
</table>
|
|
</div>
|
|
<div class="card-footer py-2">
|
|
<small class="text-muted">{{ __('abo.growth_note') }}</small>
|
|
</div>
|
|
</div>
|
|
|
|
{{-- Wachstums-Diagramm: zwei Kurven (neue Eigen-/Kunden-Abos je Monat) --}}
|
|
<div class="card mb-4">
|
|
<div class="card-body pb-2">
|
|
<h6 class="font-weight-bold mb-2">
|
|
{{ __('abo.team_growth') }}
|
|
<span class="text-muted font-weight-normal ml-1">{{ $growth['year'] }}</span>
|
|
</h6>
|
|
</div>
|
|
<hr class="m-0">
|
|
<div class="card-body pt-3 pb-2">
|
|
<canvas id="aboGrowthChart" height="90"></canvas>
|
|
</div>
|
|
</div>
|
|
|
|
<script src="/vendor/libs/chartjs/chartjs.js"></script>
|
|
<script>
|
|
(function () {
|
|
var ctx = document.getElementById('aboGrowthChart').getContext('2d');
|
|
new Chart(ctx, {
|
|
type: 'line',
|
|
data: {
|
|
labels: {!! json_encode($growthChart['labels']) !!},
|
|
datasets: [
|
|
{
|
|
label: @json(__('abo.new_own_abos')),
|
|
data: {!! json_encode($growthChart['own']) !!},
|
|
backgroundColor: 'rgba(102, 110, 232, 0.1)',
|
|
borderColor: 'rgba(102, 110, 232, 0.9)',
|
|
borderWidth: 2,
|
|
pointBackgroundColor: 'rgba(102, 110, 232, 0.9)',
|
|
pointRadius: 3,
|
|
lineTension: 0.3,
|
|
fill: false
|
|
},
|
|
{
|
|
label: @json(__('abo.new_customer_abos')),
|
|
data: {!! json_encode($growthChart['customer']) !!},
|
|
backgroundColor: 'rgba(40, 167, 69, 0.1)',
|
|
borderColor: 'rgba(40, 167, 69, 0.9)',
|
|
borderWidth: 2,
|
|
pointBackgroundColor: 'rgba(40, 167, 69, 0.9)',
|
|
pointRadius: 3,
|
|
lineTension: 0.3,
|
|
fill: false
|
|
}
|
|
]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
legend: { display: true, position: 'top' },
|
|
tooltips: { mode: 'index', intersect: false },
|
|
layout: { padding: { top: 10 } },
|
|
scales: {
|
|
yAxes: [{
|
|
ticks: {
|
|
beginAtZero: true,
|
|
stepSize: {{ $growthChart['step'] }},
|
|
suggestedMax: {{ $growthChart['suggested_max'] }},
|
|
callback: function (value) {
|
|
return Number.isInteger(value) ? value : null;
|
|
}
|
|
},
|
|
gridLines: { color: 'rgba(0,0,0,0.05)' }
|
|
}],
|
|
xAxes: [{
|
|
gridLines: { display: false }
|
|
}]
|
|
}
|
|
}
|
|
});
|
|
}());
|
|
</script>
|
|
@endsection
|