@extends('layouts.app')
@php
$fmtDt = fn($v) => $v ? \Illuminate\Support\Carbon::parse($v)->format('M j, Y H:i') : '—';
// Build a keyed map so we don't rely on variable variables.
$snapshots = [
'deviations' => $deviations ?? [],
'capa' => $capa ?? [],
'change_control' => $change_control ?? [],
'complaints' => $complaints ?? [],
'audits' => $audits ?? [],
];
$cross = $cross ?? ['aging_buckets' => ['0_7' => 0, '8_30' => 0, '31_90' => 0, '90_plus' => 0]];
// Never print arrays — coerce to int for safe interpolation.
$num = function ($value): int {
if (is_array($value)) return 0;
if (is_bool($value)) return (int) $value;
if (is_numeric($value)) return (int) $value;
return 0;
};
@endphp
@section('title', 'KPI Dashboard')
@section('content')
{{-- Header --}}
KPI Dashboard
Snapshot generated {{ $fmtDt($generated_at ?? now()) }}
· cached {{ \App\Services\KpiService::CACHE_TTL }}s
{{-- Cross-module summary --}}
Total Open Across QMS
{{ $num($cross['total_open'] ?? 0) }}
Deviations + CAPA + Changes + Complaints + Audits
Overdue Across QMS
{{ $num($cross['total_overdue'] ?? 0) }}
Past due date and still open
Supplier Requal. Due
{{ $num($cross['supplier_requal_due'] ?? 0) }}
Next 60 days
Aging (30+ Days)
{{ $num(($cross['aging_buckets']['31_90'] ?? 0) + ($cross['aging_buckets']['90_plus'] ?? 0)) }}
{{ $num($cross['aging_buckets']['90_plus'] ?? 0) }} older than 90 days
{{-- Aging buckets --}}
@php
$buckets = [
'0–7 days' => $num($cross['aging_buckets']['0_7'] ?? 0),
'8–30 days' => $num($cross['aging_buckets']['8_30'] ?? 0),
'31–90 days' => $num($cross['aging_buckets']['31_90'] ?? 0),
'90+ days' => $num($cross['aging_buckets']['90_plus'] ?? 0),
];
$max = max($buckets) ?: 1;
@endphp
@foreach($buckets as $label => $count)
{{ $label }}
{{ $count }}
@endforeach
{{-- Per-module cards --}}
@php
$modules = [
[
'key' => 'deviations', 'label' => 'Deviations & NC',
'icon' => 'exclamation-triangle', 'color' => 'warning',
'route' => 'kpi.deviations',
'open_key' => 'open', 'overdue_key' => 'overdue',
'additional_key' => 'critical', 'additional_label' => 'Critical',
],
[
'key' => 'capa', 'label' => 'CAPA',
'icon' => 'tasks', 'color' => 'success',
'route' => 'kpi.capa',
'open_key' => 'open', 'overdue_key' => 'overdue',
'additional_key' => 'awaiting_effectiveness', 'additional_label' => 'Awaiting Eff.',
],
[
'key' => 'change_control', 'label' => 'Change Control',
'icon' => 'exchange-alt', 'color' => 'primary',
'route' => 'kpi.change-control',
'open_key' => 'open', 'overdue_key' => 'overdue',
'additional_key' => 'pending_approval', 'additional_label' => 'Pending Appr.',
],
[
'key' => 'complaints', 'label' => 'Complaints',
'icon' => 'comment-dots', 'color' => 'info',
'route' => 'kpi.complaints',
'open_key' => 'open', 'overdue_key' => 'reportable_overdue',
'additional_key' => 'critical', 'additional_label' => 'Critical',
],
[
'key' => 'audits', 'label' => 'Audits',
'icon' => 'clipboard-list', 'color' => 'secondary',
'route' => 'kpi.audits',
'open_key' => 'open', 'overdue_key' => 'overdue',
'additional_key' => 'open_findings', 'additional_label' => 'Open Findings',
],
];
@endphp
@foreach($modules as $m)
@php $snap = $snapshots[$m['key']] ?? []; @endphp
{{ $m['label'] }}
Total: {{ $num($snap['total'] ?? 0) }}
Details
{{ $num($snap[$m['open_key']] ?? 0) }}
Open
{{ $num($snap[$m['overdue_key']] ?? 0) }}
Overdue
{{ $num($snap[$m['additional_key']] ?? 0) }}
{{ $m['additional_label'] }}
@php $closed = $num($snap['closed_this_month'] ?? 0); @endphp
@if($closed > 0)
{{ $closed }} closed this month
@endif
@endforeach
{{-- 30-day trend mini-charts --}}
@endsection
@push('scripts')
@endpush