Объявления: окно у игрока и редактор в админке

Окно — вариант 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:
2026-09-18 21:26:38 +03:00
co-authored by Claude Opus 5
parent a0a0e522ef
commit ab1fc3f00f
16 changed files with 1220 additions and 3 deletions
+2
View File
@@ -19,12 +19,14 @@ export const qk = {
authConfig: ["authConfig"] as const,
invitations: ["invitations"] as const,
notifications: ["notifications"] as const,
announcements: ["announcements"] as const,
devUsers: ["devUsers"] as const,
adminUsers: ["adminUsers"] as const,
adminGroups: ["adminGroups"] as const,
adminMatches: ["adminMatches"] as const,
adminLogs: ["adminLogs"] as const,
adminAchievements: ["adminAchievements"] as const,
adminAnnouncements: ["adminAnnouncements"] as const,
};
/**
+395
View File
@@ -842,6 +842,40 @@ export interface paths {
patch?: never;
trace?: never;
};
"/api/announcements/pending": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
/** Pending */
get: operations["pending_api_announcements_pending_get"];
put?: never;
post?: never;
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/api/announcements/{announcement_id}/ack": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get?: never;
put?: never;
/** Acknowledge */
post: operations["acknowledge_api_announcements__announcement_id__ack_post"];
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/api/admin/auth/login": {
parameters: {
query?: never;
@@ -1140,6 +1174,62 @@ export interface paths {
patch?: never;
trace?: never;
};
"/api/admin/announcements": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
/** List Announcements */
get: operations["list_announcements_api_admin_announcements_get"];
put?: never;
/** Create Announcement */
post: operations["create_announcement_api_admin_announcements_post"];
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/api/admin/announcements/{announcement_id}": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get?: never;
/** Update Announcement */
put: operations["update_announcement_api_admin_announcements__announcement_id__put"];
post?: never;
/** Delete Announcement */
delete: operations["delete_announcement_api_admin_announcements__announcement_id__delete"];
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/api/admin/announcements/{announcement_id}/stop": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get?: never;
put?: never;
/**
* Stop Announcement
* @description «Снять с показа»: период идущего объявления заканчивается сейчас.
*/
post: operations["stop_announcement_api_admin_announcements__announcement_id__stop_post"];
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/api/admin/audit-logs": {
parameters: {
query?: never;
@@ -1277,6 +1367,36 @@ export interface components {
/** Group Id */
group_id?: number | null;
};
/** AdminAnnouncementRead */
AdminAnnouncementRead: {
/** Id */
id: number;
/** Title */
title: string;
/** Body Html */
body_html: string;
/** Starts At */
starts_at: string;
/** Ends At */
ends_at: string;
/** Show To New Players */
show_to_new_players: boolean;
/** Revision */
revision: number;
/**
* Status
* @enum {string}
*/
status: "planned" | "live" | "finished";
/** Seen Count */
seen_count: number;
/** Audience Count */
audience_count: number;
/** Created At */
created_at: string;
/** Updated At */
updated_at: string;
};
/** AdminGroupRead */
AdminGroupRead: {
/** Id */
@@ -1361,6 +1481,76 @@ export interface components {
/** Is Active */
is_active?: boolean | null;
};
/** AnnouncementAck */
AnnouncementAck: {
/** Revision */
revision: number;
};
/** AnnouncementRead */
AnnouncementRead: {
/** Id */
id: number;
/** Title */
title: string;
/** Body Html */
body_html: string;
/** Revision */
revision: number;
/**
* Updated
* @default false
*/
updated: boolean;
};
/** AnnouncementUpdate */
AnnouncementUpdate: {
/** Title */
title: string;
/** Body Html */
body_html: string;
/**
* Starts At
* Format: date-time
*/
starts_at: string;
/**
* Ends At
* Format: date-time
*/
ends_at: string;
/**
* Show To New Players
* @default true
*/
show_to_new_players: boolean;
/**
* Reshow
* @default false
*/
reshow: boolean;
};
/** AnnouncementWrite */
AnnouncementWrite: {
/** Title */
title: string;
/** Body Html */
body_html: string;
/**
* Starts At
* Format: date-time
*/
starts_at: string;
/**
* Ends At
* Format: date-time
*/
ends_at: string;
/**
* Show To New Players
* @default true
*/
show_to_new_players: boolean;
};
/** AttachmentRead */
AttachmentRead: {
/** Id */
@@ -3980,6 +4170,61 @@ export interface operations {
};
};
};
pending_api_announcements_pending_get: {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
requestBody?: never;
responses: {
/** @description Successful Response */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["AnnouncementRead"][];
};
};
};
};
acknowledge_api_announcements__announcement_id__ack_post: {
parameters: {
query?: never;
header?: never;
path: {
announcement_id: number;
};
cookie?: never;
};
requestBody: {
content: {
"application/json": components["schemas"]["AnnouncementAck"];
};
};
responses: {
/** @description Successful Response */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["OkResponse"];
};
};
/** @description Validation Error */
422: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["HTTPValidationError"];
};
};
};
};
admin_login_api_admin_auth_login_post: {
parameters: {
query?: never;
@@ -4676,6 +4921,156 @@ export interface operations {
};
};
};
list_announcements_api_admin_announcements_get: {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
requestBody?: never;
responses: {
/** @description Successful Response */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["AdminAnnouncementRead"][];
};
};
};
};
create_announcement_api_admin_announcements_post: {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
requestBody: {
content: {
"application/json": components["schemas"]["AnnouncementWrite"];
};
};
responses: {
/** @description Successful Response */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["AdminAnnouncementRead"];
};
};
/** @description Validation Error */
422: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["HTTPValidationError"];
};
};
};
};
update_announcement_api_admin_announcements__announcement_id__put: {
parameters: {
query?: never;
header?: never;
path: {
announcement_id: number;
};
cookie?: never;
};
requestBody: {
content: {
"application/json": components["schemas"]["AnnouncementUpdate"];
};
};
responses: {
/** @description Successful Response */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["AdminAnnouncementRead"];
};
};
/** @description Validation Error */
422: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["HTTPValidationError"];
};
};
};
};
delete_announcement_api_admin_announcements__announcement_id__delete: {
parameters: {
query?: never;
header?: never;
path: {
announcement_id: number;
};
cookie?: never;
};
requestBody?: never;
responses: {
/** @description Successful Response */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["OkResponse"];
};
};
/** @description Validation Error */
422: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["HTTPValidationError"];
};
};
};
};
stop_announcement_api_admin_announcements__announcement_id__stop_post: {
parameters: {
query?: never;
header?: never;
path: {
announcement_id: number;
};
cookie?: never;
};
requestBody?: never;
responses: {
/** @description Successful Response */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["AdminAnnouncementRead"];
};
};
/** @description Validation Error */
422: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["HTTPValidationError"];
};
};
};
};
audit_logs_api_admin_audit_logs_get: {
parameters: {
query?: {