From 5b61495e354c7edb1670cc31292aee4689fadd55 Mon Sep 17 00:00:00 2001 From: subhajit Date: Tue, 21 Jul 2026 16:49:58 +0530 Subject: [PATCH] testing mode going on --- app/Http/Controllers/InterviewController.php | 100 ++ app/Models/Interview.php | 1 + ...00_add_call_status_to_interviews_table.php | 28 + resources/views/auth/login.blade.php | 6 - resources/views/dashboard.blade.php | 2 +- .../views/interview/candidate_room.blade.php | 285 ++++- resources/views/interview/index.blade.php | 1067 +++++++++++------ resources/views/interview/report.blade.php | 406 +++++++ routes/web.php | 3 + .../Feature/CandidateInterviewPortalTest.php | 33 + 10 files changed, 1526 insertions(+), 405 deletions(-) create mode 100644 database/migrations/2026_07_21_160000_add_call_status_to_interviews_table.php create mode 100644 resources/views/interview/report.blade.php 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 - -
OR
- - - ⚡ Candidate Assessment Login - diff --git a/resources/views/dashboard.blade.php b/resources/views/dashboard.blade.php index 35ae6aa..80613fc 100644 --- a/resources/views/dashboard.blade.php +++ b/resources/views/dashboard.blade.php @@ -874,7 +874,7 @@ @if($user->hasActiveInterviewZone()) - + ⚡ Interview Zone LIVE @endif diff --git a/resources/views/interview/candidate_room.blade.php b/resources/views/interview/candidate_room.blade.php index adceb0a..15e4a3b 100644 --- a/resources/views/interview/candidate_room.blade.php +++ b/resources/views/interview/candidate_room.blade.php @@ -10,9 +10,10 @@ - + + + @@ -243,53 +284,31 @@ @endphp -
- {{ $inv->candidate_name }} -
{{ $inv->candidate_email }} • {{ $inv->candidate_phone }}
-
-
- 🔑 Temp Pass: {{ $inv->temp_password }} -
-
- 🌐 Login URL: {{ route('interview.candidate.login') }} -
-
-
+
{{ $inv->candidate_name }}
+
{{ $inv->candidate_email }}
+
Pass: {{ $inv->temp_password }}
- + {{ $inv->submission_unique_id }} - - - - @if($inv->isExpired()) - - EXPIRED - - @elseif($inv->status === 'completed') - - COMPLETED - - @elseif($inv->isActiveTimeline()) - - ⚡ TIMELINE LIVE - - @else - - SCHEDULED - - @endif -
- Timeline: {{ ($inv->scheduled_at ?? $inv->created_at)->format('M d, H:i') }} – {{ $inv->expires_at->format('H:i') }} -
- - - - {{ $inv->language }} -
+ @if($inv->isExpired()) + EXPIRED + @elseif($inv->status === 'submitted') + SUBMITTED + @else + ACTIVE ({{ $inv->expires_at->diffForHumans() }}) + @endif + + + + {{ strtoupper($inv->language) }} + + + +
🔴 Tab Switches: {{ $tabSwitches }} @@ -302,6 +321,10 @@
+ + 📄 Report + + @@ -314,7 +337,7 @@ @empty - No candidate interview sessions found. Click "+ Schedule Candidate Interview" above to create one. + No candidate interviews scheduled yet. @endforelse @@ -324,51 +347,29 @@
- +
-
+
-

Schedule Candidate Assessment

+

Schedule Candidate Assessment

@csrf -
-
- - -
-
- - -
+
+ +
-
-
- - -
-
- - -
-
- - -
+
+ +
-
- - @@ -378,21 +379,14 @@
-
- -
- @foreach($interviewers as $usr) - - @endforeach -
+
+ +
-
+
- +
@@ -408,6 +402,16 @@
+ + + + + + 📄 Generate PDF Report + + @@ -438,7 +442,13 @@
+ + + + +
+ +
Candidate Camera
-
Click 'Start 2-Way Call' to connect
+
Click 'Start / Join Call' to connect
- 👤 Candidate Video + 👤 Candidate Video (100%)
@@ -510,43 +526,42 @@
Panelist (Self)
- 🎙️ Panelist Self + 🎤 Panelist Self
- -
-
-
- 💻 - Candidate Live IDE & Workspace Inspector - PYTHON - ● Live Synced + +
+
+
+ 💻 + Candidate Live IDE & Workspace Inspector + UNKNOWN + • Live Synced
-
- - - + + +
-
- +
+
- -
-
Current Code Solution (Visible even if candidate does not share screen):
- + +
+
Current Code Typed by Candidate:
+ -
Execution Stdout / Stderr Output:
- +
Execution Output (Stdout/Stderr):
+
- +
- +
Proctoring Audit: -
-
+
-
-
-
-
🚨
-
-

SOS CHEATING DETECTED

-
Proctoring engine flagged candidate cheating activity!
-
+
+
+
+
🚨
+

SUSPICIOUS CHEATING DETECTED

+
Candidate proctoring engine has flagged unusual activities.
-
- Violation details loading... +
+
Flagged Integrity Violations:
+
-
-
- - diff --git a/resources/views/interview/report.blade.php b/resources/views/interview/report.blade.php new file mode 100644 index 0000000..518ded8 --- /dev/null +++ b/resources/views/interview/report.blade.php @@ -0,0 +1,406 @@ + + + + + + Executive Evaluation Report - {{ $interview->candidate_name }} | SingleLogin + + + + + + + + + + +
+ +
+
+
+ ⚡ SingleLogin Enterprise Assessment +
+
+ Candidate Technical Competency & Behavioral Audit Report +
+
+ +
+ Executive Summary +
+ Report ID: RPT-{{ $interview->submission_unique_id }} +
+
+ Date: {{ now()->format('M d, Y - H:i:s T') }} +
+
+
+ + +
+
+ + {{ $interview->candidate_name }} +
+
+ + {{ $interview->candidate_email }} +
+
+ + {{ strtoupper($interview->language) }} +
+
+ + + {{ strtoupper($interview->status) }} + +
+
+ + +
+ +
+
+ 🛡️ Behavioral Integrity Rating +
+
+ {{ $integrityScore }}% +
+
+ {{ $integrityLabel }} +
+
+ Recorded Violations: {{ count($logs) }} incident(s) +
+
+ + +
+
+ 💻 Technical Code Competency +
+
+ {{ $codeScore }} / 100 +
+
+ {{ $codeScore >= 80 ? 'Strong Technical Match' : ($codeScore >= 50 ? 'Moderate Competency' : 'Needs Further Review') }} +
+
+ Solution Code: {{ !empty($interview->submitted_code) ? 'Submitted' : 'Pending' }} +
+
+
+ + +
+ 🔍 Behavioral Audit & Proctoring Incident Breakdown +
+
+
+
{{ $tabSwitches }}
+
Tab Switches
+
+
+
{{ $focusLosses }}
+
Focus Lost
+
+
+
{{ $gazeAnomalies }}
+
Gaze Anomalies
+
+
+
{{ $pasteEvents }}
+
Code Pastes
+
+
+ + @if(count($logs) > 0) + + + + + + + + + + @foreach($logs as $log) + + + + + + @endforeach + +
TimestampViolation CategoryAudit Details
{{ \Carbon\Carbon::parse($log['timestamp'])->format('H:i:s T') }}{{ strtoupper($log['type']) }}{{ $log['details'] ?? 'Incident flagged by proctoring engine.' }}
+ @else +
+ ✅ Zero proctoring violations or malpractice detected during the session. +
+ @endif + + +
+ 💻 Submitted Code Solution ({{ strtoupper($interview->submitted_language ?? $interview->language) }}) +
+
+{{ $interview->submitted_code ?? '// No code solution submitted by candidate.' }} +
+ + +
+ 🖥️ Engine Execution Stdout Output +
+
+{{ $interview->code_output ?? 'No execution output recorded.' }} +
+ + + @if(!empty($interview->candidate_notes) || !empty($interview->candidate_drawing)) +
+ 📝 Workspace Artifacts (Scratchpad Notes & Architecture Drawing) +
+
+ @if(!empty($interview->candidate_notes)) +
CANDIDATE NOTES:
+
{{ $interview->candidate_notes }}
+ @endif + + @if(!empty($interview->candidate_drawing)) +
CANDIDATE DRAWING DIAGRAM:
+ Candidate Drawing + @endif +
+ @endif + + +
+
+
+ HR & Lead Panel Approval +
+
+ SingleLogin Enterprise HR Board +
+
+ +
+
+ Final Recommendation +
+
+ {{ $integrityScore >= 80 && $codeScore >= 50 ? 'RECOMMENDED FOR HIRE' : 'REQUIRES FURTHER REVIEW / FLAGGED' }} +
+
+
+
+ + + diff --git a/routes/web.php b/routes/web.php index 94d25cc..b9e6cef 100644 --- a/routes/web.php +++ b/routes/web.php @@ -35,6 +35,7 @@ Route::post('/candidate/sync-code/{id}', [InterviewController::class, 'syncCandidateCode'])->name('interview.candidate.sync-code'); Route::post('/candidate/notes/{id}', [InterviewController::class, 'saveCandidateNotes'])->name('interview.candidate.notes'); Route::post('/candidate/drawing/{id}', [InterviewController::class, 'saveCandidateDrawing'])->name('interview.candidate.drawing'); +Route::post('/candidate/send-message/{id}', [InterviewController::class, 'candidateSendMessage'])->name('interview.candidate.send-message'); // User Dashboard Portal (requires authenticated user) Route::middleware(['auth', 'role:user', 'verify-ms-session'])->group(function () { @@ -67,6 +68,8 @@ Route::get('/interviews/{id}/poll', [InterviewController::class, 'getPollData'])->name('interview.poll'); Route::post('/interviews/{id}/regenerate-password', [InterviewController::class, 'regeneratePassword'])->name('interview.regenerate-password'); Route::post('/interviews/{id}/block-candidate', [InterviewController::class, 'blockCandidate'])->name('interview.block-candidate'); + Route::get('/interviews/{id}/report', [InterviewController::class, 'generateReport'])->name('interview.report'); + Route::post('/interviews/{id}/call-status', [InterviewController::class, 'updateCallStatus'])->name('interview.call-status'); }); // Admin Control Panel (requires admin role) diff --git a/tests/Feature/CandidateInterviewPortalTest.php b/tests/Feature/CandidateInterviewPortalTest.php index 3abfb0a..7ad9b61 100644 --- a/tests/Feature/CandidateInterviewPortalTest.php +++ b/tests/Feature/CandidateInterviewPortalTest.php @@ -265,4 +265,37 @@ public function test_candidate_notes_drawing_saving_and_interviewer_password_reg 'temp_password' => $interview->temp_password, ])->assertSessionHas('error'); } + + public function test_executive_candidate_evaluation_pdf_report_generation(): void + { + $hr = User::create([ + 'name' => 'Report Viewer HR', + 'email' => 'hrreport@company.com', + 'role' => 'admin', + ]); + + $interview = Interview::create([ + 'candidate_name' => 'Rachel Candidate', + 'candidate_email' => 'rachel@gmail.com', + 'candidate_phone' => '9666655555', + 'temp_password' => 'Pass-666555', + 'expires_at' => now()->addHours(2), + 'language' => 'python', + 'status' => 'completed', + 'submission_unique_id' => 'rachel-candidate_9666655555_1752000005', + 'submitted_code' => 'def solve(): return 42', + 'code_output' => 'Result: 42', + 'candidate_notes' => 'Complexity analysis: O(1)', + ]); + + $response = $this->actingAs($hr)->get(route('interview.report', $interview->id)); + + $response->assertStatus(200) + ->assertSee('Rachel Candidate') + ->assertSee('rachel@gmail.com') + ->assertSee('Executive Summary') + ->assertSee('Behavioral Integrity Rating') + ->assertSee('Technical Code Competency') + ->assertSee('Complexity analysis: O(1)'); + } }