sls/resources/views/interview/index.blade.php

396 lines
26 KiB
PHP

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Candidate Live Assessment Portal | SingleLogin</title>
@vite(['resources/css/app.css', 'resources/js/app.js'])
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Instrument+Sans:wght@400;500;600;700&family=Outfit:wght@500;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css" crossorigin="anonymous" referrerpolicy="no-referrer" />
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
</head>
<body class="bg-[#0b0f19] text-white min-h-screen flex flex-col font-['Instrument_Sans',sans-serif]">
<!-- Header Navbar -->
<header class="h-[70px] bg-slate-900/80 backdrop-blur-md border-b border-slate-700/60 flex items-center justify-between px-6 sticky top-0 z-50">
<div class="flex items-center gap-3">
<div class="w-9 h-9 rounded-lg bg-gradient-to-br from-indigo-500 to-sky-600 flex items-center justify-center font-bold text-white shadow-md shadow-indigo-500/30">
<i class="fa-solid fa-bolt text-lg"></i>
</div>
<div>
<h1 class="font-outfit text-lg font-bold text-white m-0">Candidate Live Assessment Portal</h1>
<div class="text-xs text-slate-400">Proctored IDE & Code Submissions</div>
</div>
</div>
<div class="flex items-center gap-3">
@if(Auth::user()->isAdmin())
<a href="{{ route('admin.dashboard') }}">
<x-button variant="ghost" size="sm" icon="fa-solid fa-sliders" class="text-indigo-400 border-indigo-500/40">Control Panel</x-button>
</a>
@endif
<a href="{{ route('onboarding.index') }}">
<x-button variant="secondary" size="sm" icon="fa-solid fa-rocket">Onboarding Hub</x-button>
</a>
<a href="{{ route('dashboard') }}">
<x-button variant="secondary" size="sm" icon="fa-solid fa-user">User Portal</x-button>
</a>
<form action="{{ route('logout') }}" method="POST" id="logout-form" class="inline">
@csrf
<x-button type="button" onclick="interviewerLogoutAction()" variant="danger" size="sm" icon="fa-solid fa-right-from-bracket" class="bg-red-500/20 text-red-300 border-red-500/40 font-semibold">
Logout
</x-button>
</form>
</div>
</header>
<main class="w-full mx-auto my-6 px-7 flex-1">
@if(session('success'))
<div class="mb-6 bg-emerald-500/15 border border-emerald-500/30 text-emerald-300 px-5 py-3 rounded-xl text-sm">
{{ session('success') }}
</div>
@endif
<!-- Global Admin Settings & Proctoring Control Panel -->
<section class="mb-6 p-5 bg-linear-to-r from-slate-900/95 to-slate-800/95 border border-indigo-500/35 rounded-2xl shadow-xl">
<div class="flex justify-between items-center pb-3 mb-4 border-b border-white/10">
<div>
<h3 class="font-outfit text-xl font-bold text-white m-0 flex items-center gap-2">
<i class="fa-solid fa-gear text-indigo-400"></i> Global Admin Proctoring & System Settings
</h3>
<div class="text-xs text-slate-400 mt-1">Master control panel for candidate assessment rules, system screen capturing, and module toggles</div>
</div>
<x-pill variant="primary" size="md" icon="fa-solid fa-shield-halved">
ADMIN CONTROL PANEL
</x-pill>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<!-- 1. Candidate System Screenshot Capture Master Toggle -->
<div class="p-4 bg-emerald-500/10 border border-emerald-500/30 rounded-xl">
<form action="{{ route('admin.settings.screenshot-toggle') }}" method="POST">
@csrf
<div class="mb-3">
<div class="text-sm text-white font-bold flex items-center gap-2">
<i class="fa-solid fa-camera text-emerald-400"></i> Candidate System Screenshot Capture
</div>
<div class="text-xs text-slate-400 mt-1 leading-relaxed">
Captures candidate system desktop screen automatically on call start & tab switches. Admin can stop taking screenshots anytime across all candidates.
</div>
</div>
<div class="flex justify-between items-center pt-2.5 border-t border-white/10">
<span id="global-screenshot-status-text" class="text-xs font-bold {{ \App\Models\Setting::get('enable_candidate_screenshots', '1') === '1' ? 'text-emerald-400' : 'text-red-400' }}">
{{ \App\Models\Setting::get('enable_candidate_screenshots', '1') === '1' ? '🟢 ENABLED (Active)' : '🔴 STOPPED / DISABLED' }}
</span>
@if(Auth::user() && Auth::user()->isAdmin())
<label class="inline-flex items-center gap-2 cursor-pointer">
<input type="checkbox" name="enable_candidate_screenshots" value="1" onchange="this.form.submit()" {{ \App\Models\Setting::get('enable_candidate_screenshots', '1') === '1' ? 'checked' : '' }} class="accent-emerald-500 w-5 h-5 cursor-pointer">
<span class="text-xs font-bold text-white bg-white/10 px-3 py-1 rounded-md hover:bg-white/20 transition-colors">
{{ \App\Models\Setting::get('enable_candidate_screenshots', '1') === '1' ? 'Stop Screenshot Capture' : 'Enable Screenshot Capture' }}
</span>
</label>
@endif
</div>
</form>
</div>
<!-- 2. Onboarding & Offboarding Module Master Toggle -->
<div class="p-4 bg-indigo-500/10 border border-indigo-500/30 rounded-xl">
<form action="{{ route('admin.settings.onboarding-toggle') }}" method="POST">
@csrf
<div class="mb-3">
<div class="text-sm text-white font-bold flex items-center gap-2">
<i class="fa-solid fa-rocket text-indigo-400"></i> Candidate Provisioning & Onboarding Module
</div>
<div class="text-xs text-slate-400 mt-1 leading-relaxed">
Master toggle to enable/disable automated candidate provisioning, onboarding checklists, and 1-click revocation.
</div>
</div>
<div class="flex justify-between items-center pt-2.5 border-t border-white/10">
<span class="text-xs font-bold {{ \App\Models\Setting::get('onboarding_enabled', '1') === '1' ? 'text-emerald-400' : 'text-red-400' }}">
{{ \App\Models\Setting::get('onboarding_enabled', '1') === '1' ? '🟢 ENABLED' : '🔴 DISABLED' }}
</span>
@if(Auth::user() && Auth::user()->isAdmin())
<label class="inline-flex items-center gap-2 cursor-pointer">
<input type="checkbox" name="onboarding_enabled" value="1" onchange="this.form.submit()" {{ \App\Models\Setting::get('onboarding_enabled', '1') === '1' ? 'checked' : '' }} class="accent-emerald-500 w-5 h-5 cursor-pointer">
<span class="text-xs font-bold text-white bg-white/10 px-3 py-1 rounded-md hover:bg-white/20 transition-colors">
{{ \App\Models\Setting::get('onboarding_enabled', '1') === '1' ? 'Disable Onboarding' : 'Enable Onboarding' }}
</span>
</label>
@endif
</div>
</form>
</div>
</div>
</section>
<!-- Page Header Row -->
<div class="flex justify-between items-center mb-6">
<div>
<h2 class="font-outfit text-2xl font-bold text-white m-0">Live Interview Assessments</h2>
<div class="text-xs text-slate-400 mt-0.5">Temporary Candidate Access, Real-Time Proctored IDE, and Unique Code Records</div>
</div>
<x-button onclick="document.getElementById('create-interview-modal').classList.add('active')" variant="primary" size="md" icon="fa-solid fa-plus">
Schedule Candidate Interview
</x-button>
</div>
<!-- Assessment Table Card -->
<div class="bg-slate-900/80 border border-slate-700/60 rounded-2xl p-6 shadow-2xl backdrop-blur-md">
<div class="overflow-x-auto w-full">
<table class="w-full text-left text-xs border-collapse">
<thead>
<tr class="border-b border-slate-700/60 text-slate-400 uppercase text-[0.7rem] tracking-wider">
<th class="py-3 px-4">Candidate & Temp Pass</th>
<th class="py-3 px-4">Unique Submission ID</th>
<th class="py-3 px-4">Status & Validity</th>
<th class="py-3 px-4">Language</th>
<th class="py-3 px-4">Proctoring Alerts</th>
<th class="py-3 px-4 text-right">Review & Actions</th>
</tr>
</thead>
<tbody class="divide-y divide-white/5">
@forelse($interviews as $inv)
@php
$logs = $inv->proctor_logs ?? [];
$tabSwitches = count(array_filter($logs, fn($l) => ($l['type'] ?? '') === 'tab_switch'));
$focusLost = count(array_filter($logs, fn($l) => ($l['type'] ?? '') === 'focus_lost'));
$gazeAlerts = count(array_filter($logs, fn($l) => in_array($l['type'] ?? '', ['gaze_anomaly', 'gaze_fixed_staring'])));
$lowerGaze = count(array_filter($logs, fn($l) => ($l['type'] ?? '') === 'gaze_lower_device'));
$questionRepeat = count(array_filter($logs, fn($l) => in_array($l['type'] ?? '', ['question_repeat_lower', 'question_repetition'])));
$externalAi = count(array_filter($logs, fn($l) => ($l['type'] ?? '') === 'external_ai_detected'));
@endphp
<tr class="hover:bg-white/[0.02] transition-colors">
<td class="py-3.5 px-4">
<div>
<strong class="text-white text-sm block">{{ $inv->candidate_name }}</strong>
<div class="text-xs text-slate-400 mt-0.5">{{ $inv->candidate_email }} &bull; {{ $inv->candidate_phone }}</div>
<div class="mt-1.5 flex flex-col gap-1">
<div class="text-[0.7rem] text-emerald-300 bg-emerald-500/10 px-2 py-0.5 rounded inline-block w-fit">
<i class="fa-solid fa-key text-[0.65rem] mr-1"></i> Temp Pass: <strong>{{ $inv->temp_password }}</strong>
</div>
<div class="text-[0.7rem] text-sky-400">
<i class="fa-solid fa-globe text-[0.65rem] mr-1"></i> Login URL: <a href="{{ route('interview.candidate.login') }}" target="_blank" class="text-sky-400 underline">{{ route('interview.candidate.login') }}</a>
</div>
</div>
</div>
</td>
<td class="py-3.5 px-4">
<code class="bg-white/5 px-2 py-1 rounded text-sky-400 text-xs font-mono">
{{ $inv->submission_unique_id }}
</code>
</td>
<td class="py-3.5 px-4">
@if($inv->isExpired())
<x-pill variant="danger" size="sm">EXPIRED</x-pill>
@elseif($inv->status === 'completed')
<x-pill variant="success" size="sm">COMPLETED</x-pill>
@elseif($inv->call_status === 'active')
<x-pill variant="success" size="sm" icon="fa-solid fa-phone" class="animate-pulse font-extrabold">CALL IN PROGRESS</x-pill>
@elseif($inv->isActiveTimeline())
<x-pill variant="info" size="sm" icon="fa-solid fa-bolt">TIMELINE LIVE</x-pill>
@else
<x-pill variant="warning" size="sm">SCHEDULED</x-pill>
@endif
<div class="text-[0.7rem] text-slate-400 mt-1">
Timeline: {{ ($inv->scheduled_at ?? $inv->created_at)->format('M d, H:i') }} &ndash; {{ $inv->expires_at->format('H:i') }}
</div>
</td>
<td class="py-3.5 px-4">
<x-pill variant="ghost" size="sm" class="uppercase font-bold">
{{ $inv->language }}
</x-pill>
</td>
<td class="py-3.5 px-4">
<div class="flex flex-col gap-0.5 text-xs">
<span class="{{ $tabSwitches > 0 ? 'text-red-400 font-bold' : 'text-slate-400' }}">
<i class="fa-solid fa-circle text-[0.5rem] mr-1 text-red-500"></i> Tab Switches: {{ $tabSwitches }}
</span>
<span class="{{ $focusLost > 0 ? 'text-amber-400 font-semibold' : 'text-slate-400' }}">
<i class="fa-solid fa-circle text-[0.5rem] mr-1 text-amber-500"></i> Focus Loss / Overlay: {{ $focusLost }}
</span>
<span class="{{ $gazeAlerts > 0 ? 'text-rose-400 font-semibold' : 'text-slate-400' }}">
<i class="fa-solid fa-eye mr-1"></i> Eye Gaze Anomaly: {{ $gazeAlerts }}
</span>
@if($lowerGaze > 0)
<span class="text-red-400 font-bold">
<i class="fa-solid fa-mobile-screen mr-1"></i> Lower Gaze (>10s): {{ $lowerGaze }}
</span>
@endif
@if($questionRepeat > 0)
<span class="text-rose-400 font-bold">
<i class="fa-solid fa-comments mr-1"></i> Question Repetition: {{ $questionRepeat }}
</span>
@endif
@if($externalAi > 0)
<span class="text-red-400 font-extrabold bg-red-500/20 px-1.5 py-0.5 rounded">
<i class="fa-solid fa-robot mr-1"></i> Parakeet / External AI: {{ $externalAi }}
</span>
@endif
</div>
</td>
<!-- Review & Actions: EXACTLY 2 BUTTONS USING FONT-AWESOME ICONS -->
<td class="py-3.5 px-4 text-right whitespace-nowrap">
<div class="inline-flex items-center gap-2 justify-end">
<!-- Button 1: Report Page -->
<a href="{{ route('interview.report', $inv->id) }}" target="_blank">
<x-button variant="success" size="sm" icon="fa-solid fa-file-pdf" class="bg-emerald-500/15 border-emerald-500/30 text-emerald-300 hover:bg-emerald-500/25">
PDF Report
</x-button>
</a>
<!-- Button 2: Join Call -->
@if($inv->call_status === 'active')
<a href="{{ route('interview.show', $inv->id) }}?join_call=1">
<x-button variant="success" size="sm" icon="fa-solid fa-phone-volume" class="animate-pulse font-extrabold shadow-md shadow-emerald-500/50">
Join Active Call
</x-button>
</a>
@else
<a href="{{ route('interview.show', $inv->id) }}">
<x-button variant="primary" size="sm" icon="fa-solid fa-video" class="bg-indigo-500/15 border-indigo-500/30 text-indigo-300 hover:bg-indigo-500/25">
Join Call
</x-button>
</a>
@endif
</div>
</td>
</tr>
@empty
<tr>
<td colspan="6" class="text-center text-slate-400 py-8">
No candidate interview sessions found. Click "+ Schedule Candidate Interview" above to create one.
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
</div>
</main>
<!-- Modal: Schedule Candidate Interview -->
<x-modal id="create-interview-modal" title="Schedule Candidate Assessment" maxWidth="lg">
<form action="{{ route('interview.store') }}" method="POST">
@csrf
<div class="grid grid-cols-1 md:grid-cols-2 gap-3.5 mb-3.5">
<div>
<label class="block text-xs font-medium text-slate-400 mb-1">Candidate Name <span class="text-red-500">*</span></label>
<input type="text" name="candidate_name" required placeholder="e.g. John Doe" class="w-full px-3 py-2 bg-white/5 border border-slate-700/60 rounded-lg text-white text-xs outline-none focus:border-indigo-500">
</div>
<div>
<label class="block text-xs font-medium text-slate-400 mb-1">Candidate Email <span class="text-red-500">*</span></label>
<input type="email" name="candidate_email" required placeholder="john@gmail.com" class="w-full px-3 py-2 bg-white/5 border border-slate-700/60 rounded-lg text-white text-xs outline-none focus:border-indigo-500">
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-3 gap-3 mb-3.5">
<div>
<label class="block text-xs font-medium text-slate-400 mb-1">Phone Number <span class="text-red-500">*</span></label>
<input type="text" name="candidate_phone" required placeholder="9876543210" class="w-full px-3 py-2 bg-white/5 border border-slate-700/60 rounded-lg text-white text-xs outline-none focus:border-indigo-500">
</div>
<div>
<label class="block text-xs font-medium text-slate-400 mb-1">Start Time (Timeline)</label>
<input type="datetime-local" name="scheduled_at" value="{{ now()->format('Y-m-d\TH:i') }}" class="w-full px-3 py-2 bg-slate-900 border border-slate-700/60 rounded-lg text-white text-xs outline-none focus:border-indigo-500">
</div>
<div>
<label class="block text-xs font-medium text-slate-400 mb-1">Access Duration <span class="text-red-500">*</span></label>
<select name="valid_hours" required class="w-full px-3 py-2 bg-slate-900 border border-slate-700/60 rounded-lg text-white text-xs outline-none focus:border-indigo-500">
<option value="1">1 Hour</option>
<option value="2" selected>2 Hours</option>
<option value="4">4 Hours</option>
<option value="12">12 Hours</option>
<option value="24">24 Hours</option>
</select>
</div>
</div>
<div class="mb-3.5">
<label class="block text-xs font-medium text-slate-400 mb-1">Coding Language <span class="text-red-500">*</span></label>
<select name="language" required class="w-full px-3 py-2 bg-slate-900 border border-slate-700/60 rounded-lg text-white text-xs outline-none focus:border-indigo-500">
<option value="python">Python 3</option>
<option value="cpp">C++ (GCC)</option>
<option value="c">C (GCC)</option>
<option value="java">Java 15</option>
<option value="php">PHP 8.2 / Laravel</option>
<option value="javascript">JavaScript (Node.js)</option>
</select>
</div>
<div class="mb-4">
<div class="flex justify-between items-center mb-1.5">
<label class="text-xs font-medium text-slate-400">Assign Internal Interviewers / Panelists</label>
</div>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-2 max-h-[120px] overflow-y-auto bg-black/30 p-2.5 rounded-lg border border-slate-700/60">
@foreach($interviewers as $usr)
<label class="flex items-center gap-2 text-xs text-white cursor-pointer hover:text-indigo-300">
<input type="checkbox" name="assigned_interviewers[]" value="{{ $usr->id }}" class="accent-indigo-500">
{{ $usr->name }} ({{ $usr->email }})
</label>
@endforeach
</div>
</div>
<div class="flex justify-end gap-2.5 mt-5">
<x-button type="button" onclick="document.getElementById('create-interview-modal').classList.remove('active')" variant="secondary">
Cancel
</x-button>
<x-button type="submit" variant="primary" icon="fa-solid fa-check">
Create Session & Password
</x-button>
</div>
</form>
</x-modal>
<!-- Incoming Call Ring Modal -->
<x-modal id="incoming-call-modal" maxWidth="md">
<div class="p-6 text-center">
<div class="relative w-20 h-20 mx-auto mb-5 flex items-center justify-center">
<div class="absolute -inset-3 rounded-full border-2 border-emerald-500/60 animate-ping"></div>
<div class="absolute -inset-6 rounded-full border-2 border-indigo-500/40 animate-pulse"></div>
<div class="w-16 h-16 rounded-full bg-gradient-to-br from-emerald-500 to-sky-600 flex items-center justify-center text-2xl font-bold text-white shadow-lg shadow-emerald-500/50 z-10">
<i class="fa-solid fa-phone"></i>
</div>
</div>
<div class="text-[11px] uppercase tracking-widest text-emerald-400 font-bold mb-1.5">
Incoming Assessment Call
</div>
<h3 id="incoming-cand-name" class="font-outfit text-2xl font-extrabold text-white mb-1">
Candidate Name
</h3>
<div id="incoming-starter-info" class="text-xs text-[#4b82f7] mb-4">
Initiated by Panelist
</div>
<div class="bg-white/5 border border-white/10 rounded-xl p-3 text-xs text-slate-300 mb-6 leading-relaxed">
Another interviewer has started calling the candidate. Click <strong>Accept Call</strong> to join live, or <strong>Decline</strong> if busy.
</div>
<div class="flex gap-3 justify-center">
<x-button id="incoming-decline-btn" onclick="declineIncomingCall()" variant="danger" size="md" icon="fa-solid fa-xmark" class="flex-1">
Decline / Miss
</x-button>
<x-button id="incoming-accept-btn" onclick="acceptIncomingCall()" variant="success" size="md" icon="fa-solid fa-phone" class="flex-[1.3] font-extrabold animate-pulse">
Accept Call
</x-button>
</div>
</div>
</x-modal>
<script>
window.MAX_INTERVIEWERS = {{ config('interview.max_interviewer', 4) }};
window.currentUserId = {{ Auth::id() }};
window.currentUserName = "{{ addslashes(Auth::user()->name) }}";
window.currentUserRole = "{{ Auth::user()->is_admin ? 'admin' : 'interviewer' }}";
window.csrfToken = "{{ csrf_token() }}";
</script>
</body>
</html>