Files
Kent-logistics-Laravel/resources/views/admin/chat_window.blade.php

1046 lines
30 KiB
PHP
Raw Normal View History

2025-12-15 11:03:30 +05:30
@extends('admin.layouts.app')
@section('page-title', 'Chat With ' . ($ticket->user->customer_name ?? $ticket->user->name))
@section('content')
<style>
2025-12-19 11:00:34 +05:30
:root {
--primary-gradient: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
--primary-soft: rgba(102, 126, 234, 0.12);
--message-admin: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
--message-user: #ffffff;
--bg-pattern: #0f172a;
--border-radius-lg: 18px;
--border-radius-sm: 12px;
--shadow-soft: 0 18px 45px rgba(15, 23, 42, 0.4);
--shadow-chip: 0 4px 12px rgba(15, 23, 42, 0.35);
}
body {
background: radial-gradient(circle at top, #1f2937 0, #020617 55%);
}
.chat-container {
max-width: 1180px;
margin: 0 auto;
padding: 20px;
}
/* glass card wrapper */
.chat-shell {
border-radius: 26px;
overflow: hidden;
background: radial-gradient(circle at top left, rgba(148, 163, 184, 0.15), rgba(15, 23, 42, 0.95));
box-shadow: var(--shadow-soft);
border: 1px solid rgba(148, 163, 184, 0.35);
backdrop-filter: blur(22px);
}
.chat-header {
background: radial-gradient(circle at top left, #4f46e5 0%, #4338ca 35%, #0f172a 100%);
padding: 18px 26px;
color: #e5e7eb;
position: relative;
overflow: hidden;
}
.chat-header::before {
content: '';
position: absolute;
inset: -40%;
background:
radial-gradient(circle at 0% 0%, rgba(248, 250, 252, 0.15), transparent 55%),
radial-gradient(circle at 80% 0%, rgba(248, 250, 252, 0.12), transparent 55%);
opacity: 0.5;
pointer-events: none;
}
.chat-header-content {
display: flex;
align-items: center;
justify-content: space-between;
position: relative;
z-index: 1;
}
.user-info {
display: flex;
align-items: center;
gap: 14px;
}
.user-avatar {
width: 50px;
height: 50px;
border-radius: 20px;
background: radial-gradient(circle at 0 0, #f97316 0, #ec4899 50%, #6366f1 100%);
display: flex;
align-items: center;
justify-content: center;
font-size: 22px;
font-weight: 700;
color: #f9fafb;
border: 2px solid rgba(248, 250, 252, 0.3);
box-shadow: 0 12px 30px rgba(15, 23, 42, 0.7);
}
.user-details h4 {
margin: 0;
font-size: 18px;
font-weight: 700;
color: #f9fafb;
display: flex;
align-items: center;
gap: 8px;
}
.user-details h4 span {
font-size: 11px;
padding: 2px 8px;
border-radius: 999px;
background: rgba(15, 23, 42, 0.55);
color: #e5e7eb;
text-transform: uppercase;
letter-spacing: 0.06em;
}
.user-details p {
margin: 4px 0 0;
font-size: 13px;
color: #c7d2fe;
opacity: 0.9;
}
.header-actions {
display: flex;
align-items: center;
gap: 12px;
}
.status-badge {
padding: 6px 14px;
border-radius: 999px;
font-weight: 600;
font-size: 11px;
text-transform: uppercase;
letter-spacing: 0.08em;
border: 1px solid rgba(15, 23, 42, 0.5);
display: inline-flex;
align-items: center;
gap: 6px;
box-shadow: 0 8px 20px rgba(15, 23, 42, 0.7);
}
.status-badge.open {
background: radial-gradient(circle at 0 0, #22c55e 0, #16a34a 60%, #15803d 100%);
color: #ecfdf3;
}
.status-badge.closed {
background: radial-gradient(circle at 0 0, #fb7185 0, #ef4444 60%, #b91c1c 100%);
color: #fef2f2;
}
.back-btn {
background: rgba(15, 23, 42, 0.6);
border: 1px solid rgba(148, 163, 184, 0.6);
color: #e5e7eb;
padding: 7px 18px;
border-radius: 999px;
font-weight: 500;
font-size: 12px;
letter-spacing: 0.06em;
text-transform: uppercase;
display: inline-flex;
align-items: center;
gap: 6px;
cursor: pointer;
transition: all 0.2s ease;
text-decoration: none;
box-shadow: 0 8px 20px rgba(15, 23, 42, 0.7);
backdrop-filter: blur(10px);
}
.back-btn svg {
width: 16px;
height: 16px;
}
.back-btn:hover {
background: rgba(15, 23, 42, 0.9);
transform: translateX(-3px);
border-color: #e5e7eb;
}
.chat-box {
height: calc(100vh - 320px);
min-height: 420px;
max-height: 620px;
overflow-y: auto;
background: radial-gradient(circle at top left, rgba(15, 23, 42, 0.9), rgba(15, 23, 42, 1));
padding: 20px 22px;
position: relative;
}
.chat-box::before {
content: '';
position: absolute;
inset: 0;
background:
radial-gradient(circle at 15% 25%, rgba(56, 189, 248, 0.12) 0, transparent 55%),
radial-gradient(circle at 80% 80%, rgba(129, 140, 248, 0.1) 0, transparent 50%);
pointer-events: none;
opacity: 0.9;
}
.chat-box-inner {
position: relative;
z-index: 1;
}
.chat-box::-webkit-scrollbar {
width: 8px;
}
.chat-box::-webkit-scrollbar-track {
background: rgba(15, 23, 42, 0.9);
border-radius: 999px;
}
.chat-box::-webkit-scrollbar-thumb {
background: linear-gradient(180deg, #4f46e5, #22d3ee);
border-radius: 999px;
}
.message {
max-width: 68%;
padding: 10px 14px;
margin-bottom: 14px;
font-size: 14px;
line-height: 1.55;
position: relative;
word-wrap: break-word;
border-radius: 14px;
box-shadow: 0 12px 30px rgba(15, 23, 42, 0.7);
animation: messageSlideIn 0.22s ease-out;
}
@keyframes messageSlideIn {
from { opacity: 0; transform: translateY(6px); }
to { opacity: 1; transform: translateY(0); }
}
.message.admin {
margin-left: auto;
background: radial-gradient(circle at 0 0, #4f46e5 0, #6366f1 40%, #22d3ee 100%);
color: #eef2ff;
border-radius: 14px 14px 4px 14px;
}
.message.user {
margin-right: auto;
background: rgba(15, 23, 42, 0.9);
border-radius: 14px 14px 14px 4px;
border: 1px solid rgba(148, 163, 184, 0.8);
color: #e5e7eb;
}
.message img {
max-width: 210px;
border-radius: 10px;
margin-top: 8px;
display: block;
box-shadow: 0 10px 30px rgba(15, 23, 42, 0.8);
cursor: pointer;
transition: transform 0.25s ease, box-shadow 0.25s ease;
}
.message img:hover {
transform: translateY(-2px) scale(1.01);
box-shadow: 0 16px 40px rgba(15, 23, 42, 1);
}
/* File preview container */
.file-preview {
margin-top: 8px;
display: flex;
flex-direction: column;
gap: 6px;
}
.file-item {
display: flex;
align-items: center;
gap: 10px;
padding: 8px 12px;
background: rgba(15, 23, 42, 0.55);
border-radius: 10px;
border: 1px solid rgba(148, 163, 184, 0.6);
transition: all 0.2s ease;
text-decoration: none;
color: inherit;
max-width: 250px;
}
.message.admin .file-item {
background: rgba(15, 23, 42, 0.5);
border-color: rgba(226, 232, 240, 0.7);
}
.message.user .file-item {
background: rgba(15, 23, 42, 0.7);
}
.file-item:hover {
transform: translateY(-1px);
background: rgba(15, 23, 42, 0.8);
box-shadow: 0 4px 12px rgba(15, 23, 42, 0.4);
}
.file-icon {
width: 32px;
height: 32px;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
font-size: 14px;
}
.image-file .file-icon {
background: linear-gradient(135deg, #8b5cf6, #ec4899);
color: white;
}
.pdf-file .file-icon {
background: linear-gradient(135deg, #ef4444, #dc2626);
color: white;
}
.video-file .file-icon {
background: linear-gradient(135deg, #3b82f6, #1d4ed8);
color: white;
}
.document-file .file-icon {
background: linear-gradient(135deg, #10b981, #059669);
color: white;
}
.other-file .file-icon {
background: linear-gradient(135deg, #6b7280, #4b5563);
color: white;
}
.file-info {
flex: 1;
min-width: 0;
}
.file-name {
font-size: 12px;
font-weight: 500;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
margin-bottom: 2px;
}
.file-size {
font-size: 10px;
opacity: 0.7;
color: #9ca3af;
}
.file-download-btn {
background: rgba(255, 255, 255, 0.1);
border: none;
width: 24px;
height: 24px;
border-radius: 6px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.2s ease;
flex-shrink: 0;
}
.file-download-btn svg {
width: 12px;
height: 12px;
color: #cbd5e1;
}
.file-download-btn:hover {
background: rgba(255, 255, 255, 0.2);
}
.message small {
font-size: 10px;
opacity: 0.7;
margin-top: 6px;
display: block;
text-align: right;
}
.message.user small {
text-align: left;
color: #9ca3af;
}
.message.admin small {
color: #e0f2fe;
}
.date-divider {
text-align: center;
margin: 18px 0;
position: relative;
}
.date-divider::before,
.date-divider::after {
content: '';
position: absolute;
top: 50%;
width: 40%;
height: 1px;
background: radial-gradient(circle, rgba(148, 163, 184, 0.6), transparent);
}
.date-divider::before {
left: 0;
}
.date-divider::after {
right: 0;
}
.date-divider span {
background: rgba(15, 23, 42, 0.95);
padding: 4px 12px;
border-radius: 999px;
font-size: 11px;
font-weight: 600;
color: #9ca3af;
border: 1px solid rgba(55, 65, 81, 0.8);
box-shadow: 0 6px 18px rgba(15, 23, 42, 0.9);
}
.chat-input-container {
background: radial-gradient(circle at top, rgba(15, 23, 42, 0.95), #020617);
padding: 14px 18px 18px;
border-top: 1px solid rgba(31, 41, 55, 0.9);
}
.chat-input-wrapper {
display: flex;
align-items: center;
gap: 10px;
background: rgba(15, 23, 42, 0.95);
border-radius: 999px;
padding: 4px 4px 4px 18px;
border: 1px solid rgba(55, 65, 81, 0.9);
transition: all 0.18s ease;
}
.chat-input-wrapper:focus-within {
border-color: #6366f1;
box-shadow: 0 0 0 1px rgba(129, 140, 248, 0.9);
}
.chat-input-wrapper input[type="text"] {
flex: 1;
border: none;
background: transparent;
font-size: 14px;
padding: 8px 0;
outline: none;
color: #e5e7eb;
}
.chat-input-wrapper input[type="text"]::placeholder {
color: #6b7280;
}
.file-upload-container {
position: relative;
display: flex;
align-items: center;
gap: 8px;
}
.file-upload-btn {
position: relative;
cursor: pointer;
}
.file-upload-btn input[type="file"] {
position: absolute;
opacity: 0;
inset: 0;
cursor: pointer;
}
.file-upload-btn label {
display: flex;
align-items: center;
justify-content: center;
width: 40px;
height: 40px;
border-radius: 999px;
background: radial-gradient(circle at 0 0, #22c55e 0, #22c55e 40%, #16a34a 100%);
cursor: pointer;
transition: all 0.2s ease;
box-shadow: 0 10px 25px rgba(22, 163, 74, 0.7);
}
.file-upload-btn label svg {
width: 18px;
height: 18px;
color: #ecfdf5;
}
.file-upload-btn label:hover {
transform: translateY(-1px) scale(1.03);
filter: brightness(1.05);
}
.file-name-display {
background: rgba(15, 23, 42, 0.7);
border: 1px solid rgba(55, 65, 81, 0.8);
border-radius: 8px;
padding: 6px 10px;
font-size: 12px;
color: #9ca3af;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 150px;
display: none;
}
.clear-file-btn {
background: none;
border: none;
color: #ef4444;
cursor: pointer;
padding: 4px;
border-radius: 4px;
display: none;
transition: all 0.2s ease;
}
.clear-file-btn:hover {
background: rgba(239, 68, 68, 0.1);
}
.clear-file-btn svg {
width: 14px;
height: 14px;
}
.send-btn {
background: radial-gradient(circle at 0 0, #38bdf8 0, #6366f1 50%, #4f46e5 100%);
border: none;
width: 44px;
height: 44px;
border-radius: 999px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.2s ease;
box-shadow: 0 12px 28px rgba(37, 99, 235, 0.85);
}
.send-btn svg {
width: 18px;
height: 18px;
color: #eff6ff;
}
.send-btn:hover {
transform: translateY(-1px) scale(1.04);
box-shadow: 0 18px 40px rgba(37, 99, 235, 1);
}
.send-btn:active {
transform: scale(0.96);
box-shadow: 0 8px 22px rgba(30, 64, 175, 0.9);
}
.typing-indicator {
display: none;
padding: 8px 12px;
background: rgba(15, 23, 42, 0.95);
border-radius: 999px;
max-width: 70px;
margin-bottom: 10px;
box-shadow: 0 10px 25px rgba(15, 23, 42, 0.9);
}
.typing-indicator span {
height: 6px;
width: 6px;
background: #a5b4fc;
border-radius: 999px;
display: inline-block;
margin: 0 2px;
animation: typing 1.2s infinite;
}
.typing-indicator span:nth-child(2) {
animation-delay: 0.15s;
}
.typing-indicator span:nth-child(3) {
animation-delay: 0.3s;
}
@keyframes typing {
0%, 60%, 100% {
transform: translateY(0);
opacity: 0.5;
2025-12-15 11:03:30 +05:30
}
2025-12-19 11:00:34 +05:30
30% {
transform: translateY(-6px);
opacity: 1;
}
}
@media (max-width: 768px) {
.chat-container {
padding: 10px;
}
.chat-shell {
border-radius: 20px;
}
.chat-header-content {
flex-direction: column;
align-items: flex-start;
gap: 10px;
}
.user-info {
width: 100%;
2025-12-15 11:03:30 +05:30
}
2025-12-19 11:00:34 +05:30
.header-actions {
width: 100%;
justify-content: space-between;
2025-12-15 11:03:30 +05:30
}
2025-12-19 11:00:34 +05:30
.back-btn {
padding-inline: 12px;
2025-12-15 11:03:30 +05:30
}
2025-12-19 11:00:34 +05:30
.message {
max-width: 85%;
}
.chat-box {
height: calc(100vh - 360px);
2025-12-15 11:03:30 +05:30
}
2025-12-19 11:00:34 +05:30
.file-name-display {
max-width: 100px;
}
}
2025-12-15 11:03:30 +05:30
</style>
2025-12-19 11:00:34 +05:30
<div class="chat-container">
<div class="chat-shell">
<div class="chat-header">
<div class="chat-header-content">
<div class="user-info">
<div class="user-avatar">
{{ strtoupper(substr(($ticket->user->customer_name ?? $ticket->user->name), 0, 1)) }}
</div>
<div class="user-details">
<h4>
{{ $ticket->user->customer_name ?? $ticket->user->name }}
<span>Online</span>
</h4>
<p>Ticket #{{ $ticket->id }}</p>
</div>
</div>
<div class="header-actions">
<span class="status-badge {{ $ticket->status === 'open' ? 'open' : 'closed' }}">
{{ ucfirst($ticket->status) }}
</span>
2025-12-15 11:03:30 +05:30
2025-12-19 11:00:34 +05:30
<a href="{{ route('admin.chat_support') }}" class="back-btn">
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"/>
</svg>
Back
</a>
</div>
</div>
</div>
2025-12-16 10:19:54 +05:30
2025-12-19 11:00:34 +05:30
{{-- Messages --}}
<div id="chatBox" class="chat-box">
<div class="chat-box-inner">
@foreach($messages as $msg)
<div class="message {{ $msg->sender_type === 'App\\Models\\Admin' ? 'admin' : 'user' }}">
@if($msg->message)
<div>{{ $msg->message }}</div>
@endif
2025-12-16 10:19:54 +05:30
2025-12-19 11:00:34 +05:30
@if($msg->file_path)
@php
$isImage = Str::startsWith($msg->file_type, 'image');
$isVideo = Str::startsWith($msg->file_type, 'video');
$isPdf = Str::endsWith($msg->file_path, '.pdf');
$isDocument = in_array(Str::lower(Str::afterLast($msg->file_path, '.')), ['doc', 'docx', 'txt']);
$fileName = basename($msg->file_path);
// Get file size from storage if possible
$fileSize = 'N/A';
try {
$fullPath = storage_path('app/public/' . $msg->file_path);
if (file_exists($fullPath)) {
$size = filesize($fullPath);
if ($size < 1024) {
$fileSize = $size . ' B';
} elseif ($size < 1048576) {
$fileSize = round($size / 1024, 1) . ' KB';
} else {
$fileSize = round($size / 1048576, 1) . ' MB';
}
}
} catch (Exception $e) {
$fileSize = 'Unknown';
}
// Determine file class
if ($isImage) {
$fileClass = 'image-file';
$fileIcon = '🖼️';
} elseif ($isVideo) {
$fileClass = 'video-file';
$fileIcon = '🎬';
} elseif ($isPdf) {
$fileClass = 'pdf-file';
$fileIcon = '📄';
} elseif ($isDocument) {
$fileClass = 'document-file';
$fileIcon = '📝';
} else {
$fileClass = 'other-file';
$fileIcon = '📎';
}
@endphp
2025-12-15 11:03:30 +05:30
2025-12-19 11:00:34 +05:30
<div class="file-preview">
@if($isImage)
<img src="{{ asset('storage/'.$msg->file_path) }}"
style="max-width:210px;"
class="rounded mt-2"
alt="{{ $fileName }}">
@elseif($isVideo)
<video src="{{ asset('storage/'.$msg->file_path) }}"
controls
class="mt-2 rounded"
style="max-width:200px;">
Your browser does not support the video tag.
</video>
@endif
<a href="{{ asset('storage/'.$msg->file_path) }}"
target="_blank"
class="file-item {{ $fileClass }}"
title="{{ $fileName }}">
<div class="file-icon">
{{ $fileIcon }}
</div>
<div class="file-info">
<div class="file-name">{{ $fileName }}</div>
<div class="file-size">{{ $fileSize }}</div>
</div>
<div class="file-download-btn">
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
</svg>
</div>
</a>
</div>
@endif
2025-12-16 10:19:54 +05:30
2025-12-19 11:00:34 +05:30
<small class="mt-2">
{{ $msg->created_at->format('d M h:i A') }}
</small>
</div>
@endforeach
2025-12-15 11:03:30 +05:30
2025-12-19 11:00:34 +05:30
<div class="typing-indicator" id="typingIndicator">
<span></span>
<span></span>
<span></span>
</div>
2025-12-15 11:03:30 +05:30
</div>
2025-12-19 11:00:34 +05:30
</div>
2025-12-15 11:03:30 +05:30
2025-12-19 11:00:34 +05:30
{{-- Input --}}
<div class="chat-input-container">
<div class="chat-input-wrapper">
<input type="text" id="messageInput" placeholder="Type a reply…" autocomplete="off">
<div class="file-upload-container">
<div class="file-name-display" id="fileNameDisplay"></div>
<button class="clear-file-btn" id="clearFileBtn" title="Remove file">
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
</svg>
</button>
<div class="file-upload-btn">
<input type="file" id="fileInput">
<label for="fileInput" title="Attach file">
<svg xmlns="http://www.w3.org/2000/svg" fill="none"
viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round"
stroke-width="2"
d="M15.172 7l-6.586 6.586a2 2 0 102.828 2.828l6.414-6.586a4 4 0 00-5.656-5.656l-6.415 6.585a6 6 0 108.486 8.486L20.5 13" />
</svg>
</label>
</div>
</div>
2025-12-15 11:03:30 +05:30
2025-12-19 11:00:34 +05:30
<button class="send-btn" id="sendBtn">
<svg xmlns="http://www.w3.org/2000/svg" fill="none"
viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round"
stroke-width="2"
d="M12 19l9 2-9-18-9 18 9-2zm0 0v-8" />
</svg>
</button>
2025-12-15 11:03:30 +05:30
</div>
</div>
</div>
</div>
@endsection
@section('scripts')
<script>
2025-12-19 11:00:34 +05:30
const CURRENT_ADMIN_ID = {{ auth('admin')->id() }};
2025-12-15 11:03:30 +05:30
2025-12-19 11:00:34 +05:30
// ✅ Make current admin ID available to JS
2025-12-15 11:03:30 +05:30
console.log("CHAT WINDOW: script loaded");
// -------------------------------
// WAIT FOR ECHO READY
// -------------------------------
function waitForEcho(callback, retries = 40) {
if (window.Echo) {
console.log("%c[ECHO] Ready!", "color: green; font-weight: bold;", window.Echo);
return callback();
}
console.warn("[ECHO] Not ready. Retrying...");
if (retries <= 0) {
console.error("[ECHO] FAILED to initialize after retry limit");
return;
}
setTimeout(() => waitForEcho(callback, retries - 1), 200);
}
// Scroll chat down
function scrollToBottom() {
const el = document.getElementById("chatBox");
if (el) el.scrollTop = el.scrollHeight;
}
scrollToBottom();
// -------------------------------
2025-12-19 11:00:34 +05:30
// FILE UPLOAD HANDLING
// -------------------------------
const fileInput = document.getElementById('fileInput');
const fileNameDisplay = document.getElementById('fileNameDisplay');
const clearFileBtn = document.getElementById('clearFileBtn');
fileInput.addEventListener('change', function(e) {
if (this.files.length > 0) {
const file = this.files[0];
const fileName = file.name;
const fileSize = formatFileSize(file.size);
fileNameDisplay.textContent = `${fileName} (${fileSize})`;
fileNameDisplay.style.display = 'block';
clearFileBtn.style.display = 'block';
}
});
clearFileBtn.addEventListener('click', function() {
fileInput.value = '';
fileNameDisplay.style.display = 'none';
clearFileBtn.style.display = 'none';
});
function formatFileSize(bytes) {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i];
}
function getFileIcon(fileType, fileName) {
if (fileType.startsWith('image/')) {
return '🖼️';
} else if (fileType.startsWith('video/')) {
return '🎬';
} else if (fileName.endsWith('.pdf')) {
return '📄';
} else if (fileName.match(/\.(doc|docx|txt)$/i)) {
return '📝';
} else {
return '📎';
}
}
function getFileClass(fileType, fileName) {
if (fileType.startsWith('image/')) {
return 'image-file';
} else if (fileType.startsWith('video/')) {
return 'video-file';
} else if (fileName.endsWith('.pdf')) {
return 'pdf-file';
} else if (fileName.match(/\.(doc|docx|txt)$/i)) {
return 'document-file';
} else {
return 'other-file';
}
}
// -------------------------------
// SEND MESSAGE
2025-12-15 11:03:30 +05:30
// -------------------------------
document.getElementById("sendBtn").addEventListener("click", function () {
console.log("[SEND] Attempting to send message...");
let msg = document.getElementById("messageInput").value;
let file = document.getElementById("fileInput").files[0];
if (!msg.trim() && !file) {
alert("Please type something or upload a file.");
return;
}
let formData = new FormData();
formData.append("message", msg);
if (file) formData.append("file", file);
fetch("{{ route('admin.chat.send', $ticket->id) }}", {
method: "POST",
headers: { "X-CSRF-TOKEN": "{{ csrf_token() }}" },
body: formData
})
.then(res => res.json())
.then((response) => {
console.log("[SEND] Message sent:", response);
document.getElementById("messageInput").value = "";
document.getElementById("fileInput").value = "";
2025-12-19 11:00:34 +05:30
fileNameDisplay.style.display = 'none';
clearFileBtn.style.display = 'none';
scrollToBottom();
2025-12-15 11:03:30 +05:30
})
.catch(err => console.error("[SEND] Error:", err));
});
2025-12-19 11:00:34 +05:30
// Enter key support
document.getElementById("messageInput").addEventListener("keypress", function(e) {
if (e.key === "Enter") {
document.getElementById("sendBtn").click();
}
});
2025-12-15 11:03:30 +05:30
// -------------------------------
2025-12-19 11:00:34 +05:30
// LISTEN FOR REALTIME MESSAGE
2025-12-15 11:03:30 +05:30
// ----------------------------
waitForEcho(() => {
const ticketId = "{{ $ticket->id }}";
console.log("[ECHO] Subscribing to PRIVATE channel:", `ticket.${ticketId}`);
window.Echo.private(`ticket.${ticketId}`)
.listen(".NewChatMessage", (event) => {
console.log("%c[REALTIME RECEIVED]", "color: blue; font-weight: bold;", event);
2025-12-19 11:00:34 +05:30
const msg = event;
2025-12-15 11:03:30 +05:30
const isMine =
msg.sender_type === 'App\\Models\\Admin' &&
msg.sender_id === CURRENT_ADMIN_ID;
let html = `
<div class="message ${isMine ? 'admin' : 'user'}">
2025-12-19 11:00:34 +05:30
${msg.message ? `<div>${msg.message}</div>` : ''}
2025-12-15 11:03:30 +05:30
`;
2025-12-16 10:19:54 +05:30
if (msg.file_path) {
const fileUrl = `/storage/${msg.file_path}`;
2025-12-19 11:00:34 +05:30
const fileName = msg.file_path.split('/').pop();
const fileSize = msg.file_size ? formatFileSize(msg.file_size) : 'Unknown';
const fileIcon = getFileIcon(msg.file_type || '', fileName);
const fileClass = getFileClass(msg.file_type || '', fileName);
2025-12-16 10:19:54 +05:30
2025-12-19 11:00:34 +05:30
// Check if it's image or video for preview
const isImage = (msg.file_type || '').startsWith('image');
const isVideo = (msg.file_type || '').startsWith('video');
2025-12-16 10:19:54 +05:30
2025-12-19 11:00:34 +05:30
html += `<div class="file-preview">`;
2025-12-16 10:19:54 +05:30
2025-12-19 11:00:34 +05:30
if (isImage) {
2025-12-16 10:19:54 +05:30
html += `
2025-12-19 11:00:34 +05:30
<img src="${fileUrl}"
class="rounded mt-2"
style="max-width:210px;"
alt="${fileName}">
2025-12-16 10:19:54 +05:30
`;
2025-12-19 11:00:34 +05:30
} else if (isVideo) {
2025-12-16 10:19:54 +05:30
html += `
2025-12-19 11:00:34 +05:30
<video src="${fileUrl}"
controls
class="mt-2 rounded"
style="max-width:200px;">
</video>
2025-12-16 10:19:54 +05:30
`;
2025-12-15 11:03:30 +05:30
}
2025-12-19 11:00:34 +05:30
html += `
<a href="${fileUrl}"
target="_blank"
class="file-item ${fileClass}"
title="${fileName}">
<div class="file-icon">
${fileIcon}
</div>
<div class="file-info">
<div class="file-name">${fileName}</div>
<div class="file-size">${fileSize}</div>
</div>
<div class="file-download-btn">
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
</svg>
</div>
</a>
</div>`;
}
2025-12-16 10:19:54 +05:30
2025-12-15 11:03:30 +05:30
html += `
2025-12-19 11:00:34 +05:30
<small class="mt-2">Just now</small>
2025-12-15 11:03:30 +05:30
</div>
`;
document
2025-12-19 11:00:34 +05:30
.querySelector("#chatBox .chat-box-inner")
2025-12-15 11:03:30 +05:30
.insertAdjacentHTML("beforeend", html);
scrollToBottom();
});
});
</script>
2025-12-19 11:00:34 +05:30
@endsection