212 lines
9.3 KiB
PHP
212 lines
9.3 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers;
|
|
|
|
use App\Models\Account;
|
|
use App\Models\Activity;
|
|
use App\Models\Client;
|
|
use App\Models\Contract;
|
|
// assuming model name Import
|
|
use App\Models\FieldJob; // if this model exists
|
|
use App\Models\Import;
|
|
use App\Models\SmsLog;
|
|
use App\Models\SmsProfile;
|
|
use App\Services\Sms\SmsService;
|
|
use Illuminate\Support\Facades\Cache;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\Schema;
|
|
use Inertia\Inertia;
|
|
use Inertia\Response;
|
|
|
|
class DashboardController extends Controller
|
|
{
|
|
public function __invoke(SmsService $sms): Response
|
|
{
|
|
$today = now()->startOfDay();
|
|
$cacheMinutes = 5;
|
|
|
|
// Active clients count - cached
|
|
$activeClientsCount = Cache::remember('dashboard:active_clients:'.now()->format('Y-m-d'), $cacheMinutes * 60, function () {
|
|
return Client::where('active', true)->count();
|
|
});
|
|
|
|
// Active contracts count - cached
|
|
$activeContractsCount = Cache::remember('dashboard:active_contracts:'.now()->format('Y-m-d'), $cacheMinutes * 60, function () {
|
|
return Contract::whereNull('deleted_at')->count();
|
|
});
|
|
|
|
// Sum of active contracts' account balance - cached
|
|
$totalBalance = Cache::remember('dashboard:total_balance:'.now()->format('Y-m-d'), $cacheMinutes * 60, function () {
|
|
return Account::whereHas('contract', function ($q) {
|
|
$q->whereNull('deleted_at');
|
|
})->sum('balance_amount') ?? 0;
|
|
});
|
|
|
|
// Active promises count (not expired or expires today) - cached
|
|
$activePromisesCount = Cache::remember('dashboard:active_promises:'.now()->format('Y-m-d'), $cacheMinutes * 60, function () use ($today) {
|
|
return Account::whereHas('contract', function ($q) {
|
|
$q->whereNull('deleted_at');
|
|
})
|
|
->whereNotNull('promise_date')
|
|
->whereDate('promise_date', '>=', $today)
|
|
->count();
|
|
});
|
|
|
|
// Activities (limit 10) - cached
|
|
$activities = Cache::remember('dashboard:activities', $cacheMinutes * 60, function () {
|
|
return Activity::query()
|
|
->with(['clientCase:id,uuid'])
|
|
->latest()
|
|
->limit(10)
|
|
->get(['id', 'note', 'created_at', 'client_case_id', 'contract_id', 'action_id', 'decision_id'])
|
|
->map(fn ($a) => [
|
|
'id' => $a->id,
|
|
'note' => $a->note,
|
|
'created_at' => $a->created_at,
|
|
'client_case_id' => $a->client_case_id,
|
|
'client_case_uuid' => $a->clientCase?->uuid,
|
|
'contract_id' => $a->contract_id,
|
|
'action_id' => $a->action_id,
|
|
'decision_id' => $a->decision_id,
|
|
]);
|
|
});
|
|
|
|
// 7-day trends for field jobs - cached
|
|
$trends = Cache::remember('dashboard:field_jobs_trends:'.now()->format('Y-m-d'), $cacheMinutes * 60, function () {
|
|
$start = now()->subDays(6)->startOfDay();
|
|
$end = now()->endOfDay();
|
|
|
|
$dateKeys = collect(range(0, 6))
|
|
->map(fn ($i) => now()->subDays(6 - $i)->format('Y-m-d'));
|
|
|
|
$fieldJobTrendRaw = FieldJob::whereBetween(DB::raw('COALESCE(assigned_at, created_at)'), [$start, $end])
|
|
->selectRaw('DATE(COALESCE(assigned_at, created_at)) as d, COUNT(*) as c')
|
|
->groupBy('d')
|
|
->pluck('c', 'd');
|
|
|
|
// Completed field jobs last 7 days
|
|
$fieldJobCompletedRaw = FieldJob::whereNotNull('completed_at')
|
|
->whereBetween('completed_at', [$start, $end])
|
|
->selectRaw('DATE(completed_at) as d, COUNT(*) as c')
|
|
->groupBy('d')
|
|
->pluck('c', 'd');
|
|
|
|
return [
|
|
'field_jobs' => $dateKeys->map(fn ($d) => (int) ($fieldJobTrendRaw[$d] ?? 0))->values(),
|
|
'field_jobs_completed' => $dateKeys->map(fn ($d) => (int) ($fieldJobCompletedRaw[$d] ?? 0))->values(),
|
|
'labels' => $dateKeys,
|
|
];
|
|
});
|
|
|
|
// Field jobs assigned today - cached
|
|
$fieldJobsAssignedToday = Cache::remember('dashboard:field_jobs_assigned_today:'.now()->format('Y-m-d'), $cacheMinutes * 60, function () use ($today) {
|
|
return FieldJob::query()
|
|
->whereDate(DB::raw('COALESCE(assigned_at, created_at)'), $today)
|
|
->select(['id', 'assigned_user_id', 'priority', 'assigned_at', 'created_at', 'contract_id'])
|
|
->with(['contract' => function ($q) {
|
|
$q->select('id', 'uuid', 'reference', 'client_case_id')
|
|
->with(['clientCase:id,uuid,person_id', 'clientCase.person:id,full_name', 'segments:id,name']);
|
|
}])
|
|
->latest(DB::raw('COALESCE(assigned_at, created_at)'))
|
|
->limit(15)
|
|
->get()
|
|
->map(function ($fj) {
|
|
$contract = $fj->contract;
|
|
$segmentId = null;
|
|
if ($contract && method_exists($contract, 'segments')) {
|
|
$activeSeg = $contract->segments->first();
|
|
if ($activeSeg && isset($activeSeg->pivot) && ($activeSeg->pivot->active ?? true)) {
|
|
$segmentId = $activeSeg->id;
|
|
}
|
|
}
|
|
|
|
return [
|
|
'id' => $fj->id,
|
|
'priority' => $fj->priority,
|
|
'assigned_at' => $fj->assigned_at?->toIso8601String(),
|
|
'created_at' => $fj->created_at?->toIso8601String(),
|
|
'contract' => $contract ? [
|
|
'uuid' => $contract->uuid,
|
|
'reference' => $contract->reference,
|
|
'client_case_uuid' => optional($contract->clientCase)->uuid,
|
|
'person_full_name' => optional(optional($contract->clientCase)->person)->full_name,
|
|
'segment_id' => $segmentId,
|
|
] : null,
|
|
];
|
|
});
|
|
});
|
|
|
|
// System health for timestamp
|
|
$recentActivity = Activity::query()->latest('created_at')->value('created_at');
|
|
$lastActivityMinutes = null;
|
|
if ($recentActivity) {
|
|
$lastActivityMinutes = (int) max(0, now()->diffInMinutes($recentActivity));
|
|
}
|
|
$systemHealth = [
|
|
'last_activity_minutes' => $lastActivityMinutes,
|
|
'last_activity_iso' => $recentActivity?->toIso8601String(),
|
|
'generated_at' => now()->toIso8601String(),
|
|
];
|
|
|
|
return Inertia::render('Dashboard/Index', [
|
|
'kpis' => [
|
|
'active_clients' => $activeClientsCount,
|
|
'active_contracts' => $activeContractsCount,
|
|
'total_balance' => $totalBalance,
|
|
'active_promises' => $activePromisesCount,
|
|
],
|
|
'trends' => $trends,
|
|
])->with([
|
|
'activities' => fn () => $activities,
|
|
'systemHealth' => fn () => $systemHealth,
|
|
'fieldJobsAssignedToday' => fn () => $fieldJobsAssignedToday,
|
|
'smsStats' => function () use ($sms, $today, $cacheMinutes) {
|
|
// SMS stats - cached
|
|
return Cache::remember('dashboard:sms_stats:'.now()->format('Y-m-d'), $cacheMinutes * 60, function () use ($sms, $today) {
|
|
$counts = SmsLog::query()
|
|
->whereDate('created_at', $today)
|
|
->selectRaw('profile_id, status, COUNT(*) as c')
|
|
->groupBy('profile_id', 'status')
|
|
->get()
|
|
->groupBy('profile_id')
|
|
->map(function ($rows) {
|
|
$map = [
|
|
'queued' => 0,
|
|
'sent' => 0,
|
|
'delivered' => 0,
|
|
'failed' => 0,
|
|
];
|
|
foreach ($rows as $r) {
|
|
$map[$r->status] = (int) $r->c;
|
|
}
|
|
$map['total'] = array_sum($map);
|
|
|
|
return $map;
|
|
});
|
|
|
|
$profiles = SmsProfile::query()
|
|
->orderBy('name')
|
|
->get(['id', 'name', 'active', 'api_username', 'encrypted_api_password']);
|
|
|
|
return $profiles->map(function (SmsProfile $p) use ($sms, $counts) {
|
|
try {
|
|
$balance = $sms->getCreditBalance($p);
|
|
} catch (\Throwable $e) {
|
|
$balance = '—';
|
|
}
|
|
$c = $counts->get($p->id) ?? ['queued' => 0, 'sent' => 0, 'delivered' => 0, 'failed' => 0, 'total' => 0];
|
|
|
|
return [
|
|
'id' => $p->id,
|
|
'name' => $p->name,
|
|
'active' => (bool) $p->active,
|
|
'balance' => $balance,
|
|
'today' => $c,
|
|
];
|
|
})->values();
|
|
});
|
|
},
|
|
]);
|
|
}
|
|
}
|