Kaynağa Gözat

feat(chat): add user targeting and notification tracking for chat messages

- Add manager and brigadier user ID parameters to chat partial view
- Implement notifiedUsers relationship in ChatMessage model
- Store notification recipients in chat_message_user pivot table
- Enhance chat UI to support targeted messaging
- Update notification logic to track specific recipients
- Remove unused .mcp.json configuration file
Alexander Musikhin 1 ay önce
ebeveyn
işleme
662dc6040b

+ 0 - 17
.mcp.json

@@ -1,17 +0,0 @@
-{
-  "mcpServers": {
-    "chromeDevtools": {
-      "command": "npx",
-      "args": [
-        "-y",
-        "chrome-devtools-mcp@latest",
-        "--no-usage-statistics"
-      ]
-    },
-    "github": {
-      "command": "npx",
-      "args": ["-y", "@modelcontextprotocol/server-github"],
-      "env": { "GITHUB_TOKEN": "${GITHUB_TOKEN}" }
-    }
-  }
-}

+ 4 - 0
app/Http/Controllers/ChatMessageController.php

@@ -125,6 +125,10 @@ class ChatMessageController extends Controller
                 'message' => $messageText !== '' ? $messageText : null,
             ]);
 
+            if (!empty($recipientIds)) {
+                $chatMessage->notifiedUsers()->syncWithoutDetaching($recipientIds);
+            }
+
             $files = [];
             foreach ($attachments as $attachment) {
                 $files[] = $fileService->saveUploadedFile($filePath, $attachment);

+ 8 - 1
app/Http/Controllers/OrderController.php

@@ -266,6 +266,7 @@ class OrderController extends Controller
             ->with([
                 'chatMessages.user',
                 'chatMessages.targetUser',
+                'chatMessages.notifiedUsers',
                 'chatMessages.files',
             ])
             ->find($order);
@@ -306,8 +307,14 @@ class OrderController extends Controller
             $orderModel?->brigadier_id ? (int) $orderModel->brigadier_id : null,
         ]))));
 
-        $this->data['chatUsers'] = $chatUsers->pluck('name', 'id');
+        $this->data['chatUsers'] = $chatUsers->map(fn ($u) => [
+            'id' => $u->id,
+            'name' => $u->name,
+            'role' => $u->role,
+        ])->keyBy('id');
         $this->data['chatResponsibleUserIds'] = $responsibleUserIds;
+        $this->data['chatManagerUserId'] = $orderModel?->user_id ? (int) $orderModel->user_id : null;
+        $this->data['chatBrigadierUserId'] = $orderModel?->brigadier_id ? (int) $orderModel->brigadier_id : null;
         return view('orders.show', $this->data);
     }
 

+ 8 - 1
app/Http/Controllers/ReclamationController.php

@@ -147,6 +147,7 @@ class ReclamationController extends Controller
             'order',
             'chatMessages.user',
             'chatMessages.targetUser',
+            'chatMessages.notifiedUsers',
             'chatMessages.files',
         ]);
         $chatUsers = User::query()->orderBy('name')->get(['id', 'name', 'role']);
@@ -160,8 +161,14 @@ class ReclamationController extends Controller
             $reclamation->brigadier_id ? (int) $reclamation->brigadier_id : null,
         ]))));
 
-        $this->data['chatUsers'] = $chatUsers->pluck('name', 'id');
+        $this->data['chatUsers'] = $chatUsers->map(fn ($u) => [
+            'id' => $u->id,
+            'name' => $u->name,
+            'role' => $u->role,
+        ])->keyBy('id');
         $this->data['chatResponsibleUserIds'] = $responsibleUserIds;
+        $this->data['chatManagerUserId'] = $reclamation->user_id ? (int) $reclamation->user_id : null;
+        $this->data['chatBrigadierUserId'] = $reclamation->brigadier_id ? (int) $reclamation->brigadier_id : null;
         $this->data['previous_url'] = $this->resolvePreviousUrl(
             $request,
             'previous_url_reclamations',

+ 5 - 0
app/Models/ChatMessage.php

@@ -52,6 +52,11 @@ class ChatMessage extends Model
         return $this->belongsTo(User::class, 'target_user_id');
     }
 
+    public function notifiedUsers(): BelongsToMany
+    {
+        return $this->belongsToMany(User::class, 'chat_message_user');
+    }
+
     public function files(): BelongsToMany
     {
         return $this->belongsToMany(File::class, 'chat_message_file')->withTimestamps();

+ 22 - 0
database/migrations/2026_03_22_100300_create_chat_message_user_table.php

@@ -0,0 +1,22 @@
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+return new class extends Migration
+{
+    public function up(): void
+    {
+        Schema::create('chat_message_user', function (Blueprint $table) {
+            $table->foreignId('chat_message_id')->constrained()->cascadeOnDelete();
+            $table->foreignId('user_id')->constrained()->cascadeOnDelete();
+            $table->primary(['chat_message_id', 'user_id']);
+        });
+    }
+
+    public function down(): void
+    {
+        Schema::dropIfExists('chat_message_user');
+    }
+};

+ 2 - 0
resources/views/orders/show.blade.php

@@ -350,6 +350,8 @@
                         'messages' => $order->chatMessages,
                         'users' => $chatUsers,
                         'responsibleUserIds' => $chatResponsibleUserIds,
+                        'managerUserId' => $chatManagerUserId ?? null,
+                        'brigadierUserId' => $chatBrigadierUserId ?? null,
                         'action' => route('order.chat-messages.store', $order),
                         'contextKey' => 'order-' . $order->id,
                         'submitLabel' => 'Отправить в чат',

+ 187 - 127
resources/views/partials/chat.blade.php

@@ -3,17 +3,33 @@
     $messages = $messages ?? collect();
     $users = $users ?? collect();
     $responsibleUserIds = array_map('intval', $responsibleUserIds ?? []);
+    $managerUserId = isset($managerUserId) ? (int) $managerUserId : null;
+    $brigadierUserId = isset($brigadierUserId) ? (int) $brigadierUserId : null;
+    $currentUserId = (int) auth()->id();
     $contextKey = $contextKey ?? 'chat';
     $title = $title ?? 'Чат';
     $submitLabel = $submitLabel ?? 'Отправить';
     $canSendNotifications = hasRole('admin,manager');
     $notificationValue = old('notification_type', \App\Models\ChatMessage::NOTIFICATION_NONE);
+    $notificationEnabled = $canSendNotifications && $notificationValue !== \App\Models\ChatMessage::NOTIFICATION_NONE;
+    $showAllUsers = $notificationValue === \App\Models\ChatMessage::NOTIFICATION_ALL;
     $selectedTargetUserIds = collect(old('target_user_ids', []))
         ->map(static fn ($id) => (int) $id)
         ->filter()
         ->unique()
         ->values()
         ->all();
+
+    // Роли в порядке сортировки
+    $roleOrder = [\App\Models\Role::ADMIN, \App\Models\Role::MANAGER, \App\Models\Role::BRIGADIER];
+    $roleNames = \App\Models\Role::NAMES;
+
+    // Сортировка пользователей: админы -> менеджеры -> бригадиры -> остальные
+    $sortedUsers = $users->sortBy(function ($user) use ($roleOrder) {
+        $role = $user['role'] ?? '';
+        $index = array_search($role, $roleOrder, true);
+        return $index === false ? 999 : $index;
+    });
 @endphp
 
 <div class="chat-block mt-3" data-chat-block data-context-key="{{ $contextKey }}">
@@ -30,10 +46,8 @@
                                 <strong>{{ $message->user?->name ?? 'Пользователь' }}</strong>
                                 @if($message->notification_type === \App\Models\ChatMessage::NOTIFICATION_USER && $message->targetUser)
                                     <span class="text-muted">для {{ $message->targetUser->name }}</span>
-                                @elseif($message->notification_type === \App\Models\ChatMessage::NOTIFICATION_RESPONSIBLES)
-                                    <span class="badge text-bg-light border">Уведомления: админы/менеджер/бригадир</span>
-                                @elseif($message->notification_type === \App\Models\ChatMessage::NOTIFICATION_ALL)
-                                    <span class="badge text-bg-light border">Уведомления: выбранные получатели</span>
+                                @elseif(in_array($message->notification_type, [\App\Models\ChatMessage::NOTIFICATION_RESPONSIBLES, \App\Models\ChatMessage::NOTIFICATION_ALL], true))
+                                    <span class="badge text-bg-light border">Уведомления: {{ $message->notifiedUsers->pluck('name')->join(', ') ?: 'получатели' }}</span>
                                 @endif
                             </div>
                             <small class="text-muted">{{ $message->created_at?->format('d.m.Y H:i') }}</small>
@@ -73,73 +87,66 @@
     <form action="{{ $action }}" method="post" enctype="multipart/form-data" class="mt-3" data-chat-form>
         @csrf
 
-        <div class="row g-2 align-items-start">
-            <div class="col-12 col-lg-5">
-                <label class="form-label" for="chat-message-{{ $contextKey }}">Сообщение</label>
+        {{-- Уведомления: свитч + саммари --}}
+        @if($canSendNotifications)
+            <div class="d-flex align-items-center gap-3 mb-2">
+
+                <div class="form-check form-switch mb-0">
+                    <input
+                        class="form-check-input"
+                        type="checkbox"
+                        role="switch"
+                        id="chat-notify-toggle-{{ $contextKey }}"
+                        data-chat-notify-toggle
+                        @checked($notificationEnabled)
+                    >
+                    <label class="form-check-label small" for="chat-notify-toggle-{{ $contextKey }}">
+                        <i class="bi bi-bell"></i> Уведомить
+                    </label>
+                </div>
+
+                <div class="small text-muted {{ $notificationEnabled ? '' : 'd-none' }}" data-chat-recipient-summary-wrap>
+                    <a href="#" class="text-decoration-none" data-chat-open-recipient-modal data-bs-toggle="modal" data-bs-target="#chatRecipientsModal-{{ $contextKey }}">
+                        <span data-chat-recipient-summary>Получатели не выбраны</span>
+                        <i class="bi bi-pencil-square ms-1"></i>
+                    </a>
+                </div>
+            </div>
+
+            {{-- Скрытый select для notification_type (значение управляется JS) --}}
+            <input type="hidden" name="notification_type" value="{{ $notificationValue }}" data-chat-notification-type>
+        @else
+            <input type="hidden" name="notification_type" value="{{ \App\Models\ChatMessage::NOTIFICATION_NONE }}">
+        @endif
+
+        {{-- Строка ввода: textarea + иконка файла + кнопка отправить --}}
+        <div class="d-flex align-items-center gap-2">
+            <div class="flex-grow-1">
                 <textarea
                     class="form-control"
                     id="chat-message-{{ $contextKey }}"
                     name="message"
-                    rows="3"
+                    rows="2"
                     placeholder="Введите сообщение"
                 >{{ old('message') }}</textarea>
             </div>
 
-            <div class="col-12 col-md-4 col-lg-3">
-                <label class="form-label" for="chat-notification-{{ $contextKey }}">Уведомление</label>
-                <select
-                    class="form-select chat-notification-type"
-                    id="chat-notification-{{ $contextKey }}"
-                    name="notification_type"
-                    data-chat-context-key="{{ $contextKey }}"
-                    @disabled(!$canSendNotifications)
-                >
-                    <option value="{{ \App\Models\ChatMessage::NOTIFICATION_NONE }}" @selected($notificationValue === \App\Models\ChatMessage::NOTIFICATION_NONE)>Нет</option>
-                    @if($canSendNotifications)
-                        <option value="{{ \App\Models\ChatMessage::NOTIFICATION_RESPONSIBLES }}" @selected($notificationValue === \App\Models\ChatMessage::NOTIFICATION_RESPONSIBLES)>Админы, менеджер, бригадир</option>
-                        <option value="{{ \App\Models\ChatMessage::NOTIFICATION_ALL }}" @selected($notificationValue === \App\Models\ChatMessage::NOTIFICATION_ALL)>Все</option>
-                    @endif
-                </select>
-                @if(!$canSendNotifications)
-                    <input type="hidden" name="notification_type" value="{{ \App\Models\ChatMessage::NOTIFICATION_NONE }}">
-                @endif
-            </div>
+            <input class="d-none" id="chat-attachments-{{ $contextKey }}" type="file" name="attachments[]" multiple data-chat-file-input>
 
-            <div class="col-12 col-md-4 col-lg-2">
-                <label class="form-label" for="chat-attachments-{{ $contextKey }}">Файлы</label>
-                <input class="form-control" id="chat-attachments-{{ $contextKey }}" type="file" name="attachments[]" multiple>
-            </div>
+            <button type="button" class="btn btn-outline-secondary position-relative d-flex align-items-center justify-content-center" style="width: 38px; height: 38px; padding: 0;" title="Прикрепить файл" data-chat-attach-btn>
+                <i class="bi bi-paperclip"></i>
+                <span class="d-none position-absolute top-0 start-100 translate-middle badge rounded-pill bg-primary" style="font-size: .65em;" data-chat-file-count></span>
+            </button>
 
-            <div class="col-12">
-                <div class="row g-2 align-items-center {{ $canSendNotifications && $notificationValue !== \App\Models\ChatMessage::NOTIFICATION_NONE ? '' : 'd-none' }}"
-                     data-chat-recipient-picker-wrap>
-                    <div class="col-12 col-md-auto">
-                        <button
-                            type="button"
-                            class="btn btn-outline-secondary btn-sm"
-                            data-chat-open-recipient-modal
-                            data-bs-toggle="modal"
-                            data-bs-target="#chatRecipientsModal-{{ $contextKey }}"
-                        >
-                            Выбрать получателей
-                        </button>
-                    </div>
-                    <div class="col-12 col-md">
-                        <div class="small text-muted chat-recipient-summary" data-chat-recipient-summary>
-                            Получатели не выбраны
-                        </div>
-                    </div>
-                </div>
-                <div data-chat-hidden-targets>
-                    @foreach($selectedTargetUserIds as $selectedTargetUserId)
-                        <input type="hidden" name="target_user_ids[]" value="{{ $selectedTargetUserId }}">
-                    @endforeach
-                </div>
-            </div>
+            <button class="btn btn-primary d-flex align-items-center justify-content-center" style="width: 38px; height: 38px; padding: 0;" type="submit" title="{{ $submitLabel }}">
+                <i class="bi bi-send"></i>
+            </button>
+        </div>
 
-            <div class="col-12 text-end">
-                <button class="btn btn-primary btn-sm" type="submit">{{ $submitLabel }}</button>
-            </div>
+        <div data-chat-hidden-targets>
+            @foreach($selectedTargetUserIds as $selectedTargetUserId)
+                <input type="hidden" name="target_user_ids[]" value="{{ $selectedTargetUserId }}">
+            @endforeach
         </div>
     </form>
 
@@ -152,29 +159,61 @@
                         <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Закрыть"></button>
                     </div>
                     <div class="modal-body" data-chat-recipient-modal-body>
-                        <div class="d-flex justify-content-between align-items-center gap-2 mb-2 flex-wrap">
-                            <div class="small text-muted" data-chat-recipient-hint></div>
+                        {{-- Свитч: показать всех пользователей --}}
+                        <div class="d-flex justify-content-between align-items-center mb-3">
+                            <div class="form-check form-switch mb-0">
+                                <input
+                                    class="form-check-input"
+                                    type="checkbox"
+                                    role="switch"
+                                    id="chat-show-all-toggle-{{ $contextKey }}"
+                                    data-chat-show-all-toggle
+                                    @checked($showAllUsers)
+                                >
+                                <label class="form-check-label" for="chat-show-all-toggle-{{ $contextKey }}">
+                                    Показать всех
+                                </label>
+                            </div>
                             <div class="d-flex gap-2">
-                                <button type="button" class="btn btn-sm btn-outline-primary" data-chat-check-visible>Выбрать видимых</button>
-                                <button type="button" class="btn btn-sm btn-outline-secondary" data-chat-uncheck-visible>Снять видимые</button>
+                                <button type="button" class="btn btn-sm btn-outline-primary" data-chat-check-visible>Выбрать всех</button>
+                                <button type="button" class="btn btn-sm btn-outline-secondary" data-chat-uncheck-visible>Снять всех</button>
                             </div>
                         </div>
 
                         <div class="chat-recipient-list">
-                            @foreach($users as $userId => $userName)
+                            @foreach($sortedUsers as $userId => $userData)
+                                @php
+                                    $userRole = $userData['role'] ?? '';
+                                    $isManagerOfEntity = $managerUserId && (int) $userId === $managerUserId;
+                                    $isBrigadierOfEntity = $brigadierUserId && (int) $userId === $brigadierUserId;
+                                    $roleLabel = $roleNames[$userRole] ?? '';
+                                    $displayLabel = $userData['name'];
+                                    if ($roleLabel) {
+                                        $displayLabel .= ' (' . $roleLabel . ')';
+                                    }
+                                    if ($isManagerOfEntity) {
+                                        $displayLabel .= ' — менеджер площадки';
+                                    }
+                                    if ($isBrigadierOfEntity) {
+                                        $displayLabel .= ' — бригадир площадки';
+                                    }
+                                    $isSelf = (int) $userId === $currentUserId;
+                                @endphp
                                 <label class="form-check mb-2 chat-recipient-item"
                                        data-chat-recipient-item
                                        data-user-id="{{ $userId }}"
-                                       data-user-name="{{ $userName }}"
-                                       data-chat-responsible="{{ in_array((int) $userId, $responsibleUserIds, true) ? '1' : '0' }}">
+                                       data-user-name="{{ $displayLabel }}"
+                                       data-chat-responsible="{{ in_array((int) $userId, $responsibleUserIds, true) ? '1' : '0' }}"
+                                       data-chat-self="{{ $isSelf ? '1' : '0' }}">
                                     <input
                                         class="form-check-input"
                                         type="checkbox"
                                         value="{{ $userId }}"
                                         data-chat-recipient-checkbox
-                                        @checked(in_array((int) $userId, $selectedTargetUserIds, true))
+                                        @disabled($isSelf)
+                                        @checked(!$isSelf && in_array((int) $userId, $selectedTargetUserIds, true))
                                     >
-                                    <span class="form-check-label">{{ $userName }}</span>
+                                    <span class="form-check-label {{ $isSelf ? 'text-muted' : '' }}">{{ $displayLabel }}@if($isSelf) (вы)@endif</span>
                                 </label>
                             @endforeach
                         </div>
@@ -196,17 +235,32 @@
                 const messages = block.querySelector('[data-chat-messages]');
                 const scrollButton = block.querySelector('[data-chat-scroll-bottom]');
                 const form = block.querySelector('[data-chat-form]');
-                const notificationType = block.querySelector('.chat-notification-type');
-                const recipientPickerWrap = block.querySelector('[data-chat-recipient-picker-wrap]');
-                const hiddenTargets = block.querySelector('[data-chat-hidden-targets]');
+                const notifyToggle = block.querySelector('[data-chat-notify-toggle]');
+                const notificationType = block.querySelector('[data-chat-notification-type]');
+                const showAllToggle = block.querySelector('[data-chat-show-all-toggle]');
+                const summaryWrap = block.querySelector('[data-chat-recipient-summary-wrap]');
                 const summary = block.querySelector('[data-chat-recipient-summary]');
+                const hiddenTargets = block.querySelector('[data-chat-hidden-targets]');
                 const modal = block.querySelector('.modal');
+                const fileInput = block.querySelector('[data-chat-file-input]');
+                const attachBtn = block.querySelector('[data-chat-attach-btn]');
+                const fileCount = block.querySelector('[data-chat-file-count]');
+
+                // --- Файлы ---
+                if (attachBtn && fileInput) {
+                    attachBtn.addEventListener('click', () => fileInput.click());
+                    fileInput.addEventListener('change', () => {
+                        const count = fileInput.files?.length || 0;
+                        if (fileCount) {
+                            fileCount.textContent = String(count);
+                            fileCount.classList.toggle('d-none', count === 0);
+                        }
+                    });
+                }
 
+                // --- Скролл ---
                 const scrollToBottom = (force = false) => {
-                    if (!messages) {
-                        return;
-                    }
-
+                    if (!messages) return;
                     const isNearBottom = messages.scrollHeight - messages.scrollTop - messages.clientHeight < 48;
                     if (force || isNearBottom) {
                         messages.scrollTop = messages.scrollHeight;
@@ -214,14 +268,12 @@
                 };
 
                 const syncScrollButton = () => {
-                    if (!messages || !scrollButton) {
-                        return;
-                    }
-
+                    if (!messages || !scrollButton) return;
                     const shouldShow = messages.scrollHeight - messages.scrollTop - messages.clientHeight > 80;
                     scrollButton.classList.toggle('d-none', !shouldShow);
                 };
 
+                // --- Получатели ---
                 const getSelectedIds = () => Array.from(hiddenTargets.querySelectorAll('input[name="target_user_ids[]"]'))
                     .map((input) => Number(input.value))
                     .filter((value) => value > 0);
@@ -241,11 +293,9 @@
                     .filter((item) => !item.hidden);
 
                 const syncRecipientSummary = () => {
-                    if (!summary || !notificationType) {
-                        return;
-                    }
+                    if (!summary) return;
 
-                    if (notificationType.value === 'none') {
+                    if (!notifyToggle || !notifyToggle.checked) {
                         summary.textContent = 'Уведомления выключены';
                         return;
                     }
@@ -261,31 +311,38 @@
                         .filter(Boolean)
                         .map((item) => item.dataset.userName);
 
-                    summary.textContent = 'Выбрано: ' + names.join(', ');
+                    summary.textContent = 'Получатели: ' + names.join(', ');
                 };
 
-                const applyRecipientFilter = (preserveSelection = true) => {
-                    if (!notificationType || !modal) {
-                        return;
+                const syncNotificationType = () => {
+                    if (!notificationType) return;
+                    if (!notifyToggle || !notifyToggle.checked) {
+                        notificationType.value = 'none';
+                    } else if (showAllToggle && showAllToggle.checked) {
+                        notificationType.value = 'all';
+                    } else {
+                        notificationType.value = 'responsibles';
                     }
+                };
 
-                    const isResponsibles = notificationType.value === 'responsibles';
-                    const isAll = notificationType.value === 'all';
+                const applyRecipientFilter = (preserveSelection = true) => {
+                    if (!modal) return;
+
+                    const isAll = showAllToggle && showAllToggle.checked;
                     const recipientItems = Array.from(block.querySelectorAll('[data-chat-recipient-item]'));
                     const selectedIds = new Set(getSelectedIds());
-                    const visibleIds = [];
 
                     recipientItems.forEach((item) => {
+                        const isSelf = item.dataset.chatSelf === '1';
                         const isResponsible = item.dataset.chatResponsible === '1';
-                        const visible = isAll || (isResponsibles && isResponsible);
+                        const visible = isAll || isResponsible;
                         const checkbox = item.querySelector('[data-chat-recipient-checkbox]');
 
                         item.hidden = !visible;
-                        checkbox.disabled = !visible;
+                        checkbox.disabled = !visible || isSelf;
+                        checkbox.checked = checkbox.checked && !isSelf;
 
-                        if (visible) {
-                            visibleIds.push(Number(item.dataset.userId));
-                        } else {
+                        if (!visible || isSelf) {
                             checkbox.checked = false;
                         }
                     });
@@ -308,7 +365,7 @@
                             return !checkbox.disabled && checkbox.checked;
                         });
 
-                        if (!hasVisibleSelected && visibleIds.length) {
+                        if (!hasVisibleSelected) {
                             recipientItems.forEach((item) => {
                                 const checkbox = item.querySelector('[data-chat-recipient-checkbox]');
                                 if (!checkbox.disabled) {
@@ -317,18 +374,12 @@
                             });
                         }
                     }
-
-                    const hint = block.querySelector('[data-chat-recipient-hint]');
-                    if (hint) {
-                        hint.textContent = isResponsibles
-                            ? 'Доступны только админы, менеджер и бригадир текущей сущности.'
-                            : 'Доступны все пользователи.';
-                    }
                 };
 
                 const commitRecipientSelection = () => {
-                    if (!notificationType || notificationType.value === 'none') {
+                    if (!notifyToggle || !notifyToggle.checked) {
                         setSelectedIds([]);
+                        syncNotificationType();
                         syncRecipientSummary();
                         return;
                     }
@@ -340,18 +391,14 @@
                         .filter((value) => value > 0);
 
                     setSelectedIds(ids);
+                    syncNotificationType();
                     syncRecipientSummary();
                 };
 
+                // --- Инициализация скролла ---
                 if (messages) {
-                    requestAnimationFrame(() => {
-                        scrollToBottom(true);
-                        syncScrollButton();
-                    });
-                    setTimeout(() => {
-                        scrollToBottom(true);
-                        syncScrollButton();
-                    }, 150);
+                    requestAnimationFrame(() => { scrollToBottom(true); syncScrollButton(); });
+                    setTimeout(() => { scrollToBottom(true); syncScrollButton(); }, 150);
                     messages.addEventListener('scroll', syncScrollButton);
                 }
 
@@ -359,20 +406,23 @@
                     scrollButton.addEventListener('click', () => scrollToBottom(true));
                 }
 
-                if (notificationType) {
-                    notificationType.addEventListener('change', (event) => {
-                        const enabled = event.target.value !== 'none';
+                // --- Свитч уведомлений ---
+                if (notifyToggle) {
+                    notifyToggle.addEventListener('change', () => {
+                        const enabled = notifyToggle.checked;
 
-                        if (recipientPickerWrap) {
-                            recipientPickerWrap.classList.toggle('d-none', !enabled);
+                        if (summaryWrap) {
+                            summaryWrap.classList.toggle('d-none', !enabled);
                         }
 
                         if (!enabled) {
                             setSelectedIds([]);
+                            syncNotificationType();
                             syncRecipientSummary();
                             return;
                         }
 
+                        // Включили — сразу открываем модалку с ответственными
                         applyRecipientFilter(false);
                         commitRecipientSelection();
 
@@ -382,45 +432,55 @@
                     });
                 }
 
+                // --- Свитч "Показать всех" в модалке ---
+                if (showAllToggle) {
+                    showAllToggle.addEventListener('change', () => {
+                        applyRecipientFilter(false);
+                        syncNotificationType();
+                    });
+                }
+
+                // --- Открытие модалки вручную ---
                 block.querySelector('[data-chat-open-recipient-modal]')?.addEventListener('click', () => {
                     applyRecipientFilter(true);
                 });
 
+                // --- Выбрать/снять всех ---
                 block.querySelector('[data-chat-check-visible]')?.addEventListener('click', () => {
                     visibleRecipientItems().forEach((item) => {
                         const checkbox = item.querySelector('[data-chat-recipient-checkbox]');
-                        if (checkbox) {
-                            checkbox.checked = true;
-                        }
+                        if (checkbox) checkbox.checked = true;
                     });
                 });
 
                 block.querySelector('[data-chat-uncheck-visible]')?.addEventListener('click', () => {
                     visibleRecipientItems().forEach((item) => {
                         const checkbox = item.querySelector('[data-chat-recipient-checkbox]');
-                        if (checkbox) {
-                            checkbox.checked = false;
-                        }
+                        if (checkbox) checkbox.checked = false;
                     });
                 });
 
+                // --- Применить / закрыть модалку ---
                 block.querySelector('[data-chat-apply-recipients]')?.addEventListener('click', commitRecipientSelection);
                 modal?.addEventListener('hidden.bs.modal', () => {
-                    if (notificationType && notificationType.value !== 'none') {
+                    if (notifyToggle && notifyToggle.checked) {
                         commitRecipientSelection();
                     }
                 });
 
+                // --- Сабмит формы ---
                 form?.addEventListener('submit', () => {
-                    if (notificationType && notificationType.value !== 'none') {
+                    if (notifyToggle && notifyToggle.checked) {
                         commitRecipientSelection();
                     }
                 });
 
+                // --- Начальное состояние ---
                 applyRecipientFilter(true);
-                if (notificationType && notificationType.value !== 'none') {
+                if (notifyToggle && notifyToggle.checked) {
                     commitRecipientSelection();
                 } else {
+                    syncNotificationType();
                     syncRecipientSummary();
                 }
             }

+ 2 - 0
resources/views/reclamations/edit.blade.php

@@ -608,6 +608,8 @@
                     'messages' => $reclamation->chatMessages,
                     'users' => $chatUsers,
                     'responsibleUserIds' => $chatResponsibleUserIds,
+                    'managerUserId' => $chatManagerUserId ?? null,
+                    'brigadierUserId' => $chatBrigadierUserId ?? null,
                     'action' => route('reclamations.chat-messages.store', $reclamation),
                     'contextKey' => 'reclamation-' . $reclamation->id,
                     'submitLabel' => 'Отправить в чат',