feat: UI Refactor, multi-people recording and noise cancellation #21

Merged
subhajit.pal merged 9 commits from feature/noise-cancellation into main 2026-08-21 11:33:03 +00:00
4 changed files with 107 additions and 70 deletions
Showing only changes of commit 834813731b - Show all commits

View File

@ -4,18 +4,22 @@
]) ])
<div {{ $attributes->merge(['class' => 'bg-[#121826] border border-[#1b2233] rounded-[12px] mb-4 overflow-hidden']) }}> <div {{ $attributes->merge(['class' => 'bg-[#121826] border border-[#1b2233] rounded-[12px] mb-4 overflow-hidden']) }}>
@if($title || $icon || isset($headerExtra)) @if($title || $icon || isset($header) || isset($headerExtra))
<div class="flex items-center justify-between px-5 py-3.5 border-b border-[#1b2233]"> <div class="flex items-center justify-between px-5 py-3 border-b border-[#1b2233] gap-4 flex-wrap">
<div class="flex items-center gap-2.5 font-semibold text-[14.5px] tracking-tight text-white"> <div class="flex items-center gap-2.5 font-semibold text-[14.5px] tracking-tight text-white min-w-0">
@if($icon) @if(isset($header))
<i class="{{ $icon }} text-[#4b82f7] text-[13.5px]"></i> {{ $header }}
@endif @else
@if($title) @if($icon)
<span>{{ $title }}</span> <i class="{{ $icon }} text-[#4b82f7] text-[13.5px]"></i>
@endif
@if($title)
<span>{{ $title }}</span>
@endif
@endif @endif
</div> </div>
@if(isset($headerExtra)) @if(isset($headerExtra))
<div class="flex items-center gap-2"> <div class="flex items-center gap-2 shrink-0">
{{ $headerExtra }} {{ $headerExtra }}
</div> </div>
@endif @endif

View File

@ -0,0 +1,25 @@
@props([
'align' => 'left', // left, right
'width' => 'w-80 sm:w-96',
])
@php
$alignmentClasses = $align === 'right' ? 'right-0 origin-top-right' : 'left-0 origin-top-left';
@endphp
<details class="group relative inline-block text-left select-none">
<summary class="list-none cursor-pointer p-1.5 rounded-lg bg-slate-800/80 border border-slate-700/60 text-slate-300 hover:text-white hover:bg-slate-700/60 transition-colors flex items-center justify-center w-8 h-8 focus:outline-none focus:ring-1 focus:ring-indigo-500">
{{ $trigger ?? '' }}
@if(!isset($trigger))
<i class="fa-solid fa-bars text-sm"></i>
@endif
</summary>
<!-- Backdrop for click-outside dismissal -->
<div class="fixed inset-0 z-40" onclick="this.closest('details').removeAttribute('open')"></div>
<!-- Dropdown Content Panel -->
<div class="absolute {{ $alignmentClasses }} mt-2 {{ $width }} bg-slate-900 border border-slate-700/80 rounded-xl shadow-2xl p-3.5 z-50 backdrop-blur-xl text-white divide-y divide-white/10 font-normal">
{{ $slot }}
</div>
</details>

View File

@ -2,74 +2,82 @@
'interview', 'interview',
]) ])
<div class="bg-slate-900/80 border border-slate-700/60 rounded-xl mb-4 overflow-hidden backdrop-blur-md"> <div class="flex items-center gap-3 min-w-0 flex-wrap sm:flex-nowrap">
<div class="flex items-center justify-between p-5 gap-4 flex-wrap"> <x-hamburger-menu align="left">
<div class="flex items-center gap-3.5"> <!-- Section 1: Session Details 4-Grid -->
<div class="w-[42px] h-[42px] rounded-full bg-gradient-to-br from-indigo-500 to-sky-600 flex items-center justify-center text-white font-semibold text-[15px] shrink-0 shadow-md shadow-indigo-500/20"> <div class="pb-3">
{{ $interview->candidate_initials }} <div class="text-[11px] font-bold text-slate-400 uppercase tracking-wider mb-2 flex items-center gap-1.5">
<i class="fa-solid fa-circle-info text-indigo-400"></i> Session Details
</div> </div>
<div> <div class="grid grid-cols-2 gap-2">
<div class="flex items-center gap-2 mb-0.5"> <div class="bg-slate-950/60 border border-slate-800 rounded-lg p-2">
<h2 id="modal-cand-name" class="text-[15px] font-semibold text-white m-0 leading-none">{{ $interview->candidate_name }}</h2> <div class="text-[10px] text-slate-500 uppercase tracking-wider font-semibold">Submission ID</div>
<span id="candidate-joined-pill" class="pill gray inline-flex items-center gap-1.5 font-semibold text-[11px] px-2.5 py-0.5 rounded-full border border-slate-700 text-slate-400 bg-slate-800/60"> <div id="modal-cand-uid" class="text-[11px] font-mono text-slate-300 break-all">{{ $interview->submission_unique_id }}</div>
<i class="fa-solid fa-circle text-[8px] text-slate-500"></i> Not Joined </div>
</span> <div class="bg-slate-950/60 border border-slate-800 rounded-lg p-2">
<div class="text-[10px] text-slate-500 uppercase tracking-wider font-semibold">Temp Pass</div>
<div class="text-[11px] font-mono text-emerald-400 font-bold">{{ $interview->temp_password }}</div>
</div>
<div class="bg-slate-950/60 border border-slate-800 rounded-lg p-2">
<div class="text-[10px] text-slate-500 uppercase tracking-wider font-semibold">Language</div>
<div class="text-[11px] font-mono text-slate-300 uppercase font-bold">{{ $interview->language }}</div>
</div>
<div class="bg-slate-950/60 border border-slate-800 rounded-lg p-2">
<div class="text-[10px] text-slate-500 uppercase tracking-wider font-semibold">Timeline</div>
<div class="text-[11px] font-mono text-slate-300">{{ $interview->formatted_timeline }}</div>
</div> </div>
<div class="text-xs text-slate-400 font-mono">{{ $interview->candidate_email }} &middot; {{ $interview->candidate_phone }}</div>
</div> </div>
</div> </div>
<div class="flex items-center gap-1.5 flex-wrap"> <!-- Section 2: SOS Auto-Trigger & Other Actions -->
@if($interview->isExpired()) <div class="pt-3 flex flex-col gap-2.5">
<x-pill id="timeline-status-pill" variant="danger" icon="fa-solid fa-circle">Expired</x-pill> <div class="flex items-center justify-between px-1 text-xs text-slate-300 cursor-pointer select-none" onclick="toggleSosAutoTrigger()">
@elseif($interview->status === 'completed') <span class="flex items-center gap-2 font-medium">
<x-pill id="timeline-status-pill" variant="success" icon="fa-solid fa-circle">Completed</x-pill> <i class="fa-solid fa-shield-halved text-indigo-400"></i>
@elseif($interview->call_status === 'active') <span>SOS auto-trigger alert</span>
<x-pill id="timeline-status-pill" variant="info" class="animate-pulse" icon="fa-solid fa-circle">Call in progress</x-pill> </span>
@elseif($interview->isActiveTimeline())
<x-pill id="timeline-status-pill" variant="info" icon="fa-solid fa-bolt">Timeline live</x-pill>
@else
<x-pill id="timeline-status-pill" variant="warning" icon="fa-solid fa-clock">Scheduled</x-pill>
@endif
<div class="w-px h-[18px] bg-slate-700/60 mx-1"></div>
<div class="flex items-center gap-2 text-xs text-slate-400 cursor-pointer select-none" onclick="toggleSosAutoTrigger()">
<span class="toggle relative inline-flex h-4 w-8 shrink-0 cursor-pointer rounded-full border border-slate-700 bg-slate-900 transition-colors duration-200 ease-in-out [&.on]:bg-indigo-500/20 [&.on]:border-indigo-500/50 after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:h-2.5 after:w-2.5 after:rounded-full after:bg-slate-500 after:transition-all duration-150 [&.on]:after:left-[17px] [&.on]:after:bg-indigo-400" id="sosToggle"></span> <span class="toggle relative inline-flex h-4 w-8 shrink-0 cursor-pointer rounded-full border border-slate-700 bg-slate-900 transition-colors duration-200 ease-in-out [&.on]:bg-indigo-500/20 [&.on]:border-indigo-500/50 after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:h-2.5 after:w-2.5 after:rounded-full after:bg-slate-500 after:transition-all duration-150 [&.on]:after:left-[17px] [&.on]:after:bg-indigo-400" id="sosToggle"></span>
<span>SOS auto-trigger</span>
</div> </div>
<div class="w-px h-[18px] bg-slate-700/60 mx-1"></div> <div class="grid grid-cols-3 gap-1.5 pt-1">
<x-button onclick="regenerateCandidatePassword()" variant="secondary" size="sm" icon="fa-solid fa-key" class="w-full justify-center text-[11px]">Password</x-button>
<x-button onclick="openReportPage()" variant="secondary" size="sm" icon="fa-solid fa-file-pdf" class="w-full justify-center text-[11px]">PDF Report</x-button>
<x-button onclick="blockCandidateAction()" variant="danger" size="sm" icon="fa-solid fa-ban" class="w-full justify-center bg-red-500/20 text-red-300 border-red-500/40 text-[11px]">Block</x-button>
</div>
</div>
</x-hamburger-menu>
<x-button onclick="regenerateCandidatePassword()" variant="secondary" icon="fa-solid fa-key">Password</x-button> <!-- Candidate Avatar Circle -->
<x-button onclick="openReportPage()" variant="secondary" icon="fa-solid fa-file-pdf">PDF report</x-button> <div class="w-8 h-8 rounded-full bg-gradient-to-br from-indigo-500 to-sky-600 flex items-center justify-center text-white font-bold text-xs shrink-0 shadow-md shadow-indigo-500/20">
<x-button onclick="blockCandidateAction()" variant="secondary" icon="fa-solid fa-ban">Block</x-button> {{ $interview->candidate_initials }}
</div>
<!-- Candidate Name & Email -->
<div class="min-w-0">
<h2 id="modal-cand-name" class="text-sm font-semibold text-white m-0 leading-tight truncate">
{{ $interview->candidate_name }}
</h2>
<div class="text-[11px] text-slate-400 font-mono truncate">
{{ $interview->candidate_email }} &middot; {{ $interview->candidate_phone }}
</div> </div>
</div> </div>
<!-- Submission Details Disclosure Accordion --> <!-- Status Pills (Connected Status & Timeline Status) -->
<details class="group border-t border-slate-700/60"> <div class="flex items-center gap-1.5 shrink-0 ml-1">
<summary class="flex items-center gap-2 px-5 py-2.5 text-xs text-slate-400 font-medium hover:text-slate-200 transition-colors cursor-pointer list-none select-none"> <span id="candidate-joined-pill" class="pill gray inline-flex items-center gap-1.5 font-semibold text-[10.5px] px-2 py-0.5 rounded-full border border-slate-700 text-slate-400 bg-slate-800/60">
<i class="fa-solid fa-chevron-down text-[10px] transition-transform duration-150 group-open:rotate-180"></i> <i class="fa-solid fa-circle text-[7px] text-slate-500"></i> Not Joined
<span>Show submission details</span> </span>
</summary>
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-4 gap-2.5 px-5 pb-4"> @if($interview->isExpired())
<div class="bg-slate-950/60 border border-slate-800 rounded-lg p-2.5"> <x-pill id="timeline-status-pill" variant="danger" size="sm" icon="fa-solid fa-circle">Expired</x-pill>
<div class="text-[10.5px] text-slate-500 uppercase tracking-wider mb-1 font-semibold">Submission ID</div> @elseif($interview->status === 'completed')
<div id="modal-cand-uid" class="text-xs font-mono text-slate-300 break-all">{{ $interview->submission_unique_id }}</div> <x-pill id="timeline-status-pill" variant="success" size="sm" icon="fa-solid fa-circle">Completed</x-pill>
</div> @elseif($interview->call_status === 'active')
<div class="bg-slate-950/60 border border-slate-800 rounded-lg p-2.5"> <x-pill id="timeline-status-pill" variant="info" size="sm" class="animate-pulse" icon="fa-solid fa-circle">Call in progress</x-pill>
<div class="text-[10.5px] text-slate-500 uppercase tracking-wider mb-1 font-semibold">Temp Pass</div> @elseif($interview->isActiveTimeline())
<div class="text-xs font-mono text-emerald-400 font-semibold">{{ $interview->temp_password }}</div> <x-pill id="timeline-status-pill" variant="info" size="sm" icon="fa-solid fa-bolt">Timeline live</x-pill>
</div> @else
<div class="bg-slate-950/60 border border-slate-800 rounded-lg p-2.5"> <x-pill id="timeline-status-pill" variant="warning" size="sm" icon="fa-solid fa-clock">Scheduled</x-pill>
<div class="text-[10.5px] text-slate-500 uppercase tracking-wider mb-1 font-semibold">Language</div> @endif
<div class="text-xs font-mono text-slate-300 uppercase">{{ $interview->language }}</div> </div>
</div>
<div class="bg-slate-950/60 border border-slate-800 rounded-lg p-2.5">
<div class="text-[10.5px] text-slate-500 uppercase tracking-wider mb-1 font-semibold">Timeline</div>
<div class="text-xs font-mono text-slate-300">{{ $interview->formatted_timeline }}</div>
</div>
</div>
</details>
</div> </div>

View File

@ -8,11 +8,11 @@
<script src="//cdn.metered.ca/sdk/video/1.4.6/sdk.min.js"></script> <script src="//cdn.metered.ca/sdk/video/1.4.6/sdk.min.js"></script>
@endpush @endpush
<!-- Candidate Session Summary Card -->
<x-interview.candidate-header :interview="$interview" />
<!-- Video & Screen Call Panel --> <!-- Video & Screen Call Panel -->
<x-card> <x-card>
<x-slot:header>
<x-interview.candidate-header :interview="$interview" />
</x-slot:header>
<x-slot:headerExtra> <x-slot:headerExtra>
<x-interview.call-controls :interview="$interview" /> <x-interview.call-controls :interview="$interview" />
</x-slot:headerExtra> </x-slot:headerExtra>