Files
tehnobox/resources/views/admin/chats/index.blade.php
ssww23 93a655235a
Some checks failed
Deploy / deploy (push) Has been cancelled
Initial commit
2026-03-10 00:55:37 +03:00

206 lines
9.6 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
@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