206 lines
9.6 KiB
PHP
206 lines
9.6 KiB
PHP
@extends('layouts.shop')
|
||
|
||
@section('meta_title', 'Чаты с клиентами')
|
||
|
||
@section('content')
|
||
@include('partials.breadcrumbs', [
|
||
'items' => [
|
||
['label' => 'Админка', 'url' => route('admin.dashboard')],
|
||
['label' => 'Чаты', 'url' => null],
|
||
],
|
||
])
|
||
|
||
<section class="pc-section">
|
||
<div class="pc-section-title">
|
||
<h2>Чаты с клиентами</h2>
|
||
<p>Отвечайте клиентам прямо из админки.</p>
|
||
</div>
|
||
|
||
@if ($conversations->isEmpty())
|
||
<div class="pc-card">
|
||
<p>Пока нет сообщений от клиентов.</p>
|
||
</div>
|
||
@else
|
||
<div class="pc-admin-chat-layout">
|
||
<aside class="pc-card pc-admin-chat-list">
|
||
@foreach ($conversations as $conversation)
|
||
@php
|
||
$isActive = $selectedConversation && $selectedConversation->id === $conversation->id;
|
||
$subtitle = $conversation->user?->email ?: ('Токен: ' . \Illuminate\Support\Str::limit($conversation->visitor_token, 14, '...'));
|
||
$preview = $conversation->latestMessage?->body ? \Illuminate\Support\Str::limit($conversation->latestMessage->body, 80) : 'Нет сообщений';
|
||
@endphp
|
||
<a class="pc-admin-chat-item {{ $isActive ? 'is-active' : '' }}" href="{{ route('admin.chats.index', ['conversation' => $conversation->id]) }}">
|
||
<div class="pc-admin-chat-item-top">
|
||
<strong>{{ $conversation->display_name }}</strong>
|
||
@if ($conversation->unread_count > 0)
|
||
<span class="pc-admin-chat-badge">{{ $conversation->unread_count }}</span>
|
||
@endif
|
||
</div>
|
||
<span class="pc-muted">{{ $subtitle }}</span>
|
||
@if ($conversation->status === \App\Models\ChatConversation::STATUS_CLOSED)
|
||
<span class="pc-muted">Чат закрыт</span>
|
||
@endif
|
||
<span class="pc-muted">{{ $preview }}</span>
|
||
</a>
|
||
@endforeach
|
||
</aside>
|
||
|
||
<div class="pc-card pc-admin-chat-panel">
|
||
@if ($selectedConversation)
|
||
@php
|
||
$isClosed = $selectedConversation->status === \App\Models\ChatConversation::STATUS_CLOSED;
|
||
@endphp
|
||
<div
|
||
id="pc-admin-chat"
|
||
data-fetch-url="{{ route('admin.chats.messages', $selectedConversation) }}"
|
||
data-send-url="{{ route('admin.chats.messages.store', $selectedConversation) }}"
|
||
data-csrf="{{ csrf_token() }}"
|
||
>
|
||
<div class="pc-admin-chat-head">
|
||
<div>
|
||
<h3>{{ $selectedConversation->display_name }}</h3>
|
||
<p class="pc-muted">{{ $selectedConversation->user?->email ?: 'Гость' }}</p>
|
||
<p class="pc-muted">{{ $isClosed ? 'Чат закрыт' : 'Чат открыт' }}</p>
|
||
</div>
|
||
<div class="pc-admin-chat-actions">
|
||
<form method="post" action="{{ route('admin.chats.status', $selectedConversation) }}">
|
||
@csrf
|
||
@method('patch')
|
||
<input type="hidden" name="status" value="{{ $isClosed ? \App\Models\ChatConversation::STATUS_OPEN : \App\Models\ChatConversation::STATUS_CLOSED }}">
|
||
<button class="pc-btn ghost" type="submit">{{ $isClosed ? 'Открыть чат' : 'Закрыть чат' }}</button>
|
||
</form>
|
||
<form method="post" action="{{ route('admin.chats.destroy', $selectedConversation) }}" onsubmit="return confirm('Удалить чат и все сообщения?')">
|
||
@csrf
|
||
@method('delete')
|
||
<button class="pc-btn ghost" type="submit">Удалить чат</button>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="pc-admin-chat-messages" id="pc-admin-chat-messages"></div>
|
||
|
||
<form class="pc-admin-chat-form" id="pc-admin-chat-form">
|
||
<textarea name="message" rows="3" maxlength="2000" placeholder="{{ $isClosed ? 'Чат закрыт. Сначала откройте его.' : 'Введите ответ клиенту...' }}" required @disabled($isClosed)></textarea>
|
||
<button class="pc-btn primary" type="submit" @disabled($isClosed)>{{ $isClosed ? 'Чат закрыт' : 'Отправить' }}</button>
|
||
</form>
|
||
</div>
|
||
@else
|
||
<p>Выберите чат слева.</p>
|
||
@endif
|
||
</div>
|
||
</div>
|
||
|
||
<div class="pc-pagination">
|
||
{{ $conversations->links() }}
|
||
</div>
|
||
@endif
|
||
</section>
|
||
@endsection
|
||
|
||
@if ($selectedConversation)
|
||
@push('scripts')
|
||
<script>
|
||
(() => {
|
||
const root = document.getElementById('pc-admin-chat');
|
||
const list = document.getElementById('pc-admin-chat-messages');
|
||
const form = document.getElementById('pc-admin-chat-form');
|
||
if (!root || !list || !form) {
|
||
return;
|
||
}
|
||
|
||
const fetchUrl = root.dataset.fetchUrl;
|
||
const sendUrl = root.dataset.sendUrl;
|
||
const csrf = root.dataset.csrf;
|
||
const initialMessages = @json($initialMessages);
|
||
|
||
const renderMessages = (messages) => {
|
||
if (!Array.isArray(messages)) {
|
||
return;
|
||
}
|
||
|
||
list.innerHTML = '';
|
||
messages.forEach((message) => {
|
||
const item = document.createElement('div');
|
||
item.className = `pc-admin-chat-message ${message.sender === 'admin' ? 'is-admin' : 'is-customer'}`;
|
||
|
||
const bubble = document.createElement('div');
|
||
bubble.className = 'pc-admin-chat-bubble';
|
||
bubble.textContent = message.body || '';
|
||
|
||
const meta = document.createElement('span');
|
||
meta.className = 'pc-muted';
|
||
meta.textContent = message.time || '';
|
||
|
||
item.appendChild(bubble);
|
||
item.appendChild(meta);
|
||
list.appendChild(item);
|
||
});
|
||
|
||
list.scrollTop = list.scrollHeight;
|
||
};
|
||
|
||
const fetchMessages = async () => {
|
||
try {
|
||
const response = await fetch(fetchUrl, {
|
||
method: 'GET',
|
||
headers: {
|
||
'Accept': 'application/json',
|
||
},
|
||
});
|
||
|
||
if (!response.ok) {
|
||
return;
|
||
}
|
||
|
||
const payload = await response.json();
|
||
renderMessages(payload.messages || []);
|
||
} catch (error) {
|
||
// Ignore temporary connection issues and continue polling.
|
||
}
|
||
};
|
||
|
||
form.addEventListener('submit', async (event) => {
|
||
event.preventDefault();
|
||
const textarea = form.querySelector('textarea[name="message"]');
|
||
const button = form.querySelector('button[type="submit"]');
|
||
if (!textarea || !button) {
|
||
return;
|
||
}
|
||
|
||
const message = textarea.value.trim();
|
||
if (message === '') {
|
||
return;
|
||
}
|
||
|
||
button.disabled = true;
|
||
|
||
try {
|
||
const response = await fetch(sendUrl, {
|
||
method: 'POST',
|
||
headers: {
|
||
'Accept': 'application/json',
|
||
'Content-Type': 'application/json',
|
||
'X-CSRF-TOKEN': csrf,
|
||
},
|
||
body: JSON.stringify({ message }),
|
||
});
|
||
|
||
if (!response.ok) {
|
||
return;
|
||
}
|
||
|
||
textarea.value = '';
|
||
await fetchMessages();
|
||
} finally {
|
||
button.disabled = false;
|
||
}
|
||
});
|
||
|
||
renderMessages(initialMessages);
|
||
fetchMessages();
|
||
window.setInterval(fetchMessages, 4000);
|
||
})();
|
||
</script>
|
||
@endpush
|
||
@endif
|