диалоги

02

Диалоги — причина носить его с собой

Рабочая область консоли — это сетка из трёх зон: список, тред, панель посетителя. На телефоне это становится двумя экранами и шторкой — и шторка это именно шторка, потому что переход на отдельный маршрут размонтировал бы поле ввода и потерял черновик.

9:41
Диалоги
АГ
Мои 3
Ожидают 5
Все
Оплата
Метки
Канал
🦊🍊
Лиса · Апельсин 2 2 мин
Здравствуйте, оплата не прошла, что делать?
2 мин
НовыйОплата
🐳🍕
Ирина С. 14 мин
Спасибо, всё получилось!
14 мин
Telegram
🦉🍓
Сова · Клубника 1 1 ч
А можно перенести запись на пятницу?
2 мин
ЗаписьVK
🐨🥑
Коала · Авокадо 3 ч
Понял, спасибо за помощь
3 ч
/  (левая панель WorkspaceLayout)

«Мои» и «Ожидают» остаются одним экраном за сегментированным переключателем — если развести их по отдельным пунктам навигации, взятие диалога из ожидания превратилось бы в акт навигации.

Порядок нижней навигации исправлен: Диалоги, Записи, Команда, Аналитика, Ещё — по тому, как часто к разделу обращаются за смену, а не по порядку боковой панели консоли.

Идентификаторы теперь настоящей формы. Пара эмодзи — настоящий механизм: существо + еда, назначается один раз и навсегда, набор и роли не меняются (Ago.Chat.Domain.VisitorEmojiDictionary). Никаких #4821: такого формата в продукте не существует.

Значок — бейдж, а не пара рядом. Крупная иконка существа по центру, еда — маленьким бейджем поверх правого нижнего края, без кружка под собой — размеры подобраны вживую.

Строка читается словами, а не парой символов. Когда имя посетителя неизвестно, вместо голых эмодзи и хеша — локализованное название пары (Лиса · Апельсин), а сам код остаётся мелким и бледным следом — на случай редкого совпадения в списке, не для доказательства уникальности. Название хранится не в Domain (домен остаётся без перевода), а в собственной i18n-таблице консоли, ключ — сам эмодзи-глиф, скопированный побайтово из VisitorEmojiDictionary.cs, чтобы не разойтись из-за невидимого variation selector'а.

9:41
Диалоги
АГ
Мои 3
Ожидают 5
Все
Статус
Удалить
диалог
🦊🍊
Лиса · Апельсин 2 мин
Здравствуйте, оплата не прошла, что делать?
2 мин
Не начатСообщений: 1
🐳🍕
Ирина С. 14 мин
Спасибо, всё получилось!
6 мин
Назначен: Мария П.Сообщений: 9
🦉🍓
Сова · Клубника 1 ч
А можно перенести запись на пятницу?
2 мин
Не начатСообщений: 3
🐨🥑
Коала · Авокадо 3 ч
Понял, спасибо за помощь
3 ч
ЗакрытСообщений: 12
Подгружаются по мере прокрутки
/  · третья вкладка того же экрана, а не отдельный маршрут

«Все» — третий сегмент, а не пункт навигации и не экран за «⋮». В консоли это отдельная страница администратора; на телефоне отдельный маршрут означал бы, что оператор уходит со своего списка, чтобы посмотреть чужой. Тот же экран, тот же переключатель, что уже разводит «Мои» и «Ожидают».

Вкладки просто нет у того, кому она не положена. GET /api/v1/conversations/all закрыт правом site:configure — так решил GetAllConversationsForSiteHandler, и решил намеренно: conversation:read есть у каждого оператора и открывает только его собственную очередь. У оператора без site:configure переключатель остаётся о двух сегментах — не о трёх, из которых один серый.

Это список строк, а не таблица. Первые две строки — ровно те же, что в «Моих»: имя и время с начала диалога сверху, последние слова и время последнего сообщения под ними. Третья строка — единственное, что здесь добавлено: слева состояние (Не начат — посетитель написал, никто не взял; Назначен: имя; Закрыт), справа Сообщений: N — длина диалога целиком, а не непрочитанное. Поэтому это бледный текст в весе времени, а не синий .badge, который в этом продукте всегда означает непрочитанное.

Чисел в фильтре и на вкладках нет намеренно. Закрытых диалогов у живого сайта десятки тысяч; счётчик рядом со статусом или на самой вкладке — это COUNT(*) по всей истории на каждое открытие экрана ради цифры, которую никто не читает. Список ленивый: он тянется по мере прокрутки и нигде не знает своей полной длины.

Две из трёх строк сегодня нечем заполнить — и это честная часть кадра. GetAllConversationsForSiteHandler.ToSummary собирает ConversationSummaryDto без LastMessagePreview и LastMessageAt (поля в контракте есть, этот список их не заполняет), а поля «сколько всего сообщений» нет вообще нигде. Вторая строка и Сообщений: N — это работа на стороне ago-chat, а не только рисование. Первая строка и статус доступны уже сейчас.

Свайп влево открывает удаление — и только у администратора. Право — conversation:erase; оно входит в набор администратора, который выдают RegisterSiteHandler и MintDemoTenantHandler вместе с site:erase и site:export, и отдельно оператору не выдаётся никогда. У оператора без этого права строка не свайпается вовсе — то же правило «скрывать, а не гасить», что и со скрепкой в треде. Под строкой — сплошной --danger, иконка и подпись в две строки, без других слов.

Но «удалить» здесь — это заявка, а не удаление. POST /conversations/{id}/erase отвечает 202 Accepted (RequestConversationErasureHandler): стирание происходит потом, отдельным шагом. Значит строка не имеет права просто исчезнуть по свайпу — иначе она вернётся при следующей подгрузке и будет выглядеть как сбой. Правильное поведение — подтверждение, затем строка в состоянии «стирается».

9:41
Диалоги
АГ
Мои 3
Ожидают 5
Все
Статус
🦊🍊
Лиса · Апельсин2 мин
Здравствуйте, оплата не прошла, что делать?
2 мин
Не начатСообщений: 1
🐳🍕
Ирина С.14 мин
Спасибо, всё получилось!
6 мин
Назначен: Мария П.Сообщений: 9
🦉🍓
Сова · Клубника1 ч
А можно перенести запись на пятницу?
2 мин
Не начатСообщений: 3
Подгружаются по мере прокрутки
Показывать
Не начат
Назначен
Закрыт
фильтр по статусам · та же панель, что и меню профиля, привязанная к чипу

Состояние по умолчанию именно такое. «Не начат» и «Назначен» включены, «Закрыт» выключен: закрытый диалог — это архив, и оператор должен попросить его показать, а не пролистывать мимо. На предыдущем кадре «Закрыт» включён вручную — поэтому там в списке есть закрытая строка, а здесь нет.

Три флажка, три слова, ни одной цифры — ни напротив статуса, ни на чипе, ни на вкладке «Все». Панель взята целиком у меню профиля (.popover и .pop-item), флажок — тот же .cbox, что на экране очистки хранилища. Новой формы здесь не появилось, появилось новое содержимое.

9:41
Лиса · АпельсинВиджет · в работе · 2 мин
Назначено вам · 09:38
Здравствуйте! Оплата не прошла, а деньги списались.09:38
Заказ 1048209:38
Добрый день! Сейчас посмотрю по заказу, одну минуту.09:39 ✓✓
Спасибо09:40
Платёж завис у банка, деньги вернутся в течение суток. Оформил заказ вручную — подтверждение уже ушло вам на почту.09:41 ✓
Сообщение…
/conversations/:conversationId

Порядок — это назначаемый сервером sequence, никогда не часы, а вторая галочка — это Message.DeliveredAt, собственное подтверждение виджета, а не квитанция об отправке. Приход нового назначения никогда не переключает экран.

Скрепка на этом кадре нарисована, но она условная. Консоль рисует «Прикрепить» всегда (Composer.tsx). Приложение рисует скрепку ровно тогда, когда у открытого диалога hasAttachmentUploadGrant истинно — это значение задаётся по умолчанию для сайта переключателем allowAttachmentUploadsByDefault на экране виджета и переключается для конкретного диалога из шторки посетителя тем, у кого есть conversation:attachment_upload_grant. Когда вложения не разрешены — скрепки нет вовсе, а не «серая»: то же правило «скрывать, а не гасить», которому уже следуют сам переключатель и кнопка закрытия диалога. Одно решение здесь моё, а не бэкенда: грант формально про загрузки посетителя, а скрепка — про оператора. Если их надо развести — это решается сейчас, одной строкой.

9:41
Лиса · АпельсинВиджет · в работе · 2 мин
Назначено вам · 09:38
Здравствуйте! Оплата не прошла, а деньги списались.09:38
🦊🍊
Лиса · Апельсин
Первый визит 14 марта · 3 диалога
В работе
Оплата
Срочно
+ метка
Контактные данные
⚠️ Телефон +7 9•• ••• •• 12Показать
Почта i****@mail.ruПоказать
⚠️ Имя Недействительно
⚠️ — оператор ещё не подтвердил эти данные. Одна строка на одно поле: «непроверенного телефона» отдельной строкой не бывает.
Заметки команды2
Прошлые диалоги2
Приём файлов от посетителя
Закрыть диалог
Ограничить
VisitorPanel + ContactDetailsPanel + Notes + Tags + History

Показано явно, а не только описано. Одна строка на атрибут — телефон, почта, имя — и значок ⚠️ ровно тогда, когда оператор ещё не подтвердил значение. Никаких пар «непроверенный телефон» / «проверенный телефон».

Это совпадает с тем, что уже сделала консоль: старый значок Verified/Unverified убрали, потому что он был Unverified на каждой строке без исключения. Настоящее состояние — это assessment: пусто, «Подтверждено» или «Недействительно», и третье красит строку, а не добавляет вторую.

Раскрытие замаскированного значения остаётся под аудитом: одна строка на каждое раскрытие в журнале, никогда не счётчик по оператору. Переключатель приёма файлов от посетителя живёт здесь — и это тот самый переключатель, от которого зависит скрепка в треде.

Что возвращается на планшете

На планшете возвращается собственная форма консоли: list-detail-каркас с контекстом посетителя как третьей панелью, а нижняя панель становится боковой навигационной. Тот же код, одна точка перелома — последний кадр на этой странице. Планшет — приятное дополнение, а не цель: телефон основной инструмент, и ни один этап плана не завязан на широкий экран.

03

Уведомления — обещание, ради которого всё остальное имеет смысл

Каналы уведомлений — понятие уровня ОС, у веба аналога нет. Экран нарисован для мира, где push уже есть: так выглядит обещание. Всё выключено за одной полосой, называющей задачу бэкенда, которая должна приехать первой.

9:41
Уведомления
Пока недоступно
Доставка на фоне требует push-инфраструктуры в ago-chat: регистрация устройства, рассылка из outbox, решение о доставке по оператору. Сейчас приложение оповещает только на переднем плане.
Каналы уведомлений
Назначен диалог
Звук и вибрация · высокая важность
Сообщение в моём диалоге
Звук · высокая важность
Запись ждёт решения
Звук · подтвердится сама к дедлайну
Дедлайн подтверждения близко
За час до автоподтверждения
Сообщение команде
Без звука
Когда не беспокоить
Тихие часы
22:00 – 08:00 · часовой пояс бизнеса
Статус «Отошёл» относится к оператору, а не к устройству: включив его на телефоне, вы включаете его и для десктопной консоли. Это существующее поведение сервера, а не новинка приложения.
предок — AlertSettings консоли, соответствие не один-к-одному

Экран показывает настройки уведомлений целиком и честно помечает то, что пока недоступно — вместо пустого экрана с одним объяснением, чего приложение не может. Так виден весь набор переключателей и логика каждого.

Пять каналов, по одному на вид события, потому что Android разрешает пользователю отключить канал мимо приложения — и это правильно: «диалог назначен мне» и «в команде написали» не должны выключаться вместе.