- integrate spatie/icalendar-generator for candidate & panelist .ics invitations (RSVP & 15m alerts)
- add StoreInterviewRequest, ScheduleInterviewDto, and invitation/reminder queued actions
- add migration for job_title, round, description, and reminder_sent_at on interviews table
- style datetime-local picker indicators to pure white on dark input backgrounds
- support button loading spinners with :showLoader="true" and loadingText
- auto-open schedule drawer on validation failure with inline @error alerts and old() bindings
131 lines
6.1 KiB
PHP
131 lines
6.1 KiB
PHP
@props([
|
|
'interview',
|
|
])
|
|
|
|
@php
|
|
$metrics = $interview->proctor_metrics;
|
|
if ($interview->isExpired() && $interview->call_status === 'active') {
|
|
$interview->update(['call_status' => 'ended', 'active_peers' => []]);
|
|
$interview->call_status = 'ended';
|
|
}
|
|
|
|
$loginUrl = route('interview.candidate.login');
|
|
$credentialsText = implode("\n", array_filter([
|
|
"Candidate Assessment Credentials",
|
|
"--------------------------------",
|
|
"Candidate: " . $interview->candidate_name . (!empty($interview->round) ? " ({$interview->round})" : ""),
|
|
!empty($interview->job_title) ? "Position: {$interview->job_title}" : null,
|
|
"Email: {$interview->candidate_email}",
|
|
"Phone: {$interview->candidate_phone}",
|
|
"Login URL: {$loginUrl}",
|
|
"Temp Password: {$interview->temp_password}",
|
|
"Timeline: {$interview->formatted_timeline}",
|
|
]));
|
|
@endphp
|
|
|
|
<tr class="hover:bg-white/[0.02] transition-colors">
|
|
<!-- Candidate Info & Temp Pass -->
|
|
<td class="py-3.5 px-4">
|
|
<div>
|
|
<div class="flex items-start gap-2">
|
|
<div class="min-w-0">
|
|
<div class="flex items-center gap-2">
|
|
<strong class="text-white text-sm block truncate">{{ $interview->candidate_name }}</strong>
|
|
@if(!empty($interview->round))
|
|
<span class="bg-indigo-500/20 text-indigo-300 text-[10px] font-semibold px-1.5 py-0.5 rounded border border-indigo-500/30 font-mono shrink-0">{{ $interview->round }}</span>
|
|
@endif
|
|
</div>
|
|
@if(!empty($interview->job_title))
|
|
<div class="text-[11px] text-slate-300 font-medium mt-0.5 truncate">{{ $interview->job_title }}</div>
|
|
@endif
|
|
</div>
|
|
|
|
<!-- Copy Credentials Button -->
|
|
<button
|
|
type="button"
|
|
onclick="copyInterviewCredentials(this)"
|
|
data-credentials="{{ $credentialsText }}"
|
|
title="Copy candidate & meeting credentials"
|
|
class="copy-cred-btn inline-flex items-center gap-1.5 px-2 py-1 text-[11px] font-medium rounded-md bg-white/5 border border-slate-700/60 text-slate-300 hover:text-white hover:bg-indigo-500/20 hover:border-indigo-500/40 transition-all cursor-pointer shrink-0 group select-none"
|
|
>
|
|
<i class="fa-regular fa-copy text-[11px] group-hover:scale-110 transition-transform"></i>
|
|
</button>
|
|
</div>
|
|
|
|
<div class="text-xs text-slate-400 mt-1">{{ $interview->candidate_email }} • {{ $interview->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>{{ $interview->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>
|
|
|
|
<!-- Unique Submission ID -->
|
|
<td class="py-3.5 px-4">
|
|
<code class="bg-white/5 px-2 py-1 rounded text-sky-400 text-xs font-mono">
|
|
{{ $interview->submission_unique_id }}
|
|
</code>
|
|
</td>
|
|
|
|
<!-- Status & Validity -->
|
|
<td class="py-3.5 px-4">
|
|
@if($interview->isExpired())
|
|
<x-pill variant="danger" size="sm">EXPIRED</x-pill>
|
|
@elseif($interview->status === 'completed')
|
|
<x-pill variant="success" size="sm">COMPLETED</x-pill>
|
|
@elseif(!$interview->isExpired() && $interview->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($interview->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: {{ $interview->formatted_timeline }}
|
|
</div>
|
|
</td>
|
|
|
|
<!-- Language -->
|
|
<td class="py-3.5 px-4">
|
|
<x-pill variant="ghost" size="sm" class="uppercase font-bold">
|
|
{{ $interview->language }}
|
|
</x-pill>
|
|
</td>
|
|
|
|
<!-- Proctoring Alerts -->
|
|
<td class="py-3.5 px-4">
|
|
<x-interview.proctor-badges :metrics="$metrics" />
|
|
</td>
|
|
|
|
<!-- Review & Actions -->
|
|
<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', $interview->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(!$interview->isExpired() && $interview->call_status === 'active')
|
|
<a href="{{ route('interview.show', $interview->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', $interview->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>
|