Abo-Uebersichten: Team-Ansicht konsolidiert, Monatsfilter, Wachstum & Dashboard-KPIs

- /user/abos/ot: Filter nach Eingangsmonat (start_date) ueber neuen UserAbo-Scope
- Konsolidierte Team-Abo-Ansicht nach Ebenen/Linien (Bestand, neue Abos im Monat,
  Eigen-/Kundenabos, Punkte je Ebene + Summen) via TeamAboOverviewService
- team/customers leitet auf konsolidierte Ansicht um; Navigation bereinigt
- Mitglieder-Detail-Modal mit Abo-Zusammenfassung (Eigen-Abo, Kundenabos,
  Punkte, Beginn, naechste Ausfuehrung, Ausfuehrungen)
- Wachstums-Tabelle (Jahr x Monate) + Liniendiagramm (neue Eigen-/Kundenabos)
- Dashboard-KPI-Kachel (Gesamtabos, Kundenabos, 1000-Punkte-Shops)
- i18n de/en/es/fr, Tests (Filter, Overview, Wachstum, Modal, Redirect)

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Kevin 2026-06-23 14:07:29 +00:00
parent 65b867f8ee
commit 4135726772
15 changed files with 967 additions and 174 deletions

View file

@ -703,68 +703,73 @@ class TeamController extends Controller
}
/**
* Zeigt die Abos der Team-Mitglieder an
* Konsolidierte Team-Abo-Übersicht: Abos im Team nach Ebenen/Linien gruppiert,
* inkl. Eigen-Abos, Kundenabos und neuer Abos im gewählten Monat.
*/
public function showAbos()
public function showAbos(\App\Services\Abo\TeamAboOverviewService $overviewService)
{
$user = User::find(\Auth::user()->id);
$this->setFilterVars();
$now = now();
// Hole Team-Mitglieder-IDs effizient via Sponsor-Hierarchie
$teamUserIds = AboHelper::getTeamUserIds($user->id);
// Bestandsübersicht: immer aktueller Bestand; "neu"-Badges = laufender Monat.
$overview = $overviewService->linesOverview($user, (int) $now->month, (int) $now->year);
$selectedYear = (int) Request::get('year', now()->year);
$baseQuery = \App\Models\UserAbo::whereIn('user_id', $teamUserIds)
->where('is_for', 'me')
->where('status', '>', 1);
// Hole Abos der Team-Mitglieder
$abos = (clone $baseQuery)
->with(['user', 'user.account', 'user_abo_items', 'user_abo_items.product'])
->orderBy('next_date', 'asc')
->get();
// Wachstums-Tabelle: wählbares Jahr (GET-Param), Default aktuelles Jahr.
$growthYear = (int) Request::get('year', $now->year);
$growth = $overviewService->monthlyGrowth($user, $growthYear);
$data = [
'filter_months' => HTMLHelper::getTransMonths(),
'filter_years' => HTMLHelper::getYearRange(2022),
'abos' => $abos,
'chartData' => AboHelper::getMonthlyAboCounts($baseQuery, $selectedYear, 'team_abos', $user->id),
'chartYear' => $selectedYear,
'chartYears' => HTMLHelper::getYearRange(2026),
'chartMonths' => HTMLHelper::getTransMonths(),
'currentMonthName' => HTMLHelper::getTransMonths()[(int) $now->month] ?? '',
'overview' => $overview,
'growth' => $growth,
'growthChart' => $this->buildGrowthChart($growth),
'growthYear' => $growthYear,
'filterYears' => HTMLHelper::getYearRange(2022),
];
return view('user.team.abos', $data);
}
/**
* Zeigt eine Übersicht der Kunden-Abos aller Team-Mitglieder (anonymisiert)
* Bereitet die Datenreihen für das Wachstums-Liniendiagramm auf.
*
* @param array{months: array<int, array{label: string, own_new: int, customer_new: int}>} $growth
* @return array{labels: list<string>, own: list<int>, customer: list<int>, step: int, suggested_max: int}
*/
public function showTeamCustomerAbos(): \Illuminate\View\View
private function buildGrowthChart(array $growth): array
{
$user = User::find(\Auth::user()->id);
$teamUserIds = AboHelper::getTeamUserIds($user->id);
$labels = array_map(fn (array $row): string => $row['label'], $growth['months']);
$own = array_map(fn (array $row): int => $row['own_new'], $growth['months']);
$customer = array_map(fn (array $row): int => $row['customer_new'], $growth['months']);
$selectedYear = (int) Request::get('year', now()->year);
$baseQuery = \App\Models\UserAbo::whereIn('member_id', $teamUserIds)
->where('is_for', 'ot')
->where('status', '>', 1);
$max = max(array_merge($own, $customer, [0]));
$step = match (true) {
$max <= 5 => 1,
$max <= 10 => 2,
$max <= 20 => 5,
$max <= 50 => 10,
$max <= 100 => 20,
$max <= 200 => 50,
default => 100,
};
$abos = (clone $baseQuery)
->with(['member', 'member.account', 'user_abo_items', 'user_abo_items.product'])
->orderBy('member_id')
->orderBy('next_date', 'asc')
->get();
return [
'labels' => array_values($labels),
'own' => array_values($own),
'customer' => array_values($customer),
'step' => $step,
'suggested_max' => $max + $step,
];
}
$groupedByMember = $abos->groupBy('member_id');
return view('user.team.customer_abos', [
'groupedByMember' => $groupedByMember,
'chartData' => AboHelper::getMonthlyAboCounts($baseQuery, $selectedYear, 'team_cust_abos', $user->id),
'chartYear' => $selectedYear,
'chartYears' => HTMLHelper::getYearRange(2026),
'chartMonths' => HTMLHelper::getTransMonths(),
]);
/**
* Die Kundenabos im Team sind in die konsolidierte Team-Abo-Übersicht
* (`user_abos_team_show`) eingeflossen. Diese Route bleibt für Bookmarks/Altlinks
* bestehen und leitet dauerhaft dorthin weiter.
*/
public function showTeamCustomerAbos(): \Illuminate\Http\RedirectResponse
{
return redirect()->route('user_abos_team_show');
}
/**