241 lines
9.3 KiB
PHP
241 lines
9.3 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers;
|
|
|
|
use App\Models\Event;
|
|
use App\Models\PackagePurchase;
|
|
use App\Models\Tenant;
|
|
use App\Models\TenantPackage;
|
|
use App\Services\Tenant\DashboardSummaryService;
|
|
use App\Support\TenantOnboardingState;
|
|
use Illuminate\Contracts\Auth\MustVerifyEmail;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Arr;
|
|
use Illuminate\Support\Collection;
|
|
use Inertia\Inertia;
|
|
use Inertia\Response;
|
|
|
|
class DashboardController extends Controller
|
|
{
|
|
public function __invoke(Request $request, DashboardSummaryService $summaryService): Response
|
|
{
|
|
$user = $request->user();
|
|
$tenant = $user?->tenant;
|
|
|
|
$summary = $tenant instanceof Tenant
|
|
? $summaryService->build($tenant)
|
|
: null;
|
|
|
|
$events = $tenant instanceof Tenant
|
|
? $this->collectUpcomingEvents($tenant)
|
|
: collect();
|
|
|
|
$purchases = $tenant instanceof Tenant
|
|
? $this->collectRecentPurchases($tenant)
|
|
: collect();
|
|
|
|
$activePackage = $summary['active_package'] ?? null;
|
|
$onboarding = $tenant instanceof Tenant
|
|
? $this->buildOnboardingSteps($tenant, $summary ?? [])
|
|
: [];
|
|
|
|
return Inertia::render('dashboard', [
|
|
'metrics' => $summary,
|
|
'upcomingEvents' => $events->values()->all(),
|
|
'recentPurchases' => $purchases->values()->all(),
|
|
'latestPurchase' => $purchases->first(),
|
|
'tenant' => $tenant ? [
|
|
'id' => $tenant->id,
|
|
'name' => $tenant->name,
|
|
'subscriptionStatus' => $tenant->subscription_status,
|
|
'subscriptionExpiresAt' => optional($tenant->subscription_expires_at)->toIso8601String(),
|
|
'activePackage' => $activePackage ? [
|
|
'name' => $activePackage['name'] ?? '',
|
|
'price' => $activePackage['price'] ?? null,
|
|
'expiresAt' => $activePackage['expires_at'] ?? null,
|
|
'remainingEvents' => $activePackage['remaining_events'] ?? null,
|
|
'isActive' => (bool) ($activePackage['is_active'] ?? false),
|
|
] : null,
|
|
] : null,
|
|
'emailVerification' => [
|
|
'mustVerify' => $user instanceof MustVerifyEmail,
|
|
'verified' => $user?->hasVerifiedEmail() ?? false,
|
|
],
|
|
'onboarding' => $onboarding,
|
|
]);
|
|
}
|
|
|
|
private function collectUpcomingEvents(Tenant $tenant): Collection
|
|
{
|
|
return Event::query()
|
|
->where('tenant_id', $tenant->getKey())
|
|
->withCount(['photos', 'tasks', 'joinTokens'])
|
|
->orderByRaw('date IS NULL')
|
|
->orderBy('date')
|
|
->limit(5)
|
|
->get()
|
|
->map(function (Event $event): array {
|
|
return [
|
|
'id' => $event->id,
|
|
'name' => $this->resolveEventName($event),
|
|
'slug' => $event->slug,
|
|
'status' => $event->status,
|
|
'isActive' => (bool) $event->is_active,
|
|
'date' => optional($event->date)->toIso8601String(),
|
|
'photosCount' => (int) ($event->photos_count ?? 0),
|
|
'tasksCount' => (int) ($event->tasks_count ?? 0),
|
|
'joinTokensCount' => (int) ($event->join_tokens_count ?? 0),
|
|
];
|
|
});
|
|
}
|
|
|
|
private function collectRecentPurchases(Tenant $tenant): Collection
|
|
{
|
|
$entries = collect(
|
|
$tenant->purchases()
|
|
->with('package')
|
|
->latest('purchased_at')
|
|
->limit(6)
|
|
->get()
|
|
->map(function (PackagePurchase $purchase): array {
|
|
return [
|
|
'id' => $purchase->id,
|
|
'packageName' => $purchase->package?->getNameForLocale(app()->getLocale())
|
|
?? $purchase->package?->name
|
|
?? __('Unknown package'),
|
|
'price' => $purchase->price !== null ? (float) $purchase->price : null,
|
|
'purchasedAt' => optional($purchase->purchased_at)->toIso8601String(),
|
|
'type' => $purchase->type,
|
|
'provider' => $purchase->provider,
|
|
'source' => 'purchase',
|
|
'isActive' => null,
|
|
];
|
|
})
|
|
->all()
|
|
);
|
|
|
|
if ($entries->isEmpty()) {
|
|
$packageEntries = collect(
|
|
$tenant->tenantPackages()
|
|
->with('package')
|
|
->orderByDesc('purchased_at')
|
|
->limit(6)
|
|
->get()
|
|
->map(function (TenantPackage $package): array {
|
|
return [
|
|
'id' => $package->id,
|
|
'packageName' => $package->package?->getNameForLocale(app()->getLocale())
|
|
?? $package->package?->name
|
|
?? __('Unknown package'),
|
|
'price' => $package->price !== null ? (float) $package->price : null,
|
|
'purchasedAt' => optional($package->purchased_at)->toIso8601String(),
|
|
'type' => 'tenant_package',
|
|
'provider' => 'internal',
|
|
'source' => 'tenant_package',
|
|
'isActive' => (bool) $package->active,
|
|
];
|
|
})
|
|
->all()
|
|
);
|
|
|
|
$entries = $entries->merge($packageEntries);
|
|
}
|
|
|
|
return $entries
|
|
->sortByDesc('purchasedAt')
|
|
->values()
|
|
->take(6);
|
|
}
|
|
|
|
private function buildOnboardingSteps(Tenant $tenant, array $summary): array
|
|
{
|
|
$status = TenantOnboardingState::status($tenant);
|
|
$settings = $tenant->settings ?? [];
|
|
|
|
$adminAppOpened = (bool) Arr::get($settings, 'onboarding.admin_app_opened_at')
|
|
|| $tenant->last_activity_at !== null;
|
|
|
|
$hasEvent = (bool) ($status['event'] ?? false);
|
|
$hasInvite = (bool) ($status['invite'] ?? false);
|
|
$hasBranding = (bool) ($status['palette'] ?? false)
|
|
|| (bool) Arr::get($settings, 'onboarding.branding_set', false);
|
|
$hasTaskPackage = (bool) ($status['packages'] ?? false)
|
|
|| ! empty(Arr::get($settings, 'onboarding.selected_packages'));
|
|
$hasPhotos = $tenant->photos()->exists() || (int) ($summary['new_photos'] ?? 0) > 0;
|
|
|
|
return [
|
|
[
|
|
'key' => 'admin_app',
|
|
'title' => trans('dashboard.onboarding.admin_app.title'),
|
|
'description' => trans('dashboard.onboarding.admin_app.description'),
|
|
'done' => $adminAppOpened,
|
|
'cta' => url('/event-admin'),
|
|
'ctaLabel' => trans('dashboard.onboarding.admin_app.cta'),
|
|
],
|
|
[
|
|
'key' => 'event_setup',
|
|
'title' => trans('dashboard.onboarding.event_setup.title'),
|
|
'description' => trans('dashboard.onboarding.event_setup.description'),
|
|
'done' => $hasEvent,
|
|
'cta' => url('/event-admin/events/new'),
|
|
'ctaLabel' => trans('dashboard.onboarding.event_setup.cta'),
|
|
],
|
|
[
|
|
'key' => 'invite_guests',
|
|
'title' => trans('dashboard.onboarding.invite_guests.title'),
|
|
'description' => trans('dashboard.onboarding.invite_guests.description'),
|
|
'done' => $hasInvite,
|
|
'cta' => url('/event-admin/events'),
|
|
'ctaLabel' => trans('dashboard.onboarding.invite_guests.cta'),
|
|
],
|
|
[
|
|
'key' => 'collect_photos',
|
|
'title' => trans('dashboard.onboarding.collect_photos.title'),
|
|
'description' => trans('dashboard.onboarding.collect_photos.description'),
|
|
'done' => $hasPhotos,
|
|
'cta' => url('/event-admin/dashboard'),
|
|
'ctaLabel' => trans('dashboard.onboarding.collect_photos.cta'),
|
|
],
|
|
[
|
|
'key' => 'branding',
|
|
'title' => trans('dashboard.onboarding.branding.title'),
|
|
'description' => trans('dashboard.onboarding.branding.description'),
|
|
'done' => $hasBranding && $hasTaskPackage,
|
|
'cta' => url('/event-admin/tasks'),
|
|
'ctaLabel' => trans('dashboard.onboarding.branding.cta'),
|
|
],
|
|
];
|
|
}
|
|
|
|
private function resolveEventName(Event $event): string
|
|
{
|
|
$name = $event->name;
|
|
|
|
if (is_array($name)) {
|
|
$locale = app()->getLocale();
|
|
|
|
if (! empty($name[$locale])) {
|
|
return (string) $name[$locale];
|
|
}
|
|
|
|
foreach (['de', 'en'] as $fallback) {
|
|
if (! empty($name[$fallback])) {
|
|
return (string) $name[$fallback];
|
|
}
|
|
}
|
|
|
|
$firstTranslated = reset($name);
|
|
|
|
if (is_string($firstTranslated) && $firstTranslated !== '') {
|
|
return $firstTranslated;
|
|
}
|
|
}
|
|
|
|
if (is_string($name) && $name !== '') {
|
|
return $name;
|
|
}
|
|
|
|
return __('Untitled event');
|
|
}
|
|
}
|