Окно — вариант E из макетов: заголовок в плашке-шапке, текст, точки очереди и «Понятно», крестика нет. Очередь идёт от старого к новому; закрытое сразу уходит из кэша, следующее открывается без ожидания сети. У повторно показанного объявления под заголовком — красная пометка «обновлено». AppShell показывает окно на любой странице и только когда пароль задан: обязательное окно пароля всегда первое. Новые и изменённые объявления приходят SSE-событием announcements, отложенное начало показа — перезапросом раз в 5 минут. Админка — вкладка «Объявления» с редактором R1: contenteditable и панель (жирный, курсив, золотой и красный акцент, эмодзи, снятие оформления), вставка и перетаскивание только простым текстом, счётчик символов, период показа по МСК, переключатели «показывать новым игрокам» и «показать заново». Предпросмотр — тем же окном, что у игрока. Список со статусом, счётчиком «закрыли N из M» и действиями: изменить, снять с показа, дублировать, удалить. Выделение текста в форме — полупрозрачный оранжевый вместо синего. schema.d.ts пересобран из OpenAPI. #84 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
41 lines
1.6 KiB
TypeScript
41 lines
1.6 KiB
TypeScript
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 });
|
|
}}
|
|
/>
|
|
);
|
|
}
|