Объявления: окно у игрока и редактор в админке
Окно — вариант E из макетов: заголовок в плашке-шапке, текст, точки очереди и «Понятно», крестика нет. Очередь идёт от старого к новому; закрытое сразу уходит из кэша, следующее открывается без ожидания сети. У повторно показанного объявления под заголовком — красная пометка «обновлено». AppShell показывает окно на любой странице и только когда пароль задан: обязательное окно пароля всегда первое. Новые и изменённые объявления приходят SSE-событием announcements, отложенное начало показа — перезапросом раз в 5 минут. Админка — вкладка «Объявления» с редактором R1: contenteditable и панель (жирный, курсив, золотой и красный акцент, эмодзи, снятие оформления), вставка и перетаскивание только простым текстом, счётчик символов, период показа по МСК, переключатели «показывать новым игрокам» и «показать заново». Предпросмотр — тем же окном, что у игрока. Список со статусом, счётчиком «закрыли N из M» и действиями: изменить, снять с показа, дублировать, удалить. Выделение текста в форме — полупрозрачный оранжевый вместо синего. schema.d.ts пересобран из OpenAPI. #84 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,40 @@
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
import { useAckAnnouncement, usePendingAnnouncements } from "../hooks/announcements";
|
||||
import { AnnouncementWindow } from "./AnnouncementWindow";
|
||||
|
||||
/**
|
||||
* Очередь объявлений администрации поверх любой страницы: по одному, от старого
|
||||
* к новому, следующее — после «Понятно» на предыдущем. AppShell монтирует её только
|
||||
* когда пароль задан: обязательное окно пароля всегда первое.
|
||||
*/
|
||||
export function AnnouncementDialog() {
|
||||
const { data } = usePendingAnnouncements(true);
|
||||
const ack = useAckAnnouncement();
|
||||
// Сколько уже закрыто в этой очереди — для точек «2 из 3». Закрытые сразу уходят
|
||||
// из данных запроса, так что без счётчика точки показывали бы только остаток.
|
||||
const [closed, setClosed] = useState(0);
|
||||
const items = data ?? [];
|
||||
|
||||
useEffect(() => {
|
||||
if (items.length === 0) setClosed(0);
|
||||
}, [items.length]);
|
||||
|
||||
if (items.length === 0) return null;
|
||||
const current = items[0];
|
||||
|
||||
return (
|
||||
<AnnouncementWindow
|
||||
key={current.id}
|
||||
title={current.title}
|
||||
bodyHtml={current.body_html}
|
||||
updated={current.updated}
|
||||
index={closed}
|
||||
total={closed + items.length}
|
||||
onOk={() => {
|
||||
setClosed((c) => c + 1);
|
||||
ack.mutate({ id: current.id, revision: current.revision });
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user