23-01-2026
This commit is contained in:
parent
a939cd51ef
commit
a8b395e20d
248 changed files with 29342 additions and 4805 deletions
88
resources/views/dashboard/_news.blade.php
Normal file
88
resources/views/dashboard/_news.blade.php
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
@if(isset($dashboardNews) && $dashboardNews)
|
||||
<div class="d-flex col-xl-12 align-items-stretch">
|
||||
<div class="card w-100 mb-4 border-primary">
|
||||
<h5 class="card-header with-elements bg-primary text-white">
|
||||
<div class="card-header-title">
|
||||
<i class="ion ion-md-megaphone mr-2"></i>
|
||||
{{__('home.news_updates') }}
|
||||
</div>
|
||||
<div class="card-header-elements ml-auto">
|
||||
<span class="badge badge-light">{{ $dashboardNews->getDisplayDateFormatted() }}</span>
|
||||
</div>
|
||||
</h5>
|
||||
<div class="card-body">
|
||||
{{-- Kurzer Teaser Text --}}
|
||||
<div class="news-preview">
|
||||
<h6 class="font-weight-bold mb-2">
|
||||
<i class="ion ion-md-information-circle text-primary mr-1"></i>
|
||||
{{ $dashboardNews->getLang('title') }}
|
||||
</h6>
|
||||
<p class="mb-2">
|
||||
{{ $dashboardNews->getLang('teaser') }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
{{-- Collapse für mehr Inhalt --}}
|
||||
@if($dashboardNews->getLang('content'))
|
||||
<div class="collapse" id="newsCollapse">
|
||||
<hr class="my-3">
|
||||
<div class="news-full-content">
|
||||
{!! $dashboardNews->getLang('content') !!}
|
||||
</div>
|
||||
|
||||
{{-- Datei-Links --}}
|
||||
@if($dashboardNews->hasFileLinks())
|
||||
<div class="mt-3">
|
||||
<div class="d-flex flex-wrap gap-2">
|
||||
|
||||
@foreach($dashboardNews->getFileLinks() as $linkData)
|
||||
<a href="{{ route('storage_file', [$linkData['file']->id, 'dc_file', 'download']) }}"
|
||||
class="btn btn-sm btn-outline-success"
|
||||
target="_blank"
|
||||
title="{{ $linkData['file']->original_name }}">
|
||||
<i class="ion ion-md-download mr-1"></i>
|
||||
{{ $linkData['label'] }}
|
||||
</a>
|
||||
@endforeach
|
||||
|
||||
<a href="{{ route('user_downloadcenter') }}"
|
||||
class="btn btn-sm btn-outline-success"
|
||||
title="{{ __('navigation.downloadcenter') }}">
|
||||
<i class="ion ion-ios-download mr-1"></i>
|
||||
{{ __('navigation.downloadcenter') }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
{{-- Mehr lesen Button --}}
|
||||
<div class="mt-3">
|
||||
<a class="btn btn-sm btn-outline-primary" data-toggle="collapse" href="#newsCollapse" role="button" aria-expanded="false" aria-controls="newsCollapse">
|
||||
<span class="when-closed">
|
||||
<i class="ion ion-md-arrow-dropdown"></i> {{__('home.read_more')}}
|
||||
</span>
|
||||
<span class="when-open">
|
||||
<i class="ion ion-md-arrow-dropup"></i> {{__('home.read_less')}}
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.when-open { display: none; }
|
||||
.when-closed { display: inline; }
|
||||
[aria-expanded="true"] .when-open { display: inline; }
|
||||
[aria-expanded="true"] .when-closed { display: none; }
|
||||
.gap-2 > * { margin-right: 0.5rem; margin-bottom: 0.5rem; }
|
||||
.gap-2 > *:last-child { margin-right: 0; }
|
||||
</style>
|
||||
@endif
|
||||
|
||||
|
|
@ -1,15 +1,31 @@
|
|||
@php
|
||||
$selectedMonth = request()->get('points_month', date('n'));
|
||||
$selectedYear = request()->get('points_year', date('Y'));
|
||||
@endphp
|
||||
<div class="d-flex col-xl-12 align-items-stretch">
|
||||
<div class="card w-100 mb-4">
|
||||
<h5 class="card-header with-elements">
|
||||
<div class="card-header-title">{{__('home.current_points_for') }} {{ HTMLHelper::getMonth(date('n')) }} {{ date('Y') }} </div>
|
||||
<h5 class="card-header with-elements d-flex justify-content-between align-items-center flex-wrap">
|
||||
<div class="card-header-title">{{__('home.current_points_for') }} {{ HTMLHelper::getMonth($selectedMonth) }} {{ $selectedYear }} </div>
|
||||
<div class="d-flex align-items-center mt-2 mt-md-0">
|
||||
<select id="points-month-filter" class="form-control custom-select form-control-sm mr-2" style="width: auto;">
|
||||
@foreach(HTMLHelper::getTransMonths() as $monthNum => $monthName)
|
||||
<option value="{{ $monthNum }}" {{ $selectedMonth == $monthNum ? 'selected' : '' }}>{{ $monthName }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
<select id="points-year-filter" class="form-control custom-select form-control-sm" style="width: auto;">
|
||||
@foreach(HTMLHelper::getYearRange() as $year)
|
||||
<option value="{{ $year }}" {{ $selectedYear == $year ? 'selected' : '' }}>{{ $year }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
</h5>
|
||||
<div class="card-body">
|
||||
@if($user->isActiveAccount())
|
||||
<h6>
|
||||
@php($userSalesVolume = $user->getUserSalesVolume(date('n'), date('Y'), 'first'))
|
||||
@php($userSalesVolume = $user->getUserSalesVolume($selectedMonth, $selectedYear, 'first'))
|
||||
@if($userSalesVolume)
|
||||
<div class="mb-2">
|
||||
<strong>{{ __('team.total_points') }}: {{ $userSalesVolume->getPointsKPSum() }}</strong> | {{ __('team.own') }}: {{ $userSalesVolume->month_points }} | {{ __('team.shop') }}: {{ $userSalesVolume->month_shop_points }}<br>
|
||||
<strong>{{ __('team.total_points') }}: {{ formatNumber($userSalesVolume->getPointsKPSum()) }}</strong> | {{ __('team.own') }}: {{ $userSalesVolume->getFormattedMonthKPPoints() }} | {{ __('team.shop') }}: {{ $userSalesVolume->getFormattedMonthShopPoints() }}<br>
|
||||
</div>
|
||||
<div>
|
||||
<strong>{{ __('team.total_turnover') }}: {{ formatNumber($userSalesVolume->getTotalNetSum()) }} €</strong> | {{ __('team.own') }}: {{ formatNumber($userSalesVolume->month_total_net) }} € | {{ __('team.shop') }}: {{ formatNumber($userSalesVolume->month_shop_total_net) }} €
|
||||
|
|
@ -25,12 +41,13 @@
|
|||
<th>{{__('tables.net_sales')}}</th>
|
||||
<th>{{__('tables.type')}}</th>
|
||||
<th>{{__('tables.order')}}</th>
|
||||
<th>{{__('tables.member')}}</th>
|
||||
<th>{{__('tables.info') }}</th>
|
||||
<th>{{__('tables.note') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@php($userSalesVolumes = $user->getUserSalesVolume(date('n'), date('Y')))
|
||||
@php($userSalesVolumes = $user->getUserSalesVolume($selectedMonth, $selectedYear, 'get', ['shopping_order.shopping_user']))
|
||||
@if($userSalesVolumes)
|
||||
@foreach ($userSalesVolumes as $userSalesVolume)
|
||||
<tr>
|
||||
|
|
@ -38,7 +55,7 @@
|
|||
{{ $userSalesVolume->date }}
|
||||
</td>
|
||||
<td class="text-left font-weight-semibold">
|
||||
{{ $userSalesVolume->points }}
|
||||
{{ $userSalesVolume->getFormattedPoints() }}
|
||||
</td>
|
||||
<td class="text-left font-weight-semibold">
|
||||
{{ formatNumber($userSalesVolume->total_net) }} €
|
||||
|
|
@ -56,6 +73,13 @@
|
|||
@endif
|
||||
@endif
|
||||
</td>
|
||||
<td class="text-left font-weight-semibold">
|
||||
@if($userSalesVolume->shopping_order && $userSalesVolume->shopping_order->shopping_user)
|
||||
|
||||
<span class="no-line-break"> {{ $userSalesVolume->shopping_order->shopping_user->billing_firstname }} {{ $userSalesVolume->shopping_order->shopping_user->billing_lastname }}<br>
|
||||
<small>{{ $userSalesVolume->shopping_order->shopping_user->billing_email }}</small></span>
|
||||
@endif
|
||||
</td>
|
||||
<td class="text-left font-weight-semibold">
|
||||
<span class="no-line-break"> {{ $userSalesVolume->message }}</span>
|
||||
</td>
|
||||
|
|
@ -93,5 +117,19 @@
|
|||
"url": "/js/datatables-{{ \App::getLocale() }}.json"
|
||||
}
|
||||
});
|
||||
|
||||
// Filter für Monat/Jahr
|
||||
function updatePointsFilter() {
|
||||
var month = $('#points-month-filter').val();
|
||||
var year = $('#points-year-filter').val();
|
||||
var url = new URL(window.location.href);
|
||||
url.searchParams.set('points_month', month);
|
||||
url.searchParams.set('points_year', year);
|
||||
window.location.href = url.toString();
|
||||
}
|
||||
|
||||
$('#points-month-filter, #points-year-filter').on('change', function() {
|
||||
updatePointsFilter();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
246
resources/views/dashboard/_statistics.blade.php
Normal file
246
resources/views/dashboard/_statistics.blade.php
Normal file
|
|
@ -0,0 +1,246 @@
|
|||
@php
|
||||
$selectedMonth = request()->get('stats_month', date('n'));
|
||||
$selectedYear = request()->get('stats_year', date('Y'));
|
||||
|
||||
// Start- und Enddatum für den Monat
|
||||
$startDate = \Carbon\Carbon::createFromDate($selectedYear, $selectedMonth, 1)->startOfMonth();
|
||||
$endDate = \Carbon\Carbon::createFromDate($selectedYear, $selectedMonth, 1)->endOfMonth();
|
||||
|
||||
// UserBusiness für den Monat laden (enthält Payline-Punkte)
|
||||
$userBusiness = \App\Models\UserBusiness::where('user_id', $user->id)
|
||||
->where('month', $selectedMonth)
|
||||
->where('year', $selectedYear)
|
||||
->first();
|
||||
|
||||
// UserSalesVolume für KP-Punkte
|
||||
$userSalesVolume = $user->getUserSalesVolume($selectedMonth, $selectedYear, 'first');
|
||||
|
||||
// Kunden-Umsatz Punkte (KP - Eigene Punkte + Shop)
|
||||
$customerPoints = $userSalesVolume ? $userSalesVolume->getPointsKPSum() : 0;
|
||||
|
||||
// Team-Umsatz Punkte (Payline) - Live-Berechnung wenn UserBusiness nicht verfügbar
|
||||
$teamPaylinePoints = 0;
|
||||
$isLiveCalculation = false;
|
||||
|
||||
if ($userBusiness) {
|
||||
// Gespeicherte Daten aus UserBusiness verwenden
|
||||
$teamPaylinePoints = $userBusiness->payline_points ?? 0;
|
||||
} else {
|
||||
// Live-Berechnung über TreeCalcBot (für aktuellen Monat)
|
||||
$isLiveCalculation = true;
|
||||
try {
|
||||
$treeCalcBot = new \App\Services\BusinessPlan\TreeCalcBot((int)$selectedMonth, (int)$selectedYear, 'member');
|
||||
$treeCalcBot->initBusinesslUserDetail($user);
|
||||
|
||||
if ($treeCalcBot->business_user) {
|
||||
$teamPaylinePoints = $treeCalcBot->business_user->payline_points ?? 0;
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
\Log::warning("Live-Berechnung Payline-Punkte fehlgeschlagen für User {$user->id}: " . $e->getMessage());
|
||||
$teamPaylinePoints = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Direkte Neupartner (Firstlines) in diesem Monat
|
||||
$directNewPartners = \App\User::where('m_sponsor', $user->id)
|
||||
->where('active_date', '>=', $startDate)
|
||||
->where('active_date', '<=', $endDate)
|
||||
->where('m_level', '!=', null)
|
||||
->where('payment_account', '!=', null)
|
||||
->count();
|
||||
|
||||
// ===== REKURSIVE TEAM-STRUKTUR =====
|
||||
// Hilfsfunktion um alle Team-Mitglieder rekursiv zu sammeln (inkl. Sponsor-Kette)
|
||||
$getAllTeamMemberIds = function($sponsorId, $maxDepth = 20) use (&$getAllTeamMemberIds) {
|
||||
static $cache = [];
|
||||
|
||||
if (isset($cache[$sponsorId])) {
|
||||
return $cache[$sponsorId];
|
||||
}
|
||||
|
||||
$teamIds = [];
|
||||
$currentLevel = [$sponsorId];
|
||||
$depth = 0;
|
||||
|
||||
while (!empty($currentLevel) && $depth < $maxDepth) {
|
||||
$children = \App\User::whereIn('m_sponsor', $currentLevel)
|
||||
->where('m_level', '!=', null)
|
||||
->where('payment_account', '!=', null)
|
||||
->whereNull('deleted_at')
|
||||
->pluck('id')
|
||||
->toArray();
|
||||
|
||||
$teamIds = array_merge($teamIds, $children);
|
||||
$currentLevel = $children;
|
||||
$depth++;
|
||||
}
|
||||
|
||||
$cache[$sponsorId] = $teamIds;
|
||||
return $teamIds;
|
||||
};
|
||||
|
||||
// Alle Team-Mitglieder rekursiv laden
|
||||
$allTeamMemberIds = $getAllTeamMemberIds($user->id);
|
||||
|
||||
// Neupartner im gesamten Team (rekursiv im Marketingplan)
|
||||
$teamNewPartners = 0;
|
||||
if (!empty($allTeamMemberIds)) {
|
||||
$teamNewPartners = \App\User::whereIn('id', $allTeamMemberIds)
|
||||
->where('active_date', '>=', $startDate)
|
||||
->where('active_date', '<=', $endDate)
|
||||
->count();
|
||||
}
|
||||
|
||||
// Kundenabos (is_for = 'customer' oder shop-bezogen)
|
||||
$customerAbos = \App\Models\UserAbo::where('member_id', $user->id)
|
||||
->where('is_for', 'customer')
|
||||
->whereIn('status', [1, 2]) // aktive Abos
|
||||
->where('active', true)
|
||||
->count();
|
||||
|
||||
// Eigene Abos
|
||||
$ownAbos = \App\Models\UserAbo::where('user_id', $user->id)
|
||||
->where('is_for', 'me')
|
||||
->whereIn('status', [1, 2])
|
||||
->where('active', true)
|
||||
->count();
|
||||
|
||||
// Team-Abos (rekursiv über gesamtes Team im Marketingplan)
|
||||
$teamAbos = 0;
|
||||
if (!empty($allTeamMemberIds)) {
|
||||
$teamAbos = \App\Models\UserAbo::whereIn('user_id', $allTeamMemberIds)
|
||||
->where('is_for', 'me')
|
||||
->whereIn('status', [1, 2])
|
||||
->where('active', true)
|
||||
->count();
|
||||
}
|
||||
@endphp
|
||||
|
||||
@if($user->isActiveAccount())
|
||||
<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($selectedMonth) }} {{ $selectedYear }}
|
||||
</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 }}" {{ $selectedMonth == $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 }}" {{ $selectedYear == $year ? 'selected' : '' }}>{{ $year }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
</h5>
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
{{-- Kunden-Umsatz Punkte --}}
|
||||
<div class="col-md-6 col-lg-4 mb-4">
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="bg-primary text-white rounded-circle d-flex align-items-center justify-content-center" style="width: 50px; height: 50px;">
|
||||
<i class="ion ion-md-cart" style="font-size: 1.5rem;"></i>
|
||||
</div>
|
||||
<div class="ml-3">
|
||||
<div class="text-muted small">{{ __('home.customer_turnover_points') }}</div>
|
||||
<div class="font-weight-bold text-large">{{ number_format($customerPoints, 0, ',', '.') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Team-Umsatz Punkte (Payline) --}}
|
||||
<div class="col-md-6 col-lg-4 mb-4">
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="bg-success text-white rounded-circle d-flex align-items-center justify-content-center" style="width: 50px; height: 50px;">
|
||||
<i class="ion ion-md-people" style="font-size: 1.5rem;"></i>
|
||||
</div>
|
||||
<div class="ml-3">
|
||||
<div class="text-muted small">
|
||||
{{ __('home.team_turnover_points') }} (Payline)
|
||||
@if($isLiveCalculation)
|
||||
<span class="badge badge-pill badge-info ml-1" title="{{ __('home.live_calculation_hint') }}">Live</span>
|
||||
@endif
|
||||
</div>
|
||||
<div class="font-weight-bold text-large">{{ number_format($teamPaylinePoints, 0, ',', '.') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Direkte Neupartner --}}
|
||||
<div class="col-md-6 col-lg-4 mb-4">
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="bg-info text-white rounded-circle d-flex align-items-center justify-content-center" style="width: 50px; height: 50px;">
|
||||
<i class="ion ion-md-person-add" style="font-size: 1.5rem;"></i>
|
||||
</div>
|
||||
<div class="ml-3">
|
||||
<div class="text-muted small">{{ __('home.direct_new_partners') }}</div>
|
||||
<div class="font-weight-bold text-large">{{ $directNewPartners }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Neupartner im Team --}}
|
||||
<div class="col-md-6 col-lg-4 mb-4">
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="bg-warning text-white rounded-circle d-flex align-items-center justify-content-center" style="width: 50px; height: 50px;">
|
||||
<i class="ion ion-md-contacts" style="font-size: 1.5rem;"></i>
|
||||
</div>
|
||||
<div class="ml-3">
|
||||
<div class="text-muted small">{{ __('home.team_new_partners') }}</div>
|
||||
<div class="font-weight-bold text-large">{{ $teamNewPartners }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Kundenabos --}}
|
||||
<div class="col-md-6 col-lg-4 mb-4">
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="bg-secondary text-white rounded-circle d-flex align-items-center justify-content-center" style="width: 50px; height: 50px;">
|
||||
<i class="ion ion-md-repeat" style="font-size: 1.5rem;"></i>
|
||||
</div>
|
||||
<div class="ml-3">
|
||||
<div class="text-muted small">{{ __('home.customer_subscriptions') }}</div>
|
||||
<div class="font-weight-bold text-large">{{ $customerAbos }} + {{ $ownAbos }} {{ __('home.own') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Team-Abos --}}
|
||||
<div class="col-md-6 col-lg-4 mb-4">
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="bg-dark text-white rounded-circle d-flex align-items-center justify-content-center" style="width: 50px; height: 50px;">
|
||||
<i class="ion ion-md-sync" style="font-size: 1.5rem;"></i>
|
||||
</div>
|
||||
<div class="ml-3">
|
||||
<div class="text-muted small">{{ __('home.team_subscriptions') }}</div>
|
||||
<div class="font-weight-bold text-large">{{ $teamAbos }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
// Filter für Monat/Jahr - Statistiken
|
||||
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
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue