sls/resources/views/client_calls/show.blade.php

1245 lines
60 KiB
PHP

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ $client->project_name }} | Client Call Hub | SingleLogin</title>
<!-- Google Fonts: Outfit & Inter -->
<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=Inter:wght@300;400;500;600;700&family=Outfit:wght@400;600;700;800&display=swap" rel="stylesheet">
<script>
(function () {
const savedTheme = localStorage.getItem('theme') || 'dark';
if (savedTheme === 'light') {
document.documentElement.classList.add('light-mode');
} else {
document.documentElement.classList.remove('light-mode');
}
})();
</script>
<style>
:root {
--bg-color: #0b0f19;
--card-bg: rgba(17, 24, 39, 0.55);
--card-border: rgba(255, 255, 255, 0.12);
--accent-primary: #4f46e5;
--accent-secondary: #06b6d4;
--text-main: #f3f4f6;
--text-muted: #9ca3af;
--success-color: #10b981;
--warning-color: #f59e0b;
--danger-color: #ef4444;
--input-bg: rgba(255, 255, 255, 0.04);
--modal-bg: #131b2e;
--modal-overlay: rgba(11, 15, 25, 0.85);
}
:root.light-mode {
--bg-color: #f3f4f6;
--card-bg: rgba(255, 255, 255, 0.85);
--card-border: rgba(0, 0, 0, 0.08);
--accent-primary: #4f46e5;
--accent-secondary: #0284c7;
--text-main: #1f2937;
--text-muted: #4b5563;
--success-color: #059669;
--warning-color: #d97706;
--danger-color: #dc2626;
--input-bg: rgba(0, 0, 0, 0.03);
--modal-bg: #ffffff;
--modal-overlay: rgba(243, 244, 246, 0.85);
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: 'Inter', sans-serif;
background-color: var(--bg-color);
color: var(--text-main);
min-height: 100vh;
background-image:
radial-gradient(circle at 85% 15%, rgba(99, 102, 241, 0.12) 0%, transparent 45%),
radial-gradient(circle at 15% 85%, rgba(6, 182, 212, 0.1) 0%, transparent 45%);
background-attachment: fixed;
}
/* Navbar */
.navbar {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1rem 2rem;
background: rgba(10, 15, 26, 0.6);
backdrop-filter: blur(12px);
border-bottom: 1px solid var(--card-border);
position: sticky;
top: 0;
z-index: 50;
}
:root.light-mode .navbar { background: rgba(255, 255, 255, 0.8); }
.brand-logo {
display: flex;
align-items: center;
gap: 12px;
text-decoration: none;
color: var(--text-main);
font-family: 'Outfit', sans-serif;
font-size: 1.25rem;
font-weight: 700;
}
.brand-badge {
background: linear-gradient(135deg, #4f46e5, #06b6d4);
color: white;
font-size: 0.7rem;
padding: 2px 8px;
border-radius: 999px;
font-weight: 700;
text-transform: uppercase;
}
.nav-actions {
display: flex;
align-items: center;
gap: 12px;
}
.nav-btn {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 0.5rem 1rem;
border-radius: 8px;
font-size: 0.875rem;
font-weight: 600;
text-decoration: none;
cursor: pointer;
transition: all 0.2s ease;
border: 1px solid var(--card-border);
background: var(--card-bg);
color: var(--text-main);
}
.nav-btn:hover {
border-color: rgba(99, 102, 241, 0.5);
transform: translateY(-1px);
}
.nav-btn-primary {
background: linear-gradient(135deg, #4f46e5, #6366f1);
color: white !important;
border: none;
box-shadow: 0 4px 12px rgba(79, 70, 229, 0.35);
}
.nav-btn-instant {
background: linear-gradient(135deg, #f59e0b, #d97706);
color: white !important;
border: none;
box-shadow: 0 4px 12px rgba(245, 158, 11, 0.35);
}
/* Main Container */
.main-container {
max-width: 1300px;
margin: 2rem auto;
padding: 0 1.5rem;
}
.alert {
padding: 1rem 1.25rem;
border-radius: 10px;
margin-bottom: 1.5rem;
display: flex;
align-items: center;
justify-content: space-between;
font-size: 0.9rem;
}
.alert-success {
background: rgba(16, 185, 129, 0.15);
border: 1px solid rgba(16, 185, 129, 0.35);
color: #34d399;
}
/* Profile Header Card */
.profile-header-card {
background: var(--card-bg);
backdrop-filter: blur(12px);
border: 1px solid var(--card-border);
border-radius: 18px;
padding: 2rem;
margin-bottom: 2rem;
display: flex;
justify-content: space-between;
align-items: flex-start;
flex-wrap: wrap;
gap: 1.5rem;
}
.project-headline {
font-family: 'Outfit', sans-serif;
font-size: 1.8rem;
font-weight: 800;
color: var(--text-main);
margin-bottom: 6px;
}
.client-submeta {
display: flex;
align-items: center;
gap: 16px;
color: var(--text-muted);
font-size: 0.9rem;
flex-wrap: wrap;
}
.client-meta-item {
display: flex;
align-items: center;
gap: 6px;
}
.sentiment-pill {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 6px 14px;
border-radius: 999px;
font-size: 0.85rem;
font-weight: 700;
text-transform: uppercase;
}
.sentiment-happy {
background: rgba(16, 185, 129, 0.18);
color: #34d399;
border: 1px solid rgba(16, 185, 129, 0.4);
}
.sentiment-neutral {
background: rgba(59, 130, 246, 0.18);
color: #60a5fa;
border: 1px solid rgba(59, 130, 246, 0.4);
}
.sentiment-sad {
background: rgba(245, 158, 11, 0.18);
color: #fbbf24;
border: 1px solid rgba(245, 158, 11, 0.4);
}
.sentiment-angry {
background: rgba(239, 68, 68, 0.22);
color: #f87171;
border: 1px solid rgba(239, 68, 68, 0.5);
animation: pulse-border 2s infinite;
}
/* Action Buttons Bar */
.hub-actions-bar {
display: flex;
gap: 12px;
flex-wrap: wrap;
}
/* Angry Escalation Banner */
.angry-banner {
background: rgba(239, 68, 68, 0.14);
border: 1px solid rgba(239, 68, 68, 0.35);
border-radius: 12px;
padding: 1.25rem 1.5rem;
margin-bottom: 2rem;
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
gap: 1rem;
}
.angry-banner-left {
display: flex;
align-items: center;
gap: 12px;
}
.angry-banner-title {
font-size: 0.95rem;
font-weight: 700;
color: #fca5a5;
}
.angry-banner-desc {
font-size: 0.8rem;
color: #fee2e2;
margin-top: 2px;
}
/* Tab Navigation */
.tabs-nav {
display: flex;
gap: 10px;
border-bottom: 1px solid var(--card-border);
margin-bottom: 1.75rem;
}
.tab-btn {
background: transparent;
border: none;
color: var(--text-muted);
font-size: 0.95rem;
font-weight: 600;
padding: 0.75rem 1.25rem;
cursor: pointer;
position: relative;
transition: all 0.2s ease;
}
.tab-btn:hover { color: var(--text-main); }
.tab-btn.active {
color: #818cf8;
font-weight: 700;
}
.tab-btn.active::after {
content: '';
position: absolute;
bottom: -1px;
left: 0;
right: 0;
height: 3px;
background: #6366f1;
border-radius: 3px 3px 0 0;
}
.tab-content { display: none; }
.tab-content.active { display: block; }
/* Meeting Cards */
.meetings-list {
display: flex;
flex-direction: column;
gap: 1.25rem;
}
.meeting-item-card {
background: var(--card-bg);
backdrop-filter: blur(12px);
border: 1px solid var(--card-border);
border-radius: 14px;
padding: 1.25rem 1.5rem;
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
gap: 1rem;
transition: all 0.2s ease;
}
.meeting-item-card:hover {
border-color: rgba(99, 102, 241, 0.4);
transform: translateY(-2px);
}
.meeting-title {
font-family: 'Outfit', sans-serif;
font-size: 1.15rem;
font-weight: 700;
color: var(--text-main);
margin-bottom: 4px;
}
.meeting-meta {
display: flex;
align-items: center;
gap: 14px;
font-size: 0.85rem;
color: var(--text-muted);
flex-wrap: wrap;
}
/* MoM Cards */
.mom-card {
background: var(--card-bg);
backdrop-filter: blur(12px);
border: 1px solid var(--card-border);
border-radius: 16px;
padding: 1.75rem;
margin-bottom: 1.5rem;
}
.mom-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 1.25rem;
flex-wrap: wrap;
gap: 10px;
}
.mom-call-title {
font-family: 'Outfit', sans-serif;
font-size: 1.25rem;
font-weight: 700;
}
.mom-date {
font-size: 0.85rem;
color: var(--text-muted);
}
.summary-box {
background: rgba(99, 102, 241, 0.08);
border-left: 4px solid #6366f1;
padding: 1rem 1.25rem;
border-radius: 0 8px 8px 0;
color: #cbd5e1;
font-size: 0.9rem;
line-height: 1.6;
margin-bottom: 1.25rem;
}
.action-table {
width: 100%;
border-collapse: collapse;
margin: 1rem 0;
font-size: 0.85rem;
}
.action-table th {
text-align: left;
padding: 8px 12px;
background: rgba(255, 255, 255, 0.04);
color: var(--text-muted);
font-weight: 600;
}
.action-table td {
padding: 8px 12px;
border-bottom: 1px solid var(--card-border);
color: var(--text-main);
}
.mom-full-content {
background: var(--input-bg);
border: 1px solid var(--card-border);
border-radius: 8px;
padding: 1.25rem;
font-size: 0.875rem;
line-height: 1.6;
color: #d1d5db;
white-space: pre-wrap;
margin-top: 1rem;
}
/* Modal styling */
.modal-overlay {
position: fixed;
top: 0; left: 0; right: 0; bottom: 0;
background: var(--modal-overlay);
backdrop-filter: blur(6px);
display: none;
justify-content: center;
align-items: center;
z-index: 100;
padding: 1rem;
}
.modal-card {
background: var(--modal-bg);
border: 1px solid var(--card-border);
border-radius: 16px;
width: 100%;
max-width: 600px;
max-height: 90vh;
overflow-y: auto;
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.5);
padding: 2rem;
}
.modal-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1.5rem;
}
.modal-title {
font-family: 'Outfit', sans-serif;
font-size: 1.35rem;
font-weight: 700;
}
.modal-close {
background: transparent;
border: none;
color: var(--text-muted);
font-size: 1.5rem;
cursor: pointer;
}
.form-group { margin-bottom: 1.25rem; }
.form-label {
display: block;
font-size: 0.85rem;
font-weight: 600;
color: var(--text-muted);
margin-bottom: 6px;
}
.form-control {
width: 100%;
padding: 0.7rem 1rem;
background: var(--input-bg);
border: 1px solid var(--card-border);
border-radius: 8px;
color: var(--text-main);
font-size: 0.9rem;
outline: none;
}
.form-control:focus { border-color: #6366f1; }
.form-row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1rem;
}
.share-link-box {
display: flex;
align-items: center;
gap: 8px;
background: var(--input-bg);
border: 1px solid var(--card-border);
border-radius: 8px;
padding: 8px 12px;
margin: 1rem 0;
}
.share-link-text {
flex: 1;
font-family: monospace;
font-size: 0.85rem;
color: #38bdf8;
word-break: break-all;
}
.btn-copy {
background: rgba(99, 102, 241, 0.2);
color: #818cf8;
border: 1px solid rgba(99, 102, 241, 0.4);
padding: 6px 12px;
border-radius: 6px;
font-size: 0.75rem;
font-weight: 600;
cursor: pointer;
}
.btn-copy:hover {
background: #4f46e5;
color: white;
}
</style>
</head>
<body>
<!-- Navbar -->
<nav class="navbar">
<a href="{{ route('client-calls.index') }}" class="brand-logo">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="color: #6366f1;"><polyline points="15 18 9 12 15 6"></polyline></svg>
Client Hub <span class="brand-badge">{{ $client->project_name }}</span>
</a>
<div class="nav-actions">
<!-- Theme Toggle -->
<button class="theme-toggle-btn" id="themeToggleBtn" onclick="toggleTheme()" title="Switch Light / Dark Theme" style="background: var(--card-bg); border: 1px solid var(--card-border); color: var(--text-main); width: 38px; height: 38px; border-radius: 8px; display: flex; align-items: center; justify-content: center; cursor: pointer; font-size: 1rem;">
🌙
</button>
<a href="{{ route('client-calls.logs') }}" class="nav-btn" style="border-color: #22d3ee; color: #22d3ee;">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path><polyline points="14 2 14 8 20 8"></polyline><line x1="16" y1="13" x2="8" y2="13"></line><line x1="16" y1="17" x2="8" y2="17"></line><polyline points="10 9 9 9 8 9"></polyline></svg>
Date-wise Call Logs
</a>
<a href="{{ route('client-calls.index') }}" class="nav-btn">
All Clients
</a>
<a href="{{ route('dashboard') }}" class="nav-btn">
Dashboard
</a>
</div>
</nav>
<!-- Main Container -->
<main class="main-container">
@if(session('success'))
<div class="alert alert-success">
<span>{{ session('success') }}</span>
<button style="background:transparent;border:none;color:inherit;cursor:pointer;" onclick="this.parentElement.remove()"></button>
</div>
@endif
<!-- Profile Header -->
<div class="profile-header-card">
<div>
<div style="font-size: 0.85rem; color: #818cf8; font-weight: 700; text-transform: uppercase; margin-bottom: 4px;">
Client Profile & Project Space
</div>
<h1 class="project-headline">{{ $client->project_name }}</h1>
<div class="client-submeta">
<div class="client-meta-item">
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path><circle cx="12" cy="7" r="4"></circle></svg>
<strong>{{ $client->client_name }}</strong> {{ $client->company_name ? "({$client->company_name})" : "" }}
</div>
<div class="client-meta-item">
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"></path><polyline points="22,6 12,13 2,6"></polyline></svg>
{{ $client->client_email }}
</div>
@if($client->client_phone)
<div class="client-meta-item">
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z"></path></svg>
{{ $client->client_phone }}
</div>
@endif
<div class="client-meta-item">
<span style="color: var(--text-muted);">Lead in Charge:</span>
<span style="color: #818cf8; font-weight: 600;">{{ $client->responsibleUser->name ?? 'Unassigned' }}</span>
</div>
</div>
</div>
<div style="display: flex; flex-direction: column; align-items: flex-end; gap: 1rem;">
@php
$sentiment = strtolower($client->latest_sentiment ?? 'neutral');
$sentimentClass = match($sentiment) {
'happy' => 'sentiment-happy',
'sad' => 'sentiment-sad',
'angry' => 'sentiment-angry',
default => 'sentiment-neutral',
};
$sentimentEmoji = match($sentiment) {
'happy' => '😊 Happy / Satisfied',
'sad' => '😟 Concerned / Sad',
'angry' => '😡 Frustrated / Angry',
default => '😐 Neutral / Professional',
};
@endphp
<div>
<div style="font-size: 0.75rem; color: var(--text-muted); text-align: right; margin-bottom: 4px; font-weight: 600; text-transform: uppercase;">Latest Client Mood</div>
<span class="sentiment-pill {{ $sentimentClass }}">{{ $sentimentEmoji }}</span>
</div>
<div class="hub-actions-bar">
<button class="nav-btn-instant nav-btn" onclick="openInstantMeetingModal()">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"></polygon></svg>
Start Instant Meeting
</button>
<button class="nav-btn-primary nav-btn" onclick="openScheduleModal()">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="4" width="18" height="18" rx="2" ry="2"></rect><line x1="16" y1="2" x2="16" y2="6"></line><line x1="8" y1="2" x2="8" y2="6"></line><line x1="3" y1="10" x2="21" y2="10"></line></svg>
📅 Schedule Meeting
</button>
</div>
</div>
</div>
<!-- Angry Escalation Banner (Confidential to Admin & Responsible Person) -->
@php
$latestNote = $client->callNotes->first();
@endphp
@if($sentiment === 'angry' && $latestNote && $canViewAngry)
<div class="angry-banner">
<div class="angry-banner-left">
<div style="font-size: 1.8rem;">⚠️</div>
<div>
<div class="angry-banner-title">Managerial Escalation: Client Frustration Detected in Recent Call</div>
<div class="angry-banner-desc">AI has isolated critical dissatisfaction triggers from the speech transcript. Accessible only to Admin & Responsible Lead.</div>
</div>
</div>
<button class="nav-btn" style="background: #ef4444; color: white; border: none; font-weight: 700;" onclick="openWhyAngryModal({{ $latestNote->id }})">
Why is he angry? (AI Summary)
</button>
</div>
@endif
<!-- Tabs Navigation -->
<div class="tabs-nav">
<button class="tab-btn active" onclick="switchTab('meetingsTab', this)">
📅 Scheduled & Active Meetings ({{ $client->meetings->count() }})
</button>
<button class="tab-btn" onclick="switchTab('momsTab', this)">
📝 Client Profile History: AI Notes & MoM Archive ({{ $client->callNotes->count() }})
</button>
<button class="tab-btn" onclick="switchTab('detailsTab', this)">
💼 Project Details & Assigned Team
</button>
</div>
<!-- TAB 1: Meetings & Calls -->
<div id="meetingsTab" class="tab-content active">
@if($client->meetings->isEmpty())
<div style="text-align: center; padding: 3rem; background: var(--card-bg); border-radius: 14px; border: 1px dashed var(--card-border);">
<div style="font-size: 2.5rem; margin-bottom: 10px;">📅</div>
<h3 style="font-size: 1.1rem; font-weight: 700; margin-bottom: 4px;">No Meetings Scheduled Yet</h3>
<p style="color: var(--text-muted); font-size: 0.85rem; margin-bottom: 1.25rem;">Create an instant meeting or schedule a future call with invitation emails.</p>
<button class="nav-btn-primary nav-btn" onclick="openScheduleModal()">+ Schedule First Meeting</button>
</div>
@else
<div class="meetings-list">
@foreach($client->meetings as $meeting)
@php
$isUpcoming = ($meeting->scheduled_at && $meeting->scheduled_at->isFuture()) || $meeting->status === 'scheduled';
@endphp
<div class="meeting-item-card">
<div>
<div style="display: flex; align-items: center; gap: 8px; margin-bottom: 4px;">
<span style="font-size: 0.75rem; font-weight: 700; padding: 2px 8px; border-radius: 999px; {{ $meeting->meeting_type === 'instant' ? 'background: rgba(245,158,11,0.2); color: #fbbf24;' : 'background: rgba(99,102,241,0.2); color: #818cf8;' }}">
{{ $meeting->meeting_type === 'instant' ? '⚡ Instant' : '📅 Scheduled' }}
</span>
<span style="font-size: 0.75rem; font-weight: 600; color: {{ $meeting->status === 'completed' ? '#34d399' : '#38bdf8' }}; text-transform: uppercase;">
&bull; {{ $meeting->status }}
</span>
</div>
<div class="meeting-title">{{ $meeting->title }}</div>
<div class="meeting-meta">
@if($meeting->scheduled_at)
<div>🕒 {{ $meeting->scheduled_at->format('M d, Y - h:i A') }} ({{ $meeting->duration_minutes }}m)</div>
@else
<div>🕒 Created: {{ $meeting->created_at->format('M d, Y - h:i A') }}</div>
@endif
<div>👤 Host: {{ $meeting->host->name ?? 'SingleLogin Host' }}</div>
@if(!empty($meeting->client_emails))
<div>✉️ Client Invites: {{ count($meeting->client_emails) }}</div>
@endif
</div>
@if($meeting->agenda)
<div style="font-size: 0.85rem; color: #cbd5e1; margin-top: 6px;">
<em>Agenda:</em> {{ $meeting->agenda }}
</div>
@endif
</div>
<div style="display: flex; gap: 8px; align-items: center;">
<button class="btn-copy" onclick="navigator.clipboard.writeText('{{ $meeting->join_url }}'); alert('Meeting link copied to clipboard!');">
📋 Copy Link
</button>
<a href="{{ $meeting->join_url }}" target="_blank" class="nav-btn-primary nav-btn" style="font-size: 0.85rem; padding: 0.5rem 1rem;">
👉 Join Room
</a>
</div>
</div>
@endforeach
</div>
@endif
</div>
<!-- TAB 2: AI Notes & MoM Archive -->
<div id="momsTab" class="tab-content">
@if($client->callNotes->isEmpty())
<div style="text-align: center; padding: 3rem; background: var(--card-bg); border-radius: 14px; border: 1px dashed var(--card-border);">
<div style="font-size: 2.5rem; margin-bottom: 10px;">📝</div>
<h3 style="font-size: 1.1rem; font-weight: 700; margin-bottom: 4px;">No Past Call Notes or MoMs Yet</h3>
<p style="color: var(--text-muted); font-size: 0.85rem;">When you complete a client call, AI will automatically take notes, detect client emotion, generate MoM, and archive it here.</p>
</div>
@else
@foreach($client->callNotes as $note)
@php
$noteSentiment = strtolower($note->detected_sentiment ?? 'neutral');
$noteBadgeClass = match($noteSentiment) {
'happy' => 'sentiment-happy',
'sad' => 'sentiment-sad',
'angry' => 'sentiment-angry',
default => 'sentiment-neutral',
};
$noteEmoji = match($noteSentiment) {
'happy' => '😊 Happy',
'sad' => '😟 Concerned',
'angry' => '😡 Frustrated / Angry',
default => '😐 Neutral',
};
@endphp
<div class="mom-card">
<div class="mom-header">
<div>
<div class="mom-call-title">{{ $note->meeting->title ?? 'Client Call Session' }}</div>
<div class="mom-date">
Recorded on {{ $note->created_at->format('F j, Y - h:i A') }} &bull; Note Host: {{ $note->creator->name ?? 'SingleLogin AI' }}
@if($note->emailed_at)
&bull; <span style="color: #34d399;">✉️ MoM Emailed to TL, Director, PM ({{ $note->emailed_at->format('M d, h:i A') }})</span>
@endif
</div>
</div>
<div style="display: flex; align-items: center; gap: 8px; flex-wrap: wrap;">
<button class="sentiment-pill {{ $noteBadgeClass }}" style="cursor: pointer; border: 1px solid var(--card-border);" onclick="openEmotionBreakdownModal({{ $note->id }})" title="Click to view emotion breakdown">
{{ $noteEmoji }} <span style="font-size: 0.7rem; text-decoration: underline; margin-left: 2px;">Why?</span>
</button>
<a href="{{ route('client-calls.download-mom', $note->id) }}" class="nav-btn" style="font-size: 0.75rem; padding: 4px 10px; color: #818cf8; border-color: rgba(99,102,241,0.4);">
📥 MoM (.txt)
</a>
<a href="{{ route('client-calls.download-todos', $note->id) }}" class="nav-btn" style="font-size: 0.75rem; padding: 4px 10px; color: #34d399; border-color: rgba(16,185,129,0.4);">
📋 To-Dos (.txt)
</a>
@if($noteSentiment === 'angry' && $canViewAngry)
<button class="nav-btn" style="background: #ef4444; color: white; border: none; font-size: 0.75rem; padding: 4px 10px;" onclick="openWhyAngryModal({{ $note->id }})">
Why is he angry?
</button>
@endif
<button class="nav-btn" style="font-size: 0.75rem; padding: 4px 10px;" onclick="resendEmail({{ $note->id }})">
Resend Email
</button>
</div>
</div>
<!-- Summary -->
<div style="font-size: 0.85rem; font-weight: 700; color: #a5b4fc; text-transform: uppercase; margin-bottom: 6px;">Executive Summary</div>
<div class="summary-box">
{{ $note->summary ?? 'Summary unavailable.' }}
</div>
<!-- Auto-Generated To-Do Tasks -->
@if(!empty($note->todo_items) && count($note->todo_items) > 0)
<div style="font-size: 0.85rem; font-weight: 700; color: #34d399; text-transform: uppercase; margin: 1.25rem 0 6px 0; display: flex; justify-content: space-between; align-items: center;">
<span>📋 Auto-Generated Actionable To-Dos ({{ count($note->todo_items) }})</span>
<a href="{{ route('client-calls.download-todos', $note->id) }}" style="font-size: 0.75rem; color: #34d399; text-decoration: underline;">Download .txt</a>
</div>
<div style="display: flex; flex-direction: column; gap: 6px; margin-bottom: 1.25rem;">
@foreach($note->todo_items as $todo)
<div style="display: flex; align-items: flex-start; gap: 10px; background: var(--input-bg); border: 1px solid var(--card-border); padding: 8px 12px; border-radius: 6px;">
<input type="checkbox" style="margin-top: 3px; accent-color: #10b981; cursor: pointer;" {{ ($todo['completed'] ?? false) ? 'checked' : '' }} onchange="toggleTodoOnPage(this, {{ $note->id }}, {{ $todo['id'] ?? $loop->iteration }})">
<div style="flex: 1;">
<div style="font-size: 0.85rem; color: var(--text-main); font-weight: 500; {{ ($todo['completed'] ?? false) ? 'text-decoration: line-through; opacity: 0.6;' : '' }}" id="todotext_{{ $note->id }}_{{ $todo['id'] ?? $loop->iteration }}">
{{ $todo['task'] ?? '' }}
</div>
<div style="font-size: 0.75rem; color: var(--text-muted); display: flex; gap: 12px; margin-top: 2px;">
<span>👤 {{ $todo['owner'] ?? 'Assigned Lead' }}</span>
<span> Priority: {{ $todo['priority'] ?? 'Medium' }}</span>
<span>📅 Due: {{ $todo['deadline'] ?? 'TBD' }}</span>
</div>
</div>
</div>
@endforeach
</div>
@endif
<!-- Action Items -->
@if(!empty($note->action_items) && count($note->action_items) > 0)
<div style="font-size: 0.85rem; font-weight: 700; color: #a5b4fc; text-transform: uppercase; margin: 1.25rem 0 6px 0;">Action Items & Deliverables</div>
<table class="action-table">
<thead>
<tr>
<th>Task</th>
<th>Owner</th>
<th>Deadline</th>
<th>Status</th>
</tr>
</thead>
<tbody>
@foreach($note->action_items as $act)
<tr>
<td>{{ is_array($act) ? ($act['task'] ?? json_encode($act)) : $act }}</td>
<td>{{ is_array($act) ? ($act['owner'] ?? 'Lead') : 'Lead' }}</td>
<td>{{ is_array($act) ? ($act['deadline'] ?? '-') : '-' }}</td>
<td><span style="color: #34d399;">{{ is_array($act) ? ($act['status'] ?? 'Pending') : 'Pending' }}</span></td>
</tr>
@endforeach
</tbody>
</table>
@endif
<!-- Key Decisions -->
@if(!empty($note->key_decisions) && count($note->key_decisions) > 0)
<div style="font-size: 0.85rem; font-weight: 700; color: #a5b4fc; text-transform: uppercase; margin: 1.25rem 0 6px 0;">Key Decisions Made</div>
<ul style="padding-left: 20px; font-size: 0.85rem; color: #cbd5e1; line-height: 1.6;">
@foreach($note->key_decisions as $dec)
<li>{{ is_array($dec) ? ($dec['decision'] ?? json_encode($dec)) : $dec }}</li>
@endforeach
</ul>
@endif
<!-- Expand Full MoM -->
<details style="margin-top: 1.25rem; cursor: pointer;">
<summary style="font-size: 0.85rem; font-weight: 600; color: #818cf8; outline: none;">
View Complete Structured MoM Document
</summary>
<div class="mom-full-content">{!! nl2br(e($note->mom_content)) !!}</div>
</details>
<!-- Expand Raw Spoken Transcript -->
@if($note->raw_transcript)
<details style="margin-top: 0.75rem; cursor: pointer;">
<summary style="font-size: 0.85rem; font-weight: 600; color: var(--text-muted); outline: none;">
View Raw Call Speech Transcript
</summary>
<div class="mom-full-content" style="color: #9ca3af; font-family: monospace; font-size: 0.8rem;">{!! nl2br(e($note->raw_transcript)) !!}</div>
</details>
@endif
</div>
@endforeach
@endif
</div>
<!-- TAB 3: Project Details & Team -->
<div id="detailsTab" class="tab-content">
<div class="mom-card">
<h3 style="font-size: 1.15rem; font-weight: 700; margin-bottom: 1rem;">Project Scope & Specifications</h3>
<div style="font-size: 0.9rem; color: #cbd5e1; line-height: 1.6; background: var(--input-bg); padding: 1.25rem; border-radius: 8px; border: 1px solid var(--card-border); margin-bottom: 1.5rem;">
{{ $client->project_details ?: 'No additional project specifications provided.' }}
</div>
<h3 style="font-size: 1.15rem; font-weight: 700; margin-bottom: 1rem;">Assigned Internal Project Team</h3>
<div style="display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: 1rem;">
<div style="background: var(--input-bg); border: 1px solid var(--card-border); border-radius: 8px; padding: 1rem;">
<div style="font-size: 0.75rem; color: #818cf8; font-weight: 700; text-transform: uppercase;">Responsible Project Lead</div>
<div style="font-weight: 700; font-size: 1rem; margin-top: 4px;">{{ $client->responsibleUser->name ?? 'Unassigned' }}</div>
<div style="font-size: 0.8rem; color: var(--text-muted);">{{ $client->responsibleUser->email ?? '' }}</div>
</div>
@foreach($client->getTeamMembers() as $member)
<div style="background: var(--input-bg); border: 1px solid var(--card-border); border-radius: 8px; padding: 1rem;">
<div style="font-size: 0.75rem; color: var(--text-muted); font-weight: 700; text-transform: uppercase;">Team Member</div>
<div style="font-weight: 700; font-size: 1rem; margin-top: 4px;">{{ $member->name }}</div>
<div style="font-size: 0.8rem; color: var(--text-muted);">{{ $member->email }}</div>
</div>
@endforeach
</div>
</div>
</div>
</main>
<!-- Schedule Meeting Modal -->
<div id="scheduleModal" class="modal-overlay">
<div class="modal-card">
<div class="modal-header">
<h2 class="modal-title">Schedule Future Client Meeting</h2>
<button class="modal-close" onclick="closeScheduleModal()">&times;</button>
</div>
<form action="{{ route('client-calls.schedule-meeting', $client->id) }}" method="POST">
@csrf
<div class="form-group">
<label class="form-label">Meeting Title *</label>
<input type="text" name="title" class="form-control" placeholder="e.g. Sprint 4 Review & Roadmap Sync" required>
</div>
<div class="form-row">
<div class="form-group">
<label class="form-label">Scheduled Date & Time *</label>
<input type="datetime-local" name="scheduled_at" class="form-control" required value="{{ now()->addHour()->format('Y-m-d\TH:i') }}">
</div>
<div class="form-group">
<label class="form-label">Estimated Duration (Minutes)</label>
<input type="number" name="duration_minutes" class="form-control" value="30" min="10" max="240" required>
</div>
</div>
<div class="form-group">
<label class="form-label">Meeting Agenda / Discussion Points</label>
<textarea name="agenda" class="form-control" rows="2" placeholder="Topics to cover, questions for client, demo milestones..."></textarea>
</div>
<div class="form-group">
<label class="form-label">Client Email(s) for Invitation (Comma separated)</label>
<input type="text" name="client_emails" class="form-control" value="{{ $client->client_email }}" placeholder="e.g. client@acme.com, director@acme.com">
<span style="font-size: 0.75rem; color: var(--text-muted);">External clients will receive an automated invitation email containing their direct video join link.</span>
</div>
<div class="form-group">
<label class="form-label">Invite Internal Team Members</label>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 8px; max-height: 120px; overflow-y: auto; background: var(--input-bg); border: 1px solid var(--card-border); border-radius: 8px; padding: 10px;">
@foreach($allUsers as $u)
<label style="display: flex; align-items: center; gap: 6px; font-size: 0.8rem; cursor: pointer;">
<input type="checkbox" name="team_attendees[]" value="{{ $u->id }}" {{ $u->id === Auth::id() || $u->id === $client->responsible_user_id ? 'checked' : '' }}>
<span>{{ $u->name }}</span>
</label>
@endforeach
</div>
</div>
<div style="display: flex; justify-content: flex-end; gap: 10px; margin-top: 1.5rem;">
<button type="button" class="nav-btn" onclick="closeScheduleModal()">Cancel</button>
<button type="submit" class="nav-btn-primary nav-btn">Schedule & Send Invitations</button>
</div>
</form>
</div>
</div>
<!-- Instant Meeting Modal -->
<div id="instantMeetingModal" class="modal-overlay">
<div class="modal-card">
<div class="modal-header">
<h2 class="modal-title" style="display: flex; align-items: center; gap: 8px;">
<span></span> Start Instant Meeting
</h2>
<button class="modal-close" onclick="closeInstantMeetingModal()">&times;</button>
</div>
<p style="font-size: 0.85rem; color: var(--text-muted); margin-bottom: 1.25rem;">
Create an ad-hoc instant meeting room immediately. Share the link with team members and client to join instantly like Google Meet.
</p>
<form id="instantMeetingForm" onsubmit="handleStartInstantMeeting(event)">
@csrf
<div class="form-group">
<label class="form-label">Instant Meeting Title</label>
<input type="text" id="instantTitle" name="title" class="form-control" value="Instant Sync - {{ $client->project_name }}" required>
</div>
<div class="form-group">
<label class="form-label">Client Email(s) to Invite Immediately</label>
<input type="text" id="instantEmails" name="client_emails" class="form-control" value="{{ $client->client_email }}">
</div>
<div style="display: flex; justify-content: flex-end; gap: 10px; margin-top: 1.5rem;">
<button type="button" class="nav-btn" onclick="closeInstantMeetingModal()">Cancel</button>
<button type="submit" id="btnLaunchInstant" class="nav-btn-instant nav-btn">Launch Call Now</button>
</div>
</form>
<div id="instantResultBox" style="display: none; margin-top: 1.5rem; border-top: 1px solid var(--card-border); padding-top: 1.25rem;">
<div style="font-weight: 700; font-size: 0.9rem; color: #34d399; margin-bottom: 6px;">🎉 Instant Room Ready!</div>
<div class="share-link-box">
<span id="instantLinkText" class="share-link-text"></span>
<button class="btn-copy" onclick="copyInstantLink()">Copy Link</button>
</div>
<div style="text-align: center; margin-top: 1rem;">
<a id="btnJoinInstant" href="#" target="_blank" class="nav-btn-primary nav-btn" style="width: 100%; justify-content: center;">
👉 Enter Instant Call Room
</a>
</div>
</div>
</div>
</div>
<!-- Emotion Breakdown Modal (Why happy, Why sad, Why angry) -->
<div id="emotionBreakdownModal" class="modal-overlay">
<div class="modal-card" style="max-width: 600px;">
<div class="modal-header">
<div>
<h2 class="modal-title" style="display: flex; align-items: center; gap: 8px;">
<span>🧠</span> Emotion Intelligence Breakdown
</h2>
<p id="emotionBreakdownSubmeta" style="font-size: 0.75rem; color: var(--text-muted); margin-top: 4px;"></p>
</div>
<button class="modal-close" onclick="closeEmotionBreakdownModal()">&times;</button>
</div>
<div id="emotionBreakdownContent" style="padding: 10px 0;">
<div style="text-align: center; padding: 2rem; color: var(--text-muted);">Loading analysis...</div>
</div>
<div style="display: flex; justify-content: flex-end; margin-top: 1.25rem;">
<button type="button" class="nav-btn" onclick="closeEmotionBreakdownModal()">Close</button>
</div>
</div>
</div>
<!-- Why Is He Angry Modal (Confidential to Admin & Responsible Person) -->
<div id="whyAngryModal" class="modal-overlay">
<div class="modal-card" style="max-width: 650px; border-color: rgba(239, 68, 68, 0.4);">
<div class="modal-header">
<div>
<h2 class="modal-title" style="color: #f87171; display: flex; align-items: center; gap: 8px;">
<span>😡</span> AI Root-Cause Analysis: Why is the client angry?
</h2>
<p style="font-size: 0.75rem; color: #fca5a5; margin-top: 4px;">Strictly Confidential &bull; Restricted to Admin and Assigned Lead</p>
</div>
<button class="modal-close" onclick="closeWhyAngryModal()">&times;</button>
</div>
<div id="whyAngryContent">
<div style="text-align: center; padding: 2rem; color: var(--text-muted);">
Loading AI Analysis...
</div>
</div>
<div style="display: flex; justify-content: flex-end; margin-top: 1.5rem;">
<button type="button" class="nav-btn" onclick="closeWhyAngryModal()">Close</button>
</div>
</div>
</div>
<script>
function switchTab(tabId, el) {
document.querySelectorAll('.tab-content').forEach(tc => tc.classList.remove('active'));
document.querySelectorAll('.tab-btn').forEach(tb => tb.classList.remove('active'));
document.getElementById(tabId).classList.add('active');
el.classList.add('active');
}
function openScheduleModal() { document.getElementById('scheduleModal').style.display = 'flex'; }
function closeScheduleModal() { document.getElementById('scheduleModal').style.display = 'none'; }
function openInstantMeetingModal() {
document.getElementById('instantMeetingModal').style.display = 'flex';
document.getElementById('instantResultBox').style.display = 'none';
}
function closeInstantMeetingModal() { document.getElementById('instantMeetingModal').style.display = 'none'; }
function toggleTodoOnPage(cb, noteId, todoId) {
const textEl = document.getElementById(`todotext_${noteId}_${todoId}`);
if (textEl) {
textEl.style.textDecoration = cb.checked ? 'line-through' : 'none';
textEl.style.opacity = cb.checked ? '0.6' : '1';
}
fetch(`/client-calls/notes/${noteId}/toggle-todo/${todoId}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
'X-CSRF-TOKEN': '{{ csrf_token() }}'
}
}).catch(() => {});
}
async function handleStartInstantMeeting(e) {
e.preventDefault();
const btn = document.getElementById('btnLaunchInstant');
btn.disabled = true;
btn.innerText = 'Creating Room...';
const title = document.getElementById('instantTitle').value;
const emails = document.getElementById('instantEmails').value;
try {
const res = await fetch(`{{ route('client-calls.instant-meeting', $client->id) }}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
'X-CSRF-TOKEN': '{{ csrf_token() }}',
'X-Requested-With': 'XMLHttpRequest'
},
body: JSON.stringify({ title: title, client_emails: emails, send_email_now: true })
});
const data = await res.json();
if (data.success) {
document.getElementById('instantResultBox').style.display = 'block';
document.getElementById('instantLinkText').innerText = data.join_url;
document.getElementById('btnJoinInstant').href = data.join_url;
btn.innerText = 'Created!';
}
} catch (err) {
alert('Failed to launch instant meeting. Please try again.');
btn.disabled = false;
btn.innerText = 'Launch Call Now';
}
}
function copyInstantLink() {
const link = document.getElementById('instantLinkText').innerText;
navigator.clipboard.writeText(link);
alert('Instant meeting link copied to clipboard!');
}
async function openEmotionBreakdownModal(noteId) {
const modal = document.getElementById('emotionBreakdownModal');
const content = document.getElementById('emotionBreakdownContent');
const submeta = document.getElementById('emotionBreakdownSubmeta');
modal.style.display = 'flex';
content.innerHTML = '<div style="text-align:center;padding:2rem;color:var(--text-muted);">Analyzing emotion breakdown...</div>';
try {
const res = await fetch(`/client-calls/notes/${noteId}/emotion-details`, {
headers: { 'Accept': 'application/json', 'X-Requested-With': 'XMLHttpRequest' }
});
const data = await res.json();
if (!res.ok) {
content.innerHTML = `<div style="color:#f87171;padding:1rem;">${data.error || 'Access Restricted.'}</div>`;
return;
}
submeta.innerText = `${data.client_name} • ${data.project_name} (${data.created_at})`;
const emo = data.emotion_details || {};
const bullets = emo.bullet_points || [];
const quotes = emo.trigger_quotes || [];
content.innerHTML = `
<div style="background: rgba(99,102,241,0.12); border-left: 4px solid #6366f1; padding: 12px; border-radius: 0 6px 6px 0; margin-bottom: 1rem;">
<div style="font-weight: 700; color: #818cf8; font-size: 0.95rem;">${emo.headline || 'Client Emotion Analysis'}</div>
</div>
<div style="margin-bottom: 1rem;">
<div style="font-size: 0.8rem; font-weight: 700; color: var(--text-muted); text-transform: uppercase; margin-bottom: 6px;">Key Reasons:</div>
<ul style="padding-left: 20px; font-size: 0.85rem; color: #cbd5e1; line-height: 1.6;">
${bullets.map(b => `<li>${b}</li>`).join('')}
</ul>
</div>
${quotes.length > 0 ? `
<div style="margin-bottom: 1rem;">
<div style="font-size: 0.8rem; font-weight: 700; color: var(--text-muted); text-transform: uppercase; margin-bottom: 6px;">Client Quotes:</div>
<div style="display: flex; flex-wrap: wrap; gap: 6px;">
${quotes.map(q => `<span style="background: var(--input-bg); border: 1px solid var(--card-border); padding: 3px 8px; border-radius: 4px; font-size: 0.75rem; font-style: italic;">"${q}"</span>`).join('')}
</div>
</div>` : ''}
${emo.suggested_action ? `
<div style="background: rgba(16,185,129,0.1); border: 1px solid rgba(16,185,129,0.3); border-radius: 6px; padding: 10px;">
<div style="font-size: 0.75rem; font-weight: 700; color: #34d399; text-transform: uppercase; margin-bottom: 2px;">💡 Recommended Action:</div>
<p style="font-size: 0.85rem; color: #d1fae5;">${emo.suggested_action}</p>
</div>` : ''}
`;
} catch (e) {
content.innerHTML = '<div style="color:#f87171;padding:1rem;">Failed to load emotion breakdown.</div>';
}
}
function closeEmotionBreakdownModal() { document.getElementById('emotionBreakdownModal').style.display = 'none'; }
async function openWhyAngryModal(noteId) {
const modal = document.getElementById('whyAngryModal');
const content = document.getElementById('whyAngryContent');
modal.style.display = 'flex';
content.innerHTML = '<div style="text-align:center;padding:2rem;color:var(--text-muted);">Analyzing transcript and emotion triggers...</div>';
try {
const res = await fetch(`/client-calls/notes/${noteId}/angry-analysis`, {
headers: { 'Accept': 'application/json', 'X-Requested-With': 'XMLHttpRequest' }
});
const data = await res.json();
if (!res.ok) {
content.innerHTML = `<div style="color:#f87171;padding:1rem;background:rgba(239,68,68,0.1);border-radius:8px;">${data.error || 'Access Denied.'}</div>`;
return;
}
const analysis = data.angry_analysis || {};
const triggers = analysis.trigger_words || [];
const grievances = analysis.grievances || [];
content.innerHTML = `
<div style="margin-bottom: 1.25rem;">
<div style="font-size: 0.85rem; color: var(--text-muted); margin-bottom: 4px;">Client & Project</div>
<div style="font-weight: 700; font-size: 1.1rem; color: var(--text-main);">${data.client_name} &bull; ${data.project_name}</div>
</div>
<div style="background: rgba(239,68,68,0.1); border-left: 4px solid #ef4444; padding: 14px; border-radius: 0 8px 8px 0; margin-bottom: 1.25rem;">
<div style="font-weight: 700; font-size: 0.85rem; color: #fca5a5; margin-bottom: 6px; text-transform: uppercase;">AI Executive Breakdown</div>
<p style="font-size: 0.9rem; color: #fee2e2; line-height: 1.5;">${analysis.summary_why_angry || 'Client expressed significant dissatisfaction.'}</p>
</div>
${triggers.length > 0 ? `
<div style="margin-bottom: 1.25rem;">
<div style="font-weight: 600; font-size: 0.85rem; color: var(--text-muted); margin-bottom: 6px;">Identified Angry / Negative Keywords from Call:</div>
<div style="display: flex; flex-wrap: wrap; gap: 6px;">
${triggers.map(t => `<span style="background: rgba(239,68,68,0.25); color: #fca5a5; font-size: 0.75rem; padding: 3px 8px; border-radius: 4px; border: 1px solid rgba(239,68,68,0.4); font-weight: 600;">"${t}"</span>`).join('')}
</div>
</div>
` : ''}
${grievances.length > 0 ? `
<div style="margin-bottom: 1.25rem;">
<div style="font-weight: 600; font-size: 0.85rem; color: var(--text-muted); margin-bottom: 6px;">Key Complaints / Friction Points:</div>
<ul style="padding-left: 20px; font-size: 0.85rem; color: #cbd5e1; line-height: 1.6;">
${grievances.map(g => `<li>${g}</li>`).join('')}
</ul>
</div>
` : ''}
${analysis.suggested_remedy ? `
<div style="background: rgba(16,185,129,0.1); border: 1px solid rgba(16,185,129,0.3); border-radius: 8px; padding: 12px;">
<div style="font-weight: 700; font-size: 0.8rem; color: #34d399; margin-bottom: 4px; text-transform: uppercase;">💡 Recommended Remedial Action for PM & Director:</div>
<p style="font-size: 0.85rem; color: #d1fae5; line-height: 1.5;">${analysis.suggested_remedy}</p>
</div>
` : ''}
`;
} catch (err) {
content.innerHTML = `<div style="color:#f87171;padding:1rem;">Failed to load analysis.</div>`;
}
}
function closeWhyAngryModal() {
document.getElementById('whyAngryModal').style.display = 'none';
}
async function resendEmail(noteId) {
if (!confirm('Re-send MoM and Notes email to TL, Director, PM, and Admin?')) return;
try {
const res = await fetch(`/client-calls/notes/${noteId}/resend-email`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
'X-CSRF-TOKEN': '{{ csrf_token() }}',
'X-Requested-With': 'XMLHttpRequest'
}
});
const data = await res.json();
alert(data.message || 'MoM Email dispatched!');
} catch (e) {
alert('Failed to send email.');
}
}
</script>
</body>
</html>