where('uuid', $uuid)->first(); if ($customer === null) { return; } // A closed account is terminal — the suspend/reactivate toggle must not // resurrect it to active while closed_at is still set. if ($customer->closed_at !== null || $customer->status === 'closed') { return; } $customer->update([ 'status' => $customer->status === 'suspended' ? 'active' : 'suspended', ]); $this->dispatch('notify', message: __('admin.customer_'.($customer->status === 'suspended' ? 'suspended' : 'reactivated'))); } public function render() { $locale = app()->getLocale(); $plans = config('provisioning.plans'); $customers = Customer::query() ->with('instances') ->orderBy('name') ->get(); $rows = $customers->map(function (Customer $c) use ($plans, $locale) { $instance = $c->instances->sortByDesc('id')->first(); $planKey = $instance->plan ?? null; $priceCents = $planKey !== null ? ($plans[$planKey]['price_cents'] ?? 0) : 0; return [ 'uuid' => $c->uuid, 'name' => $c->name, 'plan' => $planKey !== null ? __('billing.plan.'.$planKey) : '—', 'mrr' => Number::currency($priceCents / 100, in: 'EUR', locale: $locale), 'instance' => $instance->subdomain ?? '—', 'closed' => $c->closed_at !== null || $c->status === 'closed', 'suspended' => $c->status === 'suspended', 'status' => match (true) { $c->closed_at !== null || $c->status === 'closed' => 'closed', $c->status === 'suspended' => 'suspended', default => $instance->status ?? $c->status ?? 'provisioning', }, ]; })->all(); // Plan distribution for the doughnut — derived from live instances. $labels = []; $counts = []; foreach (array_keys($plans) as $planKey) { $n = $customers->filter(fn (Customer $c) => $c->instances->contains('plan', $planKey))->count(); if ($n > 0) { $labels[] = __('billing.plan.'.$planKey); $counts[] = $n; } } return view('livewire.admin.customers', [ 'rows' => $rows, 'plansChart' => [ 'type' => 'doughnut', 'data' => [ 'labels' => $labels, 'datasets' => [[ 'data' => $counts, 'backgroundColor' => ['token:info', 'token:accent', 'token:success-bright', 'token:warning'], 'borderWidth' => 0, ]], ], 'options' => [ 'cutout' => '62%', 'plugins' => ['legend' => ['position' => 'bottom', 'labels' => ['boxWidth' => 10, 'padding' => 12]]], ], ], ]); } }