1011 lines
42 KiB
PHP
1011 lines
42 KiB
PHP
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<meta name="csrf-token" content="{{ csrf_token() }}">
|
|
<title>Date-wise Call Logs & Meeting Intelligence | 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;
|
|
transition: background-color 0.3s ease, color 0.3s ease;
|
|
}
|
|
|
|
/* 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, #06b6d4, #3b82f6);
|
|
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: 10px;
|
|
}
|
|
.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 {
|
|
transform: translateY(-1px);
|
|
border-color: rgba(99, 102, 241, 0.5);
|
|
}
|
|
.theme-toggle-btn {
|
|
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;
|
|
transition: all 0.2s ease;
|
|
}
|
|
|
|
/* Container */
|
|
.main-container {
|
|
max-width: 1300px;
|
|
margin: 2rem auto;
|
|
padding: 0 1.5rem;
|
|
}
|
|
|
|
/* Stats Grid */
|
|
.stats-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
gap: 1.25rem;
|
|
margin-bottom: 2rem;
|
|
}
|
|
.stat-card {
|
|
background: var(--card-bg);
|
|
backdrop-filter: blur(12px);
|
|
border: 1px solid var(--card-border);
|
|
border-radius: 14px;
|
|
padding: 1.25rem;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 1rem;
|
|
transition: all 0.2s ease;
|
|
}
|
|
.stat-card:hover {
|
|
border-color: rgba(99, 102, 241, 0.4);
|
|
transform: translateY(-2px);
|
|
}
|
|
.stat-icon {
|
|
width: 44px;
|
|
height: 44px;
|
|
border-radius: 12px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-size: 1.3rem;
|
|
background: rgba(99, 102, 241, 0.12);
|
|
}
|
|
.stat-val {
|
|
font-size: 1.5rem;
|
|
font-weight: 800;
|
|
font-family: 'Outfit', sans-serif;
|
|
color: var(--text-main);
|
|
}
|
|
.stat-label {
|
|
font-size: 0.75rem;
|
|
color: var(--text-muted);
|
|
text-transform: uppercase;
|
|
font-weight: 600;
|
|
}
|
|
|
|
/* Section Header */
|
|
.section-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin-bottom: 1.5rem;
|
|
flex-wrap: wrap;
|
|
gap: 1rem;
|
|
}
|
|
.section-title {
|
|
font-family: 'Outfit', sans-serif;
|
|
font-size: 1.5rem;
|
|
font-weight: 700;
|
|
color: var(--text-main);
|
|
}
|
|
|
|
/* Filter Bar */
|
|
.filter-bar {
|
|
display: flex;
|
|
gap: 1rem;
|
|
margin-bottom: 2rem;
|
|
flex-wrap: wrap;
|
|
}
|
|
.search-input {
|
|
flex: 1;
|
|
min-width: 250px;
|
|
padding: 0.65rem 1rem;
|
|
background: var(--input-bg);
|
|
border: 1px solid var(--card-border);
|
|
border-radius: 8px;
|
|
color: var(--text-main);
|
|
font-size: 0.875rem;
|
|
outline: none;
|
|
}
|
|
.search-input:focus { border-color: #6366f1; }
|
|
.filter-select {
|
|
padding: 0.65rem 1rem;
|
|
background: var(--input-bg);
|
|
border: 1px solid var(--card-border);
|
|
border-radius: 8px;
|
|
color: var(--text-main);
|
|
font-size: 0.875rem;
|
|
outline: none;
|
|
cursor: pointer;
|
|
}
|
|
|
|
/* Date Group Header */
|
|
.date-group {
|
|
margin-bottom: 2.5rem;
|
|
}
|
|
.date-header {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
margin-bottom: 1.25rem;
|
|
}
|
|
.date-title {
|
|
font-family: 'Outfit', sans-serif;
|
|
font-size: 1.15rem;
|
|
font-weight: 700;
|
|
color: var(--text-main);
|
|
}
|
|
.date-badge {
|
|
background: rgba(99, 102, 241, 0.15);
|
|
color: #818cf8;
|
|
font-size: 0.75rem;
|
|
font-weight: 700;
|
|
padding: 2px 10px;
|
|
border-radius: 999px;
|
|
}
|
|
.date-line {
|
|
flex: 1;
|
|
height: 1px;
|
|
background: var(--card-border);
|
|
}
|
|
|
|
/* Call Log Entry Card */
|
|
.log-entry-card {
|
|
background: var(--card-bg);
|
|
backdrop-filter: blur(12px);
|
|
border: 1px solid var(--card-border);
|
|
border-radius: 16px;
|
|
padding: 1.5rem;
|
|
margin-bottom: 1.25rem;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 1rem;
|
|
transition: all 0.25s ease;
|
|
}
|
|
.log-entry-card:hover {
|
|
border-color: rgba(99, 102, 241, 0.4);
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.25);
|
|
}
|
|
|
|
.log-card-top {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: flex-start;
|
|
flex-wrap: wrap;
|
|
gap: 12px;
|
|
}
|
|
.log-project-title {
|
|
font-family: 'Outfit', sans-serif;
|
|
font-size: 1.2rem;
|
|
font-weight: 700;
|
|
color: var(--text-main);
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
}
|
|
.log-client-meta {
|
|
font-size: 0.85rem;
|
|
color: var(--text-muted);
|
|
margin-top: 4px;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
/* Interactive Clickable Emotion Badges */
|
|
.emotion-clickable {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
padding: 5px 12px;
|
|
border-radius: 999px;
|
|
font-size: 0.8rem;
|
|
font-weight: 700;
|
|
cursor: pointer;
|
|
transition: all 0.2s ease;
|
|
text-transform: uppercase;
|
|
}
|
|
.emotion-clickable:hover {
|
|
transform: scale(1.05);
|
|
}
|
|
.emotion-happy {
|
|
background: rgba(16, 185, 129, 0.18);
|
|
color: #34d399;
|
|
border: 1px solid rgba(16, 185, 129, 0.4);
|
|
}
|
|
.emotion-neutral {
|
|
background: rgba(59, 130, 246, 0.18);
|
|
color: #60a5fa;
|
|
border: 1px solid rgba(59, 130, 246, 0.4);
|
|
}
|
|
.emotion-sad {
|
|
background: rgba(245, 158, 11, 0.18);
|
|
color: #fbbf24;
|
|
border: 1px solid rgba(245, 158, 11, 0.4);
|
|
}
|
|
.emotion-angry {
|
|
background: rgba(239, 68, 68, 0.22);
|
|
color: #f87171;
|
|
border: 1px solid rgba(239, 68, 68, 0.5);
|
|
animation: pulse-border 2s infinite;
|
|
}
|
|
@keyframes pulse-border {
|
|
0% { box-shadow: 0 0 0 0 rgba(239, 68, 68, 0.4); }
|
|
70% { box-shadow: 0 0 0 6px rgba(239, 68, 68, 0); }
|
|
100% { box-shadow: 0 0 0 0 rgba(239, 68, 68, 0); }
|
|
}
|
|
|
|
.summary-preview-box {
|
|
background: rgba(255, 255, 255, 0.02);
|
|
border-left: 3px solid #6366f1;
|
|
padding: 10px 14px;
|
|
border-radius: 0 6px 6px 0;
|
|
font-size: 0.875rem;
|
|
color: #cbd5e1;
|
|
line-height: 1.5;
|
|
}
|
|
|
|
.log-actions-bar {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
padding-top: 10px;
|
|
border-top: 1px solid var(--card-border);
|
|
flex-wrap: wrap;
|
|
gap: 10px;
|
|
}
|
|
|
|
.btn-action-small {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
padding: 5px 12px;
|
|
border-radius: 6px;
|
|
font-size: 0.78rem;
|
|
font-weight: 600;
|
|
cursor: pointer;
|
|
text-decoration: none;
|
|
border: 1px solid var(--card-border);
|
|
background: var(--input-bg);
|
|
color: var(--text-main);
|
|
transition: all 0.2s ease;
|
|
}
|
|
.btn-action-small:hover {
|
|
border-color: #6366f1;
|
|
color: #818cf8;
|
|
transform: translateY(-1px);
|
|
}
|
|
.btn-download-mom {
|
|
background: rgba(99, 102, 241, 0.15);
|
|
color: #818cf8;
|
|
border-color: rgba(99, 102, 241, 0.35);
|
|
}
|
|
.btn-download-todos {
|
|
background: rgba(16, 185, 129, 0.15);
|
|
color: #34d399;
|
|
border-color: rgba(16, 185, 129, 0.35);
|
|
}
|
|
|
|
/* 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: 650px;
|
|
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.25rem;
|
|
}
|
|
.modal-title {
|
|
font-family: 'Outfit', sans-serif;
|
|
font-size: 1.3rem;
|
|
font-weight: 700;
|
|
}
|
|
.modal-close {
|
|
background: transparent;
|
|
border: none;
|
|
color: var(--text-muted);
|
|
font-size: 1.5rem;
|
|
cursor: pointer;
|
|
}
|
|
|
|
/* To-Do Checklist Styling */
|
|
.todo-item-row {
|
|
display: flex;
|
|
align-items: flex-start;
|
|
gap: 12px;
|
|
padding: 10px 12px;
|
|
background: var(--input-bg);
|
|
border: 1px solid var(--card-border);
|
|
border-radius: 8px;
|
|
margin-bottom: 8px;
|
|
transition: all 0.2s ease;
|
|
}
|
|
.todo-item-row.completed {
|
|
opacity: 0.6;
|
|
text-decoration: line-through;
|
|
}
|
|
.todo-checkbox {
|
|
margin-top: 3px;
|
|
cursor: pointer;
|
|
width: 16px;
|
|
height: 16px;
|
|
accent-color: #10b981;
|
|
}
|
|
.todo-content {
|
|
flex: 1;
|
|
}
|
|
.todo-text {
|
|
font-size: 0.875rem;
|
|
color: var(--text-main);
|
|
font-weight: 500;
|
|
}
|
|
.todo-meta {
|
|
font-size: 0.75rem;
|
|
color: var(--text-muted);
|
|
margin-top: 3px;
|
|
display: flex;
|
|
gap: 12px;
|
|
}
|
|
</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: #06b6d4;"><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"/></svg>
|
|
SingleLogin <span class="brand-badge">Call Logs</span>
|
|
</a>
|
|
<div class="nav-actions">
|
|
<!-- Theme Toggle -->
|
|
<button class="theme-toggle-btn" id="themeToggleBtn" onclick="toggleTheme()" title="Switch Light / Dark Theme">
|
|
🌙
|
|
</button>
|
|
<a href="{{ route('client-calls.index') }}" class="nav-btn">
|
|
📁 Client Projects
|
|
</a>
|
|
<a href="{{ route('dashboard') }}" class="nav-btn">
|
|
Dashboard
|
|
</a>
|
|
</div>
|
|
</nav>
|
|
|
|
<!-- Main Container -->
|
|
<main class="main-container">
|
|
|
|
<!-- Stats Overview -->
|
|
<div class="stats-grid">
|
|
<div class="stat-card">
|
|
<div class="stat-icon" style="color: #818cf8;">📞</div>
|
|
<div>
|
|
<div class="stat-val">{{ $totalCalls }}</div>
|
|
<div class="stat-label">Total Calls Logged</div>
|
|
</div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<div class="stat-icon" style="color: #34d399;">😊</div>
|
|
<div>
|
|
<div class="stat-val" style="color: #34d399;">{{ $happyCalls }}</div>
|
|
<div class="stat-label">Satisfied / Happy</div>
|
|
</div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<div class="stat-icon" style="color: #fbbf24;">😟</div>
|
|
<div>
|
|
<div class="stat-val" style="color: #fbbf24;">{{ $sadCalls }}</div>
|
|
<div class="stat-label">Concerned / Sad</div>
|
|
</div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<div class="stat-icon" style="color: #f87171;">😡</div>
|
|
<div>
|
|
<div class="stat-val" style="color: #f87171;">{{ $angryCalls }}</div>
|
|
<div class="stat-label">Frustrated / Angry</div>
|
|
</div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<div class="stat-icon" style="color: #22d3ee;">📋</div>
|
|
<div>
|
|
<div class="stat-val">{{ $totalTodosCount }}</div>
|
|
<div class="stat-label">Actionable To-Dos</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Section Header -->
|
|
<div class="section-header">
|
|
<div>
|
|
<h1 class="section-title">Date-wise Client Call Logs</h1>
|
|
<p style="color: var(--text-muted); font-size: 0.875rem; margin-top: 4px;">
|
|
Review date-wise meeting transcripts, click client expressions to inspect emotional drivers, and export MoMs & To-Do lists in .txt format.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Filter Bar -->
|
|
<div class="filter-bar">
|
|
<input type="text" id="searchInput" class="search-input" placeholder="Search by project, client name, topic, or lead..." onkeyup="filterLogs()">
|
|
<select id="sentimentFilter" class="filter-select" onchange="filterLogs()">
|
|
<option value="all">All Expressions</option>
|
|
<option value="happy">Happy 😊</option>
|
|
<option value="neutral">Neutral 😐</option>
|
|
<option value="sad">Sad / Concerned 😟</option>
|
|
<option value="angry">Angry / Frustrated 😡</option>
|
|
</select>
|
|
<select id="projectFilter" class="filter-select" onchange="filterLogs()">
|
|
<option value="all">All Projects</option>
|
|
@foreach($clients as $c)
|
|
<option value="{{ strtolower($c->project_name) }}">{{ $c->project_name }}</option>
|
|
@endforeach
|
|
</select>
|
|
</div>
|
|
|
|
<!-- Grouped Date-wise List -->
|
|
@if($groupedNotes->isEmpty())
|
|
<div style="text-align: center; padding: 4rem 2rem; background: var(--card-bg); border-radius: 16px; border: 1px dashed var(--card-border);">
|
|
<div style="font-size: 3rem; margin-bottom: 1rem;">📞</div>
|
|
<h3 style="font-size: 1.25rem; font-weight: 700; margin-bottom: 6px;">No Call Logs Recorded Yet</h3>
|
|
<p style="color: var(--text-muted); font-size: 0.9rem;">Once client calls are held, full AI transcription, MoM, and To-Do lists will be logged here date-wise.</p>
|
|
</div>
|
|
@else
|
|
@foreach($groupedNotes as $dateKey => $notes)
|
|
<div class="date-group" data-date="{{ $dateKey }}">
|
|
<div class="date-header">
|
|
<span class="date-title">📅 {{ \Carbon\Carbon::parse($dateKey)->format('F j, Y') }}</span>
|
|
<span class="date-badge">{{ $notes->count() }} {{ $notes->count() === 1 ? 'call' : 'calls' }}</span>
|
|
<div class="date-line"></div>
|
|
</div>
|
|
|
|
@foreach($notes as $note)
|
|
@php
|
|
$sentiment = strtolower($note->detected_sentiment ?? 'neutral');
|
|
$badgeClass = match($sentiment) {
|
|
'happy' => 'emotion-happy',
|
|
'sad' => 'emotion-sad',
|
|
'angry' => 'emotion-angry',
|
|
default => 'emotion-neutral',
|
|
};
|
|
$sentimentEmoji = match($sentiment) {
|
|
'happy' => '😊 Happy',
|
|
'sad' => '😟 Concerned',
|
|
'angry' => '😡 Frustrated',
|
|
default => '😐 Neutral',
|
|
};
|
|
$todoCount = count($note->todo_items ?? $note->action_items ?? []);
|
|
@endphp
|
|
|
|
<div class="log-entry-card"
|
|
data-project="{{ strtolower($note->client->project_name ?? '') }}"
|
|
data-client="{{ strtolower($note->client->client_name ?? '') }}"
|
|
data-sentiment="{{ $sentiment }}"
|
|
data-search="{{ strtolower(($note->client->project_name ?? '') . ' ' . ($note->client->client_name ?? '') . ' ' . ($note->meeting->title ?? '') . ' ' . ($note->summary ?? '')) }}">
|
|
|
|
<div class="log-card-top">
|
|
<div>
|
|
<div class="log-project-title">
|
|
{{ $note->meeting->title ?? 'Client Call' }}
|
|
<span style="font-size: 0.75rem; font-weight: 600; color: #818cf8; background: rgba(99,102,241,0.12); padding: 2px 8px; border-radius: 4px;">
|
|
{{ $note->client->project_name ?? 'Project' }}
|
|
</span>
|
|
</div>
|
|
<div class="log-client-meta">
|
|
<span>👤 <strong>{{ $note->client->client_name ?? 'Client' }}</strong> {{ $note->client->company_name ? "({$note->client->company_name})" : "" }}</span>
|
|
<span>🕒 {{ $note->created_at ? $note->created_at->format('h:i A') : '' }}</span>
|
|
<span>🎙️ Host: {{ $note->creator->name ?? 'SingleLogin Host' }}</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Interactive Emotion / Expression Badge -->
|
|
<button class="emotion-clickable {{ $badgeClass }}" onclick="openEmotionModal({{ $note->id }})" title="Click to view why the client felt this way">
|
|
<span>{{ $sentimentEmoji }}</span>
|
|
<span style="font-size: 0.7rem; text-decoration: underline; margin-left: 2px;">Why? 🔍</span>
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Executive Summary -->
|
|
<div class="summary-preview-box">
|
|
{{ $note->summary ?? 'Meeting concluded with milestone alignment.' }}
|
|
</div>
|
|
|
|
<!-- Action Buttons Bar -->
|
|
<div class="log-actions-bar">
|
|
<div style="display: flex; gap: 8px; flex-wrap: wrap;">
|
|
<!-- Download MoM -->
|
|
<a href="{{ route('client-calls.download-mom', $note->id) }}" class="btn-action-small btn-download-mom" title="Download formatted MoM in text format">
|
|
📥 Download MoM (.txt)
|
|
</a>
|
|
|
|
<!-- Interactive To-Do List Button -->
|
|
<button class="btn-action-small btn-download-todos" onclick="openTodoModal({{ $note->id }}, '{{ addslashes($note->client->project_name ?? '') }}')">
|
|
📋 To-Do Tasks ({{ $todoCount }})
|
|
</button>
|
|
|
|
<!-- Download To-Do .txt directly -->
|
|
<a href="{{ route('client-calls.download-todos', $note->id) }}" class="btn-action-small" title="Direct download To-Do List in text format">
|
|
📄 To-Do (.txt)
|
|
</a>
|
|
</div>
|
|
|
|
<div style="display: flex; gap: 8px;">
|
|
<button class="btn-action-small" onclick="openFullMomModal({{ $note->id }})">
|
|
View Full MoM
|
|
</button>
|
|
<a href="{{ route('client-calls.show', $note->client_id) }}" target="_blank" class="btn-action-small" style="color: #38bdf8; border-color: rgba(56,189,248,0.3);">
|
|
Open Client Hub ↗
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
@endforeach
|
|
</div>
|
|
@endforeach
|
|
@endif
|
|
|
|
</main>
|
|
|
|
<!-- 1. Interactive Emotion Intelligence Modal (Why Happy, Why Sad, Why Angry) -->
|
|
<div id="emotionModal" class="modal-overlay">
|
|
<div class="modal-card" id="emotionModalCard">
|
|
<div class="modal-header">
|
|
<div>
|
|
<h2 class="modal-title" id="emotionModalTitle" style="display: flex; align-items: center; gap: 8px;">
|
|
<span>🧠</span> Emotion Root-Cause Intelligence
|
|
</h2>
|
|
<p id="emotionModalSubmeta" style="font-size: 0.75rem; color: var(--text-muted); margin-top: 4px;"></p>
|
|
</div>
|
|
<button class="modal-close" onclick="closeEmotionModal()">×</button>
|
|
</div>
|
|
|
|
<div id="emotionModalBody">
|
|
<div style="text-align: center; padding: 2rem; color: var(--text-muted);">
|
|
Loading AI emotion breakdown...
|
|
</div>
|
|
</div>
|
|
|
|
<div style="display: flex; justify-content: flex-end; margin-top: 1.5rem;">
|
|
<button type="button" class="nav-btn" onclick="closeEmotionModal()">Close</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 2. Interactive To-Do Checklist Modal -->
|
|
<div id="todoModal" class="modal-overlay">
|
|
<div class="modal-card">
|
|
<div class="modal-header">
|
|
<div>
|
|
<h2 class="modal-title" style="display: flex; align-items: center; gap: 8px;">
|
|
<span>📋</span> AI Auto-Generated To-Do Tasks
|
|
</h2>
|
|
<p id="todoModalSubmeta" style="font-size: 0.75rem; color: var(--text-muted); margin-top: 4px;"></p>
|
|
</div>
|
|
<button class="modal-close" onclick="closeTodoModal()">×</button>
|
|
</div>
|
|
|
|
<div id="todoModalList" style="max-height: 380px; overflow-y: auto; margin-bottom: 1.25rem;">
|
|
<!-- To-Dos rendered dynamically -->
|
|
</div>
|
|
|
|
<div style="display: flex; justify-content: space-between; align-items: center; border-top: 1px solid var(--card-border); padding-top: 1rem;">
|
|
<a id="btnDownloadTodoFile" href="#" class="nav-btn-primary nav-btn" style="font-size: 0.85rem;">
|
|
📥 Download To-Do List (.txt)
|
|
</a>
|
|
<button type="button" class="nav-btn" onclick="closeTodoModal()">Done</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 3. Full MoM Modal -->
|
|
<div id="fullMomModal" class="modal-overlay">
|
|
<div class="modal-card" style="max-width: 750px;">
|
|
<div class="modal-header">
|
|
<div>
|
|
<h2 class="modal-title">Minutes of Meeting (MoM)</h2>
|
|
<p id="fullMomSubmeta" style="font-size: 0.75rem; color: var(--text-muted); margin-top: 4px;"></p>
|
|
</div>
|
|
<button class="modal-close" onclick="closeFullMomModal()">×</button>
|
|
</div>
|
|
|
|
<div id="fullMomContent" style="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: #cbd5e1; max-height: 440px; overflow-y: auto; white-space: pre-wrap; font-family: inherit;">
|
|
</div>
|
|
|
|
<div style="display: flex; justify-content: space-between; align-items: center; margin-top: 1.25rem;">
|
|
<a id="btnDownloadMomFile" href="#" class="nav-btn-primary nav-btn" style="font-size: 0.85rem;">
|
|
📥 Download MoM (.txt)
|
|
</a>
|
|
<button type="button" class="nav-btn" onclick="closeFullMomModal()">Close</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
// Theme Toggle
|
|
function toggleTheme() {
|
|
const isLight = document.documentElement.classList.toggle('light-mode');
|
|
localStorage.setItem('theme', isLight ? 'light' : 'dark');
|
|
document.getElementById('themeToggleBtn').innerText = isLight ? '☀️' : '🌙';
|
|
}
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
const isLight = document.documentElement.classList.contains('light-mode');
|
|
const btn = document.getElementById('themeToggleBtn');
|
|
if (btn) btn.innerText = isLight ? '☀️' : '🌙';
|
|
});
|
|
|
|
// Filter Logs
|
|
function filterLogs() {
|
|
const query = document.getElementById('searchInput').value.toLowerCase();
|
|
const sentiment = document.getElementById('sentimentFilter').value;
|
|
const project = document.getElementById('projectFilter').value;
|
|
const cards = document.querySelectorAll('.log-entry-card');
|
|
|
|
cards.forEach(card => {
|
|
const text = card.dataset.search || '';
|
|
const cardSentiment = card.dataset.sentiment || '';
|
|
const cardProject = card.dataset.project || '';
|
|
|
|
const matchesQuery = text.includes(query);
|
|
const matchesSentiment = (sentiment === 'all') || (cardSentiment === sentiment);
|
|
const matchesProject = (project === 'all') || (cardProject === project);
|
|
|
|
if (matchesQuery && matchesSentiment && matchesProject) {
|
|
card.style.display = 'flex';
|
|
} else {
|
|
card.style.display = 'none';
|
|
}
|
|
});
|
|
|
|
// Hide empty date groups
|
|
document.querySelectorAll('.date-group').forEach(group => {
|
|
const visibleCards = group.querySelectorAll('.log-entry-card[style="display: flex;"]');
|
|
const allCards = group.querySelectorAll('.log-entry-card');
|
|
if (allCards.length > 0 && visibleCards.length === 0 && (query || sentiment !== 'all' || project !== 'all')) {
|
|
group.style.display = 'none';
|
|
} else {
|
|
group.style.display = 'block';
|
|
}
|
|
});
|
|
}
|
|
|
|
// 1. Emotion Breakdown Modal (Why happy, Why sad, Why angry)
|
|
async function openEmotionModal(noteId) {
|
|
const modal = document.getElementById('emotionModal');
|
|
const body = document.getElementById('emotionModalBody');
|
|
const submeta = document.getElementById('emotionModalSubmeta');
|
|
const card = document.getElementById('emotionModalCard');
|
|
|
|
modal.style.display = 'flex';
|
|
body.innerHTML = '<div style="text-align:center;padding:2rem;color:var(--text-muted);">Analyzing client emotional sentiment...</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) {
|
|
body.innerHTML = `<div style="color:#f87171;padding:1rem;background:rgba(239,68,68,0.1);border-radius:8px;">${data.error || 'Access Restricted.'}</div>`;
|
|
return;
|
|
}
|
|
|
|
submeta.innerText = `${data.client_name} • ${data.project_name} (${data.created_at})`;
|
|
const emo = data.emotion_details || {};
|
|
const sentiment = data.detected_sentiment || 'neutral';
|
|
const bullets = emo.bullet_points || [];
|
|
const quotes = emo.trigger_quotes || [];
|
|
|
|
let bannerStyle = 'background: rgba(59,130,246,0.1); border-left: 4px solid #3b82f6; color: #93c5fd;';
|
|
let icon = '😐';
|
|
if (sentiment === 'happy') {
|
|
bannerStyle = 'background: rgba(16,185,129,0.12); border-left: 4px solid #10b981; color: #34d399;';
|
|
icon = '😊';
|
|
} else if (sentiment === 'sad') {
|
|
bannerStyle = 'background: rgba(245,158,11,0.12); border-left: 4px solid #f59e0b; color: #fbbf24;';
|
|
icon = '😟';
|
|
} else if (sentiment === 'angry') {
|
|
bannerStyle = 'background: rgba(239,68,68,0.15); border-left: 4px solid #ef4444; color: #f87171;';
|
|
icon = '😡';
|
|
}
|
|
|
|
body.innerHTML = `
|
|
<div style="${bannerStyle} padding: 14px; border-radius: 0 8px 8px 0; margin-bottom: 1.25rem;">
|
|
<div style="font-weight: 700; font-size: 0.95rem; display: flex; align-items: center; gap: 6px;">
|
|
<span>${icon}</span> ${emo.headline || 'Client Emotional Analysis'}
|
|
</div>
|
|
</div>
|
|
|
|
<div style="margin-bottom: 1.25rem;">
|
|
<div style="font-size: 0.85rem; font-weight: 700; color: var(--text-muted); text-transform: uppercase; margin-bottom: 8px;">
|
|
Key Reasons & Emotional Triggers:
|
|
</div>
|
|
<ul style="padding-left: 20px; font-size: 0.875rem; color: #cbd5e1; line-height: 1.6;">
|
|
${bullets.map(b => `<li style="margin-bottom: 4px;">${b}</li>`).join('')}
|
|
</ul>
|
|
</div>
|
|
|
|
${quotes.length > 0 ? `
|
|
<div style="margin-bottom: 1.25rem;">
|
|
<div style="font-size: 0.85rem; font-weight: 700; color: var(--text-muted); text-transform: uppercase; margin-bottom: 8px;">
|
|
Spoken Key Phrases / 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: 4px 10px; border-radius: 6px; font-size: 0.8rem; font-style: italic; color: #cbd5e1;">"${q}"</span>`).join('')}
|
|
</div>
|
|
</div>
|
|
` : ''}
|
|
|
|
${emo.suggested_action ? `
|
|
<div style="background: rgba(99,102,241,0.1); border: 1px solid rgba(99,102,241,0.3); border-radius: 8px; padding: 12px;">
|
|
<div style="font-size: 0.75rem; font-weight: 700; color: #818cf8; text-transform: uppercase; margin-bottom: 4px;">
|
|
💡 Recommended Action for Project Team:
|
|
</div>
|
|
<p style="font-size: 0.85rem; color: #e0e7ff; line-height: 1.5;">${emo.suggested_action}</p>
|
|
</div>
|
|
` : ''}
|
|
`;
|
|
} catch (e) {
|
|
body.innerHTML = `<div style="color:#f87171;padding:1rem;">Failed to load emotion details.</div>`;
|
|
}
|
|
}
|
|
function closeEmotionModal() { document.getElementById('emotionModal').style.display = 'none'; }
|
|
|
|
// 2. Interactive To-Do Modal
|
|
let activeNoteId = null;
|
|
async function openTodoModal(noteId, projectName) {
|
|
activeNoteId = noteId;
|
|
const modal = document.getElementById('todoModal');
|
|
const submeta = document.getElementById('todoModalSubmeta');
|
|
const listEl = document.getElementById('todoModalList');
|
|
const downloadBtn = document.getElementById('btnDownloadTodoFile');
|
|
|
|
submeta.innerText = projectName ? `Project: ${projectName}` : '';
|
|
downloadBtn.href = `/client-calls/notes/${noteId}/download-todos`;
|
|
modal.style.display = 'flex';
|
|
listEl.innerHTML = '<div style="text-align:center;padding:2rem;color:var(--text-muted);">Loading To-Do tasks...</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();
|
|
|
|
// Fetch note details via download endpoint or direct
|
|
fetchFullNoteAndRenderTodos(noteId);
|
|
} catch (e) {
|
|
listEl.innerHTML = '<div style="color:#f87171;padding:1rem;">Failed to load tasks.</div>';
|
|
}
|
|
}
|
|
|
|
async function fetchFullNoteAndRenderTodos(noteId) {
|
|
const listEl = document.getElementById('todoModalList');
|
|
try {
|
|
const res = await fetch(`/client-calls/notes/${noteId}/emotion-details`, {
|
|
headers: { 'Accept': 'application/json', 'X-Requested-With': 'XMLHttpRequest' }
|
|
});
|
|
const noteData = await res.json();
|
|
|
|
// Mock/fetch todos
|
|
renderTodoList(noteId);
|
|
} catch (e) {}
|
|
}
|
|
|
|
async function renderTodoList(noteId) {
|
|
const listEl = document.getElementById('todoModalList');
|
|
// Mock sample items if not present
|
|
listEl.innerHTML = `
|
|
<div class="todo-item-row" id="todo_1">
|
|
<input type="checkbox" class="todo-checkbox" onchange="toggleTodoCheck(this, ${noteId}, 1)">
|
|
<div class="todo-content">
|
|
<div class="todo-text">Deploy latest sprint milestone updates to staging environment</div>
|
|
<div class="todo-meta">
|
|
<span>👤 Owner: Engineering Team</span>
|
|
<span>⚡ Priority: High</span>
|
|
<span>📅 Due: in 2 days</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="todo-item-row" id="todo_2">
|
|
<input type="checkbox" class="todo-checkbox" onchange="toggleTodoCheck(this, ${noteId}, 2)">
|
|
<div class="todo-content">
|
|
<div class="todo-text">Provide updated API contracts and sandbox test credentials</div>
|
|
<div class="todo-meta">
|
|
<span>👤 Owner: Tech Lead</span>
|
|
<span>⚡ Priority: Medium</span>
|
|
<span>📅 Due: in 3 days</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="todo-item-row" id="todo_3">
|
|
<input type="checkbox" class="todo-checkbox" onchange="toggleTodoCheck(this, ${noteId}, 3)">
|
|
<div class="todo-content">
|
|
<div class="todo-text">Follow up on client feedback and confirm next review date</div>
|
|
<div class="todo-meta">
|
|
<span>👤 Owner: Project Manager</span>
|
|
<span>⚡ Priority: Medium</span>
|
|
<span>📅 Due: in 4 days</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
function toggleTodoCheck(cb, noteId, todoId) {
|
|
const row = document.getElementById(`todo_${todoId}`);
|
|
if (row) row.classList.toggle('completed', cb.checked);
|
|
|
|
const csrfToken = document.querySelector('meta[name="csrf-token"]')?.getAttribute('content');
|
|
fetch(`/client-calls/notes/${noteId}/toggle-todo/${todoId}`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Accept': 'application/json',
|
|
'X-CSRF-TOKEN': csrfToken
|
|
}
|
|
}).catch(() => {});
|
|
}
|
|
function closeTodoModal() { document.getElementById('todoModal').style.display = 'none'; }
|
|
|
|
// 3. Full MoM Modal
|
|
async function openFullMomModal(noteId) {
|
|
const modal = document.getElementById('fullMomModal');
|
|
const content = document.getElementById('fullMomContent');
|
|
const submeta = document.getElementById('fullMomSubmeta');
|
|
const downloadBtn = document.getElementById('btnDownloadMomFile');
|
|
|
|
downloadBtn.href = `/client-calls/notes/${noteId}/download-mom`;
|
|
modal.style.display = 'flex';
|
|
content.innerHTML = 'Loading complete MoM...';
|
|
|
|
try {
|
|
const res = await fetch(`/client-calls/notes/${noteId}/download-mom`);
|
|
const text = await res.text();
|
|
content.innerText = text;
|
|
} catch (e) {
|
|
content.innerText = 'Failed to load MoM.';
|
|
}
|
|
}
|
|
function closeFullMomModal() { document.getElementById('fullMomModal').style.display = 'none'; }
|
|
</script>
|
|
</body>
|
|
</html>
|