@extends('dashboard.layouts.app') @section('content')
@if (isset($recruitmentAnalytics)) @php $reportSections = [ 'summary' => 'Hiring Summary', 'vacancies' => 'Open Positions', 'funnel' => 'Hiring Funnel', 'source_offer' => 'Source & Offers', 'time_recruiter' => 'Time & Recruiters', 'tracker_turnover' => 'Tracker & Turnover', // 'builder' => 'Unified Report Builder', ]; $currentSectionKey = $activeReportSection ?? 'summary'; $vacancyChartRows = $recruitmentAnalytics['vacancies'] ->sortByDesc('remaining_openings') ->take(6) ->map(fn ($vacancy) => [ 'job_title' => \Illuminate\Support\Str::limit($vacancy['job_title'], 24), 'open' => (int) $vacancy['remaining_openings'], 'hired' => (int) $vacancy['hired_count'], ]) ->values() ->all(); $sourceChartRows = $recruitmentAnalytics['source_of_hire'] ->take(6) ->map(fn ($source) => [ 'source' => $source['source'], 'count' => (int) $source['count'], ]) ->values() ->all(); $stageChartRows = collect($recruitmentAnalytics['time_to_hire']['stage_averages'] ?? []) ->map(fn ($value, $stage) => [ 'stage' => \Illuminate\Support\Str::headline(str_replace('_', ' ', $stage)), 'days' => $value !== null ? (float) $value : 0, ]) ->values() ->all(); $recruiterChartRows = $recruitmentAnalytics['recruiters'] ->sortByDesc('hires_closed') ->take(6) ->map(fn ($recruiter) => [ 'name' => \Illuminate\Support\Str::limit($recruiter['recruiter'], 18), 'hires' => (int) $recruiter['hires_closed'], 'interviews' => (int) $recruiter['interviews_arranged'], ]) ->values() ->all(); $trackerStatusRows = $recruitmentAnalytics['tracker'] ->groupBy('final_status') ->map(fn ($group, $status) => [ 'status' => $status, 'count' => $group->count(), ]) ->sortByDesc('count') ->values() ->all(); $turnoverDepartmentRows = collect($recruitmentAnalytics['turnover']['departments'] ?? []) ->take(6) ->map(fn ($department) => [ 'department' => \Illuminate\Support\Str::limit($department['department'], 18), 'count' => (int) $department['count'], ]) ->values() ->all(); $turnoverQuarterRows = collect($recruitmentAnalytics['turnover']['quarterly'] ?? []) ->sortBy('quarter') ->map(fn ($quarter) => [ 'quarter' => $quarter['quarter'], 'count' => (int) $quarter['count'], ]) ->values() ->all(); $funnelWeeklyRows = $recruitmentAnalytics['funnel_weekly'] ->take(8) ->values() ->all(); $funnelMonthlyRows = $recruitmentAnalytics['funnel_monthly'] ->take(6) ->values() ->all(); $pendingOffers = max( (int) ($recruitmentAnalytics['offers']['sent'] ?? 0) - (int) ($recruitmentAnalytics['offers']['accepted'] ?? 0) - (int) ($recruitmentAnalytics['offers']['declined'] ?? 0), 0 ); $trackerExportParams = [ 'section' => 'tracker_turnover', 'scope' => 'tracker', ]; if (!empty($analyticsFilter['start_date']) && !empty($analyticsFilter['end_date'])) { $trackerExportParams['from_date'] = $analyticsFilter['start_date']->format('Y-m-d'); $trackerExportParams['to_date'] = $analyticsFilter['end_date']->format('Y-m-d'); } else { $trackerExportParams['range'] = $analyticsFilter['range'] ?? 'last_30_days'; } $reportChartData = [ 'summary' => [ 'metric_labels' => ['Open Positions', 'Closed', 'New Hires', 'Offers Sent', 'Offers Accepted'], 'metric_series' => [ (int) ($recruitmentAnalytics['board_summary']['open_positions'] ?? 0), (int) ($recruitmentAnalytics['board_summary']['positions_closed'] ?? 0), (int) ($recruitmentAnalytics['board_summary']['new_hires'] ?? 0), (int) ($recruitmentAnalytics['board_summary']['offers_sent'] ?? 0), (int) ($recruitmentAnalytics['board_summary']['offers_accepted'] ?? 0), ], 'funnel_labels' => ['CVs', 'Contacted', 'Interviews', 'Hired', 'Demo Attended', 'No-show'], 'funnel_series' => [ (int) ($recruitmentAnalytics['funnel_totals']['cvs'] ?? 0), (int) ($recruitmentAnalytics['funnel_totals']['contacted'] ?? 0), (int) ($recruitmentAnalytics['funnel_totals']['interviews'] ?? 0), (int) ($recruitmentAnalytics['funnel_totals']['hired'] ?? 0), (int) ($recruitmentAnalytics['funnel_totals']['demos_attended'] ?? 0), (int) ($recruitmentAnalytics['funnel_totals']['demos_no_show'] ?? 0), ], ], 'funnel' => [ 'overview_labels' => ['CVs', 'Contacted', 'Interviews', 'Hired', 'Demo Attended', 'No-show'], 'overview_series' => [ (int) ($recruitmentAnalytics['funnel_totals']['cvs'] ?? 0), (int) ($recruitmentAnalytics['funnel_totals']['contacted'] ?? 0), (int) ($recruitmentAnalytics['funnel_totals']['interviews'] ?? 0), (int) ($recruitmentAnalytics['funnel_totals']['hired'] ?? 0), (int) ($recruitmentAnalytics['funnel_totals']['demos_attended'] ?? 0), (int) ($recruitmentAnalytics['funnel_totals']['demos_no_show'] ?? 0), ], 'weekly_labels' => array_column($funnelWeeklyRows, 'label'), 'weekly_cvs' => array_column($funnelWeeklyRows, 'cvs'), 'weekly_hired' => array_column($funnelWeeklyRows, 'hired'), 'monthly_labels' => array_column($funnelMonthlyRows, 'label'), 'monthly_cvs' => array_column($funnelMonthlyRows, 'cvs'), 'monthly_hired' => array_column($funnelMonthlyRows, 'hired'), ], 'vacancies' => [ 'labels' => array_column($vacancyChartRows, 'job_title'), 'open_series' => array_column($vacancyChartRows, 'open'), 'hired_series' => array_column($vacancyChartRows, 'hired'), ], 'source_offer' => [ 'source_labels' => array_column($sourceChartRows, 'source'), 'source_series' => array_column($sourceChartRows, 'count'), 'offer_labels' => ['Accepted', 'Declined', 'Pending'], 'offer_series' => [ (int) ($recruitmentAnalytics['offers']['accepted'] ?? 0), (int) ($recruitmentAnalytics['offers']['declined'] ?? 0), $pendingOffers, ], ], 'time_recruiter' => [ 'stage_labels' => array_column($stageChartRows, 'stage'), 'stage_series' => array_column($stageChartRows, 'days'), 'recruiter_labels' => array_column($recruiterChartRows, 'name'), 'hire_series' => array_column($recruiterChartRows, 'hires'), 'interview_series' => array_column($recruiterChartRows, 'interviews'), ], 'tracker_turnover' => [ 'status_labels' => array_column($trackerStatusRows, 'status'), 'status_series' => array_column($trackerStatusRows, 'count'), 'turnover_labels' => ['Approved', 'Rejected', 'Pending / In Progress'], 'turnover_series' => [ (int) ($recruitmentAnalytics['turnover']['approved_count'] ?? 0), (int) ($recruitmentAnalytics['turnover']['rejected_count'] ?? 0), (int) ($recruitmentAnalytics['turnover']['pending_count'] ?? 0), ], 'department_labels' => array_column($turnoverDepartmentRows, 'department'), 'department_series' => array_column($turnoverDepartmentRows, 'count'), 'quarter_labels' => array_column($turnoverQuarterRows, 'quarter'), 'quarter_series' => array_column($turnoverQuarterRows, 'count'), ], ]; @endphp
HR CONTROL CENTER
HR Reports Dashboard
{{ $recruitmentAnalytics['date_label'] }}
@foreach ($reportSections as $sectionKey => $sectionLabel) @endforeach
Open Positions{{ $recruitmentAnalytics['board_summary']['open_positions'] }}
Positions Closed{{ $recruitmentAnalytics['board_summary']['positions_closed'] }}
New Hires{{ $recruitmentAnalytics['board_summary']['new_hires'] }}
Avg. Time to Hire{{ $recruitmentAnalytics['board_summary']['avg_time_to_hire'] !== null ? $recruitmentAnalytics['board_summary']['avg_time_to_hire'] . ' d' : 'N/A' }}
Offers Sent{{ $recruitmentAnalytics['board_summary']['offers_sent'] }}
Offers Accepted{{ $recruitmentAnalytics['board_summary']['offers_accepted'] }}
Summary Snapshot
Pipeline Mix
Vacancy Coverage
@forelse ($recruitmentAnalytics['vacancies'] as $vacancy) @empty @endforelse
Job Title Department Hiring Manager Date Opened Openings Status
{{ $vacancy['job_title'] }} {{ $vacancy['department'] }} @if (!empty($vacancy['hiring_manager_id'])) {{ $vacancy['hiring_manager'] }} @else {{ $vacancy['hiring_manager'] }} @endif {{ $vacancy['date_opened'] ?? '-' }} {{ $vacancy['remaining_openings'] }} / {{ $vacancy['vacancy_count'] }} {{ \Illuminate\Support\Str::headline($vacancy['status']) }}
No vacancies found.
Hiring Funnel
CVs: {{ $recruitmentAnalytics['funnel_totals']['cvs'] }}
Contacted: {{ $recruitmentAnalytics['funnel_totals']['contacted'] }}
Interviews: {{ $recruitmentAnalytics['funnel_totals']['interviews'] }}
Hired: {{ $recruitmentAnalytics['funnel_totals']['hired'] }}
Demos Attended: {{ $recruitmentAnalytics['funnel_totals']['demos_attended'] }}
Demos No-show: {{ $recruitmentAnalytics['funnel_totals']['demos_no_show'] }}
Weekly Funnel
@forelse ($recruitmentAnalytics['funnel_weekly']->take(8) as $row)
{{ $row['label'] }}: {{ $row['cvs'] }} CVs, {{ $row['hired'] }} Hired
@empty
No weekly data.
@endforelse
Monthly Funnel
@forelse ($recruitmentAnalytics['funnel_monthly']->take(6) as $row)
{{ $row['label'] }}: {{ $row['cvs'] }} CVs, {{ $row['hired'] }} Hired
@empty
No monthly data.
@endforelse
Source of Hire
@forelse ($recruitmentAnalytics['source_of_hire'] as $source)
{{ $source['source'] }}: {{ $source['count'] }} candidates, {{ $source['hired'] }} hired
@empty
No source data.
@endforelse
Offer Report
Sent: {{ $recruitmentAnalytics['offers']['sent'] }}
Accepted: {{ $recruitmentAnalytics['offers']['accepted'] }}
Declined: {{ $recruitmentAnalytics['offers']['declined'] }}
Rejection Reasons Summary
@forelse ($recruitmentAnalytics['offers']['reasons'] as $reason)
{{ $reason['reason'] }}: {{ $reason['count'] }}
@empty
No declined offer reasons yet.
@endforelse
Rejected Candidates
@forelse ($recruitmentAnalytics['offers']['declined_candidates']->take(6) as $declinedOffer) @empty @endforelse
Candidate Position Reason
{{ $declinedOffer['candidate'] }} {{ $declinedOffer['position'] }} {{ $declinedOffer['reason'] }}
No rejected offers yet.
Time to Hire
Longest Hiring Period: {{ $recruitmentAnalytics['time_to_hire']['overall_longest'] !== null ? $recruitmentAnalytics['time_to_hire']['overall_longest'] . ' d' : 'N/A' }}
Shortest Hiring Period: {{ $recruitmentAnalytics['time_to_hire']['overall_shortest'] !== null ? $recruitmentAnalytics['time_to_hire']['overall_shortest'] . ' d' : 'N/A' }}
Screening: {{ $recruitmentAnalytics['time_to_hire']['stage_averages']['screening'] !== null ? $recruitmentAnalytics['time_to_hire']['stage_averages']['screening'] . ' d' : 'N/A' }}
First Interview: {{ $recruitmentAnalytics['time_to_hire']['stage_averages']['first_interview'] !== null ? $recruitmentAnalytics['time_to_hire']['stage_averages']['first_interview'] . ' d' : 'N/A' }}
Second Interview: {{ $recruitmentAnalytics['time_to_hire']['stage_averages']['second_interview'] !== null ? $recruitmentAnalytics['time_to_hire']['stage_averages']['second_interview'] . ' d' : 'N/A' }}
Offer to Onboarding: {{ $recruitmentAnalytics['time_to_hire']['stage_averages']['offer_to_onboarding'] !== null ? $recruitmentAnalytics['time_to_hire']['stage_averages']['offer_to_onboarding'] . ' d' : 'N/A' }}
Interviewing Average: {{ $recruitmentAnalytics['time_to_hire']['stage_averages']['interviewing'] !== null ? $recruitmentAnalytics['time_to_hire']['stage_averages']['interviewing'] . ' d' : 'N/A' }}
@foreach ($recruitmentAnalytics['time_to_hire']['per_role']->take(6) as $role)
{{ $role['role'] }}: {{ $role['average'] !== null ? $role['average'] . ' d' : 'N/A' }}
@endforeach
Recruiter Summary
@forelse ($recruitmentAnalytics['recruiters'] as $recruiter)
@if (!empty($recruiter['staff_id'])) {{ $recruiter['recruiter'] }} @else {{ $recruiter['recruiter'] }} @endif : {{ $recruiter['cvs_sourced'] }} CVs, {{ $recruiter['interviews_arranged'] }} Interviews, {{ $recruiter['hires_closed'] }} Hires, {{ $recruiter['offer_acceptance_rate'] !== null ? $recruiter['offer_acceptance_rate'] . '%' : 'N/A' }} Offer Acceptance @if (!empty($recruiter['roles_assigned']))
Roles: {{ $recruiter['roles_assigned'] }} @endif
@empty
No recruiter data.
@endforelse
Recruitment Tracker
@can('hr_reports_generate') @endcan
@forelse ($recruitmentAnalytics['tracker']->take(12) as $candidate) @empty @endforelse
Name Phone Number Position Department Qualifications Years Interview Source Final Status Rejection Reason
@if (!empty($candidate['staff_id'])) {{ $candidate['name'] }} @elseif (!empty($candidate['applicant_id'])) {{ $candidate['name'] }} @else {{ $candidate['name'] }} @endif {{ $candidate['number'] }} {{ $candidate['position'] }} {{ $candidate['department'] }} {{ $candidate['qualifications'] }} {{ $candidate['years_of_experience'] }} {{ $candidate['interview_date'] }} {{ $candidate['source'] }} {{ $candidate['final_status'] }} {{ $candidate['rejection_reason'] }}
No candidates found.
Turnover Report
Resignation Requests: {{ $recruitmentAnalytics['turnover']['count'] }}
Approved: {{ $recruitmentAnalytics['turnover']['approved_count'] }}
Rejected: {{ $recruitmentAnalytics['turnover']['rejected_count'] }}
Pending / In Progress: {{ $recruitmentAnalytics['turnover']['pending_count'] }}
Turnover % (Approved): {{ $recruitmentAnalytics['turnover']['percentage'] !== null ? $recruitmentAnalytics['turnover']['percentage'] . '%' : 'N/A' }}
Passed Probation: {{ $recruitmentAnalytics['hiring_quality']['passed_probation'] ?? 'N/A' }}
Early Turnover (Approved): {{ $recruitmentAnalytics['hiring_quality']['early_turnover'] }}
@if ($recruitmentAnalytics['turnover']['reason_type_available'] && $recruitmentAnalytics['turnover']['reasons']->isNotEmpty())
Reasons of Resignation:
@foreach ($recruitmentAnalytics['turnover']['reasons'] as $reason)
{{ $reason['type'] }}: {{ $reason['count'] }}
@endforeach @elseif (!$recruitmentAnalytics['turnover']['reason_type_available'])
Reasons of Resignation:
Voluntary / Non-Voluntary classification not available yet.
@endif @forelse ($recruitmentAnalytics['turnover']['quarterly'] as $quarter)
Approved {{ $quarter['quarter'] }}: {{ $quarter['count'] }}
@empty
No approved resignation data.
@endforelse
@foreach ($recruitmentAnalytics['turnover']['departments'] as $department)
{{ $department['department'] }}: {{ $department['count'] }}
@endforeach
CUSTOM REPORTING
Unified Report Builder
Build a custom report by choosing a module, selecting the required columns, applying filters, and previewing the result before export.
@if ($analyticsFilter['range'] === 'custom') @endif
1. Select Module
Choose any available module to load its fields, filters, grouping, and export options.
@if ($activeModule && $moduleConfig)
@csrf @if ($analyticsFilter['range'] === 'custom') @endif
2. Select Fields
@foreach ($moduleConfig['fields'] as $key => $label)
@endforeach
3. Apply Filters
@foreach ($moduleConfig['filters'] as $field => $fConfig)
@if ($fConfig['type'] === 'select') @elseif($fConfig['type'] === 'select_static') @elseif($fConfig['type'] === 'date_range') @else @endif
@endforeach
4. Generate Or Export
@endif @if (isset($results))
Report Preview
{{ $results->count() }} records
@foreach ($selectedFields as $field) @endforeach @php $lastGroupValue = null; @endphp @forelse($results as $row) @php $currentGroupValue = $row; if ($groupBy) { foreach (explode('.', $groupBy) as $part) { $currentGroupValue = $currentGroupValue->$part ?? 'N/A'; if ($currentGroupValue === 'N/A') { break; } } } @endphp @if ($groupBy && $currentGroupValue !== $lastGroupValue) @php $lastGroupValue = $currentGroupValue; @endphp @endif @foreach ($selectedFields as $field) @endforeach @empty @endforelse
{{ $moduleConfig['fields'][$field] }}
{{ $moduleConfig['fields'][$groupBy] }}: {{ $currentGroupValue }}
@php $value = $row; $cellLink = null; foreach (explode('.', $field) as $part) { $value = $value->$part ?? 'N/A'; if ($value === 'N/A') { break; } } if ( $activeModule === 'staff' && $row instanceof \App\Models\Staff && in_array($field, ['first_name', 'last_name', 'full_name', 'staff_no'], true) ) { $cellLink = route('staffs.show', $row->id); } elseif ( $activeModule === 'applicant' && $row instanceof \App\Models\Applicant && in_array($field, ['full_name', 'email', 'contact_number'], true) ) { $cellLink = route('applicants.edit', $row->id); } elseif ($field === 'staff_id' && !empty($row->staff_id)) { $cellLink = route('staffs.show', $row->staff_id); } @endphp @if ($cellLink && $value !== 'N/A') {{ $value }} @else {{ $value }} @endif
No records found for the selected filters.
@endif
@endif
@endsection @push('styles') @endpush @push('scripts') @endpush