diff --git a/app/Http/Controllers/InterviewController.php b/app/Http/Controllers/InterviewController.php index 7c68e63..335bb50 100644 --- a/app/Http/Controllers/InterviewController.php +++ b/app/Http/Controllers/InterviewController.php @@ -309,6 +309,23 @@ public function sendWarning(Request $request, $id) return response()->json(['success' => true, 'warning' => $warning]); } + /** + * Send Real-Time Chat Message from Candidate to Interviewer/HR. + */ + public function candidateSendMessage(Request $request, $id) + { + $interview = Interview::findOrFail($id); + $request->validate(['message' => 'required|string|max:500']); + + $warning = InterviewWarning::create([ + 'interview_id' => $interview->id, + 'message' => $request->message, + 'sent_by' => null, // null sent_by indicates candidate sender + ]); + + return response()->json(['success' => true, 'warning' => $warning]); + } + /** * Live Sync Candidate Code (before submission). */ @@ -385,6 +402,7 @@ public function getPollData($id) return response()->json([ 'status' => $interview->status, + 'call_status' => $interview->call_status ?? 'idle', 'submitted_code' => $interview->submitted_code, 'submitted_language' => $interview->submitted_language, 'code_output' => $interview->code_output, @@ -398,6 +416,17 @@ public function getPollData($id) ]); } + /** + * Update Interview Call Session Status (active, idle, ended). + */ + public function updateCallStatus(Request $request, $id) + { + $interview = Interview::findOrFail($id); + $status = $request->input('call_status', 'idle'); + $interview->update(['call_status' => $status]); + return response()->json(['success' => true, 'call_status' => $status]); + } + /** * Save Candidate Silent Video Recording Blob / Snapshot Stream. */ @@ -416,4 +445,75 @@ public function uploadRecording(Request $request, $id) return response()->json(['success' => false, 'message' => 'No video payload received']); } + + /** + * Generate Comprehensive Executive Candidate Evaluation & Behavioral Report (PDF View). + */ + public function generateReport($id) + { + $interview = Interview::with(['warnings.sender'])->findOrFail($id); + + $logs = $interview->proctor_logs ?? []; + $tabSwitches = 0; + $focusLosses = 0; + $gazeAnomalies = 0; + $pasteEvents = 0; + + foreach ($logs as $l) { + $type = $l['type'] ?? ''; + if ($type === 'tab_switch') $tabSwitches++; + if ($type === 'focus_lost') $focusLosses++; + if ($type === 'gaze_anomaly') $gazeAnomalies++; + if ($type === 'paste_event') $pasteEvents++; + } + + $totalViolations = count($logs); + + // Calculate Integrity Score % + if ($totalViolations === 0) { + $integrityScore = 100; + $integrityLabel = 'Exceptional Integrity'; + $integrityColor = '#10b981'; + } elseif ($totalViolations <= 2) { + $integrityScore = 85; + $integrityLabel = 'Low Behavioral Risk'; + $integrityColor = '#06b6d4'; + } elseif ($totalViolations <= 5) { + $integrityScore = 60; + $integrityLabel = 'Moderate Integrity Concern'; + $integrityColor = '#f59e0b'; + } else { + $integrityScore = 25; + $integrityLabel = 'High Malpractice Risk'; + $integrityColor = '#ef4444'; + } + + // Calculate Technical Code Score + $codeScore = 0; + if (!empty($interview->submitted_code)) { + $codeScore += 50; + if (!empty($interview->code_output) && !str_contains(strtolower($interview->code_output), 'error')) { + $codeScore += 40; + } else { + $codeScore += 20; + } + if (!empty($interview->candidate_notes) || !empty($interview->candidate_drawing)) { + $codeScore += 10; + } + } + $codeScore = min(100, $codeScore); + + return view('interview.report', compact( + 'interview', + 'logs', + 'tabSwitches', + 'focusLosses', + 'gazeAnomalies', + 'pasteEvents', + 'integrityScore', + 'integrityLabel', + 'integrityColor', + 'codeScore' + )); + } } diff --git a/app/Models/Interview.php b/app/Models/Interview.php index 3df16d8..1fc5d82 100644 --- a/app/Models/Interview.php +++ b/app/Models/Interview.php @@ -29,6 +29,7 @@ class Interview extends Model 'candidate_drawing', 'blocked_ip', 'created_by', + 'call_status', ]; protected $casts = [ diff --git a/database/migrations/2026_07_21_160000_add_call_status_to_interviews_table.php b/database/migrations/2026_07_21_160000_add_call_status_to_interviews_table.php new file mode 100644 index 0000000..feb637a --- /dev/null +++ b/database/migrations/2026_07_21_160000_add_call_status_to_interviews_table.php @@ -0,0 +1,28 @@ +string('call_status')->default('idle')->after('status'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('interviews', function (Blueprint $table) { + $table->dropColumn('call_status'); + }); + } +}; diff --git a/resources/views/auth/login.blade.php b/resources/views/auth/login.blade.php index 1920b62..477ea77 100644 --- a/resources/views/auth/login.blade.php +++ b/resources/views/auth/login.blade.php @@ -388,12 +388,6 @@ Sign in with Microsoft - -
{{ $inv->temp_password }}
+
{{ $inv->submission_unique_id }}
-
-