@extends('layouts.app') @section('content') {{-- ── PAGE HEADER ── --}}

CDR Reports

View, analyse and export call detail records from your V-Connect PBX.
Refresh CSV Excel PDF
{{-- ── KPI CARDS ── --}}
{{-- Total Calls --}}
Total Calls
{{ number_format($kpiTotal) }}
@if($trends['total'])
{{ str_starts_with($trends['total'], '+') ? '↑' : '↓' }} {{ $trends['total'] }}
@endif
vs last period
{{-- Answered --}}
Answered
{{ number_format($kpiAnswered) }}
@if($trends['answered'])
{{ str_starts_with($trends['answered'], '+') ? '↑' : '↓' }} {{ $trends['answered'] }}
@endif
{{ $kpiTotal > 0 ? round($kpiAnswered/$kpiTotal*100,1) : 0 }}% of total
{{-- Missed --}}
Missed
{{ number_format($kpiMissed) }}
@if($trends['missed']) @php $missDown = str_starts_with($trends['missed'], '-'); @endphp
{{ $missDown ? '↓' : '↑' }} {{ $trends['missed'] }}
@endif
{{ $kpiTotal > 0 ? round($kpiMissed/$kpiTotal*100,1) : 0 }}% of total
{{-- Total Talk Time --}}
Total Talk Time
{{ $kpiTalkHuman }}
@if($trends['talk'])
{{ str_starts_with($trends['talk'], '+') ? '↑' : '↓' }} {{ $trends['talk'] }}
@endif
vs last period
{{-- Avg Duration --}}
Average Duration
{{ $kpiAvgHuman }}
@if($trends['avg'])
{{ str_starts_with($trends['avg'], '+') ? '↑' : '↓' }} {{ $trends['avg'] }}
@endif
per answered call
{{-- ── FILTERS ── --}}
{{-- Quick Presets --}}
Quick Date Presets @php $today = now()->format('Y-m-d'); $yesterday = now()->subDay()->format('Y-m-d'); $last7s = now()->subDays(6)->format('Y-m-d'); $last30s = now()->subDays(29)->format('Y-m-d'); $monthS = now()->startOfMonth()->format('Y-m-d'); @endphp
{{-- ── CHARTS ROW ── --}}
{{-- Call Activity Line Chart --}}
Call Activity
● Total Calls   ● Answered   ● Missed
{{-- Call Disposition Donut --}}
Call Disposition (All)
View full report →
@php $dTotal = array_sum($dispCounts); $dAns = $dispCounts['ANSWERED'] ?? 0; $dNoAns = $dispCounts['NO ANSWER'] ?? 0; $dBusy = $dispCounts['BUSY'] ?? 0; $dFail = $dispCounts['FAILED'] ?? 0; $r = 44; $cx = 55; $cy = 55; $circ = 2 * M_PI * $r; $slices = [ ['pct' => $dTotal > 0 ? $dAns/$dTotal : 0, 'color' => '#22c55e'], ['pct' => $dTotal > 0 ? $dNoAns/$dTotal : 0, 'color' => '#f59e0b'], ['pct' => $dTotal > 0 ? $dBusy/$dTotal : 0, 'color' => '#ef4444'], ['pct' => $dTotal > 0 ? $dFail/$dTotal : 0, 'color' => '#6366f1'], ]; $offset = 0; $paths = ''; foreach ($slices as $s) { $dash = $s['pct'] * $circ; $paths .= ""; $offset += $dash; } @endphp
{!! $dTotal > 0 ? $paths : '' !!}
{{ number_format($dTotal) }}
Total Calls
@foreach ([ ['Answered', $dAns, '#22c55e'], ['No Answer', $dNoAns, '#f59e0b'], ['Busy', $dBusy, '#ef4444'], ['Failed', $dFail, '#6366f1'], ] as [$label, $count, $color])
{{ $label }}
{{ number_format($count) }} ({{ $dTotal > 0 ? round($count/$dTotal*100,1) : 0 }}%)
@endforeach
{{-- ── CALL RECORDS TABLE ── --}}
Call Records {{ number_format($cdr->total()) }} records found
{{-- Column toggle --}} {{ $cdr->firstItem() }}–{{ $cdr->lastItem() }} of {{ number_format($cdr->total()) }}
@if($cdr->previousPageUrl()) ‹ @endif @if($cdr->nextPageUrl()) › @endif
@php // Click-to-dial: enabled only when the logged-in user has an // extension assigned. Defined once here, reused for every row. $c2dCanDial = !empty(optional(auth()->user())->extension); $c2dDialable = function ($v) { $v = trim((string) $v); return $v !== '' && preg_match('/^[0-9+*#]{2,24}$/', $v); }; @endphp @forelse ($cdr as $row) @php $dir = $row->direction ?? 'UNKNOWN'; $disp = $row->disposition ?? ''; $dirClass = match($dir) { 'OUTBOUND'=>'dir-outbound','INBOUND'=>'dir-inbound','INTERNAL'=>'dir-internal',default=>'dir-unknown' }; $dispClass = match($disp) { 'ANSWERED'=>'disp-answered','NO ANSWER'=>'disp-noanswer','BUSY'=>'disp-busy','FAILED'=>'disp-failed',default=>'disp-unknown' }; $durSecs = (int)($row->billsec ?? $row->duration ?? 0); $durFmt = sprintf('%02d:%02d', intdiv($durSecs, 60), $durSecs % 60); $recFile = $row->recordingfile ?? ''; @endphp @php // PIN attribution: dialplan stamps accountcode = PIN number, // userfield = "^". Older rows stored just the // department in userfield (no '^'). $pinRaw = (string)($row->userfield ?? ''); $pinUser = $pinRaw; $pinDept = ''; if (strpos($pinRaw, '^') !== false) { [$pinUser, $pinDept] = array_pad(explode('^', $pinRaw, 2), 2, ''); } $pinCode = (string)($row->accountcode ?? ''); // For a PIN call, show the PIN's department; otherwise the // department(s) derived from the caller/callee extension. $deptDisplay = $pinDept !== '' ? $pinDept : ($row->src_departments ?: $row->dst_departments ?: ''); @endphp @empty @endforelse
Date & Time Direction Source Destination Duration Disposition Department PIN User Recording Actions
{{ $row->calldate ?? '—' }} {{ $dir }} @if(!empty($row->is_teams)) @elseif(($row->caller_device ?? null) === 'web_phone') @elseif(($row->caller_device ?? null) === 'mobile_app') @endif @if(!empty($c2dCanDial) && $c2dDialable($row->src ?? '')) {{ $row->src }} @else
{{ $row->src ?? '—' }}
@endif @if($row->src_name && $row->src_name !== ($row->src ?? ''))
{{ $row->src_name }}
@endif @if(!empty($row->original_caller))
↳ from {{ $row->original_caller }}
@endif
@if(!empty($c2dCanDial) && $c2dDialable($row->dst ?? '')) {{ $row->dst }} @else
{{ $row->dst ?? '—' }}
@endif @if($row->dst_name && $row->dst_name !== ($row->dst ?? ''))
{{ $row->dst_name }}
@endif
{{ $durFmt }} {{ $disp ?: 'UNKNOWN' }} @if(!empty($row->is_voicemail)) VM @endif @if($deptDisplay) {{ $deptDisplay }} @else — @endif @if($pinUser !== '') {{ $pinUser }} @else — @endif @if($recFile)
{{ $durFmt }}
@else — @endif
No CDR records found for the selected filters.
Showing {{ $cdr->firstItem() ?? 0 }}–{{ $cdr->lastItem() ?? 0 }} of {{ number_format($cdr->total()) }} records
{{ $cdr->appends(request()->query())->links('pagination::bootstrap-4') }}
{{-- ── RECORDING PLAYER MODAL ── --}} {{-- ── CALL DETAIL MODAL ── --}} @endsection