@extends('layouts.app')
@section('title', 'Dashboard')
@section('content')
Operations dashboard
{{ auth()->user()->organization?->name }} · {{ now()->format('l, j F Y H:i') }} {{ config('app.timezone') }}
{{-- ============ Financial + subscriber KPIs ============ --}}
@include('components.kpi', ['label' => 'Monthly recurring revenue', 'value' => money($kpi['mrr']),
'variant' => 'good', 'href' => route('reports.financial'), 'delta' => 'ARR '.money($kpi['arr'])])
@include('components.kpi', ['label' => 'ARPU', 'value' => money($kpi['arpu']),
'variant' => 'info', 'href' => route('reports.subscribers'), 'delta' => $kpi['active_customers'].' active accounts'])
@include('components.kpi', ['label' => 'Active services', 'value' => number_format($kpi['active_services']),
'href' => route('services.index', ['status' => 'active']), 'delta' => $kpi['suspended_services'].' suspended'])
@include('components.kpi', ['label' => 'Online now', 'value' => number_format($kpi['online_now']),
'variant' => 'good', 'href' => route('radius.sessions', ['online' => 1]), 'delta' => 'live RADIUS sessions'])
@include('components.kpi', ['label' => 'Outstanding', 'value' => money($finance['outstanding']),
'variant' => $finance['outstanding'] > 0 ? 'warn' : 'good', 'href' => route('reports.collections'),
'delta' => $collections['overdue_invoices'].' overdue invoices'])
@include('components.kpi', ['label' => 'Collected today', 'value' => money($finance['collected_today']),
'variant' => 'good', 'href' => route('payments.index'), 'delta' => money($finance['collected_month']).' this month'])
{{-- Revenue trend --}}
Revenue collected — last 30 days
Successful payments by day
Report →
@include('components.chart', [
'series' => collect($charts['revenue_30d'])->map(fn ($p) => [
'label' => \Carbon\Carbon::parse($p['date'])->format('d M'),
'value' => $p['total'],
])->all(),
'type' => 'line', 'format' => 'money', 'height' => 200,
])
{{-- MRR trend --}}
MRR & subscriber trend
Nightly KPI snapshot, 12 months
Report →
@if (count($charts['mrr_trend']) > 1)
@include('components.chart', [
'series' => collect($charts['mrr_trend'])->map(fn ($p) => [
'label' => \Carbon\Carbon::parse($p['date'])->format('M'),
'value' => $p['mrr'],
])->all(),
'type' => 'line', 'format' => 'money', 'height' => 200,
])
@else
📈
No KPI snapshots yet
Run php artisan nowills:snapshot to build the first data point.
@endif
{{-- Network health --}}
Devices up
{{ $noc['devices']['up'] }}/{{ $noc['devices']['total'] }}
Availability 30d
{{ number_format($noc['availability'], 2) }}%
Active alerts
{{ $noc['alerts']['active'] }} ({{ $noc['alerts']['critical'] }} critical)
Raised today{{ $noc['alerts']['today'] }}
Maintenance windows{{ count($noc['maintenance']) }}
@if ($criticalAlerts->isNotEmpty())
Critical now
@foreach ($criticalAlerts as $alert)
@endforeach
@endif
{{-- Service delivery --}}
SLA breached
{{ $operations['tickets_breached'] }}
Devices down
{{ $operations['devices_down'] }}
Plan mix
@forelse (collect($charts['plan_mix'])->take(6) as $plan)
@php $maxSubs = collect($charts['plan_mix'])->max('subscribers') ?: 1; @endphp
{{ $plan['plan'] }}
{{ $plan['subscribers'] }}
@empty
No active subscriptions yet.
@endforelse
{{-- Collections --}}
Collection rate
{{ number_format($finance['collection_rate'] ?? 0, 1) }}%
Overdue amount
{{ money($collections['overdue_amount']) }}
New this month{{ $kpi['new_this_month'] }}
Churned this month
{{ $kpi['churned_this_month'] }}
Aging
@php $agingMax = max(1, collect($collections['aging'])->max('amount')); @endphp
@foreach ($collections['aging'] as $bucket)
{{ $bucket['label'] }}
{{ money($bucket['amount']) }}
@endforeach
{{-- Overdue invoices --}}
| Invoice | Customer | Balance | Days | |
@forelse ($overdueInvoices as $invoice)
| {{ $invoice->invoice_number }} |
{{ $invoice->customer->display_name }} |
{{ money($invoice->balance, $invoice->currency) }} |
{{ (int) $invoice->due_date->diffInDays(now()) }}
|
Collect
|
@empty
|
@include('components.empty-state', ['icon' => '✓', 'title' => 'No overdue invoices',
'message' => 'Every account is current.'])
|
@endforelse
{{-- Live sessions --}}
| Subscriber | IP | NAS | Uptime | Traffic |
@forelse ($liveSessions as $session)
|
@if ($session->service)
{{ $session->username }}
@else
{{ $session->username }}
@endif
|
{{ $session->framed_ip ?: '—' }} |
{{ $session->nas_ip }} |
{{ $session->duration_label }} |
{{ $session->traffic_label }} |
@empty
|
@include('components.empty-state', ['icon' => '🔌', 'title' => 'No live sessions',
'message' => 'Sessions appear here once the NAS starts sending RADIUS accounting.'])
|
@endforelse
{{-- Open tickets --}}
| Ticket | Customer | Priority | Owner | Age |
@forelse ($openTickets as $ticket)
|
{{ $ticket->ticket_number }}
{{ $ticket->subject }}
|
{{ $ticket->customer->display_name }} |
{{ $ticket->priority }} |
{{ $ticket->assignee?->name ?: 'Unassigned' }} |
|
@empty
| @include('components.empty-state', ['icon' => '✉', 'title' => 'No open tickets']) |
@endforelse
{{-- Pending work orders --}}
| Job | Customer | Type | Technician | Scheduled |
@forelse ($pendingWorkOrders as $wo)
| {{ $wo->work_order_number }} |
{{ $wo->customer?->display_name ?: '—' }} |
{{ title_case_words($wo->type) }} |
{{ $wo->technician?->name ?: 'Unassigned' }} |
{{ $wo->scheduled_start?->format('d M H:i') ?: '—' }} |
@empty
| @include('components.empty-state', ['icon' => '🔧', 'title' => 'No pending jobs']) |
@endforelse
{{-- Recent services --}}
| Service | Customer | Plan | RADIUS user |
Price | Status | Next bill |
@forelse ($recentServices as $service)
| {{ $service->service_number }} |
{{ $service->customer->display_name }} |
{{ $service->plan?->name }} |
{{ $service->username }} |
{{ money($service->effective_price, $service->customer->currency) }} |
{{ $service->status }} |
{{ $service->next_billing_date?->format('d M Y') ?: '—' }} |
@empty
|
@include('components.empty-state', ['icon' => '⇄', 'title' => 'No services yet',
'message' => 'Provision your first subscriber to get started.',
'action' => '+ New service'])
|
@endforelse
@endsection